<template>
    <div class="friend-view x-container">
        <div v-if="settingsReady" class="friend-view__toolbar">
            <Tabs v-model="activeSegment" class="friend-view__tabs">
                <TabsList>
                    <TabsTrigger v-for="option in segmentedOptions" :key="option.value" :value="option.value">
                        {{ option.label }}
                    </TabsTrigger>
                </TabsList>
            </Tabs>
            <div class="friend-view__actions">
                <InputGroupSearch v-model="searchTerm" class="friend-view__search" placeholder="Search Friend" />
                <TooltipWrapper :content="t('view.charts.instance_activity.settings.header')" side="top">
                    <div>
                        <Popover>
                            <PopoverTrigger asChild>
                                <Button class="rounded-full mr-2" size="icon" variant="ghost">
                                    <Settings />
                                </Button>
                            </PopoverTrigger>
                            <PopoverContent side="bottom" class="w-87.5">
                                <div class="friend-view__settings">
                                    <Field orientation="horizontal" class="friend-view__settings-row">
                                        <FieldLabel class="friend-view__settings-label">{{
                                            t('view.friends_locations.separate_same_instance_friends')
                                        }}</FieldLabel>
                                        <FieldContent>
                                            <Switch v-model="showSameInstance" />
                                        </FieldContent>
                                    </Field>
                                    <Field orientation="horizontal" class="friend-view__settings-row">
                                        <FieldLabel class="friend-view__settings-label">
                                            {{ t('view.friends_locations.scale') }}
                                        </FieldLabel>
                                        <FieldContent>
                                            <div class="friend-view__scale-control">
                                                <span class="friend-view__scale-value"
                                                    >{{ cardScalePercentLabel }}&nbsp;</span
                                                >
                                                <Slider
                                                    v-model="cardScaleValue"
                                                    class="friend-view__slider"
                                                    :min="0.5"
                                                    :max="1.0"
                                                    :step="0.01" />
                                            </div>
                                        </FieldContent>
                                    </Field>
                                    <Field orientation="horizontal" class="friend-view__settings-row">
                                        <FieldLabel class="friend-view__settings-label">
                                            {{ t('view.friends_locations.spacing') }}
                                        </FieldLabel>
                                        <FieldContent>
                                            <div class="friend-view__scale-control">
                                                <span class="friend-view__scale-value"
                                                    >{{ cardSpacingPercentLabel }}&nbsp;</span
                                                >
                                                <Slider
                                                    v-model="cardSpacingValue"
                                                    class="friend-view__slider"
                                                    :min="0.25"
                                                    :max="1.0"
                                                    :step="0.05" />
                                            </div>
                                        </FieldContent>
                                    </Field>
                                </div>
                            </PopoverContent>
                        </Popover>
                    </div>
                </TooltipWrapper>
            </div>
        </div>
        <div v-else class="friend-view__toolbar friend-view__toolbar--loading">
            <span class="friend-view__loading-text">{{ t('view.friends_locations.loading_more') }}</span>
        </div>
        <div v-if="settingsReady" ref="scrollbarRef" class="friend-view__scroll">
            <div v-if="virtualRows.length" class="friend-view__virtual" :style="virtualContainerStyle">
                <template v-for="item in virtualItems" :key="String(item.virtualItem.key)">
                    <div
                        v-if="item.row"
                        class="friend-view__virtual-row"
                        :class="`friend-view__virtual-row--${item.row.type}`"
                        :data-index="item.virtualItem.index"
                        :ref="virtualizer.measureElement"
                        :style="{ transform: `translateY(${item.virtualItem.start}px)` }">
                        <template v-if="item.row.type === 'header'">
                            <header class="friend-view__instance-header">
                                <Location
                                    class="text-xs"
                                    :location="getRowInstanceId(item.row)"
                                    style="display: inline" />
                                <span class="friend-view__instance-count">({{ getRowCount(item.row) }})</span>
                            </header>
                        </template>

                        <template v-else-if="item.row.type === 'divider'">
                            <div class="friend-view__divider"><span class="friend-view__divider-text"></span></div>
                        </template>

                        <template v-else>
                            <div class="friend-view__row">
                                <FriendLocationCard
                                    v-for="card in getRowItems(item.row)"
                                    :key="card.key"
                                    :friend="card.friend"
                                    :card-scale="cardScale"
                                    :card-spacing="cardSpacing"
                                    :display-instance-info="card.displayInstanceInfo" />
                            </div>
                        </template>
                    </div>
                </template>
            </div>
            <div v-else class="friend-view__empty">
                <DataTableEmpty type="nomatch" />
            </div>
        </div>
        <div v-else class="friend-view__initial-loading">
            <Loader2 class="friend-view__loading-icon" :size="22" />
        </div>
    </div>
</template>

<script setup>
    import { computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, ref, watch } from 'vue';
    import { Field, FieldContent, FieldLabel } from '@/components/ui/field';
    import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
    import { Loader2, Settings } from 'lucide-vue-next';
    import { Button } from '@/components/ui/button';
    import { DataTableEmpty } from '@/components/ui/data-table';
    import { InputGroupSearch } from '@/components/ui/input-group';
    import { storeToRefs } from 'pinia';
    import { useI18n } from 'vue-i18n';
    import { useVirtualizer } from '@tanstack/vue-virtual';

    import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
    import { Slider } from '../../components/ui/slider';
    import { Switch } from '../../components/ui/switch';
    import { getFriendsLocations } from '../../shared/utils/location.js';
    import { useFriendStore } from '../../stores';

    import FriendLocationCard from './components/FriendsLocationsCard.vue';
    import configRepository from '../../service/config.js';

    const { t } = useI18n();

    const friendStore = useFriendStore();
    const { onlineFriends, vipFriends, activeFriends, offlineFriends, friendsInSameInstance } =
        storeToRefs(friendStore);

    const SEGMENTED_BASE_OPTIONS = [
        { label: t('view.friends_locations.online'), value: 'online' },
        { label: t('view.friends_locations.favorite'), value: 'favorite' },
        { label: t('view.friends_locations.same_instance'), value: 'same-instance' },
        { label: t('view.friends_locations.active'), value: 'active' },
        { label: t('view.friends_locations.offline'), value: 'offline' }
    ];

    const segmentedOptions = computed(() =>
        showSameInstance.value
            ? SEGMENTED_BASE_OPTIONS
            : SEGMENTED_BASE_OPTIONS.filter((option) => option.value !== 'same-instance')
    );

    const cardScaleBase = ref(1);
    const cardSpacingBase = ref(1);

    const cardScale = computed({
        get: () => cardScaleBase.value,
        set: (value) => {
            cardScaleBase.value = value;
            configRepository.setString('VRCX_FriendLocationCardScale', value.toString());
        }
    });

    const cardSpacing = computed({
        get: () => cardSpacingBase.value,
        set: (value) => {
            cardSpacingBase.value = value;
            configRepository.setString('VRCX_FriendLocationCardSpacing', value.toString());
        }
    });

    const cardScalePercentLabel = computed(() => `${Math.round(cardScale.value * 100)}%`);
    const cardSpacingPercentLabel = computed(() => `${Math.round(cardSpacing.value * 100)}%`);
    const cardScaleValue = computed({
        get: () => [cardScale.value],
        set: (value) => {
            const next = value?.[0];
            if (typeof next === 'number') {
                cardScale.value = next;
            }
        }
    });
    const cardSpacingValue = computed({
        get: () => [cardSpacing.value],
        set: (value) => {
            const next = value?.[0];
            if (typeof next === 'number') {
                cardSpacing.value = next;
            }
        }
    });

    const showSameInstanceBase = ref(false);

    const showSameInstance = computed({
        get: () => showSameInstanceBase.value,
        set: (value) => {
            showSameInstanceBase.value = value;
            configRepository.setBool('VRCX_FriendLocationShowSameInstance', value);
        }
    });

    const settingsReady = ref(false);

    const activeSegment = ref('online');
    const searchTerm = ref('');

    const scrollbarRef = ref();
    const gridWidth = ref(0);
    let resizeObserver;
    let cleanupResize;

    const updateGridWidth = () => {
        const wrap = scrollbarRef.value;
        if (!wrap) {
            return;
        }

        gridWidth.value = wrap.clientWidth ?? 0;
    };

    const setupResizeHandling = () => {
        if (cleanupResize) {
            cleanupResize();
            cleanupResize = undefined;
        }

        const wrap = scrollbarRef.value;
        if (!wrap) {
            return;
        }

        updateGridWidth();

        if (typeof ResizeObserver !== 'undefined') {
            resizeObserver = new ResizeObserver((entries) => {
                if (!entries || entries.length === 0) {
                    return;
                }
                const [entry] = entries;
                gridWidth.value = entry.contentRect?.width ?? wrap.clientWidth ?? 0;
            });
            resizeObserver.observe(wrap);
            cleanupResize = () => {
                resizeObserver?.disconnect();
                resizeObserver = undefined;
            };
            return;
        }

        if (typeof window !== 'undefined') {
            const handleResize = () => {
                updateGridWidth();
            };
            window.addEventListener('resize', handleResize, { passive: true });
            cleanupResize = () => {
                window.removeEventListener('resize', handleResize);
            };
        }
    };

    const normalizedSearchTerm = computed(() => searchTerm.value.trim().toLowerCase());

    const toEntries = (list = [], instanceId) =>
        Array.isArray(list)
            ? list.map((friend) => ({
                  id: friend.id ?? friend.userId ?? friend.displayName,
                  friend,
                  instanceId
              }))
            : [];

    const sameInstanceGroups = computed(() => {
        const source = friendsInSameInstance?.value;
        if (!Array.isArray(source) || source.length === 0) return [];

        return source
            .map((group, index) => {
                if (!Array.isArray(group) || group.length === 0) return null;
                const friends = group;
                const instanceId = getFriendsLocations(friends) || `instance-${index + 1}`;
                return {
                    instanceId: String(instanceId),
                    friends
                };
            })
            .filter(Boolean);
    });

    const sameInstanceEntries = computed(() =>
        sameInstanceGroups.value.flatMap((group) => toEntries(group.friends, group.instanceId))
    );

    const uniqueEntries = (entries = []) => {
        const seen = new Set();
        return entries.filter((entry) => {
            const key = entry.id;
            if (!key) {
                return true;
            }
            if (seen.has(key)) {
                return false;
            }
            seen.add(key);
            return true;
        });
    };

    const filteredFriends = computed(() => {
        if (normalizedSearchTerm.value) {
            const pools = [
                ...toEntries(vipFriends.value),
                ...toEntries(onlineFriends.value),
                ...toEntries(activeFriends.value),
                ...toEntries(offlineFriends.value)
            ];

            return uniqueEntries(pools).filter(({ friend }) => {
                const haystack =
                    `${friend.displayName ?? friend.name ?? ''} ${friend.signature ?? ''} ${friend.worldName ?? ''}`.toLowerCase();
                return haystack.includes(normalizedSearchTerm.value);
            });
        }

        switch (activeSegment.value) {
            case 'online': {
                if (!showSameInstance.value) {
                    const sameEntries = sameInstanceEntries.value.map((entry) => ({
                        ...entry,
                        section: 'same-instance'
                    }));

                    const seenIds = new Set(
                        sameEntries
                            .map((entry) => entry.id)
                            .filter((id) => typeof id === 'string' || typeof id === 'number')
                    );

                    const remainingOnline = toEntries(onlineFriends.value)
                        .filter((entry) => {
                            if (!entry?.id) {
                                return true;
                            }
                            return !seenIds.has(entry.id);
                        })
                        .map((entry) => ({
                            ...entry,
                            section: 'online'
                        }));

                    return [...sameEntries, ...remainingOnline];
                }

                return toEntries(onlineFriends.value);
            }
            case 'favorite':
                return toEntries(vipFriends.value);
            case 'same-instance':
                return sameInstanceEntries.value;
            case 'active':
                return toEntries(activeFriends.value);
            case 'offline':
                return toEntries(offlineFriends.value);
            default:
                return [];
        }
    });

    const isSameInstanceView = computed(
        () => showSameInstance.value && activeSegment.value === 'same-instance' && !normalizedSearchTerm.value
    );

    const shouldMergeSameInstance = computed(
        () => !showSameInstance.value && activeSegment.value === 'online' && !normalizedSearchTerm.value
    );

    const buildSameInstanceGroups = (entries = []) => {
        if (!Array.isArray(entries) || entries.length === 0) {
            return [];
        }

        const grouped = new Map();

        for (const entry of entries) {
            const bucketId = entry.instanceId ?? entry.friend?.ref?.location ?? null;
            if (!bucketId) {
                continue;
            }
            if (!grouped.has(bucketId)) {
                grouped.set(bucketId, []);
            }
            grouped.get(bucketId).push(entry.friend);
        }

        return sameInstanceGroups.value
            .filter((group) => grouped.has(group.instanceId))
            .map((group) => ({
                instanceId: group.instanceId,
                friends: grouped.get(group.instanceId) ?? []
            }));
    };

    const sameInstanceGroupsForVirtual = computed(() => {
        if (!isSameInstanceView.value) {
            return [];
        }
        return buildSameInstanceGroups(filteredFriends.value);
    });

    const mergedSameInstanceEntries = computed(() => {
        if (!shouldMergeSameInstance.value) {
            return [];
        }
        return filteredFriends.value.filter((entry) => entry.section === 'same-instance');
    });

    const mergedOnlineEntries = computed(() => {
        if (!shouldMergeSameInstance.value) {
            return [];
        }
        return filteredFriends.value.filter((entry) => entry.section !== 'same-instance');
    });

    const mergedSameInstanceGroups = computed(() => {
        if (!shouldMergeSameInstance.value) {
            return [];
        }
        return buildSameInstanceGroups(mergedSameInstanceEntries.value);
    });

    const gridStyle = computed(() => {
        const baseWidth = 220;
        const baseGap = 14;
        const scale = cardScale.value;
        const spacing = cardSpacing.value;
        const minWidth = baseWidth * scale;
        const gap = Math.max(6, (baseGap + (scale - 1) * 10) * spacing);

        return (count = 1, options = {}) => {
            const containerWidth = Math.max(gridWidth.value ?? 0, 0);
            const itemCount = Math.max(Number(count) || 0, 0);
            const safeCount = itemCount > 0 ? itemCount : 1;
            const maxColumns = Math.max(1, Math.floor((containerWidth + gap) / (minWidth + gap)) || 1);
            const preferredColumns = options?.preferredColumns;
            const requestedColumns = preferredColumns
                ? Math.max(1, Math.min(Math.round(preferredColumns), maxColumns))
                : maxColumns;
            const columns = Math.max(1, Math.min(safeCount, requestedColumns));
            const forceStretch = Boolean(options?.forceStretch);
            const disableAutoStretch = Boolean(options?.disableAutoStretch);
            const matchMaxColumnWidth = Boolean(options?.matchMaxColumnWidth);
            const shouldStretch = !disableAutoStretch && (forceStretch || itemCount >= maxColumns);

            let cardWidth = minWidth;
            const maxColumnWidth = maxColumns > 0 ? (containerWidth - gap * (maxColumns - 1)) / maxColumns : minWidth;

            if (shouldStretch && columns > 0) {
                const columnsWidth = containerWidth - gap * (columns - 1);
                const rawWidth = columnsWidth > 0 ? columnsWidth / columns : minWidth;

                if (Number.isFinite(rawWidth) && rawWidth > 0) {
                    cardWidth = Math.max(minWidth, rawWidth);
                }
            } else if (matchMaxColumnWidth && Number.isFinite(maxColumnWidth) && maxColumnWidth > 0) {
                cardWidth = Math.max(minWidth, maxColumnWidth);
            }

            return {
                '--friend-card-min-width': `${Math.round(minWidth)}px`,
                '--friend-card-gap': `${Math.round(gap)}px`,
                '--friend-card-target-width': `${Math.round(cardWidth)}px`,
                '--friend-grid-columns': `${columns}`,
                '--friend-card-spacing': `${spacing.toFixed(2)}`
            };
        };
    });

    const getGridMetrics = (count = 1, options = {}) => {
        const baseWidth = 220;
        const baseGap = 14;
        const scale = cardScale.value;
        const spacing = cardSpacing.value;
        const minWidth = baseWidth * scale;
        const gap = Math.max(6, (baseGap + (scale - 1) * 10) * spacing);

        const containerWidth = Math.max(gridWidth.value ?? 0, 0);
        const itemCount = Math.max(Number(count) || 0, 0);
        const safeCount = itemCount > 0 ? itemCount : 1;
        const maxColumns = Math.max(1, Math.floor((containerWidth + gap) / (minWidth + gap)) || 1);
        const preferredColumns = options?.preferredColumns;
        const requestedColumns = preferredColumns
            ? Math.max(1, Math.min(Math.round(preferredColumns), maxColumns))
            : maxColumns;
        const columns = Math.max(1, Math.min(safeCount, requestedColumns));
        const forceStretch = Boolean(options?.forceStretch);
        const disableAutoStretch = Boolean(options?.disableAutoStretch);
        const matchMaxColumnWidth = Boolean(options?.matchMaxColumnWidth);
        const shouldStretch = !disableAutoStretch && (forceStretch || itemCount >= maxColumns);

        let cardWidth = minWidth;
        const maxColumnWidth = maxColumns > 0 ? (containerWidth - gap * (maxColumns - 1)) / maxColumns : minWidth;

        if (shouldStretch && columns > 0) {
            const columnsWidth = containerWidth - gap * (columns - 1);
            const rawWidth = columnsWidth > 0 ? columnsWidth / columns : minWidth;

            if (Number.isFinite(rawWidth) && rawWidth > 0) {
                cardWidth = Math.max(minWidth, rawWidth);
            }
        } else if (matchMaxColumnWidth && Number.isFinite(maxColumnWidth) && maxColumnWidth > 0) {
            cardWidth = Math.max(minWidth, maxColumnWidth);
        }

        return {
            minWidth,
            gap,
            columns,
            cardWidth
        };
    };

    const chunkCardItems = (items = [], keyPrefix = 'row') => {
        const safeItems = Array.isArray(items) ? items : [];
        if (!safeItems.length) {
            return [];
        }
        const { columns } = getGridMetrics(safeItems.length, { matchMaxColumnWidth: true });
        const safeColumns = Math.max(1, columns || 1);
        const rows = [];

        for (let index = 0; index < safeItems.length; index += safeColumns) {
            rows.push({
                type: 'cards',
                key: `${keyPrefix}:${index}`,
                items: safeItems.slice(index, index + safeColumns)
            });
        }

        return rows;
    };

    const virtualRows = computed(() => {
        const rows = [];

        if (isSameInstanceView.value) {
            for (const group of sameInstanceGroupsForVirtual.value) {
                rows.push({
                    type: 'header',
                    key: `h:${group.instanceId}`,
                    instanceId: group.instanceId,
                    count: Array.isArray(group.friends) ? group.friends.length : 0
                });

                const friends = Array.isArray(group.friends) ? group.friends : [];
                if (friends.length) {
                    const items = friends.map((friend) => ({
                        key: `f:${friend?.id ?? friend?.userId ?? friend?.displayName ?? Math.random()}`,
                        friend,
                        displayInstanceInfo: true
                    }));
                    rows.push(...chunkCardItems(items, `g:${group.instanceId}`));
                }
            }

            return rows;
        }

        if (shouldMergeSameInstance.value) {
            for (const group of mergedSameInstanceGroups.value) {
                rows.push({
                    type: 'header',
                    key: `h:${group.instanceId}`,
                    instanceId: group.instanceId,
                    count: Array.isArray(group.friends) ? group.friends.length : 0
                });

                const friends = Array.isArray(group.friends) ? group.friends : [];
                if (friends.length) {
                    const items = friends.map((friend) => ({
                        key: `f:${friend?.id ?? friend?.userId ?? friend?.displayName ?? Math.random()}`,
                        friend,
                        displayInstanceInfo: false
                    }));
                    rows.push(...chunkCardItems(items, `mg:${group.instanceId}`));
                }
            }

            if (mergedSameInstanceGroups.value.length && mergedOnlineEntries.value.length) {
                rows.push({ type: 'divider', key: 'divider:merged' });
            }

            const online = mergedOnlineEntries.value;
            if (online.length) {
                const items = online.map((entry) => ({
                    key: `e:${entry?.id ?? entry?.friend?.id ?? entry?.friend?.displayName ?? Math.random()}`,
                    friend: entry.friend,
                    displayInstanceInfo: true
                }));
                rows.push(...chunkCardItems(items, 'o:merged'));
            }

            return rows;
        }

        const entries = filteredFriends.value;
        if (entries.length) {
            const items = entries.map((entry) => ({
                key: `e:${entry?.id ?? entry?.friend?.id ?? entry?.friend?.displayName ?? Math.random()}`,
                friend: entry.friend,
                displayInstanceInfo: true
            }));
            rows.push(...chunkCardItems(items, 'r:all'));
        }
        return rows;
    });

    const virtualListStyle = computed(() => {
        const styleFn = gridStyle.value;
        const total = filteredFriends.value.length;

        // Use matchMaxColumnWidth so rows don't collapse to minWidth on short rows.
        const vars = typeof styleFn === 'function' ? styleFn(total, { matchMaxColumnWidth: true }) : {};
        return {
            ...vars
        };
    });

    const estimateRowSize = (row) => {
        if (!row) {
            return 48;
        }
        if (row.type === 'header') {
            return 32;
        }
        if (row.type === 'divider') {
            return 36;
        }

        const itemCount = Array.isArray(row.items) ? row.items.length : 0;
        const { columns, gap } = getGridMetrics(itemCount, { matchMaxColumnWidth: true });
        const safeColumns = Math.max(1, columns || 1);
        const rows = Math.max(1, Math.ceil(itemCount / safeColumns));
        const scale = cardScale.value;
        const spacing = cardSpacing.value;
        const baseCardHeight = 150;
        const cardHeight = baseCardHeight * scale * spacing;
        const rowGap = Math.max(0, gap - 4);

        return rows * cardHeight + (rows - 1) * rowGap + 8;
    };

    const virtualizer = useVirtualizer(
        computed(() => ({
            count: virtualRows.value.length,
            getScrollElement: () => scrollbarRef.value,
            estimateSize: (index) => estimateRowSize(virtualRows.value[index]),
            overscan: 5
        }))
    );

    const virtualItems = computed(() => {
        const items = virtualizer.value?.getVirtualItems?.() ?? [];
        return items.map((virtualItem) => ({
            virtualItem,
            row: virtualRows.value[virtualItem.index]
        }));
    });

    const virtualContainerStyle = computed(() => ({
        ...virtualListStyle.value,
        height: `${virtualizer.value?.getTotalSize?.() ?? 0}px`
    }));

    const getRowItems = (row) => (row && Array.isArray(row.items) ? row.items : []);
    const getRowInstanceId = (row) => (row && row.type === 'header' ? row.instanceId : '');
    const getRowCount = (row) => (row && row.type === 'header' ? row.count : 0);

    watch([searchTerm, activeSegment], () => {
        nextTick(() => {
            updateGridWidth();
            virtualizer.value?.measure?.();
        });
    });

    watch(showSameInstance, (value) => {
        if (!settingsReady.value) {
            return;
        }
        if (!value && activeSegment.value === 'same-instance') {
            activeSegment.value = 'online';
        }

        nextTick(() => {
            updateGridWidth();
            virtualizer.value?.measure?.();
        });
    });

    watch(
        () => filteredFriends.value.length,
        () => {
            nextTick(() => {
                updateGridWidth();
                virtualizer.value?.measure?.();
            });
        }
    );

    watch([cardScale, cardSpacing], () => {
        if (!settingsReady.value) {
            return;
        }
        nextTick(() => {
            updateGridWidth();
            virtualizer.value?.measure?.();
        });
    });

    watch(virtualRows, () => {
        nextTick(() => {
            virtualizer.value?.measure?.();
        });
    });

    onMounted(() => {
        nextTick(() => {
            setupResizeHandling();
            updateGridWidth();
            virtualizer.value?.measure?.();
        });
    });

    onBeforeUnmount(() => {
        if (cleanupResize) {
            cleanupResize();
            cleanupResize = undefined;
        }
    });

    async function loadInitialSettings() {
        try {
            const [storedScale, storedSpacing, storedShowSameInstance] = await Promise.all([
                configRepository.getString('VRCX_FriendLocationCardScale', '1'),
                configRepository.getString('VRCX_FriendLocationCardSpacing', '1'),
                configRepository.getBool('VRCX_FriendLocationShowSameInstance', null)
            ]);

            const parsedScale = parseFloat(storedScale);
            if (!Number.isNaN(parsedScale) && parsedScale > 0) {
                cardScaleBase.value = parsedScale;
            }

            const parsedSpacing = parseFloat(storedSpacing);
            if (!Number.isNaN(parsedSpacing) && parsedSpacing > 0) {
                cardSpacingBase.value = parsedSpacing;
            }

            if (storedShowSameInstance !== null && storedShowSameInstance !== undefined) {
                showSameInstanceBase.value = Boolean(storedShowSameInstance);
            }
        } catch (error) {
            console.error('Failed to load Friend Location preferences', error);
        } finally {
            settingsReady.value = true;
            nextTick(() => {
                setupResizeHandling();
                updateGridWidth();
                virtualizer.value?.measure?.();
            });
        }
    }

    onBeforeMount(() => {
        loadInitialSettings();
    });
</script>

<style scoped>
    .friend-view {
        display: grid;
        grid-template-rows: auto 1fr;
        gap: 16px;
        min-height: 0;
        height: 100%;
        overflow: hidden;
    }

    .friend-view.x-container {
        overflow: hidden;
    }

    .friend-view__toolbar {
        display: flex;
        gap: 20px;
        align-items: center;
        padding: 6px 2px 0 2px;
    }

    .friend-view__tabs {
        gap: 0;
    }

    .friend-view__toolbar--loading {
        justify-content: flex-end;
        font-size: 13px;
        font-weight: 500;
    }

    .friend-view__settings {
        display: grid;
        gap: 12px;
    }

    .friend-view__loading-text {
        padding-right: 12px;
    }

    .friend-view__actions {
        display: flex;
        align-items: center;
        gap: 12px;
        flex: 1;
        flex-wrap: wrap;
        justify-content: flex-end;
    }

    .friend-view__virtual {
        width: 100%;
        padding: 2px;
        box-sizing: border-box;
        position: relative;
    }

    .friend-view__virtual-row {
        width: 100%;
        box-sizing: border-box;
        position: absolute;
        left: 0;
        top: 0;
        padding-bottom: calc(var(--friend-card-gap, 14px) - 4px);
    }

    .friend-view__virtual-row--header {
        padding: 4px 10px;
        padding-bottom: calc(var(--friend-card-gap, 14px) - 4px);
    }

    .friend-view__virtual-row--divider {
        padding: 16px 4px;
        padding-bottom: calc(var(--friend-card-gap, 14px) - 4px);
    }

    .friend-view__virtual-row--cards {
        padding: 2px;
        padding-bottom: calc(var(--friend-card-gap, 14px) - 4px);
    }

    .friend-view__row {
        display: grid;
        grid-template-columns: repeat(
            var(--friend-grid-columns, 1),
            minmax(var(--friend-card-min-width, 200px), var(--friend-card-target-width, 1fr))
        );
        gap: var(--friend-card-gap, 14px);
        justify-content: start;
        padding: 2px;
        box-sizing: border-box;
    }

    .friend-view__settings-label {
        font-size: 13px;
        font-weight: 500;
        margin-right: 8px;
    }

    .friend-view__settings-row {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
    }

    .friend-view__scale-control {
        display: flex;
        align-items: center;
        gap: 10px;
        min-width: 160px;
    }

    .friend-view__scale-value {
        font-size: 12px;
        font-weight: 600;
        min-width: 42px;
        text-align: right;
    }

    .friend-view__slider {
        width: 160px;
        margin-right: 12px;
    }

    .friend-view__search {
        width: 240px;
        flex: 1;
    }

    .friend-view__scroll {
        overflow: auto;
        min-height: 0;
        height: 100%;
    }

    .friend-view__initial-loading {
        display: grid;
        place-items: center;
        min-height: 240px;
    }

    .friend-view__instance-header {
        display: flex;
        align-items: center;
        padding: 4px 2px;
        font-weight: 600;
        font-size: 13px;
    }

    .friend-view__divider {
        display: flex;
        align-items: center;
        gap: 12px;
        font-size: 13px;
        font-weight: 600;
    }

    .friend-view__divider::before,
    .friend-view__divider::after {
        content: '';
        flex: 1;
        height: 1px;
    }

    .friend-view__divider-text {
        flex: none;
    }

    .friend-view__instance-count {
        font-size: 12px;
    }

    .friend-view__empty {
        display: grid;
        place-items: center;
        min-height: 240px;
        font-size: 15px;
        letter-spacing: 0.5px;
    }

    .friend-view__loading-icon {
        animation: spin 1s linear infinite;
    }

    @keyframes spin {
        from {
            transform: rotate(0);
        }
        to {
            transform: rotate(360deg);
        }
    }
</style>
