<template>
    <component v-if="!asChild" :is="as" :class="cx('root')" v-bind="ptmi('root')">
        <div :class="cx('header')" v-bind="ptm('header')">
            <slot name="header" :value="d_search" :inputProps="inputProps">
                <input ref="input" type="text" role="combobox" :value="d_search" :class="cx('input')" v-bind="inputAttrs" @input="onInputChange" />
            </slot>
        </div>
        <div v-if="filteredModel.length === 0" :class="cx('emptyMessage')" v-bind="ptm('emptyMessage')">
            <slot v-if="isFiltering" name="emptyfilter">{{ emptyFilterMessageText }}</slot>
            <slot v-else name="empty">{{ emptyMessageText }}</slot>
        </div>
        <Menu :id="menuId" ref="menu" :model="filteredModel" :popup="false" :aria-label="ariaLabel" :aria-labelledby="ariaLabelledby" :class="[cx('list'), menuClass]" :style="menuStyle" :pt="ptm('pcMenu')" :unstyled="unstyled">
            <template v-if="$slots.item" #item="slotProps">
                <slot name="item" v-bind="slotProps" />
            </template>
            <template v-if="$slots.submenulabel" #submenulabel="slotProps">
                <slot name="submenulabel" v-bind="slotProps" />
            </template>
        </Menu>
        <div v-if="$slots.footer" :class="cx('footer')" v-bind="ptm('footer')">
            <slot name="footer" :items="filteredModel" />
        </div>
    </component>
    <slot v-else :class="cx('root')" />
</template>

<script>
import { findSingle } from '@primeuix/utils/dom';
import Menu from 'primevue/menu';
import { mergeProps } from 'vue';
import BaseCommandMenu from './BaseCommandMenu.vue';

export default {
    name: 'CommandMenu',
    extends: BaseCommandMenu,
    inheritAttrs: false,
    emits: ['update:search', 'select'],
    data() {
        return {
            d_search: this.search ?? '',
            focusedOptionId: null
        };
    },
    watch: {
        search(newValue) {
            const normalizedSearch = newValue ?? '';

            if (this.d_search !== normalizedSearch) {
                this.d_search = normalizedSearch;
                this.resetFocusedOption();
            }
        }
    },
    mounted() {
        if (this.$refs.menu) {
            this.focusFirstOption();
        }
    },
    methods: {
        onInputChange(event) {
            this.updateSearch(event.target.value);
        },
        updateSearch(value) {
            const normalizedSearch = value ?? '';

            this.d_search = normalizedSearch;
            this.$emit('update:search', normalizedSearch);
            this.resetFocusedOption();
        },
        resetFocusedOption() {
            this.$nextTick(() => {
                this.focusFirstOption();
            });
        },
        focusFirstOption() {
            const menu = this.$refs.menu;

            if (!menu) {
                this.focusedOptionId = null;

                return;
            }

            menu.focused = true;

            if (!menu.focusedOptionId) {
                menu.changeFocusedOptionIndex(0);
            }

            this.focusedOptionId = menu.focusedOptionId ?? null;
        },
        onInputKeyDown(event) {
            const menu = this.$refs.menu;

            menu.onListKeyDown(event);
            this.focusedOptionId = menu?.focusedOptionId ?? null;

            if (this.focusedOptionId) {
                this.scrollFocusedOptionInView();
            }
        },
        scrollFocusedOptionInView() {
            requestAnimationFrame(() => {
                const menu = this.$refs.menu;

                if (!menu || !this.focusedOptionId) return;
                findSingle(menu.list, `[id="${this.focusedOptionId}"]`)?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
            });
        },
        wrapCommand(item) {
            return {
                ...item,
                command: (ev) => {
                    item.command?.(ev);
                    this.$emit('select', { originalEvent: ev.originalEvent, item });
                }
            };
        },
        wrapCommands(items) {
            return items.map((item) => (item.items ? { ...item, items: this.wrapCommands(item.items) } : this.wrapCommand(item)));
        },
        filterModel(items) {
            const out = [];

            for (const item of items) {
                if (item.items) {
                    const children = this.filterModel(item.items);

                    if (children.length) out.push({ ...item, items: children });
                } else if (item.separator) {
                    continue;
                } else {
                    const score = this.scoreItem(item, this.d_search);

                    if (score > 0) out.push({ wrapped: this.wrapCommand(item), score });
                }
            }

            out.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));

            return out.map((e) => e.wrapped ?? e);
        },
        scoreItem(item, search) {
            const scorer = this.filter ?? this.defaultScorer;

            return scorer(item, search) || 0;
        },
        defaultScorer(item, search) {
            if (!search) return 1;
            const s = String(search).toLowerCase();
            const label = String(this.resolveItemLabel(item)).toLowerCase();

            if (label.includes(s)) return 1;

            const kws = Array.isArray(item.keywords) ? item.keywords : item.keywords ? [item.keywords] : [];

            for (const kw of kws) {
                if (
                    String(kw ?? '')
                        .toLowerCase()
                        .includes(s)
                )
                    return 1;
            }

            return 0;
        },
        resolveItemLabel(item) {
            return typeof item.label === 'function' ? item.label() : (item.label ?? '');
        }
    },
    computed: {
        menuId() {
            return `${this.$id}_menu`;
        },
        listId() {
            return `${this.menuId}_list`;
        },
        inputAttrs() {
            return mergeProps(
                {
                    role: 'combobox',
                    'aria-autocomplete': 'list',
                    'aria-expanded': true,
                    'aria-controls': this.filteredModel.length ? this.listId : undefined,
                    'aria-activedescendant': this.focusedOptionId,
                    autocomplete: 'off',
                    placeholder: this.placeholder,
                    onKeydown: this.onInputKeyDown
                },
                this.ptm('input')
            );
        },
        inputProps() {
            return {
                ...this.inputAttrs,
                value: this.d_search,
                onInput: this.onInputChange
            };
        },
        isFiltering() {
            return !!(this.d_search && this.d_search.length);
        },
        emptyMessageText() {
            return this.emptyMessage ?? this.$primevue?.config?.locale?.emptyMessage ?? '';
        },
        emptyFilterMessageText() {
            return this.emptyFilterMessage ?? this.$primevue?.config?.locale?.emptySearchMessage ?? this.$primevue?.config?.locale?.emptyFilterMessage ?? '';
        },
        filteredModel() {
            const source = this.model ?? [];

            return this.d_search ? this.filterModel(source) : this.wrapCommands(source);
        }
    },
    components: {
        Menu
    }
};
</script>
