/**
 * ConnectionCache — Ref-counted shared database connection pool
 *
 * Multiple MCP sessions on the same database share a single SQLite connection.
 * Connections are opened on first acquire and closed when refCount reaches 0.
 *
 * WHY SHARING IS SAFE:
 * - Node.js is single-threaded (event loop)
 * - better-sqlite3 operations are synchronous (blocking)
 * - Two tool handlers cannot truly execute simultaneously
 * - WAL mode supports concurrent reads on a single connection
 *
 * CRITICAL: NEVER use console.log() — stdout is JSON-RPC (IMM-02).
 * FAIL FAST: Throw on errors, never return null or default values.
 *
 * @module server/connection-cache
 */
import { DatabaseService } from '../services/storage/database/index.js';
import { databaseNotFoundError } from './errors.js';
// ═══════════════════════════════════════════════════════════════════════════════
// CONNECTION CACHE
// ═══════════════════════════════════════════════════════════════════════════════
/**
 * ConnectionCache — shared, ref-counted database connection pool.
 *
 * acquire(name, storagePath) opens or reuses a connection and increments refCount.
 * release(name) decrements refCount and closes immediately when it reaches 0.
 * closeAll() force-closes everything for shutdown.
 */
export class ConnectionCache {
    connections = new Map();
    /** Cumulative counters for metrics */
    totalOpened = 0;
    totalClosed = 0;
    cacheHits = 0;
    cacheMisses = 0;
    // ═══════════════════════════════════════════════════════════════════════════
    // ACQUIRE / RELEASE
    // ═══════════════════════════════════════════════════════════════════════════
    /**
     * Acquire a database connection. Opens on first use, reuses on subsequent calls.
     *
     * @param name - Database name (used as cache key)
     * @param storagePath - Storage directory containing the .db file
     * @returns The DatabaseService instance for this database
     * @throws MCPError with DATABASE_NOT_FOUND if database does not exist
     * @throws Error if DatabaseService.open() fails
     */
    acquire(name, storagePath) {
        const existing = this.connections.get(name);
        if (existing) {
            // Connection already open — increment ref count
            existing.refCount++;
            existing.lastAccess = Date.now();
            this.cacheHits++;
            return existing.db;
        }
        // New connection needed — verify database exists before opening
        if (!DatabaseService.exists(name, storagePath)) {
            throw databaseNotFoundError(name, storagePath);
        }
        // Open a new connection
        const db = DatabaseService.open(name, storagePath);
        const now = Date.now();
        const entry = {
            name,
            path: storagePath,
            db,
            refCount: 1,
            lastAccess: now,
            createdAt: now,
        };
        this.connections.set(name, entry);
        this.totalOpened++;
        this.cacheMisses++;
        console.error(`[connection-cache] Opened "${name}" (total open: ${this.connections.size})`);
        return db;
    }
    /**
     * Release a connection reference. Closes the connection when refCount reaches 0.
     *
     * @param name - Database name to release
     * @throws Error if no connection exists for this name
     */
    release(name) {
        const entry = this.connections.get(name);
        if (!entry) {
            throw new Error(`[connection-cache] Cannot release "${name}": no connection found in cache. ` +
                `This indicates a bug — release was called without a matching acquire.`);
        }
        entry.refCount--;
        entry.lastAccess = Date.now();
        if (entry.refCount <= 0) {
            // No more references — close the connection immediately
            this.closeAndRemove(name, entry);
            console.error(`[connection-cache] Closed "${name}" (total open: ${this.connections.size})`);
        }
    }
    // ═══════════════════════════════════════════════════════════════════════════
    // SHUTDOWN
    // ═══════════════════════════════════════════════════════════════════════════
    /**
     * Force-close all connections. Used during process shutdown.
     * Ignores refCounts — unconditionally closes everything.
     */
    closeAll() {
        const count = this.connections.size;
        if (count === 0)
            return;
        console.error(`[connection-cache] Closing all ${count} connection(s) for shutdown`);
        for (const [name, entry] of this.connections) {
            try {
                entry.db.close();
                this.totalClosed++;
            }
            catch (error) {
                // Log but continue closing remaining connections
                console.error(`[connection-cache] Error closing "${name}": ${error instanceof Error ? error.message : String(error)}`);
            }
        }
        this.connections.clear();
    }
    // ═══════════════════════════════════════════════════════════════════════════
    // DIAGNOSTICS
    // ═══════════════════════════════════════════════════════════════════════════
    /**
     * Get a snapshot of cache metrics for monitoring and health checks.
     */
    getMetrics() {
        const connections = [];
        for (const entry of this.connections.values()) {
            connections.push({
                name: entry.name,
                path: entry.path,
                refCount: entry.refCount,
                lastAccess: entry.lastAccess,
                createdAt: entry.createdAt,
            });
        }
        return {
            openConnections: this.connections.size,
            totalOpened: this.totalOpened,
            totalClosed: this.totalClosed,
            cacheHits: this.cacheHits,
            cacheMisses: this.cacheMisses,
            connections,
        };
    }
    /**
     * Check if a connection is currently cached for the given database name.
     */
    has(name) {
        return this.connections.has(name);
    }
    /**
     * Get the current refCount for a cached connection (for diagnostics/testing).
     *
     * @param name - Database name
     * @returns refCount, or 0 if no connection is cached
     */
    getRefCount(name) {
        const entry = this.connections.get(name);
        return entry ? entry.refCount : 0;
    }
    // ═══════════════════════════════════════════════════════════════════════════
    // INTERNAL
    // ═══════════════════════════════════════════════════════════════════════════
    /**
     * Close a connection and remove it from the cache.
     */
    closeAndRemove(name, entry) {
        try {
            entry.db.close();
        }
        catch (error) {
            console.error(`[connection-cache] Error closing "${name}": ${error instanceof Error ? error.message : String(error)}`);
            // Still remove from cache — the connection is dead either way
        }
        this.connections.delete(name);
        this.totalClosed++;
    }
}
//# sourceMappingURL=connection-cache.js.map