-- Database schema for Webstory
-- This schema supports the resources defined in proto/webstory/v1/api.proto

-- Enable UUID generation
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Stories table
CREATE TABLE IF NOT EXISTS stories (
    -- Primary key
    id SERIAL PRIMARY KEY,

    -- Story ID (used in resource name: stories/{story_id})
    story_id VARCHAR(63) NOT NULL,

    -- Title of the story
    title VARCHAR(500) NOT NULL,

    -- Content of the story
    content TEXT,

    -- Description or summary of the story
    description TEXT,

    -- Labels for organizing and categorizing the story
    labels TEXT[],

    -- Timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    -- Etag for concurrency control
    etag VARCHAR(128) NOT NULL,

    -- Constraints
    CONSTRAINT stories_story_id_unique UNIQUE (story_id),
    CONSTRAINT stories_story_id_pattern CHECK (story_id ~ '^[a-z][a-z0-9-]{2,61}$'),
    CONSTRAINT stories_etag_pattern CHECK (char_length(etag) > 0)
);

-- Create index for efficient filtering and sorting
CREATE INDEX IF NOT EXISTS idx_stories_title ON stories (title);
CREATE INDEX IF NOT EXISTS idx_stories_labels ON stories USING GIN (labels);
CREATE INDEX IF NOT EXISTS idx_stories_created_at ON stories (created_at DESC);

-- Scenes table (child of stories)
CREATE TABLE IF NOT EXISTS scenes (
    -- Primary key
    id SERIAL PRIMARY KEY,

    -- Foreign key to stories
    story_id INTEGER NOT NULL REFERENCES stories(id) ON DELETE CASCADE,

    -- Scene ID (used in resource name: stories/{story}/scenes/{scene_id})
    scene_id VARCHAR(63) NOT NULL,

    -- Scene number within the story
    scene_number INTEGER NOT NULL,

    -- Title of the scene
    title VARCHAR(500) NOT NULL,

    -- Content of the scene
    content TEXT,

    -- Description of the scene
    description TEXT,

    -- Timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    -- Etag for concurrency control
    etag VARCHAR(128) NOT NULL,

    -- Constraints
    CONSTRAINT scenes_story_scene_id_unique UNIQUE (story_id, scene_id),
    CONSTRAINT scenes_story_id_exists CHECK (story_id > 0),
    CONSTRAINT scenes_scene_id_pattern CHECK (scene_id ~ '^[a-z][0-9-]{2,61}[0-9]$'),
    CONSTRAINT scenes_scene_number_positive CHECK (scene_number > 0),
    CONSTRAINT scenes_etag_pattern CHECK (char_length(etag) > 0)
);

-- Create index for efficient queries
CREATE INDEX IF NOT EXISTS idx_scenes_story_id ON scenes (story_id);
CREATE INDEX IF NOT EXISTS idx_scenes_scene_number ON scenes (story_id, scene_number);
CREATE INDEX IF NOT EXISTS idx_scenes_title ON scenes (title);

-- Actors table (child of stories)
CREATE TABLE IF NOT EXISTS actors (
    -- Primary key
    id SERIAL PRIMARY KEY,

    -- Foreign key to stories
    story_id INTEGER NOT NULL REFERENCES stories(id) ON DELETE CASCADE,

    -- Actor ID (used in resource name: stories/{story}/actors/{actor_id})
    actor_id VARCHAR(63) NOT NULL,

    -- Name of the actor
    name_value VARCHAR(255) NOT NULL,

    -- Role or character name this actor plays in the story
    role VARCHAR(255),

    -- Optional notes about the actor
    notes TEXT,

    -- Timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    -- Etag for concurrency control
    etag VARCHAR(128) NOT NULL,

    -- Constraints
    CONSTRAINT actors_story_actor_id_unique UNIQUE (story_id, actor_id),
    CONSTRAINT actors_story_id_exists CHECK (story_id > 0),
    CONSTRAINT actors_actor_id_pattern CHECK (actor_id ~ '^[a-z][0-9a-z-]{2,61}[0-9a-z]$'),
    CONSTRAINT actors_etag_pattern CHECK (char_length(etag) > 0)
);

-- Create index for efficient queries
CREATE INDEX IF NOT EXISTS idx_actors_story_id ON actors (story_id);
CREATE INDEX IF NOT EXISTS idx_actors_name_value ON actors (story_id, name_value);
CREATE INDEX IF NOT EXISTS idx_actors_role ON actors (story_id, role);

-- Function to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    -- Update etag when row is modified (simple version using timestamp)
    NEW.etag := md5(NEW.created_at::text || NEW.updated_at::text);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger for stories table
CREATE TRIGGER update_stories_updated_at
    BEFORE UPDATE ON stories
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

-- Trigger for scenes table
CREATE TRIGGER update_scenes_updated_at
    BEFORE UPDATE ON scenes
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

-- Trigger for actors table
CREATE TRIGGER update_actors_updated_at
    BEFORE UPDATE ON actors
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

-- Function to generate new etag (called during inserts)
CREATE OR REPLACE FUNCTION generate_etag()
RETURNS TRIGGER AS $$
BEGIN
    NEW.etag := md5(NEW.id::text || NEW.created_at::text);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger for etag generation on inserts
CREATE TRIGGER generate_stories_etag
    BEFORE INSERT ON stories
    FOR EACH ROW
    EXECUTE FUNCTION generate_etag();

CREATE TRIGGER generate_scenes_etag
    BEFORE INSERT ON scenes
    FOR EACH ROW
    EXECUTE FUNCTION generate_etag();

CREATE TRIGGER generate_actors_etag
    BEFORE INSERT ON actors
    FOR EACH ROW
    EXECUTE FUNCTION generate_etag();

-- Helper function to get stories with label filtering
CREATE OR REPLACE FUNCTION get_stories(
    p_page_size INTEGER DEFAULT 50,
    p_page_token TEXT DEFAULT NULL,
    p_filter TEXT DEFAULT NULL
)
RETURNS TABLE (
    id INTEGER,
    story_id VARCHAR(63),
    title VARCHAR(500),
    content TEXT,
    description TEXT,
    labels TEXT[],
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    etag VARCHAR(128)
) AS $$
DECLARE
    v_page_size INTEGER;
    v_last_id INTEGER;
BEGIN
    -- Validate and limit page size
    v_page_size := LEAST(GREATEST(p_page_size, 1), 1000);

    -- Parse page token to get last ID for pagination
    IF p_page_token IS NOT NULL AND p_page_token != '' THEN
        v_last_id := p_page_token::INTEGER;
    END IF;

    -- Build dynamic query based on filter
    -- Note: This is a simplified version. Production code should handle
    -- filter parsing more robustly
    RETURN QUERY
    SELECT
        s.id,
        s.story_id,
        s.title,
        s.content,
        s.description,
        s.labels,
        s.created_at,
        s.updated_at,
        s.etag
    FROM stories s
    WHERE (v_last_id IS NULL OR s.id > v_last_id)
    ORDER BY s.id
    LIMIT v_page_size;
END;
$$ LANGUAGE plpgsql;
