-- Run this in the Supabase SQL Editor to create all required tables

CREATE TABLE IF NOT EXISTS wallet_links (
  id BIGSERIAL PRIMARY KEY,
  x_handle TEXT UNIQUE NOT NULL,
  x_user_id TEXT UNIQUE NOT NULL,
  wallet_address TEXT NOT NULL,
  verified_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS processed_mentions (
  tweet_id TEXT PRIMARY KEY,
  feature TEXT NOT NULL,
  response_tweet_id TEXT,
  processed_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS opinion_commits (
  id BIGSERIAL PRIMARY KEY,
  tweet_id TEXT NOT NULL,
  question TEXT NOT NULL,
  answer TEXT NOT NULL,
  answer_hash TEXT NOT NULL,
  solana_signature TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS rate_limits (
  key TEXT PRIMARY KEY,
  count INTEGER DEFAULT 0,
  window_start TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS price_snapshots (
  id BIGSERIAL PRIMARY KEY,
  price_usd DOUBLE PRECISION NOT NULL,
  volume_24h DOUBLE PRECISION,
  market_cap DOUBLE PRECISION,
  recorded_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_price_snapshots_recorded ON price_snapshots(recorded_at);

-- ============================================================
-- MEMORY SYSTEM: The Cortex
-- Episodic, Semantic, Procedural, and Self-Model memory
-- with hybrid vector + keyword retrieval
-- ============================================================

-- Enable trigram similarity for fuzzy text search
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- Enable pgvector for semantic similarity search
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE IF NOT EXISTS memories (
  id BIGSERIAL PRIMARY KEY,
  memory_type TEXT NOT NULL CHECK (memory_type IN ('episodic', 'semantic', 'procedural', 'self_model')),
  content TEXT NOT NULL,
  summary TEXT NOT NULL,
  tags TEXT[] DEFAULT '{}',
  concepts TEXT[] DEFAULT '{}',           -- structured concept ontology labels
  emotional_valence REAL DEFAULT 0,       -- -1 (negative) to 1 (positive)
  importance REAL DEFAULT 0.5,            -- 0 to 1
  access_count INTEGER DEFAULT 0,
  source TEXT,                            -- trigger: mention, market, consolidation, reflection, emergence
  source_id TEXT,                         -- tweet ID, event ID, etc.
  related_user TEXT,                      -- X user ID if relevant
  related_wallet TEXT,                    -- wallet address if relevant
  metadata JSONB DEFAULT '{}',
  created_at TIMESTAMPTZ DEFAULT NOW(),
  last_accessed TIMESTAMPTZ DEFAULT NOW(),
  decay_factor REAL DEFAULT 1.0,          -- type-specific decay (episodic=0.93, semantic=0.98, procedural=0.97, self_model=0.99)
  evidence_ids BIGINT[] DEFAULT '{}',    -- IDs of memories that support this one (Park et al. 2023)
  solana_signature TEXT,                  -- Solana tx signature if committed on-chain
  embedding vector(1024),                 -- vector embedding for semantic similarity search
  hash_id TEXT,                           -- collision-resistant hash ID (Beads-inspired)
  compacted BOOLEAN DEFAULT FALSE,        -- whether this memory has been compacted
  compacted_into TEXT,                    -- hash_id of the memory this was compacted into
  encrypted BOOLEAN DEFAULT FALSE,        -- whether content is client-side encrypted
  encryption_pubkey TEXT,                 -- Solana pubkey that encrypted this memory
  owner_wallet TEXT                       -- Solana pubkey of the memory owner
);

-- Memory fragments: granular vector decomposition for precision retrieval
-- Each memory is split into semantic fragments (summary, content chunks, tag context)
-- so vector search matches against precise sub-memory content rather than the whole blob.
CREATE TABLE IF NOT EXISTS memory_fragments (
  id BIGSERIAL PRIMARY KEY,
  memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
  fragment_type TEXT NOT NULL,            -- 'summary', 'content_chunk', 'tag_context'
  content TEXT NOT NULL,
  embedding vector(1024),
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Dream logs: consolidation, reflection, emergence sessions
CREATE TABLE IF NOT EXISTS dream_logs (
  id BIGSERIAL PRIMARY KEY,
  session_type TEXT NOT NULL CHECK (session_type IN ('consolidation', 'reflection', 'emergence', 'compaction', 'decay', 'contradiction_resolution')),
  input_memory_ids BIGINT[] DEFAULT '{}',
  output TEXT NOT NULL,
  new_memories_created BIGINT[] DEFAULT '{}',
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Indexes for memory retrieval
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type);
CREATE INDEX IF NOT EXISTS idx_memories_tags ON memories USING GIN(tags);
CREATE INDEX IF NOT EXISTS idx_memories_concepts ON memories USING GIN(concepts);
CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance DESC);
CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(related_user);
CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_memories_decay ON memories(decay_factor);
CREATE INDEX IF NOT EXISTS idx_memories_summary_trgm ON memories USING GIN(summary gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_memories_evidence ON memories USING GIN(evidence_ids);
CREATE INDEX IF NOT EXISTS idx_memories_owner ON memories(owner_wallet);

-- Vector similarity indexes (HNSW for fast approximate nearest neighbor)
CREATE INDEX IF NOT EXISTS idx_memories_embedding ON memories USING hnsw (embedding vector_cosine_ops);
CREATE INDEX IF NOT EXISTS idx_fragments_embedding ON memory_fragments USING hnsw (embedding vector_cosine_ops);
CREATE INDEX IF NOT EXISTS idx_fragments_memory_id ON memory_fragments(memory_id);

CREATE INDEX IF NOT EXISTS idx_dream_logs_type ON dream_logs(session_type);
CREATE INDEX IF NOT EXISTS idx_dream_logs_created ON dream_logs(created_at DESC);

-- ============================================================
-- VECTOR SEARCH FUNCTIONS
-- Used by the hybrid retrieval system in memory.ts
-- ============================================================

-- Semantic search across memory-level embeddings with metadata filtering
CREATE OR REPLACE FUNCTION match_memories(
  query_embedding vector(1024),
  match_threshold float DEFAULT 0.3,
  match_count int DEFAULT 10,
  filter_types text[] DEFAULT NULL,
  filter_user text DEFAULT NULL,
  min_decay float DEFAULT 0.1,
  filter_owner text DEFAULT NULL
)
RETURNS TABLE (id bigint, similarity float)
LANGUAGE plpgsql AS $$
BEGIN
  RETURN QUERY
  SELECT m.id, (1 - (m.embedding <=> query_embedding))::float AS similarity
  FROM memories m
  WHERE m.embedding IS NOT NULL
    AND m.decay_factor >= min_decay
    AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
    AND (filter_user IS NULL OR m.related_user = filter_user)
    AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
    AND (1 - (m.embedding <=> query_embedding)) > match_threshold
  ORDER BY m.embedding <=> query_embedding
  LIMIT match_count;
END;
$$;

-- ============================================================
-- MEMORY ASSOCIATION GRAPH
-- Bidirectional, typed, weighted links between memories.
-- Enables multi-hop retrieval, conflict detection, and
-- associative reasoning (A-Mem / MAGMA / SYNAPSE inspired).
-- ============================================================

CREATE TABLE IF NOT EXISTS memory_links (
  id BIGSERIAL PRIMARY KEY,
  source_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
  target_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
  link_type TEXT NOT NULL CHECK (link_type IN (
    'supports',     -- evidence-backed
    'contradicts',  -- conflicting information
    'elaborates',   -- adds detail to existing memory
    'causes',       -- causal chain
    'follows',      -- temporal sequence
    'relates',      -- general semantic association
    'resolves'      -- contradiction resolution outcome
  )),
  strength REAL DEFAULT 0.5,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(source_id, target_id, link_type)
);

CREATE INDEX IF NOT EXISTS idx_links_source ON memory_links(source_id);
CREATE INDEX IF NOT EXISTS idx_links_target ON memory_links(target_id);
CREATE INDEX IF NOT EXISTS idx_links_type ON memory_links(link_type);
CREATE INDEX IF NOT EXISTS idx_links_strength ON memory_links(strength DESC);

-- 1-hop traversal: get all memories linked to a set of IDs (both directions)
CREATE OR REPLACE FUNCTION get_linked_memories(
  seed_ids BIGINT[],
  min_strength FLOAT DEFAULT 0.1,
  max_results INT DEFAULT 20,
  filter_owner TEXT DEFAULT NULL
)
RETURNS TABLE (
  memory_id BIGINT,
  linked_from BIGINT,
  link_type TEXT,
  strength FLOAT
)
LANGUAGE sql AS $$
  SELECT DISTINCT ON (ml.target_id, ml.link_type)
    ml.target_id AS memory_id,
    ml.source_id AS linked_from,
    ml.link_type,
    ml.strength::float
  FROM memory_links ml
  JOIN memories m ON m.id = ml.target_id
  WHERE ml.source_id = ANY(seed_ids)
    AND ml.target_id != ALL(seed_ids)
    AND ml.strength >= min_strength
    AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
  UNION
  SELECT DISTINCT ON (ml.source_id, ml.link_type)
    ml.source_id AS memory_id,
    ml.target_id AS linked_from,
    ml.link_type,
    ml.strength::float
  FROM memory_links ml
  JOIN memories m ON m.id = ml.source_id
  WHERE ml.target_id = ANY(seed_ids)
    AND ml.source_id != ALL(seed_ids)
    AND ml.strength >= min_strength
    AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
  ORDER BY strength DESC
  LIMIT max_results;
$$;

-- 2-hop traversal: from seeds, get connected memories up to depth 2
CREATE OR REPLACE FUNCTION get_memory_graph(
  seed_ids BIGINT[],
  min_strength FLOAT DEFAULT 0.2,
  max_results INT DEFAULT 30
)
RETURNS TABLE (
  memory_id BIGINT,
  hop INT,
  link_type TEXT,
  strength FLOAT
)
LANGUAGE plpgsql AS $$
DECLARE
  hop1_ids BIGINT[];
BEGIN
  RETURN QUERY
  SELECT g.memory_id, 1 AS hop, g.link_type, g.strength
  FROM get_linked_memories(seed_ids, min_strength, max_results) g;

  SELECT ARRAY_AGG(DISTINCT g.memory_id)
  INTO hop1_ids
  FROM get_linked_memories(seed_ids, min_strength, max_results) g;

  IF hop1_ids IS NOT NULL AND array_length(hop1_ids, 1) > 0 THEN
    RETURN QUERY
    SELECT g2.memory_id, 2 AS hop, g2.link_type, g2.strength * 0.5 AS strength
    FROM get_linked_memories(hop1_ids, min_strength, max_results / 2) g2
    WHERE g2.memory_id != ALL(seed_ids)
      AND g2.memory_id != ALL(hop1_ids);
  END IF;
END;
$$;

-- Hebbian reinforcement: boost link strength for co-retrieved memories
CREATE OR REPLACE FUNCTION boost_link_strength(
  memory_ids BIGINT[],
  boost_amount FLOAT DEFAULT 0.05
)
RETURNS INTEGER
LANGUAGE plpgsql AS $$
DECLARE affected INTEGER;
BEGIN
  UPDATE memory_links
  SET strength = LEAST(1.0, strength + boost_amount)
  WHERE source_id = ANY(memory_ids)
    AND target_id = ANY(memory_ids);
  GET DIAGNOSTICS affected = ROW_COUNT;
  RETURN affected;
END;
$$;

-- Find unresolved contradiction pairs (no 'resolves' link spanning both memories)
CREATE OR REPLACE FUNCTION get_unresolved_contradictions(
  max_pairs INT DEFAULT 3,
  filter_owner TEXT DEFAULT NULL
)
RETURNS TABLE (
  link_id BIGINT,
  source_id BIGINT,
  target_id BIGINT,
  strength FLOAT
)
LANGUAGE sql AS $$
  SELECT
    ml.id AS link_id,
    ml.source_id,
    ml.target_id,
    ml.strength::float
  FROM memory_links ml
  JOIN memories ms ON ms.id = ml.source_id AND ms.decay_factor > 0.1
  JOIN memories mt ON mt.id = ml.target_id AND mt.decay_factor > 0.1
  WHERE ml.link_type = 'contradicts'
    AND (filter_owner IS NULL OR ms.owner_wallet = filter_owner)
    AND (filter_owner IS NULL OR mt.owner_wallet = filter_owner)
    AND NOT EXISTS (
      SELECT 1 FROM memory_links r1
      JOIN memory_links r2 ON r1.source_id = r2.source_id
      WHERE r1.link_type = 'resolves'
        AND r2.link_type = 'resolves'
        AND r1.target_id = ml.source_id
        AND r2.target_id = ml.target_id
    )
  ORDER BY ml.strength DESC, ml.created_at DESC
  LIMIT max_pairs;
$$;

-- Fragment-level semantic search with deduplication to parent memory.
-- Returns the highest similarity fragment per memory, enabling precision
-- matching against individual facts/sentences rather than whole memories.
CREATE OR REPLACE FUNCTION match_memory_fragments(
  query_embedding vector(1024),
  match_threshold float DEFAULT 0.3,
  match_count int DEFAULT 10,
  filter_owner text DEFAULT NULL
)
RETURNS TABLE (memory_id bigint, max_similarity float)
LANGUAGE plpgsql AS $$
BEGIN
  RETURN QUERY
  SELECT f.memory_id, MAX((1 - (f.embedding <=> query_embedding))::float) AS max_similarity
  FROM memory_fragments f
  JOIN memories m ON m.id = f.memory_id
  WHERE f.embedding IS NOT NULL
    AND (1 - (f.embedding <=> query_embedding)) > match_threshold
    AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
  GROUP BY f.memory_id
  ORDER BY max_similarity DESC
  LIMIT match_count;
END;
$$;

-- ============================================================
-- AGENT DASHBOARD: Orchestration & Monitoring
-- Multi-agent registry, task management, activity audit log
-- ============================================================

-- Agent registry
CREATE TABLE IF NOT EXISTS dashboard_agents (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  type TEXT DEFAULT 'claude_code' CHECK (type IN ('claude_code', 'script', 'webhook', 'clude_bot', 'content', 'research', 'dev', 'testing', 'design_audit', 'customer_journey')),
  status TEXT DEFAULT 'offline' CHECK (status IN ('online', 'offline', 'paused', 'error')),
  description TEXT,
  config JSONB DEFAULT '{}',
  heartbeat_url TEXT,
  heartbeat_interval_ms INTEGER DEFAULT 300000,
  last_heartbeat_at TIMESTAMPTZ,
  budget_monthly_usd NUMERIC(10,2) DEFAULT 0,
  budget_used_usd NUMERIC(10,2) DEFAULT 0,
  budget_reset_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_dashboard_agents_status ON dashboard_agents(status);
CREATE INDEX IF NOT EXISTS idx_dashboard_agents_type ON dashboard_agents(type);

-- Task/ticket system
CREATE TABLE IF NOT EXISTS dashboard_tasks (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  agent_id UUID REFERENCES dashboard_agents(id) ON DELETE SET NULL,
  title TEXT NOT NULL,
  description TEXT,
  status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'in_progress', 'completed', 'failed', 'cancelled')),
  priority TEXT DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'critical')),
  parent_task_id UUID REFERENCES dashboard_tasks(id) ON DELETE SET NULL,
  metadata JSONB DEFAULT '{}',
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW(),
  completed_at TIMESTAMPTZ
);

CREATE INDEX IF NOT EXISTS idx_dashboard_tasks_agent ON dashboard_tasks(agent_id);
CREATE INDEX IF NOT EXISTS idx_dashboard_tasks_status ON dashboard_tasks(status);
CREATE INDEX IF NOT EXISTS idx_dashboard_tasks_priority ON dashboard_tasks(priority);

-- Immutable activity/audit log
CREATE TABLE IF NOT EXISTS dashboard_activity (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  agent_id UUID REFERENCES dashboard_agents(id) ON DELETE SET NULL,
  action TEXT NOT NULL,
  details JSONB DEFAULT '{}',
  cost_usd NUMERIC(10,4) DEFAULT 0,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_dashboard_activity_agent ON dashboard_activity(agent_id);
CREATE INDEX IF NOT EXISTS idx_dashboard_activity_action ON dashboard_activity(action);
CREATE INDEX IF NOT EXISTS idx_dashboard_activity_created ON dashboard_activity(created_at DESC);
