import $ from "jquery";
import Micromodal from "micromodal";

import * as blueslip from "./blueslip";
import * as browser_history from "./browser_history";
import * as popovers from "./popovers";

let $active_overlay;
let close_handler;
let open_overlay_name;

function reset_state() {
    $active_overlay = undefined;
    close_handler = undefined;
    open_overlay_name = undefined;
}

export function is_active() {
    return Boolean(open_overlay_name);
}

export function is_modal_open() {
    // Check for both Bootstrap and Micromodal modals.
    return $(".modal").hasClass("in") || $(".micromodal").hasClass("modal--open");
}

export function is_overlay_or_modal_open() {
    return is_active() || is_modal_open();
}

export function info_overlay_open() {
    return open_overlay_name === "informationalOverlays";
}

export function settings_open() {
    return open_overlay_name === "settings";
}

export function streams_open() {
    return open_overlay_name === "subscriptions";
}

export function lightbox_open() {
    return open_overlay_name === "lightbox";
}

export function drafts_open() {
    return open_overlay_name === "drafts";
}

// To address bugs where mouse might apply to the streams/settings
// overlays underneath an open modal within those settings UI, we add
// this inline style to 'div.overlay.show', overriding the
// "pointer-events: all" style in app_components.css.
//
// This is kinda hacky; it only works for modals within overlays, and
// we need to make sure it gets re-enabled when the modal closes.
export function disable_background_mouse_events() {
    $("div.overlay.show").attr("style", "pointer-events: none");
}

// This removes only the inline-style of the element that
// was added in disable_background_mouse_events and
// enables the background mouse events.
export function enable_background_mouse_events() {
    $("div.overlay.show").attr("style", null);
}

export function active_modal() {
    if (!is_modal_open()) {
        blueslip.error("Programming error — Called active_modal when there is no modal open");
        return undefined;
    }

    // Check for Micromodal modals.
    const $micromodal = $(".micromodal.modal--open");
    if ($micromodal.length) {
        return `#${CSS.escape($micromodal.attr("id"))}`;
    }
    return `#${CSS.escape($(".modal.in").attr("id"))}`;
}

export function open_overlay(opts) {
    popovers.hide_all();

    if (!opts.name || !opts.$overlay || !opts.on_close) {
        blueslip.error("Programming error in open_overlay");
        return;
    }

    if ($active_overlay || open_overlay_name || close_handler) {
        blueslip.error(
            "Programming error — trying to open " +
                opts.name +
                " before closing " +
                open_overlay_name,
        );
        return;
    }

    blueslip.debug("open overlay: " + opts.name);

    // Our overlays are kind of crufty...we have an HTML id
    // attribute for them and then a data-overlay attribute for
    // them.  Make sure they match.
    if (opts.$overlay.attr("data-overlay") !== opts.name) {
        blueslip.error("Bad overlay setup for " + opts.name);
        return;
    }

    open_overlay_name = opts.name;
    $active_overlay = opts.$overlay;
    opts.$overlay.addClass("show");

    opts.$overlay.attr("aria-hidden", "false");
    $(".app").attr("aria-hidden", "true");
    $(".fixed-app").attr("aria-hidden", "true");
    $(".header").attr("aria-hidden", "true");

    close_handler = function () {
        opts.on_close();
        reset_state();
    };
}

// If conf.autoremove is true, the modal element will be removed from the DOM
// once the modal is hidden.
// If conf.micromodal is true, open a micromodal modal else open a bootstrap modal
// conf also accepts the following optional properties:
// on_show: Callback to run when the modal is triggered to show.
// on_shown: Callback to run when the modal is shown.
// on_hide: Callback to run when the modal is triggered to hide.
// on_hidden: Callback to run when the modal is hidden.
export function open_modal(selector, conf) {
    if (selector === undefined) {
        blueslip.error("Undefined selector was passed into open_modal");
        return;
    }

    if ((!conf || (conf && !conf.micromodal)) && selector[0] !== "#") {
        blueslip.error("Non-id-based selector passed in to open_modal: " + selector);
        return;
    }

    // Don't accept hash-based selector to enforce modals to have unique ids and
    // since micromodal doesn't accept hash based selectors.
    if (conf && conf.micromodal && selector[0] === "#") {
        blueslip.error("hash-based selector passed in to micromodal-based open_modal: " + selector);
        return;
    }

    if (is_modal_open()) {
        blueslip.error("open_modal() was called while " + active_modal() + " modal was open.");
        return;
    }

    blueslip.debug("open modal: " + selector);

    // Show a modal using micromodal.
    if (conf && conf.micromodal) {
        // Micromodal gets elements using the getElementById DOM function
        // which doesn't require the hash. We add it manually here.
        const id_selector = `#${selector}`;
        const $micromodal = $(id_selector);

        $micromodal.find(".modal__container").on("animationend", (event) => {
            // Micromodal doesn't support Bootstrap-style `shown.bs.modal` and
            // `hidden.bs.modal` events. We workaround this by using the animationName
            // from the native event and running the required functions after the
            // animation ends.
            const animation_name = event.originalEvent.animationName;
            if (animation_name === "mmfadeIn") {
                // Equivalent to bootstrap's "shown.bs.modal" event

                // Micromodal adds the is-open class before the modal animation
                // is complete, which isn't really helpful since a modal is open after the
                // animation is complete. So, we manually add a class after the
                // animation is complete.
                $micromodal.addClass("modal--open");
                $micromodal.removeClass("modal--opening");

                if (conf.on_shown) {
                    conf.on_shown();
                }
            } else if (animation_name === "mmfadeOut") {
                // Equivalent to bootstrap's "hidden.bs.modal" event.
                //
                // Call the on_hidden callback after the modal finishes hiding.

                $micromodal.removeClass("modal--open");
                if (conf.autoremove) {
                    $micromodal.remove();
                }
                if (conf.on_hidden) {
                    conf.on_hidden();
                }
            }
        });

        Micromodal.show(selector, {
            disableFocus: true,
            openClass: "modal--opening",
            onShow: conf?.on_show,
            onClose: conf?.on_hide,
        });
        return;
    }

    const $elem = $(selector).expectOne();
    $elem.modal("show").attr("aria-hidden", false);
    // Disable background mouse events when modal is active
    disable_background_mouse_events();
    // Remove previous alert messages from modal, if exists.
    $elem.find(".alert").hide();
    $elem.find(".alert-notification").html("");

    if (conf && conf.autoremove) {
        $elem.on("hidden.bs.modal", () => {
            $elem.remove();
        });
    }
}

export function close_overlay(name) {
    popovers.hide_all();

    if (name !== open_overlay_name) {
        blueslip.error("Trying to close " + name + " when " + open_overlay_name + " is open.");
        return;
    }

    if (name === undefined) {
        blueslip.error("Undefined name was passed into close_overlay");
        return;
    }

    blueslip.debug("close overlay: " + name);

    $active_overlay.removeClass("show");

    $active_overlay.attr("aria-hidden", "true");
    $(".app").attr("aria-hidden", "false");
    $(".fixed-app").attr("aria-hidden", "false");
    $(".header").attr("aria-hidden", "false");

    if (!close_handler) {
        blueslip.error("Overlay close handler for " + name + " not properly set up.");
        return;
    }

    close_handler();
}

export function close_active() {
    if (!open_overlay_name) {
        blueslip.warn("close_active() called without checking is_active()");
        return;
    }

    close_overlay(open_overlay_name);
}

// `conf` is an object with the following optional properties:
// * micromodal: true if the modal to close is expected to be micromodal.
// * on_hidden: Callback to run when the modal finishes hiding.
export function close_modal(selector, conf) {
    if (selector === undefined) {
        blueslip.error("Undefined selector was passed into close_modal");
        return;
    }

    if (!is_modal_open()) {
        blueslip.warn("close_active_modal() called without checking is_modal_open()");
        return;
    }

    if (
        (!conf && active_modal() !== selector) ||
        (conf && conf.micromodal && active_modal() !== `#${selector}`)
    ) {
        blueslip.error(
            "Trying to close " + selector + " modal when " + active_modal() + " is open.",
        );
        return;
    }

    blueslip.debug("close modal: " + selector);

    if (conf && conf.micromodal) {
        const id_selector = `#${selector}`;
        const $micromodal = $(id_selector);

        // On-hidden hooks should typically be registered in
        // overlays.open_modal.  However, we offer this alternative
        // mechanism as a convenience for hooks only known when
        // closing the modal.
        $micromodal.find(".modal__container").on("animationend", (event) => {
            const animation_name = event.originalEvent.animationName;
            if (animation_name === "mmfadeOut" && conf.on_hidden) {
                conf.on_hidden();
            }
        });

        Micromodal.close(selector);
        return;
    }

    const $elem = $(selector).expectOne();
    $elem.modal("hide").attr("aria-hidden", true);
}

export function close_active_modal() {
    if (!is_modal_open()) {
        blueslip.warn("close_active_modal() called without checking is_modal_open()");
        return;
    }

    // Check for Micromodal modals.
    const $micromodal = $(".micromodal.modal--open");
    if ($micromodal.length) {
        Micromodal.close(`${CSS.escape($micromodal.attr("id"))}`);
        return;
    }

    $(".modal.in").modal("hide").attr("aria-hidden", true);
}

export function close_for_hash_change() {
    $("div.overlay.show").removeClass("show");
    if ($active_overlay) {
        close_handler();
    }
}

export function open_settings() {
    open_overlay({
        name: "settings",
        $overlay: $("#settings_overlay_container"),
        on_close() {
            browser_history.exit_overlay();
        },
    });
}

export function initialize() {
    $("body").on("click", "div.overlay, div.overlay .exit", (e) => {
        let $target = $(e.target);

        if (document.getSelection().type === "Range") {
            return;
        }

        // if the target is not the div.overlay element, search up the node tree
        // until it is found.
        if ($target.is(".exit, .exit-sign, .overlay-content, .exit span")) {
            $target = $target.closest("[data-overlay]");
        } else if (!$target.is("div.overlay")) {
            // not a valid click target then.
            return;
        }

        if ($target.data("noclose")) {
            // This overlay has been marked explicitly to not be closed.
            return;
        }

        const target_name = $target.attr("data-overlay");

        close_overlay(target_name);

        e.preventDefault();
        e.stopPropagation();
    });
}
