'use client'; import { useMemo } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { useCombatStore, usePrestigeStore, fmt } from '@/lib/game/stores'; import { ELEMENT_OPPOSITES, FLOOR_ELEM_CYCLE } from '@/lib/game/constants'; import { getGuardianForFloor, getAllGuardianFloors } from '@/lib/game/data/guardian-encounters'; import type { GuardianDef } from '@/lib/game/types'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { SectionHeader } from '@/components/ui/section-header'; import { DebugName } from '@/components/game/debug/debug-context'; import { Mountain } from 'lucide-react'; // ─── Guardian Data ──────────────────────────────────────────────────────────── const GUARDIAN_FLOORS = getAllGuardianFloors(); // ─── Helper: Get Counter Element ───────────────────────────────────────────── function getCounterElement(element: string): string | null { return ELEMENT_OPPOSITES[element] || null; } function getElementColor(element: string): string { const colors: Record = { fire: '#FF6B35', water: '#4ECDC4', air: '#00D4FF', earth: '#F4A261', light: '#FFD700', dark: '#9B59B6', death: '#778CA3', void: '#4A235A', stellar: '#F0E68C', }; return colors[element] || '#9CA3AF'; } // ─── Sub-component: Floor Progress Bar ──────────────────────────────────────── function FloorProgressBar({ maxFloor, clearedFloors }: { maxFloor: number; clearedFloors: Record }) { const totalFloors = Math.min(maxFloor, 100); const clearedSet = new Set(Object.entries(clearedFloors).filter(([, v]) => v).map(([k]) => Number(k))); const rows: number[][] = []; for (let i = 0; i < totalFloors; i += 10) { rows.push(Array.from({ length: 10 }, (_, j) => i + j + 1).filter((f) => f <= totalFloors)); } return (
{rows.reverse().map((row) => (
{row.map((floor) => { const isCleared = clearedSet.has(floor); const isGuardian = !!getGuardianForFloor(floor); const isCurrent = floor === maxFloor; let bgClass = 'bg-gray-800'; if (isCleared) bgClass = 'bg-emerald-600/60'; else if (isCurrent) bgClass = 'bg-amber-600/60'; const borderClass = isGuardian ? 'border-amber-500' : isCurrent ? 'border-amber-400' : 'border-gray-700'; return (
{floor}
); })}
))}
); } function FloorLegend() { return (
Cleared
Uncleared
Guardian
Current
); } // ─── Top Stats Row ─────────────────────────────────────────────────────────── function TopStatsRow({ maxFloorReached, totalFloorsCleared, defeatedCount, insight }: { maxFloorReached: number; totalFloorsCleared: number; defeatedCount: number; insight: number; }) { return (
); } function StatCell({ value, label, color }: { value: number | string; label: string; color: string }) { return (
{value}
{label}
); } // ─── Next Guardian Card ────────────────────────────────────────────────────── function NextGuardianCard({ nextGuardian, nextGuardianData }: { nextGuardian: number; nextGuardianData: GuardianDef }) { const counterElement = getCounterElement(nextGuardianData.element); const nextFloorElement = FLOOR_ELEM_CYCLE[(nextGuardian - 1) % FLOOR_ELEM_CYCLE.length]; return (
{nextGuardian}
{nextGuardianData.name}
{nextGuardianData.element} HP: {fmt(nextGuardianData.hp)} {nextGuardianData.armor && ( Armor: {Math.round(nextGuardianData.armor * 100)}% )}
0.15)} />
); } function PreparationTips({ counterElement, nextFloorElement, hasHighArmor }: { counterElement: string | null; nextFloorElement: string | null; hasHighArmor: boolean }) { return (
Recommended Preparation:
{counterElement && (
Use {counterElement} spells for super effective damage (+50%)
)} {nextFloorElement && (
🔄 Floor element: {nextFloorElement}
)} {hasHighArmor && (
🛡️ High armor — consider armor-piercing or raw damage spells
)}
💡 Ensure mana pools are full before attempting
); } // ─── Guardian Roster ───────────────────────────────────────────────────────── function GuardianRoster({ clearedFloors }: { clearedFloors: Record }) { return (
{GUARDIAN_FLOORS.map((floor) => { const guardian = getGuardianForFloor(floor); return guardian ? ( ) : null; })}
); } function GuardianRosterItem({ floor, guardian, isDefeated }: { floor: number; guardian: GuardianDef; isDefeated: boolean }) { return (
{floor}
{guardian.name}
{guardian.element} HP: {fmt(guardian.hp)}
{isDefeated ? ( ✓ Defeated ) : ( Undefeated )}
); } // ─── Main Component ─────────────────────────────────────────────────────────── export function SpireSummaryTab() { const { maxFloorReached, clearedFloors, enterSpireMode, } = useCombatStore(useShallow((s) => ({ maxFloorReached: s.maxFloorReached, clearedFloors: s.clearedFloors, enterSpireMode: s.enterSpireMode, }))); const { insight } = usePrestigeStore(useShallow((s) => ({ insight: s.insight, }))); const defeatedGuardians = useMemo(() => { return GUARDIAN_FLOORS.filter((floor) => clearedFloors[floor]); }, [clearedFloors]); const nextGuardian = useMemo(() => { return GUARDIAN_FLOORS.find((floor) => !clearedFloors[floor]) || null; }, [clearedFloors]); const nextGuardianData = nextGuardian ? getGuardianForFloor(nextGuardian) : null; const totalFloorsCleared = useMemo(() => { return Object.values(clearedFloors).filter(Boolean).length; }, [clearedFloors]); return (
{nextGuardianData && nextGuardian && ( )}
); } SpireSummaryTab.displayName = 'SpireSummaryTab';