import type { DifficultyKey } from '../data/difficulty.ts'; import type { CooldownReduction, ExactOptimizationResult } from '../engine/types.ts'; export type AnalysisSettings = { songId: string; difficulty: DifficultyKey; cardIds: string[]; awakenings: number[]; fixedCooldownReductions?: (CooldownReduction | null)[]; allBlueNodes?: boolean[]; generationBonusOverrides?: (number | null)[]; songScoreBonusOverride?: number | null; leaderCharacterId: string; leaderOutfitCardId: string; }; export type GrowthSnapshot = { collection: Record; memberBoards: Record; boardCards: Record>; memoryCount: number; memberPowerUpPercent: number; }; export type StoredResult = Omit; export type AnalysisCapture = { settings: AnalysisSettings; growth: GrowthSnapshot; title: string; durationSeconds: number; overallPower: number; analyzedAt: string; }; export type AnalysisRecord = Omit & { id: string; name: string; snapshotId: string; result: StoredResult; }; export function compactResult(result: ExactOptimizationResult): StoredResult { const { notes, scoreBySecond, ...stored } = result; return structuredClone(stored); } // Object insertion order and board selection order must not create duplicate snapshots. export function snapshotKey(value: unknown): string { if (Array.isArray(value)) return JSON.stringify(value.map((item) => snapshotKey(item)).sort()); if (value && typeof value === 'object') return JSON.stringify(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, snapshotKey(item)])); return JSON.stringify(value) ?? 'null'; } export function currentGrowthSettings(settings: AnalysisSettings, growth: GrowthSnapshot): AnalysisSettings { const missing = settings.cardIds.filter((id) => !growth.collection[id]?.owned); if (missing.length) throw new Error('현재 보유 데이터에 없는 편성 카드가 있습니다. 보유 카드와 개화를 등록한 후 비교해 주세요.'); return { ...structuredClone(settings), awakenings: settings.cardIds.map((id) => growth.collection[id]?.awakening ?? 0) }; }