"""project-scoped rules

Revision ID: 0059
Revises: 0058
Create Date: 2026-06-01

Rules can now belong to either a rulebook topic (cross-project standard) or
a single project (project-scoped). Adds `rules.project_id`, makes `topic_id`
nullable, and adds a CHECK constraint enforcing exactly-one. The previous
unique constraint on (topic_id, title) still applies because PostgreSQL
treats NULL as distinct — two project-scoped rules with the same title and
NULL topic_id remain unique.
"""
from alembic import op
import sqlalchemy as sa


revision = "0059"
down_revision = "0058"
branch_labels = None
depends_on = None


def upgrade() -> None:
    op.add_column(
        "rules",
        sa.Column(
            "project_id",
            sa.BigInteger(),
            sa.ForeignKey("projects.id", ondelete="CASCADE"),
            nullable=True,
        ),
    )
    op.alter_column("rules", "topic_id", nullable=True)
    op.create_index("ix_rules_project_id", "rules", ["project_id"])
    op.create_check_constraint(
        "ck_rule_topic_xor_project",
        "rules",
        "(topic_id IS NULL) <> (project_id IS NULL)",
    )


def downgrade() -> None:
    op.drop_constraint("ck_rule_topic_xor_project", "rules", type_="check")
    op.drop_index("ix_rules_project_id", table_name="rules")
    # Any rule with NULL topic_id will block re-tightening. Operator must
    # migrate or delete project-scoped rules before downgrading.
    op.alter_column("rules", "topic_id", nullable=False)
    op.drop_column("rules", "project_id")
