// components/CurtainUnravel.jsx
//
// T2->T3 reveal: the single-thread unravel.
// A custom GLSL vertex shader displaces cloth vertices along a logarithmic
// spiral starting from the centre. As u_unravelProgress moves 0->1 over 4s,
// the cloth dissolves into nothing.
//
// The architectural commitment: at the inner sanctum, there is nothing being
// hidden. The substrate's depth is the design itself. The unravel is true.
//
// Wire-in: window.CurtainUnravel = CurtainUnravel

const COLS_UNRAVEL = 30;
const ROWS_UNRAVEL = 40;
const CLOTH_W_UNRAVEL = 12;
const CLOTH_H_UNRAVEL = 16;
const UNRAVEL_DURATION = 4000;

const VERTEX_SHADER = `
  uniform float u_unravelProgress;
  uniform float u_time;
  varying vec2 vUv;
  varying float vDisplacement;

  // Logarithmic spiral: r = a * exp(b * theta)
  // Each vertex's spiral starting angle is determined by its UV coordinate.
  vec3 spiralDisplace(vec3 pos, vec2 uv, float progress) {
    // Distance from centre in UV space
    vec2 centred = uv - vec2(0.5, 0.5);
    float r = length(centred);
    float theta = atan(centred.y, centred.x);

    // Spiral coefficients (Fibonacci-derived feel)
    float a = 0.05;
    float b = 0.30;

    // Each vertex spirals outward as progress increases.
    // Vertices closer to centre start later (smaller initial theta delta);
    // vertices further from centre start earlier.
    float startThreshold = r * 0.4;       // when does this vertex start unravelling?
    float localProgress = max(0.0, (progress - startThreshold) / (1.0 - startThreshold));

    // Compute spiral position
    float spiralR = a * exp(b * (theta + localProgress * 12.0));
    float spiralX = spiralR * cos(theta + localProgress * 12.0);
    float spiralY = spiralR * sin(theta + localProgress * 12.0);

    // Outward displacement scaled by progress
    float outwardScale = pow(localProgress, 1.5) * 8.0;
    vec3 outward = vec3(centred * outwardScale, 0.0);

    // Combine: original position + spiral motion + outward burst
    vec3 displaced = pos + vec3(spiralX, spiralY, sin(localProgress * 6.28) * 0.3) + outward;

    return displaced;
  }

  void main() {
    vUv = uv;
    vec3 displaced = spiralDisplace(position, uv, u_unravelProgress);

    // Compute displacement magnitude for fragment shader to use for alpha fade
    float displacementMag = length(displaced - position);
    vDisplacement = displacementMag;

    gl_Position = projectionMatrix * modelViewMatrix * vec4(displaced, 1.0);
  }
`;

const FRAGMENT_SHADER = `
  uniform vec3 u_color;
  uniform float u_unravelProgress;
  varying vec2 vUv;
  varying float vDisplacement;

  void main() {
    // Base alpha decreases with displacement (further-displaced threads fade)
    float displacementFade = clamp(1.0 - vDisplacement / 12.0, 0.0, 1.0);

    // Overall progress fade: as unravel completes, everything fades
    float progressFade = clamp(1.0 - u_unravelProgress * 1.2, 0.0, 1.0);

    float alpha = displacementFade * progressFade;

    // Subtle thread shimmer near the leading edge of the unravel
    float threadShimmer = sin(vUv.x * 80.0 + u_unravelProgress * 20.0) * 0.05 + 0.95;

    gl_FragColor = vec4(u_color * threadShimmer, alpha);
  }
`;

const { useEffect, useRef } = React;

function CurtainUnravel({ transition, onComplete }) {
  const canvasRef = useRef(null);

  useEffect(() => {
    if (!transition) return;
    if (transition.from !== 'T2' || transition.to !== 'T3') {
      // Not the unravel transition; pass through
      onComplete?.();
      return;
    }

    if (!window.THREE || !checkWebGLSupport()) {
      // Fallback: simple fade
      cssFallback(onComplete);
      return;
    }

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

  return React.createElement('div', {
    className: 'curtain-unravel-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 runUnravelAnimation(canvas, onComplete) {
  const THREE = window.THREE;

  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);

  // High-resolution plane for the unravel
  const geometry = new THREE.PlaneGeometry(
    CLOTH_W_UNRAVEL,
    CLOTH_H_UNRAVEL,
    COLS_UNRAVEL - 1,
    ROWS_UNRAVEL - 1
  );

  const uniforms = {
    u_unravelProgress: { value: 0.0 },
    u_time: { value: 0.0 },
    u_color: { value: new THREE.Color(0x0e0e0e) },  // ngahere - the deepest cloth
  };

  const material = new THREE.ShaderMaterial({
    uniforms,
    vertexShader: VERTEX_SHADER,
    fragmentShader: FRAGMENT_SHADER,
    side: THREE.DoubleSide,
    transparent: true,
  });

  const mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  // Subtle ambient particle field that becomes visible as cloth unravels
  // (the inner sanctum's "there is nothing here" felt as ambient light)
  const particleCount = 200;
  const particleGeom = new THREE.BufferGeometry();
  const particlePositions = new Float32Array(particleCount * 3);
  for (let i = 0; i < particleCount; i++) {
    particlePositions[i * 3] = (Math.random() - 0.5) * 20;
    particlePositions[i * 3 + 1] = (Math.random() - 0.5) * 20;
    particlePositions[i * 3 + 2] = (Math.random() - 0.5) * 8;
  }
  particleGeom.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3));
  const particleMat = new THREE.PointsMaterial({
    color: 0xc4881f,    // kowhai
    size: 0.04,
    transparent: true,
    opacity: 0,
  });
  const particles = new THREE.Points(particleGeom, particleMat);
  scene.add(particles);

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

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

    uniforms.u_time.value = elapsed * 0.001;
    uniforms.u_unravelProgress.value = easeInOutCubic(t);

    // Particles fade in as cloth unravels
    particleMat.opacity = Math.min(t * 1.4, 0.6);

    // Slight camera dolly inward for cinematic feel
    camera.position.z = 18 - t * 1.5;

    renderer.render(scene, camera);

    if (t >= 1) {
      // Hold the dissolved state briefly, then fade canvas out
      setTimeout(() => {
        canvas.style.transition = 'opacity 600ms ease-out';
        canvas.style.opacity = '0';
        setTimeout(() => {
          cleanup();
          onComplete?.();
        }, 600);
      }, 400);
      return;
    }

    rafId = requestAnimationFrame(step);
  }

  rafId = requestAnimationFrame(step);

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

  return cleanup;
}

function easeInOutCubic(t) {
  return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}

function cssFallback(onComplete) {
  const overlay = document.createElement('div');
  overlay.style.cssText = `
    position: fixed; inset: 0;
    background: #0e0e0e;
    z-index: 9999;
    opacity: 1;
    transition: opacity 3s ease-out;
    pointer-events: none;
  `;
  document.body.appendChild(overlay);
  setTimeout(() => { overlay.style.opacity = '0'; }, 200);
  setTimeout(() => {
    overlay.remove();
    onComplete?.();
  }, 3300);
}

window.CurtainUnravel = CurtainUnravel;
