<script lang="ts">
const External = Annotation.define<boolean>();
</script>

<script setup lang="ts">
import { dropCursor, crosshairCursor, keymap, EditorView, ViewUpdate, placeholder as placeholderExtension } from '@codemirror/view';
import { Annotation, EditorState } from '@codemirror/state';
import { indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language';
import { history, defaultKeymap, historyKeymap } from '@codemirror/commands';
import { search, searchKeymap } from '@codemirror/search';
import { closeBrackets, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
import { lintKeymap } from '@codemirror/lint';

const editor = useTemplateRef('editor');
const view = ref<EditorView>();
const state = ref<EditorState>();

const { placeholder } = defineProps<{
    placeholder?: string
}>();
const model = defineModel<string>();

onMounted(() => {
    if(editor.value)
    {
        state.value = EditorState.create({
            doc: model.value,
            extensions: [
                history(),
                search(),
                dropCursor(),
                EditorState.allowMultipleSelections.of(true),
                indentOnInput(),
                syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
                bracketMatching(),
                closeBrackets(),
                crosshairCursor(),
                placeholderExtension(placeholder ?? ''),
                EditorView.lineWrapping,
                keymap.of([
                    ...closeBracketsKeymap,
                    ...defaultKeymap,
                    ...searchKeymap,
                    ...historyKeymap,
                    ...foldKeymap,
                    ...completionKeymap,
                    ...lintKeymap
                ]),
                EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
                    if (viewUpdate.docChanged && !viewUpdate.transactions.some(tr => tr.annotation(External)))
                    {
                        model.value = viewUpdate.state.doc.toString();
                    }
                }),
                EditorView.contentAttributes.of({spellcheck: "true"}),
            ]
        });
        view.value = new EditorView({
            state: state.value,
            parent: editor.value,
        });
    }
})

onBeforeUnmount(() => {
    if (view.value) {
        view.value?.destroy()
        view.value = undefined
    }
});

watchEffect(() => {
    if (model.value === void 0) {
        return;
    }
    const currentValue = view.value ? view.value.state.doc.toString() : "";
    if (view.value && model.value !== currentValue) {
        view.value.dispatch({
            changes: { from: 0, to: currentValue.length, insert: model.value || "" },
            annotations: [External.of(true)],
        });
    }
});
</script>

<template>
    <div ref="editor" class="flex flex-1 w-full justify-stretch items-stretch border border-light-35 dark:border-dark-35 caret-light-100 dark:caret-dark-100" />
</template>

<style>
.cm-editor
{
    @apply bg-transparent;
}
.cm-editor .cm-content
{
    @apply caret-light-100;
    @apply dark:caret-dark-100;
}
</style>