-- Phase 4b+1 (v1.2 Hierarchies): extension points (§5.5).
--
-- An extension point opts a MODULE NAME into dynamic, per-tenant resolution.
-- Normally a group/parent script's `import "x"` resolves sealed-lexically
-- against the importer's own defining node — a leaf app cannot shadow it
-- (the trust boundary). When a node declares `x` an extension point, an
-- importer whose defining node is on that node's chain instead resolves `x`
-- against the INHERITING app's effective view, so each descendant app may
-- supply its own `x`. Only the declaring (parent) node can open the hole; a
-- descendant cannot make one of its parent's sealed imports dynamic, because
-- the declaration must live on the importer's own ancestry.
--
-- Owner-polymorphic exactly like vars (0048): exactly one of group_id/app_id.
-- Optional `default_script_id` is the fallback module body used when the
-- inheriting app provides no module of `name`.

CREATE TABLE extension_points (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    -- Polymorphic owner: exactly one FK set (real FKs so ON DELETE CASCADE
    -- works per owner kind and a dangling owner is impossible).
    group_id          UUID REFERENCES groups(id) ON DELETE CASCADE,
    app_id            UUID REFERENCES apps(id) ON DELETE CASCADE,
    CONSTRAINT extension_points_owner_exactly_one
        CHECK ((group_id IS NULL) <> (app_id IS NULL)),
    name              TEXT NOT NULL,
    -- Optional fallback module (a module owned by the declaring node). SET
    -- NULL on delete so deleting the module can never block — the fallback is
    -- then absent and a non-providing app errors at import time (the next plan
    -- shows the now-null default as an Update). A valid manifest apply can't
    -- reach this: validation rejects a kept ext point whose default names a
    -- module not in the bundle.
    default_script_id UUID REFERENCES scripts(id) ON DELETE SET NULL,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- One declaration per (owner, name), case-insensitive to match the module
-- name indexes (0050). Partial because the owner is split across two columns.
CREATE UNIQUE INDEX extension_points_group_uidx
    ON extension_points (group_id, LOWER(name)) WHERE group_id IS NOT NULL;
CREATE UNIQUE INDEX extension_points_app_uidx
    ON extension_points (app_id, LOWER(name)) WHERE app_id IS NOT NULL;

CREATE INDEX extension_points_group_id_idx
    ON extension_points (group_id) WHERE group_id IS NOT NULL;
CREATE INDEX extension_points_app_id_idx
    ON extension_points (app_id) WHERE app_id IS NOT NULL;
CREATE INDEX extension_points_default_script_idx
    ON extension_points (default_script_id) WHERE default_script_id IS NOT NULL;
