-- Chatbot backend support, milestone M1: chatbot tables.
-- See plans/chatbot_plan_codex.v10.md §4 (Data Model) for the canonical
-- definition. Existing tables retain camelCase column names (per migrations
-- 1-130); new bot_* tables use snake_case to match migrations 127-130.
--
-- Tables created in dependency order:
--   1. bot_sessions      — owner-scoped chatbot conversations
--   2. bot_session_documents  — explicit per-session document scope
--   3. bot_session_folders    — explicit per-session folder scope
--   4. bot_turns          — append-only turn log per session

-- ---------- bot_sessions ----------

CREATE TABLE bot_sessions (
    id          uuid         NOT NULL DEFAULT uuid_generate_v7(),
    client_id   varchar(255) NOT NULL REFERENCES clients(clientId) ON DELETE CASCADE,
    created_by  varchar(255) NOT NULL,
    created_at  timestamptz  NOT NULL DEFAULT NOW(),
    updated_at  timestamptz  NOT NULL DEFAULT NOW(),
    title       text         NOT NULL DEFAULT '',
    state       jsonb        NOT NULL DEFAULT '{}'::jsonb,
    is_deleted  boolean      NOT NULL DEFAULT false,
    CONSTRAINT pk_bot_sessions PRIMARY KEY (id)
);

CREATE INDEX idx_bot_sessions_client_user_active
    ON bot_sessions (client_id, created_by, updated_at DESC)
    WHERE is_deleted = false;

CREATE INDEX idx_bot_sessions_client_active
    ON bot_sessions (client_id, updated_at DESC)
    WHERE is_deleted = false;

-- ---------- bot_session_documents (scope) ----------

CREATE TABLE bot_session_documents (
    session_id  uuid NOT NULL REFERENCES bot_sessions(id) ON DELETE CASCADE,
    document_id uuid NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
    CONSTRAINT pk_bot_session_documents PRIMARY KEY (session_id, document_id)
);

CREATE INDEX idx_bsd_document ON bot_session_documents(document_id);

-- ---------- bot_session_folders (scope) ----------

CREATE TABLE bot_session_folders (
    session_id uuid NOT NULL REFERENCES bot_sessions(id) ON DELETE CASCADE,
    folder_id  uuid NOT NULL REFERENCES folders(id) ON DELETE CASCADE,
    CONSTRAINT pk_bot_session_folders PRIMARY KEY (session_id, folder_id)
);

CREATE INDEX idx_bsf_folder ON bot_session_folders(folder_id);

-- ---------- bot_turns ----------
--
-- bot_turns_status_fields_consistent groups session_deleted with errored
-- and abandoned: completion IS NULL AND error IS NOT NULL. This matches
-- plan §4 verbatim.

CREATE TABLE bot_turns (
    session_id          uuid        NOT NULL REFERENCES bot_sessions(id) ON DELETE CASCADE,
    ordinal             int         NOT NULL CHECK (ordinal > 0),
    prompt              text        NOT NULL,
    completion          text,
    status              text        NOT NULL DEFAULT 'in_flight',
    attempt_id          uuid        NOT NULL,
    attempt_started_at  timestamptz NOT NULL DEFAULT NOW(),
    created_at          timestamptz NOT NULL DEFAULT NOW(),
    latency_ms          int,
    tokens_in           int,
    tokens_out          int,
    error               jsonb,
    CONSTRAINT pk_bot_turns PRIMARY KEY (session_id, ordinal),
    CONSTRAINT bot_turns_status_values CHECK (
        status IN ('in_flight', 'completed', 'errored', 'abandoned', 'session_deleted')
    ),
    -- bot_turns_status_fields_consistent enforces the (status, completion,
    -- error) consistency matrix from plan §4. The trailing OR-clause
    -- (`status NOT IN (...)`) is an evaluation-order guard, not a relaxation
    -- of the rule: Postgres evaluates table-level CHECK constraints in
    -- alphabetical order by constraint name, so without this guard a row
    -- with an UNKNOWN status (e.g. 'frobnicated') trips this constraint
    -- before bot_turns_status_values gets a chance to reject the unknown
    -- enum value. With the guard, unknown statuses pass this consistency
    -- check (every branch becomes irrelevant) and bot_turns_status_values
    -- owns the rejection. For every documented status value the plan §4
    -- literal still holds verbatim.
    CONSTRAINT bot_turns_status_fields_consistent CHECK (
        (status = 'in_flight' AND completion IS NULL AND error IS NULL)
        OR (status = 'completed' AND completion IS NOT NULL AND error IS NULL)
        OR (status IN ('errored', 'abandoned', 'session_deleted') AND completion IS NULL AND error IS NOT NULL)
        OR status NOT IN ('in_flight', 'completed', 'errored', 'abandoned', 'session_deleted')
    )
);

CREATE UNIQUE INDEX bot_turns_one_inflight_per_session
    ON bot_turns (session_id)
    WHERE status = 'in_flight';

CREATE INDEX idx_bot_turns_session_completed
    ON bot_turns (session_id, ordinal)
    WHERE status = 'completed';
