import { defineStore } from 'pinia'; import { computed, ref, watch } from 'vue'; import { playersReplicant } from '../../../browser_shared/replicants'; import type { Schemas } from '../../../types'; type PlayersMap = Schemas.Players; type Player = PlayersMap[string]; const STORAGE_KEY = 'scoreko-dev.players'; const normalizePlayer = (input: unknown): Player => { const candidate = typeof input === 'object' && input !== null ? (input as Record) : {}; return { gamertag: typeof candidate.gamertag === 'string' ? candidate.gamertag : '', team: typeof candidate.team === 'string' ? candidate.team : '', country: typeof candidate.country === 'string' ? candidate.country : '', twitter: typeof candidate.twitter === 'string' ? candidate.twitter : '', realName: typeof candidate.realName === 'string' ? candidate.realName : '', pronouns: typeof candidate.pronouns === 'string' ? candidate.pronouns : '', twitch: typeof candidate.twitch === 'string' ? candidate.twitch : '', notes: typeof candidate.notes === 'string' ? candidate.notes : '', }; }; const normalizePlayers = (input: unknown): PlayersMap => { if (typeof input !== 'object' || input === null) { return {}; } const result: PlayersMap = {}; Object.entries(input as Record).forEach(([id, value]) => { if (!id) { return; } result[id] = normalizePlayer(value); }); return result; }; const readStorage = (): PlayersMap | null => { if (typeof window === 'undefined') { return null; } try { const raw = window.localStorage.getItem(STORAGE_KEY); if (!raw) { return null; } const parsed = JSON.parse(raw) as unknown; return normalizePlayers(parsed); } catch { return null; } }; const writeStorage = (value: PlayersMap) => { if (typeof window === 'undefined') { return; } try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(value)); } catch { // Ignore storage errors (quota, disabled, etc.) } }; export const usePlayersStore = defineStore('players', () => { const players = ref({}); const replicant = playersReplicant; const storageSnapshot = readStorage(); if (storageSnapshot) { players.value = storageSnapshot; } const isApplyingReplicant = ref(false); watch( () => replicant?.data, (value) => { if (!value) { return; } isApplyingReplicant.value = true; players.value = normalizePlayers(value); isApplyingReplicant.value = false; writeStorage(players.value); }, { deep: true, immediate: true } ); watch( players, (value) => { writeStorage(value); if (isApplyingReplicant.value || !replicant) { return; } replicant.data = normalizePlayers(value); replicant.save(); }, { deep: true, flush: 'sync' } ); const setPlayers = (value: PlayersMap) => { players.value = normalizePlayers(value); }; const upsertPlayer = (id: string, player: Player) => { players.value = { ...players.value, [id]: normalizePlayer(player), }; }; const removePlayer = (id: string) => { const next = { ...players.value }; delete next[id]; players.value = next; }; const rows = computed(() => Object.entries(players.value).map(([id, player]) => ({ id, ...player, }))); return { players, rows, setPlayers, upsertPlayer, removePlayer, }; });