-- ═══════════════════════════════════════════════════════════════════
-- Al Moasis (المؤسس) — SQLite Schema (Phase 1, device-local)
-- ═══════════════════════════════════════════════════════════════════
-- Idempotent. Mirrors supabase/schema.sql but adapted for SQLite:
--   - UUIDs as TEXT (generated in Node via crypto.randomUUID())
--   - TIMESTAMPTZ as TEXT (ISO 8601 strings)
--   - JSONB as TEXT (JSON.stringify/parse)
--   - VECTOR(1536) → sqlite-vec virtual table
--   - pg_trgm GIN → SQLite FTS5 (where used)
--   - No RLS / auth.users (device is single-tenant — RLS irrelevant)
--   - Service-role bypass concept doesn't apply: the device's process
--     IS the privileged caller; there's no anon JWT path on the device.
-- ═══════════════════════════════════════════════════════════════════

PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;

-- ═══════════════════════════════════════════════════════════════════
-- CORE TABLES
-- ═══════════════════════════════════════════════════════════════════

CREATE TABLE IF NOT EXISTS tenants (
  id TEXT PRIMARY KEY,
  owner_id TEXT,                              -- not FK in device mode (no auth.users); kept for parity
  business_name TEXT NOT NULL,
  business_name_ar TEXT,
  agent_name TEXT NOT NULL DEFAULT 'مساعد',
  agent_persona TEXT,
  whatsapp_phone TEXT,
  whatsapp_phone_number_id TEXT,
  workspace_path TEXT,
  language TEXT NOT NULL DEFAULT 'ar',
  timezone TEXT NOT NULL DEFAULT 'Asia/Riyadh',
  working_hours TEXT NOT NULL DEFAULT '{"start":"09:00","end":"17:00","days":[0,1,2,3,4]}',
  plan TEXT NOT NULL DEFAULT 'free',
  status TEXT NOT NULL DEFAULT 'active',
  owner_name TEXT,
  owner_phone TEXT,
  owner_email TEXT,
  metadata TEXT NOT NULL DEFAULT '{}',
  settings TEXT NOT NULL DEFAULT '{}',
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

CREATE TABLE IF NOT EXISTS customers (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  phone TEXT NOT NULL,
  name TEXT,
  language TEXT DEFAULT 'ar',
  dialect TEXT,
  tags TEXT NOT NULL DEFAULT '[]',            -- JSON array
  first_contact TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  last_contact TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  total_messages INTEGER NOT NULL DEFAULT 0,
  status TEXT NOT NULL DEFAULT 'active',
  metadata TEXT NOT NULL DEFAULT '{}',
  UNIQUE(tenant_id, phone)
);

CREATE TABLE IF NOT EXISTS messages (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  customer_id TEXT NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
  session_id TEXT,
  direction TEXT NOT NULL CHECK (direction IN ('inbound', 'outbound')),
  type TEXT NOT NULL DEFAULT 'text'
    CHECK (type IN ('text','image','audio','video','document','location','sticker','reaction')),
  content TEXT,
  media_url TEXT,
  transcript TEXT,
  model_used TEXT,
  tokens_input INTEGER DEFAULT 0,
  tokens_output INTEGER DEFAULT 0,
  cost_usd REAL DEFAULT 0,
  response_time_ms INTEGER,
  delivery_status TEXT DEFAULT 'sent'
    CHECK (delivery_status IN ('sent', 'delivered', 'read', 'failed')),
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

CREATE TABLE IF NOT EXISTS usage (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  date TEXT NOT NULL,                         -- 'YYYY-MM-DD'
  messages_count INTEGER NOT NULL DEFAULT 0,
  tokens_input INTEGER NOT NULL DEFAULT 0,
  tokens_output INTEGER NOT NULL DEFAULT 0,
  cost_usd REAL NOT NULL DEFAULT 0,
  customers_new INTEGER NOT NULL DEFAULT 0,
  customers_active INTEGER NOT NULL DEFAULT 0,
  UNIQUE(tenant_id, date)
);

CREATE TABLE IF NOT EXISTS documents (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  filename TEXT NOT NULL,
  file_path TEXT NOT NULL,
  file_type TEXT NOT NULL,
  chunk_count INTEGER DEFAULT 0,
  status TEXT NOT NULL DEFAULT 'pending'
    CHECK (status IN ('pending','processing','ready','error')),
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

CREATE TABLE IF NOT EXISTS document_chunks (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
  content TEXT NOT NULL,
  -- embedding lives in vec_document_chunks (sqlite-vec virtual table) keyed by id
  metadata TEXT NOT NULL DEFAULT '{}',
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

-- ═══════════════════════════════════════════════════════════════════
-- VECTORS — sqlite-vec virtual table for embeddings (joined to document_chunks by id)
-- ═══════════════════════════════════════════════════════════════════
-- Created at runtime by the SQLite client after loading sqlite-vec extension:
--   CREATE VIRTUAL TABLE IF NOT EXISTS vec_document_chunks USING vec0(
--     id TEXT PRIMARY KEY,
--     embedding FLOAT[1536]
--   );

-- ═══════════════════════════════════════════════════════════════════
-- BILLING (platform charges its tenants — control plane only in device mode)
-- ═══════════════════════════════════════════════════════════════════

CREATE TABLE IF NOT EXISTS invoices (
  id TEXT PRIMARY KEY,
  tenant_id TEXT REFERENCES tenants(id) ON DELETE CASCADE,
  month TEXT NOT NULL,                        -- '2026-04'
  plan_name TEXT,
  plan_price_sar REAL,
  api_cost_sar REAL,
  overage_sar REAL DEFAULT 0,
  total_sar REAL,
  profit_sar REAL,
  status TEXT DEFAULT 'pending' CHECK (status IN ('pending','paid','overdue')),
  payment_ref TEXT,
  paid_at TEXT,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  UNIQUE(tenant_id, month)
);

CREATE TABLE IF NOT EXISTS budget_config (
  id TEXT PRIMARY KEY,
  tenant_id TEXT REFERENCES tenants(id) ON DELETE CASCADE UNIQUE,
  monthly_budget_sar REAL,
  throttle_at_percent INTEGER DEFAULT 90,
  hard_limit_at_percent INTEGER DEFAULT 100,
  overage_allowed INTEGER DEFAULT 0,
  overage_rate_multiplier REAL DEFAULT 1.5,
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

-- ═══════════════════════════════════════════════════════════════════
-- CRON / SESSIONS / APPROVALS
-- ═══════════════════════════════════════════════════════════════════

CREATE TABLE IF NOT EXISTS cron_jobs (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  schedule TEXT NOT NULL,
  timezone TEXT DEFAULT 'Asia/Riyadh',
  payload TEXT NOT NULL DEFAULT '{"kind":"systemEvent"}',
  delivery TEXT DEFAULT '{"mode":"announce"}',
  enabled INTEGER DEFAULT 1,
  last_run_at TEXT,
  next_run_at TEXT NOT NULL,
  run_count INTEGER DEFAULT 0,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

CREATE TABLE IF NOT EXISTS cron_executions (
  id TEXT PRIMARY KEY,
  job_id TEXT NOT NULL REFERENCES cron_jobs(id) ON DELETE CASCADE,
  tenant_id TEXT NOT NULL,
  executed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  success INTEGER NOT NULL,
  output TEXT,
  error TEXT,
  duration_ms INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS sessions (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  label TEXT,
  kind TEXT NOT NULL DEFAULT 'isolated',
  status TEXT NOT NULL DEFAULT 'active',
  model TEXT,
  thinking INTEGER DEFAULT 0,
  timeout_seconds INTEGER,
  parent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
  root_workspace_path TEXT NOT NULL,
  message_count INTEGER DEFAULT 0,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  last_message_at TEXT
);

CREATE TABLE IF NOT EXISTS session_messages (
  id TEXT PRIMARY KEY,
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
  role TEXT NOT NULL,
  content TEXT NOT NULL,
  tool_call_id TEXT,
  tool_name TEXT,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

CREATE TABLE IF NOT EXISTS approval_requests (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
  tool_name TEXT NOT NULL,
  tool_args TEXT NOT NULL,
  reason TEXT NOT NULL,
  risk_level TEXT NOT NULL DEFAULT 'medium',
  status TEXT NOT NULL DEFAULT 'pending',
  requested_by TEXT NOT NULL,
  approved_by TEXT,
  approved_at TEXT,
  expires_at TEXT NOT NULL,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

-- ═══════════════════════════════════════════════════════════════════
-- LONG-TERM MEMORY (remember/recall tools)
-- ═══════════════════════════════════════════════════════════════════

CREATE TABLE IF NOT EXISTS customer_memories (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  customer_id TEXT NOT NULL DEFAULT '',
  namespace TEXT NOT NULL DEFAULT '',
  key TEXT NOT NULL,
  value TEXT NOT NULL,
  metadata TEXT NOT NULL DEFAULT '{}',
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  UNIQUE(tenant_id, customer_id, namespace, key)
);

-- (v2.5.2) Migrate existing tenants: add the columns if missing. Both
-- adds are idempotent because we use a defensive runtime patch in
-- src/db/sqlite-client.ts that calls these on schema-apply.

-- ═══════════════════════════════════════════════════════════════════
-- ZATCA E-INVOICES
-- ═══════════════════════════════════════════════════════════════════

CREATE TABLE IF NOT EXISTS zatca_invoices (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  customer_id TEXT NOT NULL,
  invoice_number TEXT NOT NULL,
  invoice_type TEXT NOT NULL DEFAULT 'simplified'
    CHECK (invoice_type IN ('simplified', 'standard')),
  seller_name TEXT NOT NULL,
  seller_vat_number TEXT NOT NULL,
  seller_cr_number TEXT,
  buyer_name TEXT NOT NULL,
  buyer_vat_number TEXT,
  items TEXT NOT NULL,                        -- JSON array
  subtotal REAL NOT NULL,
  vat_total REAL NOT NULL,
  total REAL NOT NULL,
  currency TEXT NOT NULL DEFAULT 'SAR',
  ubl_xml TEXT NOT NULL,
  qr_base64 TEXT NOT NULL,
  zatca_uuid TEXT,
  zatca_hash TEXT,
  zatca_clearance_response TEXT,
  status TEXT NOT NULL DEFAULT 'pending_approval'
    CHECK (status IN ('draft','pending_approval','issued','cleared','rejected','cancelled')),
  approval_request_id TEXT REFERENCES approval_requests(id) ON DELETE SET NULL,
  issued_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  approved_at TEXT,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  UNIQUE(tenant_id, invoice_number)
);

-- ═══════════════════════════════════════════════════════════════════
-- INDEXES (mirror Postgres exactly where applicable)
-- ═══════════════════════════════════════════════════════════════════

CREATE INDEX IF NOT EXISTS idx_tenants_owner          ON tenants(owner_id);
CREATE INDEX IF NOT EXISTS idx_customers_tenant       ON customers(tenant_id);
CREATE INDEX IF NOT EXISTS idx_messages_tenant        ON messages(tenant_id);
CREATE INDEX IF NOT EXISTS idx_messages_customer      ON messages(customer_id);
CREATE INDEX IF NOT EXISTS idx_messages_session       ON messages(session_id);
CREATE INDEX IF NOT EXISTS idx_messages_created       ON messages(tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_usage_tenant           ON usage(tenant_id);
CREATE INDEX IF NOT EXISTS idx_usage_date             ON usage(tenant_id, date DESC);
CREATE INDEX IF NOT EXISTS idx_documents_tenant       ON documents(tenant_id);
CREATE INDEX IF NOT EXISTS idx_document_chunks_tenant ON document_chunks(tenant_id);
CREATE INDEX IF NOT EXISTS idx_document_chunks_doc    ON document_chunks(document_id);
CREATE INDEX IF NOT EXISTS idx_invoices_tenant        ON invoices(tenant_id);
CREATE INDEX IF NOT EXISTS idx_invoices_month         ON invoices(month);
CREATE INDEX IF NOT EXISTS idx_invoices_status        ON invoices(status);
CREATE INDEX IF NOT EXISTS idx_budget_config_tenant   ON budget_config(tenant_id);
CREATE INDEX IF NOT EXISTS idx_cron_jobs_tenant       ON cron_jobs(tenant_id);
CREATE INDEX IF NOT EXISTS idx_cron_jobs_enabled      ON cron_jobs(enabled) WHERE enabled = 1;
CREATE INDEX IF NOT EXISTS idx_cron_jobs_next_run     ON cron_jobs(next_run_at) WHERE enabled = 1;
CREATE INDEX IF NOT EXISTS idx_cron_exec_job          ON cron_executions(job_id);
CREATE INDEX IF NOT EXISTS idx_cron_exec_tenant       ON cron_executions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_cron_exec_executed_at  ON cron_executions(executed_at DESC);
CREATE INDEX IF NOT EXISTS idx_sessions_tenant        ON sessions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_sessions_status        ON sessions(status);
CREATE INDEX IF NOT EXISTS idx_sessions_kind          ON sessions(kind);
CREATE INDEX IF NOT EXISTS idx_session_messages_sess  ON session_messages(session_id);
CREATE INDEX IF NOT EXISTS idx_session_messages_ca    ON session_messages(created_at);
CREATE INDEX IF NOT EXISTS idx_approval_tenant        ON approval_requests(tenant_id);
CREATE INDEX IF NOT EXISTS idx_approval_session       ON approval_requests(session_id);
CREATE INDEX IF NOT EXISTS idx_approval_status        ON approval_requests(status);
CREATE INDEX IF NOT EXISTS idx_approval_expires       ON approval_requests(expires_at) WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_cm_tenant              ON customer_memories(tenant_id);
CREATE INDEX IF NOT EXISTS idx_cm_lookup              ON customer_memories(tenant_id, customer_id, key);
CREATE INDEX IF NOT EXISTS idx_zatca_tenant           ON zatca_invoices(tenant_id);
CREATE INDEX IF NOT EXISTS idx_zatca_customer         ON zatca_invoices(tenant_id, customer_id);
CREATE INDEX IF NOT EXISTS idx_zatca_status           ON zatca_invoices(tenant_id, status) WHERE status IN ('pending_approval','issued');
CREATE INDEX IF NOT EXISTS idx_zatca_issued_at        ON zatca_invoices(tenant_id, issued_at DESC);

-- ═══════════════════════════════════════════════════════════════════
-- KNOWLEDGE GRAPHS — replaces Neo4j-backed graphiti/graphify.
-- ═══════════════════════════════════════════════════════════════════
-- Entities are nodes. Relationships are edges. Episodes are the raw
-- conversation/document chunks an entity was extracted from.
-- For document-graph (graphify) entities, kind='document'. For
-- conversation-graph (graphiti) entities, kind='conversation'.
-- This keeps both subsystems in one schema with a single set of tables.

CREATE TABLE IF NOT EXISTS kg_entities (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  -- v2: expanded from ('conversation', 'document') to include the unified
  -- KG-as-registry kinds (skill / cron / policy / tool / research). Without
  -- this expansion, the kg-seeder's INSERTs were silently failing on the
  -- legacy CHECK and the dashboard graph showed zero entities.
  kind TEXT NOT NULL CHECK (kind IN ('conversation', 'document', 'skill', 'cron', 'policy', 'tool', 'research')),
  customer_id TEXT,                            -- nullable; only set for the customer-as-Person row
  type TEXT NOT NULL,                          -- Person/Product/Location/...
  name TEXT NOT NULL,
  name_normalized TEXT NOT NULL,               -- lowercase, used for de-dup
  properties TEXT NOT NULL DEFAULT '{}',       -- JSON
  source_files TEXT NOT NULL DEFAULT '[]',     -- JSON array of filename strings (graphify)
  episode_count INTEGER NOT NULL DEFAULT 1,    -- how many times we've seen this entity
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

-- Functional unique index: COALESCE makes NULL customer_id treat consistently.
-- Without this, two Products with NULL customer_id would dedupe correctly but
-- SQLite's default NULL-distinct behavior breaks INSERT...ON CONFLICT.
CREATE UNIQUE INDEX IF NOT EXISTS idx_kg_entities_uniq
  ON kg_entities(tenant_id, kind, COALESCE(customer_id, ''), type, name_normalized);

CREATE TABLE IF NOT EXISTS kg_relationships (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  source_id TEXT NOT NULL REFERENCES kg_entities(id) ON DELETE CASCADE,
  target_id TEXT NOT NULL REFERENCES kg_entities(id) ON DELETE CASCADE,
  type TEXT NOT NULL,                          -- interested_in/purchased/located_in/...
  properties TEXT NOT NULL DEFAULT '{}',
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  UNIQUE(tenant_id, source_id, target_id, type)
);

CREATE TABLE IF NOT EXISTS kg_episodes (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  kind TEXT NOT NULL CHECK (kind IN ('conversation', 'document')),
  customer_id TEXT,                            -- nullable; only for conversation episodes
  document_path TEXT,                          -- nullable; only for document episodes
  content TEXT NOT NULL,
  entities_extracted INTEGER NOT NULL DEFAULT 0,
  relationships_extracted INTEGER NOT NULL DEFAULT 0,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

CREATE INDEX IF NOT EXISTS idx_kg_entities_tenant     ON kg_entities(tenant_id, kind);
CREATE INDEX IF NOT EXISTS idx_kg_entities_customer   ON kg_entities(tenant_id, customer_id) WHERE customer_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_kg_entities_name       ON kg_entities(tenant_id, name_normalized);
CREATE INDEX IF NOT EXISTS idx_kg_relationships_tenant ON kg_relationships(tenant_id);
CREATE INDEX IF NOT EXISTS idx_kg_relationships_src   ON kg_relationships(source_id);
CREATE INDEX IF NOT EXISTS idx_kg_relationships_tgt   ON kg_relationships(target_id);
CREATE INDEX IF NOT EXISTS idx_kg_episodes_tenant     ON kg_episodes(tenant_id, kind);
CREATE INDEX IF NOT EXISTS idx_kg_episodes_customer   ON kg_episodes(tenant_id, customer_id) WHERE customer_id IS NOT NULL;
-- (Round-5) Recent-conversation lookups for recall_conversation tool
CREATE INDEX IF NOT EXISTS idx_kg_episodes_recent     ON kg_episodes(tenant_id, customer_id, created_at DESC);

-- ═══════════════════════════════════════════════════════════════════
-- AUTONOMY LAYER — what makes Muassis an employee, not a chatbot.
--   • agenda_goals: persistent KPIs / objectives Muassis is driving toward
--   • journal_entries: append-only log of every autonomous action + outcome
--   • autonomy_policies: bounded-write rules (what to act on, what to escalate)
--   • identity_facts: business identity Muassis knows about itself
--   • escalation_queue: pending decisions surfaced to the owner
-- ═══════════════════════════════════════════════════════════════════

CREATE TABLE IF NOT EXISTS agenda_goals (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  description TEXT,
  category TEXT NOT NULL DEFAULT 'general',
  target_value REAL,
  current_value REAL DEFAULT 0,
  unit TEXT,
  deadline TEXT,
  status TEXT NOT NULL DEFAULT 'active'
    CHECK (status IN ('active', 'achieved', 'missed', 'archived')),
  priority INTEGER NOT NULL DEFAULT 3,
  metadata TEXT DEFAULT '{}',
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

CREATE INDEX IF NOT EXISTS idx_agenda_goals_tenant ON agenda_goals(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_agenda_goals_deadline ON agenda_goals(deadline) WHERE status = 'active';

CREATE TABLE IF NOT EXISTS journal_entries (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  kind TEXT NOT NULL,
  actor TEXT NOT NULL DEFAULT 'muassis',
  action TEXT NOT NULL,
  target TEXT,
  outcome TEXT NOT NULL DEFAULT 'success'
    CHECK (outcome IN ('success', 'failure', 'pending', 'escalated', 'declined')),
  reason TEXT,
  metadata TEXT DEFAULT '{}',
  related_goal_id TEXT REFERENCES agenda_goals(id) ON DELETE SET NULL,
  cost_usd REAL DEFAULT 0,
  -- Tamper-evident hash chain: each entry's hash includes the previous entry's
  -- hash. Verifying the chain means walking from genesis forward — any silent
  -- mutation breaks every downstream hash. Forensic-grade audit trail.
  prev_hash TEXT,
  entry_hash TEXT,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

CREATE INDEX IF NOT EXISTS idx_journal_tenant_created ON journal_entries(tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_journal_kind ON journal_entries(tenant_id, kind);
CREATE INDEX IF NOT EXISTS idx_journal_outcome ON journal_entries(tenant_id, outcome);

CREATE TABLE IF NOT EXISTS autonomy_policies (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  policy_key TEXT NOT NULL,
  description TEXT,
  mode TEXT NOT NULL DEFAULT 'auto'
    CHECK (mode IN ('auto', 'ask', 'never')),
  threshold_amount REAL,
  threshold_currency TEXT DEFAULT 'SAR',
  conditions TEXT DEFAULT '{}',
  enabled INTEGER DEFAULT 1,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  UNIQUE(tenant_id, policy_key)
);

CREATE INDEX IF NOT EXISTS idx_policies_tenant ON autonomy_policies(tenant_id, enabled);

CREATE TABLE IF NOT EXISTS identity_facts (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  fact_key TEXT NOT NULL,
  fact_value TEXT NOT NULL,
  category TEXT NOT NULL DEFAULT 'business',
  source TEXT NOT NULL DEFAULT 'owner',
  confidence REAL DEFAULT 1.0,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  UNIQUE(tenant_id, fact_key)
);

CREATE INDEX IF NOT EXISTS idx_identity_tenant ON identity_facts(tenant_id, category);

CREATE TABLE IF NOT EXISTS escalation_queue (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  approval_request_id TEXT REFERENCES approval_requests(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  context TEXT NOT NULL,
  recommendation TEXT NOT NULL,
  counterargument TEXT,
  alternatives TEXT DEFAULT '[]',
  risk_level TEXT NOT NULL DEFAULT 'medium',
  status TEXT NOT NULL DEFAULT 'pending'
    CHECK (status IN ('pending', 'resolved', 'expired')),
  resolution TEXT,
  expires_at TEXT NOT NULL,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  resolved_at TEXT
);

CREATE INDEX IF NOT EXISTS idx_escalation_tenant_status ON escalation_queue(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_escalation_expires ON escalation_queue(expires_at) WHERE status = 'pending';

-- ─── Inbound allowlist ─────────────────────────────────────────────
-- Who is allowed to DM the agent on each channel. Owner-authorized team
-- members + customers the agent has been asked to contact (auto-added
-- on outreach) are listed here. Inbound handlers cross-check before
-- routing through the brain. Drives the "the agent only talks to
-- people you said it can talk to" guarantee.
CREATE TABLE IF NOT EXISTS allowed_contacts (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  channel TEXT NOT NULL,
  identifier TEXT NOT NULL,
  display_name TEXT,
  role TEXT NOT NULL DEFAULT 'contact'
    CHECK (role IN ('owner', 'team', 'customer', 'lead', 'supplier', 'contact')),
  added_by TEXT NOT NULL DEFAULT 'owner'
    CHECK (added_by IN ('owner', 'agent_outreach', 'auto', 'onboarding')),
  reason TEXT,
  granted_until TEXT,
  enabled INTEGER DEFAULT 1,
  last_inbound_at TEXT,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  UNIQUE(tenant_id, channel, identifier)
);

CREATE INDEX IF NOT EXISTS idx_allowed_tenant_channel ON allowed_contacts(tenant_id, channel, enabled);
CREATE INDEX IF NOT EXISTS idx_allowed_identifier ON allowed_contacts(tenant_id, identifier);

-- ─── Workflow recall (was created via exec_sql RPC; moved here for security) ───
CREATE TABLE IF NOT EXISTS workflows (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL,
  user_message TEXT NOT NULL,
  keywords TEXT NOT NULL,
  intent TEXT NOT NULL,
  tool_sequence TEXT NOT NULL,
  response_excerpt TEXT,
  invocation_count INTEGER NOT NULL DEFAULT 1,
  last_invoked_at TEXT NOT NULL,
  created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workflows_tenant ON workflows(tenant_id);

-- ─── Cron run history (per-job execution log) ───
CREATE TABLE IF NOT EXISTS cron_runs (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  job_id TEXT NOT NULL,
  job_name TEXT,
  fired_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  finished_at TEXT,
  duration_ms INTEGER,
  success INTEGER,
  output_excerpt TEXT,
  error TEXT
);
CREATE INDEX IF NOT EXISTS idx_cron_runs_tenant ON cron_runs(tenant_id, fired_at DESC);
CREATE INDEX IF NOT EXISTS idx_cron_runs_job ON cron_runs(job_id, fired_at DESC);

-- ─── Delivery queue (file-spool retry on outbound failure) ───
CREATE TABLE IF NOT EXISTS delivery_queue (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  channel TEXT NOT NULL,
  recipient TEXT NOT NULL,
  payload TEXT NOT NULL,
  retry_count INTEGER NOT NULL DEFAULT 0,
  max_retries INTEGER NOT NULL DEFAULT 5,
  last_error TEXT,
  status TEXT NOT NULL DEFAULT 'pending'
    CHECK (status IN ('pending', 'sending', 'delivered', 'failed', 'expired')),
  next_attempt_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
  delivered_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_delivery_pending ON delivery_queue(next_attempt_at) WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_delivery_tenant ON delivery_queue(tenant_id, status);

-- ─── Subagent runs (fire-and-forget background tasks) ───
CREATE TABLE IF NOT EXISTS subagent_runs (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  parent_session_id TEXT,
  parent_customer_id TEXT,
  task TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'queued'
    CHECK (status IN ('queued', 'running', 'completed', 'failed', 'cancelled', 'timeout')),
  result_excerpt TEXT,
  error TEXT,
  metadata TEXT DEFAULT '{}',
  started_at TEXT,
  finished_at TEXT,
  duration_ms INTEGER,
  timeout_seconds INTEGER NOT NULL DEFAULT 600,
  cost_usd REAL DEFAULT 0,
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_subagent_tenant_status ON subagent_runs(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_subagent_created ON subagent_runs(tenant_id, created_at DESC);

-- Per-subagent progress/event stream (started, thinking, tool_call, finished…).
-- subagents/events.js persists every event here for the live "وكيل فرعي يعمل"
-- progress cards + post-mortem inspection. Was MISSING from the schema → every
-- write threw "no such table: subagent_events" (113×/run in logs). No FK on
-- subagent_id: events can outlive/precede the run row and must never block.
CREATE TABLE IF NOT EXISTS subagent_events (
  id TEXT PRIMARY KEY,
  subagent_id TEXT NOT NULL,
  type TEXT NOT NULL,
  message TEXT,
  data TEXT DEFAULT '{}',
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_subagent_events_sub ON subagent_events(subagent_id, created_at);

-- ─── Config audit trail ───
CREATE TABLE IF NOT EXISTS config_audit (
  id TEXT PRIMARY KEY,
  tenant_id TEXT,
  actor TEXT,
  action TEXT NOT NULL,
  target TEXT,
  before_value TEXT,
  after_value TEXT,
  metadata TEXT DEFAULT '{}',
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_config_audit_tenant ON config_audit(tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_config_audit_action ON config_audit(action, created_at DESC);

-- Autonomy-aware columns on cron_jobs (seeded defaults flag)
-- Idempotent: ALTER TABLE … ADD COLUMN errors out if already exists, so wrap in pragma check.
-- (sqlite-shim db.exec doesn't support DO blocks; we rely on a one-shot migration in autonomy/seeder.)

-- ─── App settings (key/value) ────────────────────────────────────────
-- Gateway-wide flags that aren't tenant-scoped (e.g., dashboard
-- preferences shared across tenants on the device). Tenant-scoped
-- preferences continue to live in identity_facts.
CREATE TABLE IF NOT EXISTS app_settings (
  key TEXT PRIMARY KEY,
  value TEXT NOT NULL,
  updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
