from __future__ import annotations

from app.text import levenshtein

# Romanized Hindi function words. These sound like the Devanagari, so they must
# stay in Hindi rather than be copied from the English pass.
_ROMAN_HINDI = frozenset(
    {
        "hai",
        "hain",
        "nahi",
        "nahin",
        "nhi",
        "kya",
        "kyun",
        "kyu",
        "mein",
        "main",
        "hum",
        "ham",
        "aap",
        "ap",
        "ye",
        "yeh",
        "yah",
        "woh",
        "wo",
        "vo",
        "aur",
        "toh",
        "ka",
        "ki",
        "ke",
        "se",
        "par",
        "pe",
        "bhi",
        "jo",
        "jab",
        "tab",
        "ab",
        "phir",
        "fir",
        "lekin",
        "magar",
        "kyunki",
        "bahut",
        "thoda",
        "accha",
        "achha",
        "theek",
        "thik",
        "haan",
        "han",
        "mat",
        "kar",
        "karna",
        "karenge",
        "kiya",
        "hua",
        "hui",
        "hoga",
        "hogi",
        "tha",
        "thi",
        "ho",
        "hota",
        "hoti",
        "raha",
        "rahe",
        "rahi",
        "wala",
        "wali",
        "wale",
        "iska",
        "uska",
        "unka",
        "mera",
        "teri",
        "tera",
        "hamara",
        "kyon",
        "agar",
        "ya",
        "ek",
        "do",
        "kuch",
        "sab",
        "bhai",
        "ji",
        "ha",
    }
)

_SIGNS = {
    "अ": "a",
    "आ": "aa",
    "इ": "i",
    "ई": "ii",
    "उ": "u",
    "ऊ": "uu",
    "ऋ": "ri",
    "ए": "e",
    "ऐ": "ai",
    "ओ": "o",
    "औ": "au",
    "ा": "a",
    "ि": "i",
    "ी": "ii",
    "ु": "u",
    "ू": "uu",
    "ृ": "ri",
    "े": "e",
    "ै": "ai",
    "ो": "o",
    "ौ": "au",
    "ॉ": "o",
    "क": "k",
    "ख": "kh",
    "ग": "g",
    "घ": "gh",
    "ङ": "ng",
    "च": "ch",
    "छ": "chh",
    "ज": "j",
    "झ": "jh",
    "ञ": "ny",
    "ट": "t",
    "ठ": "th",
    "ड": "d",
    "ढ": "dh",
    "ण": "n",
    "त": "t",
    "थ": "th",
    "द": "d",
    "ध": "dh",
    "न": "n",
    "प": "p",
    "फ": "ph",
    "ब": "b",
    "भ": "bh",
    "म": "m",
    "य": "y",
    "र": "r",
    "ल": "l",
    "व": "v",
    "श": "sh",
    "ष": "sh",
    "स": "s",
    "ह": "h",
    "क़": "k",
    "ख़": "kh",
    "ग़": "g",
    "ज़": "z",
    "ड़": "d",
    "ढ़": "dh",
    "फ़": "f",
    "य़": "y",
    "ं": "n",
    "ँ": "n",
    "ः": "h",
    "़": "",
    "्": "",
    "ॐ": "",
}


def blend_words(hindi_words: list[dict], english_words: list[dict], pause: float = 0.7) -> list[dict]:
    chosen = _choose(hindi_words, english_words)
    return _group(chosen, pause)


def _choose(hindi_words: list[dict], english_words: list[dict]) -> list[dict]:
    chosen: list[dict] = []
    for word in hindi_words:
        cleaned = _keep_spoken_scripts(str(word.get("text") or ""))
        if not _has_letter(cleaned):
            continue
        match = _best_overlap(word, english_words)
        text = cleaned
        marker = None
        if match is not None:
            english = _latin_word(str(match.get("text") or ""))
            if english and _prefer_english(cleaned, english):
                text = english
                marker = id(match)
        if marker is not None and chosen and chosen[-1].get("_en") == marker:
            chosen[-1]["end"] = float(word.get("end") or chosen[-1]["end"])
            continue
        item = {
            "start": float(word.get("start") or 0),
            "end": float(word.get("end") or 0),
            "text": text,
        }
        if marker is not None:
            item["_en"] = marker
        chosen.append(item)
    for item in chosen:
        item.pop("_en", None)
    return chosen


def _prefer_english(hindi: str, english: str) -> bool:
    key = english.lower()
    if key in _ROMAN_HINDI:
        return False
    if not _sounds_like(hindi, english):
        return False
    return True


def _sounds_like(hindi: str, english: str) -> bool:
    spoken = "".join(char for char in english.lower() if char.isalpha())
    heard = _latinize(hindi)
    if not spoken or not heard:
        return False
    if _close(_skeleton(spoken), _skeleton(heard)):
        return True
    return len(spoken) <= 4 and _close(spoken, heard, limit=0.45)


def _close(left: str, right: str, limit: float = 0.34) -> bool:
    if not left or not right:
        return False
    if len(left) < 3 or len(right) < 3:
        return left == right and len(left) >= 2
    if left == right:
        return True
    return levenshtein(left, right) / max(len(left), len(right)) <= limit


def _latinize(text: str) -> str:
    parts: list[str] = []
    for char in text:
        if char in _SIGNS:
            parts.append(_SIGNS[char])
        elif "a" <= char.lower() <= "z":
            parts.append(char.lower())
    return "".join(parts)


def _skeleton(text: str) -> str:
    return "".join(char for char in text if char not in "aeiou")


def _best_overlap(word: dict, others: list[dict]) -> dict | None:
    start = float(word.get("start") or 0)
    end = float(word.get("end") or start)
    if end < start:
        end = start
    best = None
    best_score = 0.0
    for other in others:
        other_start = float(other.get("start") or 0)
        other_end = float(other.get("end") or other_start)
        overlap = min(end, other_end) - max(start, other_start)
        if overlap <= 0:
            continue
        shorter = max(0.05, min(end - start, other_end - other_start))
        score = overlap / shorter
        if score > best_score:
            best_score = score
            best = other
    if best_score < 0.4:
        return None
    return best


def _keep_spoken_scripts(text: str) -> str:
    kept: list[str] = []
    for char in text:
        if char.isascii() or "\u0900" <= char <= "\u097f" or char.isspace():
            if char == "\u0950":
                continue
            kept.append(char)
        elif char in "।॥":
            kept.append(char)
    return " ".join("".join(kept).split())


def _latin_word(text: str) -> str:
    letters = [char for char in text if char.isascii() and (char.isalnum() or char in "-'")]
    word = "".join(letters).strip("-'")
    if not any(char.isalpha() for char in word):
        return ""
    return word


def _has_letter(text: str) -> bool:
    return any(char.isalpha() or char.isdigit() for char in text)


def _has_devanagari(text: str) -> bool:
    return any("\u0900" <= char <= "\u097f" for char in text)


def _group(words: list[dict], pause: float) -> list[dict]:
    if not words:
        return []
    groups = [[words[0]]]
    for word in words[1:]:
        gap = float(word["start"]) - float(groups[-1][-1]["end"])
        if gap >= pause:
            groups.append([word])
        else:
            groups[-1].append(word)
    chunks = []
    for group in groups:
        text = " ".join(item["text"] for item in group).strip()
        if not text:
            continue
        chunks.append({"start": group[0]["start"], "end": group[-1]["end"], "text": text})
    return chunks
