import { db } from './index'; import type { PicletInstance, BattleMove } from './schema'; import { PicletType, AttackType, getTypeFromConcept } from '../types/picletTypes'; import type { PicletStats } from '../types'; import { generateUnlockLevels } from '../services/unlockLevels'; // Interface for generated piclet data (from PicletResult component) interface GeneratedPicletData { name: string; imageUrl: string; imageData?: string; imageCaption: string; concept: string; imagePrompt: string; stats: PicletStats; createdAt: Date; } // Convert generated piclet data to a PicletInstance export async function generatedDataToPicletInstance(data: GeneratedPicletData, level: number = 5): Promise> { if (!data.stats) { throw new Error('Generated data must have stats to create PicletInstance'); } // All generated data must now have the battle-ready format const stats = data.stats as PicletStats; if (!stats.baseStats || !stats.specialAbility || !stats.movepool) { throw new Error('Generated stats must be in battle-ready format with baseStats, specialAbility, and movepool'); } // Calculate base stats from battle-ready format const baseHp = Math.floor(stats.baseStats.hp * 2 + 50); const baseAttack = Math.floor(stats.baseStats.attack * 1.5 + 30); const baseDefense = Math.floor(stats.baseStats.defense * 1.5 + 30); const baseSpeed = Math.floor(stats.baseStats.speed * 1.5 + 30); // Determine primary type from battle stats const normalizedType = stats.primaryType.toLowerCase(); const validType = Object.values(PicletType).find(type => type === normalizedType); const primaryType = validType || getTypeFromConcept(data.concept, data.imageCaption); if (!validType) { console.warn(`Invalid primaryType "${stats.primaryType}" from stats, falling back to concept detection`); } // Create moves from battle-ready format (without unlock levels initially) const baseMoves: BattleMove[] = stats.movepool.map(move => ({ name: move.name, type: move.type as unknown as AttackType, power: move.power, accuracy: move.accuracy, pp: move.pp, currentPp: move.pp, description: move.description, unlockLevel: 1 // Temporary, will be set below })); // Generate unlock levels for moves and special ability const { movesWithUnlocks, abilityUnlockLevel } = generateUnlockLevels(baseMoves, stats.specialAbility); const moves = movesWithUnlocks; // Field stats are variations of regular stats const baseFieldAttack = Math.floor(baseAttack * 0.8); const baseFieldDefense = Math.floor(baseDefense * 0.8); // Use Pokemon-accurate stat calculations (matching levelingService) const calculateStat = (base: number, level: number) => { if (level === 1) { return Math.max(1, Math.floor(base / 10) + 5); } return Math.floor((2 * base * level) / 100) + 5; }; const calculateHp = (base: number, level: number) => { if (level === 1) { return Math.max(1, Math.floor(base / 10) + 11); } return Math.floor((2 * base * level) / 100) + level + 10; }; const maxHp = calculateHp(baseHp, level); const bst = baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed; // Check if this is the first piclet (no existing piclets in database) const existingPiclets = await db.picletInstances.count(); const isFirstPiclet = existingPiclets === 0; return { // Type Info typeId: data.name.toLowerCase().replace(/\s+/g, '-'), nickname: data.name, primaryType: primaryType, secondaryType: undefined, // Current Stats currentHp: maxHp, maxHp, level, xp: 0, attack: calculateStat(baseAttack, level), defense: calculateStat(baseDefense, level), fieldAttack: calculateStat(baseFieldAttack, level), fieldDefense: calculateStat(baseFieldDefense, level), speed: calculateStat(baseSpeed, level), // Base Stats baseHp, baseAttack, baseDefense, baseFieldAttack, baseFieldDefense, baseSpeed, // Battle moves, nature: stats.nature, specialAbility: stats.specialAbility, specialAbilityUnlockLevel: abilityUnlockLevel, // Roster isInRoster: isFirstPiclet, rosterPosition: isFirstPiclet ? 0 : undefined, // Metadata caught: isFirstPiclet, // First piclet is automatically caught caughtAt: isFirstPiclet ? new Date() : undefined, bst, tier: stats.tier, // Use tier from stats role: 'balanced', // Could be enhanced based on stat distribution variance: 0, // Original generation data imageUrl: data.imageUrl, imageData: data.imageData, imageCaption: data.imageCaption, concept: data.concept, description: stats.description, imagePrompt: data.imagePrompt }; } // Save a new PicletInstance export async function savePicletInstance(piclet: Omit): Promise { return await db.picletInstances.add(piclet); } // Mark a Piclet as caught export async function catchPiclet(picletId: number): Promise { await db.picletInstances.update(picletId, { caught: true, caughtAt: new Date() }); } // Get only caught Piclets (for Pictuary and battle roster) export async function getCaughtPiclets(): Promise { const allPiclets = await db.picletInstances.toArray(); return allPiclets.filter(p => p.caught === true); } // Get uncaught Piclets (for encounters) export async function getUncaughtPiclets(): Promise { const allPiclets = await db.picletInstances.toArray(); return allPiclets.filter(p => p.caught === false); } // Get all PicletInstances export async function getAllPicletInstances(): Promise { return await db.picletInstances.toArray(); } // Get roster PicletInstances export async function getRosterPiclets(): Promise { const allPiclets = await db.picletInstances.toArray(); return allPiclets .filter(p => p.caught === true && // Only caught Piclets can be in roster p.rosterPosition !== undefined && p.rosterPosition !== null && p.rosterPosition >= 0 && p.rosterPosition <= 5 ) .sort((a, b) => (a.rosterPosition ?? 0) - (b.rosterPosition ?? 0)); } // Update roster position export async function updateRosterPosition(id: number, position: number | undefined): Promise { await db.picletInstances.update(id, { isInRoster: position !== undefined, rosterPosition: position }); } // Move piclet to roster export async function moveToRoster(id: number, position: number): Promise { // Check if position is already occupied const existingPiclet = await db.picletInstances .where('rosterPosition') .equals(position) .and(item => item.isInRoster) .first(); if (existingPiclet) { // Move existing piclet to storage await db.picletInstances.update(existingPiclet.id!, { isInRoster: false, rosterPosition: undefined }); } // Move new piclet to roster await db.picletInstances.update(id, { isInRoster: true, rosterPosition: position }); } // Swap roster positions export async function swapRosterPositions(id1: number, position1: number, id2: number, position2: number): Promise { await db.transaction('rw', db.picletInstances, async () => { await db.picletInstances.update(id1, { rosterPosition: position2 }); await db.picletInstances.update(id2, { rosterPosition: position1 }); }); } // Move piclet to storage export async function moveToStorage(id: number): Promise { await db.picletInstances.update(id, { isInRoster: false, rosterPosition: undefined }); } // Get storage piclets export async function getStoragePiclets(): Promise { const allPiclets = await db.picletInstances.toArray(); return allPiclets.filter(p => p.caught === true && // Only caught Piclets can be in storage (p.rosterPosition === undefined || p.rosterPosition === null || p.rosterPosition < 0 || p.rosterPosition > 5) ); } // Delete a PicletInstance export async function deletePicletInstance(id: number): Promise { await db.picletInstances.delete(id); }