"""Declarative base + conventions for all v2 models.

Conventions (binding for every model module):

- Primary keys: ``BigIntegerIdentity`` — BIGINT GENERATED **BY DEFAULT** AS
  IDENTITY, so migrate_v1.py can insert original v1 ids verbatim and call
  setval() afterwards.
- Timestamps: TIMESTAMPTZ (``DateTime(timezone=True)``). v1 TEXT ISO strings
  are parsed by the migration, not the models.
- v1 0/1 INTEGER flags become Boolean.
- v1 TEXT columns that hold data warts (plants.quantity, photo_paths,
  positions) STAY Text — "don't improve a mature schema during a port".
- No PG native enums. TEXT + CheckConstraint only where v1 code enforced the
  vocabulary (documented per column).
- Every carried entity table gains ``garden_id`` (deviation D1) via
  GardenScopedMixin.
- Naming convention below keeps constraint names deterministic so Alembic
  diffs stay clean.
"""
from __future__ import annotations

from datetime import datetime

from sqlalchemy import BigInteger, DateTime, ForeignKey, Identity, MetaData
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

NAMING_CONVENTION = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}


class Base(DeclarativeBase):
    metadata = MetaData(naming_convention=NAMING_CONVENTION)


def pk_column() -> Mapped[int]:
    """BIGINT identity PK that accepts explicit ids during migration."""
    return mapped_column(
        BigInteger, Identity(always=False), primary_key=True
    )


def garden_fk() -> Mapped[int]:
    """The D1 multi-garden scope column. NOT NULL; all rows = 1 at migration."""
    return mapped_column(
        BigInteger, ForeignKey("gardens.id"), nullable=False, index=True
    )


def created_at_column(nullable: bool = False) -> Mapped[datetime]:
    return mapped_column(DateTime(timezone=True), nullable=nullable)
