/**
 * WordPress dependencies
 */
import { useState, useEffect } from '@safe-wordpress/element';
import type { CSSProperties } from 'react';

/**
 * External dependencies
 */
import { langs } from '@uiw/codemirror-extensions-langs';
import CodeMirror, {
	EditorView,
	Decoration,
	StateField,
	RangeSetBuilder,
} from '@uiw/react-codemirror';
import { lintGutter } from '@codemirror/lint';
import { isDefined } from '@nab/utils';

/**
 * Internal dependencies
 */
import type { Props } from './props';

export const PhpEditor = ( {
	className,
	value,
	placeholder,
	before,
	after,
	readOnly,
	onBlur,
	onChange,
	onCreateEditor,
	onFocus,
}: Props ): JSX.Element => {
	const [ buffer, setBuffer ] = useState( '' );
	useEffect( () => {
		if ( buffer !== wrapPhp( value ) ) {
			setBuffer( wrapPhp( value ) );
		}
	}, [ buffer, value ] );

	return (
		<CodeMirror
			className={ className }
			value={ buffer }
			placeholder={ placeholder }
			indentWithTab={ true }
			{ ...{ indentunit: '  ' } }
			theme="light"
			readOnly={ readOnly }
			editable={ ! readOnly }
			onCreateEditor={ onCreateEditor }
			basicSetup={ {
				lineNumbers: false,
				foldGutter: false,
				highlightActiveLine: ! readOnly,
			} }
			extensions={ [
				EditorView.lineWrapping,
				langs.php?.(),
				hidePhpTags,
				...[ readOnly ? [] : [ lintGutter() ] ],
			].filter( isDefined ) }
			onChange={
				readOnly
					? undefined
					: ( nextBuf ) => {
							setBuffer( wrapPhp( nextBuf ) );
							onChange( unwrapPhp( nextBuf ) );
					  }
			}
			onFocus={ onFocus }
			onBlur={ onBlur }
			style={
				{
					'--nab-code-before': JSON.stringify( before ),
					'--nab-code-after': JSON.stringify( after ),
				} as unknown as CSSProperties
			}
		/>
	);
};

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

function wrapPhp( raw: string ) {
	if ( ! raw || raw.trimStart().startsWith( '<?php' ) ) {
		return raw;
	}
	return `<?php\n${ raw }`;
}

function unwrapPhp( buf: string ) {
	let s = buf;
	if ( s.trimStart().startsWith( '<?php' ) ) {
		s = s.trimStart().slice( 5 );
		if ( s.startsWith( '\n' ) ) {
			s = s.slice( 1 );
		}
	}
	return s;
}

const hidePhpTags = StateField.define( {
	create() {
		return Decoration.none;
	},
	update( _deco, tr ) {
		const b = new RangeSetBuilder< Decoration >();
		const doc = tr.state.doc;
		const text = doc.toString();

		// Hide "<?php" (and following newline if present)
		if ( text.startsWith( '<?php' ) ) {
			b.add( 0, 5, Decoration.replace( {} ) ); // "<?php"
			if ( doc.sliceString( 5, 6 ) === '\n' ) {
				b.add( 5, 6, Decoration.replace( {} ) );
			}
		}

		return b.finish();
	},
	provide: ( f ) => EditorView.decorations.from( f ),
} );
