// FloorCanvas — canvas / drawing logic

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// World units = feet. PX_PER_FT controls initial zoom.
const FT = 1;

function snap(v, grid) {
  if (!grid || grid <= 0) return v;
  return Math.round(v / grid) * grid;
}

function dist(a, b) { return Math.hypot(a.x - b.x, a.y - b.y); }

// ---- Shape renderers ----

function WallShape({ s, selected }) {
  const t = s.thickness || 0.5;
  const dx = s.b.x - s.a.x;
  const dy = s.b.y - s.a.y;
  const len = Math.hypot(dx, dy) || 1;
  const nx = -dy / len, ny = dx / len; // normal
  const hx = (t / 2) * nx, hy = (t / 2) * ny;
  const p1 = { x: s.a.x + hx, y: s.a.y + hy };
  const p2 = { x: s.b.x + hx, y: s.b.y + hy };
  const p3 = { x: s.b.x - hx, y: s.b.y - hy };
  const p4 = { x: s.a.x - hx, y: s.a.y - hy };
  return (
    <g>
      <polygon
        points={`${p1.x},${p1.y} ${p2.x},${p2.y} ${p3.x},${p3.y} ${p4.x},${p4.y}`}
        fill={window.COLORS.ink}
        stroke={selected ? window.COLORS.blue : window.COLORS.ink}
        strokeWidth={selected ? 2 : 0}
        vectorEffect="non-scaling-stroke"
      />
    </g>
  );
}

function DoorShape({ s, selected }) {
  // door: a in-wall opening + a swing arc. width = s.width; orientation from rotation.
  const w = s.width;
  return (
    <g transform={`translate(${s.x},${s.y}) rotate(${s.rotation})`}>
      {/* opening: white rect that overdraws wall */}
      <rect x={0} y={-0.3} width={w} height={0.6} fill={window.COLORS.paper} stroke="none" />
      {/* hinge */}
      <circle cx={0} cy={0} r={0.08} fill={window.COLORS.ink} />
      {/* door panel */}
      <line x1={0} y1={0} x2={w} y2={0} stroke={window.COLORS.ink} strokeWidth={1.5} vectorEffect="non-scaling-stroke" />
      {/* arc */}
      <path d={`M ${w} 0 A ${w} ${w} 0 0 0 0 ${-w}`} fill="none" stroke={window.COLORS.ink} strokeWidth={0.7} vectorEffect="non-scaling-stroke" strokeDasharray="0.2 0.15" />
      {selected && <rect x={-0.1} y={-0.4} width={w + 0.2} height={0.8} fill="none" stroke={window.COLORS.blue} strokeWidth={1.5} vectorEffect="non-scaling-stroke" strokeDasharray="0.2 0.15" />}
    </g>
  );
}

function WindowShape({ s, selected }) {
  const w = s.width;
  return (
    <g transform={`translate(${s.x},${s.y}) rotate(${s.rotation})`}>
      <rect x={0} y={-0.25} width={w} height={0.5} fill={window.COLORS.paper} stroke={window.COLORS.ink} strokeWidth={1.2} vectorEffect="non-scaling-stroke" />
      <line x1={0} y1={0} x2={w} y2={0} stroke={window.COLORS.ink} strokeWidth={1} vectorEffect="non-scaling-stroke" />
      {selected && <rect x={-0.1} y={-0.35} width={w + 0.2} height={0.7} fill="none" stroke={window.COLORS.blue} strokeWidth={1.5} vectorEffect="non-scaling-stroke" strokeDasharray="0.2 0.15" />}
    </g>
  );
}

function FurnitureShape({ s, selected }) {
  const f = window.FURNITURE_MAP[s.kind];
  if (!f) return null;
  return (
    <g transform={`translate(${s.x},${s.y}) rotate(${s.rotation || 0}, ${(s.w || f.w) / 2}, ${(s.h || f.h) / 2})`}>
      {f.render({ w: s.w || f.w, h: s.h || f.h })}
      {selected && (
        <rect x={-0.1} y={-0.1} width={(s.w || f.w) + 0.2} height={(s.h || f.h) + 0.2} fill="none" stroke={window.COLORS.blue} strokeWidth={1.5} vectorEffect="non-scaling-stroke" strokeDasharray="0.25 0.15" />
      )}
    </g>
  );
}

function LabelShape({ s, selected }) {
  const size = s.size || 0.6;
  const dimSize = size * 0.55;
  const dimGap = size * 0.95;
  const hasDim = !!s.dim;
  return (
    <g transform={`translate(${s.x},${s.y}) rotate(${s.rotation || 0})`}>
      <text
        x={0} y={hasDim ? -dimGap / 2 : 0}
        fontFamily="Archivo Black, Helvetica, Arial Black, sans-serif"
        fontWeight="900"
        fontSize={size}
        fill={window.COLORS.ink}
        textAnchor="middle"
        dominantBaseline="middle"
        style={{ textTransform: 'uppercase', letterSpacing: '0.04em' }}
      >
        {s.text}
      </text>
      {hasDim && (
        <text
          x={0} y={dimGap / 2}
          fontFamily="Archivo Black, Helvetica, Arial Black, sans-serif"
          fontWeight="900"
          fontSize={dimSize}
          fill={window.COLORS.ink}
          textAnchor="middle"
          dominantBaseline="middle"
          style={{ textTransform: 'uppercase', letterSpacing: '0.04em' }}
        >
          {s.dim}
        </text>
      )}
      {selected && (
        <rect x={-2.4} y={-size} width={4.8} height={hasDim ? size * 2.2 : size * 1.4} fill="none" stroke={window.COLORS.blue} strokeWidth={1.5} vectorEffect="non-scaling-stroke" strokeDasharray="0.25 0.15" />
      )}
    </g>
  );
}

function formatFeetInches(ft) {
  const sign = ft < 0 ? '-' : '';
  const abs = Math.abs(ft);
  const wholeFt = Math.floor(abs);
  const inches = Math.round((abs - wholeFt) * 12);
  if (inches === 12) return `${sign}${wholeFt + 1}'-0"`;
  return `${sign}${wholeFt}'-${inches}"`;
}

function DimensionShape({ s, selected }) {
  const len = dist(s.a, s.b);
  const mid = { x: (s.a.x + s.b.x) / 2, y: (s.a.y + s.b.y) / 2 };
  const angle = Math.atan2(s.b.y - s.a.y, s.b.x - s.a.x) * 180 / Math.PI;
  const txt = formatFeetInches(len);
  return (
    <g>
      <line x1={s.a.x} y1={s.a.y} x2={s.b.x} y2={s.b.y} stroke={window.COLORS.ink} strokeWidth={0.8} vectorEffect="non-scaling-stroke" />
      {/* ticks */}
      <g transform={`translate(${s.a.x},${s.a.y}) rotate(${angle + 90})`}>
        <line x1={0} y1={-0.3} x2={0} y2={0.3} stroke={window.COLORS.ink} strokeWidth={1.2} vectorEffect="non-scaling-stroke" />
      </g>
      <g transform={`translate(${s.b.x},${s.b.y}) rotate(${angle + 90})`}>
        <line x1={0} y1={-0.3} x2={0} y2={0.3} stroke={window.COLORS.ink} strokeWidth={1.2} vectorEffect="non-scaling-stroke" />
      </g>
      <g transform={`translate(${mid.x},${mid.y}) rotate(${Math.abs(angle) > 90 ? angle + 180 : angle})`}>
        <rect x={-txt.length * 0.18} y={-0.32} width={txt.length * 0.36} height={0.64} fill={window.COLORS.paper} />
        <text x={0} y={0} textAnchor="middle" dominantBaseline="middle" fontFamily="JetBrains Mono, monospace" fontSize={0.5} fill={window.COLORS.ink}>{txt}</text>
      </g>
      {selected && <circle cx={mid.x} cy={mid.y} r={0.3} fill="none" stroke={window.COLORS.blue} strokeWidth={1.5} vectorEffect="non-scaling-stroke" strokeDasharray="0.2 0.15" />}
    </g>
  );
}

function FloorShape({ s, selected }) {
  const fillId = `floor-${s.fill || 'tile'}`;
  return (
    <g>
      <rect x={s.x} y={s.y} width={s.w} height={s.h} fill={`url(#${fillId})`} stroke="none" />
      {selected && <rect x={s.x} y={s.y} width={s.w} height={s.h} fill="none" stroke={window.COLORS.blue} strokeWidth={1.5} vectorEffect="non-scaling-stroke" strokeDasharray="0.3 0.2" />}
    </g>
  );
}

// Dotted circles around drag-grip locations on the active selection.
// Sized in screen pixels (~14px radius) so they stay legible at any zoom.
function SelectionGrips({ shape, scale }) {
  if (!shape) return null;
  const r = 14 / (scale || 22);
  const dash = (r * 0.32).toFixed(2) + ' ' + (r * 0.22).toFixed(2);
  const ring = (cx, cy, key) => (
    <g key={key}>
      <circle cx={cx} cy={cy} r={r} fill="none" stroke={window.COLORS.blue}
        strokeWidth={1.5} strokeDasharray={dash} vectorEffect="non-scaling-stroke" />
      <circle cx={cx} cy={cy} r={r * 0.25} fill={window.COLORS.yellow}
        stroke={window.COLORS.ink} strokeWidth={1} vectorEffect="non-scaling-stroke" />
    </g>
  );
  const rad = ((shape.rotation || 0) * Math.PI) / 180;
  switch (shape.type) {
    case 'wall':
      return (
        <g>
          {ring(shape.a.x, shape.a.y, 'a')}
          {ring(shape.b.x, shape.b.y, 'b')}
          {ring((shape.a.x + shape.b.x) / 2, (shape.a.y + shape.b.y) / 2, 'mid')}
        </g>
      );
    case 'door':
    case 'window': {
      const w = shape.width;
      const cx = Math.cos(rad), cy = Math.sin(rad);
      return (
        <g>
          {ring(shape.x, shape.y, 'a')}
          {ring(shape.x + cx * w / 2, shape.y + cy * w / 2, 'mid')}
          {ring(shape.x + cx * w, shape.y + cy * w, 'b')}
        </g>
      );
    }
    case 'dimension':
      return (
        <g>
          {ring(shape.a.x, shape.a.y, 'a')}
          {ring(shape.b.x, shape.b.y, 'b')}
        </g>
      );
    default:
      return null;
  }
}

function Shape({ s, selected }) {
  switch (s.type) {
    case 'floor':      return <FloorShape s={s} selected={selected} />;
    case 'wall':       return selected ? <WallShape s={s} selected={true} /> : null; // batched in WallsLayer
    case 'door':       return <DoorShape s={s} selected={selected} />;
    case 'window':     return <WindowShape s={s} selected={selected} />;
    case 'furniture':  return <FurnitureShape s={s} selected={selected} />;
    case 'label':      return <LabelShape s={s} selected={selected} />;
    case 'dimension':  return <DimensionShape s={s} selected={selected} />;
    case 'measure':    return <window.MeasureShape s={s} selected={selected} />;
    default: return null;
  }
}

// Pattern defs — registered once in canvas <defs>
function FloorPatterns() {
  return (
    <>
      {/* Diagonal tile — warm beige */}
      <pattern id="floor-tile" patternUnits="userSpaceOnUse" width={2} height={2} patternTransform="rotate(45)">
        <rect width={2} height={2} fill="#ead9b8" />
        <rect x={0} y={0} width={2} height={2} fill="none" stroke="#c8a872" strokeWidth={0.06} />
        <rect x={0.1} y={0.1} width={1.8} height={1.8} fill="#f0dfbe" />
      </pattern>
      {/* Square tile */}
      <pattern id="floor-tile-sq" patternUnits="userSpaceOnUse" width={1.5} height={1.5}>
        <rect width={1.5} height={1.5} fill="#f0dfbe" />
        <rect width={1.5} height={1.5} fill="none" stroke="#c8a872" strokeWidth={0.06} />
      </pattern>
      {/* Herringbone wood */}
      <pattern id="floor-wood-herringbone" patternUnits="userSpaceOnUse" width={3} height={3} patternTransform="rotate(45)">
        <rect width={3} height={3} fill="#c69968" />
        <rect x={0} y={0} width={1.5} height={3} fill="#b88858" stroke="#7a5a35" strokeWidth={0.08} />
        <rect x={1.5} y={0} width={1.5} height={3} fill="#d6a878" stroke="#7a5a35" strokeWidth={0.08} />
      </pattern>
      {/* Plank wood */}
      <pattern id="floor-wood" patternUnits="userSpaceOnUse" width={6} height={1}>
        <rect width={6} height={1} fill="#c69968" />
        <line x1={0} y1={0} x2={6} y2={0} stroke="#7a5a35" strokeWidth={0.08} />
        <line x1={3} y1={0} x2={3} y2={1} stroke="#7a5a35" strokeWidth={0.05} />
      </pattern>
      {/* Pool water — gradient + ripple */}
      <pattern id="floor-water" patternUnits="userSpaceOnUse" width={4} height={4}>
        <rect width={4} height={4} fill="#5fb3e3" />
        <path d="M 0 1 Q 1 0.5 2 1 T 4 1" fill="none" stroke="#9ed1ef" strokeWidth={0.1} />
        <path d="M 0 2.5 Q 1 2 2 2.5 T 4 2.5" fill="none" stroke="#9ed1ef" strokeWidth={0.08} />
        <path d="M 0 3.5 Q 1 3 2 3.5 T 4 3.5" fill="none" stroke="#9ed1ef" strokeWidth={0.1} />
      </pattern>
      {/* Grass / foliage */}
      <pattern id="floor-grass" patternUnits="userSpaceOnUse" width={1.5} height={1.5}>
        <rect width={1.5} height={1.5} fill="#6b9a4a" />
        <circle cx={0.4} cy={0.4} r={0.15} fill="#7eb058" />
        <circle cx={1.1} cy={0.9} r={0.12} fill="#5a8a3a" />
        <circle cx={0.7} cy={1.2} r={0.1} fill="#8bc068" />
      </pattern>
      {/* Diagonal patio pavers */}
      <pattern id="floor-pavers" patternUnits="userSpaceOnUse" width={2.5} height={2.5} patternTransform="rotate(45)">
        <rect width={2.5} height={2.5} fill="#d4c098" />
        <rect width={2.5} height={2.5} fill="none" stroke="#8a7050" strokeWidth={0.08} />
      </pattern>
      {/* Carpet */}
      <pattern id="floor-carpet" patternUnits="userSpaceOnUse" width={0.8} height={0.8}>
        <rect width={0.8} height={0.8} fill="#d4c4a8" />
        <circle cx={0.2} cy={0.2} r={0.04} fill="#a89070" />
        <circle cx={0.6} cy={0.5} r={0.04} fill="#a89070" />
        <circle cx={0.3} cy={0.7} r={0.04} fill="#a89070" />
      </pattern>
      {/* Concrete */}
      <pattern id="floor-concrete" patternUnits="userSpaceOnUse" width={3} height={3}>
        <rect width={3} height={3} fill="#bcbcb4" />
        <circle cx={0.5} cy={0.5} r={0.03} fill="#8c8c80" />
        <circle cx={2.2} cy={1.3} r={0.04} fill="#8c8c80" />
        <circle cx={1.5} cy={2.4} r={0.03} fill="#8c8c80" />
      </pattern>
    </>
  );
}

// ---- Walls rendered collectively for mitered joins ----
function WallsLayer({ walls }) {
  if (!walls.length || !window.FCRooms) return null;
  const groups = window.FCRooms.buildWallPaths(walls);
  return (
    <g>
      {groups.map((g, gi) => (
        <g key={gi}>
          {g.polylines.map((pl, pi) => {
            const d = pl.pts.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ') + (pl.closed ? ' Z' : '');
            return (
              <path key={pi} d={d}
                fill="none"
                stroke={window.COLORS.ink}
                strokeWidth={g.thickness}
                strokeLinejoin="miter"
                strokeMiterlimit={4}
                strokeLinecap="square"
              />
            );
          })}
        </g>
      ))}
    </g>
  );
}

// ---- Detected rooms ----
function RoomsLayer({ walls, roomFills = {}, showAreas = true }) {
  const data = useMemo(() => walls.length && window.FCRooms ? window.FCRooms.detectRooms(walls) : { rooms: [] }, [walls]);
  if (!data.rooms.length) return null;
  return (
    <g>
      {data.rooms.map((r, i) => {
        const d = r.pts.map((p, j) => `${j === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ') + ' Z';
        const fillKey = roomFills[i] || null;
        return (
          <g key={i}>
            {fillKey
              ? <path d={d} fill={`url(#${fillKey})`} fillOpacity={0.85} stroke="none" />
              : <path d={d} fill={window.COLORS.paper2} fillOpacity={0.35} stroke="none" />}
            {showAreas && (
              <g transform={`translate(${r.centroid.x},${r.centroid.y})`} pointerEvents="none">
                <rect x={-1.4} y={-0.3} width={2.8} height={0.6} fill={window.COLORS.paper} fillOpacity={0.75} />
                <text x={0} y={0} textAnchor="middle" dominantBaseline="middle"
                  fontFamily="JetBrains Mono, monospace" fontSize={0.42} fill={window.COLORS.ink4 || '#4d4538'}>
                  {Math.round(r.area)} sq ft
                </text>
              </g>
            )}
          </g>
        );
      })}
    </g>
  );
}

// ---- The big canvas ----

function FloorCanvas({
  shapes, setShapes, ghostShapes, tool, setTool, placingKind, setPlacingKind,
  selectedId, setSelectedId, view, setView, gridSize, showGrid, showDimensions, paperStyle,
  presentation, projectName, activeFloorName,
}) {
  const stageRef = useRef(null);
  const svgRef = useRef(null);
  const [cursor, setCursor] = useState({ x: 0, y: 0 });
  const [drawStart, setDrawStart] = useState(null); // world coords during drag-draw
  const [drawCurrent, setDrawCurrent] = useState(null);
  const [dragInfo, setDragInfo] = useState(null); // { id, offset }
  const [panning, setPanning] = useState(false);
  const [spaceHeld, setSpaceHeld] = useState(false);

  // Convert client pixel coords → world (feet)
  const toWorld = useCallback((clientX, clientY) => {
    const r = stageRef.current.getBoundingClientRect();
    const x = (clientX - r.left - view.tx) / view.scale;
    const y = (clientY - r.top - view.ty) / view.scale;
    return { x, y };
  }, [view]);

  const worldSnap = (p) => ({ x: snap(p.x, gridSize), y: snap(p.y, gridSize) });

  // Keyboard
  useEffect(() => {
    const onKey = (e) => {
      if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
      if (e.code === 'Space') { e.preventDefault(); setSpaceHeld(true); }
      if (e.key === 'Escape') { setSelectedId(null); setDrawStart(null); setPlacingKind(null); setTool('select'); }
      if ((e.key === 'Delete' || e.key === 'Backspace') && selectedId) {
        e.preventDefault();
        setShapes((arr) => arr.filter((x) => x.id !== selectedId));
        setSelectedId(null);
      }
      const keymap = { v: 'select', w: 'wall', r: 'room', d: 'door', n: 'window', t: 'label', m: 'dimension', h: 'pan', g: 'floor', p: 'measure', a: 'autodim' };
      if (keymap[e.key]) { setTool(keymap[e.key]); setPlacingKind(null); }
      if (e.key === 'r' && selectedId) {
        e.preventDefault();
        setShapes((arr) => arr.map((s) => s.id === selectedId ? { ...s, rotation: ((s.rotation || 0) + 90) % 360 } : s));
      }
    };
    const onUp = (e) => { if (e.code === 'Space') setSpaceHeld(false); };
    window.addEventListener('keydown', onKey);
    window.addEventListener('keyup', onUp);
    return () => { window.removeEventListener('keydown', onKey); window.removeEventListener('keyup', onUp); };
  }, [selectedId, setShapes, setSelectedId, setPlacingKind, setTool]);

  // Touch — pinch-to-zoom and 2-finger pan. Single-finger touches fall
  // through to React's onPointerDown (mouse-emulated) so the existing
  // select/draw logic keeps working unchanged.
  useEffect(() => {
    const el = stageRef.current;
    if (!el) return;
    let pinch = null; // { startScale, startDist, startTx, startTy, midX, midY }
    const dist = (t0, t1) => Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);
    const mid  = (t0, t1) => ({ x: (t0.clientX + t1.clientX) / 2, y: (t0.clientY + t1.clientY) / 2 });

    const onStart = (e) => {
      if (e.touches.length === 2) {
        e.preventDefault();
        const r = el.getBoundingClientRect();
        const m = mid(e.touches[0], e.touches[1]);
        pinch = {
          startScale: view.scale,
          startDist: dist(e.touches[0], e.touches[1]),
          startTx: view.tx,
          startTy: view.ty,
          midX: m.x - r.left,
          midY: m.y - r.top,
          // World coords at the initial mid-point. The same world point
          // should stay anchored under the fingers as scale changes.
          worldX: (m.x - r.left - view.tx) / view.scale,
          worldY: (m.y - r.top - view.ty) / view.scale,
        };
      }
    };
    const onMove = (e) => {
      if (e.touches.length === 2 && pinch) {
        e.preventDefault();
        const r = el.getBoundingClientRect();
        const d = dist(e.touches[0], e.touches[1]);
        const m = mid(e.touches[0], e.touches[1]);
        const ratio = d / Math.max(1, pinch.startDist);
        const nextScale = Math.max(4, Math.min(200, pinch.startScale * ratio));
        // Keep world point under finger mid stable, plus apply pan from
        // mid-point movement.
        const px = m.x - r.left;
        const py = m.y - r.top;
        const tx = px - pinch.worldX * nextScale;
        const ty = py - pinch.worldY * nextScale;
        setView({ scale: nextScale, tx, ty });
      }
    };
    const onEnd = (e) => {
      if (e.touches.length < 2) pinch = null;
    };
    el.addEventListener('touchstart', onStart, { passive: false });
    el.addEventListener('touchmove',  onMove,  { passive: false });
    el.addEventListener('touchend',   onEnd);
    el.addEventListener('touchcancel', onEnd);
    return () => {
      el.removeEventListener('touchstart', onStart);
      el.removeEventListener('touchmove',  onMove);
      el.removeEventListener('touchend',   onEnd);
      el.removeEventListener('touchcancel', onEnd);
    };
  }, [view, setView]);

  // Wheel zoom
  useEffect(() => {
    const el = stageRef.current;
    if (!el) return;
    const onWheel = (e) => {
      e.preventDefault();
      const r = el.getBoundingClientRect();
      const px = e.clientX - r.left, py = e.clientY - r.top;
      const wx = (px - view.tx) / view.scale;
      const wy = (py - view.ty) / view.scale;
      const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
      const newScale = Math.max(4, Math.min(120, view.scale * factor));
      const tx = px - wx * newScale;
      const ty = py - wy * newScale;
      setView({ scale: newScale, tx, ty });
    };
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => el.removeEventListener('wheel', onWheel);
  }, [view, setView]);

  // Mouse handlers
  const onPointerDown = (e) => {
    if (e.button === 1 || (e.button === 0 && (spaceHeld || tool === 'pan'))) {
      setPanning({ startX: e.clientX, startY: e.clientY, vx: view.tx, vy: view.ty });
      return;
    }
    if (e.button !== 0) return;
    const w = toWorld(e.clientX, e.clientY);
    const sw = worldSnap(w);

    if (tool === 'select') {
      // Hit-test top-most shape
      const id = hitTest(shapes, w);
      if (id) {
        setSelectedId(id);
        const s = shapes.find((x) => x.id === id);
        const ref = (s.type === 'wall' || s.type === 'dimension' || s.type === 'measure') ? s.a : { x: s.x, y: s.y };
        setDragInfo({ id, offset: { x: w.x - ref.x, y: w.y - ref.y } });
      } else {
        setSelectedId(null);
      }
      return;
    }
    if (tool === 'autodim') {
      // Click two parallel walls → stacked perpendicular dimension chain.
      const w = toWorld(e.clientX, e.clientY);
      const walls = shapes.filter((s) => s.type === 'wall');
      let best = null, bestD = Infinity;
      for (const wl of walls) {
        const A = wl.a, B = wl.b;
        const ABx = B.x - A.x, ABy = B.y - A.y;
        const len2 = ABx * ABx + ABy * ABy;
        const tt = Math.max(0, Math.min(1, ((w.x - A.x) * ABx + (w.y - A.y) * ABy) / len2));
        const cx = A.x + tt * ABx, cy = A.y + tt * ABy;
        const d = Math.hypot(w.x - cx, w.y - cy);
        if (d < bestD) { bestD = d; best = wl; }
      }
      if (!best || bestD > 4) return;
      window.__autodimPick = window.__autodimPick || null;
      if (!window.__autodimPick) {
        window.__autodimPick = best.id;
        setSelectedId(best.id);
        return;
      }
      // second click — pair with first
      const firstId = window.__autodimPick;
      window.__autodimPick = null;
      setSelectedId(null);
      const first = shapes.find((x) => x.id === firstId);
      if (!first || first.id === best.id) return;
      // angle between
      const a1 = Math.atan2(first.b.y - first.a.y, first.b.x - first.a.x);
      const a2 = Math.atan2(best.b.y - best.a.y, best.b.x - best.a.x);
      const par = Math.abs(Math.sin(a1 - a2)) < 0.18 || Math.abs(Math.sin(a1 - a2 + Math.PI)) < 0.18;
      if (!par) { alert('Auto-dim needs two parallel walls.'); return; }
      // perpendicular foot from midpoint of first onto line of second
      const mid1 = { x: (first.a.x + first.b.x) / 2, y: (first.a.y + first.b.y) / 2 };
      const A = best.a, B = best.b;
      const ABx = B.x - A.x, ABy = B.y - A.y;
      const len2 = ABx * ABx + ABy * ABy;
      const tt = ((mid1.x - A.x) * ABx + (mid1.y - A.y) * ABy) / len2;
      const foot = { x: A.x + tt * ABx, y: A.y + tt * ABy };
      setShapes((arr) => [...arr, { id: uid(), type: 'dimension', a: mid1, b: foot }]);
      return;
    }
    if (tool === 'wall' || tool === 'room' || tool === 'dimension' || tool === 'floor' || tool === 'measure') {
      setDrawStart(sw); setDrawCurrent(sw);
      return;
    }
    if (tool === 'door' || tool === 'window') {
      // place on nearest wall
      const placed = placeOnWall(shapes, w, tool, tool === 'door' ? 3 : 3);
      if (placed) {
        setShapes((arr) => [...arr, placed]);
        setSelectedId(placed.id);
      }
      return;
    }
    if (tool === 'furniture' && placingKind) {
      const f = window.FURNITURE_MAP[placingKind];
      const id = uid();
      const newShape = { id, type: 'furniture', kind: placingKind, x: sw.x, y: sw.y, w: f.w, h: f.h, rotation: 0 };
      setShapes((arr) => [...arr, newShape]);
      setSelectedId(id);
      return;
    }
    if (tool === 'label') {
      const text = prompt('Room name', 'LIVING');
      if (!text) return;
      const dim = prompt("Dimensions (e.g. 12'-0\" X 12'-0\")", '');
      const id = uid();
      setShapes((arr) => [...arr, { id, type: 'label', x: sw.x, y: sw.y, text: text.toUpperCase(), dim: dim || '', size: 0.85, rotation: 0 }]);
      setSelectedId(id);
      return;
    }
  };

  const onPointerMove = (e) => {
    const w = toWorld(e.clientX, e.clientY);
    setCursor(w);
    if (panning) {
      setView({ ...view, tx: panning.vx + (e.clientX - panning.startX), ty: panning.vy + (e.clientY - panning.startY) });
      return;
    }
    const sw = worldSnap(w);
    if (drawStart) setDrawCurrent(sw);
    if (dragInfo) {
      setShapes((arr) => arr.map((s) => {
        if (s.id !== dragInfo.id) return s;
        const nx = snap(w.x - dragInfo.offset.x, gridSize);
        const ny = snap(w.y - dragInfo.offset.y, gridSize);
        if (s.type === 'wall' || s.type === 'dimension' || s.type === 'measure') {
          const dx = nx - s.a.x, dy = ny - s.a.y;
          return { ...s, a: { x: nx, y: ny }, b: { x: s.b.x + dx, y: s.b.y + dy } };
        }
        if (s.type === 'floor') {
          return { ...s, x: nx, y: ny };
        }
        return { ...s, x: nx, y: ny };
      }));
    }
  };

  const onPointerUp = (e) => {
    if (panning) { setPanning(false); return; }
    if (drawStart && drawCurrent) {
      const a = drawStart, b = drawCurrent;
      if (tool === 'wall' && dist(a, b) > 0.5) {
        const tol = (window.FCRooms && window.FCRooms.SNAP_TOL) || 0.4;
        setShapes((arr) => {
          const pts = [];
          for (const sh of arr) { if (sh.type === 'wall') { pts.push(sh.a); pts.push(sh.b); } }
          const snapEnd = (p) => {
            let best = null, bestD = tol;
            for (const ep of pts) {
              const d = Math.hypot(ep.x - p.x, ep.y - p.y);
              if (d < bestD) { bestD = d; best = ep; }
            }
            return best ? { x: best.x, y: best.y } : p;
          };
          return [...arr, { id: uid(), type: 'wall', a: snapEnd(a), b: snapEnd(b), thickness: 0.5 }];
        });
      } else if (tool === 'room' && Math.abs(b.x - a.x) > 1 && Math.abs(b.y - a.y) > 1) {
        const x1 = Math.min(a.x, b.x), y1 = Math.min(a.y, b.y);
        const x2 = Math.max(a.x, b.x), y2 = Math.max(a.y, b.y);
        const t = 0.5;
        setShapes((arr) => [
          ...arr,
          { id: uid(), type: 'wall', a: { x: x1, y: y1 }, b: { x: x2, y: y1 }, thickness: t },
          { id: uid(), type: 'wall', a: { x: x2, y: y1 }, b: { x: x2, y: y2 }, thickness: t },
          { id: uid(), type: 'wall', a: { x: x2, y: y2 }, b: { x: x1, y: y2 }, thickness: t },
          { id: uid(), type: 'wall', a: { x: x1, y: y2 }, b: { x: x1, y: y1 }, thickness: t },
        ]);
      } else if (tool === 'dimension' && dist(a, b) > 0.3) {
        setShapes((arr) => [...arr, { id: uid(), type: 'dimension', a, b }]);
      } else if (tool === 'measure' && dist(a, b) > 0.3) {
        setShapes((arr) => [...arr, { id: uid(), type: 'measure', a, b }]);
      } else if (tool === 'floor' && Math.abs(b.x - a.x) > 0.5 && Math.abs(b.y - a.y) > 0.5) {
        const x1 = Math.min(a.x, b.x), y1 = Math.min(a.y, b.y);
        const x2 = Math.max(a.x, b.x), y2 = Math.max(a.y, b.y);
        const id = uid();
        const fill = placingKind || 'tile';
        // insert at index 0 so floors render under walls/furniture
        setShapes((arr) => [{ id, type: 'floor', x: x1, y: y1, w: x2 - x1, h: y2 - y1, fill }, ...arr]);
        setSelectedId(id);
      }
    }
    setDrawStart(null); setDrawCurrent(null);
    setDragInfo(null);
  };

  // Ghost preview shape while drawing
  const ghost = useMemo(() => {
    if (!drawStart || !drawCurrent) return null;
    if (tool === 'wall') return <WallShape s={{ a: drawStart, b: drawCurrent, thickness: 0.5 }} />;
    if (tool === 'room') {
      const x1 = Math.min(drawStart.x, drawCurrent.x), y1 = Math.min(drawStart.y, drawCurrent.y);
      const x2 = Math.max(drawStart.x, drawCurrent.x), y2 = Math.max(drawStart.y, drawCurrent.y);
      return (
        <g className="ghost">
          <rect x={x1} y={y1} width={x2 - x1} height={y2 - y1} fill={window.COLORS.yellow} fillOpacity={0.18} stroke={window.COLORS.blue} strokeWidth={1.2} vectorEffect="non-scaling-stroke" strokeDasharray="0.3 0.2" />
          <text x={(x1 + x2) / 2} y={(y1 + y2) / 2} textAnchor="middle" dominantBaseline="middle" fontFamily="JetBrains Mono, monospace" fontSize={0.5} fill={window.COLORS.ink}>{(x2 - x1).toFixed(1)}' × {(y2 - y1).toFixed(1)}'</text>
        </g>
      );
    }
    if (tool === 'dimension') return <DimensionShape s={{ a: drawStart, b: drawCurrent }} />;
    if (tool === 'measure') return <window.MeasureShape s={{ a: drawStart, b: drawCurrent }} />;
    if (tool === 'floor') {
      const x1 = Math.min(drawStart.x, drawCurrent.x), y1 = Math.min(drawStart.y, drawCurrent.y);
      const x2 = Math.max(drawStart.x, drawCurrent.x), y2 = Math.max(drawStart.y, drawCurrent.y);
      const fillId = `floor-${placingKind || 'tile'}`;
      return (
        <g className="ghost">
          <rect x={x1} y={y1} width={x2 - x1} height={y2 - y1} fill={`url(#${fillId})`} opacity={0.7} stroke={window.COLORS.blue} strokeWidth={1.2} vectorEffect="non-scaling-stroke" strokeDasharray="0.3 0.2" />
        </g>
      );
    }
    return null;
  }, [drawStart, drawCurrent, tool, placingKind]);

  // Auto-dimensions for walls
  const wallDims = useMemo(() => {
    if (!showDimensions) return null;
    return shapes.filter((s) => s.type === 'wall').map((s) => {
      const len = dist(s.a, s.b);
      if (len < 1.5) return null;
      const mid = { x: (s.a.x + s.b.x) / 2, y: (s.a.y + s.b.y) / 2 };
      const angle = Math.atan2(s.b.y - s.a.y, s.b.x - s.a.x) * 180 / Math.PI;
      const norm = Math.abs(angle) > 90 ? angle + 180 : angle;
      const txt = formatFeetInches(len);
      return (
        <g key={'wd' + s.id} transform={`translate(${mid.x},${mid.y}) rotate(${norm})`}>
          <rect x={-txt.length * 0.13} y={-0.22} width={txt.length * 0.26} height={0.44} fill={window.COLORS.paper} />
          <text x={0} y={0} textAnchor="middle" dominantBaseline="middle" fontFamily="JetBrains Mono, monospace" fontSize={0.35} fill={window.COLORS.ink5 || '#4d4538'}>{txt}</text>
        </g>
      );
    });
  }, [shapes, showDimensions]);

  // Viewport / grid
  const r = stageRef.current ? stageRef.current.getBoundingClientRect() : { width: 1000, height: 700 };
  const viewBoxMinX = -view.tx / view.scale;
  const viewBoxMinY = -view.ty / view.scale;
  const viewBoxW = r.width / view.scale;
  const viewBoxH = r.height / view.scale;

  const cursorReadout = `${cursor.x.toFixed(2)}' , ${cursor.y.toFixed(2)}'`;

  return (
    <div
      ref={stageRef}
      className="stage"
      data-tool={spaceHeld ? 'pan' : tool}
      data-panning={!!panning}
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      onPointerUp={onPointerUp}
      onPointerLeave={onPointerUp}
    >
      <svg ref={svgRef} viewBox={`${viewBoxMinX} ${viewBoxMinY} ${viewBoxW} ${viewBoxH}`} preserveAspectRatio="xMidYMid meet">
        <defs>
          <FloorPatterns />
          <pattern id="grid-minor" x="0" y="0" width={gridSize} height={gridSize} patternUnits="userSpaceOnUse">
            <path d={`M ${gridSize} 0 L 0 0 0 ${gridSize}`} fill="none" stroke={paperStyle === 'blueprint' ? 'rgba(255,255,255,0.18)' : '#c8b99c'} strokeWidth={0.4} vectorEffect="non-scaling-stroke" />
          </pattern>
          <pattern id="grid-major" x="0" y="0" width={gridSize * 5} height={gridSize * 5} patternUnits="userSpaceOnUse">
            <rect width={gridSize * 5} height={gridSize * 5} fill="url(#grid-minor)" />
            <path d={`M ${gridSize * 5} 0 L 0 0 0 ${gridSize * 5}`} fill="none" stroke={paperStyle === 'blueprint' ? 'rgba(255,255,255,0.4)' : '#8a7d65'} strokeWidth={0.8} vectorEffect="non-scaling-stroke" />
          </pattern>
          <pattern id="dots" x="0" y="0" width={gridSize} height={gridSize} patternUnits="userSpaceOnUse">
            <circle cx={gridSize / 2} cy={gridSize / 2} r={0.05} fill="#8a7d65" />
          </pattern>
        </defs>
        {/* paper bg — defers to the current viz-style --paper unless user overrides */}
        <rect x={viewBoxMinX} y={viewBoxMinY} width={viewBoxW} height={viewBoxH} fill={paperStyle === 'blueprint' ? '#0a2347' : paperStyle === 'white' ? '#fafafa' : paperStyle === 'dots' ? 'var(--paper)' : 'var(--paper)'} />
        {showGrid && (
          <rect x={viewBoxMinX} y={viewBoxMinY} width={viewBoxW} height={viewBoxH} fill={paperStyle === 'dots' ? 'url(#dots)' : 'url(#grid-major)'} />
        )}
        {/* origin marks */}
        <line x1={-2} y1={0} x2={2} y2={0} stroke={paperStyle === 'blueprint' ? 'rgba(255,255,255,0.6)' : window.COLORS.ink} strokeWidth={0.6} vectorEffect="non-scaling-stroke" />
        <line x1={0} y1={-2} x2={0} y2={2} stroke={paperStyle === 'blueprint' ? 'rgba(255,255,255,0.6)' : window.COLORS.ink} strokeWidth={0.6} vectorEffect="non-scaling-stroke" />
        {/* ghost (other floor) underlay */}
        {ghostShapes && ghostShapes.length > 0 && (
          <g style={{ opacity: 0.22, pointerEvents: 'none' }}>
            {ghostShapes.map((s) => <Shape key={'ghost-' + s.id} s={s} selected={false} />)}
          </g>
        )}
        {/* rooms (under furniture) */}
        <RoomsLayer walls={shapes.filter((s) => s.type === 'wall')} />
        {/* floors first (under everything) */}
        {shapes.filter((s) => s.type === 'floor').map((s) => <Shape key={s.id} s={s} selected={s.id === selectedId} />)}
        {/* walls next — doors/windows will paint over them with paper-fill openings */}
        <WallsLayer walls={shapes.filter((s) => s.type === 'wall')} />
        {/* selected wall outline drawn on top */}
        {shapes.filter((s) => s.type === 'wall' && s.id === selectedId).map((s) => <Shape key={s.id} s={s} selected={true} />)}
        {/* doors + windows next — their paper-filled rect clears the wall opening */}
        {shapes.filter((s) => s.type === 'door' || s.type === 'window').map((s) => <Shape key={s.id} s={s} selected={s.id === selectedId} />)}
        {/* everything else on top */}
        {shapes.filter((s) => !['floor','wall','door','window'].includes(s.type)).map((s) => <Shape key={s.id} s={s} selected={s.id === selectedId} />)}
        {wallDims}
        {ghost}
        {/* Drag-grip rings around the active selection */}
        {selectedId && shapes.find((s) => s.id === selectedId) && (
          <SelectionGrips shape={shapes.find((s) => s.id === selectedId)} scale={view.scale} />
        )}
        {/* crosshair when drawing */}
        {(tool === 'wall' || tool === 'room' || tool === 'dimension') && !drawStart && (
          <g>
            <circle cx={snap(cursor.x, gridSize)} cy={snap(cursor.y, gridSize)} r={0.2} fill={window.COLORS.yellow} stroke={window.COLORS.ink} strokeWidth={1} vectorEffect="non-scaling-stroke" />
          </g>
        )}
      </svg>

      {presentation && (
        <div className="title-block-overlay">
          <div className="tb-row">
            <div className="tb-cell tb-title">
              <div className="tb-lbl">project</div>
              <div className="tb-val">{projectName || 'Untitled Plan'}</div>
            </div>
            <div className="tb-cell">
              <div className="tb-lbl">sheet</div>
              <div className="tb-val">{activeFloorName || 'A-101'}</div>
            </div>
            <div className="tb-cell">
              <div className="tb-lbl">scale</div>
              <div className="tb-val">1/4" = 1'</div>
            </div>
            <div className="tb-cell">
              <div className="tb-lbl">date</div>
              <div className="tb-val">{new Date().toISOString().slice(0, 10)}</div>
            </div>
            <div className="tb-cell tb-north">
              <svg viewBox="0 0 40 40" width="36" height="36">
                <circle cx="20" cy="20" r="18" fill="none" stroke="var(--ink)" strokeWidth="1.5" />
                <polygon points="20,4 25,22 20,18 15,22" fill="var(--ink)" />
                <text x="20" y="34" textAnchor="middle" fontFamily="Archivo Black, sans-serif" fontSize="9" fill="var(--ink)">N</text>
              </svg>
            </div>
          </div>
        </div>
      )}
      {!presentation && (<>
      <div className="crumb">
        <span className="lbl">tool</span>
        <span className="v t-mono">{placingKind || tool}</span>
        <span className="sep">·</span>
        <span className="lbl">xy</span>
        <span className="v t-mono">{cursorReadout}</span>
        <span className="sep">·</span>
        <span className="lbl">zoom</span>
        <span className="v t-mono">{(view.scale).toFixed(0)}</span>
      </div>

      <div className="legend">
        <div className="ttl">shortcuts</div>
        <div className="row"><kbd>V</kbd> select <kbd>W</kbd> wall <kbd>R</kbd> room</div>
        <div className="row"><kbd>D</kbd> door <kbd>N</kbd> window <kbd>T</kbd> label</div>
        <div className="row"><kbd>Space</kbd> pan <kbd>⌫</kbd> delete <kbd>Esc</kbd> cancel</div>
      </div>

      <div className="fab">
        <button title="Zoom in" onClick={() => setView((v) => ({ ...v, scale: Math.min(120, v.scale * 1.2) }))}>+</button>
        <button title="Zoom out" onClick={() => setView((v) => ({ ...v, scale: Math.max(4, v.scale / 1.2) }))}>−</button>
        <button title="Fit" onClick={() => setView({ scale: 28, tx: r.width / 2, ty: r.height / 2 })}>⊡</button>
      </div>
      </>)}
    </div>
  );
}

function uid() { return Math.random().toString(36).slice(2, 9); }

function hitTest(shapes, p) {
  // top-down hit test (last drawn first)
  for (let i = shapes.length - 1; i >= 0; i--) {
    const s = shapes[i];
    if (s.type === 'furniture' || s.type === 'door' || s.type === 'window' || s.type === 'label') {
      const w = s.w || (window.FURNITURE_MAP[s.kind]?.w || 2);
      const h = s.h || (window.FURNITURE_MAP[s.kind]?.h || 2);
      const lx = p.x - s.x, ly = p.y - s.y;
      if (s.type === 'label') {
        if (Math.abs(lx) < 1.5 && Math.abs(ly) < 0.5) return s.id;
      } else if (s.type === 'door' || s.type === 'window') {
        if (lx > -0.3 && lx < (s.width || 3) + 0.3 && Math.abs(ly) < 0.5) return s.id;
      } else {
        if (lx >= 0 && lx <= w && ly >= 0 && ly <= h) return s.id;
      }
    } else if (s.type === 'floor') {
      if (p.x >= s.x && p.x <= s.x + s.w && p.y >= s.y && p.y <= s.y + s.h) return s.id;
    } else if (s.type === 'wall' || s.type === 'dimension' || s.type === 'measure') {
      // distance to segment
      const t = s.thickness || 0.6;
      const A = s.a, B = s.b;
      const ABx = B.x - A.x, ABy = B.y - A.y;
      const len2 = ABx * ABx + ABy * ABy;
      const tt = Math.max(0, Math.min(1, ((p.x - A.x) * ABx + (p.y - A.y) * ABy) / len2));
      const cx = A.x + tt * ABx, cy = A.y + tt * ABy;
      if (Math.hypot(p.x - cx, p.y - cy) < t) return s.id;
    }
  }
  return null;
}

function placeOnWall(shapes, p, type, width) {
  // find closest wall
  let best = null, bestD = Infinity;
  for (const s of shapes) {
    if (s.type !== 'wall') continue;
    const A = s.a, B = s.b;
    const ABx = B.x - A.x, ABy = B.y - A.y;
    const len2 = ABx * ABx + ABy * ABy;
    const tt = Math.max(0, Math.min(1, ((p.x - A.x) * ABx + (p.y - A.y) * ABy) / len2));
    const cx = A.x + tt * ABx, cy = A.y + tt * ABy;
    const d = Math.hypot(p.x - cx, p.y - cy);
    if (d < bestD) { bestD = d; best = { s, cx, cy, tt }; }
  }
  if (!best || bestD > 3) return null;
  const angle = Math.atan2(best.s.b.y - best.s.a.y, best.s.b.x - best.s.a.x) * 180 / Math.PI;
  return { id: uid(), type, x: best.cx - Math.cos(angle * Math.PI / 180) * width / 2, y: best.cy - Math.sin(angle * Math.PI / 180) * width / 2, width, rotation: angle };
}

Object.assign(window, { FloorCanvas, uid });
