import { useEffect, useMemo, useRef, useState } from 'react' import { saveProfile, type CharacterProfile, type GameClass, } from '../profile' import { useDualScreen, useDualScreenWorkshopPublisher, type DualScreenWorkshopState } from '../dualScreen' import { EquipmentScreen } from './EquipmentScreen' import { TalentScreen } from './TalentScreen' type Props = { profile: CharacterProfile onBack: () => void onSaved: (profile: CharacterProfile) => void } export function CustomizeScreen({ profile, onBack, onSaved }: Props) { const [activeTab, setActiveTab] = useState<'equipment' | 'crafting' | 'talents' | 'class'>('class') const { enabled: dualScreenEnabled } = useDualScreen() const [classId, setClassId] = useState(profile.character.classId) const [slots, setSlots] = useState>(profile.abilitySlots) const [selectedSlot, setSelectedSlot] = useState(0) const [message, setMessage] = useState('') const [saving, setSaving] = useState(false) const scrollRef = useRef(0) const gameClass = profile.classes.find((candidate) => candidate.id === classId)! const abilityMap = useMemo( () => new Map(gameClass.spells.map((ability) => [ability.id, ability])), [gameClass], ) useEffect(() => { window.scrollTo(0, scrollRef.current) }, [profile]) function saveScroll() { scrollRef.current = window.scrollY } function chooseClass(nextClass: GameClass) { const starterAbilities = nextClass.spells .filter((ability) => ability.unlockLevel <= profile.character.level) .slice(0, 5) .map((ability) => ability.id) setClassId(nextClass.id) setSlots([...starterAbilities, ...Array(6 - starterAbilities.length).fill(null)]) setSelectedSlot(0) setMessage('') } function equipAbility(abilityId: number) { if (slots.includes(abilityId)) { setMessage('That ability is already equipped.') return } setSlots((current) => current.map((spellId, index) => index === selectedSlot ? abilityId : spellId), ) setMessage('') } function clearSlot() { setSlots((current) => current.map((spellId, index) => index === selectedSlot ? null : spellId), ) } const classWorkshopState = useMemo(() => { if (activeTab !== 'class') return null return { mode: 'class', title: 'Ability Library', subtitle: gameClass.name, summary: `Selected slot ${selectedSlot + 1}. ${message || 'Choose an ability for the active loadout.'}`, items: gameClass.spells.map((ability) => { const locked = ability.unlockLevel > profile.character.level const equipped = slots.includes(ability.id) return { glyph: locked ? 'L' : ability.glyph, title: ability.name, meta: locked ? `Level ${ability.unlockLevel}` : `${ability.cost} ${gameClass.resourceName}`, detail: ability.description, status: equipped ? 'Equipped' : locked ? 'Locked' : '', } }), } }, [activeTab, gameClass, message, profile.character.level, selectedSlot, slots]) useDualScreenWorkshopPublisher(classWorkshopState, dualScreenEnabled) async function persistChanges() { saveScroll() setSaving(true) setMessage('') try { const updated = await saveProfile(classId, slots) onSaved(updated) setMessage('Character saved.') } catch (reason) { setMessage(reason instanceof Error ? reason.message : 'Unable to save character.') } finally { setSaving(false) } } return (

Character Workshop

Customize Character

{([ { key: 'equipment', label: 'Equipment' }, { key: 'crafting', label: 'Crafting' }, { key: 'talents', label: 'Talents' }, { key: 'class', label: 'Class' }, ] as const).map((tab) => ( ))}
{activeTab === 'equipment' && ( )} {activeTab === 'crafting' && ( )} {activeTab === 'talents' && ( )} {activeTab === 'class' && (
{gameClass.name[0]}

Level {profile.character.level} Healer

{gameClass.name}

{gameClass.description}

Active Loadout

Ability Bar

Select a slot, then choose an ability.
{slots.map((abilityId, index) => { const ability = abilityId ? abilityMap.get(abilityId) : undefined return ( ) })}

Class Abilities

Ability Library

{gameClass.spells.map((ability) => { const locked = ability.unlockLevel > profile.character.level const equipped = slots.includes(ability.id) return ( ) })}
{message}
)}
) }