import { BaseChatModel } from '@langchain/core/language_models/chat_models';
import { DynamicStructuredTool, DynamicTool } from '@langchain/core/tools';
import { z } from 'zod';
import { Prompt } from '../../ai/manager';
import { ElectronMCPClient } from '../../ai/mcp/electron-client';
import { MCPOutputFormatter } from '../formatters/MCPOutputFormatter';
import { KubernetesTool, KubernetesToolContext } from './kubernetes';
import { AVAILABLE_TOOLS, getToolByName } from './registry';
import { ToolBase, ToolResponse } from './ToolBase';

export class ToolManager {
  private tools: ToolBase[] = [];
  private toolHandlers: Map<string, ToolBase> = new Map();
  private mcpTools: (DynamicTool | DynamicStructuredTool)[] = [];
  private mcpToolsInitialized: boolean = false;
  private mcpInitializationPromise: Promise<void> | null = null;
  private boundModel: BaseChatModel | null = null;
  private providerId: string | null = null;
  private mcpFormatter: MCPOutputFormatter | null = null;
  private mcpClient: ElectronMCPClient;

  constructor({
    kubernetesContext,
    enabledToolIds,
  }: {
    kubernetesContext?: KubernetesToolContext;
    enabledToolIds?: string[];
  } = {}) {
    console.debug('🔧 ToolManager: Initializing with enabledToolIds:', enabledToolIds);
    if (kubernetesContext) {
      this.configureKubernetesContext(kubernetesContext);
    }
    this.mcpClient = new ElectronMCPClient();
    this.initializeTools(enabledToolIds);
    this.mcpInitializationPromise = this.initializeMCPTools();
  }

  /**
   * Initialize only enabled tools from the registry
   */
  private initializeTools(enabledToolIds?: string[]): void {
    // Initialize regular tools first
    for (const ToolClass of AVAILABLE_TOOLS) {
      const tempTool = new ToolClass();
      if (enabledToolIds && !enabledToolIds.includes(tempTool.config.name)) {
        continue; // Skip tools not enabled
      }
      try {
        this.addTool(tempTool);
      } catch (error) {
        console.error(`Failed to load tool ${ToolClass.name}:`, error);
      }
    }
    // MCP tools are initialized via mcpInitializationPromise in the constructor
  }

  /**
   * Initialize MCP tools from Electron main process
   */
  private async initializeMCPTools(): Promise<void> {
    try {
      console.debug('Initializing MCP tools from Electron...');
      if (!this.mcpClient.isAvailable()) {
        this.mcpToolsInitialized = true;
        return;
      }

      // First check the server config to know which servers are actually configured.
      // This is the user's source of truth — getToolsConfig() may return stale/cached
      // tools from servers that have been removed but not yet fully disconnected.
      const serverConfigResponse = await this.mcpClient.getConfig();
      const configuredServerNames = new Set<string>();

      if (serverConfigResponse.success && serverConfigResponse.config?.servers) {
        for (const server of serverConfigResponse.config.servers as any[]) {
          if (server.enabled !== false && server.name) {
            configuredServerNames.add(server.name);
          }
        }
      }

      // If no servers are configured (or all disabled), clear MCP tools immediately
      // regardless of what getToolsConfig() might return (stale cache)
      if (configuredServerNames.size === 0) {
        this.mcpTools = [];
        this.mcpToolsInitialized = true;
        return;
      }

      // Get tools configuration (discovered tools from connected servers)
      const toolsConfigResponse = await this.mcpClient.getToolsConfig();

      if (
        !toolsConfigResponse.success ||
        !toolsConfigResponse.config ||
        Object.keys(toolsConfigResponse.config).length === 0
      ) {
        this.mcpTools = []; // Ensure MCP tools are cleared
        this.mcpToolsInitialized = true;
        return;
      }

      // Create tools from configuration, but only from servers that are actually configured
      const mcpToolsData: any[] = [];
      Object.entries(toolsConfigResponse.config).forEach(
        ([serverName, serverTools]: [string, any]) => {
          // Skip tools from servers that are no longer in the user's config
          if (!configuredServerNames.has(serverName)) {
            return;
          }
          Object.entries(serverTools).forEach(([toolName, toolConfig]: [string, any]) => {
            const fullToolName = `${serverName}__${toolName}`;
            mcpToolsData.push({
              name: fullToolName,
              description: toolConfig.description || `Tool: ${toolName} from ${serverName} server`,
              inputSchema: toolConfig.inputSchema || {}, // Use the actual schema from MCP config
              server: serverName,
              enabled: toolConfig.enabled !== false,
            });
          });
        }
      );

      // Filter MCP tools using the new configuration system
      const filteredMcpTools: typeof mcpToolsData = [];

      for (const toolData of mcpToolsData) {
        // MCP tools are controlled by their own configuration system, not legacy enabledToolIds
        // Check if tool is enabled in MCP configuration
        if (!toolData.enabled) {
          continue;
        }

        // Then check the new MCP-specific configuration
        const isEnabled = await this.mcpClient.isToolEnabled(toolData.name);
        if (!isEnabled) {
          continue;
        }

        filteredMcpTools.push(toolData);
      }

      if (filteredMcpTools.length > 0) {
        // Convert MCP tools to LangChain DynamicStructuredTool format
        this.mcpTools = filteredMcpTools.map(
          toolData =>
            new DynamicStructuredTool({
              name: toolData.name,
              description: this.buildMCPToolDescription(toolData),
              schema:
                toolData.inputSchema && Object.keys(toolData.inputSchema).length > 0
                  ? toolData.inputSchema
                  : z.object({}),
              func: async (args: any) => {
                try {
                  // Handle argument mapping for MCP tools
                  // LangChain may wrap args in different formats, need to handle properly
                  const mappedArgs = this.mapMCPToolArguments(args, toolData.inputSchema);

                  // Execute MCP tool through Electron API
                  const result = await window.desktopApi?.mcp.executeTool(
                    toolData.name,
                    mappedArgs
                  );
                  // Extract actual result from MCP response
                  const actualResult = result?.result || result;

                  // Ensure we return a string response
                  const response =
                    typeof actualResult === 'string' ? actualResult : JSON.stringify(actualResult);
                  return response;
                } catch (error) {
                  console.error(`Error executing MCP tool ${toolData.name}:`, error);
                  throw error;
                }
              },
            })
        );

        this.mcpToolsInitialized = true;

        // If we have a bound model, rebind with the new MCP tools
        if (this.boundModel && this.providerId) {
          this.boundModel = this.bindToModel(this.boundModel, this.providerId);
        }
      } else {
        this.mcpToolsInitialized = true;
      }
    } catch (error) {
      this.mcpToolsInitialized = true;
      // Continue without MCP tools - this is not a fatal error
    }
  }

  /**
   * Build a rich description for MCP tools that includes parameter info.
   * Since DynamicTool doesn't support 'schema', we embed parameter details in the description.
   */
  private buildMCPToolDescription(toolData: any): string {
    let desc = toolData.description || `MCP tool: ${toolData.name}`;
    const schema = toolData.inputSchema;
    if (schema?.properties && Object.keys(schema.properties).length > 0) {
      const params = Object.entries(schema.properties)
        .map(([name, prop]: [string, any]) => {
          const required = schema.required?.includes(name) ? ' (required)' : '';
          return `  - ${name}${required}: ${prop.description || prop.type || 'any'}`;
        })
        .join('\n');
      desc += `\n\nParameters:\n${params}`;
    }
    return desc;
  }

  /**
   * Map LangChain tool arguments to MCP tool expected format
   * Handles common argument wrapping patterns and schema mismatches
   */
  private mapMCPToolArguments(args: any, inputSchema?: any): any {
    if (!inputSchema) {
      // If no schema, return args as-is
      return args;
    }

    const schemaProps = inputSchema?.properties;

    // Handle tools that expect no parameters
    if (!schemaProps || Object.keys(schemaProps).length === 0) {
      // Return empty object for tools that expect no parameters
      return {};
    }

    // Handle empty/null/undefined args
    if (
      !args ||
      args === '' ||
      args === '""' ||
      (typeof args === 'object' && Object.keys(args).length === 0)
    ) {
      // Check if the tool has required parameters
      const requiredProps = inputSchema?.required || [];
      if (requiredProps.length === 0) {
        // Tool has no required parameters, safe to return empty object
        return {};
      } else {
        // Tool has required parameters but got empty args - create default structure
        return this.createDefaultParameterStructure(inputSchema);
      }
    }

    // First, check if args is properly structured for the schema
    if (args && typeof args === 'object') {
      // Remove any non-schema fields like 'input' that shouldn't be there
      const schemaPropertyNames = Object.keys(schemaProps);
      const cleanArgs: any = {};

      // Copy only fields that exist in the schema
      for (const [key, value] of Object.entries(args)) {
        if (schemaPropertyNames.includes(key)) {
          cleanArgs[key] = value;
        }
      }

      // If we found valid schema fields, use the cleaned args
      if (Object.keys(cleanArgs).length > 0) {
        return this.filterMCPArguments(cleanArgs, inputSchema);
      }

      // If no valid schema fields found, check for 'input' wrapper
      if ('input' in args && !schemaProps.input) {
        const inputValue = args.input;

        // Handle empty input
        if (!inputValue || inputValue === '' || inputValue === '""') {
          const requiredProps = inputSchema?.required || [];
          if (requiredProps.length === 0) {
            return {};
          }
        }

        // If the input is a primitive value and the schema has only one property
        if (
          schemaPropertyNames.length === 1 &&
          (typeof inputValue === 'string' ||
            typeof inputValue === 'number' ||
            typeof inputValue === 'boolean')
        ) {
          // Map the primitive value to the single expected property
          return { [schemaPropertyNames[0]]: inputValue };
        }

        // If the input is an object, try to unwrap it
        if (typeof inputValue === 'object' && inputValue !== null) {
          return this.filterMCPArguments(inputValue, inputSchema);
        }

        // For primitive values with multiple schema properties, try common mappings
        if (typeof inputValue === 'string') {
          // Try common parameter names
          if (schemaProps.query) {
            return { query: inputValue };
          }
          if (schemaProps.path) {
            return { path: inputValue };
          }
          if (schemaProps.directory) {
            return { directory: inputValue };
          }
          if (schemaProps.file) {
            return { file: inputValue };
          }
          if (schemaProps.name) {
            return { name: inputValue };
          }

          // If no common mapping found, map to first required property, then first property
          const requiredProps = inputSchema?.required || [];
          const targetProp = requiredProps.length > 0 ? requiredProps[0] : schemaPropertyNames[0];
          if (targetProp) {
            return { [targetProp]: inputValue };
          }
        }

        // Return the unwrapped input and let the tool handle validation
        return inputValue;
      }
    }

    // If args structure matches or is already properly formatted, filter and return
    return this.filterMCPArguments(args, inputSchema);
  }

  /**
   * Filter MCP arguments to only include required fields and fields with actual values
   */
  private filterMCPArguments(args: any, inputSchema?: any): any {
    if (!inputSchema || !args || typeof args !== 'object') {
      return args;
    }

    const schemaProps = inputSchema?.properties;
    const requiredProps = inputSchema?.required || [];

    if (!schemaProps) {
      return args;
    }

    const filteredArgs: any = {};

    // Always include all required properties, even if they have empty/default values
    for (const requiredProp of requiredProps) {
      if (requiredProp in args) {
        filteredArgs[requiredProp] = args[requiredProp];
      } else {
        // If required property is missing, add a default value based on schema
        const propSchema = schemaProps[requiredProp];
        if (propSchema) {
          if (propSchema.type === 'string') {
            filteredArgs[requiredProp] = propSchema.default || '';
          } else if (propSchema.type === 'number' || propSchema.type === 'integer') {
            filteredArgs[requiredProp] = propSchema.default || 0;
          } else if (propSchema.type === 'boolean') {
            filteredArgs[requiredProp] = propSchema.default || false;
          } else if (propSchema.type === 'array') {
            filteredArgs[requiredProp] = propSchema.default || [];
          } else if (propSchema.type === 'object') {
            filteredArgs[requiredProp] = propSchema.default || {};
          } else {
            filteredArgs[requiredProp] = propSchema.default || {};
          }
        }
      }
    }

    // Include optional properties only if they have actual values
    for (const [key, value] of Object.entries(args)) {
      // Skip if already included as required
      if (requiredProps.includes(key)) {
        continue;
      }

      // Skip if property is not in schema
      if (!(key in schemaProps)) {
        continue;
      }

      // Include only if value is meaningful (not empty string, null, undefined, or empty object/array)
      if (this.hasActualValue(value)) {
        filteredArgs[key] = value;
      }
    }
    return filteredArgs;
  }

  /**
   * Check if a value is meaningful (not empty/null/undefined)
   */
  private hasActualValue(value: any): boolean {
    // Null, undefined, or empty string are not actual values
    if (value === null || value === undefined || value === '') {
      return false;
    }

    // Empty arrays are not actual values
    if (Array.isArray(value)) {
      return value.length > 0;
    }

    // Empty objects are not actual values
    if (typeof value === 'object') {
      return Object.keys(value).length > 0;
    }

    // Numbers (including 0), booleans, and non-empty strings are actual values
    return true;
  }

  /**
   * Create default parameter structure based on schema
   * For required parameters that are missing, provide appropriate defaults
   */
  private createDefaultParameterStructure(inputSchema: any): any {
    if (!inputSchema || !inputSchema.properties) {
      return {};
    }

    const defaultParams: any = {};
    const requiredProps = inputSchema.required || [];
    const schemaProps = inputSchema.properties;

    for (const requiredProp of requiredProps) {
      if (requiredProp in schemaProps) {
        const propSchema = schemaProps[requiredProp];

        // Create appropriate default values based on type
        if (propSchema.type === 'string') {
          defaultParams[requiredProp] = propSchema.default || '';
        } else if (propSchema.type === 'number' || propSchema.type === 'integer') {
          defaultParams[requiredProp] = propSchema.default || 0;
        } else if (propSchema.type === 'boolean') {
          defaultParams[requiredProp] = propSchema.default || false;
        } else if (propSchema.type === 'array') {
          defaultParams[requiredProp] = propSchema.default || [];
        } else if (propSchema.type === 'object') {
          defaultParams[requiredProp] = propSchema.default || {};
        } else {
          // For unknown types, try to use the default or provide an empty object
          defaultParams[requiredProp] = propSchema.default || {};
        }
      }
    }

    return defaultParams;
  }

  /**
   * Configure external dependencies for tools that need them
   */
  configureKubernetesContext(context: KubernetesToolContext): void {
    // Only configure if the tool is enabled and present
    if (!this.hasTool('kubernetes_api_request')) {
      console.warn(
        'AI Assistant: KubernetesTool is disabled or not present, skipping context configuration'
      );
      return;
    }
    const kubeTool = getToolByName('kubernetes_api_request', this.tools) as KubernetesTool;
    if (kubeTool) {
      // Only set context and log if the tool context has actually changed
      if (!kubeTool.hasContext() || kubeTool.isContextDifferent(context)) {
        kubeTool.setContext(context);
      }
    } else {
      console.warn('KubernetesTool not found, cannot configure context');
    }
  }

  /**
   * Add a tool to the manager (internal method)
   */
  private addTool(tool: ToolBase): void {
    // Check for duplicate tool names
    if (this.toolHandlers.has(tool.config.name)) {
      console.warn(`Tool with name '${tool.config.name}' already exists, skipping`);
      return;
    }

    this.tools.push(tool);
    this.toolHandlers.set(tool.config.name, tool);
  }

  /**
   * Refresh MCP tools by re-fetching configuration from the Electron backend.
   * Call this when MCP configuration changes (servers added/removed/reset).
   */
  async refreshMCPTools(): Promise<void> {
    this.mcpToolsInitialized = false;
    this.mcpTools = [];
    await this.initializeMCPTools();

    // Rebind model if we have one, to update tool bindings
    if (this.boundModel && this.providerId) {
      await this.bindToModelAsync(this.boundModel, this.providerId);
    }
  }

  /**
   * Get all configured tools as LangChain tools (including MCP tools)
   */
  getLangChainTools() {
    const regularTools = this.tools.map(tool => tool.createLangChainTool());
    return [...regularTools, ...this.mcpTools];
  }

  /**
   * Get all MCP tools as LangChain tools
   */
  getMCPTools() {
    return this.mcpTools;
  }

  /**
   * Check if a specific tool is configured (including MCP tools)
   */
  hasTool(toolName: string): boolean {
    // Check regular tools first
    if (this.toolHandlers.has(toolName)) {
      return true;
    }

    // Check MCP tools
    return this.mcpTools.some(tool => tool.name === toolName);
  }

  /**
   * Execute a tool by name with the given arguments
   */
  async executeTool(
    toolName: string,
    args: Record<string, any>,
    toolCallId?: string,
    pendingPrompt?: Prompt
  ): Promise<ToolResponse> {
    if (!this.hasTool(toolName)) {
      return {
        content: JSON.stringify({
          error: true,
          message: `Tool '${toolName}' is disabled or not available.`,
        }),
        shouldAddToHistory: true,
        shouldProcessFollowUp: false,
        metadata: { error: 'tool_disabled', toolName },
      };
    }

    // Check if it's a regular tool first
    const regularTool = this.toolHandlers.get(toolName);
    if (regularTool) {
      return await regularTool.handler(args, toolCallId, pendingPrompt);
    }

    // Check if it's an MCP tool
    const mcpTool = this.mcpTools.find(tool => tool.name === toolName);
    if (mcpTool) {
      try {
        const rawResult = await mcpTool.invoke(args);

        // Check if the raw result indicates an error
        const isError = this.detectMCPError(rawResult);

        // Format the MCP output using AI if formatter is available
        let formattedContent = rawResult;
        if (this.mcpFormatter) {
          try {
            const formatted = await this.mcpFormatter.formatMCPOutput(rawResult, toolName);
            formattedContent = JSON.stringify({
              formatted: true,
              mcpOutput: formatted,
              raw: rawResult,
              isError: isError || formatted.type === 'error',
              originalArgs: args,
            });
          } catch (formatError) {
            console.warn(`Failed to format MCP output for ${toolName}:`, formatError);
            // Fall back to simple formatting
            const simpleFormatted = this.mcpFormatter.formatSimple(rawResult, toolName);
            formattedContent = JSON.stringify({
              formatted: true,
              mcpOutput: simpleFormatted,
              raw: rawResult,
              isError: isError || simpleFormatted.type === 'error',
              originalArgs: args,
            });
          }
        } else {
          // No formatter available, but still detect errors
          if (isError) {
            formattedContent = JSON.stringify({
              error: true,
              message: this.extractErrorMessage(rawResult),
              toolName,
              raw: rawResult,
              originalArgs: args,
            });
          }
        }

        return {
          content: formattedContent,
          shouldAddToHistory: true,
          shouldProcessFollowUp: false,
          metadata: {
            toolName,
            source: 'mcp',
            formatted: !!this.mcpFormatter,
            isError:
              isError ||
              (this.mcpFormatter && JSON.parse(formattedContent).mcpOutput?.type === 'error'),
            originalArgs: args,
          },
        };
      } catch (error) {
        console.error(`Error executing MCP tool ${toolName}:`, error);

        // Format execution errors properly if formatter is available
        let errorContent: string;
        if (this.mcpFormatter) {
          const errorFormatted = {
            type: 'error' as const,
            title: `Tool Execution Failed: ${toolName}`,
            summary: 'The MCP tool failed to execute due to an internal error.',
            data: {
              message: 'Tool execution failed',
              details: error instanceof Error ? error.message : 'Unknown execution error',
              suggestions: [
                'Check if the tool is properly configured and accessible',
                'Verify the input parameters match the tool requirements',
                'Try again in a few moments as this may be a temporary issue',
              ],
            },
            insights: ['Tool execution errors may indicate configuration or connectivity issues'],
            warnings: ['This tool is currently unavailable'],
            actionable_items: ['Review tool configuration and try again'],
            metadata: {
              toolName,
              responseSize: 0,
              processingTime: 0,
              dataPoints: 0,
            },
          };

          errorContent = JSON.stringify({
            formatted: true,
            mcpOutput: errorFormatted,
            raw: error instanceof Error ? error.message : 'Unknown error',
            isError: true,
            originalArgs: args,
          });
        } else {
          errorContent = JSON.stringify({
            error: true,
            message: `Error executing MCP tool: ${
              error instanceof Error ? error.message : 'Unknown error'
            }`,
            toolName,
            originalArgs: args,
          });
        }

        return {
          content: errorContent,
          shouldAddToHistory: true,
          shouldProcessFollowUp: false,
          metadata: { error: 'mcp_execution_error', toolName, isError: true },
        };
      }
    }

    throw new Error(`Tool ${toolName} not found`);
  }

  /**
   * Bind all tools to a LangChain model
   */
  bindToModel(model: BaseChatModel, providerId: string): BaseChatModel {
    try {
      // Store for potential rebinding when MCP tools are loaded
      this.providerId = providerId;

      // Initialize MCP formatter with the model
      if (!this.mcpFormatter) {
        this.mcpFormatter = new MCPOutputFormatter(model);
      }

      const langChainTools = this.getLangChainTools();
      if (langChainTools.length === 0) {
        console.warn('No tools configured for binding');
        this.boundModel = model;
        return model;
      }

      // @todo: need to fix return type of this bindToModel method.
      // @ts-ignore
      return model.bindTools(langChainTools);
    } catch (error) {
      console.error(`Error binding tools to ${providerId} model:`, error);
      this.boundModel = model;
      return model;
    }
  }

  /**
   * Bind all tools to a LangChain model, waiting for MCP tools to initialize first
   */
  async bindToModelAsync(model: BaseChatModel, providerId: string): Promise<BaseChatModel> {
    // Wait for MCP tools to initialize before binding
    await this.waitForMCPToolsInitialization();

    return this.bindToModel(model, providerId);
  }

  /**
   * Wait for MCP tools to be initialized
   */
  async waitForMCPToolsInitialization(): Promise<void> {
    if (this.mcpInitializationPromise) {
      await this.mcpInitializationPromise;
    }
  }

  /**
   * Check if MCP tools are initialized
   */
  areMCPToolsInitialized(): boolean {
    return this.mcpToolsInitialized;
  }

  /**
   * Get list of all configured tool names (including MCP tools)
   */
  getToolNames(): string[] {
    const regularToolNames = this.tools.map(tool => tool.config.name);
    const mcpToolNames = this.mcpTools.map(tool => tool.name);
    return [...regularToolNames, ...mcpToolNames];
  }

  /**
   * Get MCP client instance for configuration management
   */
  getMCPClient(): ElectronMCPClient {
    return this.mcpClient;
  }

  /**
   * Enable or disable an MCP tool
   */
  async setMCPToolEnabled(toolName: string, enabled: boolean): Promise<boolean> {
    if (!this.mcpClient.isAvailable()) {
      return false;
    }

    const { serverName, toolName: actualToolName } = this.mcpClient.parseToolName(toolName);
    const result = await this.mcpClient.setToolEnabled(serverName, actualToolName, enabled);

    if (result) {
      // Reinitialize MCP tools to reflect the change
      this.mcpToolsInitialized = false;
      this.mcpInitializationPromise = this.initializeMCPTools();
      await this.mcpInitializationPromise;

      // If we have a bound model, rebind with the updated tools
      if (this.boundModel && this.providerId) {
        this.boundModel = this.bindToModel(this.boundModel, this.providerId);
      }
    }

    return result;
  }

  /**
   * Check if an MCP tool is enabled
   */
  async isMCPToolEnabled(toolName: string): Promise<boolean> {
    if (!this.mcpClient.isAvailable()) {
      return true;
    }
    return await this.mcpClient.isToolEnabled(toolName);
  }

  /**
   * Get MCP tool statistics
   */
  async getMCPToolStats(toolName: string): Promise<any | null> {
    if (!this.mcpClient.isAvailable()) {
      return null;
    }

    const { serverName, toolName: actualToolName } = this.mcpClient.parseToolName(toolName);
    return await this.mcpClient.getToolStats(serverName, actualToolName);
  }

  /**
   * Get MCP tools configuration
   */
  async getMCPToolsConfig(): Promise<{ success: boolean; config?: any; error?: string }> {
    if (!this.mcpClient.isAvailable()) {
      return {
        success: false,
        error: 'MCP client not available - not running in Electron environment',
      };
    }
    return await this.mcpClient.getToolsConfig();
  }

  /**
   * Update MCP tools configuration
   */
  async updateMCPToolsConfig(config: any): Promise<boolean> {
    if (!this.mcpClient.isAvailable()) {
      return false;
    }

    const result = await this.mcpClient.updateToolsConfig(config);

    if (result) {
      // Reinitialize MCP tools to reflect the changes
      this.mcpToolsInitialized = false;
      this.mcpInitializationPromise = this.initializeMCPTools();
      await this.mcpInitializationPromise;

      // If we have a bound model, rebind with the updated tools
      if (this.boundModel && this.providerId) {
        this.boundModel = this.bindToModel(this.boundModel, this.providerId);
      }
    }

    return result;
  }

  /**
   * Detect if an MCP tool result indicates an error
   */
  private detectMCPError(result: string): boolean {
    try {
      const parsed = JSON.parse(result);

      // Check for explicit error indicators
      if (parsed.success === false || parsed.error === true) {
        return true;
      }

      // Check for error messages
      if (parsed.error || parsed.message?.toLowerCase().includes('error')) {
        return true;
      }

      // Check for schema mismatch or other common error patterns
      if (typeof parsed.error === 'string' && parsed.error.length > 0) {
        return true;
      }

      return false;
    } catch {
      // If not JSON, check for common error patterns in the string
      const lowerResult = result.toLowerCase();
      return (
        lowerResult.includes('error') ||
        lowerResult.includes('failed') ||
        lowerResult.includes('exception') ||
        lowerResult.includes('invalid') ||
        lowerResult.includes('schema mismatch')
      );
    }
  }

  /**
   * Extract error message from MCP tool result
   */
  private extractErrorMessage(result: string): string {
    try {
      const parsed = JSON.parse(result);

      // Try various fields that might contain the error message
      if (parsed.error && typeof parsed.error === 'string') {
        return parsed.error;
      }

      if (parsed.message) {
        return parsed.message;
      }

      if (parsed.details) {
        return parsed.details;
      }

      return 'Tool execution failed with an unspecified error';
    } catch {
      // Not JSON, return the raw result or a cleaned version
      return result.length > 200 ? result.substring(0, 200) + '...' : result;
    }
  }
}
