"""
Project Together — powered by AI: a MEMORIA do seu projeto entre os passos.

POR QUE EXISTE
  Sem isto, cada passo pediria para voce COLAR de novo o que o passo anterior ja
  tinha produzido ("cole aqui as entregas que voce gerou no passo 1"). Isso e
  friccao boba: o script que rodou o passo 1 JA TINHA as entregas na mao.

  Este modulo guarda a saida de cada passo e MONTA o prompt do passo seguinte JA
  PREENCHIDO. E o "fio condutor" do curso: voce escreve a justificativa — uma vez,
  com as suas palavras — e a corrente se puxa sozinha:

      justificativa -> objetivo e KRs -> entregas -> partes -> abordagem
                    -> escopo -> riscos -> cronograma -> plano -> execucao
                    -> status -> mudancas -> encerramento

O QUE GERA (na pasta _saida/, ao lado dos scripts)
  contexto.json          a memoria estruturada (a fonte).
  contexto.md            a mesma memoria em texto limpo, para voce consultar
                         (ou colar na IA) quando quiser.
  prompt-passo-N.txt     o prompt do passo N ja preenchido com o SEU projeto,
                         na SUA abordagem (PREDITIVA ou AGIL).

COMO OS PASSOS USAM (cada script chama no fim)
  passo 1  -> salvar(justificativa=..., okr=..., entregas=..., partes=..., ...)
              + gerar_prompt_pronto(2)
  passo 2  -> salvar(escopo=[...]) + gerar_prompt_pronto(3)
  ... e assim por diante.
"""
import json
import os
import re

import prompts
from idioma import T, Tf

HERE = os.path.dirname(os.path.abspath(__file__))
_SAIDA = os.path.join(HERE, "_saida")
_JSON = os.path.join(_SAIDA, "contexto.json")
_MD = os.path.join(_SAIDA, "contexto.md")


# ─────────────────────────────────────────────────────────────────────────────
# Formatadores — as listas dos scripts viram texto que a IA (e voce) entende
# ─────────────────────────────────────────────────────────────────────────────
def _campo(v):
    """String OU lista -> texto limpo (a IA as vezes devolve ['x','y'])."""
    if isinstance(v, (list, tuple)):
        v = ", ".join(str(x).strip() for x in v if str(x).strip())
    v = ("" if v is None else str(v)).strip()
    return v or "-"


def fmt_okr(d):
    """Os OKRs do projeto: N objetivos, cada um com os seus Key Results.

    Aceita a forma antiga (um objetivo só) — ver entradas.objetivos_do_okr.
    """
    from entradas import objetivos_do_okr
    objetivos = objetivos_do_okr(d)
    linhas = []
    for i, o in enumerate(objetivos, 1):
        n = f" {i}" if len(objetivos) > 1 else ""
        linhas.append(f"OBJETIVO{n}: {_campo(o.get('objetivo'))}")
        for j, kr in enumerate(o.get("key_results") or [], 1):
            linhas.append(Tf("- KR {0}.{1}: {2} | meta: {3} | prazo: {4} | responsável: {5}", i, j, _campo(kr.get('kr')), _campo(kr.get('meta')), _campo(kr.get('prazo')), _campo(kr.get('responsavel'))))
        linhas.append("")
    return "\n".join(linhas).strip()


def fmt_entregas(itens):
    linhas = ["ENTREGAS:"]
    for e in itens or []:
        linhas.append(Tf("- {0} | serve ao KR: {1} | requisito: {2} | aceitação: {3}", _campo(e.get('nome')), _campo(e.get('kr')), _campo(e.get('requisito')), _campo(e.get('aceitacao'))))
    return "\n".join(linhas)


def fmt_partes(itens):
    linhas = ["PARTES INTERESSADAS:"]
    for p in itens or []:
        linhas.append(Tf("- {0} [{1}] {2} | espera: {3} | poder: {4} | interesse: {5}", _campo(p.get('nome')), _campo(p.get('grupo')), _campo(p.get('papel')), _campo(p.get('espera')), _campo(p.get('poder')), _campo(p.get('interesse'))))
    return "\n".join(linhas)


def fmt_eap(itens):
    tem_fase = any((p.get("fase") or "").strip() for p in itens or [])
    cab = "EAP (fase -> entrega -> pacotes):" if tem_fase else "EAP (entrega -> pacotes de trabalho):"
    linhas = [cab]
    for p in itens or []:
        fase = (p.get("fase") or "").strip()
        pref = f"{fase} / " if fase else ""
        linhas.append(Tf("- {0}[{1}] {2} | aceitação: {3}", pref, _campo(p.get('entrega')), _campo(p.get('pacote')), _campo(p.get('aceitacao'))))
    return "\n".join(linhas)


def fmt_backlog(itens):
    linhas = ["PRODUCT BACKLOG (priorizado):"]
    for h in itens or []:
        linhas.append(Tf("- P{0} | {1} | aceitação: {2} | pontos: {3}", _campo(h.get('prioridade')), _campo(h.get('historia')), _campo(h.get('aceitacao')), _campo(h.get('pontos'))))
    return "\n".join(linhas)


def fmt_riscos(d):
    linhas = ["RISCOS:"]
    for r in d.get("riscos") or []:
        linhas.append(
            Tf("- [{0}] {1} | prob: {2} | gravidade: {3} | resposta: {4}", _campo(r.get('tipo')), _campo(r.get('risco')), _campo(r.get('probabilidade')), _campo(r.get('gravidade')), _campo(r.get('resposta'))))
    if d.get("premissas"):
        linhas += ["", "PREMISSAS:"] + [f"- {_campo(x)}" for x in d["premissas"]]
    if d.get("restricoes"):
        linhas += ["", "RESTRIÇÕES:"] + [f"- {_campo(x)}" for x in d["restricoes"]]
    return "\n".join(linhas)


def fmt_cronograma(d):
    linhas = ["CRONOGRAMA:"]
    for t in d.get("cronograma") or []:
        linhas.append(Tf("- {0} | {1} -> {2} | depende de: {3} | responsável: {4}", _campo(t.get('pacote')), _campo(t.get('inicio')), _campo(t.get('fim')), _campo(t.get('depende_de')), _campo(t.get('responsavel'))))
    if d.get("sprints"):
        linhas = ["ROADMAP DE SPRINTS:"]
        for s in d["sprints"]:
            linhas.append(Tf("- Sprint {0} ({1} -> {2}) | objetivo: {3} | itens: {4} | pontos: "
                             "{5}", _campo(s.get('sprint')), _campo(s.get('inicio')), _campo(s.get('fim')), _campo(s.get('objetivo')), _campo(s.get('itens')), _campo(s.get('pontos'))))
    if d.get("orcamento"):
        linhas += ["", "ORÇAMENTO:"]
        for o in d["orcamento"]:
            linhas.append(Tf("- {0} | {1} | R$ {2} | {3}", _campo(o.get('item')), _campo(o.get('tipo')), _campo(o.get('valor')), _campo(o.get('quando'))))
    return "\n".join(linhas)


# ─────────────────────────────────────────────────────────────────────────────
# Persistencia
# ─────────────────────────────────────────────────────────────────────────────
def _carregar():
    try:
        with open(_JSON, encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}


def salvar(**campos):
    """Grava campos na memoria (merge) e regenera contexto.md.

    Campos usados pelos passos: projeto, abordagem, justificativa, okr, entregas,
    partes, onde, marcos, ordem_de_grandeza, eap, backlog, riscos, cronograma,
    dod, engajamento, status, mudancas, encerramento.
    """
    d = _carregar()
    d.update({k: v for k, v in campos.items() if v not in (None, "", [], {})})
    os.makedirs(_SAIDA, exist_ok=True)
    with open(_JSON, "w", encoding="utf-8") as f:
        json.dump(d, f, ensure_ascii=False, indent=2)
    _render_md(d)
    return d


def _secao(titulo, corpo):
    return f"## {titulo}\n\n{corpo}\n\n" if corpo else ""


def _render_md(d):
    projeto = d.get("projeto") or "(sem nome no .env)"
    ab = d.get("abordagem") or "(a escolher no passo 1)"
    md = (f"# Contexto do projeto — {projeto}\n\n"
          f"**Abordagem de desenvolvimento:** {ab}\n\n"
          "_Gerado pelos scripts do Project Together. É a memória do seu projeto entre\n"
          "os passos: o prompt do próximo passo já sai pronto em\n"
          "prompt do próximo passo já sai pronto em prompt-passo-N.txt._\n\n")
    if d.get("justificativa"):
        md += _secao(T("Justificativa (por quê) — passo 1, escrita por VOCÊ"),
                     d["justificativa"])
    if d.get("okr"):
        md += _secao(T("OKR (por quê, medido) — passo 1"), fmt_okr(d["okr"]))
    if d.get("entregas"):
        md += _secao(T("Entregas (o quê) — passo 1"), fmt_entregas(d["entregas"]))
    if d.get("partes"):
        md += _secao(T("Partes interessadas (quem) — passo 1"), fmt_partes(d["partes"]))
    ctx = []
    if d.get("onde"):
        ctx.append(f"ONDE: {d['onde']}")
    if d.get("marcos"):
        ctx.append("QUANDO (marcos preliminares):\n" + "\n".join(
            f"- {_campo(m.get('marco'))}: {_campo(m.get('quando'))}" for m in d["marcos"]))
    if d.get("ordem_de_grandeza"):
        ctx.append(f"QUANTO (ordem de grandeza): {d['ordem_de_grandeza']}")
    if ctx:
        md += _secao(T("Contexto, marcos e ordem de grandeza — passo 1"), "\n\n".join(ctx))
    if d.get("eap"):
        md += _secao(T("Escopo — EAP (passo 2)"), fmt_eap(d["eap"]))
    if d.get("backlog"):
        md += _secao(T("Escopo — Product Backlog (passo 2)"), fmt_backlog(d["backlog"]))
    if d.get("riscos"):
        md += _secao(T("Riscos, premissas e restrições — passo 3"), fmt_riscos(d["riscos"]))
    if d.get("cronograma"):
        md += _secao(T("Cronograma e orçamento — passo 4"), fmt_cronograma(d["cronograma"]))
    if d.get("dod"):
        md += _secao(T("Definição de Pronto (DoD) — passo 5"),
                     "\n".join(f"- {_campo(x)}" for x in d["dod"]))
    os.makedirs(_SAIDA, exist_ok=True)
    with open(_MD, "w", encoding="utf-8") as f:
        f.write(md)


# ─────────────────────────────────────────────────────────────────────────────
# O prompt do PROXIMO passo, ja preenchido com o que voce produziu ate aqui
# ─────────────────────────────────────────────────────────────────────────────
def _abordagem():
    d = _carregar()
    if d.get("abordagem"):
        return d["abordagem"]
    try:
        from env import cfg
        from prompts import normalizar_abordagem
        return normalizar_abordagem(cfg().get("APPROACH"))
    except Exception:
        return "PREDITIVA"


def _resumo_projeto(d):
    """Resumo curto do termo de abertura (usado por varios prompts)."""
    partes = []
    if d.get("justificativa"):
        partes.append("JUSTIFICATIVA: " + d["justificativa"])
    if d.get("okr"):
        partes.append(fmt_okr(d["okr"]))
    if d.get("entregas"):
        partes.append(fmt_entregas(d["entregas"]))
    return "\n\n".join(partes)


def rotulo_do_passo(n, abordagem=None):
    """O nome do passo n na abordagem do aluno — a fonte e' prompts.rotulo (uma so').

    No agil, os passos do ciclo da Sprint saem com o nome do EVENTO ("Sprint Review —
    inspecionar o incremento"); nas duas abordagens, o numero e o mesmo. Import
    preguicoso como o resto deste modulo, e a prova de falta de .env."""
    try:
        from prompts import rotulo
        return rotulo(n, abordagem)
    except (Exception, SystemExit):
        return ""


def respostas_para_prompt(ids, respostas=None):
    """As respostas do quadro — com o COMO (cartao 4.1) que o curso JA SABE.

    A abordagem de desenvolvimento nao e' um cartao como os outros: ela e' uma DECISAO
    que, uma vez tomada, fica gravada no .env E na propria FORMA do quadro (as listas
    de trabalho nascem dela). Quando o cartao 4.1 ainda nao tem o texto — porque o
    aluno escolheu pelo 01a e o passo so' vai gravar o cartao adiante, ou porque ele
    escreveu a abordagem direto no .env — o prompt NAO pode dizer a IA "(ainda em
    branco no meu quadro: a abordagem)" nem o curso pode pedir ao aluno uma decisao
    que ele ja tomou: "no quadro ja esta escrito que e' agil" (Eduardo, 2026-07-24).
    Aqui a decisao conhecida entra como resposta do 4.1.

    Nao muda o que conta como ELO RESPONDIDO (proximo_elo_p1 usa as respostas cruas):
    a fronteira do curso continua sendo o cartao 4.1 de verdade.
    """
    import quadro
    respostas = quadro.respostas_do_termo(ids) if respostas is None else respostas
    if not respostas.get("4.1"):
        decidida = quadro.abordagem_do_env()
        if decidida:
            respostas = dict(respostas)
            respostas["4.1"] = f"Abordagem de desenvolvimento: {decidida}."
    return respostas


def corpo_prompt_passo(n, ids, extras=None):
    """O prompt do passo N ja preenchido — SO' O TEXTO que se cola na IA.
    Devolve (texto, faltando): `faltando` sao os [INPUT x] que o quadro ainda nao tem.

    E' o que vai para a area de transferencia. O arquivo .txt leva um cabecalho
    explicando o que fazer; esse cabecalho nao pode ir junto para a IA.

    Nao existe mais "a tabela que sabe o que alimenta o passo N": quem sabe e' o
    proprio prompt, nos parametros [INPUT x] (prompts.py). Aqui so' resolvemos.
    """
    import quadro
    from prompts import chave_do_passo, montar_prompt
    chave = chave_do_passo(n, _abordagem())
    if not chave:
        return None, []
    return quadro.resolver_inputs(montar_prompt(chave), ids, extras=extras,
                                  respostas=respostas_para_prompt(ids))


def gerar_prompt_pronto(n, ids, extras=None):
    """Escreve _saida/prompt-passo-N.txt: o prompt do passo N ja preenchido a partir
    do QUADRO. Devolve o caminho (ou None se o passo nem tem prompt)."""
    ab = _abordagem()
    corpo, faltando = corpo_prompt_passo(n, ids, extras)
    if corpo is None:
        return None
    # A linha "IDE:" saiu com o modo IDE (removido em 01/08): um caminho so', a IA
    # no chat. Deixa-la aqui mandava o aluno abrir um arquivo que ele nunca abre.
    #
    # UMA LINHA SO' DE CABECALHO (Eduardo, 2026-08-08): o arquivo inteiro E' o prompt.
    # O aluno faz Ctrl+A / Ctrl+C e cola — sem escolher onde comeca. Instrucao para o
    # ALUNO dentro do arquivo virava instrucao colada na IA; por isso ela saiu daqui e
    # vive no terminal, onde o aluno le antes de abrir o arquivo.
    cabecalho = Tf("=== PROMPT DO PASSO {0} — {1} (abordagem {2}) ===\n",
                   n, rotulo_do_passo(n, ab), prompts.nome_abordagem(ab))
    os.makedirs(_SAIDA, exist_ok=True)
    caminho = os.path.join(_SAIDA, f"prompt-passo-{n}.txt")
    with open(caminho, "w", encoding="utf-8") as f:
        f.write(cabecalho + corpo + "\n")
    return caminho


# ─────────────────────────────────────────────────────────────────────────────
# A CORRENTE DO PASSO 1 — e quem preenche o prompt de cada elo
#
# REGRA (Eduardo, 2026-07-14): o aluno NUNCA copia o conteudo de um cartao para
# dentro de um prompt. Isso e trabalho operacional: a informacao ja esta no quadro,
# e quem le o quadro e o script. Entao o script monta o prompt JA CHEIO — e o
# entrega pronto (area de transferencia + o cartao do elo, no proprio quadro).
# ─────────────────────────────────────────────────────────────────────────────
# O que cada elo PRODUZ — os cartoes do termo que ele preenche. Se o aluno ja respondeu
# esses cartoes, o elo esta feito. (De onde ele COME nao mora aqui: mora no proprio
# prompt, nos parametros [INPUT x] — prompts.py.)
_PRODUZ = {
    "okr": ("1.2",),
    "entregas": ("2.1", "2.2"),
    "partes": ("3",),
    "abordagem": ("4.1",),
    "restricoes_premissas_riscos": ("4.2", "4.3", "4.4", "4.6"),
    "quando_quanto": ("5", "6"),
}
ROTULO_ELO = {
    "okr": T("os objetivos e Key Results (OKR)"),
    "entregas": T("as entregas e os requisitos"),
    "partes": T("as partes interessadas"),
    "abordagem": T("o COMO — a abordagem de desenvolvimento"),
    "restricoes_premissas_riscos": T("restrições, premissas, riscos iniciais e o ONDE"),
    "quando_quanto": T("quando e quanto (preliminar)"),
}


def prompt_pronto(chave, ids, extras=None):
    """O prompt de UMA chave do prompts.py, ja' preenchido a partir do QUADRO.

    Devolve (texto, faltando) — `faltando` sao os [INPUT x] que o quadro ainda nao tem.
    E' o irmao avulso do prompts_da_corrente(): serve a quem pede UM prompt que nao e' um
    elo da corrente do Termo (o "partes_completar", do Processo 2 do PMBOK 8 Together).
    """
    import quadro
    from prompts import montar_prompt
    return quadro.resolver_inputs(montar_prompt(chave), ids, extras=extras,
                                  respostas=respostas_para_prompt(ids))


def prompts_da_corrente(ids, extras=None):
    """{chave do elo: (prompt PRONTO, [inputs que faltam])} para os 6 elos do passo 1.

    Um lugar so' resolve tudo: o quadro. O elo da ABORDAGEM nao tem prompt de texto —
    ele e' uma DECISAO (script 01a) — mas tem o prompt da segunda opiniao, que so' fica
    pronto quando o 01a passa o [INPUT DIAGNOSTICO].
    """
    import quadro
    from prompts import CORRENTE_P1, montar_prompt
    respostas = respostas_para_prompt(ids)
    out = {}
    for chave in CORRENTE_P1:
        texto, faltando = quadro.resolver_inputs(
            montar_prompt(chave), ids, extras=extras, respostas=respostas)
        out[chave] = (texto, faltando)
    return out


def proximo_elo_p1(ids, resolvidos=None):
    """O UNICO proximo passo do aluno na corrente do passo 1 — lido do QUADRO.

    Devolve None quando a corrente acabou. Senao, um dict:
        i, chave, rotulo  — que elo e (1..6)
        decisao=True      — e a abordagem: nao ha prompt, ha uma escolha (01a)
        prompt            — o prompt JA PREENCHIDO, pronto para colar na IA
        falta             — os cartoes que ainda faltam responder para ele sair cheio
    """
    import quadro
    from prompts import CORRENTE_P1, NOME_DO_INPUT, montar_prompt
    respostas = quadro.respostas_do_termo(ids)
    resolvidos = resolvidos or prompts_da_corrente(ids)
    for i, chave in enumerate(CORRENTE_P1, 1):
        if all(respostas.get(r) for r in _PRODUZ[chave]):
            continue                                  # este elo ja foi respondido
        texto, faltando = resolvidos.get(chave, (None, []))
        if faltando and chave != "abordagem":
            # `resolvidos` pode ter sido montado ANTES de o passo gravar os cartoes
            # desta rodada — o script le o quadro e SO' DEPOIS escreve nele. Sem esta
            # releitura, quem decide QUAL elo (respostas de agora) e quem diz O QUE
            # FALTA (a foto velha) olham quadros diferentes, e o curso pede um cartao
            # que ele mesmo acabou de preencher (Eduardo, 2026-07-24: "no quadro ja
            # esta escrito que e' agil"). Uma unica foto do quadro manda nas duas.
            texto, faltando = quadro.resolver_inputs(
                montar_prompt(chave), ids,
                respostas=respostas_para_prompt(ids, respostas))
        base = {"i": i, "chave": chave, "rotulo": ROTULO_ELO[chave],
                "decisao": chave == "abordagem", "prompt": None,
                "falta": [NOME_DO_INPUT.get(f, f) for f in faltando]}
        if chave == "abordagem":
            return base
        if not faltando:
            base["prompt"] = texto
        return base
    return None


def gerar_correntes_p1(ids, resolvidos=None):
    """Escreve em _saida/ o prompt de cada elo do passo 1 que ja esta PRONTO.

    E' a copia de seguranca do que o aluno recebe na area de transferencia e no cartao
    do elo — util para quem perdeu o clipboard ou quer reler o prompt inteiro.
    Elo com [INPUT x] faltando nao vira arquivo: prompt pela metade nao ajuda ninguem.
    """
    from prompts import CORRENTE_P1
    resolvidos = resolvidos or prompts_da_corrente(ids)
    gerados = []
    os.makedirs(_SAIDA, exist_ok=True)
    for i, chave in enumerate(CORRENTE_P1, 1):
        corpo, faltando = resolvidos.get(chave, (None, ["?"]))
        if not corpo or faltando:
            continue
        caminho = os.path.join(_SAIDA, f"prompt-passo-1-{i}-{chave}.txt")
        with open(caminho, "w", encoding="utf-8") as f:
            # Cabecalho de UMA linha: o arquivo inteiro e' o prompt (Ctrl+A, Ctrl+C).
            f.write(Tf("=== PASSO 1 · elo {0}/{1} — {2} ===\n",
                       i, len(CORRENTE_P1), chave.upper())
                    + corpo + "\n")
        gerados.append(caminho)
    return gerados
