/**
 * SessionManager — Per-session state management for multi-agent concurrency
 *
 * Each MCP session gets independent state: its own database selection, operation
 * counter, generation counter, and cached services. Multiple sessions on the
 * same database share a single SQLite connection via ConnectionCache.
 *
 * DESIGN DECISIONS:
 * - SessionManager is a singleton accessed via getSessionManager()
 * - getSession() auto-creates sessions on first access (no explicit "create" step)
 * - Operation guards are per-session: Agent A processing does not block Agent B
 * - Connection sharing is safe: Node.js single-threaded + better-sqlite3 synchronous
 *
 * CRITICAL: NEVER use console.log() — stdout is JSON-RPC (IMM-02).
 * FAIL FAST: Throw on errors, never return null or default values (AP-03, AP-05, AP-07).
 *
 * @module server/session-state
 */
import { DatabaseService } from '../services/storage/database/index.js';
import { VectorService } from '../services/storage/vector.js';
import { ConnectionCache } from './connection-cache.js';
/**
 * Notification function type for request-scoped keepalives.
 */
type NotifyFn = (notification: {
    method: string;
    params: Record<string, unknown>;
}) => Promise<void>;
/**
 * Per-session state. Each MCP session gets one of these.
 *
 * All fields that were previously global in state.ts are now per-session:
 * - currentDatabaseName + connection (via ConnectionCache)
 * - activeOperations counter
 * - dbGeneration counter
 * - cachedVectorService
 * - requestScopedNotify
 */
interface SessionState {
    /** Unique session identifier */
    readonly sessionId: string;
    /** Currently selected database name, or null if none */
    currentDatabaseName: string | null;
    /** Cached DatabaseService reference — set in selectDatabase, cleared on switch/clear */
    cachedDb: DatabaseService | null;
    /** Number of in-flight async database operations for THIS session */
    activeOperations: number;
    /** Timestamp of the most recent beginOperation() or touchOperation() call */
    lastOperationStartTime: number;
    /** Generation counter — incremented on every database switch/clear for THIS session */
    dbGeneration: number;
    /** Cached VectorService for the current database — cleared on database change */
    cachedVectorService: VectorService | null;
    /** Request-scoped notification sender for keepalives */
    requestScopedNotify: NotifyFn | null;
    /** Timestamp when this session was created */
    readonly createdAt: number;
    /** Timestamp of the most recent activity (any operation) */
    lastActivity: number;
}
/**
 * Services returned from requireDatabase — same shape as existing state.ts API.
 */
export interface DatabaseServices {
    db: DatabaseService;
    vector: VectorService;
    /** Generation counter for detecting stale references */
    generation: number;
}
/**
 * Metrics snapshot for diagnostics and monitoring.
 */
export interface SessionManagerMetrics {
    /** Number of active sessions */
    activeSessions: number;
    /** Total sessions created since startup */
    totalCreated: number;
    /** Total sessions destroyed since startup */
    totalDestroyed: number;
    /** Per-session operation counts */
    sessions: Array<{
        sessionId: string;
        currentDatabase: string | null;
        activeOperations: number;
        dbGeneration: number;
        createdAt: number;
        lastActivity: number;
    }>;
    /** Connection cache metrics */
    connectionCache: ReturnType<ConnectionCache['getMetrics']>;
}
/**
 * Maximum time (ms) an operation can be in-flight before it's considered stale.
 *
 * Active operations call touchOperation() every 15-60 seconds to refresh
 * the timestamp. If no heartbeat has been received for OPERATION_TIMEOUT_MS,
 * the operation is considered dead (crashed, disconnected, or abandoned).
 *
 * Same value as the previous global constant in state.ts.
 */
declare const OPERATION_TIMEOUT_MS: number;
/**
 * SessionManager — the core of per-session state management.
 *
 * Holds a Map<string, SessionState> for all active sessions and a shared
 * ConnectionCache for database connections. Each session operates independently:
 * its own database selection, operation counter, and generation.
 */
declare class SessionManager {
    private readonly sessions;
    private readonly connectionCache;
    /** Cumulative counters for metrics */
    private totalCreated;
    private totalDestroyed;
    /**
     * Get or create a SessionState for the given session ID.
     *
     * Auto-creates on first access — no explicit "create session" step required.
     * This keeps the API simple: tool handlers just call getSession() and it works.
     *
     * @param sessionId - The MCP session identifier
     * @returns The SessionState for this session
     */
    getSession(sessionId: string): SessionState;
    /**
     * Destroy a session — releases connection references and removes from map.
     *
     * Safe to call on a session that has already been destroyed (no-ops).
     *
     * @param sessionId - The MCP session identifier
     */
    destroySession(sessionId: string): void;
    /**
     * Select a database for a session.
     *
     * 1. Expire stale operations for this session
     * 2. If force=true, clear stuck operation counter
     * 3. If session has active operations, throw
     * 4. If session already has a DB, release old connection from cache
     * 5. Acquire new connection from cache
     * 6. Clear VectorService cache and increment generation
     * 7. Reset stuck 'processing' documents
     * 8. Load persisted config
     *
     * @param sessionId - The MCP session identifier
     * @param name - Database name to select
     * @param storagePath - Optional storage path override
     * @param force - Force-clear stuck in-flight operations before switching
     * @throws Error if operations are in-flight and force is false
     * @throws MCPError with DATABASE_NOT_FOUND if database does not exist
     */
    selectDatabase(sessionId: string, name: string, storagePath?: string, force?: boolean): void;
    /**
     * Get DatabaseService + VectorService for a session. FAIL FAST if no DB selected.
     *
     * @param sessionId - The MCP session identifier
     * @returns DatabaseServices with db, vector, and generation
     * @throws MCPError with DATABASE_NOT_SELECTED if no database is selected
     */
    requireDatabase(sessionId: string): DatabaseServices;
    /**
     * Begin tracking an in-flight operation for a session.
     *
     * Increments the session's active operation counter. While any operations
     * are active for THIS session, selectDatabase() for THIS session will throw.
     * Other sessions are NOT affected.
     *
     * @param sessionId - The MCP session identifier
     * @returns Current database generation for later validation
     * @throws MCPError if no database is selected for this session
     */
    beginOperation(sessionId: string): number;
    /**
     * End tracking an in-flight operation for a session.
     *
     * Decrements the session's active operation counter. Counter never goes below 0.
     *
     * @param sessionId - The MCP session identifier
     */
    endOperation(sessionId: string): void;
    /**
     * Refresh the operation timestamp to prevent stale-operation expiration.
     *
     * Long-running operations (e.g., CPU OCR processing 30+ minutes) must call
     * this periodically to signal they are still alive. Without this, the
     * expireStaleOperationsForSession() check would consider the operation
     * abandoned after OPERATION_TIMEOUT_MS and reset the guard.
     *
     * @param sessionId - The MCP session identifier
     */
    touchOperation(sessionId: string): void;
    /**
     * Execute an async function within a tracked database operation scope.
     *
     * Calls beginOperation() before the function and endOperation() in a finally
     * block, guaranteeing the counter is decremented even on error. Also validates
     * the generation after the function completes to detect mid-operation DB switches.
     *
     * Automatically refreshes the operation timestamp every 60 seconds to prevent
     * stale expiration during long-running work (e.g., 30+ minute CPU OCR).
     *
     * @param sessionId - The MCP session identifier
     * @param fn - Async function receiving DatabaseServices
     * @returns The result of fn
     * @throws MCPError if no database is selected
     * @throws Error if the database was switched during the operation
     */
    withDatabaseOperation<T>(sessionId: string, fn: (services: DatabaseServices) => Promise<T>): Promise<T>;
    /**
     * Clear the database selection for a session — releases connection.
     *
     * @param sessionId - The MCP session identifier
     * @param forceClose - Skip operation guard (internal use: tests, shutdown)
     * @throws Error if operations are in-flight and forceClose is false
     */
    clearDatabase(sessionId: string, forceClose?: boolean): void;
    /**
     * Check if a session has a database selected.
     *
     * @param sessionId - The MCP session identifier
     * @returns true if a database is selected
     */
    hasDatabase(sessionId: string): boolean;
    /**
     * Get the currently selected database name for a session, or null.
     *
     * @param sessionId - The MCP session identifier
     * @returns Database name or null
     */
    getCurrentDatabaseName(sessionId: string): string | null;
    /**
     * Get the number of active operations for a session (diagnostics/testing).
     *
     * @param sessionId - The MCP session identifier
     * @returns Number of active operations, or 0 if session doesn't exist
     */
    getActiveOperationCount(sessionId: string): number;
    /**
     * Validate that the database generation matches the expected value.
     *
     * The generation counter increments on every database switch/clear. A mismatch
     * means the database was switched between the time a caller obtained the
     * generation and the time it validates — indicating a stale reference.
     *
     * @param sessionId - The MCP session identifier
     * @param expectedGeneration - The generation value obtained from requireDatabase()
     * @throws Error if the current generation does not match
     */
    validateGeneration(sessionId: string, expectedGeneration: number): void;
    /**
     * Set the request-scoped notification sender for a session's keepalives.
     *
     * @param sessionId - The MCP session identifier
     * @param fn - Notification function, or null to clear
     */
    setRequestScopedNotify(sessionId: string, fn: NotifyFn | null): void;
    /**
     * Get the request-scoped notification sender for a session.
     *
     * @param sessionId - The MCP session identifier
     * @returns The notification function, or null if not set
     */
    getRequestScopedNotify(sessionId: string): NotifyFn | null;
    /**
     * Get the DatabaseService for a session from the ConnectionCache.
     * Public accessor for the bridge layer.
     *
     * @param sessionId - The MCP session identifier
     * @returns The DatabaseService for the session's current database
     * @throws MCPError if no database is selected for this session
     */
    getConnectionForSessionById(sessionId: string): DatabaseService;
    /**
     * Expire sessions that have been idle longer than ttlMs.
     *
     * Destroys sessions whose lastActivity is older than (now - ttlMs).
     * Sessions with active operations are NOT expired (they are still working).
     *
     * @param ttlMs - Maximum idle time in milliseconds
     * @returns Number of sessions expired
     */
    expireSessions(ttlMs: number): number;
    /**
     * Get a snapshot of session metrics for monitoring and health checks.
     */
    getMetrics(): SessionManagerMetrics;
    /**
     * Shut down the SessionManager — destroy all sessions and close all connections.
     * Used during process exit.
     */
    shutdown(): void;
    /**
     * Reset all state — ONLY USE IN TESTS.
     *
     * Destroys all sessions, closes all connections, and resets counters.
     */
    resetForTesting(): void;
    /**
     * Check if a session's active operations have expired and auto-reset.
     *
     * Called before any guard check to prevent permanent locks from crashed operations.
     */
    private expireStaleOperationsForSession;
    /**
     * Get the DatabaseService for a session.
     *
     * Returns the cached reference stored during selectDatabase(). The connection
     * is kept alive by the session's refCount in the ConnectionCache.
     *
     * @throws MCPError if no database is selected for this session
     */
    private getConnectionForSession;
    /**
     * Apply persisted config from a database's config_json column.
     *
     * Called after database selection to restore config values that were
     * saved in a previous session. Config is global (shared across sessions)
     * because it configures processing parameters, not per-session state.
     *
     * Returns the raw persisted config map so the bridge layer (state.ts)
     * can apply it to the global ServerConfig. Does NOT throw on failure —
     * config load is best-effort (matches original state.ts behavior).
     *
     * @param db - The newly opened DatabaseService
     * @param sessionId - For logging context
     * @returns Persisted config key-value map, or empty object on failure
     */
    applyPersistedConfig(db: DatabaseService, sessionId: string): Record<string, unknown>;
}
/**
 * Get the singleton SessionManager instance.
 *
 * Creates the instance on first call. All subsequent calls return the same instance.
 * This is the ONLY way to access the SessionManager — do NOT construct directly.
 *
 * @returns The singleton SessionManager
 */
export declare function getSessionManager(): SessionManager;
/**
 * Reset the singleton for testing. Shuts down the existing instance and
 * creates a fresh one on next getSessionManager() call.
 *
 * ONLY USE IN TESTS.
 */
export declare function resetSessionManagerForTesting(): void;
export type { SessionState, NotifyFn };
export { SessionManager, OPERATION_TIMEOUT_MS };
//# sourceMappingURL=session-state.d.ts.map