"""Avatar chat clipboard paste — text / image / file URIs (0.2.2.60).

GTK-free core; UI wires Gdk.Clipboard in main_window / input bar.
"""

from __future__ import annotations

import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable
from urllib.parse import unquote, urlparse


@dataclass(frozen=True)
class ClipboardPastePlan:
    """What to do after reading the clipboard."""

    insert_text: str = ""
    file_paths: tuple[str, ...] = ()
    handled: bool = False


def parse_uri_list(payload: str | bytes | None) -> list[str]:
    """Parse text/uri-list (GTK/GDK file clipboard) into local paths."""
    if payload is None:
        return []
    if isinstance(payload, bytes):
        try:
            text = payload.decode("utf-8", errors="replace")
        except Exception:
            return []
    else:
        text = str(payload)
    paths: list[str] = []
    for line in text.replace("\r\n", "\n").replace("\r", "\n").split("\n"):
        raw = line.strip()
        if not raw or raw.startswith("#"):
            continue
        if raw.startswith("file:"):
            parsed = urlparse(raw)
            local = unquote(parsed.path or "")
            if local:
                paths.append(local)
            continue
        candidate = Path(raw).expanduser()
        if candidate.is_file():
            paths.append(str(candidate))
    return paths


def save_image_bytes_to_temp(
    data: bytes,
    *,
    suffix: str = ".png",
    prefix: str = "iuve-clipboard-",
) -> str | None:
    """Write clipboard image bytes to a temp file for the attach/drop pipeline."""
    blob = bytes(data or b"")
    if not blob:
        return None
    ext = suffix if suffix.startswith(".") else f".{suffix}"
    try:
        handle = tempfile.NamedTemporaryFile(
            prefix=prefix,
            suffix=ext,
            delete=False,
        )
        handle.write(blob)
        handle.flush()
        handle.close()
        return handle.name
    except OSError:
        return None


def plan_clipboard_paste(
    *,
    text: str | None = None,
    file_paths: list[str] | None = None,
    image_bytes: bytes | None = None,
    image_suffix: str = ".png",
) -> ClipboardPastePlan:
    """
    Prefer files → image → plain text (same order as OS paste into rich inputs).
    Images become a temp file path so callers reuse process_dropped_file.
    """
    paths = [str(p) for p in (file_paths or []) if str(p).strip()]
    if paths:
        return ClipboardPastePlan(file_paths=tuple(paths), handled=True)

    if image_bytes:
        saved = save_image_bytes_to_temp(image_bytes, suffix=image_suffix)
        if saved:
            return ClipboardPastePlan(file_paths=(saved,), handled=True)

    body = str(text or "")
    if body:
        return ClipboardPastePlan(insert_text=body, handled=True)

    return ClipboardPastePlan(handled=False)


def insert_text_into_buffer(buffer: Any, text: str) -> None:
    """Insert clipboard text at the GTK TextBuffer cursor (or selection)."""
    body = str(text or "")
    if not body or buffer is None:
        return
    try:
        bounds = buffer.get_selection_bounds()
        if bounds and len(bounds) == 2 and bounds[0] and bounds[1]:
            buffer.delete(bounds[0], bounds[1])
        insert_iter = buffer.get_iter_at_mark(buffer.get_insert())
        buffer.insert(insert_iter, body)
    except Exception:
        try:
            end = buffer.get_end_iter()
            buffer.insert(end, body)
        except Exception:
            pass


def paste_modifier_matches(*, state: int, control_mask: int, meta_mask: int = 0) -> bool:
    """True for Ctrl (Linux/Windows) or Cmd/Meta (macOS) without Alt confusing the chord."""
    has_ctrl = bool(state & control_mask) if control_mask else False
    has_meta = bool(state & meta_mask) if meta_mask else False
    return has_ctrl or has_meta


def is_paste_keyval(keyval: int, *, key_v: int) -> bool:
    return int(keyval) == int(key_v)


def apply_clipboard_paste_plan(
    plan: ClipboardPastePlan,
    *,
    on_insert_text: Callable[[str], None] | None = None,
    on_files: Callable[[list[str]], None] | None = None,
) -> bool:
    """Dispatch a planned paste; returns True when something was applied."""
    if not plan.handled:
        return False
    if plan.file_paths and on_files is not None:
        on_files(list(plan.file_paths))
        return True
    if plan.insert_text and on_insert_text is not None:
        on_insert_text(plan.insert_text)
        return True
    return False
