import { useState } from 'react' import { useNavigate } from 'react-router-dom' import { RulesConfiguration, StepIndicator } from '../components' import { useRegions, useCreateGenlocke } from '../hooks/useGenlockes' import { useNamingCategories } from '../hooks/useRuns' import type { Game, GenlockeRules, Region } from '../types' import { DEFAULT_RULES } from '../types' import type { NuzlockeRules } from '../types/rules' import { RULE_DEFINITIONS } from '../types/rules' const STEPS = ['Name', 'Select Games', 'Rules', 'Confirm'] const DEFAULT_COLOR = '#6366f1' interface LegEntry { region: string game: Game } type PresetType = 'true' | 'normal' | 'custom' | null function buildLegsFromPreset(regions: Region[], preset: 'true' | 'normal'): LegEntry[] { const legs: LegEntry[] = [] for (const region of regions) { const targetSlug = preset === 'true' ? region.genlockeDefaults.trueGenlocke : region.genlockeDefaults.normalGenlocke const game = region.games.find((g) => g.slug === targetSlug) if (game) { legs.push({ region: region.name, game }) } } return legs } export function NewGenlocke() { const navigate = useNavigate() const { data: regions, isLoading: regionsLoading } = useRegions() const createGenlocke = useCreateGenlocke() const [step, setStep] = useState(1) const [name, setName] = useState('') const [legs, setLegs] = useState([]) const [preset, setPreset] = useState(null) const [nuzlockeRules, setNuzlockeRules] = useState(DEFAULT_RULES) const [genlockeRules, setGenlockeRules] = useState({ retireHoF: false, }) const [namingScheme, setNamingScheme] = useState(null) const { data: namingCategories } = useNamingCategories() const handlePresetSelect = (type: PresetType) => { setPreset(type) if (!regions) return if (type === 'true' || type === 'normal') { setLegs(buildLegsFromPreset(regions, type)) } else if (type === 'custom') { setLegs([]) } } const handleGameChange = (index: number, game: Game) => { setLegs((prev) => prev.map((leg, i) => (i === index ? { ...leg, game } : leg))) } const handleRemoveLeg = (index: number) => { setLegs((prev) => prev.filter((_, i) => i !== index)) } const handleAddLeg = (region: Region) => { const defaultSlug = region.genlockeDefaults.normalGenlocke const game = region.games.find((g) => g.slug === defaultSlug) ?? region.games[0] if (game) { setLegs((prev) => [...prev, { region: region.name, game }]) } } const handleMoveLeg = (index: number, direction: 'up' | 'down') => { const target = direction === 'up' ? index - 1 : index + 1 if (target < 0 || target >= legs.length) return setLegs((prev) => { const next = [...prev] ;[next[index], next[target]] = [next[target]!, next[index]!] return next }) } const handleCreate = () => { if (!name.trim() || legs.length === 0) return createGenlocke.mutate( { name: name.trim(), gameIds: legs.map((l) => l.game.id), genlockeRules, nuzlockeRules, namingScheme, }, { onSuccess: (data) => { const firstLeg = data.legs.find((l) => l.legOrder === 1) if (firstLeg?.runId) { navigate(`/runs/${firstLeg.runId}`) } else { navigate('/runs') } }, } ) } const enabledRuleCount = RULE_DEFINITIONS.filter((r) => nuzlockeRules[r.key]).length const totalRuleCount = RULE_DEFINITIONS.length // Regions not yet used in legs (for "add leg" picker) const availableRegions = regions?.filter((r) => !legs.some((l) => l.region === r.name)) ?? [] return (

New Genlocke

Set up your generational challenge.

{/* Step 1: Name */} {step === 1 && (

Name Your Genlocke

setName(e.target.value)} className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="My Genlocke" maxLength={100} autoFocus />
)} {/* Step 2: Game Selection */} {step === 2 && (

Select Games

{/* Preset buttons */}
{(['true', 'normal', 'custom'] as const).map((type) => { const labels = { true: 'True Genlocke', normal: 'Normal Genlocke', custom: 'Custom', } const descriptions = { true: 'Original releases only', normal: 'Latest version per region', custom: 'Build your own', } const isActive = preset === type return ( ) })}
{regionsLoading && (
)} {/* Legs list */} {legs.length > 0 && (
{legs.map((leg, index) => ( handleGameChange(index, game)} onRemove={() => handleRemoveLeg(index)} onMove={(dir) => handleMoveLeg(index, dir)} /> ))}
)} {/* Add leg button */} {preset === 'custom' && availableRegions.length > 0 && (
)} {/* Also allow adding extra regions for presets */} {preset && preset !== 'custom' && availableRegions.length > 0 && legs.length > 0 && (
)}
)} {/* Step 3: Rules */} {step === 3 && (
{/* Genlocke-specific rules */}

Genlocke Rules

Rules specific to the generational challenge

Hall of Fame Pokemon
{/* Naming scheme */}

Naming Scheme

Get nickname suggestions from a themed word list when catching Pokemon. Applied to all legs.

)} {/* Step 4: Confirm */} {step === 4 && (

Confirm & Start

Name

{name}

Legs ({legs.length})

    {legs.map((leg, i) => (
  1. {i + 1}.
    {leg.game.name} {leg.region.charAt(0).toUpperCase() + leg.region.slice(1)}
  2. ))}

Rules

Nuzlocke Rules
{enabledRuleCount} of {totalRuleCount} enabled
Hall of Fame
{genlockeRules.retireHoF ? 'Retire' : 'Keep'}
Naming Scheme
{namingScheme ? namingScheme.charAt(0).toUpperCase() + namingScheme.slice(1) : 'None'}
{createGenlocke.error && (
Failed to create genlocke. Please try again.
)}
)}
) } // --- Sub-components --- function LegRow({ leg, index, total, regions, onGameChange, onRemove, onMove, }: { leg: LegEntry index: number total: number regions: Region[] onGameChange: (game: Game) => void onRemove: () => void onMove: (dir: 'up' | 'down') => void }) { const region = regions.find((r) => r.name === leg.region) const games = region?.games ?? [] return (
{index + 1}.
{leg.region.charAt(0).toUpperCase() + leg.region.slice(1)}
{games.length > 1 ? ( ) : (
{leg.game.name}
)}
) } function AddLegDropdown({ regions, onAdd, }: { regions: Region[] onAdd: (region: Region) => void }) { const [open, setOpen] = useState(false) if (!open) { return ( ) } return (
Select a region to add
{regions.map((region) => ( ))}
) } function GameThumb({ game }: { game: Game }) { const [imgIdx, setImgIdx] = useState(0) const backgroundColor = game.color ?? DEFAULT_COLOR const boxArtSrcs = [`/boxart/${game.slug}.png`, `/boxart/${game.slug}.jpg`] if (imgIdx >= boxArtSrcs.length) { return (
{game.name.replace('Pokemon ', '').slice(0, 3)}
) } return ( {game.name} setImgIdx((i) => i + 1)} /> ) }