import {
    Attr,
    DEFAULT_NAMESPACE_PREFIX,
    DEFAULT_NAMESPACE_URI,
    NAMESPACE_SEPARATOR,
    NO_PARENT_INDEX,
    TYPE_STRING
} from './core'
import { ParentNodeAssembler } from './parentnode'
import { AttrAssembler } from './attr'

const { isArray, prototype : { indexOf } } = Array
const { keys } = Object

/**
 * Attributes reduce handler
 * @param {{}} attrset
 * @param {String} name
 * @param {String} value
 * @returns {{}} attrset
 */
function attrset(attrset, { name, value }) {
    attrset[name] = value
    return attrset
}

/**
 * Element DOM node assembler
 */
export class ElementAssembler extends ParentNodeAssembler {
    /**
     * Initialize the element node with the defined properties
     * @param {*} init initializing dictionary
     * @returns {Node|*} initialized node
     */
    init(init) {
        if(init) {
            if(init.constructor === Object) super.init(init)
            else this.children = init
        }
    }

    /**
     * Set the unique identifier on the element
     * @param {String} id
     */
    set id(id) {
        this.node.id = id
    }

    /**
     * Get the unique identifier of the element
     * @returns {String}
     */
    get id() {
        return this.node.id
    }

    /**
     * Set the class name of the element
     * @param {String} className
     */
    set className(className) {
        this.node.className = className
    }

    /**
     * Get the class name of the element
     * @returns {String}
     */
    get className() {
        return this.node.className
    }

    /**
     * Set the class list of the element
     * @param {*} classList token / token list / token-boolean dict {String|Array|{}}
     */
    set classList(classList) {
        if(classList) {
            const list = this.node.classList
            if(isArray(classList)) {
                classList.forEach(token => this.classList = token)
            }
            else if(typeof classList === TYPE_STRING) {
                list.add(classList)
            }
            else keys(classList).forEach(token => {
                if(classList[token]) list.add(token)
            })
        }
    }

    /**
     * Get the class list of the element as an array
     * @returns {DOMTokenList} classList interface
     */
    get classList() {
        return this.node.classList
    }

    /**
     * @param {*} attr
     */
    set attr(attr) {
        if(attr instanceof AttrAssembler) {
            attr.ownerElement = this.node
        }
        else if(attr instanceof Attr) {
            if(attr.namespaceURI) this.node.setAttributeNodeNS(attr)
            else this.node.setAttributeNode(attr)
        }
        else {
            const { namespaceURI, name, value } = attr
            if(namespaceURI) {
                this.node.setAttributeNS(namespaceURI, name, value)
            }
            else this.node.setAttribute(name, value)
        }
    }

    /**
     * Set content attributes on the element
     * @param {*} attrset dictionary object
     */
    set attrset(attrset) {
        if(isArray(attrset)) this.attributes = attrset
        else {
            const node = this.node
            for(let name in attrset) {
                const value = attrset[name]
                if(typeof value === TYPE_STRING) {
                    node.setAttribute(name, value)
                }
            }
        }
    }

    /**
     * Get all content attributes of the element as a dictionary-object
     * @returns {{}}
     */
    get attrset() {
        return this.attributes.reduce(attrset, {})
    }

    /**
     * Set content attributes on the element
     * @param {*} attributes
     */
    set attributes(attributes) {
        if(isArray(attributes)) attributes.forEach(attr => {
            if(attr) this.attr = attr
        })
        else this.attrset = attributes
    }

    /**
     * Get all attributes of the element as an array
     * @returns {Array}
     */
    get attributes() {
        return Array.from(this.node.attributes)
    }

    /**
     * Append children to the element
     * @param {*} children
     */
    set children(children) {
        this.childNodes = children
    }

    /**
     * Get all children of the element as an array
     * @returns {Array}
     */
    get children() {
        return Array.from(this.node.children).map(node => {
            return this.getInstance(node)
        })
    }

    /**
     * Get the element index among its sibling elements or -1 if it has no parent
     * @returns {Number}
     */
    get elementIndex() {
        const parentNode = this.parentNode
        return parentNode?
            indexOf.call(parentNode.children, this.node) :
            NO_PARENT_INDEX
    }

    /**
     * Create the specified Element node
     * @param {String} [namespaceURI]
     * @param {String} [qualifiedName]
     * @returns {Element|*} created element
     */
    static create({
        namespaceURI = this.namespaceURI,
        qualifiedName = this.qualifiedName
    } = this) {
        return this.document.createElementNS(namespaceURI, qualifiedName)
    }

    /**
     * Default namespace URI
     * @returns {String}
     */
    static get namespaceURI() {
        return DEFAULT_NAMESPACE_URI
    }

    /**
     * The qualified name of the element node
     * @returns {String}
     */
    static get qualifiedName() {
        const { prefix, localName } = this
        return prefix?
            prefix + NAMESPACE_SEPARATOR + localName :
            localName
    }

    /**
     * The XML namespace prefix
     * @returns {String}
     */
    static get prefix() {
        return DEFAULT_NAMESPACE_PREFIX
    }

    /**
     * The local name of the element node
     * @returns {String}
     */
    static get localName() {
        return this === ElementAssembler? element.name : this.name
    }
}

/**
 * Element assembler factory
 * @param {*} init
 * @returns {ElementAssembler}
 */
export function element(init) {
    return new ElementAssembler(init)
}

@@ -12,16 +12,30 @@
   Attr,%0A
+    Document,%0A
     DEFA
@@ -56,16 +56,16 @@
 PREFIX,%0A
-
     DEFA
@@ -161,16 +161,102 @@
 ./core'%0A
+import %7B AttrAssembler %7D from './attr'%0Aimport %7B DocumentAssembler %7D from './document'%0A
 import %7B
@@ -301,47 +301,8 @@
 ode'
-%0Aimport %7B AttrAssembler %7D from './attr'
 %0A%0Aco
@@ -4463,32 +4463,618 @@
       %7D)%0A    %7D%0A%0A
+    /**%0A     * @param %7BParentNodeAssembler%7C*%7D parentNode%0A     */%0A    set parentNode(parentNode) %7B%0A        if(parentNode instanceof DocumentAssembler) %7B%0A            parentNode.documentElement = this%0A        %7D%0A        else %7B%0A            if(parentNode instanceof Document) %7B%0A                if(parentNode.documentElement) %7B%0A                    parentNode.documentElement.remove()%0A                %7D%0A            %7D%0A            super.parentNode = parentNode%0A        %7D%0A    %7D%0A%0A    /**%0A     * @returns %7BParentNodeAssembler%7C*%7D%0A     */%0A    get parentNode() %7B%0A        return super.parentNode%0A    %7D%0A%0A
     /**%0A     * G

replace documentElement on Document parentNode setting