import type { EditorState } from '../richTextTypes'
import { insertLink } from './media'
import { addRow, deleteRow, mergeCells, splitCell, insertTable, deleteTable, insertColumn, deleteColumn, alignColumn } from './table'

export interface Command {
	name: string
	execute: (state: EditorState, value?: string) => void
	isActive?: (state: EditorState) => boolean
	getValue?: (state: EditorState) => string | null
}

export interface CommandRegistry {
	[key: string]: Command
}

export interface CommandExecutor {
	execute: (command: string, value?: string) => void
	isActive: (command: string) => boolean
	getValue: (command: string) => string | null
}

// Helper functions
function getCurrentSelection(state: EditorState): { selection: Selection, range: Range } | null {
	if (!state.doc) { return null }

	const selection = state.doc.getSelection()
	if (!selection || !selection.rangeCount) { return null }

	const range = selection.getRangeAt(0)
	return { selection, range }
}

// Alternative helper function to apply formatting to word at cursor
function applyFormattingToWordAtCursor(doc: Document, range: Range, tagName: string): boolean {
	if (!range.collapsed) { return false }

	let textNode = range.startContainer
	let offset = range.startOffset

	// If we're in an element node, try to find a text node
	if (textNode.nodeType !== Node.TEXT_NODE) {
		const element = textNode as Element
		if (element.childNodes.length > 0 && offset < element.childNodes.length) {
			const childNode = element.childNodes[offset]
			if (childNode.nodeType === Node.TEXT_NODE) {
				textNode = childNode
				offset = 0
			} else {
				return false
			}
		} else {
			return false
		}
	}

	// Check if we're already inside a formatting element
	const { parentElement } = textNode
	const existingFormatElement = parentElement?.closest(tagName.toLowerCase())

	if (existingFormatElement) {
		// Remove existing formatting
		try {
			const parent = existingFormatElement.parentNode
			if (parent) {
				// Move all children of the format element to its parent
				while (existingFormatElement.firstChild) {
					parent.insertBefore(existingFormatElement.firstChild, existingFormatElement)
				}
				parent.removeChild(existingFormatElement)

				// Position cursor at the same relative position
				const newRange = doc.createRange()
				newRange.setStart(textNode, offset)
				newRange.collapse(true)

				const selection = doc.getSelection()
				if (selection) {
					selection.removeAllRanges()
					selection.addRange(newRange)
				}

				return true
			}
		} catch (error) {
			console.error('Error removing formatting from word:', error)
			return false
		}
	}

	const text = textNode.textContent || ''
	if (!text) { return false }

	// Find word boundaries - support Hebrew, English, and numbers
	let start = offset
	let end = offset

	// Regular expression for word characters (Hebrew, English, numbers)
	const wordChar = /[\u0590-\u05FF\u0600-\u06FF\w]/

	// If we're at the end of a word (cursor right after the last character)
	// move back one position to include that word
	if (offset > 0 && wordChar.test(text[offset - 1])
		&& (offset >= text.length || !wordChar.test(text[offset]))) {
		start = offset - 1
		end = offset - 1
	}

	// Move start backwards to find word beginning
	while (start > 0 && wordChar.test(text[start - 1])) {
		start--
	}

	// Move end forwards to find word ending
	while (end < text.length && wordChar.test(text[end])) {
		end++
	}

	// If we found a word, apply formatting
	if (start < end && end > start) {
		try {
			// Split the text node into three parts: before, word, after
			const beforeText = text.substring(0, start)
			const wordText = text.substring(start, end)
			const afterText = text.substring(end)

			// Create the formatting element
			const formatElement = doc.createElement(tagName)
			formatElement.textContent = wordText

			// Replace the text node with the new structure
			const parent = textNode.parentNode
			if (parent) {
				// Create text nodes for before and after
				if (beforeText) {
					const beforeNode = doc.createTextNode(beforeText)
					parent.insertBefore(beforeNode, textNode)
				}

				parent.insertBefore(formatElement, textNode)

				if (afterText) {
					const afterNode = doc.createTextNode(afterText)
					parent.insertBefore(afterNode, textNode)
				}

				parent.removeChild(textNode)

				// Position cursor after the formatted word
				const newRange = doc.createRange()
				newRange.setStartAfter(formatElement)
				newRange.collapse(true)

				const selection = doc.getSelection()
				if (selection) {
					selection.removeAllRanges()
					selection.addRange(newRange)
				}

				return true
			}
		} catch (error) {
			console.error('Error applying formatting to word:', error)
		}
	}

	return false
}

function updateStateAfterCommand(state: EditorState) {
	if (!state.doc) { return }

	// Update content
	state.content = state.doc.body.innerHTML

	// Update selection state
	const selection = state.doc.getSelection()
	if (selection && selection.rangeCount > 0) {
		state.selection = selection
		state.range = selection.getRangeAt(0).cloneRange()
		state.rangeCount = selection.rangeCount
	}
}

function findBlockElement(node: Node): Element | null {
	let current = node.nodeType === Node.TEXT_NODE ? node.parentElement : node as Element
	while (current) {
		if (current.matches && current.matches('p,h1,h2,h3,h4,h5,h6,blockquote,li,div')) {
			return current
		}
		current = current.parentElement
	}
	return null
}

// Centralized command creation helper
function createCommand(name: string, execute: Command['execute'], isActive?: Command['isActive']): Command {
	return {
		name,
		execute: (state: EditorState, value?: string) => {
			if (!state.doc) { return }
			execute(state, value)
			updateStateAfterCommand(state)
		},
		isActive
	}
}

export function createCommandRegistry(state: EditorState): CommandRegistry {
	// History commands
	const historyCommands = {
		undo: createCommand('Undo', () => {
			if (state.undoStack.length > 0 && state.doc) {
				const lastContent = state.undoStack.pop()
				if (lastContent !== undefined) {
					state.redoStack.push(state.content)
					state.content = lastContent
					state.doc.body.innerHTML = lastContent
				}
			}
		}),
		redo: createCommand('Redo', () => {
			if (state.redoStack.length > 0 && state.doc) {
				const nextContent = state.redoStack.pop()
				if (nextContent !== undefined) {
					state.undoStack.push(state.content)
					state.content = nextContent
					state.doc.body.innerHTML = nextContent
				}
			}
		})
	}

	// Basic text formatting commands with toggle functionality
	const textCommands = {
		bold: createCommand('Bold', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			let { range, selection } = selectionInfo

			// If no text is selected, try to apply formatting to word at cursor
			if (range.collapsed) {
				const success = applyFormattingToWordAtCursor(state.doc!, range, 'b')
				if (success) { return }
				return
			}

			// Check if the selection is already bold
			const commonAncestor = range.commonAncestorContainer
			const parentElement = commonAncestor.nodeType === Node.TEXT_NODE
				? commonAncestor.parentElement
				: commonAncestor as Element

			const boldElement = parentElement?.closest('b, strong')

			if (boldElement) {
				// Remove bold formatting
				try {
					const parent = boldElement.parentNode
					while (boldElement.firstChild) {
						parent?.insertBefore(boldElement.firstChild, boldElement)
					}
					parent?.removeChild(boldElement)

					// Restore selection
					selection.removeAllRanges()
					selection.addRange(range)
				} catch (error) {
					console.error('Error removing bold:', error)
				}
			} else {
				// Apply bold formatting
				try {
					const b = state.doc!.createElement('b')

					try {
						range.surroundContents(b)
					} catch {
						const fragment = range.extractContents()
						b.appendChild(fragment)
						range.insertNode(b)
					}

					range.selectNodeContents(b)
					selection.removeAllRanges()
					selection.addRange(range)
				} catch (error) {
					console.error('Error applying bold:', error)
				}
			}
		}, (state) => {
			// isActive function for bold
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const commonAncestor = selectionInfo.range.commonAncestorContainer
			const parentElement = commonAncestor.nodeType === Node.TEXT_NODE
				? commonAncestor.parentElement
				: commonAncestor as Element

			return !!parentElement?.closest('b, strong')
		}),

		italic: createCommand('Italic', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			let { range, selection } = selectionInfo

			// If no text is selected, try to apply formatting to word at cursor
			if (range.collapsed) {
				const success = applyFormattingToWordAtCursor(state.doc!, range, 'i')
				if (success) { return }
				return
			}

			// Check if the selection is already italic
			const commonAncestor = range.commonAncestorContainer
			const parentElement = commonAncestor.nodeType === Node.TEXT_NODE
				? commonAncestor.parentElement
				: commonAncestor as Element

			const italicElement = parentElement?.closest('i, em')

			if (italicElement) {
				// Remove italic formatting
				try {
					const parent = italicElement.parentNode
					while (italicElement.firstChild) {
						parent?.insertBefore(italicElement.firstChild, italicElement)
					}
					parent?.removeChild(italicElement)

					// Restore selection
					selection.removeAllRanges()
					selection.addRange(range)
				} catch (error) {
					console.error('Error removing italic:', error)
				}
			} else {
				// Apply italic formatting
				try {
					const i = state.doc!.createElement('i')

					try {
						range.surroundContents(i)
					} catch {
						const fragment = range.extractContents()
						i.appendChild(fragment)
						range.insertNode(i)
					}

					range.selectNodeContents(i)
					selection.removeAllRanges()
					selection.addRange(range)
				} catch (error) {
					console.error('Error applying italic:', error)
				}
			}
		}, (state) => {
			// isActive function for italic
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const commonAncestor = selectionInfo.range.commonAncestorContainer
			const parentElement = commonAncestor.nodeType === Node.TEXT_NODE
				? commonAncestor.parentElement
				: commonAncestor as Element

			return !!parentElement?.closest('i, em')
		}),

		underline: createCommand('Underline', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			let { range, selection } = selectionInfo

			// If no text is selected, try to apply formatting to word at cursor
			if (range.collapsed) {
				const success = applyFormattingToWordAtCursor(state.doc!, range, 'u')
				if (success) { return }
				return
			}

			// Check if the selection is already underlined
			const commonAncestor = range.commonAncestorContainer
			const parentElement = commonAncestor.nodeType === Node.TEXT_NODE
				? commonAncestor.parentElement
				: commonAncestor as Element

			const underlineElement = parentElement?.closest('u')

			if (underlineElement) {
				// Remove underline formatting
				try {
					const parent = underlineElement.parentNode
					while (underlineElement.firstChild) {
						parent?.insertBefore(underlineElement.firstChild, underlineElement)
					}
					parent?.removeChild(underlineElement)

					// Restore selection
					selection.removeAllRanges()
					selection.addRange(range)
				} catch (error) {
					console.error('Error removing underline:', error)
				}
			} else {
				// Apply underline formatting
				try {
					const u = state.doc!.createElement('u')

					try {
						range.surroundContents(u)
					} catch {
						const fragment = range.extractContents()
						u.appendChild(fragment)
						range.insertNode(u)
					}

					range.selectNodeContents(u)
					selection.removeAllRanges()
					selection.addRange(range)
				} catch (error) {
					console.error('Error applying underline:', error)
				}
			}
		}, (state) => {
			// isActive function for underline
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const commonAncestor = selectionInfo.range.commonAncestorContainer
			const parentElement = commonAncestor.nodeType === Node.TEXT_NODE
				? commonAncestor.parentElement
				: commonAncestor as Element

			return !!parentElement?.closest('u')
		}),

		link: createCommand('Link', (state) => {
			const { openLinkModal } = state as any
			if (openLinkModal) {
				insertLink(openLinkModal, state)
			}
		}, (state) => {
			// isActive function for link
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const commonAncestor = selectionInfo.range.commonAncestorContainer
			const parentElement = commonAncestor.nodeType === Node.TEXT_NODE
				? commonAncestor.parentElement
				: commonAncestor as Element

			return !!parentElement?.closest('a')
		})
	}

	// Heading commands
	const headingCommands = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].reduce((acc, cmd) => ({
		...acc,
		[cmd]: createCommand(`Heading ${cmd}`, (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range, selection } = selectionInfo
			const currentBlock = findBlockElement(range.commonAncestorContainer)
			if (!currentBlock) { return }

			// Store the original selection information before modification
			const { startContainer } = range
			const { endContainer } = range
			const { startOffset } = range
			const { endOffset } = range
			const isCollapsed = range.collapsed

			// Check if we need to toggle off (already in the same heading)
			const isToggleOff = currentBlock.tagName.toLowerCase() === cmd.toLowerCase()
			const newTag = isToggleOff ? 'p' : cmd

			try {
				const newBlock = state.doc!.createElement(newTag)

				// Copy content from current block to new block
				while (currentBlock.firstChild) {
					newBlock.appendChild(currentBlock.firstChild)
				}

				// Replace the old block with the new one
				currentBlock.parentNode?.replaceChild(newBlock, currentBlock)

				// Restore selection within the new block
				try {
					const newRange = state.doc!.createRange()

					// Find corresponding nodes in the new block
					const findCorrespondingNode = (originalNode: Node, originalBlock: Element, newBlock: Element): Node | null => {
						if (originalNode === originalBlock) { return newBlock }

						// If it's a text node, find it by traversing the tree
						if (originalNode.nodeType === Node.TEXT_NODE) {
							const walker = state.doc!.createTreeWalker(
								newBlock,
								NodeFilter.SHOW_TEXT,
								null
							)

							let textNodeIndex = 0
							const originalWalker = state.doc!.createTreeWalker(
								originalBlock,
								NodeFilter.SHOW_TEXT,
								null
							)

							let currentOriginalNode = originalWalker.nextNode()
							while (currentOriginalNode && currentOriginalNode !== originalNode) {
								textNodeIndex++
								currentOriginalNode = originalWalker.nextNode()
							}

							let currentNewNode = walker.nextNode()
							let currentIndex = 0
							while (currentNewNode && currentIndex < textNodeIndex) {
								currentIndex++
								currentNewNode = walker.nextNode()
							}

							return currentNewNode
						}

						return newBlock.firstChild
					}

					const newStartContainer = findCorrespondingNode(startContainer, currentBlock, newBlock)
					const newEndContainer = findCorrespondingNode(endContainer, currentBlock, newBlock)

					if (newStartContainer && newEndContainer) {
						// Set start position
						if (newStartContainer.nodeType === Node.TEXT_NODE) {
							const maxOffset = Math.min(startOffset, newStartContainer.textContent?.length || 0)
							newRange.setStart(newStartContainer, maxOffset)
						} else {
							newRange.setStart(newStartContainer, 0)
						}

						// Set end position
						if (!isCollapsed) {
							if (newEndContainer.nodeType === Node.TEXT_NODE) {
								const maxOffset = Math.min(endOffset, newEndContainer.textContent?.length || 0)
								newRange.setEnd(newEndContainer, maxOffset)
							} else {
								newRange.setEnd(newEndContainer, 0)
							}
						} else {
							newRange.collapse(true)
						}

						// Apply the restored selection
						selection.removeAllRanges()
						selection.addRange(newRange)
					} else {
						// Fallback: select all content in the new block
						newRange.selectNodeContents(newBlock)
						if (isCollapsed) {
							newRange.collapse(true)
						}
						selection.removeAllRanges()
						selection.addRange(newRange)
					}
				} catch (selectionError) {
					console.warn('Error restoring selection after heading change:', selectionError)
					// Fallback: place cursor at start of the new block
					const fallbackRange = state.doc!.createRange()
					fallbackRange.selectNodeContents(newBlock)
					fallbackRange.collapse(true)
					selection.removeAllRanges()
					selection.addRange(fallbackRange)
				}

				// If we created a heading (not toggling off), ensure there's a paragraph after it
				if (!isToggleOff) {
					const nextSibling = newBlock.nextElementSibling
					if (!nextSibling) {
						// No element after the heading, create a paragraph
						const p = state.doc!.createElement('p')
						p.dir = newBlock.dir || state.doc!.body.dir
						newBlock.parentNode?.insertBefore(p, newBlock.nextSibling)
					}
				}
			} catch (error) {
				console.error(`Error applying ${cmd} heading:`, error)
			}
		}),
	}), {})

	// Block commands
	const blockCommands = {
		p: createCommand('Paragraph', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const currentBlock = findBlockElement(range.commonAncestorContainer)
			if (!currentBlock) { return }

			// Check if already a paragraph - if so, nothing to do
			if (currentBlock.tagName.toLowerCase() === 'p') {
				return
			}

			try {
				const newParagraph = state.doc!.createElement('p')

				// Copy content from current block to paragraph
				while (currentBlock.firstChild) {
					newParagraph.appendChild(currentBlock.firstChild)
				}

				// Replace the old block with the paragraph
				currentBlock.parentNode?.replaceChild(newParagraph, currentBlock)

				// Move cursor into the new paragraph
				range.selectNodeContents(newParagraph)
				range.collapse(false)
				selectionInfo.selection.removeAllRanges()
				selectionInfo.selection.addRange(range)
			} catch (error) {
				console.error('Error applying paragraph:', error)
			}
		}),

		blockquote: createCommand('Blockquote', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const currentBlock = findBlockElement(range.commonAncestorContainer)
			if (!currentBlock) { return }

			// Check if already a blockquote - if so, toggle to paragraph
			const isToggleOff = currentBlock.tagName.toLowerCase() === 'blockquote'
			const newTag = isToggleOff ? 'p' : 'blockquote'

			try {
				const newBlock = state.doc!.createElement(newTag)

				// Copy content from current block to new block
				while (currentBlock.firstChild) {
					newBlock.appendChild(currentBlock.firstChild)
				}

				// Replace the old block with the new one
				currentBlock.parentNode?.replaceChild(newBlock, currentBlock)

				// Move cursor into the new block
				range.selectNodeContents(newBlock)
				range.collapse(false)
				selectionInfo.selection.removeAllRanges()
				selectionInfo.selection.addRange(range)
			} catch (error) {
				console.error('Error applying blockquote:', error)
			}
		})
	}

	// List commands - simplified implementation
	const listCommands = {
		orderedList: createCommand('Ordered List', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const currentBlock = findBlockElement(range.commonAncestorContainer)
			if (!currentBlock) { return }

			// Check if we're inside a list item
			const listItem = currentBlock.closest('li')
			if (listItem) {
				const listParent = listItem.parentElement as HTMLElement
				if (!listParent) { return }

				// If it's already an ordered list, toggle off to paragraph
				if (listParent.tagName.toLowerCase() === 'ol') {
					const p = state.doc!.createElement('p')
					while (listItem.firstChild) {
						p.appendChild(listItem.firstChild)
					}
					listParent.parentNode?.insertBefore(p, listParent)
					listItem.remove()

					if (!listParent.querySelector('li')) {
						listParent.remove()
					}

					range.selectNodeContents(p)
					range.collapse(false)
					selectionInfo.selection.removeAllRanges()
					selectionInfo.selection.addRange(range)
					return
				}

				// Convert unordered list to ordered list
				if (listParent.tagName.toLowerCase() === 'ul') {
					const ol = state.doc!.createElement('ol')

					// Store current cursor position before conversion
					const currentOffset = range.startOffset
					const currentContainer = range.startContainer

					while (listParent.firstChild) {
						ol.appendChild(listParent.firstChild)
					}
					listParent.parentNode?.replaceChild(ol, listParent)

					// Restore cursor position
					try {
						const newRange = state.doc!.createRange()
						newRange.setStart(currentContainer, currentOffset)
						newRange.collapse(true)
						selectionInfo.selection.removeAllRanges()
						selectionInfo.selection.addRange(newRange)
					} catch {
						// Fallback: put cursor at beginning of first list item
						const firstLi = ol.querySelector('li')
						if (firstLi) {
							const newRange = state.doc!.createRange()
							newRange.selectNodeContents(firstLi)
							newRange.collapse(true)
							selectionInfo.selection.removeAllRanges()
							selectionInfo.selection.addRange(newRange)
						}
					}
					return
				}
			}

			// Convert current block to ordered list
			try {
				const ol = state.doc!.createElement('ol')
				const li = state.doc!.createElement('li')

				// Copy content to list item
				while (currentBlock.firstChild) {
					li.appendChild(currentBlock.firstChild)
				}

				ol.appendChild(li)
				currentBlock.parentNode?.replaceChild(ol, currentBlock)

				// Move cursor into the list item
				range.selectNodeContents(li)
				range.collapse(false)
				selectionInfo.selection.removeAllRanges()
				selectionInfo.selection.addRange(range)
			} catch (error) {
				console.error('Error creating ordered list:', error)
			}
		}),

		unorderedList: createCommand('Unordered List', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const currentBlock = findBlockElement(range.commonAncestorContainer)
			if (!currentBlock) { return }

			// Check if we're inside a list item
			const listItem = currentBlock.closest('li')
			if (listItem) {
				const listParent = listItem.parentElement as HTMLElement
				if (!listParent) { return }

				// If it's already an unordered list, toggle off to paragraph
				if (listParent.tagName.toLowerCase() === 'ul') {
					const p = state.doc!.createElement('p')
					while (listItem.firstChild) {
						p.appendChild(listItem.firstChild)
					}
					listParent.parentNode?.insertBefore(p, listParent)
					listItem.remove()

					if (!listParent.querySelector('li')) {
						listParent.remove()
					}

					range.selectNodeContents(p)
					range.collapse(false)
					selectionInfo.selection.removeAllRanges()
					selectionInfo.selection.addRange(range)
					return
				}

				// Convert ordered list to unordered list
				if (listParent.tagName.toLowerCase() === 'ol') {
					const ul = state.doc!.createElement('ul')

					// Store current cursor position before conversion
					const currentOffset = range.startOffset
					const currentContainer = range.startContainer

					while (listParent.firstChild) {
						ul.appendChild(listParent.firstChild)
					}
					listParent.parentNode?.replaceChild(ul, listParent)

					// Restore cursor position
					try {
						const newRange = state.doc!.createRange()
						newRange.setStart(currentContainer, currentOffset)
						newRange.collapse(true)
						selectionInfo.selection.removeAllRanges()
						selectionInfo.selection.addRange(newRange)
					} catch (e) {
						// Fallback: put cursor at beginning of first list item
						console.warn('Could not restore cursor position:', e)
						const firstLi = ul.querySelector('li')
						if (firstLi) {
							const newRange = state.doc!.createRange()
							newRange.selectNodeContents(firstLi)
							newRange.collapse(true)
							selectionInfo.selection.removeAllRanges()
							selectionInfo.selection.addRange(newRange)
						}
					}
					return
				}
			}

			// Convert current block to unordered list
			try {
				const ul = state.doc!.createElement('ul')
				const li = state.doc!.createElement('li')

				// Copy content to list item
				while (currentBlock.firstChild) {
					li.appendChild(currentBlock.firstChild)
				}

				ul.appendChild(li)
				currentBlock.parentNode?.replaceChild(ul, currentBlock)

				// Move cursor into the list item
				range.selectNodeContents(li)
				range.collapse(false)
				selectionInfo.selection.removeAllRanges()
				selectionInfo.selection.addRange(range)
			} catch (error) {
				console.error('Error creating unordered list:', error)
			}
		})
	}

	// Clear formatting command - enhanced
	const formatCommands = {
		clear: createCommand('Clear Formatting', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range, selection } = selectionInfo

			try {
				// Function to clean text from unwanted characters
				function cleanText(text: string): string {
					return text
						// Remove zero-width characters
						.replace(/[\u200B-\u200D\uFEFF]/g, '')
						// Remove extra whitespace
						.replace(/\s+/g, ' ')
						// Remove invisible characters
						.replace(/\u00A0/g, ' ') // non-breaking space to regular space
						.trim()
				}

				// Function to clean a node recursively
				function cleanNode(node: Node): Node {
					if (node.nodeType === Node.TEXT_NODE) {
						const cleanedText = cleanText(node.textContent || '')
						return state.doc!.createTextNode(cleanedText)
					}

					if (node.nodeType === Node.ELEMENT_NODE) {
						const element = node as Element
						const tagName = element.tagName.toLowerCase()

						// Preserve structural elements, links, and media
						const structuralTags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'blockquote', 'div', 'table', 'tr', 'td', 'th', 'thead', 'tbody', 'b', 'i', 'u', 'a', 'img', 'iframe', 'video', 'audio', 'figure', 'figcaption']
						// Remove formatting elements (excluding basic HTML formatting tags)
						const formattingTags = ['strong', 'em', 'span', 'font', 'strike', 'sub', 'sup', 'mark', 'del', 'ins', 'small', 'big']

						if (formattingTags.includes(tagName)) {
							// For formatting elements, just return the cleaned text content
							const cleanedText = cleanText(element.textContent || '')
							return state.doc!.createTextNode(cleanedText)
						} if (structuralTags.includes(tagName)) {
							// For structural elements, keep the element but remove all styling attributes
							const cleanElement = state.doc!.createElement(tagName)

							// Only preserve essential attributes
							if (tagName === 'a' && element.hasAttribute('href')) {
								cleanElement.setAttribute('href', element.getAttribute('href') || '')
								if (element.hasAttribute('target')) {
									cleanElement.setAttribute('target', element.getAttribute('target') || '')
								}
								if (element.hasAttribute('rel')) {
									cleanElement.setAttribute('rel', element.getAttribute('rel') || '')
								}
							} else if (tagName === 'img') {
								if (element.hasAttribute('src')) cleanElement.setAttribute('src', element.getAttribute('src') || '')
								if (element.hasAttribute('alt')) cleanElement.setAttribute('alt', element.getAttribute('alt') || '')
								if (element.hasAttribute('width')) cleanElement.setAttribute('width', element.getAttribute('width') || '')
								if (element.hasAttribute('height')) cleanElement.setAttribute('height', element.getAttribute('height') || '')
							} else if (tagName === 'iframe') {
								if (element.hasAttribute('src')) cleanElement.setAttribute('src', element.getAttribute('src') || '')
								if (element.hasAttribute('width')) cleanElement.setAttribute('width', element.getAttribute('width') || '')
								if (element.hasAttribute('height')) cleanElement.setAttribute('height', element.getAttribute('height') || '')
								if (element.hasAttribute('frameborder')) cleanElement.setAttribute('frameborder', element.getAttribute('frameborder') || '')
								if (element.hasAttribute('allowfullscreen')) cleanElement.setAttribute('allowfullscreen', element.getAttribute('allowfullscreen') || '')
							} else if (tagName === 'video' || tagName === 'audio') {
								if (element.hasAttribute('src')) cleanElement.setAttribute('src', element.getAttribute('src') || '')
								if (element.hasAttribute('controls')) cleanElement.setAttribute('controls', element.getAttribute('controls') || '')
								if (element.hasAttribute('width')) cleanElement.setAttribute('width', element.getAttribute('width') || '')
								if (element.hasAttribute('height')) cleanElement.setAttribute('height', element.getAttribute('height') || '')
							}

							// Clean and append child nodes
							Array.from(element.childNodes).forEach((child) => {
								const cleanedChild = cleanNode(child)
								if (cleanedChild.textContent?.trim() || cleanedChild.nodeType === Node.ELEMENT_NODE) {
									cleanElement.appendChild(cleanedChild)
								}
							})

							return cleanElement
						}
					}

					return state.doc!.createTextNode('')
				}

				if (range.collapsed) {
					// If no selection, clean the entire document
					const { body } = (state.doc!)
					const cleanedFragment = state.doc!.createDocumentFragment()

					Array.from(body.childNodes).forEach((node) => {
						const cleanedNode = cleanNode(node)
						if (cleanedNode.textContent?.trim() || cleanedNode.nodeType === Node.ELEMENT_NODE) {
							cleanedFragment.appendChild(cleanedNode)
						}
					})

					// Clear body and append cleaned content
					body.innerHTML = ''
					body.appendChild(cleanedFragment)

					// If body is empty, add a paragraph
					if (!body.firstElementChild) {
						const p = state.doc!.createElement('p')
						body.appendChild(p)

						// Set cursor in the paragraph
						const newRange = state.doc!.createRange()
						newRange.selectNodeContents(p)
						newRange.collapse(true)
						selection.removeAllRanges()
						selection.addRange(newRange)
					}
				} else {
					// If there's a selection, clean only the selected content
					const fragment = range.cloneContents()
					const tempDiv = state.doc!.createElement('div')
					tempDiv.appendChild(fragment)

					// Clean all nodes in the temp div
					const cleanedFragment = state.doc!.createDocumentFragment()
					Array.from(tempDiv.childNodes).forEach((node) => {
						const cleanedNode = cleanNode(node)
						if (cleanedNode.textContent?.trim() || cleanedNode.nodeType === Node.ELEMENT_NODE) {
							cleanedFragment.appendChild(cleanedNode)
						}
					})

					// Replace the selection with cleaned content
					range.deleteContents()
					range.insertNode(cleanedFragment)

					// Restore selection
					range.collapse(false)
					selection.removeAllRanges()
					selection.addRange(range)
				}
			} catch (error) {
				console.error('Error clearing formatting:', error)
			}
		})
	}

	// Table commands
	const tableCommands = {
		insertTable: createCommand('Insert Table', (state, value) => {
			console.log('insertTable command called with value:', value)

			// If we have an openTableEditor function in state, use it for new tables
			if ((state as any).openTableEditor && !value) {
				console.log('Opening table editor modal for new table')
				; (state as any).openTableEditor(null)
				return
			}

			if (!value) {
				// Default fallback
				const [rows, cols] = [3, 3]
				insertTable(rows, cols, state)
				return
			}

			// Check if value is HTML string (from our advanced table editor)
			if (value.includes('<table')) {
				console.log('Inserting HTML table')
				// Insert HTML table directly
				const selectionInfo = getCurrentSelection(state)
				if (!selectionInfo || !state.doc) {
					console.error('No selection info or document')
					return
				}

				const { range } = selectionInfo

				try {
					// Create a temporary div to parse the HTML
					const tempDiv = state.doc.createElement('div')
					tempDiv.innerHTML = value
					const table = tempDiv.querySelector('table')

					if (table) {
						console.log('Table parsed successfully, inserting...')
						// Insert the table at the current selection
						const insertedTable = table.cloneNode(true) as HTMLTableElement
						range.insertNode(insertedTable)

						// Move cursor after the table
						range.setStartAfter(insertedTable)
						range.collapse(true)

						const selection = state.doc.getSelection()
						if (selection) {
							selection.removeAllRanges()
							selection.addRange(range)
						}
						console.log('Table inserted successfully')
					} else {
						console.error('Could not parse table from HTML')
					}
				} catch (error) {
					console.error('Error inserting HTML table:', error)
				}
			} else {
				// Original grid format (e.g., "3x3")
				const [rows, cols] = value.split('x').map(Number) || [3, 3]
				insertTable(rows, cols, state)
			}
		}),
		deleteTable: createCommand('Delete Table', state => state.range && deleteTable(state.range)),
		mergeCells: createCommand('Merge Cells', state => state.range && state.doc && mergeCells(state.range, state.doc)),
		splitCells: createCommand('Split Cells', state => state.range && state.doc && splitCell(state.range, state.doc)),
		addRowBefore: createCommand('Add Row Before', state => state.range && state.doc && addRow('before', state.range, state.doc)),
		addRowAfter: createCommand('Add Row After', state => state.range && state.doc && addRow('after', state.range, state.doc)),
		deleteRow: createCommand('Delete Row', state => state.range && deleteRow(state.range)),
		insertColumnLeft: createCommand('Insert Column Left', state => state.range && insertColumn('before', state.range)),
		insertColumnRight: createCommand('Insert Column Right', state => state.range && insertColumn('after', state.range)),
		deleteColumn: createCommand('Delete Column', state => state.range && deleteColumn(state.range))
	}

	// Table alignment commands
	const alignmentCommands = ['Left', 'Center', 'Right', 'Justify'].reduce((acc, align) => ({
		...acc,
		[`tableAlign${align}`]: createCommand(`Table Align ${align}`, (state) => {
			if (state.range) {
				const alignment = align === 'Left' ? 'start' : align === 'Right' ? 'end' : align.toLowerCase() as 'center' | 'justify'
				alignColumn(state.range, alignment)
			}
		})
	}), {})

	// Text alignment commands (for paragraphs)
	const textAlignmentCommands = {
		alignLeft: createCommand('Align Left', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			if (paragraph) {
				(paragraph as HTMLElement).style.textAlign = 'left'
			}
		}, (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			return (paragraph as HTMLElement)?.style.textAlign === 'left'
		}),

		alignCenter: createCommand('Align Center', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			if (paragraph) {
				(paragraph as HTMLElement).style.textAlign = 'center'
			}
		}, (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			return (paragraph as HTMLElement)?.style.textAlign === 'center'
		}),

		alignRight: createCommand('Align Right', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			if (paragraph) {
				(paragraph as HTMLElement).style.textAlign = 'right'
			}
		}, (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			return (paragraph as HTMLElement)?.style.textAlign === 'right'
		}),

		alignJustify: createCommand('Align Justify', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			if (paragraph) {
				(paragraph as HTMLElement).style.textAlign = 'justify'
			}
		}, (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			return (paragraph as HTMLElement)?.style.textAlign === 'justify'
		}),

		textDirection: createCommand('Toggle Text Direction', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			if (paragraph) {
				const currentDir = (paragraph as HTMLElement).dir || 'ltr'
					; (paragraph as HTMLElement).dir = currentDir === 'ltr' ? 'rtl' : 'ltr'
			}
		}, (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			return (paragraph as HTMLElement)?.dir === 'rtl'
		}),

		ltrDirection: createCommand('Set Left to Right Direction', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			if (paragraph) {
				(paragraph as HTMLElement).dir = 'ltr'
			}
		}, (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			return (paragraph as HTMLElement)?.dir === 'ltr'
		}),

		rtlDirection: createCommand('Set Right to Left Direction', (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			if (paragraph) {
				(paragraph as HTMLElement).dir = 'rtl'
			}
		}, (state) => {
			const selectionInfo = getCurrentSelection(state)
			if (!selectionInfo) { return false }

			const { range } = selectionInfo
			const paragraph = range.startContainer.nodeType === Node.TEXT_NODE
				? range.startContainer.parentElement?.closest('p, h1, h2, h3, h4, h5, h6')
				: (range.startContainer as Element).closest('p, h1, h2, h3, h4, h5, h6')

			return (paragraph as HTMLElement)?.dir === 'rtl'
		})
	}

	// View state commands
	const viewCommands = {
		fullScreen: createCommand('Full Screen', (state) => { state.isFullscreen = !state.isFullscreen }, state => state.isFullscreen),
		splitView: createCommand('Split View', (state) => { state.isSplitView = !state.isSplitView }, state => state.isSplitView),
		codeView: createCommand('Code View', (state) => { state.isCodeView = !state.isCodeView }, state => state.isCodeView)
	}

	// Media commands
	const mediaCommands = {
		image: createCommand('Insert Image', (state) => {
			const { openImageModal } = state as any
			if (openImageModal) {
				openImageModal()
			} else {
				console.warn('Image insertion requires modal implementation')
			}
		}),
		video: createCommand('Insert Video', (state) => {
			const { openVideoModal } = state as any
			if (openVideoModal) {
				openVideoModal()
			} else {
				console.warn('Video insertion requires modal implementation')
			}
		}),
		embed: createCommand('Insert Embed', (state) => {
			const { openEmbedModal } = state as any
			if (openEmbedModal) {
				openEmbedModal()
			} else {
				console.warn('Embed insertion requires modal implementation')
			}
		})
	}

	return {
		...historyCommands,
		...textCommands,
		...headingCommands,
		...blockCommands,
		...listCommands,
		...formatCommands,
		...tableCommands,
		...alignmentCommands,
		...textAlignmentCommands,
		...viewCommands,
		...mediaCommands
	}
}

export function createCommandExecutor(state: EditorState, commands: CommandRegistry): CommandExecutor {
	return {
		execute(command: string, value?: string) {
			const cmd = commands[command]
			if (cmd) {
				cmd.execute(state, value)
			}
		},
		isActive(command: string): boolean {
			const cmd = commands[command]
			return cmd.isActive?.(state) || false
		},
		getValue(command: string): string | null {
			const cmd = commands[command]
			return cmd.getValue?.(state) || null
		}
	}
}
