"""Courts, balls, and touches — the board and the rally.

- courts are declarative (config jsonb decides columns/ball types/aging); ALL
  courts share the same machinery. Adding a court type is data, not code.
- balls is ONE table for every court (no per-court-type nullable FKs); config
  decides which columns render.
- touches is append-only; a ball's rally is `... ORDER BY seq`. rep() is a
  read-time projection over this raw history (§4.3).
"""
import datetime

from sqlalchemy import ForeignKey, Integer, String, Text, TIMESTAMP, text
from sqlalchemy.dialects.postgresql import ARRAY, JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column

from .base import Base, created_at, uuid_pk


class Court(Base):
    __tablename__ = "courts"

    id: Mapped[str] = uuid_pk()
    match_id: Mapped[str] = mapped_column(
        UUID(as_uuid=True), ForeignKey("matches.id", ondelete="CASCADE"), nullable=False
    )
    # e.g. "pages" | "features" | "issues" | "warmup"; drives the config.
    config_key: Mapped[str] = mapped_column(String, nullable=False)
    name: Mapped[str] = mapped_column(String, nullable=False)
    position: Mapped[int] = mapped_column(
        Integer, nullable=False, default=0, server_default=text("0")
    )
    config: Mapped[dict] = mapped_column(
        JSONB, nullable=False, server_default=text("'{}'::jsonb")
    )


class Ball(Base):
    __tablename__ = "balls"

    id: Mapped[str] = uuid_pk()
    court_id: Mapped[str] = mapped_column(
        UUID(as_uuid=True), ForeignKey("courts.id", ondelete="CASCADE"), nullable=False
    )
    title: Mapped[str] = mapped_column(String, nullable=False, server_default=text("''"))
    body: Mapped[str | None] = mapped_column(Text)  # markdown
    status: Mapped[str] = mapped_column(String, nullable=False, server_default=text("'open'"))
    review_state: Mapped[str | None] = mapped_column(String)
    category: Mapped[str | None] = mapped_column(String)
    tags: Mapped[list[str]] = mapped_column(
        ARRAY(String), nullable=False, server_default=text("'{}'::text[]")
    )
    url_staging: Mapped[str | None] = mapped_column(String)
    url_live: Mapped[str | None] = mapped_column(String)
    # Who must act (multi-assign). prev_action_to = rally memory for restore.
    action_to: Mapped[list[str]] = mapped_column(
        ARRAY(UUID(as_uuid=True)), nullable=False, server_default=text("'{}'::uuid[]")
    )
    prev_action_to: Mapped[list[str]] = mapped_column(
        ARRAY(UUID(as_uuid=True)), nullable=False, server_default=text("'{}'::uuid[]")
    )
    author_id: Mapped[str | None] = mapped_column(
        UUID(as_uuid=True), ForeignKey("players.id", ondelete="SET NULL")
    )
    resolved_at: Mapped[datetime.datetime | None] = mapped_column(TIMESTAMP(timezone=True))
    position: Mapped[int] = mapped_column(
        Integer, nullable=False, default=0, server_default=text("0")
    )
    created_at: Mapped[datetime.datetime] = created_at()


class Touch(Base):
    __tablename__ = "touches"

    id: Mapped[str] = uuid_pk()
    ball_id: Mapped[str] = mapped_column(
        UUID(as_uuid=True), ForeignKey("balls.id", ondelete="CASCADE"), nullable=False
    )
    seq: Mapped[int] = mapped_column(Integer, nullable=False)  # per-ball 1..n
    # serve | pass | spike | resolve | reopen | auto
    kind: Mapped[str] = mapped_column(String, nullable=False)
    from_player: Mapped[str | None] = mapped_column(
        UUID(as_uuid=True), ForeignKey("players.id", ondelete="SET NULL")
    )
    to_players: Mapped[list[str]] = mapped_column(
        ARRAY(UUID(as_uuid=True)), nullable=False, server_default=text("'{}'::uuid[]")
    )
    # The node context this touch happened in — the visibility scope for §4.3.
    acting_node_id: Mapped[str | None] = mapped_column(
        UUID(as_uuid=True), ForeignKey("team_nodes.id", ondelete="SET NULL")
    )
    via_rule_id: Mapped[str | None] = mapped_column(UUID(as_uuid=True))  # autopass origin
    created_at: Mapped[datetime.datetime] = created_at()
