// @wuys/map · math/horizon.js
//
// For globe (orthographic) rendering: clip polygon rings at the visible horizon.
// Each vertex has an associated z (depth from view). z ≥ 0 = front hemisphere visible;
// z < 0 = back hemisphere hidden. When an edge crosses z = 0, we compute the
// intersection point on the horizon great-circle and use it as the boundary vertex.
//
// Output: an SVG path `d` string that draws only the visible portion of the ring.

/**
 * Walk a ring of {x, y, z} points (in unit-sphere math space), emit a path d-string
 * for the visible portion. Caller maps to viewBox coords via `toView(p) → [vbx, vby]`.
 *
 * @param {Array<{x, y, z}>} projected  vertices in math space (-1..+1 disc)
 * @param {(p) => [number, number]} toView  maps math-space point to viewBox
 * @returns {string} SVG path d-attribute
 */
export function clipToHorizon(projected, toView) {
  if (!projected || projected.length < 3) return "";

  function horizon(p1, p2) {
    // Linear interpolate in math space to find the z=0 point, then renormalize to
    // sit on the visible-disc boundary. Good enough for visualization.
    const t = p1.z / (p1.z - p2.z);
    let x = (1 - t) * p1.x + t * p2.x;
    let y = (1 - t) * p1.y + t * p2.y;
    const r = Math.sqrt(x * x + y * y) || 1;
    return { x: x / r, y: y / r, z: 0 };
  }

  let d = "";
  let inSegment = false;

  function moveTo(p) {
    const [vx, vy] = toView(p);
    d += "M" + vx.toFixed(2) + "," + vy.toFixed(2);
    inSegment = true;
  }
  function lineTo(p) {
    const [vx, vy] = toView(p);
    d += "L" + vx.toFixed(2) + "," + vy.toFixed(2);
  }
  function close() {
    if (inSegment) {
      d += "Z";
      inSegment = false;
    }
  }

  for (let i = 0; i < projected.length; i++) {
    const p1 = projected[i];
    const p2 = projected[(i + 1) % projected.length];
    const v1 = p1.z >= 0;
    const v2 = p2.z >= 0;
    if (v1 && v2) {
      if (!inSegment) moveTo(p1);
      lineTo(p2);
    } else if (v1 && !v2) {
      const h = horizon(p1, p2);
      if (!inSegment) moveTo(p1);
      lineTo(h);
      close();
    } else if (!v1 && v2) {
      const h = horizon(p1, p2);
      moveTo(h);
      lineTo(p2);
    }
    // both hidden: skip
  }
  close();
  return d;
}
