<script lang="ts">
  import type { EditorProps } from '$/types';
  import { env } from '$/util/env';
  import { stateStore, urlsStore } from '$/util/state';
  import { isMac } from '$/util/util';
  import { initEditor } from '$lib/util/monacoExtra';
  import { errorDebug } from '$lib/util/util';
  import { mode } from 'mode-watcher';
  import * as monaco from 'monaco-editor';
  import monacoEditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
  import monacoJsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
  import { onMount } from 'svelte';
  import AIGlyphPopup from './AIGlyphPopup.svelte';

  const { onUpdate }: EditorProps = $props();

  let divElement: HTMLDivElement | undefined = $state();
  let editor: monaco.editor.IStandaloneCodeEditor | undefined;
  let editorOptions = {
    minimap: {
      enabled: false
    },
    overviewRulerLanes: 0,
    glyphMargin: true
  } satisfies monaco.editor.IStandaloneEditorConstructionOptions;
  let currentText = '';
  let showPopup = $state(false);
  let popupPosition = $state({ top: 0 });
  let decorationsCollection: monaco.editor.IEditorDecorationsCollection | undefined;
  let hintCollection: monaco.editor.IEditorDecorationsCollection | undefined;
  let suggestion = $state('');
  let lastMouseLine = 0;

  const quickEditHintStyle = `--quick-edit-hint-text: '${isMac ? 'Quick edit (⌘⏎)' : 'Quick edit (Ctrl+⏎)'}'`;

  const jsonModel = monaco.editor.createModel(
    '',
    'json',
    monaco.Uri.parse('internal://config.json')
  );
  const mermaidModel = monaco.editor.createModel(
    '',
    'mermaid',
    monaco.Uri.parse('internal://mermaid.mmd')
  );

  const renderQuickEditHint = () => {
    hintCollection?.clear();
    decorationsCollection?.clear();
    if (!editor || showPopup) {
      return;
    }
    const model = editor.getModel();
    const position = editor.getPosition();
    if (!model) {
      return;
    }

    if (lastMouseLine > 0 && model.id === mermaidModel.id) {
      decorationsCollection?.set([
        {
          range: new monaco.Range(lastMouseLine, 1, lastMouseLine, 1),
          options: {
            isWholeLine: true,
            glyphMarginClassName: 'suggestion-icon'
          }
        }
      ]);
    }

    if (position) {
      const { lineNumber } = position;
      if (model.getLineContent(lineNumber).trim() === '') {
        const column = model.getLineMaxColumn(lineNumber);
        hintCollection?.set([
          {
            range: new monaco.Range(lineNumber, column, lineNumber, column),
            options: {
              afterContentClassName: 'quick-edit-hint'
            }
          }
        ]);
      }
    }
  };

  onMount(() => {
    self.MonacoEnvironment = {
      getWorker(_, label) {
        if (label === 'json') {
          return new monacoJsonWorker();
        }
        return new monacoEditorWorker();
      }
    };

    if (!divElement) {
      throw new Error('divEl is undefined');
    }

    monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
      validate: true,
      enableSchemaRequest: true,
      schemas: [
        {
          fileMatch: ['config.json'],
          uri: `${env.docsUrl}/schemas/config.schema.json`
        }
      ]
    });

    initEditor(monaco);
    errorDebug();
    editor = monaco.editor.create(divElement, editorOptions);
    decorationsCollection = editor.createDecorationsCollection([]);
    hintCollection = editor.createDecorationsCollection([]);

    editor.onMouseDown((e) => {
      if (
        e.target.type === monaco.editor.MouseTargetType.GUTTER_GLYPH_MARGIN &&
        e.target.position?.lineNumber
      ) {
        const mouseEvent = e.event;
        if (!divElement) return;
        const rect = divElement.getBoundingClientRect();
        popupPosition = {
          top: mouseEvent.posy - rect.top
        };
        showPopup = !showPopup;
        renderQuickEditHint();
      }
    });

    editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
      const position = editor?.getPosition();
      if (position) {
        popupPosition = {
          top:
            (editor?.getTopForLineNumber(position.lineNumber) ?? 0) - (editor?.getScrollTop() ?? 0)
        };
        showPopup = true;
      }

      renderQuickEditHint();
    });

    editor.onDidChangeCursorPosition(() => {
      renderQuickEditHint();
    });

    editor.onDidChangeModelContent(({ isFlush }) => {
      renderQuickEditHint();
      const newText = editor?.getValue();
      if (!newText || currentText === newText || isFlush) {
        return;
      }
      currentText = newText;
      onUpdate(currentText);
    });

    const unsubscribeState = stateStore.subscribe(({ errorMarkers, editorMode, code, mermaid }) => {
      if (!editor) {
        return;
      }

      const model = editorMode === 'code' ? mermaidModel : jsonModel;

      if (editor.getModel()?.id !== model.id) {
        editor.setModel(model);
        renderQuickEditHint();
      }

      // Clear decorations if not in 'code' mode, or if the model changes
      if (editorMode !== 'code' || editor.getModel()?.id !== mermaidModel.id) {
        decorationsCollection?.clear();
      }

      // Update editor text if it's different
      const newText = editorMode === 'code' ? code : mermaid;
      if (newText !== currentText) {
        editor.setScrollTop(0);
        editor.setValue(newText);
        currentText = newText;
        renderQuickEditHint();
      }

      // Display/clear errors
      monaco.editor.setModelMarkers(model, 'mermaid', errorMarkers);
    });

    editor.onMouseMove((e) => {
      if (!editor) return;
      if (showPopup) return;
      if (editor.getModel()?.id !== mermaidModel.id) return;

      lastMouseLine = e.target.position?.lineNumber ?? 0;
      renderQuickEditHint();
    });

    editor.onMouseLeave(() => {
      lastMouseLine = 0;
      renderQuickEditHint();
    });

    const unsubscribeMode = mode.subscribe((mode) => {
      if (editor) {
        monaco.editor.setTheme(`mermaid${mode === 'dark' ? '-dark' : ''}`);
      }
    });
    const resizeObserver = new ResizeObserver((entries) => {
      editor?.layout({
        height: entries[0].contentRect.height,
        width: entries[0].contentRect.width
      });
    });

    if (divElement.parentElement) {
      resizeObserver.observe(divElement);
    }

    renderQuickEditHint();

    return () => {
      unsubscribeState();
      unsubscribeMode();
      resizeObserver.disconnect();
      jsonModel.dispose();
      mermaidModel.dispose();
      editor?.dispose();
    };
  });
</script>

<div class="relative h-full grow overflow-hidden" style={quickEditHintStyle}>
  <div bind:this={divElement} id="editor" class="h-full w-full"></div>
  <AIGlyphPopup
    top={popupPosition.top}
    show={showPopup}
    bind:suggestion
    onClose={() => {
      showPopup = false;
      suggestion = '';
      renderQuickEditHint();
    }}
    onTryFree={() => {
      window.open($urlsStore.mermaidChart({ medium: 'vibe_diagramming' }).save, '_blank');
      showPopup = false;
      suggestion = '';
      renderQuickEditHint();
    }} />
</div>

<style>
  :global(.quick-edit-hint::after) {
    content: var(--quick-edit-hint-text);
    color: #a1a1aa;
    font-size: small;
    pointer-events: none;
    user-select: none;
    margin-left: 4px;
    opacity: 0.6;
  }

  :global(.ai-glyph-margin) {
    background-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="24" height="24" viewBox="0 0 24 24"%3E%3Cpath fill="%23a855f7" d="M9 21.035l-1.185-5.917L2 14.154l5.815-1.139L9 7.098l1.328 5.917l5.672 1.139l-5.672 1.157L9 21.035zM15.42 11.22l-1.614-2.822l-2.86-.948l2.86-.928l1.614-2.86l1.248 2.86l2.902.928l-2.902.948l-1.248 2.822zm2.083 10.39l-1.04-2.583l-2.607-.647l2.607-.625l1.04-2.606l.855 2.606l2.585.625l-2.585.647l-.855 2.583z"%2F%3E%3C%2Fsvg%3E');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    cursor: pointer;
  }

  :global(.suggestion-icon) {
    background-color: #e8eaf9;
    width: 20px !important;
    height: 20px !important;
    margin-left: 4px;
    background-image: url('/icons/use-chat.svg');
    background-size: 16px 16px;
    background-repeat: no-repeat;
    background-position: center;
    border-radius: 4px;
    cursor: pointer;
  }
</style>
