# Zindex - API dependency map. Maintains a service-to-service dependency graph
# from OpenAPI specs and SDK imports. Runs on every PR touching a service spec
# AND on a weekly schedule (so newly-imported SDKs picked up by static analysis
# don't have to wait for a spec change to surface in the diagram).
#
# Drop into .github/workflows/zindex-api-topology.yml.

name: Zindex - API dependency map

on:
  pull_request:
    paths:
      - "services/*/openapi.yaml"
      - "services/*/openapi.json"
      - "packages/*/package.json"
  schedule:
    - cron: "0 9 * * 1"   # Monday 09:00 UTC weekly catch-up
  workflow_dispatch:

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  ZINDEX_API_BASE: https://api.zindex.ai

jobs:
  sync-topology:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 1. Walk every services/*/openapi.* and emit a normalised
      #    { services: [...], operations: [...] } JSON. Then walk
      #    package.json files and import statements to derive consumer→provider
      #    edges. This step is project-specific - implement parse-topology.mjs
      #    against your monorepo's conventions.
      - name: Derive topology from specs + imports
        run: |
          mkdir -p out
          node scripts/parse-topology.mjs > out/topology.json
          echo "::notice::Detected $(jq '.services | length' out/topology.json) services, $(jq '.edges | length' out/topology.json) cross-service edges"

      # 2. Capture current revision (for the diff later).
      - name: Capture current revision
        id: prev_rev
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
        run: |
          echo "rev=$(curl -fsSL -H "Authorization: Bearer $ZINDEX_API_KEY" \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID" | jq -r '.revision')" >> "$GITHUB_OUTPUT"

      # 3. Compute typed-operation batch. Reuse stable element ids derived
      #    from service names so a renamed service updates the existing node
      #    rather than producing a delete + create pair.
      - name: Compute applyOps batch
        run: node scripts/topology-to-ops.mjs out/topology.json > out/ops.json

      # 4. Apply atomically.
      - name: Apply ops to persisted scene
        id: apply
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
        run: |
          RESP=$(curl -fsSL -X POST \
            -H "Authorization: Bearer $ZINDEX_API_KEY" \
            -H "Content-Type: application/json" \
            --data-binary @out/ops.json \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/ops")
          echo "rev=$(echo "$RESP" | jq -r '.revision')" >> "$GITHUB_OUTPUT"
          echo "applied=$(echo "$RESP" | jq -r '.applied')" >> "$GITHUB_OUTPUT"

      # 5. Render.
      - name: Render scene
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
        run: |
          curl -fsSL -X POST \
            -H "Authorization: Bearer $ZINDEX_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{"format":"svg","theme":"clean"}' \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/render" \
            | jq -r '.output.content' > out/diagram.svg

      # 6. Diff against previous revision.
      - name: Diff revisions
        id: diff
        if: steps.apply.outputs.rev != steps.prev_rev.outputs.rev
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
          PREV: ${{ steps.prev_rev.outputs.rev }}
          NEW: ${{ steps.apply.outputs.rev }}
        run: |
          curl -fsSL \
            -H "Authorization: Bearer $ZINDEX_API_KEY" \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/diff?from=$PREV&to=$NEW" \
            > out/diff.json
          echo "added=$(jq -r '.summary.added' out/diff.json)" >> "$GITHUB_OUTPUT"
          echo "removed=$(jq -r '.summary.removed' out/diff.json)" >> "$GITHUB_OUTPUT"
          echo "modified=$(jq -r '.summary.modified' out/diff.json)" >> "$GITHUB_OUTPUT"

      - uses: actions/upload-artifact@v4
        id: upload
        with:
          name: api-dependency-map-svg
          path: out/diagram.svg
          retention-days: 30

      # 7. PR comment - only on pull_request runs (the weekly schedule run
      #    has no PR to comment on; it just refreshes the artifact).
      - uses: peter-evans/find-comment@v3
        if: github.event_name == 'pull_request'
        id: find_comment
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: "github-actions[bot]"
          body-includes: "<!-- zindex-bot:api-dependency-map -->"

      - uses: peter-evans/create-or-update-comment@v4
        if: github.event_name == 'pull_request'
        with:
          comment-id: ${{ steps.find_comment.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          edit-mode: replace
          body: |
            <!-- zindex-bot:api-dependency-map -->
            ### API dependency map updated · revision ${{ steps.apply.outputs.rev }}

            | | |
            |---|---|
            | Topology changes | +${{ steps.diff.outputs.added || '0' }} / -${{ steps.diff.outputs.removed || '0' }} / ~${{ steps.diff.outputs.modified || '0' }} |
            | Revision | ${{ steps.prev_rev.outputs.rev }} → ${{ steps.apply.outputs.rev }} |

            <details><summary>Download rendered SVG</summary>

            ${{ steps.upload.outputs.artifact-url }}

            </details>

            <sub>Rendered by Zindex · scene `${{ secrets.ZINDEX_SCENE_ID }}`</sub>
