# Label PRs by size (changed lines, excluding lockfiles and docs).
#
# Size thresholds:
#   XS: < 50, S: < 200, M: < 500, L: < 1000, XL: >= 1000

name: "📏 PR Size Labeler"

on:
  pull_request_target:
    types: [opened, synchronize, reopened]
  workflow_dispatch:
    inputs:
      max_items:
        description: "Maximum number of open PRs to process"
        default: "100"
        type: string

permissions:
  contents: read

jobs:
  size-label:
    if: github.event_name != 'workflow_dispatch'
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      issues: write

    steps:
      - name: Apply PR size label
        uses: actions/github-script@v8
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const { owner, repo } = context.repo;
            const pullRequest = context.payload.pull_request;
            if (!pullRequest) return;

            const sizeLabels = ['size: XS', 'size: S', 'size: M', 'size: L', 'size: XL'];
            const labelColor = 'b76e79';

            // Ensure labels exist
            for (const name of sizeLabels) {
              try {
                await github.rest.issues.getLabel({ owner, repo, name });
              } catch (error) {
                if (error?.status !== 404) throw error;
                await github.rest.issues.createLabel({
                  owner, repo, name, color: labelColor,
                });
              }
            }

            const files = await github.paginate(github.rest.pulls.listFiles, {
              owner, repo, pull_number: pullRequest.number, per_page: 100,
            });

            const excludedFiles = new Set(['poetry.lock', 'uv.lock']);
            const totalChangedLines = files.reduce((total, file) => {
              const path = file.filename ?? '';
              if (path.startsWith('docs/') || excludedFiles.has(path)) return total;
              return total + (file.additions ?? 0) + (file.deletions ?? 0);
            }, 0);

            let targetSizeLabel = 'size: XL';
            if (totalChangedLines < 50) targetSizeLabel = 'size: XS';
            else if (totalChangedLines < 200) targetSizeLabel = 'size: S';
            else if (totalChangedLines < 500) targetSizeLabel = 'size: M';
            else if (totalChangedLines < 1000) targetSizeLabel = 'size: L';

            // Remove stale size labels
            const currentLabels = await github.paginate(
              github.rest.issues.listLabelsOnIssue,
              { owner, repo, issue_number: pullRequest.number, per_page: 100 },
            );
            for (const label of currentLabels) {
              const name = label.name ?? '';
              if (sizeLabels.includes(name) && name !== targetSizeLabel) {
                await github.rest.issues.removeLabel({
                  owner, repo, issue_number: pullRequest.number, name,
                });
              }
            }

            await github.rest.issues.addLabels({
              owner, repo, issue_number: pullRequest.number, labels: [targetSizeLabel],
            });

            console.log(`PR #${pullRequest.number}: ${totalChangedLines} changed lines → ${targetSizeLabel}`);

  backfill:
    if: github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      issues: write

    steps:
      - name: Backfill size labels on open PRs
        uses: actions/github-script@v8
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const { owner, repo } = context.repo;
            const maxItems = parseInt('${{ inputs.max_items }}') || 100;

            const sizeLabels = ['size: XS', 'size: S', 'size: M', 'size: L', 'size: XL'];
            const labelColor = 'b76e79';

            // Ensure labels exist
            for (const name of sizeLabels) {
              try {
                await github.rest.issues.getLabel({ owner, repo, name });
              } catch (error) {
                if (error?.status !== 404) throw error;
                await github.rest.issues.createLabel({
                  owner, repo, name, color: labelColor,
                });
                console.log(`Created label: ${name}`);
              }
            }

            function getSizeLabel(totalChangedLines) {
              if (totalChangedLines < 50) return 'size: XS';
              if (totalChangedLines < 200) return 'size: S';
              if (totalChangedLines < 500) return 'size: M';
              if (totalChangedLines < 1000) return 'size: L';
              return 'size: XL';
            }

            const prs = await github.paginate(github.rest.pulls.list, {
              owner, repo, state: 'open', per_page: 100,
            });

            let processed = 0;
            for (const pr of prs) {
              if (processed >= maxItems) break;

              const files = await github.paginate(github.rest.pulls.listFiles, {
                owner, repo, pull_number: pr.number, per_page: 100,
              });

              const excludedFiles = new Set(['poetry.lock', 'uv.lock']);
              const totalChangedLines = files.reduce((total, file) => {
                const path = file.filename ?? '';
                if (path.startsWith('docs/') || excludedFiles.has(path)) return total;
                return total + (file.additions ?? 0) + (file.deletions ?? 0);
              }, 0);

              const targetSizeLabel = getSizeLabel(totalChangedLines);

              // Remove stale size labels
              const currentLabels = await github.paginate(
                github.rest.issues.listLabelsOnIssue,
                { owner, repo, issue_number: pr.number, per_page: 100 },
              );
              for (const label of currentLabels) {
                const name = label.name ?? '';
                if (sizeLabels.includes(name) && name !== targetSizeLabel) {
                  await github.rest.issues.removeLabel({
                    owner, repo, issue_number: pr.number, name,
                  });
                }
              }

              await github.rest.issues.addLabels({
                owner, repo, issue_number: pr.number, labels: [targetSizeLabel],
              });

              console.log(`PR #${pr.number}: ${totalChangedLines} changed lines → ${targetSizeLabel}`);
              processed++;
            }

            console.log(`\nBackfill complete. Processed ${processed} PRs.`);
