(function($) {
    "use strict";

    function TokenPicker($select, options) {
        this.$select = $select;
        this.$form = $select.closest("form#crcpw-widget-settings");
        this.settings = $.extend({}, {
            limit: 1,
            type: "",
            placeholder: "",
            allowCustom: true,
            clearLabel: this.$form.data("clearLabel") || "Clear",
            selectedLabel: this.$form.data("selectedLabel") || "Selected",
            searchingLabel: this.$form.data("searchingLabel") || "Searching...",
            noResultsLabel: this.$form.data("noResultsLabel") || "No matches found.",
            maxSelectedLabel: this.$form.data("maxSelectedLabel") || "Remove one item to add another.",
            fiatIconBase: this.$form.data("fiatIconBase") || "https://co-in.io/assets/icons",
            cryptoIconBase: this.$form.data("cryptoIconBase") || "https://www.cryptocompare.com"
        }, options || {});
        this.request = null;
        this.$root = null;
        this.$tokens = null;
        this.$count = null;
        this.$clear = null;
        this.$input = null;
        this.$status = null;
        this.currentTerm = "";
        this.currentPage = 1;
        this.hasMore = false;
        this.loadingMore = false;
        this.init();
    }

    function normalizeSymbol(value) {
        var symbol = String(value || "").trim().toLocaleUpperCase();

        try {
            return symbol.replace(/[^\p{L}\p{N}._$-]/gu, "");
        } catch (error) {
            return symbol.toUpperCase().replace(/[^A-Z0-9._$-]/g, "");
        }
    }

    TokenPicker.prototype.init = function() {
        this.build();
        this.bind();
        this.render();
    };

    TokenPicker.prototype.build = function() {
        this.$root = $(
            '<div class="crcpw-token-picker">' +
                '<div class="crcpw-token-picker__selected">' +
                    '<ul class="crcpw-token-picker__tokens"></ul>' +
                    '<div class="crcpw-token-picker__actions">' +
                        '<span class="crcpw-token-picker__count" aria-live="polite"></span>' +
                        '<button type="button" class="button-link crcpw-token-picker__clear"></button>' +
                    '</div>' +
                '</div>' +
                '<div class="crcpw-token-picker__search">' +
                    '<input type="text" class="regular-text crcpw-token-picker__input" autocomplete="off" />' +
                '</div>' +
                '<p class="description crcpw-token-picker__status" aria-live="polite"></p>' +
            '</div>'
        );

        this.$tokens = this.$root.find(".crcpw-token-picker__tokens");
        this.$count = this.$root.find(".crcpw-token-picker__count");
        this.$clear = this.$root.find(".crcpw-token-picker__clear");
        this.$input = this.$root.find(".crcpw-token-picker__input");
        this.$status = this.$root.find(".crcpw-token-picker__status");

        this.$clear.text(this.settings.clearLabel);
        this.$input.attr("placeholder", this.settings.placeholder);
        this.$input.attr("aria-label", this.settings.placeholder);

        this.$root.insertAfter(this.$select);
        this.$select.addClass("crcpw-token-picker__source--ready");
    };

    TokenPicker.prototype.bind = function() {
        var self = this;

        this.$select.on("change.crcpwTokenPicker", function() {
            self.render();
        });

        this.$root.on("click", ".crcpw-token-picker__remove", function(event) {
            event.preventDefault();
            self.remove($(this).data("value"));
        });

        this.$clear.on("click", function(event) {
            event.preventDefault();
            self.clear();
        });

        this.$input.on("keydown", function(event) {
            if (event.which === 8 && !self.$input.val()) {
                self.removeLast();
                event.preventDefault();
                return;
            }

            if (
                (event.which === 13 || event.which === 188) &&
                self.$input.autocomplete("widget").is(":visible") &&
                self.hasActiveAutocompleteItem()
            ) {
                return;
            }

            if ((event.which === 13 || event.which === 188) && (self.tryCreate(event) || !self.settings.allowCustom)) {
                event.preventDefault();
            }
        });

        this.$input.on("click", function() {
            self.$input.autocomplete("search", self.$input.val());
        });

        this.$input.on("focus", function() {
            self.$input.autocomplete("search", self.$input.val());
        });

        this.$input.autocomplete({
            minLength: 0,
            delay: 250,
            appendTo: this.$root,
            source: function(request, response) {
                self.search(request, response);
            },
            focus: function(event) {
                event.preventDefault();
            },
            select: function(event, ui) {
                event.preventDefault();
                if (!ui.item || ui.item.disabled || ui.item.isMessage) {
                    return false;
                }
                self.add(ui.item);
                self.$input.val("");
                self.$input.autocomplete("close");
                return false;
            },
            open: function() {
                self.$root.find(".ui-autocomplete").addClass("crcpw-token-picker__menu");
                self.bindMenuScroll();
            }
        });

        this.decorateMenu();
    };

    TokenPicker.prototype.decorateMenu = function() {
        var self = this;
        var autocomplete = this.$input.autocomplete("instance");

        autocomplete._renderItem = function(ul, item) {
            var $item = $("<li>");
            var $row = $('<div class="ui-menu-item-wrapper crcpw-token-picker__menu-row"></div>');
            var $copy = $('<span class="crcpw-token-picker__menu-copy"></span>');

            if (item.isMessage) {
                $item.addClass("ui-state-disabled crcpw-token-picker__menu-item--message");
                $row.text(item.label);
                return $item.append($row).appendTo(ul);
            }

            if (item.disabled) {
                $item.addClass("ui-state-disabled crcpw-token-picker__menu-item--disabled");
            }

            if (item.icon) {
                $row.append($("<img>", {
                    "class": "crcpw-token-picker__menu-icon",
                    alt: "",
                    loading: "lazy",
                    src: item.icon
                }));
            } else {
                $row.append('<span class="crcpw-token-picker__menu-icon crcpw-token-picker__menu-icon--placeholder"></span>');
            }

            $copy.append($("<span>", {
                "class": "crcpw-token-picker__menu-symbol",
                text: item.symbol
            }));

            if (item.name && item.name !== item.symbol) {
                $copy.append($("<span>", {
                    "class": "crcpw-token-picker__menu-name",
                    text: item.name
                }));
            }

            $row.append($copy);

            if (item.selected) {
                $row.append($("<span>", {
                    "class": "crcpw-token-picker__menu-state",
                    text: self.settings.selectedLabel
                }));
            }

            return $item.append($row).appendTo(ul);
        };

        autocomplete._resizeMenu = function() {
            this.menu.element.outerWidth(self.$input.outerWidth());
        };
    };

    TokenPicker.prototype.search = function(request, response) {
        var self = this;
        var selectedCount = this.selectedValues().length;

        if (this.request && this.request.readyState !== 4) {
            this.request.abort();
        }

        if (selectedCount >= this.settings.limit) {
            this.setStatus(this.settings.maxSelectedLabel, true);
            response([
                {
                    label: this.settings.maxSelectedLabel,
                    disabled: true,
                    isMessage: true
                }
            ]);
            return;
        }

        this.setStatus(this.settings.searchingLabel, false);

        this.currentTerm = request.term || "";
        this.currentPage = 1;
        this.hasMore = false;

        this.request = $.ajax({
            url: this.$form.data("ajaxUrl"),
            dataType: "json",
            cache: true,
            data: {
                action: "CRCPW_assets_data",
                nonce: this.$form.data("nonce"),
                q: request.term || "",
                type: this.settings.type,
                page: 1
            }
        }).done(function(data) {
            var items = self.normalizeItems(data);

            self.hasMore = !!(data && data.pagination && data.pagination.more);

            if (!items.length) {
                items = [
                    {
                        label: self.settings.noResultsLabel,
                        disabled: true,
                        isMessage: true
                    }
                ];
            }

            self.setStatus("", false);
            response(items);
        }).fail(function(xhr, statusText) {
            if (statusText === "abort") {
                return;
            }

            self.setStatus(self.settings.noResultsLabel, true);
            response([
                {
                    label: self.settings.noResultsLabel,
                    disabled: true,
                    isMessage: true
                }
            ]);
        });
    };

    TokenPicker.prototype.normalizeItems = function(data) {
        var self = this;
        var items = data && data.results ? data.results.slice(0) : [];
        var selected = this.selectedLookup();

        items.sort(function(first, second) {
            if (first.id === second.id) {
                return 0;
            }
            return first.id > second.id ? 1 : -1;
        });

        return $.map(items, function(item) {
            return {
                label: item.id,
                value: item.id,
                symbol: item.id,
                name: item.name || "",
                icon: self.resolveIcon(item.text, item.fiat === 1, item.icon_url || ""),
                path: item.text,
                iconUrl: item.icon_url || "",
                fiat: item.fiat === 1,
                selected: !!selected[item.id],
                disabled: !!selected[item.id]
            };
        });
    };

    TokenPicker.prototype.bindMenuScroll = function() {
        var self = this;
        var $menu = this.$input.autocomplete("widget");

        $menu.off("scroll.crcpwTokenPicker").on("scroll.crcpwTokenPicker", function() {
            if (!self.hasMore || self.loadingMore) {
                return;
            }

            if (this.scrollTop + this.clientHeight >= this.scrollHeight - 24) {
                self.loadMore();
            }
        });
    };

    TokenPicker.prototype.loadMore = function() {
        var self = this;
        var nextPage = this.currentPage + 1;

        if (!this.hasMore || this.loadingMore) {
            return;
        }

        this.loadingMore = true;
        this.setStatus(this.settings.searchingLabel, false);

        $.ajax({
            url: this.$form.data("ajaxUrl"),
            dataType: "json",
            cache: true,
            data: {
                action: "CRCPW_assets_data",
                nonce: this.$form.data("nonce"),
                q: this.currentTerm,
                type: this.settings.type,
                page: nextPage
            }
        }).done(function(data) {
            var items = self.normalizeItems(data);
            var instance = self.$input.autocomplete("instance");
            var $menu = self.$input.autocomplete("widget");

            self.currentPage = nextPage;
            self.hasMore = !!(data && data.pagination && data.pagination.more);

            if (instance && instance._renderItemData && $menu.length) {
                $.each(items, function(_, item) {
                    instance._renderItemData($menu, item);
                });
                instance.menu.refresh();
            }

            self.setStatus("", false);
        }).fail(function(xhr, statusText) {
            if (statusText !== "abort") {
                self.setStatus(self.settings.noResultsLabel, true);
            }
        }).always(function() {
            self.loadingMore = false;
        });
    };

    TokenPicker.prototype.render = function() {
        var self = this;
        var selected = this.selectedOptions();

        this.$tokens.empty();

        $.each(selected, function(_, option) {
            self.$tokens.append(self.renderToken(option));
        });

        this.$count.text(selected.length + "/" + this.settings.limit);
        this.$clear.toggle(selected.length > 0);

        if (selected.length >= this.settings.limit) {
            this.setStatus(this.settings.maxSelectedLabel, true);
        } else if (this.$status.text() === this.settings.maxSelectedLabel) {
            this.setStatus("", false);
        }
    };

    TokenPicker.prototype.renderToken = function(option) {
        var $option = $(option);
        var value = $option.val();
        var name = $option.attr("data-name") || "";
        var $token = $('<li class="crcpw-token-picker__token"></li>');
        var icon = this.resolveIcon($option.attr("data-icon"), $option.attr("data-fiat") === "1", $option.attr("data-icon-url") || "");

        if (icon) {
            $token.append($("<img>", {
                "class": "crcpw-token-picker__token-icon",
                alt: "",
                loading: "lazy",
                src: icon
            }));
        }

        $token.append($("<span>", {
            "class": "crcpw-token-picker__token-label",
            text: value,
            title: name ? value + " - " + name : value
        }));

        $token.append($("<button>", {
            type: "button",
            "class": "crcpw-token-picker__remove",
            "data-value": value,
            "aria-label": "Remove " + value,
            html: "&times;"
        }));

        return $token;
    };

    TokenPicker.prototype.add = function(item) {
        var $option = this.option(item.value);

        if (!$option.length) {
            $option = $("<option>").val(item.value).text(item.symbol).appendTo(this.$select);
        }

        $option.attr("data-icon", item.path || "");
        $option.attr("data-icon-url", item.iconUrl || "");
        $option.attr("data-name", item.name || "");
        $option.attr("data-fiat", item.fiat ? "1" : "0");
        $option.prop("selected", true);
        $option.appendTo(this.$select);

        this.$select.trigger("change");
        this.$input.trigger("focus");
    };

    TokenPicker.prototype.remove = function(value) {
        var $option = this.option(value);

        if (!$option.length) {
            return;
        }

        $option.prop("selected", false);
        this.$select.trigger("change");
        this.$input.trigger("focus");
    };

    TokenPicker.prototype.removeLast = function() {
        var selected = this.selectedOptions();

        if (!selected.length) {
            return;
        }

        this.remove($(selected[selected.length - 1]).val());
    };

    TokenPicker.prototype.clear = function() {
        this.$select.find("option:selected").prop("selected", false);
        this.$select.trigger("change");
        this.$input.val("").trigger("focus");
    };

    TokenPicker.prototype.option = function(value) {
        return this.$select.find("option").filter(function() {
            return this.value === value;
        }).first();
    };

    TokenPicker.prototype.resolveIcon = function(path, isFiat, iconUrl) {
        var fileName;

        if (iconUrl) {
            return iconUrl;
        }

        if (!path) {
            return "";
        }

        if (isFiat) {
            fileName = path.split("/").pop() || "";
            fileName = fileName.replace(/\.[^.]+$/, ".webp");

            return this.settings.fiatIconBase + fileName;
        }

        if (/^https?:\/\//i.test(path)) {
            return path;
        }

        return this.settings.cryptoIconBase + path + (path.indexOf("?") === -1 ? "?width=128" : "&width=128");
    };

    TokenPicker.prototype.selectedOptions = function() {
        return this.$select.find("option:selected").toArray();
    };

    TokenPicker.prototype.selectedValues = function() {
        return $.map(this.selectedOptions(), function(option) {
            return option.value;
        });
    };

    TokenPicker.prototype.selectedLookup = function() {
        var lookup = {};

        $.each(this.selectedValues(), function(_, value) {
            lookup[value] = true;
        });

        return lookup;
    };

    TokenPicker.prototype.hasActiveAutocompleteItem = function() {
        var instance = this.$input.autocomplete("instance");

        return !!(instance && instance.menu && instance.menu.active && instance.menu.active.length);
    };

    TokenPicker.prototype.tryCreate = function(event) {
        var value;

        if (this.$input.autocomplete("widget").is(":visible") && this.hasActiveAutocompleteItem()) {
            return false;
        }

        if (!this.settings.allowCustom) {
            return false;
        }

        value = normalizeSymbol($.trim(this.$input.val()).replace(/,+$/g, ""));

        if (!value) {
            return false;
        }

        return this.add({
            value: value,
            symbol: value,
            name: "",
            path: "",
            iconUrl: "",
            fiat: this.settings.type === "flags"
        });
    };

    TokenPicker.prototype.setStatus = function(message, isWarning) {
        this.$status.text(message || "");
        this.$status.toggleClass("is-warning", !!isWarning && !!message);
    };

    window.CRCPWTokenPicker = {
        init: function($select, options) {
            $select = $($select);

            if (
                !$select.length ||
                !$select.closest(".crcpw-admin-page form#crcpw-widget-settings").length ||
                $select.data("crcpwTokenPicker")
            ) {
                return;
            }

            $select.data("crcpwTokenPicker", new TokenPicker($select, options));
        }
    };
})(jQuery);
