#!/usr/bin/env bash
#
# O LEITOR DA LACUNA DO CI, POSTO A PROVA -- D441, desde 07/10/2026.
#
#     bash scripts/ensaiar-lacuna-do-ci.sh
#
# POR QUE ISTO EXISTE. O `conferir-lacuna-do-ci.sh` le o `ci.yml` e o
# `conferir-tudo.sh` como shell, e um leitor de shell e um analisador. A
# primeira versao da direcao nova contava `true;#php ...`, `echo 'php ...'` e
# `php ... || true` como "o CI roda"; nao via o `rodar` depois de `&&`, nem o
# portao do front, que nao chama roteiro de `scripts/`; e um comentario no fim
# de uma linha `rodar` isentava de declaracao o passo novo do CI (revisao de
# 07/10/2026). Cada uma dessas formas e um caso aqui, com a saida que a lacuna
# tem de dar. Mudou o leitor, este ensaio diz o que mudou.
#
# COMO. Cada caso copia a lacuna, o `ci.yml` e o `conferir-tudo.sh` para um
# diretorio temporario, muda as copias de UM jeito e roda `--so-declaracao`
# nelas. Os arquivos de verdade nao sao tocados.
#
# OS CASOS ACRESCENTAM, e quase nunca tiram: um job novo no fim do `ci.yml`, uma
# linha nova no fim do corredor, uma linha nova numa tabela -- com nomes que nao
# existem (`conferir-xyz.php`, `conferir-novo-ci.php`). Assim o ensaio nao
# envelhece quando um passo de verdade muda de lugar. A unica ancora num passo
# de verdade e a dos testes do front, o defeito que a primeira versao deixou
# passar; se ela sumir, o ensaio sai 2. E o primeiro caso, os arquivos de hoje
# sem mudanca, tem de dar 0: se nao der, a lacuna ja esta vermelha e os outros
# casos nao provam nada.
#
# Saida: 0 com todos os casos na saida esperada; 1 com algum fora dela; 2 quando
# nao consegue montar um caso.
#
# PARA VER O ENSAIO PEGAR UM DEFEITO, aponte-o para outra versao da lacuna:
#
#     LACUNA_A_ENSAIAR=/caminho/da/versao.sh bash scripts/ensaiar-lacuna-do-ci.sh

set -u
cd "$(dirname "$0")/.."
RAIZ=$(pwd)
LACUNA="${LACUNA_A_ENSAIAR:-$RAIZ/scripts/conferir-lacuna-do-ci.sh}"

T=$(mktemp -d) || { echo "Nao consegui criar o diretorio temporario." >&2; exit 2; }
trap 'rm -rf "$T"' EXIT

C="$T/caso"
CI_C="$C/.github/workflows/ci.yml"
COR_C="$C/scripts/conferir-tudo.sh"
LAC_C="$C/scripts/conferir-lacuna-do-ci.sh"

nao_montei() { echo "NAO CONSEGUI MONTAR O CASO: $1" >&2; exit 2; }

# A copia limpa dos tres arquivos.
monta() {
    rm -rf "$C"
    mkdir -p "$C/scripts" "$C/.github/workflows" || nao_montei "os diretorios"
    { cp "$LACUNA" "$LAC_C" && cp "$RAIZ/scripts/conferir-tudo.sh" "$COR_C" &&
        cp "$RAIZ/.github/workflows/ci.yml" "$CI_C"; } || nao_montei "a copia"
}

# Um job novo no fim do `ci.yml`, com um passo. As chaves a mais vem DEPOIS do
# `run:` -- o `if:` e o `continue-on-error` podem vir depois, e o leitor tem de
# esperar o passo acabar para decidir.
passo_no_ci() {
    local run=$1 k
    shift
    {
        printf '\n  ensaio-da-lacuna:\n    runs-on: ubuntu-latest\n    steps:\n'
        printf '      - name: ensaio\n        run: %s\n' "$run"
        for k in "$@"; do printf '        %s\n' "$k"; done
    } >> "$CI_C"
}

# Um job novo com um passo `run: |`; cada argumento e uma linha do bloco, e o
# argumento vazio e uma linha em branco.
bloco_no_ci() {
    local l
    {
        printf '\n  ensaio-da-lacuna:\n    runs-on: ubuntu-latest\n    steps:\n'
        printf '      - name: ensaio\n        run: |\n'
        for l in "$@"; do
            if [ -n "$l" ]; then printf '          %s\n' "$l"; else printf '\n'; fi
        done
    } >> "$CI_C"
}

no_corredor() { printf '%s\n' "$@" >> "$COR_C"; }

# Uma linha nova numa das duas tabelas da lacuna, logo depois do `declare`.
na_tabela() {
    awk -v t="declare -A $1=(" -v l="  $2" '{ print } $0 == t { print l; achou = 1 } END { exit !achou }' \
        "$LAC_C" > "$LAC_C.novo" && mv "$LAC_C.novo" "$LAC_C" || nao_montei "a tabela $1 da lacuna"
}

# Tira do `ci.yml` as linhas com o texto -- e exige que tire alguma.
tira_do_ci() {
    grep -vF -- "$1" "$CI_C" > "$CI_C.novo"
    [ "$(wc -l < "$CI_C.novo")" -lt "$(wc -l < "$CI_C")" ] || nao_montei "nao ha '$1' no ci.yml"
    mv "$CI_C.novo" "$CI_C"
}

# O CASO CONFERE A SAIDA E O MOTIVO. O codigo sozinho nao basta: arrancado o
# laco que exige a FORA_DO_CI, a lacuna ainda saia 1 -- morria no `set -u` ao
# listar uma chave que a tabela nao tem, e o ensaio contava isso como acerto. Os
# textos depois da descricao tem de aparecer TODOS na saida.
n=0
falhas=0
caso() {
    local esperado=$1 descricao=$2 obtido texto faltou=""
    shift 2
    bash "$LAC_C" --so-declaracao > "$T/saida.txt" 2>&1
    obtido=$?
    for texto in "$@"; do
        grep -qF -- "$texto" "$T/saida.txt" || faltou=$texto
    done
    n=$((n+1))
    if [ "$obtido" = "$esperado" ] && [ -z "$faltou" ]; then
        printf '  ok      %s  %s\n' "$obtido" "$descricao"
        return
    fi
    if [ "$obtido" != "$esperado" ]; then
        printf '  FALHOU  %s, e devia dar %s:  %s\n' "$obtido" "$esperado" "$descricao"
    else
        printf '  FALHOU  %s, mas sem "%s" na saida:  %s\n' "$obtido" "$faltou" "$descricao"
    fi
    sed 's/^/          | /' "$T/saida.txt" | head -15
    falhas=$((falhas+1))
}

XYZ="php scripts/conferir-xyz.php"
NOVO="php scripts/conferir-novo-ci.php"

# O que a lacuna diz em cada desfecho.
EM_DIA="Declaracao em dia nas duas direcoes"
SO_CORREDOR="O CORREDOR RODA, E O CI NAO -- e ninguem declarou por que"
SO_CI="PASSO NOVO NO CI, E NINGUEM DECLAROU"
VELHA="DECLARACAO QUE NAO SE APLICA MAIS"
QUEBROU="o leitor quebrou"
X_SEM_CI="    scripts/conferir-xyz.php"
NOVO_SEM_CORREDOR="    scripts/conferir-novo-ci.php"

echo ''
echo 'O LEITOR DA LACUNA DO CI (D441)'
echo '=============================================================================='

monta
caso 0 "os arquivos de hoje, sem mudanca" "$EM_DIA"

# --- a direcao nova: o que o corredor roda, o CI tem de rodar ----------------
monta; no_corredor "rodar 'xyz' $XYZ"
caso 1 "o corredor ganha um rodar, e o CI nao o roda" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "$XYZ"
caso 0 "  ... e o CI o roda num run: (controle)" "$EM_DIA"

monta; no_corredor "rodar 'xyz' $XYZ"; bloco_no_ci "echo antes" "" "# um comentario" "$XYZ"
caso 0 "  ... e o CI o roda num run: |, depois de linha em branco e comentario (controle)" "$EM_DIA"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "true" "# run: $XYZ"
caso 1 "  ... e o CI so o cita num comentario do YAML" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "true;#$XYZ"
caso 1 "  ... e o CI o poe depois de ';#', que o shell le como comentario" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "echo 'rode a mao: $XYZ'"
caso 1 "  ... e o CI so o imprime, entre aspas" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "echo $XYZ"
caso 1 "  ... e o CI so o imprime, sem aspas" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "grep -q 'scripts/conferir-xyz.php' .github/workflows/ci.yml"
caso 1 "  ... e o CI so o procura, entre aspas, num comando que nao e echo" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "$XYZ || true"
caso 1 "  ... e o CI o roda com '|| true', que engole a falha" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; bloco_no_ci "$XYZ && echo passou" "echo depois"
caso 1 "  ... e o CI o roda no meio do bloco, antes de &&, onde o bash -e nao para" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "$XYZ | tee /tmp/saida.txt"
caso 1 "  ... e o CI o roda antes de um pipe, que fica com a saida do tee" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "$XYZ" "continue-on-error: true"
caso 1 "  ... e o CI o roda num passo com continue-on-error, escrito depois do run:" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "$XYZ" "if: false"
caso 1 "  ... e o CI o roda num passo com if: false" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; bloco_no_ci "cat > /tmp/x.sh <<'FIM'" "$XYZ" "FIM"
caso 1 "  ... e o CI so o escreve no corpo de um heredoc" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; bloco_no_ci "if $XYZ; then" "  echo ok" "fi"
caso 1 "  ... e o CI so o usa como condicao de um if" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; bloco_no_ci "if [ -d scripts ]; then" "  $XYZ" "fi"
caso 0 "  ... e o CI o roda no corpo de um if, depois do then (controle)" "$EM_DIA"

monta; no_corredor "rodar 'xyz' $XYZ"; bloco_no_ci "! $XYZ"
caso 1 "  ... e o CI o roda negado com !" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "rodar 'xyz' $XYZ"; bloco_no_ci "set +e" "$XYZ"
caso 1 "  ... e o CI o roda depois de set +e" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "[ -d frontend ] && rodar 'xyz' $XYZ"
caso 1 "o corredor o roda depois de &&, e o CI nao" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "if [ -d frontend ]; then rodar 'xyz' $XYZ; fi"
caso 1 "o corredor o roda dentro de um if, e o CI nao" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "$XYZ > /tmp/portao.txt 2>&1 || falhou=\$((falhou + 1))"
caso 1 "o corredor o roda sem o rodar, e o CI nao" "$SO_CORREDOR" "$X_SEM_CI"

monta; no_corredor "# rodar 'xyz' $XYZ"
caso 0 "o corredor so o cita numa linha de comentario (nao roda)" "$EM_DIA"

monta; no_corredor "rodar 'rotas orfas' php scripts/conferir-rotas-orfas.php  # e o irmao, scripts/conferir-xyz.php"
caso 0 "o corredor so o cita no comentario do fim de outro rodar (nao roda)" "$EM_DIA"

# --- o portao sem roteiro de scripts/ vale pelo comando ----------------------
monta; no_corredor "rodar 'lint do front' npm run lint --prefix frontend"
caso 1 "o corredor ganha um rodar sem roteiro (npm), e o CI nao roda o comando" \
    "$SO_CORREDOR" "    npm run lint --prefix frontend"

monta; no_corredor "rodar 'lint do front' npm run lint --prefix frontend"; passo_no_ci "npm run lint --prefix frontend"
caso 0 "  ... e o CI roda o mesmo comando (controle)" "$EM_DIA"

monta; no_corredor "rodar 'lint do front' npm run lint --prefix frontend"
na_tabela FORA_DO_CI '["npm run lint --prefix frontend"]="ENSAIO|declarado pelo comando"'
caso 0 "  ... e a FORA_DO_CI o declara pelo comando (controle)" "$EM_DIA" "ENSAIO -- declarado pelo comando"

monta; no_corredor "rodar_conhecido 'lint do front' 'o motivo' \\" "    npm run lint --prefix frontend"
passo_no_ci "npm run lint --prefix frontend"
caso 0 "  ... num rodar_conhecido de duas linhas: os dois rotulos nao sao o comando (controle)" "$EM_DIA"

monta; tira_do_ci "npm test --prefix frontend"
caso 1 "o CI perde o passo dos testes do front" "$SO_CORREDOR" "    npm test --prefix frontend -- --maxWorkers=4"

# --- a primeira direcao: o que o CI roda, o corredor chama ou declara --------
monta; passo_no_ci "$NOVO"
caso 1 "o CI ganha um passo, e o corredor nao o chama" "$SO_CI" "$NOVO_SEM_CORREDOR"

monta; passo_no_ci "$NOVO"; no_corredor "rodar 'novo' $NOVO"
caso 0 "  ... e o corredor o chama (controle)" "$EM_DIA"

monta; passo_no_ci "$NOVO"
no_corredor "rodar 'rotas orfas' php scripts/conferir-rotas-orfas.php  # o irmao scripts/conferir-novo-ci.php fica so no CI"
caso 1 "  ... e o corredor so o cita no comentario do fim de outro rodar" "$SO_CI" "$NOVO_SEM_CORREDOR"

monta; passo_no_ci "$NOVO"; no_corredor ": <<'FIM'" "rodar 'velho' $NOVO" "FIM"
caso 1 "  ... e o corredor so o tem dentro de um : <<'FIM'" "$SO_CI" "$NOVO_SEM_CORREDOR"

# --- a declaracao que envelheceu ---------------------------------------------
monta; passo_no_ci "$NOVO"; na_tabela DECLARADO '["scripts/conferir-novo-ci.php"]="RODA|ensaio"'
caso 0 "a DECLARADO ganha o passo novo do CI (controle)" "$EM_DIA"

monta; passo_no_ci "true" "# run: $NOVO"; na_tabela DECLARADO '["scripts/conferir-novo-ci.php"]="RODA|ensaio"'
caso 1 "  ... mas o CI so o cita num comentario" "$VELHA" "scripts/conferir-novo-ci.php  (DECLARADO)"

monta; passo_no_ci "$NOVO"; no_corredor "rodar 'novo' $NOVO"
na_tabela DECLARADO '["scripts/conferir-novo-ci.php"]="RODA|ensaio"'
caso 1 "  ... mas o corredor tambem o chama" "$VELHA" "scripts/conferir-novo-ci.php  (DECLARADO)"

monta; no_corredor "rodar 'xyz' $XYZ"; na_tabela FORA_DO_CI '["scripts/conferir-xyz.php"]="ENSAIO|declarado"'
caso 0 "a FORA_DO_CI ganha o rodar novo do corredor (controle)" "$EM_DIA" "ENSAIO -- declarado"

monta; no_corredor "rodar 'rotas orfas' php scripts/conferir-rotas-orfas.php  # e o irmao, scripts/conferir-xyz.php"
na_tabela FORA_DO_CI '["scripts/conferir-xyz.php"]="ENSAIO|declarado"'
caso 1 "  ... mas o corredor so o cita no comentario do fim de outro rodar" "$VELHA" "scripts/conferir-xyz.php  (FORA_DO_CI)"

monta; no_corredor ": <<'FIM'" "rodar 'xyz' $XYZ" "FIM"
na_tabela FORA_DO_CI '["scripts/conferir-xyz.php"]="ENSAIO|declarado"'
caso 1 "  ... mas o corredor so o tem dentro de um : <<'FIM'" "$VELHA" "scripts/conferir-xyz.php  (FORA_DO_CI)"

monta; no_corredor "rodar 'xyz' $XYZ"; passo_no_ci "$XYZ"
na_tabela FORA_DO_CI '["scripts/conferir-xyz.php"]="ENSAIO|declarado"'
caso 1 "  ... mas o CI tambem o roda" "$VELHA" "scripts/conferir-xyz.php  (FORA_DO_CI)"

# --- o leitor quebrado sai 2, e nunca 0 --------------------------------------
monta; { sed 's/run:/xrun:/' "$CI_C" > "$CI_C.novo" && mv "$CI_C.novo" "$CI_C"; } || nao_montei "o sed"
caso 2 "o ci.yml sem nenhum run:" "$QUEBROU"

monta; no_corredor ": <<'FIM'" "rodar 'xyz' $XYZ"
caso 2 "o corredor acaba dentro de um heredoc" "$QUEBROU"

monta; bloco_no_ci "cat > /tmp/x <<'FIM'" "echo sem fim"
caso 2 "um run: | acaba dentro de um heredoc" "$QUEBROU"

echo ''
echo "  $n casos, $falhas fora da saida esperada."
echo ''
[ "$falhas" -eq 0 ] || exit 1
exit 0
