/********

 Convert chai-dom 1.8.1 to es module, without any other modification.
 From https://github.com/nathanboktae/chai-dom/blob/86c3423/chai-dom.js
 See https://github.com/nathanboktae/chai-dom/issues/38

 Usage:
 import { chaiDom } from '<path-to>/chai-dom';
 chai.use(chaiDom);

 Copyright (c) 2015 Nathan Black and other contributors

 This work is licensed under the terms of the MIT license. For a copy, see <https://opensource.org/licenses/MIT>.

 **********/

export function chaiDom(chai, utils) {
    var flag = utils.flag,

        elToString = function(el) {
            let desc
            if (isNodeList(el)) {
                if (el.length === 0) return 'empty NodeList'
                desc = Array.prototype.slice.call(el, 0, 5).map(elToString).join(', ')
                return el.length > 5 ? desc + '... (+' + (el.length - 5) + ' more)' : desc
            }
            if (!isHTMLElement(el)) {
                return String(el)
            }

            desc = el.tagName.toLowerCase()
            if (el.id) {
                desc += '#' + el.id
            }
            if (el.className) {
                desc += '.' + String(el.className).replace(/\s+/g, '.')
            }
            Array.prototype.forEach.call(el.attributes, function(attr) {
                if (attr.name !== 'class' && attr.name !== 'id') {
                    desc += '[' + attr.name + (attr.value ? '="' + attr.value + '"]' : ']')
                }
            })
            return desc
        },

        attrAssert = function(name, val) {
            let el = flag(this, 'object'), actual = el.getAttribute(name)

            if (!flag(this, 'negate') || undefined === val) {
                this.assert(
                    !!el.attributes[name]
                    , 'expected ' + elToString(el) + ' to have an attribute #{exp}'
                    , 'expected ' + elToString(el) + ' not to have an attribute #{exp}'
                    , name
                )
            }

            if (undefined !== val) {
                this.assert(
                    val === actual
                    , 'expected ' + elToString(el) + ' to have an attribute ' + utils.inspect(name) + ' with the value #{exp}, but the value was #{act}'
                    , 'expected ' + elToString(el) + ' not to have an attribute ' + utils.inspect(name) + ' with the value #{act}'
                    , val
                    , actual
                )
            }

            flag(this, 'object', actual)
        },

        isHTMLElement = function(el) {
            return el.nodeType === 1 // window.Node.ELEMENT_NODE
        },

        isNodeList = function(obj) {
            return Object.prototype.toString.call(obj) === '[object NodeList]'
        }

    utils.elToString = elToString
    chai.Assertion.addMethod('attr', attrAssert)
    chai.Assertion.addMethod('attribute', attrAssert)

    chai.Assertion.addMethod('class', function(className) {
        var el = flag(this, 'object')
        this.assert(
            el.classList.contains(className)
            , 'expected ' + elToString(el) + ' to have class #{exp}'
            , 'expected ' + elToString(el) + ' not to have class #{exp}'
            , className
        )
    })

    chai.Assertion.addMethod('id', function(id) {
        var el = flag(this, 'object')
        this.assert(
            el.id == id
            , 'expected ' + elToString(el) + ' to have id #{exp}'
            , 'expected ' + elToString(el) + ' not to have id #{exp}'
            , id
        )
    })

    chai.Assertion.addMethod('html', function(html) {
        var el = flag(this, 'object'), actual = flag(this, 'object').innerHTML

        if (flag(this, 'contains')) {
            this.assert(
                actual.indexOf(html) >= 0
                , 'expected #{act} to contain HTML #{exp}'
                , 'expected #{act} not to contain HTML #{exp}'
                , html
                , actual
            )
        } else {
            this.assert(
                actual === html
                , 'expected ' + elToString(el) + ' to have HTML #{exp}, but the HTML was #{act}'
                , 'expected ' + elToString(el) + ' not to have HTML #{exp}'
                , html
                , actual
            )
        }
    })

    chai.Assertion.addChainableMethod('trimmed', null, function() {
        flag(this, 'trim-text', true)
    })

    chai.Assertion.addMethod('text', function(text) {
        var obj = flag(this, 'object'), contains = flag(this, 'contains'),
            trim = flag(this, 'trim-text'), actual, result

        if (isNodeList(obj)) {
            actual = Array.prototype.map.call(obj, function(el) { return trim ? el.textContent.trim() : el.textContent })
            if (Array.isArray(text)) {
                result = contains ?
                    text[flag(this, 'negate') ? 'some' : 'every'](function(t) {
                        return Array.prototype.some.call(obj, function(el) {
                            return (trim ? el.textContent.trim() : el.textContent) === t
                        })
                    })
                    :
                    utils.eql(actual, text)

                actual = actual.join()
                text = text.join()
            } else {
                actual = actual.join('')
                result = contains ? actual.indexOf(text) >= 0 : actual === text
            }
        } else {
            actual = trim ? obj.textContent.trim() : obj.textContent
            result = contains ? actual.indexOf(text) >= 0 : actual === text
        }

        var objDesc = elToString(obj), textMsg = trim ? 'trimmed text' : 'text'
        if (contains) {
            this.assert(
                result
                , 'expected ' + objDesc + ' to contain #{exp}, but the ' + textMsg + ' was #{act}'
                , 'expected ' + objDesc + ' not to contain #{exp}, but the ' + textMsg + ' was #{act}'
                , text
                , actual
            )
        } else {
            this.assert(
                result
                , 'expected ' + objDesc + ' to have ' + textMsg + ' #{exp}, but the ' + textMsg + ' was #{act}'
                , 'expected ' + objDesc + ' not to have ' + textMsg + ' #{exp}'
                , text
                , actual
            )
        }
    })

    chai.Assertion.addMethod('value', function(value) {
        var el = flag(this, 'object'), actual = flag(this, 'object').value
        this.assert(
            flag(this, 'object').value === value
            , 'expected ' + elToString(el) + ' to have value #{exp}, but the value was #{act}'
            , 'expected ' + elToString(el) + ' not to have value #{exp}'
            , value
            , actual
        )
    })

    chai.Assertion.overwriteProperty('exist', function(_super) {
        return function() {
            var obj = flag(this, 'object')
            if (isNodeList(obj)) {
                this.assert(
                    obj.length > 0
                    , 'expected an empty NodeList to have nodes'
                    , 'expected ' + elToString(obj) + ' to not exist')
            } else {
                _super.apply(this, arguments)
            }
        }
    })

    chai.Assertion.overwriteProperty('empty', function(_super) {
        return function() {
            var obj = flag(this, 'object')
            if (isHTMLElement(obj)) {
                this.assert(
                    obj.children.length === 0
                    , 'expected ' + elToString(obj) + ' to be empty'
                    , 'expected ' + elToString(obj) + ' to not be empty')
            } else if (isNodeList(obj)) {
                this.assert(
                    obj.length === 0
                    , 'expected ' + elToString(obj) + ' to be empty'
                    , 'expected ' + elToString(obj) + ' to not be empty')
            } else {
                _super.apply(this, arguments)
            }
        }
    })

    chai.Assertion.overwriteChainableMethod('length',
        function(_super) {
            return function(length) {
                var obj = flag(this, 'object')
                if (isNodeList(obj) || isHTMLElement(obj)) {
                    var actualLength = obj.children ? obj.children.length : obj.length
                    this.assert(
                        actualLength === length
                        , 'expected ' + elToString(obj) + ' to have #{exp} children but it had #{act} children'
                        , 'expected ' + elToString(obj) + ' to not have #{exp} children'
                        , length
                        , actualLength
                    )
                } else {
                    _super.apply(this, arguments)
                }
            }
        },
        function(_super) {
            return function() {
                _super.call(this)
            }
        }
    )


    chai.Assertion.overwriteMethod('match', function(_super) {
        return function(selector) {
            var obj = flag(this, 'object')
            if (isHTMLElement(obj)) {
                this.assert(
                    obj.matches(selector)
                    , 'expected ' + elToString(obj) + ' to match #{exp}'
                    , 'expected ' + elToString(obj) + ' to not match #{exp}'
                    , selector
                )
            } else if (isNodeList(obj)) {
                this.assert(
                    (!!obj.length && Array.prototype.every.call(obj, function(el) { return el.matches(selector) }))
                    , 'expected ' + elToString(obj) + ' to match #{exp}'
                    , 'expected ' + elToString(obj) + ' to not match #{exp}'
                    , selector
                )
            } else {
                _super.apply(this, arguments)
            }
        }
    })

    chai.Assertion.overwriteChainableMethod('contain',
        function(_super) {
            return function(subitem) {
                var obj = flag(this, 'object')
                if (isHTMLElement(obj)) {
                    if (typeof subitem === 'string') {
                        this.assert(
                            !!obj.querySelector(subitem)
                            , 'expected ' + elToString(obj) + ' to contain #{exp}'
                            , 'expected ' + elToString(obj) + ' to not contain #{exp}'
                            , subitem)
                    } else {
                        this.assert(
                            obj.contains(subitem)
                            , 'expected ' + elToString(obj) + ' to contain ' + elToString(subitem)
                            , 'expected ' + elToString(obj) + ' to not contain ' + elToString(subitem))
                    }
                } else {
                    _super.apply(this, arguments)
                }
            }
        },
        function(_super) {
            return function() {
                _super.call(this)
            }
        }
    )

    chai.Assertion.addMethod('descendant', function(subitem) {
        var obj = flag(this, 'object'), actual = subitem

        if (typeof subitem === 'string') {
            actual = obj.querySelector(subitem)
            this.assert(
                !!actual
                , 'expected ' + elToString(obj) + ' to have descendant #{exp}'
                , 'expected ' + elToString(obj) + ' to not have descendant #{exp}'
                , subitem)
        } else {
            this.assert(
                obj.contains(subitem)
                , 'expected ' + elToString(obj) + ' to contain ' + elToString(subitem)
                , 'expected ' + elToString(obj) + ' to not contain ' + elToString(subitem))
        }

        flag(this, 'object', actual)
    })

    chai.Assertion.addMethod('descendants', function(selector) {
        var obj = flag(this, 'object'),
            actual = obj.querySelectorAll(selector)
        this.assert(
            !!actual.length
            , 'expected ' + elToString(obj) + ' to have descendants #{exp}'
            , 'expected ' + elToString(obj) + ' to not have descendants #{exp}'
            , selector)
        flag(this, 'object', actual)
    })

    chai.Assertion.addProperty('displayed', function() {
        var el = flag(this, 'object'),
            actual = document.body.contains(el) ? window.getComputedStyle(el).display : el.style.display

        this.assert(
            actual !== 'none'
            , 'expected ' + elToString(el) + ' to be displayed, but it was not'
            , 'expected ' + elToString(el) + ' to not be displayed, but it was as ' + actual
            , actual
        )
    })

    chai.Assertion.addProperty('visible', function() {
        var el = flag(this, 'object'),
            actual = document.body.contains(el) ? window.getComputedStyle(el).visibility : el.style.visibility

        this.assert(
            actual !== 'hidden' && actual !== 'collapse'
            , 'expected ' + elToString(el) + ' to be visible, but it was ' + (actual === 'hidden' ? 'hidden' : 'collapsed')
            , 'expected ' + elToString(el) + ' to not be visible, but it was'
            , actual
        )
    })
}