"""Question-label normalization and matching (used by extraction prompt and assembly, M1/M4)."""
from __future__ import annotations
import re
import unicodedata

BN_DIGITS = str.maketrans("০১২৩৪৫৬৭৮৯", "0123456789")
AR_DIGITS = str.maketrans("٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹", "01234567890123456789")

# Sub-part letters used in Bangladeshi papers, mapped to a canonical key.
SUBPART_ALIASES = {
    "ক": "ক", "খ": "খ", "গ": "গ", "ঘ": "ঘ", "ঙ": "ঙ", "চ": "চ",
    "a": "ক", "b": "খ", "c": "গ", "d": "ঘ", "e": "ঙ", "f": "চ",
    "i": "ক", "ii": "খ", "iii": "গ", "iv": "ঘ",
}
NOISE_WORDS = ["প্রশ্ন", "উত্তর", "উঃ", "উ:", "নং", "নম্বর", "answer", "ans", "question", "q", "no", "to", "of"]
_noise_re = re.compile(r"(?<![a-z])(" + "|".join(sorted(map(re.escape, NOISE_WORDS), key=len, reverse=True)) + r")(?![a-z])")


def normalize_digits(s: str) -> str:
    return s.translate(BN_DIGITS).translate(AR_DIGITS)


def normalize(raw: str) -> str:
    """'উত্তর: ৩ (গ)' -> '3গ'; 'Ans to Q 3 c' -> '3গ'; '৩।' -> '3'."""
    s = unicodedata.normalize("NFC", raw or "").lower()
    s = normalize_digits(s)
    s = _noise_re.sub(" ", s)
    s = re.sub(r"[\s\.\-:।,;()\[\]{}'\"#=_/\\]+", " ", s).strip()
    tokens = s.split()
    out = []
    for t in tokens:
        if t.isdigit():
            out.append(str(int(t)))
        elif t in SUBPART_ALIASES:
            out.append(SUBPART_ALIASES[t])
        else:
            # split glued forms like '3c', '৩গ', '3গ'
            m = re.fullmatch(r"(\d+)([a-z]{1,3}|[ক-চ])", t)
            if m and (m.group(2) in SUBPART_ALIASES):
                out += [str(int(m.group(1))), SUBPART_ALIASES[m.group(2)]]
            else:
                out.append(t)
    return "".join(out)


def parse(raw: str) -> tuple[str | None, str | None]:
    """Return (question_number, subpart) parsed from a raw label; either may be None."""
    n = normalize(raw)
    m = re.fullmatch(r"(\d+)?([ক-চ])?", n)
    if not m or (m.group(1) is None and m.group(2) is None):
        return None, None
    return m.group(1), m.group(2)


_LABEL_HEAD = re.compile(
    r"""^\s*(?:(?:প্রশ্ন|উত্তর|উঃ|উ:|ans(?:wer)?\.?|q\.?|question)[:\s]*(?:নং|no\.?|to)?\s*(?:q\.?\s*)?)?   # optional prefix
        (?:
           [0-9০-৯]{1,2}\s*[\.\)।:\-]?\s*(?:\(?\s*(?:[ক-চ]|[a-f]|i{1,3}|iv)\s*\))?   # 3, ৩।, 3(c), ৩(গ)
         | \(?\s*(?:[ক-চ]|[a-f]|i{1,3}|iv)\s*[\)\.।:]                                # ক) (a) গ।  (marker required)
        )
        (?:\s|$)""", re.IGNORECASE | re.VERBOSE)


def looks_like_label(text: str) -> bool:
    """True when a line starts with a question/part label. A bare letter needs a closing marker,
    so 'A cell is…' or 'ক্ষুদ্র' are not labels."""
    head = (text or "")[:14]
    return bool(_LABEL_HEAD.match(head))


def label_prefix(text: str) -> str:
    """The label part at the start of a line ('উত্তর: ২ (ক) বাংলা…' -> 'উত্তর: ২ (ক) '), or ''."""
    m = _LABEL_HEAD.match((text or "")[:14])
    return m.group(0) if m else ""


def generate_alt_labels(q_label: str, sub_label: str | None = None) -> list[str]:
    """Deterministic alternative labels shown to teachers for review (M1 step 5)."""
    n = normalize_digits(q_label)
    bn = n.translate(str.maketrans("0123456789", "০১২৩৪৫৬৭৮৯"))
    if sub_label is None:
        return [bn, n, f"Q{n}", f"Q.{n}", f"{n}.", f"{bn}।", f"{bn})", f"{n})",
                f"প্রশ্ন {bn}", f"উত্তর {bn}", f"Ans {n}", f"Ans. {n}"]
    s = sub_label
    latin = {v: k for k, v in SUBPART_ALIASES.items() if len(k) == 1 and k.isascii()}.get(s, s)
    return [s, f"{s})", f"({s})", f"{s}.", f"{bn}({s})", f"{n}({s})", f"{bn}-{s}", f"{n} {s}",
            f"{n}({latin})", f"{n}{latin}"]
