// Main app shell
const { useState, useEffect, useRef, useMemo } = React;

// Normalize presets: weight_g is canonical SI storage. Re-derive from grains at
// 2-decimal precision so display values match the original grain spec exactly.
if (window.PRESET_PROFILES && !window.__presets_normalized) {
  window.PRESET_PROFILES.forEach(p => {
    if (p.weight_gr != null) p.weight_g = p.weight_gr / 15.4324;
    delete p.weight_gr;
  });
  window.__presets_normalized = true;
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "trajectoryExaggeration": 1,
  "background": "forest",
  "target": "steel",
  "units": "metric",
  "showRangeMarkers": true,
  "theme": "graphite",
  "targetDistance": 800
}/*EDITMODE-END*/;

const THEMES = {
  graphite: { '--bg': '#0a0c0e', '--panel': '#11161a', '--panel-2': '#161c22', '--ink': '#d8dde3', '--dim': '#7a8088', '--accent': '#ff8c2a', '--line': '#1f262c' },
  arctic:   { '--bg': '#0b0e12', '--panel': '#121821', '--panel-2': '#192230', '--ink': '#dde8f2', '--dim': '#7d8a99', '--accent': '#5fb3e8', '--line': '#1d2735' },
  olive:    { '--bg': '#0e110b', '--panel': '#161a13', '--panel-2': '#1d2218', '--ink': '#dde2cf', '--dim': '#838a73', '--accent': '#b8c44a', '--line': '#252b1d' },
  amber:    { '--bg': '#0c0a08', '--panel': '#16130d', '--panel-2': '#1d1812', '--ink': '#f0e6d4', '--dim': '#9a8e78', '--accent': '#ffb347', '--line': '#2a2218' }
};

function applyTheme(theme) {
  const t = THEMES[theme] || THEMES.graphite;
  Object.entries(t).forEach(([k, v]) => document.documentElement.style.setProperty(k, v));
}

function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [profile, setProfile] = useState({ ...window.PRESET_PROFILES[4] }); // 6.5 CM Range default
  const [env, setEnv] = useState({ ...window.DEFAULT_ENV });
  const [model, setModel] = useState('standard');
  const [maxRange, setMaxRange] = useState(1500);
  const [customs, setCustoms] = useState(() => {
    try { return JSON.parse(localStorage.getItem('cb_customs') || '[]'); } catch (e) { return []; }
  });
  const [sidebar, setSidebar] = useState(true);
  const [tab, setTab] = useState('profile'); // profile | solver | scope

  const sceneRef = useRef(null);
  const sceneApi = useRef(null);

  // Theme apply
  useEffect(() => { applyTheme(tweaks.theme); }, [tweaks.theme]);

  // Solve
  const solution = useMemo(() => {
    return window.Ballistics.solveTable(profile, env, { model, maxRange_m: maxRange, stepM: 50 });
  }, [profile, env, model, maxRange]);

  const sceneCtl = sceneApi;
  const [sceneReady, setSceneReady] = useState(false);

  // Build scene once
  useEffect(() => {
    if (!sceneRef.current || sceneApi.current) return;
    sceneApi.current = window.Scene3D.build(sceneRef.current, {});
    setSceneReady(true);
  }, []);

  // Push trajectory + settings to scene
  useEffect(() => {
    if (!sceneApi.current) return;
    sceneApi.current.setTrajectory(solution.points || [], tweaks.trajectoryExaggeration);
    sceneApi.current.setMaxRange(maxRange);
    sceneApi.current.setEnv(env);
  }, [solution, tweaks.trajectoryExaggeration, maxRange, env]);

  useEffect(() => {
    if (!sceneApi.current) return;
    sceneApi.current.setBackground(tweaks.background);
  }, [tweaks.background]);

  useEffect(() => {
    if (!sceneApi.current) return;
    sceneApi.current.setTarget(tweaks.target, tweaks.targetDistance);
  }, [tweaks.target, tweaks.targetDistance]);

  useEffect(() => {
    if (!sceneApi.current) return;
    sceneApi.current.setShowMarkers(tweaks.showRangeMarkers);
  }, [tweaks.showRangeMarkers]);

  useEffect(() => {
    setTimeout(() => sceneApi.current && sceneApi.current.resize(), 100);
  }, [sidebar]);

  function setCameraPreset(p) {
    if (sceneApi.current) sceneApi.current.setCameraPreset(p);
  }
  function replayShot() {
    if (sceneApi.current) sceneApi.current.replay();
  }

  // Solution at target distance for HUD
  const targetSolution = useMemo(() => {
    const t = solution.table || [];
    if (!t.length) return null;
    let closest = t[0];
    for (const r of t) if (Math.abs(r.range_m - tweaks.targetDistance) < Math.abs(closest.range_m - tweaks.targetDistance)) closest = r;
    return closest;
  }, [solution, tweaks.targetDistance]);

  return (
    <div className="cb-root">
      <TopBar units={tweaks.units} setUnits={u => setTweak('units', u)}
        model={model} profile={profile}
        targetSolution={targetSolution} targetDistance={tweaks.targetDistance}
        sidebar={sidebar} setSidebar={setSidebar}
        theme={tweaks.theme} setTheme={t => setTweak('theme', t)} />

      <div className={'cb-body' + (sidebar ? '' : ' nosidebar')}>
        {sidebar && (
          <aside className="cb-side">
            <nav className="side-tabs">
              {[['profile','PROFILE'],['solver','SOLVER'],['scope','SCOPE']].map(([k, l]) => (
                <button key={k} className={tab === k ? 'on' : ''} onClick={() => setTab(k)}>{l}</button>
              ))}
            </nav>
            <div className="side-scroll">
              {tab === 'profile' && (
                <ProfilePanel profile={profile} setProfile={setProfile} units={tweaks.units}
                  presets={window.PRESET_PROFILES} customs={customs} setCustoms={setCustoms} />
              )}
              {tab === 'solver' && (
                <SolverPanel env={env} setEnv={setEnv} model={model} setModel={setModel}
                  maxRange={maxRange} setMaxRange={setMaxRange} units={tweaks.units} />
              )}
              {tab === 'scope' && (
                <ScopePanel profile={profile} setProfile={setProfile} table={solution.table} units={tweaks.units}
                  targetDistance={tweaks.targetDistance} setTargetDistance={d => setTweak('targetDistance', d)}
                  maxRange={maxRange} />
              )}
            </div>
          </aside>
        )}

        <main className="cb-main">
          <div className="scene-wrap">
            <div ref={sceneRef} className="scene-host" />
            <SceneOverlay
              cameraPreset="side"
              setCameraPreset={setCameraPreset}
              sceneCtl={sceneCtl}
              sceneReady={sceneReady}
              totalPoints={(solution.points || []).length}
              tweaks={tweaks} setTweak={setTweak}
              targetSolution={targetSolution}
              targetDistance={tweaks.targetDistance}
              units={tweaks.units}
              profile={profile}
            />
          </div>
          <ChartPanel table={solution.table} profile={profile} units={tweaks.units} />
        </main>
      </div>

      <TweaksPanel title="TWEAKS">
        <TweakSection label="Display">
          <TweakSlider label="Trajectory exa" value={tweaks.trajectoryExaggeration} min={1} max={200} step={1}
            onChange={v => setTweak('trajectoryExaggeration', v)} />
          <TweakToggle label="Range markers" value={tweaks.showRangeMarkers}
            onChange={v => setTweak('showRangeMarkers', v)} />
        </TweakSection>
        <TweakSection label="Scene">
          <TweakSelect label="Background" value={tweaks.background}
            options={[
              { value: 'forest', label: 'Alpine Forest' },
              { value: 'desert', label: 'Desert Plain' },
              { value: 'mountain', label: 'Mountain Valley' },
              { value: 'range', label: 'Known-Distance Range' }
            ]}
            onChange={v => setTweak('background', v)} />
          <TweakSelect label="Target face" value={tweaks.target}
            options={[
              { value: 'steel', label: 'Steel Circle' },
              { value: 'ipsc', label: 'IPSC Silhouette' },
              { value: 'deer', label: 'Deer' },
              { value: 'fox', label: 'Fox' },
              { value: 'prairie', label: 'Prairie Dog' }
            ]}
            onChange={v => setTweak('target', v)} />
          <TweakSlider label="Target distance (m)" value={tweaks.targetDistance} min={50} max={maxRange} step={50}
            onChange={v => setTweak('targetDistance', v)} />
        </TweakSection>
        <TweakSection label="Units & Theme">
          <TweakRadio label="Units" value={tweaks.units}
            options={[{ value: 'metric', label: 'SI · m / MRAD' }, { value: 'imperial', label: 'Imperial · yd / MOA' }]}
            onChange={v => setTweak('units', v)} />
          <TweakSelect label="Theme" value={tweaks.theme}
            options={[
              { value: 'graphite', label: 'Graphite (default)' },
              { value: 'arctic', label: 'Arctic Cyan' },
              { value: 'olive', label: 'Olive' },
              { value: 'amber', label: 'Amber Night' }
            ]}
            onChange={v => setTweak('theme', v)} />
        </TweakSection>
      </TweaksPanel>
    </div>
  );
}

const TopBar = ({ units, setUnits, model, profile, targetSolution, targetDistance, sidebar, setSidebar, theme, setTheme }) => {
  const u = units;
  const drop_mrad = targetSolution ? (-targetSolution.drop_m / targetSolution.range_m * 1000).toFixed(2) : '—';
  const wind_mrad = targetSolution ? (targetSolution.wind_m / targetSolution.range_m * 1000).toFixed(2) : '—';
  const v = targetSolution ? (u === 'imperial' ? (targetSolution.velocity_mps * 3.28084).toFixed(0) + ' fps' : targetSolution.velocity_mps.toFixed(0) + ' m/s') : '—';
  const e = targetSolution ? (u === 'imperial'
    ? ((0.5 * (profile.weight_g / 1000) * targetSolution.velocity_mps ** 2) * 0.737562).toFixed(0) + ' ft·lb'
    : (0.5 * (profile.weight_g / 1000) * targetSolution.velocity_mps ** 2).toFixed(0) + ' J') : '—';

  return (
    <header className="cb-top">
      <div className="cb-brand">
        <button className="hamburger" onClick={() => setSidebar(!sidebar)} aria-label="toggle sidebar">
          <span></span><span></span><span></span>
        </button>
        <div className="brand-mark">
          <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
            <circle cx="11" cy="11" r="9.5" stroke="currentColor" strokeWidth="1"/>
            <line x1="11" y1="1.5" x2="11" y2="20.5" stroke="currentColor" strokeWidth="0.6"/>
            <line x1="1.5" y1="11" x2="20.5" y2="11" stroke="currentColor" strokeWidth="0.6"/>
            <circle cx="11" cy="11" r="2.5" stroke="var(--accent)" strokeWidth="1.2" fill="none" />
            <circle cx="11" cy="11" r="0.8" fill="var(--accent)" />
          </svg>
        </div>
        <div className="brand-text">
          <span className="brand-1">COLD BORE</span>
          <span className="brand-2">BALLISTICS · v1</span>
        </div>
      </div>

      <div className="cb-readout">
        <div className="ro-block">
          <div className="ro-label">PROFILE</div>
          <div className="ro-val">{profile.name} · {profile.cartridge}</div>
        </div>
        <div className="ro-block">
          <div className="ro-label">MODEL</div>
          <div className="ro-val mono">{model.toUpperCase()}</div>
        </div>
        <div className="ro-divider"></div>
        <div className="ro-block">
          <div className="ro-label">TARGET @ {targetDistance}m</div>
          <div className="ro-val mono accent">{drop_mrad} <span className="ro-unit">mil↑</span></div>
        </div>
        <div className="ro-block">
          <div className="ro-label">WIND HOLD</div>
          <div className="ro-val mono accent">{wind_mrad} <span className="ro-unit">mil</span></div>
        </div>
        <div className="ro-block">
          <div className="ro-label">VEL</div>
          <div className="ro-val mono">{v}</div>
        </div>
        <div className="ro-block">
          <div className="ro-label">ENERGY</div>
          <div className="ro-val mono">{e}</div>
        </div>
      </div>

      <div className="cb-actions">
        <div className="seg compact">
          <button className={units === 'metric' ? 'on' : ''} onClick={() => setUnits('metric')}>SI</button>
          <button className={units === 'imperial' ? 'on' : ''} onClick={() => setUnits('imperial')}>IMP</button>
        </div>
      </div>
    </header>
  );
};

const SceneOverlay = ({ setCameraPreset, sceneCtl, sceneReady, totalPoints, tweaks, setTweak, targetSolution, targetDistance, units, profile }) => {
  const [cam, setCam] = useState('side');
  const [playState, setPlayState] = useState({ idx: 0, total: 1, t: 0, x: 0, playing: false });
  const [speed, setSpeed] = useState(0.5);

  useEffect(() => {
    if (!sceneCtl || !sceneCtl.current) return;
    sceneCtl.current.onTick((s) => setPlayState(s));
    sceneCtl.current.setSpeed(speed);
    return () => sceneCtl.current && sceneCtl.current.onTick(null);
  }, [sceneCtl, sceneReady]);

  useEffect(() => { if (sceneCtl && sceneCtl.current) sceneCtl.current.setSpeed(speed); }, [speed]);

  function pick(p) { setCam(p); setCameraPreset(p); }
  function play() { sceneCtl.current && sceneCtl.current.play(); setCam('follow'); }
  function pause() { sceneCtl.current && sceneCtl.current.pause(); }
  function stop() { sceneCtl.current && sceneCtl.current.stop(); }
  function seek(e) {
    const frac = parseFloat(e.target.value);
    sceneCtl.current && sceneCtl.current.seek(frac);
    setCam('follow');
  }
  const frac = playState.total > 0 ? playState.idx / playState.total : 0;
  const distM = playState.x ? Math.round(playState.x) : 0;
  const distLabel = units === 'imperial' ? (distM * 1.09361).toFixed(0) + ' yd' : distM + ' m';
  return (
    <>
      <div className="overlay tl">
        <div className="overlay-h">CAMERA</div>
        <div className="cam-buttons">
          {[['scope','SCOPE'],['follow','FOLLOW'],['side','SIDE'],['top','TOP'],['frame','FRAME'],['free','FREE']].map(([k, l]) => (
            <button key={k} className={cam === k ? 'on' : ''} onClick={() => pick(k)}>{l}</button>
          ))}
        </div>
        <div className="overlay-h">SCENE</div>
        <div className="cam-buttons wrap">
          {[['forest','FOREST'],['desert','DESERT'],['mountain','MTN'],['range','KD RANGE']].map(([k, l]) => (
            <button key={k} className={tweaks.background === k ? 'on' : ''} onClick={() => setTweak('background', k)}>{l}</button>
          ))}
        </div>
        <div className="overlay-h">TARGET</div>
        <div className="cam-buttons wrap">
          {[['steel','STEEL'],['ipsc','IPSC'],['deer','DEER'],['fox','FOX'],['prairie','P-DOG']].map(([k, l]) => (
            <button key={k} className={tweaks.target === k ? 'on' : ''} onClick={() => setTweak('target', k)}>{l}</button>
          ))}
        </div>
      </div>

      <div className="overlay tr">
        <div className="hud-block">
          <div className="hud-label">RANGE</div>
          <div className="hud-val mono accent">{units === 'imperial' ? (targetDistance * 1.09361).toFixed(0) + ' yd' : targetDistance + ' m'}</div>
        </div>
        {targetSolution && <>
          <div className="hud-block">
            <div className="hud-label">ELEV</div>
            <div className="hud-val mono">{(-targetSolution.drop_m / targetSolution.range_m * 1000).toFixed(2)} <span>mil</span></div>
          </div>
          <div className="hud-block">
            <div className="hud-label">WIND</div>
            <div className="hud-val mono">{(targetSolution.wind_m / targetSolution.range_m * 1000).toFixed(2)} <span>mil</span></div>
          </div>
          <div className="hud-block">
            <div className="hud-label">TOF</div>
            <div className="hud-val mono">{targetSolution.time_s.toFixed(2)} <span>s</span></div>
          </div>
          <div className="hud-block">
            <div className="hud-label">M</div>
            <div className="hud-val mono">{targetSolution.mach.toFixed(2)}</div>
          </div>
        </>}
      </div>

      <div className="overlay bl replay-panel">
        <div className="overlay-h replay-head">
          <span>REPLAY SHOT</span>
          <span className="replay-meta mono">{distLabel} · {playState.t.toFixed(2)}s</span>
        </div>
        <input className="replay-scrub" type="range" min="0" max="1" step="0.001" value={frac}
          disabled={totalPoints < 2} onChange={seek} />
        <div className="replay-ticks">
          <span></span><span></span><span></span><span></span><span></span>
        </div>
        <div className="replay-ctrls">
          {playState.playing
            ? <button className="ghost-btn" onClick={pause} title="Pause">❚❚</button>
            : <button className="ghost-btn" onClick={play} title="Play">▶</button>}
          <button className="ghost-btn" onClick={stop} title="Stop">■</button>
          <div className="replay-speed">
            <span className="kbd dim">SPD</span>
            <input type="range" min="0.05" max="3" step="0.05" value={speed}
              onChange={e => setSpeed(parseFloat(e.target.value))} />
            <span className="kbd">{speed.toFixed(2)}×</span>
          </div>
        </div>
      </div>

      <div className="overlay br">
        <div className="exa-row">
          <span className="kbd dim">EXA</span>
          <input type="range" min="1" max="200" step="1" value={tweaks.trajectoryExaggeration}
            onChange={e => setTweak('trajectoryExaggeration', parseInt(e.target.value))} />
          <span className="kbd">×{tweaks.trajectoryExaggeration}</span>
        </div>
      </div>
    </>
  );
};

const ScopePanel = ({ profile, setProfile, table, units, targetDistance, setTargetDistance, maxRange }) => {
  const [cantUnit, setCantUnit] = useState('mrad');
  const [scopeUnit, setScopeUnit] = useState('mrad');
  const cant_mrad = profile.railCant_mrad || 0;
  const cantDisplay = cantUnit === 'moa' ? +(cant_mrad * 3.4377).toFixed(2) : +cant_mrad.toFixed(2);
  const scopeMax = profile.scopeMaxElevation_mrad || 30;
  const scopeMaxDisplay = scopeUnit === 'moa' ? +(scopeMax * 3.4377).toFixed(1) : +scopeMax.toFixed(1);

  function setCant(v) {
    const m = cantUnit === 'moa' ? v / 3.4377 : v;
    setProfile({ ...profile, railCant_mrad: m });
  }
  function setScopeMax(v) {
    const m = scopeUnit === 'moa' ? v / 3.4377 : v;
    setProfile({ ...profile, scopeMaxElevation_mrad: m });
  }

  // Find max reachable range with current scope+cant budget
  const budget_mrad = scopeMax + cant_mrad;
  let maxReachable = 0;
  (table || []).forEach(r => {
    const drop_mrad = -r.drop_m / r.range_m * 1000;
    if (drop_mrad <= budget_mrad) maxReachable = r.range_m;
  });

  return (
    <div className="panel">
      <div className="panel-h">SCOPE / ZERO</div>
      <FieldGrid>
        <NumberField label="Sight ht (m)" value={profile.sightHeight_m} step={0.005}
          onChange={v => setProfile({ ...profile, sightHeight_m: v })} />
        <NumberField label="Zero (m)" value={profile.zeroDist_m} step={25}
          onChange={v => setProfile({ ...profile, zeroDist_m: v })} />
      </FieldGrid>

      <div className="panel-h sub">RAIL / BASE CANT</div>
      <div className="cant-row">
        <input className="cant-num" type="number" step={cantUnit === 'moa' ? 1 : 0.1} value={cantDisplay}
          onChange={e => setCant(parseFloat(e.target.value) || 0)} />
        <div className="seg-options" style={{ flex: 'none' }}>
          <button className={cantUnit === 'mrad' ? 'on' : ''} onClick={() => setCantUnit('mrad')}>MIL</button>
          <button className={cantUnit === 'moa' ? 'on' : ''} onClick={() => setCantUnit('moa')}>MOA</button>
        </div>
      </div>

      <div className="panel-h sub">SCOPE MAX UP</div>
      <div className="cant-row">
        <input className="cant-num" type="number" step={scopeUnit === 'moa' ? 1 : 0.5} value={scopeMaxDisplay}
          onChange={e => setScopeMax(parseFloat(e.target.value) || 0)} />
        <div className="seg-options" style={{ flex: 'none' }}>
          <button className={scopeUnit === 'mrad' ? 'on' : ''} onClick={() => setScopeUnit('mrad')}>MIL</button>
          <button className={scopeUnit === 'moa' ? 'on' : ''} onClick={() => setScopeUnit('moa')}>MOA</button>
        </div>
      </div>
      <div className="field">
        <label>Total elevation budget</label>
        <input readOnly value={budget_mrad.toFixed(1) + ' mil  ·  ' + (budget_mrad * 3.4377).toFixed(1) + ' MOA'} />
      </div>
      <div className="model-desc">
        Forward cant offsets the scope rail down, gifting elevation back to the dial.
        With current setup you can reach <b>{maxReachable} m</b> before maxing out
        the turret (scope {scopeMax} mil + rail {cant_mrad.toFixed(1)} mil).
      </div>

      <div className="panel-h sub">TARGET DISTANCE</div>
      <div className="slider-row">
        <input type="range" min="50" max={maxRange} step="25" value={targetDistance}
          onChange={e => setTargetDistance(parseInt(e.target.value))} />
        <span className="kbd">{units === 'imperial' ? (targetDistance * 1.09361).toFixed(0) + ' yd' : targetDistance + ' m'}</span>
      </div>

      <Reticle table={table} units={units} />

      <div className="panel-h sub">QUICK CARD</div>
      <div className="qcard">
        {(table || []).filter(r => r.range_m % 100 === 0).slice(0, 16).map(r => (
          <div key={r.range_m} className="qcard-row">
            <span className="qr-range">{r.range_m}m</span>
            <span className="qr-val mono accent">{(-r.drop_m / r.range_m * 1000).toFixed(1)} mil</span>
            <span className="qr-velm mono dim">{r.velocity_mps.toFixed(0)} m/s</span>
          </div>
        ))}
      </div>
    </div>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
