/**
 * WordPress dependencies
 */
import { BaseControl } from '@safe-wordpress/components';
import { _x } from '@safe-wordpress/i18n';

/**
 * External dependencies
 */
import { CompletionContext } from '@codemirror/autocomplete';
import type { TSESTree, TSESLint } from '@typescript-eslint/utils';

import { CodeEditor, ConversionActionScope, ErrorText } from '@nab/components';
import type { CAEditProps } from '@nab/types';

/**
 * Internal dependencies
 */
import type { Attributes } from './types';

export const Edit = ( {
	attributes: { snippet },
	scope,
	errors,
	setAttributes,
	setScope,
}: CAEditProps< Attributes > ): JSX.Element => (
	<>
		<BaseControl help={ <ErrorText value={ errors.snippet } /> }>
			<CodeEditor
				language="javascript"
				withBorder
				value={ snippet }
				onChange={ ( value ) => setAttributes( { snippet: value } ) }
				placeholder={ HELP }
				config={ {
					completions: [ customCompletions ],
					globals: [ 'convert', 'utils' ],
					rules: {
						'detect-convert': {
							level: 'error',
							module: detectConvertRule,
						},
					},
				} }
			/>
		</BaseControl>

		<ConversionActionScope
			scope={ scope }
			setScope={ setScope }
			error={ errors._scope }
		/>
	</>
);

// =======
// HELPERS
// =======

const detectConvertRule: TSESLint.RuleModule< 'missingConvert', [] > = {
	meta: {
		type: 'problem',
		docs: {
			description: _x(
				'Ensure convert() is called',
				'user',
				'nelio-ab-testing'
			),
		},
		messages: {
			missingConvert: _x(
				'The function convert() must be called at least once.',
				'text',
				'nelio-ab-testing'
			),
		},
		schema: [], // No schema, no options
	},
	defaultOptions: [],
	create( context ) {
		let hasConvertCall = false;

		return {
			CallExpression( node: TSESTree.CallExpression ) {
				if (
					// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
					node.callee.type === 'Identifier' &&
					node.callee.name === 'convert'
				) {
					hasConvertCall = true;
				}
			},
			'Program:exit'() {
				if ( ! hasConvertCall ) {
					context.report( {
						loc: { line: 1, column: 0 },
						messageId: 'missingConvert',
					} );
				}
			},
		};
	},
};

function customCompletions( context: CompletionContext ) {
	const utils = context.matchBefore( /utils\.\w*/ );
	if ( utils ) {
		return {
			from: utils.text.replace( /\w*$/, '' ).length + utils.from,
			options: [
				{
					label: 'onVariantReady',
					type: 'function',
					apply: 'onVariantReady(() => {\n});',
					detail: '(callback:Function) => void',
					info: _x(
						'Runs callback when variant is ready.',
						'text',
						'nelio-ab-testing'
					),
				},
			],
		};
	}

	const word = context.matchBefore( /\w*/ );
	if ( ! word ) {
		return null;
	}

	if ( word.from === word.to && ! context.explicit ) {
		return null;
	}

	return {
		from: word.from,
		options: [
			{
				label: 'convert',
				type: 'function',
				apply: 'convert()',
				detail: '() => void',
				info: _x(
					'Triggers a conversion using the appropriate variant and goal settings.',
					'text',
					'nelio-ab-testing'
				),
			},
			{
				label: 'utils',
				type: 'variable',
				apply: 'utils',
				detail: 'Object',
				info: _x(
					'Contains several helper functions.',
					'text',
					'nelio-ab-testing'
				),
			},
		],
	};
}

const HELP =
	_x(
		'Write the logic for your conversion here and call “convert()” to actually trigger the conversion when necessary.',
		'user',
		'nelio-ab-testing'
	) +
	' ' +
	_x(
		'Please keep in mind the code will not run until the DOM’s ready.',
		'user',
		'nelio-ab-testing'
	);
