import type { CommandExecutor } from '../utils/commands'

interface KeyboardShortcut {
	key: string
	modifiers?: {
		ctrl?: boolean
		alt?: boolean
		shift?: boolean
	}
	command: string
}

const shortcuts: KeyboardShortcut[] = [
	{ key: 'b', command: 'bold' },
	{ key: 'i', command: 'italic' },
	{ key: 'u', command: 'underline' },
	{ key: 'z', command: 'undo' },
	{ key: 'z', modifiers: { shift: true }, command: 'redo' },
	{ key: 'y', command: 'redo' },
	{ key: '.', modifiers: { shift: true }, command: 'orderedList' },
	{ key: '/', modifiers: { shift: true }, command: 'unorderedList' },
	{ key: ']', command: 'indent' },
	{ key: '[', command: 'outdent' },
	...Array.from({ length: 6 }, (_, i) => ({
		key: String(i + 1),
		modifiers: { alt: true },
		command: `h${i + 1}`
	}))
]

export function useEditorKeyboard(doc: Document, executor: CommandExecutor): void {
	// Handle keyboard shortcuts
	doc.addEventListener('keydown', (e) => {
		// Handle Enter key in lists
		if (e.key === 'Enter' && !e.shiftKey) {
			const selection = doc.getSelection()
			if (!selection || !selection.rangeCount) return

			const range = selection.getRangeAt(0)
			const container = range.commonAncestorContainer
			const listItem = (container.nodeType === 3 ? container.parentElement : container as Element)?.closest('li')

			if (listItem) {
				// If we're at the end of a list item
				if (range.collapsed && isAtEndOfNode(listItem, range)) {
					// If the list item is empty, break out of the list
					if (isNodeEmpty(listItem)) {
						e.preventDefault()
						// Create a new paragraph after the list
						const list = listItem.parentElement
						if (!list) return

						// Remove the empty list item
						listItem.remove()

						// If the list is now empty, remove it
						if (!list.querySelector('li')) {
							const p = doc.createElement('p')
							p.innerHTML = '<br>'
							list.parentNode?.replaceChild(p, list)

							// Set cursor in the new paragraph
							range.selectNodeContents(p)
							range.collapse(true)
							selection.removeAllRanges()
							selection.addRange(range)
						}
					} else {
						// Create a new list item
						e.preventDefault()
						const newLi = doc.createElement('li')
						newLi.innerHTML = '&nbsp;<br>' // Use non-breaking space with br
						listItem.insertAdjacentElement('afterend', newLi)

						// Move cursor to new list item
						range.selectNodeContents(newLi)
						range.collapse(true)
						selection.removeAllRanges()
						selection.addRange(range)
					}
				}
			}
		}

		// Handle other keyboard shortcuts
		if (!e.ctrlKey && !e.metaKey) return

		const matchingShortcut = shortcuts.find((shortcut) => {
			const keyMatch = shortcut.key === e.key
			const modifiersMatch = !shortcut.modifiers || (
				(!shortcut.modifiers.ctrl || e.ctrlKey || e.metaKey)
				&& (!shortcut.modifiers.alt || e.altKey)
				&& (!shortcut.modifiers.shift || e.shiftKey)
			)
			return keyMatch && modifiersMatch
		})

		if (matchingShortcut) {
			e.preventDefault()
			executor.execute(matchingShortcut.command)
		}
	})
}

// Helper function to check if we're at the end of a node
function isAtEndOfNode(node: Node, range: Range): boolean {
	if (node.nodeType === 3) { // Text node
		return range.startOffset === (node as Text).length
	}

	const { lastChild } = node
	if (!lastChild) return true

	if (lastChild.nodeType === 3) { // Text node
		return range.startContainer === lastChild && range.startOffset === lastChild.textContent?.length
	}

	return range.startContainer === node && range.startOffset === node.childNodes.length
}

// Helper function to check if a node is empty (contains only whitespace or <br> or &nbsp;)
function isNodeEmpty(node: Node): boolean {
	const text = node.textContent?.replace(/\s/g, '') || '' // Remove non-breaking spaces and whitespace
	if (text) return false

	// Check for <br> tags
	const brElements = (node as Element).getElementsByTagName('br')
	if (brElements.length === 0) return true

	// If there's only one <br> and it's the only content (besides potential &nbsp;), consider it empty
	return brElements.length === 1 && node.childNodes.length <= 2 // Allow for &nbsp; + <br>
}
