/**
 * 滚动激活
 * 参考 https://github.com/eddiemf/vue-scrollactive
 * <pro-scroll-active markItemSelector=".aaaaaaaaa" scrollContainerSelector=".el-card__body">
 *   <ul>
 *      
 *   </ul>
 * </pro-scroll-active>
 * @author xiufu.wang
 */
import { hasClass, on, off, getStyle, removeClass, addClass } from 'mars-pro/src/utils/dom'
import { stopAndPreventDomEvent } from 'mars-pro/src/pro/util'
import bezierEasing from 'bezier-easing';
import { addResizeListener, removeResizeListener } from 'mars-pro/src/utils/resize-event'

const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16));
function getIdFromHash(hash) {
    return decodeURI(hash.substr(1));
};

function onDomChange(el, fn) {
    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    let observer = new MutationObserver(fn)
    observer.observe(el, {
        childList: true,
        subtree: true,
    })
    return () => {
        observer.disconnect()
        observer = null
    }
}

function getSectionSelector(element) {
    if (element.dataset.sectionSelector) return element.dataset.sectionSelector;
    if (element.hash) return `#${getIdFromHash(element.hash)}`;
    return '';
};

export default {
    name: 'ProScrollActive',
    componentName: 'ProScrollActive',
    props: {
        scrollContainerSelector: String,
        markItemSelector: {
            type: String,
            default: '.pro-scroll-active-mark'
        },
        tag: {
            type: String,
            default: 'nav'
        },
        offset: {
            type: Number,
            default:30
        },
        // 滚动动画配置
        bezierEasingValue: {
            type: String,
            default: '.5,0,.35,1',
        },
        duration: {
            type: Number,
            default: 600,
        }
    },
    computed: {
        scrollContainer() {
            return this.getEl(this.scrollContainerSelector) || window
        },
        scrollContainerEl() {
            return this.getEl(this.scrollContainerSelector) || document.documentElement
        },
        cubicBezierArray() {
            return this.bezierEasingValue.split(',');
        }
    },
    methods: {
        getOffsetTop(element) {
            let yPosition = 0;
            let nextElement = element;
            while (nextElement) {
                yPosition += nextElement.offsetTop;
                nextElement = nextElement.offsetParent;
            }

            let sPosition = 0
            nextElement = this.scrollContainerEl
            while (nextElement) {
                sPosition += nextElement.offsetTop;
                nextElement = nextElement.offsetParent;
            }

            return yPosition - sPosition;
        },
        getEl(selector, el, contanerSelector) {
            let cur = el || this.$el.parentNode
            while (cur && cur) {
                if (contanerSelector && hasClass(cur, contanerSelector)) {
                    return cur
                }
                const targetEl = cur.querySelector(selector)
                if (targetEl) {
                    return targetEl
                }
                cur = cur.parentNode
            }
            return null
        },
        onScroll() {
            const markItems = this.scrollContainerEl.querySelectorAll(this.markItemSelector)
            const scrollTop = this.scrollContainer.scrollTop || this.scrollContainer.pageYOffset
            let curItem = markItems[0]
            let curIndex = 0
            for (let i = 0; i < markItems.length; i++) {
                const element = markItems[i];
                const offsetTop = this.getOffsetTop(curItem)
                if (scrollTop >= offsetTop) {
                    curItem = element
                    curIndex = i
                }
            }
            if (curItem && curItem) {
                const id = curItem.getAttribute('id')
                const items = this.$el.querySelectorAll('.scrollactive-item');
                for (let i = 0; i < items.length; i++) {
                    const menuElement = items[i];
                    removeClass(menuElement, 'is-active')
                    let section = getSectionSelector(menuElement)
                    section = section[0] === '#' ? section.slice(1) : section
                    if (section === id) {
                        addClass(menuElement, 'is-active')
                    }
                }
            }
        },
        handNavScroll(e) {
            stopAndPreventDomEvent(e)
            const navItem = this.getEl('.scrollactive-item', e.target, 'scrollactive-item')
            const section = getSectionSelector(navItem)
            const selectMark = this.scrollContainerEl.querySelector('#' + section)
            const toSrollTop = Math.max(0, this.getOffsetTop(selectMark) - this.offset)
            this.scrollTo(toSrollTop)
        },
        scrollTo(toTop) {
            const startingY = this.scrollContainer.scrollTop || this.scrollContainer.pageYOffset || 0
            const distanceFromTarget = toTop - startingY;
            const easing = bezierEasing(...this.cubicBezierArray);
            let startingTime = null;
            const step = (currentTime) => {
                if (!startingTime) startingTime = currentTime;

                let progress = currentTime - startingTime;
                let progressPercentage = progress / this.duration;

                if (progress >= this.duration) progress = this.duration;
                if (progressPercentage >= 1) progressPercentage = 1;

                const offset = this.offset;
                const perTick = startingY + easing(progressPercentage) * (distanceFromTarget - offset);

                this.scrollContainer.scrollTo(0, perTick);

                if (progress < this.duration) {
                    this.scrollAnimationFrame = rAF(step);
                }
            };
            rAF(step);
        }
    },
    render(h) {
        const _datas = {
            props: {
                is: this.tag
            },
            ref: 'scrollactive-nav-wrapper',
            'class': {
                'pro-scrollactive-nav': true
            },
            on: {
                click: this.handNavScroll
            }
        }
        return h(this.tag, _datas, [this.$slots.default])
    },
    mounted() {
        on(this.scrollContainer, 'scroll', this.onScroll)
        addResizeListener(this.scrollContainerEl, this.onScroll)
        this.unOnDomChange = onDomChange(this.scrollContainerEl, this.onScroll)
        this.onScroll()
    },
    beforeDestroy() {
        off(this.scrollContainer, 'scroll', this.onScroll)
        removeResizeListener(this.scrollContainerEl, this.onScroll)
        this.unOnDomChange()
    }
}