name: PR Requirements Check

on:
  pull_request_target:
    types: [opened, reopened, edited, synchronize]

jobs:
  check-requirements:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      issues: write

    steps:
      - name: Check PR has linked issue with assignee
        uses: actions/github-script@v7
        with:
          script: |
            const pr = context.payload.pull_request;
            const prNumber = pr.number;
            const prBody = pr.body || '';
            const prTitle = pr.title || '';
            const prLabels = (pr.labels || []).map(l => l.name);

            // Allow micro-fix and documentation PRs without a linked issue
            const isMicroFix = prLabels.includes('micro-fix') || /micro-fix/i.test(prTitle);
            const isDocumentation = prLabels.includes('documentation') || /\bdocs?\b/i.test(prTitle);
            if (isMicroFix || isDocumentation) {
              const reason = isMicroFix ? 'micro-fix' : 'documentation';
              console.log(`PR #${prNumber} is a ${reason}, skipping issue requirement.`);
              return;
            }

            // Extract issue numbers from body and title
            // Matches: fixes #123, closes #123, resolves #123, or plain #123
            const issuePattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)?\s*#(\d+)/gi;

            const allText = `${prTitle} ${prBody}`;
            const matches = [...allText.matchAll(issuePattern)];
            const issueNumbers = [...new Set(matches.map(m => parseInt(m[1], 10)))];

            console.log(`PR #${prNumber}:`);
            console.log(`  Found issue references: ${issueNumbers.length > 0 ? issueNumbers.join(', ') : 'none'}`);

            if (issueNumbers.length === 0) {
              const message = `## PR Requirements Warning

            This PR does not meet the contribution requirements.
            If the issue is not fixed within ~24 hours, it may be automatically closed.

            **Missing:** No linked issue found.

            **To fix:**
            1. Create or find an existing issue for this work
            2. Assign yourself to the issue
            3. Re-open this PR and add \`Fixes #123\` in the description

            **Exception:** To bypass this requirement, you can:
            - Add the \`micro-fix\` label or include \`micro-fix\` in your PR title for trivial fixes
            - Add the \`documentation\` label or include \`doc\`/\`docs\` in your PR title for documentation changes

            **Micro-fix requirements** (must meet ALL):
            | Qualifies | Disqualifies |
            |-----------|--------------|
            | < 20 lines changed | Any functional bug fix |
            | Typos & Documentation & Linting | Refactoring for "clean code" |
            | No logic/API/DB changes | New features (even tiny ones) |

            **Why is this required?** See #472 for details.`;

              const comments = await github.paginate(github.rest.issues.listComments, {
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                per_page: 100,
              });

              const botComment = comments.find(
                (c) => c.user.type === 'Bot' && c.body.includes('PR Requirements Warning')
              );

              if (!botComment) {
                await github.rest.issues.createComment({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: prNumber,
                  body: message,
                });
              }

              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                labels: ['pr-requirements-warning'],
              });

              core.setFailed('PR must reference an issue');
              return;
            }

            // Check if any linked issue has the PR author as assignee
            const prAuthor = pr.user.login;
            let issueWithAuthorAssigned = null;
            let issuesWithoutAuthor = [];

            for (const issueNum of issueNumbers) {
              try {
                const { data: issue } = await github.rest.issues.get({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: issueNum,
                });

                const assigneeLogins = (issue.assignees || []).map(a => a.login);
                if (assigneeLogins.includes(prAuthor)) {
                  issueWithAuthorAssigned = issueNum;
                  console.log(`  Issue #${issueNum} has PR author ${prAuthor} as assignee`);
                  break;
                } else {
                  issuesWithoutAuthor.push({
                    number: issueNum,
                    assignees: assigneeLogins
                  });
                  console.log(`  Issue #${issueNum} assignees: ${assigneeLogins.length > 0 ? assigneeLogins.join(', ') : 'none'} (PR author: ${prAuthor})`);
                }
              } catch (error) {
                console.log(`  Issue #${issueNum} not found or inaccessible`);
              }
            }

            if (!issueWithAuthorAssigned) {
              const issueList = issuesWithoutAuthor.map(i =>
                `#${i.number} (assignees: ${i.assignees.length > 0 ? i.assignees.join(', ') : 'none'})`
              ).join(', ');

              const message = `## PR Requirements Warning

            This PR does not meet the contribution requirements.
            If the issue is not fixed within ~24 hours, it may be automatically closed.

            **PR Author:** @${prAuthor}
            **Found issues:** ${issueList}
            **Problem:** The PR author must be assigned to the linked issue.

            **To fix:**
            1. Assign yourself (@${prAuthor}) to one of the linked issues
            2. Re-open this PR

            **Exception:** To bypass this requirement, you can:
            - Add the \`micro-fix\` label or include \`micro-fix\` in your PR title for trivial fixes
            - Add the \`documentation\` label or include \`doc\`/\`docs\` in your PR title for documentation changes

            **Micro-fix requirements** (must meet ALL):
            | Qualifies | Disqualifies |
            |-----------|--------------|
            | < 20 lines changed | Any functional bug fix |
            | Typos & Documentation & Linting | Refactoring for "clean code" |
            | No logic/API/DB changes | New features (even tiny ones) |

            **Why is this required?** See #472 for details.`;

              const comments = await github.paginate(github.rest.issues.listComments, {
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                per_page: 100,
              });

              const botComment = comments.find(
                (c) => c.user.type === 'Bot' && c.body.includes('PR Requirements Warning')
              );

              if (!botComment) {
                await github.rest.issues.createComment({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: prNumber,
                  body: message,
                });
              }

              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                labels: ['pr-requirements-warning'],
              });

              core.setFailed('PR author must be assigned to the linked issue');
            } else {
              console.log(`PR requirements met! Issue #${issueWithAuthorAssigned} has ${prAuthor} as assignee.`);
              try {
                await github.rest.issues.removeLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: prNumber,
                  name: "pr-requirements-warning"
                });
              }catch (error){
                //ignore if label doesn't exist
              }
            }