'use client'; import { useState, useMemo } from 'react'; import { Button } from '@/components/ui/button'; import { Progress } from '@/components/ui/progress'; import { GameCard } from '@/components/ui/game-card'; import { SectionHeader } from '@/components/ui/section-header'; import { StatRow } from '@/components/ui/stat-row'; import { ActionButton } from '@/components/ui/action-button'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Separator } from '@/components/ui/separator'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { AlertCircle, Wand2, Scroll, Trash2, Plus, Minus, Check } from 'lucide-react'; import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment'; import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from '@/lib/game/data/enchantment-effects'; import type { EquipmentInstance, EnchantmentDesign, DesignEffect, EquipmentCraftingProgress, EquipmentCategory } from '@/lib/game/types'; import { fmt, type GameStore } from '@/lib/game/store'; export interface EnchantmentDesignerProps { store: GameStore; selectedEquipmentType: string | null; setSelectedEquipmentType: (type: string | null) => void; selectedEffects: DesignEffect[]; setSelectedEffects: (effects: DesignEffect[]) => void; designName: string; setDesignName: (name: string) => void; selectedDesign: string | null; setSelectedDesign: (id: string | null) => void; } export function EnchantmentDesigner({ store, selectedEquipmentType, setSelectedEquipmentType, selectedEffects, setSelectedEffects, designName, setDesignName, selectedDesign, setSelectedDesign, }: EnchantmentDesignerProps) { const enchantmentDesigns = store.enchantmentDesigns; const designProgress = store.designProgress; const startDesigningEnchantment = store.startDesigningEnchantment; const cancelDesign = store.cancelDesign; const deleteDesign = store.deleteDesign; const unlockedEffects = store.unlockedEffects; const skills = store.skills; const enchantingLevel = skills.enchanting || 0; const efficiencyBonus = (skills.efficientEnchant || 0) * 0.05; // Calculate total capacity cost for current design const designCapacityCost = selectedEffects.reduce( (total, eff) => total + calculateEffectCapacityCost(eff.effectId, eff.stacks, efficiencyBonus), 0 ); // Get capacity limit for selected equipment type const selectedEquipmentCapacity = selectedEquipmentType ? EQUIPMENT_TYPES[selectedEquipmentType]?.baseCapacity || 0 : 0; const isOverCapacity = selectedEquipmentType ? designCapacityCost > selectedEquipmentCapacity : false; // Calculate design time const designTime = selectedEffects.reduce((total, eff) => total + 0.5 * eff.stacks, 1); // Add effect to design const addEffect = (effectId: string) => { const existing = selectedEffects.find(e => e.effectId === effectId); const effectDef = ENCHANTMENT_EFFECTS[effectId]; if (!effectDef) return; if (existing) { if (existing.stacks < effectDef.maxStacks) { setSelectedEffects(selectedEffects.map(e => e.effectId === effectId ? { ...e, stacks: e.stacks + 1 } : e )); } } else { setSelectedEffects([...selectedEffects, { effectId, stacks: 1, capacityCost: calculateEffectCapacityCost(effectId, 1, efficiencyBonus), }]); } }; // Remove effect from design const removeEffect = (effectId: string) => { const existing = selectedEffects.find(e => e.effectId === effectId); if (!existing) return; if (existing.stacks > 1) { setSelectedEffects(selectedEffects.map(e => e.effectId === effectId ? { ...e, stacks: e.stacks - 1 } : e )); } else { setSelectedEffects(selectedEffects.filter(e => e.effectId !== effectId)); } }; // Create design const handleCreateDesign = () => { if (!designName || !selectedEquipmentType || selectedEffects.length === 0) return; const success = startDesigningEnchantment(designName, selectedEquipmentType, selectedEffects); if (success) { // Reset form setDesignName(''); setSelectedEquipmentType(null); setSelectedEffects([]); } }; // Get available effects for selected equipment type (only unlocked ones) const getAvailableEffects = () => { if (!selectedEquipmentType) return []; const type = EQUIPMENT_TYPES[selectedEquipmentType]; if (!type) return []; return Object.values(ENCHANTMENT_EFFECTS).filter( effect => effect.allowedEquipmentCategories.includes(type.category) && unlockedEffects.includes(effect.id) ); }; // Get incompatible effects (unlocked but not for this equipment type) // Requirement (task3 bug #7): Show incompatible enchantments in greyed-out "Unavailable" section const getIncompatibleEffects = () => { if (!selectedEquipmentType) return []; const type = EQUIPMENT_TYPES[selectedEquipmentType]; if (!type) return []; return Object.values(ENCHANTMENT_EFFECTS).filter( effect => !effect.allowedEquipmentCategories.includes(type.category) && unlockedEffects.includes(effect.id) ); }; // Get equipment types that the player actually owns (has instances of) // This ensures enchantment compatibility is based on owned items, not just blueprints const getOwnedEquipmentTypes = () => { // Get all unique equipment type IDs from owned instances const ownedEquipmentTypeIds = new Set(); // Check all equipment instances the player owns for (const instance of Object.values(store.equipmentInstances)) { ownedEquipmentTypeIds.add(instance.typeId); } // Filter EQUIPMENT_TYPES to only include types the player owns return Object.values(EQUIPMENT_TYPES).filter(type => ownedEquipmentTypeIds.has(type.id)); }; const ownedEquipmentTypes = getOwnedEquipmentTypes(); const availableEffects = getAvailableEffects(); const incompatibleEffects = getIncompatibleEffects(); // Get the reason why an effect is incompatible const getIncompatibilityReason = (effect: typeof ENCHANTMENT_EFFECTS[string]): string => { if (!selectedEquipmentType) return 'No equipment selected'; const type = EQUIPMENT_TYPES[selectedEquipmentType]; if (!type) return 'Unknown equipment type'; // Check what categories this effect is allowed for const allowedCategories = effect.allowedEquipmentCategories; const equipmentCategory = type.category; if (allowedCategories.includes(equipmentCategory)) { return 'Compatible'; } // Provide specific reasons if (allowedCategories.includes('weapon' as EquipmentCategory) && equipmentCategory !== 'sword' && equipmentCategory !== 'caster' && equipmentCategory !== 'catalyst') { return `Requires a weapon (${allowedCategories.filter(c => ['sword', 'caster', 'catalyst'].includes(c)).join(', ')})`; } return `Requires ${allowedCategories.join(' or ')} equipment`; }; // Render stage return (
{/* Equipment Type Selection */} {designProgress ? (
Designing for: {EQUIPMENT_TYPES[designProgress.equipmentType]?.name}
{designProgress.name}
{designProgress.progress.toFixed(1)}h / {designProgress.required.toFixed(1)}h Cancel
) : (
{ownedEquipmentTypes.map(type => (
setSelectedEquipmentType(type.id)} role="button" tabIndex={0} aria-label={`Select ${type.name}`} >
{type.name}
Cap: {type.baseCapacity}
))}
{ownedEquipmentTypes.length === 0 && (
No equipment blueprints owned. Craft or find equipment blueprints first.
)}
)}
{/* Effect Selection */} {enchantingLevel < 1 ? (

Learn Enchanting skill to design enchantments

) : designProgress ? (
Design in progress...
{designProgress.effects.map(eff => { const def = ENCHANTMENT_EFFECTS[eff.effectId]; return (
{def?.name} x{eff.stacks} {eff.capacityCost} cap
); })}
) : !selectedEquipmentType ? (
Select an equipment type first
) : ( <>
{/* Compatible Effects */} {availableEffects.map(effect => { const selected = selectedEffects.find(e => e.effectId === effect.id); const cost = calculateEffectCapacityCost(effect.id, (selected?.stacks || 0) + 1, efficiencyBonus); return (
{effect.name}
{effect.description}
Cost: {effect.baseCapacityCost} cap | Max: {effect.maxStacks}
{selected && ( removeEffect(effect.id)} > )} addEffect(effect.id)} disabled={!selected && selectedEffects.length >= 5} >
{selected && ( {selected.stacks}/{effect.maxStacks} )}
); })} {/* Incompatible Effects - Requirement: greyed-out "Unavailable" section with tooltips */} {incompatibleEffects.length > 0 && ( <>
Unavailable
{incompatibleEffects.map(effect => { const reason = getIncompatibilityReason(effect); return (
{effect.name}
{effect.description}

Incompatible Effect

{reason}

); })} )}
{/* Selected effects summary */}
setDesignName(e.target.value)} className="w-full bg-[var(--bg-sunken)] border border-[var(--border-default)] rounded px-3 py-2 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] focus:outline-none focus:border-[var(--border-focus)]" aria-label="Design name" /> {designCapacityCost.toFixed(0)} / {selectedEquipmentCapacity} } /> {isOverCapacity ? 'Over Capacity!' : `Start Design (${designTime.toFixed(1)}h)`}
)}
{/* Saved Designs */} {enchantmentDesigns.length === 0 ? (
No saved designs yet
) : (
{enchantmentDesigns.map(design => (
setSelectedDesign(design.id)} role="button" tabIndex={0} aria-label={`Select design: ${design.name}`} >
{design.name}
{EQUIPMENT_TYPES[design.equipmentType]?.name}
{ e.stopPropagation(); deleteDesign(design.id); }} aria-label={`Delete design: ${design.name}`} >
{design.effects.length} effects | {design.totalCapacityUsed} cap
))}
)}
); } EnchantmentDesigner.displayName = 'EnchantmentDesigner';