-- Sidelinja: Initial database schema
-- Workspaces, kunnskapsgraf, jobbkø, meldinger, mediefiler
--
-- Kjøres mot en tom PostgreSQL-database.
-- Krever: PostgreSQL 15+ med pgcrypto (gen_random_uuid)

BEGIN;

-- ============================================================
-- Extensions
-- ============================================================

CREATE EXTENSION IF NOT EXISTS "pgcrypto";   -- gen_random_uuid()
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";  -- uuid_generate_v5() for deterministiske segment-UUIDs

-- ============================================================
-- 0. Felles: updated_at trigger-funksjon
-- ============================================================
-- Brukes av alle tabeller som trenger automatisk oppdatering av
-- updated_at-kolonnen. Nødvendig for inkrementell synk, cache-
-- invalidering og UI ("sist redigert").

CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = now();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- ============================================================
-- 1. Workspaces
-- ============================================================

CREATE TABLE workspaces (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name        TEXT NOT NULL,
    slug        TEXT UNIQUE NOT NULL,       -- URL-vennlig ID, brukes i filstier og media-ruting
    domain      TEXT UNIQUE,                -- Valgfritt eget domene for RSS/media
    settings    JSONB NOT NULL DEFAULT '{}', -- Workspace-config: Whisper initial_prompt, AI system-prompts
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TRIGGER trg_workspaces_updated_at BEFORE UPDATE ON workspaces
    FOR EACH ROW EXECUTE FUNCTION set_updated_at();

-- ============================================================
-- 2. Brukere (tynn cache fra Authentik)
-- ============================================================

CREATE TABLE users (
    authentik_id  TEXT PRIMARY KEY,
    display_name  TEXT NOT NULL,
    avatar_url    TEXT,
    cached_at     TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TYPE workspace_role AS ENUM ('owner', 'admin', 'member');

CREATE TABLE workspace_members (
    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
    user_id      TEXT NOT NULL REFERENCES users(authentik_id) ON DELETE CASCADE,
    role         workspace_role NOT NULL DEFAULT 'member',
    joined_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (workspace_id, user_id)
);

-- ============================================================
-- 3. Nodes — supertabell for alle grafentiteter
-- ============================================================

CREATE TYPE node_type AS ENUM (
    'tema', 'aktør', 'faktoide', 'episode', 'segment',
    'channel', 'melding', 'meeting'
);

CREATE TABLE nodes (
    id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
    node_type    node_type NOT NULL,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_nodes_workspace ON nodes(workspace_id);
CREATE INDEX idx_nodes_type      ON nodes(workspace_id, node_type);

CREATE TRIGGER trg_nodes_updated_at BEFORE UPDATE ON nodes
    FOR EACH ROW EXECUTE FUNCTION set_updated_at();

-- ============================================================
-- 4. Relasjonstyper (globale, ikke workspace-spesifikke)
-- ============================================================

CREATE TABLE relation_types (
    name        TEXT PRIMARY KEY,
    label       TEXT NOT NULL,
    description TEXT,
    system      BOOLEAN NOT NULL DEFAULT false
);

INSERT INTO relation_types (name, label, description, system) VALUES
    ('MENTIONS',     'Nevner',         'Refererer til denne aktøren/temaet',    true),
    ('ABOUT',        'Handler om',     'Faktoide eller klipp som omhandler',   true),
    ('DISCUSSED_IN', 'Diskutert i',    'Tema diskutert i dette segmentet',     true),
    ('WORKS_FOR',    'Jobber for',     'Person tilknyttet organisasjon',       true),
    ('CONTRADICTS',  'Motsier',        'Står i motsetning til',                true),
    ('PART_OF',      'Del av',         'Tilhører / er underordnet',            true),
    ('AFFECTS_AXIS', 'Påvirker akse',  'Valgomat: spørsmål påvirker akse',    true);

-- ============================================================
-- 5. Graph edges (workspace-scopet med RLS)
-- ============================================================
-- workspace_id er denormalisert fra nodes for å muliggjøre RLS direkte
-- på edges. Uten dette kan en direkte SELECT på graph_edges lekke
-- relasjoner på tvers av workspaces.

CREATE TABLE graph_edges (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    workspace_id  UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
    source_id     UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    target_id     UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    relation_type TEXT NOT NULL REFERENCES relation_types(name),
    context_id    UUID REFERENCES nodes(id) ON DELETE SET NULL,
    confidence    REAL CHECK (confidence BETWEEN 0.0 AND 1.0),
    created_by    TEXT REFERENCES users(authentik_id) ON DELETE SET NULL,
    origin        TEXT NOT NULL DEFAULT 'system',
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    CONSTRAINT no_self_reference CHECK (source_id != target_id),
    CONSTRAINT unique_edge UNIQUE (source_id, target_id, relation_type)
);

CREATE INDEX idx_edges_source    ON graph_edges(source_id);
CREATE INDEX idx_edges_target    ON graph_edges(target_id);
CREATE INDEX idx_edges_relation  ON graph_edges(relation_type);
CREATE INDEX idx_edges_workspace ON graph_edges(workspace_id);

-- ============================================================
-- 6. Detailtabeller — aktører, temaer, episoder, segmenter
-- ============================================================
-- Workspace-tilhørighet arves via FK til nodes.
-- Spørringer filtrerer alltid via JOIN med nodes.

CREATE TABLE actors (
    id   UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    type TEXT  -- 'person', 'organisasjon', etc.
);

CREATE INDEX idx_actors_name ON actors(name);

CREATE TABLE topics (
    id   UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
    name TEXT NOT NULL
);

CREATE INDEX idx_topics_name ON topics(name);

CREATE TABLE episodes (
    id           UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
    title        TEXT NOT NULL,
    published_at TIMESTAMPTZ,
    guid         TEXT UNIQUE NOT NULL  -- RSS <guid>, immutabel etter opprettelse
);

-- Forhindre endring av episode-GUID etter opprettelse.
-- RSS-klienter bruker GUID for å identifisere episoder — endring bryter feeds.
CREATE OR REPLACE FUNCTION prevent_guid_change()
RETURNS TRIGGER AS $$
BEGIN
    IF OLD.guid IS NOT NULL AND NEW.guid != OLD.guid THEN
        RAISE EXCEPTION 'episode guid er immutabel og kan ikke endres etter opprettelse';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_episodes_guid_immutable BEFORE UPDATE ON episodes
    FOR EACH ROW EXECUTE FUNCTION prevent_guid_change();

CREATE TABLE segments (
    id         UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
    episode_id UUID NOT NULL REFERENCES episodes(id) ON DELETE CASCADE,
    start_time INTERVAL NOT NULL,
    end_time   INTERVAL NOT NULL,
    transcript TEXT,
    CONSTRAINT valid_timerange CHECK (end_time > start_time)
);

CREATE INDEX idx_segments_episode ON segments(episode_id);
CREATE INDEX idx_segments_fts     ON segments USING GIN (to_tsvector('norwegian', transcript));

-- ============================================================
-- 7. Channels (chat-kontekster knyttet til vilkårlig node)
-- ============================================================
-- En channel er en meldingsstrøm knyttet til en parent-node.
-- Enhver node (tema, episode, møte, aktør, ...) kan ha én eller flere channels.
-- Config styrer hvilke capabilities kanalen har.

CREATE TABLE channels (
    id        UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
    parent_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    name      TEXT NOT NULL DEFAULT 'Diskusjon',
    config    JSONB NOT NULL DEFAULT '{
        "threads": true,
        "mentions": true,
        "attachments": true,
        "research_clips": false,
        "ttl_days": null
    }'::jsonb
    -- config.threads         — tillat reply-to-tråder
    -- config.mentions        — #/@ mention-parsing + grafkoblinger
    -- config.attachments     — filopplasting
    -- config.research_clips  — research_clip meldingstype
    -- config.ttl_days        — null = permanent, tall = auto-slett etter N dager
);

CREATE INDEX idx_channels_parent ON channels(parent_id);

-- ============================================================
-- 8. Faktoider
-- ============================================================

CREATE TABLE factoids (
    id         UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
    body       TEXT NOT NULL,
    source_url TEXT,
    created_by TEXT REFERENCES users(authentik_id) ON DELETE SET NULL,  -- NULL = slettet bruker
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE factoid_votes (
    factoid_id UUID NOT NULL REFERENCES factoids(id) ON DELETE CASCADE,
    user_id    TEXT NOT NULL REFERENCES users(authentik_id) ON DELETE CASCADE,
    vote       SMALLINT NOT NULL CHECK (vote IN (-1, 1)),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (factoid_id, user_id)
);

-- ============================================================
-- 9. Meldinger (chat)
-- ============================================================

CREATE TYPE message_type AS ENUM (
    'text',            -- Vanlig Markdown-melding
    'research_clip',   -- Innlimt artikkel (Ctrl+A)
    'factoid',         -- Faktoid delt i chat
    'voice_memo',      -- Lydmelding (lyd er master, transkripsjon som metadata)
    'system'           -- Automatisk systemmelding
);

CREATE TABLE messages (
    id           UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
    channel_id   UUID NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
    reply_to     UUID REFERENCES messages(id) ON DELETE SET NULL,
    author_id    TEXT REFERENCES users(authentik_id) ON DELETE SET NULL,  -- NULL = slettet bruker
    message_type message_type NOT NULL DEFAULT 'text',
    body         TEXT NOT NULL,
    metadata     JSONB,
    edited_at    TIMESTAMPTZ,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_messages_channel ON messages(channel_id, created_at);

CREATE TABLE message_revisions (
    id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
    body       TEXT NOT NULL,
    edited_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    CONSTRAINT revision_order UNIQUE (message_id, edited_at)
);

CREATE TABLE message_votes (
    message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
    user_id    TEXT NOT NULL REFERENCES users(authentik_id) ON DELETE CASCADE,
    vote       SMALLINT NOT NULL CHECK (vote IN (-1, 1)),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (message_id, user_id)
);

-- ============================================================
-- 10. Mediefiler (content-addressable, workspace-scopet)
-- ============================================================

CREATE TABLE media_files (
    sha256       TEXT PRIMARY KEY,
    workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
    mime_type    TEXT NOT NULL,
    file_size    BIGINT NOT NULL,
    uploaded_by  TEXT REFERENCES users(authentik_id) ON DELETE SET NULL,  -- NULL = slettet bruker
    uploaded_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_media_workspace ON media_files(workspace_id);

CREATE TABLE message_attachments (
    id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
    sha256     TEXT NOT NULL REFERENCES media_files(sha256),
    sort_order SMALLINT NOT NULL DEFAULT 0
);

-- ============================================================
-- 11. Jobbkø (workspace-scopet)
-- ============================================================

CREATE TYPE job_status AS ENUM ('pending', 'running', 'completed', 'error', 'retry');

CREATE TABLE job_queue (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    workspace_id  UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
    job_type      TEXT NOT NULL,
    payload       JSONB NOT NULL,
    status        job_status NOT NULL DEFAULT 'pending',
    priority      SMALLINT NOT NULL DEFAULT 0,
    result        JSONB,
    error_msg     TEXT,
    attempts      SMALLINT NOT NULL DEFAULT 0,
    max_attempts  SMALLINT NOT NULL DEFAULT 3,
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    started_at    TIMESTAMPTZ,
    completed_at  TIMESTAMPTZ,
    scheduled_for TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_job_queue_pending ON job_queue (priority DESC, scheduled_for ASC)
    WHERE status IN ('pending', 'retry');
CREATE INDEX idx_job_queue_workspace ON job_queue(workspace_id);

-- ============================================================
-- 12. Row-Level Security (Workspace-isolasjon)
-- ============================================================
-- SvelteKit setter session-variabel ved tilkobling:
--   SET app.current_workspace_id = '<uuid>';
--
-- Rust-workers kjører som superuser (bypasser RLS) for å prosessere
-- jobber på tvers av workspaces. Workspace-isolasjon for workers
-- sikres i applikasjonskode (job_queue.workspace_id).
--
-- Detailtabeller (actors, topics, etc.) har ikke egen RLS — de
-- arver workspace-tilhørighet via FK til nodes.
-- Applikasjonskode filtrerer via JOIN med nodes.

ALTER TABLE nodes ENABLE ROW LEVEL SECURITY;
ALTER TABLE graph_edges ENABLE ROW LEVEL SECURITY;
ALTER TABLE media_files ENABLE ROW LEVEL SECURITY;
ALTER TABLE job_queue ENABLE ROW LEVEL SECURITY;

CREATE POLICY workspace_isolation_nodes ON nodes
    USING (workspace_id = current_setting('app.current_workspace_id')::uuid);

CREATE POLICY workspace_isolation_edges ON graph_edges
    USING (workspace_id = current_setting('app.current_workspace_id')::uuid);

CREATE POLICY workspace_isolation_media ON media_files
    USING (workspace_id = current_setting('app.current_workspace_id')::uuid);

CREATE POLICY workspace_isolation_jobs ON job_queue
    USING (workspace_id = current_setting('app.current_workspace_id')::uuid);

COMMIT;
