"""Post — provenance anchor for one creator post (may contain many images).

`source_id` is nullable since alembic 0030 — filesystem-imported posts
with no live subscription have NULL source_id. `artist_id` is the
denormalized always-present link to the creator (added in 0030 so
artist-filter queries don't depend on the Source detour).
"""

from datetime import datetime

from sqlalchemy import (
    JSON,
    CheckConstraint,
    DateTime,
    ForeignKey,
    Integer,
    String,
    Text,
    UniqueConstraint,
    func,
)
from sqlalchemy.orm import Mapped, mapped_column

from .base import Base


class Post(Base):
    __tablename__ = "post"
    __table_args__ = (
        # Source-bound dedup. Postgres treats NULL != NULL so rows
        # with source_id IS NULL aren't deduped by this constraint;
        # the partial unique index `uq_post_artist_external_id_null_source`
        # (created in alembic 0030) covers that case via
        # (artist_id, external_post_id).
        UniqueConstraint("source_id", "external_post_id", name="uq_post_source_external_id"),
        CheckConstraint(
            "translation_override IN ('auto', 'force', 'original')",
            name="ck_post_translation_override",
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    source_id: Mapped[int | None] = mapped_column(
        ForeignKey("source.id", ondelete="SET NULL"), nullable=True, index=True
    )
    # Denormalized; always equals source.artist_id when source_id is set
    # (the importer is responsible for keeping them consistent on insert).
    # Filter queries (artist detail, artist-scoped posts feed) use this
    # directly instead of joining through Source.
    artist_id: Mapped[int] = mapped_column(
        ForeignKey("artist.id", ondelete="CASCADE"),
        nullable=False, index=True,
    )
    external_post_id: Mapped[str] = mapped_column(String(128), nullable=False)
    post_url: Mapped[str | None] = mapped_column(Text, nullable=True)
    post_title: Mapped[str | None] = mapped_column(Text, nullable=True)
    post_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)

    raw_metadata: Mapped[dict | None] = mapped_column(JSON, nullable=True)

    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    attachment_count: Mapped[int | None] = mapped_column(Integer, nullable=True)

    # -- Post-text translation (milestone 143). Filled by the translate_posts
    # sweep via the Interpreter LAN service so viewing is instant.
    # translated_source_lang is the DETECTED original language; "en" (or a
    # passthrough) means nothing to translate and the *_translated columns stay
    # NULL. engine_version keys the Interpreter cache — re-runs are ~1ms and a
    # model upgrade re-translates instead of serving stale.
    post_title_translated: Mapped[str | None] = mapped_column(Text, nullable=True)
    description_translated: Mapped[str | None] = mapped_column(Text, nullable=True)
    translated_source_lang: Mapped[str | None] = mapped_column(
        String(8), nullable=True
    )
    translation_engine_version: Mapped[str | None] = mapped_column(
        String(128), nullable=True
    )
    translated_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    # Sticky per-post override of the translation decision (milestone 155):
    # 'auto' = the acceptance gate decides; 'force' = always store Interpreter's
    # translation even below the confidence floor (rescue a skipped legit-foreign
    # title); 'original' = never translate, keep the original (kill a confidently
    # mis-flagged one the floor can't catch). The sweep reads this on every run,
    # and re-translate leaves 'original' posts alone, so the choice survives a
    # Re-translate-all.
    translation_override: Mapped[str] = mapped_column(
        String(16), nullable=False, default="auto", server_default="auto",
    )

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