/* eslint-disable no-alert, no-shadow, @typescript-eslint/no-shadow */
import {
	EditorView,
	keymap,
	highlightSpecialChars,
	drawSelection,
	highlightActiveLine,
	dropCursor,
} from '@codemirror/view';
import { EditorState } from '@codemirror/state';
import { history, historyKeymap } from '@codemirror/history';
import { indentOnInput, syntaxTree } from '@codemirror/language';
import { highlightActiveLineGutter } from '@codemirror/gutter';
import { defaultKeymap } from '@codemirror/commands';
import { bracketMatching } from '@codemirror/matchbrackets';
import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets';
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
import { autocompletion, completionKeymap } from '@codemirror/autocomplete';
import { commentKeymap } from '@codemirror/comment';
import { rectangularSelection } from '@codemirror/rectangular-selection';
import { HighlightStyle, tags } from '@codemirror/highlight';
import { lintKeymap, linter } from '@codemirror/lint';
import type { Diagnostic } from '@codemirror/lint';
import { hoverTooltip } from '@codemirror/tooltip';
import { GFCALC } from '@gravitywiz/codemirror-lang-gfcalc';

const { jQuery: $ } = window;

function formulaLint( view: EditorView ): readonly Diagnostic[] {
	const diagnostics: Diagnostic[] = [];

	const tree = syntaxTree( view.state );

	/* Do not lint if the document is empty. */
	if ( ! tree?.length ) {
		return diagnostics;
	}

	syntaxTree( view.state ).iterate( {
		enter: ( type, from, to ) => {
			if ( type.isError ) {
				diagnostics.push( {
					from,
					to,
					severity: 'error',
					message: 'Formula Syntax Error',
				} );
			}
		},
	} );

	return diagnostics;
}

function editorFromTextArea(
	textarea: HTMLTextAreaElement,
	extensions: any[] = []
) {
	const view = new EditorView( {
		state: EditorState.create( {
			doc: textarea.value,
			extensions: [
				hoverTooltip( ( view, pos, side ) => {
					const { from, to, text } = view.state.doc.lineAt( pos );
					let start = pos,
						end = pos;

					while (
						start > from &&
						/(F|\d|\.|{|:)/.test( text[ start - from - 1 ] )
					) {
						start--;
					}

					while (
						end < to &&
						/(\d|\.|}|:)/.test( text[ end - from ] )
					) {
						end++;
					}

					const match = text.slice( start - from, end - from );
					const shorthandRegex = /^F(\d+(\.\d+)?)/;
					const noLabelMergeTagRegex = /^{:(\d+(\.\d+)?)}/;

					if (
						( start === pos && side < 0 ) ||
						( end === pos && side > 0 ) ||
						( ! match.match( shorthandRegex ) &&
							! match.match( noLabelMergeTagRegex ) )
					) {
						return null;
					}

					const inputID =
						match.match( shorthandRegex )?.[ 1 ] ??
						match.match( noLabelMergeTagRegex )?.[ 1 ];

					if ( ! inputID ) {
						return null;
					}

					const field = window.GetFieldById( parseInt( inputID ) );
					const label = window.GetLabel( field );

					if ( ! label ) {
						return null;
					}

					return {
						pos: start,
						end,
						above: true,
						create( view ) {
							const dom = document.createElement( 'div' );
							dom.textContent = label;
							return { dom };
						},
					};
				} ),
				linter( formulaLint ),
				[
					EditorView.baseTheme( {
						'&': {
							marginBottom: '-0.75rem',
						},
						'.cm-content, .cm-gutter': {
							minHeight: '75px',
						},
						'.gm-gutters': {
							margin: '1px',
						},
						'&:not(.cm-focused) .cm-activeLine': {
							background: 'transparent',
						},
						'&.cm-focused': {
							outline: 'none !important',
						},
						'.cm-scroller': {
							overflow: 'auto',
							border: '1px solid silver',
							marginTop: '2px',
							borderRadius: '2px',
						},
						'.cm-wrap': {
							border: '1px solid silver',
						},
						'.cm-completionIcon-snippet:after': {
							content: "'✂️'",
						},
						'.cm-tooltip': {
							background: 'rgba(28, 31, 63, 0.9) !important',
							outline: 'none',
							padding: '0.625rem 1rem !important',
							borderRadius: '5px',
							font: '400 0.8125rem "Open Sans", sans-serif',
							color: '#fff',
							boxShadow: '0 4px 10px rgba(18, 25, 97, 0.0779552)',
							border: 'none !important',
						},
						'.cm-tooltip.cm-tooltip-autocomplete': {
							padding: '0 !important',
						},
						'.cm-tooltip.cm-tooltip-autocomplete > ul': {
							scrollbarWidth: 'none',
						},
						'.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar': {
							display: 'none',
						},
						'.cm-tooltip-autocomplete > ul > li': {
							margin: '0',
							padding: '0.625rem 1rem !important',
						},
						'.cm-tooltip-autocomplete ul li[aria-selected]': {
							background: 'rgba(28, 31, 63, 0.8)',
							borderRadius: '5px',
						},
						'.cm-tooltip.cm-tooltip-lint': {
							padding: '0',
						},
						'.cm-completionIcon': { marginRight: '2px' },
						'.cm-tooltip.cm-completionInfo': {
							marginRight: '3px',
						},
						'.cm-panels': {
							font: '400 .95rem "Open Sans", sans-serif',
						},
						'.cm-diagnostic': { margin: 0 },
					} ),
					highlightActiveLineGutter(),
					highlightSpecialChars(),
					history(),
					drawSelection(),
					dropCursor(),
					EditorState.allowMultipleSelections.of( true ),
					indentOnInput(),
					HighlightStyle.define( [
						{
							tag: tags.variableName,
							fontStyle: 'italic',
							color: '#e40',
						},
						{
							tag: tags.link,
							textDecoration: 'underline',
						},
						{
							tag: tags.heading,
							textDecoration: 'underline',
							fontWeight: 'bold',
						},
						{
							tag: tags.emphasis,
							fontStyle: 'italic',
						},
						{
							tag: tags.strong,
							fontWeight: 'bold',
						},
						{
							tag: tags.strikethrough,
							textDecoration: 'line-through',
						},
						{
							tag: tags.keyword,
							color: '#708',
						},
						{
							tag: [
								tags.atom,
								tags.bool,
								tags.url,
								tags.contentSeparator,
								tags.labelName,
							],
							color: '#219',
						},
						{
							tag: [ tags.literal, tags.inserted ],
							color: '#164',
						},
						{
							tag: [ tags.string, tags.deleted ],
							color: '#a11',
						},
						{
							tag: [
								tags.regexp,
								tags.escape,
								tags.special( tags.string ),
							],
							color: '#e40',
						},
						{
							tag: tags.definition( tags.variableName ),
							color: '#00f',
						},
						{
							tag: tags.local( tags.variableName ),
							color: '#30a',
						},
						{
							tag: [ tags.typeName, tags.namespace ],
							color: '#085',
						},
						{
							tag: tags.className,
							color: '#167',
						},
						{
							tag: [
								tags.special( tags.variableName ),
								tags.macroName,
							],
							color: '#256',
						},
						{
							tag: tags.definition( tags.propertyName ),
							color: '#00c',
						},
						{
							tag: tags.comment,
							color: '#940',
						},
						{
							tag: tags.meta,
							color: '#7a757a',
						},
						{
							tag: tags.invalid,
							color: '#f00',
						},
					] ),
					bracketMatching(),
					closeBrackets(),
					autocompletion(),
					rectangularSelection(),
					highlightActiveLine(),
					highlightSelectionMatches(),
					EditorView.lineWrapping,
					keymap.of( [
						...closeBracketsKeymap,
						...defaultKeymap,
						...searchKeymap,
						...historyKeymap,
						...commentKeymap,
						...completionKeymap,
						...lintKeymap,
					] ),
				],
				EditorView.updateListener.of( ( v ) => {
					if ( v.docChanged ) {
						textarea.value = view.state.doc.toString();
						textarea.dispatchEvent( new Event( 'input' ) );
					}
				} ),
				...extensions,
			],
		} ),
	} );

	textarea.parentNode!.insertBefore( view.dom, textarea );
	textarea.style.display = 'none';

	return view;
}

const initializeCodeMirror = () => {
	const textarea = document.querySelector(
		'textarea#field_calculation_formula'
	) as HTMLTextAreaElement;

	window.CalculationFormulaCodeMirrorEditorView = editorFromTextArea(
		textarea,
		[ GFCALC() ]
	);

	// @ts-ignore
	document.querySelector( '.gf_calculation_buttons' )!.style.display = 'none';
};

const overrideFormulaValidation = () => {
	const $validateFormulaLink = $(
		'textarea#field_calculation_formula + br + a'
	);

	$validateFormulaLink.attr( 'onclick', '' );
	$validateFormulaLink.attr( 'onkeypress', '' );

	$validateFormulaLink.on( 'click keypress', function ( event ) {
		let hasError = false;

		syntaxTree(
			window.CalculationFormulaCodeMirrorEditorView.state
		).iterate( {
			enter: ( type, from, to ) => {
				if ( type.isError ) {
					hasError = true;
				}
			},
		} );

		if ( hasError ) {
			alert( window.GPAC.strings.formulaError );
		} else {
			alert( window.GPAC.strings.formulaValid );
		}

		event.preventDefault();
	} );
};

const overrideFormulaMergeTagInserter = () => {
	$( '#field_calculation_formula_variable_select' )
		.attr( 'onchange', '' )
		.off( 'change.gpac' )
		.on( 'change.gpac', function ( event ) {
			const variable = $( this ).val() as string;

			const {
				CalculationFormulaCodeMirrorEditorView: cm,
			}: {
				CalculationFormulaCodeMirrorEditorView: EditorView;
			} = window as any;

			/* Current selection */
			const { from, to } = cm.state.selection.main;

			/* Re-focus the editor so selection/cursor is handled correctly. */
			cm.focus();

			cm.dispatch( {
				changes: { from, to, insert: variable },
				selection: { anchor: from + variable.length },
			} );

			event.preventDefault();
		} );
};

const syncFormulaToCodeMirror = ( field: any ) => {
	const cm = window.CalculationFormulaCodeMirrorEditorView;

	cm.dispatch( {
		changes: {
			from: 0,
			to: cm.state.doc.length,
			insert: field?.calculationFormula,
		},
	} );
};

const addCheatsheetLink = () => {
	const $link = $(
		`<a href="javascript:void(0);" style="margin-left: 1rem;">${ window.GPAC.strings.formulaCheatsheet }</a>`
	);

	$link.on( 'keypress click', () => {
		window.tb_show(
			window.GPAC.strings.cheatsheetTitle,
			'#TB_inline?width=650&amp;inlineId=gpac_cheatsheet',
			''
		);
	} );

	$link.insertBefore( $( '.field_calculation_rounding' ) );
};

$( document ).on( 'gform_load_field_settings', ( event, field, form ) => {
	/* Always override on settings load to ensure GF 2.6 AJAX saving is supported. */
	overrideFormulaMergeTagInserter();

	/* Only init once. */
	if (
		document.querySelector( '#calculation_options .cm-editor' ) &&
		window.CalculationFormulaCodeMirrorEditorView
	) {
		syncFormulaToCodeMirror( field );

		return;
	}

	initializeCodeMirror();
	overrideFormulaValidation();
	addCheatsheetLink();
} );
