const $id = document.getElementById.bind(document);
const $query = document.querySelector.bind(document);
const $queryAll = document.querySelectorAll.bind(document);
const $text = document.createTextNode.bind(document);

const $insertBefore = function(target, element) {
    target.parentNode.insertBefore(element, target);
};

const $appendChild = function(element, child) {
    switch(typeof child) {
        case 'undefined':
            break;

        case 'string':
            element.appendChild($text(child));
            break;

        case 'function':
            $appendChild(element, child());
            break;

        case 'object':
            if(child === null)
                break;

            if(child instanceof Node)
                element.appendChild(child);
            else if(child?.element instanceof Node)
                element.appendChild(child.element);
            else if(typeof child?.toString === 'function')
                element.appendChild($text(child.toString()));
            break;

        default:
            element.appendChild($text(child.toString()));
            break;
    }
};

const $appendChildren = function(element, ...children) {
    for(const child of children)
        $appendChild(element, child);
};

const $removeChildren = function(element) {
    while(element.lastChild)
        element.removeChild(element.lastChild);
};

const $fragment = function(props, ...children) {
    const fragment = document.createDocumentFragment();
    $appendChildren(fragment, ...children);
    return fragment;
};

const $element = function(type, props, ...children) {
    if(typeof type === 'function')
        return new type(props ?? {}, ...children);

    const element = document.createElement(type ?? 'div');

    if(props)
        for(let key in props) {
            const prop = props[key];
            if(prop === undefined || prop === null)
                continue;

            switch(typeof prop) {
                case 'function':
                    if(key.substring(0, 2) === 'on')
                        key = key.substring(2).toLowerCase();
                    element.addEventListener(key, prop);
                    break;

                case 'object':
                    if(prop instanceof Array) {
                        if(key === 'class')
                            key = 'classList';

                        const attr = element[key];
                        let addFunc = null;

                        if(attr instanceof Array)
                            addFunc = attr.push.bind(attr);
                        else if(attr instanceof DOMTokenList)
                            addFunc = attr.add.bind(attr);

                        if(addFunc !== null) {
                            for(let j = 0; j < prop.length; ++j)
                                addFunc(prop[j]);
                        } else {
                            if(key === 'classList')
                                key = 'class';
                            element.setAttribute(key, prop.toString());
                        }
                    } else {
                        if(key === 'class' || key === 'className')
                            key = 'classList';

                        let setFunc = null;
                        if(element[key] instanceof DOMTokenList)
                            setFunc = (ak, av) => { if(av) element[key].add(ak); };
                        else if(element[key] instanceof CSSStyleDeclaration)
                            setFunc = (ak, av) => {
                                if(ak.includes('-'))
                                    element[key].setProperty(ak, av);
                                else
                                    element[key][ak] = av;
                            };
                        else
                            setFunc = (ak, av) => { element[key][ak] = av; };

                        for(const attrKey in prop) {
                            const attrValue = prop[attrKey];
                            if(attrValue)
                                setFunc(attrKey, attrValue);
                        }
                    }
                    break;

                case 'boolean':
                    if(prop)
                        element.setAttribute(key, '');
                    break;

                default:
                    if(key === 'className')
                        key = 'class';

                    element.setAttribute(key, prop.toString());
                    break;
            }
        }

    $appendChildren(element, ...children);

    return element;
};