<script setup lang="ts">
import { formatServerDateTime } from '@sammo-ts/common';
import { computed, ref, onMounted, onUnmounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import type { inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from '@sammo-ts/gateway-api';
import { writeGameSessionTransfer } from '@sammo-ts/common/auth/gameSessionTransfer';
import DefaultLayout from '../layouts/DefaultLayout.vue';
import MapPreview from '../components/MapPreview.vue';
import { useToast } from '../composables/useToast';
import { trpc } from '../utils/trpc';
import { createGameTrpc } from '../utils/gameTrpc';
import type { GameRouter } from '../utils/gameTrpc';
import { resolveServerSeasonStatus } from '../utils/serverSeasonStatus';
import { configuredSharedIconPublicUrl, configuredUserIconPublicUrl } from '../utils/imageAssets';

type GatewayRouterOutput = inferRouterOutputs<AppRouter>;
type GameRouterOutput = inferRouterOutputs<GameRouter>;
type MeOutput = GatewayRouterOutput['me'];
type LobbyProfile = GatewayRouterOutput['lobby']['profiles'][number];
type LobbyInfo = GameRouterOutput['lobby']['info'];
type LobbyGeneral = NonNullable<LobbyInfo['myGeneral']>;
type PublicMap = GameRouterOutput['public']['getCachedMap'];
type PublicMapLayout = GameRouterOutput['public']['getMapLayout'];
type MapPreviewBundle = {
    mapData: PublicMap;
    mapLayout: PublicMapLayout;
};
type ProfileLoadState = {
    status: 'loading' | 'retrying' | 'ready';
    failures: number;
};

const PROFILE_REQUEST_TIMEOUT_MS = 10_000;
const PROFILE_RETRY_DELAYS_MS = [1_000, 2_000, 3_000, 5_000, 8_000, 15_000] as const;

const router = useRouter();
const me = ref<MeOutput>(null);
const notice = ref('');
const profiles = ref<LobbyProfile[]>([]);
const profileDetails = ref<Record<string, LobbyInfo | undefined>>({});
const profileMapPreviews = ref<Record<string, MapPreviewBundle | undefined>>({});
const profileLoadStates = ref<Record<string, ProfileLoadState | undefined>>({});
const selectedMapProfileName = ref<string | null>(null);
const entryLoading = ref<Record<string, boolean>>({});
const logoutLoading = ref(false);
const logoutError = ref('');
const { error: showErrorToast } = useToast();
const profileRetryTimers = new Map<string, number>();
const profileRequestControllers = new Map<string, AbortController>();
let lobbyMounted = true;

watch(logoutError, (value) => value && showErrorToast(value), { flush: 'sync' });
const canAccessAdmin = computed(
    () =>
        me.value?.roles.some(
            (role) =>
                role === 'superuser' || role === 'admin' || role === 'admin.superuser' || role.startsWith('admin.')
        ) ?? false
);
const needsKakaoVerification = computed(
    () =>
        me.value !== null &&
        !me.value.kakaoVerified &&
        profiles.value.some((profile) => profile.localAccountPolicy?.requiresKakaoVerification === true)
);
const userIconBaseUrl = configuredUserIconPublicUrl();
const sharedIconBaseUrl = configuredSharedIconPublicUrl();
const publicMapProfiles = computed(() =>
    profiles.value.filter((profile) => profile.status === 'RUNNING' || profile.status === 'PREOPEN')
);
const selectedMapProfile = computed(
    () => publicMapProfiles.value.find((profile) => profile.profileName === selectedMapProfileName.value) ?? null
);
const selectedMapPreview = computed(() =>
    selectedMapProfileName.value ? profileMapPreviews.value[selectedMapProfileName.value] : undefined
);

watch(publicMapProfiles, (availableProfiles) => {
    if (!availableProfiles.some((profile) => profile.profileName === selectedMapProfileName.value)) {
        selectedMapProfileName.value = availableProfiles[0]?.profileName ?? null;
    }
});

const selectMapProfile = (profileName: string): void => {
    selectedMapProfileName.value = profileName;
};

const handleMapTabKeydown = (event: KeyboardEvent, profileName: string): void => {
    const currentIndex = publicMapProfiles.value.findIndex((profile) => profile.profileName === profileName);
    if (currentIndex < 0) return;

    let nextIndex: number | null = null;
    if (event.key === 'ArrowRight') nextIndex = (currentIndex + 1) % publicMapProfiles.value.length;
    if (event.key === 'ArrowLeft') {
        nextIndex = (currentIndex - 1 + publicMapProfiles.value.length) % publicMapProfiles.value.length;
    }
    if (event.key === 'Home') nextIndex = 0;
    if (event.key === 'End') nextIndex = publicMapProfiles.value.length - 1;
    if (nextIndex === null) return;

    event.preventDefault();
    selectedMapProfileName.value = publicMapProfiles.value[nextIndex]?.profileName ?? null;
    const tabButtons = (event.currentTarget as HTMLElement).parentElement?.querySelectorAll<HTMLButtonElement>(
        '[role="tab"]'
    );
    tabButtons?.[nextIndex]?.focus();
};

const formatGraceEndsAt = (value: string | null | undefined): string => formatServerDateTime(value);
const serverSeasonStatus = (info: LobbyInfo) => resolveServerSeasonStatus(info);
const profileLoadState = (profileName: string): ProfileLoadState | undefined => profileLoadStates.value[profileName];
const setProfileLoadState = (profileName: string, state: ProfileLoadState): void => {
    profileLoadStates.value = {
        ...profileLoadStates.value,
        [profileName]: state,
    };
};
const clearProfileRetry = (profileName: string): void => {
    const timer = profileRetryTimers.get(profileName);
    if (timer !== undefined) {
        window.clearTimeout(timer);
        profileRetryTimers.delete(profileName);
    }
};
const requestOptions = (componentSignal: AbortSignal): { signal: AbortSignal } => ({
    signal: AbortSignal.any([componentSignal, AbortSignal.timeout(PROFILE_REQUEST_TIMEOUT_MS)]),
});
const encodeLegacyIconPath = (value: string): string =>
    value
        .split('/')
        .map((segment) => {
            if (segment === '.') return '%2E';
            if (segment === '..') return '%2E%2E';
            return encodeURIComponent(segment);
        })
        .join('/');
const resolveGeneralPicture = (general: LobbyGeneral): string => {
    const picture = general.picture?.trim() || 'default.jpg';
    return general.imageServer
        ? `${userIconBaseUrl.replace(/\/$/, '')}/${encodeLegacyIconPath(picture)}`
        : `${sharedIconBaseUrl}/${encodeLegacyIconPath(picture)}`;
};
const handleGeneralPictureError = (event: Event): void => {
    const image = event.currentTarget as HTMLImageElement;
    if (image.dataset.generalIconFallbackSource === image.currentSrc) {
        return;
    }
    image.dataset.generalIconFallbackSource = image.currentSrc;
    image.src = `${sharedIconBaseUrl}/default.jpg`;
};

const loadProfileDetails = async (profile: LobbyProfile, sessionToken: string | null): Promise<void> => {
    if (!lobbyMounted || (profile.status !== 'RUNNING' && profile.status !== 'PREOPEN')) {
        return;
    }
    clearProfileRetry(profile.profileName);
    profileRequestControllers.get(profile.profileName)?.abort();
    const requestController = new AbortController();
    profileRequestControllers.set(profile.profileName, requestController);
    const previousFailures = profileLoadState(profile.profileName)?.failures ?? 0;
    setProfileLoadState(profile.profileName, { status: 'loading', failures: previousFailures });

    const publicGameTrpc = createGameTrpc(profile.profile, profile.apiPort);
    let gameTrpc = publicGameTrpc;
    let authenticated = sessionToken === null;
    if (sessionToken) {
        try {
            const issued = await trpc.auth.issueGameSession.mutate(
                {
                    sessionToken,
                    profile: profile.profileName,
                },
                requestOptions(requestController.signal)
            );
            const exchanged = await publicGameTrpc.auth.exchangeGatewayToken.mutate(
                {
                    gatewayToken: issued.gameToken,
                },
                requestOptions(requestController.signal)
            );
            gameTrpc = createGameTrpc(profile.profile, profile.apiPort, exchanged.accessToken);
            authenticated = true;
        } catch (error) {
            console.error(`Failed to authenticate lobby game session for ${profile.profileName}`, error);
        }
    }

    const [infoResult, layoutResult, mapResult] = await Promise.allSettled([
        gameTrpc.lobby.info.query(undefined, requestOptions(requestController.signal)),
        gameTrpc.public.getMapLayout.query(undefined, requestOptions(requestController.signal)),
        gameTrpc.public.getCachedMap.query(undefined, requestOptions(requestController.signal)),
    ]);
    if (profileRequestControllers.get(profile.profileName) !== requestController) {
        return;
    }
    profileRequestControllers.delete(profile.profileName);
    if (!lobbyMounted) {
        return;
    }

    if (infoResult.status === 'fulfilled') {
        profileDetails.value[profile.profileName] = infoResult.value;
    } else {
        console.error(`Failed to fetch info for ${profile.profileName}`, infoResult.reason);
    }
    if (layoutResult.status === 'fulfilled' && mapResult.status === 'fulfilled') {
        profileMapPreviews.value[profile.profileName] = {
            mapLayout: layoutResult.value,
            mapData: mapResult.value,
        };
    }

    const fullyLoaded =
        authenticated &&
        infoResult.status === 'fulfilled' &&
        layoutResult.status === 'fulfilled' &&
        mapResult.status === 'fulfilled';
    if (fullyLoaded) {
        setProfileLoadState(profile.profileName, { status: 'ready', failures: 0 });
        return;
    }

    const failures = previousFailures + 1;
    setProfileLoadState(profile.profileName, { status: 'retrying', failures });
    const retryDelay = PROFILE_RETRY_DELAYS_MS[Math.min(failures - 1, PROFILE_RETRY_DELAYS_MS.length - 1)];
    const timer = window.setTimeout(() => {
        profileRetryTimers.delete(profile.profileName);
        void loadProfileDetails(profile, sessionToken);
    }, retryDelay);
    profileRetryTimers.set(profile.profileName, timer);
};

const retryProfileDetails = (profile: LobbyProfile): void => {
    clearProfileRetry(profile.profileName);
    setProfileLoadState(profile.profileName, { status: 'loading', failures: 0 });
    void loadProfileDetails(profile, window.localStorage.getItem('sammo-session-token'));
};

onMounted(async () => {
    try {
        me.value = await trpc.me.query();
        if (!me.value) {
            await router.push('/');
            return;
        }

        notice.value = await trpc.lobby.notice.query();
        profiles.value = await trpc.lobby.profiles.query();
        const sessionToken = window.localStorage.getItem('sammo-session-token');
        await Promise.all(profiles.value.map((profile) => loadProfileDetails(profile, sessionToken)));
    } catch (e) {
        console.error('Failed to load lobby', e);
    }
});

onUnmounted(() => {
    lobbyMounted = false;
    for (const timer of profileRetryTimers.values()) {
        window.clearTimeout(timer);
    }
    profileRetryTimers.clear();
    for (const controller of profileRequestControllers.values()) {
        controller.abort();
    }
    profileRequestControllers.clear();
});

const handleLogout = async () => {
    if (logoutLoading.value) {
        return;
    }
    logoutError.value = '';
    const sessionToken = window.localStorage.getItem('sammo-session-token');
    if (!sessionToken) {
        await router.replace('/');
        return;
    }
    logoutLoading.value = true;
    try {
        await trpc.auth.logout.mutate({ sessionToken });
        window.localStorage.removeItem('sammo-session-token');
        window.localStorage.removeItem('sammo-game-token');
        window.localStorage.removeItem('sammo-game-profile');
        me.value = null;
        await router.replace('/');
    } catch (error) {
        logoutError.value = error instanceof Error ? error.message : '로그아웃에 실패했습니다.';
    } finally {
        logoutLoading.value = false;
    }
};

const handleKakaoVerification = async (): Promise<void> => {
    const sessionToken = window.localStorage.getItem('sammo-session-token');
    if (!sessionToken) {
        await router.push('/');
        return;
    }
    try {
        const result = await trpc.auth.kakaoStart.query({
            mode: 'verify',
        });
        window.location.assign(result.authUrl);
    } catch (error) {
        showErrorToast(error instanceof Error ? error.message : '카카오 인증을 시작하지 못했습니다.');
    }
};

const resolveGameUrl = (path: string, profileName: string, gameToken: string): string | null => {
    const profile = profileName.split(':', 1)[0] ?? profileName;
    const baseUrl =
        import.meta.env.VITE_GAME_WEB_URL ??
        import.meta.env.VITE_GAME_WEB_URL_TEMPLATE?.replaceAll('{profile}', encodeURIComponent(profile)) ??
        '';
    if (!baseUrl) {
        return null;
    }
    const base = new URL(baseUrl, window.location.origin);
    const normalizedPath = path.replace(/^\//, '');
    const url = new URL(normalizedPath, base);
    let transferredInSessionStorage = false;
    if (url.origin === window.location.origin) {
        try {
            transferredInSessionStorage = writeGameSessionTransfer(window.sessionStorage, {
                profile: profileName,
                gatewayToken: gameToken,
            });
        } catch {
            transferredInSessionStorage = false;
        }
    }
    if (!transferredInSessionStorage) {
        url.searchParams.set('profile', profileName);
        url.searchParams.set('gameToken', gameToken);
    }
    return url.toString();
};

const handleEnter = async (profile: LobbyProfile, targetPath: string) => {
    if (entryLoading.value[profile.profileName]) {
        return;
    }
    const sessionToken = window.localStorage.getItem('sammo-session-token');
    if (!sessionToken) {
        await router.push('/');
        return;
    }
    entryLoading.value[profile.profileName] = true;
    try {
        const issued = await trpc.auth.issueGameSession.mutate({
            sessionToken,
            profile: profile.profileName,
        });
        const url = resolveGameUrl(targetPath, issued.profile, issued.gameToken);
        if (!url) {
            showErrorToast('게임 프론트엔드 주소가 설정되지 않았습니다.');
            return;
        }
        window.location.href = url;
    } catch (e) {
        console.error('Failed to issue game session', e);
        showErrorToast(e instanceof Error ? e.message : '게임 서버 접속에 실패했습니다.');
    } finally {
        entryLoading.value[profile.profileName] = false;
    }
};
</script>

<template>
    <DefaultLayout>
        <div class="max-w-5xl mx-auto pt-24 pb-8 px-4 space-y-8">
            <!-- Notice -->
            <div v-if="notice" class="text-center">
                <!-- eslint-disable-next-line vue/no-v-html -->
                <span data-testid="gateway-notice" class="text-orange-500 text-3xl font-bold" v-html="notice"></span>
            </div>

            <section
                v-if="needsKakaoVerification"
                id="kakao-verification-banner"
                class="bg-amber-950/70 border border-amber-600 rounded p-4 text-amber-100"
            >
                <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
                    <div>
                        <strong class="block text-amber-300">카카오 인증이 필요합니다.</strong>
                        <span class="text-sm">
                            유예기간이 지나면 게임에 입장할 수 없습니다. che·kwe·twe는 인증 전 장수 생성도 제한됩니다.
                        </span>
                    </div>
                    <button
                        id="connect-kakao"
                        class="shrink-0 bg-yellow-400 hover:bg-yellow-300 text-black font-bold px-5 py-2 rounded"
                        type="button"
                        @click="handleKakaoVerification"
                    >
                        카카오 인증 연결
                    </button>
                </div>
            </section>

            <!-- Server List Table -->
            <div class="bg-zinc-900 border border-zinc-800 rounded shadow-xl overflow-hidden">
                <div
                    class="bg-zinc-800 px-6 py-3 text-center font-bold text-white border-b border-zinc-700 text-xl tracking-widest"
                >
                    서 버 선 택
                </div>
                <div class="overflow-x-auto" data-testid="profile-table-scroll">
                    <table class="w-full min-w-[760px] text-sm text-left">
                        <thead class="bg-zinc-800 text-zinc-400 uppercase text-xs">
                            <tr>
                                <th class="px-4 py-3 border-b border-zinc-700 w-24 text-center">서 버</th>
                                <th class="px-4 py-3 border-b border-zinc-700">정 보</th>
                                <th class="px-4 py-3 border-b border-zinc-700 w-48 text-center" colspan="2">
                                    캐 릭 터
                                </th>
                                <th class="px-4 py-3 border-b border-zinc-700 w-32 text-center">선 택</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-zinc-800">
                            <tr
                                v-for="profile in profiles"
                                :key="profile.profileName"
                                class="hover:bg-zinc-800/50 transition-colors"
                            >
                                <!-- Server Name -->
                                <td class="px-4 py-4 text-center border-r border-zinc-800">
                                    <div
                                        :style="{ color: profile.color }"
                                        class="text-lg font-bold cursor-help"
                                        :title="
                                            profileDetails[profile.profileName]
                                                ? serverSeasonStatus(profileDetails[profile.profileName]!).period
                                                : ''
                                        "
                                    >
                                        {{ profile.korName }}섭
                                    </div>
                                    <div
                                        v-if="profileDetails[profile.profileName]"
                                        class="season-status mt-1 whitespace-nowrap text-xs text-zinc-500"
                                    >
                                        {{ serverSeasonStatus(profileDetails[profile.profileName]!).label }}
                                    </div>
                                    <div
                                        v-if="profile.localAccountPolicy?.specialAccess"
                                        class="mt-2 text-xs text-emerald-300"
                                    >
                                        특수 접근 · {{ profile.localAccountPolicy.specialAccess.kind }}
                                    </div>
                                    <div
                                        v-else-if="
                                            profile.localAccountPolicy?.requiresKakaoVerification &&
                                            !profile.localAccountPolicy.canCreateGeneral
                                        "
                                        class="mt-2 text-xs text-red-400"
                                    >
                                        인증 전 생성 불가
                                    </div>
                                    <div
                                        v-else-if="profile.localAccountPolicy?.requiresKakaoVerification"
                                        class="mt-2 text-xs text-amber-300"
                                    >
                                        {{ formatGraceEndsAt(profile.localAccountPolicy.graceEndsAt) }}까지 유예
                                    </div>
                                </td>

                                <!-- Server Info -->
                                <td class="px-4 py-4 border-r border-zinc-800">
                                    <template v-if="profileDetails[profile.profileName]">
                                        <div class="space-y-1">
                                            <div>
                                                서기 {{ profileDetails[profile.profileName]?.year }}년
                                                {{ profileDetails[profile.profileName]?.month }}월 (<span
                                                    class="text-orange-400"
                                                    >{{ profile.scenario }}</span
                                                >)
                                            </div>
                                            <div class="text-zinc-400">
                                                유저 : {{ profileDetails[profile.profileName]?.userCnt }} /
                                                {{ profileDetails[profile.profileName]?.maxUserCnt }}명
                                                <span class="text-cyan-400 ml-2"
                                                    >NPC : {{ profileDetails[profile.profileName]?.npcCnt }}명</span
                                                >
                                                <span class="text-green-400 ml-2"
                                                    >({{ profileDetails[profile.profileName]?.turnTerm }}분 턴
                                                    서버)</span
                                                >
                                            </div>
                                            <div class="text-xs text-zinc-500">
                                                (상성 설정:{{ profileDetails[profile.profileName]?.fictionMode }}),
                                                (기타 설정:{{ profileDetails[profile.profileName]?.otherTextInfo }})
                                            </div>
                                        </div>
                                    </template>
                                    <template v-else-if="profile.status === 'STOPPED'">
                                        <div class="text-center text-zinc-600 py-2">- 폐 쇄 중 -</div>
                                    </template>
                                    <template v-else-if="profileLoadState(profile.profileName)?.status === 'retrying'">
                                        <div
                                            class="text-center text-zinc-500 py-1"
                                            role="status"
                                            data-testid="profile-info-retrying"
                                        >
                                            <div>서버 응답을 기다리고 있습니다.</div>
                                            <button
                                                type="button"
                                                class="mt-2 text-xs text-orange-300 underline underline-offset-2 hover:text-orange-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-orange-300"
                                                @click="retryProfileDetails(profile)"
                                            >
                                                지금 다시 확인
                                            </button>
                                        </div>
                                    </template>
                                    <template v-else>
                                        <div class="text-center text-zinc-500 py-2">정보를 불러오는 중...</div>
                                    </template>
                                </td>

                                <!-- Character Info -->
                                <td class="px-2 py-4 w-16 border-r border-zinc-800">
                                    <div
                                        v-if="profileDetails[profile.profileName]?.myGeneral"
                                        class="w-12 h-12 mx-auto bg-zinc-800 rounded overflow-hidden border border-zinc-700"
                                    >
                                        <img
                                            :src="
                                                resolveGeneralPicture(profileDetails[profile.profileName]!.myGeneral!)
                                            "
                                            class="w-full h-full object-cover"
                                            @error="handleGeneralPictureError"
                                        />
                                    </div>
                                </td>
                                <td class="px-4 py-4 border-r border-zinc-800 text-center">
                                    <div v-if="profileDetails[profile.profileName]?.myGeneral" class="font-medium">
                                        {{ profileDetails[profile.profileName]?.myGeneral?.name }}
                                    </div>
                                    <div v-else class="text-zinc-600">- 미 등 록 -</div>
                                </td>

                                <!-- Action -->
                                <td class="px-4 py-4 text-center">
                                    <template v-if="profileDetails[profile.profileName]">
                                        <button
                                            v-if="profileDetails[profile.profileName]?.myGeneral"
                                            class="w-full bg-zinc-700 hover:bg-zinc-600 text-white py-1.5 rounded text-sm transition-colors"
                                            :disabled="entryLoading[profile.profileName]"
                                            @click="handleEnter(profile, '/')"
                                        >
                                            입장
                                        </button>
                                        <div
                                            v-else-if="
                                                profileDetails[profile.profileName]!.userCnt >=
                                                profileDetails[profile.profileName]!.maxUserCnt
                                            "
                                            class="text-zinc-500"
                                        >
                                            - 장수 등록 마감 -
                                        </div>
                                        <div v-else class="grid gap-1">
                                            <button
                                                v-if="profileDetails[profile.profileName]?.selectionPoolEnabled"
                                                class="w-full bg-zinc-700 hover:bg-zinc-600 text-white py-1.5 rounded text-sm transition-colors"
                                                :disabled="entryLoading[profile.profileName]"
                                                @click="handleEnter(profile, '/select-general')"
                                            >
                                                장수선택
                                            </button>
                                            <template v-else>
                                                <button
                                                    class="w-full bg-zinc-700 hover:bg-zinc-600 text-white py-1.5 rounded text-sm transition-colors"
                                                    :disabled="
                                                        entryLoading[profile.profileName] ||
                                                        profile.localAccountPolicy?.canCreateGeneral === false
                                                    "
                                                    @click="handleEnter(profile, '/join')"
                                                >
                                                    {{
                                                        profile.localAccountPolicy?.canCreateGeneral === false
                                                            ? '인증 필요'
                                                            : '장수생성'
                                                    }}
                                                </button>
                                                <button
                                                    v-if="profileDetails[profile.profileName]?.npcPossessionEnabled"
                                                    class="w-full bg-zinc-700 hover:bg-zinc-600 text-white py-1.5 rounded text-sm transition-colors"
                                                    :disabled="
                                                        entryLoading[profile.profileName] ||
                                                        profile.localAccountPolicy?.canCreateGeneral === false
                                                    "
                                                    @click="handleEnter(profile, '/join?tab=possess')"
                                                >
                                                    장수빙의
                                                </button>
                                            </template>
                                        </div>
                                    </template>
                                    <template v-else-if="profile.status === 'STOPPED'">
                                        <span class="text-zinc-700">-</span>
                                    </template>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
                <!-- Footer Info -->
                <div class="bg-zinc-800/50 p-4 text-xs text-zinc-500 space-y-2 border-t border-zinc-800">
                    <p class="text-red-500 font-bold">
                        ★ 1명이 2개 이상의 계정을 사용하거나 타 유저의 턴을 대신 입력하는 것이 적발될 경우 차단 될 수
                        있습니다.
                    </p>
                    <p>계정은 한번 등록으로 계속 사용합니다. 각 서버 리셋시 캐릭터만 새로 생성하면 됩니다.</p>
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-1 mt-2">
                        <p>
                            <span class="text-zinc-300 font-bold">체섭</span> : 메인서버입니다. 천하통일에 도전하여
                            왕조일람과 명예의전당에 올라봅시다! (주로 1턴=60분)
                        </p>
                        <p>
                            <span class="text-zinc-300 font-bold">퀘섭</span> : 마이너 서버 그룹1. 비교적 느린 시간으로
                            운영됩니다.
                        </p>
                        <p>
                            <span class="text-zinc-300 font-bold">풰섭</span> : 마이너 서버 그룹1. 비교적 느린 시간으로
                            운영됩니다.
                        </p>
                        <p>
                            <span class="text-zinc-300 font-bold">퉤섭</span> : 마이너 서버 그룹2. 비교적 빠른 시간으로
                            운영됩니다.
                        </p>
                        <p>
                            <span class="text-zinc-300 font-bold">냐섭</span> : 마이너 서버 그룹3. 독특한 컨셉 위주로
                            운영됩니다.
                        </p>
                        <p>
                            <span class="text-zinc-300 font-bold">퍄섭</span> : 마이너 서버 그룹3. 독특한 컨셉 위주로
                            운영됩니다.
                        </p>
                        <p>
                            <span class="text-zinc-300 font-bold">훼섭</span> : 운영자 테스트 서버입니다. 기습적으로
                            열리고, 닫힐 수 있습니다.
                        </p>
                    </div>
                </div>
            </div>

            <div class="bg-zinc-900 border border-zinc-800 rounded shadow-xl overflow-hidden">
                <div
                    class="bg-zinc-800 px-6 py-2 text-center font-bold text-white border-b border-zinc-700 tracking-widest"
                >
                    공개 지도 미리보기
                </div>
                <div class="p-4">
                    <div
                        v-if="publicMapProfiles.length"
                        class="map-preview-tabs"
                        role="tablist"
                        aria-label="공개 지도 서버 선택"
                    >
                        <button
                            v-for="profile in publicMapProfiles"
                            :id="`map-preview-tab-${profile.profileName}`"
                            :key="profile.profileName"
                            type="button"
                            role="tab"
                            :aria-selected="selectedMapProfileName === profile.profileName"
                            :aria-controls="`map-preview-panel-${profile.profileName}`"
                            :tabindex="selectedMapProfileName === profile.profileName ? 0 : -1"
                            class="map-preview-tab"
                            :class="{ 'is-active': selectedMapProfileName === profile.profileName }"
                            :style="{ '--profile-color': profile.color }"
                            @mouseenter="selectMapProfile(profile.profileName)"
                            @focus="selectMapProfile(profile.profileName)"
                            @click="selectMapProfile(profile.profileName)"
                            @keydown="handleMapTabKeydown($event, profile.profileName)"
                        >
                            {{ profile.korName }}섭
                        </button>
                    </div>
                    <div
                        v-if="selectedMapProfile"
                        :id="`map-preview-panel-${selectedMapProfile.profileName}`"
                        role="tabpanel"
                        :aria-labelledby="`map-preview-tab-${selectedMapProfile.profileName}`"
                        class="map-preview-panel"
                        data-testid="public-map-preview-panel"
                    >
                        <div class="flex items-center justify-between text-xs text-zinc-400 mb-2">
                            <span class="font-semibold" :style="{ color: selectedMapProfile.color }">
                                {{ selectedMapProfile.korName }}섭
                            </span>
                            <span>{{ selectedMapProfile.status }}</span>
                        </div>
                        <div v-if="selectedMapPreview">
                            <MapPreview
                                :map-data="selectedMapPreview.mapData"
                                :map-layout="selectedMapPreview.mapLayout"
                                mode="detail"
                            />
                            <div
                                v-if="profileDetails[selectedMapProfile.profileName]"
                                class="text-xs text-zinc-400 mt-2"
                            >
                                유저 {{ profileDetails[selectedMapProfile.profileName]?.userCnt ?? '-' }} /
                                {{ profileDetails[selectedMapProfile.profileName]?.maxUserCnt ?? '-' }} ·
                                {{ profileDetails[selectedMapProfile.profileName]?.nationCnt ?? '-' }}국 ·
                                {{ profileDetails[selectedMapProfile.profileName]?.turnTerm ?? '-' }}분 턴
                            </div>
                        </div>
                        <div v-else class="text-xs text-zinc-500 py-8 text-center">지도를 불러오는 중...</div>
                    </div>
                    <div v-else class="text-sm text-zinc-500 py-8 text-center" data-testid="public-map-empty">
                        현재 공개 중인 서버가 없습니다.
                    </div>
                </div>
            </div>

            <!-- Account Management -->
            <div class="bg-zinc-900 border border-zinc-800 rounded shadow-xl overflow-hidden">
                <div
                    class="bg-zinc-800 px-6 py-2 text-center font-bold text-white border-b border-zinc-700 tracking-widest"
                >
                    계 정 관 리
                </div>
                <div class="p-6 flex justify-center space-x-4">
                    <RouterLink
                        to="/account"
                        class="bg-zinc-800 hover:bg-zinc-700 text-white px-6 py-2 rounded border border-zinc-700 transition-colors"
                    >
                        비밀번호 & 전콘 & 탈퇴
                    </RouterLink>
                    <button
                        id="btn_logout"
                        class="legacy-logout-button"
                        :disabled="logoutLoading"
                        @click="handleLogout"
                    >
                        {{ logoutLoading ? '로그아웃 중…' : '로 그 아 웃' }}
                    </button>
                    <RouterLink
                        v-if="canAccessAdmin"
                        to="/admin"
                        class="bg-zinc-800 hover:bg-zinc-700 text-white px-6 py-2 rounded border border-zinc-700 transition-colors"
                    >
                        관리자 페이지
                    </RouterLink>
                </div>
                <p v-if="logoutError" class="px-6 pb-4 text-center text-sm text-red-400" role="alert">
                    {{ logoutError }}
                </p>
            </div>
        </div>
    </DefaultLayout>
</template>

<style scoped>
.legacy-logout-button {
    box-sizing: border-box;
    width: 200px;
    height: 48px;
    border: 0;
    border-radius: 6px;
    background: #303030;
    color: #fff;
    cursor: pointer;
    font-family: Pretendard, sans-serif;
    font-size: 16px;
    font-weight: 700;
    line-height: 24px;
    padding: 10px;
}

.map-preview-tabs {
    display: flex;
    gap: 4px;
    overflow-x: auto;
    border-bottom: 1px solid #3f3f46;
    padding: 0 2px;
}

.map-preview-tab {
    flex: 0 0 auto;
    border: 1px solid #3f3f46;
    border-bottom: 0;
    border-radius: 6px 6px 0 0;
    background: #18181b;
    color: #a1a1aa;
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
    line-height: 20px;
    padding: 7px 16px;
}

.map-preview-tab:hover,
.map-preview-tab:focus,
.map-preview-tab.is-active {
    background: #27272a;
    color: var(--profile-color, #fff);
}

.map-preview-tab:focus-visible {
    outline: 2px solid #fff;
    outline-offset: -3px;
}

.map-preview-tab.is-active {
    box-shadow: inset 0 3px 0 var(--profile-color, #fff);
}

.map-preview-panel {
    min-width: 0;
    border: 1px solid #3f3f46;
    border-top: 0;
    border-radius: 0 0 6px 6px;
    background: rgb(9 9 11 / 50%);
    padding: 12px;
}

.legacy-logout-button:hover,
.legacy-logout-button:focus,
.legacy-logout-button:active {
    background: #303030;
    color: #fff;
}

.legacy-logout-button:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 2px;
}

.legacy-logout-button:disabled {
    cursor: default;
    opacity: 0.65;
}
</style>
