<script setup lang="ts">
import { ref, watch } from 'vue'
import DesignStage from '../../../components/DesignStage.vue'
import type Player from '../../../commons/player.ts'
import { $is_run_local } from '@/commons/utils.ts';
import { assetUrl, cssAssetUrl } from '@/commons/assets.ts';

const props = defineProps<{
    rankList: Player[];
}>();

const emit = defineEmits<{
    start: [];
}>();

const RANK_LIMIT = 10;
const START_COUNTDOWN_SECONDS = 3;
const GU_FRAME_DURATION = 300;
const GU_LOOP_DELAY = 1000;
const designStage = ref<InstanceType<typeof DesignStage> | null>(null);
const rankEffects = ref<Record<string, "up" | "down" | "new">>({});
const guFrameIndex = ref(0);
const guBurstVisible = ref(false);
const guBurstKey = ref(0);
let guFramesReady: Promise<void> | undefined;
let guTimer: ReturnType<typeof setTimeout> | undefined;
let guBurstTimer: ReturnType<typeof setTimeout> | undefined;

const loadGuFrames = (dir: string) => Array.from(
    { length: 10 },
    (_, index) => assetUrl(`${dir}/${String(index + 1).padStart(2, '0')}.png`),
);

const guFrames = [
    loadGuFrames('game1/gu'),
    loadGuFrames('game1/gu2'),
];

const imageUrls = {
    bg: cssAssetUrl('game1/bg.png'),
    logo: cssAssetUrl('game1/logo.png'),
    title: cssAssetUrl('game1/title3.png'),
    fireworks: cssAssetUrl('game1/fireworks.png'),
    titleRank: cssAssetUrl('game1/title-rank.png'),
    rankNo4: cssAssetUrl('game1/no4.png'),
    rankNo1: cssAssetUrl('game1/no1.png'),
    rankNo2: cssAssetUrl('game1/no2.png'),
    rankNo3: cssAssetUrl('game1/no3.png'),
    rankIcon: cssAssetUrl('game1/no-icon.png'),
    rankNo1Icon: cssAssetUrl('game1/no1-icon.png'),
    rankNo2Icon: cssAssetUrl('game1/no2-icon.png'),
    rankNo3Icon: cssAssetUrl('game1/no3-icon.png'),
    avatar: assetUrl('avatar.png'),
};

const emptyPlayer = (index: number): Player => ({
    wechat_id: `empty-${index}`,
    nickname: '-',
    avatar: '',
    score: 0,
});

const list = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyPlayer(index)));
const playerKey = (player: Player, index = 0) => {
    if (player.userid || player.wechat_id) {
        return player.userid || player.wechat_id || `empty-${index}`;
    }
    if (player.nickname !== '-') {
        return `player-${player.nickname}-${player.avatar || ''}`;
    }
    return `empty-${index}`;
};

const playRankAnimation = (rankList: Player[]) => {
    const oldRanks = new Map(list.value.map((player, index) => [playerKey(player, index), index]));
    const next = Array.from(
        { length: RANK_LIMIT },
        (_, index) => rankList[index] ? { ...rankList[index] } : emptyPlayer(index),
    );
    const changed = next.some((player, index) => {
        const current = list.value[index] ?? emptyPlayer(index);
        return playerKey(player, index) !== playerKey(current, index)
            || player.score !== current.score;
    });

    if (!changed) return;

    const effects: Record<string, "up" | "down" | "new"> = {};
    next.forEach((player, index) => {
        const key = playerKey(player, index);
        if (key.startsWith('empty-')) return;

        const oldIndex = oldRanks.get(key);
        if (oldIndex === undefined) effects[key] = "new";
        else if (oldIndex > index) effects[key] = "up";
        else if (oldIndex < index) effects[key] = "down";
    });

    list.value = next;
    rankEffects.value = {};
    requestAnimationFrame(() => {
        rankEffects.value = effects;
    });
};

watch(
    () => props.rankList,
    (rankList) => playRankAnimation(rankList),
    { immediate: true, deep: true, flush: 'post' },
);

const startGame = () => {
    if ($is_run_local()) {
        emit('start');
    }
};

const stopPlayerGu = () => {
    if (guTimer) {
        clearTimeout(guTimer);
        guTimer = undefined;
    }
    if (guBurstTimer) {
        clearTimeout(guBurstTimer);
        guBurstTimer = undefined;
    }
    guBurstVisible.value = false;
    guFrameIndex.value = 0;
}

const preloadGuFrames = () => {
    if (!guFramesReady) {
        guFramesReady = Promise.all(guFrames.flat().map((src) => {
            const img = new Image();
            img.src = src;
            return img.decode
                ? img.decode().catch(() => undefined)
                : new Promise<void>((resolve) => {
                    img.onload = () => resolve();
                    img.onerror = () => resolve();
                });
        })).then(() => undefined);
    }

    return guFramesReady;
}

const playGuLoopDelayAnimation = () => {
    if (guBurstTimer) {
        clearTimeout(guBurstTimer);
        guBurstTimer = undefined;
    }

    guBurstVisible.value = false;
    requestAnimationFrame(() => {
        guBurstKey.value += 1;
        guBurstVisible.value = true;
        guBurstTimer = setTimeout(() => {
            guBurstVisible.value = false;
            guBurstTimer = undefined;
        }, GU_LOOP_DELAY);
    });
}

const playerGu = async () => {
    stopPlayerGu();
    const frameCount = Math.min(...guFrames.map((frames) => frames.length));
    if (frameCount <= 0) return;

    await preloadGuFrames();

    let frameIndex = 0;
    const tick = () => {
        guFrameIndex.value = frameIndex;

        if (frameIndex >= frameCount - 1) {
            frameIndex = 0;
            playGuLoopDelayAnimation();
            guTimer = setTimeout(tick, GU_LOOP_DELAY);
            return;
        }

        frameIndex += 1;
        guTimer = setTimeout(tick, GU_FRAME_DURATION);
    }

    tick();
}

const startIntro = async () => {
    playerGu();
    await designStage.value?.startCountdown(START_COUNTDOWN_SECONDS, 60);
    stopPlayerGu();
};

defineExpose({
    startIntro,
});


const avatarStyle = (item: Player) => {
    const avatar = item.avatar || imageUrls.avatar

    return {
        backgroundImage: `url("${avatar}")`,
    }
}
</script>

<template>
    <DesignStage ref="designStage">
        <div class="bg">
            <div class="img-logo"></div>
            <div class="img-title"></div>
            <div class="img-gu-group">
                <div class="img-gu">
                    <div v-for="(frame, index) in guFrames[1]" :key="`left-frame-${frame}`" class="gu-frame"
                        :class="{ active: index === guFrameIndex }" :style="{ backgroundImage: `url(${frame})` }">
                    </div>
                    <div :key="`left-${guBurstKey}`" class="gu-burst" :class="{ active: guBurstVisible }"></div>
                </div>
                <div class="img-gu">
                    <div v-for="(frame, index) in guFrames[0]" :key="`right-frame-${frame}`" class="gu-frame"
                        :class="{ active: index === guFrameIndex }" :style="{ backgroundImage: `url(${frame})` }">
                    </div>
                    <div :key="`right-${guBurstKey}`" class="gu-burst" :class="{ active: guBurstVisible }"></div>
                </div>
            </div>
            <div class="rank-container">
                <div class="rank-txt" @click="startGame()"></div>
                <TransitionGroup name="rank" tag="div" class="rank-list">
                    <div v-for="(item, index) in list" :key="playerKey(item, index)" class="rank-item"
                        :class="[rankEffects[playerKey(item, index)], index < 3 ? `rank-top-${index + 1}` : '']">
                        <div class="rank-icon" :key="`rank-icon-${index}`">{{ index > 2 ? index + 1 : '' }}</div>
                        <div class="rank-avatar" :style="avatarStyle(item)"></div>
                        <div class="rank-nickname">{{ item.nickname }}</div>
                        <div class="rank-score">{{ item.score ?? 0 }}</div>
                    </div>
                </TransitionGroup>
            </div>
        </div>
    </DesignStage>
</template>

<style scoped>
.bg {
    width: 1920px;
    height: 1080px;
    background: v-bind('imageUrls.bg') center / cover no-repeat;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    position: relative;
}

.img-logo {
    background: v-bind('imageUrls.logo');
    background-size: cover;
    width: 428px;
    height: 77px;
    position: absolute;
    left: 37px;
    top: 82px;
}

.img-title {
    background: v-bind('imageUrls.title');
    width: 967px;
    height: 122px;
    margin: 0 auto;
    margin-top: 37px;
}

.img-gu-group {
    .img-gu {
        width: 400px;
        height: 400px;
        transform: scale(1.6);
        position: absolute;
        top: 400px;
    }

    .img-gu:nth-child(1) {
        left: 200px;
    }

    .img-gu:nth-child(2) {
        right: 200px;
    }

    .img-gu {
        .gu-frame {
            position: absolute;
            inset: 0;
            opacity: 0;
            background-position: center;
            background-repeat: no-repeat;
            background-size: contain;
            pointer-events: none;
            will-change: opacity;
        }

        .gu-frame.active {
            opacity: 1;
        }

        .gu-burst {
            width: 200px;
            height: 200px;
            position: absolute;
            right: 80px;
            top: 30px;
            opacity: 0;
            transform: scale(0.6);
            transform-origin: center center;
            background: v-bind('imageUrls.fireworks') center / cover no-repeat;
            pointer-events: none;

            &.active {
                animation: gu-burst-expand 1s ease-out both;
            }
        }
    }
}

.rank-container {
    width: 503px;
    height: 810px;
    background: #fff;
    opacity: 0.9;
    border-radius: 30px;
    position: absolute;
    left: 50%;
    top: 210px;
    transform: translateX(-50%);
    z-index: 1;
    overflow: hidden;

    .rank-txt {
        background: v-bind('imageUrls.titleRank');
        width: 304px;
        height: 40px;
        margin: 10px auto auto auto;
    }

    .rank-list {
        position: relative;
    }

    .rank-item {
        background: v-bind('imageUrls.rankNo4');
        width: 468px;
        height: 68px;
        margin: 6px auto 0 auto;
        display: grid;
        grid-template-columns: 95px 68px 1fr 100px;
        align-items: center;
        color: #E00000;
        font-weight: bold;

        &.rank-top-1 {
            background: v-bind('imageUrls.rankNo1');
        }

        &.rank-top-2 {
            background: v-bind('imageUrls.rankNo2');
        }

        &.rank-top-3 {
            background: v-bind('imageUrls.rankNo3');
        }

        .rank-icon {
            background: v-bind('imageUrls.rankIcon') center / cover no-repeat;
            width: 40px;
            height: 40px;
            margin: auto auto auto 20px;
            text-align: center;
            color: white;
            font-weight: bold;
            font-size: 22px;
            line-height: 40px;
        }

        &.rank-top-1 .rank-icon {
            background: v-bind('imageUrls.rankNo1Icon') center / cover no-repeat;
            width: 60px;
            height: 38px;
            margin: auto auto auto 10px;
        }

        &.rank-top-2 .rank-icon {
            background: v-bind('imageUrls.rankNo2Icon') center / cover no-repeat;
            width: 60px;
            height: 38px;
            margin: auto auto auto 10px;
        }

        &.rank-top-3 .rank-icon {
            background: v-bind('imageUrls.rankNo3Icon') center / cover no-repeat;
            width: 60px;
            height: 38px;
            margin: auto auto auto 10px;
        }

        .rank-avatar {
            background-position: center;
            background-repeat: no-repeat;
            background-size: cover;
            width: 48px;
            height: 48px;
            border-radius: 24px;
            margin-left: 10px;
        }

        .rank-nickname {
            text-indent: 10px;
        }

        .rank-score {
            text-align: right;
            margin-right: 30px;
        }
    }
}

.rank-move {
    z-index: 1;
    transition: transform 0.65s cubic-bezier(0.22, 1, 0.36, 1);
}

.rank-enter-active,
.rank-leave-active {
    transition: opacity 0.35s ease, transform 0.35s ease;
}

.rank-leave-active {
    position: absolute;
    left: 17px;
}

.rank-enter-from,
.rank-leave-to {
    opacity: 0;
    transform: translateY(70px);
}

.rank-item.up,
.rank-item.new {
    animation: rank-up-flash 0.8s ease;
}

.rank-item.down {
    animation: rank-down-flash 0.8s ease;
}
</style>
