name: Merge PR when ready

on:
  workflow_run:
    workflows: ["CI"]
    types:
      - completed

env:
  # Allowed authors for auto-merge (update in one place)
  ALLOWED_MERGE_AUTHORS: wwwillchen,wwwillchen-bot,dyadbot

jobs:
  merge:
    # Only run if:
    # 1. The CI workflow succeeded
    # 2. The trigger was a pull request (not a push to main)
    if: >
      github.event.workflow_run.conclusion == 'success' &&
      github.event.workflow_run.event == 'pull_request'
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      actions: read
    steps:
      - name: Check PR and merge if ready
        uses: actions/github-script@v7
        env:
          ALLOWED_AUTHORS: ${{ env.ALLOWED_MERGE_AUTHORS }}
        with:
          script: |
            const owner = context.repo.owner;
            const repo = context.repo.repo;
            const headSha = context.payload.workflow_run.head_sha;
            const ciCheckSuiteId = context.payload.workflow_run.check_suite_id;

            console.log(`Checking PR for head SHA: ${headSha}`);

            // Use pull_requests from workflow_run event payload directly
            // This avoids the 100 PR limit issue when listing all open PRs
            const workflowRunPRs = context.payload.workflow_run.pull_requests;

            let pr;
            if (workflowRunPRs && workflowRunPRs.length > 0) {
              if (workflowRunPRs.length > 1) {
                console.log(`Multiple PRs (${workflowRunPRs.length}) associated with workflow run — refusing to merge to avoid ambiguity`);
                return;
              }
              const prNumber = workflowRunPRs[0].number;
              const { data: prData } = await github.rest.pulls.get({
                owner,
                repo,
                pull_number: prNumber,
              });
              pr = prData.state === 'open' ? prData : undefined;
            } else {
              // Fallback: use listPullRequestsAssociatedWithCommit for targeted lookup
              // Note: workflow_run.pull_requests is empty for fork PRs
              const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
                owner,
                repo,
                commit_sha: headSha,
              });
              const headBranch = context.payload.workflow_run.head_branch;
              const openPrs = prs.filter(p => p.state === 'open' && p.head.ref === headBranch);
              if (openPrs.length > 1) {
                console.log(`Multiple open PRs found for SHA ${headSha} on branch ${headBranch} — refusing to merge to avoid ambiguity`);
                return;
              }
              pr = openPrs[0];
            }

            if (!pr) {
              console.log('No open PR found for this SHA');
              return;
            }

            console.log(`Found PR #${pr.number}: ${pr.title}`);
            console.log(`Author: ${pr.user.login}`);

            // Check if author is allowed (read from environment variable)
            const allowedAuthors = (process.env.ALLOWED_AUTHORS || '').split(',').map(a => a.trim()).filter(Boolean);
            if (!allowedAuthors.includes(pr.user.login)) {
              console.log(`Author ${pr.user.login} is not in allowed list: ${allowedAuthors.join(', ')}`);
              return;
            }

            // Check if PR has the "merge-when-ready" label
            const labels = pr.labels.map(l => l.name);
            console.log(`PR labels: ${labels.join(', ') || '(none)'}`);

            if (!labels.includes('merge-when-ready')) {
              console.log('PR does not have "merge-when-ready" label');
              return;
            }

            // Verify all check suites have passed
            const allCheckSuites = await github.paginate(
              github.rest.checks.listSuitesForRef,
              { owner, repo, ref: headSha, per_page: 100 },
              (response) => response.data
            );

            const pendingOrFailed = allCheckSuites.filter(suite => {
              // Skip neutral conclusions (e.g., skipped workflows)
              if (suite.conclusion === 'neutral' || suite.conclusion === 'skipped' || suite.conclusion === 'cancelled') {
                console.log(`Skipping suite with conclusion '${suite.conclusion}': ${suite.app?.name || 'Unknown'}`);
                return false;
              }
              // Exclude the CI workflow's check suite (already completed successfully — it triggered this workflow)
              if (suite.id === ciCheckSuiteId) {
                console.log(`Skipping CI check suite (id=${suite.id})`);
                return false;
              }
              // Skip check suites that are in-progress (conclusion is null)
              // These are likely workflow_run-triggered workflows that started after CI
              if (suite.status === 'in_progress' || suite.status === 'queued') {
                // Check if this is a workflow_run triggered suite by comparing timestamps
                const ciCompletedAt = new Date(context.payload.workflow_run.updated_at);
                const suiteCreatedAt = new Date(suite.created_at);
                if (suiteCreatedAt >= ciCompletedAt) {
                  console.log(`Skipping workflow_run-triggered suite: ${suite.app?.name || 'Unknown'} (created after CI completed)`);
                  return false;
                }
              }
              // Check if still in progress or failed
              return suite.status !== 'completed' || suite.conclusion !== 'success';
            });

            // Guard: require at least one completed successful check suite
            const successfulSuites = allCheckSuites.filter(
              suite => suite.status === 'completed' && suite.conclusion === 'success'
            );
            if (successfulSuites.length === 0) {
              console.log('No completed successful check suites found — refusing to merge');
              return;
            }

            if (pendingOrFailed.length > 0) {
              console.log('Some check suites are still pending or have failed:');
              pendingOrFailed.forEach(suite => {
                console.log(`  - ${suite.app?.name || 'Unknown'}: status=${suite.status}, conclusion=${suite.conclusion}`);
              });
              return;
            }

            console.log('All checks passed! Merging PR...');

            // Merge the PR via squash, using sha parameter to prevent TOCTOU race condition
            try {
              await github.rest.pulls.merge({
                owner,
                repo,
                pull_number: pr.number,
                merge_method: 'squash',
                sha: headSha,
              });
              console.log(`Successfully merged PR #${pr.number}`);
            } catch (error) {
              console.log(`Failed to merge PR: ${error.message}`);
              throw error;
            }
