"use client"; /** * Progress tracking — localStorage-based profile. * Structure ready for server-side sync in the future. */ import { GAMES, GameId, TOTAL_LEVELS } from "./levels"; const PROFILE_KEY = "pt-profile"; const PROGRESS_KEY = (game: GameId) => `pt-progress-${game}`; export interface Profile { id: string; // UUID createdAt: string; // ISO date name?: string; } export interface LevelRecord { completedAt: string; // ISO datetime bestTime: number; // seconds attempts: number; } export type GameProgress = Record; // level → record // ── Profile ─────────────────────────────────────────────────────────────────── function uuid(): string { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = Math.random() * 16 | 0; return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16); }); } export function getProfile(): Profile { if (typeof window === "undefined") return { id: "ssr", createdAt: new Date().toISOString() }; const raw = localStorage.getItem(PROFILE_KEY); if (raw) try { return JSON.parse(raw) as Profile; } catch { /* corrupted, re-create */ } const profile: Profile = { id: uuid(), createdAt: new Date().toISOString() }; localStorage.setItem(PROFILE_KEY, JSON.stringify(profile)); return profile; } // ── Progress ────────────────────────────────────────────────────────────────── export function getGameProgress(game: GameId): GameProgress { if (typeof window === "undefined") return {}; const raw = localStorage.getItem(PROGRESS_KEY(game)); if (!raw) return {}; try { return JSON.parse(raw); } catch { return {}; } } export function recordLevelSolve(game: GameId, level: number, elapsed: number): GameProgress { const progress = getGameProgress(game); const existing = progress[level]; progress[level] = { completedAt: existing?.completedAt ?? new Date().toISOString(), bestTime: existing ? Math.min(existing.bestTime, elapsed) : elapsed, attempts: (existing?.attempts ?? 0) + 1, }; localStorage.setItem(PROGRESS_KEY(game), JSON.stringify(progress)); return progress; } /** Returns the next uncompleted level (1-based), or null if all done. */ export function nextLevel(game: GameId): number { const progress = getGameProgress(game); for (let l = 1; l <= TOTAL_LEVELS; l++) { if (!progress[l]) return l; } return TOTAL_LEVELS; // all done → stay on last } /** Summary stats for a game. */ export interface GameStats { completed: number; total: number; pct: number; // 0–100 bestTime: number; // fastest solve (seconds), 0 if none nextLevel: number; // next uncompleted level } export function gameStats(game: GameId): GameStats { const progress = getGameProgress(game); const completed = Object.keys(progress).length; const best = Object.values(progress).reduce((m, r) => Math.min(m, r.bestTime), Infinity); return { completed, total: TOTAL_LEVELS, pct: Math.round((completed / TOTAL_LEVELS) * 100), bestTime: isFinite(best) ? best : 0, nextLevel: nextLevel(game), }; } export function allStats(): Record { return Object.fromEntries(GAMES.map(g => [g, gameStats(g)])) as Record; }