-- Oracle Canvas Schema — Section 16.4 of the Oracle Architecture Document v1.0
-- Run this against your PostgreSQL database to create the Oracle persistence layer.
-- Requires: UUID extension, JSONB support (PostgreSQL 14+)

-- ── Prerequisites ─────────────────────────────────────────────────────────────

CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- ── Core tables ───────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS oracle_canvas_pages (
    page_id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id         TEXT NOT NULL,
    owner_id          TEXT NOT NULL,
    branch_id         TEXT NOT NULL,
    branch_name       TEXT NOT NULL DEFAULT 'main',
    page_type         TEXT NOT NULL DEFAULT 'main' CHECK (page_type IN ('main', 'fork')),
    title             TEXT NOT NULL DEFAULT 'Untitled Canvas',
    is_shared         BOOLEAN NOT NULL DEFAULT FALSE,
    head_revision     INTEGER NOT NULL DEFAULT 0,
    base_revision     INTEGER NOT NULL DEFAULT 0,
    sharing_policy    JSONB NOT NULL DEFAULT '{"shareMode":"direct_fork_only","allowReshare":false,"defaultForkVisibility":"private"}'::JSONB,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS oracle_canvas_page_revisions (
    revision_id       UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    page_id           UUID NOT NULL REFERENCES oracle_canvas_pages(page_id) ON DELETE CASCADE,
    tenant_id         TEXT NOT NULL,
    revision_number   INTEGER NOT NULL,
    commit_kind       TEXT NOT NULL CHECK (commit_kind IN ('prompt', 'merge', 'rollback', 'manual_edit')),
    commit_summary    TEXT,
    actor_id          TEXT NOT NULL,
    execution_id      UUID,
    merge_request_id  UUID,
    components_snapshot JSONB NOT NULL DEFAULT '[]'::JSONB,
    idempotency_key   TEXT UNIQUE,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE (page_id, revision_number)
);

CREATE TABLE IF NOT EXISTS oracle_canvas_components (
    component_id      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    page_id           UUID NOT NULL REFERENCES oracle_canvas_pages(page_id) ON DELETE CASCADE,
    tenant_id         TEXT NOT NULL,
    type              TEXT NOT NULL,
    title             TEXT NOT NULL,
    description       TEXT,
    version           INTEGER NOT NULL DEFAULT 1,
    lifecycle_state   TEXT NOT NULL DEFAULT 'active' CHECK (lifecycle_state IN ('draft','active','superseded','archived','revoked')),
    data_source_descriptor  JSONB NOT NULL,
    data_rows         JSONB NOT NULL DEFAULT '[]'::JSONB,
    visualization_parameters JSONB NOT NULL DEFAULT '{}'::JSONB,
    data_bindings     JSONB NOT NULL DEFAULT '{}'::JSONB,
    provenance        JSONB NOT NULL,
    rendering_hints   JSONB NOT NULL,
    layout            JSONB NOT NULL,
    access_controls   JSONB NOT NULL,
    style_signature   JSONB NOT NULL DEFAULT '{}'::JSONB,
    validation_state  JSONB NOT NULL DEFAULT '{}'::JSONB,
    audit_log         TEXT[] NOT NULL DEFAULT '{}',
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

ALTER TABLE oracle_canvas_components
    ADD COLUMN IF NOT EXISTS data_rows JSONB NOT NULL DEFAULT '[]'::JSONB;

WITH latest_revisions AS (
    SELECT DISTINCT ON (page_id, tenant_id)
        page_id,
        tenant_id,
        components_snapshot
    FROM oracle_canvas_page_revisions
    ORDER BY page_id, tenant_id, revision_number DESC
),
snapshot_components AS (
    SELECT
        latest_revisions.page_id,
        latest_revisions.tenant_id,
        component->>'componentId' AS component_id,
        COALESCE(component->'dataRows', '[]'::jsonb) AS data_rows
    FROM latest_revisions,
         jsonb_array_elements(latest_revisions.components_snapshot) AS component
)
UPDATE oracle_canvas_components occ
SET data_rows = snapshot_components.data_rows
FROM snapshot_components
WHERE occ.page_id = snapshot_components.page_id
  AND occ.tenant_id = snapshot_components.tenant_id
  AND occ.component_id::text = snapshot_components.component_id
  AND occ.data_rows = '[]'::jsonb
  AND snapshot_components.data_rows <> '[]'::jsonb;

CREATE TABLE IF NOT EXISTS oracle_prompt_executions (
    execution_id      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id         TEXT NOT NULL,
    page_id           UUID NOT NULL REFERENCES oracle_canvas_pages(page_id) ON DELETE CASCADE,
    branch_id         TEXT NOT NULL,
    actor_id          TEXT NOT NULL,
    prompt            TEXT NOT NULL,
    intent_class      TEXT NOT NULL DEFAULT 'analytical',
    status            TEXT NOT NULL DEFAULT 'received',
    model_runtime     TEXT NOT NULL DEFAULT 'nemoclaw_hosted',
    semantic_model_version TEXT NOT NULL DEFAULT 'oracle_semantic_v1',
    retrieval_plan    JSONB,
    visualization_plan JSONB,
    warnings          TEXT[] NOT NULL DEFAULT '{}',
    summary           TEXT,
    components_created TEXT[] NOT NULL DEFAULT '{}',
    client_request_id TEXT UNIQUE,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    completed_at      TIMESTAMPTZ
);

CREATE TABLE IF NOT EXISTS oracle_component_templates (
    template_id       UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id         TEXT NOT NULL,
    name              TEXT NOT NULL,
    category          TEXT NOT NULL,
    status            TEXT NOT NULL DEFAULT 'catalog_active',
    origin            TEXT NOT NULL DEFAULT 'premade',
    version           TEXT NOT NULL DEFAULT '1.0.0',
    accepted_shapes   TEXT[] NOT NULL DEFAULT '{}',
    style_signature   JSONB DEFAULT NULL,
    validation_state  JSONB DEFAULT NULL,
    provenance        JSONB DEFAULT NULL,
    use_count         INTEGER NOT NULL DEFAULT 0,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS oracle_forks (
    fork_id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    source_page_id    UUID NOT NULL REFERENCES oracle_canvas_pages(page_id),
    source_branch_id  TEXT NOT NULL,
    source_revision   INTEGER NOT NULL,
    fork_page_id      UUID NOT NULL REFERENCES oracle_canvas_pages(page_id),
    fork_branch_id    TEXT NOT NULL,
    recipient_user_id TEXT NOT NULL,
    created_by        TEXT NOT NULL,
    status            TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','merged','closed')),
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS oracle_merge_requests (
    merge_request_id  UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id         TEXT NOT NULL,
    source_page_id    UUID NOT NULL REFERENCES oracle_canvas_pages(page_id),
    source_branch_id  TEXT NOT NULL,
    source_head_revision INTEGER NOT NULL,
    target_page_id    UUID NOT NULL REFERENCES oracle_canvas_pages(page_id),
    target_branch_id  TEXT NOT NULL,
    target_base_revision INTEGER NOT NULL,
    title             TEXT NOT NULL,
    description       TEXT,
    status            TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','changes_requested','approved','merged','closed')),
    conflicts         JSONB NOT NULL DEFAULT '[]'::JSONB,
    diff_summary      JSONB DEFAULT NULL,
    resolutions       JSONB DEFAULT NULL,
    created_by        TEXT NOT NULL,
    reviewed_by       TEXT,
    reviewer_comment  TEXT,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS oracle_lineage_records (
    lineage_record_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id         TEXT NOT NULL,
    source_kind       TEXT NOT NULL,
    source_id         TEXT NOT NULL,
    transformation_type TEXT NOT NULL,
    transformation_spec_hash TEXT,
    produced_kind     TEXT NOT NULL,
    produced_id       TEXT NOT NULL,
    policy_snapshot_id TEXT,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS oracle_audit_events (
    audit_event_id    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id         TEXT NOT NULL,
    entity_type       TEXT NOT NULL,
    entity_id         TEXT NOT NULL,
    action            TEXT NOT NULL,
    actor_id          TEXT NOT NULL,
    actor_type        TEXT NOT NULL DEFAULT 'user',
    correlation_id    TEXT NOT NULL,
    execution_id      UUID,
    details           JSONB NOT NULL DEFAULT '{}'::JSONB,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ── Indexes ───────────────────────────────────────────────────────────────────

-- Canvas pages: tenant lookup, branch lookup
CREATE INDEX IF NOT EXISTS idx_oracle_pages_tenant ON oracle_canvas_pages(tenant_id);
CREATE INDEX IF NOT EXISTS idx_oracle_pages_owner ON oracle_canvas_pages(owner_id);
CREATE INDEX IF NOT EXISTS idx_oracle_pages_branch ON oracle_canvas_pages(branch_id);

-- Revisions: page-scoped revision queries
CREATE INDEX IF NOT EXISTS idx_oracle_revisions_page ON oracle_canvas_page_revisions(page_id, revision_number DESC);
CREATE INDEX IF NOT EXISTS idx_oracle_revisions_tenant ON oracle_canvas_page_revisions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_oracle_revisions_execution ON oracle_canvas_page_revisions(execution_id);

-- Components: page-scoped, lifecycle
CREATE INDEX IF NOT EXISTS idx_oracle_components_page ON oracle_canvas_components(page_id, lifecycle_state);
CREATE INDEX IF NOT EXISTS idx_oracle_components_tenant ON oracle_canvas_components(tenant_id);

-- Prompt executions: page/actor lookup
CREATE INDEX IF NOT EXISTS idx_oracle_executions_page ON oracle_prompt_executions(page_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_oracle_executions_actor ON oracle_prompt_executions(actor_id, created_at DESC);

-- Templates: tenant + category + status
CREATE INDEX IF NOT EXISTS idx_oracle_templates_tenant_cat ON oracle_component_templates(tenant_id, category, status);

-- Forks: source and recipient lookup
CREATE INDEX IF NOT EXISTS idx_oracle_forks_source ON oracle_forks(source_page_id);
CREATE INDEX IF NOT EXISTS idx_oracle_forks_recipient ON oracle_forks(recipient_user_id);

-- Merge requests: target/source page, status
CREATE INDEX IF NOT EXISTS idx_oracle_mrs_target ON oracle_merge_requests(target_page_id, status);
CREATE INDEX IF NOT EXISTS idx_oracle_mrs_source ON oracle_merge_requests(source_page_id, status);
CREATE INDEX IF NOT EXISTS idx_oracle_mrs_tenant ON oracle_merge_requests(tenant_id, status);

-- Lineage: source/produced lookups
CREATE INDEX IF NOT EXISTS idx_oracle_lineage_source ON oracle_lineage_records(source_kind, source_id);
CREATE INDEX IF NOT EXISTS idx_oracle_lineage_produced ON oracle_lineage_records(produced_kind, produced_id);
CREATE INDEX IF NOT EXISTS idx_oracle_lineage_tenant ON oracle_lineage_records(tenant_id);

-- Audit: entity lookup, correlation lookup
CREATE INDEX IF NOT EXISTS idx_oracle_audit_entity ON oracle_audit_events(entity_type, entity_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_oracle_audit_correlation ON oracle_audit_events(correlation_id);
CREATE INDEX IF NOT EXISTS idx_oracle_audit_tenant ON oracle_audit_events(tenant_id, created_at DESC);
