const $t = document.createTextNode.bind(document);
const $er = (type, props, ...children) => $e({ tag: type, attrs: props, child: children });

const $e = function(info, attrs, child, created) {
    info = info || {};

    if(typeof info === 'string') {
        info = {tag: info};
        if(attrs)
            info.attrs = attrs;
        if(child)
            info.child = child;
        if(created)
            info.created = created;
    }

    const elem = document.createElement(info.tag || 'div');

    if(info.attrs) {
        const attrs = info.attrs;

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

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

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

                        const prop = elem[key];
                        let addFunc = null;

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

                        if(addFunc !== null) {
                            for(let j = 0; j < attr.length; ++j)
                                addFunc(attr[j]);
                        } else {
                            if(key === 'classList')
                                key = 'class';
                            elem.setAttribute(key, attr.toString());
                        }
                    } else {
                        for(const attrKey in attr)
                            elem[key][attrKey] = attr[attrKey];
                    }
                    break;

                case 'boolean':
                    if(attr)
                        elem.setAttribute(key, '');
                    break;

                default:
                    if(key === 'className')
                        key = 'class';
                    elem.setAttribute(key, attr.toString());
                    break;
            }
        }
    }

    if(info.child) {
        let children = info.child;

        if(!Array.isArray(children))
            children = [children];

        for(const child of children) {
            switch(typeof child) {
                case 'string':
                    elem.appendChild($t(child));
                    break;

                case 'object':
                    if(child instanceof Element)
                        elem.appendChild(child);
                    else if(child.getElement) {
                        const childElem = child.getElement();
                        if(childElem instanceof Element)
                            elem.appendChild(childElem);
                        else
                            elem.appendChild($e(child));
                    } else
                        elem.appendChild($e(child));
                    break;

                default:
                    elem.appendChild($t(child.toString()));
                    break;
            }
        }
    }

    if(info.created)
        info.created(elem);

    return elem;
};