-- Polymodel initial SQLite projection schema (consolidated baseline).
--
-- Single non-destructive baseline combining the former 001–005 migrations into
-- their final form: record_json on content tables, persisted CIDs on social
-- records, owner-scoped upload staging, and read-your-writes pending-ops. Fresh
-- databases get the complete schema in one step, and future schema changes add
-- new migration files rather than rewriting tables, so projected data is never
-- destroyed by a migration.
--
-- WAL mode is intentionally NOT set here: sqlx runs migrations inside a
-- transaction and SQLite silently ignores journal_mode changes within one. WAL
-- is enabled programmatically on the connection pool (src/indexing/db.rs).

-- Projection state (durable cursor for crash recovery)
CREATE TABLE projection_state (
    key TEXT PRIMARY KEY,
    value TEXT NOT NULL,
    updated_at INTEGER NOT NULL
);

-- Content records (space.polymodel.library.*). record_json holds the raw ATProto
-- record so read endpoints and eager read-your-own-writes resolve from SQLite.
CREATE TABLE things (
    did TEXT NOT NULL,
    rkey TEXT NOT NULL,
    uri TEXT NOT NULL UNIQUE,
    cid TEXT NOT NULL,
    name TEXT NOT NULL,
    summary TEXT,
    license TEXT NOT NULL,
    tags_json TEXT,
    tags_text TEXT,
    instructions_text TEXT,
    cover_json TEXT,
    derived_from_uri TEXT,
    record_json TEXT,
    created_at INTEGER NOT NULL,
    indexed_at INTEGER NOT NULL,
    PRIMARY KEY (did, rkey)
);
CREATE INDEX idx_things_did ON things(did, created_at DESC);
CREATE INDEX idx_things_created ON things(created_at DESC);

CREATE TABLE models (
    did TEXT NOT NULL,
    rkey TEXT NOT NULL,
    uri TEXT NOT NULL UNIQUE,
    cid TEXT NOT NULL,
    name TEXT NOT NULL,
    summary TEXT,
    record_json TEXT,
    created_at INTEGER NOT NULL,
    indexed_at INTEGER NOT NULL,
    PRIMARY KEY (did, rkey)
);

CREATE TABLE parts (
    did TEXT NOT NULL,
    rkey TEXT NOT NULL,
    uri TEXT NOT NULL UNIQUE,
    cid TEXT NOT NULL,
    name TEXT NOT NULL,
    format TEXT,
    file_json TEXT NOT NULL,
    record_json TEXT,
    created_at INTEGER NOT NULL,
    indexed_at INTEGER NOT NULL,
    PRIMARY KEY (did, rkey)
);

-- Junction tables (relationship lookups + ordering)
CREATE TABLE thing_models (
    thing_uri TEXT NOT NULL,
    model_uri TEXT NOT NULL,
    position INTEGER NOT NULL,
    PRIMARY KEY (thing_uri, model_uri)
);
CREATE INDEX idx_thing_models_model ON thing_models(model_uri);
CREATE INDEX idx_thing_models_thing_pos ON thing_models(thing_uri, position);

CREATE TABLE model_parts (
    model_uri TEXT NOT NULL,
    part_uri TEXT NOT NULL,
    position INTEGER NOT NULL,
    PRIMARY KEY (model_uri, part_uri)
);
CREATE INDEX idx_model_parts_part ON model_parts(part_uri);
CREATE INDEX idx_model_parts_model_pos ON model_parts(model_uri, position);

-- Social record projections (space.polymodel.graph.*), with persisted record CIDs.
CREATE TABLE likes (
    did TEXT NOT NULL,
    rkey TEXT NOT NULL,
    cid TEXT NOT NULL,
    subject_uri TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    PRIMARY KEY (did, rkey),
    UNIQUE (did, subject_uri)
);
CREATE INDEX idx_likes_subject ON likes(subject_uri);

CREATE TABLE saves (
    did TEXT NOT NULL,
    rkey TEXT NOT NULL,
    cid TEXT NOT NULL,
    subject_uri TEXT NOT NULL,
    note TEXT,
    created_at INTEGER NOT NULL,
    PRIMARY KEY (did, rkey),
    UNIQUE (did, subject_uri)
);
CREATE INDEX idx_saves_subject ON saves(subject_uri);

CREATE TABLE tags (
    did TEXT NOT NULL,
    rkey TEXT NOT NULL,
    cid TEXT NOT NULL,
    subject_uri TEXT NOT NULL,
    tag TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    PRIMARY KEY (did, rkey)
);
CREATE INDEX idx_tags_subject ON tags(subject_uri);
CREATE INDEX idx_tags_tag ON tags(tag);

CREATE TABLE listitems (
    did TEXT NOT NULL,
    rkey TEXT NOT NULL,
    cid TEXT NOT NULL,
    list_uri TEXT NOT NULL,
    subject_uri TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    PRIMARY KEY (did, rkey)
);
CREATE INDEX idx_listitems_list ON listitems(list_uri, created_at);

-- Denormalized engagement counts
CREATE TABLE content_stats (
    uri TEXT PRIMARY KEY,
    like_count INTEGER NOT NULL DEFAULT 0,
    save_count INTEGER NOT NULL DEFAULT 0,
    tag_count INTEGER NOT NULL DEFAULT 0
);

-- Full-text search (FTS5, external content) over things
CREATE VIRTUAL TABLE things_fts USING fts5(
    name, summary, tags_text, instructions_text,
    content=things, content_rowid=rowid,
    tokenize='porter unicode61'
);

CREATE TRIGGER things_ai AFTER INSERT ON things BEGIN
    INSERT INTO things_fts(rowid, name, summary, tags_text, instructions_text)
    VALUES (new.rowid, new.name, new.summary, new.tags_text, new.instructions_text);
END;
CREATE TRIGGER things_ad AFTER DELETE ON things BEGIN
    INSERT INTO things_fts(things_fts, rowid, name, summary, tags_text, instructions_text)
    VALUES ('delete', old.rowid, old.name, old.summary, old.tags_text, old.instructions_text);
END;
CREATE TRIGGER things_au AFTER UPDATE ON things BEGIN
    INSERT INTO things_fts(things_fts, rowid, name, summary, tags_text, instructions_text)
    VALUES ('delete', old.rowid, old.name, old.summary, old.tags_text, old.instructions_text);
    INSERT INTO things_fts(rowid, name, summary, tags_text, instructions_text)
    VALUES (new.rowid, new.name, new.summary, new.tags_text, new.instructions_text);
END;

-- Polymodel actor profile (singleton record, rkey = "self"). Handle is not stored
-- here: it lives in `identities` (identity-level) so it stays current across
-- handle changes.
CREATE TABLE profiles (
    did TEXT PRIMARY KEY,
    display_name TEXT,
    description TEXT,
    avatar_json TEXT,
    default_license TEXT,
    pronouns TEXT,
    printers_json TEXT,
    links_json TEXT,
    record_json TEXT NOT NULL,
    indexed_at INTEGER NOT NULL
);

-- DID -> handle, refreshed by identity events. Joined by profile/actor hydration.
CREATE TABLE identities (
    did TEXT PRIMARY KEY,
    handle TEXT,
    updated_at INTEGER NOT NULL
);

-- OAuth session persistence (DB-backed ClientAuthStore for Jacquard 0.12 OAuth).
-- Structured columns power key enumeration / by-DID selection / deletes without
-- deserializing payload; payload holds the canonical ClientSessionData JSON.
CREATE TABLE oauth_sessions (
    account_did TEXT NOT NULL,
    session_id TEXT NOT NULL,
    host_url TEXT NOT NULL,
    authserver_url TEXT NOT NULL,
    token_endpoint TEXT NOT NULL,
    revocation_endpoint TEXT,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL,
    payload TEXT NOT NULL,
    PRIMARY KEY (account_did, session_id)
);
CREATE INDEX idx_oauth_sessions_did ON oauth_sessions(account_did);

-- Pending OAuth authorization requests (PAR state round-trip).
CREATE TABLE oauth_auth_requests (
    state TEXT NOT NULL,
    authserver_url TEXT NOT NULL,
    account_did TEXT,
    request_uri TEXT NOT NULL,
    token_endpoint TEXT NOT NULL,
    revocation_endpoint TEXT,
    created_at INTEGER NOT NULL,
    payload TEXT NOT NULL,
    PRIMARY KEY (state)
);
CREATE INDEX idx_oauth_auth_req_did ON oauth_auth_requests(account_did);

-- Owner-scoped upload staging for raw geometry bytes (publish flow).
CREATE TABLE upload_staging (
    owner_did TEXT NOT NULL,
    upload_id TEXT NOT NULL,
    sha256 TEXT NOT NULL,
    mime_type TEXT NOT NULL,
    size INTEGER NOT NULL,
    filename TEXT,
    status TEXT NOT NULL,
    file_json TEXT NOT NULL,
    chunks_json TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL,
    PRIMARY KEY (owner_did, upload_id)
);
CREATE INDEX idx_upload_staging_owner_updated
    ON upload_staging(owner_did, updated_at DESC);

-- Read-your-writes pending-ops tracking. Collection-agnostic; keyed by record
-- URI. Cleared by the firehose record-event projection once the Hydrant #commit
-- redelivers the event.
CREATE TABLE pending_writes (
    uri TEXT PRIMARY KEY,
    cid TEXT NOT NULL,
    value TEXT NOT NULL,
    written_at INTEGER NOT NULL
);

CREATE TABLE pending_deletes (
    uri TEXT PRIMARY KEY,
    deleted_at INTEGER NOT NULL
);
