"""Precomputed CCIP character prototypes (#1317, milestone 138).

The live matcher (ccip.match_image) needs each character's reference figure
vectors. Building that on the request path reloaded EVERY figure CCIP vector in
the library on any change (~4s, invalidated by every character accept). These
tables make the references a PRECOMPUTED, INCREMENTAL artifact refreshed off the
request path (services.ml.character_prototypes):

- CharacterPrototype: a character's reference vectors, capped to
  MLSettings.ccip_prototype_cap so MATCH cost doesn't grow with a character's
  popularity. The async matcher only READS these.
- CcipPrototypeState: a per-character fingerprint (reference count + max region
  id) so a refresh rebuilds ONLY the characters whose references changed, and
  its updated_at lets the matcher's cache reload just the advanced characters.
"""

from datetime import datetime

from pgvector.sqlalchemy import Vector
from sqlalchemy import DateTime, ForeignKey, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column

from .base import Base
from .image_region import CCIP_DIM


class CharacterPrototype(Base):
    __tablename__ = "character_prototype"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    # The character tag these vectors identify. CASCADE: deleting the tag drops
    # its prototypes.
    tag_id: Mapped[int] = mapped_column(
        ForeignKey("tag.id", ondelete="CASCADE"), nullable=False, index=True
    )
    # A reference figure/face CCIP vector (same space as
    # ImageRegion.ccip_embedding).
    ccip_embedding: Mapped[list[float]] = mapped_column(
        Vector(CCIP_DIM), nullable=False
    )
    # Provenance: the region this vector was copied from. SET NULL so pruning a
    # region doesn't delete the prototype mid-cycle (the next refresh reconciles).
    region_id: Mapped[int | None] = mapped_column(
        ForeignKey("image_region.id", ondelete="SET NULL"), nullable=True
    )


class CcipPrototypeState(Base):
    __tablename__ = "ccip_prototype_state"

    # One row per character that currently has prototypes.
    tag_id: Mapped[int] = mapped_column(
        ForeignKey("tag.id", ondelete="CASCADE"), primary_key=True
    )
    # count(reference regions) + max(region id) at last build — the cheap
    # per-character change detector that drives incremental rebuilds.
    fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
    # Bumped when this character's prototypes are rebuilt; the matcher cache
    # reloads only characters whose updated_at advanced.
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
