# retoor <retoor@molodetz.nl>

from __future__ import annotations

import re
from typing import Any, Literal

from pydantic import BaseModel, Field, field_validator, model_validator

from .capabilities import DEFAULT_LIMITS

WIDGET_TYPES = (
    "confirm",
    "choice",
    "choice_multi",
    "text",
    "number",
    "date",
    "select",
    "//",
    "group",
)

VALUE_RE = re.compile(r"^[a-z0-9_][a-z0-9_-]{0,63}$")
NAME_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]{0,63}$")
ID_RE = re.compile(r"^[a-z][a-z0-9-]{0,63}$")
CONTROL_CHARS = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")


def sanitize_text(value: Any, max_len: int) -> str:
    text = CONTROL_CHARS.sub("", str(value if value is not None else ""))
    text = " ".join(text.split())
    if len(text) > max_len:
        text = text[:max_len]
    return text


class OptionModel(BaseModel):
    value: str
    label: str
    description: str = ""
    disabled: bool = False

    @field_validator("value")
    @classmethod
    def _value(cls, v: str) -> str:
        text = sanitize_text(v, 64).lower().replace(" ", "_")
        if not VALUE_RE.match(text):
            raise ValueError(
                "option value must match ^[a-z0-9_][a-z0-9_-]{0,63}$"
            )
        return text

    @field_validator("label")
    @classmethod
    def _label(cls, v: str) -> str:
        text = sanitize_text(v, 80)
        if not text:
            raise ValueError("option label is required")
        return text

    @field_validator("description")
    @classmethod
    def _description(cls, v: str) -> str:
        return sanitize_text(v, 120)


class WidgetModel(BaseModel):
    type: Literal[
        "confirm",
        "choice",
        "choice_multi",
        "text",
        "number",
        "date",
        "select",
        "//",
        "group",
    ]
    name: str = ""
    label: str = ""
    help: str = ""
    text: str = ""
    required: bool = True
    default: Any = None
    display: Literal["auto", "radio", "select", "buttons"] = "auto"
    options: list[OptionModel] = Field(default_factory=list)
    widgets: list[WidgetModel] = Field(default_factory=list)
    max_length: int | None = None
    placeholder: str = ""
    pattern: str = ""
    min: float | None = None
    max: float | None = None
    step: float | None = None
    min_selected: int | None = None
    max_selected: int | None = None

    @field_validator("name")
    @classmethod
    def _name(cls, v: str) -> str:
        text = sanitize_text(v, 64)
        return text

    @field_validator("label", "help", "text", "placeholder", "pattern")
    @classmethod
    def _strings(cls, v: str) -> str:
        return sanitize_text(v, 500)

    @model_validator(mode="after")
    def _shape(self) -> WidgetModel:
        if self.type == "//":
            if not self.text and not self.label:
                raise ValueError("help widget (//) requires text")
            if not self.text:
                self.text = self.label
            return self
        if self.type == "group":
            if not self.label:
                raise ValueError("group requires label")
            if not self.widgets:
                raise ValueError("group requires nested widgets")
            if len(self.widgets) > 24:
                raise ValueError("group may contain at most 24 widgets")
            return self
        if not self.name or not NAME_RE.match(self.name):
            raise ValueError(
                f"widget name required and must match ^[a-zA-Z_][a-zA-Z0-9_]{{0,63}}$ (got {self.name!r})"
            )
        if not self.label:
            raise ValueError(f"widget '{self.name}' requires label")
        self.label = sanitize_text(self.label, 200)
        self.help = sanitize_text(self.help, 500)
        if self.type in ("choice", "choice_multi", "select"):
            if not self.options:
                raise ValueError(f"widget '{self.name}' requires options")
            if len(self.options) > DEFAULT_LIMITS["max_options"]:
                raise ValueError(
                    f"widget '{self.name}' has too many options (max {DEFAULT_LIMITS['max_options']})"
                )
        if self.type == "select":
            self.display = "select"
        return self


class InteractionRequest(BaseModel):
    title: str
    description: str = ""
    widgets: list[WidgetModel]
    submit_label: str = "Confirm"
    cancel_label: str = "Cancel"
    cancelable: bool = True
    timeout_sec: int = 0
    id: str = ""

    @field_validator("title")
    @classmethod
    def _title(cls, v: str) -> str:
        text = sanitize_text(v, 120)
        if not text:
            raise ValueError("title is required")
        return text

    @field_validator("description")
    @classmethod
    def _description(cls, v: str) -> str:
        return sanitize_text(v, 500)

    @field_validator("submit_label", "cancel_label")
    @classmethod
    def _labels(cls, v: str) -> str:
        return sanitize_text(v, 40) or "Confirm"

    @field_validator("timeout_sec")
    @classmethod
    def _timeout(cls, v: int) -> int:
        n = int(v or 0)
        if n < 0 or n > 86400:
            raise ValueError("timeout_sec must be between 0 and 86400")
        return n

    @field_validator("id")
    @classmethod
    def _id(cls, v: str) -> str:
        text = sanitize_text(v, 64).lower()
        if text and not ID_RE.match(text):
            raise ValueError("id must match ^[a-z][a-z0-9-]{0,63}$")
        return text

    @model_validator(mode="after")
    def _widgets(self) -> InteractionRequest:
        if not self.widgets:
            raise ValueError("widgets must contain at least one item")
        if len(self.widgets) > DEFAULT_LIMITS["max_fields"]:
            raise ValueError(
                f"at most {DEFAULT_LIMITS['max_fields']} top-level widgets"
            )
        depth = _max_depth(self.widgets)
        if depth > 3:
            raise ValueError("widget nesting depth must be ≤ 3")
        return self


class InteractionResult(BaseModel):
    interaction_id: str
    status: Literal[
        "submitted", "cancelled", "timeout", "superseded", "error", "channel_changed"
    ]
    channel_id: str
    values: dict[str, Any] = Field(default_factory=dict)
    meta: dict[str, Any] = Field(default_factory=dict)
    error: str | None = None

    def to_dict(self) -> dict[str, Any]:
        return {
            "interaction_id": self.interaction_id,
            "status": self.status,
            "channel_id": self.channel_id,
            "values": {} if self.status != "submitted" else dict(self.values),
            "meta": dict(self.meta),
            "error": self.error,
        }


def _max_depth(widgets: list[WidgetModel], depth: int = 1) -> int:
    deepest = depth
    for widget in widgets:
        if widget.type == "group" and widget.widgets:
            deepest = max(deepest, _max_depth(widget.widgets, depth + 1))
    return deepest


def validate_prompt_args(arguments: dict[str, Any]) -> InteractionRequest:
    try:
        return InteractionRequest.model_validate(arguments or {})
    except Exception as exc:
        from devplacepy.services.devii.errors import ToolInputError

        raise ToolInputError(f"Invalid ui_prompt arguments: {exc}") from exc


def count_input_fields(widgets: list[WidgetModel]) -> int:
    total = 0
    for widget in widgets:
        if widget.type == "//":
            continue
        if widget.type == "group":
            total += count_input_fields(widget.widgets)
        else:
            total += 1
    return total


def flatten_widgets(widgets: list[WidgetModel]) -> list[WidgetModel]:
    out: list[WidgetModel] = []
    for widget in widgets:
        if widget.type == "group":
            out.extend(flatten_widgets(widget.widgets))
        else:
            out.append(widget)
    return out
