"""Pydantic schemas for the plantings + plants routers.

These are the OpenAPI source of the generated TS client, so field names are
stable API contract. ``quantity`` is the carried v1 Text wart: inputs accept
int or str and normalize to a trimmed string; outputs return the raw stored
string.
"""
from __future__ import annotations

from datetime import datetime

from pydantic import BaseModel, field_validator


def _to_str(value):
    if value is None:
        return None
    return str(value).strip()


# ---------------------------------------------------------------------------
# Inputs
# ---------------------------------------------------------------------------

class Position(BaseModel):
    x: float
    y: float


class PlantRowIn(BaseModel):
    """One inline plant row on planting create/update (the JSON replacement
    for v1's plant_qty_N/plant_species_N/... form fields). ``id`` present =
    update that plant; absent = insert. On update, existing plants missing
    from the submitted array are DELETED (v1 seen_plant_ids semantics)."""

    id: int | None = None
    quantity: str | None = None
    species_id: int | None = None
    variety_id: int | None = None
    area_id: int | None = None
    source: str | None = None
    notes: str | None = None

    @field_validator("quantity", mode="before")
    @classmethod
    def _quantity_to_str(cls, value):
        return _to_str(value)


class PlantingCreate(BaseModel):
    name: str
    year_id: int | None = None
    new_year: int | None = None  # year VALUE (e.g. 2026); wins over year_id
    source: str = ""
    notes: str = ""
    status: str | None = None
    plants: list[PlantRowIn] = []


class PlantingUpdate(BaseModel):
    """PATCH body — only provided fields change. ``plants`` (when provided)
    is the FULL row set; missing existing plants are deleted."""

    name: str | None = None
    year_id: int | None = None
    new_year: int | None = None
    source: str | None = None
    notes: str | None = None
    status: str | None = None
    plants: list[PlantRowIn] | None = None


class PoolPlantCreate(BaseModel):
    """POST /api/plants — create straight into the unplanted/idea pool
    (no planting, no area — that's the point)."""

    status: str = "unplanted"
    species_id: int | None = None
    variety_id: int | None = None
    quantity: str | None = "1"
    source: str = ""
    notes: str = ""

    @field_validator("quantity", mode="before")
    @classmethod
    def _quantity_to_str(cls, value):
        return _to_str(value)


class PoolStatusRequest(BaseModel):
    status: str  # 'idea' | 'unplanted'


class MarkPlantedRequest(BaseModel):
    """Existing planting (planting_id) OR new planting (planting_name +
    optional year_id)."""

    planting_id: int | None = None
    planting_name: str | None = None
    year_id: int | None = None


class PlantUpdate(BaseModel):
    """PATCH body — only provided fields change. quantity parsing to an int
    <= 0 flips the group to status='removed' and detaches it from its area."""

    quantity: str | None = None
    species_id: int | None = None
    variety_id: int | None = None
    area_id: int | None = None
    source: str | None = None
    notes: str | None = None

    @field_validator("quantity", mode="before")
    @classmethod
    def _quantity_to_str(cls, value):
        return _to_str(value)


class AttachRequest(BaseModel):
    area_id: int
    position: Position | None = None  # required by the service (v1 400)
    icon_plants: int = 1


class MoveRequest(BaseModel):
    target_area_id: int
    move_qty: int


# ---------------------------------------------------------------------------
# Outputs
# ---------------------------------------------------------------------------

class PlantOut(BaseModel):
    id: int
    planting_id: int | None = None
    species_id: int | None = None
    variety_id: int | None = None
    area_id: int | None = None
    quantity: str | None = None
    source: str | None = None
    status: str | None = None
    notes: str | None = None
    created_at: datetime | None = None
    featured_image_path: str | None = None
    positions: str | None = None  # raw Text wart: '' or a JSON list
    is_archived: bool = False
    # v2 planner columns (read-only here; planner endpoints come later)
    flavor_rating: int | None = None
    vigor_rating: int | None = None
    would_plant_again: int | None = None
    outcome_summary: str | None = None
    # joined display fields
    species_name: str | None = None
    variety_name: str | None = None
    area_name: str | None = None
    planting_name: str | None = None
    year_value: int | None = None
    plants_per_unit: int | None = None  # pools only: COALESCE(variety, species, 1)
    display_name: str


class PlantingOut(BaseModel):
    id: int
    name: str
    year_id: int | None = None
    year_value: int | None = None
    source: str | None = None
    notes: str | None = None
    status: str
    created_at: datetime | None = None
    featured_image_path: str | None = None
    is_archived: bool = False
    group_count: int | None = None
    plant_count: int | None = None  # total quantity (non-numeric counts as 1)


class PlantingDetailOut(PlantingOut):
    plants: list[PlantOut] = []
    total_qty: int = 0


class YearGroupOut(BaseModel):
    year: int
    plantings: list[PlantingOut]


class PlantingListOut(BaseModel):
    year_groups: list[YearGroupOut]
    no_year: list[PlantingOut]
    total_plantings: int


class PlantingPickerOut(BaseModel):
    id: int
    name: str
    year_value: int | None = None


class PoolsOut(BaseModel):
    unplanted: list[PlantOut]
    ideas: list[PlantOut]
    plantings: list[PlantingPickerOut]


class AttachOut(BaseModel):
    ok: bool
    split: bool
    # mode A (same area, position appended)
    id: int | None = None
    saved: int | None = None
    # mode B (cross-area split)
    source_id: int | None = None
    target_id: int | None = None
    peeled: int | None = None
    remaining: int | None = None


class DetachOut(BaseModel):
    ok: bool
    id: int
    merged_into: int | None = None


class MoveOut(BaseModel):
    ok: bool
    id: int
    split: bool
    moved_qty: int
    target_area_id: int
    new_plant_id: int | None = None
    remaining_qty: str | None = None


class OkOut(BaseModel):
    ok: bool = True
