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

import {
  hasPullApprovalStateDrift,
  isAllowedChangedPath,
  isAllowedVisualCaptureChangedPath,
  isDeniedChangedPath,
  isPendingApprovalRun,
  listPendingApprovalRuns,
  runTargetsPullRequest,
  waitForPendingApprovalRuns,
} from "./approve-fork-pr-workflows.ts";

test("visual-pr-comment resolves empty workflow_run.pull_requests from trusted repo/branch metadata and leaves stale SHA handling to trusted-pr", async () => {
  const workflow = await readFile(new URL("../.github/workflows/visual-pr-comment.yml", import.meta.url), "utf8");
  const manifestStep = workflow.match(/- name: Read capture manifest[\s\S]*?- name: Validate live PR state for trusted checkout/u)?.[0];
  const trustedPrStep = workflow.match(/- name: Validate live PR state for trusted checkout[\s\S]*?- name: Checkout trusted base/u)?.[0];

  assert.ok(manifestStep, "Expected Read capture manifest step block in workflow");
  assert.ok(trustedPrStep, "Expected trusted-pr validation step block in workflow");
  assert.match(manifestStep, /encoded_head=.*\$head\|@uri/u);
  assert.match(manifestStep, /pulls\?state=open&head=\$encoded_head&per_page=100/);
  assert.match(manifestStep, /jq -r --arg repo "\$source_head_repository"/u);
  assert.doesNotMatch(manifestStep, /select\(\.head\.sha == \$sha/u);
  assert.match(manifestStep, /match_count=.*wc -w/u);
  assert.match(manifestStep, /if \[ "\$match_count" -gt 1 \]; then/u);
  assert.match(manifestStep, /found \$match_count matches/);
  assert.match(manifestStep, /pr_number="\$manifest_pr_number"/u);
  assert.match(trustedPrStep, /Skipping stale visual artifact for \$ARTIFACT_HEAD_SHA; current PR head is \$current_head\./u);
});

test("isPendingApprovalRun matches approval-gated fork PR runs from GitHub's captured payload shape", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  assert.equal(isPendingApprovalRun(run, pull), true);
});

test("isPendingApprovalRun also accepts action_required runs reported only in status", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const run = {
    id: 26273463770,
    name: "CI",
    event: "pull_request",
    status: "action_required",
    conclusion: null,
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  assert.equal(isPendingApprovalRun(run, pull), true);
});

test("isPendingApprovalRun rejects runs outside the allowlist or without action_required state", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  assert.equal(
    isPendingApprovalRun(
      {
        id: 26273463769,
        name: "CI",
        event: "pull_request",
        status: "completed",
        conclusion: "success",
        head_sha: "734076155c44e569304856590019cea54506fdab",
        path: ".github/workflows/ci.yml@main",
        pull_requests: [],
      },
      pull,
    ),
    false,
  );

  assert.equal(
    isPendingApprovalRun(
      {
        id: 26273463771,
        name: "Visual PR Capture",
        event: "pull_request",
        status: "completed",
        conclusion: "action_required",
        head_sha: "734076155c44e569304856590019cea54506fdab",
        path: ".github/workflows/visual-pr-capture.yml@main",
        pull_requests: [],
      },
      pull,
    ),
    false,
  );

  assert.equal(
    isPendingApprovalRun(
      {
        id: 26273463770,
        name: "Visual PR Comment",
        event: "pull_request",
        status: "completed",
        conclusion: "action_required",
        head_sha: "734076155c44e569304856590019cea54506fdab",
        path: ".github/workflows/visual-pr-comment.yml@main",
        pull_requests: [],
      },
      pull,
    ),
    false,
  );
});

test("isPendingApprovalRun approves visual capture only for strict web source changes", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/button-copy",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };
  const run = {
    id: 26273463771,
    name: "Visual PR Capture",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/visual-pr-capture.yml@main",
    pull_requests: [],
  };

  assert.equal(
    isPendingApprovalRun(run, pull, [
      { filename: "apps/web/src/components/Button.tsx", status: "modified" },
      { filename: "apps/web/src/styles/button.css", status: "modified" },
    ]),
    true,
  );
  assert.equal(
    isPendingApprovalRun(run, pull, [{ filename: "apps/web/public/logo.png", status: "modified" }]),
    false,
  );
  assert.equal(
    isPendingApprovalRun(run, pull, [
      {
        filename: "apps/web/src/components/Button.tsx",
        previous_filename: "scripts/build.ts",
        status: "renamed",
      },
    ]),
    false,
  );
});

test("runTargetsPullRequest accepts empty run.pull_requests only when the head SHA maps to this one open PR", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  assert.equal(runTargetsPullRequest(run, pull, [pull], []), true);
});

test("runTargetsPullRequest accepts fork PR runs with no GitHub PR association when head identity matches", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    head_branch: "fix/workflow-linkage",
    head_repository: { full_name: "someone/open-design" },
    status: "completed",
    conclusion: "action_required",
    head_sha: pull.head.sha,
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  assert.equal(runTargetsPullRequest(run, pull, [], [pull]), true);
});

test("runTargetsPullRequest rejects ambiguous empty run.pull_requests associations", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const otherPull = {
    ...pull,
    number: 3001,
    base: {
      ref: "release",
      sha: "8db117d728f967d108f6fdd64cb8d921d057f7f6",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  assert.equal(runTargetsPullRequest(run, pull, [pull, otherPull], []), false);
});

test("runTargetsPullRequest rejects fork PR runs when multiple open PRs share the same head identity", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const otherPull = {
    ...pull,
    number: 3001,
    base: {
      ref: "release",
      sha: "8db117d728f967d108f6fdd64cb8d921d057f7f6",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    head_branch: pull.head.ref,
    head_repository: pull.head.repo,
    status: "completed",
    conclusion: "action_required",
    head_sha: pull.head.sha,
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  assert.equal(runTargetsPullRequest(run, pull, [], [pull, otherPull]), false);
});

test("runTargetsPullRequest rejects empty associations when fork head identity does not match", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    head_branch: "different-branch",
    head_repository: { full_name: "someone/open-design" },
    status: "completed",
    conclusion: "action_required",
    head_sha: pull.head.sha,
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  assert.equal(runTargetsPullRequest(run, pull, [], [pull]), false);
});

test("runTargetsPullRequest rejects runs that GitHub already associates to a different PR", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [
      {
        number: 3001,
        head: pull.head,
        base: {
          ref: "release",
          sha: "8db117d728f967d108f6fdd64cb8d921d057f7f6",
          repo: { full_name: "nexu-io/open-design" },
        },
      },
    ],
  };

  assert.equal(runTargetsPullRequest(run, pull, [pull], []), false);
});

test("runTargetsPullRequest approves only the run that GitHub associates to the current PR when two PRs share a head SHA", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const otherPull = {
    ...pull,
    number: 3001,
    base: {
      ref: "release",
      sha: "8db117d728f967d108f6fdd64cb8d921d057f7f6",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const currentPrRun = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: pull.head.sha,
    path: ".github/workflows/ci.yml@main",
    pull_requests: [pull],
  };

  const otherPrRun = {
    ...currentPrRun,
    id: 26273463770,
    pull_requests: [otherPull],
  };

  assert.equal(runTargetsPullRequest(currentPrRun, pull, [pull, otherPull], []), true);
  assert.equal(runTargetsPullRequest(otherPrRun, pull, [pull, otherPull], []), false);
});

test("runTargetsPullRequest ignores base tip churn for the same PR association", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: pull.head.sha,
    path: ".github/workflows/ci.yml@main",
    pull_requests: [
      {
        number: pull.number,
        head: pull.head,
        base: {
          ...pull.base,
          sha: "08a88a65482123629ebda5a090c71533bd6b8a88",
        },
      },
    ],
  };

  assert.equal(runTargetsPullRequest(run, pull, [pull], []), true);
});

test("listPendingApprovalRuns paginates all pull_request runs for the head SHA and filters action_required client-side", async () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  const requestedPaths: string[] = [];
  const pendingRuns = await listPendingApprovalRuns("nexu-io/open-design", pull, {
    loadWorkflowRunsResponsePage: async (path) => {
      requestedPaths.push(path);
      if (path.endsWith("page=1")) {
        return {
          workflow_runs: Array.from({ length: 100 }, (_, index) => ({
            id: 26273463600 + index,
            name: "CI",
            event: "pull_request",
            head_branch: pull.head.ref,
            head_repository: pull.head.repo,
            status: "completed",
            conclusion: "success",
            head_sha: pull.head.sha,
            path: ".github/workflows/ci.yml@main",
            pull_requests: [],
          })),
        };
      }

      return {
        workflow_runs: [
          {
            id: 26273463769,
            name: "CI",
            event: "pull_request",
            head_branch: pull.head.ref,
            head_repository: pull.head.repo,
            status: "completed",
            conclusion: "action_required",
            head_sha: pull.head.sha,
            path: ".github/workflows/ci.yml@main",
            pull_requests: [],
          },
          {
            id: 26273463770,
            name: "CI",
            event: "pull_request",
            head_branch: pull.head.ref,
            head_repository: pull.head.repo,
            status: "completed",
            conclusion: "success",
            head_sha: pull.head.sha,
            path: ".github/workflows/ci.yml@main",
            pull_requests: [],
          },
        ],
      };
    },
    loadPullRequestsForHeadSha: async () => [],
    loadPullRequestsForHeadRef: async () => [pull],
  });

  assert.deepEqual(requestedPaths, [
    "/repos/nexu-io/open-design/actions/runs?event=pull_request&head_sha=734076155c44e569304856590019cea54506fdab&per_page=100&page=1",
    "/repos/nexu-io/open-design/actions/runs?event=pull_request&head_sha=734076155c44e569304856590019cea54506fdab&per_page=100&page=2",
  ]);
  assert.equal(requestedPaths.some((path) => path.includes("status=action_required")), false);
  assert.deepEqual(
    pendingRuns.map((run) => run.id),
    [26273463769],
  );
});

test("listPendingApprovalRuns applies strict changed-path filtering only to visual capture", async () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/readme-copy",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };
  const workflowRuns = [
    {
      id: 26273463769,
      name: "CI",
      event: "pull_request",
      head_branch: pull.head.ref,
      head_repository: pull.head.repo,
      status: "completed",
      conclusion: "action_required",
      head_sha: pull.head.sha,
      path: ".github/workflows/ci.yml@main",
      pull_requests: [],
    },
    {
      id: 26273463770,
      name: "Visual PR Capture",
      event: "pull_request",
      head_branch: pull.head.ref,
      head_repository: pull.head.repo,
      status: "completed",
      conclusion: "action_required",
      head_sha: pull.head.sha,
      path: ".github/workflows/visual-pr-capture.yml@main",
      pull_requests: [],
    },
  ];
  const deps = {
    loadWorkflowRunsResponsePage: async () => ({ workflow_runs: workflowRuns }),
    loadPullRequestsForHeadSha: async () => [pull],
  };

  assert.deepEqual(
    (await listPendingApprovalRuns("nexu-io/open-design", pull, [{ filename: "README.md", status: "modified" }], deps)).map((run) => run.id),
    [26273463769],
  );
  assert.deepEqual(
    (await listPendingApprovalRuns(
      "nexu-io/open-design",
      pull,
      [{ filename: "apps/web/src/components/Button.tsx", status: "modified" }],
      deps,
    )).map((run) => run.id),
    [26273463769, 26273463770],
  );
});

test("hasPullApprovalStateDrift ignores base tip churn but still rejects base retargeting and head drift", () => {
  const pull = {
    number: 2683,
    state: "open",
    changed_files: 1,
    head: {
      ref: "fix/workflow-linkage",
      sha: "734076155c44e569304856590019cea54506fdab",
      repo: { full_name: "someone/open-design" },
    },
    base: {
      ref: "main",
      sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
      repo: { full_name: "nexu-io/open-design" },
    },
  };

  assert.equal(hasPullApprovalStateDrift(pull, pull), false);
  assert.equal(
    hasPullApprovalStateDrift(pull, {
      ...pull,
      base: { ...pull.base, sha: "08a88a65482123629ebda5a090c71533bd6b8a88" },
    }),
    false,
  );
  assert.equal(hasPullApprovalStateDrift(pull, { ...pull, draft: true }), true);
  assert.equal(hasPullApprovalStateDrift(pull, { ...pull, state: "closed" }), true);
  assert.equal(
    hasPullApprovalStateDrift(pull, {
      ...pull,
      head: { ...pull.head, sha: "08a88a65482123629ebda5a090c71533bd6b8a88" },
    }),
    true,
  );
  assert.equal(
    hasPullApprovalStateDrift(pull, {
      ...pull,
      base: { ...pull.base, ref: "release" },
    }),
    true,
  );
});

test("isDeniedChangedPath blocks common tool config files under allowlisted source trees", () => {
  assert.equal(isDeniedChangedPath("apps/web/vitest.config.ts"), true);
  assert.equal(isDeniedChangedPath("apps/web/vite.config.ts"), true);
  assert.equal(isDeniedChangedPath("apps/web/playwright.config.ts"), true);
  assert.equal(isDeniedChangedPath("apps/web/tsconfig.sidecar.json"), true);
  assert.equal(isDeniedChangedPath("apps/daemon/tsconfig.tests.json"), true);
  assert.equal(isDeniedChangedPath("packages/contracts/tsconfig.tests.json"), true);
  assert.equal(isDeniedChangedPath("apps/packaged/tsconfig.json"), true);
  assert.equal(isDeniedChangedPath("apps/packaged/vitest.config.ts"), true);
  assert.equal(isDeniedChangedPath("apps/web/src/app/page.tsx"), false);
});

test("isAllowedChangedPath allows ordinary app and package source/test paths while keeping denied surfaces blocked", () => {
  assert.equal(isAllowedChangedPath("apps/packaged/src/main.ts"), true);
  assert.equal(isAllowedChangedPath("apps/packaged/tests/main.test.ts"), true);
  assert.equal(isAllowedChangedPath("apps/desktop/src/main.ts"), true);
  assert.equal(isAllowedChangedPath("packages/sidecar/src/index.ts"), true);
  assert.equal(isAllowedChangedPath("tools/pack/src/index.ts"), false);
  assert.equal(isAllowedChangedPath("apps/packaged/tsconfig.json"), false);
  assert.equal(isAllowedChangedPath("apps/packaged/vitest.config.ts"), false);
});

test("isAllowedVisualCaptureChangedPath is limited to web ts tsx and css source", () => {
  assert.equal(isAllowedVisualCaptureChangedPath("apps/web/src/app/page.tsx"), true);
  assert.equal(isAllowedVisualCaptureChangedPath("apps/web/src/lib/theme.ts"), true);
  assert.equal(isAllowedVisualCaptureChangedPath("apps/web/src/components/Button.css"), true);
  assert.equal(isAllowedVisualCaptureChangedPath("apps/web/src/assets/icon.svg"), false);
  assert.equal(isAllowedVisualCaptureChangedPath("apps/web/public/logo.png"), false);
  assert.equal(isAllowedVisualCaptureChangedPath("apps/web/package.json"), false);
  assert.equal(isAllowedVisualCaptureChangedPath("apps/web/tests/Button.test.tsx"), false);
  assert.equal(isAllowedVisualCaptureChangedPath("packages/contracts/src/api.ts"), false);
});

test("waitForPendingApprovalRuns retries until action_required runs appear and keeps polling through the retry window", async () => {
  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  const batches = [[], [], [run]];
  const sleeps: number[] = [];
  let now = 0;

  const pendingRuns = await waitForPendingApprovalRuns(
    async () => batches.shift() ?? [run],
    async (ms) => {
      sleeps.push(ms);
      now += ms;
    },
    () => now,
    { settlingWindowMs: 9_000 },
  );

  assert.deepEqual(pendingRuns, [run]);
  assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000]);
});

test("waitForPendingApprovalRuns keeps polling and returns the latest eligible run snapshot across the retry window", async () => {
  const ciRun = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };
  const visualRun = {
    id: 26273463770,
    name: "Visual PR Verify",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/visual-pr-verify.yml@main",
    pull_requests: [],
  };

  const batches = [[ciRun], [ciRun], [ciRun, visualRun], [ciRun, visualRun]];
  const sleeps: number[] = [];
  let now = 0;

  const pendingRuns = await waitForPendingApprovalRuns(
    async () => batches.shift() ?? [ciRun, visualRun],
    async (ms) => {
      sleeps.push(ms);
      now += ms;
    },
    () => now,
    { settlingWindowMs: 9_000 },
  );

  assert.deepEqual(pendingRuns, [ciRun, visualRun]);
  assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000]);
});

test("waitForPendingApprovalRuns drops runs that disappear in later polls", async () => {
  const staleRun = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };
  const survivingRun = {
    ...staleRun,
    id: 26273463770,
    name: "Visual PR Verify",
    path: ".github/workflows/visual-pr-verify.yml@main",
  };

  const batches = [[staleRun], [staleRun, survivingRun], [survivingRun], [survivingRun]];
  let now = 0;

  const pendingRuns = await waitForPendingApprovalRuns(
    async () => batches.shift() ?? [survivingRun],
    async (ms) => {
      now += ms;
    },
    () => now,
    { settlingWindowMs: 6_000 },
  );

  assert.deepEqual(pendingRuns, [survivingRun]);
});

test("waitForPendingApprovalRuns keeps polling until the run set is stable, even when another eligible run appears after the old three-poll budget", async () => {
  const ciRun = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };
  const visualRun = {
    id: 26273463770,
    name: "Visual PR Verify",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/visual-pr-verify.yml@main",
    pull_requests: [],
  };

  const batches = [[ciRun], [ciRun], [ciRun], [ciRun], [ciRun], [ciRun, visualRun], [ciRun, visualRun]];
  const sleeps: number[] = [];
  let now = 0;

  const pendingRuns = await waitForPendingApprovalRuns(
    async () => batches.shift() ?? [ciRun, visualRun],
    async (ms) => {
      sleeps.push(ms);
      now += ms;
    },
    () => now,
    {
      firstAppearanceTimeoutMs: 30_000,
      settlingWindowMs: 15_000,
    },
  );

  assert.deepEqual(pendingRuns, [ciRun, visualRun]);
  assert.equal(sleeps.length, 10);
});

test("waitForPendingApprovalRuns gives late first appearances their own full settling window", async () => {
  const ciRun = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };
  const visualRun = {
    id: 26273463770,
    name: "Visual PR Verify",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/visual-pr-verify.yml@main",
    pull_requests: [],
  };

  const batches = [[], [], [], [ciRun], [ciRun], [ciRun, visualRun], [ciRun, visualRun], [ciRun, visualRun]];
  const sleeps: number[] = [];
  let now = 0;

  const pendingRuns = await waitForPendingApprovalRuns(
    async () => batches.shift() ?? [ciRun, visualRun],
    async (ms) => {
      sleeps.push(ms);
      now += ms;
    },
    () => now,
    {
      firstAppearanceTimeoutMs: 12_000,
      settlingWindowMs: 9_000,
    },
  );

  assert.deepEqual(pendingRuns, [ciRun, visualRun]);
  assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000]);
});

test("waitForPendingApprovalRuns keeps polling until the first run appears, even after the old short retry budget", async () => {
  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  const batches = [[], [], [], [], [run]];
  const sleeps: number[] = [];
  let now = 0;

  const pendingRuns = await waitForPendingApprovalRuns(
    async () => batches.shift() ?? [run],
    async (ms) => {
      sleeps.push(ms);
      now += ms;
    },
    () => now,
    {
      firstAppearanceTimeoutMs: 15_000,
      settlingWindowMs: 6_000,
    },
  );

  assert.deepEqual(pendingRuns, [run]);
  assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000, 3000]);
});

test("waitForPendingApprovalRuns accepts a configurable longer polling budget before the first run appears", async () => {
  const run = {
    id: 26273463769,
    name: "CI",
    event: "pull_request",
    status: "completed",
    conclusion: "action_required",
    head_sha: "734076155c44e569304856590019cea54506fdab",
    path: ".github/workflows/ci.yml@main",
    pull_requests: [],
  };

  const batches = [[], [], [], [], [], [run]];
  const sleeps: number[] = [];
  let now = 0;

  const pendingRuns = await waitForPendingApprovalRuns(
    async () => batches.shift() ?? [run],
    async (ms) => {
      sleeps.push(ms);
      now += ms;
    },
    () => now,
    {
      firstAppearanceTimeoutMs: 18_000,
      settlingWindowMs: 3_000,
    },
  );

  assert.deepEqual(pendingRuns, [run]);
  assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000, 3000]);
});

test("waitForPendingApprovalRuns stops after the first-appearance timeout when no runs arrive", async () => {
  let calls = 0;
  const sleeps: number[] = [];
  let now = 0;

  const pendingRuns = await waitForPendingApprovalRuns(
    async () => {
      calls += 1;
      return [];
    },
    async (ms) => {
      sleeps.push(ms);
      now += ms;
    },
    () => now,
    {
      firstAppearanceTimeoutMs: 9_000,
    },
  );

  assert.deepEqual(pendingRuns, []);
  assert.equal(calls, 4);
  assert.equal(sleeps.length, 3);
  assert.equal(now, 9_000);
});
