/**
 * Database Management MCP Tools
 *
 * Extracted from src/index.ts Task 19.
 * Tools: ocr_db_create, ocr_db_list, ocr_db_select, ocr_db_stats, ocr_db_delete
 *
 * CRITICAL: NEVER use console.log() - stdout is reserved for JSON-RPC protocol.
 * Use console.error() for all logging.
 *
 * @module tools/database
 */
import { z } from 'zod';
import { DatabaseService } from '../services/storage/database/index.js';
import { VectorService } from '../services/storage/vector.js';
import { state, requireDatabase, selectDatabase, createDatabase, deleteDatabase, getDefaultStoragePath, } from '../server/state.js';
import { successResult } from '../server/types.js';
import { validateInput, DatabaseCreateInput, DatabaseListInput, DatabaseSelectInput, DatabaseStatsInput, DatabaseDeleteInput, } from '../utils/validation.js';
import { formatResponse, handleError } from './shared.js';
import { logAudit } from '../services/audit.js';
import { RegistryService } from '../services/storage/registry/index.js';
// ═══════════════════════════════════════════════════════════════════════════════
// DATABASE TOOL HANDLERS
// ═══════════════════════════════════════════════════════════════════════════════
/**
 * Handle ocr_db_create - Create a new database
 */
export async function handleDatabaseCreate(params) {
    try {
        const input = validateInput(DatabaseCreateInput, params);
        const db = createDatabase(input.name, input.description, input.storage_path);
        const path = db.getPath();
        // Register in the database registry
        const registry = RegistryService.getInstance();
        registry.registerDatabase(input.name, path, input.description, input.tags, input.metadata);
        logAudit({
            action: 'db_create',
            entityType: 'database',
            entityId: input.name,
            details: { path, description: input.description },
        });
        try {
            registry.recordAccess(input.name, 'db_create', `Created database ${input.name}`);
        }
        catch { /* activity logging must never break the main operation */ }
        return formatResponse(successResult({
            name: input.name,
            path,
            created: true,
            description: input.description,
            tags: input.tags ?? [],
            metadata: input.metadata ?? {},
            next_steps: [
                { tool: 'ocr_db_select', description: 'Select this database to start using it' },
                { tool: 'ocr_ingest_files', description: 'Ingest files into the new database' },
            ],
        }));
    }
    catch (error) {
        return handleError(error);
    }
}
/**
 * Handle ocr_db_list - List all databases
 */
export async function handleDatabaseList(params) {
    try {
        const input = validateInput(DatabaseListInput, params);
        const limit = input.limit ?? 50;
        const offset = input.offset ?? 0;
        const registry = RegistryService.getInstance();
        const filters = {
            status: input.filter_status ?? 'active',
            tags: input.filter_tags,
        };
        const allDatabases = input.filter_name
            ? registry.search(input.filter_name, filters)
            : registry.search('', filters);
        // Sort
        const sortBy = input.sort_by ?? 'name';
        const sortOrder = input.sort_order ?? 'asc';
        const sortMultiplier = sortOrder === 'desc' ? -1 : 1;
        allDatabases.sort((a, b) => {
            let cmp = 0;
            switch (sortBy) {
                case 'name':
                    cmp = a.name.localeCompare(b.name);
                    break;
                case 'created':
                    cmp = a.created_at.localeCompare(b.created_at);
                    break;
                case 'modified':
                    cmp = (a.last_accessed_at ?? '').localeCompare(b.last_accessed_at ?? '');
                    break;
                case 'last_accessed':
                    cmp = (a.last_accessed_at ?? '').localeCompare(b.last_accessed_at ?? '');
                    break;
                case 'size':
                    cmp = a.size_bytes - b.size_bytes;
                    break;
            }
            return cmp * sortMultiplier;
        });
        // Paginate
        const totalCount = allDatabases.length;
        const databases = allDatabases.slice(offset, offset + limit);
        const items = databases.map(db => {
            const item = {
                name: db.name,
                path: db.file_path,
                size_bytes: db.size_bytes,
                created_at: db.created_at,
                description: db.description,
                status: db.status,
                tags: db.tags,
                last_accessed_at: db.last_accessed_at,
            };
            if (input.include_stats) {
                item.document_count = db.document_count;
                item.chunk_count = db.chunk_count;
                item.embedding_count = db.embedding_count;
            }
            return item;
        });
        const hasMore = offset + limit < totalCount;
        return formatResponse(successResult({
            databases: items,
            total: totalCount,
            returned: items.length,
            offset,
            limit,
            has_more: hasMore,
            next_steps: [
                ...(hasMore ? [{ tool: 'ocr_db_list', description: `Get next page (offset=${offset + limit})` }] : []),
                { tool: 'ocr_db_select', description: 'Select a database to work with' },
                { tool: 'ocr_db_create', description: 'Create a new database' },
                { tool: 'ocr_search_cross_db', description: 'Search across all databases' },
            ],
        }));
    }
    catch (error) {
        return handleError(error);
    }
}
/**
 * Handle ocr_db_select - Select active database
 */
export async function handleDatabaseSelect(params) {
    try {
        const input = validateInput(DatabaseSelectInput, params);
        selectDatabase(input.database_name, undefined, input.force);
        // Record access in registry
        const registry = RegistryService.getInstance();
        registry.recordAccess(input.database_name, 'select');
        const registryEntry = registry.getDatabase(input.database_name);
        const { db } = requireDatabase();
        const stats = db.getStats();
        logAudit({
            action: 'db_select',
            entityType: 'database',
            entityId: input.database_name,
            details: { document_count: stats.total_documents },
        });
        return formatResponse(successResult({
            name: input.database_name,
            path: db.getPath(),
            selected: true,
            stats: {
                document_count: stats.total_documents,
                chunk_count: stats.total_chunks,
                embedding_count: stats.total_embeddings,
                image_embedding_count: db.getConnection().prepare('SELECT COUNT(*) as c FROM embeddings WHERE image_id IS NOT NULL').get().c,
            },
            description: registryEntry?.description ?? null,
            tags: registryEntry?.tags ?? [],
            metadata: registryEntry?.metadata ?? {},
            access_count: registryEntry?.access_count ?? 0,
            next_steps: [
                { tool: 'ocr_document_list', description: 'Browse documents in this database' },
                { tool: 'ocr_search', description: 'Search for content across all documents' },
                { tool: 'ocr_db_stats', description: 'Get detailed statistics for this database' },
            ],
        }));
    }
    catch (error) {
        return handleError(error);
    }
}
/**
 * Build stats response from database and vector services
 */
function buildStatsResponse(db, _vector) {
    const stats = db.getStats();
    const conn = db.getConnection();
    // Additional overview queries
    const fileTypeDist = conn
        .prepare('SELECT file_type, COUNT(*) as count FROM documents GROUP BY file_type ORDER BY count DESC')
        .all();
    const dateRange = conn
        .prepare('SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM documents')
        .get();
    const statusDist = conn
        .prepare('SELECT status, COUNT(*) as count FROM documents GROUP BY status')
        .all();
    const qualityStats = conn
        .prepare(`SELECT AVG(parse_quality_score) as avg_quality,
            MIN(parse_quality_score) as min_quality,
            MAX(parse_quality_score) as max_quality
     FROM ocr_results WHERE parse_quality_score IS NOT NULL`)
        .get();
    const clusterSummary = conn
        .prepare(`SELECT c.id, c.label, c.document_count, c.classification_tag
     FROM clusters c ORDER BY c.document_count DESC LIMIT 5`)
        .all();
    const recentDocs = conn
        .prepare('SELECT file_name, file_type, status, page_count, created_at FROM documents ORDER BY created_at DESC LIMIT 5')
        .all();
    const totalChunks = conn.prepare('SELECT COUNT(*) as count FROM chunks').get();
    const totalEmbeddings = conn.prepare('SELECT COUNT(*) as count FROM embeddings').get();
    const totalImages = conn.prepare('SELECT COUNT(*) as count FROM images').get();
    const ftsStatus = conn.prepare('SELECT COUNT(*) as count FROM fts_index_metadata').get();
    return {
        name: db.getName(),
        path: db.getPath(),
        size_bytes: stats.storage_size_bytes,
        document_count: stats.total_documents,
        chunk_count: stats.total_chunks,
        embedding_count: stats.total_embeddings,
        image_count: stats.total_images,
        provenance_count: stats.total_provenance,
        ocr_result_count: stats.total_ocr_results,
        pending_documents: stats.documents_by_status.pending,
        processing_documents: stats.documents_by_status.processing,
        complete_documents: stats.documents_by_status.complete,
        failed_documents: stats.documents_by_status.failed,
        extraction_count: stats.total_extractions,
        form_fill_count: stats.total_form_fills,
        comparison_count: stats.total_comparisons,
        cluster_count: stats.total_clusters,
        image_embedding_count: db.getConnection().prepare('SELECT COUNT(*) as c FROM embeddings WHERE image_id IS NOT NULL').get().c,
        ocr_quality: stats.ocr_quality,
        costs: stats.costs,
        overview: {
            total_documents: stats.total_documents,
            total_chunks: totalChunks.count,
            total_embeddings: totalEmbeddings.count,
            total_images: totalImages.count,
            file_type_distribution: fileTypeDist,
            document_date_range: dateRange ?? null,
            status_distribution: statusDist,
            quality_stats: qualityStats ?? null,
            top_clusters: clusterSummary,
            recent_documents: recentDocs,
            fts_indexed: ftsStatus.count > 0,
        },
    };
}
/**
 * Handle ocr_db_stats - Get database statistics
 */
export async function handleDatabaseStats(params) {
    try {
        const input = validateInput(DatabaseStatsInput, params);
        const statsNextSteps = [
            { tool: 'ocr_document_list', description: 'Browse documents in this database' },
            { tool: 'ocr_search', description: 'Search for content across documents' },
            { tool: 'ocr_report_overview', description: 'Get quality and corpus analytics' },
            { tool: 'ocr_benchmark_compare', description: 'Benchmark search modes against each other' },
        ];
        // If database_name is provided, temporarily open that database
        if (input.database_name && input.database_name !== state.currentDatabaseName) {
            const storagePath = getDefaultStoragePath();
            const db = DatabaseService.open(input.database_name, storagePath);
            try {
                const vector = new VectorService(db.getConnection());
                const result = buildStatsResponse(db, vector);
                // Sync stats to registry
                try {
                    const registry = RegistryService.getInstance();
                    registry.syncStats(input.database_name, {
                        document_count: result.document_count,
                        chunk_count: result.chunk_count,
                        embedding_count: result.embedding_count,
                        size_bytes: result.size_bytes,
                    });
                }
                catch (syncError) {
                    console.error(`[database] Registry stats sync failed: ${syncError instanceof Error ? syncError.message : String(syncError)}`);
                }
                return formatResponse(successResult({ ...result, next_steps: statsNextSteps }));
            }
            finally {
                db.close();
            }
        }
        // Use current database
        const { db, vector } = requireDatabase();
        const result = buildStatsResponse(db, vector);
        // Sync stats to registry
        try {
            const dbName = input.database_name ?? state.currentDatabaseName;
            if (dbName) {
                const registry = RegistryService.getInstance();
                registry.syncStats(dbName, {
                    document_count: result.document_count,
                    chunk_count: result.chunk_count,
                    embedding_count: result.embedding_count,
                    size_bytes: result.size_bytes,
                });
            }
        }
        catch (syncError) {
            console.error(`[database] Registry stats sync failed: ${syncError instanceof Error ? syncError.message : String(syncError)}`);
        }
        return formatResponse(successResult({ ...result, next_steps: statsNextSteps }));
    }
    catch (error) {
        return handleError(error);
    }
}
/**
 * Handle ocr_db_delete - Delete a database
 */
export async function handleDatabaseDelete(params) {
    try {
        const input = validateInput(DatabaseDeleteInput, params);
        deleteDatabase(input.database_name);
        // Remove from registry
        const registry = RegistryService.getInstance();
        try {
            registry.unregisterDatabase(input.database_name);
        }
        catch (regError) {
            // Database may have been removed from registry during reconciliation
            console.error(`[database] Registry unregister for '${input.database_name}': ${regError instanceof Error ? regError.message : String(regError)}`);
        }
        logAudit({
            action: 'db_delete',
            entityType: 'database',
            entityId: input.database_name,
        });
        // Note: recordAccess skipped for delete — the database is already unregistered
        return formatResponse(successResult({
            name: input.database_name,
            deleted: true,
            next_steps: [{ tool: 'ocr_db_list', description: 'List remaining databases' }],
        }));
    }
    catch (error) {
        return handleError(error);
    }
}
// ═══════════════════════════════════════════════════════════════════════════════
// TOOL DEFINITIONS FOR MCP REGISTRATION
// ═══════════════════════════════════════════════════════════════════════════════
/**
 * Database tools collection for MCP server registration
 */
export const databaseTools = {
    ocr_db_create: {
        description: '[SETUP] Use to create a new database before ingesting documents. Returns database name and path. Follow with ocr_db_select, then ocr_ingest_files or ocr_ingest_directory.',
        inputSchema: {
            name: z
                .string()
                .min(1)
                .max(64)
                .regex(/^[a-zA-Z0-9_-]+$/)
                .describe('Database name (alphanumeric, underscore, hyphen only)'),
            description: z.string().max(500).optional().describe('Optional description for the database'),
            storage_path: z.string().optional().describe('Optional storage path override'),
            tags: z.array(z.string().min(1).max(50)).max(20).optional().describe('Tags for categorization'),
            metadata: z.record(z.string(), z.string().max(200)).optional().describe('Key-value metadata'),
        },
        handler: handleDatabaseCreate,
    },
    ocr_db_list: {
        description: '[ESSENTIAL] Use first to discover available databases. Returns names, sizes, and document counts. Paginated (default 50 per page). Follow with ocr_db_select to choose one.',
        inputSchema: {
            include_stats: z.boolean().default(false).describe('Include document/chunk/embedding counts'),
            limit: z
                .number()
                .int()
                .min(1)
                .max(500)
                .default(50)
                .describe('Maximum databases to return (default 50)'),
            offset: z
                .number()
                .int()
                .min(0)
                .default(0)
                .describe('Number of databases to skip for pagination'),
            filter_name: z.string().max(200).optional().describe('Filter by name substring'),
            filter_tags: z.array(z.string()).optional().describe('Filter by tags (match any)'),
            filter_status: z.enum(['active', 'archived', 'all']).default('active').describe('Filter by status'),
            sort_by: z.enum(['name', 'created', 'modified', 'last_accessed', 'size']).default('name').describe('Sort field'),
            sort_order: z.enum(['asc', 'desc']).default('asc').describe('Sort direction'),
        },
        handler: handleDatabaseList,
    },
    ocr_db_select: {
        description: '[ESSENTIAL] Use to switch active database. All subsequent tools operate on the selected database. Returns basic stats. Prerequisite for most tools. Use force: true if a previous operation crashed and left the database locked.',
        inputSchema: {
            database_name: z.string().min(1).describe('Name of the database to select'),
            force: z.boolean().optional().describe('Force-clear stuck in-flight operations before switching. Use when database is locked after a crash or disconnect.'),
        },
        handler: handleDatabaseSelect,
    },
    ocr_db_stats: {
        description: '[ESSENTIAL] Use to check database size, document counts, quality stats, and recent activity. Returns comprehensive overview including file types, clusters, and costs.',
        inputSchema: {
            database_name: z
                .string()
                .optional()
                .describe('Database name (uses current if not specified)'),
        },
        handler: handleDatabaseStats,
    },
    ocr_db_delete: {
        description: '[DESTRUCTIVE] Use to permanently delete a database and all its data. Returns confirmation. Requires confirm=true.',
        inputSchema: {
            database_name: z.string().min(1).describe('Name of the database to delete'),
            confirm: z.literal(true).describe('Must be true to confirm deletion'),
        },
        handler: handleDatabaseDelete,
    },
};
//# sourceMappingURL=database.js.map