<template>
    <span
        :is="tag"
        v-on="$listeners"
        v-bind="$attrs"
    >
        <slot />
        <transition
            @after-leave="afterLeave"
            :name="typeof animation === 'string' ? animation : undefined"
            v-on="typeof animation === 'object' ? animation : undefined">
            <span
                v-if="(lazy ? hasShow : true)"
                v-show="isShowPopper"
                ref="content"
                :class="[bem([theme, {arrow}]), popoverClass]"
                :style="{zIndex: zIndex}">
                <template>
                    <slot name="content"/>
                    <div x-arrow v-if="arrow" :class="bem('arrow')"  />
                </template>
            </span>
        </transition>
    </span>
</template>

<script>

import merge from 'lodash/merge';
import { createComponentName, componentMixin } from '../_utils/component';

import PopperJs from 'popper.js';
import popupManager from '../_utils/popup-manager';
import { on, off } from '../_utils/dom';
import Event from 'eventemitter3';


const popperJs = 'POPPERJS';

const eventBus = new Event();

export default {
    name: createComponentName('popover'),
    mixins: [componentMixin],
    update() {
        eventBus.emit('update');
    },
    props: {
        tag: {
            type: [String, Object],
            default: 'span',
        },
        appendToBody: {
            type: Boolean,
            default: true,
        },
        animation: {
            type: [Object, String],
            default: 'gd-fade'
        },
        popoverClass: {
            type: [String, Array],
            default: '',
        },
        fixed: {
            type: Boolean,
            default: false,
        },
        visible: {
            type: Boolean,
        },
        // 主题色
        theme: {
            // 'dark' / ‘light’
            type: String,
            default: 'light',
        },
        arrow: {
            type: Boolean,
            default: true,
        },
        defaultVisible: {
            type: Boolean,
            default: false,
        },
        lazy: {
            type: Boolean,
            default: true,
        },
        trigger: {
            type: String,
            default: 'hover',
            validator: value => ['click', 'hover', 'focus'].indexOf(value) > -1,
        },
        placement: {
            type: String,
            default: 'bottom',
        },
        // 延迟多久消失
        delayonMouseLeave: {
            type: Number,
            default: 50,
        },
        // 延迟多久显示
        delayonMouseEnter: {
            type: Number,
            default: 0,
        },
        modifiers: {
            type: Object,
            default: () => {}
        },
    },
    data() {
        let visible = false;
        if (this.$hasProp('visible')) visible = this.visible;
        else if (this.$hasProp('defaultVisible')) visible = this.defaultVisible;
        return {
            isShowPopper: visible,
            zIndex: 0,
            hasShow: visible,
            mounted: false,
        };
    },
    watch: {
        isShowPopper(value) {
            if (value) this.hasShow = true;

            if (value) {
                this.$emit('show');
                popupManager.pushStack(this);
                this.updatePopper();
            }
            else {
                this.$emit('hide');
            }
        },
        visible(value) {
            this.isShowPopper = value;
        },
    },
    mounted() {
        if (this.$isServer) return;

        eventBus.on('update', this.updatePopper);

        this.eventsEnabled = true;
        this.mounted = true;

        this.initReferenceEvent();

        if (this.appendToBody && this.$refs.content) {
            this.$refs.content.parentNode.removeChild(this.$refs.content);
        }

        if (this.isShowPopper) {
            popupManager.pushStack(this);
            this.createPopper();
        }
    },
    beforeDestroy() {
        eventBus.off('update', this.updatePopper);
        this.offReferenceEvent();
        this.doDestroy();
    },
    methods: {
        afterLeave() {
            popupManager.popStack(this);
            this.$emit('after-leave');
        },
        doDestroy() {
            this.offPopperEvent();
            if (this[popperJs]) {
                this[popperJs].destroy();
                this[popperJs] = null;
            }
            if (this.appendToBody && this.appendedToBody && this.$refs.content) {
                this.appendedToBody = false;
                document.body.removeChild(this.$refs.content);
            }
        },
        initReferenceEvent() {
            switch (this.trigger) {
                case 'click':
                    on(this.$el.children[0], 'click', this.onClick);
                    on(document, 'click', this.handleDocumentClick);
                    break;
                case 'hover':
                    on(this.$el.children[0], 'mouseenter', this.onMouseEnter);
                    on(this.$el.children[0], 'mouseleave', this.onMouseLeave);
                    break;
                case 'focus':
                    on(this.$el.children[0], 'focus', this.onMouseEnter);
                    on(this.$el.children[0], 'blur', this.onMouseLeave);
                    break;
            }
        },
        initPopperEvent() {
            switch (this.trigger) {
                case 'hover':
                    on(this.$refs.content, 'mouseenter', this.onContentMouseEnter);
                    on(this.$refs.content, 'mouseleave', this.onContentMouseLeave);
                    on(this.$refs.content, 'focus', this.onMouseEnter);
                    on(this.$refs.content, 'blur', this.onMouseLeave);
                    break;
            }
        },
        offReferenceEvent() {
            switch (this.trigger) {
                case 'click':
                    off(document, 'click', this.handleDocumentClick);
                    break;
            }
        },
        offPopperEvent() {
            off(this.$refs.content, 'mouseenter', this.onContentMouseEnter);
            off(this.$refs.content, 'mouseleave', this.onContentMouseLeave);
            off(this.$refs.content, 'focus', this.onMouseEnter);
            off(this.$refs.content, 'blur', this.onMouseLeave);
        },
        changeVisible(value) {
            if (this.$hasProp('visible')) {
                this.$emit('update:visible', value);
            }
            else {
                this.isShowPopper = value;
            }
        },
        doToggle() {
            if (!this.forceShow) {
                this.updatePopper();
                this.changeVisible(!this.isShowPopper);
            }
        },
        onClick() {
            this.updatePopper();
            this.changeVisible(!this.isShowPopper);
        },
        onContentMouseEnter() {
            this.mouseInContent = true;
        },
        onContentMouseLeave() {
            this.mouseInContent = false;
            this.changeVisible(false);
        },
        onMouseEnter() {
            clearTimeout(this._timer);
            this._timer = setTimeout(() => {
                if (this.isShowPopper === true || this.leaveing) return;
                this.updatePopper();

                this.changeVisible(true);
            }, this.delayonMouseEnter);
        },
        onMouseLeave() {
            clearTimeout(this._timer);
            this._timer = setTimeout(() => {
                if (this.mouseInContent) return;
                this.changeVisible(false);
            }, this.delayonMouseLeave);
        },
        handleDocumentClick(e) {
            if (!this.$el ||
              this.elementContains(this.$el, e.target) ||
              !this.$refs.content || this.elementContains(this.$refs.content, e.target)
            ) {
                return;
            }
            this.$emit('documentClick', this);
            this.changeVisible(false);
        },
        elementContains(elm, otherElm) {
            if (typeof elm.contains === 'function') {
                return elm.contains(otherElm);
            }
            return false;
        },
        getPopperConfig() {
            let self = this;
            const config = merge({
                onCreate(data) {
                    data.instance.popper.style.zIndex = self.zIndex;
                    self.$emit('create', data);
                },
                onUpdate(data) {
                    if (self.isShowPopper) {
                        data.instance.popper.style.zIndex = self.zIndex;
                    }
                },
            }, {
                modifiers: {
                    offset: {
                        offset: 0
                    },
                    preventOverflow: {
                        padding: 0,
                    },
                }
            }, {
                placement: this.placement,
                positionFixed: this.fixed,
                eventsEnabled: true,
                removeOnDestroy: false,
                modifiers: this.modifiers,
            });
            return config;
        },
        createPopper() {
            if (!this.$el || !this.$refs.content) return;
            if (this[popperJs]) {
                this[popperJs].destroy();
            }

            this[popperJs] = new PopperJs(
                this.$el,
                this.$refs.content,
                this.getPopperConfig()
            );

            if (this.appendToBody && !this.appendedToBody) {
                this.appendedToBody = true;
                document.body.appendChild(this.$refs.content);
            }

            this.initPopperEvent();
        },
        updatePopper() {
            if (!this.isShowPopper) return;

            if (this[popperJs]) {
                this[popperJs].scheduleUpdate();
            }
            else {
                this.$nextTick(() => {
                    this.createPopper();
                });
            }
        },
    }
};
</script>

<style lang="less">
@import '../_styles/base.less';
@import '../_styles/transitions/fade.less';

@bg: #4C4E4F;

.@{popover} {
    font-size: 12px;
    position: relative;
    color: #fff;
    background: @bg;
    border-radius: 4px;

    * {
        box-sizing: border-box;
    }


    @arrow: ~'@{popover}__arrow';

    .@{arrow} {
        position: absolute;
    }

    &--arrow {
        .popper-arrow(@arrow, 6, 6, @bg);
    }

    &&--light {
        background: #fff;
        color: #000;
        box-shadow: 0 2px 8px rgba(0,0,0,0.15);
    }

    &&--light.@{popover}--arrow {
        .popper-arrow(@arrow, 6, 6, #fff, true);
    }
}
</style>
