-- Migration 005: shopping_lists and shopping_items
--
-- Trash model: shopping_lists has deleted_at for soft-delete (7-day recovery window).
-- shopping_items are hard-deleted — no trash, no recovery needed.

-- ── Enum ──────────────────────────────────────────────────────────────────────

CREATE TYPE public.list_status AS ENUM ('active', 'completed', 'archived');

-- ── shopping_lists ────────────────────────────────────────────────────────────

CREATE TABLE public.shopping_lists (
    id            uuid               PRIMARY KEY DEFAULT gen_random_uuid(),
    collective_id uuid               NOT NULL REFERENCES public.collectives(id) ON DELETE CASCADE,
    name          text               NOT NULL,
    status        public.list_status NOT NULL DEFAULT 'active',
    created_by    uuid               NOT NULL REFERENCES public.users(id) ON DELETE RESTRICT,
    created_at    timestamptz        NOT NULL DEFAULT now(),
    completed_at  timestamptz        NULL,
    deleted_at    timestamptz        NULL        -- soft delete; pg_cron purges after 7 days
);

ALTER TABLE public.shopping_lists ENABLE ROW LEVEL SECURITY;

-- Partial index: main query always filters by collective + status and excludes deleted rows
CREATE INDEX shopping_lists_collective_status_idx
    ON public.shopping_lists (collective_id, status)
    WHERE deleted_at IS NULL;

-- ── shopping_items ────────────────────────────────────────────────────────────

CREATE TABLE public.shopping_items (
    id          uuid        PRIMARY KEY DEFAULT gen_random_uuid(),
    list_id     uuid        NOT NULL REFERENCES public.shopping_lists(id) ON DELETE CASCADE,
    name        text        NOT NULL,
    quantity    numeric     NULL,
    unit        text        NULL,
    is_checked  boolean     NOT NULL DEFAULT false,
    checked_by  uuid        NULL REFERENCES public.users(id) ON DELETE SET NULL,
    checked_at  timestamptz NULL,
    sort_order  integer     NOT NULL DEFAULT 0,
    created_by  uuid        NOT NULL REFERENCES public.users(id) ON DELETE RESTRICT,
    created_at  timestamptz NOT NULL DEFAULT now()
);

ALTER TABLE public.shopping_items ENABLE ROW LEVEL SECURITY;

-- Composite index for the main list view: all items for a list in display order
CREATE INDEX shopping_items_list_order_idx
    ON public.shopping_items (list_id, sort_order);

-- ── Helper: resolve collective for an item (used in RLS policies) ─────────────
-- STABLE lets PostgreSQL cache the result within a single query.

CREATE OR REPLACE FUNCTION public.list_collective_id(p_list_id uuid)
RETURNS uuid
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
    SELECT collective_id FROM public.shopping_lists WHERE id = p_list_id;
$$;

-- ── RLS: shopping_lists ───────────────────────────────────────────────────────

-- All members (including guests) can see non-deleted lists, plus soft-deleted
-- lists within the 7-day recovery window (trash view).
CREATE POLICY shopping_lists_select
    ON public.shopping_lists FOR SELECT
    USING (
        public.is_member(collective_id)
        AND (
            deleted_at IS NULL
            OR deleted_at > now() - INTERVAL '7 days'
        )
    );

-- Active members (admin + member) can create lists.
CREATE POLICY shopping_lists_insert
    ON public.shopping_lists FOR INSERT
    WITH CHECK (
        public.is_active_member(collective_id)
        AND created_by = auth.uid()
    );

-- Active members can update lists (rename, change status, set completed_at / deleted_at).
CREATE POLICY shopping_lists_update
    ON public.shopping_lists FOR UPDATE
    USING  (public.is_active_member(collective_id))
    WITH CHECK (public.is_active_member(collective_id));

-- Active members can permanently hard-delete a list only once it has been soft-deleted.
-- pg_cron also purges old trash as superuser, bypassing RLS — intentional.
CREATE POLICY shopping_lists_delete
    ON public.shopping_lists FOR DELETE
    USING (
        public.is_active_member(collective_id)
        AND deleted_at IS NOT NULL
    );

-- ── RLS: shopping_items ───────────────────────────────────────────────────────

-- All members (including guests) can read items.
CREATE POLICY shopping_items_select
    ON public.shopping_items FOR SELECT
    USING (public.is_member(public.list_collective_id(list_id)));

-- Active members can add items.
CREATE POLICY shopping_items_insert
    ON public.shopping_items FOR INSERT
    WITH CHECK (
        public.is_active_member(public.list_collective_id(list_id))
        AND created_by = auth.uid()
    );

-- Active members can edit items (name, qty, unit, is_checked, sort_order, etc.).
CREATE POLICY shopping_items_update
    ON public.shopping_items FOR UPDATE
    USING  (public.is_active_member(public.list_collective_id(list_id)))
    WITH CHECK (public.is_active_member(public.list_collective_id(list_id)));

-- Active members can hard-delete items (no trash for items).
CREATE POLICY shopping_items_delete
    ON public.shopping_items FOR DELETE
    USING (public.is_active_member(public.list_collective_id(list_id)));

-- ── pg_cron jobs ──────────────────────────────────────────────────────────────
-- Supabase self-hosted ships with pg_cron enabled.
-- These jobs run as the database superuser and bypass RLS — intentional for maintenance.

-- Job 1: Nightly — move 'completed' lists to 'archived' after they have been
-- completed for more than 7 days.
SELECT cron.schedule(
    'archive-completed-lists',
    '0 3 * * *',
    $$
        UPDATE public.shopping_lists
        SET    status = 'archived'
        WHERE  status = 'completed'
        AND    completed_at < now() - INTERVAL '7 days'
        AND    deleted_at IS NULL;
    $$
);

-- Job 2: Nightly — permanently delete lists that have been in the trash for > 7 days.
SELECT cron.schedule(
    'purge-deleted-lists',
    '5 3 * * *',
    $$
        DELETE FROM public.shopping_lists
        WHERE  deleted_at < now() - INTERVAL '7 days';
    $$
);
