<script setup lang="ts">
import { onBeforeUnmount, onMounted, 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 { cssAssetUrl } from '@/commons/assets.ts';

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

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

const HORSE_COUNT = 6;
const HORSE_FRAME_COUNT = 91;
const HORSE_FRAME_SIZE = 150;
const HORSE_SPRITE_COLUMNS = 10;
const HORSE_FRAME_DURATION = 50;
const RACE_TICK_DURATION = 50;
const RACE_DURATION = 60_000;
const FINISH_POSITION_GAP = 55;
const RANK_RUNNING_GAP = 45;
const RANK_SPEED_FACTORS = [1.04, 1.025, 1.01, 0.995, 0.98, 0.965];
const RACE_START_X = -225;
const TRACK_MAX_X = 1595;
const BACKGROUND_WIDTH = 2358;
const STAGE_WIDTH = 1920;
const designStage = ref<InstanceType<typeof DesignStage> | null>(null);
const horseFrameIndex = ref(0);
const raceElapsed = ref(0);
const raceStarted = ref(false);
const rankedPlayerKeys = ref<string[]>([]);
const testRankList = ref<Player[]>([]);
let horseFrameTimer: ReturnType<typeof setInterval> | undefined;
let raceTimer: ReturnType<typeof setInterval> | undefined;
let raceStartTimer: ReturnType<typeof setTimeout> | undefined;

type Racer = {
    key: string;
    lane: number;
    player: Player;
    x: number;
    speed: number;
};
const START_COUNTDOWN_SECONDS = 3;
const racers = ref<Racer[]>([]);

const imageUrls = {
    bg: cssAssetUrl('game2/bg2.png'),
    logo: cssAssetUrl('game1/logo.png'),
    title: cssAssetUrl('game2/title.png'),
    fireworks: cssAssetUrl('game1/fireworks.png'),

    // fireworks: cssAssetUrl('game1/fireworks.png'),
    // titleRank: cssAssetUrl('game1/title-rank.png'),
    rankNo4: cssAssetUrl('game6/no4.png'),
    rankNo1: cssAssetUrl('game6/no1.png'),
    rankNo2: cssAssetUrl('game6/no2.png'),
    rankNo3: cssAssetUrl('game6/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: cssAssetUrl('avatar-yellow.png'),
    avatarAnimation: cssAssetUrl('avatar-yellow_animation.png'),
    mole2: cssAssetUrl('game6/mole2.png'),
    rankContainer: cssAssetUrl('game6/rank-container.png'),
    land: cssAssetUrl('game6/land.png'),
    moleShow: cssAssetUrl('game6/mole_show.png'),
    moleHide: cssAssetUrl('game6/mole_hide.png'),
    rabbitShow: cssAssetUrl('game6/rabbit_show.png'),
    rabbitHide: cssAssetUrl('game6/rabbit_hide.png'),
};

const horseSprites = [
    cssAssetUrl('game2/horse1.png'),
    cssAssetUrl('game2/horse2.png'),
    cssAssetUrl('game2/horse3.png'),
];

const playerKey = (player: Player, index = 0) => (
    String(player.userid || player.wechat_id || player.telephone || `${player.nickname}-${index}`)
);

const isRealPlayer = (player: Player) => (
    Boolean(player)
    && !String(player.wechat_id || '').startsWith('empty-')
    && player.nickname !== '-'
);

const renderRaceBackground = () => {
    const progress = Math.min(raceElapsed.value / RACE_DURATION, 1);
    const backgroundX = -(BACKGROUND_WIDTH - STAGE_WIDTH) * progress;

    return {
        backgroundImage: imageUrls.bg,
        backgroundPosition: `${backgroundX}px center`,
    };
};

const targetXForRank = (rankIndex: number) => {
    const safeRankIndex = Math.max(0, Math.min(rankIndex, HORSE_COUNT - 1));
    const progress = Math.min(raceElapsed.value / RACE_DURATION, 1);
    const runningGap = RANK_RUNNING_GAP * Math.sin(Math.PI * progress);
    const finishGap = FINISH_POSITION_GAP * progress;

    return RACE_START_X
        + (TRACK_MAX_X - RACE_START_X) * progress
        - safeRankIndex * (runningGap + finishGap);
};

const shuffledLanes = () => {
    const lanes = Array.from({ length: HORSE_COUNT }, (_, lane) => lane);

    for (let index = lanes.length - 1; index > 0; index -= 1) {
        const randomIndex = Math.floor(Math.random() * (index + 1));
        [lanes[index], lanes[randomIndex]] = [lanes[randomIndex]!, lanes[index]!];
    }

    return lanes;
};

const syncRacers = (rankList: Player[]) => {
    const topPlayers = rankList.filter(isRealPlayer).slice(0, HORSE_COUNT);
    const rankedEntries = topPlayers.map((player, index) => ({
        key: playerKey(player, index),
        player,
    }));
    const topKeys = rankedEntries.map(({ key }) => key);
    const topPlayerMap = new Map(
        rankedEntries.map(({ key, player }) => [key, player]),
    );
    const previousRacersByLane = new Map(
        racers.value.map((racer) => [racer.lane, racer]),
    );

    const retained = racers.value
        .filter((racer) => topPlayerMap.has(racer.key))
        .map((racer) => ({ ...racer, player: topPlayerMap.get(racer.key)! }));
    const retainedKeys = new Set(retained.map((racer) => racer.key));
    const availableLanes = Array.from(
        { length: HORSE_COUNT },
        (_, lane) => lane,
    ).filter((lane) => !retained.some((racer) => racer.lane === lane));
    const freeLanes = racers.value.length === 0 ? shuffledLanes() : availableLanes;
    const newcomers = rankedEntries
        .filter(({ key }) => !retainedKeys.has(key))
        .map(({ key, player }, index) => ({
            key,
            player,
            lane: freeLanes[index]!,
            x: previousRacersByLane.get(freeLanes[index]!)?.x
                ?? RACE_START_X,
            speed: previousRacersByLane.get(freeLanes[index]!)?.speed ?? 0,
        }));

    racers.value = [...retained, ...newcomers]
        .sort((a, b) => a.lane - b.lane);
    rankedPlayerKeys.value = topKeys;
};

const updateRacePositions = () => {
    if (raceElapsed.value >= RACE_DURATION) {
        racers.value.forEach((racer) => {
            racer.speed = 0;
        });
        return;
    }

    racers.value.forEach((racer) => {
        const rankIndex = rankedPlayerKeys.value.indexOf(racer.key);
        const effectiveRank = rankIndex >= 0 ? rankIndex : HORSE_COUNT - 1;
        const targetX = targetXForRank(effectiveRank);
        const speedFactor = RANK_SPEED_FACTORS[effectiveRank] ?? RANK_SPEED_FACTORS.at(-1)!;
        const desiredSpeed = Math.max((targetX - racer.x) * 0.18 * speedFactor, 0);

        racer.speed += (desiredSpeed - racer.speed) * 0.24;
        racer.x = Math.min(racer.x + Math.max(racer.speed, 0), TRACK_MAX_X);
    });
};

const resetRace = (startImmediately = true) => {
    raceElapsed.value = 0;
    raceStarted.value = startImmediately;

    const lanes = shuffledLanes();
    const leaderKey = rankedPlayerKeys.value[0];
    const leaderIndex = racers.value.findIndex((racer) => racer.key === leaderKey);
    if (leaderIndex >= 0 && lanes[leaderIndex] === 0 && lanes.length > 1) {
        [lanes[leaderIndex], lanes[1]] = [lanes[1]!, lanes[leaderIndex]!];
    }

    racers.value.forEach((racer) => {
        racer.lane = lanes[racers.value.indexOf(racer)]!;
        racer.x = RACE_START_X;
        racer.speed = 0;
    });
    racers.value.sort((a, b) => a.lane - b.lane);
};

const renderHorseFrame = (racer: Racer) => {
    const column = horseFrameIndex.value % HORSE_SPRITE_COLUMNS;
    const row = Math.floor(horseFrameIndex.value / HORSE_SPRITE_COLUMNS);

    return {
        backgroundImage: horseSprites[racer.lane % horseSprites.length],
        backgroundPosition: `${-column * HORSE_FRAME_SIZE}px ${-row * HORSE_FRAME_SIZE}px`,
        transform: `translateX(${racer.x}px) scale(1.5)`,
    };
};

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

const createTestPlayers = () => Array.from({ length: 7 }, (_, index): Player => ({
    userid: `test-player-${index + 1}`,
    wechat_id: `test-player-${index + 1}`,
    nickname: `测试玩家${index + 1}`,
    avatar: '',
    score: 700 - index * 100,
}));

const updateTestRank = () => {
    if (!$is_run_local()) return;

    if (testRankList.value.length === 0) {
        testRankList.value = createTestPlayers();
        syncRacers(testRankList.value);
        resetRace();
        return;
    }

    const player = testRankList.value[Math.floor(Math.random() * testRankList.value.length)];
    if (player) {
        player.score = Number(player.score || 0) + 80 + Math.floor(Math.random() * 180);
    }

    testRankList.value = [...testRankList.value].sort(
        (a, b) => Number(b.score || 0) - Number(a.score || 0),
    );
    syncRacers(testRankList.value);
};

onMounted(() => {
    horseFrameTimer = setInterval(() => {
        horseFrameIndex.value = (horseFrameIndex.value + 1) % HORSE_FRAME_COUNT;
    }, HORSE_FRAME_DURATION);

    raceTimer = setInterval(() => {
        if (!raceStarted.value) return;

        raceElapsed.value = Math.min(raceElapsed.value + RACE_TICK_DURATION, RACE_DURATION);
        updateRacePositions();
    }, RACE_TICK_DURATION);

});

onBeforeUnmount(() => {
    if (horseFrameTimer) {
        clearInterval(horseFrameTimer);
    }
    if (raceTimer) {
        clearInterval(raceTimer);
    }
    if (raceStartTimer) {
        clearTimeout(raceStartTimer);
    }
});

const startIntro = async () => {
    resetRace(false);
    const countdown = designStage.value?.startCountdown(START_COUNTDOWN_SECONDS, 60);

    raceStartTimer = setTimeout(() => {
        raceStarted.value = true;
        raceStartTimer = undefined;
    }, START_COUNTDOWN_SECONDS * 1000);

    await countdown;
};

defineExpose({
    startIntro,
});

const renderAvatar = (item: any | null) => {
    if (item && item.avatar) {
        return `background: url("${String(item.avatar).replace(/"/g, '\\"')}") center / cover no-repeat;`
    }
    return `background: ${imageUrls.avatar} center / cover no-repeat;`
}

watch(
    () => props.rankList,
    (rankList) => syncRacers(rankList),
    { immediate: true, deep: true },
);

</script>

<template>
    <DesignStage ref="designStage">
        <div class="bg" :style="renderRaceBackground()">
            <div v-if="$is_run_local()" class="test-click-layer"></div>
            <div class="img-logo"></div>
            <div class="img-title"></div>

            <div class="horse-container">
                <div v-for="racer in racers" :key="racer.lane" class="horse-lane">
                    <div class="horse" :style="renderHorseFrame(racer)">
                        <div class="name-container">
                            <div class="nickname">{{ racer.player.nickname }}</div>
                            <div class="img-avatar" :style="renderAvatar(racer.player)"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </DesignStage>
</template>

<style scoped>
.bg {
    width: 1920px;
    height: 1080px;
    background-repeat: no-repeat;
    background-size: 2358px 1080px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    position: relative;
}

.test-click-layer {
    position: absolute;
    inset: 0;
    z-index: 1;
}

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

.img-title {
    background: v-bind('imageUrls.title') center / cover no-repeat;
    width: 831px;
    height: 325px;
    margin: 0 auto;
    margin-top: 37px;
    z-index: 2;
}

.horse-container {
    position: absolute;
    bottom: 0px;
    left: 0;
    display: flex;
    flex-direction: column;
    width: 1920px;
    height: 500px;
    z-index: 2;
    pointer-events: none;
}

.horse-lane {
    position: relative;
    flex: 1;
    min-height: 0;
}

.horse {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 150px;
    height: 150px;
    background-repeat: no-repeat;
    background-size: 1500px 1500px;
    transform-origin: left bottom;
    transition: transform 50ms linear;

    .name-container {
        display: flex;
        align-items: center;
        gap: 8px;
        width: 150px;
        height: 42px;
        position: absolute;
        left: -100px;
        top: 50px;

        .nickname {
            max-width: 150px;
            padding: 4px 8px;
            color: #fff;
            background: rgb(0 0 0 / 55%);
            border-radius: 12px;
            text-overflow: ellipsis;
            white-space: nowrap;
            font-size: 20px;
        }

        .img-avatar {
            flex: 0 0 42px;
            width: 42px;
            height: 42px;
            border: 2px solid #fff;
            border-radius: 50%;
        }
    }
}
</style>
