import { SimplexNoise } from "../../utils/SimplexNoise.js";

/**
 * Procedural Texture Generator for Bioluminescent Cave Walls.
 * Dependency Free. Uses OffscreenCanvas for worker compatibility.
 */
export class BioluminescentCaveWallTextureGenerator {
  constructor(seed) {
    // Master noise instance for edges
    this.simplex = new SimplexNoise(seed);
  }

  /**
   * Generates the Wall Textures (Vertical Rock + Glowing Veins).
   * Returns an object containing diffuse, emissive, normal, roughness, and bump maps.
   * @param {number} size - Resolution
   * @param {number|null} variantSeed - Optional seed for center variation
   * @returns {{diffuse: OffscreenCanvas, emissive: OffscreenCanvas, normal: OffscreenCanvas, roughness: OffscreenCanvas, bump: OffscreenCanvas}}
   */
  generateWall(size = 64, variantSeed = null) {
    // Prepare Canvases
    const maps = {
      diffuse: new OffscreenCanvas(size, size),
      emissive: new OffscreenCanvas(size, size),
      normal: new OffscreenCanvas(size, size),
      roughness: new OffscreenCanvas(size, size),
      bump: new OffscreenCanvas(size, size),
    };

    const ctxs = {};
    const datas = {};

    for (const key in maps) {
      ctxs[key] = maps[key].getContext("2d");
      datas[key] = ctxs[key].createImageData(size, size);
    }

    // Initialize Variant Noise if a seed is provided
    let variantSimplex = null;
    if (variantSeed !== null) {
      variantSimplex = new SimplexNoise(variantSeed);
    }

    // Colors (Dark Rock) - Brightened base for visibility
    const COLOR_WALL_BASE = { r: 60, g: 60, b: 65 };
    const COLOR_WALL_HIGHLIGHT = { r: 90, g: 90, b: 95 };

    // Colors (Crystals) - Boosted brightness
    const COLOR_CRYSTAL_PURPLE = { r: 200, g: 80, b: 255 };
    const COLOR_CRYSTAL_CYAN = { r: 80, g: 255, b: 255 };

    // Height buffer for Normal Map calculation
    const heightBuffer = new Float32Array(size * size);

    // Pass 1: Generate Height/Color Data
    for (let x = 0; x < size; x++) {
      for (let y = 0; y < size; y++) {
        const nx = x / size;
        const ny = y / size;
        const index = (x + y * size) * 4;

        // --- EDGE BLENDING LOGIC ---
        // Calculate distance to nearest edge (0.0 to 0.5)
        const distToEdge = Math.min(nx, 1 - nx, ny, 1 - ny);

        // Define border thickness (e.g., 20% of texture size)
        const borderSize = 0.2;

        // Calculate mix factor (0 = Edge/Master, 1 = Center/Variant)
        let mix = Math.min(1, distToEdge / borderSize);
        // Smoothstep for seamless transition
        mix = mix * mix * (3 - 2 * mix);

        // Noise Function Wrapper
        // Samples Master or blends Master+Variant based on position
        const noiseFn = (freqX, freqY) => {
          const masterVal = this.simplex.noise2D(freqX, freqY);
          if (!variantSimplex) return masterVal;

          const variantVal = variantSimplex.noise2D(freqX, freqY);
          return masterVal * (1 - mix) + variantVal * mix;
        };

        // 1. Vertical Rock Strata (Base Structure)
        // Pass custom noise function
        let rockNoise = this.fbm(nx * 8, ny * 2, 3, noiseFn);

        // 1b. Micro-Detail Noise (High Frequency)
        // Adds grit and texture to the stone surface
        let detailNoise = noiseFn(nx * 32, ny * 32);
        rockNoise += detailNoise * 0.1;

        let r = this.lerp(COLOR_WALL_BASE.r, COLOR_WALL_HIGHLIGHT.r, rockNoise);
        let g = this.lerp(COLOR_WALL_BASE.g, COLOR_WALL_HIGHLIGHT.g, rockNoise);
        let b = this.lerp(COLOR_WALL_BASE.b, COLOR_WALL_HIGHLIGHT.b, rockNoise);

        // Emissive Defaults (Black)
        let er = 0,
          eg = 0,
          eb = 0;

        // Roughness Default (High for rock)
        let roughVal = 200;

        // Height Default (Based on rock noise)
        let heightVal = rockNoise * 0.5;

        // 2. Crystal Veins (Ridge Noise)
        // Sharpened ridge noise for clearer vein definition
        let rawVeinNoise = Math.abs(noiseFn(nx * 4 + 50, ny * 4 + 50));
        let veinNoise = 1.0 - Math.pow(rawVeinNoise, 0.5); // Sharpen curve

        // Threshold: Only draw where the 'ridge' is very sharp
        if (veinNoise > 0.9) {
          let colorSelector = noiseFn(nx * 2 + 100, ny * 2 + 100);
          let crystalColor =
            colorSelector > 0 ? COLOR_CRYSTAL_PURPLE : COLOR_CRYSTAL_CYAN;

          // Blend crystal color (Emissive style - bright) into diffuse
          r = this.lerp(r, crystalColor.r, 0.9);
          g = this.lerp(g, crystalColor.g, 0.9);
          b = this.lerp(b, crystalColor.b, 0.9);

          // Set Emissive Map Color (Pure crystal color)
          er = crystalColor.r;
          eg = crystalColor.g;
          eb = crystalColor.b;

          // Crystals are smooth
          roughVal = 20;
          // Crystals protrude slightly
          heightVal += 0.4;
        }

        // 3. Deep Cracks (Darker Ridge Noise)
        let crackNoise = 1.0 - Math.abs(noiseFn(nx * 10 + 200, ny * 10 + 200));
        if (crackNoise > 0.85 && veinNoise <= 0.9) {
          r *= 0.3;
          g *= 0.3;
          b *= 0.3; // Darker cracks
          heightVal -= 0.5; // Deeper cracks
          roughVal = 255; // Very rough
        }

        // Store height for Normal Map pass
        heightBuffer[x + y * size] = heightVal;

        // Write Diffuse
        datas.diffuse.data[index] = r;
        datas.diffuse.data[index + 1] = g;
        datas.diffuse.data[index + 2] = b;
        datas.diffuse.data[index + 3] = 255;

        // Write Emissive
        datas.emissive.data[index] = er;
        datas.emissive.data[index + 1] = eg;
        datas.emissive.data[index + 2] = eb;
        datas.emissive.data[index + 3] = 255;

        // Write Roughness (Grayscale)
        datas.roughness.data[index] = roughVal;
        datas.roughness.data[index + 1] = roughVal;
        datas.roughness.data[index + 2] = roughVal;
        datas.roughness.data[index + 3] = 255;

        // Write Bump (Heightmap grayscale)
        const bumpByte = Math.min(255, Math.max(0, heightVal * 255));
        datas.bump.data[index] = bumpByte;
        datas.bump.data[index + 1] = bumpByte;
        datas.bump.data[index + 2] = bumpByte;
        datas.bump.data[index + 3] = 255;
      }
    }

    // Pass 2: Generate Normal Map from Height Buffer
    // Sobel Filter approach
    for (let x = 0; x < size; x++) {
      for (let y = 0; y < size; y++) {
        const index = (x + y * size) * 4;

        // Get neighbors (clamp to edges)
        const x0 = Math.max(0, x - 1);
        const x1 = Math.min(size - 1, x + 1);
        const y0 = Math.max(0, y - 1);
        const y1 = Math.min(size - 1, y + 1);

        // Calculate slopes (Increased strength for more detail)
        const strength = 4.0;
        const dx =
          (heightBuffer[x1 + y * size] - heightBuffer[x0 + y * size]) *
          strength;
        const dy =
          (heightBuffer[x + y1 * size] - heightBuffer[x + y0 * size]) *
          strength;

        // Normalize vector (dx, dy, 1) to RGB
        // Normal is (-dx, -dy, 1) normalized
        let nx = -dx;
        let ny = -dy;
        let nz = 1.0;

        // Normalize
        const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
        nx /= len;
        ny /= len;
        nz /= len;

        // Map -1..1 to 0..255
        datas.normal.data[index] = (nx * 0.5 + 0.5) * 255;
        datas.normal.data[index + 1] = (ny * 0.5 + 0.5) * 255;
        datas.normal.data[index + 2] = (nz * 0.5 + 0.5) * 255;
        datas.normal.data[index + 3] = 255;
      }
    }

    // Finalize all canvases
    for (const key in maps) {
      ctxs[key].putImageData(datas[key], 0, 0);
    }

    return maps;
  }

  // Helper: Fractal Brownian Motion
  // Accepts an optional custom noise function to support blending
  fbm(x, y, octaves, noiseFn = null) {
    let value = 0;
    let amp = 0.5;
    let freq = 1.0;

    // Default to class simplex if no custom function provided
    const sample = noiseFn || ((sx, sy) => this.simplex.noise2D(sx, sy));

    for (let i = 0; i < octaves; i++) {
      value += sample(x * freq, y * freq) * amp;
      freq *= 2;
      amp *= 0.5;
    }
    return value + 0.5;
  }

  lerp(v0, v1, t) {
    return v0 * (1 - t) + v1 * t;
  }
}
