import type { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'
import type { EditorState, TextSelection } from '@tiptap/pm/state'
import type { Component, JSX } from 'solid-js'
import { isTextSelection } from '@tiptap/core'
import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu'
import { Logger } from 'besonders-logger'
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
import { isNeedingSuggestionMenu } from '../data/tagMatcherForAutoComplete'
import { autoCompleteCallback, AutoCompleteMenu, autoCompleteOptions, LinkOpenMenu, setAutoCompleteOptions } from './BlockTree'

const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.DEBUG) // eslint-disable-line unused-imports/no-unused-vars

const defaultShouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({
	view,
	state,
	from,
	to,
}) => {
	const { doc, selection } = state
	const { empty } = selection

	// Sometime check for `empty` is not enough.
	// Doubleclick an empty paragraph returns a node size of 2.
	// So we check also for an empty text size.
	const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection)

	// When clicking on a element inside the bubble menu the editor "blur" event
	// is called and the bubble menu item is focussed. In this case we should
	// consider the menu as part of the editor and keep showing the menu
	const isChildOfMenu = this.element.contains(document.activeElement)

	const hasEditorFocus = view.hasFocus() || isChildOfMenu

	if (!hasEditorFocus || empty || isEmptyTextBlock || !this.editor.isEditable) {
		return false
	}

	return true
}

const createMenuState = () => {
	const [selectionOrAutocomplete, setSelOrAC] = createSignal<null | 'selection' | 'autocomplete' | 'link'>(null)
	const [linkUnderCursor, setLinkUnderCursor] = createSignal<null | Record<string, any>>(null)

	return {
		selectionOrAutocomplete,
		setSelOrAC,
		linkUnderCursor,
		setLinkUnderCursor,
	}
}
type MenuState = ReturnType<typeof createMenuState>

const createShouldShowWhenSelectionOrAutoComplete = ({
	selectionOrAutocomplete,
	setSelOrAC,
	linkUnderCursor,
	setLinkUnderCursor,
}: MenuState): Exclude<BubbleMenuPluginProps['shouldShow'], null> => ({
	editor,
	view,
	state,
	from,
	to,
}) => {
	// Guard against destroyed editor or missing view
	if (editor.isDestroyed || !view?.docView) {
		DEBUG('[TiptapMenuWrapper.shouldShow] Guard blocked:', { isDestroyed: editor.isDestroyed, hasDocView: !!view?.docView })
		return false
	}

	const { doc, selection } = state
	const { empty } = selection

	// HACK: Reset state - otherwise state handling would get messy... but this way we might have double signal trigger 🤷
	if (linkUnderCursor()) setLinkUnderCursor(null)

	if (!editor.isEditable) {
		return false
	}

	// Sometime check for `empty` is not enough.
	// Doubleclick an empty paragraph returns a node size of 2.
	// So we check also for an empty text size.
	const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection)

	// When clicking on a element inside the bubble menu the editor "blur" event
	// is called and the bubble menu item is focussed. In this case we should
	// consider the menu as part of the editor and keep showing the menu
	// const isChildOfMenu = this.element.contains(document.activeElement)

	const hasEditorFocus = view.hasFocus() // || isChildOfMenu

	if (!(!hasEditorFocus || empty || isEmptyTextBlock || !editor.isEditable)) {
		setSelOrAC('selection')
		return true
	}

	const { currentWordInfo } = editor.commands.getWordInfo()
	const currentWordStart = currentWordInfo?.word?.substring(0, 1)
	const needsSuggestions = isNeedingSuggestionMenu(null, currentWordStart)
	VERBOSE({ currentWordInfo, needsSuggestions })
	if (needsSuggestions) {
		const options = autoCompleteOptions()
		DEBUG('[TiptapMenuWrapper] autocomplete trigger', { options: options.length, word: currentWordInfo?.word })
		if (!options.length) {
			setSelOrAC(null)
			return false
		}
		setSelOrAC('autocomplete')
		return true
	} else {
		setAutoCompleteOptions([]) // when i was on a tag and then drag to select, this hides the autocomplete menu
	}

	const linksUnderCursor = tiptapGetLinksUnderCursor({ state })
	if (linksUnderCursor.length) {
		setSelOrAC('link')
		setLinkUnderCursor(linksUnderCursor[0])
		return true
	}

	setSelOrAC(null)
	return false
}

type TiptapMenuWrapperProps
	= & Omit<
		BubbleMenuPluginProps,
		'element' | 'pluginKey' | 'shouldShow'
	>
	& {
		class?: string
		children?: JSX.Element
		shouldShow?: BubbleMenuPluginProps['shouldShow']
	}

const TiptapMenuWrapper: Component<TiptapMenuWrapperProps> = (props) => {
	let ref
	const pluginKey = 'note3-menu' // ? nanoid()
	const menuState = createMenuState()
	const shouldShowWhenSelectionOrAutoComplete = createShouldShowWhenSelectionOrAutoComplete(menuState)

	onMount(() => {
		const { editor, shouldShow, tippyOptions } = props

		// if (ref) {
		editor.registerPlugin(
			BubbleMenuPlugin({
				editor,
				pluginKey,
				shouldShow: shouldShowWhenSelectionOrAutoComplete,
				/* shouldShow,  : (props) => {
					// TODO show when autoComplete is needed
					if (shouldShow) {
						return shouldShow(props)
					}}
					>
					return false
				} */
				element: ref,
				tippyOptions,
			}),
		)
		// }
	})

	onCleanup(() => {
		const { editor } = props
		if (editor && !editor.isDestroyed) {
			editor.unregisterPlugin(pluginKey)
		}
	})

	return (
		<div
			ref={ref}
			class={props.class}
		>
			<Show when={menuState.selectionOrAutocomplete() === 'autocomplete'}>
				<AutoCompleteMenu options={autoCompleteOptions()} callback={autoCompleteCallback()} />
			</Show>
			<Show when={menuState.selectionOrAutocomplete() === 'link'}>
				<LinkOpenMenu link={menuState.linkUnderCursor} />
			</Show>
			<Show when={menuState.selectionOrAutocomplete() === 'selection'}>
				{props.children}
			</Show>
		</div>
	)
}

export { TiptapMenuWrapper }
export type { TiptapMenuWrapperProps }

function tiptapGetLinksUnderCursor({ state }: { state: EditorState }) {
	const { selection, doc } = state
	const cursorPos = (selection as TextSelection).$cursor?.pos ?? null
	if (cursorPos !== null) {
		const node = doc.nodeAt(cursorPos)
		const linkMarks = node?.marks.filter(mark => mark.type.name === 'link')

		if (linkMarks?.length) {
			DEBUG(`[tiptapGetLinkUnderCursor] found`, linkMarks)
			return linkMarks.map(mark => mark.attrs)
		}
	}
	return []
}
