import * as monaco from 'monaco-editor-core';
import editorWorker from 'monaco-editor-core/esm/vs/editor/editor.worker?worker';
import { useCallback, useEffect, useRef, useState } from 'react';
import { configuration, languageDef, provider } from './configs';
import { LANGUAGE_EXTENSION_POINT, LANGUAGE_ID } from './constants';
import { WorkerManager } from './worker/WorkerManager';
import DiagnosticsAdapter from './worker/adapters/DiagnosticsAdapter';
import openqasmWorker from './worker/openqasm.worker?worker';
import FormatProvider from './worker/providers/FormatProvider';
import HoverProvider from './worker/providers/HoverProvider';

self.MonacoEnvironment = {
  getWorker: function (_: string, label: string) {
    switch (label) {
      case LANGUAGE_ID:
        return new openqasmWorker();
      default:
        return new editorWorker();
    }
  },
};
export type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
export type EditorProps = {
  width: string;
  height: string;
  theme: string;
  defaultvalue: string;
  options: monaco.editor.IEditorOptions;
  onChange: (
    value: string,
    event: monaco.editor.IModelContentChangedEvent,
  ) => void;
  onMount: (editor: IStandaloneCodeEditor) => void;
  value: string;
};

/**
 * noop is a helper function that does nothing
 * @returns undefined
 */
function noop() {
  /** no-op */
}

const OpenQASMEditor = ({
  width = '800px',
  height = '500px',
  theme = 'vs-dark',
  defaultvalue = 'OPENQASM 2.0;\ninclude "qelib1.inc";\n\n// 量子程序\nqreg q[2];\ncreg c[2];\n\nh q[0];\ncx q[0], q[1];\nmeasure q -> c;',
  options = {},
  onChange = noop,
  onMount = noop,
  value,
}: Partial<EditorProps>) => {
  const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const [isEditorReady, setIsEditorReady] = useState(false);
  const preventCreation = useRef(false);
  const onMountRef = useRef(onMount);
  const subscriptionRef = useRef<monaco.IDisposable | undefined | null>(null);
  const valueRef = useRef(value);
  const preventTriggerChangeEvent = useRef(false);

  useEffect(() => {
    return () => {
      disposeEditor();
    };
  }, []);

  // 更新options
  useEffect(() => {
    if (!isEditorReady) return;
    editorRef.current?.updateOptions(options);
  }, [options, isEditorReady]);

  useEffect(() => {
    if (!isEditorReady) return;
    if (!editorRef.current || value === undefined) return;
    if (editorRef.current.getOption(monaco.editor.EditorOption.readOnly)) {
      editorRef.current.setValue(value);
    } else if (value !== editorRef.current.getValue()) {
      preventTriggerChangeEvent.current = true;
      editorRef.current.executeEdits('', [
        {
          range: editorRef.current.getModel()!.getFullModelRange(),
          text: value,
          forceMoveMarkers: true,
        },
      ]);

      editorRef.current.pushUndoStop();
      preventTriggerChangeEvent.current = false;
    }
  }, [value]);

  // 更新主题
  useEffect(() => {
    if (isEditorReady) {
      monaco.editor.setTheme(theme);
    }
  }, [theme, isEditorReady]);

  const createEditor = useCallback(() => {
    if (!containerRef.current || editorRef.current) return;
    if (preventCreation.current) return;
    monaco.languages.register(LANGUAGE_EXTENSION_POINT);
    monaco.languages.onLanguage(LANGUAGE_ID, () => {
      monaco.languages.setMonarchTokensProvider(LANGUAGE_ID, languageDef);
      monaco.languages.setLanguageConfiguration(LANGUAGE_ID, configuration);
      monaco.languages.registerCompletionItemProvider(LANGUAGE_ID, provider);
      monaco.languages.registerHoverProvider(LANGUAGE_ID, new HoverProvider());
      monaco.languages.registerDocumentFormattingEditProvider(
        LANGUAGE_ID,
        new FormatProvider(worker),
      );
    });

    const client = new WorkerManager();
    const worker = (...uris: monaco.Uri[]) => {
      const ret = client.getLanguageServiceWorker(...uris);
      return ret;
    };
    new DiagnosticsAdapter(worker);

    const model = monaco.editor.createModel(defaultvalue, LANGUAGE_ID);

    // Create editor after worker setup
    editorRef.current = monaco.editor.create(containerRef.current, {
      model,
      language: LANGUAGE_ID,
      theme,
      ...options,
      automaticLayout: true,
      minimap: {
        enabled: false,
      },
    });
    setIsEditorReady(true);
    preventCreation.current = true;
  }, [defaultvalue, options, theme]);

  useEffect(() => {
    if (!isEditorReady) return;
    onMountRef.current(editorRef.current!);
  }, [isEditorReady]);

  // createEditor
  useEffect(() => {
    if (!isEditorReady) {
      createEditor();
    }
  }, [isEditorReady, createEditor]);

  valueRef.current = value;

  useEffect(() => {
    if (isEditorReady && !!onChange) {
      subscriptionRef.current?.dispose();
      subscriptionRef.current = editorRef.current?.onDidChangeModelContent(
        (event) => {
          if (preventTriggerChangeEvent.current) return;
          onChange(editorRef.current?.getValue() ?? '', event);
        },
      );
    }
  }, [isEditorReady, onChange]);

  function disposeEditor() {
    subscriptionRef.current?.dispose();
    editorRef.current!.getModel()?.dispose();
    editorRef.current!.dispose();
  }

  return (
    <div
      ref={containerRef}
      id="editor-container"
      style={{
        width,
        height,
        overflow: 'hidden',
        border: '1px solid #ccc',
        borderRadius: '4px',
      }}
    />
  );
};

export { OpenQASMEditor };
