name: Editorial Governance

on:
  pull_request:
    paths:
      - 'config/themes/**'
      - 'code/scripts/build_theme_catalog.py'
      - 'code/scripts/validate_description_mapping.py'
      - 'code/scripts/lint_theme_editorial.py'
      - 'code/scripts/ratchet_description_thresholds.py'
      - 'code/tests/test_theme_validation_comprehensive.py'
  workflow_dispatch:

jobs:
  validate-editorial:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      issues: write
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
            python-version: '3.11'
      - name: Install deps
        run: |
          python -m pip install --upgrade pip
          python -m pip install -r requirements.txt
          if [ -f requirements-dev.txt ]; then python -m pip install -r requirements-dev.txt; fi
      - name: Build catalog (alt output, seed)
        run: |
          python code/scripts/build_theme_catalog.py --output config/themes/theme_list_ci.json --limit 0
        env:
          EDITORIAL_INCLUDE_FALLBACK_SUMMARY: '1'
          EDITORIAL_SEED: '123'
      - name: Lint editorial YAML (enforced minimum examples)
        run: |
          python code/scripts/lint_theme_editorial.py --strict --min-examples 5 --enforce-min-examples
        env:
          EDITORIAL_REQUIRE_DESCRIPTION: '1'
          EDITORIAL_REQUIRE_POPULARITY: '1'
          EDITORIAL_MIN_EXAMPLES_ENFORCE: '1'
      - name: Validate description mapping
        run: |
          python code/scripts/validate_description_mapping.py
      - name: Run regression & unit tests (editorial subset + enforcement)
        run: |
          python -m pytest -q code/tests/test_theme_validation_comprehensive.py::test_generic_description_regression code/tests/test_synergy_pairs_and_metadata_info.py code/tests/test_editorial_governance_phase_d_closeout.py code/tests/test_theme_catalog_comprehensive.py::TestThemeEnrichmentPipeline::test_validate_min_examples_warning code/tests/test_theme_catalog_comprehensive.py::TestThemeEnrichmentPipeline::test_validate_min_examples_error
        env:
          EDITORIAL_TEST_USE_FIXTURES: '1'
      - name: Ratchet proposal (non-blocking)
        run: |
          python code/scripts/ratchet_description_thresholds.py > ratchet_proposal.json || true
      - name: Upload ratchet proposal artifact
        uses: actions/upload-artifact@v4
        with:
          name: ratchet-proposal
          path: ratchet_proposal.json
      - name: Post ratchet proposal PR comment
        if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const fs = require('fs');
            const markerStart = '<!-- ratchet-proposal:description-fallback -->';
            const markerEnd = '<!-- end-ratchet-proposal -->';
            let proposal = {};
            try { proposal = JSON.parse(fs.readFileSync('ratchet_proposal.json','utf8')); } catch(e) { proposal = {error: 'Failed to read ratchet_proposal.json'}; }
            function buildBody(p) {
              if (p.error) {
                return `${markerStart}\n**Description Fallback Ratchet Proposal**\n\n:warning: Could not compute proposal: ${p.error}. Ensure history file exists and job built with EDITORIAL_INCLUDE_FALLBACK_SUMMARY=1.\n${markerEnd}`;
              }
              const curTotal = p.current_total_ceiling;
              const curPct = p.current_pct_ceiling;
              const propTotal = p.proposed_total_ceiling;
              const propPct = p.proposed_pct_ceiling;
              const changedTotal = propTotal !== curTotal;
              const changedPct = propPct !== curPct;
              const rationale = (p.rationale && p.rationale.length) ? p.rationale.map(r=>`- ${r}`).join('\n') : '- No ratchet conditions met (headroom not significant).';
              const testFile = 'code/tests/test_theme_validation_comprehensive.py';
              let updateSnippet = 'No changes recommended.';
              if (changedTotal || changedPct) {
                updateSnippet = [
                  'Update ceilings in regression test (lines asserting generic_total & generic_pct):',
                  '```diff',
                  `- assert summary.get('generic_total', 0) <= ${curTotal}, summary`,
                  `+ assert summary.get('generic_total', 0) <= ${propTotal}, summary`,
                  `- assert summary.get('generic_pct', 100.0) < ${curPct}, summary`,
                  `+ assert summary.get('generic_pct', 100.0) < ${propPct}, summary`,
                  '```' ].join('\n');
              }
              return `${markerStart}\n**Description Fallback Ratchet Proposal**\n\nLatest snapshot generic_total: **${p.latest_total}**  | median recent generic_pct: **${p.median_recent_pct}%** (window ${p.records_considered})\n\n| Ceiling | Current | Proposed |\n|---------|---------|----------|\n| generic_total | ${curTotal} | ${propTotal}${changedTotal ? ' ←' : ''} |\n| generic_pct | ${curPct}% | ${propPct}%${changedPct ? ' ←' : ''} |\n\n**Rationale**\n${rationale}\n\n${updateSnippet}\n\nHistory-based ratcheting keeps pressure on reducing generic fallback descriptions. If adopting the new ceilings, ensure editorial quality remains stable.\n\n_Analysis generated by ratchet bot._\n${markerEnd}`;
            }
            const body = buildBody(proposal);
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              per_page: 100
            });
            const existing = comments.find(c => c.body && c.body.includes(markerStart));
            if (existing) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: existing.id,
                body
              });
              core.info('Updated existing ratchet proposal comment.');
            } else {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body
              });
              core.info('Created new ratchet proposal comment.');
            }