/**
 * 支持 position offset
 * //固定到最近的overflow的dom 顶部 offset像素距离
 * <pro-affix offset="20">...</pro-affix>
 * //指定target时, target不可见时, affix会自动消失
 * <pro-affix offset="20" target="selector">...</pro-affix>
 * 参考 element-plus affix
 * @author xiufu.wang
 */
import { hasClass, on, off, getStyle, removeClass, addClass } from 'mars-pro/src/utils/dom'
import { addResizeListener, removeResizeListener } from 'mars-pro/src/utils/resize-event'
function isScroll(el) {
    const overflow = getStyle(el, 'overflow-y')
    return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s))
}
const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16));

export default {
    name: 'ProAffix',
    componentName: 'ProAffix',
    props: {
        // top | bottom
        position: {
            type: String,
            default: 'top'
        },
        offset: {
            type: Number,
            default: 0
        },
        targetOffset: {
            type: Number,
            default: 0
        },
        target: String
    },
    methods: {
        handleScroll() {
            if (this.frameId) {
                return;
            }
            this.frameId = rAF(() => {
                this.update();
                this.frameId = null;
            });
        },
        clearClass() {
            const isScrollWindow = this.scrollContainer === window
            //清除异常
            if (!isScrollWindow) {
                removeClass(this.scrollContainer, 'pro-affix--relative')
            }
            removeClass(this.$el, 'pro-affix--absolute pro-affix--fixed')
            this.$el.style.top = 'auto'
            this.$el.style.bottom = 'auto'
            this.$el.style.width = 'auto'
        },
        getOffsetTop(element) {
            let yPosition = 0;
            let nextElement = element;

            while (nextElement) {
                yPosition += nextElement.offsetTop || 0;
                nextElement = nextElement.offsetParent;
            }
            return yPosition;
        },
        update() {
            let fixed = false
            const isScrollWindow = this.scrollContainer === window
            this.clearClass()
            // affix坐标信息及尺寸
            const affixRect = this.$el.getBoundingClientRect()
            // affix宽度
            const affixWidth = this.$el.offsetWidth
            const affixHeight = this.$el.offsetHeight
            // 滚动容器的 scrollTop
            const scrollTop = isScrollWindow ? window.pageYOffset : this.scrollContainer.scrollTop
            // affi  affsetTop
            const affixOffsetTop = this.getOffsetTop(this.$el) - this.getOffsetTop(this.scrollContainer)
            // 固定在顶部
            if (this.position === 'top') {
                //固定容器
                if (!isScrollWindow) {
                    const affixOffsetParent = this.$el.offsetParent
                    const isNeedAffixFixed = (scrollTop - (affixOffsetTop - this.offset)) > 0
                    if (isNeedAffixFixed) {
                        if (affixOffsetParent !== this.scrollContainer) {
                            addClass(this.scrollContainer, 'pro-affix--relative')
                        }
                        fixed = true
                        addClass(this.$el, 'pro-affix--absolute')
                        this.$el.style.width = (affixWidth | 0) + 'px'
                        this.$el.style.top = (scrollTop + this.offset) + 'px'
                        this.$emit('update', { affixWidth: affixRect.width })
                    }
                } else {
                    const isNeedAffixFixed = (affixRect.top - this.offset) < 0
                    if (isNeedAffixFixed) {
                        fixed = true
                        addClass(this.$el, 'pro-affix--fixed')
                        this.$el.style.width = (affixWidth | 0) + 'px'
                        this.$el.style.top = this.offset + 'px'
                        this.$emit('update', { affixWidth: affixRect.width })
                    }
                }
                // 处理target
                if (this.targetElement) {
                    const rect = this.targetElement.getBoundingClientRect()
                    const targetOffsetTop = this.getOffsetTop(this.targetElement) - this.getOffsetTop(this.scrollContainer)
                    const t =  targetOffsetTop + rect.height
                    if (isScrollWindow && (rect.bottom < 0) || (!isScrollWindow && scrollTop > (t-this.targetOffset))) {
                        this.clearClass()
                        fixed = false
                        this.$emit('update', { affixWidth: 'auto' })
                    }
                }
            } else {
                if (!isScrollWindow) {
                    // affix原始高度
                    const isNeedAffixFixed = (scrollTop + this.scrollContainer.offsetHeight) < (affixOffsetTop + affixHeight)
                    if (isNeedAffixFixed) {
                        const affixOffsetParent = this.$el.offsetParent
                        if (affixOffsetParent !== this.scrollContainer) {
                            addClass(this.scrollContainer, 'pro-affix--relative')
                        }
                        fixed = true
                        addClass(this.$el, 'pro-affix--absolute')
                        this.$el.style.width = (affixWidth | 0) + 'px'
                        this.$el.style.bottom = (-1 * (scrollTop + this.offset)) + 'px'
                        this.$emit('update', { affixWidth: affixRect.width })
                    }
                } else {
                    //innerHeight
                    const isNeedAffixFixed = affixRect.bottom > window.innerHeight
                    if (isNeedAffixFixed) {
                        fixed = true
                        addClass(this.$el, 'pro-affix--fixed')
                        this.$el.style.width = (affixWidth | 0) + 'px'
                        this.$el.style.bottom = (this.offset) + 'px'
                        this.$emit('update', { affixWidth: affixRect.width })
                    }
                }

                // 处理target
                if (this.targetElement) {
                    const rect = this.targetElement.getBoundingClientRect()
                    const targetOffsetTop = this.getOffsetTop(this.targetElement) - this.getOffsetTop(this.scrollContainer)
                    if (isScrollWindow && (rect.top >= window.innerHeight) || (!isScrollWindow && (scrollTop + this.scrollContainer.offsetHeight) < targetOffsetTop)) {
                        this.clearClass()
                        fixed = false
                        this.$emit('update', { affixWidth: 'auto' })
                    }
                }
            }
            this.$emit('fixed', fixed, affixWidth)
        },
        getScrollContainer(el) {
            let parent = el
            while (parent) {
                if ([window, document, document.documentElement].includes(parent))
                    return window
                if (isScroll(parent)) return parent
                parent = parent.parentNode
            }
            return parent
        },
        getEl(selector) {
            let cur = this.$el.parentNode
            while (cur && cur && selector) {
                const el = cur.querySelector(selector)
                if (el) {
                    return el
                }
                cur = cur.parentNode
            }
            return null
        },
    },
    render() {
        const _datas = {
            'class': {
                'pro-affix': true
            }
        }
        return <div {..._datas}> {this.$slots.default} </div>
    },
    mounted() {
        this.scrollContainer = this.getScrollContainer(this.$el)
        this.targetElement = this.getEl(this.target)
        if (this.scrollContainer !== window) {
            on(this.scrollContainer, 'scroll', this.handleScroll)
        }
        on(window, 'scroll', this.handleScroll)
        addResizeListener(this.scrollContainer === window ? document.documentElement : this.scrollContainer, this.update)
    },
    beforeDestroy() {
        if (this.scrollContainer !== window) {
            off(this.scrollContainer, 'scroll', this.handleScroll)
        }
        off(window, 'scroll', this.handleScroll)
        removeResizeListener(this.scrollContainer === window ? document.documentElement : this.scrollContainer, this.update)
    }
}