"""Language packs for the DataTools GUI.

A language pack is a JSON file under ``src/i18n/packs/`` keyed by ISO 639-1
language code (``en.json``, ``es.json``, …). Keys are dotted paths
(``home.title``, ``tools.deduplicator.name``); values are the translated
strings. The English pack is canonical — missing keys in other packs fall
back to the English value, and missing keys in English fall back to the
key itself so a typo surfaces as a visible string instead of a crash.

Adding a language: drop a new ``<code>.json`` next to ``en.json`` mirroring
its key tree, then add a one-line entry to ``LANGUAGES``. The sidebar
selector picks it up automatically.

Translation lookup is intentionally tiny — no gettext, no babel, no
po-file pipeline. Format-string interpolation is supplied by callers via
``str.format``; this module only resolves keys.
"""

from __future__ import annotations

import json
from functools import lru_cache
from pathlib import Path
from typing import Any

_PACK_DIR = Path(__file__).resolve().parent / "packs"
_DEFAULT_LANG = "en"
_SESSION_KEY = "ui_lang"


# Display registry. ``label`` is what the sidebar shows; ``code`` is the
# JSON filename stem. Keep this list short — every entry must have a
# fully-translated pack in ``packs/``.
LANGUAGES: list[dict[str, str]] = [
    {"code": "en", "label": "English"},
    {"code": "es", "label": "Español"},
]


def available_languages() -> list[dict[str, str]]:
    """Return the public language registry (a fresh list each call)."""
    return [dict(entry) for entry in LANGUAGES]


@lru_cache(maxsize=8)
def _load_pack(lang: str) -> dict[str, Any]:
    """Read a pack JSON off disk. Cached so re-renders don't reparse."""
    path = _PACK_DIR / f"{lang}.json"
    if not path.exists():
        return {}
    with path.open("r", encoding="utf-8") as fh:
        return json.load(fh)


def _resolve(pack: dict[str, Any], key: str) -> Any:
    """Walk a dotted key through a nested dict. Returns None if absent."""
    node: Any = pack
    for part in key.split("."):
        if not isinstance(node, dict) or part not in node:
            return None
        node = node[part]
    return node


def t(key: str, lang: str | None = None, /, **kwargs: Any) -> str:
    """Look up *key* in the current language pack.

    Fallback chain: requested lang → English → the key itself. ``kwargs``
    are passed through ``str.format`` so call sites can use named
    placeholders (``t("upload.using", name=name)``). Missing placeholders
    are tolerated — the raw braces remain in the output rather than
    raising — because a translation file shouldn't be able to crash the
    UI.
    """
    if lang is None:
        lang = current_language()

    value = _resolve(_load_pack(lang), key)
    if value is None and lang != _DEFAULT_LANG:
        value = _resolve(_load_pack(_DEFAULT_LANG), key)
    if value is None:
        value = key

    if not isinstance(value, str):
        return str(value)

    if kwargs:
        try:
            return value.format(**kwargs)
        except (KeyError, IndexError):
            return value
    return value


def current_language() -> str:
    """Return the active language code, defaulting to English.

    Reads from ``st.session_state`` when Streamlit is loaded so a sidebar
    selector can change the language for the current session. Falls back
    to the default when called outside a Streamlit run (e.g. in tests),
    which keeps this module importable without Streamlit installed at
    that import path.
    """
    try:
        import streamlit as st
    except Exception:
        return _DEFAULT_LANG
    return st.session_state.get(_SESSION_KEY, _DEFAULT_LANG)


def set_language(lang: str) -> None:
    """Persist *lang* on the Streamlit session. No-op outside Streamlit."""
    try:
        import streamlit as st
    except Exception:
        return
    st.session_state[_SESSION_KEY] = lang


def render_language_selector(*, location: str = "sidebar") -> None:
    """Render the language picker.

    ``location`` is either ``"sidebar"`` (default) or ``"inline"``. The
    sidebar form is what the home page wires up so every tool page picks
    up the same selector through Streamlit's shared sidebar.
    """
    import streamlit as st

    target = st.sidebar if location == "sidebar" else st
    codes = [entry["code"] for entry in LANGUAGES]
    labels = {entry["code"]: entry["label"] for entry in LANGUAGES}
    current = current_language()
    if current not in codes:
        current = _DEFAULT_LANG

    choice = target.selectbox(
        t("chrome.language_label"),
        codes,
        index=codes.index(current),
        format_func=lambda c: labels.get(c, c),
        key="_ui_lang_select",
    )
    if choice != current:
        set_language(choice)
        st.rerun()


__all__ = [
    "LANGUAGES",
    "available_languages",
    "current_language",
    "render_language_selector",
    "set_language",
    "t",
]
