import React, { useState, useCallback } from 'react'; import { useDisciplineStore } from '@/lib/game/stores/discipline-slice'; import type { DisciplineDefinition } from '@/lib/game/types/disciplines'; import type { ManaType } from '@/lib/game/types/elements'; import { ELEMENTS } from '@/lib/game/constants/elements'; import { baseDisciplines } from '@/lib/game/data/disciplines/base'; import { elementalAttunementDisciplines } from '@/lib/game/data/disciplines/elemental'; import { elementalRegenDisciplines } from '@/lib/game/data/disciplines/elemental-regen'; import { elementalRegenAdvancedDisciplines } from '@/lib/game/data/disciplines/elemental-regen-advanced'; 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, checkDisciplinePrerequisites } from '@/lib/game/utils/discipline-math'; import { ALL_DISCIPLINES } from '@/lib/game/data/disciplines'; import { useManaStore } from '@/lib/game/stores/manaStore'; import { usePrestigeStore } from '@/lib/game/stores/prestigeStore'; import clsx from 'clsx'; // ─── Attunement Tabs ───────────────────────────────────────────────────────── interface AttunementTab { key: string; label: string; items: DisciplineDefinition[]; } const ATTUNEMENT_TABS: AttunementTab[] = [ { key: 'base', label: 'Base', items: baseDisciplines }, { key: 'elements', label: 'Mana Types', items: elementalAttunementDisciplines }, { key: 'elemental-regen', label: 'Elemental Flow', items: elementalRegenDisciplines }, { key: 'elemental-regen-advanced', label: 'Advanced Flow', items: elementalRegenAdvancedDisciplines }, { key: 'enchanter', label: 'Enchanter', items: enchanterDisciplines }, { key: 'fabricator', label: 'Fabricator', items: fabricatorDisciplines }, { key: 'invoker', label: 'Invoker', items: invokerDisciplines }, ]; // ─── Discipline Card Props (split from monolithic 15-field interface) ──────── export interface DisciplineCardDefinition { id: string; name: string; description: string; manaType: ManaType; baseCost: number; perkThresholds?: number[]; perkValues?: number[]; perkTypes?: string[]; perkDescriptions?: string[]; statBonus: string; statBonusLabel: string; requires?: string[]; baseValue: number; drainBase: number; difficultyFactor: number; scalingFactor: number; sourceManaTypes?: ManaType[]; conversionRate?: number; } export interface DisciplineCardRuntime { xp: number; paused: boolean; concurrentLimit: number; isLocked: boolean; missingPrereqs: string[]; missingSourceMana: string[]; } export interface DisciplineCardCallbacks { onToggle: (id: string, paused: boolean) => void; } interface DisciplineCardProps { definition: DisciplineCardDefinition; runtime: DisciplineCardRuntime; callbacks: DisciplineCardCallbacks; } // ─── Discipline Card Component ─────────────────────────────────────────────── const DisciplineCard: React.FC = ({ definition, runtime, callbacks }) => { const { id, name, description, manaType, baseCost, perkThresholds, perkValues, perkTypes, perkDescriptions, statBonusLabel, baseValue, drainBase, difficultyFactor, scalingFactor, } = definition; const { xp, paused: isPaused, concurrentLimit, isLocked, missingPrereqs, missingSourceMana } = runtime; const { onToggle } = callbacks; const displayXp = xp; const progressPercent = Math.min(displayXp / Math.max(1, concurrentLimit * 100), 100); const activeStatBonus = calculateStatBonus(baseValue, displayXp, scalingFactor); const estimatedDrain = calculateManaDrain(drainBase, displayXp, difficultyFactor); const elementDef = ELEMENTS[manaType]; const manaColor = elementDef?.color ?? '#888888'; const manaIcon = elementDef?.sym ?? '✦'; const manaName = elementDef?.name ?? manaType; const effectiveIsLocked = isLocked || missingSourceMana.length > 0; const unlockedPerks = perkTypes?.reduce((acc, typ, idx) => { const threshold = perkThresholds?.[idx]; if (threshold === undefined) return acc; const desc = perkDescriptions?.[idx]; if (typ === 'once' || typ === 'infinite') { if (displayXp >= threshold && desc) acc.push(desc); } else if (typ === 'capped') { const interval = perkValues?.[idx] ?? 1; const tier = Math.max(0, Math.floor((displayXp - threshold) / interval) + 1); if (tier > 0 && desc) acc.push(desc); } return acc; }, []); const toggleAction = () => { onToggle(id, isPaused); }; return (

{name}

{manaIcon} {manaName}

{description}

{Math.round(progressPercent)}%
0 ? 'bg-green-500' : 'bg-red-500'}`} style={{ width: `${Math.round(progressPercent)}%` }} />
Drain: {estimatedDrain.toFixed(1)}/tick Base Cost: {baseCost} XP: {displayXp}
{definition.conversionRate != null && definition.sourceManaTypes && (
Converts: {definition.sourceManaTypes.map(s => s === 'raw' ? 'raw' : ELEMENTS[s]?.name ?? s).join(' + ')} → {manaName}
)}
Stat Bonus: {activeStatBonus.toFixed(2)} on {statBonusLabel}
Perks:
    {unlockedPerks && unlockedPerks.length > 0 ? ( unlockedPerks.map((p) => (
  • {p}
  • )) ) : (
  • —locked—
  • )}
{effectiveIsLocked && (missingPrereqs.length > 0 || missingSourceMana.length > 0) && (
Requires: {[...missingPrereqs, ...missingSourceMana].join(', ')}
)}
); }; // ─── Disciplines Tab ───────────────────────────────────────────────────────── 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 [activeAttunement, setActiveAttunement] = useState('base'); const rawMana = useManaStore((s) => s.rawMana); const elements = useManaStore((s) => s.elements); const signedPacts = usePrestigeStore((s) => s.signedPacts); const handleToggle = useCallback((id: string, paused: boolean) => { if (paused) { activate(id, { rawMana, elements, signedPacts }); } else { deactivate(id); } }, [activate, deactivate, rawMana, elements, signedPacts]); 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 }; const prereqCheck = checkDisciplinePrerequisites(disc, disciplines, ALL_DISCIPLINES, elements); return ( p.threshold), perkValues: disc.perks?.map((p) => p.value), perkTypes: disc.perks?.map((p) => p.type), perkDescriptions: disc.perks?.map((p) => p.description), manaType: disc.manaType, baseCost: disc.baseCost, statBonus: disc.statBonus.stat, statBonusLabel: disc.statBonus.label, requires: disc.requires, sourceManaTypes: disc.sourceManaTypes, conversionRate: disc.conversionRate, baseValue: disc.statBonus.baseValue, drainBase: disc.drainBase, difficultyFactor: disc.difficultyFactor, scalingFactor: disc.scalingFactor, }} runtime={{ xp: discState.xp, paused: discState.paused, concurrentLimit, isLocked: !prereqCheck.canProceed, missingPrereqs: prereqCheck.missingPrereqs, missingSourceMana: disc.sourceManaTypes ? disc.sourceManaTypes.filter( (src) => src !== 'raw' && (!elements[src] || !elements[src].unlocked), ).map((src) => `${src} mana`) : [], }} callbacks={{ onToggle: handleToggle, }} /> ); })}
{/* Summary info */}
Active Disciple{activeIds.length}{activeIds.length === 1 ? '' : 's'} / {concurrentLimit}
Concurrent Limit: {concurrentLimit}
); };