"""Language-safe text sanitizer for the OmniVoice TTS wrapper.

Strips artifacts that are wrong to read aloud in ANY language — LLM
reasoning/thinking blocks, markdown, HTML/XML tags, model control/special
tokens, stray control characters — and normalizes Unicode (NFKC) + whitespace.

DELIBERATELY does NOT do English-specific normalization (number / phone /
email / currency expansion). OmniVoice is a 600+-language model; those rewrites
are correct only for English and would corrupt non-English input. The fork we
took the idea from (groxaxo/omnivoice-streaming) chains that English-only step
in — we keep only the language-neutral subset (eshpfi session 2026-06-19).

OmniVoice's own inline non-verbal symbols ([laughter], [sigh], [breath], …) are
PRESERVED — they use square brackets and contain no "](...)" link tail, so none
of the markdown/HTML rules below touch them.
"""
from __future__ import annotations

import re
import unicodedata

# LLM reasoning/thinking blocks: <think>…</think>, <reasoning>…</reasoning>, etc.
# Tag + content both removed (the content is private chain-of-thought, not speech).
_THINK_RE = re.compile(
    r"<(think|thinking|reasoning|reflection|scratchpad)>.*?</\1>",
    re.IGNORECASE | re.DOTALL,
)

# Markdown — applied while line breaks still exist (the (?m)^ anchors need them).
_CODE_FENCE_RE = re.compile(r"```.*?```", re.DOTALL)          # fenced code block → drop
_IMAGE_RE = re.compile(r"!\[[^\]]*\]\([^)]*\)")               # ![alt](url) → drop
_LINK_RE = re.compile(r"\[([^\]]+)\]\([^)]*\)")               # [text](url) → text
_INLINE_CODE_RE = re.compile(r"`([^`]*)`")                    # `code` → code
# Emphasis: ***, **, *, ~~, __ (single _ deliberately EXCLUDED so snake_case
# words like "voice_clone_prompt" survive intact).
_EMPHASIS_RE = re.compile(r"(\*\*\*|\*\*|\*|~~|__)(.*?)\1", re.DOTALL)
_HEADING_RE = re.compile(r"(?m)^[ \t]{0,3}#{1,6}[ \t]+")      # "## H" → ""
_BLOCKQUOTE_RE = re.compile(r"(?m)^[ \t]{0,3}>[ \t]?")        # "> q" → "q"
_LIST_MARKER_RE = re.compile(r"(?m)^[ \t]*(?:[-*+]|\d+[.)])[ \t]+")  # "- x" / "1. x" → "x"
_HR_RE = re.compile(r"(?m)^[ \t]{0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$")  # "---" rule → drop

# Model / chat-template control + special tokens.
_CONTROL_TOKEN_RE = re.compile(
    r"<\|[^>]*?\|>"                              # <|im_start|>, <|eot_id|>, …
    r"|</?s>"                                    # <s> </s>
    r"|<(?:pad|unk|mask|bos|eos|sep|cls)>"       # common HF special tokens
    r"|\[/?INST\]"                               # [INST] [/INST]
    r"|<</?SYS>>",                               # <<SYS>> <</SYS>>
    re.IGNORECASE,
)

# Any remaining HTML/XML tags (run AFTER think-blocks so their content is gone).
_HTML_TAG_RE = re.compile(r"<[^>]+>")

# Stray control characters (keep \n and \t; collapsed in the final whitespace pass).
_CTRL_CHARS_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")


def sanitize_tts_text(text: str) -> str:
    """Return ``text`` cleaned of artifacts that shouldn't be spoken, language-safe.

    Idempotent and Unicode-aware. Collapses all whitespace to single spaces at the
    end (the scheduler's splitter does the same, so paragraph structure is moot for
    synthesis). Returns ``""`` for falsy input.
    """
    if not text:
        return ""

    # 1. LLM reasoning/thinking blocks (tag + content).
    text = _THINK_RE.sub(" ", text)

    # 2. Markdown constructs (order matters: fences/images/links before inline/emphasis).
    text = _CODE_FENCE_RE.sub(" ", text)
    text = _IMAGE_RE.sub(" ", text)
    text = _LINK_RE.sub(r"\1", text)
    text = _INLINE_CODE_RE.sub(r"\1", text)
    text = _EMPHASIS_RE.sub(r"\2", text)
    text = _HEADING_RE.sub("", text)
    text = _BLOCKQUOTE_RE.sub("", text)
    text = _LIST_MARKER_RE.sub("", text)
    text = _HR_RE.sub(" ", text)

    # 3. Model control/special tokens, then any leftover HTML/XML tags.
    text = _CONTROL_TOKEN_RE.sub(" ", text)
    text = _HTML_TAG_RE.sub(" ", text)

    # 4. Unicode normalize (NFKC) + drop stray control characters.
    text = unicodedata.normalize("NFKC", text)
    text = _CTRL_CHARS_RE.sub("", text)

    # 5. Collapse all whitespace (incl. newlines) to single spaces, trim.
    return " ".join(text.split())
