import '../types';

let namespace = {
    svg: "http://www.w3.org/2000/svg"
    // Plain HTML doens't need a namespace, if it did it would've shown up here..
};

/**
 * Takes care of rendering a typical VNode (like div, span or any custom-element)
 *
 * @class
 * @implements {VNodeRenderer}
 */
export const NodeTreeRenderer = {
    /**
     *
     * @param {VRenderItem} item
     */
    create(item){
        let vnode = item.vnode;
        if(typeof(vnode.type) === 'string'){
            // String-type              -> DOM
            if(item.inSvg){
                return item.document.createElementNS(namespace.svg, vnode.type);
            }else{
                return item.document.createElement(vnode.type);
            }
        }else if(vnode.type?.tagName){
            // Object-type              -> CUSTOM-ELEMENT
            return item.document.createElement(vnode.type.tagName);
        }else{
            throw new Error("Unrecognized vnode type", vnode);
        }
    },
    
    /**
     * @param {VRenderItem} item
     */
    update(item, meta){
        let vnode = item.vnode;
        let vtype = meta?.normedType||item.vnode?.type;
        
        /**
         * @type {VNodeProps}
         */
        let props = vnode?.props || {};
        /**
         * @type {VNodeProps}
         */
        let oldProps = item.old?.props || {};
        let host = item.host;
    
        // Diff the props
        let propDiffs = [];
        for(let key in oldProps){
            let oldVal = oldProps[key];
            if(!props.hasOwnProperty(key)){
                propDiffs.push([key, undefined, oldVal]);
            }else{
                let newVal = props[key];
                if(oldVal!==newVal){
                    propDiffs.push([key, newVal, oldVal]);
                }
            }
        }
        for(let key in props){
            let newVal = props[key];
            if(!oldProps.hasOwnProperty(key)){
                propDiffs.push([key,newVal, undefined]);
            }
        }
        
        // Now apply each
        for(let [key, newVal, oldVal] of propDiffs){
            let special = VNODE_SPECIAL_PROPS[key];
            if(special === false) {
                // Ignore the prop
            }else if(vtype.props?.has && vtype.props.has(key)){
                // Registered prop of the Custom-Element
                host[key] = newVal;
            }else if(special instanceof Function){
                // Special prop
                special({host, newVal, oldVal, key});
            }else if(key.slice(0,2)==='on' && key[2]>='A' && key[2]<='Z'){
                // Event-prop
                // Convert event name from camelCase to lowercase
                let eventName = key[2].toLowerCase()+key.slice(3).toLowerCase(); // This used to dash case things: .replace(/[A-Z]/g, function(c){return('-'+c.toLowerCase())});
                if(!newVal){
                    host.removeEventListener(eventName, oldVal);
                }else{
                    host.addEventListener(eventName, newVal);
                }
            }else{
                // Assumed to be just an attribute
                if(newVal===undefined || newVal === false || newVal===null || newVal===''){
                    host.removeAttribute(key);
                }else if(newVal === true){
                    host.setAttribute(key, "");
                }else{
                    host.setAttribute(key, newVal);
                }
            }
        }
    }
};



// Keys of a Element that need special handling rather than simply using setAttribute
const VNODE_SPECIAL_PROPS = {
    ['key']: false,
    ['ref']: false,
    ['value']: ({host,  newVal, oldVal, key})=>{
      if(newVal!==oldVal) host.value = newVal;
    },
    ['className']: ({host,  newVal, oldVal, key})=>{
        let oldClasses = new Set();
        let newClasses = new Set();
        
        if(typeof(oldVal) === 'string'){
            oldVal = oldVal.split(' ').map(x=>x.trim()).filter(x=>x);
            for(let className of oldVal){ oldClasses.add(className); }
        }else if(typeof(oldVal)==='object'){
            for(let key in oldVal){
                if(oldVal[key]) oldClasses.add(key);
            }
        }
        
        if(typeof(newVal) === 'string'){
            newVal = newVal.split(' ').map(x=>x.trim()).filter(x=>x);
            for(let className of newVal){ newClasses.add(className); }
        }else if(typeof(newVal)==='object'){
            for(let key in newVal){
                if(newVal[key]) newClasses.add(key);
            }
        }
        
        host.classList.remove(...Array.from(oldClasses));
        host.classList.add(...Array.from(newClasses));
    },
    ['style']: ({host,  newVal, oldVal, key})=>{
        if(typeof(newVal) === 'string'){
            host.setAttribute('style', newVal);
        }else if(typeof(newVal) ==='object'){
            if(oldVal){
                for(let key in oldVal){
                    if(!newVal[key]){
                        host.style[key]=null;
                    }
                }
            }
            for(let key in newVal){
                host.style[key] = newVal[key];
            }
        }else if(!newVal){
            host.removeAttribute('style');
        }
    },
    ['events']: ({host,  newVal, oldVal, key})=>{
        //TODO!!
        throw new Error("We're still planning to implement this but it hasn't been done yet!");
    },
};
