import { useBufferStore } from "../stores/buffer-store";
import { useEditorDecorationsStore } from "../stores/decorations-store";
import { useHistoryStore } from "../stores/history-store";
import { useEditorSettingsStore } from "../stores/settings-store";
import { useEditorStateStore } from "../stores/state-store";
import { useEditorViewStore } from "../stores/view-store";
import type { Decoration, Position, Range } from "../types/editor";
import { toggleLineComment, getLineCommentTokenForLanguage } from "../utils/comment-toggle";
import { logger } from "../utils/logger";
import { calculateCursorPosition } from "../utils/position";
import type {
  EditorAPI,
  EditorEvent,
  EditorEventPayload,
  EditorSettings,
  EventHandler,
} from "./types";
import { calculateLineHeight } from "../utils/lines";

class EditorAPIImpl implements EditorAPI {
  private eventHandlers: Map<EditorEvent, Set<EventHandler<EditorEvent>>> = new Map();
  private cursorPosition: Position = { line: 0, column: 0, offset: 0 };
  private selection: Range | null = null;
  private textareaRef: HTMLTextAreaElement | null = null;
  private viewportRef: HTMLDivElement | null = null;

  constructor() {
    // Initialize event handler sets
    const events: EditorEvent[] = [
      "contentChange",
      "selectionChange",
      "cursorChange",
      "settingsChange",
      "decorationChange",
      "keydown",
    ];

    events.forEach((event) => {
      this.eventHandlers.set(event, new Set());
    });
  }

  // Content operations
  getContent(): string {
    return useEditorViewStore.getState().actions.getContent();
  }

  setContent(content: string): void {
    const bufferStore = useBufferStore.getState();
    const activeBufferId = bufferStore.activeBufferId;
    if (activeBufferId) {
      bufferStore.actions.updateBufferContent(activeBufferId, content);
    }
    this.emit("contentChange", { content, changes: [] });
  }

  insertText(text: string, position?: Position): void {
    const content = this.getContent();
    const pos = position || this.getCursorPosition();
    const before = content.substring(0, pos.offset);
    const after = content.substring(pos.offset);
    const newContent = before + text + after;

    // Calculate new cursor position first
    const newOffset = pos.offset + text.length;
    const newPos = this.offsetToPosition(newOffset);

    // Update textarea selection BEFORE setting content
    if (this.textareaRef) {
      // Set the value directly on the textarea
      this.textareaRef.value = newContent;
      // Set selection to new position
      this.textareaRef.selectionStart = this.textareaRef.selectionEnd = newOffset;

      // Now trigger the change event so React updates
      const event = new Event("input", { bubbles: true });
      this.textareaRef.dispatchEvent(event);
    } else {
      // Fallback if no textarea ref
      this.setContent(newContent);
      this.setCursorPosition(newPos);
    }
  }

  deleteRange(range: Range): void {
    const content = this.getContent();
    const before = content.substring(0, range.start.offset);
    const after = content.substring(range.end.offset);
    const newContent = before + after;

    // Calculate new cursor position
    const newOffset = range.start.offset;

    // Update textarea directly for better responsiveness
    if (this.textareaRef) {
      this.textareaRef.value = newContent;
      this.textareaRef.selectionStart = this.textareaRef.selectionEnd = newOffset;

      // Trigger change event
      const event = new Event("input", { bubbles: true });
      this.textareaRef.dispatchEvent(event);
    } else {
      this.setContent(newContent);
      this.setCursorPosition(this.offsetToPosition(newOffset));
    }
  }

  replaceRange(range: Range, text: string): void {
    const content = this.getContent();
    const before = content.substring(0, range.start.offset);
    const after = content.substring(range.end.offset);
    this.setContent(before + text + after);
  }

  // Selection operations
  getSelection(): Range | null {
    return this.selection;
  }

  setSelection(range: Range): void {
    this.selection = range;
    this.emit("selectionChange", range);
  }

  getCursorPosition(): Position {
    return this.cursorPosition;
  }

  setCursorPosition(position: Position): void {
    this.cursorPosition = position;
    this.emit("cursorChange", position);

    // Update cursor store to trigger UI updates
    useEditorStateStore.getState().actions.setCursorPosition(position);

    // Sync with textarea if available
    if (this.textareaRef) {
      this.textareaRef.selectionStart = this.textareaRef.selectionEnd = position.offset;
    }

    // Direct viewport scrolling for immediate response
    if (this.viewportRef) {
      const { fontSize, lineHeight: editorLineHeight } = this.getSettings();
      const lineHeight = calculateLineHeight(fontSize, editorLineHeight);
      const targetLineTop = position.line * lineHeight;
      const targetLineBottom = targetLineTop + lineHeight;
      const currentScrollTop = this.viewportRef.scrollTop;
      const viewportHeight = this.viewportRef.clientHeight;

      // Scroll if cursor is out of view
      if (targetLineTop < currentScrollTop) {
        this.viewportRef.scrollTop = targetLineTop;
      } else if (targetLineBottom > currentScrollTop + viewportHeight) {
        this.viewportRef.scrollTop = targetLineBottom - viewportHeight;
      }
    }
  }

  selectAll(): void {
    if (!this.textareaRef) {
      logger.warn("Editor", "Cannot select all: no textarea reference");
      return;
    }
    this.textareaRef.select();
  }

  // Internal method to update cursor and selection from external changes
  updateCursorAndSelection(cursor: Position, selection: Range | null): void {
    const cursorChanged =
      this.cursorPosition.line !== cursor.line ||
      this.cursorPosition.column !== cursor.column ||
      this.cursorPosition.offset !== cursor.offset;

    const selectionChanged =
      (this.selection === null && selection !== null) ||
      (this.selection !== null && selection === null) ||
      (this.selection !== null &&
        selection !== null &&
        (this.selection.start.offset !== selection.start.offset ||
          this.selection.end.offset !== selection.end.offset));

    if (cursorChanged) {
      this.cursorPosition = cursor;
      this.emit("cursorChange", cursor);
    }

    if (selectionChanged) {
      this.selection = selection;
      this.emit("selectionChange", selection);
    }
  }

  // Decoration operations
  addDecoration(decoration: Decoration): string {
    const id = useEditorDecorationsStore.getState().addDecoration(decoration);
    this.emit("decorationChange", { type: "add", decoration, id });
    return id;
  }

  removeDecoration(id: string): void {
    useEditorDecorationsStore.getState().removeDecoration(id);
    this.emit("decorationChange", { type: "remove", id });
  }

  updateDecoration(id: string, decoration: Partial<Decoration>): void {
    useEditorDecorationsStore.getState().updateDecoration(id, decoration);
    this.emit("decorationChange", { type: "update", id, decoration });
  }

  clearDecorations(): void {
    useEditorDecorationsStore.getState().clearDecorations();
    this.emit("decorationChange", { type: "clear" });
  }

  // Line operations
  getLines(): string[] {
    return useEditorViewStore.getState().lines;
  }

  getLine(lineNumber: number): string | undefined {
    return this.getLines()[lineNumber];
  }

  getLineCount(): number {
    return this.getLines().length;
  }

  duplicateLine(): void {
    if (!this.textareaRef) return;

    const content = this.getContent();
    const selection = useEditorStateStore.getState().selection;
    const selectionStart = this.textareaRef.selectionStart;

    if (!selection || selection.start.offset === selection.end.offset) {
      const lineStart = content.lastIndexOf("\n", selectionStart - 1) + 1;
      const lineEnd = content.indexOf("\n", selectionStart);
      const actualLineEnd = lineEnd === -1 ? content.length : lineEnd;
      const lineContent = content.slice(lineStart, actualLineEnd);

      const newContent = `${content.slice(0, actualLineEnd)}\n${lineContent}${content.slice(actualLineEnd)}`;
      const newOffset = selectionStart + lineContent.length + 1;

      this.textareaRef.value = newContent;
      this.textareaRef.selectionStart = this.textareaRef.selectionEnd = newOffset;

      const inputEvent = new Event("input", { bubbles: true });
      this.textareaRef.dispatchEvent(inputEvent);
    }
  }

  deleteLine(): void {
    if (!this.textareaRef) return;

    const content = this.getContent();
    const selection = useEditorStateStore.getState().selection;
    const selectionStart = this.textareaRef.selectionStart;

    if (!selection || selection.start.offset === selection.end.offset) {
      const lineStart = content.lastIndexOf("\n", selectionStart - 1) + 1;
      const lineEnd = content.indexOf("\n", selectionStart);
      const actualLineEnd = lineEnd === -1 ? content.length : lineEnd + 1;

      const newContent = content.slice(0, lineStart) + content.slice(actualLineEnd);

      this.textareaRef.value = newContent;
      this.textareaRef.selectionStart = this.textareaRef.selectionEnd = lineStart;

      const inputEvent = new Event("input", { bubbles: true });
      this.textareaRef.dispatchEvent(inputEvent);
    }
  }

  toggleComment(): void {
    const textarea = this.textareaRef;
    if (!textarea) return;

    const result = toggleLineComment({
      content: textarea.value,
      selectionStart: textarea.selectionStart,
      selectionEnd: textarea.selectionEnd,
      token: this.getActiveLineCommentToken(),
    });

    textarea.value = result.content;
    textarea.selectionStart = result.selectionStart;
    textarea.selectionEnd = result.selectionEnd;

    const inputEvent = new Event("input", { bubbles: true });
    textarea.dispatchEvent(inputEvent);

    this.syncSelectionFromOffsets(result.content, result.selectionStart, result.selectionEnd);
  }

  moveLineUp(): void {
    if (!this.textareaRef) return;

    const lines = this.getLines();
    const selection = useEditorStateStore.getState().selection;
    const currentPosition = useEditorStateStore.getState().cursorPosition;

    if (!selection || selection.start.offset === selection.end.offset) {
      const currentLine = currentPosition.line;
      const targetLine = currentLine - 1;

      if (targetLine < 0) return;

      const currentLineContent = lines[currentLine];
      const targetLineContent = lines[targetLine];

      const newLines = [...lines];
      newLines[currentLine] = targetLineContent;
      newLines[targetLine] = currentLineContent;

      const newContent = newLines.join("\n");
      const newCursorPosition = { ...currentPosition, line: targetLine };

      let newOffset = 0;
      for (let i = 0; i < newCursorPosition.line; i++) {
        newOffset += newLines[i].length + 1;
      }
      newOffset += newCursorPosition.column;

      this.textareaRef.value = newContent;
      this.textareaRef.selectionStart = this.textareaRef.selectionEnd = newOffset;

      const inputEvent = new Event("input", { bubbles: true });
      this.textareaRef.dispatchEvent(inputEvent);
    }
  }

  moveLineDown(): void {
    if (!this.textareaRef) return;

    const lines = this.getLines();
    const selection = useEditorStateStore.getState().selection;
    const currentPosition = useEditorStateStore.getState().cursorPosition;

    if (!selection || selection.start.offset === selection.end.offset) {
      const currentLine = currentPosition.line;
      const targetLine = currentLine + 1;

      if (targetLine >= lines.length) return;

      const currentLineContent = lines[currentLine];
      const targetLineContent = lines[targetLine];

      const newLines = [...lines];
      newLines[currentLine] = targetLineContent;
      newLines[targetLine] = currentLineContent;

      const newContent = newLines.join("\n");
      const newCursorPosition = { ...currentPosition, line: targetLine };

      let newOffset = 0;
      for (let i = 0; i < newCursorPosition.line; i++) {
        newOffset += newLines[i].length + 1;
      }
      newOffset += newCursorPosition.column;

      this.textareaRef.value = newContent;
      this.textareaRef.selectionStart = this.textareaRef.selectionEnd = newOffset;

      const inputEvent = new Event("input", { bubbles: true });
      this.textareaRef.dispatchEvent(inputEvent);
    }
  }

  copyLineUp(): void {
    if (!this.textareaRef) return;

    const content = this.getContent();
    const selection = useEditorStateStore.getState().selection;
    const currentPosition = useEditorStateStore.getState().cursorPosition;
    const selectionStart = this.textareaRef.selectionStart;

    if (!selection || selection.start.offset === selection.end.offset) {
      const currentLine = currentPosition.line;
      const lineStart = content.lastIndexOf("\n", selectionStart - 1) + 1;
      const lineEnd = content.indexOf("\n", selectionStart);
      const actualLineEnd = lineEnd === -1 ? content.length : lineEnd;
      const lineContent = content.slice(lineStart, actualLineEnd);

      const newContent = `${content.slice(0, lineStart)}${lineContent}\n${lineContent}${content.slice(actualLineEnd)}`;
      const newCursorLine = currentLine;

      let newOffset = 0;
      for (let i = 0; i < newCursorLine; i++) {
        const lines = newContent.split("\n");
        newOffset += lines[i].length + 1;
      }
      newOffset += currentPosition.column;

      this.textareaRef.value = newContent;
      this.textareaRef.selectionStart = this.textareaRef.selectionEnd = newOffset;

      const inputEvent = new Event("input", { bubbles: true });
      this.textareaRef.dispatchEvent(inputEvent);
    }
  }

  copyLineDown(): void {
    if (!this.textareaRef) return;

    const content = this.getContent();
    const selection = useEditorStateStore.getState().selection;
    const currentPosition = useEditorStateStore.getState().cursorPosition;
    const selectionStart = this.textareaRef.selectionStart;

    if (!selection || selection.start.offset === selection.end.offset) {
      const currentLine = currentPosition.line;
      const lineStart = content.lastIndexOf("\n", selectionStart - 1) + 1;
      const lineEnd = content.indexOf("\n", selectionStart);
      const actualLineEnd = lineEnd === -1 ? content.length : lineEnd;
      const lineContent = content.slice(lineStart, actualLineEnd);

      const newContent = `${content.slice(0, actualLineEnd)}\n${lineContent}${content.slice(actualLineEnd)}`;
      const newCursorLine = currentLine + 1;

      let newOffset = 0;
      for (let i = 0; i < newCursorLine; i++) {
        const lines = newContent.split("\n");
        newOffset += lines[i].length + 1;
      }
      newOffset += currentPosition.column;

      this.textareaRef.value = newContent;
      this.textareaRef.selectionStart = this.textareaRef.selectionEnd = newOffset;

      const inputEvent = new Event("input", { bubbles: true });
      this.textareaRef.dispatchEvent(inputEvent);
    }
  }

  // History operations
  undo(): void {
    const bufferStore = useBufferStore.getState();
    const activeBufferId = bufferStore.activeBufferId;

    if (!activeBufferId) {
      logger.warn("Editor", "No active buffer for undo");
      return;
    }

    const historyStore = useHistoryStore.getState();
    const entry = historyStore.actions.undo(activeBufferId);

    if (entry) {
      // Restore content
      bufferStore.actions.updateBufferContent(activeBufferId, entry.content, false);

      if (this.textareaRef) {
        this.textareaRef.value = entry.content;
      }

      // Restore cursor position if available
      if (entry.cursorPosition) {
        this.setCursorPosition(entry.cursorPosition);
      } else if (this.textareaRef) {
        this.textareaRef.selectionStart = this.textareaRef.selectionEnd = 0;
      }

      // Restore selection if it existed
      if (entry.selection) {
        this.setSelection(entry.selection);
      }

      // Emit content change event
      this.emitEvent("contentChange", { content: entry.content, changes: [] });
    }
  }

  redo(): void {
    const bufferStore = useBufferStore.getState();
    const activeBufferId = bufferStore.activeBufferId;

    if (!activeBufferId) {
      logger.warn("Editor", "No active buffer for redo");
      return;
    }

    const historyStore = useHistoryStore.getState();
    const entry = historyStore.actions.redo(activeBufferId);

    if (entry) {
      // Restore content
      bufferStore.actions.updateBufferContent(activeBufferId, entry.content, false);

      if (this.textareaRef) {
        this.textareaRef.value = entry.content;
      }

      // Restore cursor position if available
      if (entry.cursorPosition) {
        this.setCursorPosition(entry.cursorPosition);
      } else if (this.textareaRef) {
        this.textareaRef.selectionStart = this.textareaRef.selectionEnd = 0;
      }

      // Restore selection if it existed
      if (entry.selection) {
        this.setSelection(entry.selection);
      }

      // Emit content change event
      this.emitEvent("contentChange", { content: entry.content, changes: [] });
    }
  }

  canUndo(): boolean {
    const activeBufferId = useBufferStore.getState().activeBufferId;
    if (!activeBufferId) return false;

    return useHistoryStore.getState().actions.canUndo(activeBufferId);
  }

  canRedo(): boolean {
    const activeBufferId = useBufferStore.getState().activeBufferId;
    if (!activeBufferId) return false;

    return useHistoryStore.getState().actions.canRedo(activeBufferId);
  }

  // Settings
  getSettings(): EditorSettings {
    const { fontSize, lineHeight, tabSize, lineNumbers, wordWrap } =
      useEditorSettingsStore.getState();
    return {
      fontSize,
      lineHeight,
      tabSize,
      lineNumbers,
      wordWrap,
      theme: "default", // TODO: Implement theme support
    };
  }

  updateSettings(settings: Partial<EditorSettings>): void {
    const store = useEditorSettingsStore.getState();

    if (settings.fontSize !== undefined) {
      store.actions.setFontSize(settings.fontSize);
    }
    if (settings.lineHeight !== undefined) {
      store.actions.setLineHeight(settings.lineHeight);
    }
    if (settings.tabSize !== undefined) {
      store.actions.setTabSize(settings.tabSize);
    }
    if (settings.lineNumbers !== undefined) {
      store.actions.setLineNumbers(settings.lineNumbers);
    }
    if (settings.wordWrap !== undefined) {
      store.actions.setWordWrap(settings.wordWrap);
    }

    this.emit("settingsChange", settings);
  }

  // Events
  on<E extends EditorEvent>(event: E, handler: EventHandler<E>): () => void {
    const handlers = this.eventHandlers.get(event);
    if (handlers) {
      handlers.add(handler as EventHandler<EditorEvent>);
    }

    // Return unsubscribe function
    return () => this.off(event, handler);
  }

  off<E extends EditorEvent>(event: E, handler: EventHandler<E>): void {
    const handlers = this.eventHandlers.get(event);
    if (handlers) {
      handlers.delete(handler as EventHandler<EditorEvent>);
    }
  }

  private emit<E extends EditorEvent>(event: E, data: EditorEventPayload[E]): void {
    const handlers = this.eventHandlers.get(event);
    if (handlers) {
      handlers.forEach((handler) => handler(data));
    }
  }

  // Public method to safely emit events (for extensions)
  emitEvent<E extends EditorEvent>(event: E, data: EditorEventPayload[E]): void {
    this.emit(event, data);
  }

  // Set the textarea ref for syncing cursor position
  setTextareaRef(ref: HTMLTextAreaElement | null): void {
    this.textareaRef = ref;
  }

  getTextareaRef(): HTMLTextAreaElement | null {
    return this.textareaRef;
  }

  // Set the viewport ref for direct scroll manipulation
  setViewportRef(ref: HTMLDivElement | null): void {
    this.viewportRef = ref;
  }

  getViewportRef(): HTMLDivElement | null {
    return this.viewportRef;
  }

  private getActiveLineCommentToken(): string {
    const { activeBufferId, buffers } = useBufferStore.getState();
    const activeBuffer = buffers.find((buffer) => buffer.id === activeBufferId);
    const languageId =
      activeBuffer && "language" in activeBuffer && typeof activeBuffer.language === "string"
        ? activeBuffer.language
        : null;

    return getLineCommentTokenForLanguage(languageId);
  }

  private syncSelectionFromOffsets(content: string, selectionStart: number, selectionEnd: number) {
    const lines = content.split("\n");
    const cursor = calculateCursorPosition(selectionStart, lines);
    const selection =
      selectionStart === selectionEnd
        ? undefined
        : {
            start: calculateCursorPosition(selectionStart, lines),
            end: calculateCursorPosition(selectionEnd, lines),
          };

    this.cursorPosition = cursor;
    this.selection = selection ?? null;
    useEditorStateStore.getState().actions.setCursorPosition(cursor);
    useEditorStateStore.getState().actions.setSelection(selection);
    this.emit("cursorChange", cursor);
    this.emit("selectionChange", selection ?? null);
  }

  private offsetToPosition(offset: number): Position {
    const content = this.getContent();
    const lines = content.split("\n");
    let currentOffset = 0;

    for (let i = 0; i < lines.length; i++) {
      const lineLength = lines[i].length + (i < lines.length - 1 ? 1 : 0);
      if (currentOffset + lineLength >= offset) {
        return {
          line: i,
          column: offset - currentOffset,
          offset,
        };
      }
      currentOffset += lineLength;
    }

    return {
      line: lines.length - 1,
      column: lines[lines.length - 1].length,
      offset: content.length,
    };
  }
}

// Global editor API instance
export const editorAPI = new EditorAPIImpl();
