# -*- coding: utf-8 -*-
"""
Project Together — CONSERTAR OS RÓTULOS DE UMA VERSÃO ANTIGA (só no pacote em inglês).

O PROBLEMA
    Até 06/08/2026 vários pontos do curso escreviam o rótulo do campo com f-string crua
    — `f"Tipo: {tipo}"`, `f"depende de: {dep}"`, `f"COMO RODAR: ..."` —, e f-string não
    passa por T(). O pacote é bilíngue, mas esses pedaços não eram: um aluno que comprou
    o curso EM INGLÊS recebia cartões assim:

        estimated duration: 6 working days · depende de: Baseline NAV · responsável: Ana
        Tipo: people / Valor: 5000 / Quando: 2026-08 / Pacote: ...
        COMO RODAR: Cole o prompt na sua IA (navegador)...

    Os escritores já foram corrigidos — quem rodar um passo AGORA recebe "Type:", "When:",
    "depends on:", "HOW TO RUN:". Mas o quadro de quem rodou ANTES continua com o texto
    antigo, e ninguém vai reescrever 40 cartões à mão.

O QUE ESTE MÓDULO FAZ
    Acha, nas descrições dos cartões, os rótulos que a versão antiga escreveu em
    português e os troca pelo rótulo em inglês. SÓ o rótulo: o que vem DEPOIS dos
    dois-pontos é conteúdo do aluno (ou da IA dele) e não se toca — traduzir o nome de um
    pacote de trabalho seria reescrever o projeto de alguém.

    O aluno vê o antes/depois e confirma. Nada é gravado sem o "s".

DE ONDE VEM A TABELA — DO CATÁLOGO, MAS POR DUAS PORTAS ESTREITAS
    A tradução de cada rótulo já existe no textos_en.py (`'Valor: {0}' -> 'Value: {0}'`),
    então os pares são DERIVADOS dali — assim quem acrescentar um rótulo novo, com a sua
    tradução, ganha o conserto de graça, e não há uma segunda tabela para divergir da
    primeira.

    Mas derivar do catálogo INTEIRO seria largo demais. A primeira versão deste módulo
    aceitava qualquer frase terminada em dois-pontos e produziu 85 pares — entre eles
    `'3. de a ele este nome'` e `'Seu projeto'`, que não são rótulo de campo coisa
    nenhuma. Um conserto que troca a palavra errada dentro do quadro de alguém é pior do
    que não consertar nada. Então entram só duas formas, as duas verificáveis:

      1. `RÓTULO: {0}`  — o molde que um escritor de cartão usa (`Tf("Valor: {0}", v)`).
         O `{0}` é obrigatório: é ele que prova que aquilo é um campo, e não uma frase.
      2. os NOMES DE CAMPO de `passos.rotulo_de_campo` — "responsável", "até", "meta"…,
         que aparecem no cartão como `f"{rótulo}: {valor}"`, sem molde.

    Fora disso, nada. `EXTRAS` guarda os poucos rótulos que o curso escreve sem `{0}`,
    um a um, com o motivo — não é uma porta aberta, é uma lista fechada.

SÓ NO PACOTE EM INGLÊS
    Num quadro em português não há nada a consertar: o rótulo em português é o certo.
    `pares()` devolve vazio quando IDIOMA=PT, e o painel nem menciona o assunto.
"""
import re

import entradas
import quadro
from idioma import T, Tf, lang
from pa_client import list_cards, update_card

# As listas cujas descrições o CURSO escreve (e onde, portanto, moram os rótulos dele).
# A lista PARTES fica de fora de propósito: lá o texto é do aluno, escrito no Passo 1.
LISTAS = ("REFERENCIA", "ESCOPO", "ORCAMENTO", "RISCOS", "CONCLUIDO", "MUDANCAS")

# ...e INPUTS (os cartões do Termo) só para as FRASES abaixo. O resto daquele cartão é a
# resposta da IA do aluno: um rótulo solto ali é texto dele, não do curso.
LISTAS_FRASE = LISTAS + ("INPUTS",)

# PORTA 1: o molde de um campo — "Valor: {0}". O `{0}` é obrigatório (ver o cabeçalho).
_MOLDE = re.compile(r"^([^:{}\n]{2,40}):\s*\{0\}\s*$")

# ...e o que sobrou tem de PARECER UM RÓTULO: só letras, espaço, hífen e apóstrofo, no
# máximo 24 caracteres. Exigir `{0}` não bastou — o catálogo tem MENSAGENS terminadas em
# dois-pontos e valor ("[!] Nao consegui concluir a configuracao: {0}"), e elas entravam
# como se fossem campo. Pontuação, colchete, bullet, barra e seta são sinal de frase.
_PARECE_ROTULO = re.compile(r"^[^\W\d_][\w '’-]{1,23}$", re.UNICODE)

# PORTA 2: os nomes de campo que `passos.rotulo_de_campo` e `quadro.espelhar_risco_no_
# texto` escrevem. O teste `rotulos_antigos_precisos.py` falha se esta lista sair de
# sincronia com o catálogo.
#
# "Prioridade" está aqui, e não na porta 1, porque o molde dele tem DOIS espaços a
# preencher — "Prioridade: {0} ({1})" — e a porta 1 só reconhece "X: {0}". Sem esta
# linha o rótulo sobrevivia ao conserto enquanto o valor ao lado já saía em inglês
# ("Prioridade: 12 (high)") — metade traduzida, que é pior do que nenhuma.
CAMPOS = ("item", "responsável", "de quem", "até", "opções", "meta", "realizado",
          "por quê", "probabilidade", "impacto", "resposta", "dono",
          "Probabilidade", "Impacto", "Prioridade", "Dono")

# EXTRAS: rótulos que o curso escreve SEM `{0}` e que apareceram em quadros reais.
# Um a um, com o motivo — esta lista é fechada de propósito.
EXTRAS = {
    # o Passo 2 escreve "Critério de aceitação: ..." no pacote de trabalho
    "Critério de aceitação": "Acceptance criterion",
    "Criterio de aceitacao": "Acceptance criterion",
    # o Passo 7 escreve "FOCO DE HOJE:" no cartão da Daily
    "FOCO DE HOJE": "TODAY'S FOCUS",
}

# A MARCA DA 1ª LINHA do cartão de risco (Passo 3). Não é "rótulo: valor" — é a linha
# inteira —, então tem o seu próprio conserto. Quem a lê de volta é
# `vocabulario.TIPO_PONTO`, que conhece as duas línguas: trocar aqui não muda o tipo do
# ponto de atenção, só o que o aluno lê.
MARCAS = {
    "🔴 AMEAÇA": "🔴 THREAT",
    "🟢 OPORTUNIDADE": "🟢 OPPORTUNITY",
    "🔵 PREMISSA": "🔵 ASSUMPTION",
    "🟠 RESTRIÇÃO": "🟠 CONSTRAINT",
}

# FRASES INTEIRAS que o curso acrescenta ao cartão do Termo — o rodapé de uma seção, não
# um campo. Também eram f-string crua até 06/08/2026, e por isso sobreviveram em
# português num quadro em inglês, aparecendo no Termo de Abertura gerado (Eduardo, quadro
# 324/FIRE). A troca é a LINHA INTEIRA e a tradução vem do catálogo — se ela não estiver
# lá, a frase não entra (nada de meia-tradução). Lista fechada, como os EXTRAS.
FRASES = (
    "Premissa que cai vira risco — revise esta lista a cada passo.",
    "No passo 2 cada entrega se abre em pacotes de trabalho (EAP).",
    "No passo 2 cada entrega vira histórias priorizadas no Product Backlog.",
)


def frases():
    """[(frase_pt, frase_en)] — só as que já têm tradução no catálogo."""
    if lang() != "EN":
        return []
    try:
        from textos_en import TEXTOS
    except ImportError:
        return []
    return [(f, TEXTOS[f]) for f in FRASES
            if isinstance(TEXTOS.get(f), str) and TEXTOS[f].strip() and TEXTOS[f] != f]

# Rótulos que NÃO se troca, mesmo vindo de uma das portas: são iguais nas duas línguas
# (trocar não muda nada) ou são chave de busca do próprio curso.
_NAO_TROCAR = {"script", "sprint", "okr", "kr", "status", "item"}


def pares():
    """[(rótulo_pt, rótulo_en)], dos mais longos para os mais curtos — para "duração
    estimada" ser trocado antes de "duração", e nunca o contrário."""
    if lang() != "EN":
        return []
    try:
        from textos_en import TEXTOS
    except ImportError:
        return []
    out = {}

    def _por(pt, en, conferir_forma=True):
        pt, en = (pt or "").strip(), (en or "").strip()
        if not pt or not en or pt == en or pt.lower() in _NAO_TROCAR:
            return
        if conferir_forma and not (_PARECE_ROTULO.match(pt) and _PARECE_ROTULO.match(en)):
            return
        out[pt] = en

    for pt, en in TEXTOS.items():                      # porta 1: os moldes "X: {0}"
        if not isinstance(en, str):
            continue
        mp, me = _MOLDE.match(pt.strip()), _MOLDE.match(en.strip())
        if mp and me:
            _por(mp.group(1), me.group(1))
    for chave in CAMPOS:                               # porta 2: os nomes de campo
        _por(chave, TEXTOS.get(chave))
    # a lista fechada: escrita à mão, uma a uma, então não passa pelo filtro de forma
    # (é ele que existe para conter o que vem do catálogo automaticamente)
    for pt, en in EXTRAS.items():
        _por(pt, en, conferir_forma=False)
    return sorted(out.items(), key=lambda p: -len(p[0]))


def _mesma_caixa(original, novo):
    """"Responsável" -> "Owner"; "responsável" -> "owner". O cartão escreve o mesmo campo
    com caixa diferente conforme o lugar (linha própria x depois de um "·"), e trocar
    "Responsável:" por "owner:" ficaria torto no meio do cartão do aluno."""
    if original[:1].isupper() and original[1:2].islower():
        return novo[:1].upper() + novo[1:]
    if original.isupper():
        return novo.upper()
    return novo


def _trocar(texto, pares_, frases_=()):
    """Troca só o RÓTULO (o que está antes dos dois-pontos); o valor depois dos
    dois-pontos é do aluno e não se toca. Devolve (texto_novo, {(pt, en): quantas})."""
    quais = {}
    for pt, en in frases_:           # a frase inteira do curso (linha própria)
        padrao = re.compile(rf"(?m)^[ \t]*{re.escape(pt)}[ \t]*$")
        texto, k = padrao.subn(en, texto)
        if k:
            quais[(pt, en)] = k
    if pares_:                       # em PT `pares_` é vazio: nada de marca também
        for pt, en in MARCAS.items():
            # a marca é a LINHA INTEIRA (é assim que o Passo 3 a grava e como o
            # _MARCA_LINE do quadro.py a encontra) — nunca um pedaço no meio do texto
            padrao = re.compile(rf"(?m)^[ \t]*{re.escape(pt)}[ \t]*$")
            texto, k = padrao.subn(en, texto)
            if k:
                quais[(pt, en)] = k
    for pt, en in pares_:
        # O rótulo está no COMEÇO DA LINHA (com ou sem recuo) ou logo depois de um
        # separador " · " / "|" — é assim que o Passo 4 escreve a linha de detalhe do
        # pacote ("duração estimada: 6 dias · depende de: X · responsável: Y"). Preso a
        # essas duas posições, o conserto não alcança o texto corrido do aluno.
        padrao = re.compile(rf"(?im)(^[ \t]*|[·|]\s*)({re.escape(pt)})(\s*:)")
        texto, k = padrao.subn(
            lambda m: f"{m.group(1)}{_mesma_caixa(m.group(2), en)}{m.group(3)}", texto)
        if k:
            quais[(pt, en)] = k
    return texto, quais


def encontrar(ids):
    """[(card_id, lista, titulo, descricao_antiga, descricao_nova, {rótulo: n})] — só os
    cartões que têm rótulo de versão antiga. Não escreve nada."""
    pares_, frases_ = pares(), frases()
    if not pares_ and not frases_:
        return []
    achados = []
    for chave in LISTAS_FRASE:
        lid = ids.get(chave)
        if not lid:
            continue
        # Em INPUTS (os cartões do Termo) só as FRASES do curso são trocadas: o resto
        # daquele cartão é a resposta da IA do aluno.
        so_frase = chave not in LISTAS
        for c in list_cards(lid):
            desc = c.get("descricao") or ""
            if not desc.strip():
                continue
            novo, quais = _trocar(desc, () if so_frase else pares_, frases_)
            if quais:
                achados.append((c.get("id"), chave, (c.get("titulo") or "").strip(),
                                desc, novo, quais))
    return achados


def relatar(achados):
    """As linhas do painel: TODA troca distinta, com quantas vezes ela acontece.

    Mostra o inventário completo, não uma amostra: quem vai autorizar uma escrita em 74
    cartões precisa ver as 8 trocas que ela faz, e não três exemplos que sugerem o resto.
    """
    if not achados:
        return []
    por_rotulo = {}
    for *_x, quais in achados:
        for (pt, en), n in quais.items():
            a, b = por_rotulo.get((pt, en), (0, 0))
            por_rotulo[(pt, en)] = (a + n, b + 1)
    total = sum(n for n, _c in por_rotulo.values())
    L = [Tf("   {0} cartão(ões) ainda têm rótulo de uma versão antiga do curso "
            "({1} rótulo(s) em português):", len(achados), total)]
    for (pt, en), (n, _cards) in sorted(por_rotulo.items(), key=lambda kv: -kv[1][0]):
        # a marca da 1ª linha e a frase inteira não levam dois-pontos — mostrá-las com um
        # seria descrever errado a troca que o aluno está autorizando
        d = "" if (pt in MARCAS or pt in FRASES) else ":"
        L.append(Tf('      • "{0}" → "{1}"   ({2}×)', pt + d, en + d, n))
    L.append(T("   Só o RÓTULO muda — o que você e a sua IA escreveram fica igual."))
    return L


def consertar(achados, log=print):
    """Grava. Devolve quantos cartões foram atualizados."""
    n = 0
    for cid, _lista, titulo, _antiga, nova, _quais in achados:
        try:
            update_card(cid, descricao=nova)
            n += 1
        except Exception as e:
            log(Tf('   não consegui atualizar "{0}": {1}', titulo[:40], e))
    return n
