import { ChatAnthropic } from '@langchain/anthropic';
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
import {
  AIMessage,
  BaseMessage,
  ChatMessage,
  HumanMessage,
  SystemMessage,
} from '@langchain/core/messages';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts';
import { RunnablePassthrough, RunnableSequence } from '@langchain/core/runnables';
import { ChatDeepSeek } from '@langchain/deepseek';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ChatMistralAI } from '@langchain/mistralai';
import { ChatOllama } from '@langchain/ollama';
import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
import sanitizeHtml from 'sanitize-html';
import AIManager, { Prompt } from '../ai/manager';
import { basePrompt } from '../ai/prompts';
import { MCPArgumentProcessor, UserContext } from '../components/mcpOutput/MCPArgumentProcessor';
import { inlineToolApprovalManager } from '../utils/InlineToolApprovalManager';
import { ToolCall } from '../utils/ToolApprovalManager';
import { isBuiltInTool } from '../utils/ToolConfigManager';
import { apiErrorPromptTemplate, toolFailurePromptTemplate } from './PromptTemplates';
import { KubernetesToolContext, ToolManager } from './tools';
import { RecommendedTool, ToolOrchestrator } from './tools/ToolOrchestrator';

export default class LangChainManager extends AIManager {
  private model: BaseChatModel;
  private boundModel: BaseChatModel | null = null;
  private providerId: string;
  private toolManager: ToolManager;
  private currentAbortController: AbortController | null = null;
  private promptTemplate: ChatPromptTemplate;
  private outputParser: StringOutputParser;
  private useDirectToolCalling: boolean = false;

  // Response cache for common queries (in-memory)
  private responseCache: Map<string, { response: Prompt; timestamp: number }> = new Map();
  private readonly CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
  private readonly MAX_CACHE_SIZE = 30; // Maximum cached responses

  constructor(providerId: string, config: Record<string, any>, enabledTools?: string[]) {
    super();
    this.providerId = providerId;
    const enabledToolIds = enabledTools ?? [];
    console.log(
      'AI Assistant: Initializing with enabled tools:',
      enabledToolIds || 'all tools enabled'
    );
    this.toolManager = new ToolManager({ enabledToolIds }); // Only enabled tools
    this.model = this.createModel(providerId, config);

    // Initialize prompt template and output parser
    this.promptTemplate = this.createPromptTemplate();
    this.outputParser = new StringOutputParser();

    // Set up event listeners for inline tool confirmations
    this.setupToolConfirmationListeners();
  }

  // Set up event listeners for tool confirmation events
  private setupToolConfirmationListeners() {
    inlineToolApprovalManager.on('request-confirmation', (data: any) => {
      // Add the tool confirmation message to chat history
      this.addToolConfirmationMessage('', data.toolConfirmation);
    });

    inlineToolApprovalManager.on('update-confirmation', (data: any) => {
      // Update the specific tool confirmation message with new state (e.g., loading)
      this.updateToolConfirmationMessage(data.requestId, data.toolConfirmation);
    });
  }

  // Helper method to extract text content from different response formats
  private extractTextContent(content: any): string {
    if (typeof content === 'string') {
      return content;
    }

    // Handle Gemini's array format: [{ type: 'text', text: '...' }, ...]
    if (Array.isArray(content)) {
      return content
        .filter(item => item && typeof item === 'object' && item.type === 'text')
        .map(item => item.text || '')
        .join('');
    }

    // Handle object format with text property
    if (content && typeof content === 'object') {
      if (content.text) {
        return content.text;
      }
      if (content.content) {
        return this.extractTextContent(content.content);
      }
    }

    // Fallback: try to stringify
    try {
      return String(content || '');
    } catch (error) {
      console.warn('Error extracting text content:', error);
      return '';
    }
  }

  // Method to abort current request
  abort() {
    if (this.currentAbortController) {
      this.currentAbortController.abort();
      this.currentAbortController = null;
    }
  }

  /**
   * Streaming version of userSend for better perceived performance
   * Yields content as it's generated by the model
   */
  async *userSendStream(message: string): AsyncGenerator<string, Prompt, undefined> {
    const userPrompt: Prompt = { role: 'user', content: message };
    this.history.push(userPrompt);

    // Check cache first
    const cacheKey = this.getCacheKey(message);
    const cached = this.responseCache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.CACHE_TTL_MS) {
      // Cache hit - yield entire cached response at once
      yield cached.response.content;
      this.history.push(cached.response);
      return cached.response;
    }

    // Create abort controller for this request
    this.currentAbortController = new AbortController();

    try {
      const modelToUse = this.boundModel || this.model;

      // Prepare messages
      const messages = [
        new SystemMessage(this.createSystemPrompt()),
        ...this.prepareChatHistory(),
        new HumanMessage(message),
      ];

      // Stream the response
      const stream = await modelToUse.stream(messages, {
        signal: this.currentAbortController?.signal,
      });

      let fullContent = '';
      let toolCalls: any[] = [];

      for await (const chunk of stream) {
        const content = this.extractTextContent(chunk.content);
        if (content) {
          fullContent += content;
          yield content;
        }

        // Collect tool calls if present (for providers that stream them)
        if (chunk.tool_calls && chunk.tool_calls.length > 0) {
          toolCalls = chunk.tool_calls;
        }
      }

      this.currentAbortController = null;

      // Create the complete response
      const assistantPrompt: Prompt = {
        role: 'assistant',
        content: fullContent,
        toolCalls:
          toolCalls.length > 0
            ? toolCalls.map(tc => ({
                type: 'function',
                id: tc.id,
                function: {
                  name: tc.name,
                  arguments: JSON.stringify(tc.args || {}),
                },
              }))
            : undefined,
      };

      // If there are tool calls, handle them with streaming
      if (toolCalls.length > 0) {
        this.history.push(assistantPrompt);

        // Execute tool calls (this is fast - 7-14ms)
        await this.handleToolCallsForStreaming(toolCalls, assistantPrompt);

        // Stream the follow-up response after tool execution
        for await (const chunk of this.processToolResponsesStream()) {
          yield chunk;
        }

        // Return the final response (already added to history by processToolResponsesStream)
        return this.history[this.history.length - 1];
      }

      this.history.push(assistantPrompt);

      // Cache non-tool responses
      if (!assistantPrompt.toolCalls || assistantPrompt.toolCalls.length === 0) {
        this.responseCache.set(cacheKey, {
          response: { ...assistantPrompt },
          timestamp: Date.now(),
        });

        if (this.responseCache.size % 5 === 0) {
          this.cleanResponseCache();
        }

        // Clear progress steps for non-tool responses
      }

      return assistantPrompt;
    } catch (error) {
      this.currentAbortController = null;
      throw error;
    }
  }

  // Create a reusable prompt template
  private createPromptTemplate(): ChatPromptTemplate {
    return ChatPromptTemplate.fromMessages([
      ['system', '{systemPrompt}'],
      new MessagesPlaceholder('chatHistory'),
      ['human', '{input}'],
    ]);
  }

  // Create a simple chain for basic responses
  private createBasicChain() {
    const modelToUse = this.boundModel || this.model;
    return this.promptTemplate.pipe(modelToUse).pipe(this.outputParser);
  }

  /**
   * Extract the base URL from an Azure OpenAI endpoint.
   * Users may paste the full API URL (e.g., https://xxx.openai.azure.com/openai/v1/chat/completions)
   * but the SDK expects only the base URL (e.g., https://xxx.openai.azure.com).
   */
  private extractAzureBaseUrl(endpoint: string): string {
    try {
      const url = new URL(endpoint);
      // Return only the origin (protocol + host), stripping any path
      return url.origin;
    } catch {
      // If URL parsing fails, fall back to stripping trailing slashes
      return endpoint.replace(/\/+$/, '');
    }
  }

  private createModel(providerId: string, config: Record<string, any>): BaseChatModel {
    const sanitizeString = (value: unknown): string =>
      typeof value === 'string' ? value.trim() : '';
    const sanitizedConfig = {
      ...config,
      apiKey: sanitizeString(config.apiKey),
      endpoint: sanitizeString(config.endpoint),
      baseUrl: sanitizeString(config.baseUrl),
      deploymentName: sanitizeString(config.deploymentName),
      model: sanitizeString(config.model),
    };

    try {
      switch (providerId) {
        case 'openai':
          if (!sanitizedConfig.apiKey) {
            throw new Error('API key is required for OpenAI');
          }
          return new ChatOpenAI({
            apiKey: sanitizedConfig.apiKey,
            model: sanitizedConfig.model,
            verbose: true,
          });
        case 'azure':
          if (
            !sanitizedConfig.apiKey ||
            !sanitizedConfig.endpoint ||
            !sanitizedConfig.deploymentName
          ) {
            throw new Error('Incomplete Azure OpenAI configuration');
          }
          return new AzureChatOpenAI({
            // Extract only the base URL (protocol + host), stripping any path
            // e.g. "https://xxx.openai.azure.com/openai/v1/chat/completions" → "https://xxx.openai.azure.com"
            azureOpenAIEndpoint: this.extractAzureBaseUrl(sanitizedConfig.endpoint),
            azureOpenAIApiKey: sanitizedConfig.apiKey,
            azureOpenAIApiDeploymentName: sanitizedConfig.deploymentName,
            azureOpenAIApiVersion: '2025-04-01-preview',
            model: sanitizedConfig.model,
            verbose: true,
          });
        case 'anthropic':
          if (!sanitizedConfig.apiKey) {
            throw new Error('API key is required for Anthropic');
          }
          return new ChatAnthropic({
            apiKey: sanitizedConfig.apiKey,
            model: sanitizedConfig.model,
            verbose: true,
          });
        case 'mistral':
          if (!sanitizedConfig.apiKey) {
            throw new Error('API key is required for Mistral AI');
          }
          return new ChatMistralAI({
            apiKey: sanitizedConfig.apiKey,
            model: sanitizedConfig.model,
            verbose: true,
          });
        case 'gemini': {
          if (!sanitizedConfig.apiKey) {
            throw new Error('API key is required for Google Gemini');
          }
          return new ChatGoogleGenerativeAI({
            apiKey: sanitizedConfig.apiKey,
            model: sanitizedConfig.model,
            verbose: true,
          });
        }
        case 'deepseek': {
          if (!sanitizedConfig.apiKey) {
            throw new Error('API key is required for DeepSeek');
          }
          return new ChatDeepSeek({
            apiKey: sanitizedConfig.apiKey,
            model: sanitizedConfig.model,
            verbose: true,
          });
        }
        case 'vllm': {
          if (!sanitizedConfig.baseUrl) {
            throw new Error('Base URL is required for vLLM');
          }
          if (!sanitizedConfig.model) {
            throw new Error('Model is required for vLLM');
          }
          return new ChatOpenAI({
            apiKey: sanitizedConfig.apiKey || 'sk-noop',
            model: sanitizedConfig.model,
            verbose: true,
            configuration: {
              baseURL: (url => (url.endsWith('/v1') ? url : `${url}/v1`))(
                sanitizedConfig.baseUrl.replace(/\/+$/, '')
              ),
            },
          });
        }
        case 'local': {
          if (!sanitizedConfig.baseUrl) {
            throw new Error('Base URL is required for local models');
          }
          const headers: Record<string, string> = {};
          if (sanitizedConfig.apiKey) {
            headers['Authorization'] = `Bearer ${sanitizedConfig.apiKey}`;
          }
          return new ChatOllama({
            baseUrl: sanitizedConfig.baseUrl,
            model: sanitizedConfig.model,
            verbose: true,
            headers: Object.keys(headers).length ? headers : undefined,
          });
        }
        default:
          throw new Error(`Unsupported provider: ${providerId}`);
      }
    } catch (error) {
      console.error(`Error creating model for provider ${providerId}:`, error);
      throw error;
    }
  }

  async configureTools(tools: any[], kubernetesContext: KubernetesToolContext): Promise<void> {
    await this.toolManager.waitForMCPToolsInitialization();

    // Configure the Kubernetes context for the KubernetesTool
    this.toolManager.configureKubernetesContext(kubernetesContext);

    // Get all tools (including MCP tools)
    const allTools = this.toolManager.getLangChainTools();

    // Bind all tools to the model for compatible providers (OpenAI, Azure, etc.)
    // Use the async version to ensure MCP tools are properly included
    this.boundModel = await this.toolManager.bindToModelAsync(this.model, this.providerId);

    // Enable direct tool calling for better performance
    if (allTools.length > 0 && this.canUseDirectToolCalling()) {
      this.useDirectToolCalling = true;
    }
  }

  /**
   * Check if the current provider can use direct tool calling
   */
  private canUseDirectToolCalling(): boolean {
    // All major providers support direct tool calling
    return ['openai', 'azure', 'anthropic', 'mistral', 'gemini', 'vllm'].includes(this.providerId);
  }

  /**
   * Build user context from current conversation and state
   */
  private buildUserContext(): UserContext {
    // Get the most recent user message
    const recentUserMessages = this.history.filter(prompt => prompt.role === 'user').slice(-3); // Last 3 user messages for context

    const userMessage =
      recentUserMessages.length > 0
        ? recentUserMessages[recentUserMessages.length - 1].content
        : '';

    // Build conversation history
    const conversationHistory = this.history
      .slice(-10) // Last 10 messages
      .map(prompt => ({
        role: prompt.role,
        content: prompt.content,
      }));

    // Get recent tool results
    const lastToolResults: Record<string, any> = {};
    const recentToolResponses = this.history.filter(prompt => prompt.role === 'tool').slice(-5); // Last 5 tool responses

    recentToolResponses.forEach(response => {
      if (response.name) {
        try {
          const parsed = JSON.parse(response.content);
          lastToolResults[response.name] = parsed;
        } catch {
          lastToolResults[response.name] = response.content;
        }
      }
    });

    return {
      userMessage,
      conversationHistory,
      lastToolResults,
      timeContext: new Date(),
    };
  }

  /**
   * Get description for a tool (for approval dialog)
   */
  private getToolDescription(toolName: string, isMCPTool: boolean): string {
    if (isMCPTool) {
      // MCP tool descriptions can be more specific based on tool name
      if (toolName.includes('trace') || toolName.includes('profile')) {
        return 'Traces system calls and processes for debugging';
      } else if (toolName.includes('network') || toolName.includes('socket')) {
        return 'Monitors network connections and traffic';
      } else if (toolName.includes('top') || toolName.includes('process')) {
        return 'Shows running processes and resource usage';
      } else if (toolName.includes('exec') || toolName.includes('run')) {
        return 'Executes commands in containers';
      } else {
        return `Inspektor Gadget debugging tool: ${toolName}`;
      }
    } else {
      // Regular Kubernetes tools
      if (toolName.includes('kubernetes')) {
        return 'Executes Kubernetes API operations';
      }
      return `Kubernetes management tool: ${toolName}`;
    }
  }

  /**
   * Add a tool confirmation message to the history
   */
  public addToolConfirmationMessage(
    content: string,
    toolConfirmation: any,
    updateHistoryCallback?: () => void
  ): void {
    const confirmationPrompt: Prompt = {
      role: 'assistant',
      content: content,
      toolConfirmation: toolConfirmation,
      isDisplayOnly: true, // Don't send to LLM
      requestId: toolConfirmation.requestId, // Add requestId for tracking
    };
    this.history.push(confirmationPrompt);

    // Call the update callback if provided to trigger UI re-render
    if (updateHistoryCallback) {
      updateHistoryCallback();
    }
  }

  public updateToolConfirmationMessage(requestId: string, updatedToolConfirmation: any): void {
    // Find the message with matching requestId
    const messageIndex = this.history.findIndex(
      prompt => prompt.requestId === requestId && prompt.toolConfirmation
    );

    if (messageIndex !== -1) {
      // Update the tool confirmation in the existing message
      this.history[messageIndex] = {
        ...this.history[messageIndex],
        toolConfirmation: updatedToolConfirmation,
      };

      // Use the inline tool approval manager to emit update event
      inlineToolApprovalManager.emit('message-updated', { requestId, updatedToolConfirmation });
    } else {
      console.warn('⚠️ LangChainManager: Could not find tool confirmation message to update');
    }
  }

  /**
   * Refresh MCP tools when configuration changes.
   * Re-fetches tools from the Electron backend and rebinds the model.
   */
  public async refreshMCPTools(): Promise<void> {
    await this.toolManager.refreshMCPTools();
    // Rebind the model to update tool bindings
    if (this.model) {
      this.boundModel = await this.toolManager.bindToModelAsync(this.model, this.providerId);
    }
  }

  /**
   * Clear the most recent tool confirmation message from history
   * Called after tool execution completes to hide the loading dialog
   */
  public clearToolConfirmation(): void {
    // Find the most recent tool confirmation message (from the end)
    for (let i = this.history.length - 1; i >= 0; i--) {
      if (this.history[i].toolConfirmation) {
        // Remove this message from history
        this.history.splice(i, 1);
        return;
      }
    }
  }

  // Helper method to prepare chat history for prompt template
  private prepareChatHistory(): BaseMessage[] {
    // Filter out system messages and display-only messages to avoid conflicts with the system message in the prompt template
    const filteredHistory = this.history.filter(
      prompt => prompt.role !== 'system' && !prompt.isDisplayOnly
    );
    return this.convertPromptsToMessages(filteredHistory);
  }

  // Helper method to create system prompt with context
  private createSystemPrompt(): string {
    const availableTools = this.toolManager.getToolNames();
    const hasKubernetesTool = availableTools.includes('kubernetes_api_request');

    let systemPromptContent;

    if (!hasKubernetesTool) {
      // Modified prompt when Kubernetes tools are disabled
      systemPromptContent = `You are an AI assistant for the Headlamp Kubernetes UI. You help users understand and manage their Kubernetes resources through a web interface.

IMPORTANT: Kubernetes API access tools are currently DISABLED in your settings.

CRITICAL LIMITATIONS:
- You CANNOT access live cluster data (pods, deployments, services, etc.)
- You CANNOT fetch current resource information from the cluster
- You CANNOT retrieve logs, events, or real-time status information
- DO NOT promise to fetch, retrieve, or access any live cluster data

WHAT YOU CAN DO:
- Provide general Kubernetes guidance and explanations
- Generate YAML examples for resource creation
- Explain Kubernetes concepts and best practices
- Help troubleshoot based on information the user provides
- Direct users to enable tools if they need live data access

WHEN USERS ASK FOR LIVE DATA:
- Clearly explain that you cannot access live cluster information
- Inform them that Kubernetes API tools are disabled
- Provide instructions to enable tools in AI Assistant settings
- Offer to help with general guidance instead

YAML FORMATTING:
When providing Kubernetes YAML examples, use this format:

## [Resource Type] Example:

Brief explanation of the resource.

\`\`\`yaml
apiVersion: [version]
kind: [kind]
metadata:
  name: [name]
  namespace: default
spec:
  # Configuration here
\`\`\`

Note: The YAML you provide will be displayed in a preview editor with an "Edit" button that allows users to modify the configuration before applying it to their cluster.

RESPONSES:
- Format responses in markdown
- Be honest about limitations
- Always suggest enabling tools for live data access
- Provide helpful general guidance when possible
- If asked non-Kubernetes questions, politely redirect and include a light Kubernetes joke`;
    } else {
      // Original prompt when tools are available
      systemPromptContent = basePrompt;
    }

    // Add MCP tool guidance if we have MCP tools available
    const mcpTools = this.toolManager.getMCPTools();
    if (mcpTools.length > 0) {
      systemPromptContent += `

MCP TOOLS AVAILABLE:
You have access to the following MCP (Model Context Protocol) tools:
${mcpTools.map(tool => `- ${tool.name}: ${tool.description || 'No description'}`).join('\n')}

CRITICAL - WHEN TO USE MCP TOOLS:
- For ANY user question that matches an available MCP tool → USE IT immediately
- Don't overthink - if there's a tool for it, use it!
- Examples:
  * User asks about time → Use time-related tools (get_current_time, convert_time, etc.)
  * User asks to search → Use search tools
  * User asks about GitHub → Use GitHub tools
  * User asks for debugging/monitoring → Use debugging tools

TOOL USAGE GUIDANCE:

PARAMETER HANDLING:
- When calling MCP tools, read the tool schema carefully and provide the required parameters
- Extract parameters from the user's request (e.g., timezone, location, dates, names, etc.)
- Use context-aware defaults when parameters aren't specified
- If a parameter is unclear, make a reasonable assumption or use the tool's default

RESPONSE FORMATTING:
When MCP tools return data:
1. **Present results clearly** - Format the response in an easy-to-read way
2. **Add context** - Explain what the results mean, don't just show raw data
3. **Be concise** - Summarize when appropriate, don't overwhelm with details
4. **Use appropriate formatting** - Tables for structured data, lists for items, code blocks for technical output

Examples of good MCP tool responses:
- Time query → "The current time is 3:45 PM EST (8:45 PM UTC)"
- Search query → "Found 5 results: [formatted list with key details]"
- Data query → "Here are the top 3 results: [table or list with relevant information]"
- Monitoring query → "Current status: [key metrics and insights]"

ALWAYS interpret results meaningfully - don't just show raw JSON or data dumps.`;
    }

    if (this.currentContext) {
      systemPromptContent += `\n\nCURRENT CONTEXT:\n${this.currentContext}`;
    }
    return systemPromptContent;
  }

  // Helper method to create system prompt specifically for tool response processing
  private createToolResponseSystemPrompt(): string {
    const baseSystemPrompt = this.createSystemPrompt();

    // Add specific instructions for tool response processing
    const toolResponseInstructions = `

IMPORTANT: You have just received tool execution results. Your task is to:

1. ANALYZE the tool results and provide a clear, helpful response to the user
2. SUMMARIZE the information in a user-friendly way
3. DO NOT call additional tools unless the user explicitly requests more actions
4. FOCUS on explaining what the tools found or accomplished
5. If the tool results show data (like file listings, directories, etc.), present them in a clear, formatted way

The user is waiting for you to explain what the tools discovered. Provide a direct, informative response based on the tool results.`;

    return baseSystemPrompt + toolResponseInstructions;
  }

  private convertPromptsToMessages(prompts: Prompt[]): BaseMessage[] {
    return prompts.map(prompt => {
      switch (prompt.role) {
        case 'system':
          return new SystemMessage(prompt.content);
        case 'user':
          return new HumanMessage(prompt.content);
        case 'assistant':
          return new AIMessage({
            content: prompt.content,
            additional_kwargs: {},
          });
        case 'tool':
          return new AIMessage(`Tool Response (${prompt.toolCallId}): ${prompt.content}`);

        default:
          return new ChatMessage(prompt.content, prompt.role);
      }
    });
  }

  /**
   * Generate cache key for a message (simple hash)
   */
  private getCacheKey(message: string): string {
    // Create a deterministic hash from message + recent context
    const contextStr = this.history
      .slice(-3) // Include last 3 messages for context
      .map(p => `${p.role}:${p.content?.substring(0, 100)}`)
      .join('|');

    const fullStr = `${contextStr}|${message}`;
    let hash = 0;
    for (let i = 0; i < fullStr.length; i++) {
      const char = fullStr.charCodeAt(i);
      hash = (hash << 5) - hash + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    return `msg_${hash}_${message.length}`;
  }

  /**
   * Clean expired cache entries
   */
  private cleanResponseCache(): void {
    const now = Date.now();
    const expiredKeys: string[] = [];

    for (const [key, value] of this.responseCache.entries()) {
      if (now - value.timestamp > this.CACHE_TTL_MS) {
        expiredKeys.push(key);
      }
    }

    expiredKeys.forEach(key => this.responseCache.delete(key));

    // If cache is still too large, remove oldest entries
    if (this.responseCache.size > this.MAX_CACHE_SIZE) {
      const entries = Array.from(this.responseCache.entries()).sort(
        (a, b) => a[1].timestamp - b[1].timestamp
      );
      const toRemove = entries.slice(0, this.responseCache.size - this.MAX_CACHE_SIZE);
      toRemove.forEach(([key]) => this.responseCache.delete(key));
    }
  }

  /**
   * =============================================================================
   * HISTORY MANAGEMENT PATTERN (NEW - Simplified)
   * =============================================================================
   *
   * PROBLEM: History was being pushed in 28+ different places, causing:
   * - Confusion about who manages history
   * - Easy to forget to push or push twice
   * - Hard to trace history state
   *
   * NEW PATTERN:
   * 1. Only userSend() and userSendStream() manage history (main entry points)
   * 2. All internal methods return Prompt WITHOUT pushing to history
   * 3. Use addToHistory() helper for explicit history updates
   *
   * MIGRATION GUIDE:
   * - OLD: method() { const p = {...}; this.history.push(p); return p; }
   * - NEW: method() { return {...}; }  // userSend will handle history
   *
   * EXCEPTIONS (when to push directly):
   * - Tool confirmation messages (temporary UI state)
   * - Error messages that need immediate display
   * - Multi-step operations where intermediate state matters
   *
   * =============================================================================
   */

  /**
   * Centralized method to add message to history
   * Use this instead of direct this.history.push()
   */
  private addToHistory(prompt: Prompt): Prompt {
    this.history.push(prompt);
    return prompt;
  }

  /**
   * Helper to create and add user message to history
   */
  private addUserMessage(content: string): Prompt {
    return this.addToHistory({ role: 'user', content });
  }

  /**
   * Helper to create and add assistant message to history
   */
  private addAssistantMessage(content: string, additional?: Partial<Prompt>): Prompt {
    return this.addToHistory({ role: 'assistant', content, ...additional });
  }

  async userSend(message: string): Promise<Prompt> {
    // Clear previous progress steps

    const userPrompt: Prompt = { role: 'user', content: message };
    this.history.push(userPrompt);

    // Check cache first for non-tool-dependent queries
    const cacheKey = this.getCacheKey(message);
    const cached = this.responseCache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.CACHE_TTL_MS) {
      // Cache hit - return cached response
      this.history.push(cached.response);
      return cached.response;
    }

    // Create abort controller for this request
    this.currentAbortController = new AbortController();

    try {
      // FIRST: Try to orchestrate multiple relevant tools before making LLM call
      // This enables multi-tool execution for comprehensive responses
      const recommendedTools = await this.orchestrateToolsForRequest(message);

      if (recommendedTools && recommendedTools.length > 0) {
        // Execute multiple tools together for a comprehensive response
        return await this.handleMultipleToolExecution(message, recommendedTools);
      }

      // FALLBACK: Use direct tool calling if enabled
      if (this.useDirectToolCalling) {
        return await this.handleDirectToolCallingRequest(message);
      }

      const modelToUse = this.boundModel || this.model;

      // For local models, use simplified approach
      if (this.providerId === 'local') {
        return await this.handleLocalModelRequest(message, modelToUse);
      }

      // Use chain-based approach for other models
      const response = await this.handleChainBasedRequest(message, modelToUse);

      // Cache successful non-tool responses
      if (!response.toolCalls || response.toolCalls.length === 0) {
        this.responseCache.set(cacheKey, {
          response: { ...response },
          timestamp: Date.now(),
        });

        // Clean cache periodically
        if (this.responseCache.size % 5 === 0) {
          this.cleanResponseCache();
        }
      }

      return response;
    } catch (error) {
      return this.handleUserSendError(error);
    }
  }

  // Handle requests using direct tool calling (single LLM call)
  private async handleDirectToolCallingRequest(message: string): Promise<Prompt> {
    try {
      const modelToUse = this.boundModel || this.model;

      // Prepare input for the model with tools
      const chainInput = {
        systemPrompt: this.createSystemPrompt(),
        chatHistory: this.prepareChatHistory(),
        input: message,
      };

      // Convert chain input to messages
      const messages = [
        new SystemMessage(chainInput.systemPrompt),
        ...chainInput.chatHistory,
        new HumanMessage(chainInput.input),
      ];

      // Single LLM call with tool capabilities
      const response = await modelToUse.invoke(messages, {
        signal: this.currentAbortController?.signal,
      });

      this.currentAbortController = null;

      // Handle tool calls if present
      if (response.tool_calls?.length) {
        return await this.handleToolCalls(response);
      } else {
        // Handle regular response
        const assistantPrompt: Prompt = {
          role: 'assistant',
          content: this.extractTextContent(response.content),
        };
        this.history.push(assistantPrompt);

        // Clear progress steps for non-tool responses

        return assistantPrompt;
      }
    } catch (error) {
      console.error('Error in direct tool calling request:', error);

      // If direct tool calling fails, fall back to regular approach
      this.useDirectToolCalling = false;

      const modelToUse = this.boundModel || this.model;
      return await this.handleChainBasedRequest(message, modelToUse);
    }
  }

  // Handle requests for local models (simplified)
  private async handleLocalModelRequest(message: string, model: BaseChatModel): Promise<Prompt> {
    const systemMessage = new SystemMessage(this.createSystemPrompt());
    const userMessage = new HumanMessage(message);
    const messages = [systemMessage, userMessage];

    const response = await model.invoke(messages, {
      signal: this.currentAbortController.signal,
    });

    this.currentAbortController = null;

    const assistantPrompt: Prompt = {
      role: 'assistant',
      content: this.extractTextContent(response.content),
    };
    this.history.push(assistantPrompt);

    // Clear progress steps for local model responses

    return assistantPrompt;
  }

  // Handle requests using chain-based approach
  private async handleChainBasedRequest(message: string, model: BaseChatModel): Promise<Prompt> {
    // Prepare input for the chain
    const chainInput = {
      systemPrompt: this.createSystemPrompt(),
      chatHistory: this.prepareChatHistory(),
      input: message,
    };

    // For models with tools, use direct invocation to handle tool calls
    if (this.boundModel) {
      return await this.handleToolEnabledRequest(chainInput, model);
    }

    // For simple requests without tools, use the chain
    const chain = this.createBasicChain();
    const response = await chain.invoke(chainInput, {
      signal: this.currentAbortController.signal,
    });

    this.currentAbortController = null;

    const assistantPrompt: Prompt = {
      role: 'assistant',
      content: this.extractTextContent(response),
    };
    this.history.push(assistantPrompt);

    // Clear progress steps for chain-based responses

    return assistantPrompt;
  }

  // Handle requests for models with tools enabled
  private async handleToolEnabledRequest(chainInput: any, model: BaseChatModel): Promise<Prompt> {
    // Convert chain input to messages for tool-enabled models
    const messages = [
      new SystemMessage(chainInput.systemPrompt),
      ...chainInput.chatHistory,
      new HumanMessage(chainInput.input),
    ];

    // IMPORTANT: Use the boundModel (which has tools) instead of the original model
    const modelToUse = this.boundModel || model;
    const response = await modelToUse.invoke(messages, {
      signal: this.currentAbortController.signal,
    });

    this.currentAbortController = null;

    // Handle tool calls if present
    if (response.tool_calls?.length) {
      return await this.handleToolCalls(response);
    }

    // Handle regular response
    const assistantPrompt: Prompt = {
      role: 'assistant',
      content: this.extractTextContent(response.content),
    };
    this.history.push(assistantPrompt);

    // Clear progress steps after generating response without tool calls

    return assistantPrompt;
  }

  /**
   * Analyze user request to determine ALL relevant tools that should be executed together.
   * Only triggers when MCP tools are available (built-in K8s tool uses direct tool calling).
   * This avoids an extra LLM call for every message when only K8s tool is present.
   */
  private async orchestrateToolsForRequest(userMessage: string): Promise<RecommendedTool[] | null> {
    try {
      // Only orchestrate when MCP tools are available
      // The built-in K8s tool works fine with direct tool calling and doesn't need orchestration
      const mcpTools = this.toolManager.getMCPTools();
      if (mcpTools.length === 0) {
        return null;
      }

      // Quick pre-check: skip orchestration for very short or clearly conversational messages
      // to avoid an expensive LLM call on every message
      const trimmedMessage = userMessage.trim().toLowerCase();
      if (trimmedMessage.length < 10) {
        return null;
      }
      const conversationalPatterns = [
        /^(hi|hello|hey|thanks|thank you|ok|okay|yes|no|sure|great|cool|bye|goodbye)\b/i,
        /^(what can you do|who are you|help me)\b/i,
      ];
      if (conversationalPatterns.some(p => p.test(trimmedMessage))) {
        return null;
      }

      const enabledToolIds = this.toolManager.getToolNames();

      if (enabledToolIds.length === 0) {
        return null;
      }

      // IMPORTANT: Only pass MCP tools to the orchestrator, NOT built-in tools.
      // Built-in tools like kubernetes_api_request work much better with the LLM's
      // native tool calling where the model generates proper URLs with actual values.
      // The orchestrator generates template URLs like /api/v1/namespaces/{namespace}
      // which don't work as real API requests.
      const availableTools = mcpTools.map(tool => ({
        name: tool.name,
        description: tool.description || '',
      }));

      // If no MCP tools to orchestrate, skip
      if (availableTools.length === 0) {
        return null;
      }

      // Use ToolOrchestrator to analyze and recommend tools
      const recommendation = await ToolOrchestrator.analyzeAndRecommendTools(
        userMessage,
        availableTools,
        this.boundModel || this.model,
        this.history.slice(-10), // Pass last 10 messages for context
        this.currentAbortController?.signal
      );

      // Only use orchestration when multiple tools are recommended.
      // Single tool recommendations should use the normal direct tool calling flow
      // which produces better arguments (especially for kubernetes_api_request).
      if (recommendation.shouldExecuteAll && recommendation.tools.length >= 2) {
        return recommendation.tools;
      }

      // Single or zero tools — let the normal LLM tool-calling flow handle it
      return null;
    } catch (error) {
      return null;
    }
  }

  /**
   * Execute multiple tools together based on orchestration recommendation
   * Requests approval before executing each batch of tools
   * Collects results and provides a comprehensive response
   */
  private async handleMultipleToolExecution(
    userMessage: string,
    recommendedTools: RecommendedTool[]
  ): Promise<Prompt> {
    try {
      // Prepare tools with enhanced arguments (using same pattern as regular tool execution)
      const toolsForApproval = await Promise.all(
        recommendedTools.map(async tool => {
          const isMCPTool = !isBuiltInTool(tool.name);
          let processedArguments = tool.arguments || {};

          // Use AI to enhance arguments for MCP tools (same as regular flow)
          if (isMCPTool) {
            try {
              const toolSchema = await MCPArgumentProcessor.getToolSchema(tool.name);
              if (toolSchema) {
                // Build user context from current conversation
                const userContext = this.buildUserContext();

                // Store original arguments for comparison
                const originalArguments = { ...processedArguments };

                // Use AI to intelligently prepare arguments
                processedArguments = await this.enhanceArgumentsWithAI(
                  tool.name,
                  toolSchema,
                  userContext,
                  processedArguments
                );

                // Mark which fields were enhanced by LLM for UI display
                processedArguments._llmEnhanced = {
                  enhanced: true,
                  originalArgs: originalArguments,
                  enhancedFields: this.identifyEnhancedFields(
                    originalArguments,
                    processedArguments
                  ),
                };
              }
            } catch (error) {
              console.warn(`Failed to enhance arguments for ${tool.name}:`, error);
              // Fall back to original arguments
            }
          }

          return {
            id: `orchestrated-${tool.name}-${Date.now()}`,
            name: tool.name,
            description: tool.description,
            arguments: processedArguments,
            type: isMCPTool ? 'mcp' : 'regular',
            priority: tool.priority,
            reason: tool.reason,
          };
        })
      );

      const approvedToolIds: string[] = [];

      // Separate built-in tools from MCP tools (same pattern as handleToolCalls)
      const builtInToolsForApproval = toolsForApproval.filter(tool => isBuiltInTool(tool.name));
      const mcpToolsForApproval = toolsForApproval.filter(tool => !isBuiltInTool(tool.name));

      // Auto-approve all built-in tools (no user interaction needed)
      approvedToolIds.push(...builtInToolsForApproval.map(tool => tool.id));

      // Only request approval for MCP tools
      if (mcpToolsForApproval.length > 0) {
        try {
          const approvedMCPToolIds = await inlineToolApprovalManager.requestApproval(
            mcpToolsForApproval,
            this
          );
          approvedToolIds.push(...approvedMCPToolIds);
        } catch (approvalError) {
          // If user denied MCP tools but built-in tools were approved, continue with built-in only
          if (builtInToolsForApproval.length === 0) {
            const denialPrompt: Prompt = {
              role: 'assistant',
              content:
                "I understand. I won't execute those tools. Feel free to ask me something else.",
            };
            this.history.push(denialPrompt);
            return denialPrompt;
          }
          // Otherwise continue with only built-in tools
        }
      }

      // Filter approved tools and get their processed arguments
      // Match by checking if the approved ID contains the tool name as a suffix
      const approvedTools = recommendedTools.filter(tool => {
        const expectedIdPrefix = `orchestrated-${tool.name}-`;
        return approvedToolIds.some(id => id === tool.name || id.startsWith(expectedIdPrefix));
      });

      // Group tools by execution strategy (parallel vs sequential)
      const { parallel, sequential } =
        ToolOrchestrator.groupToolsByExecutionStrategy(approvedTools);

      // Execute parallel tools first
      const toolResults: Record<string, any> = {};
      const toolExecutionIds: Record<string, string> = {};

      if (parallel.length > 0) {
        const parallelPromises = parallel.map(async tool => {
          const approvalData = toolsForApproval.find(t => t.name === tool.name);
          const toolCallId = approvalData?.id || `orchestrated-${tool.name}-${Date.now()}`;
          toolExecutionIds[tool.name] = toolCallId;

          try {
            const result = await this.toolManager.executeTool(
              tool.name,
              approvalData?.arguments || tool.arguments || {}
            );
            toolResults[tool.name] = result;
            return result;
          } catch (error) {
            toolResults[tool.name] = {
              error: true,
              message: `Failed to execute ${tool.name}: ${error?.message || 'Unknown error'}`,
            };
          }
        });

        try {
          await Promise.all(parallelPromises);
        } catch (error) {
          console.error('Error executing parallel tools:', error);
          // Continue with sequential tools even if some parallel tools fail
        }
      }

      // Execute sequential tools one by one
      for (const tool of sequential) {
        const approvalData = toolsForApproval.find(t => t.name === tool.name);
        const toolCallId = approvalData?.id || `orchestrated-${tool.name}-${Date.now()}`;
        toolExecutionIds[tool.name] = toolCallId;

        try {
          const result = await this.toolManager.executeTool(
            tool.name,
            approvalData?.arguments || tool.arguments || {}
          );
          toolResults[tool.name] = result;
        } catch (error) {
          toolResults[tool.name] = {
            error: true,
            message: `Failed to execute ${tool.name}: ${error?.message || 'Unknown error'}`,
          };
        }
      }

      // DO NOT add tool results to history - we'll let the LLM response handle rendering
      // This prevents duplicate JSON rendering in the UI
      // The results are kept in memory for the response generation below

      // Use LLM to generate a comprehensive response based on tool results
      // Pass the FULL tool results with all data for the LLM to analyze
      const response = await this.generateResponseFromToolResults(userMessage, toolResults);

      // Clear the tool confirmation message from history after execution completes
      // This ensures the "Executing tools..." loading dialog is hidden
      this.clearToolConfirmation();

      return response;
    } catch (error) {
      // Fall back to regular LLM response
      const errorPrompt: Prompt = {
        role: 'assistant',
        content: `I encountered an error coordinating multiple tools: ${
          error?.message || 'Unknown error'
        }.

Please try your request again or ask a simpler question.`,
        error: true,
      };
      this.history.push(errorPrompt);
      return errorPrompt;
    }
  }

  /**
   * Execute a single tool and return its result
   */
  private async executeSingleTool(tool: RecommendedTool): Promise<any> {
    try {
      // Create a tool call object compatible with existing tool execution logic
      const toolCall = {
        id: `tool-${tool.name}-${Date.now()}`,
        function: {
          name: tool.name,
          arguments: JSON.stringify(tool.arguments || {}),
        },
      };

      // Use the existing tool manager to execute
      const toolResponse = await this.toolManager.executeTool(
        tool.name,
        tool.arguments || {},
        toolCall.id,
        { role: 'assistant', content: '' } // Placeholder prompt
      );

      return {
        success: true,
        toolName: tool.name,
        data: JSON.parse(toolResponse.content),
      };
    } catch (error) {
      return {
        success: false,
        toolName: tool.name,
        error: true,
        message: error?.message || 'Unknown error occurred',
      };
    }
  }

  /**
   * Aggregate results from multiple tools into a structured format
   */
  private aggregateToolResults(results: Record<string, any>): string {
    let aggregation = '## Tool Execution Results\n\n';

    for (const [toolName, result] of Object.entries(results)) {
      aggregation += `### ${toolName}\n`;

      if (result.error) {
        aggregation += `**Error**: ${result.message}\n\n`;
      } else if (result.success) {
        aggregation += `**Status**: Successfully executed\n`;
        aggregation += `**Data**:\n\`\`\`json\n${JSON.stringify(result.data, null, 2)}\n\`\`\`\n\n`;
      } else {
        aggregation += `**Result**:\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`\n\n`;
      }
    }

    return aggregation;
  }

  /**
   * Generate a comprehensive response based on aggregated tool results
   * Now accepts full tool results object to provide complete context to LLM
   */
  private async generateResponseFromToolResults(
    userMessage: string,
    toolResults: Record<string, any>
  ): Promise<Prompt> {
    try {
      // Use the UNBOUND model to force text output, not more tool calls
      const model = this.model;

      // Format tool results with full data for LLM analysis
      let formattedResults = '## Tool Execution Results\n\n';

      for (const [toolName, result] of Object.entries(toolResults)) {
        formattedResults += `### ${toolName}\n`;

        if (result.error || result.isError) {
          formattedResults += `**Status**: ❌ Error\n`;
          formattedResults += `**Error Message**: ${result.message || 'Unknown error'}\n\n`;
        } else {
          formattedResults += `**Status**: ✅ Success\n`;

          // Include the raw data (this is what was missing before!)
          if (result.data) {
            formattedResults += `**Data**:\n`;
            formattedResults += '```json\n';
            formattedResults += JSON.stringify(result.data, null, 2);
            formattedResults += '\n```\n\n';
          } else if (result.content) {
            // Handle case where tool returns content directly
            formattedResults += `**Data**:\n${result.content}\n\n`;
          } else {
            formattedResults += `**Result**:\n`;
            formattedResults += '```json\n';
            formattedResults += JSON.stringify(result, null, 2);
            formattedResults += '\n```\n\n';
          }
        }
      }

      const systemPrompt = `You are an AI assistant analyzing tool results and providing helpful responses.

Based on tool data AND the user's original request, generate a comprehensive response.

CRITICAL GUIDELINES:
1. If user asked to LEARN/UNDERSTAND a concept, START with educational explanation before showing data
2. If user asked "teach me about X", explain the concept clearly with examples
3. Analyze and discuss ACTUAL data from tools - reference specific values
4. Synthesize information to provide a complete picture
5. Use formatting (lists, sections) for readability
6. Explain what data means and why it matters
7. Provide actionable next steps
8. NEVER just show raw data tables - always add context and explanation

Examples:
- "teach me about ingress" → Explain what ingress is, how it works, THEN show their resources with context
- "show me pods" → Present pod data with status summary and insights
- "what is a deployment" → Explain deployment concept (don't need tool data for this)`;

      const userPrompt = `Original user request: "${userMessage}"

Tool Results:
${formattedResults}

Please analyze this data and provide a specific, detailed response that directly addresses the user's request using the information provided above. Reference specific findings from the results.`;

      const messages = [
        new SystemMessage(systemPrompt),
        ...this.prepareChatHistory().slice(-4), // Include recent context
        new HumanMessage(userPrompt),
      ];

      const response = await model.invoke(messages, {
        signal: this.currentAbortController?.signal,
      });

      this.currentAbortController = null;

      const assistantPrompt: Prompt = {
        role: 'assistant',
        content: this.extractTextContent(response.content),
      };

      this.history.push(assistantPrompt);
      return assistantPrompt;
    } catch (error) {
      console.error('Error generating response from tool results:', error);

      const fallbackPrompt: Prompt = {
        role: 'assistant',
        content: `I executed the requested tools and gathered information. There was an error generating a comprehensive summary, but here are the raw results:

${Object.entries(toolResults)
  .map(([name, result]) => `**${name}**: ${JSON.stringify(result, null, 2)}`)
  .join('\n\n')}`,
      };

      this.history.push(fallbackPrompt);
      return fallbackPrompt;
    }
  }

  // Extract tool call handling into separate method
  private async handleToolCalls(response: any): Promise<Prompt> {
    const enabledToolIds = this.toolManager.getToolNames();

    // If no tools are enabled but LLM is returning tool calls, this indicates a bug
    if (enabledToolIds.length === 0) {
      // Treat as regular response since no tools should be available
      const assistantPrompt: Prompt = {
        role: 'assistant',
        content:
          this.extractTextContent(response.content) ||
          'I apologize, but I cannot use tools as they have been disabled in your settings.',
      };
      this.history.push(assistantPrompt);

      // Clear progress steps when all tools are disabled

      return assistantPrompt;
    }

    // Filter out disabled tools from tool calls
    const allToolCalls = response.tool_calls.map(tc => ({
      type: 'function',
      id: tc.id,
      function: {
        name: tc.name,
        arguments: JSON.stringify(tc.args || {}),
      },
    }));

    // Only keep tool calls for enabled tools
    const toolCalls = allToolCalls.filter(tc => enabledToolIds.includes(tc.function.name));

    // Log if any tools were filtered out
    const filteredOutTools = allToolCalls.filter(tc => !enabledToolIds.includes(tc.function.name));

    const assistantPrompt: Prompt = {
      role: 'assistant',
      content: this.extractTextContent(response.content),
      toolCalls: toolCalls,
    };
    this.history.push(assistantPrompt);

    // If all tool calls were filtered out (all requested tools are disabled), handle gracefully
    if (toolCalls.length === 0) {
      // Add informational message about disabled tools if any were filtered
      if (filteredOutTools.length > 0) {
        const disabledToolNames = filteredOutTools.map(tc => tc.function.name).join(', ');

        // Replace the AI's response with a clear explanation instead of calling tools again
        const clarifiedResponse = `I understand you're asking for cluster data, but I cannot access live Kubernetes information because the required tools (${disabledToolNames}) are currently disabled in your settings.

To get real-time cluster data, you'll need to:
1. Go to AI Assistant settings
2. Enable the "${disabledToolNames}" tool
3. Ask your question again

Without access to the Kubernetes API, I cannot fetch current pod, deployment, service, or other resource information from your cluster.`;

        // Update the assistant prompt in history with the clarified response
        const updatedPrompt: Prompt = {
          role: 'assistant',
          content: clarifiedResponse,
        };

        // Replace the last history entry with the updated prompt
        this.history[this.history.length - 1] = updatedPrompt;

        // Clear progress steps when tools are disabled

        return updatedPrompt;
      }

      // Clear progress steps when no tools to execute

      return assistantPrompt;
    }

    // Prepare tool calls for approval with intelligent argument processing
    const toolCallsForApproval: ToolCall[] = await Promise.all(
      toolCalls.map(async tc => {
        const toolName = tc.function.name;
        const mcpTools = this.toolManager.getMCPTools();
        const isMCPTool = mcpTools.some(tool => tool.name === toolName);
        let processedArguments = JSON.parse(tc.function.arguments);

        // Use AI to enhance arguments for MCP tools
        if (isMCPTool) {
          try {
            const toolSchema = await MCPArgumentProcessor.getToolSchema(toolName);
            if (toolSchema) {
              // Build user context from current conversation
              const userContext = this.buildUserContext();

              // Store original arguments for comparison
              const originalArguments = { ...processedArguments };

              // Use AI to intelligently prepare arguments
              processedArguments = await this.enhanceArgumentsWithAI(
                toolName,
                toolSchema,
                userContext,
                processedArguments
              );

              // Mark which fields were enhanced by LLM for UI display
              processedArguments._llmEnhanced = {
                enhanced: true,
                originalArgs: originalArguments,
                enhancedFields: this.identifyEnhancedFields(originalArguments, processedArguments),
              };
            }
          } catch (error) {
            console.warn(`Failed to enhance arguments for ${toolName}:`, error);
            // Fall back to original arguments
          }
        }

        return {
          id: tc.id,
          name: toolName,
          description: this.getToolDescription(toolName, isMCPTool),
          arguments: processedArguments,
          type: isMCPTool ? 'mcp' : 'regular',
        };
      })
    );

    try {
      // Separate built-in tools from MCP tools
      const builtInTools = toolCallsForApproval.filter(tool => isBuiltInTool(tool.name));
      const mcpTools = toolCallsForApproval.filter(tool => !isBuiltInTool(tool.name));

      const approvedToolIds: string[] = [];

      // Auto-approve all built-in tools (no user interaction needed)
      const builtInToolIds = builtInTools.map(tool => tool.id);
      approvedToolIds.push(...builtInToolIds);

      // Only request approval for MCP tools
      if (mcpTools.length > 0) {
        const approvedMCPToolIds = await inlineToolApprovalManager.requestApproval(
          mcpTools,
          this // Pass the AI manager instance
        );
        approvedToolIds.push(...approvedMCPToolIds);
      }

      // Filter tool calls to only execute approved ones and update with processed arguments
      const approvedToolCalls = toolCalls
        .filter(tc => approvedToolIds.includes(tc.id))
        .map(tc => {
          // Find the processed arguments from the approval data
          const approvalData = toolCallsForApproval.find(approval => approval.id === tc.id);
          if (approvalData) {
            return {
              ...tc,
              function: {
                ...tc.function,
                arguments: JSON.stringify(approvalData.arguments),
              },
            };
          }
          return tc;
        });
      const deniedToolCalls = toolCalls.filter(tc => !approvedToolIds.includes(tc.id));

      // Add denied tool responses to history
      for (const deniedTool of deniedToolCalls) {
        this.history.push({
          role: 'tool',
          content: JSON.stringify({
            error: true,
            message: 'Tool execution denied by user',
            userFriendlyMessage: `The execution of ${deniedTool.function.name} was denied by the user.`,
          }),
          toolCallId: deniedTool.id,
          name: deniedTool.function.name,
        });
      }

      // Process approved tool calls
      if (approvedToolCalls.length > 0) {
        await this.processToolCalls(approvedToolCalls, assistantPrompt);
      }
    } catch (error) {
      // Add denial responses for all tools
      for (const toolCall of toolCalls) {
        this.history.push({
          role: 'tool',
          content: JSON.stringify({
            error: true,
            message: error.message || 'Tool execution denied',
            userFriendlyMessage: `Tool execution was denied: ${
              error.message || 'User chose not to proceed'
            }`,
          }),
          toolCallId: toolCall.id,
          name: toolCall.function.name,
        });
      }
    }

    // Check if we should process follow-up
    const toolResponses = this.history.filter(
      prompt => prompt.role === 'tool' && toolCalls.some(tc => tc.id === prompt.toolCallId)
    );

    // If there are no tool responses in history (e.g., confirmation-based operations like
    // PUT/PATCH/DELETE return shouldAddToHistory: false), skip follow-up processing.
    // Note: [].every() returns true, which would incorrectly trigger processToolResponses.
    const shouldProcessFollowUp =
      toolResponses.length > 0 &&
      toolResponses.every(response => {
        try {
          const parsed = JSON.parse(response.content);
          return parsed.shouldProcessFollowUp !== false;
        } catch {
          return true; // Default to processing follow-up if can't parse
        }
      });

    if (shouldProcessFollowUp) {
      return await this.processToolResponses();
    }

    // Clear progress steps when not processing follow-up

    return assistantPrompt;
  }

  /**
   * Handle tool calls for streaming scenario - executes tools without generating response
   * (response will be streamed separately by processToolResponsesStream)
   */
  private async handleToolCallsForStreaming(
    toolCalls: any[],
    assistantPrompt: Prompt
  ): Promise<void> {
    const enabledToolIds = this.toolManager.getToolNames();

    // Convert tool calls to expected format
    const formattedToolCalls = toolCalls.map(tc => ({
      type: 'function' as const,
      id: tc.id,
      function: {
        name: tc.name,
        arguments: JSON.stringify(tc.args || {}),
      },
    }));

    // Filter out disabled tools
    const enabledToolCalls = formattedToolCalls.filter(tc =>
      enabledToolIds.includes(tc.function.name)
    );

    // Prepare tool calls for approval
    const toolCallsForApproval: ToolCall[] = await Promise.all(
      enabledToolCalls.map(async tc => {
        const toolName = tc.function.name;
        const mcpTools = this.toolManager.getMCPTools();
        const isMCPTool = mcpTools.some(tool => tool.name === toolName);
        let processedArguments = JSON.parse(tc.function.arguments);

        // Use AI to enhance arguments for MCP tools
        if (isMCPTool) {
          try {
            const toolSchema = await MCPArgumentProcessor.getToolSchema(toolName);
            if (toolSchema) {
              const userContext = this.buildUserContext();
              const originalArguments = { ...processedArguments };

              processedArguments = await this.enhanceArgumentsWithAI(
                toolName,
                toolSchema,
                userContext,
                processedArguments
              );

              processedArguments._llmEnhanced = {
                enhanced: true,
                originalArgs: originalArguments,
                enhancedFields: this.identifyEnhancedFields(originalArguments, processedArguments),
              };
            }
          } catch (error) {
            console.warn(`Failed to enhance arguments for ${toolName}:`, error);
          }
        }

        return {
          id: tc.id,
          name: toolName,
          description: this.getToolDescription(toolName, isMCPTool),
          arguments: processedArguments,
          type: isMCPTool ? 'mcp' : 'regular',
        };
      })
    );

    try {
      // Separate built-in tools from MCP tools
      const builtInTools = toolCallsForApproval.filter(tool => isBuiltInTool(tool.name));
      const mcpTools = toolCallsForApproval.filter(tool => !isBuiltInTool(tool.name));

      const approvedToolIds: string[] = [];

      // Auto-approve built-in tools
      approvedToolIds.push(...builtInTools.map(tool => tool.id));

      // Request approval for MCP tools
      if (mcpTools.length > 0) {
        const approvedMCPToolIds = await inlineToolApprovalManager.requestApproval(mcpTools, this);
        approvedToolIds.push(...approvedMCPToolIds);
      }

      // Filter approved tool calls
      const approvedToolCalls = enabledToolCalls
        .filter(tc => approvedToolIds.includes(tc.id))
        .map(tc => {
          const approvalData = toolCallsForApproval.find(approval => approval.id === tc.id);
          if (approvalData) {
            return {
              ...tc,
              function: {
                ...tc.function,
                arguments: JSON.stringify(approvalData.arguments),
              },
            };
          }
          return tc;
        });

      // Execute approved tools (this is fast - 7-14ms per tool)
      if (approvedToolCalls.length > 0) {
        await this.processToolCalls(approvedToolCalls, assistantPrompt);
      }

      // Add denied tool responses to history
      const deniedToolCalls = enabledToolCalls.filter(tc => !approvedToolIds.includes(tc.id));
      for (const deniedTool of deniedToolCalls) {
        this.history.push({
          role: 'tool',
          content: JSON.stringify({
            error: true,
            message: 'Tool execution denied by user',
            userFriendlyMessage: `The execution of ${deniedTool.function.name} was denied by the user.`,
          }),
          toolCallId: deniedTool.id,
          name: deniedTool.function.name,
        });
      }
    } catch (error) {
      // Add denial responses for all tools
      for (const toolCall of enabledToolCalls) {
        this.history.push({
          role: 'tool',
          content: JSON.stringify({
            error: true,
            message: error.message || 'Tool execution denied',
            userFriendlyMessage: `Tool execution was denied: ${
              error.message || 'User chose not to proceed'
            }`,
          }),
          toolCallId: toolCall.id,
          name: toolCall.function.name,
        });
      }
    }
  }

  // Extract tool call processing logic
  private async processToolCalls(toolCalls: any[], assistantPrompt: Prompt): Promise<void> {
    const failedOperations: string[] = [];

    for (const toolCall of toolCalls) {
      const args = JSON.parse(toolCall.function.arguments);

      try {
        // Execute the tool call using ToolManager
        const toolResponse = await this.toolManager.executeTool(
          toolCall.function.name,
          args,
          toolCall.id,
          assistantPrompt
        );

        // Check if the response indicates an error even if the tool didn't throw
        let isErrorResponse = false;
        try {
          const parsedContent = JSON.parse(toolResponse.content);
          isErrorResponse = parsedContent.error === true;

          if (isErrorResponse) {
            const toolName = toolCall.function.name || 'unknown tool';
            const errorMsg = parsedContent.message || 'Unknown error';
            failedOperations.push(`${toolName}: ${errorMsg}`);
          }
        } catch (parseError) {
          // If content isn't JSON, check for error indicators
          const contentLower = toolResponse.content.toLowerCase();
          if (contentLower.includes('error') || contentLower.includes('failed')) {
            const toolName = toolCall.function.name || 'unknown tool';
            failedOperations.push(`${toolName}: ${toolResponse.content}`);
          }
        }

        // Only add to history if the tool response indicates we should
        if (toolResponse.shouldAddToHistory) {
          this.history.push({
            role: 'tool',
            content: toolResponse.content,
            toolCallId: toolCall.id,
            name: toolCall.function.name,
          });
        } else if (toolResponse.metadata?.requiresConfirmation) {
          // For confirmation-based operations (PUT/PATCH/DELETE), add a minimal placeholder
          // so that validateToolCallAlignment doesn't inject fake error messages.
          this.history.push({
            role: 'tool',
            content: JSON.stringify({
              status: 'pending_confirmation',
              shouldProcessFollowUp: false,
              message: toolResponse.metadata?.method
                ? `${toolResponse.metadata.method} request requires user confirmation.`
                : 'Operation requires user confirmation.',
            }),
            toolCallId: toolCall.id,
            name: toolCall.function.name,
            isDisplayOnly: true, // Don't send to LLM or display in UI
          });
        }
      } catch (error) {
        console.error('Error executing tool call:', error);

        const toolName = toolCall.function.name || 'unknown tool';
        const errorMessage = error?.message || 'Unknown error occurred';
        failedOperations.push(`${toolName}: ${errorMessage}`);

        const errorToolResponse = {
          content: JSON.stringify({
            error: true,
            message: errorMessage,
            toolName: toolName,
            request: args,
            userFriendlyMessage: `Failed to execute ${toolName}: ${errorMessage}`,
          }),
          shouldAddToHistory: true,
          shouldProcessFollowUp: true,
        };

        // Always add error responses to maintain alignment
        this.history.push({
          role: 'tool',
          content: errorToolResponse.content,
          toolCallId: toolCall.id,
          name: toolCall.function.name,
        });
      }
    }

    // If there were any failed operations, use the specialized template to ensure the AI addresses them properly
    if (failedOperations.length > 0) {
      try {
        // Use the tool failure template to generate clear error communication
        const toolErrorPrompt = await toolFailurePromptTemplate.format({
          failed_operations: failedOperations.join('\n'),
          operation_count: failedOperations.length.toString(),
          context: 'Tool execution during user request',
        });

        const errorSystemMessage = {
          role: 'system' as const,
          content: toolErrorPrompt,
          toolCallId: 'system-error-alert',
          name: 'error_handler',
        };

        this.history.push(errorSystemMessage);

        console.warn('Tool execution failures detected:', failedOperations);
      } catch (templateError) {
        console.error('Error using tool failure template:', templateError);

        // Fallback to basic error message
        const errorSystemMessage = {
          role: 'system' as const,
          content: `CRITICAL: The following operations failed and must be reported to the user:

${failedOperations.map(op => `- ${op}`).join('\n')}

You MUST:
1. Clearly inform the user that these operations failed
2. Explain what went wrong in simple terms  
3. Provide specific next steps or alternatives
4. Do not ignore or minimize these errors

Format your response to make the errors prominent and actionable.`,
          toolCallId: 'system-error-alert',
          name: 'error_handler',
        };

        this.history.push(errorSystemMessage);
        console.warn('Tool execution failures detected:', failedOperations);
      }
    }
  }

  // Handle errors in userSend method
  private async handleUserSendError(error: any): Promise<Prompt> {
    // Clear abort controller in case of error
    this.currentAbortController = null;

    console.error('Error in userSend:', error);

    // Handle abort errors
    if (error.message === 'AbortError') {
      const errorPrompt: Prompt = {
        role: 'assistant',
        content: 'Request cancelled.',
        error: true,
      };
      this.history.push(errorPrompt);
      return errorPrompt;
    }

    // For API-related errors, use specialized error template to ensure visibility
    const isApiError =
      error.message?.toLowerCase().includes('api') ||
      error.message?.toLowerCase().includes('request') ||
      error.message?.toLowerCase().includes('network') ||
      error.message?.toLowerCase().includes('fetch') ||
      error.message?.toLowerCase().includes('timeout');

    if (isApiError) {
      try {
        // Use the API error template to generate a clear, visible error response
        const errorTemplatePrompt = await apiErrorPromptTemplate.format({
          error_message: this.createUserFriendlyErrorMessage(error),
          error_details: error.message || 'Unknown error',
          context: 'API request processing',
        });

        // Generate a user-friendly response using the AI model
        const errorResponse = await this.model.invoke([
          { role: 'system', content: errorTemplatePrompt },
          { role: 'user', content: 'Please explain this error clearly and suggest next steps.' },
        ]);

        const errorText = this.extractTextContent(errorResponse);

        const apiErrorPrompt: Prompt = {
          role: 'assistant',
          content: errorText,
          error: true,
        };

        this.history.push(apiErrorPrompt);
        return apiErrorPrompt;
      } catch (templateError) {
        console.error('Error using API error template:', templateError);
        // Fall through to standard error handling
      }
    }

    // Standard error handling for non-API errors
    const errorPrompt: Prompt = {
      role: 'assistant',
      content: `Sorry, there was an error processing your request: ${this.createUserFriendlyErrorMessage(
        error
      )}`,
      error: true,
    };
    this.history.push(errorPrompt);
    return errorPrompt;
  }

  // Helper method to create user-friendly error messages
  private createUserFriendlyErrorMessage(error: any): string {
    if (!error) return 'Unknown error occurred';

    const errorMessage = error.message || error.toString();

    // Common error patterns and their user-friendly equivalents
    const errorMappings = [
      {
        pattern: /network.*error|fetch.*failed|connection.*refused/i,
        message: 'Network connection error. Please check your internet connection and try again.',
      },
      {
        pattern: /timeout|timed out/i,
        message: 'Request timed out. The operation took too long to complete.',
      },
      {
        pattern: /unauthorized|401/i,
        message: 'Authentication error. Please check your credentials.',
      },
      {
        pattern: /forbidden|403/i,
        message: 'Access denied. You may not have permission for this operation.',
      },
      { pattern: /not found|404/i, message: 'The requested resource was not found.' },
      {
        pattern: /rate limit|429/i,
        message: 'Too many requests. Please wait a moment and try again.',
      },
      { pattern: /internal server error|500/i, message: 'Server error. Please try again later.' },
      {
        pattern: /bad gateway|502/i,
        message: 'Gateway error. The server is temporarily unavailable.',
      },
      {
        pattern: /service unavailable|503/i,
        message: 'Service temporarily unavailable. Please try again later.',
      },
      {
        pattern: /gateway timeout|504/i,
        message: 'Gateway timeout. The request took too long to process.',
      },
      {
        pattern: /parse|json/i,
        message: 'Data format error. The response was not in the expected format.',
      },
      { pattern: /abort|cancel/i, message: 'Operation was cancelled.' },
    ];

    // Find matching error pattern
    for (const mapping of errorMappings) {
      if (mapping.pattern.test(errorMessage)) {
        return mapping.message;
      }
    }

    // If no pattern matches, return a simplified version of the original message
    const cleanMessage = errorMessage
      .replace(/^Error:\s*/i, '')
      .replace(/^TypeError:\s*/i, '')
      .replace(/^ReferenceError:\s*/i, '')
      .replace(/^SyntaxError:\s*/i, '')
      .trim();

    return cleanMessage || 'An unexpected error occurred. Please try again.';
  }

  // Change from 'protected' to 'public' to match the base class
  public async processToolResponses(): Promise<Prompt> {
    // Check if there are any tool responses in the history
    if (!this.hasToolResponses()) {
      return this.getLastAssistantMessage();
    }

    // Validate tool call/response alignment
    this.validateToolCallAlignment();

    try {
      // Prepare messages using a more structured approach
      const messages = this.prepareMessagesForToolResponse();

      // Create a chain for tool response processing
      const chain = this.createToolResponseChain();

      // Process the response
      const response = await chain.invoke({
        messages: messages.slice(1), // Exclude system message for the chain
        systemPrompt: this.createToolResponseSystemPrompt(), // Use specialized prompt for tool responses
      });

      return this.handleToolResponseResult(response);
    } catch (error) {
      return this.handleToolResponseError(error);
    }
  }

  /**
   * Streaming version of processToolResponses for better perceived performance
   * Yields content as it's generated by the model after tool execution
   */
  public async *processToolResponsesStream(): AsyncGenerator<string, Prompt, undefined> {
    // Check if there are any tool responses in the history
    if (!this.hasToolResponses()) {
      const lastMessage = this.getLastAssistantMessage();
      yield lastMessage.content;
      return lastMessage;
    }

    // Validate tool call/response alignment
    this.validateToolCallAlignment();

    try {
      // Prepare messages for tool response
      const systemMessage = new SystemMessage(this.createToolResponseSystemPrompt());
      const messages = this.prepareMessagesForToolResponse();

      // Use the unbound model (no tools) to avoid recursive tool calls
      const model = this.model;

      // Build final message array
      const finalMessages = [systemMessage, ...messages.slice(1)];

      // Stream the response
      const stream = await model.stream(finalMessages, {
        signal: this.currentAbortController?.signal,
      });

      let fullContent = '';

      for await (const chunk of stream) {
        const content = this.extractTextContent(chunk.content);
        if (content) {
          fullContent += content;
          yield content;
        }
      }

      // Create the complete response prompt
      const assistantPrompt: Prompt = {
        role: 'assistant',
        content: fullContent,
      };

      this.history.push(assistantPrompt);

      // Clear progress steps after streaming response

      return assistantPrompt;
    } catch (error) {
      const errorPrompt = this.handleToolResponseError(error);
      yield errorPrompt.content;

      // Clear progress steps even on error

      return errorPrompt;
    }
  }

  // Helper method to check if there are tool responses
  private hasToolResponses(): boolean {
    return this.history.some(
      prompt => prompt.role === 'tool' && prompt.toolCallId && !prompt.isDisplayOnly
    );
  }

  // Helper method to get the last assistant message
  private getLastAssistantMessage(): Prompt {
    const lastAssistantMessage = this.history
      .slice()
      .reverse()
      .find(prompt => prompt.role === 'assistant');
    return (
      lastAssistantMessage || {
        role: 'assistant',
        content: 'No tool responses to process.',
      }
    );
  }

  // Create a specialized chain for tool response processing
  private createToolResponseChain() {
    //TODO: not sure which we should use now...

    // // Use the bound model (with tools) so the LLM can make follow-up tool calls
    // // (e.g., fetching logs after retrieving pod details).
    // const modelToUse = this.boundModel || this.model;

    // Use the UNBOUND model (no tools) to force the LLM to produce a text summary
    // instead of making additional tool calls
    const model = this.model;

    // Create a runnable sequence for tool response processing
    return RunnableSequence.from([
      // Transform input to the expected format
      RunnablePassthrough.assign({
        formattedMessages: (input: any) => {
          const systemMessage = new SystemMessage(input.systemPrompt);
          return [systemMessage, ...input.messages];
        },
      }),
      // Invoke the model
      {
        formattedMessages: (input: any) => model.invoke(input.formattedMessages),
      },
      // Parse the output
      (input: any) => input.formattedMessages,
    ]);
  }

  // Validate tool call/response alignment
  private validateToolCallAlignment(): void {
    const lastAssistantMessage = this.history
      .slice()
      .reverse()
      .find(prompt => prompt.role === 'assistant' && prompt.toolCalls?.length);

    if (lastAssistantMessage?.toolCalls) {
      const expectedToolCallIds = lastAssistantMessage.toolCalls.map(tc => tc.id);
      const actualToolResponses = this.history.filter(
        prompt => prompt.role === 'tool' && expectedToolCallIds.includes(prompt.toolCallId)
      );

      if (expectedToolCallIds.length !== actualToolResponses.length) {
        console.error('Tool call/response mismatch detected', {
          expectedIds: expectedToolCallIds,
          actualResponses: actualToolResponses.map(r => r.toolCallId),
        });

        // Add missing tool responses
        // this.addMissingToolResponses(expectedToolCallIds, actualToolResponses);
      }
    }
  }

  // Prepare messages for tool response processing
  // Sends conversation history + tool response data to the LLM for descriptive analysis
  private prepareMessagesForToolResponse(): BaseMessage[] {
    const systemMessage = new SystemMessage(this.createSystemPrompt());
    const messages: BaseMessage[] = [systemMessage];

    // Add conversation history, excluding tool responses and assistant tool-call messages.
    // Tool responses are collected separately and added as context for the LLM to analyze.
    for (const prompt of this.history) {
      if (prompt.role === 'system' || prompt.isDisplayOnly) continue;
      if (prompt.role === 'tool') continue; // Tool responses handled below
      if (prompt.role === 'assistant' && prompt.toolCalls?.length) continue; // Skip tool-calling assistant messages

      messages.push(...this.convertPromptsToMessages([prompt]));
    }

    // Collect tool response data and present it to the LLM as context to analyze
    const toolResponses = this.history.filter(
      p => p.role === 'tool' && p.toolCallId && !p.isDisplayOnly
    );
    if (toolResponses.length > 0) {
      let totalResponseSize = 0;
      const MAX_RESPONSE_SIZE = 500000; // ~500KB limit

      const toolDataParts: string[] = [];
      for (const p of toolResponses) {
        const processed = this.processToolContent(p, totalResponseSize, MAX_RESPONSE_SIZE);
        if (processed) {
          totalResponseSize += processed.length;
          toolDataParts.push(processed);
        }
      }

      const toolData = toolDataParts.join('\n\n');
      if (toolData) {
        messages.push(
          new HumanMessage(
            `Here is the data retrieved from the Kubernetes API:\n\n${toolData}\n\nPlease analyze this data and provide a helpful, descriptive answer to my original question. Focus on any issues, anomalies, or relevant information. Follow all response formatting guidelines from the system prompt including resource links and suggestions.`
          )
        );
      }
    }

    return messages;
  }

  // Process tool content with size limits and sanitization
  private processToolContent(prompt: any, currentSize: number, maxSize: number): string | null {
    // Validate the tool response
    if (!prompt.content || typeof prompt.content !== 'string') {
      console.warn(`Invalid tool response format for ${prompt.toolCallId}`, prompt);
      return JSON.stringify({
        error: true,
        message: 'Invalid tool response format',
      });
    }

    const content = prompt.content;

    // Check response size after optimization handling
    const responseSize = content.length;
    if (currentSize + responseSize > maxSize) {
      console.warn(`Tool response size exceeds limit (${currentSize + responseSize}/${maxSize})`);
      return (
        prompt.content.substring(0, 100) +
        `... [Response truncated, exceeded size limit of ${maxSize} bytes]`
      );
    }

    // Sanitize content
    return this.sanitizeContent(content);
  }

  // Find the last assistant message with tool calls
  private findLastAssistantWithTools(): number {
    let lastAssistantWithToolsIndex = -1;
    for (let i = this.history.length - 1; i >= 0; i--) {
      const prompt = this.history[i];
      if (prompt.role === 'assistant' && prompt.toolCalls && prompt.toolCalls.length > 0) {
        lastAssistantWithToolsIndex = i;
        break;
      }
    }
    return lastAssistantWithToolsIndex;
  }

  // Track recursion depth to prevent infinite tool call loops
  private toolResponseDepth = 0;
  private static readonly MAX_TOOL_RESPONSE_DEPTH = 5;

  // Handle the result of tool response processing
  private async handleToolResponseResult(response: any): Promise<Prompt> {
    // Analyze and potentially correct kubectl suggestions
    const correctedResponse = await this.analyzeAndCorrectResponse(response);

    // <<<<<<< HEAD
    //     // If the LLM returned new tool calls (e.g., fetch logs after getting pod details),
    //     // execute them — unless we've hit the recursion depth limit.
    //     if (
    //       correctedResponse.tool_calls?.length > 0 &&
    //       this.toolResponseDepth < LangChainManager.MAX_TOOL_RESPONSE_DEPTH
    //     ) {
    //       console.log(
    //         `🔧 Follow-up tool calls detected in analysis response (depth ${this.toolResponseDepth}):`,
    //         correctedResponse.tool_calls.map(tc => ({ name: tc.name, args: tc.args }))
    //       );
    //       this.toolResponseDepth++;
    //       try {
    //         // Clean up intermediate tool entries before processing new tool calls
    //         const lastAssistantWithToolsIndex = this.findLastAssistantWithTools();
    //         if (lastAssistantWithToolsIndex >= 0) {
    //           this.history = this.history.slice(0, lastAssistantWithToolsIndex + 1);
    //         }
    //         return await this.handleToolCalls(correctedResponse);
    //       } finally {
    //         this.toolResponseDepth--;
    //       }
    // =======
    const extractedContent = this.extractTextContent(correctedResponse.content);

    // If the model returned empty content but has tool calls, it's trying to call more tools
    // Instead of allowing this, we should provide a fallback response based on the tool results
    if (
      (!extractedContent || extractedContent.trim().length === 0) &&
      response.tool_calls?.length > 0
    ) {
      // Get the most recent tool responses from history
      const recentToolResponses = this.history
        .filter(prompt => prompt.role === 'tool' && prompt.toolCallId)
        .slice(-3) // Get last 3 tool responses
        .map(response => ({
          name: response.name,
          content: response.content,
        }));

      // Create a fallback response based on tool results
      // For MCP tools with formatted output, return them directly without prefix
      if (recentToolResponses.length === 1) {
        const singleResponse = recentToolResponses[0];
        try {
          const parsed = JSON.parse(singleResponse.content);
          if (parsed.formatted && parsed.mcpOutput) {
            // This is a formatted MCP output, return it directly
            const assistantPrompt: Prompt = {
              role: 'assistant',
              content: singleResponse.content,
              toolCalls: [],
            };

            // Clean up history to prevent message order issues
            const lastAssistantWithToolsIndex = this.findLastAssistantWithTools();
            if (lastAssistantWithToolsIndex >= 0) {
              this.history = this.history.slice(0, lastAssistantWithToolsIndex + 1);
            }

            this.history.push(assistantPrompt);
            return assistantPrompt;
          }
        } catch (e) {
          // Not formatted MCP output, continue with fallback
        }
      }

      // Standard fallback for multiple tools or non-MCP tools
      let fallbackContent = '';

      recentToolResponses.forEach((toolResponse, index) => {
        const toolName = toolResponse.name || 'tool';
        let content = toolResponse.content;

        // Try to parse and clean up the content
        try {
          const parsed = JSON.parse(content);
          if (parsed.formatted && parsed.mcpOutput) {
            // For formatted MCP outputs, return the JSON directly
            content = toolResponse.content;
          } else if (parsed.error) {
            content = `Error: ${parsed.message || 'Tool execution failed'}`;
          } else if (parsed.userFriendlyMessage) {
            content = parsed.userFriendlyMessage;
          } else if (typeof parsed === 'object') {
            content = JSON.stringify(parsed, null, 2);
          }
        } catch (e) {
          // Content is not JSON, use as-is but clean it up
          content = content.toString().trim();
        }

        // For single formatted MCP output, return just the content
        if (recentToolResponses.length === 1) {
          fallbackContent = content;
        } else {
          // For multiple tools, use the tool name format
          fallbackContent += `${toolName}: ${content}${
            index < recentToolResponses.length - 1 ? '\n\n' : ''
          }`;
        }
      });

      const assistantPrompt: Prompt = {
        role: 'assistant',
        content: fallbackContent.trim(),
        toolCalls: [], // Don't include additional tool calls
      };

      // Clean up history to prevent message order issues
      const lastAssistantWithToolsIndex = this.findLastAssistantWithTools();
      if (lastAssistantWithToolsIndex >= 0) {
        this.history = this.history.slice(0, lastAssistantWithToolsIndex + 1);
      }

      this.history.push(assistantPrompt);
      return assistantPrompt;
      // >>>>>>> 256976e (ai-plugin: Fix tool response and update MCPSettings)
    }

    const assistantPrompt: Prompt = {
      role: 'assistant',
      // <<<<<<< HEAD
      // content: this.extractTextContent(correctedResponse.content),
      // toolCalls: [],
      content: extractedContent,
      toolCalls:
        correctedResponse.tool_calls?.map(tc => ({
          id: tc.id,
          type: 'function',
          function: {
            name: tc.name || 'kubernetes_api_request',
            arguments: JSON.stringify(tc.args || {}),
          },
        })) || [],
    };

    // Clean up history to prevent message order issues
    const lastAssistantWithToolsIndex = this.findLastAssistantWithTools();
    if (lastAssistantWithToolsIndex >= 0) {
      this.history = this.history.slice(0, lastAssistantWithToolsIndex + 1);
    }

    this.history.push(assistantPrompt);
    this.toolResponseDepth = 0; // Reset depth on final text response
    return assistantPrompt;
  }

  // Analyze response and correct kubectl suggestions
  private async analyzeAndCorrectResponse(response: any): Promise<any> {
    const responseContent = this.extractTextContent(response.content);
    if (responseContent) {
      const lowercaseContent = responseContent.toLowerCase();

      // Check for kubectl suggestion indicators
      const hasKubectlSuggestion =
        lowercaseContent.includes('kubectl') ||
        lowercaseContent.includes('run the command') ||
        lowercaseContent.includes('command line') ||
        lowercaseContent.includes('terminal') ||
        lowercaseContent.includes('shell');

      // If kubectl is being suggested, try to get a correction
      if (hasKubectlSuggestion) {
        return await this.getCorrectedResponse(response);
      }
    }

    return response;
  }

  // Get corrected response for kubectl suggestions
  private async getCorrectedResponse(originalResponse: any): Promise<any> {
    this.history.push({
      role: 'system',
      content:
        'REMINDER: Never suggest kubectl or command line tools. Always use the kubernetes_api_request tool or explain UI actions. The user is using a web dashboard and cannot access the command line.',
    });

    try {
      // Use unbound model to produce text correction
      const correctionPrompt = new SystemMessage(
        'Your last response suggested using kubectl or command line, which is not available to the user. Please revise your response to use the kubernetes_api_request tool instead.'
      );

      const messages = [correctionPrompt];
      const correctedResponse = await this.model.invoke(messages);
      return correctedResponse;
    } catch (error) {
      console.error('Error getting corrected response:', error);
      return originalResponse; // Return original if correction fails
    }
  }

  // Handle errors in tool response processing
  private handleToolResponseError(error: any): Prompt {
    console.error('Error during tool response processing:', error);

    const errorPrompt: Prompt = {
      role: 'assistant',
      content: `Sorry, there was an error processing the tool responses: ${error.message}`,
      error: true,
    };

    this.history.push(errorPrompt);
    return errorPrompt;
  }

  // Helper method to sanitize content
  private sanitizeContent(content: string): string {
    if (!content) return '';

    try {
      // If it's JSON, parse and re-stringify to ensure it's valid
      if (
        (content.trim().startsWith('{') && content.trim().endsWith('}')) ||
        (content.trim().startsWith('[') && content.trim().endsWith(']'))
      ) {
        const parsed = JSON.parse(content);
        return JSON.stringify(parsed);
      }

      // Use sanitize-html for robust HTML sanitization
      return sanitizeHtml(content, {
        allowedTags: [], // Disallow all HTML tags
        allowedAttributes: {}, // Disallow all attributes
        textFilter: text => {
          // Replace image placeholders for consistency with previous implementation
          return text.replace(/\[IMAGE\]/gi, '[IMAGE]');
        },
      });
    } catch (error) {
      console.warn('Error sanitizing content:', error);
      // If sanitization fails, return a safe version
      return typeof content === 'string'
        ? content.substring(0, 5000) // Limit length for safety
        : JSON.stringify({ error: true, message: 'Content could not be sanitized' });
    }
  }

  /**
   * Check if arguments are complete enough to skip LLM enhancement
   * Saves 5-10 seconds by avoiding unnecessary LLM calls
   */
  private checkIfArgumentsComplete(args: Record<string, any>, inputSchema: any): boolean {
    const required = inputSchema.required || [];
    const properties = inputSchema.properties || {};

    // Check 1: Are all required fields present and non-empty?
    for (const requiredField of required) {
      const value = args[requiredField];
      if (value === undefined || value === null || value === '') {
        console.log(`[ArgCheck] Missing or empty required field: ${requiredField}`);
        return false; // Need LLM to fill in required field
      }

      // Check for placeholder patterns
      if (typeof value === 'string') {
        if (
          value.includes('<optional') ||
          value.includes('optional:') ||
          value.toLowerCase() === 'tbd' ||
          value.toLowerCase() === 'not specified'
        ) {
          console.log(`[ArgCheck] Found placeholder in ${requiredField}: "${value}"`);
          return false; // Need LLM to fix placeholder
        }
      }
    }

    // Check 2: Do any provided optional fields have placeholders?
    for (const [key, value] of Object.entries(args)) {
      if (typeof value === 'string') {
        if (
          value.includes('<optional') ||
          value.includes('optional:') ||
          value.toLowerCase() === 'tbd'
        ) {
          console.log(`[ArgCheck] Found placeholder in optional field ${key}: "${value}"`);
          return false; // Need LLM to fix placeholder
        }
      }

      // Check for type mismatches (string where number expected)
      const fieldSchema = properties[key];
      if (fieldSchema?.type === 'number' && typeof value === 'string') {
        // Allow numeric strings that can be parsed
        if (!/^-?\d+(\.\d+)?$/.test(value)) {
          console.log(`[ArgCheck] Type mismatch in ${key}: expected number, got string "${value}"`);
          return false; // Need LLM to fix type
        }
      }
    }

    // All checks passed - arguments look good!
    console.log('[ArgCheck] Arguments look complete, skipping LLM enhancement');
    return true;
  }

  /**
   * Enhance arguments using AI-like intelligence
   */
  private async enhanceArgumentsWithAI(
    toolName: string,
    toolSchema: any,
    userContext: UserContext,
    originalArgs: Record<string, any>
  ): Promise<Record<string, any>> {
    const enhanced = { ...originalArgs };

    if (!toolSchema.inputSchema?.properties) {
      return enhanced;
    }

    try {
      // Use LLM to intelligently prepare arguments based on user context and tool schema
      const llmEnhancedArgs = await this.prepareLLMArguments(
        toolName,
        toolSchema,
        userContext,
        originalArgs
      );

      // Merge LLM suggestions with original arguments, preferring LLM suggestions
      Object.assign(enhanced, llmEnhancedArgs);
    } catch (error) {
      console.warn(`Failed to get LLM enhancement for ${toolName}:`, error);
      // Fall back to basic enhancement
      const properties = toolSchema.inputSchema.properties;
      const required = toolSchema.inputSchema.required || [];

      // Fill in required fields that are missing or empty
      for (const [fieldName, fieldSchema] of Object.entries(properties)) {
        const isRequired = required.includes(fieldName);
        const currentValue = enhanced[fieldName];

        if (
          isRequired &&
          (currentValue === undefined || currentValue === null || currentValue === '')
        ) {
          // Provide intelligent defaults based on field type and context
          enhanced[fieldName] = this.getIntelligentDefault(fieldName, fieldSchema, userContext);
        }
      }
    }

    return enhanced;
  }

  /**
   * Use LLM to prepare intelligent arguments based on user request and tool schema
   */
  private async prepareLLMArguments(
    toolName: string,
    toolSchema: any,
    userContext: UserContext,
    originalArgs: Record<string, any>
  ): Promise<Record<string, any>> {
    // Build prompt for argument preparation
    const argumentPreparationPrompt = this.createArgumentPreparationPrompt(
      toolName,
      toolSchema,
      userContext,
      originalArgs
    );

    try {
      // Use the existing model instance but without tools to avoid recursive tool calls
      const response = await this.model.invoke([
        { role: 'system', content: argumentPreparationPrompt.system },
        { role: 'user', content: argumentPreparationPrompt.user },
      ]);

      // Parse the LLM response to extract arguments
      const responseText = this.extractTextContent(response.content);
      const parsedArgs = this.parseArgumentsFromLLMResponse(responseText);

      return parsedArgs;
    } catch (error) {
      console.warn('Failed to prepare arguments with LLM:', error);
      return {};
    }
  }

  /**
   * Create a prompt for the LLM to prepare tool arguments
   */
  private createArgumentPreparationPrompt(
    toolName: string,
    toolSchema: any,
    userContext: UserContext,
    originalArgs: Record<string, any>
  ): { system: string; user: string } {
    const properties = toolSchema.inputSchema?.properties || {};
    const required = toolSchema.inputSchema?.required || [];

    // Create a description of the tool schema
    const schemaDescription = Object.entries(properties)
      .map(([fieldName, fieldSchema]: [string, any]) => {
        const isReq = required.includes(fieldName) ? ' (REQUIRED)' : ' (optional)';
        const type = fieldSchema.type || 'any';
        const desc = fieldSchema.description || 'No description';

        // Handle nested properties for complex objects
        let nestedProps = '';
        if (fieldSchema.properties) {
          nestedProps =
            '\n  Nested properties:\n' +
            Object.entries(fieldSchema.properties)
              .map(
                ([nestedName, nestedSchema]: [string, any]) =>
                  `    - ${nestedName} (${nestedSchema.type || 'any'}): ${
                    nestedSchema.description || 'No description'
                  }`
              )
              .join('\n');
        }

        return `- ${fieldName}${isReq} (${type}): ${desc}${nestedProps}`;
      })
      .join('\n');

    const system = `You are an expert at preparing tool arguments based on user requests. Your task is to analyze the user's request and generate appropriate arguments for the "${toolName}" tool.

TOOL SCHEMA:
${schemaDescription}

CRITICAL INSTRUCTIONS:
1. Analyze the user's request to understand their intent
2. Map their natural language request to the appropriate tool arguments
3. **ONLY include parameters that the user explicitly mentioned or that are REQUIRED**
4. **NEVER use placeholder strings like "<optional: ...>" or "TBD" or "not specified"**
5. **For number fields, ALWAYS use numeric values (e.g., 100, not "100" or "<optional>")**
6. **For optional fields not mentioned by user: OMIT them completely from the JSON**
7. Use conversation context to infer missing REQUIRED fields only
8. Return ONLY valid JSON matching the schema types exactly

TYPE RULES (CRITICAL):
- number fields → use numeric values: {"adults": 2, "minPrice": 100} NOT {"adults": "2"}
- string fields → use string values: {"location": "New York"}
- boolean fields → use true/false: {"instantBook": true}
- If field is optional and user didn't mention it → OMIT it entirely

EXAMPLES:
✅ GOOD: {"location": "New York", "adults": 2, "children": 0}
❌ BAD: {"location": "New York", "adults": "2", "minPrice": "<optional>"}
❌ BAD: {"checkin": "<optional: YYYY-MM-DD>", "pets": -2}

RESPONSE FORMAT:
Return only valid JSON with correct types. No explanations, no markdown, no placeholders.`;

    const conversationContext =
      userContext.conversationHistory
        ?.slice(-5)
        .map(msg => `${msg.role}: ${msg.content}`)
        .join('\n') || '';

    const user = `USER REQUEST: "${userContext.userMessage}"

CONVERSATION CONTEXT:
${conversationContext}

CURRENT ARGUMENTS: ${JSON.stringify(originalArgs, null, 2)}

Based on the user's request and the tool schema above, generate the appropriate arguments for the "${toolName}" tool. Focus on mapping the user's intent to the correct parameter values.

For example, if the user says "get me info only from gadget namespace", the params object should include:
{"operator.KubeManager.namespace": "gadget"}

Return the complete arguments object:`;

    return { system, user };
  }

  /**
   * Parse arguments from LLM response and validate types
   */
  private parseArgumentsFromLLMResponse(response: string): Record<string, any> {
    try {
      // Try to extract JSON from the response
      const jsonMatch = response.match(/\{[\s\S]*\}/);
      const parsed = jsonMatch ? JSON.parse(jsonMatch[0]) : JSON.parse(response.trim());

      // Validate and sanitize the parsed arguments
      return this.sanitizeLLMArguments(parsed);
    } catch (error) {
      console.warn('Failed to parse LLM response for arguments:', error, response);
      return {};
    }
  }

  /**
   * Sanitize LLM-generated arguments to fix type mismatches and remove placeholders
   */
  private sanitizeLLMArguments(args: Record<string, any>): Record<string, any> {
    const sanitized: Record<string, any> = {};

    for (const [key, value] of Object.entries(args)) {
      // Skip null/undefined
      if (value === null || value === undefined) {
        continue;
      }

      // Detect and remove placeholder strings
      if (typeof value === 'string') {
        const valueLower = value.toLowerCase();

        // Check for placeholder patterns
        if (
          value.includes('<optional') ||
          value.includes('optional:') ||
          valueLower === 'tbd' ||
          valueLower === 'not specified' ||
          valueLower === 'n/a' ||
          value.trim() === ''
        ) {
          console.warn(`[ArgSanitize] Removing placeholder value for ${key}: "${value}"`);
          continue; // Omit this field
        }

        // Try to convert string numbers to actual numbers for numeric-looking fields
        if (
          /^-?\d+(\.\d+)?$/.test(value) &&
          (key.toLowerCase().includes('price') ||
            key.toLowerCase().includes('adult') ||
            key.toLowerCase().includes('child') ||
            key.toLowerCase().includes('infant') ||
            key.toLowerCase().includes('pet') ||
            key.toLowerCase().includes('guest') ||
            key.toLowerCase().includes('count') ||
            key.toLowerCase().includes('min') ||
            key.toLowerCase().includes('max'))
        ) {
          sanitized[key] = parseFloat(value);
          console.log(
            `[ArgSanitize] Converted ${key} from string "${value}" to number ${sanitized[key]}`
          );
          continue;
        }
      }

      // Remove invalid numeric values (like negative pets)
      if (typeof value === 'number') {
        if (
          (key.toLowerCase().includes('pet') ||
            key.toLowerCase().includes('adult') ||
            key.toLowerCase().includes('child') ||
            key.toLowerCase().includes('infant')) &&
          value < 0
        ) {
          console.warn(`[ArgSanitize] Removing invalid negative value for ${key}: ${value}`);
          continue;
        }
      }

      // Recursively sanitize nested objects
      if (typeof value === 'object' && !Array.isArray(value)) {
        sanitized[key] = this.sanitizeLLMArguments(value);
        continue;
      }

      // Keep valid values
      sanitized[key] = value;
    }

    return sanitized;
  }

  /**
   * Identify which fields were enhanced by comparing original and enhanced arguments
   */
  private identifyEnhancedFields(
    original: Record<string, any>,
    enhanced: Record<string, any>
  ): string[] {
    const enhancedFields: string[] = [];

    // Compare each field to see what was added or modified
    for (const [key, enhancedValue] of Object.entries(enhanced)) {
      if (key === '_llmEnhanced') continue; // Skip metadata

      const originalValue = original[key];

      // Field is enhanced if:
      // 1. It didn't exist in original
      // 2. It was null/undefined/empty in original but has value now
      // 3. The value is different
      if (
        !(key in original) ||
        originalValue === null ||
        originalValue === undefined ||
        originalValue === '' ||
        JSON.stringify(originalValue) !== JSON.stringify(enhancedValue)
      ) {
        enhancedFields.push(key);
      }
    }

    return enhancedFields;
  }

  /**
   * Get intelligent default value for a field based on context
   */
  private getIntelligentDefault(
    fieldName: string,
    fieldSchema: any,
    userContext: UserContext
  ): any {
    const fieldType = fieldSchema.type;
    const fieldNameLower = fieldName.toLowerCase();

    // Try to extract from user context first
    if (userContext.userMessage) {
      const userMessage = userContext.userMessage.toLowerCase();

      // Extract namespace
      if (fieldNameLower.includes('namespace')) {
        const namespaceMatch = userMessage.match(/namespace[\s:]+([a-zA-Z0-9-_.]+)/i);
        if (namespaceMatch) {
          return namespaceMatch[1];
        }
        return 'default'; // Default Kubernetes namespace
      }

      // Extract container/pod names
      if (fieldNameLower.includes('container') || fieldNameLower.includes('pod')) {
        const containerMatch = userMessage.match(/(?:container|pod)[\s:]+([a-zA-Z0-9-_.]+)/i);
        if (containerMatch) {
          return containerMatch[1];
        }
      }

      // Extract commands
      if (fieldNameLower.includes('command') || fieldNameLower.includes('cmd')) {
        const commandMatch = userMessage.match(/(?:run|execute|command)[\s:]+["']([^"']+)["']/i);
        if (commandMatch) {
          return commandMatch[1];
        }
      }
    }

    // Fallback to type-based defaults
    switch (fieldType) {
      case 'object':
        return {};
      case 'array':
        return [];
      case 'string':
        if (fieldSchema.enum) {
          return fieldSchema.enum[0];
        }
        return fieldSchema.default || '';
      case 'number':
      case 'integer':
        return fieldSchema.default || fieldSchema.minimum || 0;
      case 'boolean':
        return fieldSchema.default !== undefined ? fieldSchema.default : false;
      default:
        return null;
    }
  }
}
