/**
 * Shared Tool Utilities
 *
 * Common types, formatters, and error handlers used across all tool modules.
 * Eliminates duplication of formatResponse, handleError, and type definitions.
 *
 * CRITICAL: NEVER use console.log() - stdout is reserved for JSON-RPC protocol.
 * Use console.error() for all logging.
 *
 * @module tools/shared
 */
import { MCPError, formatErrorResponse } from '../server/errors.js';
// ═══════════════════════════════════════════════════════════════════════════════
// HELPER FUNCTIONS
// ═══════════════════════════════════════════════════════════════════════════════
/** Max response size in bytes before truncation (700KB). Prevents Claude Code
 *  from spilling multi-MB tool results to disk as .txt files. */
const MAX_RESPONSE_BYTES = 700 * 1024;
/**
 * Format tool result as MCP content response.
 * If the serialized JSON exceeds MAX_RESPONSE_BYTES, arrays are progressively
 * truncated and a `_response_truncated` warning is injected so the AI knows
 * to paginate.
 */
export function formatResponse(result) {
    const json = JSON.stringify(result, null, 2);
    if (json.length <= MAX_RESPONSE_BYTES) {
        return { content: [{ type: 'text', text: json }] };
    }
    // Truncate: find arrays in the result and cap them
    const truncated = truncateResult(result, MAX_RESPONSE_BYTES);
    const truncatedJson = JSON.stringify(truncated, null, 2);
    return { content: [{ type: 'text', text: truncatedJson }] };
}
/**
 * Recursively truncate arrays in a result object until it fits within maxBytes.
 * Adds `_response_truncated` metadata so the AI knows to use pagination.
 */
function truncateResult(obj, maxBytes) {
    // Find all arrays and their sizes
    const arrays = [];
    findArrays(obj, [], arrays);
    // Sort by size descending — truncate largest arrays first
    arrays.sort((a, b) => b.size - a.size);
    const copy = JSON.parse(JSON.stringify(obj));
    let currentSize = JSON.stringify(copy, null, 2).length;
    const truncatedFields = [];
    for (const { path, arr } of arrays) {
        if (currentSize <= maxBytes)
            break;
        if (arr.length <= 5)
            continue; // Don't truncate tiny arrays
        // Cap to at most 50 items or fewer
        const cap = Math.min(50, Math.max(5, Math.floor(arr.length * 0.1)));
        setNestedValue(copy, path, arr.slice(0, cap));
        // Add count metadata next to the array
        const countPath = [...path.slice(0, -1), `_${path[path.length - 1]}_total`];
        setNestedValue(copy, countPath, arr.length);
        truncatedFields.push(`${path.join('.')} (${arr.length} → ${cap})`);
        currentSize = JSON.stringify(copy, null, 2).length;
    }
    if (truncatedFields.length > 0) {
        copy._response_truncated = {
            reason: `Response exceeded ${Math.round(maxBytes / 1024)}KB limit`,
            truncated_fields: truncatedFields,
            suggestion: 'Use limit/offset parameters or more specific filters to reduce response size',
        };
    }
    // If still too large after array truncation, return a minimal fallback response
    if (currentSize > maxBytes) {
        const finalJson = JSON.stringify(copy, null, 2);
        if (finalJson.length > maxBytes) {
            return {
                _response_truncated: {
                    reason: `Response exceeded ${Math.round(maxBytes / 1024)}KB limit and could not be reduced by array truncation`,
                    original_size_bytes: JSON.stringify(obj, null, 2).length,
                    suggestion: 'Use limit/offset parameters or more specific filters to reduce response size',
                },
            };
        }
    }
    return copy;
}
function findArrays(obj, path, result) {
    if (Array.isArray(obj)) {
        result.push({ path: [...path], arr: obj, size: JSON.stringify(obj).length });
        return;
    }
    if (obj && typeof obj === 'object') {
        for (const [key, value] of Object.entries(obj)) {
            findArrays(value, [...path, key], result);
        }
    }
}
function setNestedValue(obj, path, value) {
    let current = obj;
    for (let i = 0; i < path.length - 1; i++) {
        const next = current[path[i]];
        if (next && typeof next === 'object' && !Array.isArray(next)) {
            current = next;
        }
        else {
            return; // Path doesn't exist in copy
        }
    }
    current[path[path.length - 1]] = value;
}
/**
 * Handle errors uniformly - FAIL FAST
 */
export function handleError(error) {
    const mcpError = MCPError.fromUnknown(error);
    console.error(`[ERROR] ${mcpError.category}: ${mcpError.message}`);
    return {
        content: [{ type: 'text', text: JSON.stringify(formatErrorResponse(mcpError), null, 2) }],
        isError: true,
    };
}
// ═══════════════════════════════════════════════════════════════════════════════
// SHARED QUERY HELPERS
// ═══════════════════════════════════════════════════════════════════════════════
/**
 * Fetch provenance chain for a given provenance ID and attach to response object.
 * Returns the chain array on success. If provenanceId is null/undefined, returns undefined.
 *
 * FAIL FAST: If the provenance query fails, the error propagates up to the
 * tool handler's catch block where handleError() will produce a proper error
 * response. We do NOT silently swallow errors -- if include_provenance was
 * requested and the query fails, the tool should fail.
 *
 * Shared by clustering, comparison, file-management, and form-fill tools.
 */
export function fetchProvenanceChain(db, provenanceId, _logPrefix) {
    if (!provenanceId)
        return undefined;
    return db.getProvenanceChain(provenanceId);
}
//# sourceMappingURL=shared.js.map