"""
Project Together — powered by AI: RESET — refazer o projeto a partir de um passo.

Volta o seu quadro ao INÍCIO de um passo, para você refazer dali (útil para testar uma
sequência de novo, ou porque mudou de ideia). Apaga só as SAÍDAS dos passos N..10 —
o Passo 1 (termo de abertura, OKR e partes interessadas) e a sua trilha do curso até o
passo N-1 ficam INTACTOS. Depois do reset, rode o Passo N: como a Caixa de entrada e o
progresso dele ficam limpos, ele já te entrega o prompt novo.

O PASSO 1 É ESPECIAL — ele tem SUB-PASSOS (os 6 elos da corrente do termo de abertura:
OKR, entregas, partes, abordagem, restrições/premissas/riscos, quando/quanto). Por isso
o reset do Passo 1 aceita escolher QUAIS sub-passos refazer — em vez de só apagar uma
lista, ele DEVOLVE o texto-guia aos cartões escolhidos (eles voltam a "não respondido") e
reabre aquele(s) elo(s) na corrente. Útil quando você reaproveitou um quadro e ficou lixo
do projeto anterior num sub-passo (Eduardo, board 266, 2026-07-24).

Uso:
    py reset.py               # pergunta a partir de qual passo
    py reset.py 2             # reseta a partir do Passo 2 (refaz escopo -> riscos -> cronograma...)
    py reset.py 4 --dry-run   # só MOSTRA o que seria apagado (não apaga nada)
    py reset.py 1             # reabre o Passo 1 INTEIRO (as 7 perguntas do 5W2H)
    py reset.py 1 partes      # reabre SÓ um sub-passo (aqui, as partes interessadas)
    py reset.py 1 3 5 6       # reabre os elos 3, 5 e 6 (partes · restrições/... · quando/quanto)
    py reset.py 1 --dry-run   # simula

Segurança: SEMPRE mostra antes o que vai apagar (uma simulação) e só apaga depois que
você digitar o número do passo para confirmar. É read-only até a confirmação.
"""
import re
import sys

import catalogo_pmbok8 as cat
import contexto
import entradas
import prompts
import quadro
from env import cfg, require
from pa_client import (list_lists, find_list_id_by_name, list_cards, delete_card,
                       update_card, get_card, unassign_member, list_dependencies,
                       delete_dependency, api, limpar_cache)
from runner import done, header, step
from idioma import T, Tf

# Cada passo, ao ser resetado, apaga os cartões da SUA lista dedicada.
_LISTA_DO_PASSO = {2: "ESCOPO", 3: "RISCOS", 4: "ORCAMENTO", 7: "EXECUCAO", 9: "MUDANCAS"}
_ROTULO_LISTA = {"ESCOPO": T("escopo (EAP/Backlog)"), "RISCOS": T("riscos e premissas"),
                 "ORCAMENTO": T("itens de orçamento"), "EXECUCAO": T("cartões em execução"),
                 "MUDANCAS": T("mudanças")}
# Os dois rotulos de BASTIDOR do resumo: nao sao o quadro do aluno (a trilha do curso e
# a Caixa de entrada). Quem so' quer mostrar o que sai do QUADRO filtra por estes —
# comparar por pedaco de texto ("trilha" in rotulo) quebra assim que traduz.
_ROT_TRILHA = T("passos removidos da trilha")
_ROT_CAIXA = T("blocos [P] tirados da Caixa")
ROTULOS_DE_BASTIDOR = (_ROT_TRILHA, _ROT_CAIXA)
# Cartões que NUNCA se apaga (guias e cartões de grupo das Partes).
_PROTEGIDOS = quadro.PROTEGIDOS_PARTES + (T("0. Caixa de entrada"),)
# A linha de metadados que o Passo 4 escreve na descrição do cartão da EAP.
_META_P4 = re.compile(r"(dura[cç][aã]o estimada:|depende de:|respons[aá]vel:|Sprint\s+\d)", re.I)

# O que a API recusou durante a limpeza. Um reset que falha em silêncio é pior do que um
# reset que não roda: o aluno acha que o quadro está limpo e ele não está.
_falhas = []


def _ids_do_quadro(abordagem):
    """Mapeia {chave -> list_id} do quadro SEM criar/renomear nada (só leitura), ao
    contrário de garantir_listas. O reset não deve reestruturar o quadro."""
    listas = list_lists()
    ids = {}
    for chave, titulo in quadro.listas_do_quadro(abordagem):
        lid = find_list_id_by_name(titulo, listas)
        if lid:
            ids[chave] = lid
    return ids


def _uteis(lid):
    return [c for c in list_cards(lid)
            if (c.get("titulo") or "").strip()
            and not (c.get("titulo") or "").startswith(_PROTEGIDOS)]


def _apagar_lista(ids, chave, dry):
    lid = ids.get(chave)
    if not lid:
        return 0
    cs = _uteis(lid)
    if not dry:
        for c in cs:
            delete_card(c["id"])
    return len(cs)


def _apagar_tarefas_exec(ids, dry):
    """Apaga as TAREFAS de execucao do Passo 7 (subtarefas dos pacotes, marcadas na
    EAP → Cronograma) — SEM tocar nos pacotes/entregas da EAP (caso preditivo)."""
    lid = ids.get("ESCOPO")
    if not lid:
        return 0
    marcadas = [c for c in list_cards(lid)
                if quadro.MARCA_EXEC_P7 in (c.get("descricao") or "")]
    if not dry:
        for c in marcadas:
            delete_card(c["id"])
    return len(marcadas)


def _limpar_p4_na_eap(ids, dry):
    """Tira do cartão da EAP o que o Passo 4 escreveu — datas, dependências, membros
    (responsável) e a linha de metadados — SEM apagar o cartão (mantém a EAP do Passo 2).
    Usado quando o reset é para o Passo 3 ou 4 (que preservam a EAP)."""
    lid = ids.get("ESCOPO")
    if not lid:
        return 0
    n = 0
    for c in _uteis(lid):
        if quadro.MARCA_EXEC_P7 in (c.get("descricao") or ""):
            continue                            # tarefa de execucao (P7): nao e' dado do P4
        full = get_card(c["id"])
        preds = list_dependencies(c["id"]).get("predecessores") or []
        membros = full.get("membros") or []
        base = entradas.texto_do_cartao(full.get("descricao", ""))
        limpa = "\n".join(l for l in base.splitlines() if not _META_P4.search(l)).strip()
        sujo = bool(full.get("data_inicio") or full.get("data_vencimento")
                    or membros or preds or limpa != base.strip())
        if not sujo:
            continue
        n += 1
        if dry:
            continue
        for m in membros:
            try:
                unassign_member(c["id"], m.get("id"))
            except Exception:
                pass
        for d in preds:                       # best-effort (DELETE de dependência)
            if d.get("id") and not delete_dependency(d["id"]):
                _falhas.append(f"dependência de \"{(c.get('titulo') or '')[:30]}\"")
        # datas: null direto (update_card ignora None; "" dá erro na API)
        # O PUT vai dentro de try: sem isto, UM cartão que a API recusa (descrição longa
        # demais, por exemplo) levantava no meio do laço e o reset parava ali — os cartões
        # seguintes ficavam com os dados do Passo 4 e o aluno via "vários itens não foram
        # excluídos", sem nenhuma mensagem de erro (Eduardo, 2026-07-29).
        try:
            api("PUT", f"/cartoes/{c['id']}",
                {"data_inicio": None, "data_vencimento": None, "descricao": limpa})
        except Exception as e:
            n -= 1
            _falhas.append(f"\"{(c.get('titulo') or '')[:30]}\": {e}")
    return n


def _apagar_novos_p4(ids, dry):
    """APAGA os cartões da EAP que o PRÓPRIO Passo 4 criou — o trabalho que apareceu ao
    estimar (marca `MARCA_NOVO_P4`) e, nas rodadas antigas, os "Marco/entrega do
    cronograma" que nasciam soltos na raiz.

    Existe porque o `_limpar_p4_na_eap` só LIMPA CAMPOS de cartões que já existiam: ele
    nunca apaga cartão, para não destruir a EAP do Passo 2. Só que o Passo 4 também
    CRIA cartão — e esses sobreviviam a um reset do Passo 4, que é justamente o que
    deveria fazê-los sumir (Eduardo, 2026-07-29: "vários itens do passo 4 não foram
    excluídos"). Só apaga o que tem a NOSSA marca: cartão que você escreveu à mão fica."""
    lid = ids.get("ESCOPO")
    if not lid:
        return 0
    alvo = []
    for c in list_cards(lid):
        desc = c.get("descricao") or ""
        if quadro.MARCA_NOVO_P4 in desc or "Marco/entrega do cronograma" in desc:
            alvo.append(c)
    if not dry:
        for c in alvo:
            delete_card(c["id"])
    return len(alvo)


def _limpar_baseline(ids, dry):
    """Tira da trilha o cartão 📌 da LINHA DE BASE que o Passo 5 grava.

    O reset desfaz o que é do CURSO: o cartão 📌 e o Passo 5 na trilha, para o quadro
    voltar a dizer a verdade (o plano ainda não foi aprovado). A LINHA DE BASE congelada no
    ProjectAdm é outra coisa, e descartá-la é uma DECISÃO DO LÍDER do projeto — não um
    efeito colateral de um reset (Eduardo, 2026-07-29). Por isso ela não some aqui: o reset
    OFERECE o descarte logo depois (_oferecer_descarte), que desde 30/07 acontece no
    próprio ProjectAdm (com quem decidiu, quando e por quê na trilha dele). O registro de
    uma decisão de descarte (🗄️) nunca é apagado."""
    try:
        import baseline
        return baseline.limpar_cartao_do_curso(ids, dry=dry)
    except Exception:
        pass
    lid = ids.get("CONCLUIDO")
    if not lid:
        return 0
    n = 0
    for c in list_cards(lid):
        t = entradas._sem_acento(c.get("titulo") or "")
        if "linha de base" in t or "baseline" in t:
            n += 1
            if not dry:
                delete_card(c["id"])
    return n


def _oferecer_descarte(ids, alvo):
    """Depois de um reset que derrubou a aprovação do plano, pergunta ao LÍDER se ele
    quer descartar também a linha de base congelada no ProjectAdm.

    É a resposta ao "não é verdade que eu não posso excluir a baseline": o curso não
    decide por ele — mostra as linhas de base, deixa escolher, e registra a decisão.
    Nunca derruba o reset."""
    if 5 not in alvo:
        return
    try:
        import baseline
        if not baseline.listar():
            return
        print(T("\n  Você acabou de tirar a aprovação do plano — mas a LINHA DE BASE que o"))
        print(T("  Passo 5 congelou continua no ProjectAdm, e é contra ela que o Passo 8 e o"))
        print(T("  Passo 9 medem. Guardá-la é boa prática; descartá-la é decisão do líder."))
        try:
            resp = input(T("  Quer ver as linhas de base e descartar alguma? [s/N] ")).strip().lower()
        except (EOFError, KeyboardInterrupt):
            return
        if resp in ("s", "sim", "y", "yes"):
            baseline.escolher_e_descartar(
                ids, motivo_padrao="Reset do plano: a linha de base descreve um plano que "
                                   "não existe mais.")
    except Exception as e:
        step(Tf("[aviso] não consegui abrir as linhas de base agora: {0}", e))


def _limpar_engajamento(ids, dry):
    """Tira o 'Plano de engajamento (Passo 6)' da descrição dos cartões de Partes."""
    lid = ids.get("PARTES")
    if not lid:
        return 0
    n = 0
    for c in list_cards(lid):
        if (c.get("titulo") or "").startswith(_PROTEGIDOS):
            continue
        base = entradas.texto_do_cartao(c.get("descricao", ""))
        limpa = base.split("── Plano de engajamento")[0].rstrip()
        if limpa != base.strip():
            n += 1
            if not dry:
                update_card(c["id"], descricao=limpa)
    return n


def _limpar_dod(ids, dry):
    """Remove a Definição de Pronto (DoD) que o Passo 5 grava na trilha."""
    lid = ids.get("CONCLUIDO")
    if not lid:
        return 0
    n = 0
    for c in list_cards(lid):
        t = entradas._sem_acento(c.get("titulo") or "")
        if "definicao de pronto" in t or "dod" in t.split():
            n += 1
            if not dry:
                delete_card(c["id"])
    return n


def _limpar_progresso(ids, passos, dry):
    """Tira da trilha 'Concluído (curso)' os cartões 'Passo P —' dos passos resetados."""
    lid = ids.get("CONCLUIDO")
    if not lid:
        return 0
    n = 0
    for c in list_cards(lid):
        t = (c.get("titulo") or "")
        if any(t.startswith(f"Passo {p} ") or t.startswith(f"Passo {p}—") for p in passos):
            n += 1
            if not dry:
                delete_card(c["id"])
    return n


def _limpar_caixa(ids, passos, dry):
    """Tira da Caixa de entrada os blocos [PN]..[P10] que sobraram (o resto fica)."""
    presentes = [r for r in quadro.blocos_da_caixa(ids) if r in {f"P{p}" for p in passos}]
    if presentes and not dry:
        quadro.consumir_blocos(ids, presentes, log=lambda _m: None)
    return len(presentes)


def _executar(ids, alvo, dry):
    """Roda as ações de reset para os passos em `alvo`. Devolve {rótulo: quantidade}."""
    r = {}
    if 2 in alvo:                              # apaga a EAP inteira (leva junto os dados do P4)
        r[T("EAP (escopo) apagada")] = _apagar_lista(ids, "ESCOPO", dry)
    elif 4 in alvo:                            # P3/P4: mantém a EAP, tira só o que o P4 escreveu nela
        r[T("dados do Passo 4 limpos na EAP")] = _limpar_p4_na_eap(ids, dry)
        r[T("pacotes que o Passo 4 tinha criado, apagados")] = _apagar_novos_p4(ids, dry)
    for p in (3, 4, 9):                        # cada um apaga a sua lista dedicada
        if p in alvo:
            chave = _LISTA_DO_PASSO[p]
            r[Tf("{0} apagado(s)", _ROTULO_LISTA[chave])] = _apagar_lista(ids, chave, dry)
    if 7 in alvo:
        # Passo 7 (execucao): AGIL = a lista "Sprint Backlog"; PREDITIVA = as tarefas
        # (subtarefas dos pacotes) marcadas na EAP → Cronograma. So' uma das duas rende.
        q = _apagar_lista(ids, "EXECUCAO", dry)
        if 2 not in alvo:                       # se 2 entra, o ESCOPO inteiro ja vai junto
            q += _apagar_tarefas_exec(ids, dry)
        r[T("tarefas de execução apagadas")] = q
    if 5 in alvo:
        r[T("Definição de Pronto removida")] = _limpar_dod(ids, dry)
        r[T("cartão 📌 da linha de base removido")] = _limpar_baseline(ids, dry)
    if 6 in alvo:
        r[T("planos de engajamento limpos")] = _limpar_engajamento(ids, dry)
    r[_ROT_TRILHA] = _limpar_progresso(ids, alvo, dry)
    r[_ROT_CAIXA] = _limpar_caixa(ids, alvo, dry)
    return r


def avisos_pendentes():
    """O que a API recusou limpar na última passada (e zera a lista). Quem chama o
    `limpar_passo` de fora tem de mostrar isto: senão o aluno acha que limpou tudo."""
    falhas = list(_falhas)
    _falhas.clear()
    return falhas


def limpar_passo(ids, n, dry=False):
    """Apaga as saídas de UM passo — só dele, sem tocar nos seguintes. Devolve
    {rótulo: quantidade} (com `dry=True` só simula: nada é alterado).

    É o que o próprio passo usa quando você o roda DE NOVO, para perguntar antes se quer
    começar limpo (`passos.confirmar_regeracao`). O `reset.py N` da linha de comando
    continua sendo o irmão mais radical: aquele refaz de N até o 10."""
    return _executar(ids, [n], dry)


def _mostrar(res):
    for rotulo, qtd in res.items():
        step(f"{qtd}  {rotulo}")
    if _falhas:
        print(Tf('\n   [!] {0} coisa(s) o ProjectAdm não deixou limpar — continuam no seu '
                 'quadro:', len(_falhas)))
        for f in _falhas[:6]:
            print(f"        · {f}")
        print(T("      Apague/ajuste esses cartões à mão no quadro e rode o reset de novo."))
        _falhas.clear()


# ─────────────────────────────────────────────────────────────────────────────
# RESET DO PASSO 1 — por SUB-PASSO (elo). O Passo 1 nao apaga uma lista: ele DEVOLVE
# o texto-guia da abordagem aos cartoes do termo (o cartao volta a "nao respondido" e o
# proximo_elo_p1 reabre aquele elo). Cada elo produz certos cartoes (contexto._PRODUZ).
# ─────────────────────────────────────────────────────────────────────────────
# Apelidos aceitos para escolher um elo (alem do NOME do elo, da POSICAO 1..6 na corrente
# e do ROTULO de um cartao que ele produz, ex.: '2.1' -> entregas).
_ELO_APELIDO = {
    "okr": "okr", "objetivos": "okr",
    "entregas": "entregas", "requisitos": "entregas",
    "partes": "partes", "quem": "partes",
    "restricoes": "restricoes_premissas_riscos", "premissas": "restricoes_premissas_riscos",
    "riscos": "restricoes_premissas_riscos", "onde": "restricoes_premissas_riscos",
    "quando": "quando_quanto", "quanto": "quando_quanto",
    "abordagem": "abordagem", "como": "abordagem",
}


def _resolver_elos(tokens):
    """Tokens do usuario -> elos (na ordem da corrente). Aceita o NOME do elo ('partes'),
    a POSICAO na corrente ('3' = partes) e o ROTULO de um cartao que ele produz ('2.1' ->
    entregas). Devolve (elos, invalidos)."""
    from prompts import CORRENTE_P1
    ordem = list(CORRENTE_P1)
    escolhidos, invalidos = set(), []
    for tk in tokens:
        t = (tk or "").strip().lower()
        if t.isdigit() and 1 <= int(t) <= len(ordem):
            escolhidos.add(ordem[int(t) - 1])
        elif t in _ELO_APELIDO:
            escolhidos.add(_ELO_APELIDO[t])
        else:
            elo = next((k for k, rots in contexto._PRODUZ.items() if t in rots), None)
            if elo:
                escolhidos.add(elo)
            else:
                invalidos.append(tk)
    return [e for e in ordem if e in escolhidos], invalidos


def _mapa_rotulo_id(ids):
    """{rotulo do termo (1.1, 1.2, 2.1, 3, 4.1...) -> id do cartao}."""
    return {quadro._rotulo(t): cid
            for t, cid in quadro.cards_do_termo_ids(ids).items() if quadro._rotulo(t)}


def _guia_por_rotulo(abordagem):
    """{rotulo -> texto-guia de fabrica da abordagem} — o estado 'nao respondido'."""
    return {quadro._rotulo(t): d for t, d in quadro.cartoes_do_termo(abordagem)
            if quadro._rotulo(t)}


def _limpar_checklist_okr(rot2id, dry):
    """Best-effort: apaga os checklists do cartao 1.2 (os Key Results viram checklist ali).
    Fail-safe: se a API nao expor DELETE de checklist, nao quebra — o cartao ja voltou ao
    guia e os itens velhos somem quando voce refaz o OKR (garantir_checklist faz upsert)."""
    cid = rot2id.get("1.2")
    if not cid:
        return 0
    try:
        from pa_client import list_checklists
        chks = list_checklists(cid)
    except Exception:
        return 0
    if dry:
        return len(chks)
    n = 0
    for chk in chks:
        try:
            api("DELETE", f"/checklists/{chk['id']}")
            n += 1
        except Exception:
            pass
    return n


def _limpar_caixa_termo(ids, rotulos, dry):
    """Tira da Caixa de entrada os blocos [rotulo] dos cartoes resetados (senao a proxima
    rodada do Passo 1 redistribuiria a resposta velha por cima do guia)."""
    alvo = set(rotulos)
    presentes = [r for r in quadro.blocos_da_caixa(ids) if r in alvo]
    if presentes and not dry:
        quadro.consumir_blocos(ids, presentes, log=lambda _m: None)
    return len(presentes)


def _resetar_elos(ids, abordagem, elos, dry):
    """Devolve o texto-guia aos cartoes dos `elos` e limpa os artefatos deles. Devolve
    {rotulo: quantidade} para o _mostrar."""
    rot2id = _mapa_rotulo_id(ids)
    guias = _guia_por_rotulo(abordagem)
    r, tocados = {}, []
    for elo in elos:
        for rot in contexto._PRODUZ.get(elo, ()):
            cid = rot2id.get(rot)
            if not cid:
                continue
            if not dry:
                update_card(cid, descricao=guias.get(rot, ""))
            tocados.append(rot)
        if elo == "okr":
            r[T("checklist do OKR (1.2) apagado (best-effort)")] = _limpar_checklist_okr(rot2id, dry)
        if elo == "partes":
            r[T("partes interessadas apagadas")] = _apagar_lista(ids, "PARTES", dry)
    if tocados:
        r[Tf("cartão(ões) do termo devolvido(s) ao guia ({0})",
             ', '.join(tocados))] = len(tocados)
    r[T("bloco(s) tirado(s) da Caixa de entrada")] = _limpar_caixa_termo(ids, tocados, dry)
    r[T("Passo 1 removido da trilha (reabre)")] = _limpar_progresso(ids, [1], dry)
    return r


def _ajuda_elos():
    from prompts import CORRENTE_P1
    print(T("  Sub-passos (elos) do Passo 1 — informe por NOME, POSIÇÃO ou CARTÃO:"))
    for i, e in enumerate(CORRENTE_P1, 1):
        rots = "/".join(contexto._PRODUZ.get(e, ()))
        print(f"    {i}  {e:<27} {contexto.ROTULO_ELO.get(e, '')}  "
              + Tf("(cartão {0})", rots))
    print(T("  Ex.:  reset.py 1 partes      ·  reset.py 1 3 5 6      ·  reset.py 1  (o Passo 1 inteiro)"))


def _reset_passo_1(ids, abordagem, elo_tokens, dry):
    """reset.py 1 [elos...] — reabre o Passo 1 inteiro, ou só os sub-passos escolhidos."""
    from prompts import CORRENTE_P1
    if not elo_tokens:
        # Interativo: deixa escolher um sub-passo; Enter = o Passo 1 inteiro.
        _ajuda_elos()
        try:
            resp = input(T("\n  Quais sub-passos? (números/nomes separados por espaço; "
                         "Enter = o Passo 1 INTEIRO): ")).strip()
        except (EOFError, KeyboardInterrupt):
            resp = ""
        elo_tokens = resp.split()

    if elo_tokens:
        elos, invalidos = _resolver_elos(elo_tokens)
        if invalidos:
            print(Tf("  Não reconheci: {0}.", ', '.join(invalidos)))
            _ajuda_elos()
        if "abordagem" in elos:
            elos = [e for e in elos if e != "abordagem"]
            print(T("  (O elo ABORDAGEM (4.1) é a fronteira do curso e muda a forma do quadro"))
            print(T("   inteiro — não dá para resetá-lo isolado. Para trocar a abordagem, rode"))
            print(T("   01a_escolher_abordagem.py de novo, ou resete o Passo 1 inteiro: reset.py 1.)"))
        if not elos:
            raise SystemExit(T("  Nada válido para resetar."))
        alvo_txt = Tf("os sub-passos: {0}",
                      ', '.join(contexto.ROTULO_ELO[e] for e in elos))
    else:
        elos = list(CORRENTE_P1)     # o termo inteiro (inclui a abordagem)
        alvo_txt = T("o Passo 1 INTEIRO — as 7 perguntas do 5W2H, inclusive a justificativa "
                     "(1.1) e a abordagem (4.1)")

    header(Tf("Reset do Passo 1 — {0}  ({1})", alvo_txt, prompts.nome_abordagem(abordagem))
           + (T("   [SIMULAÇÃO]") if dry else ""))
    print(T("  Devolve os cartões escolhidos ao texto-guia (voltam a 'não respondido') e reabre"))
    # "dos Passos 2 a 10" (intervalo) foi trocado por "dos passos SEGUINTES": no curso de
    # 40 os numeros nao sao contiguos, e "Processos 5 a 40" incluiria 30 processos que
    # este reset nao toca. O filtro de vocabulario recusa intervalos justamente por isto.
    print(T("  o(s) sub-passo(s) na corrente do Passo 1. As saídas dos passos seguintes"))
    print(T("  (escopo, riscos, cronograma...) NÃO são tocadas — se o termo mudar, rode"))
    print(T("  `reset.py 2` para refazê-las."))
    if elos == list(CORRENTE_P1):
        print(T("  (A abordagem no .env continua a mesma; para trocá-la, rode 01a de novo.)"))
    print()

    _mostrar(_resetar_elos(ids, abordagem, elos, dry=True))     # simula primeiro (read-only)
    if dry:
        done(T("Simulação — NADA foi alterado. Rode sem --dry-run quando quiser aplicar."))
        return
    resp = input(T("\n  Para CONFIRMAR e aplicar, digite o número do passo (1): ")).strip()
    if resp != "1":
        raise SystemExit(T("  Cancelado — nada foi alterado."))
    print()
    _mostrar(_resetar_elos(ids, abordagem, elos, dry=False))
    done(T("Pronto — o Passo 1 reabriu o(s) sub-passo(s) escolhido(s). Rode o Passo 1: ele te "
         "leva ao primeiro elo em aberto com o prompt já pronto."))


def _passo_pedido(texto):
    """O numero que o ALUNO digitou, traduzido para o passo interno (1..10).

    O reset sempre trabalhou em passos do Project Together. No PMBOK 8 Together o
    aluno ve — e digita — o numero do PROCESSO: `reset.py 21` tem de resetar a
    aprovacao do plano, nao o "passo 21", que nao existe. Um numero que o curso nao
    executa devolve 0, e ai o main() mostra a lista em vez de apagar coisa errada.
    """
    if not str(texto).strip().isdigit():
        return 0
    pedido = int(texto)
    if cat.curriculo_corrente() == cat.CURRICULO_10:
        return pedido if 1 <= pedido <= 10 else 0
    for passo in range(1, 11):
        if cat.numero_do_passo(passo) == pedido:
            return passo
    return 0


def main():
    args = [a for a in sys.argv[1:] if a]
    dry = "--dry-run" in args
    pos = [a for a in args if not a.startswith("-")]

    limpar_cache()
    c = cfg()
    require(c, "PROJECTADM_TOKEN", "BOARD_ID")
    abordagem = quadro.abordagem_do_env()
    if not abordagem:
        raise SystemExit(T("Escolha a abordagem primeiro (rode 01a_escolher_abordagem.py) — "
                         "sem ela não sei as listas do seu quadro."))
    ids = _ids_do_quadro(abordagem)

    n = _passo_pedido(pos[0]) if (pos and pos[0].isdigit()) else 0
    if not n:
        # A lista sai NUMERADA NO CURSO DE QUEM ESTA LENDO. Ela era fixa ("2 (escopo)") e
        # o filtro de vocabulario so' trocava a palavra — o aluno do PMBOK 8 lia
        # "Processos ... 2 (escopo)" e digitava 2, que la' e' "Identificar as partes
        # interessadas". Numero errado num comando que APAGA nao e' erro de texto.
        print(Tf("{0} que dá para refazer:", cat.unidade(plural=True).capitalize()))
        for passo, o_que in ((1, T("o termo de abertura")), (2, T("escopo")),
                             (3, T("riscos")), (4, T("cronograma")), (5, T("aprovar")),
                             (6, T("partes")), (7, T("executar")), (9, T("mudanças")),
                             (10, T("encerrar"))):
            print(f"   {cat.numero_do_passo(passo)} ({o_que})")
        try:
            n = _passo_pedido((input(Tf("Resetar a partir de qual {0}? ",
                                        cat.unidade())).strip() or "0"))
        except ValueError:
            n = 0

    # O Passo 1 e' especial — tem SUB-PASSOS (os elos da corrente). reset.py 1 [elos...]
    if n == 1:
        _reset_passo_1(ids, abordagem, pos[1:], dry)
        return

    if not 2 <= n <= 10:
        # A mensagem nao pode dizer "de 1 a 10" no curso de 40: la' os numeros validos
        # sao os dos processos que o curso executa (1, 5, 9, 17, 21...), nao um intervalo.
        raise SystemExit(Tf("Informe um {0} da lista acima.", cat.unidade()))

    alvo = list(range(n, 11))
    header(Tf("Reset — refazer a partir do Passo {0}  ({1})", n, prompts.nome_abordagem(abordagem))
           + (T("   [SIMULAÇÃO]") if dry else ""))
    print(Tf("  Vou apagar as SAÍDAS dos Passos {0} a 10. O Passo 1 (termo, OKR, partes) e a", n))
    print(Tf('  sua trilha até o Passo {0} ficam intactos.\n', n - 1))

    # SEMPRE simula primeiro e mostra o impacto (read-only).
    _mostrar(_executar(ids, alvo, dry=True))

    if dry:
        done(T("Simulação — NADA foi apagado. Rode sem --dry-run quando quiser aplicar."))
        return

    resp = input(Tf('\n  Para CONFIRMAR e apagar, digite o número do passo ({0}): ', n)).strip()
    if resp != str(n):
        raise SystemExit(T("  Cancelado — nada foi apagado."))
    print()
    _mostrar(_executar(ids, alvo, dry=False))
    _oferecer_descarte(ids, alvo)
    done(Tf("Pronto — o projeto voltou ao início do Passo {0}. Rode o Passo {1}: a Caixa e o "
            "progresso dele estão limpos, então ele já te entrega o prompt novo.", n, n))


if __name__ == "__main__":
    main()
