import {$, addClass, append, attr, css, includes, isFocusable, last, on, once, parent, pointerCancel, pointerDown, pointerUp, Promise, removeClass, toFloat, toMs, width, within} from 'uikit-util';
import Class from './class';
import Container from './container';
import Togglable from './togglable';

const active = [];

export default {

    mixins: [Class, Container, Togglable],

    props: {
        selPanel: String,
        selClose: String,
        escClose: Boolean,
        bgClose: Boolean,
        stack: Boolean
    },

    data: {
        cls: 'uk-open',
        escClose: true,
        bgClose: true,
        overlay: true,
        stack: false
    },

    computed: {

        panel({selPanel}, $el) {
            return $(selPanel, $el);
        },

        transitionElement() {
            return this.panel;
        },

        bgClose({bgClose}) {
            return bgClose && this.panel;
        }

    },

    beforeDisconnect() {
        if (includes(active, this)) {
            this.toggleElement(this.$el, false, false);
        }
    },

    events: [

        {

            name: 'click',

            delegate() {
                return this.selClose;
            },

            handler(e) {
                e.preventDefault();
                this.hide();
            }

        },

        {

            name: 'toggle',

            self: true,

            handler(e, toggle) {

                if (e.defaultPrevented) {
                    return;
                }

                e.preventDefault();

                if (this.isToggled() === includes(active, this)) {
                    this.toggle();
                }
            }

        },

        {
            name: 'beforeshow',

            self: true,

            handler(e) {

                if (includes(active, this)) {
                    return false;
                }

                if (!this.stack && active.length) {
                    Promise.all(active.map(modal => modal.hide())).then(this.show);
                    e.preventDefault();
                } else {
                    active.push(this);
                }
            }

        },

        {

            name: 'show',

            self: true,

            handler() {

                const docEl = document.documentElement;

                if (width(window) > docEl.clientWidth && this.overlay) {
                    css(document.body, 'overflowY', 'scroll');
                }

                if (this.stack) {
                    css(this.$el, 'zIndex', toFloat(css(this.$el, 'zIndex')) + active.length);
                }

                addClass(docEl, this.clsPage);

                if (this.bgClose) {
                    once(this.$el, 'hide', on(document, pointerDown, ({target}) => {

                        if (last(active) !== this || this.overlay && !within(target, this.$el) || within(target, this.panel)) {
                            return;
                        }

                        once(document, `${pointerUp} ${pointerCancel} scroll`, ({defaultPrevented, type, target: newTarget}) => {
                            if (!defaultPrevented && type === pointerUp && target === newTarget) {
                                this.hide();
                            }
                        }, true);

                    }), {self: true});
                }

                if (this.escClose) {
                    once(this.$el, 'hide', on(document, 'keydown', e => {
                        if (e.keyCode === 27 && last(active) === this) {
                            this.hide();
                        }
                    }), {self: true});
                }
            }

        },

        {
            name: 'shown',

            self: true,

            handler() {
                if (!isFocusable(this.$el)) {
                    attr(this.$el, 'tabindex', '-1');
                }

                if (!$(':focus', this.$el)) {
                    this.$el.focus();
                }
            }
        },

        {

            name: 'hidden',

            self: true,

            handler() {

                if (includes(active, this)) {
                    active.splice(active.indexOf(this), 1);
                }

                if (!active.length) {
                    css(document.body, 'overflowY', '');
                }

                css(this.$el, 'zIndex', '');

                if (!active.some(modal => modal.clsPage === this.clsPage)) {
                    removeClass(document.documentElement, this.clsPage);
                }

            }

        }

    ],

    methods: {

        toggle() {
            return this.isToggled() ? this.hide() : this.show();
        },

        show() {
            if (this.container && parent(this.$el) !== this.container) {
                append(this.container, this.$el);
                return new Promise(resolve =>
                    requestAnimationFrame(() =>
                        this.show().then(resolve)
                    )
                );
            }

            return this.toggleElement(this.$el, true, animate(this));
        },

        hide() {
            return this.toggleElement(this.$el, false, animate(this));
        }

    }

};

function animate({transitionElement, _toggle}) {
    return (el, show) =>
        new Promise((resolve, reject) =>
            once(el, 'show hide', () => {
                el._reject && el._reject();
                el._reject = reject;

                _toggle(el, show);

                const off = once(transitionElement, 'transitionstart', () => {
                    once(transitionElement, 'transitionend transitioncancel', resolve, {self: true});
                    clearTimeout(timer);
                }, {self: true});

                const timer = setTimeout(() => {
                    off();
                    resolve();
                }, toMs(css(transitionElement, 'transitionDuration')));

            })
        ).then(() => delete el._reject);
}
