import { useMemo } from 'react' import type { MilitarySituation } from '@/data/mockData' import { useSituationStore } from '@/store/situationStore' import { usePlaybackStore } from '@/store/playbackStore' /** 将系列时间映射到回放日 (2026-03-01) 以便按当天时刻插值 */ function toReplayDay(iso: string, baseDay: string): string { const d = new Date(iso) const [y, m, day] = baseDay.slice(0, 10).split('-').map(Number) return new Date(y, (m || 1) - 1, day || 1, d.getUTCHours(), d.getUTCMinutes(), 0, 0).toISOString() } function interpolateAt( series: { time: string; value: number }[], at: string, baseDay = '2026-03-01' ): number { if (series.length === 0) return 0 const t = new Date(at).getTime() const mapped = series.map((p) => ({ time: toReplayDay(p.time, baseDay), value: p.value, })) const sorted = [...mapped].sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime()) const before = sorted.filter((p) => new Date(p.time).getTime() <= t) const after = sorted.filter((p) => new Date(p.time).getTime() > t) if (before.length === 0) return sorted[0].value if (after.length === 0) return sorted[sorted.length - 1].value const a = before[before.length - 1] const b = after[0] const ta = new Date(a.time).getTime() const tb = new Date(b.time).getTime() const f = tb === ta ? 1 : (t - ta) / (tb - ta) return a.value + f * (b.value - a.value) } function linearProgress(start: string, end: string, at: string): number { const ts = new Date(start).getTime() const te = new Date(end).getTime() const ta = new Date(at).getTime() if (ta <= ts) return 0 if (ta >= te) return 1 return (ta - ts) / (te - ts) } /** 根据回放时刻派生态势数据 */ export function useReplaySituation(): MilitarySituation { const situation = useSituationStore((s) => s.situation) const { isReplayMode, playbackTime } = usePlaybackStore() return useMemo(() => { if (!isReplayMode) return situation const progress = linearProgress('2026-03-01T02:00:00.000Z', '2026-03-01T11:45:00.000Z', playbackTime) // 华尔街趋势、反击情绪:按时间插值 const wsValue = interpolateAt(situation.usForces.wallStreetInvestmentTrend, playbackTime) const retValue = interpolateAt(situation.iranForces.retaliationSentimentHistory, playbackTime) // 战斗损失:从 0 线性增长到当前值 const lerp = (a: number, b: number) => Math.round(a + progress * (b - a)) const usLoss = situation.usForces.combatLosses const irLoss = situation.iranForces.combatLosses const civTotal = situation.civilianCasualtiesTotal ?? { killed: 0, wounded: 0 } const usLossesAt = { bases: { destroyed: lerp(0, usLoss.bases.destroyed), damaged: lerp(0, usLoss.bases.damaged), }, personnelCasualties: { killed: lerp(0, usLoss.personnelCasualties.killed), wounded: lerp(0, usLoss.personnelCasualties.wounded), }, civilianCasualties: { killed: 0, wounded: 0 }, aircraft: lerp(0, usLoss.aircraft), warships: lerp(0, usLoss.warships), armor: lerp(0, usLoss.armor), vehicles: lerp(0, usLoss.vehicles), drones: lerp(0, usLoss.drones ?? 0), missiles: lerp(0, usLoss.missiles ?? 0), helicopters: lerp(0, usLoss.helicopters ?? 0), submarines: lerp(0, usLoss.submarines ?? 0), tanks: lerp(0, usLoss.tanks ?? 0), civilianShips: lerp(0, usLoss.civilianShips ?? 0), airportPort: lerp(0, usLoss.airportPort ?? 0), } const irLossesAt = { bases: { destroyed: lerp(0, irLoss.bases.destroyed), damaged: lerp(0, irLoss.bases.damaged), }, personnelCasualties: { killed: lerp(0, irLoss.personnelCasualties.killed), wounded: lerp(0, irLoss.personnelCasualties.wounded), }, civilianCasualties: { killed: 0, wounded: 0 }, aircraft: lerp(0, irLoss.aircraft), warships: lerp(0, irLoss.warships), armor: lerp(0, irLoss.armor), vehicles: lerp(0, irLoss.vehicles), drones: lerp(0, irLoss.drones ?? 0), missiles: lerp(0, irLoss.missiles ?? 0), helicopters: lerp(0, irLoss.helicopters ?? 0), submarines: lerp(0, irLoss.submarines ?? 0), tanks: lerp(0, irLoss.tanks ?? 0), civilianShips: lerp(0, irLoss.civilianShips ?? 0), airportPort: lerp(0, irLoss.airportPort ?? 0), } // 被袭基地:按 damage_level 排序,高损毁先出现;根据 progress 决定显示哪些为 attacked const usLocs = situation.usForces.keyLocations || [] const attackedBases = usLocs .filter((loc) => loc.status === 'attacked') .sort((a, b) => (b.damage_level ?? 0) - (a.damage_level ?? 0)) const totalAttacked = attackedBases.length const shownAttackedCount = Math.round(progress * totalAttacked) const attackedNames = new Set( attackedBases.slice(0, shownAttackedCount).map((l) => l.name) ) const usLocsAt = usLocs.map((loc) => { if (loc.status === 'attacked' && !attackedNames.has(loc.name)) { return { ...loc, status: 'operational' as const } } return { ...loc } }) return { ...situation, lastUpdated: playbackTime, civilianCasualtiesTotal: { killed: lerp(0, civTotal.killed), wounded: lerp(0, civTotal.wounded), }, usForces: { ...situation.usForces, keyLocations: usLocsAt, combatLosses: usLossesAt, wallStreetInvestmentTrend: [ ...situation.usForces.wallStreetInvestmentTrend.filter((p) => new Date(p.time).getTime() <= new Date(playbackTime).getTime()), { time: playbackTime, value: wsValue }, ].slice(-20), }, iranForces: { ...situation.iranForces, combatLosses: irLossesAt, retaliationSentiment: retValue, retaliationSentimentHistory: [ ...situation.iranForces.retaliationSentimentHistory.filter((p) => new Date(p.time).getTime() <= new Date(playbackTime).getTime()), { time: playbackTime, value: retValue }, ].slice(-20), }, recentUpdates: (situation.recentUpdates || []).filter( (u) => new Date(u.timestamp).getTime() <= new Date(playbackTime).getTime() ), conflictEvents: situation.conflictEvents || [], conflictStats: situation.conflictStats || { total_events: 0, high_impact_events: 0, estimated_casualties: 0, estimated_strike_count: 0 }, } }, [situation, isReplayMode, playbackTime]) }