"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isSpecVisible = exports.SHAPE_TO_ICON = exports.SEARCH_ITEM_HEIGHT = void 0;
const fuzzy = require("fuzzy");
const classnames_1 = require("../../../../../client/classnames");
const element_1 = require("../../../../../client/element");
const custom_1 = require("../../../../../client/event/custom");
const focusin_1 = require("../../../../../client/event/focus/focusin");
const focusout_1 = require("../../../../../client/event/focus/focusout");
const input_1 = require("../../../../../client/event/input");
const keyboard_1 = require("../../../../../client/event/keyboard");
const click_1 = require("../../../../../client/event/pointer/click");
const pointerenter_1 = require("../../../../../client/event/pointer/pointerenter");
const scroll_1 = require("../../../../../client/event/scroll");
const parentElement_1 = require("../../../../../client/platform/web/parentElement");
const search_1 = require("../../../../../client/search");
const spec_1 = require("../../../../../client/spec");
const theme_1 = require("../../../../../client/theme");
const throttle_1 = require("../../../../../client/throttle");
const userSelect_1 = require("../../../../../client/util/style/userSelect");
const STRING_1 = require("../../../../../constant/STRING");
const string_1 = require("../../../../../util/string");
const f_1 = require("../../../../core/relation/Clamp/f");
const f_2 = require("../../../../f/object/Keys/f");
const Component_1 = require("../../../component/app/MicrophoneButton/Component");
const Component_2 = require("../../../core/component/TextBox/Component");
const Component_3 = require("../../Div/Component");
const Component_4 = require("../../Icon/Component");
const Component_5 = require("../IconButton/Component");
const Component_6 = require("../SearchInput/Component");
exports.SEARCH_ITEM_HEIGHT = 39;
const DEFAULT_STYLE = {
    position: 'relative',
    width: 'calc(100% + 4px)',
    maxWidth: '309px',
    height: 'initial',
    // overflowY: 'hidden',
    // overflowX: 'hidden',
    borderWidth: '1px 1px 0px 1px',
    borderStyle: 'solid',
    color: 'currentColor',
    borderColor: 'currentColor',
    padding: '0',
    backgroundColor: theme_1.COLOR_NONE,
    borderTopLeftRadius: '3px',
    borderTopRightRadius: '3px',
};
exports.SHAPE_TO_ICON = {
    rect: 'square',
    circle: 'circle',
};
function isSpecVisible(specs, id) {
    const spec = (0, spec_1.getSpec)(specs, id);
    const { private: _private } = spec;
    if (_private) {
        return false;
    }
    else {
        return true;
    }
}
exports.isSpecVisible = isSpecVisible;
class Search extends element_1.Element {
    constructor($props, $system) {
        super($props, $system);
        this._input_value = '';
        this._shape = 'circle';
        this._id_list = [];
        this._ordered_id_list = [];
        this._filtered_id_list = [];
        this._item = {};
        this._filtered_list_item = [];
        this._list_item_div = {};
        this._list_item_content = {};
        this._list_item_name = {};
        this._list_hidden = true;
        this._selected_id = null;
        this._selected_index = 0;
        this._scrollTop = 0;
        this._refresh_ordered_list = () => {
            const { specs } = this.$system;
            const id_list = (0, f_2.keys)(specs);
            this._id_list = id_list;
            const visible_id_list = id_list.filter((id) => isSpecVisible(specs, id));
            const ordered_id_list = visible_id_list.sort((a, b) => {
                return (0, search_1.compareByComplexity)(specs, a, b);
            });
            this._ordered_id_list = ordered_id_list;
            this._filtered_id_list = ordered_id_list;
        };
        this._set_list_item_color = (id, color) => {
            // console.log('Search', '_set_list_item_color', id, color)
            const selected_list_item = this._list_item_content[id];
            selected_list_item.$element.style.color = color;
        };
        this._add_list_item = (id, i, total) => {
            // console.trace('Search', '_add_list_item', id, i, total)
            const { specs } = this.$system;
            const spec = (0, spec_1.getSpec)(specs, id);
            const { name = '', metadata = {} } = spec;
            const icon = metadata.icon || 'question';
            const tags = metadata.tags || [];
            const tagsStr = tags.join(' ');
            // const fuzzyName = `${name} ${tagsStr}`
            const fuzzyName = name;
            const finalName = name || STRING_1.UNTITLED;
            this._item[id] = { id, name, icon, tags, fuzzyName };
            const list_item_icon = new Component_4.default({
                style: { width: '18px', height: '18px', margin: '9px' },
                icon,
            }, this.$system);
            const list_item_main_name = new Component_2.default({
                style: Object.assign({ width: 'fit-content', height: '18px', marginBottom: '2px' }, (0, userSelect_1.userSelect)('none')),
                value: finalName,
            }, this.$system);
            this._list_item_name[id] = list_item_main_name;
            const list_item_main_tags = new Component_2.default({
                style: {
                    fontSize: '12px',
                    width: 'fit-content',
                    height: '12px',
                    marginBottom: '1px',
                },
                value: tagsStr,
            }, this.$system);
            const list_item_main = new Component_3.default({
                style: {
                    display: 'flex',
                    flexDirection: 'column',
                    justifyContent: 'center',
                    marginBottom: '2px',
                },
            }, this.$system);
            list_item_main.appendChild(list_item_main_name);
            list_item_main.appendChild(list_item_main_tags);
            const list_item_content = new Component_3.default({
                className: 'search-list-item-content',
                style: {
                    display: 'flex',
                    width: '100%',
                    alignItems: 'center',
                    height: `${exports.SEARCH_ITEM_HEIGHT - 1}px`,
                    backgroundColor: theme_1.COLOR_NONE,
                },
            }, this.$system);
            list_item_content.appendChild(list_item_icon);
            list_item_content.appendChild(list_item_main);
            this._list_item_content[id] = list_item_content;
            const list_item_div_border_bottom = i < total - 1 ? `1px solid currentColor` : '';
            const list_item_div = new Component_3.default({
                className: 'search-list-item',
                style: {
                    borderBottom: list_item_div_border_bottom,
                    boxSizing: 'border-box',
                    width: `${309 - 1}px`,
                },
                data: {
                    id,
                },
            }, this.$system);
            list_item_div.preventDefault('mousedown');
            list_item_div.preventDefault('touchdown');
            list_item_div.appendChild(list_item_content);
            list_item_div.addEventListener((0, pointerenter_1.makePointerEnterListener)((event) => {
                this._on_list_item_pointer_enter(event, id);
            }));
            list_item_div.addEventListener((0, click_1.makeClickListener)({
                onClick: (event) => {
                    this._on_list_item_click(event, id);
                },
            }));
            this._list_item_div[id] = list_item_div;
            this._list.appendChild(list_item_div);
        };
        this._refresh_list_item = (id) => {
            const list_item_div = this._list_item_div[id];
            const list_item_name = this._list_item_name[id];
            const spec = this.$system.getSpec(id);
            const { name = '', metadata = {} } = spec;
            const finalName = name || STRING_1.UNTITLED;
            const icon = metadata.icon || 'question';
            const tags = metadata.tags || [];
            const tagsStr = tags.join(' ');
            // const fuzzyName = `${name} ${tagsStr}`
            const fuzzyName = name;
            list_item_div.$element.style.borderBottom = `1px solid currentColor`;
            this._item[id] = { id, name, icon, tags, fuzzyName };
            list_item_name.setProp('value', finalName);
        };
        this._on_microphone_transcript = (0, throttle_1.throttle)(this.$system, (transcript) => {
            // console.log('Search', '_on_microphone_transcript', transcript)
            const value = transcript.toLowerCase().substr(0, 30);
            this._input.setProp('value', value);
            this._input_value = value;
            this._filter_list();
            this._input.focus();
        }, 100);
        this._on_input_keydown = ({ keyCode, repeat }, _event) => {
            // console.log('Search', '_on_input_keydown', keyCode, repeat)
            // prevent arrow up/down default
            if (keyCode === 38 || keyCode === 40 || keyCode === 186) {
                _event.preventDefault();
            }
            if (repeat) {
                if (keyCode === 8) {
                    //
                }
                else {
                    _event.preventDefault();
                }
            }
        };
        this._on_input_focus_in = () => {
            // console.log('Search', '_on_input_focus_in')
            // setTimeout(() => {
            this._select_all();
            // }, 0)
            this._show_list();
        };
        this._select_all = () => {
            this._input.setSelectionRange(0, this._input_value.length);
        };
        this._on_input_click = () => {
            // this._input.setSelectionRange(0, this._input_value.length)
        };
        this._on_input_focus_out = () => {
            // console.log('Search', '_on_input_focus_out')
            this._hide_list();
        };
        this._show_list = () => {
            const { style = {} } = this.$props;
            const { color = 'currentColor' } = style;
            // console.log('Search', '_show_list')
            this._list_hidden = false;
            const filtered_total = this._filtered_id_list.length;
            const empty = filtered_total === 0;
            this._list.$element.style.display = 'block';
            this._input._input.$element.style.borderRadius = empty
                ? '3px 3px 0 0'
                : '0 0 0 0';
            this._input._input.$element.style.borderTopWidth = empty ? '0px' : '1px';
            if (!empty) {
                const last_list_item_id = this._filtered_id_list[filtered_total - 1];
                const last_list_item_div = this._list_item_div[last_list_item_id];
                last_list_item_div.$element.style.borderBottom = color;
                if (this._selected_id) {
                    this._scroll_into_item(this._selected_id);
                }
            }
            this._dispatch_list_shown();
            if (this._selected_id) {
                this._dispatch_item_selected(this._selected_id);
            }
        };
        this._dispatch_item_selected = (id) => {
            this.dispatchEvent('selected', { id });
        };
        this._dispatch_shape = () => {
            this.dispatchEvent('shape', { shape: this._shape });
        };
        this._dispatch_item_pick = (id) => {
            this.dispatchEvent('pick', { id });
        };
        this._dispatch_list_shown = () => {
            this.dispatchEvent('shown', {});
        };
        this._dispatch_list_hidden = () => {
            this.dispatchEvent('hidden', {});
        };
        this._dispatch_list_empty = () => {
            this.dispatchEvent('empty', {});
        };
        this._select_first_list_item = () => {
            const first_list_item_id = this._filtered_id_list[0];
            this.set_selected_item_id(first_list_item_id);
        };
        this._hide_list = () => {
            this._list_hidden = true;
            this._input._input.$element.style.borderTopWidth = '0';
            this._list.$element.style.display = 'none';
            this._dispatch_list_hidden();
        };
        this._on_list_item_pointer_enter = ({}, id) => {
            // console.log('Search', '_on_list_item_pointer_enter')
            this.set_selected_item_id(id);
        };
        this._on_list_item_click = ({}, id) => {
            // console.log('Search', '_on_list_item_click')
            this._dispatch_item_pick(id);
        };
        this.set_selected_item_id = (id) => {
            const prev_selected_id = this._selected_id;
            this._set_selected_item_id(id);
            if (this._selected_id !== prev_selected_id) {
                this._dispatch_item_selected(id);
            }
        };
        this._set_selected_item_id = (id) => {
            // console.log('Search', '_set_selected_item_id', id)
            const { style = {}, selectedColor = 'currentColor' } = this.$props;
            const { color = 'currentColor' } = style;
            if (this._selected_id) {
                this._set_list_item_color(this._selected_id, color);
            }
            this._selected_id = id;
            this._set_list_item_color(id, selectedColor);
            this._scroll_into_item(id);
        };
        this.select_next = (offset) => {
            // console.log('Search', 'select_next', offset)
            let index = 0;
            if (this._selected_id) {
                index = this._ordered_id_list.indexOf(this._selected_id);
            }
            const next_index = (0, f_1.clamp)(index + offset, 0, this._ordered_id_list.length - 1);
            const next_selected_spec_id = this._ordered_id_list[next_index];
            this.set_selected_item_id(next_selected_spec_id);
        };
        this._scroll_into_item = (id) => {
            // console.log('Search', '_scroll_into_item', id)
            const scroll_index = this._list.$element.scrollTop / exports.SEARCH_ITEM_HEIGHT;
            const selected_id_index = this._filtered_id_list.indexOf(id);
            if (selected_id_index - scroll_index >= 4 ||
                selected_id_index - scroll_index < 0) {
                const list_item = this._list_item_div[id];
                list_item.$element.scrollIntoView({ behavior: 'auto', block: 'start' });
                // this._list.$element.scrollTop = selected_id_index * SEARCH_ITEM_HEIGHT
                // this._list.$element.scrollTo({
                //   top: selected_id_index * SEARCH_ITEM_HEIGHT,
                //   behavior: 'auto',
                // })
            }
        };
        this._filter_list = (preserve_selected = false) => {
            // console.log('Search', '_filter_list')
            const { specs } = this.$system;
            const { style = {}, filter = () => true } = this.$props;
            const { color = 'currentColor' } = style;
            if (this._filtered_id_list.length > 0) {
                const last_list_item_id = this._filtered_id_list[this._filtered_id_list.length - 1];
                const last_list_item_div = this._list_item_div[last_list_item_id];
                last_list_item_div.$element.style.borderBottom = '1px solid currentColor';
            }
            let filtered_id_list = [];
            const filtered_score = {};
            const fuzzy_pattern = this._input_value;
            for (const id of this._ordered_id_list) {
                const { fuzzyName } = this._item[id];
                const list_item_div = this._list_item_div[id];
                const clean_fuzzy_pattern = (0, string_1.removeWhiteSpace)(fuzzy_pattern.replace('-', ' '));
                const fuzzy_match = fuzzy.match(clean_fuzzy_pattern, fuzzyName, {
                    caseSensitive: false,
                });
                if ((fuzzy_pattern === '' || fuzzy_match) &&
                    filter(id) &&
                    (this._shape === 'circle' || (0, spec_1.isComponent)(specs, id))) {
                    filtered_score[id] = 0;
                    if (fuzzy_match) {
                        const { score } = fuzzy_match;
                        filtered_score[id] = score;
                    }
                    list_item_div.$element.style.display = 'flex';
                    filtered_id_list.push(id);
                }
                else {
                    list_item_div.$element.style.display = 'none';
                }
            }
            filtered_id_list = filtered_id_list.sort((a, b) => {
                const a_score = filtered_score[a];
                const b_score = filtered_score[b];
                return b_score - a_score;
            });
            this._filtered_id_list = filtered_id_list;
            const filtered_total = filtered_id_list.length;
            if (filtered_total > 0) {
                for (let i = 0; i < filtered_total; i++) {
                    const id = this._filtered_id_list[i];
                    const list_item_div = this._list_item_div[id];
                    this._list.removeChild(list_item_div);
                    this._list.appendChild(list_item_div);
                }
                if (preserve_selected) {
                    if (this._selected_id) {
                        if (this._filtered_id_list.includes(this._selected_id)) {
                            this._scroll_into_item(this._selected_id);
                        }
                        else {
                            this._select_first_list_item();
                        }
                    }
                    else {
                        this._select_first_list_item();
                    }
                }
                else {
                    this._select_first_list_item();
                }
                if (!this._list_hidden) {
                    const last_list_item_id = filtered_id_list[filtered_total - 1];
                    const last_list_item_div = this._list_item_div[last_list_item_id];
                    last_list_item_div.$element.style.borderBottom = color;
                    this._input._input.$element.style.borderRadius = '0';
                    this._input._input.$element.style.borderTopWidth = '1px';
                }
            }
            else {
                this._input._input.$element.style.borderRadius = '3px 3px 0px 0px';
                this._input._input.$element.style.borderTopWidth = '0';
                if (this._selected_id) {
                    this._set_list_item_color(this._selected_id, color);
                }
                this._selected_id = null;
                this._dispatch_list_empty();
            }
        };
        this._on_input_input = (value) => {
            if (value === ';') {
                this._setValue(this._input_value);
                return;
            }
            this._input_value = value;
            this._filter_list();
        };
        this._on_arrow_down_keydown = () => {
            if (!this._list_hidden && this._selected_id) {
                const selected_id_index = this._filtered_id_list.indexOf(this._selected_id);
                if (selected_id_index < this._filtered_id_list.length - 1) {
                    const next_selected_id_index = selected_id_index + 1;
                    const next_selected_id = this._filtered_id_list[next_selected_id_index];
                    this.set_selected_item_id(next_selected_id);
                }
            }
        };
        this._on_arrow_up_keydown = () => {
            if (!this._list_hidden && this._selected_id) {
                const selected_id_index = this._filtered_id_list.indexOf(this._selected_id);
                if (selected_id_index > 0) {
                    const next_selected_id_index = selected_id_index - 1;
                    const next_selected_id = this._filtered_id_list[next_selected_id_index];
                    this.set_selected_item_id(next_selected_id);
                }
            }
        };
        this._on_ctrl_p_keydown = () => {
            // console.log('Search', 'on_ctrl_p_keydown')
            setTimeout(() => {
                this._hide_list();
            }, 0);
        };
        this._on_escape_keydown = () => {
            // console.log('Search', '_on_escape_keydown')
            setTimeout(() => {
                this._hide_list();
            }, 0);
        };
        this._on_enter_keydown = () => {
            if (!this._list_hidden && this._selected_id) {
                setTimeout(() => {
                    if (this._selected_id) {
                        // AD HOC
                        // on Safari apparently selecting the input might inadvertedly refocus it,
                        // which is certainly unexpected, so this call must absolutely
                        // come before dispatching a (possibly side-effecting) event
                        this._select_all();
                        this._dispatch_item_pick(this._selected_id);
                    }
                }, 0);
            }
        };
        this.getValue = () => {
            return this._input_value;
        };
        this.getShape = () => {
            return this._shape;
        };
        this.setValue = (value) => {
            // console.log('Search', 'setValue', value)
            this._setValue(value);
            this._filter_list();
        };
        this._setValue = (value) => {
            this._input_value = value;
            this._input.setProp('value', value);
        };
        this.toggleShape = () => {
            const shape = this._shape === 'circle' ? 'rect' : 'circle';
            this.setShape(shape);
        };
        this.setShape = (shape) => {
            if (this._shape !== shape) {
                this._shape = shape;
                this._shape_button.setProp('icon', exports.SHAPE_TO_ICON[shape]);
                this._dispatch_shape();
                this._filter_list();
            }
        };
        const { specs } = this.$system;
        const { className, style = {}, selected } = this.$props;
        const id_list = (0, f_2.keys)(specs);
        this._refresh_ordered_list();
        const list = new Component_3.default({
            className: 'search-list',
            style: {
                position: 'relative',
                maxHeight: `${4 * exports.SEARCH_ITEM_HEIGHT - 2}px`,
                overflowY: 'auto',
                overflowX: 'hidden',
                display: this._list_hidden ? 'none' : 'block',
                width: 'calc(100% + 3px)',
                boxSizing: 'content-box',
            },
        }, this.$system);
        list.addEventListener((0, scroll_1.makeScrollListener)((event) => {
            this._scrollTop = this._list.$element.scrollTop;
        }));
        this._list = list;
        const total = id_list.length;
        let i = 0;
        for (const id of this._ordered_id_list) {
            this._add_list_item(id, i, total);
            i++;
        }
        const input = new Component_6.default({}, this.$system);
        input.addEventListener((0, keyboard_1.makeKeydownListener)(this._on_input_keydown));
        input.addEventListener((0, focusin_1.makeFocusInListener)(this._on_input_focus_in));
        input.addEventListener((0, focusout_1.makeFocusOutListener)(this._on_input_focus_out));
        input.addEventListener((0, input_1.makeInputListener)(this._on_input_input));
        // input.addEventListener(
        //   makeClickListener({
        //     onClick: this._on_input_click,
        //   })
        // )
        input.addEventListener((0, keyboard_1.makeShortcutListener)([
            {
                combo: 'ArrowDown',
                keydown: this._on_arrow_down_keydown,
                multiple: true,
            },
            {
                combo: 'ArrowUp',
                keydown: this._on_arrow_up_keydown,
                multiple: true,
            },
            { combo: 'Escape', keydown: this._on_escape_keydown },
            {
                // combo: 'Ctrl + p',
                combo: ['Ctrl + ;', ';'],
                // combo: 'Ctrl + /',
                keydown: this._on_ctrl_p_keydown,
                strict: false,
            },
            {
                // combo: 'p',
                combo: ';',
                // combo: '/',
                keydown: (key, { ctrlKey }) => {
                    if (ctrlKey) {
                        this._on_ctrl_p_keydown();
                    }
                },
            },
            {
                combo: ['Enter', 'Shift + Enter'],
                keydown: this._on_enter_keydown,
            },
        ]));
        this._input = input;
        const microphone = new Component_1.default({
            style: {
                position: 'absolute',
                right: '0',
                bottom: '0',
                width: '18px',
                height: '18px',
                padding: '11px 9px 10px 11px',
            },
        }, this.$system);
        microphone.addEventListener((0, custom_1.makeCustomListener)('transcript', this._on_microphone_transcript));
        microphone.preventDefault('mousedown');
        microphone.preventDefault('touchdown');
        this._microphone = microphone;
        const shape_button = new Component_5.default({
            icon: 'circle',
            style: {
                position: 'absolute',
                left: '0',
                bottom: '0px',
                width: '18px',
                height: '18px',
                padding: '11px 11px 10px 9px',
            },
            title: 'layout',
        }, this.$system);
        shape_button.addEventListener((0, click_1.makeClickListener)({
            onClick: () => {
                this.toggleShape();
            },
        }));
        shape_button.preventDefault('mousedown');
        shape_button.preventDefault('touchdown');
        this._shape_button = shape_button;
        const search = new Component_3.default({
            className: (0, classnames_1.default)('search', className),
            style: Object.assign(Object.assign({}, DEFAULT_STYLE), style),
            title: 'search',
        }, this.$system);
        search.registerParentRoot(list);
        search.registerParentRoot(input);
        search.registerParentRoot(shape_button);
        search.registerParentRoot(microphone);
        this._search = search;
        const $element = (0, parentElement_1.default)($system);
        this.$element = $element;
        this.$slot = {
            default: search,
        };
        this.$unbundled = false;
        if (selected) {
            if (this._filtered_id_list.includes(selected)) {
                this._set_selected_item_id(selected);
            }
        }
        else {
            if (this._ordered_id_list.length > 0) {
                this._select_first_list_item();
            }
        }
        this.registerRoot(search);
        this.$system.specs_.subscribe([], '*', (type, path, key, data) => {
            if (path.length === 0) {
                if (type === 'set') {
                    // console.log(key, data)
                    const specId = key;
                    const spec = data;
                    if (isSpecVisible(specs, spec.id)) {
                        if (this._ordered_id_list.includes(specId)) {
                            this._refresh_list_item(specId);
                        }
                        else {
                            this._add_list_item(specId, Math.floor(this._ordered_id_list.length / 2), // RETURN
                            this._ordered_id_list.length);
                        }
                        const selected_item_id = this._selected_id;
                        this._refresh_ordered_list();
                        this._filter_list(true);
                        if (!this._list_hidden) {
                            this._set_selected_item_id(selected_item_id);
                        }
                    }
                }
            }
        });
    }
    onPropChanged(prop, current) {
        if (prop === 'style') {
            const style = Object.assign(Object.assign({}, DEFAULT_STYLE), current);
            this._search.setProp('style', style);
        }
        else if (prop === 'selectedColor') {
            if (this._selected_id) {
                const { selectedColor = 'currentColor' } = this.$props;
                this._set_list_item_color(this._selected_id, selectedColor);
            }
        }
        else if (prop === 'selected') {
            const { selected } = this.$props;
            if (selected) {
                this._set_selected_item_id(selected);
            }
        }
        else if (prop === 'filter') {
            this._filter_list();
        }
    }
    focus(options = { preventScroll: true }) {
        this._input._input.focus(options);
    }
    blur() {
        this._input._input.blur();
    }
    getList() {
        return this._filtered_id_list;
    }
    onMount() {
        // console.log('Search', 'onMount')
    }
    onConnected() {
        // console.log('Search', 'onConnected')
    }
}
exports.default = Search;
//# sourceMappingURL=Component.js.map