// Scroll-driven 3D printer. Renders an isometric printer with a bed + gantry
// and builds a model layer-by-layer based on `progress` 0..1.
//
// Models supported: 'planter' | 'stand' | 'mini' | 'logo'
// All rendered in SVG, stylized but readable — "sketchy structure, real printer".

const PrinterScene = ({ progress = 0, model = 'planter', sketch = true, width = 520, height = 560, label = true }) => {
  // Clamp
  const p = Math.max(0, Math.min(1, progress));

  // Printer constants (isometric-ish 2D projection)
  const cx = width / 2;
  const bedY = height * 0.72;
  const bedW = width * 0.58;
  const bedH = 38;
  const frameH = height * 0.62;
  const frameTop = bedY - frameH + 24;

  // Layer config per model
  const modelConf = {
    planter: { layers: 42, radiusFn: (y) => {
      // y in 0..1 bottom to top. Hex/geo planter: cone-flared with chamfer
      const r = 0.55 + 0.30 * Math.pow(y, 1.3) - 0.04 * Math.sin(y * Math.PI * 3);
      return Math.max(0.22, r);
    }, sides: 6, twist: 0.15, maxH: 220 },
    stand: { layers: 28, radiusFn: (y) => 0.8 - 0.15 * y, sides: 4, twist: 0, maxH: 160, isStand: true },
    mini: { layers: 38, radiusFn: (y) => {
      // hourglass figurine-ish: wider base, narrow middle, shoulder, small head
      if (y < 0.15) return 0.55 - y * 0.5;
      if (y < 0.55) return 0.30 + (y - 0.15) * 0.2;
      if (y < 0.7) return 0.48 - (y - 0.55) * 0.3;
      if (y < 0.85) return 0.35 - (y - 0.7) * 0.5;
      return 0.28 - (y - 0.85) * 0.8;
    }, sides: 12, twist: 0, maxH: 210 },
    logo: { layers: 24, radiusFn: (y) => 0.9, sides: 4, twist: 0, maxH: 90, isLogo: true },
  };
  const conf = modelConf[model] || modelConf.planter;

  // How many layers done
  const layersDone = Math.floor(conf.layers * p);
  const partialLayer = (conf.layers * p) - layersDone;

  // Nozzle Y position: sit the nozzle TIP just above the top of the currently-printing layer.
  // The top of the current layer is at: bedY - (layersDone + partialLayer) * layerH
  // We want the nozzle cone tip slightly above that (small clearance), and the nozzle cone
  // itself renders from (nozzleY - 6) down to (nozzleY + 1), so offset accordingly.
  const layerH = conf.maxH / conf.layers;
  const currentLayerTop = bedY - (layersDone + partialLayer) * layerH;
  const NOZZLE_CLEARANCE = 3; // px above top of print
  const nozzleY = currentLayerTop - NOZZLE_CLEARANCE;
  // Head sweeps side to side
  const headX = cx + Math.sin(p * 30) * (bedW * 0.35) * (1 - 0.4 * p);

  // Build layer polygons
  const layerPolys = [];
  const baseR = Math.min(bedW * 0.38, conf.maxH * 0.9);
  for (let i = 0; i <= layersDone; i++) {
    const yNorm = i / conf.layers;
    const layerY = bedY - i * layerH;
    let alpha = 1;
    if (i === layersDone && partialLayer > 0 && partialLayer < 1) alpha = 0.5 + partialLayer * 0.5;
    const r = conf.radiusFn(yNorm) * baseR;
    const rY = r * 0.3; // isometric squish
    const twist = (i * conf.twist);

    if (conf.isStand) {
      // draw a laptop stand: angled slab
      const slabW = r * 1.8;
      const slabTh = layerH * 0.9;
      const tilt = 10;
      layerPolys.push(
        <g key={i} opacity={alpha}>
          <polygon
            points={`
              ${cx - slabW},${layerY}
              ${cx + slabW},${layerY - tilt * (i/conf.layers)}
              ${cx + slabW},${layerY - tilt * (i/conf.layers) + slabTh}
              ${cx - slabW},${layerY + slabTh}
            `}
            fill={`hsl(28 ${45 - i*0.5}% ${55 - i * 0.3}%)`}
            stroke="#00000022" strokeWidth="0.5"
          />
        </g>
      );
    } else if (conf.isLogo) {
      // GT3D block letters extruded
      if (i < conf.layers * 0.3) {
        // base platform
        layerPolys.push(
          <ellipse key={i} cx={cx} cy={layerY} rx={r} ry={rY} fill={`hsl(210 40% ${30 + i*0.8}%)`} opacity={alpha} stroke="#00000033" strokeWidth="0.5" />
        );
      } else {
        // extruded letters outline
        const w = r * 1.3;
        layerPolys.push(
          <g key={i} opacity={alpha}>
            <rect x={cx - w} y={layerY - layerH * 0.4} width={w*2} height={layerH * 0.9}
                  fill={`hsl(210 50% ${35 + i * 0.5}%)`}
                  stroke="#00000033" strokeWidth="0.5" rx="1" />
          </g>
        );
      }
    } else {
      // Hex/polygonal layer — draw as polygon for sides < 14, else ellipse
      let pts = '';
      for (let s = 0; s < conf.sides; s++) {
        const ang = (s / conf.sides) * Math.PI * 2 + twist;
        const x = cx + Math.cos(ang) * r;
        const y = layerY + Math.sin(ang) * rY;
        pts += `${x.toFixed(1)},${y.toFixed(1)} `;
      }
      // Color: clay/terracotta for planter, cool gray for mini
      let fill;
      if (model === 'planter') {
        fill = `hsl(${18 + i * 0.3} 55% ${45 + i * 0.4}%)`;
      } else if (model === 'mini') {
        fill = `hsl(210 10% ${40 + i * 0.6}%)`;
      } else {
        fill = `hsl(200 30% ${40 + i * 0.5}%)`;
      }
      layerPolys.push(
        <polygon key={i} points={pts}
                 fill={fill}
                 stroke="#00000028" strokeWidth="0.5"
                 opacity={alpha} />
      );
    }
  }

  // Extruded plastic trail when printing
  const extruding = p > 0.01 && p < 0.995;
  const trailY = nozzleY + 10;

  // Filament spool position
  const spoolX = width - 52;
  const spoolY = frameTop + 48;

  return (
    <svg viewBox={`0 0 ${width} ${height}`} width="100%" height="100%" style={{ display: 'block', maxWidth: '100%' }}>
      <defs>
        <linearGradient id="bedGrad" x1="0" x2="0" y1="0" y2="1">
          <stop offset="0" stopColor="#2a2a2e" />
          <stop offset="1" stopColor="#141416" />
        </linearGradient>
        <linearGradient id="frameGrad" x1="0" x2="0" y1="0" y2="1">
          <stop offset="0" stopColor="#d9d3c2" />
          <stop offset="1" stopColor="#b8b19d" />
        </linearGradient>
        <pattern id="sketchBg" patternUnits="userSpaceOnUse" width="8" height="8">
          <path d="M0 8 L8 0" stroke="#00000010" strokeWidth="0.5" />
        </pattern>
        <filter id="wobble">
          <feTurbulence type="fractalNoise" baseFrequency="0.02" numOctaves="2" seed="3" />
          <feDisplacementMap in="SourceGraphic" scale="1.2" />
        </filter>
      </defs>

      {/* ground shadow */}
      <ellipse cx={cx} cy={bedY + bedH + 8} rx={bedW * 0.7} ry={8} fill="#1a1a1a" opacity="0.12" />

      {/* base/legs */}
      <rect x={cx - bedW/2 - 10} y={bedY + bedH} width={bedW + 20} height={8} fill="#1a1a1a" rx="1" />

      {/* Gantry frame: two verticals + top bar — light metal */}
      <rect x={cx - bedW/2 - 8} y={frameTop} width={12} height={frameH - 24} fill="url(#frameGrad)" stroke="#1a1a1a" strokeWidth="1.2" />
      <rect x={cx + bedW/2 - 4} y={frameTop} width={12} height={frameH - 24} fill="url(#frameGrad)" stroke="#1a1a1a" strokeWidth="1.2" />
      <rect x={cx - bedW/2 - 12} y={frameTop - 6} width={bedW + 24} height={14} fill="url(#frameGrad)" stroke="#1a1a1a" strokeWidth="1.2" rx="1" />

      {/* X-axis rail (moves up/down with nozzle) */}
      <g>
        <rect x={cx - bedW/2 - 2} y={nozzleY - 18} width={bedW + 4} height={10} fill="#ece6d6" stroke="#1a1a1a" strokeWidth="1" rx="1" />
        <line x1={cx - bedW/2} y1={nozzleY - 22} x2={cx + bedW/2} y2={nozzleY - 22} stroke="#1a1a1a" strokeWidth="0.8" strokeDasharray="2 3" opacity="0.5" />
      </g>

      {/* Print head / nozzle */}
      <g>
        <rect x={headX - 18} y={nozzleY - 28} width={36} height={22} fill="#1a1a1a" stroke="#1a1a1a" strokeWidth="1" rx="2" />
        <rect x={headX - 14} y={nozzleY - 24} width={28} height={6} fill="#ff6b35" opacity="0.85" rx="1" />
        <polygon points={`${headX - 5},${nozzleY - 6} ${headX + 5},${nozzleY - 6} ${headX + 2},${nozzleY + 1} ${headX - 2},${nozzleY + 1}`}
                 fill="#3a3a3c" stroke="#1a1a1a" strokeWidth="0.5" />
        {extruding && (
          <>
            <line x1={headX} y1={nozzleY + 1} x2={headX} y2={trailY} stroke="#ff6b35" strokeWidth="1.5" opacity="0.9" />
            <circle cx={headX} cy={trailY} r="1.8" fill="#ff6b35" opacity="0.95">
              <animate attributeName="opacity" values="0.95;0.35;0.95" dur="0.4s" repeatCount="indefinite" />
            </circle>
          </>
        )}
      </g>

      {/* Bed — lighter, fits paper palette */}
      <g>
        <ellipse cx={cx} cy={bedY + 4} rx={bedW/2 + 2} ry={bedH/2 + 2} fill="#1a1a1a" opacity="0.18" />
        <rect x={cx - bedW/2} y={bedY - 2} width={bedW} height={bedH} fill="#d9d3c2" stroke="#1a1a1a" strokeWidth="1" rx="2" />
        <rect x={cx - bedW/2 + 6} y={bedY + 2} width={bedW - 12} height={bedH * 0.4} fill="#1a1a1a" opacity="0.05" rx="1" />
        <g stroke="#1a1a1a" strokeOpacity="0.15" strokeWidth="0.5">
          {[...Array(10)].map((_, i) => (
            <line key={i} x1={cx - bedW/2 + (bedW/10)*i} y1={bedY + 2} x2={cx - bedW/2 + (bedW/10)*i} y2={bedY + bedH - 2} />
          ))}
        </g>
      </g>

      {/* printed layers — rendered from bottom up, so no z-order issues */}
      <g>{layerPolys}</g>

      {/* Filament spool */}
      <g transform={`translate(${spoolX} ${spoolY})`}>
        <circle r="22" fill="#ece6d6" stroke="#1a1a1a" strokeWidth="1.2" />
        <circle r="16" fill="none" stroke="#ff6b35" strokeWidth="8" opacity={0.55 + 0.45 * (1 - p)} />
        <circle r="5" fill="#1a1a1a" />
        <path d={`M -3 -18 Q -30 -40 ${headX - spoolX} ${nozzleY - spoolY - 20}`}
              fill="none" stroke="#ff6b35" strokeWidth="1" opacity="0.55" />
      </g>
    </svg>
  );
};

// Hook: compute scroll progress within a ref'd element
const useScrollProgress = (ref, options = {}) => {
  const { startOffset = 0, endOffset = 0, pinEnd = 0.9 } = options;
  const [p, setP] = React.useState(0);
  React.useEffect(() => {
    const handle = () => {
      if (!ref.current) return;
      const rect = ref.current.getBoundingClientRect();
      const winH = window.innerHeight;
      // progress goes from 0 when top of el enters bottom of viewport,
      // to 1 when bottom of el reaches top of viewport
      const total = rect.height + winH;
      const passed = winH - rect.top;
      let prog = passed / total;
      prog = Math.max(0, Math.min(1, prog));
      // stretch & clamp
      if (startOffset) prog = (prog - startOffset) / (1 - startOffset);
      prog = Math.max(0, Math.min(1, prog));
      // bias: finish at pinEnd so printer completes before bottom
      prog = Math.min(1, prog / pinEnd);
      setP(prog);
    };
    handle();
    window.addEventListener('scroll', handle, { passive: true });
    window.addEventListener('resize', handle);
    return () => { window.removeEventListener('scroll', handle); window.removeEventListener('resize', handle); };
  }, [ref, startOffset, endOffset, pinEnd]);
  return p;
};

Object.assign(window, { PrinterScene, useScrollProgress });
