import { useState, useEffect, useMemo } from 'react' import { calcOrbit, calcPowerBudget, calcLinkBudget, calcThermalBalance, calcMassBudget, } from '../engine/satelliteCalcs.js' const LS_KEY = 'rockettools_satellite' const DEFAULTS = { orbitInputs: { altitudeKm: 500, betaAngleDeg: 0, }, powerInputs: { formFactor: '3U', solarCellType: 'GaAs', etaCell: 0.28, etaPacking: 0.85, deployablePanelArea: 0, etaCharge: 0.95, etaDischarge: 0.97, DOD: 0.30, P_sunlight_load: 3.0, P_eclipse_load: 2.0, degradationPerYear: -0.03, designLife: 2, }, linkInputs: { P_tx_W: 1.0, G_tx_dBi: 6.0, L_feed_dB: 1.0, frequencyMHz: 437, modulation: 'BPSK', elevationAngleDeg: 5, L_atm_dB: 0.5, L_point_dB: 1.0, G_rx_dBi: 12.0, T_sys_K: 290, dataRateBps: 9600, Eb_N0_req_dB: 9.6, }, thermalInputs: { alpha: 0.90, epsilon: 0.85, A_sun: 0.01, A_earth: 0.01, A_rad: 0.02, Q_int: 3.0, }, subsystems: { adcs: { controlMode: 'sun-pointing', pointingAccuracyDeg: 5, reactionWheels: false, magnetorquers: true, mass: 200, avgPower: 0.5, peakPower: 1.0, }, comms: { frequencyMHz: 437, P_tx_W: 1.0, antennaType: 'dipole', modulation: 'BPSK', mass: 100, avgPower: 1.0, peakPower: 2.0, }, cdh: { processor: 'ARM Cortex-M4', storageGB: 2, housekeepingRateHz: 1, mass: 120, avgPower: 0.8, peakPower: 1.2, }, eps: { batteryChemistry: 'Li-ion', solarCellType: 'GaAs', mass: 300, }, structure: { materialId: 'al_6061_t6', configuration: 'standard', mass: 400, }, propulsion: { type: 'none', propellant: '', mass: 0, avgPower: 0, peakPower: 0, }, payload: { name: '', type: '', mass: 0, avgPower: 0, peakPower: 0, interface: 'UART', }, }, } function loadFromStorage() { try { const raw = localStorage.getItem(LS_KEY) return raw ? JSON.parse(raw) : null } catch { return null } } export function useSatelliteDesign() { const saved = useMemo(() => loadFromStorage(), []) const [orbitInputs, setOrbitInputs] = useState(saved?.orbitInputs ?? DEFAULTS.orbitInputs) const [powerInputs, setPowerInputs] = useState(saved?.powerInputs ?? DEFAULTS.powerInputs) const [linkInputs, setLinkInputs] = useState(saved?.linkInputs ?? DEFAULTS.linkInputs) const [thermalInputs, setThermalInputs] = useState(saved?.thermalInputs ?? DEFAULTS.thermalInputs) const [subsystems, setSubsystems] = useState(saved?.subsystems ?? DEFAULTS.subsystems) // Persist all state slices on any change useEffect(() => { try { localStorage.setItem(LS_KEY, JSON.stringify({ orbitInputs, powerInputs, linkInputs, thermalInputs, subsystems, })) } catch {} }, [orbitInputs, powerInputs, linkInputs, thermalInputs, subsystems]) // ── Calculation chain (pure useMemo, no side effects) ── const orbitData = useMemo(() => calcOrbit(orbitInputs), [orbitInputs]) const powerBudget = useMemo(() => calcPowerBudget(orbitData, powerInputs), [orbitData, powerInputs]) const linkBudget = useMemo(() => calcLinkBudget(orbitData, linkInputs), [orbitData, linkInputs]) const thermalBalance = useMemo(() => calcThermalBalance(orbitData, thermalInputs), [orbitData, thermalInputs]) const massBudget = useMemo(() => calcMassBudget(subsystems, powerInputs.formFactor), [subsystems, powerInputs.formFactor]) // ── Setters ── /** Patch a single key in orbitInputs */ function setOrbitInput(key, value) { setOrbitInputs(prev => ({ ...prev, [key]: value })) } /** Patch a single key in powerInputs */ function setPowerInput(key, value) { setPowerInputs(prev => ({ ...prev, [key]: value })) } /** Patch a single key in linkInputs */ function setLinkInput(key, value) { setLinkInputs(prev => ({ ...prev, [key]: value })) } /** Patch a single key in thermalInputs */ function setThermalInput(key, value) { setThermalInputs(prev => ({ ...prev, [key]: value })) } /** Patch one or more fields of a specific subsystem */ function setSubsystem(key, patch) { setSubsystems(prev => ({ ...prev, [key]: { ...prev[key], ...patch } })) } return { // State slices orbitInputs, setOrbitInput, powerInputs, setPowerInput, linkInputs, setLinkInput, thermalInputs, setThermalInput, subsystems, setSubsystem, // Computed results orbitData, powerBudget, linkBudget, thermalBalance, massBudget, } }