"""TUI persona schema models."""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator


class PersonaPreset(BaseModel):
    """Named argument override preset."""

    name: str = Field(..., min_length=1)
    display: str = Field(..., min_length=1)
    overrides: dict[str, Any] = Field(default_factory=dict)

    model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)


class Persona(BaseModel):
    """Primary persona record persisted as YAML."""

    name: str = Field(..., min_length=1)
    description: str = Field(default="")
    icon: str = Field(default="")
    actor: str = Field(..., min_length=1)
    color: str | None = None
    base_arguments: dict[str, Any] = Field(default_factory=dict)
    scoped_projects: list[str] = Field(default_factory=list)
    scoped_plans: list[str] = Field(default_factory=list)
    argument_presets: list[PersonaPreset] = Field(default_factory=list)
    cycle_order: int = Field(default=0, ge=0)
    greeting: str = Field(default="")

    @field_validator("actor")
    @classmethod
    def validate_actor_ref(cls, value: str) -> str:
        if "/" not in value:
            raise ValueError("actor must be namespaced (namespace/name)")
        return value

    @field_validator("name")
    @classmethod
    def validate_name_safe(cls, value: str) -> str:
        if (
            "/" in value
            or "\\" in value
            or ".." in value
            or "\x00" in value
            or any(ord(char) < 32 or ord(char) == 127 for char in value)
        ):
            raise ValueError("name must not contain path/control separators")
        return value

    @model_validator(mode="after")
    def ensure_default_preset(self) -> Persona:
        if not self.argument_presets:
            self.argument_presets = [
                PersonaPreset(name="default", display="default", overrides={})
            ]
            return self
        defaults = [
            preset for preset in self.argument_presets if preset.name == "default"
        ]
        if len(defaults) != 1:
            raise ValueError("must contain exactly one default preset")
        if defaults[0].overrides:
            raise ValueError("default preset overrides must be empty")
        return self

    def effective_arguments(self, preset_name: str | None = None) -> dict[str, Any]:
        """Compute effective arguments with shallow merge semantics."""
        effective = dict(self.base_arguments)
        if not preset_name:
            return effective
        preset = next(
            (item for item in self.argument_presets if item.name == preset_name), None
        )
        if preset is None:
            return effective
        effective.update(preset.overrides)
        return effective

    model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
