from sqlalchemy import ForeignKey, Index, Integer, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column

from scribe.models import Base
from scribe.models.base import CreatedAtMixin, iso


class NoteSupersession(Base, CreatedAtMixin):
    """A newer record's claim that it has overtaken an older one.

    WHY THE RELATION POINTS FORWARD

    The note being WRITTEN declares what it supersedes. The old record cannot
    know it has been overtaken — asking it to record its own obsolescence is
    asking it to predict the future. So the claim is made by the party that has
    the knowledge, and the demotion is derived from the far end.

    WHY A TABLE RATHER THAN A COLUMN

    It is genuinely many-to-many and partial: one note may supersede parts of
    several others, and a note may be overtaken piecemeal by several later ones.
    Both directions are queried and neither is rare —
    `superseded_id` answers the ranking question ("has this been overtaken?"),
    `superseder_id` answers the record view ("what does this replace?"). An
    array column on `notes` could be indexed for one and not the other.

    WHAT IT MEANS, AND WHAT IT DOES NOT

    A claim, never a proof. Supersession DEMOTES a record in ranked retrieval;
    it does not assert the older record was wrong, and it never hides it. A note
    that accurately described how something worked in June is still accurate
    about June — it is just no longer the answer to "how does this work".

    CASCADE IS SAFE HERE BECAUSE TRASHING IS NOT A DELETE

    `trash_svc` stamps `deleted_at` (an UPDATE), so a trashed note keeps its
    claims and `restore` brings them back intact. The cascade fires only on
    `purge_trash`, where the row genuinely goes — and a supersession claim about
    a row that no longer exists is not a fact anyone can act on.
    """

    __tablename__ = "note_supersessions"

    id: Mapped[int] = mapped_column(primary_key=True)
    # The newer record, making the claim.
    superseder_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("notes.id", ondelete="CASCADE")
    )
    # The older record, demoted by it.
    superseded_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("notes.id", ondelete="CASCADE")
    )

    __table_args__ = (
        UniqueConstraint(
            "superseder_id", "superseded_id", name="uq_note_supersessions_pair"
        ),
        # Both directions indexed — see the class docstring for why neither is
        # the rare one.
        Index("ix_note_supersessions_superseder", "superseder_id"),
        Index("ix_note_supersessions_superseded", "superseded_id"),
    )

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "superseder_id": self.superseder_id,
            "superseded_id": self.superseded_id,
            "created_at": iso(self.created_at),
        }
