"""Tag + ImageTag association.

Tags carry a ``kind`` enum and (for kind='character') an optional
``fandom_id`` FK to another tag of kind='fandom'. The uniqueness key is
(name, kind, COALESCE(fandom_id, 0)) so the same name can exist across
different kinds and the same character name can exist in different fandoms.
"""

from datetime import datetime
from enum import StrEnum

from sqlalchemy import (
    Boolean,
    CheckConstraint,
    Column,
    DateTime,
    ForeignKey,
    Integer,
    String,
    Table,
    false,
    func,
)
from sqlalchemy import (
    Enum as SQLEnum,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship

from .base import Base


class TagKind(StrEnum):
    artist = "artist"
    character = "character"
    fandom = "fandom"
    general = "general"
    series = "series"
    archive = "archive"
    post = "post"
    # `meta` and `rating` retired by operator 2026-05-26 (alembic 0023).
    # `artist` retired in FC-2d-vii-c — artists are first-class entities
    # via Artist/Source rows now, not tags — but the enum value stays
    # to keep historic tag rows queryable.


# The seeded system tags (migration 0075). Two behavior groups (#1464):
#   CHROME (banner): clusters on UI chrome, not content → HIDDEN from the default
#     gallery + from similarity; auto-applied via the sweep's chrome mode.
#   PROCESS (wip, editor screenshot): real-but-unfinished art / program screenshots
#     → SHOWN in the gallery (operator 2026-07-12), but excluded from the Explore
#     rabbit-hole; auto-applied via the sweep's process mode (provisional source,
#     ring-loud review guard).
# ALL three are excluded from OTHER concepts' head/CCIP training (training-hygiene,
# keyed on is_system); a system tag's OWN head trains on them — that's what makes
# auto-flagging work.
SYSTEM_TAG_NAMES = ("wip", "banner", "editor screenshot")
CHROME_SYSTEM_TAGS = ("banner",)
PROCESS_SYSTEM_TAGS = ("wip", "editor screenshot")
WIP_SYSTEM_TAG = "wip"

image_tag = Table(
    "image_tag",
    Base.metadata,
    Column(
        "image_record_id",
        ForeignKey("image_record.id", ondelete="CASCADE"),
        primary_key=True,
    ),
    Column("tag_id", ForeignKey("tag.id", ondelete="CASCADE"), primary_key=True),
    Column("source", String(32), nullable=False, default="manual"),
    Column("created_at", DateTime(timezone=True), nullable=False, server_default=func.now()),
)


class Tag(Base):
    __tablename__ = "tag"
    __table_args__ = (
        CheckConstraint(
            "(fandom_id IS NULL) OR (kind = 'character')",
            name="ck_tag_fandom_requires_character",
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    kind: Mapped[TagKind] = mapped_column(
        SQLEnum(TagKind, name="tag_kind", values_callable=lambda e: [m.value for m in e]),
        nullable=False,
        default=TagKind.general,
    )
    fandom_id: Mapped[int | None] = mapped_column(
        ForeignKey("tag.id", ondelete="SET NULL"), nullable=True, index=True
    )
    # System tags ship with FC (wip / banner / editor screenshot, seeded in
    # migration 0075) and drive the training-hygiene exclusions: images
    # carrying one are excluded from OTHER concepts' head training and from
    # CCIP identity references. The mechanism keys on these exact rows, so
    # they're protected from rename/merge-away/re-fandom in TagService.
    is_system: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=False, server_default=false()
    )

    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )

    images = relationship("ImageRecord", secondary=image_tag, backref="tags")
    fandom = relationship("Tag", remote_side="Tag.id", foreign_keys=[fandom_id])
