name: Contribution gate - pull requests

on:
  pull_request_target:
    types: [opened, reopened]

permissions:
  contents: read
  issues: write
  pull-requests: write

env:
  # Keep new gates observable first. Switch to "enforce" only after maintainers
  # have seeded active contributors and reviewed the dry-run signal.
  CONTRIBUTION_GATE_MODE: dry-run

jobs:
  gate:
    runs-on: ubuntu-latest
    steps:
      - name: Gate unapproved external pull requests
        uses: actions/github-script@v9
        with:
          script: |
            const pr = context.payload.pull_request;
            const owner = context.repo.owner;
            const repo = context.repo.repo;
            const privileged = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
            const gateMode = (process.env.CONTRIBUTION_GATE_MODE || 'dry-run').trim().toLowerCase();
            const enforceGate = gateMode === 'enforce';

            if (!['dry-run', 'enforce'].includes(gateMode)) {
              core.warning(`Unknown CONTRIBUTION_GATE_MODE "${gateMode}"; defaulting to dry-run.`);
            }

            if (privileged.has(pr.author_association)) return;
            if (pr.user.login === 'github-actions[bot]') return;

            function parseAllowlist(content) {
              return new Set(
                content
                  .split(/\r?\n/)
                  .map(line => line.replace(/#.*/, '').trim().toLowerCase())
                  .filter(Boolean)
              );
            }

            async function readAllowlist() {
              try {
                const { data } = await github.rest.repos.getContent({
                  owner,
                  repo,
                  path: '.github/APPROVED_CONTRIBUTORS',
                  ref: context.payload.repository.default_branch,
                });
                if (Array.isArray(data) || data.type !== 'file') return new Set();
                return parseAllowlist(
                  Buffer.from(data.content, data.encoding || 'base64').toString('utf8')
                );
              } catch (error) {
                if (error.status === 404) return new Set();
                throw error;
              }
            }

            const allowlist = await readAllowlist();
            const login = pr.user.login.toLowerCase();
            if (
              allowlist.has(`all:${login}`) ||
              allowlist.has(`pr:${login}`)
            ) {
              return;
            }

            const gateMessage = enforceGate
              ? 'This repository currently limits automated PR intake to contributors listed in `.github/APPROVED_CONTRIBUTORS`. This is a maintainer-safety control for code review and CI load, not a judgment on the contribution. A maintainer can grant recurring PR access with `/lgtm` after review; once the generated allowlist PR is merged, this pull request can be reopened or resubmitted.'
              : 'This repository is observing a maintainer-managed PR intake gate in dry-run mode, so this pull request is staying open. This note helps maintainers prepare the allowlist before any enforcement is considered.';

            const marker = '<!-- codewhale-pr-gate -->';
            const { data: comments } = await github.rest.issues.listComments({
              owner,
              repo,
              issue_number: pr.number,
              per_page: 100,
            });
            const alreadyNoted = comments.some(comment => (comment.body || '').includes(marker));
            if (!alreadyNoted) {
              await github.rest.issues.createComment({
                owner,
                repo,
                issue_number: pr.number,
                body: [
                  marker,
                  `Thanks @${pr.user.login} for taking the time to contribute.`,
                  '',
                  gateMessage,
                  '',
                  'Please read `CONTRIBUTING.md` for the expected contribution shape. A maintainer can grant recurring PR access by commenting `/lgtm` on a pull request.',
                ].join('\n'),
              });
            }

            if (!enforceGate) return;

            await github.rest.pulls.update({
              owner,
              repo,
              pull_number: pr.number,
              state: 'closed',
            });