#!/usr/bin/env python3
"""Lint dos documentos do corpus sucesu_analise.

Verifica front-matter, seções obrigatórias por tipo, seções vazias, marcadores de
preenchimento, citações caminho:linha, trechos de código e links locais.

Uso:
  lint_docs.py [caminhos...] [--json]
  (sem caminhos: varre sucesu_analise, ignorando _contrato, 00_inventario, tools)
"""

from __future__ import annotations

import argparse
import json
import os
import re
import sys

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SKIP_TOP = {"_contrato", "00_inventario", "tools", ".git", "v7_implementation"}

TIPOS = {"arquitetura", "servico", "ui", "modulo", "dados", "matriz", "reimplementacao"}
STATUS = {"pendente", "em_andamento", "revisao", "concluido"}
CRITICIDADE = {"alta", "media", "baixa"}

REQUIRED_KEYS = ["id", "tipo", "onda", "status", "unidades", "fontes", "depende_de",
                 "consumidores", "criticidade"]

HEADINGS = {
    "modulo": ["Ficha de identidade", "Efeito", "Abordagem", "Particularidades",
               "Integrabilidade", "Rotas e contratos", "Dados e modelo",
               "Fluxos e estados", "UI CMS", "UI Portal", "Configuração",
               "Observabilidade", "Seeds", "Lacunas", "Checklist de paridade",
               "Evidências"],
    "servico": ["Ficha de identidade", "Efeito", "Abordagem", "Particularidades",
                "Integrabilidade", "Rotas e endpoints", "Dados e migrations",
                "Configuração e permissões", "Observabilidade e erros", "Consumidores",
                "Lacunas", "Checklist de paridade", "Evidências"],
    "ui": ["Ficha de identidade", "Efeito", "Abordagem", "Particularidades",
           "Integrabilidade", "Catálogo de arquivos", "Acessibilidade", "Assets e build",
           "Lacunas", "Checklist de paridade", "Evidências"],
    "arquitetura": ["Ficha de identidade", "Efeito", "Abordagem", "Particularidades",
                    "Integrabilidade", "Inventário", "Riscos", "Lacunas",
                    "Checklist de paridade", "Evidências"],
    "dados": ["Efeito", "Abordagem", "Particularidades", "Integrabilidade",
              "Lacunas", "Evidências"],
    "matriz": ["Efeito", "Abordagem", "Particularidades", "Integrabilidade",
               "Lacunas", "Evidências"],
    "reimplementacao": ["Efeito", "Abordagem", "Particularidades", "Integrabilidade",
                        "Lacunas", "Evidências"],
}

CITATION = re.compile(r"([A-Za-z0-9_./-]+\.(?:php|blade\.php|js|css|json|md|sh)):(\d+)(?:-(\d+))?")
CODE_FENCE = re.compile(r"^```(php|blade|http|bash|js|sql|yaml)\b", re.MULTILINE)
PLACEHOLDER = "PREENCHER"
LINK = re.compile(r"\]\(([^)#]+\.md)(?:#[^)]*)?\)")


def parse_front_matter(text: str) -> tuple[dict, str]:
    if not text.startswith("---\n"):
        return {}, text
    end = text.find("\n---", 4)
    if end == -1:
        return {}, text
    meta: dict = {}
    key = None
    for line in text[4:end].splitlines():
        if not line.strip() or line.lstrip().startswith("#"):
            continue
        if line.startswith(("  - ", "- ")):
            if key:
                meta.setdefault(key, [])
                if isinstance(meta[key], list):
                    meta[key].append(line.split("- ", 1)[1].strip())
            continue
        if ":" in line:
            key, value = line.split(":", 1)
            key = key.strip()
            value = value.strip()
            if value.startswith("[") and value.endswith("]"):
                inner = value[1:-1].strip()
                meta[key] = [v.strip() for v in inner.split(",") if v.strip()] if inner else []
            elif value == "":
                meta[key] = []
            else:
                meta[key] = value.strip('"\'')
    return meta, text[end + 4:]


def headings(body: str) -> list[tuple[str, int]]:
    out = []
    for line in body.splitlines():
        if line.startswith("## "):
            out.append((line[3:].strip(), 2))
        elif line.startswith("### "):
            out.append((line[4:].strip(), 3))
    return out


def section_text(body: str) -> dict[str, str]:
    result: dict[str, str] = {}
    current = None
    buf: list[str] = []
    for line in body.splitlines():
        if line.startswith("## "):
            if current is not None:
                result[current] = "\n".join(buf).strip()
            current = line[3:].strip()
            buf = []
        elif current is not None:
            buf.append(line)
    if current is not None:
        result[current] = "\n".join(buf).strip()
    return result


STRICT_CITATIONS = False
warnings: list[str] = []


def lint_file(path: str) -> list[str]:
    errs: list[str] = []
    rel = os.path.relpath(path, ROOT)
    text = open(path, encoding="utf-8").read()
    meta, body = parse_front_matter(text)
    if not meta:
        return errs

    for key in REQUIRED_KEYS:
        if key not in meta:
            errs.append(f"{rel}: front-matter sem '{key}'")
    if meta.get("tipo") not in TIPOS:
        errs.append(f"{rel}: tipo inválido {meta.get('tipo')!r}")
    if meta.get("status") not in STATUS:
        errs.append(f"{rel}: status inválido {meta.get('status')!r}")
    if meta.get("criticidade") and meta["criticidade"] not in CRITICIDADE:
        errs.append(f"{rel}: criticidade inválida")
    if meta.get("status") in {"revisao", "concluido"} and not meta.get("dono"):
        errs.append(f"{rel}: status {meta['status']} sem 'dono'")
    for source in meta.get("fontes") or []:
        if not os.path.exists(os.path.join(os.path.dirname(ROOT), "sucesu-cms", source)):
            errs.append(f"{rel}: fonte inexistente {source}")

    tipo = meta.get("tipo")
    present = " | ".join(h for h, _ in headings(body))
    for required in HEADINGS.get(tipo, []):
        if required.lower() not in present.lower():
            errs.append(f"{rel}: seção obrigatória ausente: {required}")

    sections = section_text(body)
    for name, content in sections.items():
        stripped = re.sub(r"<!--.*?-->", "", content, flags=re.S).strip()
        if not stripped:
            errs.append(f"{rel}: seção vazia: {name}")

    if PLACEHOLDER in text:
        errs.append(f"{rel}: marcador {PLACEHOLDER} presente")
    if meta.get("status") in {"revisao", "concluido"}:
        if re.search(r"<(?:GRUPO|MODULO|NOME|UNIDADE|CMS\|PORTAL|0-5|alta\|media\|baixa)>", text):
            errs.append(f"{rel}: placeholders de template ainda presentes")
        cit = len(CITATION.findall(body))
        if tipo in {"modulo", "servico", "ui", "arquitetura"} and cit < 3:
            errs.append(f"{rel}: {cit} citações caminho:linha (mínimo 3)")
        fences = len(CODE_FENCE.findall(body))
        if tipo in {"modulo", "servico", "ui", "arquitetura"} and fences < 2:
            errs.append(f"{rel}: {fences} trechos de código (mínimo 2)")
        if meta.get("tipo") in {"modulo", "servico"} and "Receitas de uso" not in present:
            errs.append(f"{rel}: sem 'Receitas de uso'")
        if "Anti-uso" not in present:
            errs.append(f"{rel}: sem 'Anti-uso'")

    for src, start, end in CITATION.findall(body):
        if src.startswith(("sucesu_analise/", "agent://", "http")):
            continue
        # Nomes isolados (ex.: "Router.php:12") são abreviação de prosa; só validamos
        # citações com caminho e sem elipse.
        KNOWN = ("V2/", "app/", "config/", "tests/", "vendor/", "public/", "database/",
                 "routes/", "resources/", "storage/", "bootstrap/", "ci/", "docker/")
        if "..." in src or not src.startswith(KNOWN):
            continue
        repo = os.path.dirname(ROOT)
        candidates = [os.path.join(repo, "sucesu-cms", src),
                      os.path.join(repo, src),
                      os.path.join(repo, "sucesu-cms", "V2", src)]
        resolved = next((c for c in candidates if os.path.exists(c)), None)
        if resolved is None:
            errs.append(f"{rel}: citação para arquivo inexistente: {src}")
            continue
        total = sum(1 for _ in open(resolved, encoding="utf-8", errors="ignore"))
        first = int(start)
        last = int(end) if end else first
        if first < 1 or last > total or first > last:
            msg = (f"{rel}: citação fora do arquivo: {src}:{start}"
                   f"{('-' + end) if end else ''} (total {total})")
            (errs if STRICT_CITATIONS else warnings).append(msg)

    for target in LINK.findall(body):
        if target.startswith(("http://", "https://")):
            continue
        resolved = os.path.normpath(os.path.join(os.path.dirname(path), target))
        if not os.path.exists(resolved):
            errs.append(f"{rel}: link quebrado {target}")

    return errs


def collect(paths: list[str]) -> list[str]:
    if paths:
        out = []
        for p in paths:
            if os.path.isdir(p):
                for dp, _, fs in os.walk(p):
                    out.extend(os.path.join(dp, f) for f in fs if f.endswith(".md"))
            else:
                out.append(p)
        return out
    out = []
    for dp, dirnames, fs in os.walk(ROOT):
        rel = os.path.relpath(dp, ROOT)
        if rel.split(os.sep)[0] in SKIP_TOP:
            dirnames[:] = []
            continue
        out.extend(os.path.join(dp, f) for f in fs if f.endswith(".md"))
    return out


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("paths", nargs="*")
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--strict-citations", action="store_true",
                    help="trata faixas de citação fora do arquivo como erro")
    args = ap.parse_args()
    global STRICT_CITATIONS
    STRICT_CITATIONS = args.strict_citations

    files = collect(args.paths)
    errors = {f: lint_file(f) for f in files if parse_front_matter(open(f, encoding="utf-8").read())[0]}
    errors = {f: e for f, e in errors.items() if e}

    if args.json:
        print(json.dumps({os.path.relpath(k, ROOT): v for k, v in errors.items()},
                         ensure_ascii=False, indent=2))
    else:
        checked = len([f for f in files if parse_front_matter(open(f, encoding="utf-8").read())[0]])
        print(f"Docs com front-matter: {checked} | com erro: {len(errors)}")
        for f, errs in errors.items():
            print(f"\n{os.path.relpath(f, ROOT)}")
            for err in errs:
                print(f"  - {err}")
        if warnings:
            print(f"\nAvisos de citação ({len(warnings)}; use --strict-citations para falhar):")
            for w in warnings[:40]:
                print(f"  - {w}")
            if len(warnings) > 40:
                print(f"  ... e mais {len(warnings) - 40}")
    return 1 if errors else 0


if __name__ == "__main__":
    sys.exit(main())
