import { computed, nextTick, ref } from 'vue'
import type { ComputedRef, Ref } from 'vue'
import lightIcon from '@/assets/images/light-icon.png'
import closeIcon from '@/assets/images/close-icon.png'
import TypeIcon from '@/assets/icons/TypeIcon.svg'
import RegionIcon from '@/assets/icons/RegionIcon.svg'
import StyleIcon from '@/assets/icons/StyleIcon.svg'
import restoreIcon from '@/assets/images/restore.png'
import restoreCloseIcon from '@/assets/images/tag-close.png'
import { optionTagOrder } from './options'
import type { OptionItem, OptionTagKind } from './types'

interface UseInputEditorOptions {
	isAgentMode: Ref<boolean> | ComputedRef<boolean>
	t: (key: string) => string
	typeValue: Ref<string>
	areaValue: Ref<string>
	styleValue: Ref<string>
	typeOptions: Ref<OptionItem[]>
	areaOptions: Ref<OptionItem[]>
	styleOptions: Ref<OptionItem[]>
	onSubmit: () => void | Promise<void>
}

export function useInputEditor(options: UseInputEditorOptions) {
	const editorRef = ref<HTMLDivElement | null>(null)
	const inputValue = ref<string>('')
	const reportTags = ref<HTMLElement[]>([])
	const optionTags = ref<Partial<Record<OptionTagKind, HTMLElement>>>({})
	const optionTagKinds = ref<OptionTagKind[]>([])
	const reportPromptText = ref<Text | null>(null)
	let reportTypewriterTimeout: ReturnType<typeof setTimeout> | null = null

	const setEditorElement = (element: HTMLDivElement | null) => {
		editorRef.value = element
	}

	const focusEditor = () => {
		editorRef.value?.focus()
	}

	const stopReportTypewriter = () => {
		if (reportTypewriterTimeout) {
			clearTimeout(reportTypewriterTimeout)
			reportTypewriterTimeout = null
		}
	}

	const moveCaretToTextEnd = (textNode: Text) => {
		const selection = window.getSelection()
		if (!selection || selection.anchorNode !== textNode) return

		const range = document.createRange()
		range.setStart(textNode, textNode.data.length)
		range.collapse(true)
		selection.removeAllRanges()
		selection.addRange(range)
	}

	const typeReportPromptText = (textNode: Text, text: string, index = 0) => {
		if (reportPromptText.value !== textNode) return

		if (index >= text.length) {
			reportTypewriterTimeout = null
			return
		}

		textNode.textContent = `${textNode.textContent || ''}${text.charAt(index)}`
		handleEditorInput()
		moveCaretToTextEnd(textNode)

		reportTypewriterTimeout = setTimeout(() => {
			typeReportPromptText(textNode, text, index + 1)
		}, 30)
	}

	const removeReportPromptText = () => {
		stopReportTypewriter()
		const promptText = reportPromptText.value
		if (promptText?.parentNode) {
			const nextNode = promptText.nextSibling
			if (
				nextNode &&
				nextNode.nodeType === Node.TEXT_NODE &&
				nextNode.textContent === '\u200B'
			) {
				nextNode.remove()
			}
			promptText.parentNode.removeChild(promptText)
		}
		reportPromptText.value = null
	}

	const removeNodeWithNextSpacer = (node: Node) => {
		const nextNode = node.nextSibling
		if (nextNode && nextNode.nodeType === Node.TEXT_NODE && nextNode.textContent === '\u200B') {
			nextNode.remove()
		}
		node.parentNode?.removeChild(node)
	}

	const hasOptionTags = () => {
		return Object.values(optionTags.value).some((tag) => tag?.parentNode)
	}

	const getOptionTagValue = (kind: OptionTagKind) => {
		if (kind === 'type') return options.typeValue.value
		if (kind === 'area') return options.areaValue.value
		return options.styleValue.value
	}

	const getOptionTagIcon = (kind: OptionTagKind) => {
		if (kind === 'type') return TypeIcon
		if (kind === 'area') return RegionIcon
		return StyleIcon
	}

	const refreshOptionTagKinds = () => {
		const editor = editorRef.value
		if (!editor) {
			optionTagKinds.value = []
			return
		}

		optionTagKinds.value = Array.from(editor.children)
			.map((child) => (child as HTMLElement).dataset.optionTagKind as OptionTagKind)
			.filter(Boolean)
	}

	const getTranslatedOptionLabel = (items: OptionItem[], value: string) => {
		const option = items.find((item) => item.value === value)
		return option ? options.t(option.label) : value
	}

	const getOptionTagLabel = (kind: OptionTagKind) => {
		const value = getOptionTagValue(kind)
		if (kind === 'type') return getTranslatedOptionLabel(options.typeOptions.value, value)
		if (kind === 'area') return getTranslatedOptionLabel(options.areaOptions.value, value)
		return options.styleOptions.value.find((item) => item.value === value)?.label || value
	}

	const removeOptionTag = (kind: OptionTagKind) => {
		const tag = optionTags.value[kind]
		if (tag?.parentNode) {
			removeNodeWithNextSpacer(tag)
		}
		delete optionTags.value[kind]
		refreshOptionTagKinds()
	}

	const removeAllOptionTags = () => {
		optionTagOrder.forEach(removeOptionTag)
		optionTags.value = {}
		optionTagKinds.value = []
	}

	const clearOptionTagValue = (kind: OptionTagKind) => {
		if (kind === 'type') {
			options.typeValue.value = ''
		} else if (kind === 'area') {
			options.areaValue.value = ''
		} else {
			options.styleValue.value = ''
		}

		removeOptionTag(kind)
		handleEditorInput()
	}

	const getOptionTagInsertBefore = () => {
		const editor = editorRef.value
		if (!editor) return null

		for (const child of Array.from(editor.childNodes)) {
			if (child.nodeType === Node.ELEMENT_NODE) {
				const childKind = (child as HTMLElement).dataset.optionTagKind as OptionTagKind
				if (childKind) continue
			}
			return child
		}

		return null
	}

	const createOptionTag = (kind: OptionTagKind, label: string) => {
		const tag = document.createElement('div')
		tag.contentEditable = 'false'
		tag.className = 'editor-tag option-tag flex-center'
		tag.dataset.optionTagKind = kind
		const optionIcon = getOptionTagIcon(kind) as unknown as string

		const icon = document.createElement('img')
		icon.className = 'option-tag-icon'
		icon.src = optionIcon
		icon.addEventListener('click', (ev) => {
			ev.stopPropagation()
			clearOptionTagValue(kind)
		})

		const textSpan = document.createElement('span')
		textSpan.className = 'option-tag-text'
		textSpan.innerText = label

		tag.addEventListener('mouseenter', () => {
			icon.src = closeIcon as unknown as string
			icon.classList.add('option-tag-close')
			tag.classList.add('is-hovered')
		})
		tag.addEventListener('mouseleave', () => {
			icon.src = optionIcon
			icon.classList.remove('option-tag-close')
			tag.classList.remove('is-hovered')
		})

		tag.appendChild(icon)
		tag.appendChild(textSpan)

		return tag
	}

	const syncOptionTag = (kind: OptionTagKind) => {
		const value = getOptionTagValue(kind)
		if (!value) {
			removeOptionTag(kind)
			handleEditorInput()
			return
		}

		const label = getOptionTagLabel(kind)
		const existingTag = optionTags.value[kind]

		if (existingTag?.parentNode) {
			const text = existingTag.querySelector('.option-tag-text')
			if (text) text.textContent = label
			handleEditorInput()
			return
		}

		const editor = editorRef.value
		if (!editor) return

		const tag = createOptionTag(kind, label)
		const referenceNode = getOptionTagInsertBefore()
		if (referenceNode) {
			editor.insertBefore(tag, referenceNode)
		} else {
			editor.appendChild(tag)
		}

		if (tag.parentNode) {
			tag.parentNode.insertBefore(document.createTextNode('\u200B'), tag.nextSibling)
		}

		optionTags.value[kind] = tag
		refreshOptionTagKinds()
		handleEditorInput()
	}

	const syncAllOptionTags = () => {
		optionTagOrder.forEach(syncOptionTag)
	}

	const showPlaceholder = computed(() => {
		if (!editorRef.value) return true
		if (inputValue.value || reportTags.value.length > 0 || hasOptionTags()) return false

		const editor = editorRef.value
		const textContent = editor.textContent?.replace(/[\s\u200B\n]/g, '').trim() || ''
		const isEmptyHTML = editor.innerHTML === '' || editor.innerHTML === '<br>'
		const hasMeaningfulChildren = Array.from(editor.children).some(
			(child) => child.tagName !== 'BR' && !child.classList.contains('editor-tag')
		)

		return textContent === '' && !hasMeaningfulChildren && isEmptyHTML
	})

	const addReportTag = (text?: string) => {
		const tagText = text || options.t('Input.trendingReport')

		const tag = document.createElement('div')
		tag.contentEditable = 'false'
		const imgLeft = document.createElement('img')
		const imgClose = document.createElement('img')
		const textSpan = document.createElement('span')
		imgClose.className = 'close-icon'
		if (text) {
			tag.className = 'editor-tag restore flex-center'
			imgLeft.className = 'restore-icon'
			imgLeft.src = restoreIcon as unknown as string
			imgClose.src = restoreCloseIcon as unknown as string
			imgClose.className = 'close-icon restore'
			textSpan.className = 'restore-text'
		} else {
			tag.className = 'editor-tag report-btn flex-center'
			imgLeft.className = 'light-icon'
			imgLeft.src = lightIcon as unknown as string
			imgClose.src = closeIcon as unknown as string
		}

		textSpan.innerText = tagText

		imgClose.addEventListener('click', (ev) => {
			ev.stopPropagation()
			removeReportPromptText()
			tag.remove()
			const idx = reportTags.value.indexOf(tag)
			if (idx > -1) reportTags.value.splice(idx, 1)
			handleEditorInput()
		})

		tag.appendChild(imgLeft)
		tag.appendChild(textSpan)
		tag.appendChild(imgClose)

		const selection = window.getSelection()
		const isInEditor =
			editorRef.value && selection && editorRef.value.contains(selection.anchorNode)

		if (isInEditor && selection && selection.rangeCount > 0) {
			const range = selection.getRangeAt(0)
			range.insertNode(tag)
			focusEditor()
		} else if (editorRef.value) {
			editorRef.value.appendChild(tag)
			focusEditor()
		}

		reportTags.value.push(tag)
		const promptText = document.createTextNode('')
		if (tag.parentNode) {
			tag.parentNode.insertBefore(promptText, tag.nextSibling)
			const zwsp = document.createTextNode('\u200B')
			tag.parentNode.insertBefore(zwsp, promptText.nextSibling)
			const newRange = document.createRange()
			newRange.setStart(promptText, promptText.data.length)
			newRange.collapse(true)
			const currentSelection = window.getSelection()
			currentSelection?.removeAllRanges()
			currentSelection?.addRange(newRange)
		}
		reportPromptText.value = promptText
		typeReportPromptText(promptText, options.t('Input.reportPlaceholder'))
	}

	const toggleReportTag = (clear = false) => {
		const shouldClear = clear === true
		reportTags.value = reportTags.value.filter((tag) => tag.parentNode !== null)

		if (shouldClear) {
			removeReportPromptText()
			reportTags.value.forEach((tag) => {
				tag.remove()
			})
			reportTags.value = []
			return
		}

		if (reportTags.value.length > 0) {
			removeReportPromptText()
			reportTags.value.forEach((tag) => {
				if (
					tag.nextSibling &&
					tag.nextSibling.nodeType === Node.TEXT_NODE &&
					tag.nextSibling.textContent === '\u200B'
				) {
					tag.nextSibling.remove()
				}
				tag.remove()
			})
			reportTags.value = []
			handleEditorInput()
		} else {
			addReportTag()
		}
	}

	const cleanupEditor = () => {
		if (!editorRef.value) return

		const editor = editorRef.value

		if (editor.textContent) {
			const cleanedText = editor.textContent.replace(/[\u200B-\u200D\uFEFF]/g, '')

			if (!cleanedText.trim() && editor.children.length === 0) {
				editor.innerHTML = ''
				editor.textContent = ''
				reportTags.value = []
				removeAllOptionTags()
				stopReportTypewriter()
				reportPromptText.value = null
			}
		}
	}

	const autoResizeEditor = () => {
		if (options.isAgentMode.value) return
	}

	const handleEditorInput = () => {
		if (!editorRef.value) return

		let text = ''
		const walker = document.createTreeWalker(editorRef.value, NodeFilter.SHOW_TEXT, null)

		let node: Node | null
		while ((node = walker.nextNode())) {
			if (node.parentElement?.closest('.editor-tag')) continue
			text += node.textContent
		}

		text = text.replace(/[\s\u200B]+$/, '')
		inputValue.value = text

		const editor = editorRef.value
		const hasChildElements = editor.children.length > 0
		const hasTextContent = editor.textContent?.replace(/[\s\u200B]/g, '').trim().length > 0

		if (!hasChildElements && !hasTextContent) {
			editor.innerHTML = ''
			reportTags.value = []
			removeAllOptionTags()
			stopReportTypewriter()
			reportPromptText.value = null
		}

		autoResizeEditor()
	}

	const handleEditorPaste = (e: ClipboardEvent) => {
		e.preventDefault()
		const text = e.clipboardData?.getData('text/plain') || ''
		document.execCommand('insertText', false, text)
	}

	const handleKeyDown = (e: KeyboardEvent) => {
		if (e.key === 'Enter') {
			e.preventDefault()
			options.onSubmit()
			return
		}
		if (e.key !== 'Backspace') return

		const selection = window.getSelection()
		if (!selection || selection.rangeCount <= 0) return

		const range = selection.getRangeAt(0)
		if (!range.collapsed) return

		let nodeToDelete: Node | null = null
		const startContainer = range.startContainer
		const startOffset = range.startOffset

		if (startContainer.nodeType === Node.TEXT_NODE) {
			const textNode = startContainer as Text
			if (startOffset === textNode.length) {
				nodeToDelete = textNode.nextSibling
			}
		} else if (startContainer.nodeType === Node.ELEMENT_NODE) {
			nodeToDelete = startContainer.childNodes[startOffset]
		}

		if (
			nodeToDelete &&
			nodeToDelete.nodeType === Node.ELEMENT_NODE &&
			(nodeToDelete as Element).classList
		) {
			const element = nodeToDelete as Element
			const isEditorTag = element.classList.contains('editor-tag')
			const isReportTag = element.classList.contains('report-tag')
			const optionTagKind = (element as HTMLElement).dataset.optionTagKind as OptionTagKind

			if (optionTagKind) {
				e.preventDefault()
				clearOptionTagValue(optionTagKind)
				nextTick(() => {
					cleanupEditor()
					handleEditorInput()
				})
				return
			}

			if (isEditorTag || isReportTag) {
				e.preventDefault()
				if (reportTags.value.includes(element as HTMLElement)) {
					removeReportPromptText()
				}
				element.remove()

				const index = reportTags.value.indexOf(nodeToDelete as HTMLElement)
				if (index > -1) {
					reportTags.value.splice(index, 1)
				}

				nextTick(() => {
					cleanupEditor()
					handleEditorInput()
				})
				return
			}
		}

		nextTick(() => {
			if (editorRef.value) {
				const text = editorRef.value.textContent || ''
				const cleanedText = text.replace(/[\u200B-\u200D\uFEFF]/g, '')

				if (editorRef.value.innerHTML === '<br>' || !cleanedText.trim()) {
					editorRef.value.innerHTML = ''
					if (editorRef.value.children.length === 0) {
						reportTags.value = []
						reportPromptText.value = null
					}
				}
				handleEditorInput()
			}
		})
	}

	const clearEditorText = () => {
		if (editorRef.value) {
			editorRef.value.innerHTML = ''
		}
		inputValue.value = ''
	}

	const resetOptionTags = () => {
		optionTags.value = {}
		optionTagKinds.value = []
	}

	return {
		editorRef,
		inputValue,
		reportTags,
		optionTagKinds,
		showPlaceholder,
		setEditorElement,
		focusEditor,
		stopReportTypewriter,
		addReportTag,
		toggleReportTag,
		handleEditorInput,
		handleEditorPaste,
		handleKeyDown,
		autoResizeEditor,
		syncOptionTag,
		syncAllOptionTags,
		clearEditorText,
		resetOptionTags
	}
}
