"""
Internationalization service for Sentinel.

The Brain produces stable technical codes.
The user interface translates those codes.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any


class TranslationError(RuntimeError):
    """Raised when translation resources cannot be loaded."""


class Translator:
    """
    Translate stable Sentinel codes into user-facing text.

    Missing keys fall back to the default language, then to the key itself.
    """

    SUPPORTED_LANGUAGES = ("en", "fr")

    def __init__(
        self,
        translations_directory: str | Path | None = None,
        default_language: str = "en",
    ) -> None:
        """
        Initialize the translator.

        Args:
            translations_directory: Folder containing language JSON files.
            default_language: Fallback language.
        """
        if default_language not in self.SUPPORTED_LANGUAGES:
            raise ValueError(
                f"Unsupported default language: {default_language}"
            )

        if translations_directory is None:
            translations_directory = Path(__file__).resolve().parent

        self.translations_directory = Path(
            translations_directory
        )
        self.default_language = default_language
        self._catalogues: dict[str, dict[str, Any]] = {}

    def _load_language(
        self,
        language: str,
    ) -> dict[str, Any]:
        """Load and cache one translation catalogue."""
        if language not in self.SUPPORTED_LANGUAGES:
            language = self.default_language

        if language in self._catalogues:
            return self._catalogues[language]

        path = self.translations_directory / f"{language}.json"

        try:
            with path.open("r", encoding="utf-8") as handle:
                catalogue = json.load(handle)
        except (OSError, json.JSONDecodeError) as error:
            raise TranslationError(
                f"Unable to load translation file: {path}"
            ) from error

        self._catalogues[language] = catalogue
        return catalogue

    @staticmethod
    def _resolve_key(
        catalogue: dict[str, Any],
        key: str,
    ) -> str | None:
        """Resolve a dotted translation key."""
        current: Any = catalogue

        for part in key.split("."):
            if not isinstance(current, dict):
                return None

            current = current.get(part)

            if current is None:
                return None

        return current if isinstance(current, str) else None

    def translate(
        self,
        key: str,
        language: str | None = None,
        **variables: object,
    ) -> str:
        """
        Translate one key and interpolate named variables.

        Unknown keys return the key itself.
        """
        selected_language = language or self.default_language

        catalogue = self._load_language(
            selected_language
        )
        text = self._resolve_key(
            catalogue,
            key,
        )

        if text is None and selected_language != self.default_language:
            fallback_catalogue = self._load_language(
                self.default_language
            )
            text = self._resolve_key(
                fallback_catalogue,
                key,
            )

        if text is None:
            return key

        try:
            return text.format(**variables)
        except KeyError:
            return text
