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

/**
 * Procedural Texture Generator for Fungal Caves.
 * Dependency Free. Uses OffscreenCanvas for worker compatibility.
 */
export class DampCaveTextureGenerator {
  constructor(seed) {
    // Initialize our custom noise with the seed
    // This acts as the "Master" noise for edges/borders
    this.simplex = new SimplexNoise(seed);
  }

  /**
   * Generates the Floor Textures (Damp Stone + Moss + Puddles).
   * Returns an object containing diffuse, normal, roughness, and bump maps.
   * @param {number} size - Resolution (e.g. 64, 128)
   * @param {number|null} variantSeed - Optional seed for the center of the tile.
   * If provided, edges match Master seed, center matches Variant.
   */
  generateFloor(size = 64, variantSeed = null) {
    // Prepare Canvases
    const maps = {
      diffuse: 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);
    }

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

    for (let x = 0; x < size; x++) {
      for (let y = 0; y < size; y++) {
        // Normalize coordinates
        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. Base Rock (Fractal Noise)
        // Pass our noiseFn to fbm
        let rockVal = this.fbm(nx * 4, ny * 4, 3, noiseFn);

        // Colors (Dark Grey)
        const r = 40 + rockVal * 30;
        const g = 40 + rockVal * 30;
        const b = 45 + rockVal * 35;

        // 2. Moss (High Frequency)
        let mossVal = noiseFn(nx * 10 + 100, ny * 10 + 100);
        let isMoss = mossVal > 0.4;

        // 3. Wetness (Low spots)
        let wetVal = noiseFn(nx * 2 + 500, ny * 2 + 500);
        // Invert logic: low rockVal means a "dip" in the stone
        let isWet = wetVal > 0.5 && rockVal < -0.2;

        // Height Calc
        let height = rockVal * 0.5 + 0.5; // Base height 0-1
        if (isMoss) height += 0.1; // Moss sits on top
        if (isWet) height = 0.4; // Water is flat and low

        heightBuffer[x + y * size] = height;

        // Roughness Calc
        let roughness = 200; // Rock is rough
        if (isMoss) roughness = 255; // Moss is very rough/matte
        if (isWet) roughness = 20; // Water is very smooth/shiny

        // Write Pixel Data
        if (isWet) {
          datas.diffuse.data[index] = r * 0.6; // Darker
          datas.diffuse.data[index + 1] = g * 0.6;
          datas.diffuse.data[index + 2] = b * 0.8; // Blue tint
          datas.diffuse.data[index + 3] = 255;
        } else if (isMoss) {
          datas.diffuse.data[index] = 50 + mossVal * 20;
          datas.diffuse.data[index + 1] = 100 + mossVal * 40;
          datas.diffuse.data[index + 2] = 50;
          datas.diffuse.data[index + 3] = 255;
        } else {
          datas.diffuse.data[index] = r;
          datas.diffuse.data[index + 1] = g;
          datas.diffuse.data[index + 2] = b;
          datas.diffuse.data[index + 3] = 255;
        }

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

        // Bump Map (Grayscale Height)
        const bumpByte = Math.floor(height * 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
    for (let x = 0; x < size; x++) {
      for (let y = 0; y < size; y++) {
        const index = (x + y * size) * 4;

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

        const strength = 2.0; // Normal map intensity
        const dx =
          (heightBuffer[x1 + y * size] - heightBuffer[x0 + y * size]) *
          strength;
        const dy =
          (heightBuffer[x + y1 * size] - heightBuffer[x + y0 * size]) *
          strength;

        let nx = -dx;
        let ny = -dy;
        let nz = 1.0;

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

        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; // Returns approx -1 to 1
  }
}
