name: CI · Storybook

# Reusable stage: the @bloom/ui Storybook (M28-1) in one job. Called by ci.yml, gated there on
# the `ui` path filter (#788), so PRs that cannot affect the Storybook (server-only, chart-only,
# docs-only) skip it; pushes to main always run it.
#
# 1. Static build - must stay green on every UI-touching run, whether or not anything publishes.
# 2. Chromatic visual regression (M28-4), opt-in: a clean skip until the CHROMATIC_PROJECT_TOKEN
#    repo secret is set. TurboSnap (onlyChanged) snapshots only the stories a change affects,
#    which needs the full git history. On PRs a visual diff does NOT fail CI
#    (exitZeroOnChanges) - it surfaces on the PR's "UI Tests" Chromatic check for human review,
#    and baselines are only ever accepted by a reviewer in the Chromatic UI (no
#    autoAcceptChanges anywhere). Pushes to main stay strict: by then all diffs should have been
#    accepted on the PR, so a failure there means an unreviewed visual change slipped through.
# 3. Hosted Storybook on Cloudflare Pages (M28-3), opt-in: a no-op until BOTH the shared
#    Cloudflare creds (CLOUDFLARE_API_TOKEN secret + CLOUDFLARE_ACCOUNT_ID variable) AND the
#    Storybook-specific CLOUDFLARE_STORYBOOK_PAGES_PROJECT variable are set - the project
#    variable is the real opt-in switch, since the account creds also serve the web/admin
#    deploys. PRs publish to a per-PR branch alias with a rolling PR comment; main publishes the
#    stable production alias.
#
# Secrets are invisible to `if:`, so the opt-in guards surface them through job-level env vars.

on:
  workflow_call:
    secrets:
      CHROMATIC_PROJECT_TOKEN:
        required: false
      CLOUDFLARE_API_TOKEN:
        required: false

permissions:
  contents: read
  pull-requests: write

jobs:
  storybook:
    name: Storybook (build, Chromatic, publish)
    runs-on: bloom-arc
    env:
      HAS_CHROMATIC: ${{ secrets.CHROMATIC_PROJECT_TOKEN != '' }}
      HAS_CF: ${{ secrets.CLOUDFLARE_API_TOKEN != '' && vars.CLOUDFLARE_ACCOUNT_ID != '' && vars.CLOUDFLARE_STORYBOOK_PAGES_PROJECT != '' }}
    steps:
      # Full history only when Chromatic's TurboSnap will trace changed files against baseline
      # commits; the build and the Pages publish need just the tip.
      - uses: actions/checkout@v4
        with:
          fetch-depth: ${{ env.HAS_CHROMATIC != 'true' && 1 || 0 }}

      - uses: pnpm/action-setup@v4
        with:
          version: 10.6.3

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
          cache-dependency-path: pnpm-lock.yaml

      # Workspace-root install (single lockfile, #481); build + publish stay in packages/ui.
      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build Storybook
        id: build
        working-directory: packages/ui
        run: pnpm run build-storybook

      # Chromatic builds its own copy: TurboSnap needs the build's module stats, which the
      # static build above does not emit.
      - name: Publish to Chromatic
        if: ${{ !cancelled() && env.HAS_CHROMATIC == 'true' }}
        uses: chromaui/action@v18
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
          workingDir: packages/ui
          buildScriptName: build-storybook
          onlyChanged: true
          exitZeroOnChanges: ${{ github.event_name == 'pull_request' }}

      # PRs publish under a deterministic pr-<number> branch alias; main is the Pages
      # project's production branch, so pushing it updates the stable alias.
      - name: Publish to Cloudflare Pages
        if: ${{ !cancelled() && steps.build.outcome == 'success' && env.HAS_CF == 'true' }}
        id: deploy
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
          # pnpm is the workspace PM; npm can't parse workspace:* deps (#520/#524)
          packageManager: pnpm
          workingDirectory: packages/ui
          command: >-
            pages deploy storybook-static
            --project-name=${{ vars.CLOUDFLARE_STORYBOOK_PAGES_PROJECT }}
            --branch=${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'main' }}

      # One rolling comment per PR (upserted by marker), pointing at the stable per-PR alias.
      - name: Comment preview URL on the PR
        if: ${{ github.event_name == 'pull_request' && steps.deploy.outcome == 'success' }}
        uses: actions/github-script@v7
        env:
          PREVIEW_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url || steps.deploy.outputs.deployment-url }}
        with:
          script: |
            const marker = '<!-- storybook-preview -->';
            const body = [
              marker,
              '### 📖 Storybook preview',
              '',
              `${process.env.PREVIEW_URL} (commit ${context.payload.pull_request.head.sha.slice(0, 12)})`,
            ].join('\n');
            const { owner, repo } = context.repo;
            const issue_number = context.payload.pull_request.number;
            const { data: comments } = await github.rest.issues.listComments({
              owner,
              repo,
              issue_number,
              per_page: 100,
            });
            const existing = comments.find((c) => c.body?.includes(marker));
            if (existing) {
              await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
            } else {
              await github.rest.issues.createComment({ owner, repo, issue_number, body });
            }
