/**
 * Graph Edge Writer — ADR-130 Phase 1
 *
 * Provides a minimal interface for inserting rows into the graph_edges
 * sql.js table defined by MEMORY_SCHEMA_V3.
 *
 * This module is intentionally thin: it opens the shared sql.js SQLite db
 * (the same file used by memory-initializer storeEntry), ensures the
 * graph_edges table exists, and returns a better-sqlite3-compatible
 * prepared-statement interface.
 *
 * The module is designed for fire-and-forget callers — every public function
 * suppresses errors internally so callers never need try/catch.
 *
 * @module v3/cli/memory/graph-edge-writer
 */

import * as path from 'path';
import * as fs from 'fs';
import * as crypto from 'crypto';
import { getMemoryRoot } from './memory-initializer.js';
import { encodeEmbedding } from './embedding-quantization.js';

// ============================================================================
// Lazy-cached sql.js db handle
// ============================================================================

let _db: any = null;
let _dbPath = '';
let _dbInitializing = false;

/**
 * Return the sql.js Database instance for graph_edges writes.
 * Creates the graph_edges table if it is absent (idempotent).
 * Returns null if sql.js is not available or db cannot be opened.
 */
export async function getBridgeDb(customDbPath?: string): Promise<any | null> {
  const dbPath = customDbPath ?? path.join(getMemoryRoot(), 'memory.db');

  if (_db && _dbPath === dbPath) return _db;
  if (_dbInitializing) return null;
  _dbInitializing = true;

  try {
    if (!fs.existsSync(dbPath)) return null;

    const initSqlJs = (await import('sql.js')).default;
    const SQL = await initSqlJs();
    const fileBuffer = fs.readFileSync(dbPath);
    const db = new SQL.Database(fileBuffer);

    // Ensure graph_edges table exists (in case this is an older DB that
    // predates ADR-130 Phase 1 schema migration).
    db.run(`
      CREATE TABLE IF NOT EXISTS graph_edges (
        id              TEXT PRIMARY KEY,
        source_id       TEXT NOT NULL,
        target_id       TEXT NOT NULL,
        relation        TEXT NOT NULL,
        weight          REAL DEFAULT 1.0,
        confidence      REAL DEFAULT 1.0,
        decay_rate      REAL DEFAULT 0.0,
        last_reinforced TEXT,
        witness_id      TEXT,
        embedding_ref   TEXT,
        metadata        TEXT,
        created_at      TEXT NOT NULL
      )
    `);
    db.run(`CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges (source_id)`);
    db.run(`CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges (target_id)`);
    db.run(`CREATE INDEX IF NOT EXISTS idx_graph_edges_relation ON graph_edges (relation)`);
    db.run(`CREATE INDEX IF NOT EXISTS idx_graph_edges_reinforced ON graph_edges (last_reinforced)`);

    _db = db;
    _dbPath = dbPath;
    return db;
  } catch {
    return null;
  } finally {
    _dbInitializing = false;
  }
}

/**
 * Persist the in-memory sql.js database back to disk.
 * Called after each write to keep the file consistent.
 */
async function flushDb(db: any): Promise<void> {
  try {
    if (!_dbPath) return;
    const data = db.export();
    fs.writeFileSync(_dbPath, Buffer.from(data));
  } catch { /* non-fatal */ }
}

// ============================================================================
// Public write API
// ============================================================================

export interface GraphEdgeInput {
  sourceId: string;
  targetId: string;
  relation: string;
  weight?: number;
  confidence?: number;
  decayRate?: number;
  lastReinforced?: string;   // ISO-8601
  witnessId?: string;
  embedding?: number[];       // raw 384-dim float; encoded automatically
  metadata?: Record<string, unknown>;
  dbPath?: string;
}

/**
 * Insert a single edge into graph_edges.
 * Fire-and-forget — errors are suppressed.
 * Returns true if the write succeeded, false otherwise.
 */
export async function insertGraphEdge(input: GraphEdgeInput): Promise<boolean> {
  try {
    const db = await getBridgeDb(input.dbPath);
    if (!db) return false;

    const id = `edge-${crypto.randomUUID()}`;
    const createdAt = new Date().toISOString();

    let embeddingRef: string | null = null;
    if (input.embedding && input.embedding.length > 0) {
      embeddingRef = encodeEmbedding(input.embedding);
    }

    const metaStr = input.metadata ? JSON.stringify(input.metadata) : null;

    db.run(
      `INSERT OR IGNORE INTO graph_edges
        (id, source_id, target_id, relation, weight, confidence, decay_rate,
         last_reinforced, witness_id, embedding_ref, metadata, created_at)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      [
        id,
        input.sourceId,
        input.targetId,
        input.relation,
        input.weight ?? 1.0,
        input.confidence ?? 1.0,
        input.decayRate ?? 0.0,
        input.lastReinforced ?? null,
        input.witnessId ?? null,
        embeddingRef,
        metaStr,
        createdAt,
      ],
    );

    await flushDb(db);
    return true;
  } catch {
    return false;
  }
}

/**
 * Query graph_edges by source_id.
 * Returns rows or empty array on error.
 */
export async function queryEdgesBySource(
  sourceId: string,
  relation?: string,
  dbPath?: string,
): Promise<Array<{ id: string; source_id: string; target_id: string; relation: string; weight: number }>> {
  try {
    const db = await getBridgeDb(dbPath);
    if (!db) return [];

    const sql = relation
      ? `SELECT id, source_id, target_id, relation, weight FROM graph_edges WHERE source_id = ? AND relation = ? LIMIT 1000`
      : `SELECT id, source_id, target_id, relation, weight FROM graph_edges WHERE source_id = ? LIMIT 1000`;
    const args = relation ? [sourceId, relation] : [sourceId];

    const result = db.exec(sql, args);
    if (!result?.[0]) return [];

    const cols = result[0].columns;
    return result[0].values.map((row: unknown[]) => {
      const obj: Record<string, unknown> = {};
      cols.forEach((c: string, i: number) => { obj[c] = row[i]; });
      return obj as any;
    });
  } catch {
    return [];
  }
}

/**
 * Count rows in graph_edges (for test assertions).
 */
export async function countGraphEdges(dbPath?: string): Promise<number> {
  try {
    const db = await getBridgeDb(dbPath);
    if (!db) return 0;
    const result = db.exec(`SELECT COUNT(*) FROM graph_edges`);
    return (result?.[0]?.values?.[0]?.[0] as number) ?? 0;
  } catch {
    return 0;
  }
}

/**
 * Reset the cached db handle (for tests that need a fresh DB).
 */
export function _resetBridgeDb(): void {
  _db = null;
  _dbPath = '';
}
