"""Processa o wiki-sac-export.json (gerado por coletar_no_navegador.js) e monta a base local.

Uso:
    python scripts/processar_wiki.py caminho/para/wiki-sac-export.json

Gera, na pasta base-sac/:
    livros/...            um .md por página (com frontmatter) + imagens baixadas ao lado
    indice.json           estrutura livros > capítulos > páginas
    base_sac_completa.md  tudo em um único arquivo
    chunks.jsonl          trechos com metadados para RAG
Pode ser executado de novo: imagens já baixadas não são baixadas outra vez.
"""
import json
import os
import re
import sys
import time
import unicodedata
import urllib.request
from pathlib import Path
from urllib.parse import urlparse, unquote

BASE = Path(__file__).resolve().parent.parent
LIVROS_DIR = BASE / "livros"
PAUSA_IMG = 0.3
CHUNK_MAX = 1500

IMG_RE = re.compile(r"!\[([^\]]*)\]\(([^)\s]+)(?:\s+\"[^\"]*\")?\)")
# [![alt](miniatura)](original)  -> imagem envolvida em link para a original
IMG_LINK_RE = re.compile(r"\[!\[([^\]]*)\]\(([^)\s]+)\)\]\(([^)\s]+)\)")
HTML_IMG_RE = re.compile(r"<img[^>]+src=\"([^\"]+)\"[^>]*>", re.I)


def slugify(s, maxlen=60):
    s = unicodedata.normalize("NFKD", s).encode("ascii", "ignore").decode()
    s = re.sub(r"[^a-zA-Z0-9]+", "-", s).strip("-").lower()
    return s[:maxlen].strip("-") or "sem-titulo"


def yaml_str(s):
    return '"' + (s or "").replace("\\", "\\\\").replace('"', '\\"') + '"'


def original_url(u):
    """Troca a miniatura redimensionada pela imagem original."""
    return re.sub(r"/scaled-\d+-/", "/", u)


baixadas = {}  # url -> Path local
falhas = []


def baixar(url, destino_dir):
    if url in baixadas:
        return baixadas[url]
    nome = unquote(Path(urlparse(url).path).name) or "imagem"
    nome = re.sub(r"[<>:\"/\\|?*]", "_", nome)
    destino = destino_dir / nome
    if not destino.exists() or destino.stat().st_size == 0:
        destino_dir.mkdir(parents=True, exist_ok=True)
        for tentativa in range(5):
            try:
                req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (base-sac export)"})
                with urllib.request.urlopen(req, timeout=60) as r:
                    destino.write_bytes(r.read())
                time.sleep(PAUSA_IMG)
                break
            except urllib.error.HTTPError as e:
                if e.code == 429:
                    time.sleep(int(e.headers.get("Retry-After") or 60) + 2)
                    continue
                falhas.append(f"{url}: HTTP {e.code}")
                return None
            except Exception as e:  # noqa: BLE001
                if tentativa == 4:
                    falhas.append(f"{url}: {e}")
                    return None
                time.sleep(3)
    baixadas[url] = destino
    return destino


def processar_imagens(md, pasta_md, pasta_img):
    """Baixa as imagens da página e reescreve os links para caminhos relativos."""
    def rel(p):
        return Path(os.path.relpath(p, pasta_md)).as_posix()

    def local_para(url_thumb, url_orig=None):
        cand = [url_orig] if url_orig else []
        cand += [original_url(url_thumb), url_thumb]
        for u in dict.fromkeys(c for c in cand if c and c.startswith("http")):
            p = baixar(u, pasta_img)
            if p:
                return p
        return None

    def sub_link(m):
        alt, thumb, orig = m.groups()
        if not re.search(r"\.(png|jpe?g|gif|webp|bmp|svg)$", urlparse(orig).path, re.I):
            orig = None
        p = local_para(thumb, orig)
        return f"![{alt}]({rel(p)})" if p else m.group(0)

    def sub_img(m):
        alt, url = m.group(1), m.group(2)
        if not url.startswith("http"):
            return m.group(0)
        p = local_para(url)
        return f"![{alt}]({rel(p)})" if p else m.group(0)

    def sub_html(m):
        url = m.group(1)
        if not url.startswith("http"):
            return m.group(0)
        p = local_para(url)
        return f"![]({rel(p)})" if p else m.group(0)

    md = IMG_LINK_RE.sub(sub_link, md)
    md = IMG_RE.sub(sub_img, md)
    md = HTML_IMG_RE.sub(sub_html, md)
    return md


def texto_para_ia(md, pasta_md):
    """Troca imagens por uma marcação textual com o caminho a partir de base-sac/."""
    def sub(m):
        alt, caminho = m.group(1), m.group(2)
        if caminho.startswith("http"):
            return f"[imagem: {alt or 'sem nome'} — {caminho}]"
        abs_ = (pasta_md / caminho).resolve()
        try:
            caminho = abs_.relative_to(BASE).as_posix()
        except ValueError:
            pass
        return f"[imagem: {alt or Path(caminho).name} — {caminho}]"
    return IMG_RE.sub(sub, md)


def remover_titulo_duplicado(md, titulo):
    linhas = md.lstrip().splitlines()
    if linhas and linhas[0].lstrip("# ").strip() == titulo.strip():
        return "\n".join(linhas[1:]).lstrip("\n")
    return md.strip()


def dividir_chunks(texto, maximo=CHUNK_MAX):
    blocos = re.split(r"\n\s*\n", texto)
    atual, saida = "", []
    for b in blocos:
        b = b.strip()
        if not b:
            continue
        while len(b) > maximo:
            corte = b.rfind(" ", 0, maximo)
            corte = corte if corte > maximo // 2 else maximo
            if atual:
                saida.append(atual)
                atual = ""
            saida.append(b[:corte].strip())
            b = b[corte:].strip()
        if len(atual) + len(b) + 2 > maximo and atual:
            saida.append(atual)
            atual = b
        else:
            atual = f"{atual}\n\n{b}" if atual else b
    if atual:
        saida.append(atual)
    return saida


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)
    dados = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
    indice = {"origem": dados["origem"], "coletado_em": dados["coletado_em"], "livros": []}
    completo = [f"# Base de Conhecimento SAC\n\nFonte: {dados['origem']} (coletado em {dados['coletado_em'][:10]})\n"]
    chunks = []
    total_paginas = sem_conteudo = 0

    for li, livro in enumerate(dados["livros"], 1):
        pasta_livro = LIVROS_DIR / f"{li:02d}-{livro['slug']}"
        pasta_livro.mkdir(parents=True, exist_ok=True)
        (pasta_livro / "_livro.md").write_text(
            f"---\nlivro: {yaml_str(livro['nome'])}\nurl_original: {livro['url']}\n---\n\n"
            f"# {livro['nome']}\n\n{livro.get('descricao') or ''}\n", encoding="utf-8")
        ind_livro = {"nome": livro["nome"], "descricao": livro.get("descricao"), "url": livro["url"],
                     "pasta": pasta_livro.relative_to(BASE).as_posix(), "itens": []}
        completo.append(f"\n\n# LIVRO: {livro['nome']}\n\n{livro.get('descricao') or ''}\n")
        print(f"[{li:02d}] {livro['nome']}")

        # organiza páginas soltas e capítulos na ordem do site
        ordem_item = 0
        for item in livro["itens"]:
            ordem_item += 1
            if item["tipo"] == "capitulo":
                pasta = pasta_livro / f"{ordem_item:02d}-{item['slug']}"
                pasta.mkdir(parents=True, exist_ok=True)
                paginas = item["paginas"]
                capitulo = item["nome"]
                ind_cap = {"capitulo": capitulo, "descricao": item.get("descricao"), "url": item["url"],
                           "pasta": pasta.relative_to(BASE).as_posix(), "paginas": []}
                ind_livro["itens"].append(ind_cap)
                completo.append(f"\n\n## CAPÍTULO: {capitulo}\n")
                destino_ind = ind_cap["paginas"]
                numerar = True
            else:
                pasta, paginas, capitulo = pasta_livro, [item], None
                destino_ind = ind_livro["itens"]
                numerar = False

            for pi, pag in enumerate(paginas, 1):
                total_paginas += 1
                prefixo = f"{pi:02d}" if numerar else f"{ordem_item:02d}"
                nome_base = f"{prefixo}-{slugify(pag.get('slug') or pag['titulo'])}"
                arq = pasta / f"{nome_base}.md"
                md = pag.get("markdown")
                if not md:
                    sem_conteudo += 1
                    md = "_(conteúdo não coletado)_"
                corpo = remover_titulo_duplicado(md, pag["titulo"])
                corpo = processar_imagens(corpo, pasta, pasta / "imagens" / nome_base)
                fm = (f"---\ntitulo: {yaml_str(pag['titulo'])}\nlivro: {yaml_str(livro['nome'])}\n"
                      + (f"capitulo: {yaml_str(capitulo)}\n" if capitulo else "")
                      + f"url_original: {pag['url']}\n---\n\n")
                arq.write_text(f"{fm}# {pag['titulo']}\n\n{corpo}\n", encoding="utf-8")

                caminho_rel = arq.relative_to(BASE).as_posix()
                destino_ind.append({"titulo": pag["titulo"], "url": pag["url"], "arquivo": caminho_rel})

                texto = texto_para_ia(corpo, pasta)
                trilha = " > ".join(x for x in [livro["nome"], capitulo, pag["titulo"]] if x)
                completo.append(f"\n\n### {pag['titulo']}\n\n_Caminho: {trilha} — {pag['url']}_\n\n{texto}\n")
                for ci, ch in enumerate(dividir_chunks(texto) or [""]):
                    chunks.append({
                        "id": f"{livro['slug']}/{pag.get('slug', nome_base)}#{ci}",
                        "livro": livro["nome"], "capitulo": capitulo, "pagina": pag["titulo"],
                        "url": pag["url"], "arquivo": caminho_rel, "trecho": ci,
                        "texto": f"{trilha}\n\n{ch}".strip(),
                    })
                print(f"     - {pag['titulo']}")
        indice["livros"].append(ind_livro)

    (BASE / "indice.json").write_text(json.dumps(indice, ensure_ascii=False, indent=2), encoding="utf-8")
    (BASE / "base_sac_completa.md").write_text("".join(completo), encoding="utf-8")
    with open(BASE / "chunks.jsonl", "w", encoding="utf-8") as f:
        for c in chunks:
            f.write(json.dumps(c, ensure_ascii=False) + "\n")

    print("\n=== Relatório ===")
    print(f"Livros: {len(dados['livros'])}  Páginas: {total_paginas}  Sem conteúdo: {sem_conteudo}")
    print(f"Imagens (únicas): {len(baixadas)}  Falhas: {len(falhas)}  Chunks: {len(chunks)}")
    for fl in falhas:
        print("  FALHA:", fl)
    (BASE / "relatorio_falhas.txt").write_text("\n".join(falhas), encoding="utf-8")


if __name__ == "__main__":
    main()
