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

/**
 * Procedural Texture Generator for Contested Frontier.
 * Dependency Free. Uses OffscreenCanvas for worker compatibility.
 */
export class TrenchTextureGenerator {
  constructor(seed) {
    this.simplex = new SimplexNoise(seed);
  }

  /**
   * Generates the Ground Textures (Mud + Grass Patches).
   * @param {number} size - Resolution (e.g. 64, 128)
   * @param {number} bias - Bias for grass likelihood (-1.0 to 1.0). Negative = Muddy, Positive = Grassy.
   */
  generateFloor(size = 64, variantSeed = null, bias = 0.0) {
    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);
    }

    let variantSimplex = null;
    if (variantSeed !== null) {
      variantSimplex = new SimplexNoise(variantSeed);
    }

    const heightBuffer = new Float32Array(size * size);

    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;

        // Noise for Mud vs Grass distribution
        const noiseFn = (freqX, freqY) => {
          const val = this.simplex.noise2D(freqX, freqY);
          if (variantSimplex) {
            return (
              (val + variantSimplex.noise2D(freqX * 1.1, freqY * 1.1)) * 0.5
            );
          }
          return val;
        };

        // Base Terrain Noise
        let baseVal = this.fbm(nx * 3, ny * 3, 4, noiseFn);

        // Grass Noise (High Frequency)
        // Grass appears on "higher" drier spots
        let grassVal = this.fbm(nx * 10, ny * 10, 2, noiseFn);

        // Adjust threshold based on bias
        // Base required baseVal > 0.2.
        // With bias 0.5 (Grassy), we want easier grass -> require baseVal > 0.0
        // With bias -0.5 (Muddy), we want harder grass -> require baseVal > 0.4
        const grassThreshold = 0.2 - bias * 0.3;

        const isGrass = baseVal > grassThreshold && grassVal > -bias * 0.5;

        // Heights
        let height = baseVal * 0.5 + 0.5; // 0-1
        if (isGrass) height += 0.15; // Grass sits on top

        // Mud logic: Low spots are smoother and darker
        const isDeepMud = baseVal < -0.3;
        if (isDeepMud) {
          height *= 0.8; // Sink a bit
        }

        heightBuffer[x + y * size] = height;

        // Colors
        let r, g, b, roughness;

        if (isGrass) {
          // Dead/Dying Grass Green/Brown
          r = 60 + grassVal * 40;
          g = 80 + grassVal * 40;
          b = 40 + grassVal * 20;
          roughness = 240; // Rough
        } else if (isDeepMud) {
          // Wet Mud (Darker, Saturated)
          r = 50 + baseVal * 20;
          g = 40 + baseVal * 20;
          b = 30 + baseVal * 10;
          roughness = 40; // Wet/Shiny
        } else {
          // Dried Dirt (Lighter Brown)
          r = 90 + baseVal * 30;
          g = 75 + baseVal * 30;
          b = 60 + baseVal * 30;
          roughness = 200; // Dry
        }

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

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

        // Bump
        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;
      }
    }

    // Normal Map Calculation
    this.computeNormalMap(datas.normal, heightBuffer, size);

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

    return maps;
  }

  /**
   * Generates Wall Textures (Trench Walls - Wood/Dirt mix).
   */
  generateWall(size = 64, variantSeed = null) {
    // Simplified: Just use dirt texture for now, maybe add wooden planks later if needed
    // For now, reusing floor generation logic but tweaking colors for side walls
    // Walls are usually muddy so we pass a negative bias
    return this.generateFloor(size, variantSeed, -0.5);
  }

  computeNormalMap(targetData, heightBuffer, size) {
    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 = 3.0;
        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);
        targetData.data[index] = ((nx / len) * 0.5 + 0.5) * 255;
        targetData.data[index + 1] = ((ny / len) * 0.5 + 0.5) * 255;
        targetData.data[index + 2] = ((nz / len) * 0.5 + 0.5) * 255;
        targetData.data[index + 3] = 255;
      }
    }
  }

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