// app.jsx — root composition. Component implementations live in /components/*.jsx.

const { useState, useEffect } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "radius": 15,
  "theme": "light",
  "accent": "orange",
  "darkLevel": 0.30,
  "tint": "neutral",
  "surfaceContrast": 1.0,
  "glass": 0.45,
  "sceneBg": 0.20,
  "density": "cozy",
  "layout": "floating",
  "pointSize": 0.11,
  "pointOpacity": 0.95,
  "haloSize": 0.22,
  "haloOpacity": 0.28,
  "lineOpacity": 0.32,
  "shadowOpacity": 0.55,
  "showGrid": true,
  "showDrops": true,
  "pointShape": "circle"
}/*EDITMODE-END*/;

const TINT_MAP = {
  neutral: { hue: 260, chroma: 0.010 },
  cool:    { hue: 240, chroma: 0.022 },
  warm:    { hue:  65, chroma: 0.014 },
  violet:  { hue: 300, chroma: 0.022 },
};

const ACCENT_MAP = {
  blue:   { primary: 'oklch(0.55 0.14 240)', primaryFg: '#fff', ring: 'oklch(0.55 0.14 240 / 0.35)' },
  orange: { primary: 'oklch(0.66 0.17 50)',  primaryFg: '#fff', ring: 'oklch(0.66 0.17 50 / 0.35)' },
  green:  { primary: 'oklch(0.58 0.14 155)', primaryFg: '#fff', ring: 'oklch(0.58 0.14 155 / 0.35)' },
  violet: { primary: 'oklch(0.55 0.18 295)', primaryFg: '#fff', ring: 'oklch(0.55 0.18 295 / 0.35)' },
};

const STATION = {
  name:      'TS-A · Riverbank Pier',
  id:        'STN-2026-0421',
  location:  'Site A · Block 3',
  model:     'Total Station / TS-1200',
  serial:    'TS12-008243',
  operator:  'Lin H.',
  project:   'Bridge Survey Q2',
  easting:   '298,452.317',
  northing: '2,770,118.642',
  elevation:'  12.084 m',
  crs:       'TWD97 / TM2 zone 121',
};

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  const [scene, setScene] = useState(() => window.__sceneState || {
    shownPts: 0, totalPts: 131, playVT: 0, maxT: 137, isPlaying: true,
    playSpeed: 1, autoRotate: true,
    stats: { xMin: -6.4, xMax: -1.95, yMin: -0.63, yMax: 4.16, zMin: 0.14, zMax: 1.30, sMax: 6.50 },
    hover: null,
  });
  const [sheetOpen, setSheetOpen] = useState(false);
  const [collapsed, setCollapsed] = useState({ info: false, playback: false, legend: false });
  const [files, setFiles] = useState([
    { id: 'f-001', name: 'pier_riverbank_2026-05-05.csv', size: '12.4 KB', points: 131, uploadedAt: '2026-05-05 09:42' },
    { id: 'f-002', name: 'pier_riverbank_2026-04-28.csv', size: '11.9 KB', points: 124, uploadedAt: '2026-04-28 14:11' },
    { id: 'f-003', name: 'baseline_check_2026-04-21.csv', size: '8.6 KB',  points:  92, uploadedAt: '2026-04-21 10:03' },
  ]);
  const [activeFileId, setActiveFileId] = useState('f-001');

  const handleUploadFiles = (fileList) => {
    const fmtSize = (b) => b < 1024 ? `${b} B` : `${(b/1024).toFixed(1)} KB`;
    const ts = (() => {
      const d = new Date();
      const p = (n) => String(n).padStart(2, '0');
      return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
    })();
    const next = fileList.map((f, i) => ({
      id: `f-${Date.now()}-${i}`,
      name: f.name,
      size: fmtSize(f.size),
      points: '—',
      uploadedAt: ts,
    }));
    setFiles((prev) => [...next, ...prev]);
    if (next[0]) setActiveFileId(next[0].id);
  };
  const toggle = (k) => setCollapsed((c) => ({ ...c, [k]: !c[k] }));

  useEffect(() => {
    const onUpdate = (e) => setScene((s) => ({ ...s, ...e.detail }));
    window.addEventListener('scene:update', onUpdate);
    return () => window.removeEventListener('scene:update', onUpdate);
  }, []);

  useEffect(() => {
    window.dispatchEvent(new CustomEvent('ui:tweak', { detail: {
      theme: t.theme, accent: t.accent, darkLevel: t.darkLevel, sceneBg: t.sceneBg,
    }}));
  }, [t.theme, t.accent, t.darkLevel, t.sceneBg]);

  useEffect(() => {
    const root = document.documentElement;
    root.dataset.theme = t.theme;
    root.style.setProperty('--radius', `${t.radius}px`);
    if (t.theme === 'dark') {
      const L = t.darkLevel;
      const tint = TINT_MAP[t.tint] || TINT_MAP.neutral;
      const h = tint.hue;
      const c = tint.chroma;
      const sc = t.surfaceContrast;
      const step = (n) => (L + 0.04 * n * sc).toFixed(3);
      const cardAlpha = (1 - t.glass * 0.34).toFixed(2);
      root.style.setProperty('--background', `oklch(${L} ${c} ${h})`);
      root.style.setProperty('--card',       `oklch(${step(1)} ${(c + 0.002).toFixed(3)} ${h} / ${cardAlpha})`);
      root.style.setProperty('--muted',      `oklch(${step(2)} ${c} ${h})`);
      root.style.setProperty('--secondary',  `oklch(${step(2)} ${c} ${h})`);
      root.style.setProperty('--accent',     `oklch(${step(3)} ${c} ${h})`);
      root.style.setProperty('--border',     `oklch(${step(4)} ${c} ${h})`);
      root.style.setProperty('--input',       `oklch(${step(3)} ${c} ${h})`);
      root.style.setProperty('--card-blur',  `${Math.round(t.glass * 18)}px`);
    } else {
      ['--background','--card','--muted','--secondary','--accent','--border','--input','--card-blur']
        .forEach(p => root.style.removeProperty(p));
    }
    const a = ACCENT_MAP[t.accent] || ACCENT_MAP.blue;
    root.style.setProperty('--primary', a.primary);
    root.style.setProperty('--primary-foreground', a.primaryFg);
    root.style.setProperty('--ring', a.ring);
  }, [t.radius, t.theme, t.accent, t.darkLevel, t.tint, t.surfaceContrast, t.glass]);

  useEffect(() => { document.body.dataset.layout = t.layout; }, [t.layout]);

  useEffect(() => { callScene('setPointSize',     t.pointSize); },     [t.pointSize]);
  useEffect(() => { callScene('setPointOpacity',  t.pointOpacity); },  [t.pointOpacity]);
  useEffect(() => { callScene('setHaloSize',      t.haloSize); },      [t.haloSize]);
  useEffect(() => { callScene('setHaloOpacity',   t.haloOpacity); },   [t.haloOpacity]);
  useEffect(() => { callScene('setLineOpacity',   t.lineOpacity); },   [t.lineOpacity]);
  useEffect(() => { callScene('setShadowOpacity', t.shadowOpacity); }, [t.shadowOpacity]);
  useEffect(() => { callScene('setShowGrid',      t.showGrid); },      [t.showGrid]);
  useEffect(() => { callScene('setShowDrops',     t.showDrops); },     [t.showDrops]);
  useEffect(() => { callScene('setPointShape',    t.pointShape); },    [t.pointShape]);

  const callScene = (fn, ...args) => window.__scene && window.__scene[fn] && window.__scene[fn](...args);

  return (
    <>
      <TopBar
        deviceModel={STATION.model}
        stationName={STATION.name}
        totalPts={scene.totalPts}
        shownPts={scene.shownPts}
        onOpenSheet={() => setSheetOpen(true)}
        files={files}
        activeFileId={activeFileId}
        onSelectFile={setActiveFileId}
        onUploadFiles={handleUploadFiles}
      />

      <InfoCard
        stats={scene.stats}
        shownPts={scene.shownPts}
        totalPts={scene.totalPts}
        density={t.density}
        collapsed={collapsed.info}
        onToggle={() => toggle('info')}
      />

      <PlaybackCard
        playVT={scene.playVT}
        maxT={scene.maxT}
        shownPts={scene.shownPts}
        totalPts={scene.totalPts}
        isPlaying={scene.isPlaying}
        playSpeed={scene.playSpeed}
        autoRotate={scene.autoRotate}
        density={t.density}
        collapsed={collapsed.playback}
        onToggle={() => toggle('playback')}
        onTogglePlay={() => callScene('togglePlay')}
        onReplay={() => callScene('replay')}
        onSetSpeed={(v) => callScene('setSpeed', v)}
        onToggleRotate={(v) => callScene('setAutoRotate', v)}
        onSeek={(v) => callScene('seek', v)}
      />

      <LegendCard
        stats={scene.stats}
        density={t.density}
        collapsed={collapsed.legend}
        onToggle={() => toggle('legend')}
      />

      <HintStrip />

      <HoverCard data={scene.hover} />

      <StationSheet
        open={sheetOpen}
        onClose={() => setSheetOpen(false)}
        station={STATION}
        stats={scene.stats}
        totalPts={scene.totalPts}
        shownPts={scene.shownPts}
        playVT={scene.playVT}
        maxT={scene.maxT}
      />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Theme" />
        <TweakRadio  label="Mode"   value={t.theme}   options={['light', 'dark']}
                     onChange={(v) => setTweak('theme', v)} />
        {t.theme === 'dark' && (
          <>
            <TweakSlider label="Brightness" value={t.darkLevel}
                         min={0.16} max={0.55} step={0.01}
                         onChange={(v) => setTweak('darkLevel', v)} />
            <TweakSelect label="Tint" value={t.tint}
                         options={['neutral', 'cool', 'warm', 'violet']}
                         onChange={(v) => setTweak('tint', v)} />
            <TweakSlider label="Surface contrast" value={t.surfaceContrast}
                         min={0.5} max={1.6} step={0.05}
                         onChange={(v) => setTweak('surfaceContrast', v)} />
            <TweakSlider label="Glassmorphism" value={t.glass}
                         min={0} max={1} step={0.05}
                         onChange={(v) => setTweak('glass', v)} />
            <TweakSlider label="Scene background" value={t.sceneBg}
                         min={0.05} max={0.50} step={0.01}
                         onChange={(v) => setTweak('sceneBg', v)} />
          </>
        )}
        <TweakColor  label="Accent" value={ACCENT_MAP[t.accent].primary}
                     options={Object.values(ACCENT_MAP).map(a => a.primary)}
                     onChange={(v) => {
                       const key = Object.keys(ACCENT_MAP).find(k => ACCENT_MAP[k].primary === v) || 'blue';
                       setTweak('accent', key);
                     }} />

        <TweakSection label="Shape" />
        <TweakSlider label="Corner radius" value={t.radius}
                     min={0} max={20} step={1} unit="px"
                     onChange={(v) => setTweak('radius', v)} />

        <TweakSection label="Layout" />
        <TweakRadio  label="Density" value={t.density}
                     options={['compact', 'cozy']}
                     onChange={(v) => setTweak('density', v)} />
        <TweakRadio  label="Panels"  value={t.layout}
                     options={['floating', 'sidebar']}
                     onChange={(v) => setTweak('layout', v)} />

        <TweakSection label="Point cloud" />
        <TweakSelect label="Point shape" value={t.pointShape}
                     options={['circle', 'square', 'ring', 'diamond', 'triangle', 'cross', 'plus', 'soft']}
                     onChange={(v) => setTweak('pointShape', v)} />
        <TweakSlider label="Point size"     value={t.pointSize}    min={0.02} max={0.30} step={0.005}
                     onChange={(v) => setTweak('pointSize', v)} />
        <TweakSlider label="Point opacity"  value={t.pointOpacity} min={0.1}  max={1}    step={0.05}
                     onChange={(v) => setTweak('pointOpacity', v)} />
        <TweakSlider label="Halo size"      value={t.haloSize}     min={0}    max={0.6}  step={0.01}
                     onChange={(v) => setTweak('haloSize', v)} />
        <TweakSlider label="Halo opacity"   value={t.haloOpacity}  min={0}    max={0.8}  step={0.02}
                     onChange={(v) => setTweak('haloOpacity', v)} />
        <TweakSlider label="Trail opacity"  value={t.lineOpacity}  min={0}    max={1}    step={0.02}
                     onChange={(v) => setTweak('lineOpacity', v)} />
        <TweakSlider label="Shadow opacity" value={t.shadowOpacity} min={0}   max={1}    step={0.02}
                     onChange={(v) => setTweak('shadowOpacity', v)} />
        <TweakToggle label="Show grid"      value={t.showGrid}
                     onChange={(v) => setTweak('showGrid', v)} />
        <TweakToggle label="Drop lines (end)" value={t.showDrops}
                     onChange={(v) => setTweak('showDrops', v)} />
      </TweaksPanel>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById('ui-root'));
root.render(<App />);
