-- Data model v2 (t-paliad-024) + full English rename (t-paliad-025).
--
-- Replaces paliad.akten with a single self-referential paliad.projects tree,
-- and renames every remaining German table/column in paliad.* to English.
-- Only user-facing i18n strings stay bilingual (owned by the frontend).
--
-- Visibility is purely team-based (direct + inherited up the path) + admin.
-- Office becomes an informational attribute on users only — no project-level
-- office gate anymore. Cases are associated with lead partners, not offices.
--
-- Migration is one-shot: creates the new schema, renames child tables in
-- place and rewrites their FKs (same UUIDs as akten.id), drops paliad.akten,
-- replaces can_see_akte() with can_see_project(). All existing akten rows
-- survive as projects rows of type='case' with parent_id=NULL (orphan cases —
-- admin/partners reparent them under real clients through the new UI).

-- ============================================================================
-- 1. users: primary office stays; add additional_offices for partners
--    who work across offices.
-- ============================================================================
ALTER TABLE paliad.users
    ADD COLUMN IF NOT EXISTS additional_offices text[] NOT NULL DEFAULT '{}';

-- ============================================================================
-- 2. paliad.projects — the single hierarchical tree.
--    type in (client, litigation, patent, case, project).
--    Roots (parent_id NULL) are typically type='client' but not enforced —
--    a generic 'project' root is also valid (e.g., internal knowledge project).
-- ============================================================================
CREATE TABLE paliad.projects (
    id                    uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    type                  text NOT NULL CHECK (type IN (
                              'client','litigation','patent','case','project'
                          )),
    parent_id             uuid REFERENCES paliad.projects(id) ON DELETE CASCADE,
    -- Materialised path of UUID labels joined by '.'; always includes self
    -- as the last label. Root: path = id::text. Child: path = parent.path
    -- || '.' || id::text. Maintained by the trigger below; never write
    -- directly from the service layer.
    path                  text NOT NULL,

    title                 text NOT NULL,
    reference             text,
    description           text,
    status                text NOT NULL DEFAULT 'active'
                          CHECK (status IN ('active','archived','closed')),

    -- created_by is nullable (matches akten; users can be deleted from
    -- auth.users). A NULL creator does NOT grant visibility — the row is
    -- only visible via explicit team membership (or admin).
    created_by            uuid REFERENCES auth.users(id) ON DELETE SET NULL,

    -- Client-specific (type='client'). Nullable for other types.
    industry              text,
    country               text,
    billing_reference     text,

    -- ClientMatter numbers — external HLC billing/DMS identifiers, not
    -- generated by Paliad. Format: CCCCCCC.MMMMMMM (7+7 digits).
    -- * client_number lives on the Client-level Project and is inherited
    --   (by convention in the UI, not enforced) down the tree.
    -- * matter_number is assigned independently at any sub-level
    --   (litigation / patent / case). Children may override the inherited
    --   client_number — rare but allowed.
    -- Both are nullable; search/filter is across the tree.
    client_number         text CHECK (client_number IS NULL OR client_number ~ '^[0-9]{7}$'),
    matter_number         text CHECK (matter_number IS NULL OR matter_number ~ '^[0-9]{7}$'),

    -- netDocuments: HLC's DMS. We can't integrate via API, so we store a
    -- bookmark URL per project. UI renders as an external-link button.
    netdocuments_url      text,

    -- Patent-specific (type='patent'). Nullable for other types.
    patent_number         text,
    filing_date           date,
    grant_date            date,

    -- Case-specific (type='case'). Nullable for other types.
    court                 text,
    case_number           text,
    proceeding_type_id    integer REFERENCES paliad.proceeding_types(id) ON DELETE SET NULL,

    metadata              jsonb NOT NULL DEFAULT '{}',
    ai_summary            text,

    created_at            timestamptz NOT NULL DEFAULT now(),
    updated_at            timestamptz NOT NULL DEFAULT now(),

    CONSTRAINT projects_parent_self_differs CHECK (parent_id IS NULL OR parent_id <> id)
);

-- text_pattern_ops index supports LIKE 'prefix.%' for fast descendant lookup.
CREATE INDEX projects_path_prefix_idx   ON paliad.projects (path text_pattern_ops);
CREATE INDEX projects_parent_idx        ON paliad.projects (parent_id);
CREATE INDEX projects_type_status_idx   ON paliad.projects (type, status);
CREATE INDEX projects_reference_idx     ON paliad.projects (reference)     WHERE reference     IS NOT NULL;
CREATE INDEX projects_client_number_idx ON paliad.projects (client_number) WHERE client_number IS NOT NULL;
CREATE INDEX projects_matter_number_idx ON paliad.projects (matter_number) WHERE matter_number IS NOT NULL;

-- ============================================================================
-- 3. Path maintenance triggers.
--    BEFORE INSERT / BEFORE UPDATE OF parent_id: recompute this row's path.
--    AFTER UPDATE OF path: propagate the new prefix to every descendant.
-- ============================================================================
CREATE OR REPLACE FUNCTION paliad.projects_sync_path()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
    parent_path text;
BEGIN
    IF NEW.parent_id IS NULL THEN
        NEW.path := NEW.id::text;
    ELSE
        SELECT path INTO parent_path
          FROM paliad.projects
         WHERE id = NEW.parent_id;
        IF parent_path IS NULL THEN
            RAISE EXCEPTION 'parent project % not found', NEW.parent_id;
        END IF;
        -- Reject cycles: parent's path cannot contain this row's id.
        IF parent_path = NEW.id::text
           OR parent_path LIKE '%.' || NEW.id::text
           OR parent_path LIKE NEW.id::text || '.%'
           OR parent_path LIKE '%.' || NEW.id::text || '.%'
        THEN
            RAISE EXCEPTION 'cannot set parent to own descendant';
        END IF;
        NEW.path := parent_path || '.' || NEW.id::text;
    END IF;
    RETURN NEW;
END;
$$;

CREATE TRIGGER projects_sync_path_before
    BEFORE INSERT OR UPDATE OF parent_id ON paliad.projects
    FOR EACH ROW EXECUTE FUNCTION paliad.projects_sync_path();

CREATE OR REPLACE FUNCTION paliad.projects_rewrite_subtree()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
    IF OLD.path IS DISTINCT FROM NEW.path THEN
        UPDATE paliad.projects
           SET path = NEW.path || substring(path FROM length(OLD.path) + 1)
         WHERE path LIKE OLD.path || '.%';
    END IF;
    RETURN NEW;
END;
$$;

CREATE TRIGGER projects_rewrite_subtree_after
    AFTER UPDATE OF path ON paliad.projects
    FOR EACH ROW EXECUTE FUNCTION paliad.projects_rewrite_subtree();

-- ============================================================================
-- 4. paliad.project_teams — membership. inherited=false rows are writes;
--    inherited=true is a flag the service layer may set on read to annotate
--    rows derived from an ancestor's team. Writes always use inherited=false.
-- ============================================================================
CREATE TABLE paliad.project_teams (
    id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id  uuid NOT NULL REFERENCES paliad.projects(id) ON DELETE CASCADE,
    user_id     uuid NOT NULL REFERENCES auth.users(id)      ON DELETE CASCADE,
    role        text NOT NULL CHECK (role IN (
                    'lead','associate','pa','of_counsel',
                    'local_counsel','expert','observer'
                )),
    inherited   boolean NOT NULL DEFAULT false,
    added_by    uuid REFERENCES auth.users(id) ON DELETE SET NULL,
    created_at  timestamptz NOT NULL DEFAULT now(),

    UNIQUE (project_id, user_id)
);

CREATE INDEX project_teams_project_idx ON paliad.project_teams (project_id);
CREATE INDEX project_teams_user_idx    ON paliad.project_teams (user_id);

-- ============================================================================
-- 5. paliad.departments — structural partner units (distinct from project teams).
--    A user's Department membership is orthogonal to their project-team roles.
-- ============================================================================
CREATE TABLE paliad.departments (
    id            uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    name          text NOT NULL,
    lead_user_id  uuid REFERENCES auth.users(id) ON DELETE SET NULL,
    office        text NOT NULL CHECK (office IN (
                      'munich','duesseldorf','hamburg',
                      'amsterdam','london','paris','milan'
                  )),
    created_at    timestamptz NOT NULL DEFAULT now(),
    updated_at    timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX departments_office_idx ON paliad.departments (office);
CREATE INDEX departments_lead_idx   ON paliad.departments (lead_user_id) WHERE lead_user_id IS NOT NULL;

CREATE TABLE paliad.department_members (
    department_id uuid NOT NULL REFERENCES paliad.departments(id) ON DELETE CASCADE,
    user_id       uuid NOT NULL REFERENCES auth.users(id)         ON DELETE CASCADE,
    created_at    timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (department_id, user_id)
);

CREATE INDEX department_members_user_idx ON paliad.department_members (user_id);

-- ============================================================================
-- 6. Data migration: akten → projects (same UUIDs), collaborators+created_by
--    → project_teams.
--    All existing akten become type='case' orphans (parent_id NULL). Admins
--    reparent under real clients via the new UI.
-- ============================================================================
INSERT INTO paliad.projects (
    id, type, parent_id, path,
    title, reference, status,
    court, case_number,
    created_by, metadata, ai_summary,
    created_at, updated_at
)
SELECT
    a.id,
    'case'::text,
    NULL::uuid,
    a.id::text,                  -- root path = id (no parent)
    a.title,
    NULLIF(a.aktenzeichen, ''),  -- aktenzeichen → reference
    a.status,
    a.court,
    a.court_ref,
    a.created_by,
    a.metadata,
    a.ai_summary,
    a.created_at,
    a.updated_at
  FROM paliad.akten a;

-- Creator → team lead (skip NULL creators; they remain admin-only orphans).
INSERT INTO paliad.project_teams (project_id, user_id, role, inherited, added_by)
SELECT a.id, a.created_by, 'lead', false, a.created_by
  FROM paliad.akten a
 WHERE a.created_by IS NOT NULL
ON CONFLICT (project_id, user_id) DO NOTHING;

-- Collaborators → team associates (dedup against the creator row).
INSERT INTO paliad.project_teams (project_id, user_id, role, inherited, added_by)
SELECT a.id, collab_id::uuid, 'associate', false, a.created_by
  FROM paliad.akten a
       CROSS JOIN LATERAL unnest(a.collaborators) AS collab_id
 WHERE collab_id IS NOT NULL
ON CONFLICT (project_id, user_id) DO NOTHING;

-- ============================================================================
-- 7. Rename child tables to English + rewrite FK columns (akte_id → project_id).
--    Tables: parteien→parties, fristen→deadlines, termine→appointments,
--            dokumente→documents, akten_events→project_events, notizen→notes.
--    Column renames: termin_type→appointment_type, akten_event_id→
--    project_event_id, frist_id→deadline_id, termin_id→appointment_id.
-- ============================================================================

-- Drop dependent RLS policies on children (rebuilt below against project_id).
DROP POLICY IF EXISTS parteien_all              ON paliad.parteien;
DROP POLICY IF EXISTS fristen_all               ON paliad.fristen;
DROP POLICY IF EXISTS termine_select            ON paliad.termine;
DROP POLICY IF EXISTS termine_insert            ON paliad.termine;
DROP POLICY IF EXISTS termine_update            ON paliad.termine;
DROP POLICY IF EXISTS termine_delete            ON paliad.termine;
DROP POLICY IF EXISTS dokumente_all             ON paliad.dokumente;
DROP POLICY IF EXISTS akten_events_all          ON paliad.akten_events;
DROP POLICY IF EXISTS notizen_all               ON paliad.notizen;
DROP POLICY IF EXISTS checklist_instances_select ON paliad.checklist_instances;
DROP POLICY IF EXISTS checklist_instances_insert ON paliad.checklist_instances;
DROP POLICY IF EXISTS checklist_instances_update ON paliad.checklist_instances;
DROP POLICY IF EXISTS checklist_instances_delete ON paliad.checklist_instances;
-- akten itself — drop policies before we drop the table.
DROP POLICY IF EXISTS akten_select ON paliad.akten;
DROP POLICY IF EXISTS akten_insert ON paliad.akten;
DROP POLICY IF EXISTS akten_update ON paliad.akten;
DROP POLICY IF EXISTS akten_delete ON paliad.akten;

-- parteien → parties
ALTER TABLE paliad.parteien DROP CONSTRAINT IF EXISTS parteien_akte_id_fkey;
ALTER TABLE paliad.parteien RENAME TO parties;
ALTER TABLE paliad.parties RENAME COLUMN akte_id TO project_id;
ALTER TABLE paliad.parties
    ADD CONSTRAINT parties_project_id_fkey FOREIGN KEY (project_id)
        REFERENCES paliad.projects(id) ON DELETE CASCADE;
DROP INDEX IF EXISTS paliad.parteien_akte_idx;
CREATE INDEX parties_project_idx ON paliad.parties (project_id);

-- fristen → deadlines
ALTER TABLE paliad.fristen DROP CONSTRAINT IF EXISTS fristen_akte_id_fkey;
ALTER TABLE paliad.fristen RENAME TO deadlines;
ALTER TABLE paliad.deadlines RENAME COLUMN akte_id TO project_id;
ALTER TABLE paliad.deadlines
    ADD CONSTRAINT deadlines_project_id_fkey FOREIGN KEY (project_id)
        REFERENCES paliad.projects(id) ON DELETE CASCADE;
DROP INDEX IF EXISTS paliad.fristen_akte_idx;
DROP INDEX IF EXISTS paliad.fristen_status_due_date_idx;
DROP INDEX IF EXISTS paliad.fristen_due_date_idx;
CREATE INDEX deadlines_project_idx         ON paliad.deadlines (project_id);
CREATE INDEX deadlines_status_due_date_idx ON paliad.deadlines (status, due_date);
CREATE INDEX deadlines_due_date_idx        ON paliad.deadlines (due_date);

-- termine → appointments (akte_id was nullable)
ALTER TABLE paliad.termine DROP CONSTRAINT IF EXISTS termine_akte_id_fkey;
ALTER TABLE paliad.termine RENAME TO appointments;
ALTER TABLE paliad.appointments RENAME COLUMN akte_id     TO project_id;
ALTER TABLE paliad.appointments RENAME COLUMN termin_type TO appointment_type;
ALTER TABLE paliad.appointments DROP CONSTRAINT IF EXISTS termine_termin_type_check;
ALTER TABLE paliad.appointments
    ADD CONSTRAINT appointments_appointment_type_check
        CHECK (appointment_type IS NULL OR appointment_type IN (
            'hearing', 'meeting', 'consultation', 'deadline_hearing'
        ));
ALTER TABLE paliad.appointments
    ADD CONSTRAINT appointments_project_id_fkey FOREIGN KEY (project_id)
        REFERENCES paliad.projects(id) ON DELETE CASCADE;
DROP INDEX IF EXISTS paliad.termine_akte_idx;
DROP INDEX IF EXISTS paliad.termine_start_at_idx;
CREATE INDEX appointments_project_idx  ON paliad.appointments (project_id) WHERE project_id IS NOT NULL;
CREATE INDEX appointments_start_at_idx ON paliad.appointments (start_at);

-- dokumente → documents
ALTER TABLE paliad.dokumente DROP CONSTRAINT IF EXISTS dokumente_akte_id_fkey;
ALTER TABLE paliad.dokumente RENAME TO documents;
ALTER TABLE paliad.documents RENAME COLUMN akte_id TO project_id;
ALTER TABLE paliad.documents
    ADD CONSTRAINT documents_project_id_fkey FOREIGN KEY (project_id)
        REFERENCES paliad.projects(id) ON DELETE CASCADE;
DROP INDEX IF EXISTS paliad.dokumente_akte_idx;
CREATE INDEX documents_project_idx ON paliad.documents (project_id);

-- akten_events → project_events
ALTER TABLE paliad.akten_events DROP CONSTRAINT IF EXISTS akten_events_akte_id_fkey;
ALTER TABLE paliad.akten_events RENAME TO project_events;
ALTER TABLE paliad.project_events RENAME COLUMN akte_id TO project_id;
ALTER TABLE paliad.project_events
    ADD CONSTRAINT project_events_project_id_fkey FOREIGN KEY (project_id)
        REFERENCES paliad.projects(id) ON DELETE CASCADE;
DROP INDEX IF EXISTS paliad.akten_events_akte_created_idx;
CREATE INDEX project_events_project_created_idx
    ON paliad.project_events (project_id, created_at DESC);

-- notizen → notes. Polymorphic parent stays; all FK columns get English names.
ALTER TABLE paliad.notizen DROP CONSTRAINT IF EXISTS notizen_exactly_one_parent;
ALTER TABLE paliad.notizen DROP CONSTRAINT IF EXISTS notizen_akte_id_fkey;
ALTER TABLE paliad.notizen DROP CONSTRAINT IF EXISTS notizen_frist_id_fkey;
ALTER TABLE paliad.notizen DROP CONSTRAINT IF EXISTS notizen_termin_id_fkey;
ALTER TABLE paliad.notizen DROP CONSTRAINT IF EXISTS notizen_akten_event_id_fkey;
ALTER TABLE paliad.notizen RENAME TO notes;
ALTER TABLE paliad.notes RENAME COLUMN akte_id        TO project_id;
ALTER TABLE paliad.notes RENAME COLUMN frist_id       TO deadline_id;
ALTER TABLE paliad.notes RENAME COLUMN termin_id      TO appointment_id;
ALTER TABLE paliad.notes RENAME COLUMN akten_event_id TO project_event_id;
ALTER TABLE paliad.notes
    ADD CONSTRAINT notes_project_id_fkey FOREIGN KEY (project_id)
        REFERENCES paliad.projects(id) ON DELETE CASCADE,
    ADD CONSTRAINT notes_deadline_id_fkey FOREIGN KEY (deadline_id)
        REFERENCES paliad.deadlines(id) ON DELETE CASCADE,
    ADD CONSTRAINT notes_appointment_id_fkey FOREIGN KEY (appointment_id)
        REFERENCES paliad.appointments(id) ON DELETE CASCADE,
    ADD CONSTRAINT notes_project_event_id_fkey FOREIGN KEY (project_event_id)
        REFERENCES paliad.project_events(id) ON DELETE CASCADE;
DROP INDEX IF EXISTS paliad.notizen_akte_idx;
DROP INDEX IF EXISTS paliad.notizen_frist_idx;
DROP INDEX IF EXISTS paliad.notizen_termin_idx;
DROP INDEX IF EXISTS paliad.notizen_akten_event_idx;
CREATE INDEX notes_project_idx       ON paliad.notes (project_id)       WHERE project_id       IS NOT NULL;
CREATE INDEX notes_deadline_idx      ON paliad.notes (deadline_id)      WHERE deadline_id      IS NOT NULL;
CREATE INDEX notes_appointment_idx   ON paliad.notes (appointment_id)   WHERE appointment_id   IS NOT NULL;
CREATE INDEX notes_project_event_idx ON paliad.notes (project_event_id) WHERE project_event_id IS NOT NULL;
ALTER TABLE paliad.notes
    ADD CONSTRAINT notes_exactly_one_parent CHECK (
        (CASE WHEN project_id       IS NOT NULL THEN 1 ELSE 0 END) +
        (CASE WHEN deadline_id      IS NOT NULL THEN 1 ELSE 0 END) +
        (CASE WHEN appointment_id   IS NOT NULL THEN 1 ELSE 0 END) +
        (CASE WHEN project_event_id IS NOT NULL THEN 1 ELSE 0 END) = 1
    );

-- checklist_instances — akte_id → project_id (table name already English).
ALTER TABLE paliad.checklist_instances DROP CONSTRAINT IF EXISTS checklist_instances_akte_id_fkey;
ALTER TABLE paliad.checklist_instances RENAME COLUMN akte_id TO project_id;
ALTER TABLE paliad.checklist_instances
    ADD CONSTRAINT checklist_instances_project_id_fkey FOREIGN KEY (project_id)
        REFERENCES paliad.projects(id) ON DELETE SET NULL;
DROP INDEX IF EXISTS paliad.checklist_instances_akte_idx;
CREATE INDEX checklist_instances_project_idx
    ON paliad.checklist_instances (project_id) WHERE project_id IS NOT NULL;

-- ============================================================================
-- 8. Drop paliad.akten (and its visibility helpers).
-- ============================================================================
DROP TABLE paliad.akten;
DROP FUNCTION IF EXISTS paliad.can_see_akte(uuid);
DROP FUNCTION IF EXISTS paliad.notiz_is_visible(uuid, uuid, uuid, uuid);

-- ============================================================================
-- 9. Visibility: can_see_project(id) — team-based only.
--    A user sees a project iff:
--      - admin, or
--      - direct team member (project_teams.project_id = target), or
--      - inherited team member on any ancestor of target (walk UP path).
-- ============================================================================
CREATE OR REPLACE FUNCTION paliad.can_see_project(_project_id uuid)
RETURNS boolean
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = paliad, public
AS $$
    SELECT EXISTS (
        SELECT 1 FROM paliad.users u
         WHERE u.id = auth.uid() AND u.role = 'admin'
    )
    OR EXISTS (
        -- Any team membership (direct or ancestor). `path` always includes
        -- the target's own id as the last label, so this handles direct too.
        SELECT 1
          FROM paliad.projects      target
          JOIN paliad.project_teams pt
            ON pt.user_id = auth.uid()
           AND pt.project_id = ANY(string_to_array(target.path, '.')::uuid[])
         WHERE target.id = _project_id
    );
$$;

COMMENT ON FUNCTION paliad.can_see_project(uuid) IS
    'Team-based visibility predicate for paliad.projects. Direct or inherited '
    '(ancestor) membership grants access. Admins see all.';

-- Helper: note visibility — dispatches by whichever parent FK is set.
CREATE OR REPLACE FUNCTION paliad.note_is_visible(
    _project_id       uuid,
    _deadline_id      uuid,
    _appointment_id   uuid,
    _project_event_id uuid
) RETURNS boolean
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = paliad, public
AS $$
    SELECT CASE
        WHEN _project_id IS NOT NULL THEN paliad.can_see_project(_project_id)
        WHEN _deadline_id IS NOT NULL THEN paliad.can_see_project(
            (SELECT project_id FROM paliad.deadlines WHERE id = _deadline_id))
        WHEN _appointment_id IS NOT NULL THEN
            CASE
                WHEN (SELECT project_id FROM paliad.appointments WHERE id = _appointment_id) IS NULL
                    THEN (SELECT created_by FROM paliad.appointments WHERE id = _appointment_id) = auth.uid()
                ELSE paliad.can_see_project(
                    (SELECT project_id FROM paliad.appointments WHERE id = _appointment_id))
            END
        WHEN _project_event_id IS NOT NULL THEN paliad.can_see_project(
            (SELECT project_id FROM paliad.project_events WHERE id = _project_event_id))
        ELSE false
    END;
$$;

-- ============================================================================
-- 10. RLS: enable + policies on the new tables.
-- ============================================================================
ALTER TABLE paliad.projects           ENABLE ROW LEVEL SECURITY;
ALTER TABLE paliad.project_teams      ENABLE ROW LEVEL SECURITY;
ALTER TABLE paliad.departments        ENABLE ROW LEVEL SECURITY;
ALTER TABLE paliad.department_members ENABLE ROW LEVEL SECURITY;

-- projects
CREATE POLICY projects_select ON paliad.projects
    FOR SELECT TO authenticated
    USING (paliad.can_see_project(id));

-- INSERT: creating a root project is open to any authenticated user.
-- Creating a child requires visibility on the parent. The service must
-- add the creator to project_teams (role='lead') in the same transaction
-- so the creator can see the row afterwards.
CREATE POLICY projects_insert ON paliad.projects
    FOR INSERT TO authenticated
    WITH CHECK (
        parent_id IS NULL
        OR paliad.can_see_project(parent_id)
    );

CREATE POLICY projects_update ON paliad.projects
    FOR UPDATE TO authenticated
    USING      (paliad.can_see_project(id))
    WITH CHECK (paliad.can_see_project(id));

-- Delete: team visibility + admin/lead role. Cascade walks down the tree.
CREATE POLICY projects_delete ON paliad.projects
    FOR DELETE TO authenticated
    USING (
        paliad.can_see_project(id)
        AND EXISTS (
            SELECT 1 FROM paliad.users u
             WHERE u.id = auth.uid() AND u.role IN ('partner','admin')
        )
    );

-- project_teams: everyone on the team (including ancestor-inherited members)
-- can list and modify the team. Anyone with visibility on the project can
-- insert themselves as the initial member (used by the creator-add-self
-- flow). Partner/admin can remove.
CREATE POLICY project_teams_select ON paliad.project_teams
    FOR SELECT TO authenticated
    USING (paliad.can_see_project(project_id));

CREATE POLICY project_teams_insert ON paliad.project_teams
    FOR INSERT TO authenticated
    WITH CHECK (
        -- creator adding self, or already-a-member adding someone else
        user_id = auth.uid()
        OR paliad.can_see_project(project_id)
    );

CREATE POLICY project_teams_update ON paliad.project_teams
    FOR UPDATE TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

CREATE POLICY project_teams_delete ON paliad.project_teams
    FOR DELETE TO authenticated
    USING (
        paliad.can_see_project(project_id)
        AND (
            user_id = auth.uid()  -- remove self
            OR EXISTS (
                SELECT 1 FROM paliad.users u
                 WHERE u.id = auth.uid() AND u.role IN ('partner','admin')
            )
        )
    );

-- departments: any authenticated user can read. Only admins write.
CREATE POLICY departments_select ON paliad.departments
    FOR SELECT TO authenticated
    USING (true);

CREATE POLICY departments_write ON paliad.departments
    FOR ALL TO authenticated
    USING (
        EXISTS (SELECT 1 FROM paliad.users u
                 WHERE u.id = auth.uid() AND u.role = 'admin')
    )
    WITH CHECK (
        EXISTS (SELECT 1 FROM paliad.users u
                 WHERE u.id = auth.uid() AND u.role = 'admin')
    );

CREATE POLICY department_members_select ON paliad.department_members
    FOR SELECT TO authenticated
    USING (true);

CREATE POLICY department_members_write ON paliad.department_members
    FOR ALL TO authenticated
    USING (
        user_id = auth.uid()
        OR EXISTS (SELECT 1 FROM paliad.users u
                    WHERE u.id = auth.uid() AND u.role = 'admin')
    )
    WITH CHECK (
        user_id = auth.uid()
        OR EXISTS (SELECT 1 FROM paliad.users u
                    WHERE u.id = auth.uid() AND u.role = 'admin')
    );

-- ============================================================================
-- 11. RLS: rebuild child-table policies against project_id / can_see_project.
-- ============================================================================
CREATE POLICY parties_all ON paliad.parties
    FOR ALL TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

CREATE POLICY deadlines_all ON paliad.deadlines
    FOR ALL TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

-- appointments — project_id nullable; personal (NULL) = creator-only.
CREATE POLICY appointments_select ON paliad.appointments
    FOR SELECT TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );
CREATE POLICY appointments_insert ON paliad.appointments
    FOR INSERT TO authenticated
    WITH CHECK (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );
CREATE POLICY appointments_update ON paliad.appointments
    FOR UPDATE TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    )
    WITH CHECK (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );
CREATE POLICY appointments_delete ON paliad.appointments
    FOR DELETE TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );

CREATE POLICY documents_all ON paliad.documents
    FOR ALL TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

ALTER TABLE paliad.project_events ENABLE ROW LEVEL SECURITY;
CREATE POLICY project_events_all ON paliad.project_events
    FOR ALL TO authenticated
    USING      (paliad.can_see_project(project_id))
    WITH CHECK (paliad.can_see_project(project_id));

-- notes — polymorphic parent dispatch.
CREATE POLICY notes_all ON paliad.notes
    FOR ALL TO authenticated
    USING      (paliad.note_is_visible(project_id, deadline_id, appointment_id, project_event_id))
    WITH CHECK (paliad.note_is_visible(project_id, deadline_id, appointment_id, project_event_id));

-- checklist_instances — mirrors appointments: personal (NULL) = creator-only.
CREATE POLICY checklist_instances_select ON paliad.checklist_instances
    FOR SELECT TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );
CREATE POLICY checklist_instances_insert ON paliad.checklist_instances
    FOR INSERT TO authenticated
    WITH CHECK (
        created_by = auth.uid()
        AND (project_id IS NULL OR paliad.can_see_project(project_id))
    );
CREATE POLICY checklist_instances_update ON paliad.checklist_instances
    FOR UPDATE TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    )
    WITH CHECK (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );
CREATE POLICY checklist_instances_delete ON paliad.checklist_instances
    FOR DELETE TO authenticated
    USING (
        (project_id IS NULL     AND created_by = auth.uid())
        OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
    );
