import type { DeckCategory } from '#lib/content'; import { cardCategory, categoryOrder, categoryTitles, copyLimit, deckSizeLimit, isBasicEnergy } from './cardKind'; import type { TcgCard } from './types'; export interface BuilderCard { id: string; name: string; supertype?: string; subtypes?: string[]; image: string; setCode?: string; number?: string; } export interface BuilderEntry { card: BuilderCard; count: number; } export interface BuilderSection { category: DeckCategory; title: string; total: number; entries: BuilderEntry[]; } const storageKey = 'tcg-deck-builder'; const defaultDeckName = 'my deck'; export function toBuilderCard(card: TcgCard): BuilderCard { return { id: card.id, name: card.name, supertype: card.supertype, subtypes: card.subtypes, image: card.images.small, setCode: card.set.ptcgoCode, number: card.number }; } class DeckBuilderState { name = $state(defaultDeckName); entries = $state([]); private loaded = false; total = $derived(this.entries.reduce((sum, entry) => sum + entry.count, 0)); sections = $derived( categoryOrder .map((category) => { const entries = this.entries.filter((entry) => cardCategory(entry.card) === category); return { category, title: categoryTitles[category], total: entries.reduce((sum, entry) => sum + entry.count, 0), entries }; }) .filter((section) => section.entries.length > 0) ); load() { if (this.loaded) return; this.loaded = true; try { const stored = JSON.parse(localStorage.getItem(storageKey) ?? 'null'); if (!stored) return; this.name = typeof stored.name === 'string' ? stored.name : defaultDeckName; this.entries = Array.isArray(stored.entries) ? stored.entries : []; } catch { return; } } private save() { try { localStorage.setItem(storageKey, JSON.stringify({ name: this.name, entries: this.entries })); } catch { return; } } countOf(cardId: string): number { return this.entries.find((entry) => entry.card.id === cardId)?.count ?? 0; } private copiesNamed(name: string): number { return this.entries.filter((entry) => entry.card.name === name).reduce((sum, entry) => sum + entry.count, 0); } canAdd(card: Pick): boolean { if (this.total >= deckSizeLimit) return false; return isBasicEnergy(card) || this.copiesNamed(card.name) < copyLimit; } add(card: TcgCard) { if (!this.canAdd(card)) return; const entry = this.entries.find((item) => item.card.id === card.id); if (entry) entry.count += 1; else this.entries.push({ card: toBuilderCard(card), count: 1 }); this.save(); } increase(cardId: string) { const entry = this.entries.find((item) => item.card.id === cardId); if (!entry || !this.canAdd(entry.card)) return; entry.count += 1; this.save(); } remove(cardId: string) { const entry = this.entries.find((item) => item.card.id === cardId); if (!entry) return; entry.count -= 1; if (entry.count <= 0) this.entries = this.entries.filter((item) => item.card.id !== cardId); this.save(); } rename(name: string) { this.name = name; this.save(); } replace(name: string, entries: BuilderEntry[]) { this.name = name; this.entries = entries; this.save(); } clear() { this.entries = []; this.save(); } exportText(): string { const lines = this.sections.flatMap((section) => [ `${section.title}: ${section.total}`, ...section.entries.map((entry) => [entry.count, entry.card.name, entry.card.setCode, entry.card.number].filter(Boolean).join(' ')), '' ]); return [...lines, `Total Cards: ${this.total}`].join('\n'); } } export const deckBuilder = new DeckBuilderState();