(function($) {
    "use strict";

    $.fn.markItUp = function(o, extraSettings) {
        let method, params, options, r, a, l;
        r = a = l = false; // ctrlKey, shiftKey, altKey

        if (typeof o === "string") {
            method = o;
            params = extraSettings;
        }

        options = {
            id: "",
            nameSpace: "",
            root: "",
            previewHandler: false,
            previewInWindow: "",
            previewInElement: "",
            previewAutoRefresh: true,
            previewPosition: "after",
            previewParser: false,
            previewParserPath: "",
            previewParserVar: "data",
            previewParserAjaxType: "POST",
            resizeHandle: true,
            beforeInsert: "",
            afterInsert: "",
            onEnter: {},
            onShiftEnter: {},
            onCtrlEnter: {},
            onTab: {},
            markupSet: [{}]
        };

        $.extend(options, o, extraSettings);

        if (!options.root) {
            $("script").each(function(e, t) {
                const r = $(t).get(0).src.match(/(.*)jquery\.markitup(\.pack)?\.js$/);
                if (r !== null) {
                    options.root = r[1];
                }
            });
        }

        return this.each(function() {
            let $$, u, levels, scrollPosition, f, m, g, v, header, footer, previewWindow, abort, I;
            $$ = $(this);
            u = this; // textarea
            levels = [];
            abort = false;
            scrollPosition = f = 0; // caretPosition
            m = -1; // caretOffset
            I = ""; // selection
            g = null; // clicked (button object)

            // Store plugin methods for access in call handlers
            const pluginMethods = {
                preview: preview,
                markup: markup,
                get: get,
                insert: insert,
                set: set
            };

            options.previewParserPath = localize(options.previewParserPath);

            if (method) {
                switch (method) {
                    case "remove":
                        remove();
                        break;
                    case "insert":
                        markup(params);
                        break;
                    default:
                        $.error(`Method ${method} does not exist on jQuery.markItUp`);
                }
                return;
            } else {
                init();
            }

            function localize(e, t) {
                return t ? e.replace(/("|')~\//g, `$1${options.root}`) : e.replace(/^~\//, options.root);
            }

            function init() {
                let id = "", nameSpace = "";
                if (options.id) {
                    id = `id="${options.id}"`;
                } else if ($$.attr("id")) {
                    id = `id="markItUp${$$.attr("id").substr(0, 1).toUpperCase() + $$.attr("id").substr(1)}"`;
                }
                if (options.nameSpace) {
                    nameSpace = `class="${options.nameSpace}"`;
                }

                $$.wrap(`<div ${nameSpace}></div>`);
                $$.wrap(`<div ${id} class="markItUp"></div>`);
                $$.wrap('<div class="markItUpContainer"></div>');
                $$.addClass("markItUpEditor");

                // Store plugin methods in jQuery data
                $$.data('markItUp', pluginMethods);

                header = $('<div class="markItUpHeader"></div>').insertBefore($$);
                $(dropMenus(options.markupSet)).appendTo(header);

                footer = $('<div class="markItUpFooter"></div>').insertAfter($$);

                if (options.resizeHandle === true) {
                    const resizeHandle = $('<div class="markItUpResizeHandle"></div>').insertAfter($$).on("mousedown.markItUp", function(e) {
                        const i = $$.height(), n = e.clientY;
                        const t = function(e) {
                            $$.css("height", Math.max(20, e.clientY + i - n) + "px");
                            return false;
                        };
                        const r = function(e) {
                            $("html").off("mousemove.markItUp", t).off("mouseup.markItUp", r);
                            return false;
                        };
                        $("html").on("mousemove.markItUp", t).on("mouseup.markItUp", r);
                    });
                    footer.append(resizeHandle);
                }

                $$.on("keydown.markItUp", keyPressed).on("keyup", keyPressed);
                $$.on("insertion.markItUp", function(e, t) {
                    if (t.target !== false) {
                        get();
                    }
                    if (u === $.markItUp.focused) {
                        markup(t);
                    }
                });
                $$.on("focus.markItUp", function() {
                    $.markItUp.focused = this;
                });
            }

            function dropMenus(markupSet) {
                const ul = $("<ul></ul>");
                let i = 0;
                $("li:hover > ul", ul).css("display", "block");

                $.each(markupSet, function() {
                    const g = this; // button (minified as g)
                    let t = "", title, li, j;
                    title = g.title
                        ? g.key
                            ? (g.title || "") + ` [Ctrl+${g.key}]`
                            : (g.title || "")
                        : g.key
                            ? (g.name || "") + ` [Ctrl+${g.key}]`
                            : (g.name || "");
                    const key = g.key ? `accesskey="${g.key}"` : "";

                    if (g.separator) {
                        li = $(`<li class="markItUpSeparator">${g.separator || ""}</li>`).appendTo(ul);
                    } else {
                        i++;
                        for (j = levels.length - 1; j >= 0; j--) {
                            t += levels[j] + "-";
                        }
                        li = $(`<li class="markItUpButton markItUpButton${t + i} ${g.className || ""}"><a href="#" ${key} title="${title}">${g.name || ""}</a></li>`)
                            .on("contextmenu.markItUp", function() {
                                return false;
                            })
                            .on("click.markItUp", function(e) {
                                e.preventDefault();
                            })
                            .on("focusin.markItUp", function() {
                                $$.focus();
                            })
                            .on("mouseup", function(e) {
                                if (typeof g.call === "function") {
                                    g.call.call(this, e); // Ensure 'this' is the <a> element
                                }
                                setTimeout(() => markup(g), 1);
                                return false;
                            })
                            .on("mouseenter.markItUp", function() {
                                $("> ul", this).show();
                                $(document).one("click", () => $("ul ul", header).hide());
                            })
                            .on("mouseleave.markItUp", function() {
                                $("> ul", this).hide();
                            })
                            .appendTo(ul);

                        if (g.dropMenu) {
                            levels.push(i);
                            $(li).addClass("markItUpDropMenu").append(dropMenus(g.dropMenu));
                        }
                    }
                });
                levels.pop();
                return ul;
            }

            function magicMarkups(e) {
                if (!e) return "";
                e = e.toString();
                e = e.replace(/\(\!\(([\s\S]*?)\)\!\)/g, function(e, t) {
                    const r = t.split("|!|");
                    return l === true ? (r[1] !== undefined ? r[1] : r[0]) : (r[1] === undefined ? "" : r[0]);
                });
                e = e.replace(/\[\!\[([\s\S]*?)\]\!\]/g, function(e, t) {
                    const r = t.split(":!:");
                    if (abort === true) return false;
                    const value = prompt(r[0], r[1] ? r[1] : "");
                    if (value === null) {
                        abort = true;
                    }
                    return value;
                });
                return e;
            }

            function prepare(e) {
                if ($.isFunction(e)) {
                    e = e(v);
                }
                return magicMarkups(e);
            }

            function build(e) {
                const R = prepare(g.openWith);
                const x = prepare(g.placeHolder);
                const A = prepare(g.replaceWith);
                const F = prepare(g.closeWith);
                const L = prepare(g.openBlockWith);
                const N = prepare(g.closeBlockWith);
                const q = g.multiline;
                let block;

                if (A !== "") {
                    block = R + A + F;
                } else if (I === "" && x !== "") {
                    block = R + x + F;
                } else {
                    e = e || I;
                    let lines = [e], c = [];
                    if (q === true) {
                        lines = e.split(/\r?\n/);
                    }
                    for (let p = 0; p < lines.length; p++) {
                        let line = lines[p];
                        let u;
                        if ((u = line.match(/ *$/))) {
                            c.push(R + line.replace(/ *$/g, "") + F + u);
                        } else {
                            c.push(R + line + F);
                        }
                    }
                    block = c.join("\n");
                }
                block = L + block + N;
                return { block, openBlockWith: L, openWith: R, replaceWith: A, placeHolder: x, closeWith: F, closeBlockWith: N };
            }

            function markup(e) {
                let t, r, i, n, string, start;
                v = g = e;
                I = get();
                $.extend(v, {
                    line: "",
                    root: options.root,
                    textarea: u,
                    selection: I || "",
                    caretPosition: f,
                    ctrlKey: r,
                    shiftKey: a,
                    altKey: l
                });

                prepare(options.beforeInsert);
                prepare(g.beforeInsert);

                if ((r === true && a === true) || e.multiline === true) {
                    prepare(g.beforeMultiInsert);
                }

                $.extend(v, { line: 1 });

                if (r === true && a === true) {
                    const lines = I.split(/\r?\n/);
                    r = 0;
                    i = lines.length;
                    for (n = 0; n < i; n++) {
                        if ($.trim(lines[n]) !== "") {
                            $.extend(v, { line: ++r, selection: lines[n] });
                            lines[n] = build(lines[n]).block;
                        } else {
                            lines[n] = "";
                        }
                    }
                    string = { block: lines.join("\n") };
                    start = f;
                    t = string.block.length;
                } else if (r === true) {
                    string = build(I);
                    start = f + string.openWith.length;
                    t = string.block.length - string.openWith.length - string.closeWith.length;
                    t -= string.block.match(/ $/)? 1 : 0;
                } else if (a === true) {
                    string = build(I);
                    start = f;
                    t = string.block.length;
                } else {
                    string = build(I);
                    start = f + string.block.length;
                    t = 0;
                }

                if (I === "" && string.replaceWith === "") {
                    m += string.block.length;
                    start = f + string.openBlockWith.length + string.openWith.length;
                    t = string.block.length - string.openBlockWith.length - string.openWith.length - string.closeWith.length - string.closeBlockWith.length;
                    m = $$.val().substring(f, $$.val().length).length;
                }

                $.extend(v, { caretPosition: f, scrollPosition });

                if (string.block !== I && abort === false) {
                    insert(string.block);
                    set(start, t);
                } else {
                    m = -1;
                }

                get();
                $.extend(v, { line: "", selection: I });

                if ((r === true && a === true) || e.multiline === true) {
                    prepare(g.afterMultiInsert);
                }
                prepare(g.afterInsert);
                prepare(options.afterInsert);

                if (previewWindow && options.previewAutoRefresh) {
                    refreshPreview();
                }

                u.dispatchEvent(new Event("input"));
                a = l = r = abort = false;
            }

            function insert(e) {
                if (document.selection) {
                    document.selection.createRange().text = e;
                } else {
                    u.value = u.value.substring(0, f) + e + u.value.substring(f + I.length, u.value.length);
                }
            }

            function set(e, t) {
                if (u.createTextRange) {
                    const range = u.createTextRange();
                    range.collapse(true);
                    range.moveStart("character", e);
                    range.moveEnd("character", t);
                    range.select();
                } else if (u.setSelectionRange) {
                    u.setSelectionRange(e, e + t);
                }
                u.scrollTop = scrollPosition;
                u.focus();
            }

            function get() {
                "use strict";
                let T;
                u.focus();
                scrollPosition = u.scrollTop;
                if (document.selection) {
                    T = document.selection.createRange().text;
                    if ($.browser && $.browser.msie) {
                        const range = document.selection.createRange(), t = range.duplicate();
                        t.moveToElementText(u);
                        f = -1;
                        while (t.inRange(range)) {
                            t.moveStart("character");
                            f++;
                        }
                    } else {
                        f = u.selectionStart;
                    }
                } else {
                    f = u.selectionStart;
                    T = u.value.substring(f, u.selectionEnd);
                }
                I = T;
                return T;
            }

            function preview() {
                if (typeof options.previewHandler === "function") {
                    options.previewHandler($$.val());
                    return;
                }

                if (options.previewInElement) {
                    refreshPreview();
                    options.previewInElement.show();
                    return;
                }

                const previewContainer = $('<div class="markItUpPreview" style="display: none;"></div>');
                if (options.previewPosition === "after") {
                    previewContainer.insertAfter(footer);
                } else {
                    previewContainer.insertBefore(header);
                }

                options.previewInElement = previewContainer;
                previewContainer.show();
                refreshPreview();

                previewWindow = previewContainer[0];
            }

            function refreshPreview() {
                renderPreview();
            }

            function renderPreview() {
                let t = $$.val();

                if (options.previewParser && typeof options.previewParser === "function") {
                    t = options.previewParser(t);
                    writeInPreview(t);
                    return;
                }

                if (options.previewParserPath !== "") {
                    $.ajax({
                        type: options.previewParserAjaxType,
                        dataType: "text",
                        global: false,
                        url: options.previewParserPath,
                        data: `${options.previewParserVar}=${encodeURIComponent(t)}`,
                        success: function(e) {
                            writeInPreview(localize(e, 1));
                        },
                        error: function(err) {
                            console.error("Preview AJAX error:", err);
                        }
                    });
                    return;
                }

                writeInPreview(t);
            }

            function writeInPreview(e) {
                if (options.previewInElement && options.previewInElement.length) {
                    options.previewInElement.html(e);

                    $$.trigger('markitup:previewUpdated');
                }
            }

            function keyPressed(e) {
                a = e.shiftKey;
                l = e.altKey;
                r = (!e.altKey && !e.ctrlKey) && (e.ctrlKey || e.metaKey);

                if (e.type === "keydown") {
                    if (r === true) {
                        const li = $(`a[accesskey="${e.keyCode === 13 ? "\\n" : String.fromCharCode(e.keyCode)}"]`, header).parent("li");
                        if (li.length !== 0) {
                            r = false;
                            setTimeout(() => li.triggerHandler("mouseup"), 1);
                            return false;
                        }
                    }
                    if (e.keyCode === 13 || e.keyCode === 10) {
                        if (r === true) {
                            r = false;
                            markup(options.onCtrlEnter);
                            return options.onCtrlEnter.keepDefault;
                        }
                        if (a === true) {
                            a = false;
                            markup(options.onShiftEnter);
                            return options.onShiftEnter.keepDefault;
                        }
                        markup(options.onEnter);
                        return options.onEnter.keepDefault;
                    }
                    if (e.keyCode === 9) {
                        if (a !== true && r !== true && l !== true) {
                            if (m !== -1) {
                                get();
                                m = $$.val().length - m;
                                set(m, 0);
                                m = -1;
                                return false;
                            } else {
                                markup(options.onTab);
                                return options.onTab.keepDefault;
                            }
                        }
                    }
                }
            }

            function remove() {
                $$.off(".markItUp").removeClass("markItUpEditor");
                $$.parent("div").parent("div.markItUp").parent("div").replaceWith($$);
                const e = $$.parent("div").parent("div.markItUp").parent("div");
                if (e.length) {
                    e.replaceWith($$);
                }
                $$.data("markItUp", null);
            }
        });
    };

    $.fn.markItUpRemove = function() {
        return this.each(function() {
            $(this).markItUp("remove");
        });
    };

    $.markItUp = function(e) {
        const t = { target: false };
        $.extend(t, e);
        if (t.target) {
            return $(t.target).each(function() {
                $(this).focus();
                $(this).trigger("insertion", [t]);
            });
        } else {
            $("textarea").trigger("insertion", [t]);
        }
    };
})($);
