// Three.js renderer for zigzag-engine half_braid geometry
// Renders VISIBLE elements only from explosion poset
// Visibility: point at geom_dim d is visible iff coords[d..] are all singular

const GEOMETRY_DATA = {
  "metadata": {
    "source": "half_braid.json",
    "dimension": 3,
    "total_points": 23,
    "visible_points": 7,
    "total_covers": 35
  },
  "vertices": [
    { "id": 21, "label": "vertex_0", "point": "s0,s0,s0", "coords": [0.0, 0.0, -0.5] },
    { "id": 22, "label": "vertex_1", "point": "s1,s0,s0", "coords": [0.0, 0.0, 0.5] }
  ],
  "wires": [
    { "id": 18, "label": "wire_0", "point": "r0,s0,s0", "coords": [0.0, 0.0, -1.0],
      "endpoints": [21, 22], "endpoint_coords": [[0.0, 0.0, -0.5], [0.0, 0.0, 0.5]] },
    { "id": 19, "label": "wire_1", "point": "r1,s0,s0", "coords": [0.0, 0.0, 0.0],
      "endpoints": [21, 22], "endpoint_coords": [[0.0, 0.0, -0.5], [0.0, 0.0, 0.5]] },
    { "id": 20, "label": "wire_2", "point": "r2,s0,s0", "coords": [0.0, 0.0, 1.0],
      "endpoints": [21, 22], "endpoint_coords": [[0.0, 0.0, -0.5], [0.0, 0.0, 0.5]] }
  ],
  "surfaces": [
    { "id": 16, "label": "surface_0", "point": "r0,r0,s0", "coords": [0.0, -1.0, -1.0], "boundary_wires": [18] },
    { "id": 17, "label": "surface_1", "point": "r0,r1,s0", "coords": [0.0, 0.0, -1.0], "boundary_wires": [18] }
  ]
};

// Coordinate mapping: coords are already [x, y, z] layout coordinates
// Scale for better visualization
const SCALE = 2.0;
function mapCoords(coords) {
  return [coords[0] * SCALE, coords[1] * SCALE, coords[2] * SCALE];
}

// Wire colors by group
const WIRE_COLORS = {
  input: 0x4fc3f7,    // blue - r0 in third coord
  crossing: 0xe040fb, // purple - s0 in third coord
  output: 0x66bb6a,   // green - r1 in third coord
  selfloop: 0xff7043  // orange - self-loops
};

function getWireGroup(wire) {
  if (wire.endpoints[0] === wire.endpoints[1]) return 'selfloop';
  const point = wire.point;
  const thirdCoord = point.split(',')[2];
  if (thirdCoord === 'r0') return 'input';
  if (thirdCoord === 's0') return 'crossing';
  if (thirdCoord === 'r1') return 'output';
  return 'crossing';
}

// Variational elastic curve solver
// Minimizes E = τ·Σ|Δp|² + β·Σ|Δ²p|² + κ·|p_mid - waypoint|²
function solveElasticCurve(p0, p1, waypoint, tau, beta, kappa, resolution) {
  const n = resolution; // interior points
  const total = n + 2;  // including endpoints

  // Build matrix A = τ·DᵀD + β·D²ᵀD² + κ·(waypoint spring at midpoint)
  // DᵀD tridiagonal: main=2, off=-1
  // D²ᵀD² pentadiagonal: [1, -4, 6, -4, 1]

  // We solve for interior points only (indices 1..n)
  // Matrix is n×n

  const A = [];
  const bx = new Array(n).fill(0);
  const by = new Array(n).fill(0);
  const bz = new Array(n).fill(0);

  for (let i = 0; i < n; i++) {
    A[i] = new Array(n).fill(0);
  }

  // Build DᵀD (tension term)
  for (let i = 0; i < n; i++) {
    A[i][i] += 2 * tau;
    if (i > 0) A[i][i-1] += -tau;
    if (i < n-1) A[i][i+1] += -tau;
  }

  // Boundary contributions to RHS for tension term
  bx[0] += tau * p0[0];
  by[0] += tau * p0[1];
  bz[0] += tau * p0[2];
  bx[n-1] += tau * p1[0];
  by[n-1] += tau * p1[1];
  bz[n-1] += tau * p1[2];

  // Build D²ᵀD² (bending term) - pentadiagonal
  // For interior point i, D²p[i] = p[i-2] - 4p[i-1] + 6p[i] - 4p[i+1] + p[i+2]
  for (let i = 0; i < n; i++) {
    A[i][i] += 6 * beta;
    if (i > 0) A[i][i-1] += -4 * beta;
    if (i > 1) A[i][i-2] += beta;
    if (i < n-1) A[i][i+1] += -4 * beta;
    if (i < n-2) A[i][i+2] += beta;
  }

  // Boundary contributions for bending term
  // At i=0: needs p[-1]=p0, p[-2] (extrapolate as p0)
  // At i=1: needs p[-1]=p0
  // At i=n-2: needs p[n]=p1
  // At i=n-1: needs p[n]=p1, p[n+1] (extrapolate as p1)

  // i=0 contributions from p0 (at index -1) and extrapolated p0 (at index -2)
  bx[0] += beta * (4 * p0[0] - p0[0]); // -4*p[-1] + p[-2], but these go to RHS with sign flip
  by[0] += beta * (4 * p0[1] - p0[1]);
  bz[0] += beta * (4 * p0[2] - p0[2]);

  if (n > 1) {
    bx[1] += beta * p0[0];
    by[1] += beta * p0[1];
    bz[1] += beta * p0[2];
  }

  // i=n-1 contributions from p1 (at index n) and extrapolated p1 (at index n+1)
  bx[n-1] += beta * (4 * p1[0] - p1[0]);
  by[n-1] += beta * (4 * p1[1] - p1[1]);
  bz[n-1] += beta * (4 * p1[2] - p1[2]);

  if (n > 1) {
    bx[n-2] += beta * p1[0];
    by[n-2] += beta * p1[1];
    bz[n-2] += beta * p1[2];
  }

  // Waypoint spring at midpoint
  const midIdx = Math.floor(n / 2);
  A[midIdx][midIdx] += kappa;
  bx[midIdx] += kappa * waypoint[0];
  by[midIdx] += kappa * waypoint[1];
  bz[midIdx] += kappa * waypoint[2];

  // Solve using Gaussian elimination with partial pivoting
  function solveTridiagonal(A, b) {
    const n = b.length;
    const x = new Array(n);
    const Ac = A.map(row => [...row]);
    const bc = [...b];

    // Forward elimination
    for (let k = 0; k < n-1; k++) {
      // Find pivot
      let maxIdx = k;
      for (let i = k+1; i < n; i++) {
        if (Math.abs(Ac[i][k]) > Math.abs(Ac[maxIdx][k])) maxIdx = i;
      }
      // Swap rows
      [Ac[k], Ac[maxIdx]] = [Ac[maxIdx], Ac[k]];
      [bc[k], bc[maxIdx]] = [bc[maxIdx], bc[k]];

      if (Math.abs(Ac[k][k]) < 1e-10) continue;

      for (let i = k+1; i < n; i++) {
        const factor = Ac[i][k] / Ac[k][k];
        for (let j = k; j < n; j++) {
          Ac[i][j] -= factor * Ac[k][j];
        }
        bc[i] -= factor * bc[k];
      }
    }

    // Back substitution
    for (let i = n-1; i >= 0; i--) {
      let sum = bc[i];
      for (let j = i+1; j < n; j++) {
        sum -= Ac[i][j] * x[j];
      }
      x[i] = Math.abs(Ac[i][i]) > 1e-10 ? sum / Ac[i][i] : 0;
    }

    return x;
  }

  const solX = solveTridiagonal(A, bx);
  const solY = solveTridiagonal(A, by);
  const solZ = solveTridiagonal(A, bz);

  // Build full curve with endpoints
  const curve = [p0];
  for (let i = 0; i < n; i++) {
    curve.push([solX[i], solY[i], solZ[i]]);
  }
  curve.push(p1);

  return curve;
}

// Generate self-loop curve (parametric loop offset toward waypoint)
function generateSelfLoop(center, waypoint, resolution) {
  const points = [];
  const offset = [
    waypoint[0] - center[0],
    waypoint[1] - center[1],
    waypoint[2] - center[2]
  ];
  const offsetMag = Math.sqrt(offset[0]**2 + offset[1]**2 + offset[2]**2);
  const norm = offsetMag > 0.01 ? offset.map(x => x / offsetMag) : [0, 1, 0];

  // Create perpendicular vectors for the loop plane
  let perp1, perp2;
  if (Math.abs(norm[1]) < 0.9) {
    perp1 = [norm[2], 0, -norm[0]];
  } else {
    perp1 = [0, norm[2], -norm[1]];
  }
  const mag1 = Math.sqrt(perp1[0]**2 + perp1[1]**2 + perp1[2]**2);
  perp1 = perp1.map(x => x / mag1);

  perp2 = [
    norm[1]*perp1[2] - norm[2]*perp1[1],
    norm[2]*perp1[0] - norm[0]*perp1[2],
    norm[0]*perp1[1] - norm[1]*perp1[0]
  ];

  const loopRadius = Math.min(offsetMag * 0.6, 0.8);
  const loopCenter = [
    center[0] + norm[0] * offsetMag * 0.4,
    center[1] + norm[1] * offsetMag * 0.4,
    center[2] + norm[2] * offsetMag * 0.4
  ];

  for (let i = 0; i <= resolution; i++) {
    const t = (i / resolution) * 2 * Math.PI;
    const x = loopCenter[0] + loopRadius * (Math.cos(t) * perp1[0] + Math.sin(t) * perp2[0]);
    const y = loopCenter[1] + loopRadius * (Math.cos(t) * perp1[1] + Math.sin(t) * perp2[1]);
    const z = loopCenter[2] + loopRadius * (Math.cos(t) * perp1[2] + Math.sin(t) * perp2[2]);
    points.push([x, y, z]);
  }

  return points;
}

function ZigzagRenderer() {
  const containerRef = React.useRef(null);
  const sceneRef = React.useRef(null);
  const cameraRef = React.useRef(null);
  const rendererRef = React.useRef(null);
  const wireObjectsRef = React.useRef([]);
  const waypointObjectsRef = React.useRef([]);
  const surfaceObjectsRef = React.useRef([]);
  const labelObjectsRef = React.useRef([]);

  const [tension, setTension] = React.useState(1.0);
  const [bending, setBending] = React.useState(0.5);
  const [kappa, setKappa] = React.useState(2.0);
  const [resolution, setResolution] = React.useState(20);
  const [showWaypoints, setShowWaypoints] = React.useState(true);
  const [showSurfaces, setShowSurfaces] = React.useState(true);
  const [showLabels, setShowLabels] = React.useState(true);

  // Orbit controls state
  const orbitRef = React.useRef({
    theta: Math.PI / 4,
    phi: Math.PI / 4,
    radius: 12,
    target: [2, 2, 2],
    isDragging: false,
    lastX: 0,
    lastY: 0
  });

  // Initialize Three.js scene
  React.useEffect(() => {
    if (!containerRef.current || !window.THREE) return;

    const THREE = window.THREE;
    const width = containerRef.current.clientWidth;
    const height = containerRef.current.clientHeight;

    // Scene
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x0a0a0f);
    sceneRef.current = scene;

    // Camera
    const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 100);
    cameraRef.current = camera;

    // Renderer
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.setPixelRatio(window.devicePixelRatio);
    containerRef.current.appendChild(renderer.domElement);
    rendererRef.current = renderer;

    // Lights
    const ambient = new THREE.AmbientLight(0xffffff, 0.4);
    scene.add(ambient);

    const directional = new THREE.DirectionalLight(0xffffff, 0.8);
    directional.position.set(5, 10, 5);
    scene.add(directional);

    // Add vertices as glowing spheres
    const vertexGeom = new THREE.SphereGeometry(0.15, 32, 32);
    const vertexMat = new THREE.MeshStandardMaterial({
      color: 0xf0c040,
      emissive: 0xf0c040,
      emissiveIntensity: 0.5
    });

    GEOMETRY_DATA.vertices.forEach(v => {
      const pos = mapCoords(v.coords);
      const mesh = new THREE.Mesh(vertexGeom, vertexMat);
      mesh.position.set(pos[0], pos[1], pos[2]);
      scene.add(mesh);
    });

    // Grid helper
    const gridHelper = new THREE.GridHelper(10, 10, 0x333344, 0x222233);
    gridHelper.position.y = -0.5;
    scene.add(gridHelper);

    // Axes helper
    const axesHelper = new THREE.AxesHelper(3);
    scene.add(axesHelper);

    // Mouse controls
    const canvas = renderer.domElement;

    canvas.addEventListener('mousedown', (e) => {
      orbitRef.current.isDragging = true;
      orbitRef.current.lastX = e.clientX;
      orbitRef.current.lastY = e.clientY;
    });

    canvas.addEventListener('mousemove', (e) => {
      if (!orbitRef.current.isDragging) return;
      const dx = e.clientX - orbitRef.current.lastX;
      const dy = e.clientY - orbitRef.current.lastY;
      orbitRef.current.theta -= dx * 0.01;
      orbitRef.current.phi = Math.max(0.1, Math.min(Math.PI - 0.1, orbitRef.current.phi + dy * 0.01));
      orbitRef.current.lastX = e.clientX;
      orbitRef.current.lastY = e.clientY;
    });

    canvas.addEventListener('mouseup', () => {
      orbitRef.current.isDragging = false;
    });

    canvas.addEventListener('mouseleave', () => {
      orbitRef.current.isDragging = false;
    });

    canvas.addEventListener('wheel', (e) => {
      e.preventDefault();
      orbitRef.current.radius = Math.max(3, Math.min(30, orbitRef.current.radius + e.deltaY * 0.01));
    });

    // Animation loop
    function animate() {
      requestAnimationFrame(animate);

      const orbit = orbitRef.current;
      const x = orbit.target[0] + orbit.radius * Math.sin(orbit.phi) * Math.cos(orbit.theta);
      const y = orbit.target[1] + orbit.radius * Math.cos(orbit.phi);
      const z = orbit.target[2] + orbit.radius * Math.sin(orbit.phi) * Math.sin(orbit.theta);

      camera.position.set(x, y, z);
      camera.lookAt(orbit.target[0], orbit.target[1], orbit.target[2]);

      renderer.render(scene, camera);
    }
    animate();

    // Resize handler
    const handleResize = () => {
      const w = containerRef.current.clientWidth;
      const h = containerRef.current.clientHeight;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    };
    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
      renderer.dispose();
      if (containerRef.current && renderer.domElement) {
        containerRef.current.removeChild(renderer.domElement);
      }
    };
  }, []);

  // Update wires when parameters change
  React.useEffect(() => {
    if (!sceneRef.current || !window.THREE) return;

    const THREE = window.THREE;
    const scene = sceneRef.current;

    // Remove old wire objects
    wireObjectsRef.current.forEach(obj => scene.remove(obj));
    wireObjectsRef.current = [];

    // Create wire lookup for surfaces
    const wireById = {};

    // Add wires
    GEOMETRY_DATA.wires.forEach(wire => {
      const p0 = mapCoords(wire.endpoint_coords[0]);
      const p1 = mapCoords(wire.endpoint_coords[1]);
      const waypoint = mapCoords(wire.coords);

      const group = getWireGroup(wire);
      const color = WIRE_COLORS[group];

      let curvePoints;
      if (wire.endpoints[0] === wire.endpoints[1]) {
        // Self-loop
        curvePoints = generateSelfLoop(p0, waypoint, resolution);
      } else {
        // Elastic curve
        curvePoints = solveElasticCurve(p0, p1, waypoint, tension, bending, kappa, resolution);
      }

      wireById[wire.id] = curvePoints;

      // Create tube geometry
      const points = curvePoints.map(p => new THREE.Vector3(p[0], p[1], p[2]));
      const curve = new THREE.CatmullRomCurve3(points);
      const tubeGeom = new THREE.TubeGeometry(curve, resolution * 2, 0.04, 8, false);
      const tubeMat = new THREE.MeshStandardMaterial({
        color: color,
        emissive: color,
        emissiveIntensity: 0.3
      });
      const tube = new THREE.Mesh(tubeGeom, tubeMat);
      scene.add(tube);
      wireObjectsRef.current.push(tube);
    });

    // Store wire paths for surface rendering
    sceneRef.current.userData.wireById = wireById;

  }, [tension, bending, kappa, resolution]);

  // Update waypoint visibility
  React.useEffect(() => {
    if (!sceneRef.current || !window.THREE) return;

    const THREE = window.THREE;
    const scene = sceneRef.current;

    // Remove old waypoint objects
    waypointObjectsRef.current.forEach(obj => scene.remove(obj));
    waypointObjectsRef.current = [];

    if (showWaypoints) {
      const waypointGeom = new THREE.SphereGeometry(0.08, 16, 16);
      const waypointMat = new THREE.MeshStandardMaterial({
        color: 0xf0c040,
        emissive: 0xf0c040,
        emissiveIntensity: 0.6,
        transparent: true,
        opacity: 0.8
      });

      GEOMETRY_DATA.wires.forEach(wire => {
        const pos = mapCoords(wire.coords);
        const mesh = new THREE.Mesh(waypointGeom, waypointMat);
        mesh.position.set(pos[0], pos[1], pos[2]);
        scene.add(mesh);
        waypointObjectsRef.current.push(mesh);
      });
    }
  }, [showWaypoints]);

  // Update surface visibility
  React.useEffect(() => {
    if (!sceneRef.current || !window.THREE) return;

    const THREE = window.THREE;
    const scene = sceneRef.current;

    // Remove old surface objects
    surfaceObjectsRef.current.forEach(obj => scene.remove(obj));
    surfaceObjectsRef.current = [];

    if (showSurfaces) {
      const surfaceMat = new THREE.MeshStandardMaterial({
        color: 0xff6b6b,
        emissive: 0xff6b6b,
        emissiveIntensity: 0.4,
        transparent: true,
        opacity: 0.6
      });

      const lineMat = new THREE.LineBasicMaterial({
        color: 0xff6b6b,
        transparent: true,
        opacity: 0.4
      });

      GEOMETRY_DATA.surfaces.forEach(surface => {
        const pos = mapCoords(surface.coords);

        // Surface center point
        const sphereGeom = new THREE.SphereGeometry(0.06, 12, 12);
        const sphere = new THREE.Mesh(sphereGeom, surfaceMat);
        sphere.position.set(pos[0], pos[1], pos[2]);
        scene.add(sphere);
        surfaceObjectsRef.current.push(sphere);

        // Lines to boundary wire midpoints
        const wireById = scene.userData.wireById || {};
        surface.boundary_wires.forEach(wireId => {
          const wire = GEOMETRY_DATA.wires.find(w => w.id === wireId);
          if (wire) {
            const wirePos = mapCoords(wire.coords);
            const geometry = new THREE.BufferGeometry().setFromPoints([
              new THREE.Vector3(pos[0], pos[1], pos[2]),
              new THREE.Vector3(wirePos[0], wirePos[1], wirePos[2])
            ]);
            const line = new THREE.Line(geometry, lineMat);
            scene.add(line);
            surfaceObjectsRef.current.push(line);
          }
        });
      });
    }
  }, [showSurfaces, tension, bending, kappa, resolution]);

  // Update label visibility
  React.useEffect(() => {
    if (!sceneRef.current || !window.THREE) return;

    const THREE = window.THREE;
    const scene = sceneRef.current;

    // Remove old label objects
    labelObjectsRef.current.forEach(obj => scene.remove(obj));
    labelObjectsRef.current = [];

    if (showLabels) {
      // Create canvas-based sprite labels
      GEOMETRY_DATA.vertices.forEach(v => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = 128;
        canvas.height = 64;

        ctx.fillStyle = 'rgba(10, 10, 15, 0.8)';
        ctx.fillRect(0, 0, 128, 64);
        ctx.strokeStyle = '#f0c040';
        ctx.lineWidth = 2;
        ctx.strokeRect(2, 2, 124, 60);

        ctx.font = 'bold 20px monospace';
        ctx.fillStyle = '#f0c040';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(v.label, 64, 32);

        const texture = new THREE.CanvasTexture(canvas);
        const spriteMat = new THREE.SpriteMaterial({ map: texture, transparent: true });
        const sprite = new THREE.Sprite(spriteMat);

        const pos = mapCoords(v.coords);
        sprite.position.set(pos[0], pos[1] + 0.4, pos[2]);
        sprite.scale.set(1, 0.5, 1);

        scene.add(sprite);
        labelObjectsRef.current.push(sprite);
      });
    }
  }, [showLabels]);

  const panelStyle = {
    position: 'absolute',
    top: '20px',
    left: '20px',
    background: 'rgba(10, 10, 20, 0.85)',
    backdropFilter: 'blur(10px)',
    padding: '20px',
    borderRadius: '12px',
    color: '#e0e0e0',
    fontFamily: 'monospace',
    fontSize: '14px',
    border: '1px solid rgba(240, 192, 64, 0.3)',
    minWidth: '220px'
  };

  const sliderStyle = {
    width: '100%',
    marginTop: '4px',
    accentColor: '#f0c040'
  };

  const checkboxStyle = {
    accentColor: '#f0c040',
    marginRight: '8px'
  };

  return React.createElement('div', { style: { width: '100vw', height: '100vh', position: 'relative' } },
    React.createElement('div', { ref: containerRef, style: { width: '100%', height: '100%' } }),
    React.createElement('div', { style: panelStyle },
      React.createElement('h3', { style: { margin: '0 0 15px 0', color: '#f0c040' } }, 'Zigzag Renderer'),

      React.createElement('div', { style: { marginBottom: '12px' } },
        React.createElement('label', null, `Tension τ: ${tension.toFixed(2)}`),
        React.createElement('input', {
          type: 'range', min: '0.1', max: '5', step: '0.1', value: tension,
          onChange: (e) => setTension(parseFloat(e.target.value)),
          style: sliderStyle
        })
      ),

      React.createElement('div', { style: { marginBottom: '12px' } },
        React.createElement('label', null, `Bending β: ${bending.toFixed(2)}`),
        React.createElement('input', {
          type: 'range', min: '0', max: '2', step: '0.05', value: bending,
          onChange: (e) => setBending(parseFloat(e.target.value)),
          style: sliderStyle
        })
      ),

      React.createElement('div', { style: { marginBottom: '12px' } },
        React.createElement('label', null, `Waypoint κ: ${kappa.toFixed(2)}`),
        React.createElement('input', {
          type: 'range', min: '0', max: '10', step: '0.1', value: kappa,
          onChange: (e) => setKappa(parseFloat(e.target.value)),
          style: sliderStyle
        })
      ),

      React.createElement('div', { style: { marginBottom: '12px' } },
        React.createElement('label', null, `Resolution: ${resolution}`),
        React.createElement('input', {
          type: 'range', min: '5', max: '50', step: '1', value: resolution,
          onChange: (e) => setResolution(parseInt(e.target.value)),
          style: sliderStyle
        })
      ),

      React.createElement('hr', { style: { border: 'none', borderTop: '1px solid rgba(240, 192, 64, 0.3)', margin: '15px 0' } }),

      React.createElement('div', { style: { marginBottom: '8px' } },
        React.createElement('label', null,
          React.createElement('input', {
            type: 'checkbox', checked: showWaypoints,
            onChange: (e) => setShowWaypoints(e.target.checked),
            style: checkboxStyle
          }),
          'Waypoints'
        )
      ),

      React.createElement('div', { style: { marginBottom: '8px' } },
        React.createElement('label', null,
          React.createElement('input', {
            type: 'checkbox', checked: showSurfaces,
            onChange: (e) => setShowSurfaces(e.target.checked),
            style: checkboxStyle
          }),
          'Surfaces'
        )
      ),

      React.createElement('div', { style: { marginBottom: '8px' } },
        React.createElement('label', null,
          React.createElement('input', {
            type: 'checkbox', checked: showLabels,
            onChange: (e) => setShowLabels(e.target.checked),
            style: checkboxStyle
          }),
          'Labels'
        )
      ),

      React.createElement('hr', { style: { border: 'none', borderTop: '1px solid rgba(240, 192, 64, 0.3)', margin: '15px 0' } }),

      React.createElement('div', { style: { fontSize: '11px', color: '#888' } },
        React.createElement('div', null, '🔵 Input (r0)'),
        React.createElement('div', null, '🟣 Crossing (s0)'),
        React.createElement('div', null, '🟢 Output (r1)'),
        React.createElement('div', null, '🟠 Self-loop')
      )
    )
  );
}

// Export for use
window.ZigzagRenderer = ZigzagRenderer;
