-- 157_scenario_builder_foundation — t-paliad-340 / m/paliad#153 B0
--
-- Schema foundation for the Litigation Builder (PRD
-- docs/plans/prd-procedures-litigation-planner-2026-05-27.md §5.1 + §5.2).
-- Phase B0 of the 7-slice train described in PRD §7.1. DB-only — no UI
-- depends on these tables yet; B1 wires the builder shell on top.
--
-- What this migration adds:
--
--   1. Six new columns on paliad.scenarios for the builder shape:
--        owner_id, status, origin_project_id, promoted_project_id,
--        stichtag, notes.
--      Two relaxations on existing columns:
--        - spec NOT NULL → NULL (the builder normalises spec contents
--          into scenario_proceedings / scenario_events; new rows skip
--          spec entirely. Legacy callers from mig 145 still provide it
--          explicitly, so they keep inserting valid rows.)
--        - DROP CONSTRAINT scenarios_unique_per_scope (the builder
--          allows multiple "Unbenanntes Szenario" + multiple scratch
--          scenarios per user — uniqueness on (project_id, created_by,
--          name) blocks that. The legacy service treated the constraint
--          as UX collision avoidance, not correctness.)
--
--   2. Three new tables for the normalised builder shape:
--        - paliad.scenario_proceedings (one row per proceeding in a
--          scenario; multi-proceeding constellations + spawned children)
--        - paliad.scenario_events       (one row per event card on the
--          canvas; planned / filed / skipped state + actual_date + notes
--          + per-card optional horizon)
--        - paliad.scenario_shares       (read-only team shares; owner is
--          the sole editor)
--
--   3. One new column on paliad.projects:
--        - origin_scenario_id — audit trail for promote-to-project
--          (B5; the column lands now so the FK is in place when the
--          wizard arrives).
--
--   4. New helper function paliad.can_see_scenario(_scenario_id) that
--      mirrors paliad.can_see_project's STABLE SECURITY DEFINER shape.
--      Visibility logic:
--        - global_admin sees everything,
--        - owner_id = auth.uid() (builder-owned scenarios),
--        - scenario_shares.shared_with_user_id = auth.uid()
--          (read-only shared scenarios),
--        - legacy project-scoped scenarios (owner_id IS NULL AND
--          project_id IS NOT NULL) follow can_see_project(project_id),
--        - legacy abstract scenarios (owner_id IS NULL AND project_id
--          IS NULL) follow created_by = auth.uid().
--
--   5. Replacement RLS policies on paliad.scenarios that fold builder
--      visibility together with the legacy shape. The legacy
--      project_* / abstract_* policies are dropped (they covered only
--      legacy paths) and rewritten as a single pair of policies that
--      treats owner_id, scenario_shares, and the legacy paths uniformly.
--
--      Builder-only RLS for the three new tables: read = scenario
--      visibility; write = scenario owner (or legacy editor) only.
--
-- PRD §5.1 deviations called out for the reader:
--
--   - PRD specs `proceeding_type_id uuid REFERENCES paliad.proceeding_types(id)`.
--     The live column is `integer` (see paliad.proceeding_types.id);
--     scenario_proceedings.proceeding_type_id is integer here to match
--     the real FK target. PRD authors did not check the column type;
--     this migration uses the truth on disk.
--
--   - PRD references `auth.users(id)` for owner_id and share columns;
--     the established paliad convention (see paliad.projects.created_by,
--     paliad.scenarios.created_by) uses `paliad.users(id)`. Same UUIDs
--     either way (paliad.users.id == auth.users.id), but the FK targets
--     paliad.users to stay consistent with project tables.
--
-- Audit-first: all DDL ran clean against a BEGIN/ROLLBACK probe on the
-- live DB before this file was committed. paliad.scenarios has 0 rows
-- (verified pre-mig), so the column additions and constraint relaxations
-- have no data impact.

BEGIN;

SELECT set_config(
    'paliad.audit_reason',
    'mig 157: Scenario builder foundation (t-paliad-340 / m/paliad#153 B0)',
    true
);

-- ----------------------------------------------------------------
-- 1. paliad.scenarios — additive columns + constraint relaxations
-- ----------------------------------------------------------------

ALTER TABLE paliad.scenarios
    ADD COLUMN owner_id            uuid NULL REFERENCES paliad.users(id) ON DELETE CASCADE,
    ADD COLUMN status              text NOT NULL DEFAULT 'active'
        CHECK (status IN ('active','archived','promoted')),
    ADD COLUMN origin_project_id   uuid NULL REFERENCES paliad.projects(id) ON DELETE SET NULL,
    ADD COLUMN promoted_project_id uuid NULL REFERENCES paliad.projects(id) ON DELETE SET NULL,
    ADD COLUMN stichtag            date NULL,
    ADD COLUMN notes               text NULL;

ALTER TABLE paliad.scenarios ALTER COLUMN spec DROP NOT NULL;
ALTER TABLE paliad.scenarios DROP CONSTRAINT IF EXISTS scenarios_unique_per_scope;

CREATE INDEX scenarios_owner_status_idx
    ON paliad.scenarios(owner_id, status)
    WHERE owner_id IS NOT NULL;

CREATE INDEX scenarios_updated_idx
    ON paliad.scenarios(owner_id, updated_at DESC)
    WHERE owner_id IS NOT NULL;

COMMENT ON COLUMN paliad.scenarios.owner_id IS
    'Litigation Builder owner (PRD §5.1). NULL = legacy composition-spec '
    'scenario from m/paliad#124 Slice D (mig 145). Builder rows MUST have '
    'owner_id set; the application enforces it via ScenarioBuilderService.';

COMMENT ON COLUMN paliad.scenarios.status IS
    'Lifecycle: active (default; user-editable) / archived (soft-deleted, '
    'still visible in side panel) / promoted (converted to project via '
    'B5 wizard; read-only). Legacy mig-145 rows default to active.';

COMMENT ON COLUMN paliad.scenarios.origin_project_id IS
    'Set when the scenario was exported from an existing project '
    '("Im Builder öffnen" — Akte mode, PRD §2.3).';

COMMENT ON COLUMN paliad.scenarios.promoted_project_id IS
    'Set after the scenario was promoted to a real project via the 3-step '
    'wizard (PRD §5.4). Together with paliad.projects.origin_scenario_id, '
    'forms the bidirectional audit link.';

COMMENT ON COLUMN paliad.scenarios.stichtag IS
    'Scenario-level default Stichtag; per-proceeding overrides in '
    'paliad.scenario_proceedings.stichtag take precedence.';

-- ----------------------------------------------------------------
-- 2. paliad.scenario_proceedings — one proceeding per scenario row
-- ----------------------------------------------------------------

CREATE TABLE paliad.scenario_proceedings (
    id                              uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    scenario_id                     uuid NOT NULL
        REFERENCES paliad.scenarios(id) ON DELETE CASCADE,
    proceeding_type_id              integer NOT NULL
        REFERENCES paliad.proceeding_types(id),
    primary_party                   text NULL
        CHECK (primary_party IN ('claimant','defendant')),
    scenario_flags                  jsonb NOT NULL DEFAULT '{}'::jsonb
        CHECK (jsonb_typeof(scenario_flags) = 'object'),
    parent_scenario_proceeding_id   uuid NULL
        REFERENCES paliad.scenario_proceedings(id) ON DELETE CASCADE,
    spawn_anchor_event_id           uuid NULL
        REFERENCES paliad.sequencing_rules(id),
    ordinal                         int NOT NULL DEFAULT 0,
    stichtag                        date NULL,
    detailgrad                      text NOT NULL DEFAULT 'selected'
        CHECK (detailgrad IN ('selected','all_options')),
    appeal_target                   text NULL,
    collapsed                       boolean NOT NULL DEFAULT false,
    created_at                      timestamptz NOT NULL DEFAULT now(),
    updated_at                      timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX scenario_proceedings_scenario_idx
    ON paliad.scenario_proceedings(scenario_id, ordinal);

CREATE INDEX scenario_proceedings_parent_idx
    ON paliad.scenario_proceedings(parent_scenario_proceeding_id)
    WHERE parent_scenario_proceeding_id IS NOT NULL;

COMMENT ON TABLE paliad.scenario_proceedings IS
    'One proceeding inside a Litigation Builder scenario. Multiple rows '
    'per scenario for multi-proceeding constellations. '
    'parent_scenario_proceeding_id self-refs for spawned children '
    '(e.g. upc.ccr.cfi spawned by with_ccr on upc.inf.cfi). '
    'PRD §5.1, m/paliad#153 B0.';

COMMENT ON COLUMN paliad.scenario_proceedings.primary_party IS
    'Per-proceeding perspective ("our side"). NULL = no perspective '
    'picked yet (both party columns render with natural labels). '
    'Per-proceeding so multi-jurisdiction constellations can flip side '
    'independently (PRD §3.3).';

COMMENT ON COLUMN paliad.scenario_proceedings.scenario_flags IS
    'Per-proceeding flags (e.g. {"with_ccr": true, "with_amend": false}). '
    'Mirrors paliad.projects.scenario_flags shape but lives per-proceeding-'
    'per-scenario. Validated by the application against '
    'paliad.scenario_flag_catalog at write time.';

COMMENT ON COLUMN paliad.scenario_proceedings.spawn_anchor_event_id IS
    'Which sequencing_rule of the parent proceeding caused this spawn. '
    'NULL for root proceedings. Used by the UI to place the spawned child '
    'triplet directly below the parent at the spawn node (PRD §3.6).';

COMMENT ON COLUMN paliad.scenario_proceedings.ordinal IS
    'Stack order on canvas (top to bottom). Siblings under the same '
    'parent (or top-level) are ordered by ordinal asc, then created_at.';

COMMENT ON COLUMN paliad.scenario_proceedings.detailgrad IS
    'Per-proceeding optional-detail toggle: selected (only explicitly '
    'chosen optionals + mandatories) or all_options (every optional '
    'sequencing_rule surfaces). Matches today''s Verfahrensablauf pattern.';

-- ----------------------------------------------------------------
-- 3. paliad.scenario_events — one event card on the canvas
-- ----------------------------------------------------------------

CREATE TABLE paliad.scenario_events (
    id                     uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    scenario_proceeding_id uuid NOT NULL
        REFERENCES paliad.scenario_proceedings(id) ON DELETE CASCADE,
    sequencing_rule_id     uuid NULL
        REFERENCES paliad.sequencing_rules(id),
    procedural_event_id    uuid NULL
        REFERENCES paliad.procedural_events(id),
    custom_label           text NULL,
    state                  text NOT NULL DEFAULT 'planned'
        CHECK (state IN ('planned','filed','skipped')),
    actual_date            date NULL,
    skip_reason            text NULL,
    notes                  text NULL,
    horizon_optional       int NOT NULL DEFAULT 0
        CHECK (horizon_optional >= 0),
    created_at             timestamptz NOT NULL DEFAULT now(),
    updated_at             timestamptz NOT NULL DEFAULT now(),
    CONSTRAINT scenario_events_one_anchor CHECK (
        (sequencing_rule_id  IS NOT NULL)::int +
        (procedural_event_id IS NOT NULL)::int +
        (custom_label        IS NOT NULL)::int >= 1
    )
);

CREATE INDEX scenario_events_proceeding_idx
    ON paliad.scenario_events(scenario_proceeding_id);

-- A single proceeding can't carry two cards for the same sequencing rule
-- (each rule maps to one card). Free-form / procedural_event-only cards
-- skip this uniqueness — multiple custom cards per proceeding are OK.
CREATE UNIQUE INDEX scenario_events_rule_uniq_idx
    ON paliad.scenario_events(scenario_proceeding_id, sequencing_rule_id)
    WHERE sequencing_rule_id IS NOT NULL;

COMMENT ON TABLE paliad.scenario_events IS
    'One event card on the Litigation Builder canvas. Captures state '
    '(planned/filed/skipped), actual_date, notes, skip_reason, and the '
    'per-card optional-horizon setting. At least one of '
    '(sequencing_rule_id, procedural_event_id, custom_label) must be '
    'set — sequencing-rule-backed cards are the common case; free-form '
    'cards exist for events the catalog doesn''t cover yet. '
    'PRD §3.4 / §5.1.';

COMMENT ON COLUMN paliad.scenario_events.state IS
    '3-state machine: planned (default, future event with computed date) '
    '/ filed (past event, actual_date set) / skipped (user chose not to '
    'file; optional skip_reason). No "overdue" enum — that''s derived '
    '(date < today AND state=planned), not stored. PRD Q10 / §3.4.';

COMMENT ON COLUMN paliad.scenario_events.actual_date IS
    'Set when state=filed (real-world filing date) OR when state=planned '
    'and the user overrode the computed date (court-set events, manual '
    'tweaks). NULL when the computed date is canonical.';

COMMENT ON COLUMN paliad.scenario_events.horizon_optional IS
    'Per-card "show N more optional follow-ups" affordance. Default 0 '
    '(hidden). PRD Q4 / §3.4.';

-- ----------------------------------------------------------------
-- 4. paliad.scenario_shares — read-only team shares
-- ----------------------------------------------------------------

CREATE TABLE paliad.scenario_shares (
    id                  uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    scenario_id         uuid NOT NULL
        REFERENCES paliad.scenarios(id) ON DELETE CASCADE,
    shared_with_user_id uuid NOT NULL
        REFERENCES paliad.users(id) ON DELETE CASCADE,
    created_at          timestamptz NOT NULL DEFAULT now(),
    created_by          uuid NOT NULL REFERENCES paliad.users(id),
    UNIQUE (scenario_id, shared_with_user_id)
);

CREATE INDEX scenario_shares_user_idx
    ON paliad.scenario_shares(shared_with_user_id);

COMMENT ON TABLE paliad.scenario_shares IS
    'Read-only team shares for Litigation Builder scenarios. Owner '
    '(paliad.scenarios.owner_id) is the sole editor; rows here grant '
    'view-only access to other paliad users. PRD Q12 / §5.1.';

-- ----------------------------------------------------------------
-- 5. paliad.projects.origin_scenario_id — promote-to-project trail
-- ----------------------------------------------------------------

ALTER TABLE paliad.projects
    ADD COLUMN origin_scenario_id uuid NULL
        REFERENCES paliad.scenarios(id) ON DELETE SET NULL;

CREATE INDEX projects_origin_scenario_idx
    ON paliad.projects(origin_scenario_id)
    WHERE origin_scenario_id IS NOT NULL;

COMMENT ON COLUMN paliad.projects.origin_scenario_id IS
    'FK to the scenario this project was promoted from (B5 wizard). '
    'NULL = project was created directly, not via Builder. Together with '
    'paliad.scenarios.promoted_project_id, forms the bidirectional audit '
    'link. PRD §5.2.';

-- ----------------------------------------------------------------
-- 6. paliad.can_see_scenario — visibility helper
-- ----------------------------------------------------------------

CREATE OR REPLACE FUNCTION paliad.can_see_scenario(_scenario_id uuid)
    RETURNS boolean
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'paliad', 'public'
AS $func$
    SELECT EXISTS (
        SELECT 1 FROM paliad.users u
         WHERE u.id = auth.uid() AND u.global_role = 'global_admin'
    )
    OR EXISTS (
        SELECT 1 FROM paliad.scenarios s
         WHERE s.id = _scenario_id AND s.owner_id = auth.uid()
    )
    OR EXISTS (
        SELECT 1 FROM paliad.scenario_shares sh
         WHERE sh.scenario_id = _scenario_id
           AND sh.shared_with_user_id = auth.uid()
    )
    -- Legacy project-scoped scenarios (mig 145) — visible via project
    -- team membership.
    OR EXISTS (
        SELECT 1 FROM paliad.scenarios s
         WHERE s.id = _scenario_id
           AND s.owner_id IS NULL
           AND s.project_id IS NOT NULL
           AND paliad.can_see_project(s.project_id)
    )
    -- Legacy abstract scenarios (mig 145) — owner-only via created_by.
    OR EXISTS (
        SELECT 1 FROM paliad.scenarios s
         WHERE s.id = _scenario_id
           AND s.owner_id IS NULL
           AND s.project_id IS NULL
           AND s.created_by = auth.uid()
    );
$func$;

COMMENT ON FUNCTION paliad.can_see_scenario(uuid) IS
    'Returns true if the caller (auth.uid()) can see the given scenario. '
    'Mirrors paliad.can_see_project. Covers builder-owned scenarios '
    '(owner_id), read-only shares (scenario_shares), and the two legacy '
    'paths from mig 145 (project-scoped via can_see_project, abstract '
    'via created_by). Used by RLS on all four scenario_* tables.';

-- ----------------------------------------------------------------
-- 7. RLS — replace legacy scenarios policies + new tables
-- ----------------------------------------------------------------

-- Replace mig-145's four policies with a single pair that handles
-- builder + legacy shapes together.
DROP POLICY IF EXISTS scenarios_project_select   ON paliad.scenarios;
DROP POLICY IF EXISTS scenarios_project_mutate   ON paliad.scenarios;
DROP POLICY IF EXISTS scenarios_abstract_select  ON paliad.scenarios;
DROP POLICY IF EXISTS scenarios_abstract_mutate  ON paliad.scenarios;

CREATE POLICY scenarios_select ON paliad.scenarios
    FOR SELECT USING (paliad.can_see_scenario(id));

-- Write rule: builder owner, legacy project team member (if no owner),
-- or legacy abstract creator (if no owner + no project). Shares are
-- read-only — they don't grant mutate.
CREATE POLICY scenarios_owner_mutate ON paliad.scenarios
    FOR ALL
    USING (
        owner_id = auth.uid()
        OR (owner_id IS NULL AND project_id IS NOT NULL AND paliad.can_see_project(project_id))
        OR (owner_id IS NULL AND project_id IS NULL AND created_by = auth.uid())
    )
    WITH CHECK (
        owner_id = auth.uid()
        OR (owner_id IS NULL AND project_id IS NOT NULL AND paliad.can_see_project(project_id))
        OR (owner_id IS NULL AND project_id IS NULL AND created_by = auth.uid())
    );

-- scenario_proceedings — visibility piggybacks on the parent scenario.
ALTER TABLE paliad.scenario_proceedings ENABLE ROW LEVEL SECURITY;

CREATE POLICY scenario_proceedings_select ON paliad.scenario_proceedings
    FOR SELECT USING (paliad.can_see_scenario(scenario_id));

CREATE POLICY scenario_proceedings_mutate ON paliad.scenario_proceedings
    FOR ALL
    USING (EXISTS (
        SELECT 1 FROM paliad.scenarios s
         WHERE s.id = scenario_id
           AND (s.owner_id = auth.uid()
             OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
             OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
    ))
    WITH CHECK (EXISTS (
        SELECT 1 FROM paliad.scenarios s
         WHERE s.id = scenario_id
           AND (s.owner_id = auth.uid()
             OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
             OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
    ));

-- scenario_events — visibility piggybacks on the parent scenario via
-- the proceeding row.
ALTER TABLE paliad.scenario_events ENABLE ROW LEVEL SECURITY;

CREATE POLICY scenario_events_select ON paliad.scenario_events
    FOR SELECT
    USING (EXISTS (
        SELECT 1 FROM paliad.scenario_proceedings sp
         WHERE sp.id = scenario_proceeding_id
           AND paliad.can_see_scenario(sp.scenario_id)
    ));

CREATE POLICY scenario_events_mutate ON paliad.scenario_events
    FOR ALL
    USING (EXISTS (
        SELECT 1 FROM paliad.scenario_proceedings sp
         JOIN paliad.scenarios s ON s.id = sp.scenario_id
         WHERE sp.id = scenario_proceeding_id
           AND (s.owner_id = auth.uid()
             OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
             OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
    ))
    WITH CHECK (EXISTS (
        SELECT 1 FROM paliad.scenario_proceedings sp
         JOIN paliad.scenarios s ON s.id = sp.scenario_id
         WHERE sp.id = scenario_proceeding_id
           AND (s.owner_id = auth.uid()
             OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
             OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
    ));

-- scenario_shares — recipient can see their share rows; the scenario
-- owner (or legacy editor) can manage them.
ALTER TABLE paliad.scenario_shares ENABLE ROW LEVEL SECURITY;

CREATE POLICY scenario_shares_select ON paliad.scenario_shares
    FOR SELECT
    USING (
        shared_with_user_id = auth.uid()
        OR EXISTS (
            SELECT 1 FROM paliad.scenarios s
             WHERE s.id = scenario_id
               AND (s.owner_id = auth.uid()
                 OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
                 OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
        )
    );

CREATE POLICY scenario_shares_mutate ON paliad.scenario_shares
    FOR ALL
    USING (EXISTS (
        SELECT 1 FROM paliad.scenarios s
         WHERE s.id = scenario_id
           AND (s.owner_id = auth.uid()
             OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
             OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
    ))
    WITH CHECK (EXISTS (
        SELECT 1 FROM paliad.scenarios s
         WHERE s.id = scenario_id
           AND (s.owner_id = auth.uid()
             OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
             OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
    ));

-- ----------------------------------------------------------------
-- 8. updated_at triggers on the new tables (reuse the function mig 145
--    already created for paliad.scenarios).
-- ----------------------------------------------------------------

CREATE TRIGGER scenario_proceedings_touch_updated_at_trg
    BEFORE UPDATE ON paliad.scenario_proceedings
    FOR EACH ROW
    EXECUTE FUNCTION paliad.scenarios_touch_updated_at();

CREATE TRIGGER scenario_events_touch_updated_at_trg
    BEFORE UPDATE ON paliad.scenario_events
    FOR EACH ROW
    EXECUTE FUNCTION paliad.scenarios_touch_updated_at();

-- ----------------------------------------------------------------
-- 9. Informational NOTICE.
-- ----------------------------------------------------------------

DO $$
BEGIN
    RAISE NOTICE '[mig 157] paliad.scenarios extended with builder columns (0 legacy rows affected)';
    RAISE NOTICE '[mig 157] paliad.scenario_proceedings created';
    RAISE NOTICE '[mig 157] paliad.scenario_events created';
    RAISE NOTICE '[mig 157] paliad.scenario_shares created';
    RAISE NOTICE '[mig 157] paliad.projects.origin_scenario_id added';
    RAISE NOTICE '[mig 157] paliad.can_see_scenario(uuid) created';
END $$;

COMMIT;
