import type { EditorState, EditorDebugInterface, EditorDebuggerInstance } from '../richTextTypes'
import { useModal } from '@bagelink/vue'
import { reactive } from 'vue'
import { EditorDebugger } from '../utils/debug'
import { isStyleActive } from '../utils/selection'

function preserveIframes(content: string): { html: string, iframes: HTMLIFrameElement[] } {
	const temp = document.createElement('div')
	temp.innerHTML = content
	const iframes: HTMLIFrameElement[] = []
	const placeholders: string[] = []

	// Find all iframes and replace them with placeholders
	temp.querySelectorAll('iframe').forEach((iframe, index) => {
		const placeholder = `<!--iframe-${index}-->`
		iframes.push(iframe.cloneNode(true) as HTMLIFrameElement)
		placeholders.push(placeholder)
		iframe.replaceWith(placeholder)
	})

	return {
		html: temp.innerHTML,
		iframes
	}
}

function restoreIframes(doc: Document, content: string, iframes: HTMLIFrameElement[]) {
	// Find all iframe placeholders and restore them
	const placeholderPattern = /<!--iframe-(\d+)-->/g
	doc.body.innerHTML = content.replace(placeholderPattern, (_, index) => {
		const iframe = iframes[Number(index)]
		return iframe ? iframe.outerHTML : ''
	})
}

export function useEditor() {
	const modal = useModal()
	let cleanupListeners: (() => void) | null = null

	const state = reactive<EditorState>({
		content: '',
		doc: undefined,
		selection: null,
		selectedStyles: new Set<string>(),
		isFullscreen: false,
		isSplitView: false,
		isCodeView: false,
		hasInit: false,
		undoStack: [],
		redoStack: [],
		rangeCount: 0,
		range: null,
		modal,
		debug: undefined
	})

	// Centralized state update functions
	const updateState = {
		styles: () => {
			if (!state.doc) return
			const styles = new Set<string>()
			const styleTypes = [
				'bold',
				'italic',
				'underline',
				'h1',
				'h2',
				'h3',
				'h4',
				'h5',
				'h6',
				'blockquote',
				'table',
				'p',
				'ol',
				'li'
			]
			styleTypes.forEach((style) => {
				if (state.doc && isStyleActive(style, state.doc)) {
					styles.add(style)
				}
			})
			state.selectedStyles = styles
		},
		content: (source: 'html' | 'text') => {
			if (!state.doc) return

			// Only push to undo stack if content has changed
			const currentContent = state.doc.body.innerHTML
			if (currentContent !== state.content) {
				state.undoStack.push(state.content)
				state.redoStack = []
			}

			// Store current selection
			const selection = state.doc.getSelection()
			const range = selection?.rangeCount ? selection.getRangeAt(0).cloneRange() : null

			if (source === 'html') {
				// Preserve iframes before setting content
				const preserved = preserveIframes(state.content)
				state.doc.body.innerHTML = preserved.html

				// Restore iframes after a short delay to ensure the document is ready
				setTimeout(() => {
					if (state.doc) {
						restoreIframes(state.doc, state.content, preserved.iframes)

						// Restore selection if it existed
						if (range && selection) {
							try {
								selection.removeAllRanges()
								selection.addRange(range)
							} catch (e) {
							}
						}
					}
				}, 0)
			} else {
				state.doc.body.textContent = state.content
			}
		},
		selection: () => {
			if (!state.doc) return
			const newSelection = state.doc.getSelection()
			if (!newSelection) {
				return
			}

			try {
				if (!state.doc.body.contains(newSelection.anchorNode)) {
					state.doc.body.focus()
					return
				}

				// Only update if selection has actually changed
				const hasSelectionChanged = !state.selection
					|| state.selection !== newSelection
					|| state.rangeCount !== newSelection.rangeCount
					|| (newSelection.rangeCount > 0 && state.range && (
						state.range.startContainer !== newSelection.getRangeAt(0).startContainer
						|| state.range.startOffset !== newSelection.getRangeAt(0).startOffset
						|| state.range.endContainer !== newSelection.getRangeAt(0).endContainer
						|| state.range.endOffset !== newSelection.getRangeAt(0).endOffset
					))

				if (hasSelectionChanged) {
					state.selection = newSelection
					state.rangeCount = newSelection.rangeCount

					if (newSelection.rangeCount > 0) {
						state.range = newSelection.getRangeAt(0).cloneRange()
					}

					// Update styles less frequently
					requestAnimationFrame(() => {
						updateState.styles()
					})
				}
			} catch (e) {
				state.selection = null
				state.range = null
				state.rangeCount = 0
				state.selectedStyles = new Set()
			}
		}
	}

	// History management
	const history = {
		undo: () => {
			if (state.undoStack.length === 0) return
			state.redoStack.push(state.content)
			const lastContent = state.undoStack.pop()
			if (lastContent !== undefined) {
				state.content = lastContent
				updateState.content('html')
			}
		},
		redo: () => {
			if (state.redoStack.length === 0) return
			state.undoStack.push(state.content)
			const nextContent = state.redoStack.pop()
			if (nextContent !== undefined) {
				state.content = nextContent
				updateState.content('html')
			}
		}
	}

	// Content cleanup utilities
	const cleanup = {
		emptyTags: (doc: Document) => {
			const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT, null)
			const nodesToRemove: Element[] = []
			let node = walker.nextNode() as Element

			while (node) {
				if (['br', 'img', 'hr', 'input'].includes(node.tagName.toLowerCase())) {
					node = walker.nextNode() as Element
					continue
				}

				const textContent = node.textContent?.trim() || ''
				const innerHTML = node.innerHTML.trim()
				const hasOnlyBr = innerHTML === '<br>' || innerHTML === '<br/>'
				const hasOnlyNbsp = innerHTML === '&nbsp;' || textContent === '\u00A0'
				const isEmpty = !textContent && !innerHTML
				const isDirectChildOfBody = node.parentElement === doc.body

				if (isEmpty || hasOnlyNbsp || (hasOnlyBr && !isDirectChildOfBody)) {
					if (isDirectChildOfBody && !node.matches('p')) {
						const p = doc.createElement('p')
						p.innerHTML = '<br>'
						node.parentNode?.replaceChild(p, node)
					} else {
						nodesToRemove.push(node)
					}
				}
				node = walker.nextNode() as Element
			}
			nodesToRemove.forEach((node) => { node.remove() })
		},
		normalizeContent: (doc: Document) => {
			if (!doc.body.firstElementChild) {
				const p = doc.createElement('p')
				p.dir = doc.body.dir
				p.innerHTML = '<br>'
				doc.body.appendChild(p)
			} else {
				const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
				const textNodes: Text[] = []
				let node: Node | null
				while ((node = walker.nextNode())) {
					if (node.parentElement === doc.body) {
						textNodes.push(node as Text)
					}
				}
				textNodes.forEach((textNode) => {
					if (textNode.textContent?.trim()) {
						const p = doc.createElement('p')
						p.dir = doc.body.dir
						p.appendChild(textNode.cloneNode())
						doc.body.replaceChild(p, textNode)
					} else {
						doc.body.removeChild(textNode)
					}
				})
			}
		}
	}

	function setupEventListeners(doc: Document) {
		// Clean up existing listeners if they exist
		if (cleanupListeners) {
			cleanupListeners()
			cleanupListeners = null
		}

		let isUpdating = false

		const events = {
			input: () => {
				if (isUpdating) return
				isUpdating = true

				// Immediately update content
				const newContent = doc.body.innerHTML
				if (newContent !== state.content) {
					state.content = newContent
				}

				isUpdating = false
			},
			selectionchange: () => {
				if (!isUpdating) {
					updateState.selection()
				}
			},
			mouseup: () => {
				if (!isUpdating) {
					updateState.selection()
				}
			},
			keyup: (e: KeyboardEvent) => {
				if (!isUpdating) {
					if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
						updateState.selection()
					}
				}
			}
		}

		// Only add listeners if they haven't been added yet
		Object.entries(events).forEach(([event, handler]) => {
			doc.addEventListener(event, handler as EventListener)
		})

		// Store cleanup function
		cleanupListeners = () => {
			Object.entries(events).forEach(([event, handler]) => {
				doc.removeEventListener(event, handler as EventListener)
			})
		}

		return cleanupListeners
	}

	function init(doc: Document) {
		if (state.hasInit) {
			if (cleanupListeners) {
				cleanupListeners()
				cleanupListeners = null
			}
		}

		state.doc = doc
		state.hasInit = true as const

		// Store state reference in document for table operations
		(doc as any).editorState = state

		// Initial setup without triggering updates
		if (state.content) {
			const preserved = preserveIframes(state.content)
			doc.body.innerHTML = preserved.html
			setTimeout(() => {
				if (state.doc) {
					restoreIframes(doc, state.content, preserved.iframes)
				}
			}, 0)
		}

		cleanup.normalizeContent(doc)

		// Set initial selection at the end
		const range = doc.createRange()
		const selection = doc.getSelection()
		if (selection) {
			range.selectNodeContents(doc.body)
			range.collapse(false)
			selection.removeAllRanges()
			selection.addRange(range)
			state.range = range.cloneRange()
			state.selection = selection
			state.rangeCount = selection.rangeCount
		}

		// Setup event listeners immediately
		cleanupListeners = setupEventListeners(doc)
	}

	function initDebugger() {
		if (!state.debug) {
			const debugInstance: EditorDebuggerInstance = new EditorDebugger()
			const debug: EditorDebugInterface = {
				debugger: debugInstance,
				logCommand: (command: string, value?: string) => {
					debugInstance.logCommand(command, value, state)
				},
				getSession: () => debugInstance.getSession(),
				clearSession: () => {
					debugInstance.clearSession()
				},
				downloadSession: () => {
					debugInstance.downloadSession()
				},
				exportDebugWithPrompt: (message?: string) => debugInstance.exportSessionWithPrompt(message)
			}
			state.debug = debug
		}
	}

	// Add cleanup on component unmount
	if (typeof window !== 'undefined') {
		window.addEventListener('beforeunload', () => {
			if (cleanupListeners) {
				cleanupListeners()
			}
		})
	}

	return {
		state,
		init,
		updateState,
		history,
		initDebugger,
		cleanup: () => {
			if (cleanupListeners) {
				cleanupListeners()
				cleanupListeners = null
			}
		}
	}
}
