/**
 * Wave Hero Section Template
 * 
 * A hero section with animated wave bars using Three.js instanced rendering.
 * Features bloom post-processing, film grain, and mouse-reactive glow effects.
 * 
 * Dependencies:
 * - three
 * - gsap
 */

'use client';

import React, { useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import gsap from 'gsap';

// ============================================
// TYPES
// ============================================
export interface WaveHeroProps {
  className?: string;
  style?: React.CSSProperties;
  extendLeftPx?: number;
  title?: string;
  subtitle?: string;
  placeholder?: string;
  buttonText?: string;
  onPromptSubmit?: (value: string) => void;
  children?: React.ReactNode;
}

// ============================================
// FILM GRAIN SHADER
// ============================================
const FilmGrainShader = {
  uniforms: {
    tDiffuse: { value: null as THREE.Texture | null },
    time: { value: 0 },
    intensity: { value: 1.1 },
    grainScale: { value: 0.5 },
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    #ifdef GL_ES
      precision highp int;
      precision mediump float;
    #else
      precision mediump float;
    #endif
    uniform sampler2D tDiffuse;
    uniform float time;
    uniform float intensity;
    uniform float grainScale;
    varying vec2 vUv;

    float sparkleNoise(vec2 p) {
      vec2 jPos = p + vec2(37.0, 17.0) * fract(time * 0.07);
      vec3 p3 = fract(vec3(jPos.xyx) * vec3(.1031, .1030, .0973) + time * 0.1);
      p3 += dot(p3, p3.yxz + 19.19);
      return fract((p3.x + p3.y) * p3.z);
    }

    void main() {
      vec4 color = texture2D(tDiffuse, vUv);
      vec2 pos = gl_FragCoord.xy * 0.5 * grainScale;
      float noise = sparkleNoise(pos);
      noise = noise * 2.0 - 1.0;
      vec3 result = color.rgb + noise * intensity * 0.1;
      gl_FragColor = vec4(result, color.a);
    }
  `,
};

function createFilmGrainPass(intensity = 0.9, grainScale = 0.3) {
  const pass = new ShaderPass(FilmGrainShader as any);
  (pass.uniforms as any).intensity.value = intensity;
  (pass.uniforms as any).grainScale.value = grainScale;
  return pass;
}

// ============================================
// BAR MATERIAL SHADER
// ============================================
function createInstancedMaterial() {
  const baseCol = new THREE.Color('hsl(220,100%,50%)');
  const emisCol = new THREE.Color('#1f3dbc');

  return new THREE.ShaderMaterial({
    defines: { USE_INSTANCING: '' },
    uniforms: {
      uMouseClipX: { value: 0 },
      uHalfW: { value: 0 },
      uMaxGlowDist: { value: 690 },
      uGlowFalloff: { value: 0.6 },
      uSmoothSpeed: { value: 0 },
      uGainMul: { value: 1 },
      uBaseY: { value: 0 },
      w1Gain: { value: 10 },
      w1Len: { value: 0.5 },
      w1Phase: { value: 0 },
      w2Gain: { value: 0 },
      w2Len: { value: 0.5 },
      w2Phase: { value: 0 },
      uFixedTipPx: { value: 10 },
      uMinBottomWidthPx: { value: 0 },
      uColor: { value: baseCol },
      uEmissive: { value: emisCol },
      uBaseEmissive: { value: 0.05 },
      uRotationAngle: { value: THREE.MathUtils.degToRad(23.4) },
    },
    vertexShader: `
      attribute float aXPos, aPosNorm, aGroup, aGlow;
      uniform float uMouseClipX, uHalfW, uMaxGlowDist, uGlowFalloff;
      uniform float uGainMul, uBaseY;
      uniform float w1Gain, w1Len, w1Phase;
      uniform float w2Gain, w2Len, w2Phase;
      uniform float uRotationAngle;
      varying float vGlow, vPulse, vHeight;
      varying vec2 vUv;

      float sineH(float g, float len, float ph, float t){
        return max(20.0, (sin(ph + t * len) * 0.5 + 0.6) * g * uGainMul);
      }

      void main(){
        vUv = uv;
        float h1 = sineH(w1Gain, w1Len, w1Phase, aPosNorm);
        float h2 = sineH(w2Gain, w2Len, w2Phase, aPosNorm);
        vHeight = mix(h1, h2, aGroup);

        vec3 pos = position;
        pos.x += aXPos;
        pos.y = 0.0;

        float height = vHeight * uv.y;
        pos.x += height * tan(uRotationAngle);
        pos.y += height;

        pos.y += uBaseY;

        vec4 clip = projectionMatrix * modelViewMatrix * vec4(pos,1.0);
        float dxPx = abs(uMouseClipX - clip.x/clip.w) * uHalfW;
        float prox = clamp(1.0 - pow(dxPx / uMaxGlowDist, uGlowFalloff), 0.0, 1.0);

        vGlow  = aGlow;
        vPulse = prox;
        gl_Position = clip;
      }
    `,
    fragmentShader: `
      #ifdef GL_ES
        precision highp int;
        precision mediump float;
      #else
        precision mediump float;
      #endif
      uniform vec3 uColor, uEmissive;
      uniform float uBaseEmissive;
      uniform float uFixedTipPx, uMinBottomWidthPx;
      varying float vGlow, vPulse, vHeight;
      varying vec2 vUv;

      void main(){
        float tipProp = clamp(uFixedTipPx / vHeight, 0.0, 0.95);
        float transitionY = 1.0 - tipProp;
        float xFromCenter = abs(vUv.x - 0.5) * 2.0;
        float px = fwidth(vUv.x);
        float allowedWidth;

        if (vUv.y >= transitionY){
          float topPos = (vUv.y - transitionY) / tipProp;
          allowedWidth = 1.0 - pow(topPos, 0.9);
        } else {
          float bottomPos = vUv.y / transitionY;
          allowedWidth = max(uMinBottomWidthPx * px * 10.0, pow(bottomPos, 0.5));
        }

        float alpha = smoothstep(-px, px, allowedWidth - xFromCenter);
        if (alpha < 0.01) discard;

        float emissiveStrength = uBaseEmissive + vGlow * 0.9 + vPulse * 0.15;
        vec3 finalColor = uColor + uEmissive * emissiveStrength;
        gl_FragColor = vec4(finalColor, 0.35 * alpha);
      }
    `,
    side: THREE.FrontSide,
    transparent: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
  });
}

// ============================================
// WAVE KEYFRAMES
// ============================================
const waveKeyframes1 = [
  { time: 0, gain: 10, frequency: 0, waveLength: 0.5 },
  { time: 4, gain: 300, frequency: 1, waveLength: 0.5 },
  { time: 6, gain: 300, frequency: 4, waveLength: Math.PI * 1.5 },
  { time: 8, gain: 225, frequency: 4, waveLength: Math.PI * 1.5 },
  { time: 10, gain: 500, frequency: 1, waveLength: Math.PI * 1.5 },
  { time: 14, gain: 225, frequency: 3, waveLength: Math.PI * 1.5 },
  { time: 22, gain: 100, frequency: 6, waveLength: Math.PI * 1.5 },
  { time: 28, gain: 0, frequency: 0.9, waveLength: 0.5 },
  { time: 30, gain: 128, frequency: 0.9, waveLength: 0.5 },
  { time: 32, gain: 190, frequency: 1.42, waveLength: 0.5 },
  { time: 39, gain: 499, frequency: 4.0, waveLength: Math.PI * 1.5 },
  { time: 40, gain: 500, frequency: 4.0, waveLength: Math.PI * 1.5 },
  { time: 42, gain: 400, frequency: 2.82, waveLength: Math.PI * 1.5 },
  { time: 44, gain: 327, frequency: 2.56, waveLength: Math.PI * 1.5 },
  { time: 48, gain: 188, frequency: 5.4, waveLength: 0.5 },
  { time: 52, gain: 32, frequency: 0.1, waveLength: 0.5 },
  { time: 55, gain: 10, frequency: 0, waveLength: 0.5 },
];

const waveKeyframes2 = [
  { time: 0, gain: 0, frequency: 0, waveLength: 0.5 },
  { time: 9, gain: 0, frequency: 0, waveLength: 0.5 },
  { time: 10, gain: 400, frequency: 1, waveLength: 0.5 },
  { time: 13, gain: 300, frequency: 4, waveLength: Math.PI * 1.5 },
  { time: 24, gain: 96, frequency: 2, waveLength: 0.5 },
  { time: 28, gain: 0, frequency: 0.9, waveLength: 0.5 },
  { time: 30, gain: 142, frequency: 0.9, waveLength: 0.5 },
  { time: 36, gain: 374, frequency: 4.0, waveLength: Math.PI * 1.5 },
  { time: 38, gain: 375, frequency: 4.0, waveLength: Math.PI * 1.5 },
  { time: 40, gain: 300, frequency: 2.26, waveLength: Math.PI * 1.5 },
  { time: 44, gain: 245, frequency: 2.05, waveLength: Math.PI * 1.5 },
  { time: 48, gain: 141, frequency: 5.12, waveLength: 0.5 },
  { time: 52, gain: 24, frequency: 0.08, waveLength: 0.5 },
  { time: 55, gain: 8, frequency: 0, waveLength: 0.5 },
];

// ============================================
// TYPING PLACEHOLDER HOOK
// ============================================
function useTypingPlaceholder(prompt: string, basePlaceholder: string) {
  const suggestionsRef = useRef<string[]>([
    ' fitness app',
    ' recipe generator',
    ' marketing landing page',
    ' travel itinerary planner',
    ' blog engine',
    ' customer support chatbot',
    ' personal finance dashboard',
  ]);
  const [animatedPlaceholder, setAnimatedPlaceholder] = useState<string>(basePlaceholder);
  const typingStateRef = useRef({
    suggestionIndex: 0,
    charIndex: 0,
    deleting: false,
    running: true,
  });
  const timersRef = useRef<number[]>([]);

  useEffect(() => {
    typingStateRef.current.running = true;
    const typeSpeed = 70;
    const deleteSpeed = 40;
    const pauseAtEnd = 1200;
    const pauseBetween = 500;

    function schedule(fn: () => void, delay: number) {
      const id = window.setTimeout(fn, delay);
      timersRef.current.push(id);
    }

    function clearTimers() {
      for (const id of timersRef.current) window.clearTimeout(id);
      timersRef.current = [];
    }

    function step() {
      if (!typingStateRef.current.running) return;
      if (prompt !== '') {
        setAnimatedPlaceholder(basePlaceholder);
        schedule(step, 300);
        return;
      }

      const state = typingStateRef.current;
      const suggestions = suggestionsRef.current;
      const current = suggestions[state.suggestionIndex % suggestions.length] || '';

      if (!state.deleting) {
        const nextIndex = state.charIndex + 1;
        const next = current.slice(0, nextIndex);
        setAnimatedPlaceholder(basePlaceholder + next);
        state.charIndex = nextIndex;
        if (nextIndex >= current.length) {
          schedule(() => {
            state.deleting = true;
            step();
          }, pauseAtEnd);
        } else {
          schedule(step, typeSpeed);
        }
      } else {
        const nextIndex = Math.max(0, state.charIndex - 1);
        const next = current.slice(0, nextIndex);
        setAnimatedPlaceholder(basePlaceholder + next);
        state.charIndex = nextIndex;
        if (nextIndex <= 0) {
          state.deleting = false;
          state.suggestionIndex = (state.suggestionIndex + 1) % suggestions.length;
          schedule(step, pauseBetween);
        } else {
          schedule(step, deleteSpeed);
        }
      }
    }

    clearTimers();
    schedule(step, 400);
    return () => {
      typingStateRef.current.running = false;
      clearTimers();
    };
  }, [prompt, basePlaceholder]);

  return animatedPlaceholder;
}

// ============================================
// COMPONENT
// ============================================
export function WaveHero({
  className,
  style,
  extendLeftPx = 320,
  title = 'Build with AI.',
  subtitle = 'The AI Fullstack Engineer. Build prototypes, apps, and websites',
  placeholder = 'Describe what you want to create...',
  buttonText = 'Generate',
  onPromptSubmit,
  children,
}: WaveHeroProps) {
  const [prompt, setPrompt] = useState('');
  const containerRef = useRef<HTMLDivElement | null>(null);
  const waveRef = useRef<HTMLDivElement | null>(null);
  const basePlaceholder = 'Make me a';
  const animatedPlaceholder = useTypingPlaceholder(prompt, basePlaceholder);

  useEffect(() => {
    if (!containerRef.current || !waveRef.current) return;

    // Wave state
    const wave1 = { gain: 10, frequency: 0, waveLength: 0.5, currentAngle: 0 };
    const wave2 = { gain: 0, frequency: 0, waveLength: 0.5, currentAngle: 0 };

    // Mouse & glow config
    const mouse = { x: 0, y: 0, active: false };
    let proxyMouseX = 0,
      proxyMouseY = 0,
      proxyInitialized = false;

    const glowConfig = {
      maxGlowDistance: 690,
      speedScale: 0.52,
      fadeSpeed: 4.4,
      glowFalloff: 0.6,
      mouseSmoothing: 30.0,
    };

    const glowDynamics = {
      accumulation: 1.2,
      decay: 3.3,
      max: 40.0,
      accumEase: 1.5,
      speedEase: 8.5,
    };

    // THREE.js setup
    let DPR_CAP = 2;
    const mm = gsap.matchMedia();
    mm.add('(max-resolution: 180dpi)', () => {
      DPR_CAP = 1.5;
    });
    const EFFECT_PR = Math.min(window.devicePixelRatio, DPR_CAP) * 0.5;

    const waveContainer = waveRef.current!;
    while (waveContainer.firstChild) {
      waveContainer.removeChild(waveContainer.firstChild);
    }

    const waveRenderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });
    waveRenderer.setPixelRatio(EFFECT_PR);
    waveRenderer.toneMapping = THREE.ACESFilmicToneMapping;
    waveRenderer.toneMappingExposure = 1.0;
    waveRenderer.autoClear = false;
    waveContainer.appendChild(waveRenderer.domElement);

    const waveScene = new THREE.Scene();
    waveScene.fog = null as any;
    waveScene.add(new THREE.AmbientLight(0xffffff, 0.2));

    let waveCamera: THREE.OrthographicCamera;
    let waveComposer: EffectComposer;
    let waveBloomPass: UnrealBloomPass;
    let grainPass: ShaderPass;
    let cameraWidth = 0,
      cameraHeight = 0,
      waveCameraInitialized = false;

    let setMouseNDC: (v: number) => void,
      setSmoothSpeed: (v: number) => void,
      setPhase1: (v: number) => void,
      setPhase2: (v: number) => void;

    // Bar instancing
    const MAX_BARS = 256;
    const FIXED_BAR_WIDTH = 14;
    const FIXED_BAR_GAP = 10;
    const EXTEND_LEFT_PX = extendLeftPx;

    let instancedBars: THREE.InstancedMesh | null = null;
    let currentBarCount = 0;
    let barMaterial: THREE.ShaderMaterial;
    let barCenters: Float32Array | null = null;

    function updateGlowDistance() {
      if (!barMaterial) return;
      const totalWidth = currentBarCount * (FIXED_BAR_WIDTH + FIXED_BAR_GAP) - FIXED_BAR_GAP;
      const spanPx = totalWidth * 0.3;
      glowConfig.maxGlowDistance = spanPx;
      (barMaterial.uniforms as any).uMaxGlowDist.value = spanPx;
    }

    function setupQuickSetters() {
      const u = (instancedBars!.material as THREE.ShaderMaterial).uniforms as any;
      setMouseNDC = gsap.quickSetter(u.uMouseClipX, 'value') as any;
      setSmoothSpeed = gsap.quickSetter(u.uSmoothSpeed, 'value') as any;
      setPhase1 = gsap.quickSetter(u.w1Phase, 'value') as any;
      setPhase2 = gsap.quickSetter(u.w2Phase, 'value') as any;
    }

    const MAX_KEYFRAME_GAIN = 500;
    const SCREEN_COVERAGE = 0.6;
    function updateGainMultiplier() {
      if (!barMaterial) return;
      const targetPx = cameraHeight * SCREEN_COVERAGE;
      (barMaterial.uniforms as any).uGainMul.value = targetPx / MAX_KEYFRAME_GAIN;
    }

    let rect = { left: 0, top: 0 };
    const listeners: Array<() => void> = [];

    function setupPointerTracking() {
      const el = waveRenderer.domElement;
      const readCoords = (e: PointerEvent | TouchEvent): { x: number; y: number } => {
        if ('clientX' in e) return { x: (e as PointerEvent).clientX, y: (e as PointerEvent).clientY };
        const t = (e as TouchEvent).touches?.[0] || (e as TouchEvent).changedTouches?.[0];
        return t ? { x: t.clientX, y: t.clientY } : { x: mouse.x, y: mouse.y };
      };
      const updatePos = (e: any, active: boolean) => {
        const { x, y } = readCoords(e);
        const r = rect;
        mouse.x = x - r.left;
        mouse.y = y - r.top;
        mouse.active = active;
        if (!proxyInitialized) {
          proxyMouseX = mouse.x;
          proxyMouseY = mouse.y;
          proxyInitialized = true;
        }
      };
      const activate = (e: any) => updatePos(e, true);
      const move = (e: any) => updatePos(e, true);
      const deactivate = () => {
        mouse.active = false;
      };

      el.addEventListener('pointerdown', activate, { passive: true });
      el.addEventListener('pointermove', move, { passive: true });
      window.addEventListener('pointerup', deactivate as any, { passive: true });
      el.addEventListener('pointerleave', deactivate as any, { passive: true });
      el.addEventListener('touchstart', activate as any, { passive: true });
      el.addEventListener('touchmove', move as any, { passive: true });
      window.addEventListener('touchend', deactivate as any, { passive: true });
      window.addEventListener('touchcancel', deactivate as any, { passive: true });

      listeners.push(() => {
        el.removeEventListener('pointerdown', activate as any);
        el.removeEventListener('pointermove', move as any);
        window.removeEventListener('pointerup', deactivate as any);
        el.removeEventListener('pointerleave', deactivate as any);
        el.removeEventListener('touchstart', activate as any);
        el.removeEventListener('touchmove', move as any);
        window.removeEventListener('touchend', deactivate as any);
        window.removeEventListener('touchcancel', deactivate as any);
      });
    }

    let smoothSpeed = 0;

    function accumulateGlow(dt: number) {
      if (!instancedBars) return;
      const attr = instancedBars.geometry.getAttribute('aGlow') as THREE.InstancedBufferAttribute;
      const arr = attr.array as Float32Array;

      const mouseWorldX = proxyMouseX - cameraWidth * 0.5;
      const mDist = glowConfig.maxGlowDistance;
      const fall = glowConfig.glowFalloff;

      const decayLerp = 1.0 - Math.exp(-glowDynamics.decay * dt);
      const addEase = 1.0 - Math.exp(-glowDynamics.accumEase * dt);
      const vmax = glowDynamics.max;

      for (let i = 0; i < currentBarCount; i++) {
        const dx = Math.abs(mouseWorldX - (barCenters as Float32Array)[i]);
        const hit = dx < mDist ? 1.0 - Math.pow(dx / mDist, fall) : 0.0;

        const targetAdd = hit * smoothSpeed;
        const add = targetAdd * addEase;

        let g = (arr as any)[i] + add - (arr as any)[i] * decayLerp;

        if (g > vmax) g = vmax;
        (arr as any)[i] = (arr as any)[i + currentBarCount] = g;
      }
      (attr as any).needsUpdate = true;
    }

    function createInstancedBars() {
      if (instancedBars) {
        waveScene.remove(instancedBars);
        instancedBars.geometry.dispose();
        (instancedBars.material as any).dispose();
        instancedBars = null;
      }

      const waveWidth = cameraWidth;
      const span = waveWidth + EXTEND_LEFT_PX;
      let barCount = Math.min(MAX_BARS, Math.max(1, Math.floor((span + FIXED_BAR_GAP) / (FIXED_BAR_WIDTH + FIXED_BAR_GAP))));
      const gap = barCount > 1 ? (span - barCount * FIXED_BAR_WIDTH) / (barCount - 1) : 0;
      currentBarCount = barCount;

      const startX = -waveWidth / 2 - EXTEND_LEFT_PX;
      const instCnt = barCount * 2;
      barCenters = new Float32Array(barCount);

      const aXPos = new Float32Array(instCnt);
      const aPosNorm = new Float32Array(instCnt);
      const aGroup = new Float32Array(instCnt);
      const aGlow = new Float32Array(instCnt).fill(0);

      for (let i = 0; i < barCount; i++) {
        const x = startX + FIXED_BAR_WIDTH / 2 + i * (FIXED_BAR_WIDTH + gap);
        (barCenters as Float32Array)[i] = x;
        const t = barCount > 1 ? i / (barCount - 1) : 0;
        aXPos[i] = x;
        aXPos[i + barCount] = x;
        aPosNorm[i] = t;
        aPosNorm[i + barCount] = t;
        aGroup[i] = 0;
        aGroup[i + barCount] = 1;
      }

      const geo = new THREE.PlaneGeometry(FIXED_BAR_WIDTH, 1, 1, 1);
      geo.translate(0, 0.5, 0);
      geo.setAttribute('aXPos', new THREE.InstancedBufferAttribute(aXPos, 1));
      geo.setAttribute('aPosNorm', new THREE.InstancedBufferAttribute(aPosNorm, 1));
      geo.setAttribute('aGroup', new THREE.InstancedBufferAttribute(aGroup, 1));
      geo.setAttribute('aGlow', new THREE.InstancedBufferAttribute(aGlow, 1).setUsage(THREE.DynamicDrawUsage));

      barMaterial = createInstancedMaterial();
      instancedBars = new THREE.InstancedMesh(geo, barMaterial, instCnt);
      instancedBars.frustumCulled = false;
      waveScene.add(instancedBars);

      setupQuickSetters();
      updateGlowDistance();
    }

    function buildKeyframeTweens(target: any, keyframes: Array<any>) {
      const tl = gsap.timeline();
      for (let i = 0; i < keyframes.length - 1; i++) {
        const cur = keyframes[i];
        const nxt = keyframes[i + 1];
        const duration = nxt.time - cur.time;
        tl.to(target, { gain: nxt.gain, frequency: nxt.frequency, waveLength: nxt.waveLength, duration, ease: 'power2.inOut' }, cur.time);
      }
      return tl;
    }

    function initWaveThree() {
      cameraWidth = waveContainer.clientWidth;
      cameraHeight = waveContainer.clientHeight;
      waveCamera = new THREE.OrthographicCamera(-cameraWidth / 2, cameraWidth / 2, cameraHeight / 2, -cameraHeight / 2, -1000, 1000);
      waveCamera.position.z = 10;
      waveCamera.lookAt(0, 0, 0);

      waveRenderer.setSize(cameraWidth, cameraHeight);
      waveComposer = new EffectComposer(waveRenderer);
      (waveComposer as any).setPixelRatio(EFFECT_PR);

      const waveRenderPass = new RenderPass(waveScene, waveCamera);
      waveComposer.addPass(waveRenderPass);

      waveBloomPass = new UnrealBloomPass(new THREE.Vector2(cameraWidth, cameraHeight), 1.0, 0.68, 0.0);
      (waveBloomPass as any).resolution.set(cameraWidth * 0.5, cameraHeight * 0.5);
      waveComposer.addPass(waveBloomPass);

      grainPass = createFilmGrainPass();
      waveComposer.addPass(grainPass);

      createInstancedBars();
      setupPointerTracking();
      updateGainMultiplier();
      waveCameraInitialized = true;
    }

    let pendingW = 0, pendingH = 0, heavyResizeTimer: any = null;

    function onResize(newW: number, newH: number) {
      if (!waveCameraInitialized) return;
      pendingW = newW;
      pendingH = newH;

      cameraWidth = newW;
      cameraHeight = newH;
      waveCamera.left = -cameraWidth / 2;
      waveCamera.right = cameraWidth / 2;
      waveCamera.top = cameraHeight / 2;
      waveCamera.bottom = -cameraHeight / 2;
      waveCamera.updateProjectionMatrix();

      const waveWidth = cameraWidth;
      const span = waveWidth + EXTEND_LEFT_PX;
      let barCount = Math.min(MAX_BARS, Math.max(1, Math.floor((span + FIXED_BAR_GAP) / (FIXED_BAR_WIDTH + FIXED_BAR_GAP))));
      const gap = barCount > 1 ? (span - barCount * FIXED_BAR_WIDTH) / (barCount - 1) : 0;

      if (barCount !== currentBarCount) {
        currentBarCount = barCount;
        createInstancedBars();
      } else {
        const startX = -waveWidth / 2 - EXTEND_LEFT_PX;
        const aX = instancedBars!.geometry.getAttribute('aXPos') as THREE.InstancedBufferAttribute;
        const aT = instancedBars!.geometry.getAttribute('aPosNorm') as THREE.InstancedBufferAttribute;

        for (let i = 0; i < barCount; i++) {
          const x = startX + FIXED_BAR_WIDTH / 2 + i * (FIXED_BAR_WIDTH + gap);
          const t = barCount > 1 ? i / (barCount - 1) : 0;
          (aX.array as any)[i] = (aX.array as any)[i + barCount] = x;
          (aT.array as any)[i] = (aT.array as any)[i + barCount] = t;
        }
        aX.needsUpdate = true;
        aT.needsUpdate = true;
      }

      (barMaterial.uniforms as any).uHalfW.value = cameraWidth * 0.5;
      updateGainMultiplier();
      updateGlowDistance();

      clearTimeout(heavyResizeTimer);
      heavyResizeTimer = setTimeout(applyHeavyResize, 10);
      rect = waveRenderer.domElement.getBoundingClientRect();
    }

    function applyHeavyResize() {
      heavyResizeTimer = null;
      waveRenderer.setPixelRatio(EFFECT_PR);
      waveRenderer.setSize(pendingW, pendingH);
      (waveComposer as any).setSize(pendingW, pendingH);
      (waveBloomPass as any)?.setSize(pendingW, pendingH);
      (grainPass as any)?.setSize(pendingW, pendingH);
      (grainPass.uniforms as any).grainScale.value = 0.5;
    }

    function disposeWaveScene() {
      gsap.globalTimeline.clear();
      waveScene.traverse((obj: any) => {
        if (obj.isMesh) {
          obj.geometry.dispose();
          if (Array.isArray(obj.material)) {
            obj.material.forEach((m: any) => m.dispose());
          } else {
            obj.material.dispose();
          }
        }
      });
      (grainPass as any)?.dispose?.();
      (waveBloomPass as any)?.dispose?.();
      (waveComposer as any)?.dispose?.();
      (waveRenderer as any)?.dispose?.();
      instancedBars = null;
    }

    const ticker = () => {
      if (!waveCameraInitialized || !instancedBars) return;
      const dt = (gsap.ticker.deltaRatio() as number) * (1 / 60);

      wave1.currentAngle = (wave1.currentAngle + wave1.frequency * dt) % (Math.PI * 2);
      wave2.currentAngle = (wave2.currentAngle + wave2.frequency * dt) % (Math.PI * 2);
      setPhase1(wave1.currentAngle);
      setPhase2(wave2.currentAngle);

      const kMouse = 1.0 - Math.exp(-glowConfig.mouseSmoothing * dt);
      proxyMouseX += (mouse.x - proxyMouseX) * kMouse;
      proxyMouseY += (mouse.y - proxyMouseY) * kMouse;

      const dx = mouse.active ? mouse.x - proxyMouseX : 0;
      const dy = mouse.active ? mouse.y - proxyMouseY : 0;
      const rawSpeed = Math.hypot(dx, dy * 0.1) * glowConfig.speedScale;

      const kSpeed = 1.0 - Math.exp(-glowDynamics.speedEase * dt);
      smoothSpeed += (rawSpeed - smoothSpeed) * kSpeed;
      setSmoothSpeed(smoothSpeed);

      const u = (instancedBars.material as THREE.ShaderMaterial).uniforms as any;
      u.w1Gain.value = wave1.gain;
      u.w1Len.value = wave1.waveLength;
      u.w2Gain.value = wave2.gain;
      u.w2Len.value = wave2.waveLength;

      const mouseClipX = (proxyMouseX / cameraWidth) * 2 - 1;
      setMouseNDC(mouseClipX);
      let baseOffset = 40;
      if (window.innerWidth < 768) baseOffset = 20;
      u.uBaseY.value = -cameraHeight * 0.5 + baseOffset;

      (grainPass.uniforms as any).time.value += dt * 0.2;

      accumulateGlow(dt);
      waveComposer.render();
    };

    // Init
    initWaveThree();
    onResize(waveContainer.clientWidth, waveContainer.clientHeight);

    gsap.to(waveContainer.querySelector('canvas'), { opacity: 1, duration: 1, ease: 'power2.out' });

    const mainTimeline = gsap.timeline({ repeat: -1 });
    mainTimeline.add(buildKeyframeTweens(wave1, waveKeyframes1), 0);
    mainTimeline.add(buildKeyframeTweens(wave2, waveKeyframes2), 0);
    mainTimeline.play(0);

    gsap.ticker.add(ticker);

    const ro = new ResizeObserver((entries) => {
      for (const e of entries) {
        if (e.target === waveContainer) {
          onResize(e.contentRect.width, e.contentRect.height);
        }
      }
    });
    ro.observe(waveContainer);
    listeners.push(() => gsap.ticker.remove(ticker));
    listeners.push(() => ro.disconnect());

    const onVisibility = () => {
      document.hidden ? gsap.globalTimeline.pause() : gsap.globalTimeline.resume();
    };
    document.addEventListener('visibilitychange', onVisibility);
    listeners.push(() => document.removeEventListener('visibilitychange', onVisibility));

    return () => {
      listeners.forEach((fn) => fn());
      try {
        disposeWaveScene();
      } catch {}
      try {
        const canvas = waveRenderer.domElement;
        if (canvas && canvas.parentElement === waveContainer) {
          waveContainer.removeChild(canvas);
        }
      } catch {}
    };
  }, [extendLeftPx]);

  return (
    <section ref={containerRef} className={className} style={{ position: 'relative', width: '100%', height: '100vh', ...style }} aria-label="Animated hero">
      {children}
      
      {/* Default Content Overlay */}
      {!children && (
        <div
          style={{
            position: 'absolute',
            inset: 0,
            zIndex: 3,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            pointerEvents: 'none',
            padding: '24px',
          }}
        >
          <div className="max-w-3xl w-full text-center" style={{ pointerEvents: 'auto' }}>
            <h1 className="text-white text-3xl sm:text-5xl font-semibold tracking-tight drop-shadow-[0_1px_8px_rgba(31,61,188,0.25)]">
              {title}
            </h1>
            <p className="text-gray-300/90 mt-3 sm:mt-4 text-sm sm:text-base">{subtitle}</p>
            <form
              className="mt-6 sm:mt-8 flex items-center justify-center"
              onSubmit={(e) => {
                e.preventDefault();
                onPromptSubmit?.(prompt);
              }}
            >
              <div className="relative w-full sm:w-[720px]">
                <div className="relative rounded-2xl p-[2px] shadow-[0_1px_2px_0_rgba(0,0,0,0.06)] bg-gradient-to-br from-white/10 via-white/5 to-black/20">
                  <textarea
                    value={prompt}
                    onChange={(e) => setPrompt(e.target.value)}
                    placeholder={animatedPlaceholder}
                    rows={5}
                    className="w-full h-32 sm:h-36 resize-none rounded-2xl bg-[rgba(15,15,20,0.55)] border border-white/10 text-white placeholder:text-white/40 outline-none focus:ring-2 focus:ring-[#1f3dbc]/40 focus:border-[#1f3dbc]/40 backdrop-blur-md px-4 py-4 pr-16"
                  />
                </div>
                <button
                  type="submit"
                  aria-label={buttonText}
                  className="absolute right-3 bottom-3 inline-flex items-center justify-center w-10 h-10 rounded-xl bg-[#f0f2ff] text-black hover:bg-white transition-colors"
                >
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="w-5 h-5">
                    <path d="M7 17L17 7" />
                    <path d="M7 7h10v10" />
                  </svg>
                </button>
              </div>
            </form>
          </div>
        </div>
      )}

      {/* Wave Canvas */}
      <div ref={waveRef} id="waveCanvas" style={{ position: 'absolute', inset: 0, zIndex: 1, opacity: 0.8 }} />
    </section>
  );
}

// ============================================
// USAGE EXAMPLE
// ============================================
/*
import { WaveHero } from './wave-hero';

function LandingPage() {
  return (
    <WaveHero
      title="Build with AI."
      subtitle="The AI Fullstack Engineer. Build prototypes, apps, and websites"
      placeholder="Make me a"
      buttonText="Generate"
      onPromptSubmit={(value) => console.log('Prompt:', value)}
    />
  );
}

// With custom content overlay
function CustomHero() {
  return (
    <WaveHero>
      <div className="absolute inset-0 z-10 flex items-center justify-center">
        <h1 className="text-6xl text-white">Custom Content</h1>
      </div>
    </WaveHero>
  );
}
*/

export default WaveHero;

