import { useBufferStore } from "@/features/editor/stores/buffer-store";
import { useHistoryStore } from "@/features/editor/stores/history-store";
import { useEditorViewStore } from "@/features/editor/stores/view-store";
import { calculateOffsetFromPosition } from "@/features/editor/utils/position";
import { useEditorStateStore } from "@/features/editor/stores/state-store";
import { useVimStore } from "@/features/vim/stores/vim-store";

export interface VimEditingCommands {
  deleteLine: () => void;
  yankLine: () => void;
  paste: () => void;
  pasteAbove: () => void;
  undo: () => void;
  redo: () => void;
  deleteChar: () => void;
  deleteCharBefore: () => void;
  replaceChar: (char: string) => void;
  substituteChar: () => void;
  openLineBelow: () => void;
  openLineAbove: () => void;
  appendToLine: () => void;
  insertAtLineStart: () => void;
  deleteVisualSelection: (startOffset: number, endOffset: number) => void;
  yankVisualSelection: (startOffset: number, endOffset: number) => void;
}

export const createVimEditing = (): VimEditingCommands => {
  const getCursorPosition = () => useEditorStateStore.getState().cursorPosition;
  const setCursorPosition = (position: any) =>
    useEditorStateStore.getState().actions.setCursorPosition(position);
  const getLines = () => useEditorViewStore.getState().lines;
  const getContent = () => useEditorViewStore.getState().actions.getContent();

  // Update buffer content
  const updateContent = (newContent: string) => {
    const { actions, activeBufferId } = useBufferStore.getState();
    if (activeBufferId) {
      actions.updateBufferContent(activeBufferId, newContent);

      // Update textarea value directly without triggering input event
      // Vim mode manages its own history, so we don't want to trigger
      // the app-store's debounced history tracking
      const textarea = document.querySelector(".editor-textarea") as HTMLTextAreaElement;
      if (textarea) {
        textarea.value = newContent;
        // Don't dispatch input event - vim handles its own history
      }
    }
  };

  // Save state for undo
  const saveUndoState = () => {
    const { activeBufferId } = useBufferStore.getState();
    if (!activeBufferId) return;

    const currentContent = getContent();
    const currentPos = getCursorPosition();

    // Push to centralized history store
    useHistoryStore.getState().actions.pushHistory(activeBufferId, {
      content: currentContent,
      cursorPosition: currentPos,
      timestamp: Date.now(),
    });
  };

  // Update textarea cursor position
  const updateTextareaCursor = (newPosition: any, shouldFocus = false) => {
    const textarea = document.querySelector(".editor-textarea") as HTMLTextAreaElement;
    if (textarea) {
      // Focus first if requested - setting selection on blurred textarea may not persist
      if (shouldFocus && document.activeElement !== textarea) {
        textarea.focus();
      }
      textarea.selectionStart = textarea.selectionEnd = newPosition.offset;
      textarea.dispatchEvent(new Event("select"));
    }
  };

  return {
    deleteLine: () => {
      saveUndoState();

      const currentPos = getCursorPosition();
      const lines = getLines();

      if (lines.length <= 1) {
        useVimStore.getState().actions.writeToRegister(`${lines[0]}\n`, true, true);
        updateContent("");
        setCursorPosition({ line: 0, column: 0, offset: 0 });
        updateTextareaCursor({ line: 0, column: 0, offset: 0 });
        return;
      }

      useVimStore.getState().actions.writeToRegister(`${lines[currentPos.line]}\n`, true, true);

      // Remove the line
      const newLines = lines.filter((_, index) => index !== currentPos.line);
      const newContent = newLines.join("\n");

      // Adjust cursor position
      const newLine = Math.min(currentPos.line, newLines.length - 1);
      const newColumn = Math.min(currentPos.column, newLines[newLine].length);
      const newOffset = calculateOffsetFromPosition(newLine, newColumn, newLines);

      updateContent(newContent);
      const newPosition = { line: newLine, column: newColumn, offset: newOffset };
      setCursorPosition(newPosition);
      updateTextareaCursor(newPosition);
    },

    yankLine: () => {
      const currentPos = getCursorPosition();
      const lines = getLines();

      useVimStore.getState().actions.writeToRegister(`${lines[currentPos.line]}\n`, true, false);
    },

    paste: () => {
      const reg = useVimStore.getState().actions.readFromRegister();
      if (!reg?.content) return;

      saveUndoState();

      const currentPos = getCursorPosition();
      const lines = getLines();

      if (reg.linewise) {
        const newLines = [...lines];
        newLines.splice(currentPos.line + 1, 0, reg.content.replace(/\n$/, ""));
        const newContent = newLines.join("\n");

        const newLine = currentPos.line + 1;
        const newOffset = calculateOffsetFromPosition(newLine, 0, newLines);

        updateContent(newContent);
        const newPosition = { line: newLine, column: 0, offset: newOffset };
        setCursorPosition(newPosition);
        updateTextareaCursor(newPosition);
      } else {
        const currentContent = getContent();
        const newContent =
          currentContent.slice(0, currentPos.offset) +
          reg.content +
          currentContent.slice(currentPos.offset);

        updateContent(newContent);

        const newOffset = currentPos.offset + reg.content.length;
        const newLines = newContent.split("\n");
        let line = 0;
        let offset = 0;

        while (offset + newLines[line].length + 1 <= newOffset && line < newLines.length - 1) {
          offset += newLines[line].length + 1;
          line++;
        }

        const column = newOffset - offset;
        const newPosition = { line, column, offset: newOffset };
        setCursorPosition(newPosition);
        updateTextareaCursor(newPosition);
      }
    },

    pasteAbove: () => {
      const reg = useVimStore.getState().actions.readFromRegister();
      if (!reg?.content || !reg.linewise) return;

      saveUndoState();

      const currentPos = getCursorPosition();
      const lines = getLines();

      const newLines = [...lines];
      newLines.splice(currentPos.line, 0, reg.content.replace(/\n$/, ""));
      const newContent = newLines.join("\n");

      const newOffset = calculateOffsetFromPosition(currentPos.line, 0, newLines);

      updateContent(newContent);
      const newPosition = { line: currentPos.line, column: 0, offset: newOffset };
      setCursorPosition(newPosition);
      updateTextareaCursor(newPosition);
    },

    undo: () => {
      const { activeBufferId } = useBufferStore.getState();
      if (!activeBufferId) return;

      const historyStore = useHistoryStore.getState();
      if (!historyStore.actions.canUndo(activeBufferId)) return;

      const currentPos = getCursorPosition();

      // Get previous state from history
      const entry = historyStore.actions.undo(activeBufferId);
      if (!entry) return;

      // Restore content
      updateContent(entry.content);

      // Restore cursor position if available, otherwise maintain current position
      if (entry.cursorPosition) {
        setCursorPosition(entry.cursorPosition);
        updateTextareaCursor(entry.cursorPosition);
      } else {
        // Try to maintain cursor position within new content bounds
        const newLines = entry.content.split("\n");
        const newLine = Math.min(currentPos.line, newLines.length - 1);
        const newColumn = Math.min(currentPos.column, newLines[newLine].length);
        const newOffset = calculateOffsetFromPosition(newLine, newColumn, newLines);

        const newPosition = { line: newLine, column: newColumn, offset: newOffset };
        setCursorPosition(newPosition);
        updateTextareaCursor(newPosition);
      }
    },

    redo: () => {
      const { activeBufferId } = useBufferStore.getState();
      if (!activeBufferId) return;

      const historyStore = useHistoryStore.getState();
      if (!historyStore.actions.canRedo(activeBufferId)) return;

      // Get next state from history
      const entry = historyStore.actions.redo(activeBufferId);
      if (!entry) return;

      // Restore content
      updateContent(entry.content);

      // Restore cursor position if available, otherwise maintain current position
      if (entry.cursorPosition) {
        setCursorPosition(entry.cursorPosition);
        updateTextareaCursor(entry.cursorPosition);
      } else {
        // Try to maintain cursor position within new content bounds
        const currentPos = getCursorPosition();
        const newLines = entry.content.split("\n");
        const newLine = Math.min(currentPos.line, newLines.length - 1);
        const newColumn = Math.min(currentPos.column, newLines[newLine].length);
        const newOffset = calculateOffsetFromPosition(newLine, newColumn, newLines);

        const newPosition = { line: newLine, column: newColumn, offset: newOffset };
        setCursorPosition(newPosition);
        updateTextareaCursor(newPosition);
      }
    },

    deleteChar: () => {
      saveUndoState();

      const currentPos = getCursorPosition();
      const currentContent = getContent();

      if (currentPos.offset < currentContent.length) {
        const deletedChar = currentContent[currentPos.offset];
        useVimStore.getState().actions.writeToRegister(deletedChar, false, true);

        const newContent =
          currentContent.slice(0, currentPos.offset) + currentContent.slice(currentPos.offset + 1);

        updateContent(newContent);
        updateTextareaCursor(currentPos);
      }
    },

    deleteCharBefore: () => {
      saveUndoState();

      const currentPos = getCursorPosition();
      const currentContent = getContent();

      if (currentPos.offset > 0) {
        const deletedChar = currentContent[currentPos.offset - 1];
        useVimStore.getState().actions.writeToRegister(deletedChar, false, true);

        const newContent =
          currentContent.slice(0, currentPos.offset - 1) + currentContent.slice(currentPos.offset);

        updateContent(newContent);

        // Move cursor back one position
        const newPosition = {
          line: currentPos.line,
          column: Math.max(0, currentPos.column - 1),
          offset: currentPos.offset - 1,
        };
        setCursorPosition(newPosition);
        updateTextareaCursor(newPosition);
      }
    },

    replaceChar: (char: string) => {
      saveUndoState();

      const currentPos = getCursorPosition();
      const currentContent = getContent();

      if (currentPos.offset < currentContent.length) {
        // Replace character at cursor with new character
        const newContent =
          currentContent.slice(0, currentPos.offset) +
          char +
          currentContent.slice(currentPos.offset + 1);

        updateContent(newContent);
        // Cursor position stays the same
        updateTextareaCursor(currentPos);
      }
    },

    substituteChar: () => {
      saveUndoState();

      const currentPos = getCursorPosition();
      const currentContent = getContent();

      if (currentPos.offset < currentContent.length) {
        const deletedChar = currentContent[currentPos.offset];
        useVimStore.getState().actions.writeToRegister(deletedChar, false, true);

        // Delete character and enter insert mode
        const newContent =
          currentContent.slice(0, currentPos.offset) + currentContent.slice(currentPos.offset + 1);

        updateContent(newContent);
        updateTextareaCursor(currentPos);
      }
    },

    openLineBelow: () => {
      saveUndoState();

      const currentPos = getCursorPosition();
      const lines = getLines();

      // Add new empty line below current line
      const newLines = [...lines];
      newLines.splice(currentPos.line + 1, 0, "");
      const newContent = newLines.join("\n");

      // Move cursor to beginning of new line
      const newLine = currentPos.line + 1;
      const newOffset = calculateOffsetFromPosition(newLine, 0, newLines);

      updateContent(newContent);
      const newPosition = { line: newLine, column: 0, offset: newOffset };
      setCursorPosition(newPosition);
      updateTextareaCursor(newPosition);
    },

    openLineAbove: () => {
      saveUndoState();

      const currentPos = getCursorPosition();
      const lines = getLines();

      // Add new empty line above current line
      const newLines = [...lines];
      newLines.splice(currentPos.line, 0, "");
      const newContent = newLines.join("\n");

      // Move cursor to beginning of new line (same line number since we inserted above)
      const newOffset = calculateOffsetFromPosition(currentPos.line, 0, newLines);

      updateContent(newContent);
      const newPosition = { line: currentPos.line, column: 0, offset: newOffset };
      setCursorPosition(newPosition);
      updateTextareaCursor(newPosition);
    },

    appendToLine: () => {
      const currentPos = getCursorPosition();
      const lines = getLines();

      // Move cursor to end of current line
      const targetColumn = lines[currentPos.line].length;
      const newOffset = calculateOffsetFromPosition(currentPos.line, targetColumn, lines);
      const newPosition = { line: currentPos.line, column: targetColumn, offset: newOffset };
      setCursorPosition(newPosition);
      // Focus before setting selection - called before entering insert mode
      updateTextareaCursor(newPosition, true);
    },

    insertAtLineStart: () => {
      const currentPos = getCursorPosition();
      const lines = getLines();

      // Move cursor to start of current line (after any indentation)
      const currentLine = lines[currentPos.line];
      let firstNonWhitespace = 0;
      while (
        firstNonWhitespace < currentLine.length &&
        /\s/.test(currentLine[firstNonWhitespace])
      ) {
        firstNonWhitespace++;
      }

      const targetColumn = firstNonWhitespace;
      const newOffset = calculateOffsetFromPosition(currentPos.line, targetColumn, lines);
      const newPosition = { line: currentPos.line, column: targetColumn, offset: newOffset };
      setCursorPosition(newPosition);
      // Focus before setting selection - called before entering insert mode
      updateTextareaCursor(newPosition, true);
    },

    deleteVisualSelection: (startOffset: number, endOffset: number) => {
      saveUndoState();

      const currentContent = getContent();
      const start = Math.min(startOffset, endOffset);
      const end = Math.max(startOffset, endOffset);

      const deletedContent = currentContent.slice(start, end);
      useVimStore.getState().actions.writeToRegister(deletedContent, false, true);

      // Delete the selection
      const newContent = currentContent.slice(0, start) + currentContent.slice(end);
      updateContent(newContent);

      // Move cursor to start of deleted selection
      const newLines = newContent.split("\n");
      let line = 0;
      let offset = 0;

      while (offset + newLines[line].length + 1 <= start && line < newLines.length - 1) {
        offset += newLines[line].length + 1;
        line++;
      }

      const column = start - offset;
      const newPosition = { line, column, offset: start };
      setCursorPosition(newPosition);
      updateTextareaCursor(newPosition);
    },

    yankVisualSelection: (startOffset: number, endOffset: number) => {
      const currentContent = getContent();
      const start = Math.min(startOffset, endOffset);
      const end = Math.max(startOffset, endOffset);

      const selectedContent = currentContent.slice(start, end);
      useVimStore.getState().actions.writeToRegister(selectedContent, false, false);
    },
  };
};
