-- =============================================================================
-- Life OS - Release 1 COMPLETE Schema
-- Self-hosted PostgreSQL 16 on defiant-01 (Hetzner)
-- Database: lifeos_dev
-- Generated from Architecture Design Document v2.0
-- =============================================================================

-- Extensions
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- =============================================================================
-- LOOKUP TABLE: Context Types
-- =============================================================================

CREATE TABLE context_types (
    id              SERIAL          PRIMARY KEY,
    value           TEXT            NOT NULL UNIQUE,
    label           TEXT            NOT NULL,
    description     TEXT,
    is_system       BOOLEAN         NOT NULL DEFAULT true,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- =============================================================================
-- CORE HIERARCHY
-- =============================================================================

CREATE TABLE domains (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    name            TEXT            NOT NULL,
    color           TEXT,
    description     TEXT,
    icon            TEXT,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE areas (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    domain_id       UUID            NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
    name            TEXT            NOT NULL,
    description     TEXT,
    icon            TEXT,
    color           TEXT,
    status          TEXT            NOT NULL DEFAULT 'active',
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE projects (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    domain_id       UUID            NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
    area_id         UUID            REFERENCES areas(id) ON DELETE SET NULL,
    name            TEXT            NOT NULL,
    description     TEXT,
    status          TEXT            NOT NULL DEFAULT 'active',
    priority        INTEGER         NOT NULL DEFAULT 3,
    start_date      DATE,
    target_date     DATE,
    completed_at    TIMESTAMPTZ,
    color           TEXT,
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- Forward-declare releases for tasks.release_id FK
CREATE TABLE releases (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    name            TEXT            NOT NULL,
    version_label   TEXT,
    description     TEXT,
    status          TEXT            NOT NULL DEFAULT 'planned',
    target_date     DATE,
    released_at     DATE,
    release_notes   TEXT,
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- Forward-declare contacts for tasks.waiting_for_contact_id FK
CREATE TABLE contacts (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    first_name      TEXT            NOT NULL,
    last_name       TEXT,
    company         TEXT,
    role            TEXT,
    email           TEXT,
    phone           TEXT,
    notes           TEXT,
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE tasks (
    id                      UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    domain_id               UUID        REFERENCES domains(id) ON DELETE CASCADE,
    area_id                 UUID        REFERENCES areas(id) ON DELETE SET NULL,
    project_id              UUID        REFERENCES projects(id) ON DELETE SET NULL,
    release_id              UUID        REFERENCES releases(id) ON DELETE SET NULL,
    parent_id               UUID        REFERENCES tasks(id) ON DELETE SET NULL,
    title                   TEXT        NOT NULL,
    description             TEXT,
    priority                INTEGER     NOT NULL DEFAULT 3,
    status                  TEXT        NOT NULL DEFAULT 'open',
    due_date                DATE,
    deadline                TIMESTAMPTZ,
    recurrence              TEXT,
    estimated_minutes       INTEGER,
    energy_required         TEXT,
    context                 TEXT,
    is_custom_context       BOOLEAN     NOT NULL DEFAULT false,
    waiting_for_contact_id  UUID        REFERENCES contacts(id) ON DELETE SET NULL,
    waiting_since           DATE,
    import_batch_id         UUID,
    tags                    TEXT[],
    sort_order              INTEGER     NOT NULL DEFAULT 0,
    is_deleted              BOOLEAN     NOT NULL DEFAULT false,
    deleted_at              TIMESTAMPTZ,
    completed_at            TIMESTAMPTZ,
    search_vector           TSVECTOR,
    created_at              TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at              TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- =============================================================================
-- KNOWLEDGE MANAGEMENT
-- =============================================================================

CREATE TABLE note_folders (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    parent_id       UUID            REFERENCES note_folders(id) ON DELETE CASCADE,
    name            TEXT            NOT NULL,
    auto_generated  BOOLEAN         NOT NULL DEFAULT false,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- Forward-declare meetings for notes.meeting_id FK
CREATE TABLE meetings (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    parent_id       UUID            REFERENCES meetings(id) ON DELETE SET NULL,
    title           TEXT            NOT NULL,
    meeting_date    DATE            NOT NULL,
    start_at        TIMESTAMPTZ,
    end_at          TIMESTAMPTZ,
    location        TEXT,
    status          TEXT            NOT NULL DEFAULT 'scheduled',
    priority        INTEGER,
    recurrence      TEXT,
    agenda          TEXT,
    transcript      TEXT,
    notes_body      TEXT,
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE notes (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    domain_id       UUID            REFERENCES domains(id) ON DELETE CASCADE,
    project_id      UUID            REFERENCES projects(id) ON DELETE SET NULL,
    folder_id       UUID            REFERENCES note_folders(id) ON DELETE SET NULL,
    meeting_id      UUID            REFERENCES meetings(id) ON DELETE SET NULL,
    title           TEXT            NOT NULL,
    body            TEXT,
    content_format  TEXT            NOT NULL DEFAULT 'rich',
    is_meeting_note BOOLEAN         NOT NULL DEFAULT false,
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE decisions (
    id                  UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    title               TEXT        NOT NULL,
    rationale           TEXT,
    status              TEXT        NOT NULL DEFAULT 'proposed',
    impact              TEXT        NOT NULL DEFAULT 'medium',
    decided_at          DATE,
    meeting_id          UUID        REFERENCES meetings(id) ON DELETE SET NULL,
    superseded_by_id    UUID        REFERENCES decisions(id) ON DELETE SET NULL,
    tags                TEXT[],
    sort_order          INTEGER     NOT NULL DEFAULT 0,
    is_deleted          BOOLEAN     NOT NULL DEFAULT false,
    deleted_at          TIMESTAMPTZ,
    search_vector       TSVECTOR,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE lists (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    domain_id       UUID            REFERENCES domains(id) ON DELETE CASCADE,
    area_id         UUID            REFERENCES areas(id) ON DELETE SET NULL,
    project_id      UUID            REFERENCES projects(id) ON DELETE SET NULL,
    name            TEXT            NOT NULL,
    list_type       TEXT            NOT NULL DEFAULT 'checklist',
    description     TEXT,
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE list_items (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    list_id         UUID            NOT NULL REFERENCES lists(id) ON DELETE CASCADE,
    parent_item_id  UUID            REFERENCES list_items(id) ON DELETE SET NULL,
    content         TEXT            NOT NULL,
    completed       BOOLEAN         NOT NULL DEFAULT false,
    completed_at    TIMESTAMPTZ,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE links (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    domain_id       UUID            REFERENCES domains(id) ON DELETE CASCADE,
    area_id         UUID            REFERENCES areas(id) ON DELETE SET NULL,
    project_id      UUID            REFERENCES projects(id) ON DELETE SET NULL,
    label           TEXT            NOT NULL,
    url             TEXT            NOT NULL,
    description     TEXT,
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE files (
    id                  UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    filename            TEXT        NOT NULL,
    original_filename   TEXT        NOT NULL,
    storage_path        TEXT        NOT NULL,
    mime_type           TEXT,
    size_bytes          INTEGER,
    description         TEXT,
    tags                TEXT[],
    sort_order          INTEGER     NOT NULL DEFAULT 0,
    is_deleted          BOOLEAN     NOT NULL DEFAULT false,
    deleted_at          TIMESTAMPTZ,
    search_vector       TSVECTOR,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- =============================================================================
-- SYSTEM LEVEL: Appointments
-- =============================================================================

CREATE TABLE appointments (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    title           TEXT            NOT NULL,
    description     TEXT,
    location        TEXT,
    start_at        TIMESTAMPTZ     NOT NULL,
    end_at          TIMESTAMPTZ,
    all_day         BOOLEAN         NOT NULL DEFAULT false,
    recurrence      TEXT,
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- =============================================================================
-- SYSTEM LEVEL: Milestones
-- =============================================================================

CREATE TABLE milestones (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    release_id      UUID            REFERENCES releases(id) ON DELETE SET NULL,
    project_id      UUID            REFERENCES projects(id) ON DELETE SET NULL,
    name            TEXT            NOT NULL,
    target_date     DATE            NOT NULL,
    completed_at    DATE,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- =============================================================================
-- SYSTEM LEVEL: Processes
-- =============================================================================

CREATE TABLE processes (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    name            TEXT            NOT NULL,
    description     TEXT,
    process_type    TEXT            NOT NULL DEFAULT 'checklist',
    category        TEXT,
    status          TEXT            NOT NULL DEFAULT 'draft',
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE process_steps (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    process_id      UUID            NOT NULL REFERENCES processes(id) ON DELETE CASCADE,
    title           TEXT            NOT NULL,
    instructions    TEXT,
    expected_output TEXT,
    estimated_days  INTEGER,
    context         TEXT,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE process_runs (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    process_id      UUID            NOT NULL REFERENCES processes(id) ON DELETE CASCADE,
    title           TEXT            NOT NULL,
    status          TEXT            NOT NULL DEFAULT 'not_started',
    process_type    TEXT            NOT NULL,
    task_generation TEXT            NOT NULL DEFAULT 'all_at_once',
    project_id      UUID            REFERENCES projects(id) ON DELETE SET NULL,
    contact_id      UUID            REFERENCES contacts(id) ON DELETE SET NULL,
    started_at      TIMESTAMPTZ,
    completed_at    TIMESTAMPTZ,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE process_run_steps (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    run_id          UUID            NOT NULL REFERENCES process_runs(id) ON DELETE CASCADE,
    title           TEXT            NOT NULL,
    instructions    TEXT,
    status          TEXT            NOT NULL DEFAULT 'pending',
    completed_by_id UUID            REFERENCES contacts(id) ON DELETE SET NULL,
    completed_at    TIMESTAMPTZ,
    notes           TEXT,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- =============================================================================
-- SYSTEM LEVEL: Daily Focus
-- =============================================================================

CREATE TABLE daily_focus (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    focus_date      DATE            NOT NULL,
    task_id         UUID            NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    slot            INTEGER,
    completed       BOOLEAN         NOT NULL DEFAULT false,
    note            TEXT,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- =============================================================================
-- SYSTEM LEVEL: Capture Queue
-- =============================================================================

CREATE TABLE capture (
    id                  UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    raw_text            TEXT        NOT NULL,
    processed           BOOLEAN     NOT NULL DEFAULT false,
    converted_to_type   TEXT,
    converted_to_id     UUID,
    area_id             UUID        REFERENCES areas(id) ON DELETE SET NULL,
    project_id          UUID        REFERENCES projects(id) ON DELETE SET NULL,
    list_id             UUID        REFERENCES lists(id) ON DELETE SET NULL,
    import_batch_id     UUID,
    sort_order          INTEGER     NOT NULL DEFAULT 0,
    is_deleted          BOOLEAN     NOT NULL DEFAULT false,
    deleted_at          TIMESTAMPTZ,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- =============================================================================
-- SYSTEM LEVEL: Task Templates
-- =============================================================================

CREATE TABLE task_templates (
    id                  UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    name                TEXT        NOT NULL,
    description         TEXT,
    priority            INTEGER,
    estimated_minutes   INTEGER,
    energy_required     TEXT,
    context             TEXT,
    tags                TEXT[],
    sort_order          INTEGER     NOT NULL DEFAULT 0,
    is_deleted          BOOLEAN     NOT NULL DEFAULT false,
    deleted_at          TIMESTAMPTZ,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE task_template_items (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    template_id     UUID            NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
    title           TEXT            NOT NULL,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- =============================================================================
-- TIME MANAGEMENT
-- =============================================================================

CREATE TABLE time_entries (
    id                  UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    task_id             UUID        NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    start_at            TIMESTAMPTZ NOT NULL,
    end_at              TIMESTAMPTZ,
    duration_minutes    INTEGER,
    notes               TEXT,
    is_deleted          BOOLEAN     NOT NULL DEFAULT false,
    deleted_at          TIMESTAMPTZ,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE time_blocks (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    task_id         UUID            REFERENCES tasks(id) ON DELETE SET NULL,
    title           TEXT            NOT NULL,
    context         TEXT,
    energy          TEXT,
    start_at        TIMESTAMPTZ     NOT NULL,
    end_at          TIMESTAMPTZ     NOT NULL,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE time_budgets (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    domain_id       UUID            NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
    weekly_hours    DECIMAL         NOT NULL,
    effective_from  DATE            NOT NULL,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- =============================================================================
-- SYSTEM LEVEL: Weblink Directory
-- =============================================================================

CREATE TABLE weblink_folders (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    parent_id       UUID            REFERENCES weblink_folders(id) ON DELETE CASCADE,
    name            TEXT            NOT NULL,
    auto_generated  BOOLEAN         NOT NULL DEFAULT false,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

CREATE TABLE weblinks (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    label           TEXT            NOT NULL,
    url             TEXT            NOT NULL,
    description     TEXT,
    tags            TEXT[],
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    search_vector   TSVECTOR,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- =============================================================================
-- SYSTEM LEVEL: Reminders (polymorphic)
-- =============================================================================

CREATE TABLE reminders (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    entity_type     TEXT            NOT NULL,
    entity_id       UUID            NOT NULL,
    remind_at       TIMESTAMPTZ     NOT NULL,
    note            TEXT,
    delivered       BOOLEAN         NOT NULL DEFAULT false,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now()
);

-- =============================================================================
-- UNIVERSAL: Dependencies (polymorphic DAG)
-- =============================================================================

CREATE TABLE dependencies (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    blocker_type    TEXT            NOT NULL,
    blocker_id      UUID            NOT NULL,
    dependent_type  TEXT            NOT NULL,
    dependent_id    UUID            NOT NULL,
    dependency_type TEXT            NOT NULL DEFAULT 'finish_to_start',
    lag_days        INTEGER         NOT NULL DEFAULT 0,
    note            TEXT,
    is_deleted      BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    UNIQUE (blocker_type, blocker_id, dependent_type, dependent_id, dependency_type),
    CHECK (NOT (blocker_type = dependent_type AND blocker_id = dependent_id))
);

-- =============================================================================
-- JUNCTION TABLES
-- =============================================================================

-- Notes <-> Projects (M2M)
CREATE TABLE note_projects (
    note_id         UUID            NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
    project_id      UUID            NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    is_primary      BOOLEAN         NOT NULL DEFAULT false,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (note_id, project_id)
);

-- Notes <-> Notes (wiki graph)
CREATE TABLE note_links (
    source_note_id  UUID            NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
    target_note_id  UUID            NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (source_note_id, target_note_id)
);

-- Files <-> any entity (polymorphic M2M)
CREATE TABLE file_mappings (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    file_id         UUID            NOT NULL REFERENCES files(id) ON DELETE CASCADE,
    context_type    TEXT            NOT NULL,
    context_id      UUID            NOT NULL,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    UNIQUE (file_id, context_type, context_id)
);

-- Releases <-> Projects (M2M)
CREATE TABLE release_projects (
    release_id      UUID            NOT NULL REFERENCES releases(id) ON DELETE CASCADE,
    project_id      UUID            NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (release_id, project_id)
);

-- Releases <-> Domains (M2M)
CREATE TABLE release_domains (
    release_id      UUID            NOT NULL REFERENCES releases(id) ON DELETE CASCADE,
    domain_id       UUID            NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (release_id, domain_id)
);

-- Contacts <-> Tasks
CREATE TABLE contact_tasks (
    contact_id      UUID            NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    task_id         UUID            NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    role            TEXT,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (contact_id, task_id)
);

-- Contacts <-> Projects
CREATE TABLE contact_projects (
    contact_id      UUID            NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    project_id      UUID            NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    role            TEXT,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (contact_id, project_id)
);

-- Contacts <-> Lists
CREATE TABLE contact_lists (
    contact_id      UUID            NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    list_id         UUID            NOT NULL REFERENCES lists(id) ON DELETE CASCADE,
    role            TEXT,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (contact_id, list_id)
);

-- Contacts <-> List Items
CREATE TABLE contact_list_items (
    contact_id      UUID            NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    list_item_id    UUID            NOT NULL REFERENCES list_items(id) ON DELETE CASCADE,
    role            TEXT,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (contact_id, list_item_id)
);

-- Contacts <-> Appointments
CREATE TABLE contact_appointments (
    contact_id      UUID            NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    appointment_id  UUID            NOT NULL REFERENCES appointments(id) ON DELETE CASCADE,
    role            TEXT,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (contact_id, appointment_id)
);

-- Contacts <-> Meetings
CREATE TABLE contact_meetings (
    contact_id      UUID            NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    meeting_id      UUID            NOT NULL REFERENCES meetings(id) ON DELETE CASCADE,
    role            TEXT,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (contact_id, meeting_id)
);

-- Decisions <-> Projects
CREATE TABLE decision_projects (
    decision_id     UUID            NOT NULL REFERENCES decisions(id) ON DELETE CASCADE,
    project_id      UUID            NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (decision_id, project_id)
);

-- Decisions <-> Contacts
CREATE TABLE decision_contacts (
    decision_id     UUID            NOT NULL REFERENCES decisions(id) ON DELETE CASCADE,
    contact_id      UUID            NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    role            TEXT,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (decision_id, contact_id)
);

-- Meetings <-> Tasks
CREATE TABLE meeting_tasks (
    meeting_id      UUID            NOT NULL REFERENCES meetings(id) ON DELETE CASCADE,
    task_id         UUID            NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    source          TEXT,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (meeting_id, task_id)
);

-- Process Run Steps <-> Tasks
CREATE TABLE process_run_tasks (
    run_step_id     UUID            NOT NULL REFERENCES process_run_steps(id) ON DELETE CASCADE,
    task_id         UUID            NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (run_step_id, task_id)
);

-- Weblinks <-> Folders (M2M)
CREATE TABLE folder_weblinks (
    folder_id       UUID            NOT NULL REFERENCES weblink_folders(id) ON DELETE CASCADE,
    weblink_id      UUID            NOT NULL REFERENCES weblinks(id) ON DELETE CASCADE,
    sort_order      INTEGER         NOT NULL DEFAULT 0,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    PRIMARY KEY (folder_id, weblink_id)
);

-- =============================================================================
-- INDEXES
-- =============================================================================

-- Sort order indexes
CREATE INDEX idx_domains_sort           ON domains(sort_order);
CREATE INDEX idx_areas_sort             ON areas(domain_id, sort_order);
CREATE INDEX idx_projects_sort          ON projects(domain_id, sort_order);
CREATE INDEX idx_projects_area_sort     ON projects(area_id, sort_order);
CREATE INDEX idx_tasks_project_sort     ON tasks(project_id, sort_order);
CREATE INDEX idx_tasks_parent_sort      ON tasks(parent_id, sort_order);
CREATE INDEX idx_tasks_domain_sort      ON tasks(domain_id, sort_order);
CREATE INDEX idx_list_items_sort        ON list_items(list_id, sort_order);
CREATE INDEX idx_list_items_parent_sort ON list_items(parent_item_id, sort_order);
CREATE INDEX idx_weblink_folders_sort   ON weblink_folders(parent_id, sort_order);

-- Lookup indexes
CREATE INDEX idx_tasks_status           ON tasks(status);
CREATE INDEX idx_tasks_due_date         ON tasks(due_date);
CREATE INDEX idx_tasks_priority         ON tasks(priority);
CREATE INDEX idx_projects_status        ON projects(status);
CREATE INDEX idx_daily_focus_date       ON daily_focus(focus_date);
CREATE INDEX idx_appointments_start     ON appointments(start_at);
CREATE INDEX idx_capture_processed      ON capture(processed);
CREATE INDEX idx_file_mappings_context  ON file_mappings(context_type, context_id);
CREATE INDEX idx_dependencies_blocker   ON dependencies(blocker_type, blocker_id);
CREATE INDEX idx_dependencies_dependent ON dependencies(dependent_type, dependent_id);
CREATE INDEX idx_reminders_entity       ON reminders(entity_type, entity_id);
CREATE INDEX idx_time_entries_task      ON time_entries(task_id);
CREATE INDEX idx_meetings_date          ON meetings(meeting_date);

-- Full-text search GIN indexes
CREATE INDEX idx_domains_search         ON domains USING GIN(search_vector);
CREATE INDEX idx_areas_search           ON areas USING GIN(search_vector);
CREATE INDEX idx_projects_search        ON projects USING GIN(search_vector);
CREATE INDEX idx_tasks_search           ON tasks USING GIN(search_vector);
CREATE INDEX idx_notes_search           ON notes USING GIN(search_vector);
CREATE INDEX idx_contacts_search        ON contacts USING GIN(search_vector);
CREATE INDEX idx_meetings_search        ON meetings USING GIN(search_vector);
CREATE INDEX idx_decisions_search       ON decisions USING GIN(search_vector);
CREATE INDEX idx_lists_search           ON lists USING GIN(search_vector);
CREATE INDEX idx_links_search           ON links USING GIN(search_vector);
CREATE INDEX idx_files_search           ON files USING GIN(search_vector);
CREATE INDEX idx_weblinks_search        ON weblinks USING GIN(search_vector);
CREATE INDEX idx_processes_search       ON processes USING GIN(search_vector);
CREATE INDEX idx_appointments_search    ON appointments USING GIN(search_vector);

-- =============================================================================
-- SEARCH VECTOR TRIGGERS
-- =============================================================================

CREATE OR REPLACE FUNCTION update_search_vector() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.title, '') || ' ' ||
        coalesce(NEW.description, '') || ' ' ||
        coalesce(NEW.name, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), '')
    );
    RETURN NEW;
EXCEPTION WHEN undefined_column THEN
    -- Fallback for tables with different column names
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Per-table triggers with correct columns
CREATE OR REPLACE FUNCTION update_domains_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english', coalesce(NEW.name, ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_domains_search BEFORE INSERT OR UPDATE ON domains
    FOR EACH ROW EXECUTE FUNCTION update_domains_search();

CREATE OR REPLACE FUNCTION update_areas_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.name, '') || ' ' || coalesce(NEW.description, ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_areas_search BEFORE INSERT OR UPDATE ON areas
    FOR EACH ROW EXECUTE FUNCTION update_areas_search();

CREATE OR REPLACE FUNCTION update_projects_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.name, '') || ' ' || coalesce(NEW.description, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_projects_search BEFORE INSERT OR UPDATE ON projects
    FOR EACH ROW EXECUTE FUNCTION update_projects_search();

CREATE OR REPLACE FUNCTION update_tasks_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.title, '') || ' ' || coalesce(NEW.description, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_tasks_search BEFORE INSERT OR UPDATE ON tasks
    FOR EACH ROW EXECUTE FUNCTION update_tasks_search();

CREATE OR REPLACE FUNCTION update_notes_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.title, '') || ' ' || coalesce(NEW.body, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_notes_search BEFORE INSERT OR UPDATE ON notes
    FOR EACH ROW EXECUTE FUNCTION update_notes_search();

CREATE OR REPLACE FUNCTION update_contacts_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.first_name, '') || ' ' || coalesce(NEW.last_name, '') || ' ' ||
        coalesce(NEW.company, '') || ' ' || coalesce(NEW.email, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_contacts_search BEFORE INSERT OR UPDATE ON contacts
    FOR EACH ROW EXECUTE FUNCTION update_contacts_search();

CREATE OR REPLACE FUNCTION update_meetings_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.title, '') || ' ' || coalesce(NEW.agenda, '') || ' ' ||
        coalesce(NEW.notes_body, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_meetings_search BEFORE INSERT OR UPDATE ON meetings
    FOR EACH ROW EXECUTE FUNCTION update_meetings_search();

CREATE OR REPLACE FUNCTION update_decisions_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.title, '') || ' ' || coalesce(NEW.rationale, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_decisions_search BEFORE INSERT OR UPDATE ON decisions
    FOR EACH ROW EXECUTE FUNCTION update_decisions_search();

CREATE OR REPLACE FUNCTION update_lists_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.name, '') || ' ' || coalesce(NEW.description, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_lists_search BEFORE INSERT OR UPDATE ON lists
    FOR EACH ROW EXECUTE FUNCTION update_lists_search();

CREATE OR REPLACE FUNCTION update_links_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.label, '') || ' ' || coalesce(NEW.url, '') || ' ' ||
        coalesce(NEW.description, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_links_search BEFORE INSERT OR UPDATE ON links
    FOR EACH ROW EXECUTE FUNCTION update_links_search();

CREATE OR REPLACE FUNCTION update_files_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.original_filename, '') || ' ' || coalesce(NEW.description, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_files_search BEFORE INSERT OR UPDATE ON files
    FOR EACH ROW EXECUTE FUNCTION update_files_search();

CREATE OR REPLACE FUNCTION update_weblinks_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.label, '') || ' ' || coalesce(NEW.url, '') || ' ' ||
        coalesce(NEW.description, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_weblinks_search BEFORE INSERT OR UPDATE ON weblinks
    FOR EACH ROW EXECUTE FUNCTION update_weblinks_search();

CREATE OR REPLACE FUNCTION update_processes_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.name, '') || ' ' || coalesce(NEW.description, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_processes_search BEFORE INSERT OR UPDATE ON processes
    FOR EACH ROW EXECUTE FUNCTION update_processes_search();

CREATE OR REPLACE FUNCTION update_appointments_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.title, '') || ' ' || coalesce(NEW.description, '') || ' ' ||
        coalesce(NEW.location, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_appointments_search BEFORE INSERT OR UPDATE ON appointments
    FOR EACH ROW EXECUTE FUNCTION update_appointments_search();

CREATE OR REPLACE FUNCTION update_releases_search() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('pg_catalog.english',
        coalesce(NEW.name, '') || ' ' || coalesce(NEW.description, '') || ' ' ||
        coalesce(NEW.version_label, '') || ' ' ||
        coalesce(array_to_string(NEW.tags, ' '), ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_releases_search BEFORE INSERT OR UPDATE ON releases
    FOR EACH ROW EXECUTE FUNCTION update_releases_search();

-- =============================================================================
-- SEED DATA: Context Types
-- =============================================================================

INSERT INTO context_types (value, label, is_system, sort_order) VALUES
    ('deep_work', 'Deep Work', true, 10),
    ('quick', 'Quick', true, 20),
    ('waiting', 'Waiting', true, 30),
    ('someday', 'Someday', true, 40),
    ('meeting', 'Meeting', true, 50),
    ('errand', 'Errand', true, 60);
