-- mig 115 — t-paliad-225 / m/paliad#61 Slice B — explicit sharing +
-- admin-promotion plumbing for user-authored checklists.
--
-- Design: docs/design-user-checklists-2026-05-20.md §3.2 / §4.2 / §4.3
-- / §4.5.
--
-- Introduces paliad.checklist_shares with the polymorphic recipient
-- pattern (xor-check enforces exactly one recipient_* column populated
-- per recipient_kind). Extends paliad.can_see_checklist with the
-- explicit-share branches so the 'shared' visibility level actually
-- gates anything.
--
-- Sections:
--   1. CREATE TABLE paliad.checklist_shares (+ indexes + RLS).
--   2. CREATE OR REPLACE paliad.can_see_checklist — adds 4 share
--      branches (user / office / partner_unit / project).
--
-- Idempotent throughout.

-- ============================================================================
-- 1. paliad.checklist_shares — explicit grants for a single checklist.
--
-- recipient_kind disambiguates which recipient_* column is populated.
-- The XOR check makes the constraint structurally enforce "exactly one
-- recipient_<kind> non-null per row". Per-kind UNIQUE partial indexes
-- prevent duplicate grants per (checklist, recipient).
--
-- Slice A's checklists.visibility CHECK already includes 'shared' so no
-- ALTER is needed here.
-- ============================================================================

CREATE TABLE IF NOT EXISTS paliad.checklist_shares (
    id                        uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    checklist_id              uuid NOT NULL REFERENCES paliad.checklists(id) ON DELETE CASCADE,
    recipient_kind            text NOT NULL
        CHECK (recipient_kind IN ('user', 'office', 'partner_unit', 'project')),
    recipient_user_id         uuid REFERENCES paliad.users(id)         ON DELETE CASCADE,
    recipient_office          text,
    recipient_partner_unit_id uuid REFERENCES paliad.partner_units(id) ON DELETE CASCADE,
    recipient_project_id      uuid REFERENCES paliad.projects(id)      ON DELETE CASCADE,
    granted_by                uuid NOT NULL REFERENCES paliad.users(id) ON DELETE SET NULL,
    granted_at                timestamptz NOT NULL DEFAULT now(),

    CONSTRAINT checklist_shares_recipient_xor CHECK (
        (recipient_kind = 'user'
         AND recipient_user_id        IS NOT NULL
         AND recipient_office          IS NULL
         AND recipient_partner_unit_id IS NULL
         AND recipient_project_id      IS NULL)
     OR (recipient_kind = 'office'
         AND recipient_office          IS NOT NULL
         AND recipient_user_id         IS NULL
         AND recipient_partner_unit_id IS NULL
         AND recipient_project_id      IS NULL)
     OR (recipient_kind = 'partner_unit'
         AND recipient_partner_unit_id IS NOT NULL
         AND recipient_user_id         IS NULL
         AND recipient_office          IS NULL
         AND recipient_project_id      IS NULL)
     OR (recipient_kind = 'project'
         AND recipient_project_id      IS NOT NULL
         AND recipient_user_id         IS NULL
         AND recipient_office          IS NULL
         AND recipient_partner_unit_id IS NULL)
    )
);

-- Hot-path lookup for the visibility predicate.
CREATE INDEX IF NOT EXISTS checklist_shares_lookup_idx
    ON paliad.checklist_shares (checklist_id);

-- Uniqueness per recipient kind. Partial indexes so a NULL recipient_<other>
-- doesn't collide with another row's NULL recipient_<other>.
CREATE UNIQUE INDEX IF NOT EXISTS checklist_shares_user_uniq
    ON paliad.checklist_shares (checklist_id, recipient_user_id)
    WHERE recipient_kind = 'user';
CREATE UNIQUE INDEX IF NOT EXISTS checklist_shares_office_uniq
    ON paliad.checklist_shares (checklist_id, recipient_office)
    WHERE recipient_kind = 'office';
CREATE UNIQUE INDEX IF NOT EXISTS checklist_shares_partner_unit_uniq
    ON paliad.checklist_shares (checklist_id, recipient_partner_unit_id)
    WHERE recipient_kind = 'partner_unit';
CREATE UNIQUE INDEX IF NOT EXISTS checklist_shares_project_uniq
    ON paliad.checklist_shares (checklist_id, recipient_project_id)
    WHERE recipient_kind = 'project';

COMMENT ON TABLE paliad.checklist_shares IS
    'Explicit grants for paliad.checklists. Polymorphic recipient '
    '(user/office/partner_unit/project) enforced by recipient_xor CHECK. '
    'Owner of the checklist grants and revokes; global_admin can revoke '
    'as well. Slice B (t-paliad-225) — see can_see_checklist body for '
    'the visibility branches that consume these rows.';

ALTER TABLE paliad.checklist_shares ENABLE ROW LEVEL SECURITY;

-- SELECT: caller can see the row if they own the parent checklist OR
-- they are the recipient (for user-kind grants — recipients shouldn't
-- be surprised by who else can also see the checklist) OR global_admin.
DROP POLICY IF EXISTS checklist_shares_select ON paliad.checklist_shares;
CREATE POLICY checklist_shares_select
    ON paliad.checklist_shares FOR SELECT TO authenticated
    USING (
        EXISTS (
            SELECT 1 FROM paliad.checklists c
             WHERE c.id = checklist_id AND c.owner_id = auth.uid()
        )
        OR (recipient_kind = 'user' AND recipient_user_id = auth.uid())
        OR EXISTS (
            SELECT 1 FROM paliad.users u
             WHERE u.id = auth.uid() AND u.global_role = 'global_admin'
        )
    );

-- INSERT: only the checklist owner can grant; granted_by must be self.
DROP POLICY IF EXISTS checklist_shares_insert ON paliad.checklist_shares;
CREATE POLICY checklist_shares_insert
    ON paliad.checklist_shares FOR INSERT TO authenticated
    WITH CHECK (
        EXISTS (
            SELECT 1 FROM paliad.checklists c
             WHERE c.id = checklist_id AND c.owner_id = auth.uid()
        )
        AND granted_by = auth.uid()
    );

-- DELETE: owner OR global_admin. No UPDATE policy — grants are
-- immutable, revoke = DELETE + re-insert with the corrected recipient.
DROP POLICY IF EXISTS checklist_shares_delete ON paliad.checklist_shares;
CREATE POLICY checklist_shares_delete
    ON paliad.checklist_shares FOR DELETE TO authenticated
    USING (
        EXISTS (
            SELECT 1 FROM paliad.checklists c
             WHERE c.id = checklist_id AND c.owner_id = auth.uid()
        )
        OR EXISTS (
            SELECT 1 FROM paliad.users u
             WHERE u.id = auth.uid() AND u.global_role = 'global_admin'
        )
    );

-- ============================================================================
-- 2. paliad.can_see_checklist — extend with the 4 share branches.
--
-- Owner + firm/global branches stay as in mig 114. Share branches:
--   - user        — the row's recipient_user_id matches the caller
--   - office      — recipient_office matches caller's office OR is in
--                   their additional_offices array
--   - partner_unit — caller is a member of the recipient partner_unit
--   - project      — caller can see the recipient project (reuses
--                    paliad.can_see_project, ltree-walked)
--
-- can_see_project reads auth.uid() through SECURITY DEFINER inheritance
-- (same pattern effective_project_admin uses in mig 111).
-- ============================================================================

CREATE OR REPLACE FUNCTION paliad.can_see_checklist(_user_id uuid, _checklist_id uuid)
RETURNS boolean
LANGUAGE sql STABLE SECURITY DEFINER
SET search_path TO 'paliad', 'public'
AS $$
    -- Owner
    SELECT EXISTS (
        SELECT 1 FROM paliad.checklists c
         WHERE c.id = _checklist_id AND c.owner_id = _user_id
    )
    -- firm / global
    OR EXISTS (
        SELECT 1 FROM paliad.checklists c
         WHERE c.id = _checklist_id AND c.visibility IN ('firm', 'global')
    )
    -- Explicit share: user
    OR EXISTS (
        SELECT 1 FROM paliad.checklist_shares s
         WHERE s.checklist_id = _checklist_id
           AND s.recipient_kind = 'user'
           AND s.recipient_user_id = _user_id
    )
    -- Explicit share: office (caller's primary OR additional offices)
    OR EXISTS (
        SELECT 1
          FROM paliad.checklist_shares s
          JOIN paliad.users u ON u.id = _user_id
         WHERE s.checklist_id = _checklist_id
           AND s.recipient_kind = 'office'
           AND (s.recipient_office = u.office
                OR s.recipient_office = ANY(u.additional_offices))
    )
    -- Explicit share: partner_unit (caller is a member)
    OR EXISTS (
        SELECT 1
          FROM paliad.checklist_shares s
          JOIN paliad.partner_unit_members pum
            ON pum.partner_unit_id = s.recipient_partner_unit_id
           AND pum.user_id = _user_id
         WHERE s.checklist_id = _checklist_id
           AND s.recipient_kind = 'partner_unit'
    )
    -- Explicit share: project (caller can see the project via existing predicate)
    OR EXISTS (
        SELECT 1 FROM paliad.checklist_shares s
         WHERE s.checklist_id = _checklist_id
           AND s.recipient_kind = 'project'
           AND paliad.can_see_project(s.recipient_project_id)
    );
$$;

COMMENT ON FUNCTION paliad.can_see_checklist(uuid, uuid) IS
    'True iff the user owns the checklist OR firm/global visibility OR '
    'an explicit share row matches the caller (by user / office / '
    'partner_unit / project ancestry).';
