"""Pydantic validation models for bitable view config substructures (U5).

The ``View.config`` JSONB column holds arbitrary JSON. To keep grouping +
conditional formatting well-formed, the route layer validates the
``group_by`` and ``conditional_formatting`` sub-keys against the schemas
defined here before persisting. Other config keys (filters / sort /
hidden_fields) are left untouched — this validation is additive.

ponytail: the validator is a pure function over a dict; no DB access, no
side effects. The route converts ``ValidationError`` to HTTP 422.
"""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, ValidationError

# 7 operators supported by the conditional format rule matcher in
# groupingRulesUtils.ts. Kept in sync via test_conditional_formatting.py.
ConditionalOperator = Literal[
    "equals",
    "not-equals",
    "contains",
    "is-empty",
    "greater-than",
    "less-than",
    "between",
]

# 8 color keys mapped to --bitable-cf-<key>-{bg,fg} tokens in
# bitable-tokens.css. Adding a new color requires a token + a Literal entry.
ColorKey = Literal[
    "red",
    "orange",
    "yellow",
    "green",
    "blue",
    "purple",
    "gray",
    "neutral",
]

GroupDirection = Literal["asc", "desc"]

# U5 spec: max 3 group_by levels. Matches Feishu / Twenty UX.
MAX_GROUP_BY_FIELDS = 3


class GroupByItem(BaseModel):
    """A single grouping level — field + sort direction."""

    model_config = ConfigDict(extra="forbid")

    field_id: str = Field(min_length=1, max_length=64)
    direction: GroupDirection = "asc"


class ConditionalFormatRule(BaseModel):
    """A single conditional formatting rule.

    ``bold`` defaults to True for WCAG 1.4.1 (color-blind accessibility) —
    color alone must not be the only visual cue, so we also bold the text
    unless the user explicitly disables it.
    """

    model_config = ConfigDict(extra="forbid")

    field_id: str = Field(min_length=1, max_length=64)
    operator: ConditionalOperator
    # value is required even for is-empty (the matcher ignores it, but the
    # schema keeps the field uniform — UI sends "" for is-empty).
    value: str = Field(default="", max_length=512)
    color_key: ColorKey
    bold: bool = True
    enabled: bool = True


class ViewConfigSchema(BaseModel):
    """Validated sub-structure of View.config for U5.

    Only the ``group_by`` and ``conditional_formatting`` keys are validated;
    other config keys (filters / sort / hidden_fields) pass through unchanged.
    """

    model_config = ConfigDict(extra="ignore")

    group_by: list[GroupByItem] = Field(default_factory=list, max_length=MAX_GROUP_BY_FIELDS)
    conditional_formatting: list[ConditionalFormatRule] = Field(default_factory=list)


class ViewConfigValidationError(ValueError):
    """Raised when View.config fails U5 schema validation.

    Subclass of ValueError so existing ``except ValueError`` handlers in the
    route layer pick it up. Carries the structured error detail for the 422
    response body.
    """

    def __init__(self, message: str, errors: list[dict[str, object]]) -> None:
        super().__init__(message)
        self.errors = errors


def validate_view_config(config: dict[str, object] | None) -> None:
    """Validate the U5 sub-keys of a View.config dict in place.

    Raises ``ViewConfigValidationError`` if ``group_by`` or
    ``conditional_formatting`` are present but malformed. Absent keys are
    treated as empty lists (no validation needed).

    Other config keys (filters / sort / hidden_fields) are NOT validated
    here — they have their own loose shape and remain unchanged.
    """
    if not config:
        return
    if "group_by" not in config and "conditional_formatting" not in config:
        return
    try:
        ViewConfigSchema.model_validate(
            {
                "group_by": config.get("group_by", []),
                "conditional_formatting": config.get("conditional_formatting", []),
            }
        )
    except ValidationError as exc:
        raise ViewConfigValidationError(
            "Invalid view config (group_by / conditional_formatting)",
            exc.errors(),
        ) from exc
