<script lang="ts">
  import { onMount, untrack } from 'svelte';
  import { EditorState } from '@codemirror/state';
  import {
    EditorView,
    keymap,
    lineNumbers,
    highlightActiveLine,
    highlightActiveLineGutter,
    drawSelection,
    highlightSpecialChars,
  } from '@codemirror/view';
  import {
    defaultKeymap,
    history,
    historyKeymap,
    indentWithTab,
  } from '@codemirror/commands';
  import { bracketMatching, indentOnInput } from '@codemirror/language';
  import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
  import { lintKeymap } from '@codemirror/lint';
  import { store } from '../store.svelte';
  import { mermaidSupport } from '../cm-mermaid';

  let host: HTMLDivElement;
  let view: EditorView | undefined;

  const theme = EditorView.theme(
    {
      '&': {
        height: '100%',
        fontSize: '13.5px',
        backgroundColor: 'var(--bg-input)',
        color: 'var(--text)',
      },
      '.cm-content': {
        fontFamily: 'var(--mono)',
        caretColor: 'var(--accent)',
        padding: '12px 0',
      },
      '.cm-scroller': { fontFamily: 'var(--mono)', lineHeight: '1.55' },
      '.cm-gutters': {
        backgroundColor: 'var(--bg-input)',
        color: 'var(--text-faint)',
        border: 'none',
      },
      '.cm-activeLineGutter': { backgroundColor: 'transparent', color: 'var(--text-dim)' },
      '.cm-activeLine': { backgroundColor: 'rgba(255,255,255,0.03)' },
      '&.cm-focused .cm-cursor': { borderLeftColor: 'var(--accent)' },
      '.cm-selectionBackground, &.cm-focused .cm-selectionBackground, ::selection': {
        backgroundColor: 'var(--accent-soft) !important',
      },
      '.cm-matchingBracket': { backgroundColor: 'var(--accent-soft)', outline: 'none' },

      // Autocomplete popup
      '.cm-tooltip': {
        backgroundColor: 'var(--bg-elev-2)',
        border: '1px solid var(--border-strong)',
        borderRadius: '6px',
        color: 'var(--text)',
        boxShadow: '0 8px 28px rgba(0,0,0,0.45)',
      },
      '.cm-tooltip.cm-tooltip-autocomplete > ul': { fontFamily: 'var(--mono)', maxHeight: '16em' },
      '.cm-tooltip-autocomplete ul li[aria-selected]': {
        backgroundColor: 'var(--accent)',
        color: '#fff',
      },
      '.cm-completionIcon': { color: 'var(--text-dim)', paddingRight: '0.6em' },
      '.cm-completionDetail': { color: 'var(--text-faint)', fontStyle: 'normal' },
      '.cm-completionInfo': {
        backgroundColor: 'var(--bg-elev-2)',
        border: '1px solid var(--border-strong)',
        borderRadius: '6px',
        color: 'var(--text-dim)',
      },

      // Lint diagnostics
      '.cm-diagnostic': {
        fontFamily: 'var(--mono)',
        fontSize: '12px',
        borderLeft: '3px solid var(--red)',
        padding: '6px 8px',
      },
      '.cm-diagnostic-error': { borderLeftColor: 'var(--red)' },
      '.cm-lintRange-error': {
        backgroundImage: 'none',
        textDecoration: 'underline wavy var(--red)',
        textDecorationSkipInk: 'none',
      },
      '.cm-lint-marker-error': { content: '""' },
    },
    { dark: true }
  );

  onMount(() => {
    view = new EditorView({
      parent: host,
      state: EditorState.create({
        doc: store.content,
        extensions: [
          lineNumbers(),
          highlightActiveLineGutter(),
          highlightSpecialChars(),
          history(),
          drawSelection(),
          indentOnInput(),
          bracketMatching(),
          closeBrackets(),
          highlightActiveLine(),
          ...mermaidSupport(),
          keymap.of([
            ...closeBracketsKeymap,
            ...defaultKeymap,
            ...historyKeymap,
            ...lintKeymap,
            indentWithTab,
          ]),
          theme,
          EditorView.lineWrapping,
          EditorView.updateListener.of((u) => {
            if (u.docChanged) {
              store.content = u.state.doc.toString();
            }
          }),
        ],
      }),
    });
    return () => view?.destroy();
  });

  // Replace the whole document when a different diagram is loaded (activeId) or
  // when content is swapped programmatically (revision, e.g. an applied
  // optimization). Read content untracked so plain typing never resets it.
  $effect(() => {
    void store.activeId; // re-run when the selected diagram changes…
    void store.revision; // …or when an optimization rewrites the source.
    if (!view) return;
    untrack(() => {
      const incoming = store.content;
      if (incoming !== view!.state.doc.toString()) {
        view!.dispatch({
          changes: { from: 0, to: view!.state.doc.length, insert: incoming },
        });
      }
    });
  });
</script>

<div class="editor" bind:this={host}></div>

<style>
  .editor {
    height: 100%;
    overflow: hidden;
    background: var(--bg-input);
  }
</style>
