#!/usr/bin/env python3
"""Il cancello della pipeline: se qui qualcosa non torna, il libro non si costruisce.

Uso:    python build/controlla.py      (su Windows può servire: py build/controlla.py)
Serve:  Python 3.8 o più recente. Nessuna libreria da installare.
Esito:  codice 0 se è tutto a posto, 1 se ci sono errori (così anche costruisci.py si ferma).

I controlli sono cinque, tutti sui file di `contenuti/`:
  1. il nome del file è NN-slug.md, con numero unico;
  2. c'è l'intestazione tra --- con `titolo` e `stato`;
  3. lo stato è uno dei tre ammessi: bozza, rivisto, chiuso;
  4. le immagini citate esistono davvero su disco;
  5. non è rimasto nessun TODO-VERIFICA aperto.
Il quinto è il più importante: è la regola della verità. Quello che non hai verificato
non finisce in un file consegnato a qualcuno.
"""

import re
import sys
from pathlib import Path

RADICE = Path(__file__).resolve().parent.parent
CONTENUTI = RADICE / "contenuti"

STATI_AMMESSI = ("bozza", "rivisto", "chiuso")
NOME_FILE = re.compile(r"^(\d{2})-[a-z0-9\-]+\.md$")
IMMAGINE = re.compile(r"!\[[^\]]*\]\(([^)\s]+)")
PAROLA = re.compile(r"[0-9A-Za-zÀ-ÿ'’]+")


def leggi_frontmatter(testo):
    """Ritorna (dati, corpo). Intestazione minima: righe `chiave: valore` tra ---."""
    righe = testo.splitlines()
    if not righe or righe[0].strip() != "---":
        return {}, testo
    dati = {}
    for i, riga in enumerate(righe[1:], start=1):
        if riga.strip() == "---":
            return dati, "\n".join(righe[i + 1:]).lstrip("\n")
        if ":" in riga:
            chiave, valore = riga.split(":", 1)
            dati[chiave.strip()] = valore.strip().strip('"').strip("'")
    return {}, testo  # intestazione aperta e mai chiusa: la trattiamo come assente


def conta_parole(corpo):
    senza_codice = re.sub(r"```.*?```", " ", corpo, flags=re.S)
    return len(PAROLA.findall(senza_codice))


def controlla():
    """Ritorna (capitoli, errori, avvisi)."""
    errori, avvisi, capitoli = [], [], []

    if not CONTENUTI.is_dir():
        return [], ["manca la cartella `contenuti/`: è lì che vivono i capitoli"], []

    file_md = sorted(CONTENUTI.glob("*.md"))
    if not file_md:
        return [], ["`contenuti/` è vuota: serve almeno un capitolo"], []

    numeri_visti = {}
    for percorso in file_md:
        nome = percorso.name
        etichetta = f"contenuti/{nome}"

        corrispondenza = NOME_FILE.match(nome)
        if not corrispondenza:
            errori.append(f"{etichetta}: il nome deve essere NN-slug.md (es. 03-resi.md)")
            continue
        numero = corrispondenza.group(1)
        if numero in numeri_visti:
            errori.append(f"{etichetta}: numero {numero} già usato da {numeri_visti[numero]}")
            continue
        numeri_visti[numero] = nome

        testo = percorso.read_text(encoding="utf-8")
        dati, corpo = leggi_frontmatter(testo)

        if not dati.get("titolo"):
            errori.append(f"{etichetta}: manca `titolo` nell'intestazione tra ---")
            continue
        stato = dati.get("stato", "")
        if stato not in STATI_AMMESSI:
            errori.append(
                f"{etichetta}: `stato` e' \"{stato or 'vuoto'}\", "
                f"ammessi: {', '.join(STATI_AMMESSI)}"
            )
            continue

        for numero_riga, riga in enumerate(corpo.splitlines(), start=1):
            if "TODO-VERIFICA" in riga:
                errori.append(
                    f"{etichetta}:{numero_riga}: verifica aperta ({riga.strip()[:60]}): "
                    f"o la chiudi con una fonte o togli l'affermazione. Non si stampa."
                )

        for citata in IMMAGINE.findall(corpo):
            if citata.startswith(("http://", "https://")):
                continue
            if not (RADICE / citata).exists():
                errori.append(f"{etichetta}: l'immagine `{citata}` non esiste su disco")

        parole = conta_parole(corpo)
        tetto = dati.get("parole_max")
        if tetto and tetto.isdigit() and parole > int(tetto):
            avvisi.append(
                f"{etichetta}: {parole} parole contro un tetto di {tetto} "
                f"(+{parole - int(tetto)})"
            )
        if stato == "bozza":
            avvisi.append(f"{etichetta}: ancora in bozza")

        capitoli.append(
            {
                "percorso": percorso,
                "numero": numero,
                "titolo": dati["titolo"],
                "stato": stato,
                "corpo": corpo,
                "parole": parole,
            }
        )

    return capitoli, errori, avvisi


def stampa_esito(capitoli, errori, avvisi):
    print("CONTROLLO - capitoli in contenuti/\n")
    if capitoli:
        larghezza = max(len(c["titolo"]) for c in capitoli)
        for c in capitoli:
            print(f"  {c['numero']}  {c['titolo']:<{larghezza}}  {c['stato']:<7} {c['parole']:>6} parole")
        print(f"\n  Totale: {len(capitoli)} capitoli, {sum(c['parole'] for c in capitoli)} parole")

    if avvisi:
        print("\nAVVISI (non fermano il build):")
        for a in avvisi:
            print(f"  - {a}")

    if errori:
        print("\nERRORI (il build si ferma qui):")
        for e in errori:
            print(f"  ERRORE  {e}")
        quanti = f"{len(errori)} errore" if len(errori) == 1 else f"{len(errori)} errori"
        print(f"\n{quanti}. Si corregge la fonte in contenuti/, mai l'uscita in fatto/.")
    else:
        print("\nNessun errore: si puo' costruire (python build/costruisci.py).")


if __name__ == "__main__":
    capitoli, errori, avvisi = controlla()
    stampa_esito(capitoli, errori, avvisi)
    sys.exit(1 if errori else 0)
