import React, { useEffect, useState, useCallback } from 'react'; import { useDisciplineStore } from '@/lib/game/stores/discipline-slice'; import type { DisciplineDefinition } from '@/lib/game/types/disciplines'; import { baseDisciplines } from '@/lib/game/data/disciplines/base'; import { enchanterDisciplines } from '@/lib/game/data/disciplines/enchanter'; import { fabricatorDisciplines } from '@/lib/game/data/disciplines/fabricator'; import { invokerDisciplines } from '@/lib/game/data/disciplines/invoker'; import { calculateStatBonus, calculateManaDrain } from '@/lib/game/utils/discipline-math'; import clsx from 'clsx'; interface AttunementTab { key: string; label: string; items: DisciplineDefinition[]; } const ATTUNEMENT_TABS: AttunementTab[] = [ { key: 'base', label: 'Base', items: baseDisciplines }, { key: 'enchanter', label: 'Enchanter', items: enchanterDisciplines }, { key: 'fabricator', label: 'Fabricator', items: fabricatorDisciplines }, { key: 'invoker', label: 'Invoker', items: invokerDisciplines }, ]; interface DisciplineCardProps { id: string; name: string; description: string; perkThresholds?: number[]; perkValues?: number[]; perkTypes?: string[]; statBonus: string; baseValue: number; drainBase: number; difficultyFactor: number; scalingFactor: number; xp: number; paused: boolean; concurrentLimit: number; onToggle: (id: string, paused: boolean) => void; } const DisciplineCard: React.FC = ({ id, name, description, perkThresholds, perkValues, perkTypes, statBonus, baseValue, drainBase, difficultyFactor, scalingFactor, xp, paused, concurrentLimit, onToggle, }) => { const displayXp = xp; const progressPercent = Math.min(displayXp / Math.max(1, concurrentLimit * 100), 100); const isPaused = paused; const activeStatBonus = calculateStatBonus(baseValue, displayXp, scalingFactor); const estimatedDrain = calculateManaDrain(drainBase, displayXp, difficultyFactor); const unlockedPerks = perkTypes?.reduce((acc, typ, idx) => { const threshold = perkThresholds?.[idx]; if (threshold === undefined) return acc; if (typ === 'once' || typ === 'infinite') { if (displayXp >= threshold) acc.push(`${typ}-${idx}`); } else if (typ === 'capped') { const interval = perkValues?.[idx] ?? 1; const tier = Math.max(0, Math.floor((displayXp - threshold) / interval) + 1); if (tier > 0) acc.push(`${typ}-${idx}`); } return acc; }, []); const toggleAction = () => { onToggle(id, isPaused); }; return (

{name}

{description}

{Math.round(progressPercent)}%
0 ? 'bg-green-500' : 'bg-red-500'}`} style={{ width: `${Math.round(progressPercent)}%` }} />
Drain: {estimatedDrain.toFixed(1)} ✦{' '} XP: {displayXp}
Stat Bonus: {activeStatBonus.toFixed(2)} on {statBonus}
Perks:
    {unlockedPerks && unlockedPerks.length > 0 ? ( unlockedPerks.map((p) => (
  • {p.replace(/-([0-9]+)$/, ' $1')}
  • )) ) : (
  • —locked—
  • )}
); }; export const DisciplinesTab: React.FC = () => { const activeIds = useDisciplineStore((s) => s.activeIds); const concurrentLimit = useDisciplineStore((s) => s.concurrentLimit); const disciplines = useDisciplineStore((s) => s.disciplines); const activate = useDisciplineStore((s) => s.activate); const deactivate = useDisciplineStore((s) => s.deactivate); const [mounted, setMounted] = useState(false); const [activeAttunement, setActiveAttunement] = useState('base'); useEffect(() => { setMounted(true); }, []); const handleToggle = useCallback((id: string, paused: boolean) => { if (paused) { activate(id); } else { deactivate(id); } }, [activate, deactivate]); if (!mounted) { return (
Loading disciplines…
); } const activeTab = ATTUNEMENT_TABS.find((t) => t.key === activeAttunement); return (
{/* Tab bar */}
{ATTUNEMENT_TABS.map((tab) => { const isActiveTab = activeAttunement === tab.key; return ( ); })}
{/* Discipline cards — only render active tab */}
{activeTab?.items.map((disc) => { const discState = disciplines[disc.id] ?? { xp: 0, paused: true }; return ( p.threshold)} perkValues={disc.perks?.map((p) => p.value)} perkTypes={disc.perks?.map((p) => p.type)} statBonus={disc.statBonus.stat} baseValue={disc.statBonus.baseValue} drainBase={disc.drainBase} difficultyFactor={disc.difficultyFactor} scalingFactor={disc.scalingFactor} xp={discState.xp} paused={discState.paused} concurrentLimit={concurrentLimit} onToggle={handleToggle} /> ); })}
{/* Summary info */}
Active Disciple{activeIds.length}{activeIds.length === 1 ? '' : 's'} / {concurrentLimit}
Concurrent Limit: {concurrentLimit}
); };