import type { EditorState } from '../richTextTypes'
import { analyzeSelection, restoreSelection, getBlocksBetween } from './selection'

export function formatting(state: EditorState) {
	const { doc } = state
	if (!doc) { return { text: () => {}, block: () => {}, list: () => {}, clear: () => {} } }

	const text = (command: string) => {
		const selection = doc.getSelection()
		if (!selection || !selection.rangeCount) { return }

		const range = selection.getRangeAt(0)
		const container = range.commonAncestorContainer
		const parentBlock = container.nodeType === 3 ? container.parentElement : container as HTMLElement

		// Preserve RTL direction when applying text formatting
		const isRTL = parentBlock?.closest('[dir="rtl"]') !== null

		// Don't apply inline styles directly to block elements
		if (parentBlock?.tagName.match(/^H[1-6]|P|BLOCKQUOTE|LI$/)) {
			if (!range.collapsed && range.toString().trim()) {
				let element: HTMLElement
				if (command === 'underline') { element = doc.createElement('u') }
				else if (command === 'bold') { element = doc.createElement('b') }
				else if (command === 'italic') { element = doc.createElement('i') }
				else { return }

				if (isRTL) { element.dir = 'rtl' }
				range.surroundContents(element)
			}
		} else {
			if (range.collapsed) { return } // No selection, nothing to format

			let element: HTMLElement
			if (command === 'bold') { element = doc.createElement('b') }
			else if (command === 'italic') { element = doc.createElement('i') }
			else if (command === 'underline') { element = doc.createElement('u') }
			else { return }

			try {
				range.surroundContents(element)
			} catch {
				// If surroundContents fails (e.g., for selections across multiple nodes)
				// Extract the fragment, wrap it, and insert it back
				const fragment = range.extractContents()
				element.appendChild(fragment)
				range.insertNode(element)
			}
		}
	}

	const block = (command: string, tag: string) => {
		const selection = doc.getSelection()
		if (!selection || !selection.rangeCount) { return }

		const range = selection.getRangeAt(0)
		const container = range.commonAncestorContainer
		const parentBlock = container.nodeType === 3 ? container.parentElement : container as HTMLElement
		const isRTL = parentBlock?.closest('[dir="rtl"]') !== null

		// Remove any invalid inline style wrapping
		if (parentBlock?.closest('u, b, i')) {
			const wrapper = parentBlock.closest('u, b, i')
			if (wrapper) {
				const parent = wrapper.parentNode
				while (wrapper.firstChild) {
					parent?.insertBefore(wrapper.firstChild, wrapper)
				}
				wrapper.remove()
			}
		}

		// Create a new block element of the desired type
		const newBlock = doc.createElement(tag)
		if (isRTL) { newBlock.dir = 'rtl' }

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

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

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

			// Move cursor into the new block
			range.selectNodeContents(newBlock)
			range.collapse(false)
			selection.removeAllRanges()
			selection.addRange(range)
		}
	}

	const list = (command: string) => {
		if (!state.doc || !state.range || !state.selection) { return }

		const listTag = command === 'orderedList' ? 'ol' : 'ul'
		const selectionInfo = analyzeSelection(state.doc, state.range)
		if (!selectionInfo) { return }

		const isRTL = selectionInfo.parent.closest('[dir="rtl"]') !== null

		// If we're inside a list item, handle toggling off or switching list type
		const listItem = selectionInfo.parent.closest('li')
		if (listItem) {
			const listParent = listItem.parentElement
			if (!listParent) { return }

			// If it's the same list type, toggle off
			if (listParent.tagName.toLowerCase() === listTag) {
				// Create a paragraph from the list item content
				const p = state.doc.createElement('p') as HTMLParagraphElement
				if (isRTL) { p.dir = 'rtl' }
				while (listItem.firstChild) {
					p.appendChild(listItem.firstChild)
				}

				listParent.parentNode?.insertBefore(p, listParent)
				listItem.remove()

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

				restoreSelection(state.doc, state.range, state.selection, selectionInfo, p)
				return
			}

			// Convert to the other list type
			const newList = state.doc.createElement(listTag) as HTMLElement
			if (isRTL) { newList.dir = 'rtl' }
			const allItems = Array.from(listParent.children)
			allItems.forEach(item => newList.appendChild(item.cloneNode(true)))
			listParent.parentNode?.replaceChild(newList, listParent)

			const newListItem = newList.children[Array.from(allItems).indexOf(listItem)]
			restoreSelection(state.doc, state.range, state.selection, selectionInfo, newListItem)
			return
		}

		// For single block conversion
		if (!selectionInfo.isMultiBlock) {
			const li = state.doc.createElement('li')
			const listEl = state.doc.createElement(listTag) as HTMLElement
			if (isRTL) { listEl.dir = 'rtl' }

			while (selectionInfo.startBlock.firstChild) {
				li.appendChild(selectionInfo.startBlock.firstChild)
			}

			listEl.appendChild(li)
			selectionInfo.startBlock.parentNode?.replaceChild(listEl, selectionInfo.startBlock)
			restoreSelection(state.doc, state.range, state.selection, selectionInfo, li)
			return
		}

		// Handle multi-block selection
		const blocks = getBlocksBetween(selectionInfo.startBlock, selectionInfo.endBlock)
		const listEl = state.doc.createElement(listTag) as HTMLElement
		if (isRTL) { listEl.dir = 'rtl' }

		blocks.forEach((block) => {
			if (!state.doc) { return }
			const li = state.doc.createElement('li')
			while (block.firstChild) {
				li.appendChild(block.firstChild)
			}
			listEl.appendChild(li)
			block.remove()
		})

		selectionInfo.startBlock.parentNode?.insertBefore(listEl, selectionInfo.startBlock)
		restoreSelection(state.doc, state.range, state.selection, selectionInfo, listEl)
	}

	const clear = () => {
		console.log('[Clear Format] Starting clear format process', state)
		console.assert(state, '[Clear Format] State must exist')
		console.assert(state.doc, '[Clear Format] Document must exist')
		console.assert(state.range, '[Clear Format] Range must exist')
		console.assert(state.selection, '[Clear Format] Selection must exist')

		if (!state.doc || !state.range || !state.selection) {
			console.log('[Clear Format] No document or selection')
			return
		}

		const selectionInfo = analyzeSelection(state.doc, state.range)
		console.log('[Clear Format] Selection info:', selectionInfo)
		if (!selectionInfo) {
			console.log('[Clear Format] No valid selection info')
			return
		}

		// If selection is collapsed (just a cursor), return
		if (state.range.collapsed) {
			console.log('[Clear Format] Selection is collapsed, nothing to clear')
			return
		}

		console.log('[Clear Format] Processing selection:', {
			startContainer: state.range.startContainer,
			endContainer: state.range.endContainer,
			selectedText: state.range.toString()
		})

		// Get the selected content
		const fragment = state.range.cloneContents()
		const tempDiv = state.doc.createElement('div')
		tempDiv.appendChild(fragment)

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

		// Function to recursively clean a node and its children
		const cleanNode = (node: Node): Node => {
			if (!state.doc) { return node }

			// Text nodes can be returned as-is
			if (node.nodeType === 3) {
				return node.cloneNode(true)
			}

			if (node.nodeType === 1) { // Element node
				const el = node as HTMLElement
				const nodeName = el.nodeName.toLowerCase()
				const inlineTags = ['b', 'i', 'u', 'strong', 'em', 'span', 'font', 'strike', 'sub', 'sup']

				console.log('[Clear Format] Processing element:', nodeName, {
					hasStyle: el.hasAttribute('style'),
					style: el.getAttribute('style'),
					className: el.className
				})

				// For inline formatting elements, just extract the text content
				if (inlineTags.includes(nodeName)) {
					// Create a text node with the element's content
					const textContent = el.textContent || ''
					console.log('[Clear Format] Extracting text from inline element:', textContent)
					return state.doc.createTextNode(textContent)
				}

				// For block elements, preserve the element type but remove formatting
				const newEl = state.doc.createElement(nodeName)

				// Remove any style and class attributes
				if (el.hasAttribute('style')) {
					console.log('[Clear Format] Removing style attribute:', el.getAttribute('style'))
				}
				if (el.className) {
					console.log('[Clear Format] Removing class:', el.className)
				}

				// Transfer only specific attributes we want to keep (like href for links)
				if (nodeName === 'a' && el.hasAttribute('href')) {
					newEl.setAttribute('href', el.getAttribute('href') || '')
					if (el.hasAttribute('target')) {
						newEl.setAttribute('target', el.getAttribute('target') || '')
					}
					console.log('[Clear Format] Preserving link attributes')
				}

				// For images, preserve src and alt
				if (nodeName === 'img') {
					if (el.hasAttribute('src')) { newEl.setAttribute('src', el.getAttribute('src') || '') }
					if (el.hasAttribute('alt')) { newEl.setAttribute('alt', el.getAttribute('alt') || '') }
					console.log('[Clear Format] Preserving image attributes')
				}

				// Clean and append all child nodes
				Array.from(el.childNodes).forEach((child) => {
					newEl.appendChild(cleanNode(child))
				})

				return newEl
			}

			return node.cloneNode(true)
		}

		// Clean all nodes in the fragment
		const cleanedFragment = state.doc.createDocumentFragment()
		Array.from(tempDiv.childNodes).forEach((node) => {
			cleanedFragment.appendChild(cleanNode(node))
		})

		// For debugging: check what the cleaned HTML looks like
		const debugDiv = state.doc.createElement('div')
		debugDiv.appendChild(cleanedFragment.cloneNode(true))
		console.log('[Clear Format] Cleaned HTML:', debugDiv.innerHTML)

		// Replace the content
		state.range.deleteContents()
		state.range.insertNode(cleanedFragment)

		// Restore selection
		restoreSelection(state.doc, state.range, state.selection, selectionInfo)

		// Update content state
		state.content = state.doc.body.innerHTML
		console.log('[Clear Format] Updated document HTML:', state.content)
	}

	return { text, block, list, clear }
}

export function getBlockElement(node: Node): HTMLElement | null {
	const element = node.nodeType === 3 ? node.parentElement : node as HTMLElement
	if (!element) { return null }
	return element.closest('p,h1,h2,h3,h4,h5,h6,blockquote,li') || element
}

export function adjustIndentation(state: EditorState, increase: boolean) {
	const { doc, range } = state
	if (!doc || !range) { return }

	const blockElement = getBlockElement(range.commonAncestorContainer)
	if (!blockElement) { return }

	const currentMargin = Number.parseInt(blockElement.style.marginLeft || '0', 10)
	const step = 20 // pixels to indent/outdent
	const newMargin = increase ? currentMargin + step : Math.max(0, currentMargin - step)

	blockElement.style.marginLeft = `${newMargin}px`
}
