# -*- coding: utf-8 -*-
"""PMBOK 8 Together — o runner dos 40 PROCESSOS.

PROCESSO, NAO "ETAPA" (Eduardo, 14/08/2026)
    Sao dois cursos, e a palavra separa um do outro:

        Project Together  = 10 PASSOS     — um passo do METODO, que agrupa varios
                                            processos do Guia (o passo 6 cobre quatro).
        PMBOK 8 Together  = 40 PROCESSOS  — cada um E' um processo do Guia PMBOK 8,
                                            com o codigo dele e as saidas que o Guia
                                            lhe atribui.

    Chamar os dois de "etapa" apagava a diferenca que separa os produtos. A palavra nao
    esta escrita a mao aqui: ela vem do catalogo (`unidade`), pelo `cat.rotulo_unidade`.

A DIFERENCA PARA OS 10 PASSOS
    Os 10 passos sao 10 SCRIPTS (01_iniciar_projeto.py ... 10_encerrar.py): cada um sabe,
    no proprio codigo, o que le' e o que escreve. Para 40 isso nao escala, e nem precisa:
    o catalogo ja' diz quais sao as saidas de cada processo e que gerador as produz.

    Entao aqui nao ha' 40 scripts — ha' UM, dirigido por dado. Um processo novo custa uma
    linha no catalogo, nao um arquivo.

QUEM ESTA RODANDO — e por que isto importa
    Antes de qualquer coisa, este runner DECLARA o curso e o processo da vez
    (`cat.definir_curriculo` / `cat.definir_unidade`). A declaracao vai pelo AMBIENTE, e
    o ambiente atravessa o subprocesso: e' assim que o `01_iniciar_projeto.py` — que serve
    aos DOIS cursos — sabe que foi chamado pelo PMBOK 8 Together, e no processo 2 entrega
    o complemento das partes interessadas em vez do proximo elo do Termo de Abertura.

USO
    py processo.py            mostra os 40 processos e o estado de cada um
    py processo.py 4          roda o processo 4
    py processo.py 1-6        roda do 1 ao 6, na ordem
"""
import os
import subprocess
import sys

import catalogo_pmbok8 as cat
import quadro
from env import cfg
from idioma import T, Tf

CURRICULO = cat.CURRICULO_40
HERE = os.path.dirname(os.path.abspath(__file__))

# A PRIMEIRA COISA: dizer quem esta rodando. Tudo o que vier depois — o rotulo na tela, o
# carimbo dentro do documento, o script chamado em subprocesso — le' esta declaracao.
cat.definir_curriculo(CURRICULO)

TOTAL = cat.total(CURRICULO)


def _rotulo(n):
    """"Processo 7" — a palavra vem do catalogo, nunca escrita a mao."""
    return cat.rotulo_unidade(n, CURRICULO)


def _ids():
    """Os IDs de lista que o processo 1 ja' gravou no .env.

    NAO chamamos `garantir_listas` aqui, pelo mesmo motivo de `gerar_documentos.py`:
    rodar um processo nao pode reestruturar o quadro de ninguem."""
    c = cfg()
    ids = {k[5:]: int(v) for k, v in c.items()
           if k.startswith("LIST_") and str(v).strip().isdigit()}
    if "INPUTS" not in ids:
        print(Tf("\nAinda não encontrei o seu quadro. Rode o {0} primeiro — ele monta",
                 _rotulo(1)))
        print(T("o quadro e escreve o seu Termo de Abertura."))
        raise SystemExit(0)
    return ids


def _fn(nome):
    import artefatos
    return getattr(artefatos, nome, None)


def _estado(spec):
    """O que se pode dizer desta saida hoje, sem abrir o quadro."""
    if spec.get("gerador"):
        return "OK", T("gera")
    if not spec.get("gerar_v1"):
        return "--", T("adiado (abordagem ágil, fora da v1)")
    if spec.get("falta"):
        return "!!", Tf("sem fonte: {0}", spec["falta"])
    if spec.get("confirmar"):
        return "..", Tf("a construir — deriva do Termo; você confirma: {0}", spec["confirmar"])
    return "..", T("a construir — a fonte já está no quadro")


# ─────────────────────────────────────────────────────────────────────────────
# OS PROCESSOS QUE NAO CRIAM DOCUMENTO NOVO (Eduardo, 15/08/2026)
#
#     "Se nao houver saidas no processo, traga as orientacoes necessarias para o
#      processo ou uma alternativa para passar pelo processo."
#
# Tres dos 40 nao tem saida nomeada no Guia — a coluna Saida da aba `Process` lista, para
# eles, so' "Atualizacoes de...". Ate' aqui o runner imprimia uma frase generica e voltava
# de maos vazias: o aluno rodava o processo, nao recebia arquivo nenhum e concluia que o
# curso tinha quebrado. E' a armadilha que a analise previa em §8.2 — e ela custa mais
# caro num curso de 40 processos, onde o aluno passa por ela tres vezes.
#
# A resposta e' a promessa do curso inteira, nao a metade dela: "a cada processo, um
# documento NOVO — ou a versao nova de um documento que voce ja' tem". Aqui sai a segunda
# metade. QUAL documento ganha versao nova esta' no catalogo (`unidades[n].atualiza`);
# as FRASES ficam aqui, porque texto do aluno passa por T() e o extrator so' acha aqui.
_ORIENTACAO = {
    15: (T("Planejar o gerenciamento das comunicações não gera um plano separado: o Guia "
           "manda o resultado dele para DENTRO do plano de gerenciamento do projeto."),
         T("Responda no quadro, para cada parte interessada: o que ela precisa saber, com "
           "que frequência e por qual canal. As ferramentas que o Guia dá para isto são a "
           "análise de requisitos da comunicação, os métodos e as tecnologias de "
           "comunicação e os modelos de comunicação.")),
    18: (T("Realizar Análise de Riscos não cria registro novo: ele PREENCHE o Registro dos "
           "Riscos que você já tem — probabilidade, impacto e a prioridade que sai dos "
           "dois."),
         T("Rode o script deste processo e responda a probabilidade e o impacto de cada "
           "risco. O quadro calcula a prioridade (probabilidade × impacto) e grava no "
           "cartão. As ferramentas do Guia aqui são a categorização dos riscos, a coleta e "
           "a análise de dados.")),
    39: (T("Avaliar e Implementar Mudanças entrega DECISÃO, não documento novo: as "
           "solicitações aprovadas e a nova linha de base dos planos que elas mudaram."),
         T("Registre cada solicitação na lista Mudanças do quadro com o impacto ESCRITO "
           "antes da decisão, e marque o que foi aprovado ou rejeitado. O Guia chama as "
           "ferramentas deste processo de ferramentas de controle de mudanças, tomada de "
           "decisão e — na abordagem adaptativa — gestão do backlog.")),
}


def _sem_saida_nova(n, e, ids, abordagem, todos):
    """O processo que so' ATUALIZA: a orientação + a versão nova do documento.

    Devolve a mesma coisa que `rodar()`: [(saida, caminho)] — para o aluno, um processo
    que atualiza e um que cria terminam igual, com arquivo na pasta."""
    o_que, como = _ORIENTACAO.get(int(n), ("", ""))
    print(T("\n  ESTE PROCESSO NÃO CRIA DOCUMENTO NOVO — ele gera a versão nova de um"))
    print(T("  documento que você já tem. É assim no Guia, e é assim que a elaboração"))
    print(T("  progressiva aparece: no mesmo arquivo, uma versão adiante.\n"))
    if o_que:
        print("  " + o_que + "\n")
        print(T("  O QUE FAZER:"))
        print("  " + como + "\n")

    alvos = e.get("atualiza") or []
    if not alvos:
        print(Tf("  Rode os {0} que alimentam este documento e volte aqui.",
                 cat.unidade(CURRICULO, plural=True)))
        return []

    print(T("  Gerando a versão nova de:"))
    gerados, feitos = [], set()
    for nome in alvos:
        spec = todos.get(nome) or {}
        fn_nome = spec.get("gerador")
        if not fn_nome or fn_nome in feitos:
            continue
        feitos.add(fn_nome)
        fn = _fn(fn_nome)
        if not fn:
            print(Tf("  [ERRO] o catálogo aponta {0}(), que não existe.", fn_nome))
            continue
        try:
            saida, _av = fn(ids, abordagem=abordagem, log=lambda m: print("   " + m))
        except Exception as exc:
            print(f"\n  [ERRO] {nome}: {exc}")
            continue
        if not saida:
            print(Tf("\n  [--] {0}: ainda não há dado no quadro para gerar.", nome))
            continue
        for caminho in ([saida] if isinstance(saida, str) else list(saida)):
            gerados.append((nome, caminho))
            print(f"\n  [OK] {nome}")
            print(f"       {caminho}")
    if gerados:
        print(T("\n  Abra o arquivo e compare com a versão anterior: é ali que você vê o"))
        print(T("  que este processo acrescentou ao seu projeto."))
    return gerados


def listar():
    processos = cat.unidades(CURRICULO)
    todos = cat.carregar()["artefatos"]
    print("=" * 78)
    print(Tf("  {0} — os {1} {2}", cat.nome_curso(CURRICULO), TOTAL,
             cat.unidade(CURRICULO, plural=True)))
    print("=" * 78)
    area_ant = None
    for n in sorted(processos, key=int):
        e = processos[n]
        if e.get("area_foco") != area_ant:
            area_ant = e.get("area_foco")
            print(f"\n── {area_ant} " + "─" * max(0, 60 - len(str(area_ant))))
        saidas = e.get("artefatos") or []
        marca = "OK" if e.get("geradores") else ("--" if not saidas else "..")
        print(f"  [{marca}] {int(n):>2}. {e['rotulo'][:44]:46} {e.get('dominio','')}")
        for nome in saidas:
            st, txt = _estado(todos[nome])
            print(f"          [{st}] {nome[:40]:42} {txt[:44]}")
        if not saidas:
            alvos = e.get("atualiza") or []
            print(f"          {T('(nenhum documento novo — este processo gera uma versão nova)')}")
            for nome in alvos:
                print(f"          [↺] {nome[:40]:42} {T('versão nova')}")


def rodar(n):
    e = cat.unidade_do(n, CURRICULO)
    if not e:
        print(Tf("{0} não existe (são 1 a {1}).", _rotulo(n), TOTAL))
        return []
    # O processo da vez, declarado ANTES de rodar qualquer coisa: e' o que faz o script
    # chamado em subprocesso saber onde ele esta, e o documento sair carimbado certo.
    cat.definir_unidade(n)
    todos = cat.carregar()["artefatos"]
    print("\n" + "=" * 78)
    print(Tf("  {0} de {1} — {2}", _rotulo(n), TOTAL, e["rotulo"]))
    print(Tf("  Domínio: {0} · Área de foco: {1}", e.get("dominio"), e.get("area_foco")))
    print("=" * 78)

    # 1) O SCRIPT DO PROCESSO — quem monta o quadro e conversa com você.
    #    UM GERADOR NAO PERGUNTA NADA: ele lê o quadro e escreve o documento. Sem rodar
    #    o script antes, o processo 1 tentava gerar o Termo de Abertura de um quadro que
    #    ainda não existia — nada perguntava o nome do projeto e não havia de onde tirar
    #    o documento. (Eduardo, ao rodar o pacote 12578.)
    script = e.get("script")
    if script:
        caminho = os.path.join(HERE, script)
        if os.path.exists(caminho):
            print(T("\n  Este processo monta (ou atualiza) o seu quadro antes de escrever."))
            print(Tf("  >>> {0}\n", script) + "-" * 62)
            subprocess.run([sys.executable, caminho], cwd=HERE)
            print("-" * 62)
        else:
            print(Tf("\n  [!] {0} não veio neste pacote — rode a opção 0 do menu "
                     "(Atualizar a versão do curso).", script))

    ids = _ids()
    abordagem = quadro.abordagem_do_env() or "PREDITIVA"

    saidas = e.get("artefatos") or []
    if not saidas:
        return _sem_saida_nova(n, e, ids, abordagem, todos)
    gerados, feitos = [], set()
    for nome in saidas:
        spec = todos[nome]
        st, txt = _estado(spec)
        if st != "OK":
            print(f"\n  [{st}] {nome}")
            print(f"        {txt}")
            continue
        fn_nome = spec["gerador"]
        if fn_nome in feitos:               # um gerador pode emitir 2 saidas (EAP+Dicionario)
            continue
        feitos.add(fn_nome)
        fn = _fn(fn_nome)
        if not fn:
            print(Tf("  [ERRO] o catálogo aponta {0}(), que não existe.", fn_nome))
            continue
        try:
            saida, _av = fn(ids, abordagem=abordagem, log=lambda m: print("   " + m))
        except Exception as exc:
            print(f"\n  [ERRO] {nome}: {exc}")
            continue
        if not saida:
            print(Tf("\n  [--] {0}: ainda não há dado no quadro para gerar.", nome))
            continue
        for caminho in ([saida] if isinstance(saida, str) else list(saida)):
            gerados.append((nome, caminho))
            print(f"\n  [OK] {nome}")
            print(f"       {caminho}")
    return gerados


def main():
    arg = (sys.argv[1] if len(sys.argv) > 1 else "").strip()
    if not arg:
        listar()
        return
    if "-" in arg:
        ini, fim = (int(x) for x in arg.split("-", 1))
        alvos = range(ini, fim + 1)
    else:
        alvos = [int(arg)]
    total = []
    for n in alvos:
        total += rodar(n)
    print("\n" + "=" * 78)
    print(Tf("  {0} documento(s) em _saida/documentos/", len(total)))
    print("=" * 78)
    for nome, caminho in total:
        print(f"   · {nome}")


if __name__ == "__main__":
    main()
