-- t-paliad-182 / Fristen Phase 3 Slice 1 (Step A of
-- docs/design-fristen-phase2-2026-05-15.md §3.1).
--
-- Additive only: extends paliad.deadline_rules with the unified-rule
-- columns the Phase 3 calculator + rule editor will use.
--
-- NO drops in this slice. Legacy columns (is_mandatory, is_optional,
-- condition_flag, condition_rule_id) stay live until Slice 9. Compat-
-- mode readers consume both shapes during the transition window
-- (design §3.2 "Cutover ordering").
--
-- Column-by-column rationale:
--   trigger_event_id          — event-rooted dispatch (Pipeline C unification, §2.5).
--   spawn_proceeding_type_id  — cross-proceeding spawn resolution (Q7, §2.6).
--   combine_op                — composite-rule arithmetic 'max'/'min' (R.198/R.213).
--   condition_expr            — jsonb condition grammar replacing condition_flag (Q6, §2.4).
--   priority                  — 4-way enum mandatory|recommended|optional|informational (Q3, §2.3).
--   is_court_set              — explicit replacement of the runtime heuristic (Q12).
--   lifecycle_state           — draft|published|archived for the rule editor (Q5, §4.2).
--   draft_of                  — draft self-FK pointing at the published row it replaces.
--   published_at              — promotion timestamp, NULL while draft.
--
-- FK type notes:
--   trigger_event_id is BIGINT (paliad.trigger_events.id is bigint, mig 028).
--   spawn_proceeding_type_id is INTEGER (paliad.proceeding_types.id is
--     serial = int4, mig 003).
--   draft_of is UUID (self-FK on paliad.deadline_rules.id).
-- The design doc (§2.1) calls them "int FK" loosely; the actual schemas
-- demand the precise int width, hence bigint/integer here.
--
-- Indexes:
--   FK lookups for trigger_event_id + spawn_proceeding_type_id (sparse,
--   most rules have neither — partial WHERE NOT NULL keeps the index
--   small).
--   lifecycle_state is queried by the admin /admin/rules listing's
--   default filter (state='published'); plain btree is fine, no
--   WHERE clause so 'draft' / 'archived' rows index too.
--
-- Idempotent: every ADD COLUMN uses IF NOT EXISTS. Re-applying is a
-- no-op. Tracker advances 77 → 78.

-- =============================================================================
-- 1. New columns on paliad.deadline_rules
-- =============================================================================

ALTER TABLE paliad.deadline_rules
    ADD COLUMN IF NOT EXISTS trigger_event_id          bigint,
    ADD COLUMN IF NOT EXISTS spawn_proceeding_type_id  integer,
    ADD COLUMN IF NOT EXISTS combine_op                text,
    ADD COLUMN IF NOT EXISTS condition_expr            jsonb,
    ADD COLUMN IF NOT EXISTS priority                  text NOT NULL DEFAULT 'mandatory',
    ADD COLUMN IF NOT EXISTS is_court_set              boolean NOT NULL DEFAULT false,
    ADD COLUMN IF NOT EXISTS lifecycle_state           text NOT NULL DEFAULT 'published',
    ADD COLUMN IF NOT EXISTS draft_of                  uuid,
    ADD COLUMN IF NOT EXISTS published_at              timestamptz;

COMMENT ON COLUMN paliad.deadline_rules.trigger_event_id IS
    'Optional FK to paliad.trigger_events. When non-NULL, this rule is '
    'event-rooted (Pipeline C unification, design §2.5). When NULL the '
    'rule is proceeding-rooted via proceeding_type_id. Exactly one of '
    'the two must be set after Slice 3 backfill (enforced by a CHECK '
    'constraint added in Slice 9 after legacy callers retire).';

COMMENT ON COLUMN paliad.deadline_rules.spawn_proceeding_type_id IS
    'When is_spawn=true, points at the target proceeding whose rule set '
    'the calculator follows when this rule fires (cross-proceeding '
    'spawn, design §2.6). Backfilled in Slice 7 for the 8 live spawn '
    'rules.';

COMMENT ON COLUMN paliad.deadline_rules.combine_op IS
    'NULL = single-anchor arithmetic. ''max'' / ''min'' = composite-rule '
    'arithmetic combining (duration_value, duration_unit) with '
    '(alt_duration_value, alt_duration_unit). Used by R.198 / R.213 '
    '("31d OR 20 working_days, whichever is longer / shorter").';

COMMENT ON COLUMN paliad.deadline_rules.condition_expr IS
    'jsonb gating expression replacing condition_flag (Q6, design §2.4). '
    'Grammar: {"flag": "<name>"} | {"op":"and"|"or", "args":[...]} | '
    '{"op":"not", "args":[<node>]}. NULL or {} = unconditional. '
    'Backfilled in Slice 2 from condition_flag; new code reads this, '
    'falls back to condition_flag during the transition window.';

COMMENT ON COLUMN paliad.deadline_rules.priority IS
    'Unified 4-way enum (Q3, design §2.3) replacing the is_mandatory + '
    'is_optional pair. Allowed: mandatory | recommended | optional | '
    'informational. Default ''mandatory'' on new rows; legacy rows get '
    'backfilled in Slice 2 from the (is_mandatory, is_optional) pair.';

COMMENT ON COLUMN paliad.deadline_rules.is_court_set IS
    'Replaces the runtime heuristic (primary_party=''court'' OR '
    'event_type IN (...)) with an explicit column (Q12). Default false '
    'on new rows; Slice 2 backfills from the heuristic so behaviour is '
    'unchanged at first.';

COMMENT ON COLUMN paliad.deadline_rules.lifecycle_state IS
    'Rule-editor lifecycle (Q5, design §4.2). draft = work-in-progress '
    'admin edit; published = live, calculator-visible; archived = '
    'historical (kept for audit). Default ''published'' so every '
    'existing row stays live without an UPDATE.';

COMMENT ON COLUMN paliad.deadline_rules.draft_of IS
    'When lifecycle_state=''draft'', points at the published rule this '
    'draft will replace on publish. NULL on published or archived '
    'rows. NULL also on net-new drafts (no prior published peer).';

COMMENT ON COLUMN paliad.deadline_rules.published_at IS
    'Timestamp this row entered lifecycle_state=''published''. NULL '
    'while draft, populated on publish, retained through archive. '
    'Distinct from updated_at (which moves on every edit).';

-- =============================================================================
-- 2. Foreign keys
-- =============================================================================
--
-- DEFERRABLE INITIALLY IMMEDIATE keeps normal-statement semantics
-- intact while still letting backfill migrations defer until end-of-
-- transaction if they need to (e.g. when Slice 3 inserts a rule row
-- whose trigger_event_id references a row inserted in the same tx).

ALTER TABLE paliad.deadline_rules
    ADD CONSTRAINT deadline_rules_trigger_event_id_fkey
    FOREIGN KEY (trigger_event_id)
    REFERENCES paliad.trigger_events(id)
    ON DELETE SET NULL
    DEFERRABLE INITIALLY IMMEDIATE;

ALTER TABLE paliad.deadline_rules
    ADD CONSTRAINT deadline_rules_spawn_proceeding_type_id_fkey
    FOREIGN KEY (spawn_proceeding_type_id)
    REFERENCES paliad.proceeding_types(id)
    ON DELETE SET NULL
    DEFERRABLE INITIALLY IMMEDIATE;

ALTER TABLE paliad.deadline_rules
    ADD CONSTRAINT deadline_rules_draft_of_fkey
    FOREIGN KEY (draft_of)
    REFERENCES paliad.deadline_rules(id)
    ON DELETE SET NULL
    DEFERRABLE INITIALLY IMMEDIATE;

-- =============================================================================
-- 3. CHECK constraints on enum-style columns
-- =============================================================================
--
-- combine_op: NULL (unset) or one of two values.
ALTER TABLE paliad.deadline_rules
    ADD CONSTRAINT deadline_rules_combine_op_check
    CHECK (combine_op IS NULL OR combine_op IN ('max', 'min'));

-- priority: 4-way enum.
ALTER TABLE paliad.deadline_rules
    ADD CONSTRAINT deadline_rules_priority_check
    CHECK (priority IN ('mandatory', 'recommended', 'optional', 'informational'));

-- lifecycle_state: 3-way enum.
ALTER TABLE paliad.deadline_rules
    ADD CONSTRAINT deadline_rules_lifecycle_state_check
    CHECK (lifecycle_state IN ('draft', 'published', 'archived'));

-- =============================================================================
-- 4. Indexes
-- =============================================================================

CREATE INDEX IF NOT EXISTS deadline_rules_trigger_event_id_idx
    ON paliad.deadline_rules (trigger_event_id)
    WHERE trigger_event_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS deadline_rules_spawn_proceeding_type_id_idx
    ON paliad.deadline_rules (spawn_proceeding_type_id)
    WHERE spawn_proceeding_type_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS deadline_rules_lifecycle_state_idx
    ON paliad.deadline_rules (lifecycle_state);
