(function ($) {
    $.fn.customTypeahead = function (data) {
        this.each((index, element) => {
            if (data)
                prepareTypeahead($(element), setDefaults(data, $(element)));
        });
    };

    function setDefaults(data, $element) {
        let elementData = data ? Object.assign({}, data) : {};
        elementData.value = elementData.value ? elementData.value : "Id";
        elementData.display = elementData.display ? elementData.display : "Text";

        elementData.columns = elementData.columns;
        elementData.rowBelow = elementData.rowBelow ? elementData.rowBelow : undefined;
        elementData.items = elementData.items ? elementData.items : undefined;

        elementData.parentElement = elementData.parentElement ? elementData.parentElement : undefined;

        elementData.width = elementData.width ? elementData.width : undefined;
        elementData.minWidth = elementData.minWidth ? elementData.minWidth : undefined;
        elementData.typeaheadClass = elementData.typeaheadClass ? elementData.typeaheadClass : undefined;

        elementData.minSearchLength = elementData.minSearchLength !== undefined ? elementData.minSearchLength : 3;
        elementData.focusOpen = elementData.focusOpen ? elementData.focusOpen : false;

        elementData.url = elementData.url ? elementData.url : $element.attr("data-url");
        elementData.urlPropsFunction = elementData.urlPropsFunction ? elementData.urlPropsFunction : undefined;
        elementData.urlSearchProp = elementData.urlSearchProp ? elementData.urlSearchProp : "search";
        elementData.urlRequestMethod = elementData.urlRequestMethod ? elementData.urlRequestMethod : "GET";

        elementData.paginate = elementData.paginate ? elementData.paginate : false;
        elementData.pageSize = elementData.pageSize ? elementData.pageSize : 30;

        elementData.localization = elementData.localization ? elementData.localization : { noResults: "No results" };

        elementData.onSelect = elementData.onSelect ? elementData.onSelect : undefined;

        elementData.options = data.options;

        return elementData;
    }

    let lastSearch;
    let mutationObserver;
    function prepareTypeahead($element, data) {
        $element.addClass("custom-typeahead");

        if ($element.next().hasClass("custom-typeahead-display")) return;

        let $cloneElement = $element.clone();
        $cloneElement.addClass("custom-typeahead-display").removeAttr("id").removeAttr("name");
        $cloneElement.insertAfter($element);
        $element.css({ "opacity": "0", "height": "0", "padding": "0", "border": "0", "margin": "0" });

        $element.off("change.customTypeahead");
        $element.on("change.customTypeahead", function () {
            $cloneElement.val($element.val());
        });

        if (mutationObserver !== undefined) mutationObserver.disconnect();
        mutationObserver = new MutationObserver(function (mutationsList, observer) { if ($element.is(':disabled')) closeThisCustomTypeaheads($element); });
        mutationObserver.observe($element[0], { attributes: true, childList: false, subtree: false });

        $cloneElement.off("focus.customTypeahead");
        $cloneElement.on("focus.customTypeahead", function () {
            if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) {
                $cloneElement[0].scrollIntoView(true);
            }
            if ($element.hasClass("custom-typeahead-open") || (lastSearch && lastSearch.length > 0 && lastSearch === $cloneElement.val())) return;

            $element.trigger("focus-custom-typeahead");

            if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) {
                setTimeout(function() { generateFocusTypeahead($element, $cloneElement, data); }, 800);
            }
            else {
                generateFocusTypeahead($element, $cloneElement, data);
            }
        });

        $element.off("focus.customTypeahead");
        $element.on("focus.customTypeahead", function () {
            $cloneElement.trigger("focus");
        });

        $element.off("blur.customTypeahead");
        $element.on("blur.customTypeahead", function () {
            $cloneElement.trigger("blur");
        });
    }

    function generateFocusTypeahead($element, $cloneElement, data) {
        let $typeaheadDropdown = generateTypeaheadDropdown($cloneElement, $element, data);

        if (data.focusOpen && $cloneElement.val().length >= data.minSearchLength) {
            getData(data, $typeaheadDropdown, $element, $cloneElement, $cloneElement.val(), true);
        }

        let typingTimer;
        let currentVal;
        $cloneElement.off("keydown.customTypeahead");
        $cloneElement.on("keydown.customTypeahead", function (e) {
            if (e.keyCode == 37 || e.keyCode == 39 || e.ctrlKey) return;

            if (e.keyCode == 38) {
                var $selectedOption = $typeaheadDropdown.find(".custom-typeahead-option-selected");
                var $prevOption = $selectedOption.prev();
                if ($prevOption.hasClass("custom-typeahead-rowBelow")) $prevOption = $prevOption.prev();
                if ($prevOption.length > 0) {
                    $selectedOption.removeClass("custom-typeahead-option-selected");
                    $prevOption.addClass("custom-typeahead-option-selected");
                    $prevOption[0].scrollIntoView(false);
                }

                return;
            }
            if (e.keyCode == 40) {
                var $selectedOption = $typeaheadDropdown.find(".custom-typeahead-option-selected");
                var $nextOption = $selectedOption.next();
                if ($nextOption.hasClass("custom-typeahead-rowBelow")) $nextOption = $nextOption.next();
                if ($nextOption.length > 0) {
                    $selectedOption.removeClass("custom-typeahead-option-selected");
                    $nextOption.addClass("custom-typeahead-option-selected");
                    $nextOption[0].scrollIntoView(false);
                }

                return;
            }
            if (e.keyCode == 13) {
                if (typingTimer) return;

                e.preventDefault();
                e.stopPropagation();
                $typeaheadDropdown.find(".custom-typeahead-option-selected").trigger("click");
                return;
            }

            currentVal = undefined;

            clearTimeout(typingTimer);
            typingTimer = setTimeout(function () {
                if ($cloneElement.val().length < data.minSearchLength || $cloneElement.val() === currentVal) return;

                currentVal = $cloneElement.val();
                getData(data, $typeaheadDropdown, $element, $cloneElement, $cloneElement.val(), true);
                typingTimer = null;
            }, data.typingDelay ? data.typingDelay : 500);
        });
    }

    function generateTypeaheadDropdown($CloneElement, $originalelement, data) {
        let $dropdown = $("<div class='custom-typeahead-dropdown paw-area-v " + (data.typeaheadClass ? data.typeaheadClass : "") + "'></div>");
        let $contentHolder = $('<div class="paw-col-d paw-scrollable"></div>');
        let $content = $("<div class='custom-typeahead-content'></div>");
        let $loader = $("<div class='custom-typeahead-dropdown-loader' style='display:none'><i class='fa fa-spinner fa-spin'></i><div>");

        if (data.paginate) {
            $dropdown.bind("scroll", function () {
                if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight - 20) {
                    getData(data, $dropdown, $originalelement, $CloneElement, $CloneElement.val(), false);
                }
            });
        }

        $content.append(generateContentTable(data));
        $contentHolder.append($content);
        $dropdown.append($contentHolder);

        // additional options at the bottom
        if (paw.utils.value.isArray(data.options)) {
            let $optionsRow = $('<div class="paw-col paw-area-h custom-typeahead-dropdown-options p-t-3x"><div class="paw-col-d"></div></div>');
            let $options = $('<div class="paw-col"></div>');
            paw.utils.iterable.iterate(data.options, function (option) {
                var markup = [];
                markup.push('<button class="paw-btn paw-btn-plain p-3x custom-typeahead-dropdown-option">');
                if (option.icon) {
                    markup.push('<i class="' + option.icon + '"></i>');
                }
                if (option.label) {
                    markup.push('<span>'+ option.label + '</span>');
                }
                markup.push('</button>');
                var $option = $(markup.join('\n'));
                $options.append($option);
                $option.on('click', option.onSelected); // deregister on close
            });
            $optionsRow.append($options);
            $dropdown.append($optionsRow);
        }

        $contentHolder.append($loader);

        let positionLeft = $CloneElement.offset().left - $(window).scrollLeft();
        let positionTop = $CloneElement.offset().top - $(window).scrollTop() + $CloneElement.outerHeight();

        if (data.parentElement) {
            $dropdown.css({ "position": "relative", "padding": "0", "height": "100%", "min-height": "100%", "width": (data.width ? data.width : "100%"), "min-width": (data.minWidth ? data.minWidth : "100%") })
        } else {
            $dropdown.css({ "top": positionTop, "left": positionLeft, "width": (data.width ? data.width : ""), "min-width": (data.minWidth ? data.minWidth : $CloneElement.innerWidth()) })
        }

        return $dropdown;
    }

    function generateContentTable(data) {
        let $contnetTable = $("<table class='custom-typeahead-content-table'></table>");
        let $contnetTableHead = $("<thead></thead>");
        let $contnetTableHeadTr = $("<tr></tr>");

        if (data.columns) {
            for (let i = 0; i < data.columns.length; i++) {
                $contnetTableHeadTr.append("<th " + (data.columns[i].width ? "width='" + data.columns[i].width + "'" : "") + " class='" + (data.columns[i].headerClass ? data.columns[i].headerClass : "") + "'>"
                    + data.columns[i].name + "</th>");
            }
        }
        else {
            $contnetTableHeadTr.append("<th></th>");
        }
        $contnetTableHead.append($contnetTableHeadTr);

        $contnetTable.append($contnetTableHead);
        $contnetTable.append("<tbody></tbody>");

        return $contnetTable;
    }

    var request;
    function getData(data, $typeaheadDropdown, $original, $display, search, clearData) {
        let $loader = $typeaheadDropdown.find(".custom-typeahead-dropdown-loader");
        if (lastSearch === search && $loader.is(":visible")) return;

        lastSearch = search;
        if (request) request.abort();

        if ($("body .custom-typeahead-dropdown").length === 0) {
            (data.parentElement ? data.parentElement : $("body")).append($typeaheadDropdown, data);
            fixPosition($typeaheadDropdown);
        }

        $original.addClass("custom-typeahead-open");

        let $contentTableBody = $typeaheadDropdown.find(".custom-typeahead-content-table tbody");

        $loader.css("display", 'block');
        if (clearData) $contentTableBody.empty();

        if (data.items) {
            let rows = generateDataRows(data, data.items, $original, $display, false);
            $contentTableBody.append(rows);
            $typeaheadDropdown.find(".custom-typeahead-dropdown-loader").css("display", 'none');
            fixPosition($typeaheadDropdown);
        }
        else {
            let urlProps = data.urlPropsFunction ? data.urlPropsFunction() : {};
            urlProps[data.urlSearchProp] = search;
            if (data.urlRequestMethod.toUpperCase() == "POST") {
                urlProps.length = data.pageSize;
                urlProps.start = $typeaheadDropdown.attr("data-page") === undefined ? 1 : parseInt($typeaheadDropdown.attr("data-page")) + 1;

                request = $.post(data.url, urlProps)
                    .done(function (result) {
                        let rows = generateDataRows(data, result, $original, $display, !clearData);
                        if (rows) {
                            $contentTableBody.append(rows);
                        }

                        $typeaheadDropdown.find(".custom-typeahead-dropdown-loader").css("display", 'none');
                        fixPosition($typeaheadDropdown);
                        $typeaheadDropdown.attr("data-page", urlProps.start);
                        request = null;
                    });
            }
            else {
                var requestUrl = data.url;
                let keys = Object.keys(urlProps)

                keys.forEach((key, index) => {
                    requestUrl += requestUrl.indexOf("?") > -1 ? "&" : "?";
                    requestUrl += (encodeURIComponent(key) + "=");
                    requestUrl += encodeURIComponent(urlProps[key]);
                });

                request = $.get(requestUrl)
                    .done(function (result) {
                        let rows = generateDataRows(data, result, $original, $display, !clearData);
                        if (rows) {
                            $contentTableBody.append(rows);
                        }

                        $typeaheadDropdown.find(".custom-typeahead-dropdown-loader").css("display", 'none');
                        fixPosition($typeaheadDropdown);
                        $typeaheadDropdown.attr("data-page", urlProps.start);
                        request = null;
                    });
            }
        }
    }

    function generateDataRows(data, rowData, $original, $display, omitNoResult) {
        if (!rowData || !Array.isArray(rowData) || rowData.length === 0) {
            if (omitNoResult) return null;
            return $("<tr><td class='custom-typeahead-error' colspan='" + (data.columns ? data.columns.length : 1) + "'>" + data.localization.noResults + "</td></tr>");
        }

        let rows = [];
        for (let i = 0; i < rowData.length; i++) {
            let $contnetTableHeadTr = $("<tr class='custom-typeahead-option " + (i == 0 ? "custom-typeahead-option-selected" : "") + " " + (data.rowBelow ? "custom-typeahead-rowBelow-head" : "") + "'></tr>");
            if (data.columns) {
                for (let j = 0; j < data.columns.length; j++) {
                    $contnetTableHeadTr.append("<td " + (data.columns[j].width ? "width='" + data.columns[j].width + "'" : "") + " class='" + (data.columns[j].cellClass ? data.columns[j].cellClass : "") + "'>"
                        + getDataValue(rowData[i], data.columns[j].value) + "</td>");
                }
            }
            else {
                $contnetTableHeadTr.append("<td>" + rowData[i][data.display] + "</td>");
            }

            $contnetTableHeadTr.on("click", function (e) {
                e.preventDefault();
                e.stopPropagation();

                $original.val(rowData[i][data.value]);
                $display.val(rowData[i][data.display]);
                lastSearch = rowData[i][data.display];

                if (data.onSelect) data.onSelect(rowData[i]);

                closeAllCustomTypeaheads();
            });

            rows.push($contnetTableHeadTr);

            //Row below
            if (data.rowBelow) {
                let $contentTableRowBelowTr = $("<tr class='custom-typeahead-option custom-typeahead-rowBelow'></tr>");
                $contentTableRowBelowTr.append(data.rowBelow instanceof Function ? data.rowBelow(rowData[i]) : data.rowBelow);
                $contentTableRowBelowTr.on("mouseenter", function () {
                    $(this).closest("table").find(".custom-typeahead-option-selected").removeClass("custom-typeahead-option-selected");
                    $(this).prev().addClass("custom-typeahead-option-selected");
                });
                $contentTableRowBelowTr.on("click", function (e) {
                    e.preventDefault();
                    e.stopPropagation();
                    $(this).prev().trigger("click");
                });
                rows.push($contentTableRowBelowTr);
            }
        }

        return rows;
    }

    function getDataValue(item, valueName) {
        if (!valueName) return;

        if (typeof valueName === 'function')
            return valueName(item);

        return item[valueName];
    }

    function fixPosition($typeaheadDropdown) {
        fixwidth($typeaheadDropdown);
        fixHeight($typeaheadDropdown);
    }

    function fixwidth($typeaheadDropdown) {
        let minWidth = 300;
        let dropdownWidth = $typeaheadDropdown.width();
        let dropdownLeft = $typeaheadDropdown.position().left;
        let windowWidth = $(window).width();

        if (dropdownLeft + dropdownWidth > windowWidth) {
            let correctedWidth = dropdownWidth - ((dropdownLeft + dropdownWidth) - windowWidth);
            if (correctedWidth > minWidth) {
                $typeaheadDropdown.css("min-width", correctedWidth).css("width", correctedWidth);
            }
            else {
                let correctedLeft = dropdownLeft - (minWidth - correctedWidth);
                correctedWidth = minWidth;

                if (correctedLeft > 0) {
                    $typeaheadDropdown.css("min-width", correctedWidth).css("width", correctedWidth).css("left", correctedLeft);
                }
                else {
                    $typeaheadDropdown.css("min-width", windowWidth).css("width", windowWidth).css("left", 0);
                }
            }
        }
    }

    function fixHeight($typeaheadDropdown) {
        let minHeight = 150;
        let dropdownHeight = $typeaheadDropdown.height();
        let dropdownTop = $typeaheadDropdown.position().top;
        let windowHeight = $(window).height();

        if (dropdownTop + dropdownHeight > windowHeight) {
            let correctedHeight = dropdownHeight - ((dropdownTop + dropdownHeight) - windowHeight);
            if (correctedHeight > minHeight) {
                $typeaheadDropdown.css("min-height", correctedHeight).css("height", correctedHeight);
            }
            else {
                let correctedTop = dropdownTop - (minHeight - correctedHeight);
                correctedHeight = minHeight;

                if (correctedTop > 0) {
                    $typeaheadDropdown.css("min-height", correctedHeight).css("height", correctedHeight).css("top", correctedTop);
                }
                else {
                    $typeaheadDropdown.css("min-height", windowHeight).css("height", windowHeight).css("top", 0);
                }
            }
        }
    }
}(jQuery));


function closeThisCustomTypeaheads($element) {
    if (!$element.hasClass("custom-typeahead-open")) return;

    closeAllCustomTypeaheads();
}

function closeAllCustomTypeaheads() {
    $(".custom-typeahead-open .custom-typeahead-dropdown-options button.custom-typeahead-dropdown-option").off('click');
    $(".custom-typeahead-open").removeClass("custom-typeahead-open").blur();
    $(".custom-typeahead-dropdown").remove();
}

$(document).ready(function () {
    $(".custom-typeahead").customTypeahead();

    $(window).resize(function () {
        if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) return;

        closeAllCustomTypeaheads();
    });

    $(window).scroll(function () {
        closeAllCustomTypeaheads();
    });

    $(".divided-content-main").on("scroll", function () {
        closeAllCustomTypeaheads();
    });

    $(window).mousedown(function (e) {
        if ($(e.target).hasClass('custom-typeahead-dropdown') || $(e.target).hasClass('custom-typeahead') || $(e.target).closest('.custom-typeahead-dropdown').length > 0) return;

        closeAllCustomTypeaheads();
    });

    $('body').bind('touchmove', function (e) {
        if ($(e.target).hasClass('custom-typeahead-dropdown') || $(e.target).hasClass('custom-typeahead') || $(e.target).closest('.custom-typeahead-dropdown').length > 0) return;

        closeAllCustomTypeaheads();
    });
});