<template>
	<div ref="root" @keydown.stop></div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { EditorView, keymap } from '@codemirror/view';
import { EditorState, Prec } from '@codemirror/state';
import { history, redo, undo } from '@codemirror/commands';

import { workflowHelpers } from '@/mixins/workflowHelpers';
import { expressionManager } from '@/mixins/expressionManager';
import { completionManager } from '@/mixins/completionManager';
import { expressionInputHandler } from '@/plugins/codemirror/inputHandlers/expression.inputHandler';
import { n8nLang } from '@/plugins/codemirror/n8nLang';
import { highlighter } from '@/plugins/codemirror/resolvableHighlighter';
import { inputTheme } from './theme';
import { forceParse } from '@/utils/forceParse';
import { acceptCompletion, autocompletion } from '@codemirror/autocomplete';

import type { IVariableItemSelected } from '@/Interface';

export default defineComponent({
	name: 'ExpressionEditorModalInput',
	mixins: [expressionManager, completionManager, workflowHelpers],
	props: {
		modelValue: {
			type: String,
			required: true,
		},
		path: {
			type: String,
			required: true,
		},
		isReadOnly: {
			type: Boolean,
			default: false,
		},
	},
	data() {
		return {
			editor: null as EditorView | null,
		};
	},
	mounted() {
		const extensions = [
			inputTheme(),
			autocompletion(),
			Prec.highest(
				keymap.of([
					{ key: 'Tab', run: acceptCompletion },
					{
						any: (_: EditorView, event: KeyboardEvent) => {
							if (event.key === 'Escape') {
								event.stopPropagation();
								this.$emit('close');
							}

							return false;
						},
					},
					{ key: 'Mod-z', run: undo },
					{ key: 'Mod-Shift-z', run: redo },
				]),
			),
			n8nLang(),
			history(),
			expressionInputHandler(),
			EditorView.lineWrapping,
			EditorState.readOnly.of(this.isReadOnly),
			EditorView.contentAttributes.of({ 'data-gramm': 'false' }), // disable grammarly
			EditorView.domEventHandlers({ scroll: forceParse }),
			EditorView.updateListener.of((viewUpdate) => {
				if (!this.editor || !viewUpdate.docChanged) return;

				this.editorState = this.editor.state;

				highlighter.removeColor(this.editor, this.plaintextSegments);
				highlighter.addColor(this.editor, this.resolvableSegments);

				setTimeout(() => {
					this.editor?.focus(); // prevent blur on paste
					try {
						this.trackCompletion(viewUpdate, this.path);
					} catch {}
				});

				this.$emit('change', {
					value: this.unresolvedExpression,
					segments: this.displayableSegments,
				});
			}),
		];

		this.editor = new EditorView({
			parent: this.$refs.root as HTMLDivElement,
			state: EditorState.create({
				doc: this.modelValue.startsWith('=') ? this.modelValue.slice(1) : this.modelValue,
				extensions,
			}),
		});

		this.editorState = this.editor.state;
		this.editor.focus();

		highlighter.addColor(this.editor, this.resolvableSegments);

		this.editor.dispatch({
			selection: { anchor: this.editor.state.doc.length },
		});

		this.$emit('change', {
			value: this.unresolvedExpression,
			segments: this.displayableSegments,
		});
	},
	beforeUnmount() {
		this.editor?.destroy();
	},
	methods: {
		itemSelected({ variable }: IVariableItemSelected) {
			if (!this.editor || this.isReadOnly) return;

			const OPEN_MARKER = '{{';
			const CLOSE_MARKER = '}}';

			const { doc, selection } = this.editor.state;
			const { head } = selection.main;

			const isInsideResolvable =
				doc.toString().slice(0, head).includes(OPEN_MARKER) &&
				doc.toString().slice(head, doc.length).includes(CLOSE_MARKER);

			const insert = isInsideResolvable
				? variable
				: [OPEN_MARKER, variable, CLOSE_MARKER].join(' ');

			this.editor.dispatch({
				changes: {
					from: head,
					insert,
				},
			});
		},
	},
});
</script>

<style lang="scss"></style>
