/* eslint-disable @typescript-eslint/no-namespace */
import { AsyncLocalStorage } from 'node:async_hooks';
import { ToolArgumentValidationError, coerceToolArguments, validateToolArguments, } from './schemaValidator.js';
import { validateToolPreconditions, validateAIFlowPatterns, EDIT_WITHOUT_READ } from './toolPreconditions.js';
import { safeTruncate } from './resultVerification.js';
import { logDebug } from '../utils/debugLogger.js';
const toolExecutionContext = new AsyncLocalStorage();
/**
 * Report incremental progress for the currently executing tool.
 * Tools can call this to surface live status updates (e.g., indexing files).
 */
export function reportToolProgress(progress) {
    const context = toolExecutionContext.getStore();
    if (!context?.observer?.onToolProgress) {
        return;
    }
    const current = Number.isFinite(progress.current) ? Math.max(0, progress.current) : 0;
    const total = progress.total !== undefined ? Math.max(current, progress.total) : current;
    try {
        context.observer.onToolProgress(context.call, {
            current,
            total,
            message: progress.message,
        });
    }
    catch {
        // Swallow observer errors so they don't break tool execution
    }
}
// Idempotent tools that can be safely cached
const CACHEABLE_TOOLS = new Set([
    'Read',
    'read_file',
    'Glob',
    'glob_search',
    'Grep',
    'grep_search',
    'find_definition',
    'analyze_code_quality',
    'extract_exports',
]);
function buildOptimizationHint(warning) {
    const code = warning.code;
    const suggestion = warning.suggestion || warning.message;
    switch (code) {
        case 'SEARCH_BROAD_PATTERN':
        case 'SEARCH_CONTEXT_OVERFLOW_RISK':
            return (`\n\n<optimization-hint>\n` +
                `⚡ SEARCH: ${suggestion}\n` +
                `</optimization-hint>`);
        case EDIT_WITHOUT_READ:
        case 'EDIT_PLACEHOLDER':
            return (`\n\n<optimization-hint>\n` +
                `⚡ EDIT: Read the file first to copy exact whitespace. ${suggestion}\n` +
                `</optimization-hint>`);
        case 'NPM_INCOMPLETE_WORKFLOW':
            return (`\n\n<optimization-hint>\n` +
                `⚡ PUBLISH: ${suggestion}\n` +
                `</optimization-hint>`);
        default:
            return null;
    }
}
/**
 * Type-safe utility functions for tool runtime operations
 */
export var ToolRuntimeUtils;
(function (ToolRuntimeUtils) {
    /**
     * Creates a type-safe tool definition with inferred parameter types
     */
    function createToolDefinition(definition) {
        return definition;
    }
    ToolRuntimeUtils.createToolDefinition = createToolDefinition;
    /**
     * Creates a type-safe tool suite with inferred tool types
     */
    function createToolSuite(suite) {
        return suite;
    }
    ToolRuntimeUtils.createToolSuite = createToolSuite;
    /**
     * Type guard to check if a tool definition matches expected parameter schema
     */
    function isToolDefinition(__tool, expectedSchema) {
        if (!expectedSchema)
            return true;
        // In a real implementation, this would validate against the schema
        return true;
    }
    ToolRuntimeUtils.isToolDefinition = isToolDefinition;
})(ToolRuntimeUtils || (ToolRuntimeUtils = {}));
export class ToolRuntime {
    registry = new Map();
    registrationOrder = [];
    observer;
    contextManager;
    cache = new Map();
    enableCache;
    cacheTTLMs;
    toolHistory = [];
    maxHistorySize = 50; // Keep last 50 tool calls for AI flow pattern detection
    diffSnapshots = [];
    maxDiffSnapshots = 5; // Keep only the most recent git diff outputs
    maxDiffSnapshotLength = 4000;
    constructor(baseTools = [], options = {}) {
        this.observer = options.observer ?? null;
        this.contextManager = options.contextManager ?? null;
        this.enableCache = options.enableCache ?? true;
        this.cacheTTLMs = options.cacheTTLMs ?? 5 * 60 * 1000; // 5 minutes default
        if (baseTools.length) {
            this.registerSuite({
                id: 'runtime.core',
                description: 'Core runtime metadata tools',
                tools: baseTools,
            });
        }
    }
    registerSuite(suite) {
        if (!suite?.id?.trim()) {
            throw new Error('Tool suite id cannot be blank.');
        }
        this.unregisterSuite(suite.id);
        for (const definition of suite.tools ?? []) {
            this.addTool(definition, suite.id);
        }
    }
    unregisterSuite(id) {
        if (!id?.trim()) {
            return;
        }
        for (const [name, record] of this.registry.entries()) {
            if (record.suiteId === id) {
                this.registry.delete(name);
                this.removeFromOrder(name);
            }
        }
    }
    listProviderTools() {
        return this.registrationOrder
            .map((name) => this.registry.get(name))
            .filter((record) => Boolean(record))
            .map(({ definition }) => ({
            name: definition.name,
            description: definition.description,
            ...(definition.parameters && { parameters: definition.parameters }),
        }));
    }
    async execute(call, _context) {
        const record = this.registry.get(call.name);
        const rawArgs = normalizeToolArguments(call.arguments);
        const args = coerceToolArguments(record?.definition.parameters, rawArgs);
        const augmentedCall = { ...call, args };
        if (!record) {
            const message = `Tool "${call.name}" is not available.`;
            this.observer?.onToolError?.(augmentedCall, message);
            // Performance monitoring removed - no legacy components
            return message;
        }
        // Check if tool is cacheable
        const isCacheable = record.definition.cacheable ?? CACHEABLE_TOOLS.has(call.name);
        const cacheTtl = typeof record.definition.cacheTtlMs === 'number' && Number.isFinite(record.definition.cacheTtlMs)
            ? Math.max(0, Math.floor(record.definition.cacheTtlMs))
            : this.cacheTTLMs;
        const canUseCache = this.enableCache && isCacheable && cacheTtl > 0;
        // Try to get from cache
        if (canUseCache) {
            const cacheKey = this.getCacheKey({ ...call, arguments: args });
            const cached = this.cache.get(cacheKey);
            if (cached && Date.now() - cached.timestamp < cacheTtl) {
                this.observer?.onCacheHit?.(augmentedCall);
                this.observer?.onToolResult?.(augmentedCall, cached.result);
                // Record cache hit as successful execution with 0ms time
                // Performance monitoring removed - no legacy components
                this.recordToolHistory({
                    toolName: call.name,
                    args,
                    timestamp: Date.now(),
                    success: true,
                    hasOutput: hasNonEmptyOutput(cached.result),
                });
                return cached.result;
            }
        }
        this.observer?.onToolStart?.(augmentedCall);
        // Performance monitoring removed - no legacy components
        try {
            validateToolArguments(record.definition.name, record.definition.parameters, args);
            // Pre-flight AI flow validation - catch common tool usage failures
            const preflightWarnings = validateToolPreconditions(call.name, args);
            for (const warning of preflightWarnings) {
                this.observer?.onToolWarning?.(augmentedCall, warning);
            }
            // Advanced AI flow pattern validation using tool history
            const aiFlowWarnings = validateAIFlowPatterns(call.name, args, this.toolHistory);
            for (const warning of aiFlowWarnings) {
                this.observer?.onToolWarning?.(augmentedCall, warning);
            }
            // Collect optimization hints to inject into result
            const optimizationHints = [];
            const seenOptimizationCodes = new Set();
            for (const warning of [...preflightWarnings, ...aiFlowWarnings]) {
                if (warning.code && seenOptimizationCodes.has(warning.code)) {
                    continue;
                }
                const hint = buildOptimizationHint(warning);
                if (hint) {
                    if (warning.code) {
                        seenOptimizationCodes.add(warning.code);
                    }
                    optimizationHints.push(hint);
                }
            }
            const result = await toolExecutionContext.run({ call: augmentedCall, observer: this.observer ?? undefined }, async () => record.definition.handler(args));
            let output = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
            let snapshotCandidate = output;
            // Truncate output if context manager is available
            if (this.contextManager) {
                const truncated = this.contextManager.truncateToolOutput(output, call.name);
                if (truncated.wasTruncated) {
                    output = truncated.content;
                    // Log truncation for debugging
                    if (process.env['DEBUG_CONTEXT']) {
                        logDebug(`[Context Manager] Truncated ${call.name} output: ${truncated.originalLength} -> ${truncated.truncatedLength} chars`);
                    }
                }
                snapshotCandidate = output;
            }
            this.recordDiffSnapshot(args, snapshotCandidate);
            // Cache the result if cacheable
            if (canUseCache) {
                const cacheKey = this.getCacheKey({ ...call, arguments: args });
                this.cache.set(cacheKey, {
                    result: output,
                    timestamp: Date.now(),
                });
            }
            // Append optimization hints to guide future LLM behavior
            if (optimizationHints.length > 0) {
                output = output + optimizationHints.join('');
            }
            this.observer?.onToolResult?.(augmentedCall, output);
            this.recordToolHistory({
                toolName: call.name,
                args,
                timestamp: Date.now(),
                success: true,
                hasOutput: hasNonEmptyOutput(output),
            });
            // Record successful execution
            // Performance monitoring removed - no legacy components
            return output;
        }
        catch (error) {
            let formatted;
            if (error instanceof ToolArgumentValidationError) {
                formatted = error.message;
            }
            else {
                const message = error instanceof Error ? error.message : String(error);
                formatted = `Failed to run "${call.name}": ${message}`;
            }
            this.observer?.onToolError?.(augmentedCall, formatted);
            this.recordToolHistory({
                toolName: call.name,
                args,
                timestamp: Date.now(),
                success: false,
                hasOutput: hasNonEmptyOutput(formatted),
                error: formatted,
            });
            // Record failed execution - no legacy performance monitoring
            return formatted;
        }
    }
    getCacheKey(call) {
        return `${call.name}:${JSON.stringify(call.arguments)}`;
    }
    clearCache() {
        this.cache.clear();
    }
    getCacheStats() {
        let totalSize = 0;
        for (const entry of this.cache.values()) {
            totalSize += entry.result.length;
        }
        return {
            size: totalSize,
            entries: this.cache.size,
        };
    }
    clearToolHistory() {
        this.toolHistory.length = 0;
    }
    getToolHistory() {
        return this.toolHistory;
    }
    recordToolHistory(entry) {
        this.toolHistory.push(entry);
        if (this.toolHistory.length > this.maxHistorySize) {
            this.toolHistory.shift();
        }
    }
    clearDiffSnapshots() {
        this.diffSnapshots.length = 0;
    }
    getDiffSnapshots() {
        return this.diffSnapshots;
    }
    addTool(definition, suiteId) {
        if (!definition?.name?.trim()) {
            throw new Error(`Tool names cannot be blank (suite "${suiteId}").`);
        }
        if (this.registry.has(definition.name)) {
            const owner = this.registry.get(definition.name)?.suiteId ?? 'unknown';
            throw new Error(`Tool "${definition.name}" already registered by suite "${owner}".`);
        }
        this.registry.set(definition.name, {
            suiteId,
            definition,
        });
        this.registrationOrder.push(definition.name);
    }
    removeFromOrder(name) {
        const index = this.registrationOrder.indexOf(name);
        if (index >= 0) {
            this.registrationOrder.splice(index, 1);
        }
    }
    recordDiffSnapshot(args, output) {
        const command = this.findGitDiffCommand(args);
        if (!command || !output) {
            return;
        }
        this.diffSnapshots.push({
            command,
            output: safeTruncate(output, this.maxDiffSnapshotLength, 'git-diff'),
            timestamp: Date.now(),
        });
        if (this.diffSnapshots.length > this.maxDiffSnapshots) {
            this.diffSnapshots.shift();
        }
    }
    findGitDiffCommand(args) {
        for (const command of this.extractCommands(args)) {
            if (this.isGitDiffCommand(command)) {
                return command;
            }
        }
        return null;
    }
    extractCommands(args) {
        const commands = [];
        const command = args['command'];
        if (typeof command === 'string' && command.trim()) {
            commands.push(command.trim());
        }
        const commandList = args['commands'];
        if (Array.isArray(commandList)) {
            for (const value of commandList) {
                if (typeof value === 'string' && value.trim()) {
                    commands.push(value.trim());
                }
            }
        }
        return commands;
    }
    isGitDiffCommand(command) {
        return /\bgit\s+(?:--no-pager\s+)?(?:diff|show)\b/i.test(command);
    }
}
export function createDefaultToolRuntime(context, toolSuites = [], options = {}) {
    // Start with no default introspection tools - they waste tokens
    // Model knows its capabilities from tool definitions
    const runtime = new ToolRuntime([], options);
    for (const suite of toolSuites) {
        runtime.registerSuite(suite);
    }
    return runtime;
}
// Removed unused introspection tools (context_snapshot, capabilities_overview, profile_details)
// Model already knows capabilities from tool definitions; these just wasted tokens
function hasNonEmptyOutput(output) {
    return typeof output === 'string' && output.trim().length > 0;
}
function normalizeToolArguments(value) {
    if (value instanceof Map) {
        return Object.fromEntries(value.entries());
    }
    if (isRecord(value)) {
        return value;
    }
    if (typeof value === 'string') {
        const trimmed = value.trim();
        if (!trimmed) {
            return {};
        }
        try {
            const parsed = JSON.parse(trimmed);
            return isRecord(parsed) ? parsed : {};
        }
        catch {
            return {};
        }
    }
    return {};
}
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
//# sourceMappingURL=toolRuntime.js.map