name: Approve Contributor

on:
  issue_comment:
    types: [created]

jobs:
  approve:
    if: ${{ !github.event.issue.pull_request }}
    runs-on: ubuntu-latest
    permissions:
      contents: write
      issues: write
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.repository.default_branch }}

      - name: Update contributor approval
        id: update
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');

            const APPROVED_FILE = '.github/APPROVED_CONTRIBUTORS';
            const VALID_CAPABILITIES = new Set(['issue', 'pr']);
            const issueAuthor = context.payload.issue.user.login;
            const commenter = context.payload.comment.user.login;
            const commentBody = (context.payload.comment.body || '').trim();

            let targetCapability;
            if (/\blgtmi\b/i.test(commentBody)) {
              targetCapability = 'issue';
            } else if (/\blgtm\b/i.test(commentBody)) {
              targetCapability = 'pr';
            } else {
              console.log('Comment does not match lgtm or lgtmi');
              core.setOutput('status', 'skipped');
              return;
            }

            try {
              const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
                owner: context.repo.owner,
                repo: context.repo.repo,
                username: commenter,
              });

              if (!['admin', 'maintain', 'write'].includes(permissionLevel.permission)) {
                console.log(`${commenter} does not have write access`);
                core.setOutput('status', 'skipped');
                return;
              }
            } catch {
              console.log(`${commenter} does not have collaborator access`);
              core.setOutput('status', 'skipped');
              return;
            }

            function parseApprovedUsers(content) {
              const lines = content.split('\n');
              const entries = [];
              const users = new Map();

              for (const line of lines) {
                const trimmed = line.trim();
                if (!trimmed || trimmed.startsWith('#')) {
                  entries.push({ type: 'other', line });
                  continue;
                }

                const parts = trimmed.split(/\s+/);
                if (parts.length !== 2) {
                  console.log(`Skipping malformed line: ${line}`);
                  entries.push({ type: 'other', line });
                  continue;
                }

                const [username, capability] = parts;
                const normalizedCapability = capability.toLowerCase();
                if (!VALID_CAPABILITIES.has(normalizedCapability)) {
                  console.log(`Skipping line with invalid capability: ${line}`);
                  entries.push({ type: 'other', line });
                  continue;
                }

                const normalizedUser = username.toLowerCase();
                const entry = { type: 'user', username, normalizedUser, capability: normalizedCapability };
                entries.push(entry);
                users.set(normalizedUser, entry);
              }

              return { entries, users };
            }

            function stringifyApprovedUsers(entries) {
              const normalizedEntries = [...entries];

              while (normalizedEntries.length > 0) {
                const lastEntry = normalizedEntries[normalizedEntries.length - 1];
                if (lastEntry.type !== 'other' || lastEntry.line.trim() !== '') {
                  break;
                }
                normalizedEntries.pop();
              }

              return `${normalizedEntries
                .map((entry) => (entry.type === 'user' ? `${entry.username} ${entry.capability}` : entry.line))
                .join('\n')}\n`;
            }

            const content = fs.readFileSync(APPROVED_FILE, 'utf8');
            const { entries, users } = parseApprovedUsers(content);
            const normalizedAuthor = issueAuthor.toLowerCase();
            const existingEntry = users.get(normalizedAuthor);
            const existingCapability = existingEntry?.capability ?? null;

            if (existingCapability === 'pr' || existingCapability === targetCapability) {
              core.setOutput('status', 'already');
              core.setOutput('capability', existingCapability);
              console.log(`${issueAuthor} is already approved for ${existingCapability}`);
              return;
            }

            if (existingEntry) {
              existingEntry.capability = targetCapability;
            } else {
              entries.push({ type: 'user', username: issueAuthor, normalizedUser: normalizedAuthor, capability: targetCapability });
            }

            fs.writeFileSync(APPROVED_FILE, stringifyApprovedUsers(entries));
            core.setOutput('status', existingCapability ? 'updated' : 'added');
            core.setOutput('capability', targetCapability);
            console.log(`Set ${issueAuthor} capability to ${targetCapability}`);

      - name: Commit and push
        if: steps.update.outputs.status == 'added' || steps.update.outputs.status == 'updated'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add .github/APPROVED_CONTRIBUTORS
          git diff --staged --quiet || git commit -m "chore: approve contributor ${{ github.event.issue.user.login }}"
          git push

      - name: Comment on issue
        if: steps.update.outputs.status == 'added' || steps.update.outputs.status == 'updated' || steps.update.outputs.status == 'already'
        uses: actions/github-script@v7
        with:
          script: |
            const issueAuthor = context.payload.issue.user.login;
            const capability = '${{ steps.update.outputs.capability }}';
            const defaultBranch = context.payload.repository.default_branch;
            let body;

            if ('${{ steps.update.outputs.status }}' === 'already') {
              body = `@${issueAuthor} is already approved.`;
            } else if (capability === 'issue') {
              body = [
                `@${issueAuthor} approved for issues. Your future issues will not be auto-closed. PRs still require \`lgtm\`.`,
                '',
                `See [CONTRIBUTING.md](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/${defaultBranch}/CONTRIBUTING.md).`,
              ].join('\n');
            } else {
              body = [
                `@${issueAuthor} approved for issues and PRs. Your future issues and PRs will not be auto-closed.`,
                '',
                `See [CONTRIBUTING.md](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/${defaultBranch}/CONTRIBUTING.md).`,
              ].join('\n');
            }

            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body,
            });

