import type { DirectiveBinding } from 'vue'

interface PatternValue {
	pattern?: RegExp
	transform?: (value: string) => string
}

const DEFAULT_PATTERNS: Record<string, PatternValue> = {
	pint: { pattern: /\d/ },
	int: { pattern: /[\d-]/ },
	pnum: { pattern: /[\d.]/ },
	tel: { pattern: /[\d.\-()\s+]/ },
	money: { pattern: /[\d.\s,]/ },
	number: { pattern: /[\d\-.]/ },
	num: { pattern: /[\d\-.]/ },
	hex: { pattern: /[0-9a-f]/i },
	email: { pattern: /[\w.\-@]/ },
	alpha: { pattern: /[a-z_]/i },
	alphanum: { pattern: /\w/ },
	lower: { pattern: /[a-z]/i, transform: (v: string) => v.toLowerCase() },
	upper: { pattern: /[A-Z]/i, transform: (v: string) => v.toUpperCase() },
	slug: { pattern: /[a-z0-9-]/i, transform: (v: string) => v.toLowerCase().replace(/\s/g, '-') },
}

interface PatternState {
	validateOnly?: boolean
	pattern?: RegExp
	modifier?: string
	keydownEvent?: EventListenerOrEventListenerObject
	pasteEvent?: EventListenerOrEventListenerObject
}

const stateMap = new WeakMap<HTMLElement, PatternState>()

function getTarget(el: HTMLElement): HTMLInputElement | HTMLTextAreaElement | null {
	if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
		return el
	}
	const target = el.querySelector('input, textarea')
	return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement ? target : null
}

function getModifiers(binding: DirectiveBinding): string {
	const modifiers = Object.keys(binding.modifiers || {})
	return modifiers.length ? modifiers[modifiers.length - 1] : ''
}

function getRegex(state: PatternState): RegExp {
	if (state.pattern) return state.pattern
	if (state.modifier) return DEFAULT_PATTERNS[state.modifier].pattern || /./
	return /./
}

function bindEvents(el: HTMLElement, target: HTMLInputElement | HTMLTextAreaElement, state: PatternState) {
	state.keydownEvent = ((event: KeyboardEvent) => { onKeydown(event, target, state) }) as EventListener
	state.pasteEvent = ((event: ClipboardEvent) => { onPaste(event, target, state) }) as EventListener

	target.addEventListener('keydown', state.keydownEvent)
	target.addEventListener('paste', state.pasteEvent)
}

function unbindEvents(target: HTMLInputElement | HTMLTextAreaElement, state: PatternState) {
	if (state.keydownEvent) target.removeEventListener('keydown', state.keydownEvent)
	if (state.pasteEvent) target.removeEventListener('paste', state.pasteEvent)

	state.keydownEvent = undefined
	state.pasteEvent = undefined
}

function onKeydown(event: KeyboardEvent, target: HTMLInputElement | HTMLTextAreaElement, state: PatternState) {
	if (event.ctrlKey || event.altKey || event.metaKey || event.key === 'Tab' || event.key === 'Backspace' || event.key === 'Delete' || event.key === 'ArrowLeft' || event.key === 'ArrowRight' || event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Enter') return

	const regex = getRegex(state)
	const testKey = event.key

	if (!regex.test(testKey)) {
		event.preventDefault()
	} else if (state.modifier) {
		const { transform } = DEFAULT_PATTERNS[state.modifier]
		if (transform) {
			console.log('transform', transform)
			event.preventDefault()
			const transformedValue = transform(testKey)
			const cursorPos = target.selectionStart ?? 0
			const newValue = target.value.slice(0, cursorPos) + transformedValue + target.value.slice(cursorPos)
			target.value = newValue
			target.setSelectionRange(cursorPos + transformedValue.length, cursorPos + transformedValue.length)
		}
	}
}

function onPaste(event: ClipboardEvent, target: HTMLInputElement | HTMLTextAreaElement, state: PatternState) {
	const regex = getRegex(state)
	const clipboardData = event.clipboardData?.getData('text') || ''

	if (!regex.test(clipboardData)) {
		event.preventDefault()
	} else if (state.modifier) {
		const { transform } = DEFAULT_PATTERNS[state.modifier]
		if (transform) {
			event.preventDefault()
			const transformedValue = transform(clipboardData)
			const cursorPos = target.selectionStart ?? 0
			const newValue = target.value.slice(0, cursorPos) + transformedValue + target.value.slice(cursorPos)
			target.value = newValue
			target.setSelectionRange(cursorPos + transformedValue.length, cursorPos + transformedValue.length)
		}
	}
}

const pattern = {
	beforeMount(el: HTMLElement, binding: DirectiveBinding) {
		const target = getTarget(el)
		if (!target) return

		const state: PatternState = {
			modifier: getModifiers(binding),
		}

		if (typeof binding.value === 'object') {
			state.pattern = binding.value.pattern || /./
			state.validateOnly = binding.value.validateOnly || false
		}

		stateMap.set(el, state)
		bindEvents(el, target, state)
	},
	updated(el: HTMLElement, binding: DirectiveBinding) {
		const target = getTarget(el)
		if (!target) return

		const state = stateMap.get(el)
		if (!state) return

		unbindEvents(target, state)

		state.modifier = getModifiers(binding)

		if (typeof binding.value === 'object') {
			state.pattern = binding.value.pattern || /./
			state.validateOnly = binding.value.validateOnly || false
		}

		bindEvents(el, target, state)
	},
	unmounted(el: HTMLElement) {
		const target = getTarget(el)
		const state = stateMap.get(el)

		if (target && state) {
			unbindEvents(target, state)
		}

		stateMap.delete(el)
	},
}

export default pattern
