"""Phonetic matching engine combining exact, metaphone, and edit-distance lookup."""

from __future__ import annotations

import json
import logging
import os
from dataclasses import dataclass
from pathlib import Path

logger = logging.getLogger(__name__)


@dataclass
class MatchResult:
    """Result of a phonetic dictionary match."""
    english_word: str
    score: float  # 0.0 to 1.0
    match_type: str  # "exact", "phonetic", "edit_distance"
    frequency_rank: int  # Lower = more common


class PhoneticMatcher:
    """English word lookup using exact match, Double Metaphone, and edit distance.

    Uses a frequency-weighted English dictionary for scoring matches.
    Falls back gracefully if optional dependencies (metaphone, symspellpy) are unavailable.
    """

    def __init__(self, dict_path: str | Path | None = None, max_edit_distance: int = 1):
        self._max_edit_distance = max_edit_distance

        # Core data structures
        self._word_set: set[str] = set()  # Lowercase English words
        self._word_freq: dict[str, int] = {}  # word -> frequency rank (1 = most common)
        self._metaphone_index: dict[str, list[str]] = {}  # metaphone code -> words

        # Optional components (loaded lazily)
        self._symspell = None
        self._metaphone_func = None

        self._load_dictionary(dict_path)
        self._init_metaphone()
        self._init_symspell()

    def _load_dictionary(self, dict_path: str | Path | None) -> None:
        """Load English dictionary with frequency data.

        Priority:
        1. Explicit dict_path if provided
        2. Bundled en_dict_30k.json (CMU Dict + SUBTLEX-US, 30K words)
        3. Hardcoded built-in dictionary (~600 common words)
        """
        if dict_path and os.path.exists(dict_path):
            self._load_from_file(Path(dict_path))
            return

        # Try bundled dictionary (generated by scripts/build_dictionary.py)
        bundled = Path(__file__).parent.parent / "data" / "en_dict_30k.json"
        if bundled.exists():
            self._load_from_file(bundled)
            logger.info("Loaded bundled dictionary from %s", bundled)
            return

        self._load_builtin()

    def _load_from_file(self, path: Path) -> None:
        """Load dictionary from a file. Supports JSON and text formats."""
        if path.suffix == ".json":
            with open(path) as f:
                data = json.load(f)
            for rank, (word, _freq) in enumerate(
                sorted(data.items(), key=lambda x: -x[1]), 1
            ):
                w = word.lower().strip()
                self._word_set.add(w)
                self._word_freq[w] = rank
        else:
            # Text file: one word per line, optionally with frequency
            with open(path) as f:
                for rank, line in enumerate(f, 1):
                    parts = line.strip().split()
                    if parts:
                        w = parts[0].lower()
                        self._word_set.add(w)
                        self._word_freq[w] = rank

    def _load_builtin(self) -> None:
        """Load a minimal built-in dictionary of common English words.

        This covers the most frequent English words used in code-mixed Indian
        language speech. A production deployment should use CMU Dict + SUBTLEX-US.
        """
        # Top ~500 English words commonly found in Indian code-mixed speech
        common_words = [
            # Greetings & social
            "hello", "hi", "bye", "goodbye", "thanks", "thank", "sorry", "please",
            "welcome", "okay", "ok", "yes", "no", "sure", "fine", "sir", "madam",
            # Time & scheduling
            "meeting", "schedule", "time", "today", "tomorrow", "yesterday",
            "morning", "evening", "night", "afternoon", "monday", "tuesday",
            "wednesday", "thursday", "friday", "saturday", "sunday",
            "january", "february", "march", "april", "may", "june", "july",
            "august", "september", "october", "november", "december",
            # Numbers
            "one", "two", "three", "four", "five", "six", "seven", "eight",
            "nine", "ten", "eleven", "twelve", "twenty", "thirty", "forty",
            "fifty", "hundred", "thousand", "million",
            # Work & office
            "office", "work", "job", "project", "team", "manager", "boss",
            "client", "customer", "company", "business", "report", "document",
            "file", "email", "message", "call", "phone", "mobile",
            "computer", "laptop", "system", "software", "website", "online",
            "internet", "server", "data", "database", "password", "login",
            "account", "profile", "update", "download", "upload", "install",
            "cancel", "approve", "submit", "confirm", "reject", "accept",
            "presentation", "deadline", "budget", "status", "balance",
            "reset", "review", "process", "request", "access", "address",
            "support", "issue", "ticket", "feedback", "performance",
            "policy", "notice", "reference", "salary", "transfer",
            "deposit", "payment", "invoice", "receipt", "refund",
            "discount", "offer", "deal", "order", "delivery",
            "target", "result", "record", "register", "certificate",
            # Communication
            "send", "receive", "forward", "reply", "copy", "paste", "delete",
            "save", "print", "share", "post", "comment", "like", "follow",
            # Actions
            "check", "start", "stop", "open", "close", "create", "make",
            "set", "get", "put", "take", "give", "come", "go", "run",
            "wait", "try", "help", "need", "want", "know", "think",
            "see", "look", "find", "use", "tell", "ask", "talk",
            "miss", "fix", "join", "leave", "move", "change", "apply",
            "book", "pick", "drop", "handle", "manage", "attend", "cancel",
            # Common adjectives
            "good", "bad", "big", "small", "new", "old", "fast", "slow",
            "easy", "hard", "important", "ready", "free", "busy", "late",
            "early", "last", "next", "first", "second", "third", "final",
            "down", "up", "out", "off", "on", "top", "best", "worst",
            "clear", "correct", "wrong", "urgent", "pending", "done",
            # Prepositions & conjunctions
            "at", "in", "on", "for", "to", "from", "with", "by", "of",
            "about", "after", "before", "between", "through", "during",
            "and", "or", "but", "so", "because", "if", "then", "also",
            # Pronouns & articles
            "i", "you", "he", "she", "we", "they", "it", "me", "him",
            "her", "us", "them", "my", "your", "his", "our", "their",
            "the", "a", "an", "this", "that", "these", "those",
            # Common nouns
            "people", "person", "man", "woman", "child", "friend", "family",
            "house", "home", "room", "door", "car", "bus", "train", "road",
            "city", "country", "school", "college", "university", "class",
            "paper", "money", "price", "number", "problem", "answer",
            "question", "idea", "plan", "way", "thing", "place", "food",
            "water", "tea", "coffee", "lunch", "dinner", "breakfast",
            "doctor", "hospital", "medicine", "bank", "market", "shop",
            # Tech & modern
            "app", "video", "photo", "camera", "screen", "battery", "charge",
            "wifi", "bluetooth", "network", "signal", "power", "button",
            "link", "click", "search", "google", "whatsapp", "youtube",
            "facebook", "instagram", "twitter",
            "browser", "chrome", "website", "notification", "setting",
            # Verbs (common forms)
            "is", "are", "was", "were", "been", "being", "have", "has",
            "had", "do", "does", "did", "will", "would", "can", "could",
            "should", "must", "shall", "might",
            "going", "coming", "doing", "making", "getting", "taking",
            "working", "calling", "sending", "checking",
            # Adverbs
            "very", "much", "more", "most", "really", "just", "only",
            "already", "still", "again", "too", "here", "there", "now",
            "later", "soon", "always", "never", "sometimes", "so",
            # Other common
            "same", "different", "available", "possible", "complete",
            "total", "direct", "actual", "special", "general", "main",
            "full", "half", "double", "single", "extra", "basic",
            "percent", "per", "plus", "minus", "point", "zero",
        ]
        for rank, word in enumerate(common_words, 1):
            w = word.lower()
            self._word_set.add(w)
            self._word_freq[w] = rank

        logger.info("Loaded built-in dictionary with %d words", len(self._word_set))

    def _init_metaphone(self) -> None:
        """Initialize Double Metaphone index."""
        try:
            from metaphone import doublemetaphone
            self._metaphone_func = doublemetaphone

            for word in self._word_set:
                codes = doublemetaphone(word)
                for code in codes:
                    if code:
                        self._metaphone_index.setdefault(code, []).append(word)

            logger.info("Metaphone index built with %d codes", len(self._metaphone_index))
        except ImportError:
            logger.warning("metaphone package not installed; phonetic matching disabled")

    def _init_symspell(self) -> None:
        """Initialize SymSpell for edit-distance lookup."""
        try:
            from symspellpy import SymSpell
            self._symspell = SymSpell(max_dictionary_edit_distance=self._max_edit_distance)
            for word in self._word_set:
                freq = max(1, len(self._word_set) - self._word_freq.get(word, len(self._word_set)))
                self._symspell.create_dictionary_entry(word, freq)
            logger.info("SymSpell index built with %d words", len(self._word_set))
        except ImportError:
            logger.warning("symspellpy package not installed; edit-distance matching disabled")

    # Romanization variants → canonical English word.
    # These handle common Indic transliteration patterns that don't match via
    # standard phonetic/edit-distance algorithms.
    _TRANSLIT_VARIANTS: dict[str, str] = {
        # "at" variants from different scripts
        "aat": "at", "ait": "at", "aet": "at", "et": "at",
        # "you" variants
        "yu": "you", "yoo": "you",
        # "much" variants
        "mack": "much", "mac": "much", "mach": "much",
        # "so" variants
        "so": "so",
        # "meeting" variants
        "mitin": "meeting", "mitinh": "meeting", "meetin": "meeting",
        "miiting": "meeting", "miting": "meeting", "mitting": "meeting",
        # "send" variants
        "send": "send", "sEnd": "send",
        # "document" variants
        "dokument": "document", "dokyument": "document",
        "dokyumeint": "document", "dakument": "document",
        "dokyumend": "document", "dakumeint": "document",
        "dokyumendt": "document", "takkument": "document",
        "takkumeint": "document", "taakkument": "document",
        # "later" variants
        "letar": "later", "letr": "later",
        # "report" variants
        "riport": "report", "ripoort": "report", "repoort": "report",
        "ripord": "report", "riporat": "report",
        # "please" variants
        "pliz": "please", "pleez": "please", "plez": "please",
        "pliiz": "please", "pliis": "please",
        # "check" variants
        "chek": "check", "chekk": "check",
        # "thank" variants
        "thank": "thank", "thenk": "thank", "thaink": "thank",
        # "call" variants
        "kal": "call", "kol": "call",
        # "for" variants
        "for": "for", "phor": "for", "for": "for",
        # "five" variants
        "faiv": "five", "phaiv": "five",
        # "your" variants
        "yor": "your", "yur": "your",
        # "cancel" variants (कैंसल, ক্যান্সেল, கேன்சல், etc.)
        "kansal": "cancel", "kainsal": "cancel", "kensal": "cancel",
        "kyansel": "cancel", "kainsel": "cancel", "kensel": "cancel",
        "kyaansel": "cancel", "kaynsel": "cancel", "kyansal": "cancel",
        "kainsl": "cancel", "kansel": "cancel", "keensal": "cancel",
        "kenchal": "cancel", "keenchal": "cancel",
        # "approve" variants (अप्रूव)
        "apruv": "approve", "apruve": "approve", "aapruv": "approve",
        "appruv": "approve", "apruuv": "approve",
        # "submit" variants (सबमिट, సబ్మిట్, etc.)
        "sabamit": "submit", "sabmit": "submit", "submeet": "submit",
        "sabameet": "submit", "sabmeet": "submit",
        # "presentation" variants (प्रेजेंटेशन)
        "prejenteshan": "presentation", "prejainteshan": "presentation",
        "prezenteshan": "presentation", "prejentesan": "presentation",
        "prajenteshan": "presentation", "prejeenteshan": "presentation",
        # "password" variants (पासवर्ड)
        "pasavard": "password", "paasavard": "password", "pasvard": "password",
        "pasward": "password", "paasward": "password", "pasavord": "password",
        # "reset" variants (रीसेट)
        "riset": "reset", "reeset": "reset", "riiseet": "reset",
        "riseet": "reset", "reesedt": "reset",
        # "deadline" variants (डेडलाइन)
        "dedalan": "deadline", "dedlain": "deadline", "dedlaain": "deadline",
        "dedalain": "deadline", "dedaalin": "deadline", "deedlain": "deadline",
        # "miss" variants (मिस)
        "mis": "miss",
        # "budget" variants (बजट)
        "bajat": "budget", "bajeet": "budget", "bajet": "budget",
        "bajed": "budget",
        # "status" variants (स्टेटस)
        "stetas": "status", "steetas": "status", "stets": "status",
        "staitas": "status", "statas": "status",
        # "balance" variants (बैलेंस)
        "bailens": "balance", "baalens": "balance", "belens": "balance",
        "bailans": "balance", "bealens": "balance", "beilens": "balance",
        # "down" variants (डाउन)
        "daun": "down", "daaun": "down", "daoun": "down",
        # "slow" variants (स्लो, ਸਲੋ, etc.)
        "slo": "slow", "sloo": "slow", "salo": "slow",
        # "share" variants (शेयर, শেয়ার, షేర్, etc.)
        "sheyar": "share", "shear": "share", "sheyr": "share",
        "sheyaar": "share", "sher": "share", "sheer": "share",
        # "internet" variants
        "intaranet": "internet", "intarnet": "internet", "intaraneet": "internet",
        "intarnedt": "internet", "intaranaitt": "internet",
        "intaranait": "internet",
        # "office" variants (ऑफिस, ஆபிஸ், ఆఫీస్, etc.)
        "aafis": "office", "ofis": "office", "aphis": "office",
        "aafees": "office", "ophs": "office", "aafis": "office",
        # "project" variants (प्रोजेक्ट, ప్రాజెక్ట్, etc.)
        "projekt": "project", "projeck": "project", "praajekt": "project",
        "projeekd": "project", "proajekd": "project", "prajeekd": "project",
        "projekdt": "project", "projeekt": "project",
        "prajekt": "project", "purajekt": "project", "projaikat": "project",
        "prajeekt": "project", "projaikt": "project",
        # "team" variants
        "tiim": "team", "teem": "team", "tim": "team",
        # "message" variants (मैसेज, মেসেজ, etc.)
        "maisej": "message", "mesej": "message", "meseej": "message",
        "maisez": "message", "mesez": "message", "maiseez": "message",
        # "server" variants
        "sarvar": "server", "sarver": "server",
        # "account" variants
        "akaunt": "account", "akaaunt": "account", "akaound": "account",
        # "important" variants (इम्पोर्टेन्ट)
        "importaint": "important", "importeint": "important",
        "impoortaint": "important", "importand": "important",
        # "ready" variants (रेडी, ரெடி, etc.)
        "redi": "ready", "reedi": "ready", "redee": "ready",
        "raidi": "ready", "raidee": "ready", "reti": "ready",
        "retee": "ready",
        # "very" variants (वेरी)
        "veri": "very", "veree": "very", "vairi": "very",
        # "please" variants (Gujarati પ્લીઝ → plijh, etc.)
        "plijh": "please", "plij": "please", "pliijh": "please",
        "pleejh": "please",
        # "message" variants (Tamil மெசேஜ் → mechez, etc.)
        "mechez": "message", "mecheez": "message",
        # "office" variants
        "aphisl": "office", "ophisl": "office",
        "apis": "office", "aapis": "office", "aphis": "office",
        "ophisamadhye": "office",
        # "software" variants (सॉफ्टवेयर, সফটওয়্যার, etc.)
        "sophtaveyar": "software", "saphataoyyar": "software",
        "chaptver": "software", "saphtver": "software",
        "safatavear": "software", "softver": "software",
        "sophtver": "software",
        # "update" variants (अपडेट, আপডেট, etc.)
        "apadet": "update", "aptet": "update", "apdet": "update",
        "appadet": "update", "apadedt": "update",
        # "network" variants (नेटवर्क, etc.)
        "netavark": "network", "naittavarak": "network",
        "netavarak": "network",
        # "issue" variants (इश्यू, ইস্যু, etc.)
        "ishyu": "issue", "isyu": "issue", "ishyuu": "issue",
        # "restart" variants
        "ristart": "restart", "reestart": "restart",
        # "number" variants (नंबर)
        "nanbar": "number", "nambar": "number",
        # "email" variants (ईमेल)
        "imel": "email", "eemel": "email",
        # "lunch" variants (लंच)
        "lanck": "lunch", "lanch": "lunch",
        # "dinner" variants (डिनर)
        "dinar": "dinner", "deenar": "dinner",
        # "file" variants (फाइल, ফাইল)
        "fail": "file", "faayl": "file", "phail": "file",
        # "download" variants (डाउनलोड, ডাউনলোড)
        "daunalod": "download", "daunlod": "download",
        "daaunlod": "download",
        # "server" variants (Bengali সার্ভার, Tamil சர்வர்)
        "sarbhar": "server", "charvar": "server",
        # "password" variants (Tamil பாஸ்வேர்ட், Punjabi ਪਾਸਵਰਡ)
        "pasvert": "password", "pasavarad": "password",
        "paasvert": "password",
        # "search" variants
        "sarck": "search",
        # "online" variants (ऑनलाइन, ਆਨਲਾਈਨ, অনলাইন, etc.)
        "onalaain": "online", "onalain": "online", "analain": "online",
        "aanalaaeen": "online", "aanalain": "online", "onlain": "online",
        "anlain": "online", "onalaiin": "online",
        # "approve" variants (अप्रूव, অ্যাপ্রুভ, ਅਪਰੂਵ, etc.)
        "aproov": "approve", "aparoov": "approve", "ajaaprubh": "approve",
        "ayaprubh": "approve", "aprubh": "approve",
        # "payment" variants (पेमेंट, পেমেন্ট, ਪੇਮੈਂਟ, etc.)
        "pement": "payment", "pemaint": "payment", "pemaiint": "payment",
        "pemaint": "payment", "pemaaint": "payment",
        # "internet" variants (expanded for Bengali, etc.)
        "intaaranet": "internet", "intaranaitt": "internet",
        "intaranait": "internet",
        # "software" variants (expanded)
        "saphataoyajaar": "software", "sophtavear": "software",
        "saaphtavear": "software", "saphtavear": "software",
        "safataveyar": "software",
        # "server" variants (expanded)
        "saarbhaar": "server", "sarvhar": "server", "saravar": "server",
        # "share" variants (expanded for Bengali শেয়ার, Gurmukhi ਸ਼ੇਅਰ)
        "sheyaaar": "share", "shaear": "share", "sheyar": "share",
        # "cancel" variants (expanded for Bengali ক্যান্সেল, Kannada, Gurmukhi)
        "kjaansel": "cancel", "kyaansal": "cancel", "kainsal": "cancel",
        # "budget" variants (expanded for Tamil, Telugu)
        "patjet": "budget", "badjet": "budget",
        # "reset" variants (expanded for Tamil ரீசெட்)
        "reechet": "reset", "richet": "reset", "reesait": "reset",
        # "down" variants (expanded for Tamil டவுன், Gurmukhi)
        "tavun": "down", "daaoon": "down",
        # "network" variants (expanded)
        "netvark": "network",
        # "project" variants (expanded for Tamil)
        "puraajekt": "project",
        # "meeting" variants (expanded)
        "meetting": "meeting",
        # "document" variants (expanded for Gurmukhi)
        "daakoomaint": "document",
        # "password" variants (expanded for Gurmukhi)
        "paasavarad": "password",
        # "deadline" variants (expanded)
        "dedalaain": "deadline", "tetlain": "deadline",
        # "file" variants — override false positive "faail"→"well"
        "faail": "file", "faayl": "file",
        # "issue" variants — override false positive "ishyoo"→"which", "isju"→"is"
        "ishyoo": "issue", "isju": "issue",
        # "lunch" variants — override false positive "lnch"→"long"
        "lnch": "lunch",
        # "system" variants
        "sistam": "system", "sistm": "system",
        # "restart" variants (expanded)
        "reestaart": "restart",
        # "mobile" variants
        "mobaail": "mobile", "mobail": "mobile",
        # "number" variants (expanded)
        "nnbar": "number",
        # "plan" variants
        "plaan": "plan",
        # "database" variants
        "daataabes": "database", "detaabes": "database",
        # "backup" variants
        "baikaap": "backup", "baikap": "backup",
        # "download" variants (expanded)
        "daaunalod": "download",
        # "online" variants (expanded for Bengali, Tamil)
        "analaain": "online", "aanlain": "online",
        # "update" variants (expanded for Bengali, Tamil)
        "aapadet": "update", "apteet": "update",
        # "budget" variants (expanded for Bengali)
        "baajet": "budget",
        # "payment" variants (expanded for Tamil, Telugu, Kannada)
        "peement": "payment",
        # "software" variants (expanded for Tamil, Gujarati)
        "chaaptveer": "software", "sophtaver": "software",
        # "password" variants (expanded for Tamil, Telugu, Kannada)
        "paasveert": "password", "paasvard": "password",
        # "search" variants (expanded)
        "sarch": "search",
        # "approve" variants (expanded for Gujarati)
        "eproov": "approve",
        # "network" variants (expanded for Bengali, Tamil)
        "netaoyaaark": "network", "netvork": "network",
        # "password" variants (expanded for Bengali)
        "paasaoyaaard": "password",
        # "software" variants (expanded for Telugu, Kannada)
        "saaphtveer": "software",
        # "update" variants (expanded for Telugu, Kannada)
        "apdeet": "update",
    }

    def lookup(self, romanized_word: str, top_k: int = 3) -> list[MatchResult]:
        """Look up a romanized word against the English dictionary.

        Tries in order: transliteration variants, exact match, metaphone match,
        edit-distance match.

        Args:
            romanized_word: The romanized form of an Indic word.
            top_k: Maximum number of results to return.

        Returns:
            List of MatchResult sorted by score (highest first).
        """
        word = romanized_word.lower().strip()
        if not word:
            return []

        results: list[MatchResult] = []

        # 0. Check transliteration variant table (highest priority for known patterns)
        if word in self._TRANSLIT_VARIANTS:
            eng = self._TRANSLIT_VARIANTS[word]
            if eng in self._word_set:
                results.append(MatchResult(
                    english_word=eng,
                    score=0.95,  # High but not 1.0 (not an exact match)
                    match_type="translit_variant",
                    frequency_rank=self._word_freq.get(eng, 999999),
                ))
                return results[:top_k]

        # 1. Exact match
        if word in self._word_set:
            results.append(MatchResult(
                english_word=word,
                score=1.0,
                match_type="exact",
                frequency_rank=self._word_freq.get(word, 999999),
            ))
            return results[:top_k]

        # 2. Metaphone match
        # Skip metaphone entirely for very short romanized inputs — they produce
        # too many false positives (e.g., "am", "an", "he", "ja" match dozens of words)
        if self._metaphone_func and len(word) > 2:
            codes = self._metaphone_func(word)
            seen: set[str] = set()
            for code in codes:
                if code and code in self._metaphone_index:
                    for candidate in self._metaphone_index[code]:
                        if candidate not in seen:
                            seen.add(candidate)
                            freq_rank = self._word_freq.get(candidate, 999999)
                            # Score based on phonetic match + frequency
                            freq_score = 1.0 / (1.0 + freq_rank / 100.0)
                            score = 0.7 + 0.3 * freq_score
                            # Penalize short English words — they match too many
                            # native words phonetically (go, so, do, me, up, in, etc.)
                            if len(candidate) <= 2:
                                score = max(0.2, score - 0.35)
                            elif len(candidate) <= 3:
                                score = max(0.3, score - 0.20)
                            results.append(MatchResult(
                                english_word=candidate,
                                score=score,
                                match_type="phonetic",
                                frequency_rank=freq_rank,
                            ))

        # 3. Edit-distance match
        # Skip edit-distance for very short romanized inputs — too many false positives
        if self._symspell and len(word) > 2:
            from symspellpy import Verbosity
            suggestions = self._symspell.lookup(
                word, Verbosity.CLOSEST, max_edit_distance=self._max_edit_distance
            )
            for sug in suggestions:
                if sug.term not in {r.english_word for r in results}:
                    freq_rank = self._word_freq.get(sug.term, 999999)
                    # Score inversely proportional to edit distance
                    dist_score = 1.0 - sug.distance / max(len(word), 1)
                    freq_score = 1.0 / (1.0 + freq_rank / 100.0)
                    score = 0.4 * dist_score + 0.3 * freq_score + 0.3 * (1 if sug.distance <= 1 else 0)
                    # Penalize short English words — they match too many
                    # native words via edit distance
                    if len(sug.term) <= 2:
                        score = max(0.2, score - 0.35)
                    elif len(sug.term) <= 3:
                        score = max(0.3, score - 0.20)
                    results.append(MatchResult(
                        english_word=sug.term,
                        score=score,
                        match_type="edit_distance",
                        frequency_rank=freq_rank,
                    ))

        # Sort by score descending
        results.sort(key=lambda r: (-r.score, r.frequency_rank))
        return results[:top_k]

    def is_english(self, romanized_word: str, threshold: float = 0.6) -> tuple[bool, MatchResult | None]:
        """Check if a romanized word is likely English.

        Args:
            romanized_word: Romanized form of the word.
            threshold: Minimum score to consider a match.

        Returns:
            Tuple of (is_english, best_match_or_none).
        """
        matches = self.lookup(romanized_word, top_k=1)
        if matches and matches[0].score >= threshold:
            return True, matches[0]
        return False, None

    @property
    def vocab_size(self) -> int:
        return len(self._word_set)
