import type { RefObject } from "react";
import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
import { EDITOR_CONSTANTS } from "@/features/editor/config/constants";
import { isDragScrolling } from "@/features/editor/hooks/use-drag-scroll";
import type { Cursor, MultiCursorState, Position, Range } from "@/features/editor/types/editor";
import { createSelectors } from "@/utils/zustand-selectors";
import { useBufferStore } from "./buffer-store";

// Types for editor state caching
export interface EditorViewState {
  cursor: Position;
  selection?: Range;
  scrollTop: number;
  scrollLeft: number;
}

// Editor View State Cache Manager - caches cursor position and scroll offset per buffer
class EditorViewStateCacheManager {
  private cache = new Map<string, EditorViewState>();
  private readonly MAX_CACHE_SIZE = EDITOR_CONSTANTS.MAX_POSITION_CACHE_SIZE;

  setCursor(bufferId: string, position: Position): void {
    const cached = this.cache.get(bufferId);
    if (cached && this.positionsEqual(cached.cursor, position)) {
      return;
    }

    this.ensureCacheSize(bufferId);

    const existing = this.cache.get(bufferId);
    this.cache.set(bufferId, {
      cursor: { ...position },
      selection: existing?.selection ? { ...existing.selection } : undefined,
      scrollTop: existing?.scrollTop ?? 0,
      scrollLeft: existing?.scrollLeft ?? 0,
    });
  }

  setSelection(bufferId: string, selection?: Range): void {
    const existing = this.cache.get(bufferId);
    this.ensureCacheSize(bufferId);

    this.cache.set(bufferId, {
      cursor: existing?.cursor ?? { line: 0, column: 0, offset: 0 },
      selection: selection ? { ...selection } : undefined,
      scrollTop: existing?.scrollTop ?? 0,
      scrollLeft: existing?.scrollLeft ?? 0,
    });
  }

  setScroll(bufferId: string, scrollTop: number, scrollLeft: number): void {
    const existing = this.cache.get(bufferId);
    if (existing && existing.scrollTop === scrollTop && existing.scrollLeft === scrollLeft) {
      return;
    }

    this.ensureCacheSize(bufferId);

    this.cache.set(bufferId, {
      cursor: existing?.cursor ?? { line: 0, column: 0, offset: 0 },
      selection: existing?.selection ? { ...existing.selection } : undefined,
      scrollTop,
      scrollLeft,
    });
  }

  set(bufferId: string, state: EditorViewState): void {
    this.ensureCacheSize(bufferId);
    this.cache.set(bufferId, {
      cursor: { ...state.cursor },
      selection: state.selection ? { ...state.selection } : undefined,
      scrollTop: state.scrollTop,
      scrollLeft: state.scrollLeft,
    });
  }

  get(bufferId: string): EditorViewState | null {
    const cached = this.getWithFallback(bufferId);
    if (!cached) return null;
    return {
      cursor: { ...cached.cursor },
      selection: cached.selection ? { ...cached.selection } : undefined,
      scrollTop: cached.scrollTop,
      scrollLeft: cached.scrollLeft,
    };
  }

  getCursor(bufferId: string): Position | null {
    const cached = this.cache.get(bufferId);
    if (!cached) return null;
    return { ...cached.cursor };
  }

  clear(bufferId?: string): void {
    if (bufferId) {
      this.cache.delete(bufferId);
    } else {
      this.cache.clear();
    }
  }

  private getWithFallback(bufferId: string): EditorViewState | null {
    const exact = this.cache.get(bufferId);
    if (exact) return exact;

    const viewKeySeparatorIndex = bufferId.lastIndexOf(":");
    if (viewKeySeparatorIndex >= 0) {
      const plainBufferId = bufferId.slice(viewKeySeparatorIndex + 1);
      return this.cache.get(plainBufferId) ?? null;
    }

    for (const [cachedKey, cachedState] of this.cache.entries()) {
      if (cachedKey.endsWith(`:${bufferId}`)) {
        return cachedState;
      }
    }

    return null;
  }

  private ensureCacheSize(bufferId: string): void {
    if (this.cache.size >= this.MAX_CACHE_SIZE && !this.cache.has(bufferId)) {
      const firstKey = this.cache.keys().next().value;
      if (firstKey) {
        this.cache.delete(firstKey);
      }
    }
  }

  private positionsEqual(pos1: Position, pos2: Position): boolean {
    return pos1.line === pos2.line && pos1.column === pos2.column && pos1.offset === pos2.offset;
  }
}

const viewStateCache = new EditorViewStateCacheManager();

const ensureCursorVisible = (position: Position) => {
  if (typeof window === "undefined") return;

  // Skip scroll adjustment during drag selection auto-scroll
  if (isDragScrolling()) return;

  const editorElement = useEditorStateStore.getState().editorRef?.current;
  const scopedTextarea =
    editorElement?.querySelector<HTMLTextAreaElement>("textarea.editor-textarea") ?? null;
  const focusedTextarea =
    document.activeElement instanceof HTMLTextAreaElement &&
    document.activeElement.classList.contains("editor-textarea")
      ? document.activeElement
      : null;
  const textarea = scopedTextarea ?? focusedTextarea;

  if (!textarea) return;

  const computedStyle = window.getComputedStyle(textarea);
  const lineHeight =
    Number.parseFloat(computedStyle.lineHeight) || EDITOR_CONSTANTS.DEFAULT_LINE_HEIGHT;
  const paddingTop =
    Number.parseFloat(computedStyle.paddingTop) || EDITOR_CONSTANTS.EDITOR_PADDING_TOP;
  const targetTop = position.line * lineHeight + paddingTop;
  const targetBottom = targetTop + lineHeight;
  const currentScrollTop = textarea.scrollTop;
  const viewportHeight = textarea.clientHeight || 0;

  if (targetTop < currentScrollTop) {
    textarea.scrollTop = targetTop;
  } else if (targetBottom > currentScrollTop + viewportHeight) {
    textarea.scrollTop = Math.max(0, targetBottom - viewportHeight);
  }
};

// State Interface
interface EditorState {
  // Cursor state
  cursorPosition: Position;
  selection?: Range;
  desiredColumn?: number;
  cursorVisible: boolean;

  // Multi-cursor state
  multiCursorState: MultiCursorState | null;

  // Layout state
  scrollTop: number;
  scrollLeft: number;
  viewportHeight: number;

  // Instance state
  value: string;
  onChange: (
    value: string,
    previousValue?: string,
    previousCursorPosition?: Position,
    previousSelection?: Range,
  ) => void;
  filePath: string;
  editorRef: RefObject<HTMLDivElement | null> | null;
  placeholder?: string;
  disabled: boolean;
  activeEditorViewKey: string | null;

  // Actions
  actions: EditorStateActions;
}

interface EditorStateActions {
  // Cursor actions
  setCursorPosition: (position: Position, options?: { ensureVisible?: boolean }) => void;
  setSelection: (selection?: Range) => void;
  setDesiredColumn: (column?: number) => void;
  setCursorVisibility: (visible: boolean) => void;
  getCachedPosition: (bufferId: string) => Position | null;
  getCachedViewState: (bufferId: string) => EditorViewState | null;
  cacheViewStateForBuffer: (bufferId: string, state: EditorViewState) => void;
  clearPositionCache: (bufferId?: string) => void;
  restorePositionForFile: (bufferId: string) => EditorViewState;
  resetOnBufferSwitch: () => void;

  // Multi-cursor actions
  enableMultiCursor: () => void;
  disableMultiCursor: () => void;
  addCursor: (position: Position, selection?: Range) => void;
  removeCursor: (cursorId: string) => void;
  updateCursor: (cursorId: string, position: Position, selection?: Range) => void;
  clearSecondaryCursors: () => void;

  // Layout actions
  setScroll: (scrollTop: number, scrollLeft: number) => void;
  setScrollForBuffer: (bufferId: string | null, scrollTop: number, scrollLeft: number) => void;
  setViewportHeight: (height: number) => void;

  // Instance actions
  setRefs: (refs: { editorRef: RefObject<HTMLDivElement | null> }) => void;
  setContent: (
    value: string,
    onChange: (
      value: string,
      previousValue?: string,
      previousCursorPosition?: Position,
      previousSelection?: Range,
    ) => void,
  ) => void;
  setFileInfo: (filePath: string) => void;
  setPlaceholder: (placeholder?: string) => void;
  setDisabled: (disabled: boolean) => void;
  setActiveEditorViewKey: (viewKey: string | null) => void;
}

export const useEditorStateStore = createSelectors(
  create<EditorState>()(
    subscribeWithSelector((set) => ({
      // Cursor state
      cursorPosition: { line: 0, column: 0, offset: 0 },
      cursorVisible: false,
      selection: undefined,
      desiredColumn: undefined,

      // Multi-cursor state
      multiCursorState: null,

      // Layout state
      scrollTop: 0,
      scrollLeft: 0,
      viewportHeight: EDITOR_CONSTANTS.DEFAULT_VIEWPORT_HEIGHT,

      // Instance state
      value: "",
      onChange: () => {},
      filePath: "",
      editorRef: null,
      placeholder: undefined,
      disabled: false,
      activeEditorViewKey: null,

      // Actions
      actions: {
        // Cursor actions
        setCursorPosition: (position, options) => {
          const { activeBufferId } = useBufferStore.getState();
          const activeEditorViewKey = useEditorStateStore.getState().activeEditorViewKey;
          const viewKey = activeEditorViewKey ?? activeBufferId;
          if (viewKey) {
            viewStateCache.setCursor(viewKey, position);
          }
          set({ cursorPosition: position });
          if (options?.ensureVisible !== false) {
            ensureCursorVisible(position);
          }
        },
        setSelection: (selection) => {
          const { activeBufferId } = useBufferStore.getState();
          const activeEditorViewKey = useEditorStateStore.getState().activeEditorViewKey;
          const viewKey = activeEditorViewKey ?? activeBufferId;
          if (viewKey) {
            viewStateCache.setSelection(viewKey, selection);
          }
          set({ selection });
        },
        setDesiredColumn: (column) => set({ desiredColumn: column }),
        setCursorVisibility: (visible) => set({ cursorVisible: visible }),
        getCachedPosition: (bufferId) => viewStateCache.getCursor(bufferId),
        getCachedViewState: (bufferId) => viewStateCache.get(bufferId),
        cacheViewStateForBuffer: (bufferId, state) => {
          viewStateCache.set(bufferId, state);
        },
        clearPositionCache: (bufferId) => viewStateCache.clear(bufferId),
        restorePositionForFile: (bufferId) => {
          const cachedState = viewStateCache.get(bufferId);
          const restoredState = cachedState ?? {
            cursor: { line: 0, column: 0, offset: 0 },
            scrollTop: 0,
            scrollLeft: 0,
          };

          set({
            cursorPosition: restoredState.cursor,
            selection: restoredState.selection,
            scrollTop: restoredState.scrollTop,
            scrollLeft: restoredState.scrollLeft,
          });

          return restoredState;
        },
        resetOnBufferSwitch: () => {
          set({
            multiCursorState: null,
            selection: undefined,
            desiredColumn: undefined,
          });
        },

        // Multi-cursor actions
        enableMultiCursor: () =>
          set((state) => {
            if (state.multiCursorState) return state;

            const primaryCursorId = `cursor-${Date.now()}-0`;
            return {
              multiCursorState: {
                cursors: [
                  {
                    id: primaryCursorId,
                    position: state.cursorPosition,
                    selection: state.selection,
                  },
                ],
                primaryCursorId,
              },
            };
          }),

        disableMultiCursor: () => set({ multiCursorState: null }),

        addCursor: (position, selection) =>
          set((state) => {
            if (!state.multiCursorState) return state;

            // Check for duplicate cursor at the same position
            const isDuplicate = state.multiCursorState.cursors.some(
              (cursor) =>
                cursor.position.line === position.line &&
                cursor.position.column === position.column,
            );
            if (isDuplicate) return state;

            const newCursorId = `cursor-${Date.now()}-${state.multiCursorState.cursors.length}`;
            const newCursor: Cursor = {
              id: newCursorId,
              position,
              selection,
            };

            return {
              multiCursorState: {
                ...state.multiCursorState,
                cursors: [...state.multiCursorState.cursors, newCursor],
              },
            };
          }),

        removeCursor: (cursorId) =>
          set((state) => {
            if (!state.multiCursorState) return state;

            const cursors = state.multiCursorState.cursors.filter((c) => c.id !== cursorId);

            if (cursors.length === 0) {
              return { multiCursorState: null };
            }

            let primaryCursorId = state.multiCursorState.primaryCursorId;
            if (cursorId === primaryCursorId && cursors.length > 0) {
              primaryCursorId = cursors[0].id;
            }

            return {
              multiCursorState: {
                cursors,
                primaryCursorId,
              },
            };
          }),

        updateCursor: (cursorId, position, selection) =>
          set((state) => {
            if (!state.multiCursorState) return state;

            const cursors = state.multiCursorState.cursors.map((cursor) =>
              cursor.id === cursorId
                ? { ...cursor, position, selection: selection ?? cursor.selection }
                : cursor,
            );

            return {
              multiCursorState: {
                ...state.multiCursorState,
                cursors,
              },
            };
          }),

        clearSecondaryCursors: () =>
          set((state) => {
            if (!state.multiCursorState) return state;

            const primaryCursor = state.multiCursorState.cursors.find(
              (c) => c.id === state.multiCursorState!.primaryCursorId,
            );

            if (!primaryCursor) {
              return { multiCursorState: null };
            }

            return {
              multiCursorState: {
                cursors: [primaryCursor],
                primaryCursorId: primaryCursor.id,
              },
              cursorPosition: primaryCursor.position,
              selection: primaryCursor.selection,
            };
          }),

        // Layout actions
        setScroll: (scrollTop, scrollLeft) => {
          const { activeBufferId } = useBufferStore.getState();
          const activeEditorViewKey = useEditorStateStore.getState().activeEditorViewKey;
          const viewKey = activeEditorViewKey ?? activeBufferId;
          if (viewKey) {
            viewStateCache.setScroll(viewKey, scrollTop, scrollLeft);
          }
          set({ scrollTop, scrollLeft });
        },
        setScrollForBuffer: (bufferId, scrollTop, scrollLeft) => {
          // Cache scroll for the specified buffer (avoids race condition when buffer switches)
          if (bufferId) {
            viewStateCache.setScroll(bufferId, scrollTop, scrollLeft);
          }
          // Only update global state if this is still the active buffer
          const activeBufferId = useBufferStore.getState().activeBufferId;
          if (bufferId === activeBufferId) {
            set({ scrollTop, scrollLeft });
          }
        },
        setViewportHeight: (height) => set({ viewportHeight: height }),

        // Instance actions
        setRefs: (refs) => set(refs),
        setContent: (value, onChange) => set({ value, onChange }),
        setFileInfo: (filePath) => set({ filePath }),
        setPlaceholder: (placeholder) => set({ placeholder }),
        setDisabled: (disabled) => set({ disabled }),
        setActiveEditorViewKey: (activeEditorViewKey) => set({ activeEditorViewKey }),
      },
    })),
  ),
);
