import * as React from 'react';
import { ForwardedRef, RefObject } from 'react';
import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import { EditorProps, LogMessageCallback, OnEventCallback } from '@joplin/editor/types';
import createEditor from '@joplin/editor/CodeMirror/createEditor';
import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import setupVim from '@joplin/editor/CodeMirror/utils/setupVim';
import useKeymap from './utils/useKeymap';
import CommandService from '@joplin/lib/services/CommandService';
import { SearchMarkers } from '../../../utils/useSearchMarkers';
import localisation from './utils/localisation';
import Resource from '@joplin/lib/models/Resource';
import { parseResourceUrl } from '@joplin/lib/urlUtils';
import { resourceFilename } from '@joplin/lib/models/utils/resourceUtils';
import getResourceBaseUrl from '../../../utils/getResourceBaseUrl';
import useContentScriptRegistration from './utils/useContentScriptRegistration';

interface Props extends EditorProps {
	style: React.CSSProperties;
	pluginStates: PluginStates;
	initialSelectionRef: RefObject<number>;

	onEditorPaste: (event: Event)=> void;
	externalSearch: SearchMarkers;
	useLocalSearch: boolean;
}

const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
	const editorContainerRef = useRef<HTMLDivElement|null>(null);
	const [editor, setEditor] = useState<CodeMirrorControl|null>(null);

	// The editor will only be created once, so callbacks that could
	// change need to be stored as references.
	const onEventRef = useRef<OnEventCallback>(props.onEvent);
	const onLogMessageRef = useRef<LogMessageCallback>(props.onLogMessage);

	useEffect(() => {
		onEventRef.current = props.onEvent;
		onLogMessageRef.current = props.onLogMessage;
	}, [props.onEvent, props.onLogMessage]);

	useEffect(() => {
		if (!editor) {
			return () => {};
		}

		const pasteEventHandler = (_editor: unknown, ...args: unknown[]) => {
			const event = args[0] as Event;
			props.onEditorPaste(event);
		};

		editor.on('paste', pasteEventHandler);

		return () => {
			editor.off('paste', pasteEventHandler);
		};
	}, [editor, props.onEditorPaste]);

	useImperativeHandle(ref, () => {
		return editor;
	}, [editor]);

	useContentScriptRegistration({ editor, pluginStates: props.pluginStates });

	useEffect(() => {
		if (!editorContainerRef.current) return () => {};

		const editorProps: EditorProps = {
			...props,
			localisations: localisation(),
			onEvent: event => onEventRef.current(event),
			onLogMessage: message => onLogMessageRef.current(message),
		};

		const editor = createEditor(editorContainerRef.current, {
			...editorProps,
			resolveImageSrc: async (src, reloadCounter) => {
				const url = parseResourceUrl(src);
				if (!url.itemId) return null;
				const item = await Resource.load(url.itemId);
				if (!item) return null;
				return `${getResourceBaseUrl()}/${resourceFilename(item)}${reloadCounter ? `?r=${reloadCounter}` : ''}`;
			},
		});
		editor.addStyles({
			'.cm-scroller': { overflow: 'auto' },
			'&.CodeMirror': {
				height: 'unset',
				background: 'unset',
				overflow: 'unset',
				direction: 'unset',
			},
		});
		const cursor = props.initialSelectionRef.current;
		editor.select(cursor, cursor);

		setEditor(editor);

		return () => {
			editor.remove();
		};
	// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Should run just once
	}, []);

	useEffect(() => {
		if (!editor) {
			return;
		}

		const searchState = editor.getSearchState();
		const externalSearchText = props.externalSearch.keywords.map(k => k.value).join(' ') || searchState.searchText;

		if (externalSearchText === searchState.searchText && searchState.dialogVisible === props.useLocalSearch) {
			return;
		}

		editor.setSearchState({
			...searchState,
			dialogVisible: props.useLocalSearch,
			searchText: externalSearchText,
		});
	}, [editor, props.externalSearch, props.useLocalSearch]);

	const theme = props.settings.themeData;
	useEffect(() => {
		if (!editor) return () => {};

		const styles = editor.addStyles({
			'& .cm-search-marker *, & .cm-search-marker': {
				color: theme.searchMarkerColor,
				backgroundColor: theme.searchMarkerBackgroundColor,
			},
			'& .cm-search-marker-selected *, & .cm-search-marker-selected': {
				background: `${theme.selectedColor2} !important`,
				color: `${theme.color2} !important`,
			},
		});

		return () => {
			styles.remove();
		};
	}, [editor, theme]);

	useEffect(() => {
		editor?.updateSettings(props.settings);
	}, [props.settings, editor]);

	useEffect(() => {
		if (!editor) {
			return;
		}

		setupVim(editor, {
			sync: () => {
				void CommandService.instance().execute('synchronize');
			},
		});
	}, [editor]);

	useKeymap(editor);

	return (
		<div
			style={props.style}
			ref={editorContainerRef}
		></div>
	);
};

export default forwardRef(Editor);
