import React from 'react';
import { ChatHistoryManager } from './ChatHistoryManager';
import {
  ChatContextMenu,
  IMentionContext
} from './ChatContextMenu/ChatContextMenu';
import { Contents } from '@jupyterlab/services';
import { ToolService } from '../Services/ToolService';
import { RichTextChatInput } from './RichTextChatInput';
import { IChatService } from '../Services/IChatService';
import { ConversationService } from './ConversationService';
import { ChatMessages } from './ChatMessages';
import { ChatUIHelper } from './ChatUIHelper';
import { ChatboxContext } from '../Components/ChatboxContext';
import { ChatRequestStatus, IChatMessage, ICheckpoint } from '../types';
import { AppStateService } from '../AppState';
import { NotebookCellStateService } from '../Services/NotebookCellStateService';
import { convertMentionsToContextTags } from '../utils/contextTagUtils';
// Note: Icon imports removed - icons are now part of React components
import { checkTokenLimit, MAX_RECOMMENDED_TOKENS } from '../utils/tokenUtils';
import { ChatBoxWidget } from '../Components/ChatboxWidget';
import type {
  CellContext,
  MentionDropdownRef,
  RichTextInputRef
} from '../Components/Chat';
import {
  ContextRow,
  MentionDropdown,
  ModeSelector,
  RichTextInput,
  SendButton,
  TokenProgressIndicator
} from '../Components/Chat';
import { IMountedComponent, mountComponent } from '../utils/reactMount';
import { useChatStore } from '../stores/chat';

/**
 * Input element type that supports both textarea and rich text input
 * Can be undefined when using React-based input
 */
type ChatInputElement = HTMLTextAreaElement | RichTextChatInput | undefined;

/**
 * Manages chat input functionality and creates the complete input container
 */
export class ChatInputManager {
  private chatInput: ChatInputElement;
  private chatHistoryManager: ChatHistoryManager;
  private userMessageHistory: string[] = [];
  private historyPosition: number = -1;
  private unsavedInput: string = '';
  private mentionDropdown!: ChatContextMenu;

  // Add map to track all currently active mentions by context.id
  private activeContexts: Map<string, IMentionContext> = new Map();

  private onContextSelected: ((context: IMentionContext) => void) | null = null;
  private onContextRemoved: ((context_id: string) => void) | null = null;
  private onResetChat: (() => void) | null = null;

  // Dependencies for sendMessage and revertAndSend
  private chatBoxWidget: ChatBoxWidget;
  private chatService?: IChatService;
  private conversationService?: ConversationService;
  private messageComponent?: ChatMessages;
  private uiHelper?: ChatUIHelper;
  private contextHandler?: ChatboxContext;
  private sendButtonContainer?: HTMLElement;
  private sendButtonMounted?: IMountedComponent;
  private modeSelectorContainer?: HTMLElement;
  private modeSelectorMounted?: IMountedComponent;
  private tokenProgressContainer?: HTMLElement;
  private tokenProgressMounted?: IMountedComponent;
  private contextRowMounted?: IMountedComponent;
  private currentTokenCount: number = 0;
  private isCompacting: boolean = false;
  private modeName: 'agent' | 'ask' | 'fast' = 'agent';
  private isProcessingMessage: boolean = false;
  private checkpointToRestore: ICheckpoint | null = null;
  private cancelMessage?: () => void;
  private onMessageSent?: () => void;
  private onModeSelected?: (mode: 'agent' | 'ask' | 'fast') => void;

  // Input container elements
  private inputContainer?: HTMLElement;
  private chatboxWrapper?: HTMLElement;
  private contextRow?: HTMLElement;
  private inputRow?: HTMLElement;
  private contextDisplay?: HTMLElement;
  private addContextButton?: HTMLButtonElement;

  // React-based RichTextInput (alternative to chatInput class)
  private richTextInputRef = React.createRef<RichTextInputRef>();
  private richTextInputContainer?: HTMLElement;
  private richTextInputMounted?: IMountedComponent;
  private useReactInput: boolean = false;
  private placeholder: string =
    'What would you like me to generate or analyze?';

  // React-based MentionDropdown (alternative to ChatContextMenu)
  private mentionDropdownRef = React.createRef<MentionDropdownRef>();
  private mentionDropdownContainer?: HTMLElement;
  private mentionDropdownMounted?: IMountedComponent;
  private useReactMentionDropdown: boolean = false;
  private currentMentionStart: number = -1;

  // Store dependencies for mention dropdown
  private contentManager: Contents.IManager;
  private toolService: ToolService;

  constructor(
    chatInput: ChatInputElement,
    chatHistoryManager: ChatHistoryManager,
    contentManager: Contents.IManager,
    toolService: ToolService,
    chatBoxWidget: ChatBoxWidget,
    onContextSelected?: (context: IMentionContext) => void,
    onContextRemoved?: (context_id: string) => void,
    onResetChat?: () => void,
    onModeSelected?: (mode: 'agent' | 'ask' | 'fast') => void,
    options?: { useReactInput?: boolean; placeholder?: string }
  ) {
    this.chatInput = chatInput;
    this.chatHistoryManager = chatHistoryManager;
    this.contentManager = contentManager;
    this.toolService = toolService;
    this.chatBoxWidget = chatBoxWidget;
    this.onContextSelected = onContextSelected || null;
    this.onContextRemoved = onContextRemoved || null;
    this.onResetChat = onResetChat || null;
    this.onModeSelected = onModeSelected;

    // Use React input if explicitly requested or if no chatInput provided
    this.useReactInput = options?.useReactInput ?? !chatInput;
    // Use React MentionDropdown when using React input
    this.useReactMentionDropdown = this.useReactInput;
    if (options?.placeholder) {
      this.placeholder = options.placeholder;
    }

    // Set up event handlers for textarea (only if using class-based input)
    if (!this.useReactInput && this.chatInput) {
      this.setupEventHandlers();
    }

    // Load user message history
    void this.loadUserMessageHistory();
  }

  /**
   * Set up event handlers for textarea
   * Only called when using class-based input (not React input)
   */
  private setupEventHandlers(): void {
    // Guard: this method should only be called with class-based input
    if (!this.chatInput) {
      return;
    }

    const inputElement = this.isRichTextInput(this.chatInput)
      ? this.chatInput.getInputElement()
      : this.chatInput;

    // Auto-resize the textarea as content grows
    if (this.isRichTextInput(this.chatInput)) {
      this.chatInput.addInputEventListener('input', () => {
        this.resizeTextarea();
      });
    } else if (inputElement) {
      inputElement.addEventListener('input', () => {
        this.resizeTextarea();
      });
    }

    // Handle keydown events for submission and special key combinations
    const keydownHandler = (event: Event) => {
      const keyEvent = event as KeyboardEvent;

      // Handle tab and enter when mention dropdown is visible
      if (this.mentionDropdown.getIsVisible()) {
        if (keyEvent.key === 'Tab') {
          keyEvent.preventDefault();
          this.handleTabCompletion();
          return;
        }
        if (keyEvent.key === 'Enter') {
          keyEvent.preventDefault();
          this.handleEnterWithMention();
          return;
        }
      }

      // Handle enter for message submission
      if (keyEvent.key === 'Enter') {
        if (keyEvent.shiftKey) {
          // Allow Shift+Enter for new lines
          return;
        }

        // Check if we have a complete mention that should be processed first
        if (this.hasCompleteMentionAtCursor()) {
          keyEvent.preventDefault();
          this.processCompleteMention();
          return;
        }

        // Normal enter - send message
        keyEvent.preventDefault();
        void this.sendMessage();
        return;
      }

      // Message history navigation with arrow keys
      if (keyEvent.key === 'ArrowUp') {
        // Only navigate history if cursor is at the beginning of the text or input is empty
        if (this.getSelectionStart() === 0 || this.getInputValue() === '') {
          keyEvent.preventDefault();
          this.navigateHistory('up');
        }
      } else if (keyEvent.key === 'ArrowDown') {
        // Only navigate history if cursor is at the end of the text or input is empty
        if (
          this.getSelectionStart() === this.getInputLength() ||
          this.getInputValue() === ''
        ) {
          keyEvent.preventDefault();
          this.navigateHistory('down');
        }
      }
    };

    if (this.isRichTextInput(this.chatInput)) {
      this.chatInput.addInputEventListener('keydown', keydownHandler);
    } else if (inputElement) {
      inputElement.addEventListener('keydown', keydownHandler);
    }
  }

  /**
   * Load the user's message history from all chat threads
   */
  public async loadUserMessageHistory(): Promise<void> {
    this.userMessageHistory = [];

    // Iterate through all notebooks
    const notebookIds = await this.chatHistoryManager.getNotebookIds();
    for (const notebookId of notebookIds) {
      // Get all threads for this notebook
      const threads = this.chatHistoryManager.getThreadsForNotebook(notebookId);
      if (!threads) {
        continue;
      }

      // Extract user messages from each thread
      for (const thread of threads) {
        const userMessages = thread.messages
          .filter(msg => msg.role === 'user' && typeof msg.content === 'string')
          .map(msg => (typeof msg.content === 'string' ? msg.content : ''));

        // Add non-empty messages to history
        userMessages.forEach(msg => {
          if (msg && !this.userMessageHistory.includes(msg)) {
            this.userMessageHistory.push(msg);
          }
        });
      }
    }

    // Reset the position to start at the most recent message
    this.historyPosition = -1;
    this.unsavedInput = '';

    // Sort the history so the most recently used messages are at the end
    // This makes arrow-key navigation more intuitive
    this.userMessageHistory.sort((a, b) => {
      // Keep shorter messages (which tend to be more general/reusable) at the end
      if (a.length !== b.length) {
        return a.length - b.length;
      }
      return a.localeCompare(b);
    });

    console.log(
      `[ChatInputManager] Loaded ${this.userMessageHistory.length} user messages for history navigation`
    );
  }

  /**
   * Navigate through user message history
   * @param direction 'up' for older messages, 'down' for newer messages
   */
  public navigateHistory(direction: 'up' | 'down'): void {
    // If no history, nothing to do
    if (this.userMessageHistory.length === 0) {
      return;
    }

    // Save current input if this is the first navigation action
    if (this.historyPosition === -1) {
      this.unsavedInput = this.getInputValue();
    }

    if (direction === 'up') {
      // Navigate to previous message (older)
      if (this.historyPosition < this.userMessageHistory.length - 1) {
        this.historyPosition++;
        const historyMessage =
          this.userMessageHistory[
            this.userMessageHistory.length - 1 - this.historyPosition
          ];
        this.setInputValue(historyMessage);
        // Place cursor at end of text
        const length = historyMessage.length;
        this.setSelectionRange(length, length);
      }
    } else {
      // Navigate to next message (newer)
      if (this.historyPosition > 0) {
        this.historyPosition--;
        const historyMessage =
          this.userMessageHistory[
            this.userMessageHistory.length - 1 - this.historyPosition
          ];
        this.setInputValue(historyMessage);
        // Place cursor at end of text
        const length = historyMessage.length;
        this.setSelectionRange(length, length);
      } else if (this.historyPosition === 0) {
        // Restore the unsaved input when reaching the bottom of history
        this.historyPosition = -1;
        this.setInputValue(this.unsavedInput);
        // Place cursor at end of text
        const length = this.unsavedInput.length;
        this.setSelectionRange(length, length);
      }
    }

    // Resize the textarea to fit the content
    this.resizeTextarea();
  }

  /**
   * Resize the textarea based on its content
   */
  public resizeTextarea(): void {
    const maxHeight = 150; // Maximum height in pixels

    if (this.useReactInput) {
      // React input handles its own height via CSS, but we can set overflow
      const ref = this.richTextInputRef.current;
      if (ref) {
        const scrollHeight = ref.getScrollHeight();
        if (scrollHeight <= maxHeight) {
          ref.setOverflowY('hidden');
        } else {
          ref.setOverflowY('auto');
        }
      }
    } else if (this.isRichTextInput(this.chatInput)) {
      // Reset height to auto to get the correct scrollHeight
      this.chatInput.setHeight('auto');
      // Set the height to match the content (with a max height)
      const scrollHeight = this.chatInput.getScrollHeight();
      if (scrollHeight <= maxHeight) {
        this.chatInput.setHeight(scrollHeight + 'px');
        this.chatInput.setOverflowY('hidden');
      } else {
        this.chatInput.setHeight(maxHeight + 'px');
        this.chatInput.setOverflowY('auto');
      }
    } else if (this.chatInput) {
      // Reset height to auto to get the correct scrollHeight
      this.chatInput.style.height = 'auto';
      // Set the height to match the content (with a max height)
      const scrollHeight = this.chatInput.scrollHeight;
      if (scrollHeight <= maxHeight) {
        this.chatInput.style.height = scrollHeight + 'px';
        this.chatInput.style.overflowY = 'hidden';
      } else {
        this.chatInput.style.height = maxHeight + 'px';
        this.chatInput.style.overflowY = 'auto';
      }
    }
  }

  /**
   * Set the value for either input type
   */
  public setInputValue(value: string): void {
    if (this.useReactInput) {
      this.richTextInputRef.current?.setPlainText(value);
    } else if (this.isRichTextInput(this.chatInput)) {
      this.chatInput.setPlainText(value);
    } else if (this.chatInput) {
      this.chatInput.value = value;
    }

    // Trigger input event to detect removed contexts
    // this.detectDeletedContexts();
  }

  /**
   * Clear a resolved context ID so it can be re-added on next mention
   * Call this when a context is removed from the context list
   */
  public clearResolvedContext(contextId: string): void {
    if (this.isRichTextInput(this.chatInput)) {
      this.chatInput.clearResolvedContext(contextId);
    }
    // Also remove from activeContexts
    this.activeContexts.delete(contextId);
  }

  /**
   * Get the plain text value from either input type
   */
  private getInputValue(): string {
    if (this.useReactInput) {
      return this.richTextInputRef.current?.getPlainText().trim() || '';
    } else if (this.isRichTextInput(this.chatInput)) {
      return this.chatInput.getPlainText().trim();
    } else if (this.chatInput) {
      return this.chatInput.value.trim();
    }
    return '';
  }

  /**
   * Check if the input is a RichTextChatInput
   */
  private isRichTextInput(input: ChatInputElement): input is RichTextChatInput {
    return input instanceof RichTextChatInput;
  }

  /**
   * Get selection start position for either input type
   */
  private getSelectionStart(): number {
    if (this.useReactInput) {
      return this.richTextInputRef.current?.getSelectionStart() || 0;
    } else if (this.isRichTextInput(this.chatInput)) {
      return this.chatInput.getSelectionStart();
    } else if (this.chatInput) {
      return this.chatInput.selectionStart || 0;
    }
    return 0;
  }

  /**
   * Set selection range for either input type
   */
  private setSelectionRange(start: number, end: number): void {
    if (this.useReactInput) {
      this.richTextInputRef.current?.setSelectionRange(start, end);
    } else if (this.isRichTextInput(this.chatInput)) {
      this.chatInput.setSelectionRange(start, end);
    } else if (this.chatInput) {
      this.chatInput.selectionStart = start;
      this.chatInput.selectionEnd = end;
    }
  }

  /**
   * Get the current input text length
   */
  private getInputLength(): number {
    return this.getInputValue().length;
  }

  /**
   * Clear the input
   */
  public clearInput(): void {
    if (this.useReactInput) {
      this.richTextInputRef.current?.clear();
    } else if (this.isRichTextInput(this.chatInput)) {
      this.chatInput.clear();
    } else if (this.chatInput) {
      this.chatInput.value = '';
      this.chatInput.style.height = 'auto'; // Reset height after clearing
    }
    this.focus();
  }

  /**
   * Add a message to history
   */
  public addToHistory(message: string): void {
    if (!this.userMessageHistory.includes(message)) {
      this.userMessageHistory.push(message);
    }

    // Reset history navigation
    this.historyPosition = -1;
    this.unsavedInput = '';
  }

  /**
   * Focus the input
   */
  public focus(): void {
    if (this.useReactInput) {
      this.richTextInputRef.current?.focus();
    } else if (this.chatInput) {
      if (this.isRichTextInput(this.chatInput)) {
        this.chatInput.focus();
      } else {
        this.chatInput.focus();
      }
    }
  }

  /**
   * Get the current input value (public method)
   */
  public getCurrentInputValue(): string {
    return this.getInputValue();
  }

  /**
   * Set the placeholder text for the input
   */
  public setPlaceholder(placeholder: string): void {
    this.placeholder = placeholder;
    if (this.useReactInput) {
      // For React input, we need to re-render with new placeholder
      this.createReactRichTextInput();
    } else if (this.isRichTextInput(this.chatInput)) {
      this.chatInput.setPlaceholder(placeholder);
    }
  }

  /**
   * Set the dependencies needed for sendMessage and revertAndSend functionality.
   *
   * Note: sendButton is no longer required as the React SendButton component
   * is now created internally in createSendButton(). The parameter is kept
   * for backward compatibility but is ignored.
   *
   * NOTE: modeSelector is also now created internally in createModeSelector().
   * The parameter is kept for backward compatibility but is ignored.
   */
  public setDependencies(dependencies: {
    chatService: IChatService;
    conversationService: ConversationService;
    messageComponent: ChatMessages;
    uiHelper: ChatUIHelper;
    contextHandler: ChatboxContext;
    sendButton?: HTMLButtonElement | HTMLElement; // Optional, for backward compatibility
    modeSelector?: HTMLElement; // Optional, for backward compatibility
    cancelMessage: () => void;
    onMessageSent?: () => void;
  }): void {
    this.chatService = dependencies.chatService;
    this.conversationService = dependencies.conversationService;
    this.messageComponent = dependencies.messageComponent;
    this.uiHelper = dependencies.uiHelper;
    this.contextHandler = dependencies.contextHandler;
    // Note: sendButton and modeSelector are now created internally
    this.cancelMessage = dependencies.cancelMessage;
    this.onMessageSent = dependencies.onMessageSent;
  }

  /**
   * Set the mode name
   */
  public setModeName(modeName: 'agent' | 'ask' | 'fast'): void {
    this.modeName = modeName;
  }

  /**
   * Get the current processing state
   */
  public getIsProcessingMessage(): boolean {
    return this.isProcessingMessage;
  }

  /**
   * Set the processing state
   */
  public setIsProcessingMessage(value: boolean): void {
    this.isProcessingMessage = value;
  }

  /**
   * Get the current checkpoint to restore
   */
  public getCheckpointToRestore(): ICheckpoint | null {
    return this.checkpointToRestore;
  }

  /**
   * Set the checkpoint to restore
   */
  public setCheckpointToRestore(checkpoint: ICheckpoint | null): void {
    this.checkpointToRestore = checkpoint;
  }

  /**
   * Send a message to the AI service
   */
  public async sendMessage(
    cell_context?: string,
    hidden?: boolean
  ): Promise<void> {
    const perfStart = performance.now();
    console.log('[PERF] ChatInputManager.sendMessage - START');

    // Check if dependencies are set
    if (
      !this.chatService ||
      !this.conversationService ||
      !this.messageComponent ||
      !this.uiHelper ||
      !this.contextHandler ||
      !this.sendButtonContainer ||
      !this.modeSelectorContainer
    ) {
      console.error(
        'ChatInputManager dependencies not set. Call setDependencies() first.'
      );
      return;
    }

    const userInput = this.getCurrentInputValue();
    if (!userInput || this.isProcessingMessage) {
      return;
    }

    if (this.checkpointToRestore) {
      await this.conversationService.finishCheckpointRestoration(
        this.checkpointToRestore
      );
    }

    // Hide the waiting reply box when user sends a message
    AppStateService.getWaitingUserReplyBoxManager().hide();

    // Special command to reset the chat
    if (userInput.toLowerCase() === 'reset') {
      // We'll need to add a callback for this
      if (this.onResetChat) {
        this.onResetChat();
      }
      this.clearInput();
      return;
    }

    // Add message to history navigation
    this.addToHistory(userInput);

    // Notify that a message is being sent (triggers switch to history widget)
    if (this.onMessageSent) {
      this.onMessageSent();
    }

    // Set processing state - update both local and chatStore for React components
    // Note: React ModeSelector automatically subscribes to isProcessing from the store
    this.isProcessingMessage = true;
    useChatStore.getState().setProcessing(true);
    AppStateService.getPlanStateDisplay().setLoading(true);

    // Reset LLM state display to generating state, clearing any diff state
    this.uiHelper.resetToGeneratingState('Generating...');

    // Check if the chat client has been properly initialized
    if (!this.chatService.isInitialized()) {
      this.messageComponent.addSystemMessage(
        '❌ API key is not set. Please configure it in the settings.'
      );
      this.isProcessingMessage = false;
      useChatStore.getState().setProcessing(false);
      AppStateService.getPlanStateDisplay().setLoading(false);
      this.uiHelper.hideLoadingIndicator();
      return;
    }
    const system_messages: string[] = [];
    // Clear the input
    this.clearInput();

    // Convert @mentions to context tags for message processing
    const { message: processedUserInput, unresolvedMentions } =
      convertMentionsToContextTags(userInput, this.activeContexts);
    console.log('[ChatInputManager] Original message:', userInput);
    console.log(
      '[ChatInputManager] Processed message with context tags:',
      processedUserInput
    );
    if (unresolvedMentions.length > 0) {
      console.log(
        '[ChatInputManager] Unresolved mentions:',
        unresolvedMentions
      );
    }

    // Display the user message in the UI (with context tags for proper styling)
    this.messageComponent.addUserMessage(processedUserInput, hidden);

    // Initialize messages with the user query (with context tags for API processing)
    const newUserMessage = { role: 'user', content: processedUserInput };

    try {
      // Make sure the conversation service knows which notebook we're targeting
      const currentNotebookId = AppStateService.getCurrentNotebookId();
      if (currentNotebookId) {
        this.conversationService.setNotebookId(currentNotebookId);

        const cellChanges =
          await NotebookCellStateService.detectChanges(currentNotebookId);
        const changesSummary =
          NotebookCellStateService.generateChangeSummaryMessage(cellChanges);
        if (changesSummary) {
          system_messages.push(changesSummary);
          console.log(
            '[ChatInputManager] Detected notebook changes, added to system messages'
          );
        }
      }

      // Add cell changes to system messages if there are any

      const messages = [newUserMessage];
      if (cell_context) {
        system_messages.push(cell_context);
      }
      const mentionContexts = this.messageComponent.getMentionContexts();
      if (mentionContexts.size > 0) {
        system_messages.push(this.contextHandler.getCurrentContextMessage());
      }

      system_messages.push(
        this.contextHandler.getCurrentWorkingDirectoryMessage()
      );

      // Add message about unresolved mentions if any
      if (unresolvedMentions.length > 0) {
        const mentionList = unresolvedMentions.map(m => `@${m}`).join(', ');
        system_messages.push(
          `The following mentions could not be resolved: ${mentionList}. ` +
            `The context for these mentions was not found. ` +
            `Please ask the user to add these contexts again using the @ mention feature, or proceed without them.`
        );
      }

      // Proceed with sending the message

      AppStateService.getNotebookDiffManager().clearDiffs();

      const perfBeforeConversation = performance.now();
      console.log(
        `[PERF] ChatInputManager.sendMessage - Before processConversation (${(perfBeforeConversation - perfStart).toFixed(2)}ms elapsed)`
      );

      await this.conversationService.processConversation(
        messages,
        system_messages,
        this.modeName
      );

      const perfAfterConversation = performance.now();
      console.log(
        `[PERF] ChatInputManager.sendMessage - After processConversation (${(perfAfterConversation - perfBeforeConversation).toFixed(2)}ms)`
      );

      // Cache the current notebook state after successful message processing
      if (currentNotebookId) {
        await NotebookCellStateService.cacheCurrentNotebookState(
          currentNotebookId
        );
      }
      console.log(
        '[ChatInputManager] Cached notebook state after message processing'
      );
    } catch (error) {
      console.error('Error in conversation processing:', error);

      // Only show error if we're not cancelled
      if (this.chatService.getRequestStatus() !== ChatRequestStatus.CANCELLED) {
        // Check if this is a subscription/authentication error
        const errorMessage =
          error instanceof Error ? error.message : String(error);

        // Check for authentication errors in the error message
        const isAuthError =
          errorMessage.includes('authentication_error') ||
          errorMessage.includes('Invalid API key') ||
          (errorMessage.includes('401') && errorMessage.includes('error'));

        if (isAuthError) {
          // Display subscription card instead of error message
          this.messageComponent.displaySubscriptionCard();
        } else {
          // Display regular error message
          this.messageComponent.addErrorMessage(`❌ ${errorMessage}`);
        }
      }
    } finally {
      // Reset state - React ModeSelector auto-updates via store subscription
      this.isProcessingMessage = false;
      useChatStore.getState().setProcessing(false);
      AppStateService.getPlanStateDisplay().setLoading(false);
    }
  }

  /**
   * Continue the LLM loop without requiring new user input
   * This continues processing without creating or displaying a new user message
   */
  public async continueMessage(cell_context?: string): Promise<void> {
    // Check if dependencies are set
    if (
      !this.chatService ||
      !this.conversationService ||
      !this.messageComponent ||
      !this.uiHelper ||
      !this.contextHandler ||
      !this.sendButtonContainer ||
      !this.modeSelectorContainer
    ) {
      console.error(
        'ChatInputManager dependencies not set. Call setDependencies() first.'
      );
      return;
    }

    if (this.isProcessingMessage) {
      return;
    }

    if (this.checkpointToRestore) {
      await this.conversationService.finishCheckpointRestoration(
        this.checkpointToRestore
      );
    }

    // Hide the waiting reply box when continuing
    AppStateService.getWaitingUserReplyBoxManager().hide();

    // Set processing state - update both local and chatStore for React components
    // Note: React ModeSelector automatically subscribes to isProcessing from the store
    this.isProcessingMessage = true;
    useChatStore.getState().setProcessing(true);
    AppStateService.getPlanStateDisplay().setLoading(true);

    // Reset LLM state display to generating state, clearing any diff state
    this.uiHelper.resetToGeneratingState('Generating...');

    // Check if the chat client has been properly initialized
    if (!this.chatService.isInitialized()) {
      this.messageComponent.addSystemMessage(
        '❌ API key is not set. Please configure it in the settings.'
      );
      this.isProcessingMessage = false;
      useChatStore.getState().setProcessing(false);
      AppStateService.getPlanStateDisplay().setLoading(false);
      this.uiHelper.hideLoadingIndicator();
      return;
    }

    const system_messages: string[] = [];

    try {
      // Make sure the conversation service knows which notebook we're targeting
      const currentNotebookId = AppStateService.getCurrentNotebookId();
      if (currentNotebookId) {
        this.conversationService.setNotebookId(currentNotebookId);

        const cellChanges =
          await NotebookCellStateService.detectChanges(currentNotebookId);
        const changesSummary =
          NotebookCellStateService.generateChangeSummaryMessage(cellChanges);
        if (changesSummary) {
          system_messages.push(changesSummary);
          console.log(
            '[ChatInputManager] Detected notebook changes, added to system messages'
          );
        }
      }

      // Continue without adding a new user message
      const messages: Array<{ role: string; content: string }> = [];
      if (cell_context) {
        system_messages.push(cell_context);
      }
      const mentionContexts = this.messageComponent.getMentionContexts();
      if (mentionContexts.size > 0) {
        system_messages.push(this.contextHandler.getCurrentContextMessage());
      }

      system_messages.push(
        this.contextHandler.getCurrentWorkingDirectoryMessage()
      );

      // Proceed with continuing the conversation

      AppStateService.getNotebookDiffManager().clearDiffs();

      await this.conversationService.processConversation(
        messages,
        system_messages,
        this.modeName
      );

      // Cache the current notebook state after successful message processing
      if (currentNotebookId) {
        await NotebookCellStateService.cacheCurrentNotebookState(
          currentNotebookId
        );
      }
      console.log(
        '[ChatInputManager] Cached notebook state after continuing message'
      );
    } catch (error) {
      console.error('Error in conversation processing:', error);

      // Only show error if we're not cancelled
      if (this.chatService.getRequestStatus() !== ChatRequestStatus.CANCELLED) {
        // Check if this is a subscription/authentication error
        const errorMessage =
          error instanceof Error ? error.message : String(error);

        // Check for authentication errors in the error message
        const isAuthError =
          errorMessage.includes('authentication_error') ||
          errorMessage.includes('Invalid API key') ||
          (errorMessage.includes('401') && errorMessage.includes('error'));

        if (isAuthError) {
          // Display subscription card instead of error message
          this.messageComponent.displaySubscriptionCard();
        } else {
          // Display regular error message
          this.messageComponent.addErrorMessage(`❌ ${errorMessage}`);
        }
      }
    } finally {
      // Reset state - React ModeSelector auto-updates via store subscription
      this.isProcessingMessage = false;
      useChatStore.getState().setProcessing(false);
      AppStateService.getPlanStateDisplay().setLoading(false);

      // Set needsContinue to false after continueMessage finishes
      const currentThread = this.chatHistoryManager.getCurrentThread();
      if (currentThread && currentThread.needsContinue) {
        currentThread.needsContinue = false;
        console.log(
          '[ChatInputManager] Set needsContinue to false after continueMessage finished'
        );
      }
    }
  }

  /**
   * Handle tab completion for mentions
   */
  private handleTabCompletion(): void {
    // Use the dropdown's own selection mechanism which handles highlighted items
    if (this.useReactMentionDropdown) {
      this.mentionDropdownRef.current?.selectHighlighted();
    } else {
      this.mentionDropdown.selectHighlightedItem();
    }
  }

  /**
   * Handle enter key when mention dropdown is visible
   */
  private handleEnterWithMention(): void {
    // Use the dropdown's own selection mechanism which handles highlighted items
    if (this.useReactMentionDropdown) {
      this.mentionDropdownRef.current?.selectHighlighted();
    } else {
      this.mentionDropdown.selectHighlightedItem();
    }
  }

  /**
   * Complete a mention with the given name
   */
  private completeMentionWithName(name: string): void {
    const currentInput = this.getInputValue();
    const cursorPos = this.getSelectionStart();

    // Find the @ symbol before the cursor
    let mentionStart = -1;
    for (let i = cursorPos - 1; i >= 0; i--) {
      if (currentInput[i] === '@') {
        mentionStart = i;
        break;
      }
      if (currentInput[i] === ' ' || currentInput[i] === '\n') {
        break;
      }
    }

    if (mentionStart === -1) {
      return;
    }

    // Replace the partial mention with the complete one - replace spaces with underscores
    const beforeMention = currentInput.substring(0, mentionStart);
    const afterCursor = currentInput.substring(cursorPos);
    const displayName = name.replace(/\s+/g, '_');
    const replacement = `@${displayName} `;

    this.setInputValue(beforeMention + replacement + afterCursor);

    // Set cursor after the completed mention
    const newCursorPos = mentionStart + replacement.length;
    this.setSelectionRange(newCursorPos, newCursorPos);

    // The mention dropdown should already handle adding to context
  }

  /**
   * Check if there's a complete mention at the cursor position
   */
  private hasCompleteMentionAtCursor(): boolean {
    const currentInput = this.getInputValue();
    const cursorPos = this.getSelectionStart();

    // Look for @mention pattern before cursor
    const beforeCursor = currentInput.substring(0, cursorPos);
    const mentionMatch = beforeCursor.match(/@(\w+)\s*$/);

    return mentionMatch !== null;
  }

  /**
   * Process a complete mention (add to context without sending message)
   */
  private processCompleteMention(): void {
    const currentInput = this.getInputValue();
    const cursorPos = this.getSelectionStart();

    // Look for @mention pattern before cursor
    const beforeCursor = currentInput.substring(0, cursorPos);
    const mentionMatch = beforeCursor.match(/@(\w+)\s*$/);

    if (mentionMatch) {
      const mentionName = mentionMatch[1];

      // Try to find this mention in the available contexts
      // This is a simplified approach - in a real implementation you'd want to
      // search through all context categories for a matching name
      console.log(`Processing complete mention: ${mentionName}`);

      // Focus back to input for continued typing
      this.focus();
    }
  }

  /**
   * Create the complete input container with all its components
   */
  public createInputContainer(): HTMLElement {
    // Create input container with text input and send button
    this.inputContainer = document.createElement('div');
    this.inputContainer.className = 'sage-ai-input-container';
    this.inputContainer.style.position = 'relative'; // Add relative positioning

    // Create inner chatbox wrapper for the focused styling
    this.chatboxWrapper = document.createElement('div');
    this.chatboxWrapper.className = 'sage-ai-chatbox-wrapper';

    // Create context row (first row)
    this.createContextRow();

    // Create input row (second row)
    this.createInputRow();

    // Assemble the input structure
    this.chatboxWrapper.appendChild(this.contextRow!);
    this.chatboxWrapper.appendChild(this.inputRow!);
    this.inputContainer.appendChild(this.chatboxWrapper);

    // Initialize the mention dropdown with the input container
    // For React input, we need to wait for the ref to be available
    const initMentionDropdown = () => {
      let inputElement: HTMLElement | null = null;

      if (this.useReactInput) {
        inputElement = this.richTextInputRef.current?.getInputElement() || null;
      } else if (this.isRichTextInput(this.chatInput)) {
        inputElement = this.chatInput.getInputElement();
      } else if (this.chatInput) {
        inputElement = this.chatInput as HTMLElement;
      }

      if (!inputElement) {
        console.warn(
          '[ChatInputManager] Input element not available for mention dropdown'
        );
        return;
      }

      if (this.useReactMentionDropdown) {
        // Use React MentionDropdown
        this.createReactMentionDropdown(inputElement);
      } else {
        // Use DOM-based ChatContextMenu
        this.mentionDropdown = new ChatContextMenu(
          inputElement,
          this.inputContainer!,
          this.getContentManager(),
          this.getToolService()
        );

        // Set up the context selection callback for the mention dropdown
        this.mentionDropdown.setContextSelectedCallback(
          (context: IMentionContext) => {
            // Store the context when selected
            this.activeContexts.set(context.id, context);

            // Update input contexts if applicable
            if (this.useReactInput) {
              this.richTextInputRef.current?.setActiveContexts(
                this.activeContexts
              );
            } else if (this.isRichTextInput(this.chatInput)) {
              this.chatInput.setActiveContexts(this.activeContexts);
            }

            if (this.onContextSelected) {
              this.onContextSelected(context);
            }
          }
        );
      }
    };

    // For React input, delay initialization to allow ref to be set
    if (this.useReactInput) {
      setTimeout(initMentionDropdown, 0);
    } else {
      initMentionDropdown();
    }

    // Set up callback for when mentions are resolved (e.g., on paste)
    if (this.isRichTextInput(this.chatInput)) {
      const richTextInput = this.chatInput;
      richTextInput.setOnMentionResolved((context: IMentionContext) => {
        // Always add context when mention is resolved (handles re-adding after removal)
        // Using Map.set() is idempotent, so duplicates are not a concern
        this.activeContexts.set(context.id, context);
        richTextInput.setActiveContexts(this.activeContexts);

        if (this.onContextSelected) {
          this.onContextSelected(context);
        }
      });

      // Set up lookup function to find contexts from the global cache
      richTextInput.setAvailableContextsLookup((name: string) => {
        const cachedContexts = AppStateService.getCachedContexts();
        for (const contexts of cachedContexts.values()) {
          for (const context of contexts) {
            const normalizedName = context.name.replace(/\s+/g, '_');
            if (context.name === name || normalizedName === name) {
              return context;
            }
          }
        }
        return undefined;
      });
    }

    return this.inputContainer;
  }

  /**
   * Create the context row using the React ContextRow component.
   *
   * The ContextRow component handles:
   * - "Add Context" button with @ icon
   * - Displaying active context items (cells and mentions)
   * - Remove functionality for each context item
   * - Automatic styling based on context type
   */
  private createContextRow(): void {
    // Create container element for the React ContextRow
    this.contextRow = document.createElement('div');
    this.contextRow.className = 'sage-ai-context-row-container';

    // Initial render
    this.renderContextRow();
  }

  /**
   * Render or update the React ContextRow component.
   * Called when context items change.
   */
  public renderContextRow(): void {
    if (!this.contextRow) {
      return;
    }

    // Get cell contexts from NotebookContextManager
    const notebookId = AppStateService.getCurrentNotebookId();
    const contextManager = AppStateService.getNotebookContextManager();
    const cellContexts: CellContext[] =
      notebookId && contextManager
        ? contextManager.getContextCells(notebookId)
        : [];

    // Handler for "Add Context" button click
    const handleAddContext = () => {
      // Focus the input first
      this.focus();

      // Use setTimeout to ensure focus is complete before inserting
      setTimeout(() => {
        // Get the input element
        let inputElement: HTMLElement | null = null;
        if (this.useReactInput) {
          inputElement =
            this.richTextInputRef.current?.getInputElement() || null;
        } else if (this.isRichTextInput(this.chatInput)) {
          inputElement = this.chatInput.getInputElement();
        } else if (this.chatInput) {
          inputElement = this.chatInput;
        }

        if (!inputElement) {
          return;
        }

        // Use execCommand to insert @ at cursor position
        // This properly triggers input events and maintains cursor position
        document.execCommand('insertText', false, '@');
      }, 0);
    };

    // Handler for removing a mention context
    const handleRemoveMentionContext = (
      contextId: string,
      contextName: string
    ) => {
      // Import context store for direct removal
      const { useContextStore } = require('../stores/contextStore');

      if (this.messageComponent) {
        // This removes from ChatMessages.mentionContexts, persists to storage, and updates Zustand
        this.messageComponent.removeMentionContext(contextId);
      } else {
        // Fallback: directly remove from Zustand store if messageComponent not available
        console.warn(
          '[ChatInputManager] messageComponent not available, removing context directly from store'
        );
        useContextStore.getState().removeContext(contextId);
      }

      // Also remove from the input text
      const currentInput = this.getCurrentInputValue();
      const escapedName = contextName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
      const cleanedInput = currentInput
        .replace(new RegExp(`@\\{?${escapedName}\\}?`, 'g'), '')
        .trim();
      this.setInputValue(cleanedInput);

      // Re-render to update the display (mention contexts auto-update via Zustand)
      this.renderContextRow();
    };

    // Handler for removing a cell context
    const handleRemoveCellContext = (cellId: string) => {
      if (notebookId && contextManager) {
        contextManager.removeCellFromContext(notebookId, cellId);

        // Update context buttons in notebook cells
        const notebookTools = AppStateService.getNotebookTools();
        const currentNotebookPanel = notebookTools.getCurrentNotebook()?.widget;
        if (currentNotebookPanel) {
          const contextCellHighlighter =
            AppStateService.getContextCellHighlighter();
          contextCellHighlighter.addContextButtonsToAllCells(
            currentNotebookPanel
          );
        }
      }

      // Re-render to update the display
      this.renderContextRow();
    };

    // Get mention contexts from ChatMessages (user-selected contexts)
    // This is the source of truth for user-selected contexts, NOT the contextStore
    // (contextStore is used for available contexts loaded by ContextCacheService)
    const mentionContextsMap =
      this.messageComponent?.getMentionContexts() ?? new Map();
    const mentionContexts = Array.from(mentionContextsMap.values());

    const element = React.createElement(ContextRow, {
      cellContexts,
      mentionContexts,
      onAddContext: handleAddContext,
      onRemoveMentionContext: handleRemoveMentionContext,
      onRemoveCellContext: handleRemoveCellContext
    });

    if (this.contextRowMounted) {
      this.contextRowMounted.update(element);
    } else {
      this.contextRowMounted = mountComponent(this.contextRow, element);
    }
  }

  /**
   * Create the input row with rich text input, send button, and mode selector
   */
  private createInputRow(): void {
    this.inputRow = document.createElement('div');
    this.inputRow.className = 'sage-ai-input-row';

    // Add the rich text input to the input row
    if (this.useReactInput) {
      // Create container and mount React RichTextInput
      this.richTextInputContainer = document.createElement('div');
      this.richTextInputContainer.className =
        'sage-ai-rich-text-input-container';
      this.richTextInputContainer.style.display = 'contents';
      this.inputRow.appendChild(this.richTextInputContainer);
      this.createReactRichTextInput();
    } else if (this.isRichTextInput(this.chatInput)) {
      this.inputRow.appendChild(this.chatInput.getElement());
    } else if (this.chatInput) {
      this.inputRow.appendChild(this.chatInput);
    }

    // Create token progress wrapper (contains circle and compress button)
    this.createTokenProgressWrapper();

    // Create send button
    this.createSendButton();

    // Create mode selector
    this.createModeSelector();

    // Add token progress container, send button container and mode selector to input row
    this.inputRow.appendChild(this.tokenProgressContainer!);
    this.inputRow.appendChild(this.sendButtonContainer!);
    this.inputRow.appendChild(this.modeSelectorContainer!);
  }

  /**
   * Create and mount the React RichTextInput component.
   */
  private createReactRichTextInput(): void {
    if (!this.richTextInputContainer) {
      return;
    }

    // Create a new ref for this render
    this.richTextInputRef =
      React.createRef<RichTextInputRef>() as React.RefObject<RichTextInputRef>;

    const element = React.createElement(RichTextInput, {
      ref: this.richTextInputRef,
      placeholder: this.placeholder,
      activeContexts: this.activeContexts,
      onInput: () => {
        // Re-render send button to update hasContent state
        this.renderSendButton?.();
      },
      onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => {
        this.handleReactInputKeyDown(event);
      }
    });

    if (this.richTextInputMounted) {
      this.richTextInputMounted.update(element);
    } else {
      this.richTextInputMounted = mountComponent(
        this.richTextInputContainer,
        element
      );
    }

    // Setup event handlers after mounting (need to wait for ref)
    setTimeout(() => {
      this.setupReactInputEventHandlers();
    }, 0);
  }

  /**
   * Handle keydown events for React input.
   * Mirrors the logic in setupEventHandlers for class-based input.
   */
  private handleReactInputKeyDown(
    event: React.KeyboardEvent<HTMLDivElement>
  ): void {
    // Check if mention dropdown is visible (works with both DOM and React dropdown)
    const isDropdownVisible = this.useReactMentionDropdown
      ? this.mentionDropdownRef.current?.isVisible()
      : this.mentionDropdown?.getIsVisible();

    // Handle tab and enter when mention dropdown is visible
    if (isDropdownVisible) {
      if (event.key === 'Tab') {
        event.preventDefault();
        this.handleTabCompletion();
        return;
      }
      if (event.key === 'Enter') {
        event.preventDefault();
        this.handleEnterWithMention();
        return;
      }
      // Handle arrow keys for dropdown navigation
      if (event.key === 'ArrowDown') {
        event.preventDefault();
        if (this.useReactMentionDropdown) {
          this.mentionDropdownRef.current?.navigate('down');
        }
        return;
      }
      if (event.key === 'ArrowUp') {
        event.preventDefault();
        if (this.useReactMentionDropdown) {
          this.mentionDropdownRef.current?.navigate('up');
        }
        return;
      }
      if (event.key === 'Escape') {
        event.preventDefault();
        if (this.useReactMentionDropdown) {
          this.mentionDropdownRef.current?.hide();
        }
        return;
      }
    }

    // Handle enter for message submission
    if (event.key === 'Enter') {
      if (event.shiftKey) {
        // Allow Shift+Enter for new lines
        return;
      }

      // Check if we have a complete mention that should be processed first
      if (this.hasCompleteMentionAtCursor()) {
        event.preventDefault();
        this.processCompleteMention();
        return;
      }

      // Normal enter - send message
      event.preventDefault();
      void this.sendMessage();
      return;
    }

    // Message history navigation with arrow keys
    if (event.key === 'ArrowUp') {
      // Only navigate history if cursor is at the beginning of the text or input is empty
      if (this.getSelectionStart() === 0 || this.getInputValue() === '') {
        event.preventDefault();
        this.navigateHistory('up');
      }
    } else if (event.key === 'ArrowDown') {
      // Only navigate history if cursor is at the end of the text or input is empty
      if (
        this.getSelectionStart() === this.getInputLength() ||
        this.getInputValue() === ''
      ) {
        event.preventDefault();
        this.navigateHistory('down');
      }
    }
  }

  /**
   * Set up event handlers for React input (for mention dropdown integration).
   */
  private setupReactInputEventHandlers(): void {
    const inputElement = this.richTextInputRef.current?.getInputElement();
    if (!inputElement) {
      console.warn(
        '[ChatInputManager] React input element not available for event handlers'
      );
      return;
    }

    // The mention dropdown needs the raw DOM element to attach listeners
    // This is handled in createInputContainer after the dropdown is created
  }

  /**
   * Create and mount the React MentionDropdown component.
   * Sets up event listeners to detect @ mentions and control the dropdown.
   */
  private createReactMentionDropdown(inputElement: HTMLElement): void {
    // Create container for the dropdown
    this.mentionDropdownContainer = document.createElement('div');
    this.mentionDropdownContainer.className =
      'sage-ai-mention-dropdown-container';
    this.inputContainer?.appendChild(this.mentionDropdownContainer);

    // Create a new ref
    this.mentionDropdownRef = React.createRef<MentionDropdownRef>();

    // Handler for context selection
    const handleContextSelected = (context: IMentionContext) => {
      // Store the context when selected
      this.activeContexts.set(context.id, context);

      // Update input contexts if applicable
      if (this.useReactInput) {
        this.richTextInputRef.current?.setActiveContexts(this.activeContexts);
      } else if (this.isRichTextInput(this.chatInput)) {
        this.chatInput.setActiveContexts(this.activeContexts);
      }

      if (this.onContextSelected) {
        this.onContextSelected(context);
      }
    };

    // Mount the component
    const element = React.createElement(MentionDropdown, {
      ref: this.mentionDropdownRef,
      inputElement: inputElement,
      parentElement: this.inputContainer || null,
      onContextSelected: handleContextSelected,
      onVisibilityChange: (visible: boolean) => {
        // Track visibility for keyboard handling
        if (!visible) {
          this.currentMentionStart = -1;
        }
      }
    });

    this.mentionDropdownMounted = mountComponent(
      this.mentionDropdownContainer,
      element
    );

    // Set up input event listener to detect @ mentions
    inputElement.addEventListener('input', this.handleMentionInput);
  }

  /**
   * Handle input events to detect @ mentions for React MentionDropdown
   */
  private handleMentionInput = (): void => {
    if (!this.useReactMentionDropdown) return;

    const cursorPosition = this.getSelectionStart();
    const inputValue = this.getInputValue();
    const dropdownRef = this.mentionDropdownRef.current;

    if (!dropdownRef) return;

    // Check if dropdown is visible
    if (dropdownRef.isVisible()) {
      // Check if cursor moved outside of the current mention
      if (
        cursorPosition < this.currentMentionStart ||
        !inputValue
          .substring(this.currentMentionStart, cursorPosition)
          .startsWith('@')
      ) {
        dropdownRef.hide();
        return;
      }

      // Update the mention text for filtering
      const mentionText = inputValue.substring(
        this.currentMentionStart + 1,
        cursorPosition
      );
      dropdownRef.updateMentionText(mentionText);
      return;
    }

    // Look for a new mention - check if @ was just typed
    if (inputValue.charAt(cursorPosition - 1) === '@') {
      this.currentMentionStart = cursorPosition - 1;
      dropdownRef.show(this.currentMentionStart);
    }
  };

  // Store reference to renderSendButton for input callback
  private renderSendButton?: () => void;

  /**
   * Create the send button using the React SendButton component.
   *
   * The SendButton component handles:
   * - Visual state (send icon vs stop icon)
   * - Enabled/disabled states based on content and processing
   * - Click handling for send and cancel actions
   *
   * This method:
   * - Creates a container element for the React component
   * - Mounts the SendButton with appropriate callbacks
   * - Sets up input listeners to update hasContent state
   */
  private createSendButton(): void {
    // Create container element for the React SendButton
    this.sendButtonContainer = document.createElement('div');
    this.sendButtonContainer.className = 'sage-ai-send-button-container';

    // Handler for send action
    const handleSend = () => {
      console.log('[ChatInputManager] SendButton: Send clicked');
      void this.sendMessage();
    };

    // Handler for cancel action
    const handleCancel = () => {
      console.log('[ChatInputManager] SendButton: Cancel clicked', {
        isProcessingMessage: this.isProcessingMessage,
        hasCancelMessage: !!this.cancelMessage
      });

      // CRITICAL: Always cancel when processing
      if (this.cancelMessage) {
        console.log('[ChatInputManager] Calling cancelMessage callback');
        this.cancelMessage();
      } else {
        // Fallback: try to call the chatbox widget's cancelMessage directly
        console.warn(
          '[ChatInputManager] cancelMessage callback is not defined! Using fallback...'
        );
        const chatWidget = AppStateService.getChatContainerSafe()?.chatWidget;
        if (chatWidget) {
          console.log(
            '[ChatInputManager] Calling cancelMessage via AppState fallback'
          );
          chatWidget.cancelMessage();
        } else {
          console.error(
            '[ChatInputManager] CRITICAL: Could not find chat widget to cancel message'
          );
        }
      }
    };

    // Helper to render the SendButton with current state
    const renderSendButton = () => {
      const hasContent = this.getInputValue().trim() !== '';
      const element = React.createElement(SendButton, {
        hasContent,
        onSend: handleSend,
        onCancel: handleCancel
      });

      if (this.sendButtonMounted) {
        this.sendButtonMounted.update(element);
      } else {
        this.sendButtonMounted = mountComponent(
          this.sendButtonContainer!,
          element
        );
      }
    };

    // Initial render
    renderSendButton();

    // Store reference for React input to use
    this.renderSendButton = renderSendButton;

    // Update button state when input content changes
    const updateSendButtonState = () => {
      renderSendButton();
    };

    // Listen for input changes to update button state
    // Only add listeners for class-based input (React input uses onInput callback)
    if (!this.useReactInput && this.chatInput) {
      this.chatInput.addEventListener('input', updateSendButtonState);
      this.chatInput.addEventListener('keyup', updateSendButtonState);
      this.chatInput.addEventListener('paste', () => {
        // Use setTimeout to ensure paste content is processed
        setTimeout(updateSendButtonState, 0);
      });
    }
  }

  /**
   * Create the token progress indicator using React TokenProgressIndicator component.
   *
   * The component displays:
   * - Circular progress bar showing token usage
   * - Color coding (blue < 40%, orange 40-70%, red >= 70%)
   * - Tooltip with exact token count
   * - "Compact" button when usage >= 40%
   */
  private createTokenProgressWrapper(): void {
    // Create container element for the React TokenProgressIndicator
    this.tokenProgressContainer = document.createElement('div');
    this.tokenProgressContainer.className =
      'sage-ai-token-progress-container-wrapper';

    // Render the initial component with 0 tokens
    this.renderTokenProgress();
  }

  /**
   * Render or update the TokenProgressIndicator React component
   */
  private renderTokenProgress(): void {
    if (!this.tokenProgressContainer) {
      return;
    }

    const element = React.createElement(TokenProgressIndicator, {
      tokenCount: this.currentTokenCount,
      maxTokens: MAX_RECOMMENDED_TOKENS,
      onCompact: () => this.handleCompressHistory(),
      isCompacting: this.isCompacting
    });

    if (this.tokenProgressMounted) {
      this.tokenProgressMounted.update(element);
    } else {
      this.tokenProgressMounted = mountComponent(
        this.tokenProgressContainer,
        element
      );
    }
  }

  /**
   * Handle compress history button click.
   * Sends a hidden message to trigger the compress_history tool.
   */
  private async handleCompressHistory(): Promise<void> {
    if (
      !this.messageComponent ||
      this.isProcessingMessage ||
      this.isCompacting
    ) {
      return;
    }

    // Set compacting state and re-render to show loading state
    this.isCompacting = true;
    this.renderTokenProgress();

    try {
      // Save current input value
      const currentInput = this.getInputValue();

      // Set input to a message that will trigger compression
      this.setInputValue(
        'Please compress the chat history to reduce token usage. Keep the 10 most recent messages uncompressed.'
      );

      // Send as hidden message
      await this.sendMessage(undefined, true);

      // Restore original input value
      this.setInputValue(currentInput);
    } catch (error) {
      console.error(
        '[ChatInputManager] Error sending compress message:',
        error
      );
      if (this.messageComponent) {
        this.messageComponent.addSystemMessage(
          `⚠️ Error compressing chat history: ${error instanceof Error ? error.message : String(error)}`
        );
      }
    } finally {
      // Re-enable button after a short delay to allow message processing to start
      setTimeout(() => {
        this.isCompacting = false;
        this.renderTokenProgress();
      }, 500);
    }
  }

  /**
   * Update the token progress indicator based on current conversation history.
   *
   * Calculates total tokens from:
   * 1. Persisted usage data from Claude responses (preferred)
   * 2. Estimation based on message content (fallback)
   *
   * Then re-renders the React TokenProgressIndicator component with the new count.
   */
  public updateTokenProgress(messages?: IChatMessage[]): void {
    const conversationHistory =
      messages || this.messageComponent?.getMessageHistory() || [];

    // Try to calculate tokens from persisted usage data first
    let totalTokens = 0;
    let hasUsageData = false;

    for (const message of conversationHistory) {
      if (message.usage) {
        hasUsageData = true;
        // Sum: cache_creation + cache_read + input + output tokens
        totalTokens +=
          (message.usage.cache_creation_input_tokens || 0) +
          (message.usage.cache_read_input_tokens || 0) +
          (message.usage.input_tokens || 0) +
          (message.usage.output_tokens || 0);
      }
    }

    // Fall back to estimation if no usage data is available
    const tokenLimitCheck = checkTokenLimit(conversationHistory);
    const actualTokens = hasUsageData
      ? totalTokens
      : tokenLimitCheck.estimatedTokens;

    // Update state and re-render React component
    this.currentTokenCount = actualTokens;
    this.renderTokenProgress();

    // Calculate percentage for CTA visibility
    const percentage = Math.min(
      Math.round((actualTokens / MAX_RECOMMENDED_TOKENS) * 100),
      100
    );

    // Update new prompt CTA visibility based on token percentage
    this.chatBoxWidget.updateNewPromptCtaVisibility(percentage);
  }

  /**
   * Create the mode selector using the React ModeSelector component.
   *
   * The ModeSelector component handles:
   * - Displaying the current mode with icon and title
   * - Dropdown menu with all available modes (Agent, Ask, Hands-on)
   * - Mode selection and state management via Zustand
   * - Animations for open/close transitions
   *
   * This method:
   * - Creates a container element for the React component
   * - Mounts the ModeSelector with the onModeChange callback
   * - Syncs mode selection with the parent ChatInputManager
   */
  private createModeSelector(): void {
    // Create container element for the React ModeSelector
    this.modeSelectorContainer = document.createElement('div');
    this.modeSelectorContainer.className = 'sage-ai-mode-selector-container';

    // Handler for mode change - syncs with ChatInputManager and notifies parent
    const handleModeChange = (mode: 'agent' | 'ask' | 'fast') => {
      console.log('[ChatInputManager] ModeSelector: Mode changed to', mode);
      this.modeName = mode;
      this.onModeSelected?.(mode);
    };

    // Mount the React ModeSelector component
    this.modeSelectorMounted = mountComponent(
      this.modeSelectorContainer,
      React.createElement(ModeSelector, {
        onModeChange: handleModeChange,
        disabled: false
      })
    );
  }

  /**
   * Get the input container element
   */
  public getInputContainer(): HTMLElement | undefined {
    return this.inputContainer;
  }

  /**
   * Get the send button container element.
   *
   * The container holds the React SendButton component.
   * This method is provided for backward compatibility and positioning purposes.
   */
  public getSendButton(): HTMLElement | undefined {
    return this.sendButtonContainer;
  }

  /**
   * Get the mode selector container element.
   *
   * The container holds the React ModeSelector component.
   * This method is provided for backward compatibility and positioning purposes.
   */
  public getModeSelector(): HTMLElement | undefined {
    return this.modeSelectorContainer;
  }

  /**
   * Get the context display element
   */
  public getContextDisplay(): HTMLElement | undefined {
    return this.contextDisplay;
  }

  /**
   * Get the content manager
   */
  private getContentManager(): Contents.IManager {
    return this.contentManager;
  }

  /**
   * Get the tool service
   */
  private getToolService(): ToolService {
    return this.toolService;
  }
}
