-- v1.1.1: Trigger framework — Layout E (design notes §2 + §7).
--
-- A parent `triggers` table holds the common columns (script_id, retry
-- config, dispatch_mode, registered-by principal); per-kind detail
-- tables hold the kind-specific filter columns. v1.1.1 ships two
-- kinds: KV (collection_glob + ops) and dead_letter (source / trigger
-- / script filters). Future kinds (cron, pubsub, queue, email) extend
-- the parent and add their own detail table.
--
-- `registered_by_principal` captures the admin user that registered
-- the trigger. The dispatcher resolves this back to a `Principal` at
-- execution time so the trigger runs as the user that set it up
-- (design notes §4: "a trigger execution runs as the principal that
-- registered the trigger").
--
-- HTTP routes stay in their own `routes` table for now (Phase 3
-- production schema with its own trie-index columns); the dispatcher
-- discriminates HTTP outbox rows by `source_kind = 'http'` and
-- `trigger_id` referencing `routes.id`. Folding routes into triggers
-- is a v1.2 cleanup, not a v1.1.1 requirement.

CREATE TABLE triggers (
    id                       UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    app_id                   UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
    script_id                UUID NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,
    kind                     TEXT NOT NULL CHECK (kind IN ('kv', 'dead_letter')),
    enabled                  BOOLEAN NOT NULL DEFAULT TRUE,
    -- Async by default — sync would mean the trigger fires inline with
    -- the originating mutation, which v1.1.1 doesn't support.
    dispatch_mode            TEXT NOT NULL DEFAULT 'async'
        CHECK (dispatch_mode IN ('sync', 'async')),
    -- Defaults applied at write time so the row is auditable on its
    -- own. Per-trigger overrides set on create; the env-defined
    -- defaults provide the fallback values.
    retry_max_attempts       INT  NOT NULL,
    retry_backoff            TEXT NOT NULL
        CHECK (retry_backoff IN ('exponential', 'linear', 'constant')),
    retry_base_ms            INT  NOT NULL,
    registered_by_principal  UUID NOT NULL REFERENCES admin_users(id) ON DELETE CASCADE,
    created_at               TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at               TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- The dispatcher's hot lookup: "all enabled triggers for app X of
-- kind Y". Indexed only when enabled = TRUE so disabled rows don't
-- pollute the index.
CREATE INDEX idx_triggers_app_kind_enabled
    ON triggers (app_id, kind)
    WHERE enabled = TRUE;

-- One row per KV trigger. `collection_glob` accepts:
--   "*"               — any collection in the app
--   "widgets"         — exact match
--   "users:*"         — prefix wildcard (matched in Rust, not SQL)
-- `ops` is the subset of {insert, update, delete} this trigger
-- subscribes to. Empty array means "any op" (the trigger fires on
-- every mutation; admin endpoint validates this).
CREATE TABLE kv_trigger_details (
    trigger_id        UUID PRIMARY KEY REFERENCES triggers(id) ON DELETE CASCADE,
    collection_glob   TEXT NOT NULL,
    ops               TEXT[] NOT NULL
);

-- One row per dead-letter trigger. All three filter columns are
-- nullable — NULL means "no filter on this dimension". A trigger
-- with all three nullable filters fires on every dead-letter row.
CREATE TABLE dead_letter_trigger_details (
    trigger_id          UUID PRIMARY KEY REFERENCES triggers(id) ON DELETE CASCADE,
    source_filter       TEXT,
    trigger_id_filter   UUID,
    script_id_filter    UUID
);
