"""
Together Kit — o IDIOMA do curso (compartilhado por TODOS os cursos Together).

O PARAMETRO
    IDIOMA=PT   (padrao) — Portugues, para escritoriodeprojetos.com.br
    IDIOMA=EN            — Ingles, para projectmanagement.com.br
    Fica no .env do aluno, ao lado de ABORDAGEM e BOARD_ID. O bootstrap grava
    conforme o pacote que a pessoa baixou; ninguem precisa escolher nada.

POR QUE UM CATALOGO COM A CHAVE EM PORTUGUES (e nao chaves tipo "menu.titulo")
    O curso tem ~62 mil palavras de texto para o aluno espalhadas em 30 arquivos.
    Inventar uma chave para cada uma seria um segundo projeto — e cada chave
    inventada e' uma chance a mais de escrever a chave errada e mostrar vazio.
    Aqui a CHAVE E' O PROPRIO TEXTO EM PORTUGUES:

        print(T("Rode o Passo 1 de novo — ele le o quadro e segue com voce."))

    Consequencias, todas boas para este curso:
      - o codigo continua legivel em portugues (o time escreve em portugues);
      - traducao que falta cai no portugues — degrada, nunca quebra nem some;
      - traduzir vira um trabalho de DADOS (preencher textos_en.py), separavel
        de mexer no codigo, e revisavel num diff;
      - `--faltando` lista exatamente o que ainda nao foi traduzido.

    O preco: se alguem editar o texto em portugues, a traducao daquela frase
    volta a faltar. E' por isso que existe o `--faltando`: ele acusa na hora.

DADOS ESTRUTURADOS (prompts.py, quadro.py) NAO usam T()
    Os prompts e os cartoes do quadro nao sao frases soltas: sao tabelas
    (_CORPOS, FORMATOS, _TERMO_PREDITIVA...). Para esses vale uma TABELA PARALELA
    por idioma, escolhida por `escolher(pt, en)` — fica muito mais facil de
    revisar lado a lado do que 200 strings gigantes dentro de um dicionario.

FRASE COM VARIAVEL DENTRO: Tf(), NUNCA UMA f-string PARTIDA
    `f"{n} riscos em {lista}"` nao pode virar T() de pedaco: em ingles a ordem das
    palavras muda, e quem traduz nao ve a frase inteira. A frase vira UM template
    com posicoes numeradas, e as variaveis viram argumentos:

        Tf("{0} risco(s) na lista {1}.", n, lista)

    O numero e' o que deixa o tradutor REORDENAR ("in list {1} there are {0}...").
    E se a traducao vier com um placeholder errado, o Tf cai no portugues em vez
    de derrubar o passo do aluno — a mesma promessa do T().

Uso:
    from idioma import T, Tf, lang, escolher
    print(T("Quadro montado."))
    print(Tf("{0} cartao(oes) criado(s).", n))
    CORPOS = escolher(_CORPOS_PT, _CORPOS_EN)
"""
import os
import re

IDIOMAS = ("PT", "EN")
IDIOMA_PADRAO = "PT"

_SINONIMOS = {
    "pt": "PT", "pt-br": "PT", "ptbr": "PT", "portugues": "PT", "português": "PT",
    "br": "PT", "portuguese": "PT",
    "en": "EN", "en-us": "EN", "eng": "EN", "ingles": "EN", "inglês": "EN",
    "english": "EN", "us": "EN",
}

# Os valores que uma VARIAVEL DE AMBIENTE pode usar para trocar o idioma do curso.
# Deliberadamente MENOR que _SINONIMOS: ver _do_ambiente().
_SINONIMOS_ESTRITOS = {
    "pt": "PT", "portugues": "PT", "português": "PT", "portuguese": "PT",
    "en": "EN", "ingles": "EN", "inglês": "EN", "english": "EN",
}
# Marca de valor de LOCALE do sistema operacional: "pt_BR.UTF-8", "en_US:en", "C.UTF-8".
_MARCA_DE_LOCALE = re.compile(r"[._:@]")

_cache = None
_origem = None


def normalizar(valor):
    """Valor do .env -> PT ou EN (tolera acento, sinonimo e caixa).

    Desconhecido cai no padrao: o .env e' do aluno, e um curso em portugues e' o que
    o curso sempre foi. Para decidir se um valor e' NOSSO, use normalizar_estrito().
    """
    v = (valor or "").strip()
    achado = _SINONIMOS.get(v.lower())
    if achado:
        return achado
    up = v.upper()
    return up if up in IDIOMAS else IDIOMA_PADRAO


def normalizar_estrito(valor):
    """PT/EN se o valor for RECONHECIDAMENTE nosso; None se nao der para afirmar.

    A diferenca para o normalizar() e' o silencio: aqui "en_US.UTF-8", "C" ou vazio
    devolvem None em vez de virarem portugues por omissao. Quem pergunta assim quer
    saber se ALGUEM ESCOLHEU um idioma — nao receber um palpite.
    """
    v = (valor or "").strip()
    if not v:
        return None
    up = v.upper()
    if up in IDIOMAS:
        return up
    return _SINONIMOS_ESTRITOS.get(v.lower())


def _do_ambiente():
    """O idioma pedido por uma VARIAVEL DE AMBIENTE — (valor, nome da variavel).

    POR QUE ISTO E' TAO DESCONFIADO (quadro 324, 07/08/2026)
    `LANGUAGE` nao e' uma variavel nossa: e' a variavel do gettext, e um monte de
    ambiente a define sozinho ("en_US:en", "pt_BR.UTF-8"). Ate' aqui QUALQUER valor
    nela decidia o idioma do curso — e, como o normalizar() devolve portugues para o
    que nao reconhece, um `LANGUAGE=en_US` no shell punha um curso EM INGLES para
    rodar EM PORTUGUES, sem uma linha de aviso. Num quadro montado em ingles isso
    custava 8 listas vazias em portugues, porque quem procura lista procura PELO NOME.

    A regra que fica: o idioma do curso mora no .env do aluno. Uma variavel de
    ambiente so' fala mais alto quando e' inequivocamente NOSSA — TOGETHER_LANGUAGE
    (o nome que so' este curso usa), ou LANGUAGE/IDIOMA trazendo um valor que o curso
    escreve ("PT"/"EN"). Qualquer coisa com cara de locale e' ignorada.
    """
    v = (os.environ.get("TOGETHER_LANGUAGE") or "").strip()
    if v:
        return normalizar_estrito(v), "TOGETHER_LANGUAGE"
    for nome in ("LANGUAGE", "IDIOMA"):
        v = (os.environ.get(nome) or "").strip()
        if not v or _MARCA_DE_LOCALE.search(v):
            continue
        achado = normalizar_estrito(v)
        if achado:
            return achado, nome
    return None, None


def lang():
    """O idioma do curso, resolvido uma vez por execucao.

    Ordem: TOGETHER_LANGUAGE > LANGUAGE/IDIOMA do ambiente (so' com valor nosso) >
    LANGUAGE/IDIOMA do .env > portugues.

    Nunca levanta excecao: um pacote sem .env (ou sem a chave) e' portugues, que
    e' o comportamento que o curso sempre teve.
    """
    global _cache, _origem
    if _cache is not None:
        return _cache
    valor, de_onde = _do_ambiente()
    if valor:
        _cache, _origem = valor, de_onde
        return _cache
    try:
        from env import cfg
        c = cfg()
        # LANGUAGE e' o nome atual da chave; IDIOMA continua respondendo pelos .env que
        # ja' existem (o env.cfg() faz o mesmo apelido — ver env.ALIASES).
        do_arquivo = c.get("LANGUAGE") or c.get("IDIOMA")
    except BaseException:
        # BaseException, nao Exception: env.cfg() faz `raise SystemExit` quando
        # nao acha o .env, e SystemExit nao e' Exception. Sem isto, importar
        # idioma.py fora da pasta de um aluno MATA o processo.
        do_arquivo = None
    _cache = normalizar(do_arquivo)
    _origem = ".env" if normalizar_estrito(do_arquivo) else "padrao"
    return _cache


def origem():
    """De ONDE veio o idioma desta execucao: 'TOGETHER_LANGUAGE', 'LANGUAGE', 'IDIOMA',
    '.env', 'forcado' (definir()) ou 'padrao' (ninguem disse — assumimos portugues)."""
    if _origem is None:
        lang()
    return _origem


def escolhido():
    """True quando ALGUEM escolheu este idioma (o aluno, no .env, ou um override).

    'padrao' significa que ninguem disse nada. A diferenca importa para quem vai
    RENOMEAR as listas do quadro de alguem: seguir um palpite nosso e reescrever o
    quadro de um aluno nao sao a mesma coisa (ver quadro.garantir_listas)."""
    return origem() != "padrao"


def definir(valor):
    """Forca o idioma nesta execucao (usado pelos testes e pelo --idioma)."""
    global _cache, _origem
    _cache = normalizar(valor)
    _origem = "forcado"
    return _cache


def escolher(pt, en):
    """A versao PT ou EN de um dado ESTRUTURADO (dict, lista, tupla)."""
    return en if lang() == "EN" and en is not None else pt


def mesclar(pt, en):
    """Como o escolher(), mas CHAVE A CHAVE — para uma tabela traduzida pela metade.

    Traduzir prompts.py e quadro.py e' trabalho de semanas, e ele avanca por
    partes. Com o escolher() puro, uma tabela EN incompleta apagaria os prompts
    que ainda nao foram traduzidos; aqui cada chave cai sozinha no portugues, e o
    curso continua rodando inteiro enquanto a traducao anda. Valor vazio ("" ou
    None) na tabela EN conta como "ainda nao traduzido".

    Desce em dicionarios aninhados (os prompts sao {chave: {"corpo": ...}}).
    """
    if lang() == "PT" or en is None:
        return pt
    if not isinstance(pt, dict) or not isinstance(en, dict):
        return en if en else pt
    saida = dict(pt)
    for k, v in en.items():
        if isinstance(v, dict) and isinstance(saida.get(k), dict):
            saida[k] = mesclar(saida[k], v)
        elif v:
            saida[k] = v
    return saida


def _catalogo():
    """O dicionario {texto_pt: texto_en} do idioma corrente ({} em portugues)."""
    if lang() == "PT":
        return {}
    try:
        from textos_en import TEXTOS
        return TEXTOS
    except Exception:
        return {}


# ─────────────────────────────────────────────────────────────────────────────
# FILTROS DE SAIDA — a ultima coisa que acontece com o texto antes de o aluno ver
#
# Existe por causa de UM problema real: os ~2.000 textos do curso foram escritos no
# Project Together e dizem "Passo 5". O PMBOK 8 Together roda os MESMOS arquivos e
# la' nao existe passo nenhum — e' o Processo 21. Reescrever as 2.000 frases (e as
# 2.000 traducoes) para passar o numero como argumento seria semanas de trabalho e
# uma chance nova de erro por frase.
#
# O filtro roda DEPOIS da traducao e DEPOIS do format(), entao pega tanto o literal
# ("Passo 5") quanto o template ("Passo {0}" com 5 no argumento) — e pega o ingles
# ("Step 5") pelo mesmo caminho. Em portugues, no curso de 10, ele e' NO-OP: "Passo
# 5" reescrito com o vocabulario dos 10 passos e' "Passo 5".
#
# Quem registra e' o catalogo (`catalogo_pmbok8.ativar_vocabulario`) — o idioma nao
# conhece curriculo nenhum, e sem ninguem registrando nada isto some.
# ─────────────────────────────────────────────────────────────────────────────
_FILTROS = []


def registrar_filtro(fn):
    """Registra um filtro `str -> str` aplicado a todo texto que vai para o aluno."""
    if fn not in _FILTROS:
        _FILTROS.append(fn)
    return fn


def limpar_filtros():
    """Tira todos os filtros (os testes usam para voltar ao estado limpo)."""
    _FILTROS.clear()


def _filtrar(texto):
    """Aplica os filtros. Um filtro com defeito NUNCA derruba o passo do aluno."""
    for f in _FILTROS:
        try:
            novo = f(texto)
            if isinstance(novo, str):
                texto = novo
        except Exception:
            pass
    return texto


def _traduzir(texto):
    """So' a traducao, sem filtro — o Tf precisa dela crua para so' filtrar no fim.

    VALOR VAZIO = AINDA NAO TRADUZIDO, e cai no portugues. O `mesclar()` sempre tratou
    "" assim (`elif v:`), mas o T() usava `.get(texto, texto)` — e `.get` de uma chave
    QUE EXISTE com valor "" devolve "", nao o padrao. Resultado: 108 frases do pacote em
    ingles saiam EM BRANCO (achado em 14/08/2026, ao conferir os placeholders). Degradar
    para o portugues e' a promessa deste modulo; sumir com a frase nunca foi.
    """
    if lang() == "PT":
        return texto
    return _catalogo().get(texto) or texto


def T(texto):
    """Traduz uma frase para o aluno. Sem traducao, devolve o portugues."""
    return _filtrar(_traduzir(texto))


def Tf(texto, *args, **kwargs):
    """Traduz e preenche um template com {0}, {1}... Nunca derruba o passo.

    Traducao com placeholder errado (um {2} num template de dois) nao pode custar
    o passo do aluno: cai no portugues, que sempre casa com os argumentos que o
    codigo passou. Ultimo recurso: o template cru, sem preencher.

    O filtro roda DEPOIS do format(), nunca antes: "Passo {0}" nao casa com nenhum
    filtro; "Passo 5", preenchido, casa. Era essa a razao de o Tf nao chamar o T().
    """
    traduzido = _traduzir(texto)
    if traduzido is not texto:
        try:
            return _filtrar(traduzido.format(*args, **kwargs))
        except (IndexError, KeyError, ValueError, AttributeError):
            pass
    try:
        return _filtrar(texto.format(*args, **kwargs))
    except (IndexError, KeyError, ValueError, AttributeError):
        return _filtrar(texto)


def faltando(textos):
    """Quais das frases dadas ainda nao tem traducao no idioma corrente."""
    cat = _catalogo()
    return [t for t in textos if t not in cat]
