/**
 * Database Sharing & Transfer MCP Tools
 *
 * Transfer databases between systems with metadata preservation.
 *
 * CRITICAL: NEVER use console.log() - stdout is reserved for JSON-RPC protocol.
 *
 * @module tools/sharing
 */
import { z } from 'zod';
import Database from 'better-sqlite3';
import { existsSync, statSync, mkdirSync, writeFileSync, readFileSync, copyFileSync, readdirSync, rmSync, mkdtempSync } from 'fs';
import { tmpdir } from 'os';
import { basename, dirname, join } from 'path';
import { state, selectDatabase, getDefaultStoragePath } from '../server/state.js';
import { successResult } from '../server/types.js';
import { validateInput, sanitizePath, ensureWritablePath } from '../utils/validation.js';
import { formatResponse, handleError } from './shared.js';
import { getDatabasePath, validateName, queryDatabaseCounts } from '../services/storage/database/helpers.js';
import { DatabaseError, DatabaseErrorCode } from '../services/storage/database/types.js';
import { RegistryService } from '../services/storage/registry/index.js';
// =============================================================================
// VALIDATION SCHEMAS
// =============================================================================
const DbTransferInput = z.object({
    database_name: z.string().min(1).optional().describe('Database name (uses current if not specified)'),
    output_path: z.string().min(1).describe('Output directory path for transfer bundle'),
    include_registry_metadata: z.boolean().default(true).describe('Include tags, metadata, description from registry'),
});
const DbReceiveInput = z.object({
    input_path: z.string().min(1).describe('Path to transfer bundle directory'),
    new_name: z.string().min(1).max(64).regex(/^[a-zA-Z0-9_-]+$/).optional().describe('Override database name'),
    auto_select: z.boolean().default(false).describe('Select database after import'),
});
const DbShareInput = z.object({
    database_name: z.string().min(1).optional()
        .describe('Database to share (uses current if not specified)'),
});
const DbImportSharedInput = z.object({
    bundle_name: z.string().min(1).optional()
        .describe('Name of the bundle directory in the shared folder to import'),
    new_name: z.string().min(1).max(64).regex(/^[a-zA-Z0-9_-]+$/).optional()
        .describe('Override the database name during import'),
    list: z.boolean().default(false)
        .describe('List available bundles instead of importing'),
    auto_select: z.boolean().default(false)
        .describe('Select the imported database after import'),
});
// =============================================================================
// CONSTANTS
// =============================================================================
function getSharedDir() {
    return process.env.OCR_PROVENANCE_SHARE_DIR || '/export';
}
// =============================================================================
// MANIFEST TYPE
// =============================================================================
const TransferManifestSchema = z.object({
    version: z.literal(1),
    database_name: z.string().min(1),
    created_at: z.string(),
    document_count: z.number().int().nonnegative(),
    chunk_count: z.number().int().nonnegative(),
    embedding_count: z.number().int().nonnegative(),
    size_bytes: z.number().int().nonnegative(),
    tags: z.array(z.string()).default([]),
    metadata: z.record(z.string()).default({}),
    description: z.string().nullable().default(null),
});
// =============================================================================
// HANDLER: ocr_db_transfer
// =============================================================================
async function handleDbTransfer(params) {
    try {
        const input = validateInput(DbTransferInput, params);
        // Resolve database name
        const dbName = input.database_name ?? state.currentDatabaseName;
        if (!dbName) {
            throw new DatabaseError('No database specified and no database currently selected. Provide database_name or select a database first.', DatabaseErrorCode.DATABASE_NOT_FOUND);
        }
        // Get source path and verify it exists
        const storagePath = getDefaultStoragePath();
        const sourcePath = getDatabasePath(dbName, storagePath);
        if (!existsSync(sourcePath)) {
            throw new DatabaseError(`Database file not found on disk: ${sourcePath}`, DatabaseErrorCode.DATABASE_NOT_FOUND);
        }
        // Sanitize, verify writable (not /host read-only mount), and create directory
        const safePath = ensureWritablePath(sanitizePath(input.output_path));
        mkdirSync(safePath, { recursive: true });
        // VACUUM INTO database to bundle directory
        const destDbPath = join(safePath, `${dbName}.db`);
        const sourceDb = new Database(sourcePath, { readonly: true });
        try {
            sourceDb.exec(`VACUUM INTO '${destDbPath.replace(/'/g, "''")}'`);
        }
        finally {
            sourceDb.close();
        }
        // Query counts from the backup copy
        const backupDb = new Database(destDbPath, { readonly: true });
        let counts;
        try {
            counts = queryDatabaseCounts(backupDb);
        }
        finally {
            backupDb.close();
        }
        // Get file size
        const dbStat = statSync(destDbPath);
        const sizeBytes = dbStat.size;
        // Get registry metadata if requested
        let tags = [];
        let metadata = {};
        let description = null;
        if (input.include_registry_metadata) {
            const registry = RegistryService.getInstance();
            const entry = registry.getDatabase(dbName);
            if (entry) {
                tags = entry.tags;
                metadata = entry.metadata;
                description = entry.description ?? null;
            }
        }
        // Write manifest
        const manifest = {
            version: 1,
            database_name: dbName,
            created_at: new Date().toISOString(),
            document_count: counts.document_count,
            chunk_count: counts.chunk_count,
            embedding_count: counts.embedding_count,
            size_bytes: sizeBytes,
            tags,
            metadata,
            description,
        };
        const manifestPath = join(safePath, 'manifest.json');
        writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
        console.error(`[sharing] Transfer bundle created: ${dbName} -> ${safePath} (${sizeBytes} bytes)`);
        return formatResponse(successResult({
            database_name: dbName,
            bundle_path: safePath,
            contents: [`${dbName}.db`, 'manifest.json'],
            size_bytes: sizeBytes,
            document_count: counts.document_count,
            chunk_count: counts.chunk_count,
            embedding_count: counts.embedding_count,
            next_steps: [
                { tool: 'ocr_db_receive', description: 'Import this transfer bundle on another system' },
                { tool: 'ocr_db_backup', description: 'Create a standalone backup instead' },
            ],
        }));
    }
    catch (error) {
        return handleError(error);
    }
}
// =============================================================================
// HANDLER: ocr_db_receive
// =============================================================================
async function handleDbReceive(params) {
    try {
        const input = validateInput(DbReceiveInput, params);
        // Sanitize and verify input path
        const safePath = sanitizePath(input.input_path);
        if (!existsSync(safePath) || !statSync(safePath).isDirectory()) {
            throw new DatabaseError(`Transfer bundle directory not found: ${safePath}`, DatabaseErrorCode.DATABASE_NOT_FOUND);
        }
        // Find .db file in the directory
        const dbFiles = readdirSync(safePath).filter(f => f.endsWith('.db'));
        if (dbFiles.length === 0) {
            throw new DatabaseError('No .db file found in transfer bundle', DatabaseErrorCode.DATABASE_NOT_FOUND);
        }
        if (dbFiles.length > 1) {
            throw new DatabaseError(`Multiple .db files found in transfer bundle (${dbFiles.join(', ')}), expected exactly one`, DatabaseErrorCode.SCHEMA_MISMATCH);
        }
        const sourceDbPath = join(safePath, dbFiles[0]);
        // Read manifest if it exists (optional), with runtime validation
        const manifestPath = join(safePath, 'manifest.json');
        let manifest = null;
        if (existsSync(manifestPath)) {
            try {
                const manifestContent = readFileSync(manifestPath, 'utf-8');
                const parsed = JSON.parse(manifestContent);
                const result = TransferManifestSchema.safeParse(parsed);
                if (result.success) {
                    manifest = result.data;
                }
                else {
                    console.error(`[sharing] Invalid manifest.json (ignored): ${result.error.message}`);
                }
            }
            catch (e) {
                console.error(`[sharing] Failed to parse manifest.json (ignored): ${e instanceof Error ? e.message : String(e)}`);
            }
        }
        // Integrity check on the .db file
        const checkDb = new Database(sourceDbPath, { readonly: true });
        let counts;
        try {
            const integrityResult = checkDb.pragma('integrity_check');
            const integrityStatus = integrityResult[0]?.integrity_check ?? 'unknown';
            if (integrityStatus !== 'ok') {
                throw new DatabaseError(`Database file failed integrity check: ${integrityStatus}`, DatabaseErrorCode.SCHEMA_MISMATCH);
            }
            // Query counts
            counts = queryDatabaseCounts(checkDb);
        }
        finally {
            checkDb.close();
        }
        // Determine target name
        let targetName;
        if (input.new_name) {
            targetName = input.new_name;
        }
        else if (manifest?.database_name) {
            targetName = manifest.database_name;
        }
        else {
            // Derive from .db filename
            const base = dbFiles[0];
            targetName = base.endsWith('.db') ? base.slice(0, -3) : base;
        }
        // Validate target name
        validateName(targetName);
        // Check registry AND filesystem for conflicts
        const storagePath = getDefaultStoragePath();
        const targetPath = getDatabasePath(targetName, storagePath);
        const registry = RegistryService.getInstance();
        if (registry.getDatabase(targetName)) {
            throw new DatabaseError(`Database '${targetName}' already exists in registry`, DatabaseErrorCode.DATABASE_ALREADY_EXISTS);
        }
        if (existsSync(targetPath)) {
            throw new DatabaseError(`Database file already exists on disk: ${targetPath}`, DatabaseErrorCode.DATABASE_ALREADY_EXISTS);
        }
        // Copy .db file to databases directory
        mkdirSync(dirname(targetPath), { recursive: true });
        copyFileSync(sourceDbPath, targetPath);
        // Register in registry with manifest metadata
        const tags = manifest?.tags ?? [];
        const metadata = manifest?.metadata ?? {};
        const description = manifest?.description ?? null;
        registry.registerDatabase(targetName, targetPath, description ?? undefined, tags, metadata);
        // Sync stats
        const targetStat = statSync(targetPath);
        registry.syncStats(targetName, {
            document_count: counts.document_count,
            chunk_count: counts.chunk_count,
            embedding_count: counts.embedding_count,
            size_bytes: targetStat.size,
        });
        // Auto-select if requested
        if (input.auto_select) {
            selectDatabase(targetName);
        }
        console.error(`[sharing] Receive complete: ${safePath} -> ${targetName} (${counts.document_count} docs, ${counts.chunk_count} chunks, ${counts.embedding_count} embeddings)`);
        return formatResponse(successResult({
            imported_name: targetName,
            imported_path: targetPath,
            document_count: counts.document_count,
            chunk_count: counts.chunk_count,
            embedding_count: counts.embedding_count,
            manifest_found: manifest !== null,
            auto_selected: input.auto_select,
            tags,
            metadata,
            description,
            next_steps: [
                { tool: 'ocr_db_select', description: `Select the imported database "${targetName}"` },
                { tool: 'ocr_db_stats', description: 'Verify imported database statistics' },
                { tool: 'ocr_health_check', description: 'Run health check on the imported database' },
            ],
        }));
    }
    catch (error) {
        return handleError(error);
    }
}
// =============================================================================
// HANDLER: ocr_db_share
// =============================================================================
async function handleDbShare(params) {
    try {
        const input = validateInput(DbShareInput, params);
        // Resolve database name
        const dbName = input.database_name ?? state.currentDatabaseName;
        if (!dbName) {
            throw new DatabaseError('No database specified and no database currently selected. Provide database_name or select a database first.', DatabaseErrorCode.DATABASE_NOT_FOUND);
        }
        const sharedDir = getSharedDir();
        console.error(`[sharing] Sharing database '${dbName}' to ${sharedDir}`);
        // Call handleDbTransfer internally to create the bundle
        const outputPath = join(sharedDir, `${dbName}-bundle`);
        const transferResult = await handleDbTransfer({
            database_name: dbName,
            output_path: outputPath,
            include_registry_metadata: true,
        });
        // Check if the transfer failed
        if (transferResult.isError) {
            return transferResult;
        }
        // Parse the transfer result to extract data
        const resultText = transferResult.content[0]?.text;
        if (!resultText) {
            throw new DatabaseError('Transfer returned empty result', DatabaseErrorCode.DATABASE_NOT_FOUND);
        }
        const parsed = JSON.parse(resultText);
        if (!parsed.success) {
            return transferResult;
        }
        const data = parsed.data;
        // Build host_path based on environment
        const hostHome = process.env.OCR_PROVENANCE_HOST_HOME;
        const hostPath = hostHome
            ? `${hostHome}/ocr-provenance-exports/${dbName}-bundle/`
            : data.bundle_path;
        console.error(`[sharing] Share complete: bundle_path=${data.bundle_path}, host_path=${hostPath}`);
        return formatResponse(successResult({
            host_path: hostPath,
            bundle_path: data.bundle_path,
            contents: data.contents,
            size_bytes: data.size_bytes,
            document_count: data.document_count,
            chunk_count: data.chunk_count,
            embedding_count: data.embedding_count,
            instructions: 'Bundle created. Share this folder with others. They can import it with ocr_db_import_shared.',
        }));
    }
    catch (error) {
        return handleError(error);
    }
}
// =============================================================================
// HANDLER: ocr_db_import_shared
// =============================================================================
async function handleDbImportShared(params) {
    try {
        const input = validateInput(DbImportSharedInput, params);
        const sharedDir = getSharedDir();
        // Validate shared directory exists
        if (!existsSync(sharedDir) || !statSync(sharedDir).isDirectory()) {
            throw new DatabaseError('Shared directory /export not found. Ensure the container was started with the export volume mount (-v ~/ocr-provenance-exports:/export:rw).', DatabaseErrorCode.DATABASE_NOT_FOUND);
        }
        // List mode: if list=true OR no bundle_name provided
        if (input.list || !input.bundle_name) {
            console.error(`[sharing] Listing available bundles in ${sharedDir}`);
            const entries = readdirSync(sharedDir, { withFileTypes: true });
            const bundles = [];
            // Scan for subdirectories containing at least one .db file
            for (const entry of entries) {
                if (entry.isDirectory()) {
                    const dirPath = join(sharedDir, entry.name);
                    const dirContents = readdirSync(dirPath);
                    const hasDb = dirContents.some(f => f.endsWith('.db'));
                    if (!hasDb)
                        continue;
                    let hasManifest = false;
                    let manifestDbName = null;
                    let manifestDocCount = null;
                    let sizeBytes = 0;
                    // Try reading manifest
                    const manifestPath = join(dirPath, 'manifest.json');
                    if (existsSync(manifestPath)) {
                        hasManifest = true;
                        try {
                            const manifestContent = readFileSync(manifestPath, 'utf-8');
                            const manifestParsed = JSON.parse(manifestContent);
                            const result = TransferManifestSchema.safeParse(manifestParsed);
                            if (result.success) {
                                manifestDbName = result.data.database_name;
                                manifestDocCount = result.data.document_count;
                                sizeBytes = result.data.size_bytes;
                            }
                        }
                        catch (e) {
                            console.error(`[sharing] Failed to parse manifest in ${entry.name}: ${e instanceof Error ? e.message : String(e)}`);
                        }
                    }
                    // If no manifest size, compute from .db files
                    if (sizeBytes === 0) {
                        for (const f of dirContents) {
                            if (f.endsWith('.db')) {
                                sizeBytes += statSync(join(dirPath, f)).size;
                            }
                        }
                    }
                    bundles.push({
                        name: entry.name,
                        path: dirPath,
                        has_manifest: hasManifest,
                        database_name: manifestDbName,
                        document_count: manifestDocCount,
                        size_bytes: sizeBytes,
                    });
                }
            }
            // Scan for bare .db files directly in /export
            for (const entry of entries) {
                if (entry.isFile() && entry.name.endsWith('.db')) {
                    const filePath = join(sharedDir, entry.name);
                    const fileStat = statSync(filePath);
                    const baseName = entry.name.slice(0, -3); // strip .db
                    bundles.push({
                        name: entry.name,
                        path: filePath,
                        has_manifest: false,
                        database_name: baseName,
                        document_count: null,
                        size_bytes: fileStat.size,
                    });
                }
            }
            if (bundles.length === 0) {
                throw new DatabaseError('No bundles found in /export/. Ask the sender to place the bundle in ~/ocr-provenance-exports/.', DatabaseErrorCode.DATABASE_NOT_FOUND);
            }
            console.error(`[sharing] Found ${bundles.length} bundle(s) in ${sharedDir}`);
            return formatResponse(successResult({
                bundles,
                count: bundles.length,
                shared_directory: sharedDir,
            }));
        }
        // Import mode: bundle_name is provided
        console.error(`[sharing] Importing bundle '${input.bundle_name}' from ${sharedDir}`);
        const bundlePath = sanitizePath(join(sharedDir, input.bundle_name));
        // Build receiveParams once for both directory and bare .db paths
        const receiveParams = {
            auto_select: input.auto_select,
        };
        if (input.new_name) {
            receiveParams.new_name = input.new_name;
        }
        if (existsSync(bundlePath) && statSync(bundlePath).isDirectory()) {
            receiveParams.input_path = bundlePath;
            return await handleDbReceive(receiveParams);
        }
        // Check if it's a bare .db file
        const bareDbPath = bundlePath.endsWith('.db') ? bundlePath : `${bundlePath}.db`;
        if (existsSync(bareDbPath) && statSync(bareDbPath).isFile()) {
            // Create a temporary bundle directory and copy the .db file there
            const tempDir = mkdtempSync(join(tmpdir(), 'ocr-import-'));
            console.error(`[sharing] Bare .db file detected, creating temp bundle at ${tempDir}`);
            try {
                copyFileSync(bareDbPath, join(tempDir, basename(bareDbPath)));
                receiveParams.input_path = tempDir;
                return await handleDbReceive(receiveParams);
            }
            finally {
                // Clean up temp directory
                try {
                    rmSync(tempDir, { recursive: true, force: true });
                }
                catch (cleanupErr) {
                    console.error(`[sharing] Failed to clean up temp dir ${tempDir}: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`);
                }
            }
        }
        throw new DatabaseError(`Bundle not found: '${input.bundle_name}' does not exist in ${sharedDir} as a directory or .db file`, DatabaseErrorCode.DATABASE_NOT_FOUND);
    }
    catch (error) {
        return handleError(error);
    }
}
// =============================================================================
// TOOL DEFINITIONS
// =============================================================================
export const sharingTools = {
    ocr_db_transfer: {
        description: '[SHARING] Package a database for transfer to another system. Creates a directory bundle with database file, manifest, and registry metadata.',
        inputSchema: DbTransferInput.shape,
        handler: handleDbTransfer,
    },
    ocr_db_receive: {
        description: '[SHARING] Import a transfer bundle from another system. Registers database with preserved metadata.',
        inputSchema: DbReceiveInput.shape,
        handler: handleDbReceive,
    },
    ocr_db_share: {
        description: '[SHARING] One-step database export to the shared folder. Creates a transfer bundle accessible from the host filesystem for sharing with others.',
        inputSchema: DbShareInput.shape,
        handler: handleDbShare,
    },
    ocr_db_import_shared: {
        description: '[SHARING] List or import database bundles from the shared folder. Use list:true to discover available bundles, or provide bundle_name to import.',
        inputSchema: DbImportSharedInput.shape,
        handler: handleDbImportShared,
    },
};
//# sourceMappingURL=sharing.js.map