<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import { EditorView } from 'codemirror';
  import { Compartment } from '@codemirror/state';
  import { lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, keymap } from '@codemirror/view';
  import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
  import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
  import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
  import { foldGutter, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language';
  import { lintKeymap } from '@codemirror/lint';
  import { csoundMode } from '@hlolli/codemirror-lang-csound';
  import { oneDark } from '@codemirror/theme-one-dark';
  import { vim } from '@replit/codemirror-vim';
  import type { EditorSettingsStore } from '../../stores/editorSettings';
  import {
    flashField,
    flash,
    getSelection,
    getBlock,
    getDocument
  } from '../../editor/block-eval';

  interface Props {
    value: string;
    onChange?: (value: string) => void;
    onExecute?: (code: string, source: 'selection' | 'block' | 'document') => void;
    editorSettings: EditorSettingsStore;
    mode: 'composition' | 'livecoding';
  }

  let {
    value = '',
    onChange,
    onExecute,
    editorSettings,
    mode = 'composition'
  }: Props = $props();

  let editorContainer: HTMLDivElement;
  let editorView: EditorView | null = null;

  const lineNumbersCompartment = new Compartment();
  const lineWrappingCompartment = new Compartment();
  const vimCompartment = new Compartment();

  function handleExecute() {
    if (!editorView) return;

    if (mode === 'composition') {
      const doc = getDocument(editorView.state);
      flash(editorView, doc.from, doc.to);
      onExecute?.(doc.text, 'document');
      return;
    }

    const selection = getSelection(editorView.state);
    if (selection.text) {
      flash(editorView, selection.from, selection.to);
      onExecute?.(selection.text, 'selection');
      return;
    }

    const block = getBlock(editorView.state);
    if (block.text) {
      flash(editorView, block.from, block.to);
      onExecute?.(block.text, 'block');
      return;
    }

    const doc = getDocument(editorView.state);
    flash(editorView, doc.from, doc.to);
    onExecute?.(doc.text, 'document');
  }

  const evaluateKeymap = keymap.of([
    {
      key: 'Mod-e',
      run: () => {
        handleExecute();
        return true;
      }
    }
  ]);

  onMount(() => {
    const baseExtensions = [
      highlightActiveLineGutter(),
      highlightSpecialChars(),
      history(),
      foldGutter(),
      drawSelection(),
      dropCursor(),
      indentOnInput(),
      syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
      bracketMatching(),
      closeBrackets(),
      autocompletion(),
      rectangularSelection(),
      crosshairCursor(),
      highlightSelectionMatches(),
      keymap.of([
        ...closeBracketsKeymap,
        ...defaultKeymap,
        ...searchKeymap,
        ...historyKeymap,
        ...foldKeymap,
        ...completionKeymap,
        ...lintKeymap
      ])
    ];

    const initSettings = $editorSettings;

    editorView = new EditorView({
      doc: value,
      extensions: [
        ...baseExtensions,
        csoundMode({ fileType: 'csd' }),
        oneDark,
        evaluateKeymap,
        flashField(),
        lineNumbersCompartment.of(initSettings.showLineNumbers ? lineNumbers() : []),
        lineWrappingCompartment.of(initSettings.enableLineWrapping ? EditorView.lineWrapping : []),
        vimCompartment.of(initSettings.vimMode ? vim() : []),
        EditorView.updateListener.of((update) => {
          if (update.docChanged && onChange) {
            onChange(update.state.doc.toString());
          }
        }),
        EditorView.theme({
          '&': {
            fontSize: `${initSettings.fontSize}px`,
            fontFamily: initSettings.fontFamily
          }
        })
      ],
      parent: editorContainer
    });
  });

  $effect(() => {
    const settings = $editorSettings;
    if (!editorView) return;

    editorView.dispatch({
      effects: [
        lineNumbersCompartment.reconfigure(settings.showLineNumbers ? lineNumbers() : []),
        lineWrappingCompartment.reconfigure(settings.enableLineWrapping ? EditorView.lineWrapping : []),
        vimCompartment.reconfigure(settings.vimMode ? vim() : [])
      ]
    });

    editorView.dom.style.fontSize = `${settings.fontSize}px`;
    editorView.dom.style.fontFamily = settings.fontFamily;
  });

  onDestroy(() => {
    if (editorView) {
      editorView.destroy();
    }
  });

  $effect(() => {
    if (editorView && value !== editorView.state.doc.toString()) {
      editorView.dispatch({
        changes: { from: 0, to: editorView.state.doc.length, insert: value }
      });
    }
  });

  export function getSelectedText(): string | null {
    if (!editorView) return null;
    const { text } = getSelection(editorView.state);
    return text || null;
  }

  export function getCurrentBlock(): string | null {
    if (!editorView) return null;
    const { text } = getBlock(editorView.state);
    return text || null;
  }

  export function getFullDocument(): string {
    if (!editorView) return '';
    const { text } = getDocument(editorView.state);
    return text;
  }

  export function evaluateWithFlash(text: string, from: number | null, to: number | null) {
    if (editorView && from !== null && to !== null) {
      flash(editorView, from, to);
    }
  }
</script>

<div class="editor-wrapper">
  <div bind:this={editorContainer} class="editor-container"></div>
</div>

<style>
  .editor-wrapper {
    width: 100%;
    height: 100%;
    overflow: hidden;
  }

  .editor-container {
    width: 100%;
    height: 100%;
  }

  :global(.cm-editor) {
    height: 100%;
  }

  :global(.cm-scroller) {
    overflow: auto;
  }
</style>
