export function createGrassField(THREE, config, scene) {
  const count = config.count || 50000;
  const areaSize = config.areaSize || 50;
  const bladeHeight = config.bladeHeight || 1.0;
  const bladeWidth = config.bladeWidth || 0.1;
  const segments = config.bladeSegments || 4;
  const bladeCurve = config.bladeCurve || 0.3;
  const heightVar = config.heightVariation || 0.3;
  const colorVar = config.colors?.variation || 0.15;
  const aoStrength = config.colors?.aoStrength || 0.3;
  const windTurbulence = config.windTurbulence || 0.1;
  
  // Determine mode early so we can use it throughout the function
  const mode = config.mode || 'plane';
  const tunnelRadius = config.tunnelRadius || 10;
  const tunnelLength = config.tunnelLength || 100;
  const tunnelOffsetZ = config.tunnelOffsetZ || 0;
  
  // Determine if tunnel mode
  let isTunnelMode = (mode === 'tunnel' || config.mode === 'tunnel');
  
  // Force tunnel mode if tunnelRadius is specified
  if (config.tunnelRadius && config.tunnelRadius > 0) {
    isTunnelMode = true;
  }
  
  // Parse colors (support 3-color gradient: base -> mid -> tip)
  const baseColor = new THREE.Color(config.colors?.base || '#2d5a1e');
  const midColor = new THREE.Color(config.colors?.mid || config.colors?.base || '#2d5a1e');
  const tipColor = new THREE.Color(config.colors?.tip || '#7cb342');

  // Create multi-segment blade geometry for more detailed, curved grass
  const bladeGeo = new THREE.BufferGeometry();
  const verticesArray = [];
  const uvsArray = [];
  const indicesArray = [];
  
  // Build blade with multiple segments
  for (let i = 0; i <= segments; i++) {
    const t = i / segments;
    const y = t * bladeHeight;
    // Blade gets narrower toward tip
    const width = bladeWidth * (1.0 - t * 0.7);
    // Natural curve - blade bends backward slightly
    const curve = t * t * bladeCurve * bladeHeight;
    
    // Left vertex
    verticesArray.push(-width / 2, y, curve);
    uvsArray.push(0, t);
    
    // Right vertex
    verticesArray.push(width / 2, y, curve);
    uvsArray.push(1, t);
  }
  
  // Create triangles connecting segments
  for (let i = 0; i < segments; i++) {
    const base = i * 2;
    // First triangle
    indicesArray.push(base, base + 1, base + 2);
    // Second triangle
    indicesArray.push(base + 1, base + 3, base + 2);
  }
  
  bladeGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(verticesArray), 3));
  bladeGeo.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvsArray), 2));
  bladeGeo.setIndex(indicesArray);
  bladeGeo.computeVertexNormals();

  // Create grass shader material with enhanced visuals
  const grassMaterial = new THREE.ShaderMaterial({
    uniforms: {
      uTime: { value: 0 },
      uWindSpeed: { value: config.windSpeed || 0.8 },
      uWindStrength: { value: config.windStrength || 0.4 },
      uWindTurbulence: { value: windTurbulence },
      uWindFrequency: { value: config.windFrequency || 1.0 },
      uGustStrength: { value: config.gustStrength || 0.3 },
      uBaseColor: { value: baseColor },
      uMidColor: { value: midColor },
      uTipColor: { value: tipColor },
      uAOStrength: { value: aoStrength },
      uLightDir: { value: new THREE.Vector3(0.5, 1, 0.3).normalize() },
      uAmbient: { value: 0.35 },
      uFogColor: { value: new THREE.Color(0xa8d8e8) },
      uFogNear: { value: 5.0 },
      uFogFar: { value: 80.0 },
      // Displacement uniforms (synced from tunnel mesh)
      uDispStrength: { value: 0.0 },
      uDispFrequency: { value: 2.0 },
      uDispSpeed: { value: 0.5 },
      uDispNoiseScale: { value: 1.0 },
      uTunnelMode: { value: isTunnelMode ? 1.0 : 0.0 },
    },
    vertexShader: `
      uniform float uTime;
      uniform float uWindSpeed;
      uniform float uWindStrength;
      uniform float uWindTurbulence;
      uniform float uWindFrequency;
      uniform float uGustStrength;
      uniform float uDispStrength;
      uniform float uDispFrequency;
      uniform float uDispSpeed;
      uniform float uDispNoiseScale;
      uniform float uTunnelMode;
      
      attribute vec3 offset;
      attribute float scale;
      attribute float rotation;
      attribute vec3 colorVariation;
      attribute float bladePhase;
      attribute vec3 bladeNormal;
      
      varying vec2 vUv;
      varying vec3 vNormal;
      varying vec3 vColorVar;
      varying float vHeight;
      varying float vFogDepth;
      
      mat3 rotateY(float angle) {
        float s = sin(angle);
        float c = cos(angle);
        return mat3(c, 0, s, 0, 1, 0, -s, 0, c);
      }
      
      // Create rotation matrix to align Y-up with arbitrary normal
      // Uses orthonormal basis construction (simpler and more robust)
      mat3 alignToNormal(vec3 n) {
        // Handle zero or near-zero normals - default to Y-up (no rotation)
        float len = length(n);
        if (len < 0.001) return mat3(1.0);
        
        // This will be our new Y axis (the direction the blade points)
        vec3 newY = n / len;
        
        // Find a vector that's not parallel to newY for cross product
        // For tunnel mode, normals are in XY plane, so Z works well
        vec3 notParallel = abs(newY.z) < 0.9 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
        
        // Build orthonormal basis
        vec3 newX = normalize(cross(newY, notParallel));
        vec3 newZ = cross(newX, newY);
        
        // Return matrix where columns are the new basis vectors
        // When multiplied: local X->newX, local Y->newY, local Z->newZ
        // So blade pointing in local +Y will point in newY direction (toward center)
        return mat3(newX, newY, newZ);
      }
      
      // Simple noise function
      float noise(vec2 p) {
        return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
      }
      
      // 3D Simplex noise for displacement (matches tunnel shader)
      vec3 mod289_3(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
      vec4 mod289_4(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
      vec4 permute(vec4 x) { return mod289_4(((x*34.0)+1.0)*x); }
      vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
      
      float snoise(vec3 v) {
        const vec2 C = vec2(1.0/6.0, 1.0/3.0);
        const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
        
        vec3 i  = floor(v + dot(v, C.yyy));
        vec3 x0 = v - i + dot(i, C.xxx);
        
        vec3 g = step(x0.yzx, x0.xyz);
        vec3 l = 1.0 - g;
        vec3 i1 = min(g.xyz, l.zxy);
        vec3 i2 = max(g.xyz, l.zxy);
        
        vec3 x1 = x0 - i1 + C.xxx;
        vec3 x2 = x0 - i2 + C.yyy;
        vec3 x3 = x0 - D.yyy;
        
        i = mod289_3(i);
        vec4 p = permute(permute(permute(
          i.z + vec4(0.0, i1.z, i2.z, 1.0))
          + i.y + vec4(0.0, i1.y, i2.y, 1.0))
          + i.x + vec4(0.0, i1.x, i2.x, 1.0));
        
        float n_ = 0.142857142857;
        vec3 ns = n_ * D.wyz - D.xzx;
        
        vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
        
        vec4 x_ = floor(j * ns.z);
        vec4 y_ = floor(j - 7.0 * x_);
        
        vec4 x = x_ * ns.x + ns.yyyy;
        vec4 y = y_ * ns.x + ns.yyyy;
        vec4 h = 1.0 - abs(x) - abs(y);
        
        vec4 b0 = vec4(x.xy, y.xy);
        vec4 b1 = vec4(x.zw, y.zw);
        
        vec4 s0 = floor(b0) * 2.0 + 1.0;
        vec4 s1 = floor(b1) * 2.0 + 1.0;
        vec4 sh = -step(h, vec4(0.0));
        
        vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
        vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
        
        vec3 p0 = vec3(a0.xy, h.x);
        vec3 p1 = vec3(a0.zw, h.y);
        vec3 p2 = vec3(a1.xy, h.z);
        vec3 p3 = vec3(a1.zw, h.w);
        
        vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
        p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
        
        vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
        m = m * m;
        return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
      }
      
      // Calculate tunnel displacement at a position (matches tunnel shader)
      vec3 calcTunnelDisplacement(vec3 worldPos) {
        if (uDispStrength < 0.001 || uTunnelMode < 0.5) return vec3(0.0);
        
        vec3 noisePos = worldPos * uDispNoiseScale * uDispFrequency;
        noisePos.z += uTime * uDispSpeed;
        
        // Layer multiple octaves for organic look
        float noise1 = snoise(noisePos) * 0.5;
        float noise2 = snoise(noisePos * 2.0 + 100.0) * 0.25;
        float noise3 = snoise(noisePos * 4.0 + 200.0) * 0.125;
        float totalNoise = noise1 + noise2 + noise3;
        
        // Add flowing sine waves (using Z for tunnel length direction)
        // The tunnel is rotated 90° so its length runs along Z axis
        float wave1 = sin(worldPos.z * uDispFrequency * 3.0 + uTime * uDispSpeed * 2.0) * 0.3;
        float wave2 = sin(worldPos.z * uDispFrequency * 5.0 - uTime * uDispSpeed * 1.5 + 2.0) * 0.15;
        
        float dispAmount = (totalNoise + wave1 + wave2) * uDispStrength;
        
        // Displacement is along the normal pointing toward center (for tunnel)
        // The blade normal already points inward, so we use it
        return bladeNormal * dispAmount;
      }
      
void main() {
        vUv = uv;
        vColorVar = colorVariation;
        vHeight = uv.y;
        
        // Scale the blade
        vec3 pos = position * vec3(1.0, scale, 1.0);
        
        // Rotate around local Y (before aligning to surface)
        pos = rotateY(rotation) * pos;
        
        // Beautiful smooth wind simulation
        float time = uTime * uWindSpeed;
        float freq = uWindFrequency;
        
        // Create flowing wind using multiple sine waves at different scales
        // Primary wave - gentle rolling motion across the field
        float wave1 = sin(time * 0.5 * freq + offset.x * 0.1 + offset.z * 0.08 + bladePhase);
        
        // Secondary wave - slower, larger scale undulation
        float wave2 = sin(time * 0.3 * freq + offset.x * 0.05 - offset.z * 0.06 + bladePhase * 0.7) * 0.6;
        
        // Tertiary wave - adds organic variation
        float wave3 = sin(time * 0.7 * freq + offset.x * 0.15 + offset.z * 0.12 + bladePhase * 1.3) * 0.3;
        
        // Combine waves for natural flowing motion
        float windWave = (wave1 + wave2 + wave3) * 0.5;
        
        // Gentle gusts - slow-moving intensity variations
        float gustTime = uTime * 0.1;
        float gust1 = sin(gustTime + offset.x * 0.03 + offset.z * 0.02) * 0.5 + 0.5;
        float gust2 = sin(gustTime * 0.7 + offset.x * 0.02 - offset.z * 0.025) * 0.5 + 0.5;
        float gust = mix(gust1, gust2, 0.5);
        gust = smoothstep(0.0, 1.0, gust); // Smooth the gust for gentler transitions
        
        // Height-based bend - cubic for more natural grass physics
        float heightFactor = uv.y * uv.y * uv.y;
        
        // Final bend strength with gust influence
        float bendStrength = uWindStrength * (0.6 + gust * uGustStrength);
        
        // Primary bend direction (wind blows mostly in one direction)
        float bendX = heightFactor * bendStrength * windWave;
        
        // Secondary sway perpendicular to main wind
        float swayWave = sin(time * 0.4 * freq + offset.x * 0.12 + bladePhase * 0.5);
        float bendZ = heightFactor * bendStrength * 0.25 * swayWave;
        
        pos.x += bendX;
        pos.z += bendZ;
        
        // Compensate height
        float bendMagnitude = sqrt(bendX * bendX + bendZ * bendZ);
        pos.y -= bendMagnitude * 0.2;
        
        // Align blade to surface normal (for tunnel mode, points toward center)
        mat3 normalAlign = alignToNormal(bladeNormal);
        pos = normalAlign * pos;
        
        // Apply world offset
        pos += offset;
        
        // Apply tunnel displacement (grass follows the mesh distortion)
        vec3 dispOffset = calcTunnelDisplacement(offset);
        pos += dispOffset;
        
        // Calculate normal
        vec3 bentNormal = normalize(normalAlign * rotateY(rotation) * vec3(-bendX * 0.3, 1.0, -bendZ * 0.3));
        vNormal = bentNormal;
        
        vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
        vFogDepth = -mvPosition.z;
        
        gl_Position = projectionMatrix * mvPosition;
      }
    `,
    fragmentShader: `
      uniform vec3 uBaseColor;
      uniform vec3 uMidColor;
      uniform vec3 uTipColor;
      uniform vec3 uLightDir;
      uniform float uAmbient;
      uniform float uAOStrength;
      uniform vec3 uFogColor;
      uniform float uFogNear;
      uniform float uFogFar;
      
      varying vec2 vUv;
      varying vec3 vNormal;
      varying vec3 vColorVar;
      varying float vHeight;
      varying float vFogDepth;

  void main() {
        // 3-stop gradient: base -> mid -> tip
        vec3 grassColor;
        if (vHeight < 0.5) {
          grassColor = mix(uBaseColor, uMidColor, vHeight * 2.0);
        } else {
          grassColor = mix(uMidColor, uTipColor, (vHeight - 0.5) * 2.0);
        }
        
        // Apply color variation
        grassColor += vColorVar * 0.12;
        
        // Ambient occlusion at base (darker near ground)
        float ao = 1.0 - (1.0 - vHeight) * (1.0 - vHeight) * uAOStrength;
        
        // Lighting
        float NdotL = max(dot(vNormal, uLightDir), 0.0);
        float diff = NdotL * 0.55;
        
        // Subsurface scattering approximation (light through grass)
        float sss = pow(max(0.0, dot(-vNormal, uLightDir)), 2.0) * 0.3 * vHeight;
        
        // Rim lighting for depth
        float rim = pow(1.0 - abs(dot(normalize(vNormal), vec3(0.0, 0.0, 1.0))), 3.0) * 0.15;
        
        float light = uAmbient + diff + sss + rim;
        light *= ao;
        
        // Add subtle brightness variation at tips
        light += vHeight * vHeight * 0.1;
        
        vec3 finalColor = grassColor * light;
        
        // Apply fog
        float fogFactor = smoothstep(uFogNear, uFogFar, vFogDepth);
        finalColor = mix(finalColor, uFogColor, fogFactor);
        
        gl_FragColor = vec4(finalColor, 1.0);
      }
    `,
    side: THREE.DoubleSide,
  });

  // Create instanced mesh
  const instancedMesh = new THREE.InstancedMesh(bladeGeo, grassMaterial, count);
  
  // Instance attributes
  const offsets = new Float32Array(count * 3);
  const scales = new Float32Array(count);
  const rotations = new Float32Array(count);
  const colorVars = new Float32Array(count * 3);
  const bladePhases = new Float32Array(count);
  
  // Optional clumping
  const clumping = config.density?.clumping || 0;
  const falloff = config.density?.falloff || 1;
  
  // Support different placement modes
  // Additional attribute for tunnel mode - stores the normal direction for each blade
  const normals = new Float32Array(count * 3);
  
  for (let i = 0; i < count; i++) {
    let x, y, z;
    let nx = 0, ny = 1, nz = 0; // Normal direction (up for plane, toward center for tunnel)
    
    if (isTunnelMode) {
      // Tunnel mode: grass grows from inside of a cylinder along Z axis
      const theta = Math.random() * Math.PI * 2; // Angle around cylinder
      z = (Math.random() - 0.5) * tunnelLength + tunnelOffsetZ; // Position along tunnel
      
      // Position on inside surface of cylinder (XY plane forms the circular cross-section)
      x = Math.cos(theta) * tunnelRadius;
      y = Math.sin(theta) * tunnelRadius;
      
      // Normal points toward center (inward) - grass grows toward center
      nx = -Math.cos(theta);
      ny = -Math.sin(theta);
      nz = 0;
      
    } else {
      // Plane mode (default): grass on XZ plane
      if (clumping > 0 && Math.random() < clumping) {
        // Clump around a random center
        const clumpX = (Math.random() - 0.5) * areaSize;
        const clumpZ = (Math.random() - 0.5) * areaSize;
        x = clumpX + (Math.random() - 0.5) * 3;
        z = clumpZ + (Math.random() - 0.5) * 3;
      } else {
        x = (Math.random() - 0.5) * areaSize;
        z = (Math.random() - 0.5) * areaSize;
      }
      y = 0;
      nx = 0; ny = 1; nz = 0;
    }
    
    offsets[i * 3] = x;
    offsets[i * 3 + 1] = y;
    offsets[i * 3 + 2] = z;
    
    normals[i * 3] = nx;
    normals[i * 3 + 1] = ny;
    normals[i * 3 + 2] = nz;
    
    // Random scale (height variation)
    if (isTunnelMode) {
      scales[i] = 0.5 + Math.random() * heightVar * 2;
    } else {
      // Distance falloff for plane mode
      const distFromCenter = Math.sqrt(x * x + z * z) / (areaSize * 0.5);
      const distFactor = 1.0 - distFromCenter * falloff * 0.3;
      scales[i] = (0.5 + Math.random() * heightVar * 2) * Math.max(0.4, distFactor);
    }
    
    // Random rotation around blade's up axis
    rotations[i] = Math.random() * Math.PI * 2;
    
    // Random color variation
    colorVars[i * 3] = (Math.random() - 0.5) * colorVar;
    colorVars[i * 3 + 1] = (Math.random() - 0.5) * colorVar;
    colorVars[i * 3 + 2] = (Math.random() - 0.5) * colorVar;
    
    // Random phase offset for wind (prevents synchronization)
    bladePhases[i] = Math.random() * Math.PI * 2;
  }
  
  // Add instance attributes
  instancedMesh.geometry.setAttribute('offset', new THREE.InstancedBufferAttribute(offsets, 3));
  instancedMesh.geometry.setAttribute('scale', new THREE.InstancedBufferAttribute(scales, 1));
  instancedMesh.geometry.setAttribute('rotation', new THREE.InstancedBufferAttribute(rotations, 1));
  instancedMesh.geometry.setAttribute('colorVariation', new THREE.InstancedBufferAttribute(colorVars, 3));
  instancedMesh.geometry.setAttribute('bladePhase', new THREE.InstancedBufferAttribute(bladePhases, 1));
  instancedMesh.geometry.setAttribute('bladeNormal', new THREE.InstancedBufferAttribute(normals, 3));
  
  // Dummy matrix for InstancedMesh (not really used since we handle transforms in shader)
  const dummy = new THREE.Matrix4();
  for (let i = 0; i < count; i++) {
    dummy.identity();
    instancedMesh.setMatrixAt(i, dummy);
  }
  instancedMesh.instanceMatrix.needsUpdate = true;
  
  // Disable frustum culling - we handle positions in shader so bounding box is wrong
  instancedMesh.frustumCulled = false;
  
  scene.add(instancedMesh);
  
  // Return the mesh directly - material is accessible via mesh.material
  return instancedMesh;
}
