from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field, model_validator

from .theme_tokens import DEFAULT_THEME_PALETTE

SlideType = Literal[
    "title", "section", "columns", "comparison", "timeline", "kpi_grid", "quote", "image_text", "image", "custom"
]
DesignVariant = Literal["plain", "cards", "bordered", "accent_left"]
DesignDensity = Literal["compact", "medium", "spacious"]
DesignTone = Literal["professional", "minimal", "bold"]
DesignBackground = Literal["light", "dark", "gradient"]
DeckSize = Literal["wide_16_9", "standard_4_3"]
ImagePosition = Literal["left", "right", "bottom"]
CustomElementType = Literal["text", "shape"]
CustomShapeType = Literal["rect", "rounded_rect", "line"]
TABLE_CARD_MAX_COLUMNS = 6
TABLE_CARD_MAX_ROWS = 10
KPI_CARD_VALUE_MAX_CHARS = 32
KPI_CARD_VALUE_MAX_WORDS = 5
KPI_CARD_LABEL_MAX_CHARS = 40
KPI_CARD_LABEL_MAX_WORDS = 6
KPI_CARD_DETAIL_MAX_BEATS = 2
KPI_CARD_DETAIL_MAX_CHARS = 110
FOUR_CARD_TEXT_TITLE_MAX_CHARS = 40
FOUR_CARD_TEXT_BODY_MAX_BEATS = 3
FOUR_CARD_TEXT_BODY_MAX_CHARS = 160


class ThemeSpec(BaseModel):
    font_family: str = "Arial"
    primary_color: str = DEFAULT_THEME_PALETTE.accent_primary
    background_color: str = DEFAULT_THEME_PALETTE.background_default


class DeckConfig(BaseModel):
    size: DeckSize = "wide_16_9"
    theme: ThemeSpec = Field(default_factory=ThemeSpec)


class DesignSpec(BaseModel):
    variant: DesignVariant = "cards"
    density: DesignDensity = "medium"
    tone: DesignTone = "professional"
    background: DesignBackground = "light"


class LayoutSpec(BaseModel):
    columns: int | None = None
    gap: float | None = None


class TableSpec(BaseModel):
    columns: list[str]
    rows: list[list[str]]

    @model_validator(mode="after")
    def validate_table(self) -> TableSpec:
        if not self.columns:
            raise ValueError("Tables require at least one column")
        if any(not column.strip() for column in self.columns):
            raise ValueError("Tables require non-empty column headers")
        if not self.rows:
            raise ValueError("Tables require at least one row")
        if any(len(row) != len(self.columns) for row in self.rows):
            raise ValueError("Table rows must match the declared column count")
        if not any(any(cell.strip() for cell in row) for row in self.rows):
            raise ValueError("Tables must contain at least one non-empty cell")
        return self


class ColumnContent(BaseModel):
    title: str
    body: str | None = None
    table: TableSpec | None = None

    @model_validator(mode="after")
    def validate_column_content(self) -> ColumnContent:
        if not self.title.strip():
            raise ValueError("Column title must be non-empty")
        if not ((self.body and self.body.strip()) or self.table is not None):
            raise ValueError("Column content requires body or table")
        return self


class ComparisonSide(BaseModel):
    title: str
    body: str | None = None
    table: TableSpec | None = None

    @model_validator(mode="after")
    def validate_comparison_side(self) -> ComparisonSide:
        if not self.title.strip():
            raise ValueError("Comparison side title must be non-empty")
        if not ((self.body and self.body.strip()) or self.table is not None):
            raise ValueError("Comparison side requires body or table")
        return self


class TimelineItem(BaseModel):
    title: str
    body: str


class KpiMetric(BaseModel):
    value: str
    label: str
    detail: str | None = None


class ImageTextSpec(BaseModel):
    asset_id: str | None = None
    position: ImagePosition = "left"
    title: str | None = None
    body: str
    image_label: str | None = None


class ImageSpec(BaseModel):
    asset_id: str
    image_label: str | None = None


class CustomElement(BaseModel):
    type: CustomElementType
    text: str | None = None
    shape: CustomShapeType | None = None
    x: float
    y: float
    w: float
    h: float
    font_size: int | None = None
    fill_color: str | None = None
    line_color: str | None = None

    @model_validator(mode="after")
    def validate_custom_element(self) -> CustomElement:
        if self.type == "text" and not self.text:
            raise ValueError("Custom text elements require text")
        if self.type == "shape" and not self.shape:
            raise ValueError("Custom shape elements require a shape type")
        return self


class SlideSpec(BaseModel):
    type: SlideType
    title: str | None = None
    subtitle: str | None = None
    body: str | None = None
    layout: LayoutSpec = Field(default_factory=LayoutSpec)
    design: DesignSpec = Field(default_factory=DesignSpec)
    columns: list[ColumnContent] = Field(default_factory=list)
    left: ComparisonSide | None = None
    right: ComparisonSide | None = None
    items: list[TimelineItem] = Field(default_factory=list)
    metrics: list[KpiMetric] = Field(default_factory=list)
    quote: str | None = None
    attribution: str | None = None
    image_text: ImageTextSpec | None = None
    image: ImageSpec | None = None
    elements: list[CustomElement] = Field(default_factory=list)

    def _validate_required_title(self, slide_label: str) -> None:
        if not self.title:
            raise ValueError(f"{slide_label} slides require a title")

    def _validate_columns_slide(self) -> None:
        declared_columns = self.layout.columns or len(self.columns)
        if declared_columns not in {2, 3, 4}:
            raise ValueError("Columns slides require 2, 3, or 4 columns")
        if len(self.columns) != declared_columns:
            raise ValueError("Columns slide content count must match layout.columns")

    def _validate_comparison_slide(self) -> None:
        if self.left is None or self.right is None:
            raise ValueError("Comparison slides require left and right sections")

    def _validate_timeline_slide(self) -> None:
        if len(self.items) < 2:
            raise ValueError("Timeline slides require at least two items")

    def _validate_kpi_grid_slide(self) -> None:
        if len(self.metrics) < 2:
            raise ValueError("KPI grid slides require at least two metrics")

    def _validate_quote_slide(self) -> None:
        if not self.quote:
            raise ValueError("Quote slides require quote text")

    def _validate_image_text_slide(self) -> None:
        if self.image_text is None:
            raise ValueError("Image text slides require image_text content")

    def _validate_image_slide(self) -> None:
        if self.image is None:
            raise ValueError("Image slides require image content")

    def _validate_custom_slide(self) -> None:
        if not self.elements:
            raise ValueError("Custom slides require at least one element")

    @model_validator(mode="after")
    def validate_slide(self) -> SlideSpec:
        slide_type = self.type
        if slide_type == "title":
            self._validate_required_title("Title")
        elif slide_type == "section":
            self._validate_required_title("Section")
        elif slide_type == "columns":
            self._validate_columns_slide()
        elif slide_type == "comparison":
            self._validate_comparison_slide()
        elif slide_type == "timeline":
            self._validate_timeline_slide()
        elif slide_type == "kpi_grid":
            self._validate_kpi_grid_slide()
        elif slide_type == "quote":
            self._validate_quote_slide()
        elif slide_type == "image_text":
            self._validate_image_text_slide()
        elif slide_type == "image":
            self._validate_image_slide()
        elif slide_type == "custom":
            self._validate_custom_slide()
        return self


class DeckSpec(BaseModel):
    deck: DeckConfig = Field(default_factory=DeckConfig)
    slides: list[SlideSpec]

    @model_validator(mode="after")
    def validate_deck(self) -> DeckSpec:
        if not self.slides:
            raise ValueError("Deck must contain at least one slide")
        return self
