"""Normalization helpers for opaque model-facing token strings.

These helpers are intentionally conservative:
- only remove whitespace/formatting noise used by copy-paste issues
- only canonicalize when the result still matches an expected strict shape
- otherwise return the original input unchanged
"""
from __future__ import annotations

import re

_UUID_HYPHEN_RE = re.compile(
    r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
)
_UUID_COMPACT_RE = re.compile(r"^[0-9a-fA-F]{32}$")
_HEX_RE = re.compile(r"^[0-9a-fA-F]+$")
_BG_HANDLE_RE = re.compile(r"^bg_[0-9a-f]{10}$")
_KT_TASK_ID_RE = re.compile(r"^kt_[0-9a-f]{14}$")
_ATT_BARE_RE = re.compile(r"^att_(\d+)$", re.IGNORECASE)
_ATT_URI_RE = re.compile(r"^attachment://att_(\d+)$", re.IGNORECASE)

# Common hidden formatting characters seen in model / clipboard output.
_FORMAT_CHARS = {
    "\u200b",  # ZWSP
    "\u200c",  # ZWNJ
    "\u200d",  # ZWJ
    "\ufeff",  # BOM / ZWNBSP
    "\u2060",  # Word joiner
    "\u202a",  # LRE
    "\u202b",  # RLE
    "\u202d",  # LRO
    "\u202e",  # RLO
}


def _strip_noise(value: str) -> str:
    return "".join(ch for ch in value if (not ch.isspace() and ch not in _FORMAT_CHARS))


def _bounded_str(value: str, *, max_len: int) -> str | None:
    if not isinstance(value, str):
        return None
    if len(value) > max_len:
        return None
    return value


def normalize_uuid_token(value: str, *, max_len: int = 256) -> str:
    src = _bounded_str(value, max_len=max_len)
    if src is None:
        return value
    cleaned = _strip_noise(src)
    if _UUID_HYPHEN_RE.fullmatch(cleaned):
        return cleaned.lower()
    if _UUID_COMPACT_RE.fullmatch(cleaned):
        c = cleaned.lower()
        return f"{c[:8]}-{c[8:12]}-{c[12:16]}-{c[16:20]}-{c[20:32]}"
    return value


def normalize_hex_id_token(
    value: str,
    *,
    allowed_lengths: tuple[int, ...] = (8, 12),
    max_len: int = 256,
) -> str:
    src = _bounded_str(value, max_len=max_len)
    if src is None:
        return value
    cleaned = _strip_noise(src)
    if _HEX_RE.fullmatch(cleaned) and len(cleaned) in allowed_lengths:
        return cleaned.lower()
    return value


def normalize_bg_handle_token(value: str, *, max_len: int = 256) -> str:
    src = _bounded_str(value, max_len=max_len)
    if src is None:
        return value
    cleaned = _strip_noise(src).lower()
    if _BG_HANDLE_RE.fullmatch(cleaned):
        return cleaned
    return value


def normalize_stream_id_token(value: str, *, max_len: int = 512) -> str:
    src = _bounded_str(value, max_len=max_len)
    if src is None:
        return value
    cleaned = _strip_noise(src)
    if not cleaned:
        return value
    if len(cleaned) > max_len:
        return value
    return cleaned


def normalize_attachment_ref_token(value: str, *, max_len: int = 256) -> str:
    src = _bounded_str(value, max_len=max_len)
    if src is None:
        return value
    cleaned = _strip_noise(src)
    m = _ATT_BARE_RE.fullmatch(cleaned)
    if m:
        return f"att_{m.group(1)}"
    m = _ATT_URI_RE.fullmatch(cleaned)
    if m:
        return f"attachment://att_{m.group(1)}"
    return value


def normalize_kt_task_id_token(value: str, *, max_len: int = 64) -> str:
    """Normalize kanban task ids of the form ``kt_<14 hex>``.

    Returns the original value unchanged unless whitespace/format noise can be
    stripped into a canonical task id.
    """
    src = _bounded_str(value, max_len=max_len)
    if src is None:
        return value
    cleaned = _strip_noise(src).lower()
    if _KT_TASK_ID_RE.fullmatch(cleaned):
        return cleaned
    return value


def normalize_token_by_kind(kind: str, value: str) -> str:
    if not isinstance(value, str):
        return value
    if kind == "uuid":
        return normalize_uuid_token(value)
    if kind == "hex_id":
        return normalize_hex_id_token(value)
    if kind == "bg_handle":
        return normalize_bg_handle_token(value)
    if kind == "stream_id":
        return normalize_stream_id_token(value)
    if kind == "attachment_ref":
        return normalize_attachment_ref_token(value)
    if kind == "kt_task":
        return normalize_kt_task_id_token(value)
    raise ValueError(f"Unknown token kind: {kind}")
