# Template canônico do workflow de docs — copie para .forgejo/workflows/docs.yml
# no repo do app e troque <app> pelo slug do app (o mesmo do mkdocs.yml).
# Fonte da verdade: https://fonte.xadm.biz/xadm/documentacao (templates/docs.yml)
# Padrão: https://docs.xadm.biz/padroes/publicar-docs/
#
# O site central docs.xadm.biz sincroniza do Garage em ~90s — nada mais a fazer.
#
# Secrets (DOCS_S3_ENDPOINT / DOCS_S3_ACCESS_KEY / DOCS_S3_SECRET_KEY): já
# existem na organização xadm do Forgejo — repo na org herda, nada a criar.
# Aviso de falha do CI: webhook nativo da org (nada no YAML) — forgejo.md.
#
# Versões da toolchain: NÃO mude os pins aqui — a fonte da verdade é
# https://docs.xadm.biz/padroes/publicar-docs/ (seção "Toolchain").
name: docs

on:
  push:
    branches: [master]      # master → publica a versão "dev" (preview, fora do dropdown)
    tags: ["v*"]            # vX.Y.Z → snapshot da release (doc versionada, decisão 0004)

jobs:
  publicar-docs:
    runs-on: docker
    # Imagem de CI da org POR STACK+VERSÃO (fábrica — decisão 0003): toolchain
    # pré-instalada, sem apt-install/setup-java/git-clone por run. Aponte a da SUA
    # stack (a mesma versão do ci.yml / docs/app.json toolchain.*):
    #   Java/Micronaut (gera Javadoc) → fonte.xadm.biz/xadm/ci-java:<v>
    #   Flutter (gera dartdoc)        → fonte.xadm.biz/xadm/ci-flutter:<v>
    #   docs puro (MkDocs)            → fonte.xadm.biz/xadm/ci-base
    # node vem em todas (actions JS — checkout). Bootstrap: ci-images/README.md.
    container: fonte.xadm.biz/xadm/ci-base
    steps:
      - name: Cache da toolchain de docs (pip + npm)
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/pip
            ~/.npm
          # Acelera o pip do mkdocs e o npm do mermaid (download vai pro cache).
          # Bump a key ao mudar as versões abaixo — invalida o cache.
          key: docs-toolchain-mkdocs1.6.1-material9.7.6-mermaid11
          restore-keys: docs-toolchain-
      - name: Instalar mkdocs (pins — toolchain do build de docs)
        run: |
          # Só o mkdocs: o resto da toolchain já vem na imagem base. Os pins têm
          # fonte única em docs.xadm.biz/padroes/publicar-docs/ (Toolchain) — NÃO
          # mude aqui. swagger-ui-tag só é usado se o app embute OpenAPI
          # (docs/public/api/); print-site é sempre.
          pip3 install --quiet --break-system-packages \
            mkdocs==1.6.1 mkdocs-material==9.7.6 pymdown-extensions==10.21.3 \
            mkdocs-print-site-plugin==2.8 mkdocs-swagger-ui-tag==0.8.0

      - uses: actions/checkout@v4

      - name: Definir alvo da versão (tag vX.Y.Z → snapshot; master → dev)
        run: |
          # Doc versionada (decisão 0004): tag vX.Y.Z publica o snapshot da release
          # em <app>/X.Y.Z/; master publica o preview em <app>/dev/ (fora do dropdown).
          # Tag fora de X.Y.Z (pré-release etc.) NÃO versiona (ALVO=skip).
          ref="${GITHUB_REF#refs/}"
          case "$ref" in
            tags/v[0-9]*.[0-9]*.[0-9]*)
              v="${ref#tags/v}"
              case "$v" in
                *[!0-9.]*) echo "tag '$v' fora de X.Y.Z — não versiona"; alvo=skip ;;
                *)         alvo="$v"; echo "EH_RELEASE=1" >> "$GITHUB_ENV" ;;
              esac ;;
            heads/master) alvo=dev ;;
            *) echo "ref '$ref' fora do contrato — não publica"; alvo=skip ;;
          esac
          echo "ALVO=$alvo" >> "$GITHUB_ENV"
          echo "alvo da publicação: $alvo"
          # Slug = fonte ÚNICA em docs/app.json (não hardcodar <app> aqui nem no mkdocs.yml).
          slug=$(python3 -c "import json;print(json.load(open('docs/app.json'))['slug'])") \
            || { echo "ERRO: docs/app.json sem 'slug' — é a fonte única do identificador (publicar-docs.md)"; exit 1; }
          echo "SLUG=$slug" >> "$GITHUB_ENV"

      - name: Validar frontmatter
        run: |
          # Script mantido no repo central e publicado no site (o repo é
          # privado; o site é público — fonte única, sem token nem cópia).
          curl -fsSL -o /tmp/valida-frontmatter.py \
            https://docs.xadm.biz/toolchain/valida-frontmatter.py
          python3 /tmp/valida-frontmatter.py docs/

      - name: Baixar hooks de doc (governança + Visão técnica)
        run: |
          # Hooks do mkdocs.yml: precisam existir ANTES do build. Fonte única no
          # site central (igual ao validador). Se o build reclamar de um
          # scripts/*.py faltando, é este passo que falhou.
          mkdir -p scripts
          curl -fsSL -o scripts/frontmatter-cabecalho.py \
            https://docs.xadm.biz/toolchain/frontmatter-cabecalho.py
          curl -fsSL -o scripts/visao-tecnica.py \
            https://docs.xadm.biz/toolchain/visao-tecnica.py

      - name: Lint dos diagramas Mermaid
        run: |
          # Mermaid com erro de sintaxe renderiza CRU no site e passa pelo
          # --strict (constituição §5). mermaid.parse FALHA o build aqui. Node
          # puro (sem Chromium); mermaid + jsdom (DOM mínimo).
          npm install --no-save --silent mermaid@11 jsdom
          curl -fsSL -o lint-mermaid.mjs https://docs.xadm.biz/toolchain/lint-mermaid.mjs
          node lint-mermaid.mjs docs/

      # ── CONSUMIDOR: importar fato de outro repo (constituição §1.9) ─────
      # O --8<-- não atravessa repo, mas o BUILD atravessa. Onde o leitor precisa
      # NA MÃO de um fato cujo dono é outro app (contrato de ingestão de um hub),
      # importe fresco em vez de copiar — cópia nasce certa e envelhece calada.
      # ANTES do build (o --8<-- precisa do arquivo em disco). O -f FALHA o build
      # se o dono despublicar/renomear: o alerta é o vermelho, não um leitor daqui
      # a três meses. docs/_importado/ é EFÊMERO: .gitignore + exclude_docs (NÃO
      # not_in_nav, que não tira do build: o fato importado viraria página no site
      # deste repo, servindo o contrato do dono como se fosse seu).
      # Versionar o importado o torna de volta uma cópia. Detalhe: publicar-docs.md.
      # - name: Importar fatos de outros repos (frescos)
      #   run: |
      #     mkdir -p docs/_importado
      #     curl -fsSL https://docs.xadm.biz/aplicacoes/<app-dono>/raw/<fato>.md \
      #       -o docs/_importado/<fato>.md


      # Referência de API é gerada PELO CI DE DOCS (constituição §5), ANTES do
      # build e PARA DENTRO de docs/ — assim o link é Markdown normal e o --strict
      # valida (link quebrado = falha; sem HTML cru driblando o strict; sem
      # depender do app deployado). A toolchain da stack já está na imagem base
      # (o JDK da imagem ci-java:<v> p/ Javadoc); Flutter, clone o SDK do .fvmrc
      # (ver ci.yml). App que gera Javadoc aqui ganha com o "Cache do Gradle" —
      # incluído abaixo no caminho Java (paridade com o ci.yml).
      #
      # DESCOMENTE JUNTO COM O `container:` (lá em cima, ~linha 31) → ci-java:<v>
      # ou ci-flutter:<v>. O default é ci-base, que NÃO tem JDK: descomentar só o
      # bloco derruba este workflow no push, e nenhum gate avisa antes — o ci.yml já
      # aponta ci-java e passa verde, e o pré-flight da /xadm-release builda na
      # máquina do dev, onde há JDK. Os dois viajam juntos; é o mesmo par do
      # Dockerfile/.dockerignore, só que aqui os dois vivem neste arquivo.
      # Split de visibilidade (constituição §5): Javadoc/dartdoc (árvore de
      # classes) = INTERNO em docs/dev/api/; OpenAPI (contrato) = PÚBLICO em
      # docs/public/api/openapi.yaml (embutido por docs/public/api/index.md via
      # swagger-ui-tag). Gere SÓ o que o app expõe. Os gerados são .gitignore.
      # Cache antes de gerar (paridade com ci.yml): Java → Gradle; Flutter → pub.
      # - name: Cache do Gradle    # Java/Micronaut
      #   uses: actions/cache@v4
      #   with:
      #     path: |
      #       ~/.gradle/caches
      #       ~/.gradle/wrapper
      #     key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
      #     restore-keys: gradle-
      # - name: Cache do pub       # Flutter (dartdoc)
      #   uses: actions/cache@v4
      #   with:
      #     path: ~/.pub-cache
      #     key: pub-${{ hashFiles('**/pubspec.lock') }}
      #     restore-keys: pub-
      # - name: Gerar referência de API
      #   run: |
      #     ## Java/Micronaut:
      #     # Guarda: sem JDK o gradlew abaixo diz "JAVA_HOME is not set" e manda
      #     # SETAR JAVA_HOME — não faça isso. Aqui a toolchain vem da imagem
      #     # (decisão 0003): a cura é o `container:`, não env var nem setup-java.
      #     command -v java >/dev/null 2>&1 || {
      #       echo "::error::sem JDK nesta imagem: o bloco de API está descomentado, mas o container: (topo do arquivo) ainda é ci-base. Troque para fonte.xadm.biz/xadm/ci-java:<v> — a mesma versão do ci.yml e do docs/app.json (toolchain.java)."
      #       exit 1; }
      #     ./gradlew javadoc -x test --no-daemon
      #     mkdir -p docs/dev/api && cp -r build/docs/javadoc/. docs/dev/api/
      #     ./gradlew classes --no-daemon          # micronaut-openapi emite o spec ao compilar
      #     mkdir -p docs/public/api && cp build/classes/java/main/META-INF/swagger/*.yml docs/public/api/openapi.yaml
      #     ## Flutter (ref interna):
      #     # dart doc --output docs/dev/api .

      # ── Contrato REST — a prosa confere com o OpenAPI? (constituição §6) ─
      # Descomente junto com o bloco Java acima (precisa do spec gerado). O mesmo
      # check roda no ci.yml, que pega o rename no PR de código; aqui ele pega o
      # caminho inverso — prosa editada sem tocar código (inclusive edição pela web
      # do Forgejo, que não passa pelo ci.yml). Tolerante: sem spec, sai 0.
      # - name: Contrato REST (prosa × OpenAPI)
      #   run: |
      #     curl -fsSL https://docs.xadm.biz/padroes/checa-rotas.py -o checa-rotas.py
      #     python3 checa-rotas.py

      - name: Buildar site de docs (site_url terminando na versão)
        run: |
          [ "$ALVO" = skip ] && { echo "nada a publicar"; exit 0; }
          # Doc versionada: cada versão é buildada com site_url=.../<app>/<ALVO>/ —
          # o seletor do Material busca ../versions.json relativo a esse base e
          # detecta a versão pelo último segmento (spec 011 / decisão 0004).
          sed -i "s#^site_url:.*#site_url: https://docs.xadm.biz/aplicacoes/${SLUG}/${ALVO}/#" mkdocs.yml
          # Um site só. docs/ é privado por padrão; o público vive em
          # docs/public/ (constituição §5). Sem login ainda — tudo é servido;
          # o gating de tudo-menos-/public/ é spec futura. O validador já
          # garante que páginas de public/ não linkam para fora de public/.
          mkdocs build --strict
          # Admonition cru: o `!!! note` mal indentado NÃO é erro para o MkDocs —
          # ele vê um parágrafo começando com "!!!", renderiza literal e sai verde;
          # o leitor é que vê o marcador na página. Dentro de item de lista o bloco
          # precisa de 4 espaços (parágrafo de continuação aceita 2 — é a armadilha).
          # Checa o HTML: `!!!` em <pre> é código exibido, em <p> é bug.
          curl -fsSL https://docs.xadm.biz/padroes/checa-admonition.py -o checa-admonition.py
          python3 checa-admonition.py
          # privado/ segue excluído: enquanto não há login, é a guarda do que
          # é sensível de verdade (anexo de cliente). exclude_docs no mkdocs.yml.
          privado="$(find site -type d -name privado 2>/dev/null)"
          if [ -n "$privado" ]; then
            echo "ERRO: pasta privado/ no site/ (exclude_docs faltando no mkdocs.yml):"
            echo "$privado"
            exit 1
          fi
          # API ref: gerada ANTES do build, para dentro de docs/ (passo acima) —
          # o link Markdown a valida no --strict, sem cp pós-build nem guard de grep.

      - name: Publicar no Garage
        env:
          RCLONE_CONFIG_GARAGE_TYPE: s3
          RCLONE_CONFIG_GARAGE_PROVIDER: Other
          RCLONE_CONFIG_GARAGE_ENDPOINT: ${{ secrets.DOCS_S3_ENDPOINT }}
          RCLONE_CONFIG_GARAGE_ACCESS_KEY_ID: ${{ secrets.DOCS_S3_ACCESS_KEY }}
          RCLONE_CONFIG_GARAGE_SECRET_ACCESS_KEY: ${{ secrets.DOCS_S3_SECRET_KEY }}
          RCLONE_CONFIG_GARAGE_REGION: garage
          RCLONE_CONFIG_GARAGE_FORCE_PATH_STYLE: "true"
        run: |
          [ "$ALVO" = skip ] && { echo "nada a publicar"; exit 0; }
          # Falha cedo e claro se algum secret DOCS_S3_* faltar — senão o rclone
          # tenta um endpoint vazio e morre com "no such host" após ~3min de retry.
          : "${RCLONE_CONFIG_GARAGE_ENDPOINT:?DOCS_S3_ENDPOINT vazio/ausente — cadastre os 3 secrets DOCS_S3_* na organização xadm do Forgejo (publicar-docs.md)}"
          : "${RCLONE_CONFIG_GARAGE_ACCESS_KEY_ID:?DOCS_S3_ACCESS_KEY vazio/ausente — idem DOCS_S3_* na org}"
          : "${RCLONE_CONFIG_GARAGE_SECRET_ACCESS_KEY:?DOCS_S3_SECRET_KEY vazio/ausente — idem DOCS_S3_* na org}"
          # Publica na SUBPASTA da versão — NUNCA `rclone sync` a raiz <app>/
          # (apagaria todas as versões + app.json + versions.json — decisão 0004).
          rclone sync site "garage:docs-sites/${SLUG}/${ALVO}" --quiet
          # app.json à RAIZ do bucket — metadata version-independent que o central lê de
          # docs-sites/<slug>/app.json (sync-apps.sh). copyto de UM arquivo (aditivo, NUNCA
          # sync da raiz). Em TODO push (não só release): sem isto, app que só publicou
          # em dev/ aparece em "Outros (sem app.json)" no portal.
          rclone copyto site/app.json "garage:docs-sites/${SLUG}/app.json" --quiet
          # ── DONO: fatos que OUTROS repos consomem (constituição §1.9) ────
          # Publicar o fato é obrigação do DONO — consumidor que copia é sintoma de
          # dono que não publicou. Vale para o que a geração NÃO cobre (a prosa do
          # contrato, listas que vivem no código); o contrato REST em si é o OpenAPI
          # gerado — publique-o no bloco de API acima. RAIZ, como o app.json e pela
          # mesma razão: é contrato entre apps, não conteúdo versionado — o consumidor
          # importa o ATUAL. `copy` é aditivo; NUNCA sync da raiz. Descomente se
          # este app é dono de fato que outro repo mostra. Detalhe: publicar-docs.md.
          # mkdir -p site/raw && cp docs/public/contratos/*.md site/raw/
          # rclone copy site/raw "garage:docs-sites/${SLUG}/raw" --quiet

      - name: Atualizar versions.json + redirect da raiz (só em release)
        if: ${{ env.EH_RELEASE == '1' }}
        env:
          RCLONE_CONFIG_GARAGE_TYPE: s3
          RCLONE_CONFIG_GARAGE_PROVIDER: Other
          RCLONE_CONFIG_GARAGE_ENDPOINT: ${{ secrets.DOCS_S3_ENDPOINT }}
          RCLONE_CONFIG_GARAGE_ACCESS_KEY_ID: ${{ secrets.DOCS_S3_ACCESS_KEY }}
          RCLONE_CONFIG_GARAGE_SECRET_ACCESS_KEY: ${{ secrets.DOCS_S3_SECRET_KEY }}
          RCLONE_CONFIG_GARAGE_REGION: garage
          RCLONE_CONFIG_GARAGE_FORCE_PATH_STYLE: "true"
        run: |
          [ "${EH_RELEASE:-}" = 1 ] || { echo "não é release — sem regenerar"; exit 0; }
          # Lê as versões já no bucket e (re)escreve versions.json + index.html na
          # RAIZ <app>/ via rclone rcat (aditivo; NUNCA sync da raiz). Script no
          # central, baixado fresco (fonte única, igual aos validadores).
          curl -fsSL -o /tmp/publica-versao.py https://docs.xadm.biz/toolchain/publica-versao.py
          python3 /tmp/publica-versao.py "${SLUG}"
