import Database from 'better-sqlite3';
import { existsSync, mkdirSync } from 'fs';
import { dirname } from 'path';
import { ensureKnowledgeSchema } from './short-term/schema.js';
export class KnowledgeGraph {
    db;
    constructor(dbPath) {
        const dir = dirname(dbPath);
        if (!existsSync(dir)) {
            mkdirSync(dir, { recursive: true });
        }
        this.db = new Database(dbPath);
        ensureKnowledgeSchema(this.db);
        // Optimize for read-heavy graph queries
        this.db.pragma('journal_mode = WAL');
        this.db.pragma('synchronous = NORMAL');
        this.db.pragma('busy_timeout = 5000');
    }
    // ============================================================
    // ENTITY OPERATIONS
    // ============================================================
    /**
     * Upsert an entity. If (type, name) already exists, updates last_seen
     * and increments mention_count.
     */
    upsertEntity(type, name, description) {
        const now = new Date().toISOString();
        const existing = this.db
            .prepare('SELECT * FROM entities WHERE type = ? AND name = ?')
            .get(type, name);
        if (existing) {
            this.db
                .prepare(`
        UPDATE entities
        SET last_seen = ?, mention_count = mention_count + 1,
            description = COALESCE(?, description)
        WHERE id = ?
      `)
                .run(now, description ?? null, existing.id);
            return {
                id: existing.id,
                type: existing.type,
                name: existing.name,
                description: (description ?? existing.description),
                firstSeen: existing.first_seen,
                lastSeen: now,
                mentionCount: existing.mention_count + 1,
            };
        }
        const result = this.db
            .prepare(`
      INSERT INTO entities (type, name, description, first_seen, last_seen, mention_count)
      VALUES (?, ?, ?, ?, ?, 1)
    `)
            .run(type, name, description ?? null, now, now);
        return {
            id: Number(result.lastInsertRowid),
            type,
            name,
            description,
            firstSeen: now,
            lastSeen: now,
            mentionCount: 1,
        };
    }
    /**
     * Get an entity by type and name.
     */
    getEntity(type, name) {
        const row = this.db
            .prepare('SELECT * FROM entities WHERE type = ? AND name = ?')
            .get(type, name);
        if (!row)
            return null;
        return this.rowToEntity(row);
    }
    /**
     * Get an entity by ID.
     */
    getEntityById(id) {
        const row = this.db.prepare('SELECT * FROM entities WHERE id = ?').get(id);
        if (!row)
            return null;
        return this.rowToEntity(row);
    }
    /**
     * Search entities by type.
     */
    getEntitiesByType(type, limit = 50) {
        const rows = this.db
            .prepare('SELECT * FROM entities WHERE type = ? ORDER BY mention_count DESC LIMIT ?')
            .all(type, limit);
        return rows.map((r) => this.rowToEntity(r));
    }
    /**
     * Search entities by name pattern (LIKE search).
     */
    searchEntities(query, limit = 20) {
        const rows = this.db
            .prepare('SELECT * FROM entities WHERE name LIKE ? ORDER BY mention_count DESC LIMIT ?')
            .all(`%${query}%`, limit);
        return rows.map((r) => this.rowToEntity(r));
    }
    /**
     * Delete an entity and all its relationships.
     */
    deleteEntity(id) {
        const deleteRels = this.db.prepare('DELETE FROM relationships WHERE source_id = ? OR target_id = ?');
        const deleteEntity = this.db.prepare('DELETE FROM entities WHERE id = ?');
        const txn = this.db.transaction(() => {
            deleteRels.run(id, id);
            const result = deleteEntity.run(id);
            return result.changes > 0;
        });
        return txn();
    }
    // ============================================================
    // RELATIONSHIP OPERATIONS
    // ============================================================
    /**
     * Add or strengthen a relationship between two entities.
     * If the relationship already exists, increases strength.
     */
    addRelationship(sourceId, targetId, relation, strength = 1.0) {
        const now = new Date().toISOString();
        const existing = this.db
            .prepare('SELECT * FROM relationships WHERE source_id = ? AND target_id = ? AND relation = ?')
            .get(sourceId, targetId, relation);
        if (existing) {
            const newStrength = Math.min(existing.strength + strength, 10.0);
            this.db
                .prepare(`
        UPDATE relationships SET strength = ?, timestamp = ? WHERE id = ?
      `)
                .run(newStrength, now, existing.id);
            return {
                id: existing.id,
                sourceId,
                targetId,
                relation,
                strength: newStrength,
                timestamp: now,
            };
        }
        const result = this.db
            .prepare(`
      INSERT INTO relationships (source_id, target_id, relation, strength, timestamp)
      VALUES (?, ?, ?, ?, ?)
    `)
            .run(sourceId, targetId, relation, strength, now);
        return {
            id: Number(result.lastInsertRowid),
            sourceId,
            targetId,
            relation,
            strength,
            timestamp: now,
        };
    }
    /**
     * Get all relationships for an entity (both directions).
     */
    getRelationships(entityId) {
        const rows = this.db
            .prepare(`
      SELECT * FROM relationships
      WHERE source_id = ? OR target_id = ?
      ORDER BY strength DESC
    `)
            .all(entityId, entityId);
        return rows.map((r) => this.rowToRelationship(r));
    }
    /**
     * Delete a specific relationship.
     */
    deleteRelationship(id) {
        const result = this.db.prepare('DELETE FROM relationships WHERE id = ?').run(id);
        return result.changes > 0;
    }
    // ============================================================
    // GRAPH QUERIES
    // ============================================================
    /**
     * Get an entity with all its relationships and related entities.
     * Uses a single JOIN query instead of N+1 individual queries.
     */
    queryEntityGraph(type, name) {
        const entity = this.getEntity(type, name);
        if (!entity || !entity.id)
            return null;
        // Single JOIN query to get all relationships with their related entities
        const rows = this.db
            .prepare(`
      SELECT r.id as relId, r.relation, r.strength, r.source_id as sourceId, r.target_id as targetId,
             e.id as entityId, e.type, e.name, e.description, e.first_seen as firstSeen,
             e.last_seen as lastSeen, e.mention_count as mentionCount
      FROM relationships r
      JOIN entities e ON (
        CASE WHEN r.source_id = ? THEN r.target_id ELSE r.source_id END = e.id
      )
      WHERE r.source_id = ? OR r.target_id = ?
    `)
            .all(entity.id, entity.id, entity.id);
        const relationships = rows.map((row) => ({
            relation: row.relation,
            direction: (row.sourceId === entity.id ? 'outgoing' : 'incoming'),
            relatedEntity: {
                id: row.entityId,
                type: row.type,
                name: row.name,
                description: row.description || undefined,
                firstSeen: row.firstSeen,
                lastSeen: row.lastSeen,
                mentionCount: row.mentionCount,
            },
            strength: row.strength,
        }));
        return { entity, relationships };
    }
    /**
     * Find entities connected to a given entity within N hops.
     * Uses a recursive CTE for efficient graph traversal in a single query.
     */
    traverseGraph(entityId, maxDepth = 2) {
        const rows = this.db
            .prepare(`
      WITH RECURSIVE reachable(id, depth) AS (
        SELECT ?, 0
        UNION ALL
        SELECT
          CASE WHEN r.source_id = reachable.id THEN r.target_id ELSE r.source_id END,
          reachable.depth + 1
        FROM reachable
        JOIN relationships r ON (r.source_id = reachable.id OR r.target_id = reachable.id)
        WHERE reachable.depth < ?
      )
      SELECT DISTINCT e.id, e.type, e.name, e.description, e.first_seen as firstSeen,
             e.last_seen as lastSeen, e.mention_count as mentionCount
      FROM reachable
      JOIN entities e ON e.id = reachable.id
    `)
            .all(entityId, maxDepth);
        return rows.map((row) => ({
            id: row.id,
            type: row.type,
            name: row.name,
            description: row.description || undefined,
            firstSeen: row.firstSeen,
            lastSeen: row.lastSeen,
            mentionCount: row.mentionCount,
        }));
    }
    /**
     * Get graph statistics.
     */
    getStats() {
        const entityCount = this.db.prepare('SELECT COUNT(*) as c FROM entities').get().c;
        const relationshipCount = this.db.prepare('SELECT COUNT(*) as c FROM relationships').get().c;
        const typeRows = this.db
            .prepare('SELECT DISTINCT type FROM entities ORDER BY type')
            .all();
        return {
            entityCount,
            relationshipCount,
            entityTypes: typeRows.map((r) => r.type),
        };
    }
    /**
     * Close the database connection.
     */
    close() {
        this.db.close();
    }
    // ============================================================
    // PRIVATE HELPERS
    // ============================================================
    rowToEntity(row) {
        return {
            id: row.id ?? 0,
            type: row.type ?? 'unknown',
            name: row.name ?? '',
            description: row.description,
            firstSeen: row.first_seen ?? new Date().toISOString(),
            lastSeen: row.last_seen ?? new Date().toISOString(),
            mentionCount: row.mention_count ?? 0,
        };
    }
    rowToRelationship(row) {
        return {
            id: row.id ?? 0,
            sourceId: row.source_id ?? 0,
            targetId: row.target_id ?? 0,
            relation: row.relation ?? '',
            strength: row.strength ?? 0,
            timestamp: row.timestamp ?? new Date().toISOString(),
        };
    }
}
//# sourceMappingURL=knowledge-graph.js.map