import { db } from './db'; import { newId } from '@/lib/ids'; import { campaignSchema, characterSchema, encounterSchema, diceRollSchema, type Campaign, type CampaignDraft, type Character, type Encounter, type DiceRoll, } from '@/lib/schemas'; import { getSystem } from '@/lib/rules'; function now(): string { return new Date().toISOString(); } // ---------------- Campaigns ---------------- export const campaignsRepo = { async list(): Promise { const all = await db.campaigns.toArray(); return all.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); }, get(id: string): Promise { return db.campaigns.get(id); }, async create(draft: CampaignDraft): Promise { const ts = now(); const campaign = campaignSchema.parse({ ...draft, id: newId(), description: draft.description ?? '', createdAt: ts, updatedAt: ts, }); await db.campaigns.add(campaign); return campaign; }, async update(id: string, patch: Partial>): Promise { await db.campaigns.update(id, { ...patch, updatedAt: now() }); }, /** Delete a campaign and ALL its children atomically (real cascade). */ async remove(id: string): Promise { await db.transaction('rw', db.campaigns, db.characters, db.encounters, db.diceRolls, async () => { await db.characters.where('campaignId').equals(id).delete(); await db.encounters.where('campaignId').equals(id).delete(); await db.diceRolls.where('campaignId').equals(id).delete(); await db.campaigns.delete(id); }); }, }; // ---------------- Characters ---------------- export const charactersRepo = { listByCampaign(campaignId: string): Promise { return db.characters.where('campaignId').equals(campaignId).toArray(); }, get(id: string): Promise { return db.characters.get(id); }, async create( campaignId: string, draft: Pick & { system: Character['system']; }, ): Promise { const ts = now(); const sys = getSystem(draft.system); const character = characterSchema.parse({ id: newId(), campaignId, system: draft.system, kind: draft.kind, name: draft.name, ancestry: draft.ancestry, className: draft.className, level: draft.level, abilities: sys.defaultAbilityScores(), hp: { current: 1, max: 1, temp: 0 }, speed: 30, armorBonus: 0, skillRanks: {}, saveRanks: {}, perceptionRank: 'trained', notes: '', createdAt: ts, updatedAt: ts, }); await db.characters.add(character); return character; }, async update(id: string, patch: Partial>): Promise { await db.characters.update(id, { ...patch, updatedAt: now() }); }, async remove(id: string): Promise { await db.characters.delete(id); }, }; // ---------------- Encounters ---------------- export const encountersRepo = { listByCampaign(campaignId: string): Promise { return db.encounters.where('campaignId').equals(campaignId).toArray(); }, get(id: string): Promise { return db.encounters.get(id); }, async create(campaignId: string, name: string): Promise { const ts = now(); const encounter = encounterSchema.parse({ id: newId(), campaignId, name, status: 'planning', round: 0, turnIndex: 0, combatants: [], createdAt: ts, updatedAt: ts, }); await db.encounters.add(encounter); return encounter; }, /** Persist a full encounter (combat state lives as one document). */ async save(encounter: Encounter): Promise { const validated = encounterSchema.parse({ ...encounter, updatedAt: now() }); await db.encounters.put(validated); }, async remove(id: string): Promise { await db.encounters.delete(id); }, }; // ---------------- Dice rolls ---------------- export const diceRepo = { async recent(campaignId: string | undefined, limit = 50): Promise { const coll = campaignId ? db.diceRolls.where('campaignId').equals(campaignId) : db.diceRolls.toCollection(); const all = await coll.toArray(); return all.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, limit); }, async add(roll: Omit): Promise { const record = diceRollSchema.parse({ ...roll, id: newId(), createdAt: now() }); await db.diceRolls.add(record); return record; }, async clear(campaignId?: string): Promise { if (campaignId) await db.diceRolls.where('campaignId').equals(campaignId).delete(); else await db.diceRolls.clear(); }, };