#!/usr/bin/env bash
# Valida a consistência entre PRDs, stories e o arquivo de controle.
# Uso: docs/validar.sh   (ou bash docs/validar.sh)
# Sai com 1 se encontrar qualquer divergência.

cd "$(dirname "$0")" || exit 1
falhas=0
erro() { echo "  ❌ $1"; falhas=$((falhas + 1)); }
ok()   { echo "  ✓ $1"; }

echo "═══ 1. Cobertura: itens da seção 11 dos PRDs têm arquivo de story ═══"
for f in prd/*.md; do
  sed -n '/## 11. Stories derivadas/,/^---/p' "$f" | grep '^- \[ \]' |
    grep -oE '`[A-Z]{2}-[0-9]+`' | tr -d '`' | while read -r c; do
      ls stories/"$c"-*.md >/dev/null 2>&1 || echo "  ❌ $c listado em $(basename "$f") sem arquivo"
    done
done
sem_codigo=$(for f in prd/*.md; do
  sed -n '/## 11. Stories derivadas/,/^---/p' "$f" | grep '^- \[ \]' | grep -vcE '`[A-Z]{2}-[0-9]+`'
done | paste -sd+ - | bc)
[ "$sem_codigo" = "0" ] && ok "todo item da seção 11 carrega o código da story" \
  || erro "$sem_codigo itens da seção 11 sem código — rastreabilidade vira casamento de prosa"

echo
echo "═══ 2. Story órfã: arquivo que nenhum PRD lista ═══"
for f in stories/*.md; do
  [ "$(basename "$f")" = "README.md" ] && continue
  c=$(basename "$f" | grep -oE '^[A-Z]{2}-[0-9]+')
  grep -rq "\`$c\`" prd/ 2>/dev/null || erro "$c não é listada por nenhum PRD"
done
ok "verificação de órfãs concluída"

echo
echo "═══ 3. Referências cruzadas entre tasks ═══"
quebradas=0
while read -r _ _ story task; do
  sf=$(ls stories/"$story"-*.md 2>/dev/null | head -1)
  if [ -z "$sf" ]; then erro "referência a $story (story inexistente)"; quebradas=1
  elif ! grep -q "\`$task\`" "$sf"; then erro "$story não tem a task $task"; quebradas=1; fi
done < <(grep -ohE 'depende de [A-Z]{2}-[0-9]+ [A-Z]{2,3}-[0-9]+' stories/*.md | sort -u)
[ "$quebradas" = "0" ] && ok "todas as referências cruzadas resolvem"

echo
echo "═══ 4. Controle × stories: contadores de task ═══"
[ -f CONTROLE.md ] || { erro "CONTROLE.md não existe"; echo; echo "$falhas divergência(s)"; exit 1; }
# Sem subshell: a saída vai para um arquivo e as falhas são contadas no shell
# principal, senão o exit code sai 0 mesmo com divergência reportada.
tmp=$(mktemp)
for f in stories/*.md; do
  [ "$(basename "$f")" = "README.md" ] && continue
  c=$(basename "$f" | grep -oE '^[A-Z]{2}-[0-9]+')
  tot=$(grep -c '^- \[[ x]\]' "$f"); feitas=$(grep -c '^- \[x\]' "$f")
  # cada linha do controle para esta story: "- [ ] `CC-3` ... — **0/7** tasks"
  while IFS= read -r linha; do
    [ -z "$linha" ] && continue
    ctrl=$(echo "$linha" | grep -oE '\*\*[0-9]+/[0-9]+\*\*' | tr -d '*')
    marcada=$(echo "$linha" | grep -qE '^- \[x\]' && echo sim || echo nao)
    [ "$ctrl" = "$feitas/$tot" ] || echo "$c: controle diz $ctrl, stories dizem $feitas/$tot" >>"$tmp"
    if [ "$marcada" = "sim" ] && [ "$feitas" != "$tot" ]; then
      echo "$c marcada como concluída no controle com $feitas de $tot tasks feitas" >>"$tmp"
    fi
    if [ "$marcada" = "nao" ] && [ "$feitas" = "$tot" ] && [ "$tot" != "0" ]; then
      echo "$c tem todas as $tot tasks feitas mas não está marcada no controle" >>"$tmp"
    fi
  done < <(grep -E "^- \[[ x]\] \`$c\`" CONTROLE.md)
done
if [ -s "$tmp" ]; then while IFS= read -r l || [ -n "$l" ]; do erro "$l"; done <"$tmp"; else ok "contadores batem com as stories"; fi
rm -f "$tmp"

echo
echo "═══ 5. Controle: PRD marcado sem todas as stories fechadas ═══"
awk '
  /^## \[[ x]\] PRD/ { prd=$0; marcado=($0 ~ /^## \[x\]/); abertas=0; nome=$0; next }
  /^## / && prd { if (marcado && abertas>0) printf "  ❌ %s marcado com %d story(ies) aberta(s)\n", nome, abertas; prd=""; }
  /^- \[ \] `/ && prd { abertas++ }
  END { if (marcado && abertas>0) printf "  ❌ %s marcado com %d story(ies) aberta(s)\n", nome, abertas }
' CONTROLE.md
ok "verificação de PRD concluída"

echo
echo "═══ 6. Controle: tabela de resumo × linhas de story ═══"
# A verificação 4 confere story por story, mas a tabela do topo era somada à
# mão — e foi por ali que o controle passou a superestimar 3 tasks sem que nada
# reclamasse. Aqui a soma é recalculada das próprias linhas do controle.
tmp6=$(mktemp)
python3 - "$tmp6" <<'PYEOF'
import re, sys, pathlib
saida = pathlib.Path(sys.argv[1])
t = pathlib.Path('CONTROLE.md').read_text()
linhas = t.splitlines()
# Percorre o corpo agrupando as linhas de story por seção de PRD.
secoes, atual = {}, None
for l in linhas:
    m = re.match(r'## (?:\[[ x]\] )?PRD — (.+?)(?: —|$)', l)
    if m:
        atual = m.group(1).strip()
        secoes.setdefault(atual, [])
        continue
    if l.startswith('## '):
        atual = None
        continue
    m = re.match(r'- \[[ x]\] `[A-Z]{2}-\d+`.*\*\*(\d+)/(\d+)\*\* tasks', l)
    if m and atual:
        secoes[atual].append((int(m.group(1)), int(m.group(2))))

erros = []
for nome, pares in secoes.items():
    if not pares:
        continue
    f, tt = sum(a for a, _ in pares), sum(b for _, b in pares)
    fechadas = sum(1 for a, b in pares if a == b)
    linha = next((l for l in linhas if l.startswith('| [') and nome in l), None)
    if not linha:
        erros.append(f'{nome}: sem linha na tabela de resumo')
        continue
    campos = [c.strip() for c in linha.split('|')]
    if campos[2] != f'{fechadas}/{len(pares)}':
        erros.append(f'{nome}: tabela diz {campos[2]} stories, corpo diz {fechadas}/{len(pares)}')
    if campos[3] != f'{f}/{tt}':
        erros.append(f'{nome}: tabela diz {campos[3]} tasks, corpo diz {f}/{tt}')

# Newline final obrigatória: `while read` descarta a última linha sem ela,
# e um erro sozinho passaria batido — o pior modo de falha num validador.
saida.write_text(''.join(e + '\n' for e in erros))
PYEOF
if [ -s "$tmp6" ]; then while IFS= read -r l || [ -n "$l" ]; do erro "$l"; done <"$tmp6"; else ok "a tabela de resumo bate com as linhas de story"; fi
rm -f "$tmp6"

echo
echo "═══ Resumo ═══"
echo "  stories: $(ls -1 stories/*.md | grep -vc README)"
echo "  tasks:   $(grep -h '^- \[ \]' stories/*.md | wc -l | tr -d ' ') abertas · $(grep -h '^- \[x\]' stories/*.md | wc -l | tr -d ' ') feitas"
echo
if [ "$falhas" = "0" ]; then echo "✅ sem divergências estruturais"; else echo "⚠️ $falhas divergência(s)"; exit 1; fi
