"""
Project Together — powered by AI: a SAUDE DO SEU PROJETO (rode quando quiser).

Este e o painel do projeto — irmao do verificar_okrs.py, mas mais amplo. Uma
pergunta so: "como esta o meu projeto AGORA, e o que eu faco a seguir?".

Ele le o QUADRO (nunca a caixa de entrada, nunca a memoria local) e responde em
quatro partes — depois de conferir, antes de tudo, se as pecas estao no lugar:

  0. CONFORMIDADE DO PACOTE  (conformidade.py)
     O seu .env tem UMA linha por dado? O seu quadro tem UMA lista por papel, no
     idioma do seu pacote? Vem primeiro porque e a fundacao das outras quatro: um
     .env com PROJETO ao lado de PROJECT (voce edita uma linha, o curso le a outra)
     ou um quadro com a mesma lista escrita nos dois idiomas fazem todo o resto do
     painel medir a coisa errada. Roda ANTES de montar as listas, para voce decidir
     sobre o idioma enquanto o quadro ainda esta como voce o deixou.

  1. ONDE EU ESTOU NOS 10 PASSOS
     Quais passos ja concluir (lista "Concluido (curso)") e qual e o proximo.
     Os 10 passos nao sao uma fila: depois do Passo 1 voce roda o que o projeto
     pedir — e os Passos 6, 7, 8 e 9 voce REPETE ao longo do projeto (monitorar e
     controlar mudancas sao continuos, nao um evento no fim). Guia PMBOK 8: os
     dominios de desempenho sao concorrentes e interativos.

  2. O TERMO DE ABERTURA ESTA COMPLETO?  (Passo 1)
     As 7 perguntas do 5W2H respondidas — a fundacao de todo o resto.

  3. O QUE EU PROMETI AINDA ESTA DE PE?  (OKR — o mesmo do verificar_okrs.py)
     Objetivos e Key Results medíveis, e quanto de cada um ja foi entregue.

  4. PRAZO E CUSTO: PLANEJADO x REALIZADO
     O progresso das entregas (escopo x pronto) e a comparacao com a BASELINE
     capturada no Passo 5. Sem baseline, o script diz o que falta em vez de chutar.

O QUE ELE NAO FAZ — dizer que um Key Result foi ATINGIDO. O valor real vive no
mundo, nao no quadro; quem o mede e voce, no Passo 10 (e depois dele — muitos
beneficios so aparecem apos o encerramento). Aqui medimos o que o quadro sabe.

O Passo 8 (Monitorar e Status Report) roda este mesmo codigo. E' o mesmo python
para as duas necessidades: acompanhar o andamento e medir o progresso.

LER x ESCREVER. O diagnostico() so' LE o quadro — e por isso o Passo 8 pode usa-lo
para emitir o Status Report sem risco (relatorio nao mexe no quadro de quem o le).
As escritas deste script vivem no main(), fora do diagnostico:
  · a CONFORMIDADE (secao 0) pergunta antes de tocar no seu .env: --conformidade /
    --sem-conformidade para rodar sem ninguem na frente. A unica coisa que ela faz
    sozinha e' apagar uma lista NOSSA duplicada e VAZIA do quadro (o `garantir_listas`
    faz isso ao montar) — e quando faz, o log dela aparece na tela.
  · ANTES de auditar, ele traz para o texto o que voce escolheu nos DROPDOWNS dos
    cartoes de risco (Probabilidade/Impacto/Dono) e recalcula a Prioridade — senao o
    painel cobra o que voce ja respondeu. Nao decide nada: so' transcreve a sua escolha.
  · DEPOIS de mostrar as lacunas, ele PERGUNTA se pode marcar os cartoes apontados com
    a etiqueta "🔎 Revisar" — e so' marca se voce disser que sim. Marcar tambem
    RECONCILIA: o que voce ja corrigiu perde a etiqueta.
Para rodar sem ninguem na frente: --marcar / --sem-marcar.

Execute: Ctrl+F5 no VS Code, ou pelo menu (tecla P).
"""
import re

import auditoria
import catalogo_pmbok8 as cat
import conformidade
import entradas
import progresso
import prompts
import quadro
import rotulos_antigos
import verificar_okrs
from env import cfg, require
from runner import header
from idioma import T, Tf


# ── O VOCABULARIO DO CURSO QUE ESTA RODANDO ──────────────────────────────────
# Quem traduz "Passo 5" -> "Processo 21" e' o CATALOGO (ver o bloco `numero_do_passo`
# la'). Aqui ficam so' os apelidos curtos, porque estas quatro chamadas aparecem
# dezenas de vezes neste arquivo.
_num = cat.numero_do_passo
_nums = cat.numeros_dos_passos
_rot = cat.rotulo_do_passo


def _rots(ns):
    """"Passos 1, 2, 3 e 4" · "Processos 1, 5, 9 e 17" — o ` e ` traduzido."""
    return cat.rotulos_dos_passos(ns, T(" e "))


def _percorreu():
    """"os 10 passos" · "todos os processos que o curso executa".

    O numero so' aparece quando o curso executa TODAS as unidades dele — no PMBOK 8
    Together os scripts entregam a saida de 17 dos 40, e dizer "os 40" seria mentira."""
    if cat.executa_tudo():
        return Tf("os {0} {1}", cat.total(), cat.unidade(plural=True))
    return Tf("todos os {0} que o curso executa", cat.unidade(plural=True))


# ── As 7 perguntas do 5W2H (rotulo do cartao -> nome legivel). 4.6 (ONDE) e opcional:
#    "em branco" e uma resposta legitima ("decido no momento certo").
TERMO = [
    ("1.1", T("Justificativa (POR QUÊ)"), True),
    ("1.2", T("Objetivos e Key Results (OKR)"), True),
    ("2.1", T("Produtos e entregas (O QUÊ)"), True),
    ("2.2", T("Requisitos"), True),
    ("3", T("Partes interessadas (QUEM)"), True),
    ("4.1", T("Abordagem de desenvolvimento (COMO)"), True),
    ("4.2", T("Restrições"), False),
    ("4.3", T("Premissas"), False),
    ("4.4", T("Riscos iniciais"), False),
    ("4.6", T("Onde (ONDE)"), False),
    ("5", T("Linha do tempo (QUANDO)"), False),
    ("6", T("Custos (QUANTO)"), False),
]

# Passos que sao CONTINUOS/RECORRENTES — o aluno os repete ao longo do projeto (nao
# ha "concluir" definitivo). Nao entram na conta de "quanto falta".
PASSOS_RECORRENTES = {6, 7, 8, 9}


def _cards_uteis(ids, chave):
    """Os cartoes de trabalho de uma lista (sem o 'Guia desta lista' nem cartoes vazios).

    UMA LISTA QUE NAO EXISTE MAIS NAO TEM CARTOES — e nao pode derrubar o diagnostico
    (Eduardo, 17/08/2026, board 270). O `.env` guarda os LIST_* de TODAS as listas que o
    quadro ja teve; quem apaga uma lista no ProjectAdm (ou troca de abordagem) deixa a
    chave para tras apontando para um id morto. Quem monta `ids` a partir do `.env` — o
    `gerar_documentos.py` (tecla D) e' o caso — passava esse id para ca' e o 404 subia ate
    matar o Status Report inteiro: um artefato de PASSO 8 perdido por causa de uma linha
    velha do .env, com a mensagem "404: Lista nao encontrada", que nao diz nada ao aluno.
    Aqui a resposta honesta e' lista vazia; quem PRECISA da lista (os passos) a pede pelo
    `garantir_listas`, que a encontra ou a cria."""
    lid = ids.get(chave)
    if not lid:
        return []
    from pa_client import list_cards, ErroAPI
    try:
        cards = list_cards(lid)
    except ErroAPI as e:
        if getattr(e, "codigo", None) == 404:
            return []
        raise
    out = []
    for c in cards:
        t = (c.get("titulo") or "").strip()
        if t and not quadro.e_guia(t):
            out.append(c)
    return out


def _baseline(ids):
    """A linha de base que VALE hoje. Devolve (data, existe) — (None, False) se nao ha.

    A FONTE DA VERDADE E' O PROJECTADM (baseline.vigente()), nao o cartao da trilha.
    Este leitor so' conhecia o titulo '📌 Baseline do plano — DATA', que desde 2026-07-29
    e' apenas o FALLBACK de quando a API de baseline nao responde: no caminho normal o
    Passo 5 congela a linha de base NATIVA e escreve '📌 Linha de base aprovada — DATA'
    (e o Passo 9, ao aprovar uma mudanca, escreve '📌 Linha de base — MC-NNN').

    Resultado do bug (Ativalog/quadro 293, medido em 19/08/2026): o quadro tinha 4 linhas
    de base congeladas, a vigente era a #19 — e o painel dizia "Baseline ainda NAO
    capturada", mandando o aluno rodar de novo um Passo 5 que ele ja' tinha feito. Pior
    do que a mensagem: o "sem baseline" ia no `resumo()` que entra DENTRO do prompt e do
    cartao do Status Report — a IA recebia como fato que o projeto nao tinha plano
    aprovado e escrevia alerta em cima disso.
    """
    try:
        import baseline as _bl
        b = _bl.vigente()
        if b:
            quando = str(b.get("data_criacao") or b.get("criado_em")
                         or b.get("data") or "") or _bl._nome_da(b)
            m = re.search(r"(\d{4}-\d{2}-\d{2})", quando)
            return (m.group(1) if m else "?"), True
    except Exception:
        pass                    # sem rede/API o cartao 📌 da trilha ainda responde
    # O cartao e' a reserva — e aceita os DOIS titulos: o de hoje (traduzido, via
    # baseline.CARTAO_PREFIXO, para o pacote ingles) e o antigo, dos quadros que ja
    # existiam. Um titulo que so' o escritor conhece e' um leitor cego.
    try:
        from baseline import CARTAO_PREFIXO as _pref
    except Exception:
        _pref = "📌 Linha de base"
    for c in _cards_uteis(ids, "CONCLUIDO"):
        t = (c.get("titulo") or "")
        if t.startswith((_pref, "📌 Linha de base", "📌 Baseline do plano")):
            m = re.search(r"(\d{4}-\d{2}-\d{2})", t)
            return (m.group(1) if m else "?"), True
    return None, False


def _status_reports(ids):
    """Quantos Status Report (Passo 8) ja foram emitidos — a evidencia de monitoramento."""
    return [c for c in _cards_uteis(ids, "CONCLUIDO")
            if (c.get("titulo") or "").startswith("📊 Status Report")]


def _cartoes_marcados(ids):
    """Os cartões que HOJE carregam a etiqueta 🔎 Revisar no quadro — para o painel
    APONTAR onde revisar (queixa do Eduardo, 2026-07-24: 'tenho 2 cartões marcados e o
    relatório não avisou'). Aqui só LEMOS a etiqueta: quem a aplica/remove são os passos
    que ESCREVEM e, no fim deste script, o `marcar()` — que só roda se você confirmar.
    Um cartão pode seguir marcado depois de corrigido, até que o passo que o marcou (ou
    este painel) rode de novo e reconcilie.

    Fail-safe: qualquer erro de API -> [] (o painel nunca quebra por isto).
    Custo: as etiquetas vem NA LISTAGEM dos cartoes desde 2026-07-30 (o Henry corrigiu o
    `GET /listas/{id}/cartoes`, que devolvia `etiquetas: []` para todo cartao — item 0e do
    NEEDS_FOR_HENRY). Antes disto era 1 GET por cartao, em paralelo, e por isso o painel
    pedia esta leitura como opcional; agora ela sai das listas que o painel ja le.
    O GET individual continua como reserva, para um payload sem a chave.
    Devolve [{'id', 'titulo'}, ...]."""
    try:
        from pa_client import list_etiquetas, list_cards, etiquetas_do_cartao
        alvo = auditoria._ETIQUETA_REVISAR[0].strip().lower()
        eid = next((str(e.get("id")) for e in list_etiquetas()
                    if (e.get("nome") or "").strip().lower() == alvo), None)
    except Exception:
        return []
    if not eid:
        return []
    cartoes = []
    for l in ("INPUTS", "ESCOPO", "RISCOS", "ORCAMENTO", "PARTES", "MUDANCAS"):
        if not ids.get(l):
            continue
        try:
            for c in list_cards(ids[l]):
                t = (c.get("titulo") or "").strip()
                if t and not quadro.e_guia(t):
                    cartoes.append(c)
        except Exception:
            continue

    def _tem(c):
        try:
            etq = c.get("etiquetas")
            if not isinstance(etq, list):
                etq = etiquetas_do_cartao(c["id"])
            if eid in {str(e.get("id")) for e in etq}:
                return {"id": c["id"], "titulo": (c.get("titulo") or "").strip()}
        except Exception:
            return None
        return None
    marcados = [m for m in (_tem(c) for c in cartoes) if m]
    return marcados


def diagnostico(ids, abordagem=None, ler_etiquetas=False):
    """Le o quadro e devolve o diagnostico do projeto (sem efeito colateral: so LE).

    `ler_etiquetas`=True acrescenta os cartões marcados 🔎 Revisar. Desde 2026-07-30 isto
    e' quase gratis (as etiquetas vem na listagem — ver _cartoes_marcados); o padrao segue
    False porque quem manda no CONTEUDO do Status Report e' o Passo 8, nao o custo."""
    feitos = progresso.feitos()
    respostas = quadro.respostas_do_termo(ids)

    termo = [{"rotulo": r, "nome": nome, "obrigatorio": obr,
              "ok": bool((respostas.get(r) or "").strip())}
             for r, nome, obr in TERMO]
    faltam_obrig = [t for t in termo if t["obrigatorio"] and not t["ok"]]

    # Progresso das entregas: planejado (escopo) x em execucao x pronto.
    #  - AGIL: le as listas de fluxo (EXECUCAO/PRONTO).
    #  - PREDITIVA: nao ha listas de fluxo; o status vive no cartao. "Pronto" = cartao da
    #    EAP → Cronograma CONCLUIDO; "em execucao" = os demais (ainda pendentes).
    escopo = _cards_uteis(ids, "ESCOPO")
    if ids.get("PRONTO"):                        # AGIL
        execucao = _cards_uteis(ids, "EXECUCAO")
        prontos = _cards_uteis(ids, "PRONTO")
    else:                                         # PREDITIVA
        prontos = [c for c in escopo if quadro.card_concluido(c)]
        execucao = [c for c in escopo if not quadro.card_concluido(c)]
    total_plan = len(escopo)
    total_pronto = len(prontos)
    pct = round(100 * total_pronto / total_plan) if total_plan else 0

    base_data, tem_baseline = _baseline(ids)
    reports = _status_reports(ids)

    okr = verificar_okrs.avaliar(ids)

    # Auditoria de completude do PLANO (a mesma do Passo 5 — Passos 1 a 4): so' quando
    # ha' as listas de trabalho (etapa 2). Na etapa 1 nao ha escopo/riscos/orcamento.
    abordagem = abordagem or quadro.abordagem_do_env()
    achados = auditoria.auditar_plano(ids, abordagem) if "ESCOPO" in ids else []

    # Conferencias dos passos JA CONCLUIDOS alem do plano (6 a 10): cada passo confere o
    # seu proprio output; aqui o painel agrega o que os passos ja rodados deixaram em
    # aberto. So' os passos concluidos entram (nao acusamos um passo que o aluno nao rodou).
    achados_passos = []
    if "ESCOPO" in ids:
        for n in sorted(x for x in feitos if 6 <= x <= 10):
            ach = auditoria.checar(n, ids, abordagem)
            if ach:
                achados_passos.append((n, ach))

    proximo = next((n for n in range(1, 11) if n not in feitos), None)
    marcados = _cartoes_marcados(ids) if (ler_etiquetas and "ESCOPO" in ids) else []
    # Rótulos que uma versão anterior do curso escreveu em português num quadro em
    # inglês. Só LÊ aqui (o conserto é opt-in, no main); no pacote PT devolve [] sem
    # tocar no quadro — não há nada a consertar.
    antigos = rotulos_antigos.encontrar(ids) if ler_etiquetas else []
    return {
        "feitos": sorted(n for n in feitos if 1 <= n <= 10),
        "proximo": proximo,
        "abordagem": abordagem,
        "termo": termo, "faltam_obrig": faltam_obrig,
        "escopo": total_plan, "execucao": len(execucao), "prontos": total_pronto,
        "pct": pct, "tem_escopo": "ESCOPO" in ids,
        "baseline": base_data, "tem_baseline": tem_baseline,
        "reports": len(reports),
        "okr": okr,
        "auditoria": achados,
        "auditoria_passos": achados_passos,
        "marcados": marcados,
        "rotulos_antigos": antigos,
    }


def _linhas(d):
    L = []

    # 1) ONDE ESTOU — nos 10 passos ou nos 40 processos, conforme o curso
    L.append(Tf("1) ONDE VOCÊ ESTÁ NOS {0} {1}",
                cat.total(), cat.unidade(plural=True).upper()))
    if d["feitos"]:
        L.append(T("   Concluídos: ") + " ".join(f"✓{n}" for n in _nums(d["feitos"])))
    else:
        L.append(Tf("   Nenhum {0} registrado como concluído ainda.", cat.unidade()))
    agil = d.get("abordagem") == "AGIL"
    if d["proximo"]:
        L.append(Tf("   ▶ Próximo na sequência: {0}.", _rot(d['proximo'])))
    elif agil:
        # No agil, percorrer tudo nao e' o fim: o ultimo (a Retrospectiva) fecha a
        # Sprint e devolve o aluno ao planejamento dela. Dizer "acabou" mentiria para
        # quem esta na Sprint 2.
        L.append(Tf("   🔄 Você percorreu {0} — e no ágil eles se repetem:", _percorreu()))
        L.append(Tf("   {0} são o ciclo da Sprint.", _rots([5, 6, 7, 8, 9, 10])))
    else:
        L.append(Tf("   🏁 Você percorreu {0}.", _percorreu()))
    L.append(Tf("   Lembre: depois do {0}, a ordem é a que o projeto pedir.", _rot(1)))
    if agil:
        L.append(Tf("   {0} são a SPRINT e voltam a cada ciclo:", _rots([5, 6, 7, 8, 9, 10])))
        L.append(Tf("   Planning ({0}), Daily ({1}, todo dia útil), Review ({2}),",
                    _num(5), _num(7), _num(8)))
        L.append(Tf("   Retrospectiva ({0}) — e de volta ao {1}. O {2} (partes interessadas)",
                    _num(10), _num(5), _rot(6)))
        L.append(Tf("   e o {0} (mudanças) correm junto.", _rot(9)))
    else:
        L.append(Tf("   {0} você REPETE ao longo do projeto — monitorar e",
                    _rots([6, 7, 8, 9])))
        L.append(T("   controlar mudanças são contínuos, não um evento no fim (Guia PMBOK 8)."))
    L.append("")

    # 2) TERMO DE ABERTURA
    L.append(Tf("2) TERMO DE ABERTURA ({0}) — as 7 perguntas do 5W2H", _rot(1)))
    for t in d["termo"]:
        marca = "[OK]" if t["ok"] else ("[X]" if t["obrigatorio"] else "[ ]")
        extra = "" if t["obrigatorio"] else T("  (opcional)")
        L.append(f"   {marca} {t['rotulo']:<4} {t['nome']}{extra}")
    if d["faltam_obrig"]:
        L.append(Tf("   -> Responda os cartões marcados [X] no seu quadro e rode o {0}.",
                    _rot(1)))
    else:
        L.append(T("   Termo de abertura completo: o projeto está aprovado e alinhado."))
    L.append("")

    # 3) OKR — reaproveita o verificar_okrs
    L.append(T("3) O QUE VOCÊ PROMETEU — OKR"))
    L.append("   " + verificar_okrs.resumo(d["okr"]).replace("\n", "\n   "))
    L.append("")

    # 4) PRAZO E CUSTO: PLANEJADO x REALIZADO
    L.append(T("4) PRAZO E CUSTO — planejado x realizado"))
    if not d["tem_escopo"]:
        L.append(Tf("   O escopo ainda não existe (rode o {0}). Sem escopo não há o que",
                    _rot(2)))
        L.append(T("   medir de progresso de entregas."))
    else:
        L.append(Tf("   Entregas/pacotes no escopo: {0}  ·  em execução: {1}  ·  prontos: {2}  "
                    "({3}%)", d['escopo'], d['execucao'], d['prontos'], d['pct']))
    if d["tem_baseline"]:
        L.append(Tf("   📌 Baseline capturada no {0} ({1}). Toda mudança se mede contra ela "
                    "({2}).", _rot(5), d['baseline'], _rot(9)))
    else:
        L.append(T("   Baseline ainda NÃO capturada. Sem ela, 'atrasou' e 'estourou o custo'"))
        L.append(Tf("   não têm contra o quê comparar — rode o {0} para fotografar o plano.",
                    _rot(5)))
    L.append(Tf("   Status Reports emitidos ({0}): {1}.", _rot(8), d['reports']))
    L.append("")

    # 5) CONFERENCIAS DE COMPLETUDE (as mesmas de cada passo)
    if d["tem_escopo"]:
        ach = d.get("auditoria", [])
        lac, ate = auditoria.contagem(ach)
        L.append(Tf("5) CONFERÊNCIAS DE COMPLETUDE — o plano ({0})", _rots([1, 2, 3, 4])))
        if not ach:
            L.append(T("   ✅ Nenhuma lacuna mecânica: há entrega para cada KR, todo risco"))
            L.append(T("   está avaliado e tratado, e todo pacote tem data e responsável."))
        else:
            if lac:
                L.append(Tf("   ❌ {0} lacuna(s) a resolver antes de aprovar:", lac))
                L += [f"      ✗ {a['resumo']}" for a in ach if a["nivel"] == auditoria.LACUNA]
            if ate:
                L.append(Tf("   • {0} ponto(s) de atenção (não travam a aprovação):", ate))
                L += [f"      • {a['resumo']}" for a in ach if a["nivel"] == auditoria.ATENCAO]

        # As conferencias dos passos ja concluidos alem do plano (engajamento, execucao,
        # status report, mudancas, encerramento) — so' aparecem quando o passo foi rodado.
        for n, achn in d.get("auditoria_passos", []):
            L.append(Tf("   {0} ({1}):", _rot(n), auditoria.PASSO_ROTULO.get(n, '')))
            for a in achn:
                marca = "✗" if a["nivel"] == auditoria.LACUNA else "•"
                L.append(f"      {marca} {a['resumo']}")

        # Os cartões que carregam a etiqueta 🔎 Revisar HOJE (lidos do quadro). O passo os
        # marcou para você revisar; o link abre cada um em um clique. Um que já foi
        # corrigido continua marcado até você rodar de novo o passo que o marcou.
        marcados = d.get("marcados") or []
        if marcados:
            L.append(Tf("   🔎 {0} cartão(ões) marcado(s) 🔎 Revisar no seu quadro:", len(marcados)))
            for m in marcados:
                titulo = re.sub(r"\s+", " ", (m.get("titulo") or "")).strip()[:48]
                link = auditoria._links([m["id"]])
                L.append(f"      • {titulo}" + (f"  ↳ {link}" if link else ""))
            L.append(Tf("   Já corrigiu algum? Rode de novo este painel (ou o {0} que o",
                        cat.unidade()))
            L.append(T("   marcou): a etiqueta do que foi resolvido sai sozinha."))
        L.append("   " + auditoria.CONVITE)
        L.append("")

    # 6) RÓTULOS DE UMA VERSÃO ANTIGA (só no pacote em inglês — ver rotulos_antigos.py)
    # Não é uma lacuna do PLANO: é dívida do CURSO. Fica fora do bloco de conferências
    # de propósito — quem herdou um cartão com "Tipo:/Pacote:" não fez nada errado.
    antigos = d.get("rotulos_antigos") or []
    if antigos:
        L.append(T("6) RÓTULOS DE UMA VERSÃO ANTERIOR DO CURSO"))
        L += rotulos_antigos.relatar(antigos)
        L.append("")
    return L


def _acoes(d):
    a = []
    if d["faltam_obrig"]:
        faltas = ", ".join(t["rotulo"] for t in d["faltam_obrig"])
        a.append(Tf("Complete o Termo de Abertura: faltam os cartões {0} ({1}).",
                    faltas, _rot(1)))
    # As acoes do OKR (meta sem numero, entrega orfa...) vem prontas do verificar_okrs.
    a += verificar_okrs._acoes(d["okr"])
    if d["tem_escopo"] and not d["tem_baseline"]:
        a.append(Tf("Aprove o plano e capture a baseline ({0}) — é o marco zero para "
                    "medir prazo e custo depois.", _rot(5)))
    if d["tem_baseline"] and d["reports"] == 0:
        a.append(Tf("Você já tem baseline mas nenhum Status Report: rode o {0} para "
                    "comparar planejado x realizado e comunicar quem decide.", _rot(8)))
    if d["proximo"] and d["proximo"] not in PASSOS_RECORRENTES:
        a.append(Tf("Na sequência do curso, o próximo {0} novo é o {1}.",
                    cat.unidade(), _rot(d['proximo'])))
    return a


def relatorio(d):
    """O diagnostico em texto — o que o Passo 8 pode incluir no Status Report."""
    L = _linhas(d)
    acoes = _acoes(d)
    if acoes:
        L.append(T("O QUE FAZER AGORA"))
        L += [f"  - {x}" for x in acoes]
    else:
        L.append(Tf("Projeto saudável pelo que o quadro mostra: termo completo, OKR íntegro "
                    "e plano com baseline. Siga monitorando ({0}).", _rot(8)))
    return "\n".join(L).strip()


def resumo(d):
    """Uma linha: o estado do projeto — util no topo de um Status Report."""
    partes = [Tf("{0} de {1} {2}",
                 len(_nums(d["feitos"])), cat.total(), cat.unidade(plural=True))]
    if d["tem_escopo"]:
        partes.append(Tf("entregas {0}/{1} ({2}%)", d['prontos'], d['escopo'], d['pct']))
    partes.append(T("baseline ✓") if d["tem_baseline"] else T("sem baseline"))
    okr_pend = sum(1 for o in d["okr"]["objetivos"] for k in o["krs"] if k["faltas"])
    if okr_pend:
        partes.append(Tf("{0} KR(s) com pendência", okr_pend))
    return T("Projeto: ") + " · ".join(partes) + "."


def trazer_escolhas_dos_campos(ids, log=lambda _m: None):
    """Traz para o TEXTO do cartão o que você escolheu nos DROPDOWNS (Probabilidade,
    Impacto, Dono) e recalcula a Prioridade. Devolve quantos cartões mudaram.

    Por que o painel faz isto ANTES de auditar (Eduardo, 2026-07-27: "eu inclui a
    probabilidade e o impacto... ele ainda identificou 3 riscos sem avaliação"): a
    auditoria lê o TEXTO do cartão, mas desde que os campos personalizados existem o
    aluno edita o MENU. A ponte campo → texto (`campos.aplicar`) só rodava no Passo 3, no
    Passo 5 e no configurar_campos.py — então entre um passo e outro o painel auditava
    uma foto velha e cobrava o que já tinha sido respondido. Ler errado é pior do que
    escrever: isto não decide nada do plano, só TRANSCREVE a escolha que já é sua.

    Fail-safe: qualquer erro (sem campos no servidor, sem lista de riscos) → 0."""
    try:
        import campos
        cps = campos.garantir_campos_risco(ids, log=log)
        return campos.aplicar(ids, cps, log=log) if cps else 0
    except Exception as e:
        log(Tf("  [aviso] não consegui ler os seus campos personalizados agora: {0}", e))
        return 0


def marcar(ids, abordagem, d, log=print):
    """MARCA no quadro (etiqueta 🔎 Revisar) os cartões apontados pelas lacunas do PLANO —
    os Passos 1 a 4 de uma vez. Devolve (marcados, desmarcados, com_etiqueta) ou None.

    Por que existe (Eduardo, 2026-07-27: "acredito que seja importante nesse momento
    marcar os cartões identificados como Revisar"): o painel é o ÚNICO lugar que vê as
    lacunas dos quatro passos do plano juntas. Apontar 3 riscos sem avaliação e deixar o
    quadro sem marca nenhuma obriga o aluno a procurar cartão por cartão — a lista aqui
    cobra e o quadro não mostra onde.

    A BASE do escopo é a do Passo 5 (INPUTS · ESCOPO · RISCOS · ORÇAMENTO), que é o mesmo
    conjunto que `auditar_plano` confere. Somam-se as listas dos passos ALÉM do plano que
    este painel também auditou (6 → PARTES, 9 → MUDANÇAS): o painel MOSTRA o que falta
    neles, então tem de poder tirar a etiqueta deles também. Sem isso, um cartão de Partes
    marcado pelo Passo 6 só perdia a marca rodando o Passo 6 de novo — e o painel ficava
    listando para sempre uma etiqueta que ele mesmo dizia que cairia sozinha (Eduardo,
    quadro 324/FIRE, 06/08/2026). Como `marcar_no_quadro` RECONCILIA por lista (recomputa
    TODAS as conferências da lista), somar uma lista aqui não apaga a marca de outra etapa.

    Escrever é opt-in: `diagnostico()` segue só-leitura, porque o Passo 8 o usa para o
    Status Report — e emitir um relatório nunca pode mexer no quadro de quem o lê."""
    if not d.get("tem_escopo"):
        return None
    # Os passos JÁ CONCLUÍDOS além do plano — não os que por acaso acusaram algo agora.
    # Um passo que acabou de ficar limpo é justamente o que tem etiqueta velha a recolher.
    listas = list(auditoria.STEP_LISTAS[5])
    for n in (d.get("feitos") or []):
        if n < 6:
            continue
        for l in auditoria.STEP_LISTAS.get(n, ()):
            if l not in listas:
                listas.append(l)
    try:
        return auditoria.marcar_no_quadro(ids, abordagem, tuple(listas), log=log)
    except Exception as e:
        log(Tf("  [aviso] não consegui marcar os cartões agora: {0}", e))
        return None


def _quer_marcar(argv, tem_lacuna, tem_marca_velha=False):
    """--marcar / --sem-marcar (para rodar sem gente na frente); senão, pergunta.
    Enter = sim (é o que o aluno quer 9 vezes em 10).

    Pergunta quando há o que MARCAR — e também quando não há lacuna nenhuma mas o quadro
    ainda tem cartões com a etiqueta 🔎 Revisar. Esse segundo caso é o de quem acabou de
    consertar tudo: `marcar_no_quadro` RECONCILIA (marca e desmarca), mas antes ele nunca
    era chamado sem lacuna — então a etiqueta de um problema já resolvido ficava no quadro
    para sempre, e o painel a listava toda vez dizendo "a tag cai sozinha quando resolver"
    (Eduardo, quadro 324/FIRE, 06/08/2026)."""
    if "--sem-marcar" in argv:
        return False
    if "--marcar" in argv:
        return True
    if not tem_lacuna:
        if not tem_marca_velha:
            return False
        try:
            r = input(T("\n  Nada pendente no plano. Tiro a etiqueta 🔎 Revisar dos cartões "
                        "que sobraram marcados? [S/n] "))
        except (EOFError, KeyboardInterrupt):
            return False
        return not r.strip().lower().startswith("n")
    try:
        r = input(T("\n  Marco estes cartões com a etiqueta 🔎 Revisar no seu quadro? [S/n] "))
    except (EOFError, KeyboardInterrupt):
        return False
    return not r.strip().lower().startswith("n")


def _quer_consertar_rotulos(argv):
    """--rotulos / --sem-rotulos; senão, pergunta.

    O padrão aqui é NÃO (ao contrário da etiqueta, cujo padrão é sim): isto reescreve a
    descrição de cartões do aluno, e reescrever texto de alguém sem um sim explícito não
    é o nosso jeito de trabalhar — mesmo quando a troca é segura e reversível."""
    if "--sem-rotulos" in argv:
        return False
    if "--rotulos" in argv:
        return True
    try:
        r = input(T("\n  Troco esses rótulos pelos do curso em inglês? [s/N] "))
    except (EOFError, KeyboardInterrupt):
        return False
    return r.strip().lower() in ("s", "sim", "y", "yes")


def _quer_consertar_conformidade(argv):
    """--conformidade / --sem-conformidade; senão, pergunta. Enter = sim.

    O padrão é SIM porque nada aqui é texto seu: são linhas que a ATUALIZAÇÃO do curso
    deixou repetidas no seu .env (PROJECT ao lado de PROJETO). O painel mostra, linha por
    linha, o valor que fica e o que sai — e o conserto é uma edição de arquivo, que você
    pode desfazer."""
    if "--sem-conformidade" in argv:
        return False
    if "--conformidade" in argv:
        return True
    try:
        r = input(T("\n  Deixo um dado por linha no seu .env (mostrado acima)? [S/n] "))
    except (EOFError, KeyboardInterrupt):
        return False
    return not r.strip().lower().startswith("n")


def _quer_seguir_idioma_do_quadro(argv, alvo):
    """O quadro está numa língua e o pacote em outra: gravo a do QUADRO no .env?

    Padrão NÃO — de propósito. Dizer "não" mantém o comportamento de sempre (o curso
    renomeia as suas listas para o idioma do pacote, preservando id e cartões); dizer
    "sim" muda o idioma do curso inteiro, que é uma decisão maior do que a que o painel
    está autorizado a tomar por conta própria."""
    if "--sem-conformidade" in argv:
        return False
    if "--idioma-do-quadro" in argv:
        return True
    try:
        r = input(Tf("\n  Gravo LANGUAGE={0} no .env, para o curso seguir o idioma do seu "
                     "quadro? [s/N] ", alvo))
    except (EOFError, KeyboardInterrupt):
        return False
    return r.strip().lower() in ("s", "sim", "y", "yes")


def _conferir_conformidade(argv, c, abordagem):
    """Seção 0 do painel: o .env e as listas do quadro estão conformes?

    Roda ANTES de `garantir_listas` — e essa ordem é a razão de ser da seção. É ali que o
    curso monta/renomeia/limpa as listas; conferir depois seria conferir o que ele acabou
    de arrumar, e a pergunta sobre o idioma chegaria com as listas já renomeadas.

    Devolve (parar, mostrar_montagem):
      · parar — o idioma do .env mudou. Os textos desta execução já foram resolvidos no
        idioma antigo, então a resposta honesta é pedir para rodar de novo.
      · mostrar_montagem — há lista duplicada/perdida no quadro. Quem monta as listas é o
        `garantir_listas`, e é ELE que apaga a duplicada vazia; nesse caso o painel deixa
        o log dele aparecer. Apagar lista de alguém em silêncio, nunca."""
    from pa_client import list_lists
    try:
        listas = list_lists()
    except Exception:
        listas = None                    # sem rede: as conferências do .env ainda valem
    achados = conformidade.conferir(c, listas, abordagem)
    print(T("0) CONFORMIDADE DO PACOTE — o seu .env e as listas do seu quadro"))
    for linha in conformidade.relatar(achados):
        print(linha)
    print()
    mostrar = any(a["tipo"] in ("listas_duplicadas", "listas_perdidas") for a in achados)
    idioma_quadro = next((a for a in achados if a["tipo"] == "idioma_quadro"), None)
    if idioma_quadro and _quer_seguir_idioma_do_quadro(argv, idioma_quadro["dados"]):
        conformidade.consertar([idioma_quadro])
        return True, mostrar
    resto = [a for a in conformidade.consertavel(achados) if a is not idioma_quadro]
    if resto and _quer_consertar_conformidade(argv):
        conformidade.consertar(resto)
        print()
    return False, mostrar


def main(argv=None):
    import sys
    argv = list(sys.argv[1:] if argv is None else argv)
    c = cfg()
    require(c, "PROJECTADM_TOKEN", "BOARD_ID")
    # Funciona nas duas etapas: sem abordagem (etapa 1) le so as 3 listas do 5W2H; com
    # abordagem, o quadro inteiro. O painel dos 10 passos vale desde o Passo 1.
    abordagem = quadro.abordagem_do_env() or quadro.abordagem_do_quadro(c)
    # nome_abordagem() e' o nome NA TELA: o .env guarda sempre a chave interna
    # (PREDITIVA/AGIL), em qualquer idioma — imprimir a chave crua vaza portugues
    # no pacote em ingles ("(PREDITIVA)" em vez de "(PREDICTIVE)").
    # O NOME do curso vem do catalogo: o mesmo painel abre o Project Together e o
    # PMBOK 8 Together, e cada aluno tem de ver o nome do curso que ele comprou.
    header(Tf("{0} — Saúde do Projeto", cat.nome_curso())
           + (f"  ({prompts.nome_abordagem(abordagem)})" if abordagem
              else T("  (abordagem ainda não escolhida)")))
    print()
    parar, mostrar_montagem = _conferir_conformidade(argv, c, abordagem)
    if parar:
        print(T("   Rode este painel de novo — agora no idioma do seu quadro."))
        return
    if mostrar_montagem:
        print(T("   Arrumando as listas do quadro:"))
    ids = quadro.garantir_listas(abordagem, log=print if mostrar_montagem else (lambda _m: None))
    if mostrar_montagem:
        print()
    # ANTES de auditar: traz o que você escolheu nos dropdowns para o texto (senão o
    # painel cobra o que você já respondeu). Silencioso quando não há nada a trazer.
    trazidos = trazer_escolhas_dos_campos(ids)
    if trazidos:
        print(Tf('  [OK] {0} cartão(ões) atualizado(s) com o que você escolheu nos menus '
                 '(Probabilidade, Impacto, Dono) — a Prioridade foi recalculada.\n', trazidos))
    d = diagnostico(ids, ler_etiquetas=True)
    print(relatorio(d))

    # Marcar no quadro o que este painel acabou de apontar (com a sua confirmação).
    apontados = {str(x) for a in d.get("auditoria", []) for x in (a.get("cartoes") or [])}
    if _quer_marcar(argv, bool(apontados), bool(d.get("marcados"))):
        res = marcar(ids, abordagem, d)
        if res:
            marc, limpo, com_marca = res
            extra = [Tf("{0} agora", marc)] if marc else []
            if limpo:
                extra.append(Tf("{0} já resolvido(s) desmarcado(s)", limpo))
            if com_marca:
                detalhe = (Tf(" ({0})", ' · '.join(extra)) if extra
                           else T(" (já estavam marcados)"))
                print(Tf('\n   [OK] {0} cartão(ões) com a etiqueta 🔎 Revisar{1} — filtre o '
                         'quadro por ela e vá direto ao que falta.', com_marca, detalhe))
            elif limpo:
                print(Tf('\n   [OK] {0} cartão(ões) resolvido(s): tirei a etiqueta 🔎 Revisar. '
                         'Nada pendente no plano.', limpo))
            else:
                # Sem cartão marcado: ou não há lacuna, ou a lacuna que sobrou não mora
                # em cartão nenhum (ex.: "o orçamento ainda não existe" — falta a lista
                # inteira). Dizer isso evita o silêncio que parece "não marquei".
                print(T("\n  [OK] Nenhum cartão a revisar no plano. O que falta (se falta) "
                      "não está num cartão — está na lista de ações acima."))

    # Consertar os rótulos que uma versão anterior do curso deixou em português (só no
    # pacote em inglês). Opt-in, como a etiqueta: o painel mostra o antes/depois e só
    # grava com o seu "s". --rotulos / --sem-rotulos para rodar sem ninguém na frente.
    antigos = d.get("rotulos_antigos") or []
    if antigos and _quer_consertar_rotulos(argv):
        n = rotulos_antigos.consertar(antigos)
        print(Tf("\n   [OK] {0} cartão(ões) com os rótulos no idioma do curso. O que você "
                 "e a sua IA escreveram não mudou.", n))
    print()
    print("=" * 62)
    print(T("O valor REAL de cada KR (o número que aconteceu no mundo) é você quem mede,"))
    print(Tf("no {0} e depois dele. Aqui o quadro responde pelo que ele sabe: por onde",
             _rot(10)))
    print(T("você anda, se o plano ainda alcança o que prometeu, e quanto já saiu."))
    print("=" * 62)


if __name__ == "__main__":
    main()
