"""Pydantic v2 schemas for the areas + layout domain.

Request models tolerate v1's form-era looseness on purpose (binding port
semantics): dims accept blank strings (-> 0.0), parent_id accepts '' (-> None),
sketch_rotation accepts strings and snaps to 45-degree steps. The parsing
itself lives in services/areas.py — schemas only admit the loose types.
"""
from __future__ import annotations

from datetime import datetime
from typing import Any, Optional, Union

from pydantic import BaseModel, Field

# Loose input types (parsed by the service, v1 semantics).
DimIn = Union[float, int, str, None]
ParentIdIn = Union[int, str, None]
RotationIn = Union[int, str, None]


# ---------------------------------------------------------------------------
# Shared fragments
# ---------------------------------------------------------------------------

class SpeciesCount(BaseModel):
    name: str
    count: int


class SketchGeometryOut(BaseModel):
    """Output of sketch_geometry() — SVG viewBox + centered rect + rotation."""
    vb_w: float
    vb_h: float
    cx: float
    cy: float
    rect_x: float
    rect_y: float
    rect_w: float
    rect_h: float
    pad: float
    rotation: int


# ---------------------------------------------------------------------------
# Areas — responses
# ---------------------------------------------------------------------------

class AreaOut(BaseModel):
    id: int
    name: str
    notes: Optional[str] = ""
    parent_id: Optional[int] = None
    sort_order: Optional[int] = None
    short_name: str = ""
    length_ft: Optional[float] = 0
    width_ft: Optional[float] = 0
    garden_area_sqft: Optional[float] = 0
    structure_features: Optional[str] = ""
    sunlight: Optional[str] = ""
    soil_environment: Optional[str] = ""
    featured_image_path: Optional[str] = ""
    sketch_rotation: int = 0
    parent_pos_x: Optional[float] = None
    parent_pos_y: Optional[float] = None
    is_archived: bool = False
    created_at: Optional[datetime] = None
    dims: str = ""


class AreaTreeNode(BaseModel):
    id: int
    name: str
    short_name: str = ""
    parent_id: Optional[int] = None
    sort_order: Optional[int] = None
    length_ft: Optional[float] = 0
    width_ft: Optional[float] = 0
    garden_area_sqft: Optional[float] = 0
    sketch_rotation: int = 0
    featured_image_path: str = ""
    is_archived: bool = False
    dims: str = ""
    child_count: int = 0
    plant_count: int = 0
    summary: list[SpeciesCount] = Field(default_factory=list)
    children: list["AreaTreeNode"] = Field(default_factory=list)


class AreaOption(BaseModel):
    """One flattened-picker option (v1 flattened_area_options)."""
    id: int
    path: str
    parent: str
    name: str
    compact: str


class PickerArea(BaseModel):
    """One node of the compact drill-down picker (v1 area_tree_payload)."""
    id: int
    name: str
    short_name: str = ""
    parent_id: Optional[int] = None
    children: list[int] = Field(default_factory=list)
    path_short: str = ""


class AreaPicker(BaseModel):
    roots: list[int]
    areas: dict[int, PickerArea]


class AreasIndexOut(BaseModel):
    tree: list[AreaTreeNode]
    options: list[AreaOption]
    picker: AreaPicker
    status_filter: str = "planted"


class Breadcrumb(BaseModel):
    id: int
    name: str
    short_name: str = ""


class AreaChildSummary(BaseModel):
    id: int
    name: str
    dims: str = ""
    child_count: int = 0
    plant_count: int = 0
    summary: list[SpeciesCount] = Field(default_factory=list)
    featured_image_path: str = ""


class PlantSummaryRow(BaseModel):
    species: str
    variety: str
    qty: int


class AreaPlantSummary(BaseModel):
    total_qty: int
    species_count: int
    variety_count: int
    planting_count: int
    plant_list: list[PlantSummaryRow] = Field(default_factory=list)


class AreaPlantRow(BaseModel):
    """One direct plant_group row on the detail page (planting-cluster sorted)."""
    id: int
    planting_id: Optional[int] = None
    planting_name: Optional[str] = None
    planting_year: Optional[int] = None
    species_id: Optional[int] = None
    species_name: Optional[str] = None
    variety_id: Optional[int] = None
    variety_name: Optional[str] = None
    quantity: str = ""
    status: str = "planted"
    notes: str = ""
    featured_image_path: str = ""


class ResolvedPosition(BaseModel):
    """One rendered sketch icon (v1 tuple (x, y, plants_in_icon, sqft_per_icon))."""
    x: float
    y: float
    plant_count: int
    sqft: float


class SketchPlant(BaseModel):
    plant_id: int
    species_id: Optional[int] = None
    species_name: str = ""
    variety_id: Optional[int] = None
    variety_name: str = ""
    status: str = "planted"
    quantity: str = ""
    color: str = ""
    positions_resolved: list[ResolvedPosition] = Field(default_factory=list)


class SketchChild(BaseModel):
    id: int
    name: str
    short_name: str = ""
    length_ft: Optional[float] = 0
    width_ft: Optional[float] = 0
    sketch_rotation: int = 0
    resolved_x: float
    resolved_y: float


class AreaSketch(BaseModel):
    geometry: Optional[SketchGeometryOut] = None
    plants: list[SketchPlant] = Field(default_factory=list)
    children: list[SketchChild] = Field(default_factory=list)
    child_label_size_ft: float = 1.5


class AreaDetailOut(BaseModel):
    area: AreaOut
    breadcrumbs: list[Breadcrumb]
    children: list[AreaChildSummary]
    current_dims: str = ""
    current_total: int = 0
    current_summary: list[SpeciesCount] = Field(default_factory=list)
    summary: AreaPlantSummary
    plants: list[AreaPlantRow] = Field(default_factory=list)
    sketch: AreaSketch
    status_filter: str = "planted"
    show_archived: bool = False


# ---------------------------------------------------------------------------
# Areas — requests
# ---------------------------------------------------------------------------

class AreaCreate(BaseModel):
    name: str
    notes: str = ""
    parent_id: ParentIdIn = None
    short_name: str = ""
    length_ft: DimIn = ""
    width_ft: DimIn = ""
    garden_area_sqft: DimIn = ""
    structure_features: str = ""
    sunlight: str = ""
    soil_environment: str = ""
    sketch_rotation: RotationIn = 0


class AreaUpdate(BaseModel):
    """PATCH body — only fields present in the request are applied
    (the router passes model_dump(exclude_unset=True) to the service)."""
    name: Optional[str] = None
    notes: Optional[str] = None
    parent_id: ParentIdIn = None
    short_name: Optional[str] = None
    length_ft: DimIn = None
    width_ft: DimIn = None
    garden_area_sqft: DimIn = None
    structure_features: Optional[str] = None
    sunlight: Optional[str] = None
    soil_environment: Optional[str] = None
    sketch_rotation: RotationIn = None


class ReorderRequest(BaseModel):
    """v1 POST /areas/reorder body."""
    area_id: int
    parent_id: ParentIdIn = None
    new_index: int


class ParentPositionRequest(BaseModel):
    """v1 POST /api/areas/{id}/parent-position body: center point in feet,
    parent natural-orientation rect-local."""
    x: float
    y: float


class PositionsSaveRequest(BaseModel):
    """v1 POST /api/plants/{id}/positions body. Entries are loosely typed on
    purpose: v1 silently skipped non-dict / unparseable items, and the
    service preserves that."""
    positions: list[Any]


# ---------------------------------------------------------------------------
# Simple mutation acks
# ---------------------------------------------------------------------------

class OkOut(BaseModel):
    ok: bool = True


class AreaDeleteOut(BaseModel):
    ok: bool = True
    id: int
    parent_id: Optional[int] = None


class ParentPositionOut(BaseModel):
    ok: bool = True
    id: int
    x: float
    y: float


class PositionsSaveOut(BaseModel):
    ok: bool = True
    id: int
    saved: int


# ---------------------------------------------------------------------------
# Layout mode
# ---------------------------------------------------------------------------

class LayoutArea(BaseModel):
    id: int
    name: str
    short_name: str = ""
    length_ft: float
    width_ft: float
    sketch_rotation: int = 0


class PlacedIcon(BaseModel):
    """One canvas icon (a slice of a plant_group, up to ppu plants)."""
    plant_id: int
    icon_index: int
    x: float
    y: float
    side: float
    sqft: float
    plant_count: int
    species_name: str = ""
    variety_name: str = ""
    species_id: Optional[int] = None
    variety_id: Optional[int] = None
    status: str = "planted"
    color: str = ""


class LayoutTile(BaseModel):
    """One tray tile (inventory of this area, or a global pool group).
    ``count`` is in PLANTS (display); ``inv_plants_each`` is the FIFO queue
    of per-icon plant counts."""
    plant_id: int
    species_name: str = "?"
    variety_name: str = ""
    species_id: Optional[int] = None
    variety_id: Optional[int] = None
    status: str = "planted"
    source_area_name: str = ""
    qty_total: int
    ppu: int
    sqft: float
    side: float
    count: int
    inv_plants_each: list[int] = Field(default_factory=list)
    color: str = ""


class LayoutOut(BaseModel):
    area: LayoutArea
    L: float
    W: float
    compass_rot: int
    g: SketchGeometryOut
    placed_icons: list[PlacedIcon] = Field(default_factory=list)
    inventory_tiles: list[LayoutTile] = Field(default_factory=list)
    unplanted_pool_tiles: list[LayoutTile] = Field(default_factory=list)
    idea_pool_tiles: list[LayoutTile] = Field(default_factory=list)
