import type { EditorState } from '../richTextTypes'
import { formatting } from './formatting'
import { insertImage, insertLink, insertEmbed } 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
}

// 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)
		},
		isActive
	}
}

// Create formatting commands helper
function createFormattingCommand(state: EditorState, type: 'text' | 'block' | 'list', command: string, tag?: string): Command {
	const format = formatting(state)
	return createCommand(
		command,
		() => {
			if (!state.doc) return

			if (type === 'text') {
				format.text(command)
			}
			else if (type === 'block') {
				format.block(command, tag || command)
			}
			else if (type === 'list') {
				const selection = state.doc.getSelection()
				if (!selection || !selection.rangeCount) return

				const range = selection.getRangeAt(0)

				// If there's no content or the selection is collapsed
				if (range.collapsed && (!range.startContainer.textContent?.trim() || range.startContainer === state.doc.body)) {
					// Create a new list with an empty item
					const list = state.doc.createElement(command === 'orderedList' ? 'ol' : 'ul')
					const li = state.doc.createElement('li')
					// Use a non-breaking space with br to ensure proper rendering
					li.innerHTML = '&nbsp;<br>'
					list.appendChild(li)

					// If we're in an empty paragraph, replace it
					const currentBlock = range.startContainer.nodeType === 1
						? range.startContainer as Element
						: range.startContainer.parentElement

					if (currentBlock?.tagName.toLowerCase() === 'p' && isNodeEmpty(currentBlock)) {
						currentBlock.parentNode?.replaceChild(list, currentBlock)
					} else {
						// Otherwise insert at cursor
						range.insertNode(list)
					}

					// Move cursor into the list item
					range.selectNodeContents(li)
					range.collapse(true)
					selection.removeAllRanges()
					selection.addRange(range)
				} else {
					format.list(command)
				}
			}
		},
		() => state.selectedStyles.has(command)
	)
}

// Helper function to check if a node is empty (contains only whitespace or <br>)
function isNodeEmpty(node: Node): boolean {
	// Check for text content after removing whitespace and nonbreaking spaces
	const text = node.textContent?.replace(/\s/g, '') || ''
	if (text) return false

	// Check for <br> tags
	const brElements = (node as Element).getElementsByTagName ? (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>
}

export function createCommandRegistry(state: EditorState): CommandRegistry {
	const format = formatting(state)

	// 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
					if (state.doc) 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
					if (state.doc) state.doc.body.innerHTML = nextContent
				}
			}
		})
	}

	// Basic text formatting commands
	const textCommands = {
		bold: createCommand('Bold', (state) => {
			console.log('[Command] Bold called')
			if (!state.doc || !state.range || !state.selection) return

			const { range } = state
			if (range.collapsed) return

			try {
				// Create a span with bold style
				const span = state.doc.createElement('span')
				span.style.fontWeight = 'bold'

				// Try to surround contents, if that fails use extract and insert
				try {
					range.surroundContents(span)
				} catch {
					// Handle selections that cross node boundaries
					const fragment = range.extractContents()
					span.appendChild(fragment)
					range.insertNode(span)
				}

				// Select the new formatted content
				range.selectNodeContents(span)
				state.selection.removeAllRanges()
				state.selection.addRange(range)

				// Update content
				state.content = state.doc.body.innerHTML
			} catch (error) {
				console.error('[Command] Error applying bold:', error)
			}
		}),

		italic: createCommand('Italic', (state) => {
			console.log('[Command] Italic called')
			if (!state.doc || !state.range || !state.selection) return

			const { range } = state
			if (range.collapsed) return

			try {
				// Create a span with italic style
				const span = state.doc.createElement('span')
				span.style.fontStyle = 'italic'

				// Try to surround contents, if that fails use extract and insert
				try {
					range.surroundContents(span)
				} catch {
					// Handle selections that cross node boundaries
					const fragment = range.extractContents()
					span.appendChild(fragment)
					range.insertNode(span)
				}

				// Select the new formatted content
				range.selectNodeContents(span)
				state.selection.removeAllRanges()
				state.selection.addRange(range)

				// Update content
				state.content = state.doc.body.innerHTML
			} catch (error) {
				console.error('[Command] Error applying italic:', error)
			}
		}),

		underline: createCommand('Underline', (state) => {
			console.log('[Command] Underline called')
			if (!state.doc || !state.range || !state.selection) return

			const { range } = state
			if (range.collapsed) return

			try {
				// Create a span with underline style
				const span = state.doc.createElement('span')
				span.style.textDecoration = 'underline'

				// Try to surround contents, if that fails use extract and insert
				try {
					range.surroundContents(span)
				} catch {
					// Handle selections that cross node boundaries
					const fragment = range.extractContents()
					span.appendChild(fragment)
					range.insertNode(span)
				}

				// Select the new formatted content
				range.selectNodeContents(span)
				state.selection.removeAllRanges()
				state.selection.addRange(range)

				// Update content
				state.content = state.doc.body.innerHTML
			} catch (error) {
				console.error('[Command] Error applying underline:', error)
			}
		})
	}

	// Heading commands
	const headingCommands = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].reduce((acc, cmd) => ({
		...acc,
		[cmd]: createCommand(`Heading ${cmd}`, (state) => {
			console.log(`[Command] ${cmd} heading called`)
			if (!state.doc || !state.range || !state.selection) return

			const { range, doc, selection } = state
			const container = range.commonAncestorContainer
			const parentBlock = container.nodeType === 3 ? container.parentElement : container as HTMLElement

			// Find current block element
			const currentBlock = parentBlock?.closest('p,h1,h2,h3,h4,h5,h6,blockquote,div') || parentBlock

			// Check if we need to toggle off (already in a heading with the same tag)
			const isToggleOff = currentBlock?.tagName.toLowerCase() === cmd.toLowerCase()

			// Determine which tag to apply
			const newTag = isToggleOff ? 'p' : cmd

			try {
				// Create a new block element of the proper type
				const newBlock = doc.createElement(newTag)

				if (currentBlock) {
					// 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) // Move to end
					selection.removeAllRanges()
					selection.addRange(range)
				}

				// Update content
				state.content = doc.body.innerHTML
			} catch (error) {
				console.error(`[Command] Error applying ${cmd} heading:`, error)
			}
		}),
	}), {})

	// Block commands
	const blockCommands = {
		p: createCommand('Paragraph', (state) => {
			console.log('[Command] Paragraph called')
			if (!state.doc || !state.range || !state.selection) return

			const { range, doc, selection } = state
			const container = range.commonAncestorContainer
			const parentBlock = container.nodeType === 3 ? container.parentElement : container as HTMLElement

			// Find current block element
			const currentBlock = parentBlock?.closest('p,h1,h2,h3,h4,h5,h6,blockquote,div') || parentBlock

			// Check if already a paragraph - if so, nothing to do
			if (currentBlock?.tagName.toLowerCase() === 'p') {
				console.log('[Command] Already a paragraph')
				return
			}

			try {
				// Create a new paragraph
				const newParagraph = doc.createElement('p')

				if (currentBlock) {
					// 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) // Move to end
					selection.removeAllRanges()
					selection.addRange(range)
				}

				// Update content
				state.content = doc.body.innerHTML
			} catch (error) {
				console.error('[Command] Error applying paragraph:', error)
			}
		}),
		blockquote: createFormattingCommand(state, 'block', 'blockquote')
	}

	// List commands
	const listCommands = {
		orderedList: createFormattingCommand(state, 'list', 'orderedList'),
		unorderedList: createFormattingCommand(state, 'list', 'unorderedList')
	}

	// Table commands
	const tableCommands = {
		insertTable: createCommand('Insert Table', (state, value) => {
			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))
	}

	// Alignment commands
	const alignmentCommands = ['Left', 'Center', 'Right', 'Justify'].reduce((acc, align) => ({
		...acc,
		[`align${align}`]: createCommand(`Align ${align}`, state => state.range && alignColumn(state.range, align.toLowerCase() as 'left' | 'center' | 'right' | 'justify'))
	}), {})

	// 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) => { state.modal && insertImage(state.modal, state) }),
		link: createCommand('Insert Link', state => state.modal && state.range && insertLink(state.modal, state)),
		embed: createCommand('Insert Embed', (state) => { state.modal && insertEmbed(state.modal, state) })
	}

	// Other formatting commands
	const otherCommands = {
		clear: createCommand('Clear Formatting', (state) => {
			console.log('[Command] Clear formatting called')
			console.log('[Command] Current selection:', state.selection?.toString())
			console.log('[Command] Range exists:', !!state.range)
			console.log('[Command] Document exists:', !!state.doc)

			try {
				if (!state.doc || !state.range || !state.selection) {
					console.log('[Command] Missing required state for clear formatting')
					return
				}

				const { selection, range, doc } = state

				if (range.collapsed) {
					console.log('[Command] Selection is collapsed, nothing to clear')
					return
				}

				// Clone the contents to work with
				const fragment = range.cloneContents()
				const tempDiv = doc.createElement('div')
				tempDiv.appendChild(fragment)

				console.log('[Command] Original HTML:', tempDiv.innerHTML)

				// Process all elements to remove styling but keep structure
				const structuralTags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'blockquote', 'div', 'table', 'tr', 'td', 'th']
				const inlineTags = ['b', 'i', 'u', 'strong', 'em', 'span', 'font', 'strike', 'sub', 'sup']

				// Function to clean an element of styling
				const cleanElement = (element: Element) => {
					// Remove style and class attributes
					element.removeAttribute('style')
					element.removeAttribute('class')

					// Process child elements
					Array.from(element.children).forEach((child) => {
						if (inlineTags.includes(child.tagName.toLowerCase())) {
							// For inline formatting elements, replace with their text content
							const textContent = child.textContent || ''
							const textNode = doc.createTextNode(textContent)
							child.replaceWith(textNode)
						} else {
							// For structural elements, keep them but clean styling
							cleanElement(child)
						}
					})
				}

				// Clean all elements
				Array.from(tempDiv.querySelectorAll('*')).forEach((element) => {
					const tagName = element.tagName.toLowerCase()

					// For inline formatting tags, we'll remove and keep only their content
					// which happens in cleanElement when processing children

					// For structural tags, keep them but remove styling
					if (structuralTags.includes(tagName)) {
						cleanElement(element)
					}
				})

				console.log('[Command] Cleaned HTML:', tempDiv.innerHTML)

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

				// Create a new document fragment to hold the cleaned content
				const cleanedFragment = doc.createDocumentFragment()

				// Append all nodes from the tempDiv
				while (tempDiv.firstChild) {
					cleanedFragment.appendChild(tempDiv.firstChild)
				}

				range.insertNode(cleanedFragment)

				// Try to restore a reasonable selection
				range.collapse(false)
				selection.removeAllRanges()
				selection.addRange(range)

				// Update content
				state.content = doc.body.innerHTML
				console.log('[Command] Clear formatting completed')
			} catch (error) {
				console.error('[Command] Error in clear formatting:', error)
			}
		}),
		indent: createCommand('Indent', () => { format.text('indent') }),
		outdent: createCommand('Outdent', () => { format.text('outdent') })
	}

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

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
		}
	}
}
