"""ImagePrediction — one row per (image, tagger vocab prediction).

Replaces the image_record.tagger_predictions JSON blob (#768). Storing the
raw Camie/booru vocab name (not a tag_id) preserves the suggestion read
path's semantics: raw_name → canonical Tag resolution happens at read time
via the alias map, and accepting a prediction can CREATE the Tag. The store
floor (ml_settings.tagger_store_floor) is applied at WRITE time, so only
predictions >= the floor land here.
"""

from sqlalchemy import Float, ForeignKey, Index, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column

from .base import Base


class ImagePrediction(Base):
    __tablename__ = "image_prediction"
    __table_args__ = (
        UniqueConstraint(
            "image_record_id", "raw_name", name="image_raw_name",
        ),
        # Per-image read (suggestion build) and the "images with tag X above
        # Y" query the JSON blob never allowed.
        Index("ix_image_prediction_image", "image_record_id"),
        Index("ix_image_prediction_name_score", "raw_name", "score"),
    )

    id: Mapped[int] = mapped_column(primary_key=True)
    image_record_id: Mapped[int] = mapped_column(
        ForeignKey("image_record.id", ondelete="CASCADE"), nullable=False,
    )
    # The raw tagger vocab key (booru form) — NOT a tag_id. Resolved to a
    # canonical Tag at read time, exactly as the old JSON keys were.
    raw_name: Mapped[str] = mapped_column(String(255), nullable=False)
    category: Mapped[str] = mapped_column(String(64), nullable=False)
    score: Mapped[float] = mapped_column(Float, nullable=False)
