import { getDatabase } from '../../database/init.js';
function parseAliases(raw) {
    try {
        return JSON.parse(raw || '[]');
    }
    catch {
        return [];
    }
}
export function registerGraphRoutes(app, requireNotLocked) {
    app.get('/api/graph/entities', requireNotLocked, (req, res) => {
        try {
            const db = getDatabase();
            const type = typeof req.query.type === 'string' ? req.query.type : undefined;
            const minMentions = typeof req.query.minMentions === 'string' ? parseInt(req.query.minMentions, 10) : 0;
            const limit = typeof req.query.limit === 'string' ? Math.min(parseInt(req.query.limit, 10), 5000) : 100;
            const offset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : 0;
            let whereClause = 'WHERE 1=1';
            const params = [];
            if (type) {
                whereClause += ' AND type = ?';
                params.push(type);
            }
            if (minMentions > 0) {
                whereClause += ' AND memory_count >= ?';
                params.push(minMentions);
            }
            const total = db.prepare(`SELECT COUNT(*) as count FROM entities ${whereClause}`).get(...params).count;
            const rows = db.prepare(`SELECT * FROM entities ${whereClause} ORDER BY memory_count DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
            res.json({
                entities: rows.map((row) => ({
                    id: row.id,
                    name: row.name,
                    type: row.type,
                    memoryCount: row.memory_count ?? 0,
                    aliases: parseAliases(row.aliases),
                    createdAt: row.created_at,
                    updatedAt: row.updated_at,
                })),
                total,
                offset,
                limit,
                hasMore: offset + limit < total,
            });
        }
        catch (error) {
            res.status(500).json({ error: error.message });
        }
    });
    app.get('/api/graph/entities/:id/triples', requireNotLocked, (req, res) => {
        try {
            const db = getDatabase();
            const id = parseInt(req.params.id, 10);
            if (Number.isNaN(id)) {
                return res.status(400).json({ error: 'Invalid entity ID' });
            }
            const rows = db.prepare(`
        SELECT t.*, s.name as subject_name, s.type as subject_type,
               o.name as object_name, o.type as object_type
        FROM triples t
        JOIN entities s ON s.id = t.subject_id
        JOIN entities o ON o.id = t.object_id
        WHERE t.subject_id = ? OR t.object_id = ?
        ORDER BY t.created_at DESC
      `).all(id, id);
            res.json({ triples: rows });
        }
        catch (error) {
            res.status(500).json({ error: error.message });
        }
    });
    app.get('/api/graph/entities/:id/memories', requireNotLocked, (req, res) => {
        try {
            const db = getDatabase();
            const id = parseInt(req.params.id, 10);
            if (Number.isNaN(id)) {
                return res.status(400).json({ error: 'Invalid entity ID' });
            }
            const rows = db.prepare(`
        SELECT m.id, m.title, m.type, m.category, m.salience, m.created_at
        FROM memories m
        JOIN memory_entities me ON me.memory_id = m.id
        WHERE me.entity_id = ?
        ORDER BY m.salience DESC, m.created_at DESC
        LIMIT 50
      `).all(id);
            res.json({ memories: rows });
        }
        catch (error) {
            res.status(500).json({ error: error.message });
        }
    });
    app.get('/api/graph/entities/:id/neighbourhood', requireNotLocked, (req, res) => {
        try {
            const db = getDatabase();
            const id = parseInt(req.params.id, 10);
            if (Number.isNaN(id)) {
                return res.status(400).json({ error: 'Invalid entity ID' });
            }
            const focal = db.prepare('SELECT id, name, type, memory_count as memoryCount, aliases FROM entities WHERE id = ?').get(id);
            if (!focal) {
                return res.status(404).json({ error: 'Entity not found' });
            }
            focal.aliases = parseAliases(focal.aliases);
            const triplesAll = db.prepare(`
        SELECT t.id, t.subject_id, t.object_id, t.predicate,
               s.name as subject_name, s.type as subject_type, s.memory_count as subject_count,
               o.name as object_name, o.type as object_type, o.memory_count as object_count
        FROM triples t
        JOIN entities s ON s.id = t.subject_id
        JOIN entities o ON o.id = t.object_id
        WHERE (t.subject_id = ? OR t.object_id = ?)
        ORDER BY
          CASE WHEN t.predicate != 'related_to' THEN 0 ELSE 1 END,
          CASE WHEN t.subject_id = ? THEN o.memory_count ELSE s.memory_count END DESC
      `).all(id, id, id);
            const neighbourIds = new Map();
            const meaningfulTriples = [];
            const relatedToTriples = [];
            for (const triple of triplesAll) {
                const neighbourId = triple.subject_id === id ? triple.object_id : triple.subject_id;
                const count = triple.subject_id === id ? triple.object_count : triple.subject_count;
                if (neighbourId === id)
                    continue;
                if (triple.predicate !== 'related_to') {
                    meaningfulTriples.push(triple);
                    if (!neighbourIds.has(neighbourId)) {
                        neighbourIds.set(neighbourId, { predicate: triple.predicate, count });
                    }
                }
                else {
                    relatedToTriples.push(triple);
                }
            }
            for (const triple of relatedToTriples) {
                if (neighbourIds.size >= 25)
                    break;
                const neighbourId = triple.subject_id === id ? triple.object_id : triple.subject_id;
                const count = triple.subject_id === id ? triple.object_count : triple.subject_count;
                if (!neighbourIds.has(neighbourId)) {
                    neighbourIds.set(neighbourId, { predicate: 'related_to', count });
                }
            }
            const includedTriples = [
                ...meaningfulTriples.filter((triple) => {
                    const neighbourId = triple.subject_id === id ? triple.object_id : triple.subject_id;
                    return neighbourIds.has(neighbourId);
                }),
                ...relatedToTriples.filter((triple) => {
                    const neighbourId = triple.subject_id === id ? triple.object_id : triple.subject_id;
                    return neighbourIds.has(neighbourId);
                }),
            ];
            const seenTriples = new Set();
            const uniqueTriples = includedTriples.filter((triple) => {
                if (seenTriples.has(triple.id))
                    return false;
                seenTriples.add(triple.id);
                return true;
            });
            const neighbours = [];
            if (neighbourIds.size > 0) {
                const ids = [...neighbourIds.keys()];
                const placeholders = ids.map(() => '?').join(',');
                const rows = db.prepare(`
          SELECT id, name, type, memory_count as memoryCount, aliases
          FROM entities WHERE id IN (${placeholders})
        `).all(...ids);
                for (const row of rows) {
                    row.aliases = parseAliases(row.aliases);
                    neighbours.push(row);
                }
            }
            res.json({
                focal,
                neighbours,
                triples: uniqueTriples,
                totalConnections: triplesAll.length,
            });
        }
        catch (error) {
            res.status(500).json({ error: error.message });
        }
    });
    app.get('/api/graph/triples', requireNotLocked, (req, res) => {
        try {
            const db = getDatabase();
            const predicate = typeof req.query.predicate === 'string' ? req.query.predicate : undefined;
            const limit = typeof req.query.limit === 'string' ? Math.min(parseInt(req.query.limit, 10), 10000) : 100;
            const offset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : 0;
            let whereClause = '';
            const params = [];
            if (predicate) {
                whereClause = 'WHERE t.predicate = ?';
                params.push(predicate);
            }
            const total = db.prepare(`SELECT COUNT(*) as count FROM triples t ${whereClause}`).get(...params).count;
            const triples = db.prepare(`
        SELECT t.*, s.name as subject_name, s.type as subject_type,
               o.name as object_name, o.type as object_type
        FROM triples t
        JOIN entities s ON s.id = t.subject_id
        JOIN entities o ON o.id = t.object_id
        ${whereClause}
        ORDER BY t.created_at DESC
        LIMIT ? OFFSET ?
      `).all(...params, limit, offset);
            res.json({ triples, total, offset, limit, hasMore: offset + limit < total });
        }
        catch (error) {
            res.status(500).json({ error: error.message });
        }
    });
    app.get('/api/graph/search', requireNotLocked, (req, res) => {
        try {
            const db = getDatabase();
            const q = typeof req.query.q === 'string' ? req.query.q : '';
            const limit = Math.min(parseInt(req.query.limit, 10) || 20, 100);
            if (!q) {
                return res.status(400).json({ error: 'Query parameter "q" is required' });
            }
            const rows = db.prepare(`SELECT * FROM entities WHERE LOWER(name) LIKE ? ORDER BY memory_count DESC LIMIT ?`).all(`%${q.toLowerCase()}%`, limit);
            res.json({
                entities: rows.map((row) => ({
                    id: row.id,
                    name: row.name,
                    type: row.type,
                    memoryCount: row.memory_count ?? 0,
                    aliases: parseAliases(row.aliases),
                })),
            });
        }
        catch (error) {
            res.status(500).json({ error: error.message });
        }
    });
    app.get('/api/graph/paths', requireNotLocked, (req, res) => {
        try {
            const db = getDatabase();
            const fromName = typeof req.query.from === 'string' ? req.query.from : '';
            const toName = typeof req.query.to === 'string' ? req.query.to : '';
            if (!fromName || !toName) {
                return res.status(400).json({ error: 'Both "from" and "to" query parameters are required' });
            }
            const fromRow = db.prepare('SELECT * FROM entities WHERE LOWER(name) = LOWER(?)').get(fromName);
            if (!fromRow) {
                return res.status(404).json({ error: `Entity "${fromName}" not found` });
            }
            const toRow = db.prepare('SELECT * FROM entities WHERE LOWER(name) = LOWER(?)').get(toName);
            if (!toRow) {
                return res.status(404).json({ error: `Entity "${toName}" not found` });
            }
            if (fromRow.id === toRow.id) {
                return res.json({ path: [{ entity: fromRow.name, predicate: '(self)' }], sourceMemories: [] });
            }
            const maxDepth = 4;
            const visited = new Map();
            visited.set(fromRow.id, {
                id: fromRow.id,
                name: fromRow.name,
                parentId: null,
                predicate: '',
                sourceMemoryId: null,
            });
            let frontier = [fromRow.id];
            let found = false;
            for (let depth = 0; depth < maxDepth && !found; depth++) {
                const nextFrontier = [];
                for (const nodeId of frontier) {
                    const outgoing = db.prepare('SELECT t.object_id as next_id, t.predicate, t.source_memory_id, e.name FROM triples t JOIN entities e ON e.id = t.object_id WHERE t.subject_id = ?').all(nodeId);
                    for (const row of outgoing) {
                        if (!visited.has(row.next_id)) {
                            visited.set(row.next_id, {
                                id: row.next_id,
                                name: row.name,
                                parentId: nodeId,
                                predicate: row.predicate,
                                sourceMemoryId: row.source_memory_id,
                            });
                            nextFrontier.push(row.next_id);
                            if (row.next_id === toRow.id) {
                                found = true;
                                break;
                            }
                        }
                    }
                    if (found)
                        break;
                    const incoming = db.prepare('SELECT t.subject_id as next_id, t.predicate, t.source_memory_id, e.name FROM triples t JOIN entities e ON e.id = t.subject_id WHERE t.object_id = ?').all(nodeId);
                    for (const row of incoming) {
                        if (!visited.has(row.next_id)) {
                            visited.set(row.next_id, {
                                id: row.next_id,
                                name: row.name,
                                parentId: nodeId,
                                predicate: `~${row.predicate}`,
                                sourceMemoryId: row.source_memory_id,
                            });
                            nextFrontier.push(row.next_id);
                            if (row.next_id === toRow.id) {
                                found = true;
                                break;
                            }
                        }
                    }
                    if (found)
                        break;
                }
                frontier = nextFrontier;
                if (frontier.length === 0)
                    break;
            }
            if (!found) {
                return res.json({ path: [], sourceMemories: [], message: 'No path found' });
            }
            const path = [];
            const sourceMemoryIds = [];
            let current = visited.get(toRow.id);
            while (current) {
                path.unshift({ entity: current.name, predicate: current.predicate });
                if (current.sourceMemoryId)
                    sourceMemoryIds.push(current.sourceMemoryId);
                current = current.parentId !== null ? visited.get(current.parentId) : undefined;
            }
            const sourceMemories = sourceMemoryIds.length > 0
                ? db.prepare(`SELECT id, title FROM memories WHERE id IN (${sourceMemoryIds.map(() => '?').join(',')})`).all(...sourceMemoryIds)
                : [];
            res.json({ path, sourceMemories });
        }
        catch (error) {
            res.status(500).json({ error: error.message });
        }
    });
}
