RED.typeSearch = (function() {

    var shade;

    var disabled = false;
    var dialog = null;
    var searchInput;
    var searchResults;
    var searchResultsDiv;
    var selected = -1;
    var visible = false;

    var activeFilter = "";
    var addCallback;
    var cancelCallback;
    var moveCallback;

    var typesUsed = {};

    function search(val) {
        activeFilter = val.toLowerCase();
        var visible = searchResults.editableList('filter');
        searchResults.editableList('sort');
        setTimeout(function() {
            selected = 0;
            searchResults.children().removeClass('selected');
            searchResults.children(":visible:first").addClass('selected');
        },100);

    }

    function ensureSelectedIsVisible() {
        var selectedEntry = searchResults.find("li.selected");
        if (selectedEntry.length === 1) {
            var scrollWindow = searchResults.parent();
            var scrollHeight = scrollWindow.height();
            var scrollOffset = scrollWindow.scrollTop();
            var y = selectedEntry.position().top;
            var h = selectedEntry.height();
            if (y+h > scrollHeight) {
                scrollWindow.animate({scrollTop: '-='+(scrollHeight-(y+h)-10)},50);
            } else if (y<0) {
                scrollWindow.animate({scrollTop: '+='+(y-10)},50);
            }
        }
    }

    function moveDialog(dx,dy) {
        var pos = dialog.position();
        pos.top = (pos.top + dy)+"px";
        pos.left = (pos.left + dx)+"px";
        dialog.css(pos);
        moveCallback(dx,dy);

    }
    function createDialog() {
        dialog = $("<div>",{id:"red-ui-type-search",class:"red-ui-search red-ui-type-search"}).appendTo("#red-ui-main-container");
        var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
        searchInput = $('<input type="text" id="red-ui-type-search-input">').attr("placeholder",RED._("search.addNode")).appendTo(searchDiv).searchBox({
            delay: 50,
            change: function() {
                search($(this).val());
            }
        });
        searchInput.on('keydown',function(evt) {
            var children = searchResults.children(":visible");
            if (evt.keyCode === 40 && evt.shiftKey) {
                evt.preventDefault();
                moveDialog(0,10);
            } else if (evt.keyCode === 38 && evt.shiftKey) {
                evt.preventDefault();
                moveDialog(0,-10);
            } else if (evt.keyCode === 39 && evt.shiftKey) {
                evt.preventDefault();
                moveDialog(10,0);
            } else if (evt.keyCode === 37 && evt.shiftKey) {
                evt.preventDefault();
                moveDialog(-10,0);
            } else if (children.length > 0) {
                if (evt.keyCode === 40) {
                    // Down
                    if (selected < children.length-1) {
                        if (selected > -1) {
                            $(children[selected]).removeClass('selected');
                        }
                        selected++;
                    }
                    $(children[selected]).addClass('selected');
                    ensureSelectedIsVisible();
                    evt.preventDefault();
                } else if (evt.keyCode === 38) {
                    if (selected > 0) {
                        if (selected < children.length) {
                            $(children[selected]).removeClass('selected');
                        }
                        selected--;
                    }
                    $(children[selected]).addClass('selected');
                    ensureSelectedIsVisible();
                    evt.preventDefault();
                } else if ((evt.metaKey || evt.ctrlKey) && evt.keyCode === 13 ) {
                    evt.preventDefault();
                    // (ctrl or cmd) and enter
                    var index = Math.max(0,selected);
                    if (index < children.length) {
                        var n = $(children[index]).find(".red-ui-editableList-item-content").data('data');
                        if (!/^_action_:/.test(n.type)) {
                            typesUsed[n.type] = Date.now();
                        }
                        if (n.def.outputs === 0) {
                            confirm(n);
                        } else {
                            addCallback(n.type,true);
                        }
                        $("#red-ui-type-search-input").val("").trigger("keyup");
                        setTimeout(function() {
                            $("#red-ui-type-search-input").focus();
                        },100);
                    }
                } else if (evt.keyCode === 13) {
                    evt.preventDefault();
                    // Enter
                    var index = Math.max(0,selected);
                    if (index < children.length) {
                        // TODO: dips into editableList impl details
                        confirm($(children[index]).find(".red-ui-editableList-item-content").data('data'));
                    }
                }
            } else {
                if (evt.keyCode === 13 ) {
                    // Stop losing focus if [Cmd]-Enter is pressed on an empty list
                    evt.stopPropagation();
                    evt.preventDefault();
                }
            }
        });

        searchResultsDiv = $("<div>",{class:"red-ui-search-results-container"}).appendTo(dialog);
        searchResults = $('<ol>',{style:"position: absolute;top: 0;bottom: 0;left: 0;right: 0;"}).appendTo(searchResultsDiv).editableList({
            addButton: false,
            filter: function(data) {
                if (activeFilter === "" ) {
                    return true;
                }
                if (data.recent || data.common) {
                    return false;
                }
                return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1);
            },
            sort: function(A,B) {
                if (activeFilter === "") {
                    return A.i - B.i;
                }
                var Ai = A.index.indexOf(activeFilter);
                var Bi = B.index.indexOf(activeFilter);
                if (Ai === -1) {
                    return 1;
                }
                if (Bi === -1) {
                    return -1;
                }
                if (Ai === Bi) {
                    return sortTypeLabels(A,B);
                }
                return Ai-Bi;
            },
            addItem: function(container,i,object) {
                var def = object.def;
                object.index = object.type.toLowerCase();
                if (object.separator) {
                    container.addClass("red-ui-search-result-separator")
                }
                var div = $('<div>',{class:"red-ui-search-result"}).appendTo(container);

                var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
                if (object.type === "junction") {
                    nodeDiv.addClass("red-ui-palette-icon-junction");
                } else if (/^_action_:/.test(object.type)) {
                    nodeDiv.addClass("red-ui-palette-icon-junction")
                } else {
                    var colour = RED.utils.getNodeColor(object.type,def);
                    nodeDiv.css('backgroundColor',colour);
                }
                var icon_url = RED.utils.getNodeIcon(def);

                var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
                RED.utils.createIconElement(icon_url, iconContainer, false);

                if (/^subflow:/.test(object.type)) {
                    var sf = RED.nodes.subflow(object.type.substring(8));
                    if (sf.in.length > 0) {
                        $('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv);
                    }
                    if (sf.out.length > 0) {
                        $('<div/>',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv);
                    }
                } else if (!/^_action_:/.test(object.type) && object.type !== "junction") {
                    if (def.inputs > 0) {
                        $('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv);
                    }
                    if (def.outputs > 0) {
                        $('<div/>',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv);
                    }
                }

                var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);

                var label = object.label;
                object.index += "|"+label.toLowerCase();

                $('<div>',{class:"red-ui-search-result-node-label"}).text(label).appendTo(contentDiv);

                div.on("click", function(evt) {
                    evt.preventDefault();
                    confirm(object);
                });
            },
            scrollOnAdd: false
        });

    }
    function confirm(def) {
        hide();
        if (!/^_action_:/.test(def.type)) {
            typesUsed[def.type] = Date.now();
        }
        addCallback(def.type);
    }

    function handleMouseActivity(evt) {
        if (visible) {
            var t = $(evt.target);
            while (t.prop('nodeName').toLowerCase() !== 'body') {
                if (t.attr('id') === 'red-ui-type-search') {
                    return;
                }
                t = t.parent();
            }
            hide(true);
            if (cancelCallback) {
                cancelCallback();
            }
        }
    }
    function show(opts) {
        if (!visible) {
            if (dialog === null) {
                createDialog();
                RED.keyboard.add("red-ui-type-search","escape",function(){
                    hide();
                    if (cancelCallback) {
                        cancelCallback();
                    }
                });
            }
            visible = true;
        } else {
            dialog.hide();
            searchResultsDiv.hide();
        }
        $(document).off('mousedown.red-ui-type-search');
        $(document).off('mouseup.red-ui-type-search');
        $(document).off('click.red-ui-type-search');
        $(document).off('touchstart.red-ui-type-search');
        $(document).off('mousedown.red-ui-type-search');
        setTimeout(function() {
            $(document).on('mousedown.red-ui-type-search',handleMouseActivity);
            $(document).on('mouseup.red-ui-type-search',handleMouseActivity);
            $(document).on('click.red-ui-type-search',handleMouseActivity);
            $(document).on('touchstart.red-ui-type-search',handleMouseActivity);
        },200);

        refreshTypeList(opts);
        addCallback = opts.add;
        cancelCallback = opts.cancel;
        moveCallback = opts.move;
        RED.events.emit("type-search:open");
        //shade.show();
        if ($("#red-ui-main-container").height() - opts.y - 195 < 0) {
            opts.y = opts.y - 275;
        }
        const dialogWidth = dialog.width() || 300 // default is 300 (defined in class .red-ui-search)
        const workspaceWidth = $('#red-ui-workspace').width()
        if (workspaceWidth > dialogWidth && workspaceWidth - opts.x - dialogWidth < 0) {
            opts.x = opts.x - (dialogWidth - RED.view.node_width)
        }
        dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
        searchResultsDiv.slideDown(300);
        setTimeout(function() {
            searchResultsDiv.find(".red-ui-editableList-container").scrollTop(0);
            if (!opts.disableFocus) {
                searchInput.trigger("focus");
            }
        },200);
    }
    function hide(fast) {
        if (visible) {
            visible = false;
            if (dialog !== null) {
                searchResultsDiv.slideUp(fast?50:200,function() {
                    dialog.hide();
                    searchInput.searchBox('value','');
                });
                //shade.hide();
            }
            RED.events.emit("type-search:close");
            RED.view.focus();
            $(document).off('mousedown.red-ui-type-search');
            $(document).off('mouseup.red-ui-type-search');
            $(document).off('click.red-ui-type-search');
            $(document).off('touchstart.red-ui-type-search');
        }
    }
    function getTypeLabel(type, def) {
        var label = type;
        if (typeof def.paletteLabel !== "undefined") {
            try {
                label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
                label += " ("+type+")";
            } catch(err) {
                console.log("Definition error: "+type+".paletteLabel",err);
            }
        }
        return label;
    }
    function sortTypeLabels(a,b) {
        var al = a.label.toLowerCase();
        var bl = b.label.toLowerCase();
        if (al < bl) {
            return -1;
        } else if (al === bl) {
            return 0;
        } else {
            return 1;
        }
    }
    function applyFilter(filter,type,def) {
        if (!filter) {
            // No filter; allow everything
            return true
        }
        if (type === 'junction') {
            // Only allow Junction is there's no specific type filter
            return !filter.type
        }
        if (filter.type) {
            // Handle explicit type filter
            return filter.type === type
        }
        if (!def) {
            // No node definition available - allow it
            return true
        }
        // Check if the filter is for input/outputs and apply
        return (!filter.input || def.inputs > 0) &&
                (!filter.output || def.outputs > 0)
    }
    function refreshTypeList(opts) {
        var i;
        searchResults.editableList('empty');
        searchInput.searchBox('value','').focus();
        selected = -1;
        var common = [
            'inject','debug','function','change','switch','junction'
        ].filter(function(t) { return applyFilter(opts.filter,t,RED.nodes.getType(t)); });

        // if (opts.filter && opts.filter.input && opts.filter.output && !opts.filter.type) {
        //     if (opts.filter.spliceMultiple) {
        //         common.push('_action_:core:split-wires-with-junctions')
        //     }
        //     common.push('_action_:core:split-wire-with-link-nodes')
        // }

        var recentlyUsed = Object.keys(typesUsed);
        recentlyUsed.sort(function(a,b) {
            return typesUsed[b]-typesUsed[a];
        });
        recentlyUsed = recentlyUsed.filter(function(t) {
            return applyFilter(opts.filter,t,RED.nodes.getType(t)) && common.indexOf(t) === -1;
        });

        var items = [];
        RED.nodes.registry.getNodeTypes().forEach(function(t) {
            var def = RED.nodes.getType(t);
            if (def.set?.enabled !== false && def.category !== 'config' && t !== 'unknown' && t !== 'tab') {
                items.push({type:t,def: def, label:getTypeLabel(t,def)});
            }
        });
        items.push({ type: 'junction', def: { inputs:1, outputs: 1, label: 'junction', type: 'junction'}, label: 'junction' })
        items.sort(sortTypeLabels);

        var commonCount = 0;
        var item;
        var index = 0;
        for(i=0;i<common.length;i++) {
            var itemDef = RED.nodes.getType(common[i]);
            if (common[i] === 'junction') {
                itemDef = { inputs:1, outputs: 1, label: 'junction', type: 'junction'}
            } else if (/^_action_:/.test(common[i]) ) {
                itemDef = { inputs:1, outputs: 1, label: common[i], type: common[i]}
            }
            if (itemDef) {
                item = {
                    type: common[i],
                    common: true,
                    def: itemDef,
                    i: index++
                };
                item.label = getTypeLabel(item.type,item.def);
                if (i === common.length-1) {
                    item.separator = true;
                }
                searchResults.editableList('addItem', item);
            }
        }
        for(i=0;i<Math.min(5,recentlyUsed.length);i++) {
            item = {
                type:recentlyUsed[i],
                def: RED.nodes.getType(recentlyUsed[i]),
                recent: true,
                i: index++
            };
            item.label = getTypeLabel(item.type,item.def);
            if (i === recentlyUsed.length-1) {
                item.separator = true;
            }
            searchResults.editableList('addItem', item);
        }
        for (i=0;i<items.length;i++) {
            if (applyFilter(opts.filter,items[i].type,items[i].def)) {
                items[i].i = index++;
                searchResults.editableList('addItem', items[i]);
            }
        }
        setTimeout(function() {
            selected = 0;
            searchResults.children(":first").addClass('selected');
        },100);
    }

    return {
        show: show,
        refresh: refreshTypeList,
        hide: hide
    };

})();
