/* SkyOps — Processing Studio: layer workbench viewer (giro3d-style).
   Layers left · viewer center (2D/3D, measure/area/profile/select) · inspector right. */

const { useState: uSPS, useMemo: uMPS, useRef: uRPS } = React;

const PS_SCALE = 0.5; // metres per SVG unit
function psHexLerp(a, b, t) {
  const pa = [1,3,5].map(i => parseInt(a.slice(i,i+2),16)), pb = [1,3,5].map(i => parseInt(b.slice(i,i+2),16));
  return `rgb(${pa.map((v,i) => Math.round(v + (pb[i]-v)*t)).join(",")})`;
}
function psRamp(stops, t) {
  t = Math.max(0, Math.min(1, t));
  for (let i = 1; i < stops.length; i++) if (t <= stops[i][0]) {
    const [t0,c0] = stops[i-1], [t1,c1] = stops[i];
    return psHexLerp(c0, c1, (t - t0) / ((t1 - t0) || 1));
  }
  return stops[stops.length-1][1];
}
const PS_RAMPS = {
  ironbow: [[0,"#0b0b3b"],[0.3,"#5a1a8c"],[0.55,"#c4204a"],[0.8,"#f08a1c"],[1,"#fff3b0"]],
  rainbow: [[0,"#1e3a8a"],[0.25,"#06b6d4"],[0.5,"#22c55e"],[0.75,"#eab308"],[1,"#ef4444"]],
  gray:    [[0,"#111111"],[1,"#f5f5f5"]],
  terrain: [[0,"#1e3a5f"],[0.35,"#2f6f6a"],[0.6,"#8c8a3c"],[0.85,"#b07a3a"],[1,"#e8e2d0"]],
  ndvi:    [[0,"#7f1d1d"],[0.4,"#ca8a04"],[0.7,"#65a30d"],[1,"#064e3b"]],
};
const PS_RAMP_CSS = (k) => `linear-gradient(90deg, ${PS_RAMPS[k].map(([t,c]) => `${c} ${t*100}%`).join(",")})`;

function psField(seed = 7) {
  let s = seed; const rnd = () => (s = (s * 9301 + 49297) % 233280) / 233280;
  const D = {
    "2-5":  { k: "Hot cell",      sev: "critical", dT: 28.4, rgb: false },
    "6-18": { k: "Bypass diode",  sev: "high",     dT: 14.1, rgb: false },
    "9-3":  { k: "PID",           sev: "medium",   dT: 6.4,  rgb: false },
    "11-21":{ k: "Hot cell",      sev: "high",     dT: 19.7, rgb: true },
    "4-23": { k: "Soiling",       sev: "low",      dT: 2.1,  rgb: true },
    "7-9":  { k: "Module crack",  sev: "high",     dT: 11.2, rgb: true },
    "1-15": { k: "Bird dropping", sev: "low",      dT: 3.4,  rgb: true },
  };
  const out = [];
  for (let r = 0; r < 14; r++) for (let c = 0; c < 26; c++) {
    const x = 70 + c * 33 + (c > 12 ? 26 : 0), y = 64 + r * 31;
    const d = D[`${r}-${c}`];
    const off = r === 12 && c >= 8 && c <= 13;
    const soil = rnd() < 0.07 || (d && d.k === "Soiling");
    const t = 41 + rnd() * 4 + (d ? d.dT : 0) + (off ? 9 : 0);
    out.push({
      id: `A${String(r+1).padStart(2,"0")}-${String(c+1).padStart(2,"0")}`, r, c, x, y, w: 28, h: 22,
      temp: +t.toFixed(1), soil,
      def: d ? { ...d } : off ? { k: "String offline", sev: "critical", dT: 9.0, rgb: false } : null,
      str: `STR-${String(r * 5 + Math.floor(c / 6) + 1).padStart(3,"0")}`,
    });
  }
  return out;
}
const psElev = (x, y) => 312 + 8 * Math.sin(x / 140) + 5 * Math.cos(y / 90) + 2 * Math.sin((x + y) / 60);
const PS_SEV = { critical: "#ef4444", high: "#f97316", medium: "#eab308", low: "#22c55e" };
Object.assign(window, { psRamp, PS_RAMPS, PS_RAMP_CSS, psField, psElev, PS_SEV });

const PS_LAYERS = [
  { g: "Base", items: [
    { k: "basemap", n: "Satellite basemap", m: "Esri · 30 cm", sw: "linear-gradient(135deg,#2a3324,#3b3a2a)" } ]},
  { g: "Products", items: [
    { k: "ortho",   n: "Orthomosaic RGB",   m: "COG · 1.24 cm/px · 18.4 GB", sw: "linear-gradient(135deg,#1f3550,#3e5f80)", src: "products/orthomosaic.tif", stage: "Ortho + DSM" },
    { k: "thermal", n: "Thermal ortho",     m: "LWIR · 4 cm/px · 6.2 GB", ramp: "ironbow", src: "products/orthomosaic_thermal.tif", stage: "Thermal register" },
    { k: "dsm",     n: "Surface model",     m: "DSM · 5 cm/px · 3.1 GB",  ramp: "terrain", src: "products/dsm.tif", stage: "Ortho + DSM" },
    { k: "ndvi",    n: "Vegetation index",  m: "NDVI · 8 cm/px · 1.1 GB", ramp: "ndvi", src: "products/ndvi.tif", stage: "Ortho + DSM" } ]},
  { g: "3D", items: [
    { k: "pc",    n: "Point cloud",    m: "COPC · 84.2M pts · 12.6 GB", sw: "radial-gradient(circle,#a78bfa,#1e1b4b)", src: "products/pointcloud.copc.laz", stage: "Dense reconstruction" },
    { k: "mesh",  n: "Textured mesh",  m: "GLB · 14.2M tris · 4.2 GB", sw: "repeating-linear-gradient(45deg,#14b8a6 0 2px,#0f172a 2px 6px)", src: "products/mesh.glb", stage: "Dense reconstruction" },
    { k: "splat", n: "Gaussian splat", m: "3DGS · 2.1M splats", sw: "radial-gradient(circle at 40% 40%,#f472b6,#0f172a)", src: "products/splat.ksplat", stage: "Post-process" } ]},
  { g: "Intelligence", items: [
    { k: "panels", n: "Panel index",   m: "124,810 modules", sw: "repeating-linear-gradient(90deg,#4a90d9 0 3px,transparent 3px 6px)", src: "reports/panel_index.geojson", stage: "Thermal register" },
    { k: "det",    n: "Detections",    m: "1,204 · model v3.1", sw: "#ef4444", src: "reports/detections.geojson", stage: "Anomaly detection" },
    { k: "gaps",   n: "Coverage gaps", m: "7.6% of AOI · sortie 11", sw: "repeating-linear-gradient(45deg,#ef4444 0 2px,transparent 2px 5px)", src: "reports/coverage.geojson", stage: "Validation" } ]},
];
const PS_ALL = PS_LAYERS.flatMap(g => g.items);
const PS_RANGES = { thermal: [38, 72, "°C"], dsm: [300, 326, "m"], ndvi: [-0.2, 0.9, ""] };

function ViewStudio({ selJob }) {
  const panels = uMPS(() => psField(7), []);
  const [vis, setVis] = uSPS({ basemap: true, ortho: true, thermal: false, dsm: false, ndvi: false, pc: false, mesh: false, splat: false, panels: true, det: true, gaps: true });
  const [op, setOp] = uSPS({ thermal: 0.9, dsm: 0.55, ndvi: 0.6, pc: 0.9, mesh: 0.7, splat: 0.8 });
  const [ramp, setRamp] = uSPS({ thermal: "ironbow", dsm: "terrain", ndvi: "ndvi" });
  const [active, setActive] = uSPS("ortho");
  const [tool, setTool] = uSPS("select");
  const [pts, setPts] = uSPS([]);
  const [sel, setSel] = uSPS(null);
  const [view, setView] = uSPS("2d");
  const [cur, setCur] = uSPS(null);
  const [tab, setTab] = uSPS("sel");
  const svgRef = uRPS(null);

  const toSvg = (e) => {
    const s = svgRef.current, p = s.createSVGPoint();
    p.x = e.clientX; p.y = e.clientY;
    const r = p.matrixTransform(s.getScreenCTM().inverse());
    return [r.x, r.y];
  };
  const panelAt = ([x, y]) => panels.find(p => x >= p.x && x <= p.x + p.w && y >= p.y && y <= p.y + p.h);
  const onClick = (e) => {
    const pt = toSvg(e);
    if (tool === "select") { const p = panelAt(pt); setSel(p || null); if (p) setTab("sel"); return; }
    if (tool === "measure" || tool === "profile") setPts(prev => prev.length >= 2 ? [pt] : [...prev, pt]);
    if (tool === "area") setPts(prev => [...prev, pt]);
  };
  const pickTool = (t) => { setTool(t); setPts([]); };
  const toggle = (k) => { setVis(v => ({ ...v, [k]: !v[k] })); setActive(k); };

  const dist = pts.length === 2 ? Math.hypot(pts[1][0]-pts[0][0], pts[1][1]-pts[0][1]) * PS_SCALE : null;
  const area = pts.length >= 3 ? Math.abs(pts.reduce((a, p, i) => { const q = pts[(i+1) % pts.length]; return a + p[0]*q[1] - q[0]*p[1]; }, 0) / 2) * PS_SCALE * PS_SCALE : null;
  const tRange = PS_RANGES.thermal;
  const tCol = (t) => psRamp(PS_RAMPS[ramp.thermal], (t - tRange[0]) / (tRange[1] - tRange[0]));
  const legendKey = ["thermal","dsm","ndvi"].find(k => k === active && vis[k]) || ["thermal","dsm","ndvi"].find(k => vis[k]);
  const curPanel = cur ? panelAt(cur) : null;
  const median = 42.9;

  const cells = [];
  for (let r = 0; r < 22; r++) for (let c = 0; c < 40; c++) cells.push([c * 25, r * 25.5]);

  return (
    <div className="ps">
      {/* ── Layers ── */}
      <aside className="ps-side ps-layers">
        <div className="ps-side-hd"><span>Layers</span><span className="mono ps-muted">{Object.values(vis).filter(Boolean).length} on</span></div>
        <div className="ps-scroll">
          {PS_LAYERS.map(g => (
            <div key={g.g} className="ps-lg">
              <div className="ps-lg-h">{g.g}</div>
              {g.items.map(l => (
                <div key={l.k} className={`ps-l ${active === l.k ? "is-act" : ""} ${vis[l.k] ? "" : "is-off"}`} onClick={() => setActive(l.k)}>
                  <button className="ps-eye" onClick={(e) => { e.stopPropagation(); toggle(l.k); }} aria-pressed={vis[l.k]} title={vis[l.k] ? "Hide" : "Show"}>
                    <Icon name={vis[l.k] ? "eye" : "eye_off"} size={12}/>
                  </button>
                  <span className="ps-sw" style={{ background: l.ramp ? PS_RAMP_CSS(ramp[l.k] || l.ramp) : l.sw }}/>
                  <span className="ps-l-b"><span className="ps-l-n">{l.n}</span><span className="ps-l-m mono">{l.m}</span></span>
                </div>
              ))}
            </div>
          ))}
        </div>
        <div className="ps-side-ft mono">EPSG:25829 · ETRS89 / UTM 29N</div>
      </aside>

      {/* ── Viewer ── */}
      <div className="ps-view">
        <div className="ps-tb">
          <div className="ps-seg">
            {[["2d","2D"],["3d","3D"]].map(([k,l]) => <button key={k} className={view === k ? "on" : ""} onClick={() => setView(k)}>{l}</button>)}
          </div>
          <div className="ps-tools">
            {[["select","crosshair","Select"],["measure","ruler","Distance"],["area","box","Area"],["profile","activity","Profile"]].map(([k,ic,l]) => (
              <button key={k} className={tool === k ? "on" : ""} onClick={() => pickTool(k)} title={l}><Icon name={ic} size={12}/><span>{l}</span></button>
            ))}
          </div>
          {pts.length > 0 && <button className="ps-clear" onClick={() => setPts([])}><Icon name="x" size={11}/>Clear</button>}
          <span className="grow"/>
          <span className="ps-hint">{tool === "select" ? "Click a module to inspect" : tool === "area" ? "Click to add vertices" : "Click two points"}</span>
        </div>

        <div className="ps-stage">
          <div className={`ps-cam ${view === "3d" ? "is-3d" : ""}`}>
            <svg ref={svgRef} viewBox="0 0 1000 560" preserveAspectRatio="xMidYMid meet" className={`ps-svg tool-${tool}`}
                 onClick={onClick} onMouseMove={(e) => setCur(toSvg(e))} onMouseLeave={() => setCur(null)}>
              <defs>
                <pattern id="ps-hatch" width="7" height="7" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><line x1="0" y1="0" x2="0" y2="7" stroke="#ef4444" strokeWidth="1.4" opacity=".7"/></pattern>
                <filter id="ps-blur"><feGaussianBlur stdDeviation="3"/></filter>
              </defs>
              <rect width="1000" height="560" fill="#0a0f1a"/>
              {vis.basemap && <g>
                <rect width="1000" height="560" fill="#262b20"/>
                {cells.filter((_, i) => i % 3 === 0).map(([x,y], i) => <rect key={i} x={x} y={y} width="25" height="25.5" fill="#323626" opacity={0.25 + (i % 5) * 0.08}/>)}
                <path d="M0 40 H1000 M0 520 H1000 M528 0 V560" stroke="#4a4638" strokeWidth="14"/>
              </g>}
              {vis.ndvi && <g opacity={op.ndvi}>{cells.map(([x,y], i) => <rect key={i} x={x} y={y} width="25" height="25.5" fill={psRamp(PS_RAMPS[ramp.ndvi], 0.35 + 0.5 * Math.abs(Math.sin(x / 90 + y / 70)))}/>)}</g>}
              {vis.ortho && panels.map(p => <g key={p.id}>
                <rect x={p.x} y={p.y} width={p.w} height={p.h} fill="#1c2f48" stroke="#0b1220" strokeWidth=".8"/>
                <path d={`M${p.x + p.w/3} ${p.y}V${p.y+p.h}M${p.x + 2*p.w/3} ${p.y}V${p.y+p.h}M${p.x} ${p.y + p.h/2}H${p.x+p.w}`} stroke="#2c4566" strokeWidth=".5"/>
                {p.soil && <rect x={p.x + 3} y={p.y + p.h - 9} width={p.w - 6} height="7" fill="#8a7a55" opacity=".7"/>}
                {p.def?.k === "Module crack" && <path d={`M${p.x+4} ${p.y+3} L${p.x+14} ${p.y+12} L${p.x+24} ${p.y+19}`} stroke="#cbd5e1" strokeWidth=".8"/>}
                {p.def?.k === "Bird dropping" && <circle cx={p.x+18} cy={p.y+8} r="2.2" fill="#f1f5f9"/>}
              </g>)}
              {vis.dsm && <g opacity={op.dsm}>{cells.map(([x,y], i) => <rect key={i} x={x} y={y} width="25" height="25.5" fill={psRamp(PS_RAMPS[ramp.dsm], (psElev(x,y) - 300) / 26)}/>)}</g>}
              {vis.thermal && <g opacity={op.thermal}>
                <rect width="1000" height="560" fill={tCol(36)} opacity=".85"/>
                {panels.map(p => <rect key={p.id} x={p.x} y={p.y} width={p.w} height={p.h} fill={tCol(p.temp)}/>)}
                {panels.filter(p => p.def && p.def.dT > 10).map(p => <circle key={p.id} cx={p.x + p.w*0.6} cy={p.y + p.h*0.4} r="6" fill={tCol(p.temp + 10)} filter="url(#ps-blur)"/>)}
              </g>}
              {vis.pc && <g opacity={op.pc}>{Array.from({ length: 1100 }).map((_, i) => {
                const x = (i * 97.3) % 1000, y = (i * 53.7) % 560;
                return <circle key={i} cx={x} cy={y} r=".9" fill={psRamp(PS_RAMPS.terrain, (psElev(x,y) - 300) / 26)}/>;
              })}</g>}
              {vis.mesh && <g stroke="#14b8a6" strokeWidth=".35" opacity={op.mesh} fill="none">
                {Array.from({ length: 23 }).map((_, i) => <path key={"h"+i} d={`M0 ${i*25} H1000`}/>)}
                {Array.from({ length: 41 }).map((_, i) => <path key={"v"+i} d={`M${i*25} 0 L${i*25 - 140} 560`}/>)}
              </g>}
              {vis.splat && <g opacity={op.splat} filter="url(#ps-blur)">{Array.from({ length: 220 }).map((_, i) => {
                const x = (i * 131.7) % 1000, y = (i * 71.3) % 560;
                return <ellipse key={i} cx={x} cy={y} rx={4 + (i % 5)} ry={3 + (i % 3)} fill={`hsl(${20 + (i * 37) % 60} 60% ${40 + (i % 4) * 8}%)`} opacity=".7"/>;
              })}</g>}
              {vis.panels && panels.map(p => <rect key={p.id} x={p.x} y={p.y} width={p.w} height={p.h} fill="none" stroke="rgba(96,165,250,.45)" strokeWidth=".6"/>)}
              {vis.gaps && <g><path d="M760 360 L930 350 L945 470 L780 480 Z" fill="url(#ps-hatch)" stroke="#ef4444" strokeWidth="1.2" strokeDasharray="5 3"/>
                <text x="770" y="352" fontSize="10" fill="#fca5a5" fontFamily="var(--font-mono)">GAP · re-fly</text></g>}
              {vis.det && panels.filter(p => p.def).map(p => (
                <g key={p.id}>
                  <circle cx={p.x + p.w/2} cy={p.y + p.h/2} r="13" fill="none" stroke={PS_SEV[p.def.sev]} strokeWidth="1.6"/>
                  {p.def.sev === "critical" && <text x={p.x + p.w/2 + 16} y={p.y + 4} fontSize="9.5" fill={PS_SEV.critical} fontFamily="var(--font-mono)" fontWeight="600">+{p.def.dT}°C</text>}
                </g>
              ))}
              {sel && <rect x={sel.x - 3} y={sel.y - 3} width={sel.w + 6} height={sel.h + 6} fill="none" stroke="#60a5fa" strokeWidth="2.2" rx="2"/>}
              {(tool === "measure" || tool === "profile") && pts.length > 0 && <g>
                {pts.length === 2 && <line x1={pts[0][0]} y1={pts[0][1]} x2={pts[1][0]} y2={pts[1][1]} stroke="#fbbf24" strokeWidth="1.8" strokeDasharray="5 3"/>}
                {pts.map((p, i) => <circle key={i} cx={p[0]} cy={p[1]} r="4" fill="#fbbf24" stroke="#0a0f1a" strokeWidth="1.5"/>)}
                {dist != null && tool === "measure" && <g transform={`translate(${(pts[0][0]+pts[1][0])/2} ${(pts[0][1]+pts[1][1])/2 - 14})`}>
                  <rect x="-44" y="-11" width="88" height="20" rx="4" fill="#0a0f1a" stroke="#fbbf24"/>
                  <text textAnchor="middle" y="3" fontSize="11" fill="#fbbf24" fontFamily="var(--font-mono)">{dist.toFixed(1)} m</text></g>}
              </g>}
              {tool === "area" && pts.length > 0 && <g>
                <polygon points={pts.map(p => p.join(",")).join(" ")} fill="rgba(251,191,36,.15)" stroke="#fbbf24" strokeWidth="1.6"/>
                {pts.map((p, i) => <circle key={i} cx={p[0]} cy={p[1]} r="3.5" fill="#fbbf24"/>)}
              </g>}
            </svg>
          </div>

          <div className="ps-float-r">
            <button title="Zoom in">+</button><button title="Zoom out">−</button>
            <button title="Fit to AOI"><Icon name="maximize" size={12}/></button>
            <button title="North up"><Icon name="compass" size={12}/></button>
          </div>

          {legendKey && (
            <div className="ps-legend">
              <div className="ps-legend-h">{PS_ALL.find(l => l.k === legendKey).n}</div>
              <div className="ps-legend-bar" style={{ background: PS_RAMP_CSS(ramp[legendKey]) }}/>
              <div className="ps-legend-l mono"><span>{PS_RANGES[legendKey][0]}{PS_RANGES[legendKey][2]}</span><span>{PS_RANGES[legendKey][1]}{PS_RANGES[legendKey][2]}</span></div>
            </div>
          )}

          {(dist != null && tool === "profile") && <PsProfile a={pts[0]} b={pts[1]} len={dist} onClose={() => setPts([])}/>}
          {area != null && <div className="ps-readout mono">Area {area.toFixed(0)} m² · {pts.length} vertices · perimeter {(pts.reduce((a, p, i) => { const q = pts[(i+1) % pts.length]; return a + Math.hypot(q[0]-p[0], q[1]-p[1]); }, 0) * PS_SCALE).toFixed(0)} m</div>}
        </div>

        <div className="ps-status mono">
          {cur ? <>
            <span>E {(586400 + cur[0] * PS_SCALE).toFixed(1)}</span>
            <span>N {(4268700 - cur[1] * PS_SCALE).toFixed(1)}</span>
            <span>Z {psElev(cur[0], cur[1]).toFixed(2)} m</span>
            {curPanel && <span className="ps-st-mod">{curPanel.id}{vis.thermal ? ` · ${curPanel.temp}°C` : ""}</span>}
          </> : <span className="ps-muted">Move cursor over the scene</span>}
          <span className="grow"/>
          <span>{view === "3d" ? "Perspective · pitch 48°" : "Top-down"}</span>
          <span>1:620 · 0.5 m/px</span>
          <span>{selJob?.id}</span>
        </div>
      </div>

      {/* ── Inspector ── */}
      <aside className="ps-side ps-insp">
        <div className="ps-tabs">
          {[["sel","Selection"],["layer","Layer"],["out","Outputs"]].map(([k,l]) => <button key={k} className={tab === k ? "on" : ""} onClick={() => setTab(k)}>{l}</button>)}
        </div>
        <div className="ps-scroll">
          {tab === "sel" && (sel ? (
            <div className="ps-sec">
              <div className="ps-sel-hd">
                <div><div className="mono ps-sel-id">{sel.id}</div><div className="ps-muted ps-sm">{sel.str} · row {sel.r + 1} · col {sel.c + 1}</div></div>
                {sel.def && <span className="ps-sev" style={{ color: PS_SEV[sel.def.sev], borderColor: PS_SEV[sel.def.sev] }}>{sel.def.sev}</span>}
              </div>
              <div className="ps-kv"><span>Temperature</span><b className="mono">{sel.temp} °C</b></div>
              <div className="ps-kv"><span>ΔT vs field median</span><b className="mono" style={{ color: sel.temp - median > 10 ? PS_SEV.critical : undefined }}>{sel.temp - median >= 0 ? "+" : ""}{(sel.temp - median).toFixed(1)} °C</b></div>
              <div className="ps-kv"><span>Elevation</span><b className="mono">{psElev(sel.x, sel.y).toFixed(2)} m</b></div>
              <div className="ps-kv"><span>Soiling (RGB)</span><b>{sel.soil ? "visible" : "clean"}</b></div>
              <div className="ps-kv"><span>Detection</span><b>{sel.def ? sel.def.k : "none"}</b></div>
              {sel.def && <div className="ps-kv"><span>Evidence</span><b>{sel.def.rgb ? "thermal + RGB" : "thermal only"}</b></div>}
              <div className="ps-thumbs">
                <div><div className="ps-thumb ps-thumb-rgb"/><span className="caps">RGB</span></div>
                <div><div className="ps-thumb" style={{ background: `radial-gradient(circle at 60% 40%, ${tCol(sel.temp + 6)}, ${tCol(sel.temp)} 45%, ${tCol(40)})` }}/><span className="caps">Thermal</span></div>
              </div>
              {sel.def && <button className="btn btn-primary btn-xs ps-full" onClick={() => window.dispatchEvent(new CustomEvent("skyops:stage",{detail:"review"}))}><Icon name="eye" size={11}/>Open in Review</button>}
            </div>
          ) : <div className="ps-empty"><Icon name="crosshair" size={18}/><p>Pick the <b>Select</b> tool and click a module to see its temperature, elevation and detections.</p></div>)}

          {tab === "layer" && (() => {
            const l = PS_ALL.find(x => x.k === active);
            const hasOp = op[l.k] != null;
            return (
              <div className="ps-sec">
                <div className="ps-sel-hd"><div><div className="ps-sel-id">{l.n}</div><div className="ps-muted ps-sm mono">{l.m}</div></div>
                  <button className="ps-eye" onClick={() => toggle(l.k)}><Icon name={vis[l.k] ? "eye" : "eye_off"} size={12}/></button></div>
                {hasOp && <div className="ps-ctl"><span>Opacity</span><input type="range" min="0" max="1" step="0.05" value={op[l.k]} onChange={e => setOp(o => ({ ...o, [l.k]: +e.target.value }))}/><b className="mono">{Math.round(op[l.k] * 100)}%</b></div>}
                {l.ramp && <div className="ps-ctl"><span>Colormap</span>
                  <select value={ramp[l.k]} onChange={e => setRamp(r => ({ ...r, [l.k]: e.target.value }))}>
                    {(l.k === "thermal" ? ["ironbow","rainbow","gray"] : l.k === "dsm" ? ["terrain","rainbow","gray"] : ["ndvi","rainbow"]).map(k => <option key={k} value={k}>{k}</option>)}
                  </select></div>}
                {l.ramp && <div className="ps-hist">{Array.from({ length: 28 }).map((_, i) => <span key={i} style={{ height: `${18 + 70 * Math.exp(-Math.pow((i - 9) / 5, 2)) + (i > 20 ? 6 : 0)}%`, background: psRamp(PS_RAMPS[ramp[l.k]], i / 27) }}/>)}</div>}
                <div className="ps-kv"><span>Source</span><b className="mono ps-sm">{l.src || "external"}</b></div>
                <div className="ps-kv"><span>Produced by</span><b>{l.stage || "—"}</b></div>
                <div className="ps-kv"><span>CRS</span><b className="mono">EPSG:25829</b></div>
                <div className="ps-kv"><span>Job</span><b className="mono">{selJob?.id}</b></div>
              </div>
            );
          })()}

          {tab === "out" && (
            <div className="ps-sec ps-outs">
              <button className="btn btn-secondary btn-xs ps-full" onClick={() => window.dispatchEvent(new CustomEvent("skyops:stage",{detail:"files"}))}><Icon name="folder" size={11}/>Open all in Files</button>
              {PROC_OUTPUTS.map(o => (
                <div key={o.key} className="ps-out">
                  <span className="mono ps-out-t">{o.tag}</span>
                  <span className="ps-out-n">{o.label}<span className="mono ps-muted">{o.ext}</span></span>
                  <span className="mono ps-muted ps-sm">{o.size}</span>
                  <button className="fx-dl" title="Download"><Icon name="download" size={11}/></button>
                </div>
              ))}
            </div>
          )}
        </div>
      </aside>
    </div>
  );
}

function PsProfile({ a, b, len, onClose }) {
  const N = 48;
  const z = Array.from({ length: N }).map((_, i) => { const t = i / (N - 1); return psElev(a[0] + (b[0]-a[0]) * t, a[1] + (b[1]-a[1]) * t); });
  const mn = Math.min(...z), mx = Math.max(...z);
  const path = z.map((v, i) => `${i ? "L" : "M"}${(i / (N - 1)) * 560} ${70 - ((v - mn) / ((mx - mn) || 1)) * 60}`).join(" ");
  return (
    <div className="ps-profile">
      <div className="ps-profile-hd"><span>Height profile</span><span className="mono ps-muted">{len.toFixed(1)} m · Δz {(mx - mn).toFixed(2)} m · min {mn.toFixed(1)} · max {mx.toFixed(1)}</span><button onClick={onClose}><Icon name="x" size={11}/></button></div>
      <svg viewBox="0 0 560 80" preserveAspectRatio="none"><path d={path + " L560 80 L0 80 Z"} fill="rgba(251,191,36,.15)"/><path d={path} fill="none" stroke="#fbbf24" strokeWidth="1.6" vectorEffect="non-scaling-stroke"/></svg>
    </div>
  );
}

window.ViewStudio = ViewStudio;
