import { enqueue } from './pulse'
import { isComment, safeSetAttribute } from './util'

/* === Common DOM Updates === */

/**
 * Create a new element with the given tag name and attributes
 * 
 * @param element - The parent element to append the new element to
 * @param tag - The tag name of the new element
 * @param attributes - The attributes to set on the new element
 * @returns {Promise<Element>}
 */
const ce = <E extends Element>(
	parent: E,
	tag: string,
	attributes: Record<string, string> = {},
	text?: string
): Promise<Element> => enqueue(() => {
	const child = document.createElement(tag)
	for (const [key, value] of Object.entries(attributes))
		safeSetAttribute(child, key, value)
	if (text) child.textContent = text
	parent.append(child)
	return child
}, [parent, 'e'])

/**
 * Remove the given element from its parent
 * 
 * @param element -	The element to remove
 * @returns {Promise<null>}
 */
const re = <E extends Element>(
	element: E
): Promise<null> => enqueue(() => {
	element.remove()
	return null
}, [element, 'r'])

/**
 * Update the text content of the given element while preserving comments
 * 
 * @param element - The element whose text content to update
 * @param text - The new text content
 * @returns {Promise<E>}
 */
const st = <E extends Element>(
	element: E,
	text: string
): Promise<E> => enqueue(() => {
	Array.from(element.childNodes)
		.filter(node => !isComment(node))
		.forEach(node => node.remove())
	element.append(document.createTextNode(text))
	return element
}, [element, 't'])

/**
 * Update an attribute of the given element
 * 
 * @param element - The element whose attribute to update
 * @param attribute - The attribute to update
 * @param value - The new value
 * @returns {Promise<E>}
 */
const sa = <E extends Element>(
	element: E,
    attribute: string,
    value: string
): Promise<E> => enqueue(() => {
	safeSetAttribute(element, attribute, value)
	return element
}, [element, `a:${attribute}`])

/**
 * Remove an attribute of the given element
 * 
 * @param element - The element whose attribute to remove
 * @param attribute - The attribute to remove
 * @returns {Promise<E>}
 */
const ra = <E extends Element>(
	element: E,
    attribute: string
): Promise<E> => enqueue(() => {
	element.removeAttribute(attribute)
	return element
}, [element, `a:${attribute}`])

/**
 * Toggle an attribute of the given element
 * 
 * @param element - The element whose attribute to toggle
 * @param attribute - The attribute to toggle
 * @param value - The new value
 * @returns {Promise<E>}
 */
const ta = <E extends Element>(
	element: E,
    attribute: string,
    value: boolean
): Promise<E> => enqueue(() => {
	element.toggleAttribute(attribute, value)
	return element
}, [element, `a:${attribute}`])

/**
 * Toggle a class of the given element
 * 
 * @param element - The element whose class to toggle
 * @param token - The class token to toggle
 * @param value - The new value
 * @returns {Promise<E>}
 */
const tc = <E extends Element>(
	element: E,
    token: string,
    value: boolean
): Promise<E> => enqueue(() => {
	element.classList.toggle(token, value)
	return element
}, [element, `c:${token}`])

/**
 * Update a style property of the given element
 * 
 * @param element - The element whose style property to update
 * @param property - The style property to update
 * @param value - The new value
 * @returns {Promise<E>}
 */
const ss = <E extends HTMLElement | SVGElement | MathMLElement>(
	element: E,
    property: string,
    value: string
): Promise<E> => enqueue(() => {
	element.style.setProperty(property, value)
	return element
}, [element, `s:${property}`])

/**
 * Remove a style property of the given element
 * 
 * @param element - The element to update
 * @param property - The style property to remove
 * @returns {Promise<E>}
 */
const rs = <E extends HTMLElement | SVGElement | MathMLElement>(
	element: E,
    property: string
): Promise<E> => enqueue(() => {
	element.style.removeProperty(property)
	return element
}, [element, `s:${property}`])

/**
 * Replace the inner HTML of the given element
 * 
 * @param element - The element whose inner HTML to update
 * @param content - The new inner HTML
 * @returns {Promise<E>}
 */
const dangerouslySetInnerHTML = <E extends Element>(
	element: E,
    content: string
): Promise<E> => enqueue(() => {
	element.innerHTML = content
    return element
}, [element, 'h'])

export { ce, re, st, sa, ra, ta, tc, ss, rs, dangerouslySetInnerHTML }