<template>
    <div class="x-container">
        <div style="margin: 0 0 10px; display: flex; align-items: center">
            <InputGroupField
                :model-value="searchText"
                :placeholder="t('view.search.search_placeholder')"
                style="flex: 1"
                clearable
                @input="updateSearchText"
                @keyup.enter="search" />
            <TooltipWrapper side="bottom" :content="t('view.search.clear_results_tooltip')">
                <Button class="rounded-full ml-2" size="icon" variant="ghost" @click="handleClearSearch"
                    ><Trash2
                /></Button>
            </TooltipWrapper>
        </div>
        <TabsUnderline
            v-model="activeSearchTab"
            :items="searchTabs"
            aria-label="Search tabs"
            :unmount-on-hide="false"
            style="margin-top: 15px">
            <template #user>
                <div style="min-height: 60px">
                    <label class="inline-flex items-center gap-2" style="margin-left: 10px">
                        <Checkbox v-model="searchUserByBio" />
                        <span>{{ t('view.search.user.search_by_bio') }}</span>
                    </label>
                    <label class="inline-flex items-center gap-2" style="margin-left: 10px">
                        <Checkbox v-model="searchUserSortByLastLoggedIn" />
                        <span>{{ t('view.search.user.sort_by_last_logged_in') }}</span>
                    </label>
                    <div class="x-friend-list" style="min-height: 500px">
                        <div
                            v-for="user in searchUserResults"
                            :key="user.id"
                            class="x-friend-item"
                            @click="showUserDialog(user.id)">
                            <div class="avatar">
                                <img :src="userImage(user, true)" loading="lazy" />
                            </div>
                            <div class="detail">
                                <span class="name" v-text="user.displayName"></span>
                                <span
                                    v-if="randomUserColours"
                                    class="block truncate text-xs"
                                    :class="user.$trustClass"
                                    v-text="user.$trustLevel"></span>
                                <span
                                    v-else
                                    class="block truncate text-xs"
                                    :style="{ color: user.$userColour }"
                                    v-text="user.$trustLevel"></span>
                            </div>
                        </div>
                    </div>
                    <ButtonGroup v-if="searchUserResults.length" style="margin-top: 15px">
                        <Button
                            variant="outline"
                            size="sm"
                            :disabled="!searchUserParams.offset"
                            @click="handleMoreSearchUser(-1)">
                            <ArrowLeft />
                            {{ t('view.search.prev_page') }}
                        </Button>
                        <Button
                            variant="outline"
                            size="sm"
                            :disabled="searchUserResults.length < 10"
                            @click="handleMoreSearchUser(1)">
                            <ArrowRight />
                            {{ t('view.search.next_page') }}
                        </Button>
                    </ButtonGroup>
                </div>
            </template>
            <template #world>
                <div style="min-height: 60px">
                    <div class="inline-flex justify-between mb-4 w-full">
                        <Select
                            :model-value="searchWorldCategoryIndex"
                            @update:modelValue="handleSearchWorldCategorySelect"
                            style="margin-bottom: 15px">
                            <SelectTrigger size="sm">
                                <SelectValue :placeholder="t('view.search.world.category')" />
                            </SelectTrigger>
                            <SelectContent>
                                <SelectGroup>
                                    <SelectItem
                                        v-for="row in cachedConfig.dynamicWorldRows"
                                        :key="row.index"
                                        :value="row.index">
                                        {{ row.name }}
                                    </SelectItem>
                                </SelectGroup>
                            </SelectContent>
                        </Select>
                        <label class="inline-flex items-center gap-2" style="margin-left: 10px">
                            <Checkbox v-model="searchWorldLabs" />
                            <span>{{ t('view.search.world.community_lab') }}</span>
                        </label>
                    </div>
                    <div class="x-friend-list" style="min-height: 500px">
                        <div
                            v-for="world in searchWorldResults"
                            :key="world.id"
                            class="x-friend-item"
                            @click="showWorldDialog(world.id)">
                            <div class="avatar">
                                <img :src="world.thumbnailImageUrl" loading="lazy" />
                            </div>
                            <div class="detail">
                                <span class="name" v-text="world.name"></span>
                                <span v-if="world.occupants" class="block truncate text-xs"
                                    >{{ world.authorName }} ({{ world.occupants }})</span
                                >
                                <span v-else class="block truncate text-xs" v-text="world.authorName"></span>
                            </div>
                        </div>
                    </div>
                    <ButtonGroup v-if="searchWorldResults.length" style="margin-top: 15px">
                        <Button
                            variant="outline"
                            size="sm"
                            :disabled="!searchWorldParams.offset"
                            @click="moreSearchWorld(-1)">
                            <ArrowLeft />
                            {{ t('view.search.prev_page') }}
                        </Button>
                        <Button
                            variant="outline"
                            size="sm"
                            :disabled="searchWorldResults.length < 10"
                            @click="moreSearchWorld(1)">
                            <ArrowRight />
                            {{ t('view.search.next_page') }}
                        </Button>
                    </ButtonGroup>
                </div>
            </template>
            <template #avatar>
                <div style="min-height: 60px">
                    <div style="display: flex; align-items: center; justify-content: space-between">
                        <div style="display: flex; align-items: center">
                            <Select
                                v-if="avatarRemoteDatabaseProviderList.length > 1"
                                :model-value="avatarRemoteDatabaseProvider"
                                @update:modelValue="setAvatarProvider"
                                style="margin-right: 5px">
                                <SelectTrigger size="sm">
                                    <SelectValue :placeholder="t('view.search.avatar.search_provider')" />
                                </SelectTrigger>
                                <SelectContent>
                                    <SelectGroup>
                                        <SelectItem
                                            v-for="provider in avatarRemoteDatabaseProviderList"
                                            :key="provider"
                                            :value="provider">
                                            {{ provider }}
                                        </SelectItem>
                                    </SelectGroup>
                                </SelectContent>
                            </Select>
                            <TooltipWrapper side="bottom" :content="t('view.search.avatar.refresh_tooltip')">
                                <Button
                                    class="rounded-full ml-1"
                                    variant="outline"
                                    size="icon-sm"
                                    :disabled="userDialog.isAvatarsLoading"
                                    @click="refreshUserDialogAvatars">
                                    <Spinner v-if="userDialog.isAvatarsLoading" />
                                    <RefreshCw v-else />
                                </Button>
                            </TooltipWrapper>
                            <span style="font-size: 14px; margin-left: 5px; margin-right: 5px">{{
                                t('view.search.avatar.result_count', {
                                    count: searchAvatarResults.length
                                })
                            }}</span>
                        </div>
                        <div style="display: flex; align-items: center">
                            <RadioGroup
                                :model-value="searchAvatarFilter"
                                class="flex items-center gap-4"
                                style="margin: 5px"
                                @update:modelValue="handleSearchAvatarFilterChange">
                                <div class="flex items-center space-x-2">
                                    <RadioGroupItem id="searchAvatarFilter-all" value="all" />
                                    <label for="searchAvatarFilter-all">{{ t('view.search.avatar.all') }}</label>
                                </div>
                                <div class="flex items-center space-x-2">
                                    <RadioGroupItem id="searchAvatarFilter-public" value="public" />
                                    <label for="searchAvatarFilter-public">{{ t('view.search.avatar.public') }}</label>
                                </div>
                                <div class="flex items-center space-x-2">
                                    <RadioGroupItem id="searchAvatarFilter-private" value="private" />
                                    <label for="searchAvatarFilter-private">{{
                                        t('view.search.avatar.private')
                                    }}</label>
                                </div>
                            </RadioGroup>
                            <Separator orientation="vertical" class="mx-2 h-5" />
                            <RadioGroup
                                :model-value="searchAvatarFilterRemote"
                                class="flex items-center gap-4"
                                style="margin: 5px"
                                @update:modelValue="handleSearchAvatarFilterRemoteChange">
                                <div class="flex items-center space-x-2">
                                    <RadioGroupItem id="searchAvatarFilterRemote-all" value="all" />
                                    <label for="searchAvatarFilterRemote-all">{{ t('view.search.avatar.all') }}</label>
                                </div>
                                <div class="flex items-center space-x-2">
                                    <RadioGroupItem id="searchAvatarFilterRemote-local" value="local" />
                                    <label for="searchAvatarFilterRemote-local">{{
                                        t('view.search.avatar.local')
                                    }}</label>
                                </div>
                                <div class="flex items-center space-x-2">
                                    <RadioGroupItem
                                        id="searchAvatarFilterRemote-remote"
                                        value="remote"
                                        :disabled="!avatarRemoteDatabase" />
                                    <label for="searchAvatarFilterRemote-remote">{{
                                        t('view.search.avatar.remote')
                                    }}</label>
                                </div>
                            </RadioGroup>
                        </div>
                    </div>
                    <div style="display: flex; justify-content: end">
                        <RadioGroup
                            :model-value="searchAvatarSort"
                            :disabled="searchAvatarFilterRemote !== 'local'"
                            class="flex items-center gap-4"
                            style="margin: 5px"
                            @update:modelValue="handleSearchAvatarSortChange">
                            <div class="flex items-center space-x-2">
                                <RadioGroupItem id="searchAvatarSort-name" value="name" />
                                <label for="searchAvatarSort-name">{{ t('view.search.avatar.sort_name') }}</label>
                            </div>
                            <div class="flex items-center space-x-2">
                                <RadioGroupItem id="searchAvatarSort-update" value="update" />
                                <label for="searchAvatarSort-update">{{ t('view.search.avatar.sort_update') }}</label>
                            </div>
                            <div class="flex items-center space-x-2">
                                <RadioGroupItem id="searchAvatarSort-created" value="created" />
                                <label for="searchAvatarSort-created">{{ t('view.search.avatar.sort_created') }}</label>
                            </div>
                        </RadioGroup>
                    </div>
                    <div class="x-friend-list" style="margin-top: 20px; min-height: 500px">
                        <div
                            v-for="avatar in searchAvatarPage"
                            :key="avatar.id"
                            class="x-friend-item"
                            @click="showAvatarDialog(avatar.id)">
                            <div class="avatar">
                                <img v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" loading="lazy" />
                                <img v-else-if="avatar.imageUrl" :src="avatar.imageUrl" loading="lazy" />
                            </div>
                            <div class="detail">
                                <span class="name" v-text="avatar.name"></span>
                                <span
                                    v-if="avatar.releaseStatus === 'public'"
                                    class="block truncate text-xs"
                                    v-text="avatar.releaseStatus"></span>
                                <span
                                    v-else-if="avatar.releaseStatus === 'private'"
                                    class="block truncate text-xs"
                                    v-text="avatar.releaseStatus"></span>
                                <span v-else class="block truncate text-xs" v-text="avatar.releaseStatus"></span>
                                <span class="block truncate text-xs" v-text="avatar.authorName"></span>
                            </div>
                        </div>
                    </div>
                    <ButtonGroup v-if="searchAvatarPage.length" style="margin-top: 15px">
                        <Button
                            variant="outline"
                            size="sm"
                            :disabled="!searchAvatarPageNum"
                            @click="moreSearchAvatar(-1)">
                            <ArrowLeft />
                            {{ t('view.search.prev_page') }}
                        </Button>
                        <Button
                            variant="outline"
                            size="sm"
                            :disabled="
                                searchAvatarResults.length < 10 ||
                                (searchAvatarPageNum + 1) * 10 >= searchAvatarResults.length
                            "
                            @click="moreSearchAvatar(1)">
                            <ArrowRight />
                            {{ t('view.search.next_page') }}
                        </Button>
                    </ButtonGroup>
                </div>
            </template>
            <template #group>
                <div style="min-height: 60px">
                    <div class="x-friend-list" style="min-height: 500px">
                        <div
                            v-for="group in searchGroupResults"
                            :key="group.id"
                            class="x-friend-item"
                            @click="showGroupDialog(group.id)">
                            <div class="avatar">
                                <img :src="getSmallThumbnailUrl(group.iconUrl)" loading="lazy" />
                            </div>
                            <div class="detail">
                                <span class="name">
                                    <span v-text="group.name"></span>
                                    <span style="margin-left: 5px; font-weight: normal">({{ group.memberCount }})</span>
                                    <span
                                        style="
                                            margin-left: 5px;
                                            color: #909399;
                                            font-weight: normal;
                                            font-family: monospace;
                                            font-size: 12px;
                                        "
                                        >{{ group.shortCode }}.{{ group.discriminator }}</span
                                    >
                                </span>
                                <span class="block truncate text-xs" v-text="group.description"></span>
                            </div>
                        </div>
                    </div>
                    <ButtonGroup v-if="searchGroupResults.length" style="margin-top: 15px">
                        <Button
                            variant="outline"
                            size="sm"
                            :disabled="!searchGroupParams.offset"
                            @click="moreSearchGroup(-1)">
                            <ArrowLeft />
                            {{ t('view.search.prev_page') }}
                        </Button>
                        <Button
                            variant="outline"
                            size="sm"
                            :disabled="searchGroupResults.length < 10"
                            @click="moreSearchGroup(1)">
                            <ArrowRight />
                            {{ t('view.search.next_page') }}
                        </Button>
                    </ButtonGroup>
                </div>
            </template>
        </TabsUnderline>
    </div>
</template>

<script setup>
    import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
    import { ArrowLeft, ArrowRight, RefreshCw, Trash2 } from 'lucide-vue-next';
    import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
    import { computed, ref } from 'vue';
    import { Button } from '@/components/ui/button';
    import { ButtonGroup } from '@/components/ui/button-group';
    import { Checkbox } from '@/components/ui/checkbox';
    import { InputGroupField } from '@/components/ui/input-group';
    import { Separator } from '@/components/ui/separator';
    import { Spinner } from '@/components/ui/spinner';
    import { TabsUnderline } from '@/components/ui/tabs';
    import { storeToRefs } from 'pinia';
    import { useI18n } from 'vue-i18n';

    import {
        useAdvancedSettingsStore,
        useAppearanceSettingsStore,
        useAuthStore,
        useAvatarProviderStore,
        useAvatarStore,
        useGroupStore,
        useSearchStore,
        useUserStore,
        useWorldStore
    } from '../../stores';
    import {
        compareByCreatedAt,
        compareByName,
        compareByUpdatedAt,
        convertFileUrlToImageUrl,
        replaceBioSymbols,
        userImage
    } from '../../shared/utils';
    import { groupRequest, worldRequest } from '../../api';

    const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
    const { avatarRemoteDatabase } = storeToRefs(useAdvancedSettingsStore());
    const { avatarRemoteDatabaseProviderList, avatarRemoteDatabaseProvider } = storeToRefs(useAvatarProviderStore());
    const { setAvatarProvider } = useAvatarProviderStore();
    const { userDialog } = storeToRefs(useUserStore());
    const { showUserDialog, refreshUserDialogAvatars } = useUserStore();
    const { showAvatarDialog, lookupAvatars, cachedAvatars } = useAvatarStore();
    const { cachedWorlds, showWorldDialog } = useWorldStore();
    const { showGroupDialog } = useGroupStore();
    const { searchText, searchUserResults } = storeToRefs(useSearchStore());
    const { clearSearch, moreSearchUser } = useSearchStore();
    const { cachedConfig } = storeToRefs(useAuthStore());

    const { t } = useI18n();

    const activeSearchTab = ref('user');
    const searchTabs = computed(() => [
        { value: 'user', label: t('view.search.user.header') },
        { value: 'world', label: t('view.search.world.header') },
        { value: 'avatar', label: t('view.search.avatar.header') },
        { value: 'group', label: t('view.search.group.header') }
    ]);

    const searchUserParams = ref({});
    const searchUserByBio = ref(false);
    const searchUserSortByLastLoggedIn = ref(false);

    const isSearchUserLoading = ref(false);
    const isSearchWorldLoading = ref(false);
    const isSearchAvatarLoading = ref(false);
    const isSearchGroupLoading = ref(false);

    const searchWorldOption = ref('');
    const searchWorldLabs = ref(false);
    const searchWorldParams = ref({});

    const searchWorldCategoryIndex = ref(null);
    const searchWorldResults = ref([]);

    function handleSearchAvatarFilterChange(value) {
        searchAvatarFilter.value = value;
        searchAvatar();
    }

    function handleSearchAvatarFilterRemoteChange(value) {
        searchAvatarFilterRemote.value = value;
        searchAvatar();
    }

    function handleSearchAvatarSortChange(value) {
        searchAvatarSort.value = value;
        searchAvatar();
    }

    const searchAvatarFilter = ref('');
    const searchAvatarSort = ref('');
    const searchAvatarFilterRemote = ref('');
    const searchAvatarPageNum = ref(0);
    const searchAvatarResults = ref([]);
    const searchAvatarPage = ref([]);

    const searchGroupParams = ref({});
    const searchGroupResults = ref([]);

    function getSmallThumbnailUrl(url) {
        return convertFileUrlToImageUrl(url);
    }

    function handleClearSearch() {
        searchUserParams.value = {};
        searchWorldParams.value = {};
        searchWorldResults.value = [];
        searchAvatarResults.value = [];
        searchAvatarPage.value = [];
        searchAvatarPageNum.value = 0;
        searchGroupParams.value = {};
        searchGroupResults.value = [];
        clearSearch();
    }

    function updateSearchText(text) {
        searchText.value = text;
    }

    function handleSearchTabChange(tabName) {
        searchText.value = '';
        activeSearchTab.value = tabName;
    }

    function search() {
        switch (activeSearchTab.value) {
            case 'user':
                searchUser();
                break;
            case 'world':
                searchWorld({});
                break;
            case 'avatar':
                searchAvatar();
                break;
            case 'group':
                searchGroup();
                break;
        }
    }

    async function searchUser() {
        searchUserParams.value = {
            n: 10,
            offset: 0,
            search: searchText.value,
            customFields: searchUserByBio.value ? 'bio' : 'displayName',
            sort: searchUserSortByLastLoggedIn.value ? 'last_login' : 'relevance'
        };
        await handleMoreSearchUser();
    }

    async function handleMoreSearchUser(go = null) {
        isSearchUserLoading.value = true;
        await moreSearchUser(go, searchUserParams.value);
        isSearchUserLoading.value = false;
    }

    function searchWorld(ref) {
        searchWorldOption.value = '';
        searchWorldCategoryIndex.value = ref?.index ?? null;
        const params = {
            n: 10,
            offset: 0
        };
        switch (ref.sortHeading) {
            case 'featured':
                params.sort = 'order';
                params.featured = 'true';
                break;
            case 'trending':
                params.sort = 'popularity';
                params.featured = 'false';
                break;
            case 'updated':
                params.sort = 'updated';
                break;
            case 'created':
                params.sort = 'created';
                break;
            case 'publication':
                params.sort = 'publicationDate';
                break;
            case 'shuffle':
                params.sort = 'shuffle';
                break;
            case 'active':
                searchWorldOption.value = 'active';
                break;
            case 'recent':
                searchWorldOption.value = 'recent';
                break;
            case 'favorite':
                searchWorldOption.value = 'favorites';
                break;
            case 'labs':
                params.sort = 'labsPublicationDate';
                break;
            case 'heat':
                params.sort = 'heat';
                params.featured = 'false';
                break;
            default:
                params.sort = 'relevance';
                params.search = replaceBioSymbols(searchText.value);
                break;
        }
        params.order = ref.sortOrder || 'descending';
        if (ref.sortOwnership === 'mine') {
            params.user = 'me';
            params.releaseStatus = 'all';
        }
        if (ref.tag) {
            params.tag = ref.tag;
        }
        if (!searchWorldLabs.value) {
            if (params.tag) {
                params.tag += ',system_approved';
            } else {
                params.tag = 'system_approved';
            }
        }
        // TODO: option.platform
        searchWorldParams.value = params;
        moreSearchWorld();
    }

    function handleSearchWorldCategorySelect(index) {
        searchWorldCategoryIndex.value = index;
        const row = cachedConfig.value?.dynamicWorldRows?.find((r) => r.index === index);
        searchWorld(row || {});
    }

    function moreSearchWorld(go) {
        const params = searchWorldParams.value;
        if (go) {
            params.offset += params.n * go;
            if (params.offset < 0) {
                params.offset = 0;
            }
        }
        isSearchWorldLoading.value = true;
        worldRequest
            .getWorlds(params, searchWorldOption.value)
            .finally(() => {
                isSearchWorldLoading.value = false;
            })
            .then((args) => {
                const map = new Map();
                for (const json of args.json) {
                    const ref = cachedWorlds.get(json.id);
                    if (typeof ref !== 'undefined') {
                        map.set(ref.id, ref);
                    }
                }
                searchWorldResults.value = Array.from(map.values());
                return args;
            });
    }

    async function searchAvatar() {
        let ref;
        isSearchAvatarLoading.value = true;
        if (!searchAvatarFilter.value) {
            searchAvatarFilter.value = 'all';
        }
        if (!searchAvatarSort.value) {
            searchAvatarSort.value = 'name';
        }
        if (!searchAvatarFilterRemote.value) {
            searchAvatarFilterRemote.value = 'all';
        }
        if (searchAvatarFilterRemote.value !== 'local') {
            searchAvatarSort.value = 'name';
        }
        const avatars = new Map();
        const query = searchText.value;
        const queryUpper = query.toUpperCase();
        if (!query) {
            for (ref of cachedAvatars.values()) {
                switch (searchAvatarFilter.value) {
                    case 'all':
                        avatars.set(ref.id, ref);
                        break;
                    case 'public':
                        if (ref.releaseStatus === 'public') {
                            avatars.set(ref.id, ref);
                        }
                        break;
                    case 'private':
                        if (ref.releaseStatus === 'private') {
                            avatars.set(ref.id, ref);
                        }
                        break;
                }
            }
            isSearchAvatarLoading.value = false;
        } else {
            if (searchAvatarFilterRemote.value === 'all' || searchAvatarFilterRemote.value === 'local') {
                for (ref of cachedAvatars.values()) {
                    let match = ref.name.toUpperCase().includes(queryUpper);
                    if (!match && ref.description) {
                        match = ref.description.toUpperCase().includes(queryUpper);
                    }
                    if (!match && ref.authorName) {
                        match = ref.authorName.toUpperCase().includes(queryUpper);
                    }
                    if (match) {
                        switch (searchAvatarFilter.value) {
                            case 'all':
                                avatars.set(ref.id, ref);
                                break;
                            case 'public':
                                if (ref.releaseStatus === 'public') {
                                    avatars.set(ref.id, ref);
                                }
                                break;
                            case 'private':
                                if (ref.releaseStatus === 'private') {
                                    avatars.set(ref.id, ref);
                                }
                                break;
                        }
                    }
                }
            }
            if (
                (searchAvatarFilterRemote.value === 'all' || searchAvatarFilterRemote.value === 'remote') &&
                avatarRemoteDatabase.value &&
                query.length >= 3
            ) {
                const data = await lookupAvatars('search', query);
                if (data && typeof data === 'object') {
                    data.forEach((avatar) => {
                        avatars.set(avatar.id, avatar);
                    });
                }
            }
            isSearchAvatarLoading.value = false;
        }
        const avatarsArray = Array.from(avatars.values());
        if (searchAvatarFilterRemote.value === 'local') {
            switch (searchAvatarSort.value) {
                case 'updated':
                    avatarsArray.sort(compareByUpdatedAt);
                    break;
                case 'created':
                    avatarsArray.sort(compareByCreatedAt);
                    break;
                case 'name':
                    avatarsArray.sort(compareByName);
                    break;
            }
        }
        searchAvatarPageNum.value = 0;
        searchAvatarResults.value = avatarsArray;
        searchAvatarPage.value = avatarsArray.slice(0, 10);
    }
    function moreSearchAvatar(n) {
        let offset;
        if (n === -1) {
            searchAvatarPageNum.value--;
            offset = searchAvatarPageNum.value * 10;
        }
        if (n === 1) {
            searchAvatarPageNum.value++;
            offset = searchAvatarPageNum.value * 10;
        }
        searchAvatarPage.value = searchAvatarResults.value.slice(offset, offset + 10);
    }
    async function searchGroup() {
        searchGroupParams.value = {
            n: 10,
            offset: 0,
            query: replaceBioSymbols(searchText.value)
        };
        await moreSearchGroup();
    }
    async function moreSearchGroup(go) {
        const params = searchGroupParams.value;
        if (go) {
            params.offset += params.n * go;
            if (params.offset < 0) {
                params.offset = 0;
            }
        }
        isSearchGroupLoading.value = true;
        await groupRequest
            .groupSearch(params)
            .finally(() => {
                isSearchGroupLoading.value = false;
            })
            .then((args) => {
                const map = new Map();
                for (const json of args.json) {
                    map.set(json.id, json);
                }
                searchGroupResults.value = Array.from(map.values());
                return args;
            });
    }
</script>
