/**
 * @description 封装 DOM 操作
 * @wangfupeng
 */

import Editor from '../editor/index'
import { toArray } from './util'

// 记录元素基于上一个相对&绝对定位的位置信息
type OffsetDataType = {
    top: number
    left: number
    width: number
    height: number
    parent: Element | null
}

// 记录代理事件绑定
type listener = (e: Event) => void
type EventItem = {
    elem: HTMLElement
    selector: string
    fn: listener
    agentFn: listener
}
const AGENT_EVENTS: EventItem[] = []

/**
 * 根据 html 字符串创建 elem
 * @param {String} html html
 */
function _createElemByHTML(html: string): HTMLElement[] {
    const div = document.createElement('div')
    div.innerHTML = html
    const elems = div.children
    return toArray(elems)
}

/**
 * 判断是否是 DOM List
 * @param selector DOM 元素或列表
 */
function _isDOMList<T extends HTMLCollection | NodeList>(selector: unknown): selector is T {
    if (!selector) {
        return false
    }
    if (selector instanceof HTMLCollection || selector instanceof NodeList) {
        return true
    }
    return false
}

/**
 * 封装 querySelectorAll
 * @param selector css 选择器
 */
function _querySelectorAll(selector: string): HTMLElement[] {
    const elems = document.querySelectorAll(selector)
    return toArray(elems)
}

/**
 * 封装 _styleArrTrim
 * @param styleArr css
 */
function _styleArrTrim(style: string | string[]): string[] {
    let styleArr: string[] = []
    let resultArr: string[] = []

    if (!Array.isArray(style)) {
        // 有 style，将 style 按照 `;` 拆分为数组
        styleArr = style.split(';')
    } else {
        styleArr = style
    }

    styleArr.forEach(item => {
        // 对每项样式，按照 : 拆分为 key 和 value
        let arr = item.split(':').map(i => {
            return i.trim()
        })
        if (arr.length === 2) {
            resultArr.push(arr[0] + ':' + arr[1])
        }
    })
    return resultArr
}

export type DomElementSelector =
    | string
    | DomElement
    | HTMLElement
    | Element
    | Document
    | HTMLCollection
    | Node
    | NodeList
    | HTMLElement[]
    | EventTarget
    | null
    | undefined

// 构造函数
export class DomElement<T extends DomElementSelector = DomElementSelector> {
    // 定义属性
    selector!: T
    length: number
    elems: HTMLElement[]
    dataSource: Map<string, any>

    /**
     * 构造函数
     * @param selector 任一类型的选择器
     */
    constructor(selector: T) {
        // 初始化属性
        this.elems = []
        this.length = this.elems.length
        this.dataSource = new Map()

        if (!selector) {
            return
        }

        // 原本就是 DomElement 实例，则直接返回
        if (selector instanceof DomElement) {
            return selector
        }

        let selectorResult: HTMLElement[] = [] // 存储查询结果
        const nodeType = selector instanceof Node ? selector.nodeType : -1
        this.selector = selector

        if (nodeType === 1 || nodeType === 9) {
            selectorResult = [selector as HTMLElement]
        } else if (_isDOMList(selector)) {
            // DOM List
            selectorResult = toArray(selector)
        } else if (selector instanceof Array) {
            // Element 数组（其他数据类型，暂时忽略）
            selectorResult = selector
        } else if (typeof selector === 'string') {
            // 字符串
            const tmpSelector = selector.replace('/\n/mg', '').trim()
            if (tmpSelector.indexOf('<') === 0) {
                // 如 <div>
                selectorResult = _createElemByHTML(tmpSelector)
            } else {
                // 如 #id .class
                selectorResult = _querySelectorAll(tmpSelector)
            }
        }

        const length = selectorResult.length
        if (!length) {
            // 空数组
            return this
        }

        // 加入 DOM 节点
        let i = 0
        for (; i < length; i++) {
            this.elems.push(selectorResult[i])
        }
        this.length = length
    }

    /**
     * 获取元素 id
     */
    get id(): string {
        return this.elems[0].id
    }

    /**
     * 遍历所有元素，执行回调函数
     * @param fn 回调函数
     */
    forEach(fn: (ele: HTMLElement, index?: number) => boolean | unknown): DomElement {
        for (let i = 0; i < this.length; i++) {
            const elem = this.elems[i]
            const result = fn.call(elem, elem, i)
            if (result === false) {
                break
            }
        }
        return this
    }

    /**
     * 克隆元素
     * @param deep 是否深度克隆
     */
    clone(deep: boolean = false): DomElement {
        const cloneList: HTMLElement[] = []
        this.elems.forEach(elem => {
            cloneList.push(elem.cloneNode(!!deep) as HTMLElement)
        })
        return $(cloneList)
    }

    /**
     * 获取第几个元素
     * @param index index
     */
    get(index: number = 0): DomElement {
        const length = this.length
        if (index >= length) {
            index = index % length
        }
        return $(this.elems[index])
    }

    /**
     * 获取第一个元素
     */
    first(): DomElement {
        return this.get(0)
    }

    /**
     * 获取最后一个元素
     */
    last(): DomElement {
        const length = this.length
        return this.get(length - 1)
    }

    /**
     * 绑定事件
     * @param type 事件类型
     * @param selector DOM 选择器
     * @param fn 事件函数
     */
    on(type: string, fn: Function): DomElement
    on(type: string, selector: string, fn: Function): DomElement
    on(type: string, selector: string | Function, fn?: Function): DomElement {
        if (!type) return this

        // 没有 selector ，只有 type 和 fn
        if (typeof selector === 'function') {
            fn = selector
            selector = ''
        }

        return this.forEach(elem => {
            // 没有事件代理
            if (!selector) {
                // 无代理
                elem.addEventListener(type, fn as listener)
                return
            }

            // 有事件代理
            const agentFn: listener = function (e) {
                const target = e.target as HTMLElement
                if (target.matches(selector as string)) {
                    ;(fn as listener).call(target, e)
                }
            }
            elem.addEventListener(type, agentFn)

            // 缓存代理事件
            AGENT_EVENTS.push({
                elem: elem,
                selector: selector as string,
                fn: fn as listener,
                agentFn,
            })
        })
    }

    /**
     * 解绑事件
     * @param type 事件类型
     * @param selector DOM 选择器
     * @param fn 事件函数
     */
    off(type: string, fn: Function): DomElement
    off(type: string, selector: string, fn: Function): DomElement
    off(type: string, selector: string | Function, fn?: Function): DomElement {
        if (!type) return this

        // 没有 selector ，只有 type 和 fn
        if (typeof selector === 'function') {
            fn = selector
            selector = ''
        }

        return this.forEach(function (elem: HTMLElement) {
            // 解绑事件代理
            if (selector) {
                let idx = -1
                for (let i = 0; i < AGENT_EVENTS.length; i++) {
                    let item = AGENT_EVENTS[i]
                    if (item.selector === selector && item.fn === fn && item.elem === elem) {
                        idx = i
                        break
                    }
                }
                if (idx !== -1) {
                    const { agentFn } = AGENT_EVENTS.splice(idx, 1)[0]
                    elem.removeEventListener(type, agentFn)
                }
            } else {
                // @ts-ignore
                elem.removeEventListener(type, fn)
            }
        })
    }

    /**
     * 设置/获取 属性
     * @param key key
     * @param val value
     */
    attr(key: string): string
    attr(key: string, val: string): DomElement
    attr(key: string, val?: string): DomElement | string {
        if (val == null) {
            // 获取数据
            return this.elems[0].getAttribute(key) || ''
        }

        // 否则，设置属性
        return this.forEach(function (elem: HTMLElement) {
            elem.setAttribute(key, val)
        })
    }

    /**
     * 删除 属性
     * @param key key
     */
    removeAttr(key: string): void {
        this.forEach(function (elem: HTMLElement) {
            elem.removeAttribute(key)
        })
    }

    /**
     * 添加 css class
     * @param className css class
     */
    addClass(className?: string): DomElement {
        if (!className) {
            return this
        }

        return this.forEach(function (elem: HTMLElement) {
            if (elem.className) {
                // 当前有 class
                let arr: string[] = elem.className.split(/\s/)
                arr = arr.filter(item => {
                    return !!item.trim()
                })
                // 添加 class
                if (arr.indexOf(className) < 0) {
                    arr.push(className)
                }
                // 修改 elem.class
                elem.className = arr.join(' ')
            } else {
                // 当前没有 class
                elem.className = className
            }
        })
    }

    /**
     * 添加 css class
     * @param className css class
     */
    removeClass(className?: string): DomElement {
        if (!className) {
            return this
        }
        return this.forEach(function (elem: HTMLElement) {
            if (!elem.className) {
                // 当前无 class
                return
            }

            let arr: string[] = elem.className.split(/\s/)
            arr = arr.filter(item => {
                item = item.trim()
                // 删除 class
                if (!item || item === className) {
                    return false
                }
                return true
            })
            // 修改 elem.class
            elem.className = arr.join(' ')
        })
    }

    /**
     * 是否有传入的 css class
     * @param className css class
     */
    hasClass(className?: string): boolean {
        if (!className) {
            return false
        }
        const elem = this.elems[0]
        if (!elem.className) {
            // 当前无 class
            return false
        }
        let arr: string[] = elem.className.split(/\s/)
        return arr.includes(className) // 是否包含
    }

    /**
     * 修改 css
     * @param key css key
     * @param val css value
     */
    // css(key: string): string
    css(key: string, val?: string | number): DomElement {
        let currentStyle: string
        if (val == '') {
            currentStyle = ''
        } else {
            currentStyle = `${key}:${val};`
        }
        return this.forEach(elem => {
            const style = (elem.getAttribute('style') || '').trim()
            if (style) {
                // 有 style，将 style 按照 `;` 拆分为数组
                let resultArr: string[] = _styleArrTrim(style)

                // 替换现有的 style
                resultArr = resultArr.map(item => {
                    if (item.indexOf(key) === 0) {
                        return currentStyle
                    } else {
                        return item
                    }
                })
                // 新增 style
                if (currentStyle != '' && resultArr.indexOf(currentStyle) < 0) {
                    resultArr.push(currentStyle)
                }

                // 去掉 空白
                if (currentStyle == '') {
                    resultArr = _styleArrTrim(resultArr)
                }

                // 重新设置 style
                elem.setAttribute('style', resultArr.join('; '))
            } else {
                // 当前没有 style
                elem.setAttribute('style', currentStyle)
            }
        })
    }

    /**
     * 封装 getBoundingClientRect
     */
    getBoundingClientRect(): DOMRect {
        const elem = this.elems[0]
        return elem.getBoundingClientRect()
    }

    /**
     * 显示
     */
    show(): DomElement {
        return this.css('display', 'block')
    }

    /**
     * 隐藏
     */
    hide(): DomElement {
        return this.css('display', 'none')
    }

    /**
     * 获取子节点（只有 DOM 元素）
     */
    children(): DomElement | null {
        const elem = this.elems[0]
        if (!elem) {
            return null
        }

        return $(elem.children)
    }

    /**
     * 获取子节点（包括文本节点）
     */
    childNodes(): DomElement | null {
        const elem = this.elems[0]
        if (!elem) {
            return null
        }

        return $(elem.childNodes)
    }

    /**
     * 将子元素全部替换
     * @param $children 新的child节点
     */
    replaceChildAll($children: DomElement): void {
        const parent = this.getNode()
        const elem = this.elems[0]
        while (elem.hasChildNodes()) {
            parent.firstChild && elem.removeChild(parent.firstChild)
        }
        this.append($children)
    }

    /**
     * 增加子节点
     * @param $children 子节点
     */
    append($children: DomElement): DomElement {
        return this.forEach(elem => {
            $children.forEach(function (child: HTMLElement) {
                elem.appendChild(child)
            })
        })
    }

    /**
     * 移除当前节点
     */
    remove(): DomElement {
        return this.forEach(elem => {
            if (elem.remove) {
                elem.remove()
            } else {
                const parent = elem.parentElement
                parent && parent.removeChild(elem)
            }
        })
    }

    /**
     * 当前元素，是否包含某个子元素
     * @param $child 子元素
     */
    isContain($child: DomElement): boolean {
        const elem = this.elems[0]
        const child = $child.elems[0]
        return elem.contains(child)
    }

    /**
     * 获取当前元素的尺寸和位置信息
     */
    getSizeData(): DOMRect {
        const elem = this.elems[0]
        // 可得到 bottom height left right top width 的数据
        return elem.getBoundingClientRect()
    }

    /**
     * 获取当前元素 nodeName
     */
    getNodeName(): string {
        const elem = this.elems[0]
        return elem.nodeName
    }

    /**
     * 根据元素位置获取元素节点（默认获取0位置的节点）
     * @param n 元素节点位置
     */
    getNode(n: number = 0): Node {
        let elem: Node
        elem = this.elems[n]
        return elem
    }

    /**
     * 获取当前元素可视高度
     */
    getClientHeight(): number {
        const elem = this.elems[0]
        return elem.clientHeight
    }

    /**
     * 获取当前元素可视宽度
     */
    // getClientWidth(): number {
    //     const elem = this.elems[0]
    //     return elem.clientWidth
    // }

    /**
     * 查询
     * @param selector css 选择器
     */
    find(selector: string): DomElement {
        const elem = this.elems[0]
        return $(elem.querySelectorAll(selector))
    }

    /**
     * 获取/设置 元素 text
     * @param val text 值
     */
    text(): string
    text(val: string): DomElement
    text(val?: string): DomElement | string {
        if (!val) {
            // 获取 text
            const elem = this.elems[0]

            return elem.innerHTML.replace(/<[^>]+>/g, () => '')
        } else {
            // 设置 text
            return this.forEach(function (elem: HTMLElement) {
                elem.innerHTML = val
            })
        }
    }

    /**
     * 设置/获取 元素 html
     * @param val html 值
     */
    html(): string
    html(val: string): DomElement
    html(val?: string): DomElement | string {
        const elem = this.elems[0]
        if (!val) {
            // 获取 html
            return elem.innerHTML
        } else {
            // 设置 html
            elem.innerHTML = val
            return this
        }
    }

    /**
     * 获取元素 value
     */
    val(): string {
        const elem = this.elems[0]
        return (elem as any).value.trim() // 暂用 any
    }

    /**
     * focus 到当前元素
     */
    focus(): DomElement {
        return this.forEach(elem => {
            elem.focus()
        })
    }

    /**
     * 当前元素前一个兄弟节点
     */
    prev(): DomElement {
        const elem = this.elems[0]
        return $(elem.previousElementSibling)
    }

    /**
     * 当前元素后一个兄弟节点
     */
    next(): DomElement {
        const elem = this.elems[0]
        return $(elem.nextElementSibling)
    }

    /**
     * 获取父元素
     */
    parent(): DomElement {
        const elem = this.elems[0]
        return $(elem.parentElement)
    }

    /**
     * 查找父元素，直到满足 selector 条件
     * @param selector css 选择器
     * @param curElem 从哪个元素开始查找，默认为当前元素
     */
    parentUntil(selector: string, curElem?: HTMLElement): DomElement | null {
        const elem = curElem || this.elems[0]
        if (elem.nodeName === 'BODY') {
            return null
        }

        const parent = elem.parentElement
        if (parent === null) {
            return null
        }

        if (parent.matches(selector)) {
            // 找到，并返回
            return $(parent)
        }

        // 继续查找，递归
        return this.parentUntil(selector, parent)
    }

    /**
     * 查找父元素，直到满足 selector 条件,或者 到达 编辑区域容器以及菜单栏容器
     * @param selector css 选择器
     * @param curElem 从哪个元素开始查找，默认为当前元素
     */
    parentUntilEditor(selector: string, editor: Editor, curElem?: HTMLElement): DomElement | null {
        const elem = curElem || this.elems[0]
        if ($(elem).equal(editor.$textContainerElem) || $(elem).equal(editor.$toolbarElem)) {
            return null
        }

        const parent = elem.parentElement
        if (parent === null) {
            return null
        }

        if (parent.matches(selector)) {
            // 找到，并返回
            return $(parent)
        }

        // 继续查找，递归
        return this.parentUntilEditor(selector, editor, parent)
    }

    /**
     * 判读是否相等
     * @param $elem 元素
     */
    equal($elem: DomElement | HTMLElement): boolean {
        if ($elem instanceof DomElement) {
            return this.elems[0] === $elem.elems[0]
        } else if ($elem instanceof HTMLElement) {
            return this.elems[0] === $elem
        } else {
            return false
        }
    }

    /**
     * 将该元素插入到某个元素前面
     * @param selector css 选择器
     */
    insertBefore(selector: string | DomElement): DomElement {
        const $referenceNode = $(selector)
        const referenceNode = $referenceNode.elems[0]
        if (!referenceNode) {
            return this
        }
        return this.forEach(elem => {
            const parent = referenceNode.parentNode as Node
            parent.insertBefore(elem, referenceNode)
        })
    }

    /**
     * 将该元素插入到selector元素后面
     * @param selector css 选择器
     */
    insertAfter(selector: string | DomElement): DomElement {
        const $referenceNode = $(selector)
        const referenceNode = $referenceNode.elems[0]
        const anchorNode = referenceNode && referenceNode.nextSibling
        if (!referenceNode) {
            return this
        }
        return this.forEach(function (elem: HTMLElement) {
            const parent = referenceNode.parentNode as Node
            if (anchorNode) {
                parent.insertBefore(elem, anchorNode)
            } else {
                parent.appendChild(elem)
            }
        })
    }

    /**
     * 设置/获取 数据
     * @param key key
     * @param value value
     */
    data<T>(key: string, value?: T): T | undefined {
        if (value != null) {
            // 设置数据
            this.dataSource.set(key, value)
        } else {
            // 获取数据
            return this.dataSource.get(key)
        }
    }

    /**
     * 获取当前节点的顶级(段落)
     * @param editor 富文本实例
     */
    getNodeTop(editor: Editor): DomElement {
        if (this.length < 1) {
            return this
        }

        const $parent = this.parent()
        if (editor.$textElem.equal($parent)) {
            return this
        }

        return $parent.getNodeTop(editor)
    }

    /**
     * 获取当前 节点 基与上一个拥有相对或者解决定位的父容器的位置
     * @param editor 富文本实例
     */
    getOffsetData(): OffsetDataType {
        const $node = this.elems[0]
        return {
            top: $node.offsetTop,
            left: $node.offsetLeft,
            width: $node.offsetWidth,
            height: $node.offsetHeight,
            parent: $node.offsetParent,
        }
    }

    /**
     * 从上至下进行滚动
     * @param top 滚动的值
     */
    scrollTop(top: number): void {
        const $node = this.elems[0]
        $node.scrollTo({ top })
    }
}

// new 一个对象
function $(...arg: ConstructorParameters<typeof DomElement>): DomElement {
    return new DomElement(...arg)
}

export default $
