#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""validar-plano.py — valida o PLANO-ADEQUACAO.md antes de UMA linha de codigo ser editada.

Para que serve (modo ADEQUACAO do SKILL.md, passo 4): numa adequacao de material existente ao SEED Design
System, o agente escreve primeiro um plano — uma linha por peca do app, com a secao do canon que sustenta a
mudanca — e SO edita depois que este script disser que todas as linhas sao validas. E o padrao
"plan-validate-execute": erro de leitura do canon aparece no plano, que custa nada desfazer, e nao no codigo.

O que ele mede, contra o indice do canon deste pacote (references/indice-ia.json):
  1. toda linha da tabela do plano cita ao menos uma secao, na forma `<canonico> §<secao>`
     (ex.: `seed-componentes §46`, `seed-composicao §01`, `marca-seed §12.7`) ou uma tela de referencia
     na forma `telas/<slug>` (ex.: `telas/tela-shell`);
  2. o canonico citado existe;
  3. a secao citada existe naquele canonico (para `§12.7`, vale a secao-mae `12`);
  4. nenhuma secao citada esta marcada como superada — quando o indice publicar o estado por secao
     (campo `status`); enquanto nao publicar, este item sai como NAO MEDI, nunca como aprovado.

Formato do plano: uma tabela Markdown. Toda linha que comeca por "|" e nao e cabecalho nem separador e uma
linha do plano. Sugestao de colunas (o script nao exige as colunas, exige a citacao):

    | arquivo:linha | o que muda | secao do canon | versao |
    |---|---|---|---|
    | src/Rail.tsx:12 | fundo do rail vai para a classe do registry | seed-componentes §93 | v1.65 |

Uso:  python scripts/validar-plano.py [PLANO-ADEQUACAO.md]
Sai 0 = todas as linhas validas · 1 = alguma linha invalida (NAO edite) · 2 = plano ou indice ausente.
"""
import json
import pathlib
import re
import sys

for _s in (sys.stdout, sys.stderr):
    if hasattr(_s, 'reconfigure'):
        _s.reconfigure(encoding='utf-8', errors='replace')

AQUI = pathlib.Path(__file__).resolve().parent
INDICE = AQUI.parent / 'references' / 'indice-ia.json'
plano = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else 'PLANO-ADEQUACAO.md')

if not plano.is_file():
    print('ERRO: plano nao encontrado: %s — grave o PLANO-ADEQUACAO.md antes de editar (passo 4).' % plano)
    sys.exit(2)
if not INDICE.is_file():
    print('NAO MEDI: %s nao existe neste pacote — sem o indice do canon nao ha o que validar.' % INDICE)
    sys.exit(2)

indice = json.loads(INDICE.read_text(encoding='utf-8'))
secoes, estados, titulos = {}, {}, {}
for ent in indice['canonicos']:
    stem = pathlib.Path(ent['fonte']).stem
    secoes[stem] = set()
    for f in ent['fatias']:
        secoes[stem].add(f['secao'])
        estados[(stem, f['secao'])] = f.get('status')
        titulos[(stem, f['secao'])] = f.get('titulo', '')

CITACAO = re.compile(r'([a-z][a-z0-9-]{2,})\s*§\s*([0-9]{1,3}[a-z]?(?:\.[0-9]{1,3})?)')
TELA = re.compile(r'telas/([a-z0-9-]+)')

linhas = []
for n, l in enumerate(plano.read_text(encoding='utf-8').split('\n'), 1):
    s = l.strip()
    if not s.startswith('|'):
        continue
    if set(s) <= set('|-: '):          # separador da tabela
        continue
    celulas = [c.strip().lower() for c in s.strip('|').split('|')]
    if celulas and celulas[0] in ('arquivo', 'arquivo:linha', 'peca', 'peça', 'arquivo · linha'):
        continue                        # cabecalho
    linhas.append((n, s))

if not linhas:
    print('ERRO: nenhuma linha de tabela encontrada em %s. O plano e uma tabela Markdown — veja o exemplo no '
          'cabecalho deste script.' % plano)
    sys.exit(2)

problemas = []
sem_estado = 0
citadas = set()
for n, s in linhas:
    achados = CITACAO.findall(s)
    telas = TELA.findall(s)
    if not achados and not telas:
        problemas.append('linha %d: nenhuma secao citada — toda linha do plano cita `<canonico> §<secao>` '
                         'ou `telas/<slug>`' % n)
        continue
    for stem, sec in achados:
        if stem not in secoes:
            problemas.append('linha %d: canonico "%s" nao existe no indice (existem: %s)'
                             % (n, stem, ', '.join(sorted(secoes))))
            continue
        alvo = sec if sec in secoes[stem] else sec.split('.')[0]
        if alvo not in secoes[stem]:
            problemas.append('linha %d: %s §%s nao existe (o canonico tem %d secoes)'
                             % (n, stem, sec, len(secoes[stem])))
            continue
        citadas.add((stem, alvo))
        est = estados.get((stem, alvo))
        if est is None:
            sem_estado += 1
        elif est == 'superseded':
            problemas.append('linha %d: %s §%s esta SUPERADA (status: superseded) — siga o que a sucessora diz'
                             % (n, stem, alvo))
        elif est == 'rascunho':
            problemas.append('linha %d: %s §%s e RASCUNHO — pode ser citado como "ha um rascunho sobre isso", '
                             'nunca como decisao' % (n, stem, alvo))

validas = len(linhas) - len({int(p.split()[1].rstrip(':')) for p in problemas})
print('%d de %d linhas validas · %d secoes distintas citadas' % (validas, len(linhas), len(citadas)))
if sem_estado:
    print('NAO MEDI o estado de %d citacao(oes): este indice ainda nao publica o campo `status` por secao. '
          'Confira a mao, na fatia, se a regra que voce vai aplicar nao foi superada.' % sem_estado)
for p in problemas:
    print('  FALHA ' + p)
if problemas:
    print('NAO EDITE. Corrija o plano (volte ao passo 3 do modo ADEQUACAO) e rode este script de novo.')
    sys.exit(1)
print('Plano valido — pode editar (passo 5). Aplique peca a peca, na ordem da tabela.')
sys.exit(0)
