"""
Together Kit — bootstrap de primeira execucao (compartilhado por TODOS os cursos).

Chamado pelos launchers Iniciar.bat (Windows) / Iniciar.command (Mac). O aluno nao
instala nada e nao edita arquivo nenhum:

  1. o Python ja vem dentro do pacote (pasta python\\, portatil — portable_python.py);
  2. o aluno informa UMA coisa: o token do ProjectAdm;
  3. criamos o PORTFOLIO do curso e o QUADRO do aluno;
  4. a cada abertura, vemos se saiu VERSAO NOVA do curso e oferecemos atualizar
     (checar_atualizacao — desligue com CHECAR_ATUALIZACAO=NAO no .env);
  5. abre o menu do curso (menu.py).

VOCABULARIO (o que o aluno le na tela):
  PORTFOLIO = a pasta que agrupa os trabalhos (a API chama de "projeto" — nos nao).
  QUADRO    = o board. E ele o PROJETO do aluno, e o unico ID que guardamos: BOARD_ID.

CAMINHO FELIZ (desde 2026-07-13): o ProjectAdm corrigiu o POST /projetos — quem cria
um portfolio pela API agora entra como membro (papel gerente) dele. Ou seja: o
bootstrap cria o portfolio E o quadro sozinho, e o aluno nao precisa abrir o site.
As checagens abaixo (can_access_portfolio / can_use_board) FICAM: elas custam uma
chamada e sao o que garante que nunca gravamos no .env um quadro que a API nao abre —
se algum dia isso voltar a acontecer, o aluno cai no plano B em vez de travar no meio
do Passo 1.

A IA E' A DO NAVEGADOR, e e' o unico caminho: o curso imprime o prompt, o aluno cola na
IA que ja' usa (ChatGPT, Gemini, Claude, DeepSeek...) e cola a resposta no cartao "0. Caixa
de entrada" do quadro. O curso nao chama IA nenhuma e nao escolhe IA por ninguem.
O modo IDE (`AI_TYPE=IDE`) foi removido em 01/08/2026 — a chave nao existe mais.
"""
import json
import os
import subprocess
import sys
from idioma import T, Tf

try:
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
    pass

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)

# O TEXTO-GUIA DO TOKEN NAO MORA AQUI (Eduardo, 20/08/2026 — pacote EN 4779 do PM).
#
# Ate hoje esta linha era `_PLACE = ("", "0", "SEU_TOKEN_AQUI")`: uma SEGUNDA lista de
# textos-guia, ao lado da de verdade (env._PLACEHOLDERS). O .env do pacote EN sai com
# `PROJECTADM_TOKEN=YOUR_TOKEN_HERE`, que esta na lista do env.py mas NAO estava nesta.
# O estrago: `tem_token` dava True, o bootstrap PULAVA a pergunta do token, e o
# env.require() logo adiante abortava com "fill in PROJECTADM_TOKEN". Como o launcher
# nao pausa depois de um erro, o aluno EN via a janela preta abrir e FECHAR — sem
# mensagem, sem pergunta, sem curso. O pacote PT nunca sentiu: o texto-guia dele
# estava nas duas listas.
#
# E o mesmo buraco que o env.py ja tinha tapado em 02/08 ("o pacote EN batizou o quadro
# de WRITE YOUR PROJECT NAME HERE"), reaberto por copia. Por isso agora nao ha lista
# aqui: quem responde "isso ainda e texto-guia?" e o env.e_placeholder(), unico dono da
# resposta, nos dois idiomas.
def _texto_guia(valor):
    """O valor do token ainda e o texto-guia do template (PT ou EN) ou vazio?"""
    from env import e_placeholder          # tardio, como o resto do arquivo faz
    return e_placeholder(valor, "PROJECTADM_TOKEN")


LINHA = "=" * 64


def curso():
    """Dados do curso (marcador .together), com fallbacks genericos."""
    try:
        with open(os.path.join(HERE, ".together"), encoding="utf-8") as f:
            d = json.load(f)
    except Exception:
        d = {}
    label = d.get("label", "Curso Together")
    return label, d.get("board_name") or f"Meu projeto — {label}"


def pedir_token():
    """Pede o token e so aceita quando ele realmente funciona na API."""
    from env import save_cfg
    print(T("\nAbra o ProjectAdm > seu perfil > API Keys, gere uma chave e cole aqui."))
    print(T("(e a unica coisa que voce precisa digitar neste curso)\n"))
    while True:
        t = input(T("   TOKEN do ProjectAdm = ")).strip()
        if not t or _texto_guia(t):
            print(T("   (vazio — cole o token e tecle Enter)\n"))
            continue
        save_cfg({"PROJECTADM_TOKEN": t})       # pa_client le o token do .env
        try:
            import pa_client
            pa_client.list_portfolios()         # valida de verdade
            print("   token OK.\n")
            return
        except Exception as e:
            msg = str(e)
            if "401" in msg or "403" in msg:
                print(T("   Esse token nao foi aceito pelo ProjectAdm. Gere outro e cole de novo.\n"))
            else:
                print(Tf("   Nao consegui falar com o ProjectAdm ({0}).", e))
                print(T("   Verifique a internet e tente de novo.\n"))


def pedir_portfolio(pa, label):
    """Plano B: o ALUNO cria o portfolio no site (um clique) e nos achamos pelo nome.

    So chega aqui se a API recusar a criacao do portfolio ou devolver um portfolio que
    ela mesma nao abre depois (era o comportamento ate 2026-07-13). No caminho normal o
    bootstrap faz tudo sozinho e o aluno nunca ve esta tela.
    """
    print(T("\n  Falta o seu PORTFOLIO — a pasta onde o seu projeto vai morar."))
    print(T("  Nao consegui criar pela API nesta conta, entao faca assim"))
    print(T("  (leva 10 segundos, e uma vez so):\n"))
    print(T("     1. abra https://projectadm.com"))
    print(T("     2. clique em Novo Portfolio (ou Novo Projeto, se for esse o nome do botao)"))
    print(T("     3. de a ele este nome:\n"))
    print(f"           {label}\n")
    print(T("     4. volte aqui e tecle Enter\n"))
    alvo = label.strip().lower()
    while True:
        input(T("  [Enter] quando o portfolio estiver criado... "))
        acessiveis = pa.accessible_portfolios()
        for p in acessiveis:
            if (p.get("nome") or "").strip().lower() == alvo:
                print(Tf('  portfolio: "{0}" (encontrado)', p['nome']))
                return p["id"]
        if len(acessiveis) == 1:
            # nunca use o unico portfolio da conta sem perguntar: ele pode ser de OUTRO
            # curso (o quadro deste curso iria parar no lugar errado).
            p = acessiveis[0]
            print(Tf('\n  Nao achei um portfolio com esse nome. O unico que voce tem e "{0}".', p['nome']))
            ok = input(T("  Quer usar esse mesmo? [Enter = sim / n = eu crio o certo agora] ")).strip().lower()
            if ok != "n":
                print(Tf('  portfolio: "{0}"', p['nome']))
                return p["id"]
            continue
        if len(acessiveis) > 1:
            print(T("\n  Nao achei um portfolio com esse nome. Os seus portfolios:"))
            for i, p in enumerate(acessiveis, 1):
                print(f"     {i}) {p['nome']}")
            esc = input(T("  Digite o numero do portfolio do curso (ou Enter para procurar de novo): ")).strip()
            if esc.isdigit() and 1 <= int(esc) <= len(acessiveis):
                p = acessiveis[int(esc) - 1]
                print(Tf('  portfolio: "{0}"', p['nome']))
                return p["id"]
        else:
            print(T("  Ainda nao vejo nenhum portfolio seu nesta conta — confira se salvou.\n"))


def garantir_portfolio(pa, label):
    """Um portfolio que o aluno REALMENTE pode usar (criado pela API; conferido antes de usar)."""
    alvo = label.strip().lower()
    for p in pa.accessible_portfolios():
        if (p.get("nome") or "").strip().lower() == alvo:
            print(Tf('  portfolio: "{0}" (ja existia)', p['nome']))
            return p["id"]

    # cria pela API — e so aceita se conseguir ABRIR depois (a lista mente: ela mostra
    # portfolios da organizacao que o token nem sempre pode usar).
    ja_existe = any((p.get("nome") or "").strip().lower() == alvo for p in pa.list_portfolios())
    if not ja_existe:
        try:
            pid = pa.create_portfolio(
                label, Tf("Portfolio do curso {0}. Cada quadro aqui dentro e um projeto seu.", label))
            if pa.can_access_portfolio(pid):
                print(Tf('  portfolio: "{0}" (criado)', label))
                return pid
        except Exception as e:
            print(Tf("  (nao consegui criar o portfolio pela API: {0})", e))

    return pedir_portfolio(pa, label)


def garantir_quadro(pa, label, nome_quadro, pid):
    """O quadro do aluno dentro do portfolio — criado por NOS. Confere se da para usar."""
    q = pa.find_board(nome_quadro, pid)
    if q and pa.can_use_board(q["id"]):
        print(Tf('  quadro: "{0}" (ja existia)', q['nome']))
        return q["id"]
    bid = pa.create_board(
        pid, nome_quadro,
        Tf("O seu projeto no curso {0}. Cada lista responde uma pergunta do 5W2H.", label))
    if not pa.can_use_board(bid):
        raise RuntimeError(
            T("criei o quadro, mas a API nao deixa mexer nele — o portfolio escolhido nao "
            "pertence a esta conta. Crie um portfolio pelo site e rode o Iniciar de novo."))
    print(Tf('  quadro: "{0}" (criado)', nome_quadro))
    return bid


def preparar_ambiente(label, nome_quadro):
    """Deixa o aluno com um QUADRO utilizavel e o BOARD_ID no .env."""
    from env import cfg, get_int, save_cfg
    import pa_client

    if get_int(cfg(), "BOARD_ID") > 0:
        return                                   # ja configurado

    print(T("Preparando o seu ambiente no ProjectAdm (leva alguns segundos)..."))
    pid = garantir_portfolio(pa_client, label)
    bid = garantir_quadro(pa_client, label, nome_quadro, pid)

    save_cfg({"BOARD_ID": str(bid)})             # o unico ID que o curso precisa
    print(T("\n  Pronto. O seu quadro esta no ProjectAdm e o curso ja sabe qual e."))
    print(T("  Voce nunca mais vai precisar digitar isso.\n"))


def quadro_utilizavel(uid_board):
    """O quadro do .env ainda serve? (um quadro dentro de um portfolio que a API nao abre
    responde 403 em tudo — melhor descobrir aqui do que no meio do Passo 1)."""
    try:
        import pa_client
        return pa_client.can_use_board(uid_board)
    except Exception:
        return True          # sem internet/token: nao e problema de quadro; segue o jogo


def configurar():
    """O assistente completo: token (unica pergunta) + ambiente."""
    from env import cfg, get_int, save_cfg
    data = cfg()
    tem_token = not _texto_guia(data.get("PROJECTADM_TOKEN", ""))
    board = get_int(data, "BOARD_ID")

    if tem_token and board > 0:
        if quadro_utilizavel(board):
            return                               # tudo certo -> menu
        print(LINHA)
        print(T("  O seu quadro nao esta acessivel pela API"))
        print(LINHA)
        print(Tf("  O quadro {0} existe, mas esta dentro de um portfolio que o ProjectAdm", board))
        print(T("  nao libera para a API — nada feito nele chegaria ao curso. Vamos refazer:"))
        print(T("  desta vez o portfolio e criado por VOCE, no site, e ele funciona.\n"))
        save_cfg({"BOARD_ID": "0"})
        board = 0

    label, nome_quadro = curso()
    if not (tem_token and board > 0):
        print(LINHA)
        print(Tf("  {0} — configuracao (so na primeira vez)", label))
        print(LINHA)
    if not tem_token:
        pedir_token()
    preparar_ambiente(label, nome_quadro)


# ─────────────────────────────────────────────────────────────────────────────
# "SAIU VERSAO NOVA?" — a checagem que roda ANTES do menu (Eduardo, 2026-07-30)
#
# ONDE MORA, E POR QUE AQUI E NAO NO Iniciar.bat: o .bat e' um launcher de 3 linhas
# que tem de continuar burro (ele roda antes de existir Python garantido, e um erro
# nele deixa o aluno sem curso nenhum). E ha um motivo mais forte: a opcao 0 do MENU
# atualiza os arquivos enquanto o menu esta rodando — o aluno continua na tela do
# menu ANTIGO ate fechar e abrir de novo. Atualizando aqui, antes de abrir o menu,
# quem abre ja e' o codigo novo.
#
# AS DUAS REGRAS QUE ISTO NUNCA PODE QUEBRAR:
#   1. nunca segurar o aluno. Sem internet, site fora do ar, DNS lento: erro
#      engolido, menu abre. A checagem roda a cada abertura, e por isso tem um
#      ORCAMENTO de tempo explicito (ORCAMENTO_CHECAGEM_S) — nao um timeout por
#      requisicao que se some ao proximo.
#   2. nunca atualizar sozinho. O update troca TODOS os scripts; quem decide isso
#      no meio de um projeto e' o aluno. Perguntamos — com [S/n], um Enter resolve.
#
# COMO SABEMOS QUE HA VERSAO NOVA, SEM BAIXAR 12 MB:
#   a) `versao_url` (o caminho exato): um .json de ~100 bytes publicado junto com o
#      pacote, com a versao que esta no ar. Comparamos com a `versao` gravada no
#      .together que veio DENTRO do zip do aluno. Funciona ate para quem esta com um
#      pacote de 3 meses atras.
#   b) sem `versao_url` (pacotes ja distribuidos): um HEAD no proprio link de
#      download devolve o Content-Length em ~0,5 s. O tamanho do zip muda a cada
#      publicacao; guardamos o ultimo visto em .together_versao (arquivo LOCAL, que
#      nao vem no zip). Na primeira vez so registramos — nao ha com o que comparar.
# ─────────────────────────────────────────────────────────────────────────────
MARCADOR_VERSAO = ".together_versao"       # estado LOCAL (nunca vem dentro do zip)


def _estado_versao():
    try:
        with open(os.path.join(HERE, MARCADOR_VERSAO), encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}


def _gravar_estado_versao(**campos):
    d = _estado_versao()
    d.update(campos)
    try:
        with open(os.path.join(HERE, MARCADOR_VERSAO), "w", encoding="utf-8") as f:
            json.dump(d, f, ensure_ascii=False)
    except OSError:
        pass                                  # pasta somente-leitura: seguir sem estado


# ─────────────────────────────────────────────────────────────────────────────
# A CHECAGEM ACONTECE EM TODA ABERTURA DO CURSO (Eduardo, 14/08/2026)
#
# Havia uma JANELA: uma vez por dia de calendario, depois uma vez a cada 6 horas. Ela
# existia para nao pagar rede a cada abertura — e cobrou caro duas vezes, sempre do mesmo
# jeito: QUEM PUBLICA PUBLICA DEPOIS DE JA TER ABERTO O CURSO.
#   08/08  publicamos as 18:20; quem abriu de manha ficou cego ate o dia seguinte.
#   14/08  publicamos as 16:22; a ultima checagem tinha sido as 11:49, e a proxima so'
#          seria as 17:49 — a deteccao respondia certo, ninguem a chamava.
#
# O que a janela economizava, medido: 130 a 240 ms para um JSON de ~100 bytes. Nao paga o
# preco de um aviso que chega tarde. A janela saiu; no lugar dela ficaram duas travas que
# NAO dependem do relogio:
#
#   ORCAMENTO DE TEMPO     a checagem inteira tem ORCAMENTO_CHECAGEM_S. Sem rede ela
#                          desiste depressa e o menu abre — e' a promessa de sempre,
#                          "a checagem nunca pode segurar o menu", que agora vale a cada
#                          abertura e por isso virou um limite explicito.
#   SILENCIO POR VERSAO    quem ja tentou (`tentou`) ou ja recusou (`recusou`) a versao X
#                          nao ouve falar dela de novo. Sem isto, perguntar a toda
#                          abertura viraria assedio: o aluno diz "agora nao" e o curso
#                          repete a pergunta no minuto seguinte. Versao NOVA no ar volta
#                          a perguntar, porque o numero muda.
ORCAMENTO_CHECAGEM_S = 3.0


def _agora_ts():
    import time
    return int(time.time())


def _prazo(segundos=None):
    """O instante em que a checagem tem de ter acabado (relogio monotonico).

    Le' ORCAMENTO_CHECAGEM_S na HORA da chamada, e nao no `def`: valor de parametro
    padrao congela no import, e ai' o teste (ou quem quisesse afrouxar o orcamento num
    ambiente lento) mudaria a constante sem efeito nenhum.
    """
    import time
    return time.monotonic() + (ORCAMENTO_CHECAGEM_S if segundos is None else segundos)


def _resta(prazo, minimo=0.3):
    """Quanto sobrou do orcamento — None quando nao vale mais a pena tentar.

    O `minimo` evita a tentativa de meio segundo que so' serve para atrasar o menu: se
    o que resta nao da' nem para um handshake, a resposta honesta e' "nao sei".
    """
    import time
    s = prazo - time.monotonic()
    return s if s >= minimo else None


def _versao_publicada(url, timeout=None):
    """A versao que esta no ar, lida do .json publicado ao lado do pacote."""
    import urllib.request
    req = urllib.request.Request(url, headers={"User-Agent": "together-kit-versao"})
    with urllib.request.urlopen(req, timeout=timeout or ORCAMENTO_CHECAGEM_S) as r:
        return (json.loads(r.read().decode("utf-8")) or {}).get("versao") or ""


def _tamanho_publicado(url, timeout=None):
    """O Content-Length do zip que esta no ar (HEAD — nao baixa o arquivo)."""
    import urllib.request

    class _Head(urllib.request.Request):
        def get_method(self):
            return "HEAD"

    req = _Head(url, headers={"User-Agent": "together-kit-versao"})
    with urllib.request.urlopen(req, timeout=timeout or ORCAMENTO_CHECAGEM_S) as r:
        return int(r.headers.get("Content-Length") or 0)


def _ja_dispensada(versao):
    """Esta versao ja foi tentada (e nao entrou) ou recusada pelo aluno?"""
    est = _estado_versao()
    return bool(versao) and versao in (est.get("tentou"), est.get("recusou"))


# O carimbo de versao e' AAAA.MM.DD.HHMM, com zero a esquerda — entao ele se compara como
# texto e da' a ordem certa, sem precisar de biblioteca de versao.
def _e_mais_nova(no_ar, minha):
    """A versao do ar e' mais NOVA que a minha? (e nao apenas diferente)

    Comparar com `!=` mandava atualizar tambem quando a pasta estava A FRENTE do
    publicado — o caso de quem constroi o pacote localmente e ainda nao publicou. Com a
    janela isso aparecia de vez em quando; com a checagem a cada abertura viraria um
    aviso fixo, apontando para tras. Fora do formato conhecido, mantemos o comportamento
    antigo: diferente ja e' motivo para avisar.
    """
    import re
    formato = re.compile(r"^\d{4}\.\d{2}\.\d{2}\.\d{4}$")
    if formato.match(no_ar or "") and formato.match(minha or ""):
        return no_ar > minha
    return no_ar != minha


def _ha_versao_nova(d):
    """(True/False, descricao, versao_no_ar). Nunca levanta — erro vira 'nao sei'."""
    # (a) o sidecar da versao: o caminho exato. Se ele nao responder (ainda nao
    #     publicado, 404, sem rede), NAO desistimos — caimos no (b). O aviso de versao
    #     nova nao pode depender de um unico arquivo estar no ar.
    prazo = _prazo()
    versao_url = (d.get("versao_url") or "").strip()
    minha = (d.get("versao") or "").strip()
    if versao_url and minha:
        try:
            sobra = _resta(prazo)
            no_ar = _versao_publicada(versao_url, timeout=sobra) if sobra else ""
            if no_ar:
                if not _e_mais_nova(no_ar, minha):
                    return False, "", ""      # igual — ou a minha pasta esta' a frente
                # JA TENTAMOS ESTA VERSAO E ELA NAO ENTROU — ou o aluno ja disse "agora
                # nao"? Entao calamos. O primeiro caso acontece quando o site anuncia uma
                # versao que o zip ainda nao tem (o sidecar subiu, o zip nao; ou um cache
                # antigo respondeu): sem a trava, o aluno atualizava, a versao continuava
                # a mesma e o aviso voltava sempre. O segundo passou a importar quando a
                # checagem virou de TODA abertura: sem ele, "agora nao" seria perguntado
                # de novo no minuto seguinte. Um alarme que nunca desliga e' pior do que
                # nao ter alarme.
                if _ja_dispensada(no_ar):
                    return False, "", ""
                return True, Tf("a sua é {0}; no ar está a {1}", minha, no_ar), no_ar
        except Exception:
            pass

    # (b) o HEAD no proprio link de download: o Content-Length do zip publicado.
    url = (d.get("download_url") or "").strip()
    if not url:
        return False, "", ""
    try:
        sobra = _resta(prazo)
        if not sobra:
            return False, "", ""          # o orcamento acabou no (a): o menu vem primeiro
        tamanho = _tamanho_publicado(url, timeout=sobra)
    except Exception:
        return False, "", ""
    if not tamanho:
        return False, "", ""
    visto = _estado_versao().get("tamanho")
    if not visto:
        # primeira checagem: nao ha com o que comparar. Registramos o tamanho de hoje
        # e ficamos quietos — avisar "saiu versao nova" para quem ACABOU de baixar
        # seria mentira, e o aluno perderia a confianca no aviso.
        _gravar_estado_versao(tamanho=tamanho)
        return False, "", ""
    if tamanho != visto:
        return True, "o pacote publicado mudou de tamanho", ""
    return False, "", ""


def checar_atualizacao():
    """A CADA ABERTURA, ve se saiu versao nova e OFERECE atualizar. Nunca levanta.

    Quem impede o assedio nao e' mais o relogio (a janela saiu — ver ORCAMENTO_CHECAGEM_S):
    e' o silencio por versao. Perguntou e o aluno disse nao? Aquela versao nao volta a ser
    oferecida; a proxima, sim.
    """
    try:
        from env import cfg
        # O "nao" do aluno vem no idioma dele: o .env do pacote EN traz CHECK_UPDATE=YES,
        # e quem quisesse desligar escreveria NO — que nao estava nesta lista, entao a
        # checagem continuava ligada e a escolha dele era ignorada em silencio.
        if (cfg().get("CHECK_UPDATE", "SIM") or "SIM").strip().upper() in (
                "NAO", "N", "0", "OFF", "NO", "FALSE", "NAO CHECAR"):
            return
    except (Exception, SystemExit):
        # SystemExit TAMBEM: env.cfg() nao devolve erro, ele MATA o processo
        # (`raise SystemExit("ERRO: arquivo .env nao encontrado")`). Como SystemExit nao
        # e' Exception, o `except Exception:` daqui — e o do fim da funcao — deixavam
        # passar: um .env ausente ou ilegivel derrubava o menu inteiro na checagem de
        # versao, que e' justamente a parte que "nunca pode segurar o menu".
        pass                                  # sem .env legivel: segue com o padrao (checar)
    try:
        d = _curso_bruto()
        if not ((d.get("versao_url") or "").strip() or (d.get("download_url") or "").strip()):
            return                            # pacote de pre-lancamento: nao ha o que checar
        nova, detalhe, no_ar = _ha_versao_nova(d)
        # `checado_em` continua sendo gravado, mas agora e' so' DIAGNOSTICO — "quando este
        # curso falou com o site pela ultima vez". Ele nao segura mais nada: quem lia esse
        # campo para decidir era a janela, e a janela saiu.
        _gravar_estado_versao(checado_em=_agora_ts())
        if not nova:
            return
        print("\n" + LINHA)
        print(T("  SAIU UMA VERSAO NOVA DO CURSO") + (f" ({detalhe})" if detalhe else ""))
        print(LINHA)
        print(T("  A atualizacao troca os scripts do curso e PRESERVA o seu .env — o seu"))
        print(T("  token, o seu quadro e o seu progresso continuam exatamente como estao."))
        try:
            r = input(T("\n  Atualizar agora? [S/n] ")).strip().lower()
        except (EOFError, KeyboardInterrupt):
            return
        if r not in ("", "s", "sim", "y", "yes"):
            # O "nao" fica GRAVADO, e por versao. A checagem agora roda a cada abertura:
            # sem isto, o aluno que adiou uma vez responderia a mesma pergunta em toda
            # abertura ate ceder — que e' o oposto de respeitar o "agora nao" dele.
            # A opcao 0 do menu continua atualizando na hora, quando ELE quiser.
            if no_ar:
                _gravar_estado_versao(recusou=no_ar)
            print(T("  Ok — voce pode atualizar quando quiser pela opcao 0 do menu.\n"))
            return
        _atualizar_agora(no_ar)
    except (Exception, SystemExit):
        return                                # a checagem NUNCA pode segurar o menu


def _curso_bruto():
    """O .together inteiro (o curso() acima devolve so' label/board_name)."""
    try:
        with open(os.path.join(HERE, ".together"), encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}


def _atualizar_agora(no_ar=""):
    """Roda o update.py e anota o que foi instalado, para nao reperguntar amanha."""
    up = os.path.join(HERE, "update.py")
    if not os.path.exists(up):
        print(T("  (nao achei o update.py nesta pasta — atualize pela opcao 0 do menu.)"))
        return
    print()
    subprocess.run([sys.executable, up], cwd=HERE)
    d = _curso_bruto()                        # o .together pode ter vindo novo no zip
    try:
        if no_ar:
            _gravar_estado_versao(tentou=no_ar)
        if not (d.get("versao_url") or "").strip():
            _gravar_estado_versao(tamanho=_tamanho_publicado(d.get("download_url") or ""))
    except Exception:
        pass
    print(T("\n  Pronto — o menu abaixo ja e' a versao nova.\n"))


def open_menu():
    menu = os.path.join(HERE, "menu.py")
    if os.path.exists(menu):
        subprocess.run([sys.executable, menu], cwd=HERE)
    else:
        print(T("[!] menu.py nao encontrado nesta pasta (extraiu o curso direto aqui?)."))
        input(T("Enter para sair..."))


if __name__ == "__main__":
    try:
        configurar()
    except (KeyboardInterrupt, EOFError):
        print(T("\n(configuracao cancelada — clique no Iniciar de novo quando quiser)"))
        sys.exit(0)
    except SystemExit:
        raise
    except Exception as e:
        print(Tf('\n[!] Nao consegui concluir a configuracao: {0}', e))
        print(T("    Rode o Iniciar de novo — ele continua de onde parou."))
        input(T("    Enter para abrir o menu mesmo assim..."))
    checar_atualizacao()
    open_menu()
