name: Playwright report

on:
  pull_request:
    types: [opened, synchronize, reopened]

permissions:
  contents: read

concurrency:
  group: playwright-dochost-${{ github.event.pull_request.number }}
  cancel-in-progress: false

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - name: Run Playwright
        run: npx playwright test --project=chromium --reporter=html
        env:
          CI: 'true'
          PLAYWRIGHT_HTML_OPEN: never
          PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report
      - name: Save complete report and attachments
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v6
        with:
          name: playwright-report
          path: playwright-report/
          if-no-files-found: ignore

  share:
    needs: test
    if: >-
      ${{ !cancelled() && github.event.pull_request.head.repo.full_name == github.repository
          && github.actor != 'dependabot[bot]' }}
    runs-on: ubuntu-latest
    timeout-minutes: 5
    permissions:
      pull-requests: write
    steps:
      - uses: actions/download-artifact@v7
        with:
          name: playwright-report
          path: playwright-report
      - name: Publish report and update PR comment
        uses: actions/github-script@v8
        env:
          DOCHOST_API_KEY: ${{ secrets.DOCHOST_API_KEY }}
          TEST_RESULT: ${{ needs.test.result }}
        with:
          script: |
            const fs = require('node:fs');
            const key = process.env.DOCHOST_API_KEY;
            if (!key) throw new Error('Set the DOCHOST_API_KEY repository secret.');

            const body = fs.readFileSync('playwright-report/index.html', 'utf8');
            const marker = '<!-- dochost-playwright-report -->';
            const { owner, repo } = context.repo;
            const issue_number = context.payload.pull_request.number;
            const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${context.runId}`;

            async function call(name, args) {
              const response = await fetch('https://dochost.io/api/mcp', {
                method: 'POST',
                headers: {
                  Authorization: `Bearer ${key}`,
                  'Content-Type': 'application/json',
                  Accept: 'application/json, text/event-stream',
                },
                body: JSON.stringify({
                  jsonrpc: '2.0', id: 1, method: 'tools/call',
                  params: { name, arguments: args },
                }),
                signal: AbortSignal.timeout(30_000),
              });
              if (!response.ok) throw new Error(`DocHost ${name}: HTTP ${response.status}`);
              const { result, error } = await response.json();
              if (error || result?.isError || !result?.structuredContent) {
                throw new Error(`DocHost ${name} failed. Check API key, page access and file size.`);
              }
              return result.structuredContent;
            }

            const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: issue_number });
            if (pr.head.sha !== context.payload.pull_request.head.sha) {
              core.notice('Skipping a superseded commit.');
              return;
            }

            const comments = await github.paginate(github.rest.issues.listComments, {
              owner, repo, issue_number, per_page: 100,
            });
            const previous = comments.find(({ user, body }) =>
              user?.login === 'github-actions[bot]' && body?.startsWith(marker + '\n'));
            const slug = previous?.body.match(/^<!-- dochost-slug: ([a-z0-9-]{1,64}) -->$/m)?.[1];
            if (previous && !slug) throw new Error('Existing report comment is missing its DocHost slug.');

            let report;
            if (slug) {
              const live = await call('get_page_content', { slug });
              if (!live.editable) throw new Error('The existing report is not editable.');
              await call('update_page', {
                slug, body, format: 'html', ifVersion: live.version,
                ...(live.sha256 ? { ifSha: live.sha256 } : {}),
              });
              report = live;
            } else {
              report = await call('publish', { body, format: 'html', public: false });
            }

            const url = new URL(report.url);
            if (url.protocol !== 'https:' || !/^[a-z0-9-]{1,64}$/.test(report.slug)) {
              throw new Error('DocHost returned an unexpected URL or slug.');
            }
            const comment = [
              marker,
              `<!-- dochost-slug: ${report.slug} -->`,
              '### Playwright HTML report',
              `[Open the report](${url.href}) · [Actions run and full artifact](${runUrl})`,
              `Test job: **${process.env.TEST_RESULT}** · Commit: \`${pr.head.sha.slice(0, 7)}\``,
            ].join('\n');

            if (previous) {
              await github.rest.issues.updateComment({ owner, repo, comment_id: previous.id, body: comment });
            } else {
              await github.rest.issues.createComment({ owner, repo, issue_number, body: comment });
            }
