// components/CurtainReveal.jsx
//
// Three.js cloth simulation for T0->T1 and T1->T2 tier reveals.
// 30x40 vertex grid with verlet integration, gravity, and wind force.
// Targets 60fps on M1/Pixel 6+. Falls back to CSS transform on devices
// without WebGL support.
//
// Wire-in: window.CurtainReveal = CurtainReveal

const { useEffect, useRef, useState } = React;

const COLS = 30;
const ROWS = 40;
const CLOTH_WIDTH = 12;   // Three.js world units
const CLOTH_HEIGHT = 16;
const REST_DIST_X = CLOTH_WIDTH / (COLS - 1);
const REST_DIST_Y = CLOTH_HEIGHT / (ROWS - 1);
const GRAVITY = -9.8 * 0.0008;  // scaled for per-frame physics
const DAMPING = 0.99;
const ITERATIONS = 4;  // constraint solver iterations per frame

const TIER_CLOTH_PRESETS = {
  'T0->T1': {
    color: 0x1f8a3a,        // pounamu
    weight: 1.0,
    windStrength: 0.012,
    duration: 4400,
    label: 'velvet_pull',
  },
  'T1->T2': {
    color: 0x0e0e0e,        // ngahere
    weight: 1.6,             // heavier; falls slower
    windStrength: 0.008,
    duration: 4400,
    label: 'heavy_pull',
  },
};

function CurtainReveal({ transition, onComplete }) {
  const canvasRef = useRef(null);
  const animationRef = useRef(null);
  const [supportsWebGL, setSupportsWebGL] = useState(true);

  useEffect(() => {
    if (!transition) return;

    const presetKey = `${transition.from}->${transition.to}`;
    const preset = TIER_CLOTH_PRESETS[presetKey];
    if (!preset) {
      // Tier transition not handled by cloth (e.g. T2->T3 uses unravel shader)
      onComplete?.();
      return;
    }

    if (!checkWebGLSupport()) {
      setSupportsWebGL(false);
      cssTransformFallback(canvasRef.current, preset, onComplete);
      return;
    }

    if (!window.THREE) {
      console.warn('[CurtainReveal] Three.js not loaded; using fallback');
      setSupportsWebGL(false);
      cssTransformFallback(canvasRef.current, preset, onComplete);
      return;
    }

    const cleanup = runClothAnimation(canvasRef.current, preset, onComplete);
    animationRef.current = cleanup;
    return () => { if (cleanup) cleanup(); };
  }, [transition]);

  return React.createElement('div', {
    className: 'curtain-reveal-overlay',
    style: {
      position: 'fixed',
      inset: 0,
      zIndex: 9999,
      pointerEvents: 'none',
    },
  }, React.createElement('canvas', {
    ref: canvasRef,
    style: { width: '100%', height: '100%', display: 'block' },
  }));
}

function checkWebGLSupport() {
  try {
    const canvas = document.createElement('canvas');
    return !!(window.WebGLRenderingContext &&
      (canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
  } catch (e) {
    return false;
  }
}

function runClothAnimation(canvas, preset, onComplete) {
  const THREE = window.THREE;
  const { color, weight, windStrength, duration } = preset;

  const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
  camera.position.set(0, 0, 18);

  // Lighting
  scene.add(new THREE.AmbientLight(0xffffff, 0.4));
  const directional = new THREE.DirectionalLight(0xffffff, 0.7);
  directional.position.set(-5, 8, 6);
  scene.add(directional);

  // Build cloth particles (verlet integration)
  const particles = [];
  for (let r = 0; r < ROWS; r++) {
    for (let c = 0; c < COLS; c++) {
      const x = (c - (COLS - 1) / 2) * REST_DIST_X;
      const y = (-(r) + (ROWS - 1) / 2) * REST_DIST_Y;
      const z = 0;
      particles.push({
        x, y, z,
        px: x, py: y, pz: z,  // previous position (verlet)
        pinned: r === 0,      // top row pinned to rod
        mass: weight,
      });
    }
  }

  // Build cloth mesh geometry
  const geometry = new THREE.BufferGeometry();
  const positions = new Float32Array(COLS * ROWS * 3);
  const indices = [];
  for (let r = 0; r < ROWS - 1; r++) {
    for (let c = 0; c < COLS - 1; c++) {
      const a = r * COLS + c;
      const b = r * COLS + c + 1;
      const cIdx = (r + 1) * COLS + c;
      const d = (r + 1) * COLS + c + 1;
      indices.push(a, cIdx, b);
      indices.push(b, cIdx, d);
    }
  }
  geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geometry.setIndex(indices);
  geometry.computeVertexNormals();

  const material = new THREE.MeshPhongMaterial({
    color,
    side: THREE.DoubleSide,
    flatShading: false,
    shininess: 18,
  });
  const mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  // Rod (visual anchor)
  const rodGeom = new THREE.CylinderGeometry(0.1, 0.1, CLOTH_WIDTH + 1, 12);
  const rodMat = new THREE.MeshPhongMaterial({ color: 0x2c2c2a });
  const rod = new THREE.Mesh(rodGeom, rodMat);
  rod.rotation.z = Math.PI / 2;
  rod.position.y = (ROWS - 1) / 2 * REST_DIST_Y + 0.2;
  scene.add(rod);

  const startTime = performance.now();
  let rafId = null;
  let canvasFadeStart = null;

  function step() {
    const now = performance.now();
    const elapsed = now - startTime;
    const t = Math.min(elapsed / duration, 1);

    // Phase 1 (0-200ms): hold
    // Phase 2 (200-1400ms): rod tilts, releases right side
    // Phase 3 (1400-3400ms): wind + gravity pulls cloth aside
    // Phase 4 (3400-4400ms): cloth settles, fade out canvas

    let rodTilt = 0;
    let releaseRightSide = false;
    let activeWind = 0;

    if (elapsed < 200) {
      // Hold
    } else if (elapsed < 1400) {
      const phaseT = (elapsed - 200) / 1200;
      rodTilt = Math.PI / 2 * easeInOut(phaseT);
      releaseRightSide = phaseT > 0.5;
    } else if (elapsed < 3400) {
      rodTilt = Math.PI / 2;
      releaseRightSide = true;
      activeWind = windStrength * easeOutSine((elapsed - 1400) / 2000);
    } else {
      rodTilt = Math.PI / 2;
      releaseRightSide = true;
      activeWind = windStrength * 0.3;
      if (!canvasFadeStart) canvasFadeStart = now;
      const fadeT = Math.min((now - canvasFadeStart) / 600, 1);
      canvas.style.opacity = `${1 - fadeT}`;
      if (fadeT >= 1) {
        cleanup();
        onComplete?.();
        return;
      }
    }

    rod.rotation.x = rodTilt * 0.4;  // subtle rod tilt for visual

    // Update pinning based on rod release
    for (let c = 0; c < COLS; c++) {
      const p = particles[c];
      if (releaseRightSide && c >= COLS / 2) {
        p.pinned = false;
      }
    }

    // Verlet integration
    for (const p of particles) {
      if (p.pinned) continue;
      const vx = (p.x - p.px) * DAMPING;
      const vy = (p.y - p.py) * DAMPING;
      const vz = (p.z - p.pz) * DAMPING;
      p.px = p.x;
      p.py = p.y;
      p.pz = p.z;
      p.x += vx + activeWind;          // horizontal wind
      p.y += vy + GRAVITY / p.mass;    // gravity
      p.z += vz;
    }

    // Constraint solver iterations
    for (let iter = 0; iter < ITERATIONS; iter++) {
      // Horizontal constraints
      for (let r = 0; r < ROWS; r++) {
        for (let c = 0; c < COLS - 1; c++) {
          const a = particles[r * COLS + c];
          const b = particles[r * COLS + c + 1];
          satisfyConstraint(a, b, REST_DIST_X);
        }
      }
      // Vertical constraints
      for (let r = 0; r < ROWS - 1; r++) {
        for (let c = 0; c < COLS; c++) {
          const a = particles[r * COLS + c];
          const b = particles[(r + 1) * COLS + c];
          satisfyConstraint(a, b, REST_DIST_Y);
        }
      }
    }

    // Update geometry positions
    for (let i = 0; i < particles.length; i++) {
      positions[i * 3] = particles[i].x;
      positions[i * 3 + 1] = particles[i].y;
      positions[i * 3 + 2] = particles[i].z;
    }
    geometry.attributes.position.needsUpdate = true;
    geometry.computeVertexNormals();

    renderer.render(scene, camera);
    rafId = requestAnimationFrame(step);
  }

  rafId = requestAnimationFrame(step);

  function cleanup() {
    if (rafId !== null) {
      cancelAnimationFrame(rafId);
      rafId = null;
    }
    geometry.dispose();
    material.dispose();
    rodGeom.dispose();
    rodMat.dispose();
    renderer.dispose();
  }

  return cleanup;
}

function satisfyConstraint(a, b, restDist) {
  if (a.pinned && b.pinned) return;
  const dx = b.x - a.x;
  const dy = b.y - a.y;
  const dz = b.z - a.z;
  const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
  if (dist === 0) return;
  const diff = (dist - restDist) / dist;
  if (a.pinned) {
    b.x -= dx * diff;
    b.y -= dy * diff;
    b.z -= dz * diff;
  } else if (b.pinned) {
    a.x += dx * diff;
    a.y += dy * diff;
    a.z += dz * diff;
  } else {
    const half = diff * 0.5;
    a.x += dx * half;
    a.y += dy * half;
    a.z += dz * half;
    b.x -= dx * half;
    b.y -= dy * half;
    b.z -= dz * half;
  }
}

function easeInOut(t) { return t < 0.5 ? 2*t*t : -1 + (4 - 2*t)*t; }
function easeOutSine(t) { return Math.sin((t * Math.PI) / 2); }

function cssTransformFallback(container, preset, onComplete) {
  const overlay = document.createElement('div');
  overlay.style.cssText = `
    position: fixed; inset: 0;
    background: linear-gradient(90deg, #${preset.color.toString(16).padStart(6, '0')} 50%, transparent 50%);
    z-index: 9999;
    transform: translateX(0);
    transition: transform 2.5s cubic-bezier(0.6, 0, 0.4, 1);
    pointer-events: none;
  `;
  document.body.appendChild(overlay);
  setTimeout(() => { overlay.style.transform = 'translateX(100%)'; }, 50);
  setTimeout(() => {
    overlay.remove();
    onComplete?.();
  }, 2700);
}

window.CurtainReveal = CurtainReveal;
