// ExternalBlock.jsx — "external nodes" connected to the Harper canvas.
//
// External blocks represent systems that live OUTSIDE the canvas volume.
// They sit 3 world units away from the canvas walls, on the same z=0 floor
// plane (no pedestal of their own — they hover on the void), and are
// connected back to the canvas pedestal center by a thin warm-gray
// connector line.
//
// Sides (named for the two posts they sit between, looking at the iso view):
//
//                       BACK post (gx=0, gy=0)        ← top of screen
//                       /              \
//             back-left              back-right
//             (between BACK & LEFT)   (between BACK & RIGHT)
//                /                            \
//        LEFT post (0,D)                    RIGHT post (W,0)
//                \                            /
//             front-left              front-right
//             (between LEFT & FRONT)  (between RIGHT & FRONT)
//                       \              /
//                       FRONT post (W, D)              ← bottom of screen
//
// Each side has a "perpendicular axis" (the axis the block extrudes along
// from the canvas) and a "tangent axis" (the wall it's centered on):
//
//   side          wall         perpAxis  tangent     centerLine
//   ----          ----         --------  -------     ----------
//   back-left     x = 0        -x        gy in [0,D] gx = -OFFSET
//   back-right    y = 0        -y        gx in [0,W] gy = -OFFSET
//   front-right   x = W        +x        gy in [0,D] gx = W+OFFSET
//   front-left    y = D        +y        gx in [0,W] gy = D+OFFSET
//
// EXT_OFFSET is the gap (world units) between the canvas wall and the
// near-face of the external block.

const EXT_OFFSET = 3;
const EXT_SIDES = ['back-left', 'back-right', 'front-left', 'front-right'];
const EXT_SIZES = [
  { dx: 1, dy: 1, dz: 1 },
  { dx: 1, dy: 2, dz: 1 },
  { dx: 2, dy: 1, dz: 1 },
  { dx: 1, dy: 2, dz: 2 },
  { dx: 2, dy: 1, dz: 2 },
];

// Compute the world-space (gx, gy, gz) origin (=near-bottom corner) of an
// external block on `side` of a canvas with footprint W × D, centered on
// that side's wall midpoint.
//
// For two-block sides, `slotIndex` ∈ {0, 1} offsets the block along the
// tangent axis so the two blocks sit side-by-side without overlap.
function externalOrigin(side, dx, dy, W, D, slotIndex = 0, slotCount = 1) {
  // Block extents along the wall's tangent axis (the dimension that runs
  // PARALLEL to the wall, i.e. the dim that affects centering).
  // For sides facing -x or +x  (back-left, front-right) → tangent is y, tangentDim = dy
  // For sides facing -y or +y  (back-right, front-left) → tangent is x, tangentDim = dx
  const tangentDim = (side === 'back-left' || side === 'front-right') ? dy : dx;
  const wallLen    = (side === 'back-left' || side === 'front-right') ? D : W;

  // Tangent center: midpoint of the wall.
  let tangentCenter = wallLen / 2;

  // For two blocks on a side, push them apart along the tangent.
  // We pack them at integer grid positions using the SUM of their
  // tangentDims so they don't overlap, with a small visual gap of 0.
  // slotIndex=0 → "first" block (toward smaller tangent coord)
  // slotIndex=1 → "second" block (toward larger tangent coord)
  if (slotCount === 2) {
    // Each block occupies tangentDim along the wall. Assume the OTHER
    // block on this side has the same tangentDim (we don't have it here;
    // worst-case we use this block's own tangentDim for spacing — this
    // works when both blocks share the same dim, and is a reasonable
    // approximation otherwise).
    const half = tangentDim / 2 + 0.5; // 0.5u gap between the two blocks
    tangentCenter = wallLen / 2 + (slotIndex === 0 ? -half : half);
  }

  // Origin in tangent direction (subtract half the dim so the block
  // CENTERS on tangentCenter).
  const tangentOrigin = tangentCenter - tangentDim / 2;

  let gx, gy;
  switch (side) {
    case 'back-left':
      // Wall x=0; perp = -x. Block lives at gx ∈ [-OFFSET-dx, -OFFSET].
      gx = -EXT_OFFSET - dx;
      gy = tangentOrigin;
      break;
    case 'back-right':
      gx = tangentOrigin;
      gy = -EXT_OFFSET - dy;
      break;
    case 'front-right':
      gx = W + EXT_OFFSET;
      gy = tangentOrigin;
      break;
    case 'front-left':
      gx = tangentOrigin;
      gy = D + EXT_OFFSET;
      break;
    default:
      gx = 0; gy = 0;
  }

  return { gx, gy, gz: 0 };
}

// Compute the connector polyline (in world coords, list of {x,y,z} points)
// from the canvas pedestal center to a single external block's near face.
// The line:
//   1. Starts at the canvas pedestal center (gx=W/2, gy=D/2, gz=0).
//   2. Exits the canvas through the appropriate wall midpoint.
//   3. Travels straight along the perpendicular axis to the external block.
//   4. Lands on the near face center of the external block (at z = dz/2).
//
// For two blocks on the same side, the caller supplies a "branch point"
// instead of computing it directly — see `connectorsForSide`.
function _exitPoint(side, W, D) {
  switch (side) {
    case 'back-left':   return { x: 0,   y: D / 2, z: 0 };
    case 'back-right':  return { x: W/2, y: 0,     z: 0 };
    case 'front-right': return { x: W,   y: D / 2, z: 0 };
    case 'front-left':  return { x: W/2, y: D,     z: 0 };
    default:            return { x: W/2, y: D/2,   z: 0 };
  }
}

// "Trunk end": the point along the side's perpendicular axis where the
// trunk meets the elbow cross-bar. For TWO blocks, this is the perp
// distance at which the trunk turns 90° to run parallel to the wall.
// We place it halfway between the canvas wall and the external blocks'
// near faces, so the elbow reads as a clean cable run.
function _trunkElbowDistFromWall() {
  return EXT_OFFSET / 2; // halfway between wall and near-face
}

// Near-face center (in world coords) of an external block — the point the
// connector terminates at. Returns the BOTTOM-CENTER of the face on the
// side facing the canvas (z = floor of block, not vertical center) so the
// line lands at the foot of the block's canvas-facing face.
//
// The rendered ExternalBlock applies a 0.08 PAD inset on every axis (see
// ExternalBlock.jsx). The terminus must include that inset on the
// canvas-facing axis or the connector ends short of the block by PAD
// world units, leaving a small visible gap.
function _nearFaceCenter(side, ext, W, D) {
  const PAD = 0.08; // must match ExternalBlock's inset
  const { gx, gy, gz } = externalOrigin(side, ext.dx, ext.dy, W, D, ext._slotIndex ?? 0, ext._slotCount ?? 1);
  const z = gz + PAD; // bottom of inset face
  switch (side) {
    case 'back-left':   return { x: gx + ext.dx - PAD, y: gy + ext.dy / 2,   z }; // canvas-facing face: x = gx+dx (minus inset)
    case 'back-right':  return { x: gx + ext.dx / 2,   y: gy + ext.dy - PAD, z };
    case 'front-right': return { x: gx + PAD,          y: gy + ext.dy / 2,   z };
    case 'front-left':  return { x: gx + ext.dx / 2,   y: gy + PAD,          z };
    default:            return { x: gx + ext.dx / 2,   y: gy + ext.dy / 2,   z };
  }
}

// Connector start point — the BOTTOM-FRONT edge of the pedestal on the
// side that faces the external block. This puts the line's origin where
// the user can see it (exiting just below the pedestal at the wall's
// midpoint), rather than buried inside the canvas at center.
//
// We sit at the wall midpoint (tangent), at perpendicular = wall plane
// (so it's exactly at the canvas wall, not floating in space), and at
// z = -PEDESTAL (the pedestal underside).
function _pedestalAnchor(side, W, D) {
  const PED = 0.16;
  switch (side) {
    case 'back-left':   return { x: 0,   y: D / 2, z: -PED };
    case 'back-right':  return { x: W/2, y: 0,     z: -PED };
    case 'front-right': return { x: W,   y: D / 2, z: -PED };
    case 'front-left':  return { x: W/2, y: D,     z: -PED };
    default:            return { x: W/2, y: D/2,   z: -PED };
  }
}

// For a given side, project a world-space point that is `dist` units past
// the canvas wall along the side's perpendicular axis. Used for both the
// trunk endpoint (elbow) and any block-side intermediate stops.
//   side='back-right' (perp = -y): point is (W/2-default tangent, -dist, z)
// We accept the tangent value as a parameter so the caller can place
// points anywhere along the cross-bar.
function _perpProjectedPoint(side, distFromWall, tangent, z, W, D) {
  switch (side) {
    case 'back-left':   return { x: -distFromWall,    y: tangent,         z };
    case 'back-right':  return { x: tangent,          y: -distFromWall,   z };
    case 'front-right': return { x: W + distFromWall, y: tangent,         z };
    case 'front-left':  return { x: tangent,          y: D + distFromWall, z };
    default:            return { x: tangent, y: distFromWall, z };
  }
}

// Tangent value (along the wall) of the canvas exit / wall midpoint.
function _wallMidTangent(side, W, D) {
  return (side === 'back-left' || side === 'front-right') ? D / 2 : W / 2;
}

// Tangent value (along the wall) of an external block's near-face center —
// i.e. the block's tangent-axis center.
function _blockTangent(side, ext, W, D) {
  const face = _nearFaceCenter(side, ext, W, D);
  return (side === 'back-left' || side === 'front-right') ? face.y : face.x;
}

// Given all externals on a single side, build the world-space polylines
// for the connector. Returns an array of polylines (each a list of
// {x,y,z} points). The renderer just draws each polyline.
//
//   1 block on side  →  ONE straight polyline: pedestal-center → near-face
//                       (collinear; no bends — the block is centered on
//                       the wall and the trunk runs straight through).
//
//   2 blocks on side →  An H-shape made of THREE polylines:
//
//                  trunk        cross-bar
//        canvas ───────●─────────────●           block 1 face
//                              │       \
//                              │        ────●  block 1 center stub
//                              │
//                              ●        ────●  block 2 center stub
//                                     /
//                                    ●
//
//       In words:
//         a) "trunk":  pedestal-center → elbow point (perp distance
//            EXT_OFFSET/2 past the wall, at wall tangent midpoint).
//         b) "cross-bar": runs perpendicular to the trunk along the wall
//            tangent, from above one block's tangent-center to above the
//            other.
//         c) "stubs" (one per block): from the cross-bar at that block's
//            tangent-center, parallel to the original trunk again, to the
//            block's near-face center.
function connectorsForSide(side, externalsOnSide, W, D) {
  if (!externalsOnSide.length) return [];
  const start = _pedestalAnchor(side, W, D);
  // "Lift" point: same wall position as the anchor, but at z=0 so the
  // subsequent perpendicular run is grid-parallel. Without this, the
  // start→elbow segment slants diagonally from z=-PED to z=0 and reads
  // as off-axis in iso. With it, the path drops straight down (anchor →
  // lift) and then runs purely perpendicular along grid lines.
  const lift = { x: start.x, y: start.y, z: 0 };

  if (externalsOnSide.length === 1) {
    // anchor → lift (vertical drop) → near-face (perpendicular run).
    // Block is centered on wall mid so lift and face share a tangent
    // value; the segment is axis-aligned along the perp axis.
    const face = _nearFaceCenter(side, externalsOnSide[0], W, D);
    return [[start, lift, face]];
  }

  // Two blocks: shared TRUNK from the pedestal anchor to a SPLIT POINT
  // sitting 1 world unit short of the blocks' near faces, then each branch
  // diverges tangentially along the wall to its own block's tangent center
  // and finally turns 90° to land on the near-face-bottom.
  //
  //                      shared trunk             branch tangent
  //   anchor ─────────────────────────●─────────────────●
  //   (wall mid, z=-PED)              │                  \
  //                                   │                   ● near face (block 1)
  //                                   │
  //                                   ●─────────────●
  //                                                  \
  //                                                   ● near face (block 2)
  //
  //   • elbow:        perp = EXT_OFFSET - 1, tangent = wall midpoint, z=0
  //   • branch slide: perp = EXT_OFFSET - 1, tangent = block-center,  z=0
  //   • near face:    perp = EXT_OFFSET,     tangent = block-center,  z=0
  //
  // Each branch returns 4 points; the shared start→elbow segment overdraws
  // cleanly since both branches use identical points there.
  const elbowDist = EXT_OFFSET - 1;          // 1u from the external blocks
  const wallMid   = _wallMidTangent(side, W, D);
  const elbow     = _perpProjectedPoint(side, elbowDist, wallMid, 0, W, D);

  const buildBranch = (ext) => {
    const t = _blockTangent(side, ext, W, D);
    const face = _nearFaceCenter(side, ext, W, D);
    // Slide point: at the elbow's perp distance, but at the block's tangent.
    const slide = _perpProjectedPoint(side, elbowDist, t, 0, W, D);
    return [start, lift, elbow, slide, face];
  };

  return [buildBranch(externalsOnSide[0]), buildBranch(externalsOnSide[1])];
}

// Render a single external block. `block` is { id, color, label, dx, dy, dz,
// side, _slotIndex, _slotCount }. We compute its origin internally so the
// caller doesn't need to.
function ExternalBlock({ block, W, D, selected, onSelect }) {
  const PAD = 0.08;
  const slot = block._slotIndex ?? 0;
  const count = block._slotCount ?? 1;
  const { gx, gy, gz } = externalOrigin(block.side, block.dx, block.dy, W, D, slot, count);
  const dim = { x: block.dx - PAD * 2, y: block.dy - PAD * 2, z: block.dz - PAD * 2 };
  const origin = iso(gx + PAD, gy + PAD, gz + PAD);

  // Selection ring: outline the block's footprint on the floor (z=gz).
  const ringPts = (() => {
    if (!selected) return null;
    const m = 0.06;
    const x0 = gx + m, x1 = gx + block.dx - m;
    const y0 = gy + m, y1 = gy + block.dy - m;
    const p0 = iso(x0, y0, gz);
    const p1 = iso(x1, y0, gz);
    const p2 = iso(x1, y1, gz);
    const p3 = iso(x0, y1, gz);
    return `${p0.x},${p0.y} ${p1.x},${p1.y} ${p2.x},${p2.y} ${p3.x},${p3.y}`;
  })();

  return (
    <g
      style={{ cursor: onSelect ? 'pointer' : 'default' }}
      onMouseDown={(e) => {
        if (e.button !== 0 || !onSelect) return;
        e.stopPropagation();
        onSelect();
      }}
    >
      {selected && (
        <polygon
          points={ringPts}
          fill="none"
          stroke="var(--teal-400)"
          strokeWidth="2"
          strokeDasharray="5 3"
          style={{ pointerEvents: 'none' }}
        />
      )}
      <g transform={`translate(${origin.x}, ${origin.y})`}>
        <BlockShape color={block.color || 'slate'} type="solid" dim={dim} label={block.label} labelScale={block.labelScale ?? 1} />
      </g>
    </g>
  );
}

// Render the connector lines for one side (trunk + branches). World coords
// are projected via the shared `iso` helper so the connector lives in the
// same iso scene as everything else.
function ExternalConnector({ side, externalsOnSide, W, D }) {
  if (!externalsOnSide.length) return null;
  const polylines = connectorsForSide(side, externalsOnSide, W, D);
  const toStr = (pts) => pts.map(p => {
    const sp = iso(p.x, p.y, p.z);
    return `${sp.x},${sp.y}`;
  }).join(' ');

  // Color reads as muted warm-gray, slightly brighter than block edge so
  // the line is visible against the void.
  const stroke = '#8b8170';
  const strokeWidth = 3;

  return (
    <g style={{ pointerEvents: 'none' }}>
      {polylines.map((pl, i) => (
        <polyline
          key={i}
          points={toStr(pl)}
          fill="none"
          stroke={stroke}
          strokeOpacity="0.9"
          strokeWidth={strokeWidth}
          strokeLinecap="round"
          strokeLinejoin="round"
        />
      ))}
    </g>
  );
}

// A small swatch used in the Direction picker — shows a tilted cube with
// a connector stub coming out of the side that faces the canvas. Used in
// the panel so the user can see at a glance which direction maps to which.
//
// The swatch is drawn as a small iso scene at fixed scale: we render a
// 1x1x1 block in the corner of an imaginary canvas, with a connector line
// coming out of the appropriate side.
function ExtDirectionSwatch({ side, active, size = 56 }) {
  // Tiny iso scene: place a fake 1u canvas at origin (W=D=H=1), and an
  // external 1u block on the chosen side. Then project everything and
  // crop / scale to fit the swatch.
  const W = 1, D = 1, H = 1;
  const ext = { dx: 1, dy: 1, dz: 1, side, _slotIndex: 0, _slotCount: 1 };

  // Project all the points we care about and find a bounding box.
  const cubePts = [
    iso(0,0,0), iso(W,0,0), iso(W,D,0), iso(0,D,0),
    iso(0,0,H), iso(W,0,H), iso(W,D,H), iso(0,D,H),
  ];
  const { gx, gy, gz } = externalOrigin(side, ext.dx, ext.dy, W, D, 0, 1);
  // Mini connector: from canvas wall midpoint to the block's near face.
  const exit = _exitPoint(side, W, D);
  const face = _nearFaceCenter(side, ext, W, D);
  const extPts = [
    iso(gx, gy, 0), iso(gx + ext.dx, gy, 0), iso(gx + ext.dx, gy + ext.dy, 0), iso(gx, gy + ext.dy, 0),
    iso(gx, gy, ext.dz), iso(gx + ext.dx, gy, ext.dz), iso(gx + ext.dx, gy + ext.dy, ext.dz), iso(gx, gy + ext.dy, ext.dz),
  ];
  const connPts = [
    iso(W/2, D/2, 0),
    iso(exit.x, exit.y, exit.z),
    iso(face.x, face.y, face.z),
  ];

  const all = [...cubePts, ...extPts, ...connPts];
  const xs = all.map(p => p.x), ys = all.map(p => p.y);
  const minX = Math.min(...xs), maxX = Math.max(...xs);
  const minY = Math.min(...ys), maxY = Math.max(...ys);
  const w = maxX - minX, h = maxY - minY;
  const pad = 4;
  const scale = Math.min((size - pad * 2) / w, (size - pad * 2) / h);
  const tx = -minX * scale + (size - w * scale) / 2;
  const ty = -minY * scale + (size - h * scale) / 2;
  const T = (p) => ({ x: p.x * scale + tx, y: p.y * scale + ty });

  const cubeOutline = (pts) => {
    const C = pts.map(T);
    return `${C[0].x},${C[0].y} ${C[1].x},${C[1].y} ${C[2].x},${C[2].y} ${C[3].x},${C[3].y}`;
  };
  // Cube faces (front-left + front-right visible)
  const blockFaces = (pts, fill) => {
    const C = pts.map(T);
    return (
      <g>
        {/* top */}
        <polygon points={`${C[4].x},${C[4].y} ${C[5].x},${C[5].y} ${C[6].x},${C[6].y} ${C[7].x},${C[7].y}`} fill={fill.top} />
        {/* right (x=max) */}
        <polygon points={`${C[5].x},${C[5].y} ${C[1].x},${C[1].y} ${C[2].x},${C[2].y} ${C[6].x},${C[6].y}`} fill={fill.right} />
        {/* left (y=max) */}
        <polygon points={`${C[7].x},${C[7].y} ${C[6].x},${C[6].y} ${C[2].x},${C[2].y} ${C[3].x},${C[3].y}`} fill={fill.left} />
      </g>
    );
  };

  const slatePal = PALETTE.slate;
  const tealMuted = { top: 'rgba(45, 212, 160, 0.18)', right: 'rgba(45, 212, 160, 0.10)', left: 'rgba(45, 212, 160, 0.06)' };

  const C1 = connPts.map(T);

  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      {/* Canvas: faint teal cube outline */}
      <polygon
        points={cubeOutline([cubePts[0], cubePts[1], cubePts[2], cubePts[3]])}
        fill="rgba(45, 212, 160, 0.12)"
        stroke={active ? 'var(--teal-300)' : 'var(--teal-500)'}
        strokeOpacity={active ? '0.9' : '0.55'}
        strokeWidth="1"
      />
      {/* Connector */}
      <polyline
        points={`${C1[0].x},${C1[0].y} ${C1[1].x},${C1[1].y} ${C1[2].x},${C1[2].y}`}
        fill="none"
        stroke={active ? '#cbbfa6' : '#8b8170'}
        strokeOpacity="0.95"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      {/* External block */}
      {blockFaces(extPts, slatePal)}
    </svg>
  );
}

window.ExternalBlock = ExternalBlock;
window.ExternalConnector = ExternalConnector;
window.ExtDirectionSwatch = ExtDirectionSwatch;
window.externalOrigin = externalOrigin;
window.connectorsForSide = connectorsForSide;
window.EXT_SIDES = EXT_SIDES;
window.EXT_SIZES = EXT_SIZES;
window.EXT_OFFSET = EXT_OFFSET;
