"""Post-transcription text cleanup. Four levels:

- raw       — verbatim, no changes (default)
- light     — local regex: strip filler words, normalize spaces/punctuation. No API.
- standard  — Claude (via local `claude` CLI): clean + preserve voice and meaning
- polished  — Claude (via local `claude` CLI): rewrite as a polished written message

`standard` and `polished` use the user's installed `claude` CLI from Claude Code —
no API key needed, no manual setup. The user's existing Claude Code session does
the work. On any failure (missing CLI, no auth, timeout) → fall back to `light`.
"""

from __future__ import annotations

import json
import re

# ── Filler words to strip in "light" mode ─────────────────────────────────
# Whole-word match (word boundaries) — case-insensitive. Order doesn't matter.
RU_FILLERS = [
    r"эээ+", r"эмм+", r"ммм+", r"ааа+",
    r"ну", r"типа", r"как бы", r"в общем", r"короче", r"значит",
    r"в принципе", r"собственно", r"вот", r"это самое",
    r"понимаешь", r"понятно", r"ага", r"угу", r"да-да",
    r"скажем так", r"то есть", r"знаешь",
]
EN_FILLERS = [
    r"um+", r"uh+", r"er+", r"hmm+",
    r"like", r"you know", r"basically", r"actually", r"literally",
    r"sort of", r"kind of", r"i mean", r"you see", r"right\?",
]

_FILLER_PATTERNS = [
    re.compile(r"(?<!\w)(?:" + "|".join(RU_FILLERS) + r")(?!\w)", re.IGNORECASE),
    re.compile(r"(?<!\w)(?:" + "|".join(EN_FILLERS) + r")(?!\w)", re.IGNORECASE),
]

_REPEATED_PUNCT = re.compile(r"\s+([,.!?;:])")  # space before punctuation
_MULTI_SPACE = re.compile(r"\s{2,}")
_MULTI_COMMA = re.compile(r"(,\s*){2,}")
_TRAILING_PUNCT_PILE = re.compile(r"([,!?]){2,}")
_EDGE_SPACES_LINE = re.compile(r"^[ \t]+|[ \t]+$", re.MULTILINE)
# after removing fillers we often get "punct at start of sentence" or " punct word":
# "  , я думаю …" → "я думаю …"   ".  Это …" stays as-is
_LEADING_PUNCT = re.compile(r"(?:^|(?<=[.!?…]\s))[\s,;:]+", re.MULTILINE)
# orphan punctuation surrounded by whitespace (left after filler removal)
_ORPHAN_PUNCT = re.compile(r"\s+([,;])(?=\s|$)")


def _light_local(text: str) -> str:
    """Pure-Python rule-based cleanup — no network, instant."""
    cleaned = text
    for pat in _FILLER_PATTERNS:
        cleaned = pat.sub("", cleaned)
    # collapse the resulting whitespace + punctuation mess
    cleaned = _ORPHAN_PUNCT.sub("", cleaned)             # " , " left between words
    cleaned = _MULTI_COMMA.sub(", ", cleaned)
    cleaned = _REPEATED_PUNCT.sub(r"\1", cleaned)
    cleaned = _TRAILING_PUNCT_PILE.sub(r"\1", cleaned)
    cleaned = _MULTI_SPACE.sub(" ", cleaned)
    cleaned = _EDGE_SPACES_LINE.sub("", cleaned)
    cleaned = _LEADING_PUNCT.sub("", cleaned)            # sentence-start leftover punct
    # capitalise the start of each sentence
    parts = re.split(r"([.!?…]\s+)", cleaned)
    parts = [
        (p[:1].upper() + p[1:]) if i % 2 == 0 and p else p
        for i, p in enumerate(parts)
    ]
    cleaned = "".join(parts).strip()
    if cleaned and cleaned[-1] not in ".!?…":
        cleaned += "."
    return cleaned


# ── Claude API prompts ───────────────────────────────────────────────────





# Common locations the .app's launchd-spawned process won't have in PATH —
# explicitly probe these so a Finder-launched Lirelle can still find `claude`.


DEFAULT_CLEANUP_MODEL = "claude-haiku-4-5"


def _server_clean(text: str, *, level: str, timeout: float = 60.0) -> str:
    """Call the server's /cleanup endpoint. Uses the session token saved by
    licensing.activate_with_code() — no Anthropic API key on the client.

    Raises on any failure (no auth, network down, plan gate, quota gate);
    caller is expected to catch and fall back to _light_local. We never
    swallow errors here — silent fall-through is what made the old CLI
    path look like Pro Plus was working when it wasn't."""
    import json as _json
    import urllib.error
    import urllib.request

    from .licensing import LICENSE_SERVER_URL, _load_session

    session = _load_session()
    if not session or not session.get("token"):
        raise RuntimeError("not logged in — open Account and activate first")

    payload = _json.dumps({"text": text, "level": level}).encode("utf-8")
    req = urllib.request.Request(
        f"{LICENSE_SERVER_URL.rstrip('/')}/cleanup",
        data=payload,
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {session['token']}",
        },
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            body = _json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        try:
            err = _json.loads(e.read().decode("utf-8"))
            detail = err.get("detail") or err.get("error") or f"HTTP {e.code}"
        except Exception:
            detail = f"HTTP {e.code}"
        raise RuntimeError(f"server cleanup rejected: {detail}") from e
    except urllib.error.URLError as e:
        raise RuntimeError(f"can't reach cleanup server: {e.reason}") from e

    cleaned = (body.get("text") or "").strip()
    if not cleaned:
        raise RuntimeError("server returned empty cleanup result")
    return cleaned


def clean_text(text: str, *, level: str,
               model: str = DEFAULT_CLEANUP_MODEL) -> str:
    """Apply cleanup at given level. Returns cleaned text (or local light on
    any failure). `standard` / `polished` now go through the server's
    /cleanup endpoint — no `claude` CLI on the customer's Mac, no per-user
    API key. `model` is kept in the signature for backwards compatibility
    with old call sites but is ignored: the server picks the model based on
    CLEANUP_MODEL_STANDARD / CLEANUP_MODEL_POLISHED env vars.

    Note: `model` is intentionally unused — kept only so existing callers
    in app.py:_process_body don't have to be updated in lockstep."""
    text = (text or "").strip()
    if not text or level == "raw":
        return text
    if level == "light":
        return _light_local(text)
    if level not in ("standard", "polished"):
        print(f"[cleanup] unknown level '{level}' — returning raw")
        return text
    try:
        out = _server_clean(text, level=level)
        return out
    except Exception as e:
        # Network / auth / plan / quota — fall back to local light cleanup.
        # Logged so we can see in customer logs what's actually happening.
        print(f"[cleanup] server cleanup failed: {e} — falling back to light")
        return _light_local(text)
