"""Small, tenant-safe contract for grouping existing pages or dataset items.

Collections own membership and presentation metadata only. They do not create
another content or record model, and they never resolve data outside the
current project. Dataset resolution remains the responsibility of the caller.
"""

from __future__ import annotations

import re
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator

from src.domain.datasets.contract import DatasetContract

COLLECTION_CONTRACT_VERSION = 1
_KEY_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
_ROUTE_RE = re.compile(r"^/[A-Za-z0-9][A-Za-z0-9_./{}-]*$")

CollectionPresentation = Literal[
    "index",
    "grid",
    "list",
    "nav",
    "featured",
    "breadcrumbs",
    "pagination",
]


class CollectionDatasetRef(BaseModel):
    """Reference to an existing Dataset, optionally inlined for codegen."""

    model_config = ConfigDict(extra="forbid")
    dataset_id: str = Field(min_length=1, max_length=128)
    dataset: DatasetContract | None = None

    @model_validator(mode="after")
    def inline_dataset_must_match_id(self) -> CollectionDatasetRef:
        if self.dataset is not None and self.dataset.id != self.dataset_id:
            raise ValueError("dataset.id must match dataset_id")
        return self


class CollectionContract(BaseModel):
    """Routable grouping over existing pages or dataset-backed items."""

    model_config = ConfigDict(extra="forbid")
    schema_version: Literal[1] = 1
    key: str = Field(min_length=1, max_length=64)
    title: str = Field(min_length=1, max_length=160)
    slug: str = Field(min_length=1, max_length=160)
    description: str | None = Field(default=None, max_length=2000)
    source: CollectionDatasetRef
    parent_key: str | None = Field(default=None, max_length=64)
    route: str | None = Field(default=None, max_length=240)
    item_route: str | None = Field(default=None, max_length=240)
    filter: dict[str, Any] = Field(default_factory=dict)
    sort: list[str] = Field(default_factory=list, max_length=8)
    page_size: int | None = Field(default=None, ge=1, le=100)
    presentation: CollectionPresentation = "index"

    @field_validator("key", "parent_key")
    @classmethod
    def valid_key(cls, value: str | None) -> str | None:
        if value is not None and not _KEY_RE.fullmatch(value):
            raise ValueError("collection keys must use lowercase letters, digits, '-' or '_'")
        return value

    @field_validator("route", "item_route")
    @classmethod
    def valid_route(cls, value: str | None) -> str | None:
        if value is not None and not _ROUTE_RE.fullmatch(value):
            raise ValueError("collection routes must be project-relative paths")
        return value

    @model_validator(mode="after")
    def route_requirements(self) -> CollectionContract:
        if self.parent_key == self.key:
            raise ValueError("a collection cannot be its own parent")
        if self.presentation in {"index", "grid", "list", "featured"} and not self.route:
            raise ValueError(f"presentation {self.presentation!r} requires route")
        if self.item_route and "{item.slug}" not in self.item_route:
            raise ValueError("item_route must contain {item.slug}")
        return self


def validate_collection(payload: CollectionContract | dict[str, Any]) -> CollectionContract:
    if isinstance(payload, CollectionContract):
        return payload
    return CollectionContract.model_validate(payload)
