// ============================================================
// Floor Plan View — with inspection stops + robotic unit
// ============================================================
const { useState: useStateF, useEffect: useEffectF, useRef: useRefF, useMemo: useMemoF } = React;

// path motion with inspection stops — AXIS-ALIGNED (4-way) only
// Each segment a→b decomposes into an L-path: move along X first, then Y.
// Facing is snapped to cardinal (E/S/W/N).
function snapCardinal(rad) {
  // returns the nearest of 0, PI/2, PI, -PI/2
  const TAU = Math.PI * 2;
  const a = ((rad % TAU) + TAU) % TAU; // 0..2PI
  const q = Math.round(a / (Math.PI / 2)) % 4;
  return [0, Math.PI / 2, Math.PI, -Math.PI / 2][q];
}

function robotMotion(path, speed, t) {
  if (!path || path.length === 0) return { x: 0, y: 0, angle: 0, state: 'moving', scanPhase: 0, wpIdx: 0 };
  if (path.length < 2) {
    const p = path[0];
    return { x: p.x, y: p.y,
             angle: snapCardinal(p.inspect?.facing || 0),
             state: 'inspecting', scanPhase: (t % 2) / 2, wpIdx: 0 };
  }
  const spd = Math.max(0.01, speed);
  // Build micro-segments: between each pair of waypoints insert a corner
  // so motion is X-first then Y (Manhattan L-path).
  // legs: [{x1,y1,x2,y2, dur, angle, endWpIdx, isInspect?}]
  const legs = [];
  for (let i = 0; i < path.length; i++) {
    const a = path[i];
    const b = path[(i + 1) % path.length];
    // X leg
    if (b.x !== a.x) {
      legs.push({
        x1: a.x, y1: a.y, x2: b.x, y2: a.y,
        dur: Math.abs(b.x - a.x) / spd,
        angle: b.x > a.x ? 0 : Math.PI,
        endWpIdx: i, // still moving toward b
      });
    }
    // Y leg
    if (b.y !== a.y) {
      legs.push({
        x1: b.x, y1: a.y, x2: b.x, y2: b.y,
        dur: Math.abs(b.y - a.y) / spd,
        angle: b.y > a.y ? Math.PI / 2 : -Math.PI / 2,
        endWpIdx: i,
      });
    }
    // If a === b, still add a zero-duration stub so index stays sane
    if (b.x === a.x && b.y === a.y) {
      legs.push({ x1: a.x, y1: a.y, x2: b.x, y2: b.y, dur: 0, angle: 0, endWpIdx: i });
    }
    // Inspection hold at b
    const inspectDur = b.inspect?.duration || 0;
    if (inspectDur > 0) {
      legs.push({
        isInspect: true,
        x1: b.x, y1: b.y, x2: b.x, y2: b.y,
        dur: inspectDur,
        angle: snapCardinal(b.inspect?.facing ?? 0),
        endWpIdx: (i + 1) % path.length,
      });
    }
  }

  const cycle = legs.reduce((s, l) => s + l.dur, 0) || 1;
  let rem = t % cycle;
  for (const l of legs) {
    if (rem < l.dur) {
      const f = l.dur > 0 ? rem / l.dur : 0;
      if (l.isInspect) {
        return {
          x: l.x1, y: l.y1,
          angle: l.angle,
          state: 'inspecting',
          scanPhase: f,
          wpIdx: l.endWpIdx,
        };
      }
      return {
        x: l.x1 + (l.x2 - l.x1) * f,
        y: l.y1 + (l.y2 - l.y1) * f,
        angle: l.angle,
        state: 'moving', scanPhase: 0,
        wpIdx: l.endWpIdx,
      };
    }
    rem -= l.dur;
  }
  return { x: path[0].x, y: path[0].y, angle: 0, state: 'moving', scanPhase: 0, wpIdx: 0 };
}

// Robot unit — bigger, chassis + sensor dome + heading lamp + scan cone
function RobotUnit({ cx, cy, angle, state, scanPhase, size = 22 }) {
  const s = size;
  const scanning = state === 'inspecting';
  // scan cone sweeps ±45° while inspecting
  const sweepHalf = Math.PI / 4;
  const sweep = Math.sin(scanPhase * Math.PI * 4) * sweepHalf;
  const lookAngle = angle + (scanning ? sweep : 0);
  return (
    <g transform={`translate(${cx} ${cy})`}>
      {/* scan cone when inspecting */}
      {scanning && (
        <g transform={`rotate(${(lookAngle * 180) / Math.PI})`}>
          <defs>
            <linearGradient id={`sc-${cx.toFixed(0)}-${cy.toFixed(0)}`} x1="0" y1="0" x2="1" y2="0">
              <stop offset="0" stopColor="var(--accent)" stopOpacity="0.35"/>
              <stop offset="1" stopColor="var(--accent)" stopOpacity="0"/>
            </linearGradient>
          </defs>
          <path d={`M 0 0 L ${s*3} ${-s*1.2} A ${s*3.3} ${s*3.3} 0 0 1 ${s*3} ${s*1.2} Z`}
                fill={`url(#sc-${cx.toFixed(0)}-${cy.toFixed(0)})`} opacity="0.9"/>
          <path d={`M 0 0 L ${s*3} ${-s*1.2} A ${s*3.3} ${s*3.3} 0 0 1 ${s*3} ${s*1.2} Z`}
                fill="none" stroke="var(--accent)" strokeWidth="0.7" opacity="0.55"/>
          {/* scan sweep line */}
          <line x1="0" y1="0" x2={s*3.1} y2="0" stroke="var(--accent)" strokeWidth="1.2"
                opacity="0.9" strokeLinecap="round"/>
        </g>
      )}

      {/* outer patrol ring */}
      <circle r={s * 1.3} fill="none" stroke="var(--accent)" strokeWidth="0.6" opacity="0.3"
              strokeDasharray="2 3"/>

      {/* chassis — hexagonal body */}
      <g transform={`rotate(${(angle * 180) / Math.PI})`}>
        {/* tracks (top/bottom) */}
        <rect x={-s*0.85} y={-s*0.95} width={s*1.7} height={s*0.28} rx="2"
              fill="#1a2130" stroke="var(--fg-2)" strokeWidth="0.6"/>
        <rect x={-s*0.85} y={s*0.67} width={s*1.7} height={s*0.28} rx="2"
              fill="#1a2130" stroke="var(--fg-2)" strokeWidth="0.6"/>
        {/* track treads */}
        {Array.from({ length: 6 }).map((_, i) => (
          <g key={i}>
            <line x1={-s*0.85 + i * s*0.28} y1={-s*0.95} x2={-s*0.85 + i * s*0.28} y2={-s*0.67}
                  stroke="var(--fg-2)" strokeWidth="0.5" opacity="0.7"/>
            <line x1={-s*0.85 + i * s*0.28} y1={s*0.67} x2={-s*0.85 + i * s*0.28} y2={s*0.95}
                  stroke="var(--fg-2)" strokeWidth="0.5" opacity="0.7"/>
          </g>
        ))}

        {/* body plate */}
        <path d={`M ${-s*0.7} ${-s*0.7} L ${s*0.5} ${-s*0.7} L ${s*0.9} 0 L ${s*0.5} ${s*0.7} L ${-s*0.7} ${s*0.7} L ${-s*0.9} 0 Z`}
              fill="var(--bg-2)" stroke="var(--accent)" strokeWidth="1.1"/>

        {/* panel lines */}
        <path d={`M ${-s*0.55} ${-s*0.55} L ${s*0.45} ${-s*0.55} L ${s*0.7} 0 L ${s*0.45} ${s*0.55} L ${-s*0.55} ${s*0.55} L ${-s*0.75} 0 Z`}
              fill="none" stroke="var(--accent)" strokeWidth="0.5" opacity="0.5"/>

        {/* sensor dome — center */}
        <circle r={s*0.35} fill="var(--bg-1)" stroke="var(--accent)" strokeWidth="0.9"/>
        <circle r={s*0.22} fill="var(--accent)" opacity={scanning ? 0.9 : 0.55}
                style={{ filter: 'drop-shadow(0 0 4px var(--accent))' }}/>
        <circle r={s*0.1} fill="#ffffff" opacity="0.85"/>

        {/* forward lamp */}
        <rect x={s*0.55} y={-s*0.15} width={s*0.3} height={s*0.3} rx="1"
              fill={scanning ? 'var(--accent)' : 'var(--fg-1)'}
              opacity="0.9" style={{ filter: 'drop-shadow(0 0 3px var(--accent))' }}/>

        {/* status LEDs on top plate */}
        <circle cx={-s*0.35} cy={-s*0.3} r="1.2" fill={scanning ? 'var(--warn)' : 'var(--ok)'}/>
        <circle cx={-s*0.35} cy={-s*0.05} r="1.2" fill={scanning ? 'var(--accent)' : 'var(--fg-3)'}/>
        <circle cx={-s*0.35} cy={s*0.2} r="1.2" fill="var(--fg-3)"/>

        {/* rear antenna */}
        <line x1={-s*0.7} y1="0" x2={-s*1.1} y2={-s*0.4} stroke="var(--fg-1)" strokeWidth="0.8"/>
        <circle cx={-s*1.1} cy={-s*0.4} r="1.2" fill="var(--accent)"/>
      </g>

      {/* state ring */}
      {scanning && (
        <circle r={s * 0.75} fill="none" stroke="var(--warn)" strokeWidth="1"
                strokeDasharray="3 3" opacity="0.7">
          <animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="3s" repeatCount="indefinite"/>
        </circle>
      )}
    </g>
  );
}

// ============================================================
// Group renderers — three detail levels (simple / status / full)
// ============================================================
function GroupSimple({ g, x, y, w, h, col, health }) {
  // Just box + tag + health.
  return (
    <>
      <rect className={`rg-rect ${health.level}`} x={x + 2} y={y + 2} width={w - 4} height={h - 4} rx="1"/>
      <text className="rg-label" x={x + 10} y={y + 18}>{g.tag}</text>
      <circle cx={x + w - 12} cy={y + 14} r="4" fill={col}
              style={{ filter: `drop-shadow(0 0 4px ${col})` }}/>
    </>
  );
}

function GroupStatus({ g, x, y, w, h, col, health }) {
  // Current per-rack bar mode
  const n = g.racks.length;
  const slotW = (w - 16) / n;
  const barW = slotW - 4;
  return (
    <>
      <rect className={`rg-rect ${health.level}`} x={x + 2} y={y + 2} width={w - 4} height={h - 4} rx="1"/>
      {g.racks.map((rk, i) => {
        const rh = rackHealth(rk);
        const bx = x + 8 + i * slotW;
        const by = y + h - 18;
        const rcol = rh.level === 'ok' ? 'var(--ok)'
                   : rh.level === 'warn' ? 'var(--warn)'
                   : rh.level === 'bad' ? 'var(--bad)'
                   : 'var(--fg-3)';
        if (rh.level === 'absent') {
          return (
            <rect key={i} x={bx} y={by} width={barW} height={8}
                  fill="none" stroke="var(--line-faint)" strokeWidth="1"
                  strokeDasharray="2 2" rx="1"/>
          );
        }
        const prop = rh.total > 0 ? rh.on / rh.total : 0;
        return (
          <g key={i}>
            <rect x={bx} y={by} width={barW} height={8}
                  fill="none" stroke={rcol} strokeWidth="0.6" opacity="0.6" rx="1"/>
            <rect x={bx} y={by} width={Math.max(1, barW * prop)} height={8}
                  fill={rcol} opacity={rh.level === 'off' ? 0.2 : 0.55} rx="1"/>
            {rh.susp > 0 && (
              <circle cx={bx + barW - 2} cy={by - 2} r="1.6"
                      fill="var(--warn)"
                      style={{ filter: 'drop-shadow(0 0 3px var(--warn))' }}>
                <animate attributeName="opacity" values="0.3;1;0.3" dur="1.2s" repeatCount="indefinite"/>
              </circle>
            )}
          </g>
        );
      })}
      <text className="rg-label" x={x + 10} y={y + 16}>{g.tag}</text>
      <text className="rg-sub" x={x + 10} y={y + 28}>{g.racks.length} RACKS · {health.total} DEV</text>
    </>
  );
}

/* Full mode (SVG portion) — just an empty framed box.
   Actual rack columns are rendered as HTML overlays above the SVG
   so we can reuse the exact RackColumn component from the rack tab. */
function GroupFull({ g, x, y, w, h, col, health }) {
  return (
    <>
      <rect className={`rg-rect ${health.level}`} x={x + 2} y={y + 2} width={w - 4} height={h - 4} rx="1"
            fill="rgba(6,9,14,0.55)"/>
      <text className="rg-label" x={x + 10} y={y + 16}>{g.tag}</text>
      <text className="rg-sub" x={x + w - 10} y={y + 16} textAnchor="end">
        {health.on}/{health.total} ON · {health.susp} SUSP
      </text>
    </>
  );
}

/* HTML overlay — only used when detail=full.
   Positions each rack column using the group's top-left as anchor,
   lets them overflow the group box at real size. */
function FullRackOverlay({ groups, cfg, ox, oy, cell }) {
  const { M, K } = cfg.params;
  // NO-OP toggle for floor preview (read-only)
  const noop = () => {};
  const tt = { show: () => {}, move: () => {}, hide: () => {} };
  // Fit one rack column into exactly one grid cell horizontally.
  // The real RackColumn is ~120px wide; scale = cell / 120 roughly.
  const rackNativeW = 120;
  const scale = Math.max(0.25, Math.min(0.9, cell / rackNativeW));
  return (
    <div className="floor-full-overlay">
      {groups.map(g => {
        if ((cfg.params.floorDetail || 'status') !== 'full') return null;
        const gx = ox + g.x * cell;
        const gy = oy + g.y * cell;
        const gh = g.h * cell;
        return (
          <div key={g.id}
               className="floor-full-group"
               style={{ position: 'absolute', left: gx, top: gy + 22, width: groupWidth(g) * cell, height: gh - 22 }}>
            <div className="floor-full-rackrow" style={{ gap: 0 }}>
              {g.racks.map(r => (
                <div key={r.id} className="floor-full-rack-wrap"
                     style={{ width: cell, transform: `scale(${scale})`, transformOrigin: 'top left' }}>
                  <div style={{ width: cell / scale }}>
                    <RackColumn rack={r} M={M} K={K}
                                onToggle={noop} tt={tt} scanning={false} />
                  </div>
                </div>
              ))}
            </div>
          </div>
        );
      })}
    </div>
  );
}

function FloorPlan({ cfg, onPickGroup, speed }) {
  const { gridW, gridH } = cfg.params;
  const wrapRef = useRefF(null);
  const [size, setSize] = useStateF({ w: 1000, h: 700 });
  const tt = useTooltip();
  const t = useTick(speed);

  useEffectF(() => {
    if (!wrapRef.current) return;
    const ro = new ResizeObserver(([e]) => {
      setSize({ w: e.contentRect.width, h: e.contentRect.height });
    });
    ro.observe(wrapRef.current);
    return () => ro.disconnect();
  }, []);

  const pad = 44;
  const cell = Math.min((size.w - pad * 2) / gridW, (size.h - pad * 2) / gridH);
  const W = cell * gridW, H = cell * gridH;
  const ox = (size.w - W) / 2, oy = (size.h - H) / 2;

  // trail points for each robot (only while moving)
  const trailRef = useRefF({});
  const states = {};
  cfg.robots.forEach(r => {
    const m = robotMotion(r.path, r.speed, t);
    const prev = trailRef.current[r.id] || [];
    const next = m.state === 'moving' ? [...prev, m].slice(-32) : prev.slice(-24);
    trailRef.current[r.id] = next;
    states[r.id] = { motion: m, trail: next };
  });

  return (
    <div className="floor-wrap view-enter">
      <div className="floor-canvas" ref={wrapRef} onMouseMove={tt.move}>
        <div className="bp-grid" />
        <Corners />
        <div className="bp-vignette" />

        <svg className="floor-svg" viewBox={`0 0 ${size.w} ${size.h}`} preserveAspectRatio="none">
          {/* axis ticks */}
          <g>
            {Array.from({ length: gridW + 1 }).map((_, i) => (
              <text key={`tx${i}`} className="axis-tick" x={ox + i * cell} y={oy - 8} textAnchor="middle">
                {String(i).padStart(2, '0')}
              </text>
            ))}
            {Array.from({ length: gridH + 1 }).map((_, j) => (
              <text key={`ty${j}`} className="axis-tick" x={ox - 8} y={oy + j * cell + 3} textAnchor="end">
                {String.fromCharCode(65 + j)}
              </text>
            ))}
          </g>

          <rect x={ox} y={oy} width={W} height={H} fill="none" stroke="var(--line-soft)" strokeWidth="1" />
          <rect x={ox + 6} y={oy + 6} width={W - 12} height={H - 12} fill="none" stroke="var(--line-faint)" strokeWidth="0.5" strokeDasharray="2 4" />

          {/* entrances */}
          {cfg.entrances.map(e => {
            const x = ox + e.x * cell, y = oy + e.y * cell;
            const w = e.w * cell, h = e.h * cell;
            return (
              <g key={e.id} className="entrance-g">
                <rect className="entrance-rect" x={x} y={y} width={w} height={h} rx="1" />
                <text className="entrance-label" x={x + w / 2} y={y + h / 2 + 3} textAnchor="middle">ENTRY</text>
              </g>
            );
          })}

          {/* rack groups */}
          {cfg.groups.map(g => {
            const health = groupHealth(g);
            const x = ox + g.x * cell, y = oy + g.y * cell;
            const w = groupWidth(g) * cell, h = g.h * cell;
            const col = health.level === 'ok' ? 'var(--ok)' : health.level === 'warn' ? 'var(--warn)' : 'var(--bad)';
            const detail = cfg.params.floorDetail || 'status';
            return (
              <g key={g.id} onClick={() => onPickGroup(g.id)}
                 onMouseEnter={(e) => tt.show({
                   title: `${g.tag} · Rack Group`,
                   rows: [
                     ['Racks', g.racks.length],
                     ['Powered', `${health.on}/${health.total}`],
                     ['Suspicious', health.susp, health.susp ? 'var(--warn)' : 'var(--ok)'],
                     ['Status', health.level.toUpperCase(), col],
                   ]
                 }, e)}
                 onMouseLeave={tt.hide}
                 style={{ color: col, cursor: 'pointer' }}>
                {detail === 'simple'
                  ? <GroupSimple g={g} x={x} y={y} w={w} h={h} col={col} health={health} />
                  : detail === 'full'
                  ? <GroupFull g={g} x={x} y={y} w={w} h={h} col={col} health={health} M={cfg.params.M} />
                  : <GroupStatus g={g} x={x} y={y} w={w} h={h} col={col} health={health} />}
                {/* corner brackets — always */}
                <g stroke={col} strokeWidth="1" fill="none">
                  <path d={`M ${x+2} ${y+8} L ${x+2} ${y+2} L ${x+8} ${y+2}`} />
                  <path d={`M ${x+w-8} ${y+2} L ${x+w-2} ${y+2} L ${x+w-2} ${y+8}`} />
                  <path d={`M ${x+2} ${y+h-8} L ${x+2} ${y+h-2} L ${x+8} ${y+h-2}`} />
                  <path d={`M ${x+w-8} ${y+h-2} L ${x+w-2} ${y+h-2} L ${x+w-2} ${y+h-8}`} />
                </g>
              </g>
            );
          })}

          {/* robot paths + robots */}
          {cfg.robots.map(r => {
            // Manhattan (L-shaped) path rendering
            const lPts = [];
            for (let i = 0; i < r.path.length; i++) {
              const a = r.path[i];
              const b = r.path[(i + 1) % r.path.length];
              lPts.push([ox + a.x * cell, oy + a.y * cell]);
              if (a.x !== b.x || a.y !== b.y) {
                lPts.push([ox + b.x * cell, oy + a.y * cell]); // corner
              }
            }
            if (lPts.length) lPts.push([ox + r.path[0].x * cell, oy + r.path[0].y * cell]);
            const pts = lPts.map(p => p.join(',')).join(' ');
            const st = states[r.id];
            const cx = ox + st.motion.x * cell;
            const cy = oy + st.motion.y * cell;
            const trailPts = st.trail.map(p => `${ox + p.x * cell},${oy + p.y * cell}`).join(' ');
            return (
              <g key={r.id}>
                <polyline className="robot-path" points={pts} />
                <polyline className="robot-trail" points={trailPts} />

                {/* waypoints */}
                {r.path.map((p, i) => {
                  const px = ox + p.x * cell, py = oy + p.y * cell;
                  const isInspect = !!p.inspect;
                  return (
                    <g key={i}>
                      {isInspect && (
                        <>
                          {/* inspection marker */}
                          <circle cx={px} cy={py} r="8" fill="none"
                                  stroke="var(--warn)" strokeWidth="1" opacity="0.7"
                                  strokeDasharray="2 2"/>
                          {/* facing arrow */}
                          <line
                            x1={px} y1={py}
                            x2={px + Math.cos(p.inspect.facing) * 14}
                            y2={py + Math.sin(p.inspect.facing) * 14}
                            stroke="var(--warn)" strokeWidth="1.2" opacity="0.8"/>
                          <path
                            d={`M 0 -3 L 5 0 L 0 3 Z`}
                            transform={`translate(${px + Math.cos(p.inspect.facing) * 14}, ${py + Math.sin(p.inspect.facing) * 14}) rotate(${p.inspect.facing * 180 / Math.PI})`}
                            fill="var(--warn)" opacity="0.9"/>
                        </>
                      )}
                      <circle cx={px} cy={py} r={isInspect ? 3 : 2.2}
                              fill={isInspect ? 'var(--warn)' : 'var(--accent-dim)'}
                              opacity="0.9" />
                    </g>
                  );
                })}

                {/* ping ring */}
                <circle cx={cx} cy={cy} className="robot-ping-ring" />

                {/* bigger, more robotic unit */}
                <RobotUnit cx={cx} cy={cy}
                  angle={st.motion.angle}
                  state={st.motion.state}
                  scanPhase={st.motion.scanPhase}
                  size={Math.max(14, cell * 0.38)} />

                {/* label */}
                <g transform={`translate(${cx + cell * 0.55} ${cy - cell * 0.55})`}>
                  <rect x="0" y="-8" width="58" height="16" rx="2"
                        fill="var(--bg-0)" fillOpacity="0.8"
                        stroke={st.motion.state === 'inspecting' ? 'var(--warn)' : 'var(--accent)'}
                        strokeWidth="0.8"/>
                  <text x="5" y="3" fontFamily="var(--font-mono)" fontSize="9"
                        fill={st.motion.state === 'inspecting' ? 'var(--warn)' : 'var(--accent)'}>
                    {r.name} · {st.motion.state === 'inspecting' ? 'SCAN' : 'MOVE'}
                  </text>
                </g>
              </g>
            );
          })}
        </svg>

        {(cfg.params.floorDetail || 'status') === 'full' && (
          <FullRackOverlay groups={cfg.groups} cfg={cfg} ox={ox} oy={oy} cell={cell} />
        )}

        <Tooltip tip={tt.tip} />
      </div>

      <aside className="floor-side">
        <div className="panel">
          <div className="panel-title"><span>RANGE</span><span className="spacer"/><span className="mono">{gridW}×{gridH}</span></div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
            <div className="stat-row ok"><span className="k">RG</span><span className="v">{cfg.groups.length}</span></div>
            <div className="stat-row"><span className="k">Robots</span><span className="v">{cfg.robots.length}</span></div>
            <div className="stat-row"><span className="k">Entries</span><span className="v">{cfg.entrances.length}</span></div>
            <div className="stat-row"><span className="k">Ver</span><span className="v">1.0</span></div>
          </div>
        </div>

        <div className="panel">
          <div className="panel-title"><span>Legend · 범례</span><span className="spacer"/></div>
          <div className="legend-row ok">
            <div className="legend-swatch fill" />
            <span className="nm">정상 · Normal</span>
            <span className="ct">{cfg.groups.filter(g => groupHealth(g).level === 'ok').length}</span>
          </div>
          <div className="legend-row warn">
            <div className="legend-swatch fill" />
            <span className="nm">경고 · Warning</span>
            <span className="ct">{cfg.groups.filter(g => groupHealth(g).level === 'warn').length}</span>
          </div>
          <div className="legend-row bad">
            <div className="legend-swatch fill" />
            <span className="nm">위험 · Critical</span>
            <span className="ct">{cfg.groups.filter(g => groupHealth(g).level === 'bad').length}</span>
          </div>
        </div>

        <div className="panel">
          <div className="panel-title"><span>Active Units · 로봇</span><span className="spacer"/></div>
          {cfg.robots.map(r => {
            const m = states[r.id].motion;
            const col = m.state === 'inspecting' ? 'var(--warn)' : 'var(--accent)';
            return (
              <div key={r.id} style={{ padding: '8px 0', borderBottom: '1px dashed var(--line-faint)' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                  <span className="mono" style={{ color: col }}>{r.name}</span>
                  <span className="mono" style={{ color: col, fontSize: 10, letterSpacing: '0.14em' }}>
                    {m.state === 'inspecting' ? 'INSPECTING' : 'PATROLLING'}
                  </span>
                </div>
                <div className="mono" style={{ fontSize: 10, color: 'var(--fg-2)', marginTop: 2 }}>
                  POS [{m.x.toFixed(1).padStart(4)}, {m.y.toFixed(1).padStart(4)}] · HDG {(m.angle * 180/Math.PI).toFixed(0)}°
                </div>
                <div className="mono" style={{ fontSize: 10, color: 'var(--fg-3)', marginTop: 2 }}>
                  SPEED {r.speed.toFixed(2)}/s · WP {m.wpIdx + 1}/{r.path.length}
                </div>
              </div>
            );
          })}
        </div>

        <div className="panel">
          <div className="panel-title"><span>Groups</span><span className="spacer"/></div>
          {cfg.groups.map(g => {
            const h = groupHealth(g);
            const col = h.level === 'ok' ? 'var(--ok)' : h.level === 'warn' ? 'var(--warn)' : 'var(--bad)';
            return (
              <button key={g.id} onClick={() => onPickGroup(g.id)}
                style={{
                  display: 'flex', width: '100%', alignItems: 'center', gap: 10,
                  padding: '8px 4px', borderBottom: '1px dashed var(--line-faint)', textAlign: 'left',
                }}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: col, boxShadow: `0 0 6px ${col}` }}/>
                <span className="mono" style={{ color: 'var(--fg-0)' }}>{g.tag}</span>
                <span className="mono dim" style={{ marginLeft: 'auto' }}>{h.on}/{h.total}</span>
              </button>
            );
          })}
        </div>
      </aside>
    </div>
  );
}

Object.assign(window, { FloorPlan, robotMotion, RobotUnit });
