/* eslint-disable react-refresh/only-export-components */
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from "react";

/** The languages the project ships (same set as the GitHub README). Labels are endonyms. */
export const LANGS = [
  { code: "en", label: "English" },
  { code: "pt", label: "Português" },
  { code: "es", label: "Español" },
  { code: "fr", label: "Français" },
  { code: "de", label: "Deutsch" },
  { code: "zh", label: "中文" },
  { code: "ja", label: "日本語" },
] as const;

export type Lang = (typeof LANGS)[number]["code"];

/** Translation keys. English is the source of truth; other languages mirror these keys. */
type Dict = Record<string, string>;

const en: Dict = {
  "nav.chat": "Chat",
  "nav.memory": "Memory",
  "nav.skills": "Skills",
  "nav.schedule": "Schedule",
  "nav.tasks": "Tasks",
  "nav.settings": "Settings",
  "theme.light": "Light theme",
  "theme.dark": "Dark theme",
  "sessions.new": "New chat",
  "sessions.empty": "No conversations yet.",
  "common.delete": "Delete",
  "common.save": "Save",
  "common.set": "Set",
  "common.replace": "Replace",
  "common.add": "Add",
  "common.search": "search…",
  "composer.placeholder": "Message Chimera…",
  "composer.hint": "Enter to send · Shift+Enter for a new line",
  "composer.stop": "Stop",
  "composer.send": "Send",
  "chat.header.new": "New chat",
  "chat.header.chat": "Chat",
  "chat.empty.body":
    "Ask anything. Tokens stream live; the panel on the right shows the tools, cost, and memory each turn actually used.",
  "activity.idle": "idle",
  "activity.thinking": "thinking…",
  "activity.streaming": "streaming…",
  "activity.done": "done",
  "activity.tools": "Tools",
  "activity.tokens": "Tokens",
  "activity.memory": "Memory",
  "activity.noTools": "no tools this turn",
  "activity.costUnavailable": "unavailable",
  "activity.exclCache": "(excl. cache)",
  "activity.factsRecalled": "{n} recalled",
  "settings.title": "Settings",
  "settings.card.appearance": "Appearance",
  "settings.card.status": "Status",
  "settings.card.model": "Model",
  "settings.card.apiKeys": "API keys",
  "settings.card.memory": "Memory",
  "settings.card.cacheSandbox": "Cache & sandbox",
  "settings.card.server": "Server",
  "settings.row.language": "Language",
  "settings.hint.language": "the interface language",
  "settings.row.providersWithKey": "Providers with a key",
  "settings.row.modelLadder": "Model ladder (weak · mid · top)",
  "settings.hint.modelLadder": "resolved from cost mode + overrides",
  "settings.row.defaultModel": "Default model",
  "settings.row.costMode": "Cost mode",
  "settings.hint.costMode": "how the tier ladder is filled",
  "settings.row.cascade": "Cascade",
  "settings.hint.cascade": "weak → gate → mid → gate → fusion",
  "settings.row.backend": "Backend",
  "settings.row.semantic": "Semantic recall",
  "settings.hint.semantic": "embeddings, falls back to FTS",
  "settings.row.completionCache": "Completion cache",
  "settings.hint.completionCache": "deterministic (temp=0) requests only",
  "settings.row.sandbox": "Sandbox",
  "settings.row.bearer": "API bearer token",
  "settings.hint.bearer": "required for write endpoints when set",
  "settings.isSet": "set",
  "settings.notSet": "not set",
  "settings.none": "none",
  "settings.pasteKey": "paste key…",
  "settings.saveError": "Couldn't save — is the bearer token required?",
  "memory.title": "Memory",
  "memory.addFact": "Add a fact",
  "memory.placeholder": "a durable fact Chimera should remember…",
  "memory.persona": "persona",
  "memory.stored": "Stored facts",
  "memory.emptySearch": "No facts match that search.",
  "memory.empty": "No stored facts yet.",
  "memory.unverified": "unverified",
  "skills.title": "Skills",
  "skills.learned": "Learned skills",
  "skills.retire": "Retire",
  "skills.active": "active",
  "skills.stats": "{uses} uses · {wins} wins",
  "cron.title": "Schedule",
  "cron.jobs": "Scheduled jobs",
  "cron.empty": "No scheduled jobs. Add one with `chimera cron add`.",
  "cron.agent": "agent",
  "cron.enable": "Enable",
  "cron.disable": "Disable",
  "tasks.title": "Tasks",
  "tasks.projects": "Projects",
  "tasks.projectsEmpty": "No projects. Start one with `chimera project start --spec spec.yaml`.",
  "tasks.board": "Board",
  "tasks.boardEmpty": "The board is empty.",
  "tasks.highRisk": "high risk",
  "tasks.approveStep": "Approve step",
  "tasks.approvePlan": "Approve plan",
  "tasks.deny": "Deny",
  "tasks.iter": "iter {a}/{b}",
  "tasks.awaitingStep": "A high-risk step needs approval (card {card}).",
  "tasks.awaitingPlan": "The initial plan needs approval before it runs.",
  "common.approve": "Approve",
  "skills.empty": "No learned skills yet — they're distilled from verified runs.",
};

const pt: Dict = {
  "nav.chat": "Chat",
  "nav.memory": "Memória",
  "nav.skills": "Habilidades",
  "nav.schedule": "Agenda",
  "nav.tasks": "Tarefas",
  "nav.settings": "Configurações",
  "theme.light": "Tema claro",
  "theme.dark": "Tema escuro",
  "sessions.new": "Nova conversa",
  "sessions.empty": "Nenhuma conversa ainda.",
  "common.delete": "Excluir",
  "common.save": "Salvar",
  "common.set": "Definir",
  "common.replace": "Substituir",
  "common.add": "Adicionar",
  "common.search": "buscar…",
  "composer.placeholder": "Escreva para o Chimera…",
  "composer.hint": "Enter para enviar · Shift+Enter para nova linha",
  "composer.stop": "Parar",
  "composer.send": "Enviar",
  "chat.header.new": "Nova conversa",
  "chat.header.chat": "Conversa",
  "chat.empty.body":
    "Pergunte qualquer coisa. Os tokens aparecem ao vivo; o painel à direita mostra as ferramentas, o custo e a memória que cada turno realmente usou.",
  "activity.idle": "ocioso",
  "activity.thinking": "pensando…",
  "activity.streaming": "transmitindo…",
  "activity.done": "concluído",
  "activity.tools": "Ferramentas",
  "activity.tokens": "Tokens",
  "activity.memory": "Memória",
  "activity.noTools": "nenhuma ferramenta neste turno",
  "activity.costUnavailable": "indisponível",
  "activity.exclCache": "(excl. cache)",
  "activity.factsRecalled": "{n} recuperado(s)",
  "settings.title": "Configurações",
  "settings.card.appearance": "Aparência",
  "settings.card.status": "Status",
  "settings.card.model": "Modelo",
  "settings.card.apiKeys": "Chaves de API",
  "settings.card.memory": "Memória",
  "settings.card.cacheSandbox": "Cache e sandbox",
  "settings.card.server": "Servidor",
  "settings.row.language": "Idioma",
  "settings.hint.language": "o idioma da interface",
  "settings.row.providersWithKey": "Provedores com chave",
  "settings.row.modelLadder": "Escada de modelos (fraco · médio · topo)",
  "settings.hint.modelLadder": "resolvida pelo modo de custo + overrides",
  "settings.row.defaultModel": "Modelo padrão",
  "settings.row.costMode": "Modo de custo",
  "settings.hint.costMode": "como a escada de tiers é preenchida",
  "settings.row.cascade": "Cascata",
  "settings.hint.cascade": "fraco → gate → médio → gate → fusão",
  "settings.row.backend": "Backend",
  "settings.row.semantic": "Recuperação semântica",
  "settings.hint.semantic": "embeddings, com fallback para FTS",
  "settings.row.completionCache": "Cache de respostas",
  "settings.hint.completionCache": "apenas requisições determinísticas (temp=0)",
  "settings.row.sandbox": "Sandbox",
  "settings.row.bearer": "Token bearer da API",
  "settings.hint.bearer": "exigido nos endpoints de escrita quando definido",
  "settings.isSet": "definida",
  "settings.notSet": "não definida",
  "settings.none": "nenhum",
  "settings.pasteKey": "cole a chave…",
  "settings.saveError": "Não foi possível salvar — o token bearer é exigido?",
  "memory.title": "Memória",
  "memory.addFact": "Adicionar um fato",
  "memory.placeholder": "um fato duradouro que o Chimera deve lembrar…",
  "memory.persona": "persona",
  "memory.stored": "Fatos armazenados",
  "memory.emptySearch": "Nenhum fato corresponde à busca.",
  "memory.empty": "Nenhum fato armazenado ainda.",
  "memory.unverified": "não verificado",
  "skills.title": "Habilidades",
  "skills.learned": "Habilidades aprendidas",
  "skills.retire": "Aposentar",
  "skills.active": "ativa",
  "skills.stats": "{uses} usos · {wins} acertos",
  "cron.title": "Agenda",
  "cron.jobs": "Tarefas agendadas",
  "cron.empty": "Nenhuma tarefa agendada. Adicione uma com `chimera cron add`.",
  "cron.agent": "agente",
  "cron.enable": "Ativar",
  "cron.disable": "Desativar",
  "tasks.title": "Tarefas",
  "tasks.projects": "Projetos",
  "tasks.projectsEmpty": "Nenhum projeto. Inicie um com `chimera project start --spec spec.yaml`.",
  "tasks.board": "Quadro",
  "tasks.boardEmpty": "O quadro está vazio.",
  "tasks.highRisk": "alto risco",
  "tasks.approveStep": "Aprovar passo",
  "tasks.approvePlan": "Aprovar plano",
  "tasks.deny": "Negar",
  "tasks.iter": "iter {a}/{b}",
  "tasks.awaitingStep": "Um passo de alto risco precisa de aprovação (card {card}).",
  "tasks.awaitingPlan": "O plano inicial precisa de aprovação antes de rodar.",
  "common.approve": "Aprovar",
  "skills.empty": "Nenhuma habilidade aprendida ainda — são destiladas de execuções verificadas.",
};

const es: Dict = {
  "nav.chat": "Chat",
  "nav.memory": "Memoria",
  "nav.skills": "Habilidades",
  "nav.schedule": "Agenda",
  "nav.tasks": "Tareas",
  "nav.settings": "Ajustes",
  "theme.light": "Tema claro",
  "theme.dark": "Tema oscuro",
  "sessions.new": "Nuevo chat",
  "sessions.empty": "Aún no hay conversaciones.",
  "common.delete": "Eliminar",
  "common.save": "Guardar",
  "common.set": "Definir",
  "common.replace": "Reemplazar",
  "common.add": "Añadir",
  "common.search": "buscar…",
  "composer.placeholder": "Escribe a Chimera…",
  "composer.hint": "Enter para enviar · Shift+Enter para una nueva línea",
  "composer.stop": "Detener",
  "composer.send": "Enviar",
  "chat.header.new": "Nuevo chat",
  "chat.header.chat": "Chat",
  "chat.empty.body":
    "Pregunta lo que sea. Los tokens llegan en vivo; el panel de la derecha muestra las herramientas, el coste y la memoria que cada turno usó realmente.",
  "activity.idle": "inactivo",
  "activity.thinking": "pensando…",
  "activity.streaming": "transmitiendo…",
  "activity.done": "listo",
  "activity.tools": "Herramientas",
  "activity.tokens": "Tokens",
  "activity.memory": "Memoria",
  "activity.noTools": "sin herramientas en este turno",
  "activity.costUnavailable": "no disponible",
  "activity.exclCache": "(excl. caché)",
  "activity.factsRecalled": "{n} recuperado(s)",
  "settings.title": "Ajustes",
  "settings.card.appearance": "Apariencia",
  "settings.card.status": "Estado",
  "settings.card.model": "Modelo",
  "settings.card.apiKeys": "Claves de API",
  "settings.card.memory": "Memoria",
  "settings.card.cacheSandbox": "Caché y sandbox",
  "settings.card.server": "Servidor",
  "settings.row.language": "Idioma",
  "settings.hint.language": "el idioma de la interfaz",
  "settings.row.providersWithKey": "Proveedores con clave",
  "settings.row.modelLadder": "Escala de modelos (débil · medio · alto)",
  "settings.hint.modelLadder": "resuelta por el modo de coste + overrides",
  "settings.row.defaultModel": "Modelo por defecto",
  "settings.row.costMode": "Modo de coste",
  "settings.hint.costMode": "cómo se rellena la escala de niveles",
  "settings.row.cascade": "Cascada",
  "settings.hint.cascade": "débil → gate → medio → gate → fusión",
  "settings.row.backend": "Backend",
  "settings.row.semantic": "Recuperación semántica",
  "settings.hint.semantic": "embeddings, con respaldo en FTS",
  "settings.row.completionCache": "Caché de respuestas",
  "settings.hint.completionCache": "solo peticiones deterministas (temp=0)",
  "settings.row.sandbox": "Sandbox",
  "settings.row.bearer": "Token bearer de la API",
  "settings.hint.bearer": "requerido en endpoints de escritura cuando se define",
  "settings.isSet": "definida",
  "settings.notSet": "sin definir",
  "settings.none": "ninguno",
  "settings.pasteKey": "pega la clave…",
  "settings.saveError": "No se pudo guardar — ¿se requiere el token bearer?",
  "memory.title": "Memoria",
  "memory.addFact": "Añadir un hecho",
  "memory.placeholder": "un hecho duradero que Chimera debería recordar…",
  "memory.persona": "persona",
  "memory.stored": "Hechos guardados",
  "memory.emptySearch": "Ningún hecho coincide con la búsqueda.",
  "memory.empty": "Aún no hay hechos guardados.",
  "memory.unverified": "no verificado",
  "skills.title": "Habilidades",
  "skills.learned": "Habilidades aprendidas",
  "skills.retire": "Retirar",
  "skills.active": "activa",
  "skills.stats": "{uses} usos · {wins} aciertos",
  "cron.title": "Agenda",
  "cron.jobs": "Tareas programadas",
  "cron.empty": "No hay tareas programadas. Añade una con `chimera cron add`.",
  "cron.agent": "agente",
  "cron.enable": "Activar",
  "cron.disable": "Desactivar",
  "tasks.title": "Tareas",
  "tasks.projects": "Proyectos",
  "tasks.projectsEmpty": "No hay proyectos. Inicia uno con `chimera project start --spec spec.yaml`.",
  "tasks.board": "Tablero",
  "tasks.boardEmpty": "El tablero está vacío.",
  "tasks.highRisk": "alto riesgo",
  "tasks.approveStep": "Aprobar paso",
  "tasks.approvePlan": "Aprobar plan",
  "tasks.deny": "Denegar",
  "tasks.iter": "iter {a}/{b}",
  "tasks.awaitingStep": "Un paso de alto riesgo necesita aprobación (card {card}).",
  "tasks.awaitingPlan": "El plan inicial necesita aprobación antes de ejecutarse.",
  "common.approve": "Aprobar",
  "skills.empty": "Aún no hay habilidades aprendidas — se destilan de ejecuciones verificadas.",
};

const fr: Dict = {
  "nav.chat": "Chat",
  "nav.memory": "Mémoire",
  "nav.skills": "Compétences",
  "nav.schedule": "Planning",
  "nav.tasks": "Tâches",
  "nav.settings": "Paramètres",
  "theme.light": "Thème clair",
  "theme.dark": "Thème sombre",
  "sessions.new": "Nouveau chat",
  "sessions.empty": "Aucune conversation pour l'instant.",
  "common.delete": "Supprimer",
  "common.save": "Enregistrer",
  "common.set": "Définir",
  "common.replace": "Remplacer",
  "common.add": "Ajouter",
  "common.search": "rechercher…",
  "composer.placeholder": "Écrire à Chimera…",
  "composer.hint": "Entrée pour envoyer · Maj+Entrée pour une nouvelle ligne",
  "composer.stop": "Arrêter",
  "composer.send": "Envoyer",
  "chat.header.new": "Nouveau chat",
  "chat.header.chat": "Chat",
  "chat.empty.body":
    "Demandez ce que vous voulez. Les tokens s'affichent en direct ; le panneau de droite montre les outils, le coût et la mémoire réellement utilisés à chaque tour.",
  "activity.idle": "inactif",
  "activity.thinking": "réflexion…",
  "activity.streaming": "diffusion…",
  "activity.done": "terminé",
  "activity.tools": "Outils",
  "activity.tokens": "Tokens",
  "activity.memory": "Mémoire",
  "activity.noTools": "aucun outil ce tour-ci",
  "activity.costUnavailable": "indisponible",
  "activity.exclCache": "(hors cache)",
  "activity.factsRecalled": "{n} récupéré(s)",
  "settings.title": "Paramètres",
  "settings.card.appearance": "Apparence",
  "settings.card.status": "Statut",
  "settings.card.model": "Modèle",
  "settings.card.apiKeys": "Clés d'API",
  "settings.card.memory": "Mémoire",
  "settings.card.cacheSandbox": "Cache et sandbox",
  "settings.card.server": "Serveur",
  "settings.row.language": "Langue",
  "settings.hint.language": "la langue de l'interface",
  "settings.row.providersWithKey": "Fournisseurs avec une clé",
  "settings.row.modelLadder": "Échelle de modèles (faible · moyen · haut)",
  "settings.hint.modelLadder": "résolue par le mode de coût + overrides",
  "settings.row.defaultModel": "Modèle par défaut",
  "settings.row.costMode": "Mode de coût",
  "settings.hint.costMode": "comment l'échelle de niveaux est remplie",
  "settings.row.cascade": "Cascade",
  "settings.hint.cascade": "faible → gate → moyen → gate → fusion",
  "settings.row.backend": "Backend",
  "settings.row.semantic": "Rappel sémantique",
  "settings.hint.semantic": "embeddings, repli sur FTS",
  "settings.row.completionCache": "Cache des réponses",
  "settings.hint.completionCache": "requêtes déterministes uniquement (temp=0)",
  "settings.row.sandbox": "Sandbox",
  "settings.row.bearer": "Token bearer de l'API",
  "settings.hint.bearer": "requis sur les endpoints d'écriture s'il est défini",
  "settings.isSet": "définie",
  "settings.notSet": "non définie",
  "settings.none": "aucun",
  "settings.pasteKey": "collez la clé…",
  "settings.saveError": "Échec de l'enregistrement — le token bearer est-il requis ?",
  "memory.title": "Mémoire",
  "memory.addFact": "Ajouter un fait",
  "memory.placeholder": "un fait durable que Chimera devrait retenir…",
  "memory.persona": "persona",
  "memory.stored": "Faits enregistrés",
  "memory.emptySearch": "Aucun fait ne correspond à la recherche.",
  "memory.empty": "Aucun fait enregistré pour l'instant.",
  "memory.unverified": "non vérifié",
  "skills.title": "Compétences",
  "skills.learned": "Compétences apprises",
  "skills.retire": "Retirer",
  "skills.active": "active",
  "skills.stats": "{uses} usages · {wins} réussites",
  "cron.title": "Planning",
  "cron.jobs": "Tâches planifiées",
  "cron.empty": "Aucune tâche planifiée. Ajoutez-en une avec `chimera cron add`.",
  "cron.agent": "agent",
  "cron.enable": "Activer",
  "cron.disable": "Désactiver",
  "tasks.title": "Tâches",
  "tasks.projects": "Projets",
  "tasks.projectsEmpty": "Aucun projet. Démarrez-en un avec `chimera project start --spec spec.yaml`.",
  "tasks.board": "Tableau",
  "tasks.boardEmpty": "Le tableau est vide.",
  "tasks.highRisk": "risque élevé",
  "tasks.approveStep": "Approuver l'étape",
  "tasks.approvePlan": "Approuver le plan",
  "tasks.deny": "Refuser",
  "tasks.iter": "iter {a}/{b}",
  "tasks.awaitingStep": "Une étape à risque élevé requiert une approbation (card {card}).",
  "tasks.awaitingPlan": "Le plan initial requiert une approbation avant de s'exécuter.",
  "common.approve": "Approuver",
  "skills.empty": "Aucune compétence apprise pour l'instant — elles sont distillées d'exécutions vérifiées.",
};

const de: Dict = {
  "nav.chat": "Chat",
  "nav.memory": "Speicher",
  "nav.skills": "Fähigkeiten",
  "nav.schedule": "Zeitplan",
  "nav.tasks": "Aufgaben",
  "nav.settings": "Einstellungen",
  "theme.light": "Helles Thema",
  "theme.dark": "Dunkles Thema",
  "sessions.new": "Neuer Chat",
  "sessions.empty": "Noch keine Unterhaltungen.",
  "common.delete": "Löschen",
  "common.save": "Speichern",
  "common.set": "Festlegen",
  "common.replace": "Ersetzen",
  "common.add": "Hinzufügen",
  "common.search": "suchen…",
  "composer.placeholder": "Nachricht an Chimera…",
  "composer.hint": "Enter zum Senden · Umschalt+Enter für eine neue Zeile",
  "composer.stop": "Stopp",
  "composer.send": "Senden",
  "chat.header.new": "Neuer Chat",
  "chat.header.chat": "Chat",
  "chat.empty.body":
    "Frag was du willst. Tokens erscheinen live; das Panel rechts zeigt die Tools, die Kosten und den Speicher, die jede Runde tatsächlich genutzt hat.",
  "activity.idle": "inaktiv",
  "activity.thinking": "denkt…",
  "activity.streaming": "streamt…",
  "activity.done": "fertig",
  "activity.tools": "Tools",
  "activity.tokens": "Tokens",
  "activity.memory": "Speicher",
  "activity.noTools": "keine Tools in dieser Runde",
  "activity.costUnavailable": "nicht verfügbar",
  "activity.exclCache": "(ohne Cache)",
  "activity.factsRecalled": "{n} abgerufen",
  "settings.title": "Einstellungen",
  "settings.card.appearance": "Darstellung",
  "settings.card.status": "Status",
  "settings.card.model": "Modell",
  "settings.card.apiKeys": "API-Schlüssel",
  "settings.card.memory": "Speicher",
  "settings.card.cacheSandbox": "Cache & Sandbox",
  "settings.card.server": "Server",
  "settings.row.language": "Sprache",
  "settings.hint.language": "die Sprache der Oberfläche",
  "settings.row.providersWithKey": "Anbieter mit Schlüssel",
  "settings.row.modelLadder": "Modellleiter (schwach · mittel · stark)",
  "settings.hint.modelLadder": "aus Kostenmodus + Overrides ermittelt",
  "settings.row.defaultModel": "Standardmodell",
  "settings.row.costMode": "Kostenmodus",
  "settings.hint.costMode": "wie die Stufenleiter gefüllt wird",
  "settings.row.cascade": "Kaskade",
  "settings.hint.cascade": "schwach → Gate → mittel → Gate → Fusion",
  "settings.row.backend": "Backend",
  "settings.row.semantic": "Semantischer Abruf",
  "settings.hint.semantic": "Embeddings, Rückfall auf FTS",
  "settings.row.completionCache": "Antwort-Cache",
  "settings.hint.completionCache": "nur deterministische Anfragen (temp=0)",
  "settings.row.sandbox": "Sandbox",
  "settings.row.bearer": "API-Bearer-Token",
  "settings.hint.bearer": "für Schreib-Endpunkte erforderlich, wenn gesetzt",
  "settings.isSet": "gesetzt",
  "settings.notSet": "nicht gesetzt",
  "settings.none": "keine",
  "settings.pasteKey": "Schlüssel einfügen…",
  "settings.saveError": "Speichern fehlgeschlagen — ist der Bearer-Token erforderlich?",
  "memory.title": "Speicher",
  "memory.addFact": "Fakt hinzufügen",
  "memory.placeholder": "ein dauerhafter Fakt, den Chimera sich merken soll…",
  "memory.persona": "Persona",
  "memory.stored": "Gespeicherte Fakten",
  "memory.emptySearch": "Kein Fakt passt zur Suche.",
  "memory.empty": "Noch keine gespeicherten Fakten.",
  "memory.unverified": "nicht verifiziert",
  "skills.title": "Fähigkeiten",
  "skills.learned": "Erlernte Fähigkeiten",
  "skills.retire": "Zurückziehen",
  "skills.active": "aktiv",
  "skills.stats": "{uses} Nutzungen · {wins} Erfolge",
  "cron.title": "Zeitplan",
  "cron.jobs": "Geplante Jobs",
  "cron.empty": "Keine geplanten Jobs. Füge einen mit `chimera cron add` hinzu.",
  "cron.agent": "Agent",
  "cron.enable": "Aktivieren",
  "cron.disable": "Deaktivieren",
  "tasks.title": "Aufgaben",
  "tasks.projects": "Projekte",
  "tasks.projectsEmpty": "Keine Projekte. Starte eines mit `chimera project start --spec spec.yaml`.",
  "tasks.board": "Board",
  "tasks.boardEmpty": "Das Board ist leer.",
  "tasks.highRisk": "hohes Risiko",
  "tasks.approveStep": "Schritt genehmigen",
  "tasks.approvePlan": "Plan genehmigen",
  "tasks.deny": "Ablehnen",
  "tasks.iter": "iter {a}/{b}",
  "tasks.awaitingStep": "Ein Schritt mit hohem Risiko braucht Genehmigung (card {card}).",
  "tasks.awaitingPlan": "Der ursprüngliche Plan braucht Genehmigung, bevor er läuft.",
  "common.approve": "Genehmigen",
  "skills.empty": "Noch keine erlernten Fähigkeiten — sie werden aus verifizierten Läufen destilliert.",
};

const zh: Dict = {
  "nav.chat": "对话",
  "nav.memory": "记忆",
  "nav.skills": "技能",
  "nav.schedule": "计划",
  "nav.tasks": "任务",
  "nav.settings": "设置",
  "theme.light": "浅色主题",
  "theme.dark": "深色主题",
  "sessions.new": "新对话",
  "sessions.empty": "还没有对话。",
  "common.delete": "删除",
  "common.save": "保存",
  "common.set": "设置",
  "common.replace": "替换",
  "common.add": "添加",
  "common.search": "搜索…",
  "composer.placeholder": "给 Chimera 发消息…",
  "composer.hint": "回车发送 · Shift+回车换行",
  "composer.stop": "停止",
  "composer.send": "发送",
  "chat.header.new": "新对话",
  "chat.header.chat": "对话",
  "chat.empty.body":
    "随便问。Token 会实时流式显示；右侧面板显示每一轮实际使用的工具、成本和记忆。",
  "activity.idle": "空闲",
  "activity.thinking": "思考中…",
  "activity.streaming": "输出中…",
  "activity.done": "完成",
  "activity.tools": "工具",
  "activity.tokens": "Token",
  "activity.memory": "记忆",
  "activity.noTools": "本轮未使用工具",
  "activity.costUnavailable": "不可用",
  "activity.exclCache": "（不含缓存）",
  "activity.factsRecalled": "已调用 {n} 条",
  "settings.title": "设置",
  "settings.card.appearance": "外观",
  "settings.card.status": "状态",
  "settings.card.model": "模型",
  "settings.card.apiKeys": "API 密钥",
  "settings.card.memory": "记忆",
  "settings.card.cacheSandbox": "缓存与沙箱",
  "settings.card.server": "服务器",
  "settings.row.language": "语言",
  "settings.hint.language": "界面语言",
  "settings.row.providersWithKey": "已配置密钥的提供商",
  "settings.row.modelLadder": "模型阶梯（弱 · 中 · 强）",
  "settings.hint.modelLadder": "由成本模式 + 覆盖项决定",
  "settings.row.defaultModel": "默认模型",
  "settings.row.costMode": "成本模式",
  "settings.hint.costMode": "如何填充分级阶梯",
  "settings.row.cascade": "级联",
  "settings.hint.cascade": "弱 → 判定 → 中 → 判定 → 融合",
  "settings.row.backend": "后端",
  "settings.row.semantic": "语义召回",
  "settings.hint.semantic": "使用嵌入，回退到 FTS",
  "settings.row.completionCache": "补全缓存",
  "settings.hint.completionCache": "仅限确定性请求（temp=0）",
  "settings.row.sandbox": "沙箱",
  "settings.row.bearer": "API Bearer 令牌",
  "settings.hint.bearer": "设置后写入接口需要它",
  "settings.isSet": "已设置",
  "settings.notSet": "未设置",
  "settings.none": "无",
  "settings.pasteKey": "粘贴密钥…",
  "settings.saveError": "保存失败 — 是否需要 Bearer 令牌？",
  "memory.title": "记忆",
  "memory.addFact": "添加一条事实",
  "memory.placeholder": "一条 Chimera 应长期记住的事实…",
  "memory.persona": "人设",
  "memory.stored": "已存事实",
  "memory.emptySearch": "没有匹配的事实。",
  "memory.empty": "还没有存储的事实。",
  "memory.unverified": "未验证",
  "skills.title": "技能",
  "skills.learned": "已学技能",
  "skills.retire": "停用",
  "skills.active": "启用",
  "skills.stats": "{uses} 次使用 · {wins} 次成功",
  "cron.title": "计划",
  "cron.jobs": "计划任务",
  "cron.empty": "没有计划任务。用 `chimera cron add` 添加一个。",
  "cron.agent": "代理",
  "cron.enable": "启用",
  "cron.disable": "停用",
  "tasks.title": "任务",
  "tasks.projects": "项目",
  "tasks.projectsEmpty": "没有项目。用 `chimera project start --spec spec.yaml` 启动一个。",
  "tasks.board": "看板",
  "tasks.boardEmpty": "看板为空。",
  "tasks.highRisk": "高风险",
  "tasks.approveStep": "批准该步骤",
  "tasks.approvePlan": "批准计划",
  "tasks.deny": "拒绝",
  "tasks.iter": "第 {a}/{b} 轮",
  "tasks.awaitingStep": "一个高风险步骤需要批准（card {card}）。",
  "tasks.awaitingPlan": "初始计划在运行前需要批准。",
  "common.approve": "批准",
  "skills.empty": "还没有习得的技能——它们从已验证的运行中提炼。",
};

const ja: Dict = {
  "nav.chat": "チャット",
  "nav.memory": "メモリ",
  "nav.skills": "スキル",
  "nav.schedule": "スケジュール",
  "nav.tasks": "タスク",
  "nav.settings": "設定",
  "theme.light": "ライトテーマ",
  "theme.dark": "ダークテーマ",
  "sessions.new": "新しいチャット",
  "sessions.empty": "まだ会話がありません。",
  "common.delete": "削除",
  "common.save": "保存",
  "common.set": "設定",
  "common.replace": "置き換え",
  "common.add": "追加",
  "common.search": "検索…",
  "composer.placeholder": "Chimera にメッセージ…",
  "composer.hint": "Enter で送信 · Shift+Enter で改行",
  "composer.stop": "停止",
  "composer.send": "送信",
  "chat.header.new": "新しいチャット",
  "chat.header.chat": "チャット",
  "chat.empty.body":
    "何でも聞いてください。トークンはライブで流れ、右のパネルには各ターンで実際に使われたツール・コスト・メモリが表示されます。",
  "activity.idle": "待機中",
  "activity.thinking": "思考中…",
  "activity.streaming": "生成中…",
  "activity.done": "完了",
  "activity.tools": "ツール",
  "activity.tokens": "トークン",
  "activity.memory": "メモリ",
  "activity.noTools": "このターンではツールなし",
  "activity.costUnavailable": "利用不可",
  "activity.exclCache": "（キャッシュ除く）",
  "activity.factsRecalled": "{n} 件を呼び出し",
  "settings.title": "設定",
  "settings.card.appearance": "外観",
  "settings.card.status": "ステータス",
  "settings.card.model": "モデル",
  "settings.card.apiKeys": "API キー",
  "settings.card.memory": "メモリ",
  "settings.card.cacheSandbox": "キャッシュとサンドボックス",
  "settings.card.server": "サーバー",
  "settings.row.language": "言語",
  "settings.hint.language": "インターフェースの言語",
  "settings.row.providersWithKey": "キー設定済みのプロバイダー",
  "settings.row.modelLadder": "モデル階層（弱 · 中 · 強）",
  "settings.hint.modelLadder": "コストモード + 上書きから決定",
  "settings.row.defaultModel": "既定のモデル",
  "settings.row.costMode": "コストモード",
  "settings.hint.costMode": "階層のラダーをどう埋めるか",
  "settings.row.cascade": "カスケード",
  "settings.hint.cascade": "弱 → ゲート → 中 → ゲート → 融合",
  "settings.row.backend": "バックエンド",
  "settings.row.semantic": "セマンティック検索",
  "settings.hint.semantic": "埋め込み、FTS にフォールバック",
  "settings.row.completionCache": "補完キャッシュ",
  "settings.hint.completionCache": "確定的なリクエストのみ（temp=0）",
  "settings.row.sandbox": "サンドボックス",
  "settings.row.bearer": "API ベアラートークン",
  "settings.hint.bearer": "設定時は書き込みエンドポイントで必須",
  "settings.isSet": "設定済み",
  "settings.notSet": "未設定",
  "settings.none": "なし",
  "settings.pasteKey": "キーを貼り付け…",
  "settings.saveError": "保存できませんでした — ベアラートークンが必要ですか？",
  "memory.title": "メモリ",
  "memory.addFact": "事実を追加",
  "memory.placeholder": "Chimera が覚えておくべき恒久的な事実…",
  "memory.persona": "ペルソナ",
  "memory.stored": "保存済みの事実",
  "memory.emptySearch": "検索に一致する事実がありません。",
  "memory.empty": "保存された事実はまだありません。",
  "memory.unverified": "未検証",
  "skills.title": "スキル",
  "skills.learned": "習得したスキル",
  "skills.retire": "引退",
  "skills.active": "有効",
  "skills.stats": "{uses} 回使用 · {wins} 回成功",
  "cron.title": "スケジュール",
  "cron.jobs": "予定されたジョブ",
  "cron.empty": "予定されたジョブはありません。`chimera cron add` で追加してください。",
  "cron.agent": "エージェント",
  "cron.enable": "有効化",
  "cron.disable": "無効化",
  "tasks.title": "タスク",
  "tasks.projects": "プロジェクト",
  "tasks.projectsEmpty": "プロジェクトがありません。`chimera project start --spec spec.yaml` で開始してください。",
  "tasks.board": "ボード",
  "tasks.boardEmpty": "ボードは空です。",
  "tasks.highRisk": "高リスク",
  "tasks.approveStep": "ステップを承認",
  "tasks.approvePlan": "計画を承認",
  "tasks.deny": "拒否",
  "tasks.iter": "iter {a}/{b}",
  "tasks.awaitingStep": "高リスクのステップに承認が必要です（card {card}）。",
  "tasks.awaitingPlan": "実行前に初期計画の承認が必要です。",
  "common.approve": "承認",
  "skills.empty": "習得したスキルはまだありません — 検証済みの実行から抽出されます。",
};

const DICTS: Record<Lang, Dict> = { en, pt, es, fr, de, zh, ja };

const STORAGE_KEY = "chimera.lang";

function detectLang(): Lang {
  const stored = localStorage.getItem(STORAGE_KEY);
  if (stored && stored in DICTS) return stored as Lang;
  const nav = (navigator.language || "en").slice(0, 2).toLowerCase();
  return (nav in DICTS ? nav : "en") as Lang;
}

/** Substitute `{name}` placeholders from `params`. */
function interpolate(template: string, params?: Record<string, string | number>): string {
  if (!params) return template;
  return template.replace(/\{(\w+)\}/g, (_, k: string) =>
    k in params ? String(params[k]) : `{${k}}`,
  );
}

export type TFunc = (key: string, params?: Record<string, string | number>) => string;

interface I18nValue {
  lang: Lang;
  setLang: (l: Lang) => void;
  t: TFunc;
}

const I18nContext = createContext<I18nValue | null>(null);

export function I18nProvider({ children }: { children: ReactNode }) {
  const [lang, setLangState] = useState<Lang>(detectLang);

  useEffect(() => {
    document.documentElement.lang = lang;
  }, [lang]);

  const setLang = useCallback((l: Lang) => {
    localStorage.setItem(STORAGE_KEY, l);
    setLangState(l);
  }, []);

  const t = useCallback<TFunc>(
    (key, params) => {
      const table = DICTS[lang] ?? en;
      const template = table[key] ?? en[key] ?? key;
      return interpolate(template, params);
    },
    [lang],
  );

  const value = useMemo<I18nValue>(() => ({ lang, setLang, t }), [lang, setLang, t]);
  return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
}

export function useI18n(): I18nValue {
  const ctx = useContext(I18nContext);
  if (!ctx) throw new Error("useI18n must be used within I18nProvider");
  return ctx;
}

/** Convenience: just the `t` function. */
export function useT(): TFunc {
  return useI18n().t;
}
