import type { Attributes, BglFormSchemaT, Path } from '@bagelink/vue'

const debouncers = new Map<() => void, ReturnType<typeof setTimeout>>()

export function debounce(fn: () => void, wait: number = 500) {
	clearTimeout(debouncers.get(fn))
	const timeout = setTimeout(() => {
		fn()
		debouncers.delete(fn)
	}, wait)
	debouncers.set(fn, timeout)
}

export function slugify(str: string) {
	return str
		.toLowerCase()
		.replace(/[^a-z0-9-\s]/g, '')
		.split(/[\s-]+/)
		.join('-')
}

export function keyToLabel(key?: string): string | undefined {
	if (key === undefined) return key
	return (
		key
			.split('_')
			.map(k => k.charAt(0).toUpperCase() + k.slice(1))
			.join(' ') || key
	)
}

export async function copyText(text: string, cb?: (msg: string) => void) {
	await navigator.clipboard.writeText(text)
	if (cb) cb('Copied to clipboard')
}

export function initials(...strArr: string[]) {
	strArr = strArr.flatMap(str => str.split(/\s/))
	return strArr.map(str => str.charAt(0)).join('')
}

export function useEscape(event: KeyboardEvent, closeModel: () => void) {
	if (event.key === 'Escape') {
		closeModel()
	}
}

export function classify(fieldVal?: any, row?: any, ...classes: any[]) {
	return classes
		.map((cls) => {
			if (typeof cls === 'function') return cls(fieldVal, row)
			return cls
		})
		.join(' ')
}

export function bindAttrs<T, P extends Path<T>>(
	attrs?: Attributes<T, P>,
	fieldVal?: any,
	row?: T
) {
// TODO: Fix this so that you don't have to return a fn for other on* event handlers
	if (!attrs) return {}

	const exclude = ['class', 'onClick'] as const
	const arr = Object.entries(attrs)
		.filter(([key]) => !exclude.includes(key as typeof exclude[number]))
		.map(([key, value]) => [
			key,
			typeof value === 'function' ? value(fieldVal, row) : value,
		])

	const resolvedAttrs: { [key: string]: any } = Object.fromEntries(arr)

	// resolvedAttrs.onClick = attrs.onClick
	// add back the rest of the attrs that were excluded //! except for class
	for (const key of exclude.slice(1)) {
		if (attrs[key]) {
			resolvedAttrs[key] = attrs[key]
		}
	}

	return resolvedAttrs
}

export function iffer(field: any, itemData: any) {
	if (field['v-if'] === undefined) return true
	if (typeof field['v-if'] === 'boolean') return field['v-if']
	if (typeof field['v-if'] === 'string') return true
	if (typeof field['v-if'] === 'function')
		return field['v-if']?.(itemData?.[field.id], itemData)
	return true
}

export function denullify(itemData?: { [key: string]: any }, fieldID?: string) {
	if (!fieldID) return
	return itemData ? itemData[fieldID] : undefined
}

export const isDate = (dateToTest: any) => !Number.isNaN(Date.parse(dateToTest))

export function getFallbackSchema<T>(
	data?: any[],
	columns?: string[]
): BglFormSchemaT<T> {
	const keys = Array.from(new Set((data ?? []).flatMap(Object.keys)))

	const schema = keys.map(id => ({
		id,
		label: keyToLabel(id),
		transform: (val: any) => {
			const dateFields = ['created_at', 'updated_at']
			if (dateFields.includes(id)) return val ? new Date(val).toLocaleString() : val
			return val
		},
	})) as BglFormSchemaT<T>

	return columns
		? schema.filter(f => columns.includes(f.id as string) || !f.id)
		: schema
}

export function sleep(ms: number = 100) {
	return new Promise(resolve => setTimeout(resolve, ms))
}

// Keep track of loading scripts
const scriptsLoading = new Map<string, Promise<void>>()

export async function appendScript(src: string, options?: { id?: string }): Promise<void> {
	const scriptId = options?.id || src
	await sleep(1)
	// If this script is already loading, return the existing promise
	if (scriptsLoading.has(scriptId)) {
		return scriptsLoading.get(scriptId)!
	}

	// Check if script is already in the document
	if (options?.id && document.getElementById(options.id)) {
		return Promise.resolve()
	} else if (document.querySelector(`script[src="${src}"]`)) {
		return Promise.resolve()
	}

	// Create a new loading promise for this script
	const loadingPromise = new Promise<void>((resolve, reject) => {
		const script = document.createElement('script')
		script.src = src
		if (options?.id) {
			script.id = options.id
		}

		script.onload = () => {
			resolve()
			// Remove from loading scripts map when done
			scriptsLoading.delete(scriptId)
		}

		script.onerror = (err) => {
			reject(err)
			// Remove from loading scripts map on error
			scriptsLoading.delete(scriptId)
		}

		document.head.append(script)
	})

	// Store the loading promise for this script
	scriptsLoading.set(scriptId, loadingPromise)

	return loadingPromise
}

export function appendStyle(src: string): Promise<void> {
	return new Promise((resolve, reject) => {
		if (document.querySelector(`link[href="${src}"]`)) {
			resolve()
			return
		}
		const link = document.createElement('link')
		link.href = src
		link.rel = 'stylesheet'
		link.onload = () => { resolve() }
		link.onerror = reject
		document.head.append(link)
	})
}

export function normalizeURL(url: string) {
	if (url.startsWith('https://')) return url
	url = url.replace(/http:\/\//, '')
	return `https://${url}`
}

export function normalizeDimension(value: string | number | undefined, defaultMetric = 'px'): string | undefined {
	if (typeof value === 'number') return `${value}${defaultMetric}`
	return value
}

export * as bagelFormUtils from './BagelFormUtils'
export { useForm } from './BagelFormUtils'

export { useLang } from './lang'

export { formatString } from './strings'
export { useDebounceFn } from '@vueuse/core'

const fileBaseUrl = (import.meta.env.VITE_FILE_BASE_URL || 'https://files.bagel.design').replace(/\/$/, '')
const bagelBaseUrl = import.meta.env.VITE_BAGEL_BASE_URL

const URL_REGEX = /^https?:\/\/|^\/\//

export function pathKeyToURL(pathKey?: string) {
	if (!pathKey || URL_REGEX.test(pathKey)) return pathKey
	if (pathKey.startsWith('static/')) {
		return `${bagelBaseUrl}/${pathKey}`
	}
	return `${fileBaseUrl}/${pathKey}`
}

export function getNestedValue(obj: any, path?: string, defaultValue: any = undefined): any {
	if (!path) return obj
	const keys = path.split(/[.[]/)
	let current = obj

	for (const key of keys) {
		if (!current || typeof current !== 'object' || !(key in current)) {
			return defaultValue
		}
		current = current[key]
	}

	return current ?? defaultValue
}

const upgradeHeaders = (url: string) => url.replace(/http:\/\//, '//')

export function downloadFile(source: string | Blob, fileName?: string) {
	const link = document.createElement('a')
	link.target = '_blank'

	if (typeof source === 'string') {
		link.href = upgradeHeaders(source)
		link.download = fileName || source.split('/').pop() || 'download'
	} else {
		const url = URL.createObjectURL(source)
		link.href = url
		link.download = fileName || 'download'
		URL.revokeObjectURL(url)
	}

	document.body.appendChild(link)
	link.click()
	document.body.removeChild(link)
}
