(function () {
    var backdrop = null;
    var modal = null;
    var titleEl = null;
    var bodyEl = null;
    var cancelBtn = null;
    var confirmBtn = null;
    var footerEl = null;
    var prevFocus = null;
    var currentOpts = null;
    var onKeydown = null;

    function build() {
        backdrop = document.createElement('div');
        backdrop.className = 'modal-backdrop';

        modal = document.createElement('div');
        modal.className = 'modal panel panel-floating';
        modal.setAttribute('role', 'dialog');
        modal.setAttribute('aria-modal', 'true');

        var header = document.createElement('div');
        header.className = 'panel-header';
        titleEl = document.createElement('span');
        header.appendChild(titleEl);

        bodyEl = document.createElement('div');
        bodyEl.className = 'panel-body';

        footerEl = document.createElement('div');
        footerEl.className = 'panel-footer';

        cancelBtn = document.createElement('button');
        cancelBtn.type = 'button';
        confirmBtn = document.createElement('button');
        confirmBtn.type = 'button';

        modal.appendChild(header);
        modal.appendChild(bodyEl);
        modal.appendChild(footerEl);
        backdrop.appendChild(modal);

        backdrop.addEventListener('mousedown', function (e) {
            if (e.target === backdrop) close();
        });
        wireDrag(header);
        cancelBtn.addEventListener('click', close);
        confirmBtn.addEventListener('click', function () {
            if (confirmBtn.disabled) return;
            if (currentOpts && currentOpts.confirm && currentOpts.confirm.onConfirm) {
                currentOpts.confirm.onConfirm();
            }
        });
    }

    // wireDrag makes the modal draggable by `handle`. Dragging switches the
    // modal to fixed positioning so flexbox alignment doesn't fight us.
    function wireDrag(handle) {
        var dragging = false;
        var startX, startY, originX, originY;

        function onDown(e) {
            if (e.button !== undefined && e.button !== 0) return;
            var pt = e.touches ? e.touches[0] : e;
            var rect = modal.getBoundingClientRect();
            modal.classList.add('is-dragged');
            modal.style.position = 'fixed';
            modal.style.left = rect.left + 'px';
            modal.style.top = rect.top + 'px';
            dragging = true;
            startX = pt.clientX;
            startY = pt.clientY;
            originX = rect.left;
            originY = rect.top;
            e.preventDefault();
        }

        function onMove(e) {
            if (!dragging) return;
            var pt = e.touches ? e.touches[0] : e;
            var dx = pt.clientX - startX;
            var dy = pt.clientY - startY;
            var w = modal.offsetWidth;
            var h = modal.offsetHeight;
            var maxX = window.innerWidth - w;
            var maxY = window.innerHeight - h;
            var x = Math.max(0, Math.min(maxX, originX + dx));
            var y = Math.max(0, Math.min(maxY, originY + dy));
            modal.style.left = x + 'px';
            modal.style.top = y + 'px';
        }

        function onUp() { dragging = false; }

        handle.addEventListener('mousedown', onDown);
        handle.addEventListener('touchstart', onDown, { passive: false });
        document.addEventListener('mousemove', onMove);
        document.addEventListener('touchmove', onMove, { passive: false });
        document.addEventListener('mouseup', onUp);
        document.addEventListener('touchend', onUp);
    }

    function open(opts) {
        if (backdrop && backdrop.parentNode) close();
        if (!backdrop) build();

        currentOpts = opts;
        prevFocus = document.activeElement;

        modal.classList.remove('is-dragged');
        modal.style.position = '';
        modal.style.left = '';
        modal.style.top = '';

        titleEl.textContent = opts.title || '';

        bodyEl.textContent = '';
        if (opts.body instanceof Node) {
            bodyEl.appendChild(opts.body);
        } else if (typeof opts.body === 'string') {
            bodyEl.textContent = opts.body;
        }

        var confirmOpts = opts.confirm || {};
        var cancelOpts = opts.cancel || {};

        confirmBtn.textContent = confirmOpts.label || 'OK';
        confirmBtn.className = 'btn' + (confirmOpts.danger ? ' danger' : '');
        confirmBtn.disabled = !!confirmOpts.initiallyDisabled;

        cancelBtn.textContent = cancelOpts.label || 'CANCEL';
        cancelBtn.className = 'btn';

        footerEl.textContent = '';
        if (opts.swapButtons) {
            footerEl.appendChild(confirmBtn);
            footerEl.appendChild(cancelBtn);
        } else {
            footerEl.appendChild(cancelBtn);
            footerEl.appendChild(confirmBtn);
        }

        document.body.appendChild(backdrop);

        setTimeout(function () {
            if (cancelOpts.autofocus) {
                cancelBtn.focus();
                return;
            }
            var firstInput = bodyEl.querySelector('input, textarea, select');
            if (firstInput) {
                firstInput.focus();
                if (firstInput.select) firstInput.select();
            } else {
                confirmBtn.focus();
            }
        }, 0);

        var enterConfirms = confirmOpts.enterConfirms !== false;
        onKeydown = function (e) {
            if (e.key === 'Escape') {
                e.preventDefault();
                close();
                return;
            }
            if (e.key === 'Enter' && enterConfirms) {
                var tag = (e.target && e.target.tagName) || '';
                if (tag === 'TEXTAREA') return;
                e.preventDefault();
                if (!confirmBtn.disabled) confirmBtn.click();
            }
        };
        document.addEventListener('keydown', onKeydown);

        return {
            close: close,
            setConfirmDisabled: function (d) { confirmBtn.disabled = !!d; },
            confirmButton: confirmBtn
        };
    }

    function close() {
        if (!backdrop || !backdrop.parentNode) return;
        if (onKeydown) {
            document.removeEventListener('keydown', onKeydown);
            onKeydown = null;
        }
        backdrop.parentNode.removeChild(backdrop);
        currentOpts = null;
        var toRestore = prevFocus;
        prevFocus = null;
        if (toRestore && toRestore.focus) {
            try { toRestore.focus(); } catch (e) {}
        }
    }

    window.openModal = open;
    window.closeModal = close;
})();
