"""ChatPart protocol — normalize · content cache · legacy derivation."""

from __future__ import annotations

from typing import Any, Literal, TypedDict

from niannian.chat.stickers import validate_sticker_id
from niannian.chat.stickers_catalog import resolve_sticker_id
from niannian.chat.user_stickers import is_user_sticker_id

PartType = Literal["text", "sticker", "image", "link", "file"]


class TextPart(TypedDict):
    type: Literal["text"]
    text: str


class StickerPart(TypedDict):
    type: Literal["sticker"]
    sticker_id: str


class ImagePart(TypedDict):
    type: Literal["image"]
    attachment_id: str


class LinkPart(TypedDict):
    type: Literal["link"]
    url: str


class FilePart(TypedDict):
    type: Literal["file"]
    attachment_id: str
    name: str
    mime: str


ChatPart = TextPart | StickerPart | ImagePart | LinkPart | FilePart


def normalize_parts(parts: list[dict[str, Any]]) -> list[dict[str, Any]]:
    """Validate and normalize a parts array (SSOT for message content)."""
    if not parts:
        raise ValueError("parts must not be empty")
    out: list[dict[str, Any]] = []
    for i, part in enumerate(parts):
        if not isinstance(part, dict):
            raise ValueError(f"part[{i}] must be an object")
        ptype = part.get("type")
        if ptype == "text":
            text = part.get("text")
            if not isinstance(text, str) or not text.strip():
                raise ValueError(f"part[{i}] text required")
            out.append({"type": "text", "text": text})
        elif ptype == "sticker":
            sticker_id = part.get("sticker_id")
            if not isinstance(sticker_id, str) or not sticker_id.strip():
                raise ValueError(f"part[{i}] sticker_id required")
            canonical = resolve_sticker_id(sticker_id)
            if not is_user_sticker_id(canonical):
                validate_sticker_id(sticker_id)
            out.append({"type": "sticker", "sticker_id": canonical})
        elif ptype == "image":
            attachment_id = part.get("attachment_id")
            if not isinstance(attachment_id, str) or not attachment_id.strip():
                raise ValueError(f"part[{i}] attachment_id required")
            out.append({"type": "image", "attachment_id": attachment_id.strip()})
        elif ptype == "link":
            url = part.get("url")
            if not isinstance(url, str) or not url.strip():
                raise ValueError(f"part[{i}] url required")
            out.append({"type": "link", "url": url.strip()})
        elif ptype == "file":
            attachment_id = part.get("attachment_id")
            name = part.get("name")
            mime = part.get("mime")
            if not isinstance(attachment_id, str) or not attachment_id.strip():
                raise ValueError(f"part[{i}] attachment_id required")
            if not isinstance(name, str) or not name.strip():
                raise ValueError(f"part[{i}] name required")
            if not isinstance(mime, str) or not mime.strip():
                raise ValueError(f"part[{i}] mime required")
            out.append(
                {
                    "type": "file",
                    "attachment_id": attachment_id.strip(),
                    "name": name.strip(),
                    "mime": mime.strip(),
                }
            )
        else:
            raise ValueError(f"part[{i}] unknown type: {ptype!r}")
    return out


def normalize_text_to_parts(text: str) -> list[dict[str, Any]]:
    """Convert legacy plain-text input to a single text part."""
    return normalize_parts([{"type": "text", "text": text.strip()}])


def parts_to_content(parts: list[dict[str, Any]]) -> str:
    """Derive searchable content cache from text parts."""
    texts = [p["text"] for p in parts if p.get("type") == "text" and p.get("text")]
    return "\n".join(texts)


def parts_from_legacy(
    *,
    content: str,
    meta: dict[str, Any],
    parts: list[dict[str, Any]] | None = None,
) -> list[dict[str, Any]]:
    """Derive parts from a legacy turn (content / meta.sticker_id) when parts unset."""
    if parts is not None:
        return parts
    out: list[dict[str, Any]] = []
    if content and content.strip():
        out.append({"type": "text", "text": content})
    sticker_id = meta.get("sticker_id")
    if sticker_id:
        out.append({"type": "sticker", "sticker_id": str(sticker_id)})
    return out


def resolve_message_input(
    text: str,
    parts: list[dict[str, Any]] | None,
) -> tuple[list[dict[str, Any]], str]:
    """Resolve API send payload to (normalized_parts, content cache)."""
    if parts is not None:
        normalized = normalize_parts(parts)
        return normalized, parts_to_content(normalized)
    if text.strip():
        normalized = normalize_text_to_parts(text)
        return normalized, text.strip()
    raise ValueError("empty message")
