"""
Transliteration between Greek/Hebrew and academic Latin (ASCII).

Provides a stateless ``Transliterator`` class and module-level convenience
functions.  All functions are pure and do not depend on any other Accordance
Port module (avoids import cycles).

Usage
-----
    from accordance_parser.transliteration import Transliterator

    Transliterator.to_latin("λόγος", "el")       # → "logos"
    Transliterator.to_native("logos", "el")      # → "λογος"
    Transliterator.to_native_variants("theos", "el")  # → ["θεος", "θηος", "θεω", "θηω"]
    Transliterator.normalize("λόγος")            # → "λογος"
    Transliterator.classify("logos")             # → (True, "el")
"""

from __future__ import annotations

import unicodedata
from itertools import product

# =========================================================================
# Mapping tables
# =========================================================================

# Greek → Latin academic transliteration
GREEK_TO_LATIN: dict[str, str] = {
    "α": "a",
    "β": "b",
    "γ": "g",
    "δ": "d",
    "ε": "e",
    "ζ": "z",
    "η": "ē",
    "θ": "th",
    "ι": "i",
    "κ": "k",
    "λ": "l",
    "μ": "m",
    "ν": "n",
    "ξ": "x",
    "ο": "o",
    "π": "p",
    "ρ": "r",
    "σ": "s",
    "ς": "s",
    "τ": "t",
    "υ": "u",
    "φ": "ph",
    "χ": "ch",
    "ψ": "ps",
    "ω": "ō",
    # Uppercase
    "Α": "A",
    "Β": "B",
    "Γ": "G",
    "Δ": "D",
    "Ε": "E",
    "Ζ": "Z",
    "Η": "Ē",
    "Θ": "Th",
    "Ι": "I",
    "Κ": "K",
    "Λ": "L",
    "Μ": "M",
    "Ν": "N",
    "Ξ": "X",
    "Ο": "O",
    "Π": "P",
    "Ρ": "R",
    "Σ": "S",
    "Τ": "T",
    "Υ": "U",
    "Φ": "Ph",
    "Χ": "Ch",
    "Ψ": "Ps",
    "Ω": "Ō",
    # Rough breathing (polytonic Greek)
    "ἁ": "ha",
    "ἑ": "he",
    "ἡ": "hē",
    "ἱ": "hi",
    "ὁ": "ho",
    "ὑ": "hy",
    "ὡ": "hō",
    "ἃ": "ha",
    "ἓ": "he",
    "ἣ": "hē",
    "ἳ": "hi",
    "ὃ": "ho",
    "ὓ": "hy",
    "ὣ": "hō",
    "ἅ": "ha",
    "ἕ": "he",
    "ἥ": "hē",
    "ἵ": "hi",
    "ὅ": "ho",
    "ὕ": "hy",
    "ὥ": "hō",
    "ἇ": "ha",
    "ἧ": "hē",
    "ἷ": "hi",
    "὇": "ho",
    "ὗ": "hy",
    "ὧ": "hō",
    "Ἁ": "Ha",
    "Ἑ": "He",
    "Ἡ": "Hē",
    "Ἱ": "Hi",
    "Ὁ": "Ho",
    "Ὑ": "Hy",
    "Ὡ": "Hō",
    "Ἃ": "Ha",
    "Ἓ": "He",
    "Ἣ": "Hē",
    "Ἳ": "Hi",
    "Ὃ": "Ho",
    "Ὓ": "Hy",
    "Ὣ": "Hō",
    "Ἅ": "Ha",
    "Ἕ": "He",
    "Ἥ": "Hē",
    "Ἵ": "Hi",
    "Ὅ": "Ho",
    "Ὕ": "Hy",
    "Ὥ": "Hō",
    "Ἇ": "Ha",
    "Ἧ": "Hē",
    "Ἷ": "Hi",
    "὏": "Ho",
    "Ὗ": "Hy",
    "Ὧ": "Hō",
    # Initial rho with rough breathing
    "ῥ": "rh",
    "Ῥ": "Rh",
}

# Latin → Greek reverse mapping (for transliterated-query matching)
LATIN_TO_GREEK: dict[str, str] = {
    "th": "θ",
    "ph": "φ",
    "ch": "χ",
    "ps": "ψ",
    "a": "α",
    "b": "β",
    "g": "γ",
    "d": "δ",
    "e": "ε",
    "z": "ζ",
    "i": "ι",
    "k": "κ",
    "l": "λ",
    "m": "μ",
    "n": "ν",
    "x": "ξ",
    "o": "ο",
    "p": "π",
    "r": "ρ",
    "s": "σ",
    "t": "τ",
    "u": "υ",
    "ē": "η",
    "ō": "ω",
}

# Hebrew → Latin academic transliteration
HEBREW_TO_LATIN: dict[str, str] = {
    "א": "ʾ",
    "ב": "b",
    "ג": "g",
    "ד": "d",
    "ה": "h",
    "ו": "w",
    "ז": "z",
    "ח": "ḥ",
    "ט": "ṭ",
    "י": "y",
    "כ": "k",
    "ך": "k",
    "ל": "l",
    "מ": "m",
    "ם": "m",
    "נ": "n",
    "ן": "n",
    "ס": "s",
    "ע": "ʿ",
    "פ": "p",
    "ף": "p",
    "צ": "ṣ",
    "ץ": "ṣ",
    "ק": "q",
    "ר": "r",
    "שׂ": "ś",
    "שׁ": "š",
    "ש": "š",
    "ת": "t",
    "בּ": "b",
    "כּ": "k",
    "פּ": "p",
    "תּ": "t",
    "גּ": "g",
    "דּ": "d",
    "טּ": "ṭ",
}

# Latin → Hebrew reverse mapping
LATIN_TO_HEBREW: dict[str, str] = {
    "b": "ב",
    "g": "ג",
    "d": "ד",
    "h": "ה",
    "w": "ו",
    "z": "ז",
    "y": "י",
    "k": "כ",
    "l": "ל",
    "m": "מ",
    "n": "נ",
    "s": "ס",
    "p": "פ",
    "q": "ק",
    "r": "ר",
    "t": "ת",
}

# Hebrew digraphs for Latin → Hebrew reverse mapping (longest-first)
_HEBREW_DIGRAPHS: dict[str, str] = {
    "sh": "שׁ",
    "SH": "שׁ",  # shin
    "ts": "צ",
    "TS": "צ",  # tsade
    "ch": "ח",
    "CH": "ח",  # chet
    "kh": "כ",
    "KH": "כ",  # kaf without dagesh
    "th": "ת",
    "TH": "ת",  # tav without dagesh
    "ph": "פ",
    "PH": "פ",  # pe without dagesh
    "bh": "ב",
    "BH": "ב",  # bet without dagesh
    "dh": "ד",
    "DH": "ד",  # dalet without dagesh
    "gh": "ג",
    "GH": "ג",  # gimel without dagesh
}

# Vowels to skip in Hebrew reverse transliteration
_VOWELS = set("aeiouāēīōū")


# =========================================================================
# Internal helpers
# =========================================================================


def _strip_accents_only(text: str) -> str:
    """Strip Greek accent marks but keep breathings (dasia U+0314, psili U+0313).

    Strips: oxia (U+0301), varia (U+0300), perispomeni (U+0342), macron (U+0304),
    breve (U+0306), diaeresis (U+0308), iota subscript (U+0345).
    """
    nfd = unicodedata.normalize("NFD", text)
    result: list[str] = []
    _strip_cps = {0x300, 0x301, 0x342, 0x304, 0x306, 0x308, 0x345}
    for ch in nfd:
        if ord(ch) in _strip_cps:
            continue
        result.append(ch)
    return unicodedata.normalize("NFC", "".join(result))


# =========================================================================
# Transliterator
# =========================================================================


class Transliterator:
    """Stateless transliteration between Greek/Hebrew and academic Latin."""

    # ── to_latin ───────────────────────────────────────────────────────

    @staticmethod
    def to_latin(text: str, language: str) -> str:
        """Convert Greek or Hebrew text to ASCII Latin academic transliteration.

        Args:
            text: Greek or Hebrew Unicode text.
            language: ``"el"`` (Greek) or ``"he"`` (Hebrew).

        Returns:
            Academic Latin transliteration (ASCII with occasional diacritics:
            ē/ō for Greek, ḥ/ṭ/ṣ/š for Hebrew).

        Examples:
            >>> Transliterator.to_latin("λόγος", "el")
            'logos'
            >>> Transliterator.to_latin("בְּרֵאשִׁית", "he")
            'brʾšyt'
        """
        if language == "el":
            return _transliterate_greek_impl(text)
        elif language == "he":
            return _transliterate_hebrew_impl(text)
        else:
            raise ValueError(f"Unsupported language: {language!r}. Use 'el' or 'he'.")

    # ── to_native ──────────────────────────────────────────────────────

    @staticmethod
    def to_native(text: str, language: str) -> str:
        """Convert ASCII Latin transliteration back to native script.

        Uses the primary mapping (e→ε, o→ο for Greek).  For multi-variant
        search including ambiguous vowels, use :meth:`to_native_variants`.

        Args:
            text: ASCII Latin transliteration (e.g. ``"logos"``, ``"shalom"``).
            language: ``"el"`` (Greek) or ``"he"`` (Hebrew).

        Returns:
            Native-script string (Greek or Hebrew Unicode, no accents).
        """
        if language == "el":
            return _reverse_transliterate_greek_impl(text)
        elif language == "he":
            return _reverse_transliterate_hebrew_impl(text)
        else:
            raise ValueError(f"Unsupported language: {language!r}. Use 'el' or 'he'.")

    # ── to_native_variants ─────────────────────────────────────────────

    @staticmethod
    def to_native_variants(text: str, language: str) -> list[str]:
        """Convert Latin transliteration to native script with ambiguity variants.

        For Greek, vowels ``e`` and ``o`` are ambiguous (ε/η, ο/ω) so up to
        4 candidate strings are returned.  For Hebrew, returns a single-element
        list (no ambiguity).

        Args:
            text: ASCII Latin transliteration.
            language: ``"el"`` or ``"he"``.

        Returns:
            List of candidate native-script strings (1–4 elements).
        """
        if language == "el":
            return _reverse_transliterate_greek_variants_impl(text)
        elif language == "he":
            return [_reverse_transliterate_hebrew_impl(text)]
        else:
            raise ValueError(f"Unsupported language: {language!r}. Use 'el' or 'he'.")

    # ── normalize ──────────────────────────────────────────────────────

    @staticmethod
    def normalize(text: str) -> str:
        """Strip Greek/Hebrew combining marks via NFD decomposition.

        Also normalizes final sigma (ς) → regular sigma (σ) for consistent
        searching across Greek text sources.

        Args:
            text: Input string (Greek, Hebrew, or mixed).

        Returns:
            Text with combining marks removed.

        Examples:
            >>> Transliterator.normalize("λόγος")
            'λογοσ'
            >>> Transliterator.normalize("בְּרֵאשִׁית")
            'בראשית'
        """
        nfd = unicodedata.normalize("NFD", text)
        return "".join(
            "σ" if c == "ς" else c for c in nfd if not unicodedata.combining(c)
        )

    # ── classify ───────────────────────────────────────────────────────

    @staticmethod
    def classify(text: str) -> tuple[bool, str]:
        """Detect if text looks like a transliteration of Greek or Hebrew.

        Heuristic: the text must be ASCII-only alphabetic (no digits).
        Greek markers: ``th``, ``ph``, ``ch``, ``ps``, ``ē``, ``ō``, or
        starts with ``rh``.  Hebrew markers: ``ḥ``, ``ṭ``, ``ṣ``, ``ʾ``, ``ʿ``,
        ``š``, ``ś``.

        Args:
            text: Query string to classify.

        Returns:
            ``(is_transliteration, language_hint)`` where *language_hint*
            is ``"el"``, ``"he"``, or ``""``.

        Examples:
            >>> Transliterator.classify("logos")
            (True, 'el')
            >>> Transliterator.classify("bereshit")
            (True, 'el')
            >>> Transliterator.classify("love123")
            (False, '')
            >>> Transliterator.classify("")
            (False, '')
        """
        if not text:
            return False, ""
        # Must be ASCII-only with no digits
        if not all(c.isascii() and (c.isalpha() or c.isspace()) for c in text):
            return False, ""
        lower = text.strip().lower()
        # Hebrew translit markers (non-ASCII diacritics are already filtered
        # by the ASCII gate above, so these are mainly for Hebrew-ish ASCII
        # digraphs)
        hebrew_markers = {"ḥ", "ṭ", "ṣ", "ʾ", "ʿ", "š", "ś"}
        if any(m in lower for m in hebrew_markers):
            return True, "he"
        # Greek translit markers
        greek_markers = {"th", "ph", "ch", "ps", "ē", "ō"}
        if any(m in lower for m in greek_markers) or lower.startswith("rh"):
            return True, "el"
        # Ambiguous: could be either; default to Greek since it's more common
        return True, "el"


# =========================================================================
# Implementation functions (shared by Transliterator and convenience wrappers)
# =========================================================================


def _transliterate_greek_impl(text: str) -> str:
    """Convert Greek Unicode text to ASCII Latin academic transliteration.

    Handles polytonic Greek (accents, breathings, iota subscript).
    Rough breathing prefixes 'h'; smooth breathing is ignored; accents
    are stripped; iota subscript is ignored.
    """
    result: list[str] = []
    for ch in text:
        # 1. Direct mapping lookup (catches precomposed breathing chars)
        if ch in GREEK_TO_LATIN:
            result.append(GREEK_TO_LATIN[ch])
        else:
            # 2. Strip accents (keep breathings), try again
            stripped = _strip_accents_only(ch)
            if stripped in GREEK_TO_LATIN:
                result.append(GREEK_TO_LATIN[stripped])
            else:
                # 3. Strip ALL combining marks as final fallback
                nfd = unicodedata.normalize("NFD", stripped)
                base = "".join(c for c in nfd if not unicodedata.combining(c))
                base = unicodedata.normalize("NFC", base)
                if base in GREEK_TO_LATIN:
                    result.append(GREEK_TO_LATIN[base])
                else:
                    result.append(base)
    return "".join(result)


def _transliterate_hebrew_impl(text: str) -> str:
    """Convert Hebrew Unicode text to ASCII Latin academic transliteration.

    Strips vowel points (niqqud) and cantillation marks, but preserves
    shin dot (U+05C1), sin dot (U+05C2), and dagesh (U+05BC) so that
    the correct mapping is selected (e.g., שׂ→ś vs ש→š).
    """
    # Combining marks to preserve (they distinguish letters)
    _PRESERVE = frozenset({"\u05c1", "\u05c2", "\u05bc"})  # shin dot, sin dot, dagesh

    nfd = unicodedata.normalize("NFD", text)
    result: list[str] = []
    i = 0
    while i < len(nfd):
        base = nfd[i]
        i += 1
        # Collect following combining marks, separating preserved from discarded
        preserved: list[str] = []
        while i < len(nfd) and unicodedata.combining(nfd[i]):
            if nfd[i] in _PRESERVE:
                preserved.append(nfd[i])
            i += 1
        # Build candidate with preserved marks
        if preserved:
            candidate = base + "".join(preserved)
            result.append(
                HEBREW_TO_LATIN.get(candidate, HEBREW_TO_LATIN.get(base, base))
            )
        else:
            result.append(HEBREW_TO_LATIN.get(base, base))
    return "".join(result)


def _reverse_transliterate_greek_variants_impl(latin: str) -> list[str]:
    """Convert ASCII Latin back to basic Greek, generating variants for
    ambiguous vowels (e→ε/η, o→ο/ω) since users rarely type macrons.

    Also detects h-prefix before word-initial vowels as rough breathing
    (ἁγνός→"hagnos"→ἁγνοσ) and rh-prefix as rough rho (ῥῆμα→"rhēma"→ῥημα).

    Returns up to 4 candidate Greek strings.
    """
    lower = latin.lower()
    # Detect word-initial rough breathing: h + vowel → rough breathing char
    rough_prefix = ""
    start = 0
    if lower.startswith("rh"):
        rough_prefix = "ῥ"
        start = 2
    elif len(lower) >= 2 and lower[0] == "h":
        # h + vowel at word start → rough breathing on that vowel
        vowel_map = {
            "a": "ἁ",
            "e": "ἑ",
            "ē": "ἡ",
            "i": "ἱ",
            "o": "ὁ",
            "u": "ὑ",
            "y": "ὑ",
            "ō": "ὡ",
        }
        rough_prefix = vowel_map.get(lower[1], "")
        if rough_prefix:
            start = 2
        else:
            # h followed by non-vowel - retain as is (rare)
            start = 0
    # Parse into segments: each is either a known mapping or a raw char
    segments: list[list[str]] = []  # list of alternatives per position
    if rough_prefix:
        segments.append([rough_prefix])
    i = start
    while i < len(lower):
        matched = False
        for length in (2, 1):
            fragment = lower[i : i + length]
            if fragment in LATIN_TO_GREEK:
                # Ambiguous vowels: generate variants
                if fragment == "e":
                    segments.append(["ε", "η"])
                elif fragment == "o":
                    segments.append(["ο", "ω"])
                else:
                    segments.append([LATIN_TO_GREEK[fragment]])
                i += length
                matched = True
                break
        if not matched:
            segments.append([lower[i]])
            i += 1

    # Generate all combinations (cartesian product)
    variants: list[str] = []
    for combo in product(*segments):
        variants.append("".join(combo))
    return variants


def _reverse_transliterate_greek_impl(latin: str) -> str:
    """Convert an ASCII Latin transliteration back to basic Greek (no accents),
    using the primary mapping (e→ε, o→ο).
    """
    return _reverse_transliterate_greek_variants_impl(latin)[0]


def _reverse_transliterate_hebrew_impl(latin: str) -> str:
    """Convert an ASCII Latin transliteration back to basic Hebrew consonants.

    Handles common digraphs (sh→שׁ, ts→צ, ch→ח) and strips vowels.
    """
    lower = latin.lower().strip()
    result: list[str] = []
    i = 0
    while i < len(lower):
        # Try 2-char digraphs first
        if i + 1 < len(lower):
            digraph = lower[i : i + 2]
            if digraph in _HEBREW_DIGRAPHS:
                result.append(_HEBREW_DIGRAPHS[digraph])
                i += 2
                continue
        ch = lower[i]
        if ch in LATIN_TO_HEBREW:
            result.append(LATIN_TO_HEBREW[ch])
        elif ch not in _VOWELS and ch.isalpha():
            # Unknown consonant — keep as-is (might be a transliteration convention)
            result.append(ch)
        # Vowels: silently skip
        i += 1
    return "".join(result)


# =========================================================================
# Module-level convenience functions
# =========================================================================


def transliterate_greek(text: str) -> str:
    """Convert Greek Unicode text to ASCII Latin academic transliteration.

    Convenience wrapper around ``Transliterator.to_latin(text, "el")``.

    Examples:
        'λόγος' → 'logos'
        'ἀγάπη' → 'agapē'
        'πνεῦμα' → 'pneuma'
        'ῥῆμα' → 'rhēma'
    """
    return Transliterator.to_latin(text, "el")


def transliterate_hebrew(text: str) -> str:
    """Convert Hebrew Unicode text to ASCII Latin academic transliteration.

    Convenience wrapper around ``Transliterator.to_latin(text, "he")``.

    Examples:
        'בְּרֵאשִׁית' → 'brʾšyt'
    """
    return Transliterator.to_latin(text, "he")


def reverse_transliterate_greek(latin: str) -> str:
    """Convert ASCII Latin to basic Greek using the primary mapping (e→ε, o→ο).

    Convenience wrapper around ``Transliterator.to_native(text, "el")``.
    For multi-variant results use ``reverse_transliterate_greek_variants()``.
    """
    return Transliterator.to_native(latin, "el")


def reverse_transliterate_hebrew(latin: str) -> str:
    """Convert ASCII Latin transliteration back to basic Hebrew consonants.

    Convenience wrapper around ``Transliterator.to_native(text, "he")``.
    """
    return Transliterator.to_native(latin, "he")


def reverse_transliterate_greek_variants(latin: str) -> list[str]:
    """Convert ASCII Latin to Greek, generating variants for ambiguous vowels.

    Convenience wrapper around ``Transliterator.to_native_variants(text, "el")``.
    """
    return Transliterator.to_native_variants(latin, "el")
