"""API response shapes for the hub pages (airports now; brands and categories follow).

Kept apart from `schemas.py` so the hub work never edits the catalogue's shapes
while another stream owns them. The frontend's TypeScript client is generated
from these like every other response model.
"""

from datetime import datetime

from pydantic import BaseModel

from app.models.editorial import ArticleOut
from app.models.schemas import CategoryCount, ProductSummary


class AirportShop(BaseModel):
    """One retailer's storefront at the airport (an airport can hold several)."""

    code: str
    retailer_name: str
    products: int = 0
    last_collected_at: datetime | None = None


class AirportTerminal(BaseModel):
    """One terminal's duty free, written: where the main store is, and the
    specialty boutiques around it. `specialty` is absent where a terminal has
    none worth naming, not where nobody has looked yet."""

    name: str
    airlines: str | None = None
    duty_free: str
    specialty: str | None = None


class AirportService(BaseModel):
    """Something the shops there offer that changes how you shop: reserve and
    collect, a pre-order desk, collection on arrival."""

    title: str
    body: str


class AirportGuide(BaseModel):
    """The written guide to an airport's duty free (`services.airport_guides`):
    who operates it, where the shops are by terminal, and what else is worth
    knowing before the gate. Every field is optional but the terminals, because
    a half-written guide is still worth showing; what is missing shows in the
    demo's placeholder view."""

    operator: str | None = None
    overview: str | None = None
    #: One line on where the shops sit relative to security.
    access: str | None = None
    terminals: list[AirportTerminal] = []
    #: Specialty shops for an airport whose guide does not split by terminal.
    specialty: str | None = None
    services: list[AirportService] = []
    #: The airport's own terminal map, where it marks the shops.
    map_url: str | None = None
    map_label: str | None = None
    #: Only where they bear on shopping (a store that opens later than the pier).
    hours: str | None = None
    #: When a person last checked these locations, shown with the guide, because
    #: retailers move between gates.
    checked: str | None = None


class AirportSummary(BaseModel):
    """An airport with a page: the identity, the words in its URL, and its size."""

    iata: str
    path: str
    name: str
    city: str | None = None
    country: str | None = None
    currency: str
    products: int = 0
    last_collected_at: datetime | None = None


class AirportDetail(AirportSummary):
    """Everything the airport page shows. Every number here is counted in the
    database; the page never types one."""

    # Plain `= []` defaults on purpose: they reach the OpenAPI schema as
    # defaults, so the generated TypeScript types are required, not optional.
    shops: list[AirportShop] = []
    #: Products here that are also priced at another airport we track.
    comparable: int = 0
    #: Comparable products for which this airport is the cheapest place we know.
    cheapest_here: int = 0
    exclusives: int = 0
    categories: list[CategoryCount] = []
    #: The biggest savings for a shopper standing at this airport, best first.
    savings: list[ProductSummary] = []
    #: A sample of the travel-retail exclusives sold here.
    exclusive_items: list[ProductSummary] = []
    # The product list, one page of it, with the filters that shaped it.
    category: str | None = None
    sort: str = "featured"
    total: int = 0
    limit: int = 24
    offset: int = 0
    items: list[ProductSummary] = []
    #: The Professor's published write-up for this airport, when there is one
    #: (`editorial.airport_writeup`); seeded with the page so the SPA draws it
    #: without a second request and the server body shows the same text.
    writeup: ArticleOut | None = None
    #: The written guide to the airport's duty free, where one exists
    #: (`services.airport_guides`); seeded with the page like the write-up.
    guide: AirportGuide | None = None


class BrandAirport(BaseModel):
    """One airport that stocks the brand: how much of it, and how often it is
    the cheapest of our airports for a bottle sold at two or more."""

    iata: str
    path: str
    name: str
    products: int = 0
    cheapest_for: int = 0
    last_collected_at: datetime | None = None


class BrandSummary(BaseModel):
    """A brand with a page: its slug (the brands table's, never a folded
    string), the spelling shown, and its size."""

    slug: str
    path: str
    name: str
    products: int = 0
    last_collected_at: datetime | None = None
    #: The family most of its products sit in (taxonomy.majority_family: liquor,
    #: beauty, ...), so the header's department panels list their own houses.
    family: str | None = None


class BrandDetail(BrandSummary):
    """Everything the brand page shows; every number counted in the database."""

    airports: list[BrandAirport] = []
    #: Products of the brand priced at two or more of our airports.
    comparable: int = 0
    exclusives: int = 0
    categories: list[CategoryCount] = []
    # The product list, one page of it, with the filters that shaped it.
    category: str | None = None
    sort: str = "featured"
    total: int = 0
    limit: int = 24
    offset: int = 0
    items: list[ProductSummary] = []


class DatasetFacts(BaseModel):
    """What the public data page and the Dataset markup state, every figure
    counted over visible airports at request time."""

    observations: int = 0
    products: int = 0
    airports: int = 0
    retailers: int = 0
    currencies: int = 0
    with_barcode: int = 0
    awards: int = 0
    first_observed_at: datetime | None = None
    last_observed_at: datetime | None = None
    airport_list: list[AirportSummary] = []
    categories: list[CategoryCount] = []
