import React, { useEffect, useRef, useMemo, useCallback } from 'react';
import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightSpecialChars } from '@codemirror/view';
import { EditorState, Compartment } from '@codemirror/state';
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
import { indentOnInput, bracketMatching, foldGutter, foldKeymap } from '@codemirror/language';
import { closeBrackets, closeBracketsKeymap, autocompletion, completionKeymap } from '@codemirror/autocomplete';
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
import { lintKeymap } from '@codemirror/lint';
import { getLanguageExtension } from './languages';
import { nexusThemeExtension } from './theme';
import { useEditorStore } from '@/core/store';

interface CodeEditorProps {
  content: string;
  language: string;
  onChange?: (content: string) => void;
  readOnly?: boolean;
  className?: string;
  onLineClick?: (line: number) => void;
  onCursorLineChange?: (line: number) => void;
  bookmarks?: Array<{ line: number; id: string }>;
}

export const CodeEditor: React.FC<CodeEditorProps> = ({
  content,
  language,
  onChange,
  readOnly = false,
  className = '',
  onLineClick,
  onCursorLineChange,
  bookmarks = [],
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const viewRef = useRef<EditorView | null>(null);
  const onChangeRef = useRef(onChange);
  const onCursorLineChangeRef = useRef(onCursorLineChange);
  const { settings } = useEditorStore();

  useEffect(() => {
    onCursorLineChangeRef.current = onCursorLineChange;
  }, [onCursorLineChange]);
  
  // Compartments for dynamic settings
  const fontSizeCompartment = useRef(new Compartment());
  const fontFamilyCompartment = useRef(new Compartment());
  const lineNumbersCompartment = useRef(new Compartment());
  const wordWrapCompartment = useRef(new Compartment());
  const tabSizeCompartment = useRef(new Compartment());

  // Keep onChange ref updated
  useEffect(() => {
    onChangeRef.current = onChange;
  }, [onChange]);

  const baseExtensions = useMemo(() => {
    return [
      highlightActiveLineGutter(),
      highlightSpecialChars(),
      history(),
      foldGutter(),
      drawSelection(),
      dropCursor(),
      EditorState.allowMultipleSelections.of(true),
      indentOnInput(),
      bracketMatching(),
      closeBrackets(),
      autocompletion(),
      rectangularSelection(),
      crosshairCursor(),
      highlightActiveLine(),
      highlightSelectionMatches(),
      keymap.of([
        ...closeBracketsKeymap,
        ...defaultKeymap,
        ...searchKeymap,
        ...historyKeymap,
        ...foldKeymap,
        ...completionKeymap,
        ...lintKeymap,
        indentWithTab,
      ]),
      nexusThemeExtension,
      getLanguageExtension(language),
      EditorView.updateListener.of((update) => {
        if (update.docChanged && onChangeRef.current) {
          onChangeRef.current(update.state.doc.toString());
        }
        if (update.selectionSet && onCursorLineChangeRef.current) {
          const line = update.state.doc.lineAt(update.state.selection.main.head);
          onCursorLineChangeRef.current(line.number);
        }
      }),
      EditorState.readOnly.of(readOnly),
      // Dynamic compartments
      fontSizeCompartment.current.of([]),
      fontFamilyCompartment.current.of([]),
      lineNumbersCompartment.current.of([]),
      wordWrapCompartment.current.of([]),
      tabSizeCompartment.current.of([]),
    ];
  }, [language, readOnly]);

  // Initialize editor
  useEffect(() => {
    if (!containerRef.current) return;

    const state = EditorState.create({
      doc: content,
      extensions: baseExtensions,
    });

    const view = new EditorView({
      state,
      parent: containerRef.current,
    });

    viewRef.current = view;

    return () => {
      view.destroy();
      viewRef.current = null;
    };
  }, [baseExtensions]);

  // Update settings when they change (runs after editor is initialized)
  useEffect(() => {
    const view = viewRef.current;
    if (!view) return;

    const dispatch = view.dispatch.bind(view);

    // Update fontSize
    if (settings.fontSize) {
      dispatch({
        effects: fontSizeCompartment.current.reconfigure(
          EditorView.contentAttributes.of({
            style: `font-size: ${settings.fontSize}px;`,
          })
        ),
      });
    }

    // Update fontFamily
    if (settings.fontFamily) {
      dispatch({
        effects: fontFamilyCompartment.current.reconfigure(
          EditorView.contentAttributes.of({
            style: `font-family: ${settings.fontFamily}, monospace;`,
          })
        ),
      });
    }

    // Update lineNumbers
    dispatch({
      effects: lineNumbersCompartment.current.reconfigure(
        settings.lineNumbers !== false ? lineNumbers() : []
      ),
    });

    // Update wordWrap
    dispatch({
      effects: wordWrapCompartment.current.reconfigure(
        settings.wordWrap !== false
          ? EditorView.lineWrapping
          : []
      ),
    });

    // Update tabSize
    if (settings.tabSize) {
      dispatch({
        effects: tabSizeCompartment.current.reconfigure(
          EditorState.tabSize.of(settings.tabSize)
        ),
      });
    }
  }, [settings.fontSize, settings.fontFamily, settings.lineNumbers, settings.wordWrap, settings.tabSize]);

  // Update content when it changes externally
  useEffect(() => {
    const view = viewRef.current;
    if (!view) return;

    const currentContent = view.state.doc.toString();
    if (content !== currentContent) {
      view.dispatch({
        changes: { from: 0, to: currentContent.length, insert: content },
      });
    }
  }, [content]);

  return (
    <div
      ref={containerRef}
      className={`flex-1 min-h-0 w-full flex flex-col overflow-hidden ${className}`}
      style={{ minHeight: 0 }}
    />
  );
};

export default CodeEditor;
