"""Remove PDF extraction artifacts (<<<Q:N>>> markers, etc.) from question text."""
from __future__ import annotations

import re

MARKER_TOKEN = re.compile(r"<<<Q:\d+>>>", re.I)
QUESTION_MARKER = re.compile(r"Question:\s*\d+\s*(?:Certy\s*IQ\s*)?", re.I)
CERTYIQ = re.compile(r"Certy\s*IQ", re.I)
# Leaked next-question headers at end of explanation/reference
TRAILING_QTYPE = re.compile(
    r"\s*(?:"
    r"DRAG\s*DROP|HOTSPOT|SELECT\s+AND\s+PLACE|"
    r"K[EÉ]O\s+THẢ|V[UÙ]NG\s+N[OÓ]NG"
    r")\s*-\s*$",
    re.I,
)


def clean_extracted_text(text: str | None) -> str | None:
    if text is None:
        return None
    t = str(text)
    if not t.strip():
        return None
    t = MARKER_TOKEN.sub("", t)
    t = QUESTION_MARKER.sub("", t)
    t = CERTYIQ.sub("", t)
    t = TRAILING_QTYPE.sub("", t)
    t = re.sub(r"[ \t]+\n", "\n", t)
    t = re.sub(r"\n{3,}", "\n\n", t)
    t = re.sub(r"  +", " ", t)
    t = t.strip()
    return t if t else None


def clean_bilingual_field(obj: dict | None) -> bool:
    if not obj or not isinstance(obj, dict):
        return False
    changed = False
    for lang in ("en", "vi"):
        if lang not in obj:
            continue
        cleaned = clean_extracted_text(obj.get(lang))
        if cleaned != obj.get(lang):
            obj[lang] = cleaned
            changed = True
    return changed


def clean_review_item(item: dict) -> bool:
    changed = False
    for key in ("questionText", "stem", "explanation", "reference", "note"):
        if key not in item:
            continue
        cleaned = clean_extracted_text(item.get(key))
        if cleaned != item.get(key):
            item[key] = cleaned
            changed = True
    if item.get("references"):
        new_refs = [
            c for r in item["references"] if (c := clean_extracted_text(r))
        ]
        if new_refs != item["references"]:
            item["references"] = new_refs
            changed = True
    for opt in item.get("options") or []:
        t = opt.get("text")
        if isinstance(t, str):
            c = clean_extracted_text(t)
            if c != t:
                opt["text"] = c
                changed = True
    return changed


def _deep_clean_strings(obj, seen: set[int] | None = None) -> bool:
    """Clean every string value in a nested dict/list structure."""
    if seen is None:
        seen = set()
    changed = False
    oid = id(obj)
    if oid in seen:
        return False
    if isinstance(obj, dict):
        seen.add(oid)
        for key, val in list(obj.items()):
            if isinstance(val, str):
                cleaned = clean_extracted_text(val)
                if cleaned != val:
                    obj[key] = cleaned
                    changed = True
            elif isinstance(val, (dict, list)):
                if _deep_clean_strings(val, seen):
                    changed = True
    elif isinstance(obj, list):
        seen.add(oid)
        for i, val in enumerate(obj):
            if isinstance(val, str):
                cleaned = clean_extracted_text(val)
                if cleaned != val:
                    obj[i] = cleaned
                    changed = True
            elif isinstance(val, (dict, list)):
                if _deep_clean_strings(val, seen):
                    changed = True
    return changed


def clean_question(q: dict) -> bool:
    return _deep_clean_strings(q)
