import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";

await import("./linkedin-feed-dom.js");

const Dom = globalThis.NexusLinkedInFeedDom;

function actionControl(label, state = {}) {
  return {
    disabled: false,
    hidden: false,
    isConnected: true,
    innerText: "",
    textContent: "",
    getAttribute(name) {
      if (name === "aria-label") return state.label ?? label;
      if (name === "aria-disabled") return state.ariaDisabled || null;
      if (name === "disabled") return null;
      if (name === "aria-hidden") return null;
      return null;
    },
  };
}

test("invitation action confirmation waits for a real control-state transition", async () => {
  const state = { label: "Accept Ada's invitation" };
  const accept = actionControl("Accept Ada's invitation", state);
  const scope = { isConnected: true, querySelectorAll: () => [accept] };
  let polls = 0;

  const confirmed = await Dom.waitForActionControlSettled(accept, {
    scope,
    actionPattern: /accept/i,
    timeoutMs: 50,
    pollMs: 1,
    sleep: async () => {
      polls += 1;
      if (polls === 2) accept.isConnected = false;
    },
  });

  assert.equal(confirmed, true);
  accept.isConnected = true;
  assert.equal(
    await Dom.waitForActionControlSettled(accept, {
      scope,
      actionPattern: /accept/i,
      timeoutMs: -1,
      sleep: async () => {},
    }),
    false,
  );
});

test("dialog action discovery is scoped to a visible confirmation dialog", async () => {
  const original = actionControl("Withdraw invitation");
  const confirm = actionControl("Withdraw");
  const hiddenConfirm = actionControl("Withdraw");
  const hiddenDialog = {
    hidden: false,
    getAttribute: (name) => (name === "aria-hidden" ? "true" : null),
    querySelectorAll: () => [hiddenConfirm],
  };
  const dialog = {
    hidden: false,
    offsetParent: null,
    getClientRects: () => [{ width: 320, height: 180 }],
    getAttribute: () => null,
    querySelectorAll: () => [confirm],
  };
  const root = {
    querySelectorAll(selector) {
      if (selector.includes('role="dialog"')) return [hiddenDialog, dialog];
      return [original];
    },
  };

  assert.equal(Dom.findDialogAction(root, "Withdraw"), confirm);
  assert.equal(
    await Dom.waitForDialogAction(root, "Withdraw", {
      timeoutMs: 0,
      sleep: async () => {},
    }),
    confirm,
  );
});

test("Accept and Withdraw workflows count only confirmed outcomes", () => {
  const source = readFileSync(new URL("./content-linkedin.js", import.meta.url), "utf8");
  const acceptStart = source.indexOf("async function handleAcceptInvites");
  const acceptEnd = source.indexOf("async function handleAcceptAndCollect", acceptStart);
  const accept = source.slice(acceptStart, acceptEnd);
  const collectorStart = acceptEnd;
  const collectorEnd = source.indexOf("async function handleWithdrawInvites", collectorStart);
  const collector = source.slice(collectorStart, collectorEnd);
  const withdrawStart = source.indexOf("async function handleWithdrawInvites");
  const withdrawEnd = source.indexOf("async function handleFollowConnections", withdrawStart);
  const withdraw = source.slice(withdrawStart, withdrawEnd);
  const batchStart = source.indexOf("async function runAcceptAndDmBatch");
  const batchEnd = source.indexOf("function buildLinkedInPanel", batchStart);
  const acceptAndDm = source.slice(batchStart, batchEnd);

  assert.match(accept, /waitForActionControlSettled\(btn, \{/);
  assert.ok(accept.indexOf("if (!confirmed)") < accept.indexOf("accepted++"));
  assert.doesNotMatch(accept, /btn\.click\(\);\s*accepted\+\+/);
  assert.match(accept, /const remaining = count - attempted/);
  assert.match(collector, /accepted: confirmed/);

  assert.match(withdraw, /waitForDialogAction\(/);
  assert.match(withdraw, /confirmButton\.click\(\)/);
  assert.match(withdraw, /waitForActionControlSettled\(btn, \{/);
  assert.match(withdraw, /withdrawFlowBlocked = true/);
  assert.match(withdraw, /const remaining = count - attempted/);
  assert.ok(withdraw.indexOf("if (!confirmed)") < withdraw.indexOf("withdrawn++"));
  const failedConfirmation = withdraw.slice(
    withdraw.indexOf("if (!confirmed)"),
    withdraw.indexOf("withdrawn++"),
  );
  assert.match(failedConfirmation, /return false/);

  assert.match(
    acceptAndDm,
    /const acceptConfirmed = await globalThis\.NexusLinkedInFeedDom\.waitForActionControlSettled/,
  );
  assert.ok(acceptAndDm.indexOf("if (!acceptConfirmed)") < acceptAndDm.indexOf("accepted++"));
});
