import {
  getCurrentProject,
  ProjectConfig,
  subscribeToProjectChange,
  useChainType,
  useModelKey,
  useProjectLoading,
} from "@/aiParams";
import { ChainType } from "@/chainFactory";
import { AddImageModal } from "@/components/modals/AddImageModal";
import { Button } from "@/components/ui/button";
import { ModelSelector } from "@/components/ui/ModelSelector";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { ChatToolControls } from "./ChatToolControls";
import { isPlusChain } from "@/utils";
import {
  mergeWebTabContexts,
  normalizeUrlString,
  normalizeWebTabContext,
} from "@/utils/urlNormalization";

import { useSettingsValue } from "@/settings/model";
import { SelectedTextContext, WebTabContext } from "@/types/message";
import { isAllowedFileForNoteContext } from "@/utils";
import { CornerDownLeft, Image, Loader2, StopCircle, X } from "lucide-react";
import { App, Notice, TFile } from "obsidian";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { $getSelection, $isRangeSelection } from "lexical";
import { ContextControl } from "./ContextControl";
import { $removePillsByPath } from "./pills/NotePillNode";
import { $removeActiveNotePills } from "./pills/ActiveNotePillNode";
import { $removePillsByURL } from "./pills/URLPillNode";
import { $removePillsByFolder } from "./pills/FolderPillNode";
import { $removePillsByToolName, $createToolPillNode } from "./pills/ToolPillNode";
import { $removeActiveWebTabPills } from "./pills/ActiveWebTabPillNode";
import { $findWebTabPills, $removeWebTabPillsByUrl } from "./pills/WebTabPillNode";
import LexicalEditor from "./LexicalEditor";

interface ChatInputProps {
  inputMessage: string;
  setInputMessage: (message: string) => void;
  handleSendMessage: (metadata?: {
    toolCalls?: string[];
    urls?: string[];
    contextNotes?: TFile[];
    contextFolders?: string[];
    webTabs?: WebTabContext[];
  }) => void;
  isGenerating: boolean;
  onStopGenerating: () => void;
  app: App;
  contextNotes: TFile[];
  setContextNotes: React.Dispatch<React.SetStateAction<TFile[]>>;
  includeActiveNote: boolean;
  setIncludeActiveNote: (include: boolean) => void;
  includeActiveWebTab: boolean;
  setIncludeActiveWebTab: (include: boolean) => void;
  activeWebTab: WebTabContext | null;
  selectedImages: File[];
  onAddImage: (files: File[]) => void;
  setSelectedImages: React.Dispatch<React.SetStateAction<File[]>>;
  disableModelSwitch?: boolean;
  selectedTextContexts?: SelectedTextContext[];
  onRemoveSelectedText?: (id: string) => void;
  showProgressCard: () => void;
  showIndexingCard?: () => void;
  focusMessages?: () => void;
  /** Whether Vim navigation is enabled (passed from parent to avoid redundant settings reads) */
  vimNavigationEnabled?: boolean;

  // Edit mode props
  editMode?: boolean;
  onEditSave?: (
    text: string,
    context: {
      notes: TFile[];
      urls: string[];
      folders: string[];
    }
  ) => void;
  onEditCancel?: () => void;
  initialContext?: {
    notes?: TFile[];
    urls?: string[];
    folders?: string[];
  };
}

const ChatInput: React.FC<ChatInputProps> = ({
  inputMessage,
  setInputMessage,
  handleSendMessage,
  isGenerating,
  onStopGenerating,
  app,
  contextNotes,
  setContextNotes,
  includeActiveNote,
  setIncludeActiveNote,
  includeActiveWebTab,
  setIncludeActiveWebTab,
  activeWebTab,
  selectedImages,
  onAddImage,
  setSelectedImages,
  disableModelSwitch,
  selectedTextContexts,
  onRemoveSelectedText,
  showProgressCard,
  showIndexingCard,
  focusMessages,
  vimNavigationEnabled = false,
  editMode = false,
  onEditSave,
  onEditCancel,
  initialContext,
}) => {
  const [contextUrls, setContextUrls] = useState<string[]>(initialContext?.urls || []);
  const [contextFolders, setContextFolders] = useState<string[]>(initialContext?.folders || []);
  const [contextWebTabs, setContextWebTabs] = useState<WebTabContext[]>([]);
  const containerRef = useRef<HTMLDivElement>(null);
  const lexicalEditorRef = useRef<any>(null);
  const [currentModelKey, setCurrentModelKey] = useModelKey();
  const [currentChain] = useChainType();
  const [isProjectLoading] = useProjectLoading();
  const settings = useSettingsValue();
  const [currentActiveNote, setCurrentActiveNote] = useState<TFile | null>(() => {
    const activeFile = app.workspace.getActiveFile();
    return isAllowedFileForNoteContext(activeFile) ? activeFile : null;
  });
  const [selectedProject, setSelectedProject] = useState<ProjectConfig | null>(null);
  const [notesFromPills, setNotesFromPills] = useState<{ path: string; basename: string }[]>([]);
  const [urlsFromPills, setUrlsFromPills] = useState<string[]>([]);
  const [foldersFromPills, setFoldersFromPills] = useState<string[]>([]);
  const [toolsFromPills, setToolsFromPills] = useState<string[]>([]);
  const [webTabsFromPills, setWebTabsFromPills] = useState<WebTabContext[]>([]);
  const isCopilotPlus = isPlusChain(currentChain);

  // Merge badge-only contextWebTabs with pills-derived webTabsFromPills for display
  // Uses shared normalization policy from urlNormalization.ts
  const mergedContextWebTabs = useMemo(() => {
    return mergeWebTabContexts([...contextWebTabs, ...webTabsFromPills]);
  }, [contextWebTabs, webTabsFromPills]);

  /**
   * Extract WebTabPillNode data directly from the Lexical editor at send time.
   * This avoids React state synchronization races (webTabsFromPills) when the user sends quickly.
   */
  const getWebTabsFromEditorSnapshot = (): WebTabContext[] => {
    const editor = lexicalEditorRef.current;
    if (!editor) {
      return webTabsFromPills;
    }

    return editor.read(() => {
      const pills = $findWebTabPills();
      return pills.map((pill) => ({
        url: pill.getURL(),
        title: pill.getTitle(),
        faviconUrl: pill.getFaviconUrl(),
      }));
    });
  };

  // Toggle states for vault, web search, composer, and autonomous agent
  const [vaultToggle, setVaultToggle] = useState(false);
  const [webToggle, setWebToggle] = useState(false);
  const [composerToggle, setComposerToggle] = useState(false);
  const [autonomousAgentToggle, setAutonomousAgentToggle] = useState(
    settings.enableAutonomousAgent
  );
  const [loadingMessageIndex, setLoadingMessageIndex] = useState(0);
  const loadingMessages = [
    "Loading the project context...",
    "Processing context files...",
    "If you have many files in context, this can take a while...",
  ];

  // Sync autonomous agent toggle with settings and chain type
  useEffect(() => {
    if (currentChain === ChainType.PROJECT_CHAIN) {
      // Force off in Projects mode
      setAutonomousAgentToggle(false);
    } else {
      // In other modes, use the actual settings value
      setAutonomousAgentToggle(settings.enableAutonomousAgent);
    }
  }, [settings.enableAutonomousAgent, currentChain]);

  useEffect(() => {
    if (currentChain === ChainType.PROJECT_CHAIN) {
      setSelectedProject(getCurrentProject());

      const unsubscribe = subscribeToProjectChange((project) => {
        setSelectedProject(project);
      });

      return () => {
        unsubscribe();
      };
    } else {
      setSelectedProject(null);
    }
  }, [currentChain]);

  useEffect(() => {
    if (!isProjectLoading) return;

    const interval = setInterval(() => {
      setLoadingMessageIndex((prev) => (prev + 1) % loadingMessages.length);
    }, 3000);

    return () => clearInterval(interval);
  }, [isProjectLoading, loadingMessages.length]);

  const getDisplayModelKey = (): string => {
    if (
      selectedProject &&
      currentChain === ChainType.PROJECT_CHAIN &&
      selectedProject.projectModelKey
    ) {
      return selectedProject.projectModelKey;
    }
    return currentModelKey;
  };

  const onSendMessage = () => {
    // Handle edit mode
    if (editMode && onEditSave) {
      onEditSave(inputMessage, {
        notes: contextNotes,
        urls: contextUrls,
        folders: contextFolders,
      });
      return;
    }

    // Combine badge-only web tabs with the send-time Lexical snapshot of WebTab pills.
    // This avoids React state synchronization races when the user sends quickly.
    // Active Web Tab is handled by ChatManager.
    const webTabsFromEditor = getWebTabsFromEditorSnapshot();
    const allWebTabs = mergeWebTabContexts([...contextWebTabs, ...webTabsFromEditor]);

    if (!isCopilotPlus) {
      // Non-Plus chains: only webTabs needs explicit passing
      // - contextNotes: Chat.tsx has state, closure can access
      // - contextFolders: {folderPath} in text gets expanded by processPrompt()
      // - webTabs: passed here, Active Web Tab injected by ChatManager
      handleSendMessage({
        webTabs: allWebTabs,
      });
      return;
    }

    // Build tool calls based on toggle states
    const toolCalls: string[] = [];
    // Only add tool calls when autonomous agent is off
    // When autonomous agent is on, it handles all tools internally
    if (!autonomousAgentToggle) {
      const messageLower = inputMessage.toLowerCase();

      // Only add tools from buttons if they're not already in the message
      if (vaultToggle && !messageLower.includes("@vault")) {
        toolCalls.push("@vault");
      }
      if (webToggle && !messageLower.includes("@websearch") && !messageLower.includes("@web")) {
        toolCalls.push("@websearch");
      }
      if (composerToggle && !messageLower.includes("@composer")) {
        toolCalls.push("@composer");
      }
    }

    handleSendMessage({
      toolCalls,
      contextNotes,
      urls: contextUrls,
      contextFolders,
      webTabs: allWebTabs,
    });
  };

  // Handle when pills are removed from the editor
  const handleNotePillsRemoved = (removedNotes: { path: string; basename: string }[]) => {
    const removedPaths = new Set(removedNotes.map((note) => note.path));

    setContextNotes((prev) => {
      return prev.filter((contextNote) => {
        // Remove any note whose pill was removed
        return !removedPaths.has(contextNote.path);
      });
    });
  };

  // Handle when URLs are removed from pills (when pills are deleted in editor)
  const handleURLPillsRemoved = (removedUrls: string[]) => {
    const removedUrlSet = new Set(removedUrls);

    setContextUrls((prev) => {
      return prev.filter((url) => {
        if (removedUrlSet.has(url)) {
          return false;
        }
        return true;
      });
    });
  };

  // Handle when tools are removed from pills (when pills are deleted in editor)
  const handleToolPillsRemoved = (removedTools: string[]) => {
    if (!isCopilotPlus || autonomousAgentToggle) return;

    // Update tool button states based on removed pills
    removedTools.forEach((tool) => {
      switch (tool) {
        case "@vault":
          setVaultToggle(false);
          break;
        case "@websearch":
        case "@web":
          setWebToggle(false);
          break;
        case "@composer":
          setComposerToggle(false);
          break;
      }
    });
  };

  // Sync tool button states with tool pills
  useEffect(() => {
    if (!isCopilotPlus || autonomousAgentToggle) return;

    // Update button states based on current tool pills
    const hasVault = toolsFromPills.includes("@vault");
    const hasWeb = toolsFromPills.includes("@websearch") || toolsFromPills.includes("@web");
    const hasComposer = toolsFromPills.includes("@composer");

    setVaultToggle(hasVault);
    setWebToggle(hasWeb);
    setComposerToggle(hasComposer);
  }, [toolsFromPills, isCopilotPlus, autonomousAgentToggle]);

  // Handle when context notes are removed from the context menu
  // This should remove all corresponding pills from the editor
  const handleContextNoteRemoved = (notePath: string) => {
    if (lexicalEditorRef.current) {
      lexicalEditorRef.current.update(() => {
        $removePillsByPath(notePath);
      });
    }

    // Also immediately update notesFromPills to prevent stale data from re-adding the note
    setNotesFromPills((prev) => prev.filter((note) => note.path !== notePath));
  };

  // Handle when context URLs are removed from the context menu
  // This should remove all corresponding URL pills from the editor
  const handleURLContextRemoved = (url: string) => {
    if (lexicalEditorRef.current) {
      lexicalEditorRef.current.update(() => {
        $removePillsByURL(url);
      });
    }

    // Also immediately update urlsFromPills to prevent stale data from re-adding the URL
    setUrlsFromPills((prev) => prev.filter((pillUrl) => pillUrl !== url));
  };

  // Handle when context folders are removed from the context menu
  // This should remove all corresponding folder pills from the editor
  const handleFolderContextRemoved = (folderPath: string) => {
    if (lexicalEditorRef.current) {
      lexicalEditorRef.current.update(() => {
        $removePillsByFolder(folderPath);
      });
    }

    // Also immediately update foldersFromPills to prevent stale data from re-adding the folder
    setFoldersFromPills((prev) => prev.filter((pillFolder) => pillFolder !== folderPath));
  };

  // Unified handler for adding to context (from popover @ mention)
  const handleAddToContext = (category: string, data: any) => {
    switch (category) {
      case "activeNote":
        // Set active note context flag (no pill needed - context badge shows it)
        setIncludeActiveNote(true);
        break;
      case "notes":
        if (data instanceof TFile) {
          const activeNote = app.workspace.getActiveFile();
          if (activeNote && data.path === activeNote.path) {
            setIncludeActiveNote(true);
            setContextNotes((prev) => prev.filter((n) => n.path !== data.path));
          } else {
            setContextNotes((prev) => {
              const existingNote = prev.find((n) => n.path === data.path);
              if (existingNote) {
                return prev; // Note already exists, no change needed
              } else {
                return [...prev, data];
              }
            });
          }
        }
        break;
      case "tools":
        // Add tool pill to lexical editor when selected from @ mention typeahead
        if (typeof data === "string" && lexicalEditorRef.current) {
          lexicalEditorRef.current.update(() => {
            // Insert tool pill at current cursor position
            const selection = $getSelection();
            if ($isRangeSelection(selection)) {
              const toolPill = $createToolPillNode(data);
              selection.insertNodes([toolPill]);
            }
          });
          // Note: toolsFromPills will be updated automatically via ToolPillSyncPlugin
        }
        break;
      case "folders":
        // For folders from context menu, update contextFolders directly (no pills in editor)
        if (data && data.path) {
          const folderPath = data.path;
          setContextFolders((prev) => {
            const exists = prev.find((f) => f === folderPath);
            if (!exists) {
              return [...prev, folderPath];
            }
            return prev;
          });
        }
        break;
      case "webTabs":
        // Badge-only behavior (like notes): add to contextWebTabs state, no pill insertion
        if (data && typeof data.url === "string") {
          const normalized = normalizeWebTabContext(data as WebTabContext);
          if (!normalized) break;

          // If selecting the active web tab, toggle the active badge instead
          const activeUrl = normalizeUrlString(activeWebTab?.url);
          if (activeUrl && normalized.url === activeUrl) {
            setIncludeActiveWebTab(true);
            setContextWebTabs((prev) =>
              prev.filter((t) => normalizeUrlString(t.url) !== activeUrl)
            );
            break;
          }

          setContextWebTabs((prev) => mergeWebTabContexts([...prev, normalized]));
        }
        break;
      case "activeWebTab":
        // Badge-only behavior (like activeNote): toggle include flag, no pill insertion
        setIncludeActiveWebTab(true);
        // Remove from contextWebTabs if it was added as a regular tab
        {
          const activeUrl = normalizeUrlString(activeWebTab?.url);
          if (activeUrl) {
            setContextWebTabs((prev) =>
              prev.filter((t) => normalizeUrlString(t.url) !== activeUrl)
            );
          }
        }
        break;
    }
  };

  // Unified handler for removing from context (from context menu badges)
  const handleRemoveFromContext = (category: string, data: any) => {
    switch (category) {
      case "activeNote":
        // Remove active note pill from editor and turn off flag
        setIncludeActiveNote(false);
        if (lexicalEditorRef.current) {
          lexicalEditorRef.current.update(() => {
            $removeActiveNotePills();
          });
        }
        break;
      case "notes":
        if (typeof data === "string") {
          // data is the path
          // Check if this is the active note
          if (currentActiveNote?.path === data && includeActiveNote) {
            setIncludeActiveNote(false);
          } else {
            // Remove from contextNotes
            setContextNotes((prev) => prev.filter((note) => note.path !== data));
          }
          // Also remove corresponding pills from editor
          handleContextNoteRemoved(data);
        }
        break;
      case "urls":
        if (typeof data === "string") {
          setContextUrls((prev) => prev.filter((u) => u !== data));
          handleURLContextRemoved(data);
        }
        break;
      case "folders":
        if (typeof data === "string") {
          // data is the path
          setContextFolders((prev) => prev.filter((f) => f !== data));
          handleFolderContextRemoved(data);
        }
        break;
      case "selectedText":
        if (typeof data === "string") {
          // data is the id
          onRemoveSelectedText?.(data);
        }
        break;
      case "activeWebTab":
        // Remove active web tab pill from editor and turn off flag
        setIncludeActiveWebTab(false);
        if (lexicalEditorRef.current) {
          lexicalEditorRef.current.update(() => {
            $removeActiveWebTabPills();
          });
        }
        break;
      case "webTabs":
        // Remove web tab from contextWebTabs state
        if (typeof data === "string") {
          const url = normalizeUrlString(data);
          if (!url) break;

          setContextWebTabs((prev) => prev.filter((t) => normalizeUrlString(t.url) !== url));
          // Also immediately update pills-derived state to avoid UI re-adding during sync lag
          setWebTabsFromPills((prev) => prev.filter((t) => normalizeUrlString(t.url) !== url));
          // Also remove any corresponding pills from editor (if any exist)
          if (lexicalEditorRef.current) {
            lexicalEditorRef.current.update(() => {
              $removeWebTabPillsByUrl(url);
            });
          }
        }
        break;
    }
  };

  // Handle when folders are removed from pills (when pills are deleted in editor)
  const handleFolderPillsRemoved = (removedFolders: string[]) => {
    const removedFolderPaths = new Set(removedFolders);

    setContextFolders((prev) => {
      return prev.filter((folder) => {
        if (removedFolderPaths.has(folder)) {
          return false; // Remove this folder
        }
        return true; // Keep this folder
      });
    });
  };

  // Pill-to-context synchronization (when pills are added)
  useEffect(() => {
    setContextNotes((prev) => {
      const contextPaths = new Set(prev.map((note) => note.path));

      // Find notes that need to be added
      const newNotesFromPills = notesFromPills.filter((pillNote) => {
        // Only add if not already in context
        return !contextPaths.has(pillNote.path);
      });

      // Add completely new notes from pills
      const newFiles: TFile[] = [];
      newNotesFromPills.forEach((pillNote) => {
        const file = app.vault.getAbstractFileByPath(pillNote.path);
        if (file instanceof TFile) {
          newFiles.push(file);
        }
      });

      return [...prev, ...newFiles];
    });
  }, [notesFromPills, app.vault, setContextNotes]);

  // URL pill-to-context synchronization (when URL pills are added) - only for Plus chains
  useEffect(() => {
    if (isPlusChain(currentChain)) {
      setContextUrls((prev) => {
        const contextUrlSet = new Set(prev);

        // Find URLs that need to be added
        const newUrlsFromPills = urlsFromPills.filter((pillUrl) => {
          // Only add if not already in context
          return !contextUrlSet.has(pillUrl);
        });

        // Add completely new URLs from pills
        if (newUrlsFromPills.length > 0) {
          return Array.from(new Set([...prev, ...newUrlsFromPills]));
        }

        return prev;
      });
    } else {
      // Clear URLs for non-Plus chains
      setContextUrls([]);
    }
  }, [urlsFromPills, currentChain]);

  // Folder-to-context synchronization (when folders are added via pills)
  useEffect(() => {
    setContextFolders((prev) => {
      const contextFolderPaths = new Set(prev);

      // Find folders that need to be added
      const newFoldersFromPills = foldersFromPills.filter((pillFolder) => {
        // Only add if not already in context
        return !contextFolderPaths.has(pillFolder);
      });

      // Add completely new folders from pills
      return [...prev, ...newFoldersFromPills];
    });
  }, [foldersFromPills]);

  // Update the current active note whenever it changes
  useEffect(() => {
    let timeoutId: ReturnType<typeof setTimeout>;

    const handleActiveLeafChange = () => {
      // Clear any existing timeout
      clearTimeout(timeoutId);

      // Set new timeout
      timeoutId = setTimeout(() => {
        const activeNote = app.workspace.getActiveFile();
        setCurrentActiveNote(isAllowedFileForNoteContext(activeNote) ? activeNote : null);
      }, 100); // Wait 100ms after the last event because it fires multiple times
    };

    const eventRef = app.workspace.on("active-leaf-change", handleActiveLeafChange);

    return () => {
      clearTimeout(timeoutId); // Clean up any pending timeout
      // cspell:disable-next-line
      app.workspace.offref(eventRef); // Remove event listener
    };
  }, [app.workspace]);

  const onEditorReady = useCallback((editor: any) => {
    lexicalEditorRef.current = editor;
  }, []);

  // Handle Escape key for edit mode
  useEffect(() => {
    if (!editMode || !onEditCancel) return;

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        event.preventDefault();
        onEditCancel();
      }
    };

    document.addEventListener("keydown", handleKeyDown);
    return () => document.removeEventListener("keydown", handleKeyDown);
  }, [editMode, onEditCancel]);

  // Handle tool button toggle-off events - remove corresponding pills
  const handleVaultToggleOff = useCallback(() => {
    if (lexicalEditorRef.current && isCopilotPlus) {
      lexicalEditorRef.current.update(() => {
        $removePillsByToolName("@vault");
      });
    }
  }, [isCopilotPlus]);

  const handleWebToggleOff = useCallback(() => {
    if (lexicalEditorRef.current && isCopilotPlus) {
      lexicalEditorRef.current.update(() => {
        $removePillsByToolName("@websearch");
        $removePillsByToolName("@web");
      });
    }
  }, [isCopilotPlus]);

  const handleComposerToggleOff = useCallback(() => {
    if (lexicalEditorRef.current && isCopilotPlus) {
      lexicalEditorRef.current.update(() => {
        $removePillsByToolName("@composer");
      });
    }
  }, [isCopilotPlus]);

  // Active note pill sync callbacks
  const handleActiveNoteAdded = useCallback(() => {
    setIncludeActiveNote(true);
  }, [setIncludeActiveNote]);

  const handleActiveNoteRemoved = useCallback(() => {
    setIncludeActiveNote(false);
  }, [setIncludeActiveNote]);

  // Active web tab pill sync callbacks (mirror activeNote behavior)
  // Active Web Tab URL resolution is now handled by ChatManager at send time
  const handleActiveWebTabAdded = useCallback(() => {
    setIncludeActiveWebTab(true);
  }, [setIncludeActiveWebTab]);

  const handleActiveWebTabRemoved = useCallback(() => {
    setIncludeActiveWebTab(false);
  }, [setIncludeActiveWebTab]);

  // Handle tag selection from typeahead - auto-enable vault search
  const handleTagSelected = useCallback(() => {
    if (isCopilotPlus && !autonomousAgentToggle && !vaultToggle) {
      setVaultToggle(true);
      new Notice("Vault search enabled for tag query");
    }
  }, [isCopilotPlus, autonomousAgentToggle, vaultToggle]);

  return (
    <div
      className="tw-flex tw-w-full tw-flex-col tw-gap-0.5 tw-rounded-md tw-border tw-border-solid tw-border-border tw-px-1 tw-pb-1 tw-pt-2 tw-@container/chat-input"
      ref={containerRef}
    >
      {/* Hide context controls in edit mode - editing only changes text, not context */}
      {!editMode && (
        <ContextControl
          contextNotes={contextNotes}
          includeActiveNote={includeActiveNote}
          activeNote={currentActiveNote}
          includeActiveWebTab={includeActiveWebTab}
          activeWebTab={activeWebTab}
          contextUrls={contextUrls}
          contextFolders={contextFolders}
          contextWebTabs={mergedContextWebTabs}
          selectedTextContexts={selectedTextContexts}
          showProgressCard={showProgressCard}
          showIndexingCard={showIndexingCard}
          lexicalEditorRef={lexicalEditorRef}
          onAddToContext={handleAddToContext}
          onRemoveFromContext={handleRemoveFromContext}
        />
      )}

      {selectedImages.length > 0 && (
        <div className="selected-images">
          {selectedImages.map((file, index) => (
            <div key={index} className="image-preview-container">
              <img
                src={URL.createObjectURL(file)}
                alt={file.name}
                className="selected-image-preview"
              />
              <button
                className="remove-image-button"
                onClick={() => setSelectedImages((prev) => prev.filter((_, i) => i !== index))}
                title="Remove image"
              >
                <X className="tw-size-4" />
              </button>
            </div>
          ))}
        </div>
      )}

      <div className="tw-relative">
        {isProjectLoading && (
          <div className="tw-absolute tw-inset-0 tw-z-modal tw-flex tw-items-center tw-justify-center tw-bg-primary tw-opacity-80 tw-backdrop-blur-sm">
            <div className="tw-flex tw-items-center tw-gap-2">
              <Loader2 className="tw-size-4 tw-animate-spin" />
              <span className="tw-text-sm">{loadingMessages[loadingMessageIndex]}</span>
            </div>
          </div>
        )}
        <LexicalEditor
          value={inputMessage}
          onChange={(value) => setInputMessage(value)}
          onSubmit={onSendMessage}
          onNotesChange={setNotesFromPills}
          onNotesRemoved={handleNotePillsRemoved}
          onActiveNoteAdded={handleActiveNoteAdded}
          onActiveNoteRemoved={handleActiveNoteRemoved}
          onURLsChange={isCopilotPlus ? setUrlsFromPills : undefined}
          onURLsRemoved={isCopilotPlus ? handleURLPillsRemoved : undefined}
          onToolsChange={isCopilotPlus ? setToolsFromPills : undefined}
          onToolsRemoved={isCopilotPlus ? handleToolPillsRemoved : undefined}
          onFoldersChange={setFoldersFromPills}
          onFoldersRemoved={handleFolderPillsRemoved}
          onWebTabsChange={setWebTabsFromPills}
          onActiveWebTabAdded={handleActiveWebTabAdded}
          onActiveWebTabRemoved={handleActiveWebTabRemoved}
          onEditorReady={onEditorReady}
          onImagePaste={onAddImage}
          onTagSelected={handleTagSelected}
          placeholder={"Your AI assistant for Obsidian • @ to add context • / for custom prompts"}
          disabled={isProjectLoading}
          isCopilotPlus={isCopilotPlus}
          currentActiveFile={currentActiveNote}
          currentChain={currentChain}
          vimNavigationEnabled={vimNavigationEnabled}
          focusMessages={focusMessages}
        />
      </div>

      <div className="tw-flex tw-h-6 tw-justify-between tw-gap-1 tw-px-1">
        {isGenerating ? (
          <div className="tw-flex tw-items-center tw-gap-1 tw-px-1 tw-text-sm tw-text-muted">
            <Loader2 className="tw-size-3 tw-animate-spin" />
            <span>Generating...</span>
          </div>
        ) : (
          <div className="tw-min-w-0 tw-flex-1">
            <ModelSelector
              variant="ghost2"
              size="fit"
              disabled={disableModelSwitch}
              value={getDisplayModelKey()}
              onChange={(modelKey) => {
                // In project mode, we don't update the global model key
                // as the project model takes precedence
                if (currentChain !== ChainType.PROJECT_CHAIN) {
                  setCurrentModelKey(modelKey);
                }
              }}
              className="tw-max-w-full tw-truncate"
            />
          </div>
        )}

        <div className="tw-flex tw-items-center tw-gap-1">
          {isGenerating ? (
            <Button
              variant="ghost2"
              size="fit"
              className="tw-text-muted"
              onClick={() => onStopGenerating()}
            >
              <StopCircle className="tw-size-4" />
              Stop
            </Button>
          ) : (
            <>
              <ChatToolControls
                vaultToggle={vaultToggle}
                setVaultToggle={setVaultToggle}
                webToggle={webToggle}
                setWebToggle={setWebToggle}
                composerToggle={composerToggle}
                setComposerToggle={setComposerToggle}
                autonomousAgentToggle={autonomousAgentToggle}
                setAutonomousAgentToggle={setAutonomousAgentToggle}
                currentChain={currentChain}
                onVaultToggleOff={handleVaultToggleOff}
                onWebToggleOff={handleWebToggleOff}
                onComposerToggleOff={handleComposerToggleOff}
              />
              <TooltipProvider delayDuration={0}>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      variant="ghost2"
                      size="fit"
                      className="tw-text-muted hover:tw-text-accent"
                      onClick={() => {
                        new AddImageModal(app, onAddImage).open();
                      }}
                    >
                      <Image className="tw-size-4" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent className="tw-px-1 tw-py-0.5">Add image(s)</TooltipContent>
                </Tooltip>
              </TooltipProvider>
              {editMode && onEditCancel && (
                <Button
                  variant="ghost2"
                  size="fit"
                  className="tw-text-muted"
                  onClick={onEditCancel}
                >
                  <span>cancel</span>
                </Button>
              )}
              <Button
                variant="ghost2"
                size="fit"
                className="tw-text-muted"
                onClick={() => onSendMessage()}
              >
                <CornerDownLeft className="!tw-size-3" />
                <span>{editMode ? "save" : "chat"}</span>
              </Button>
            </>
          )}
        </div>
      </div>
    </div>
  );
};

ChatInput.displayName = "ChatInput";

export default ChatInput;
