<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';

import { isLegacyArchiveProfile, type LegacyArchiveProfile } from '@sammo-ts/common';

import GeneralBasicCard from '../components/main/GeneralBasicCard.vue';
import GeneralBattleSummary, { type GeneralBattleSummaryData } from '../components/main/GeneralBattleSummary.vue';
import GeneralRecordPanels from '../components/main/GeneralRecordPanels.vue';
import {
    GENERAL_RECORD_TYPES,
    type GeneralRecordCollection,
    type GeneralRecordType,
} from '../components/generalRecords';
import LegacyGeneralProgress from '../components/ui/LegacyGeneralProgress.vue';
import PanelCard from '../components/ui/PanelCard.vue';
import SkeletonLines from '../components/ui/SkeletonLines.vue';
import { legacyLuminanceTextColor } from '../utils/legacyNationColor';
import { trpc } from '../utils/trpc';

type Archive = Awaited<ReturnType<typeof trpc.archive.myPastPlays.query>>;
type ArchiveSeason = Archive['seasons'][number] & {
    sourceProfile?: string;
    source?: string;
    openedAt?: string | null;
    date?: string | null;
    status?: 'OPEN' | 'COMPLETED' | 'ABANDONED' | 'LEGACY';
    cancellationId?: string | null;
    cancelledAt?: string | null;
};

type ArchiveGeneral = {
    id: number;
    name: string;
    picture?: string | null;
    imageServer?: number | null;
    npcState: number;
    officerLevel: number;
    officerLevelText: string;
    officerCityName?: string | null;
    generalType?: string;
    leadershipBonus?: number;
    stats: { leadership: number; strength: number; intelligence: number };
    gold: number;
    rice: number;
    crew: number;
    train: number;
    atmos: number;
    injury: number;
    experience: number;
    dedication: number;
    age?: number;
    retirementYear?: number;
    turnTime?: string | null;
    crewTypeId?: number;
    crewTypeName?: string;
    traits?: { personal: string; specialWar: string; specialDomestic: string };
    progression: {
        experienceLevel: number;
        dedicationLevel: number;
        dedicationText: string;
        statExperience: { leadership: number; strength: number; intelligence: number };
        statUpgradeLimit: number;
        dex: number[];
    };
};

type ArchiveLogChannel = {
    available: boolean;
    entries: Array<{ id: number | string; text: string }>;
};

type PastPlayDetail = {
    sourceProfile: string;
    source: string;
    serverId: string;
    generalNo: number;
    dynastyPath: string | null;
    nation: { name: string; color: string } | null;
    general: ArchiveGeneral;
    masteryAvailable: boolean;
    battle: GeneralBattleSummaryData & {
        winRate?: number | null;
        killRate?: number | null;
    };
    hallBattle: {
        available: boolean;
        semantics: 'independent-records';
        strategies: number | null;
        warnum: number | null;
        wins: number | null;
        winRate: number | null;
        occupied: number | null;
        killCrew: number | null;
        killRate: number | null;
        killCrewPerson: number | null;
        killRatePerson: number | null;
    };
    logs: Partial<Record<GeneralRecordType, ArchiveLogChannel>>;
};

type PastPlayDetailInput = {
    source: string;
    serverId: string;
    generalNo: number;
};

const queryPastPlayDetail = trpc.archive.myPastPlayDetail.query as unknown as (
    input: PastPlayDetailInput
) => Promise<PastPlayDetail>;

const profileLabels: Record<LegacyArchiveProfile, string> = {
    che: '체',
    kwe: '퀘',
    pwe: '풰',
    twe: '퉤',
    nya: '냐',
    pya: '퍄',
    hwe: '훼',
};

const archive = ref<Archive | null>(null);
const loading = ref(false);
const error = ref<string | null>(null);
const selectedKey = ref<string | null>(null);
const detail = ref<PastPlayDetail | null>(null);
const detailLoading = ref(false);
const detailError = ref<string | null>(null);

const loadArchive = async () => {
    if (loading.value) return;
    loading.value = true;
    error.value = null;
    try {
        archive.value = await trpc.archive.myPastPlays.query();
    } catch (cause) {
        error.value = cause instanceof Error ? cause.message : '지난 플레이를 불러오지 못했습니다.';
    } finally {
        loading.value = false;
    }
};

const yearMonth = (value: number): string => `${Math.floor(value / 100)}년 ${value % 100}월`;
const valueOrDash = (value: number | string | null): string => (value === null || value === '' ? '-' : String(value));
const hallNumber = (value: number | null): string =>
    value === null ? '-' : new Intl.NumberFormat('ko-KR', { maximumFractionDigits: 0 }).format(value);
const hallPercent = (value: number | null): string => (value === null ? '-' : `${value.toFixed(2)}%`);
const plainLog = (value: string): string => value.replace(/<[^>]+>/g, '');
const seasonSourceProfile = (season: ArchiveSeason): string => season.sourceProfile ?? '현재 서버';
const profileDisplayName = (profile: string): string =>
    isLegacyArchiveProfile(profile) ? profileLabels[profile] : profile;
const seasonSource = (season: ArchiveSeason): string => season.source ?? 'current';
const detailKey = (season: ArchiveSeason, generalNo: number): string =>
    `${seasonSourceProfile(season)}:${seasonSource(season)}:${season.serverId}:${generalNo}`;
const isSelectedSeason = (season: ArchiveSeason): boolean =>
    selectedKey.value?.startsWith(`${seasonSourceProfile(season)}:${seasonSource(season)}:${season.serverId}:`) ??
    false;

const formatOpenedAt = (season: ArchiveSeason): string => {
    const value = season.openedAt ?? season.date;
    if (!value) return '개장일 미상';
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) return '개장일 미상';
    return `${new Intl.DateTimeFormat('ko-KR', { dateStyle: 'medium', timeZone: 'Asia/Seoul' }).format(date)} 개장`;
};
const archiveLabel = (season: ArchiveSeason): string =>
    season.status === 'ABANDONED' ? '취소 게임' : '이전 서버 기록';
const archiveIdentifier = (season: ArchiveSeason): string =>
    season.status === 'ABANDONED' && season.cancellationId
        ? `취소 ID ${season.cancellationId.slice(0, 8)}`
        : season.serverId;

const selectGeneral = async (season: ArchiveSeason, generalNo: number): Promise<void> => {
    const key = detailKey(season, generalNo);
    if (selectedKey.value === key) {
        selectedKey.value = null;
        detail.value = null;
        detailError.value = null;
        return;
    }

    selectedKey.value = key;
    detail.value = null;
    detailError.value = null;
    detailLoading.value = true;
    try {
        const result = await queryPastPlayDetail({
            source: seasonSource(season),
            serverId: season.serverId,
            generalNo,
        });
        if (selectedKey.value === key) detail.value = result;
    } catch (cause) {
        if (selectedKey.value === key) {
            detailError.value = cause instanceof Error ? cause.message : '지난 장수 상세 기록을 불러오지 못했습니다.';
        }
    } finally {
        if (selectedKey.value === key) detailLoading.value = false;
    }
};

const archiveRecords = computed<GeneralRecordCollection>(() => {
    const result: GeneralRecordCollection = {};
    for (const type of GENERAL_RECORD_TYPES) {
        const channel = detail.value?.logs[type];
        result[type] = (channel?.entries ?? []).map((entry) => ({ id: entry.id, content: plainLog(entry.text) }));
    }
    return result;
});

const unavailableRecords = computed<GeneralRecordType[]>(() =>
    GENERAL_RECORD_TYPES.filter((type) => {
        const channel = detail.value?.logs[type];
        return channel ? !channel.available : type !== 'generalHistory';
    })
);

const battleSummary = computed<GeneralBattleSummaryData>(() => ({
    ...detail.value?.battle,
    experience: detail.value?.general.experience ?? null,
    dedicationText: detail.value?.general.progression.dedicationText ?? null,
}));

onMounted(() => {
    void loadArchive();
});
</script>

<template>
    <main id="container" class="past-plays-page legacy-bg0">
        <header class="title-row legacy-bg1">
            <h1>내 지난 플레이 보기</h1>
            <nav>
                <RouterLink class="legacy-button" to="/">돌아가기</RouterLink>
                <button class="legacy-button" type="button" :disabled="loading" @click="loadArchive">새로고침</button>
            </nav>
        </header>

        <p class="page-note">종료된 기수와 관리자가 보존한 취소 게임의 내 장수 기록입니다.</p>
        <p v-if="error" class="error-row">{{ error }}</p>
        <p v-else-if="loading && !archive" class="empty-row">불러오는 중...</p>
        <p v-else-if="archive?.seasons.length === 0" class="empty-row">보관된 지난 플레이가 없습니다.</p>

        <section v-for="season in archive?.seasons ?? []" :key="detailKey(season, 0)" class="season-card">
            <div class="season-heading legacy-bg2">
                <div class="season-identity">
                    <strong class="archive-label" :class="{ abandoned: season.status === 'ABANDONED' }">
                        {{ archiveLabel(season) }}
                    </strong>
                    <strong>{{ profileDisplayName(seasonSourceProfile(season)) }}</strong>
                    <span>{{ archiveIdentifier(season) }}</span>
                </div>
                <div class="season-meta">
                    <span>{{ formatOpenedAt(season) }}</span>
                    <span>
                        {{ season.scenarioName ?? '시나리오 미상' }}
                        <template v-if="season.status !== 'ABANDONED' && season.season !== null">
                            · {{ season.season }}기
                        </template>
                    </span>
                </div>
            </div>

            <div class="table-scroll">
                <table>
                    <thead>
                        <tr>
                            <th>장수명</th>
                            <th>소속</th>
                            <th>마지막 기록</th>
                            <th>통솔</th>
                            <th>무력</th>
                            <th>지력</th>
                            <th>관직</th>
                            <th>성격</th>
                            <th>내정 특기</th>
                            <th>전투 특기</th>
                            <th>상세</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="general in season.generals" :key="general.generalNo">
                            <td class="general-name">{{ general.name }}</td>
                            <td>
                                <span
                                    class="nation-name"
                                    :style="{
                                        backgroundColor: general.nationColor,
                                        color: legacyLuminanceTextColor(general.nationColor),
                                    }"
                                >
                                    {{ general.nationName }}
                                </span>
                            </td>
                            <td>{{ yearMonth(general.lastYearMonth) }}</td>
                            <td>{{ valueOrDash(general.leadership) }}</td>
                            <td>{{ valueOrDash(general.strength) }}</td>
                            <td>{{ valueOrDash(general.intel) }}</td>
                            <td>{{ valueOrDash(general.officerLevelText) }}</td>
                            <td>{{ valueOrDash(general.personal) }}</td>
                            <td>{{ valueOrDash(general.special) }}</td>
                            <td>{{ valueOrDash(general.special2) }}</td>
                            <td>
                                <button
                                    class="legacy-button detail-toggle"
                                    type="button"
                                    :aria-expanded="selectedKey === detailKey(season, general.generalNo)"
                                    @click="selectGeneral(season, general.generalNo)"
                                >
                                    {{ selectedKey === detailKey(season, general.generalNo) ? '접기' : '상세 보기' }}
                                </button>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <div v-if="isSelectedSeason(season)" class="detail-region">
                <SkeletonLines v-if="detailLoading" :lines="8" />
                <p v-else-if="detailError" class="detail-error" role="alert">{{ detailError }}</p>
                <div v-else-if="detail" class="detail-shell">
                    <div class="detail-source">
                        <span>{{ profileDisplayName(detail.sourceProfile) }} · {{ detail.source }}</span>
                        <RouterLink
                            v-if="detail.dynastyPath"
                            class="legacy-button nation-archive-link"
                            :to="detail.dynastyPath"
                        >
                            이 기수 국가 정보
                        </RouterLink>
                    </div>
                    <div class="detail-grid">
                        <PanelCard title="장수 정보">
                            <GeneralBasicCard
                                class="archive-general-card"
                                :general="detail.general"
                                :loading="false"
                                :nation-color="detail.nation?.color"
                            >
                                <template #details>
                                    <GeneralBattleSummary :summary="battleSummary" show-win-rate rate-scale="percent" />
                                    <section
                                        v-if="detail.hallBattle.available"
                                        class="hall-battle-record"
                                        data-hall-battle-record
                                    >
                                        <div class="hall-battle-record__heading">
                                            <strong>명예의 전당 보존 기록</strong>
                                            <span>항목별 기록 시점이 서로 다를 수 있습니다.</span>
                                        </div>
                                        <dl>
                                            <div>
                                                <dt>전투</dt>
                                                <dd>{{ hallNumber(detail.hallBattle.warnum) }}</dd>
                                            </div>
                                            <div>
                                                <dt>승리</dt>
                                                <dd>{{ hallNumber(detail.hallBattle.wins) }}</dd>
                                            </div>
                                            <div>
                                                <dt>승률</dt>
                                                <dd>{{ hallPercent(detail.hallBattle.winRate) }}</dd>
                                            </div>
                                            <div>
                                                <dt>계략</dt>
                                                <dd>{{ hallNumber(detail.hallBattle.strategies) }}</dd>
                                            </div>
                                            <div>
                                                <dt>점령</dt>
                                                <dd>{{ hallNumber(detail.hallBattle.occupied) }}</dd>
                                            </div>
                                            <div>
                                                <dt>사살</dt>
                                                <dd>{{ hallNumber(detail.hallBattle.killCrew) }}</dd>
                                            </div>
                                            <div>
                                                <dt>살상률</dt>
                                                <dd>{{ hallPercent(detail.hallBattle.killRate) }}</dd>
                                            </div>
                                            <div>
                                                <dt>대인 사살</dt>
                                                <dd>{{ hallNumber(detail.hallBattle.killCrewPerson) }}</dd>
                                            </div>
                                            <div>
                                                <dt>대인 살상률</dt>
                                                <dd>{{ hallPercent(detail.hallBattle.killRatePerson) }}</dd>
                                            </div>
                                        </dl>
                                    </section>
                                    <LegacyGeneralProgress
                                        v-if="detail.masteryAvailable"
                                        :general="detail.general"
                                        :show-primary="false"
                                    />
                                    <div v-else class="archive-unavailable">
                                        이 기수에는 숙련도 기록이 보존되지 않았습니다.
                                    </div>
                                </template>
                            </GeneralBasicCard>
                        </PanelCard>
                        <PanelCard title="장수 기록" subtitle="보존된 과거 기록">
                            <GeneralRecordPanels :records="archiveRecords" :unavailable="unavailableRecords" />
                        </PanelCard>
                    </div>
                </div>
            </div>
        </section>
    </main>
</template>

<style scoped>
.past-plays-page {
    width: min(1000px, 100%);
    min-height: 100vh;
    margin: 0 auto;
    color: #eee;
    background-color: #302016;
    background-image: var(--sammo-texture-walnut);
    font-family: var(--sammo-font-sans);
    font-size: 14px;
}

.title-row,
.season-heading {
    border: 1px solid #666;
    background-color: #14241b;
    background-image: var(--sammo-texture-green);
}

.title-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 10px;
}

.title-row h1 {
    margin: 0;
    color: skyblue;
    font-size: 18px;
}

.title-row nav,
.season-identity,
.season-meta,
.detail-source {
    display: flex;
    align-items: center;
    gap: 6px;
}

.legacy-button {
    box-sizing: border-box;
    min-height: 28px;
    padding: 4px 9px;
    border: 1px solid #2d5d7f;
    border-radius: 4px;
    color: #fff;
    background: #315f86;
    font: inherit;
    font-weight: 700;
    text-decoration: none;
    cursor: pointer;
}

.legacy-button:hover,
.legacy-button:focus-visible {
    border-color: skyblue;
    color: skyblue;
}

.legacy-button:active {
    transform: translateY(1px);
}

.legacy-button:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.page-note,
.empty-row,
.error-row {
    margin: 0;
    padding: 12px 10px;
    border-inline: 1px solid #666;
    border-bottom: 1px solid #666;
}

.page-note,
.season-heading span {
    color: #bbb;
}

.error-row,
.detail-error {
    color: #ff8d8d;
}

.season-card {
    margin-top: 12px;
}

.season-heading {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    padding: 7px 9px;
}

.season-heading strong {
    color: skyblue;
}

.archive-label {
    padding: 2px 5px;
    border: 1px solid #777;
    color: #fff !important;
    background: #00582c;
}

.archive-label.abandoned {
    border-color: #956f38;
    background: #66471f;
}

.season-meta {
    justify-content: flex-end;
}

.table-scroll {
    overflow-x: auto;
}

table {
    width: 100%;
    min-width: 940px;
    border-collapse: collapse;
    table-layout: auto;
    background: #191919;
}

th,
td {
    padding: 6px 7px;
    border: 1px solid #666;
    text-align: center;
    white-space: nowrap;
}

th {
    color: #ddd;
    background: #303030;
    font-weight: 600;
}

.general-name {
    color: skyblue;
    font-weight: 600;
}

.nation-name {
    display: inline-block;
    min-width: 54px;
    padding: 2px 5px;
    border: 1px solid rgb(255 255 255 / 25%);
    text-shadow: 0 1px 1px #000;
}

.detail-toggle,
.nation-archive-link {
    min-height: 24px;
    padding-block: 2px;
}

.detail-region {
    border: 1px solid #666;
    background: #101010;
}

.detail-error {
    margin: 0;
    padding: 10px 12px;
}

.detail-source {
    justify-content: space-between;
    min-height: 34px;
    padding: 4px 8px;
    border-bottom: 1px solid #666;
    background-color: #14241b;
    background-image: var(--sammo-texture-green);
}

.detail-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.detail-grid :deep(.panel-card) {
    height: 100%;
    border-radius: 0;
    box-shadow: none;
}

.detail-grid :deep(.panel-body) {
    padding: 0;
}

.detail-grid :deep(.panel-title) {
    color: skyblue;
    font-size: 18px;
    font-weight: 500;
}

.archive-unavailable {
    min-height: 28px;
    padding: 5px 8px;
    border-top: 1px solid #666;
    color: #bbb;
    text-align: center;
}

.hall-battle-record {
    border-top: 1px solid #666;
}

.hall-battle-record__heading {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 5px 8px;
    background: rgb(20 75 42 / 70%);
}

.hall-battle-record__heading span {
    color: #bbb;
    font-size: 12px;
}

.hall-battle-record dl {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    margin: 0;
}

.hall-battle-record dl > div {
    display: grid;
    grid-template-columns: minmax(70px, 1fr) 1fr;
    min-height: 24px;
    border-right: 1px solid #777;
    border-bottom: 1px solid #777;
}

.hall-battle-record dt,
.hall-battle-record dd {
    margin: 0;
    padding: 2px 5px;
}

.hall-battle-record dt {
    background: rgb(20 75 42 / 45%);
    text-align: center;
}

.hall-battle-record dd {
    text-align: right;
}

@media (max-width: 640px) {
    .title-row,
    .season-heading,
    .season-identity,
    .season-meta {
        align-items: flex-start;
        flex-direction: column;
    }

    .season-meta {
        justify-content: flex-start;
    }

    .detail-grid {
        grid-template-columns: 1fr;
    }

    .hall-battle-record__heading {
        align-items: flex-start;
        flex-direction: column;
    }
}
</style>
