"""Pydantic models for slide definitions and presentation structure."""

from __future__ import annotations

from enum import Enum
from typing import Annotated, Literal, Optional, Union

from pydantic import BaseModel, Field, field_validator

ALLOWED_HYPERLINK_SCHEMES = {"http://", "https://", "mailto:"}


def _validate_hyperlink(v: str | None) -> str | None:
    """Reject dangerous URI schemes (javascript:, file:, data:, etc.)."""
    if v is None:
        return v
    v_lower = v.lower().strip()
    if any(v_lower.startswith(scheme) for scheme in ALLOWED_HYPERLINK_SCHEMES):
        return v
    if ":" in v_lower.split("/")[0]:
        # Has a scheme but it's not in the allow-list
        raise ValueError(
            f"Unsupported hyperlink scheme. Allowed: http://, https://, mailto:"
        )
    # No scheme — treat as relative URL (safe)
    return v


class SlideType(str, Enum):
    TITLE = "title"
    SECTION = "section"
    CONTENT = "content"
    TWO_COLUMN = "two_column"
    PROS_CONS = "pros_cons"
    CHART = "chart"
    DIAGRAM = "diagram"
    BLANK = "blank"
    COMPARISON = "comparison"
    TABLE = "table"
    # Advanced layouts
    QUOTE = "quote"
    BIG_NUMBER = "big_number"
    PROCESS = "process"
    TIMELINE = "timeline"
    STEP_CARDS = "step_cards"
    # Composition layouts
    DASHBOARD = "dashboard"
    IMAGE_TEXT = "image_text"
    ICON_GRID = "icon_grid"
    # Production layouts
    TEAM = "team"
    CLOSING = "closing"
    SWOT = "swot"
    FUNNEL = "funnel"
    AGENDA = "agenda"


class ChartType(str, Enum):
    BAR = "bar"
    LINE = "line"
    PIE = "pie"
    COLUMN = "column"
    AREA = "area"


class ShapeType(str, Enum):
    RECTANGLE = "rectangle"
    ROUNDED_RECTANGLE = "rounded_rectangle"
    CIRCLE = "circle"
    OVAL = "oval"
    ARROW_RIGHT = "arrow_right"
    ARROW_LEFT = "arrow_left"
    ARROW_UP = "arrow_up"
    ARROW_DOWN = "arrow_down"
    CHEVRON = "chevron"
    DIAMOND = "diamond"
    TRIANGLE = "triangle"
    LINE = "line"
    PENTAGON = "pentagon"
    HEXAGON = "hexagon"
    CALLOUT = "callout"


class Position(BaseModel):
    """Absolute position and dimensions on a slide, in inches."""

    left: float = Field(description="Left edge position in inches")
    top: float = Field(description="Top edge position in inches")
    width: float = Field(description="Width in inches")
    height: float = Field(description="Height in inches")


class TextStyle(BaseModel):
    """Typography styling for text elements."""

    font_name: Optional[str] = None
    font_size: Optional[int] = Field(default=None, description="Font size in points")
    bold: bool = False
    italic: bool = False
    underline: bool = False
    color: Optional[str] = Field(default=None, description="Hex color code, e.g. '#1A2B3C'")
    alignment: Optional[str] = Field(
        default=None, description="Text alignment: left, center, right, justify"
    )
    line_spacing: Optional[float] = Field(
        default=None, description="Line spacing multiplier, e.g. 1.2"
    )


class TextRun(BaseModel):
    """A contiguous run of text with uniform styling."""

    text: str
    style: Optional[TextStyle] = None
    hyperlink: Optional[str] = Field(default=None, description="URL for clickable text (http://, https://, mailto: only)")

    @field_validator("hyperlink")
    @classmethod
    def check_hyperlink(cls, v: str | None) -> str | None:
        return _validate_hyperlink(v)


class Paragraph(BaseModel):
    """A paragraph consisting of one or more text runs."""

    runs: list[TextRun] = Field(default_factory=list)
    level: int = Field(default=0, description="Indentation level for bullets (0-based)")
    alignment: Optional[str] = None
    bullet: bool = False
    space_after: Optional[float] = Field(default=None, description="Space after in points")
    space_before: Optional[float] = Field(default=None, description="Space before in points")


class TextBox(BaseModel):
    """A text box element containing paragraphs."""

    type: Literal["textbox"] = "textbox"
    paragraphs: list[Paragraph]
    position: Optional[Position] = None
    style: Optional[TextStyle] = None
    fill_color: Optional[str] = None


class BulletList(BaseModel):
    """A bullet list element."""

    type: Literal["bullet_list"] = "bullet_list"
    items: list[str] = Field(max_length=30)
    position: Optional[Position] = None
    style: Optional[TextStyle] = None
    bullet_char: Optional[str] = Field(default=None, description="Custom bullet character")


class ChartDataSeries(BaseModel):
    """A single data series for a chart."""

    name: str = Field(max_length=200)
    values: list[float] = Field(max_length=200)


class ChartData(BaseModel):
    """Chart data with categories and series."""

    categories: list[str] = Field(max_length=100)
    series: list[ChartDataSeries] = Field(max_length=20)


class ChartElement(BaseModel):
    """A chart element on a slide."""

    type: Literal["chart"] = "chart"
    chart_type: ChartType
    data: ChartData
    title: Optional[str] = None
    position: Optional[Position] = None
    show_legend: bool = True
    show_data_labels: bool = False


class ShapeElement(BaseModel):
    """A geometric shape element."""

    type: Literal["shape"] = "shape"
    shape_type: ShapeType
    text: Optional[str] = None
    fill_color: Optional[str] = None
    border_color: Optional[str] = None
    border_width: Optional[float] = Field(default=None, description="Border width in points")
    position: Optional[Position] = None
    style: Optional[TextStyle] = None
    rotation: Optional[float] = Field(default=None, description="Rotation in degrees")
    hyperlink: Optional[str] = Field(default=None, description="URL — clicking the shape opens this link (http://, https://, mailto: only)")

    @field_validator("hyperlink")
    @classmethod
    def check_hyperlink(cls, v: str | None) -> str | None:
        return _validate_hyperlink(v)


class ConnectorElement(BaseModel):
    """A connector line between two shapes."""

    type: Literal["connector"] = "connector"
    start_shape_index: int = Field(description="Index of the start shape in the slide elements list")
    end_shape_index: int = Field(description="Index of the end shape in the slide elements list")
    color: Optional[str] = None
    width: Optional[float] = Field(default=None, description="Line width in points")


class CropBox(BaseModel):
    """Cropping offsets as fractions of the image dimensions (0.0 to 1.0)."""

    left: float = Field(default=0.0, ge=0.0, le=1.0)
    top: float = Field(default=0.0, ge=0.0, le=1.0)
    right: float = Field(default=0.0, ge=0.0, le=1.0)
    bottom: float = Field(default=0.0, ge=0.0, le=1.0)


class ImageElement(BaseModel):
    """An image element referenced by file path."""

    type: Literal["image"] = "image"
    path: str = Field(description="File path to the image")
    position: Optional[Position] = None
    alt_text: Optional[str] = None
    crop: Optional[CropBox] = Field(default=None, description="Crop offsets as fractions 0.0-1.0")
    mask_shape: Optional[ShapeType] = Field(default=None, description="Shape to mask/clip the image into")
    hyperlink: Optional[str] = Field(default=None, description="URL — clicking the image opens this link (http://, https://, mailto: only)")

    @field_validator("hyperlink")
    @classmethod
    def check_hyperlink(cls, v: str | None) -> str | None:
        return _validate_hyperlink(v)


class TableCell(BaseModel):
    """A single cell in a table."""

    text: str
    style: Optional[TextStyle] = None
    fill_color: Optional[str] = None
    col_span: int = 1
    row_span: int = 1


class TableElement(BaseModel):
    """A table element with rows and columns."""

    type: Literal["table"] = "table"
    rows: list[list[TableCell]] = Field(max_length=100)
    position: Optional[Position] = None
    header_row: bool = True
    col_widths: Optional[list[float]] = Field(
        default=None, max_length=50, description="Column widths in inches"
    )


class GroupElement(BaseModel):
    """A group of elements treated as a single unit."""

    type: Literal["group"] = "group"
    elements: list[SlideElement] = Field(default_factory=list)
    position: Optional[Position] = None


SlideElement = Annotated[
    Union[
        TextBox,
        BulletList,
        ChartElement,
        ShapeElement,
        ConnectorElement,
        ImageElement,
        TableElement,
        GroupElement,
    ],
    Field(discriminator="type"),
]

# Rebuild models that use forward references
GroupElement.model_rebuild()


class ColumnContent(BaseModel):
    """Content for a single column in multi-column layouts."""

    title: Optional[str] = Field(default=None, max_length=200)
    bullets: list[str] = Field(default_factory=list, max_length=30)
    elements: list[SlideElement] = Field(default_factory=list, max_length=50)
    icon: Optional[str] = Field(default=None, description="Icon name or path for column header")


class ProcessStep(BaseModel):
    """A single step in a process flow, timeline, or step-cards layout."""

    title: str = Field(max_length=100)
    description: Optional[str] = Field(default=None, max_length=500)
    date: Optional[str] = Field(default=None, max_length=50, description="Date/label for timeline")
    status: Literal["completed", "active", "future"] = "future"


class KpiCard(BaseModel):
    """A single KPI metric card for dashboard layouts."""

    value: str = Field(max_length=50, description="The metric value, e.g. '47%', '$1.2M'")
    label: str = Field(max_length=100, description="Label below the value")
    change: Optional[str] = Field(default=None, max_length=50, description="Change text, e.g. '+12%'")
    trend: Literal["up", "down", "neutral"] = "neutral"
    color: Optional[str] = Field(default=None, description="Override accent color for this card")


class IconItem(BaseModel):
    """An icon + label item for icon grid layouts."""

    icon: str = Field(max_length=10, description="Emoji or single char to use as icon")
    title: str = Field(max_length=100)
    description: Optional[str] = Field(default=None, max_length=300)


class TeamMember(BaseModel):
    """A team member bio card."""

    name: str = Field(max_length=100)
    role: str = Field(max_length=200)
    bio: Optional[str] = Field(default=None, max_length=500)
    image_path: Optional[str] = Field(default=None, description="Path to headshot image")


class SwotData(BaseModel):
    """SWOT analysis data."""

    strengths: list[str] = Field(default_factory=list, max_length=8)
    weaknesses: list[str] = Field(default_factory=list, max_length=8)
    opportunities: list[str] = Field(default_factory=list, max_length=8)
    threats: list[str] = Field(default_factory=list, max_length=8)


class FunnelStage(BaseModel):
    """A single stage in a funnel diagram."""

    label: str = Field(max_length=100)
    value: Optional[str] = Field(default=None, max_length=50, description="Metric value, e.g. '1,200'")
    description: Optional[str] = Field(default=None, max_length=300)


class AgendaItem(BaseModel):
    """A single item in a visual agenda."""

    title: str = Field(max_length=200)
    description: Optional[str] = Field(default=None, max_length=500)
    status: Literal["completed", "active", "upcoming"] = "upcoming"


class SlideDefinition(BaseModel):
    """Complete definition for a single slide."""

    slide_type: SlideType
    title: Optional[str] = Field(default=None, max_length=500)
    subtitle: Optional[str] = Field(default=None, max_length=500)
    body: Optional[str] = Field(default=None, max_length=5000)
    bullets: list[str] = Field(default_factory=list, max_length=30)
    columns: list[ColumnContent] = Field(default_factory=list, max_length=6)
    pros: list[str] = Field(default_factory=list, max_length=20)
    cons: list[str] = Field(default_factory=list, max_length=20)
    elements: list[SlideElement] = Field(default_factory=list, max_length=50)
    speaker_notes: Optional[str] = Field(default=None, max_length=5000)
    background_color: Optional[str] = None
    transition: Optional[str] = None
    # Quote slide fields
    quote_attribution: Optional[str] = Field(default=None, max_length=200, description="Person name for quote attribution")
    quote_attribution_role: Optional[str] = Field(default=None, max_length=200, description="Role/title for quote attribution")
    # Process/timeline fields
    steps: list[ProcessStep] = Field(default_factory=list, max_length=10, description="Steps for process/timeline/step-cards layouts")
    active_step: Optional[int] = Field(default=None, ge=0, description="0-based index of the active step")
    # Dashboard fields
    kpis: list[KpiCard] = Field(default_factory=list, max_length=12, description="KPI cards for dashboard layouts")
    # Icon grid fields
    icons: list[IconItem] = Field(default_factory=list, max_length=12, description="Icon items for icon_grid layouts")
    # Image-text layout fields
    image_path: Optional[str] = Field(default=None, description="Image path for image_text layout")
    image_position: Literal["left", "right"] = "left"
    # Team slide fields
    team_members: list[TeamMember] = Field(default_factory=list, max_length=8, description="Team member cards for team layouts")
    # Closing slide fields
    cta_text: Optional[str] = Field(default=None, max_length=200, description="Call-to-action button text")
    contact_info: list[str] = Field(default_factory=list, max_length=6, description="Contact details (email, phone, etc.)")
    # SWOT slide fields
    swot: Optional[SwotData] = Field(default=None, description="SWOT analysis data")
    # Funnel slide fields
    funnel_stages: list[FunnelStage] = Field(default_factory=list, max_length=8, description="Funnel stages from top to bottom")
    # Agenda slide fields
    agenda_items: list[AgendaItem] = Field(default_factory=list, max_length=12, description="Agenda items for agenda layouts")
    active_agenda_item: Optional[int] = Field(default=None, ge=0, description="0-based index of the active agenda item")


class PresentationMetadata(BaseModel):
    """Metadata for the presentation file."""

    title: str
    subtitle: Optional[str] = None
    author: Optional[str] = None
    company: Optional[str] = None
    date: Optional[str] = None
    subject: Optional[str] = None
    category: Optional[str] = None


class PresentationDefinition(BaseModel):
    """Complete definition for a presentation deck."""

    metadata: PresentationMetadata
    slides: list[SlideDefinition] = Field(max_length=100)
    width_inches: float = Field(default=13.333, ge=1.0, le=50.0, description="Slide width (16:9 default)")
    height_inches: float = Field(default=7.5, ge=1.0, le=50.0, description="Slide height (16:9 default)")
