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

/**
 * Procedural Texture Generator for the Rusting Wastes.
 * Generates tiled metal plating with rust patches and rivets.
 * Dependency Free. Uses OffscreenCanvas.
 */
export class RustedFloorTextureGenerator {
  constructor(seed) {
    this.simplex = new SimplexNoise(seed);
  }

  /**
   * Generates the Floor Textures (Rusted Metal Plates).
   * Returns an object containing diffuse, normal, roughness, and bump maps.
   * @param {number} size - Resolution
   * @param {number|null} variantSeed - Optional seed for rust pattern variation
   */
  generateFloor(size = 64, variantSeed = null) {
    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
    let rustNoise = this.simplex;
    if (variantSeed !== null) {
      rustNoise = new SimplexNoise(variantSeed);
    }

    // Configuration
    const TILE_COUNT = 4; // 4x4 grid of plates
    const TILE_SIZE = size / TILE_COUNT;
    const SEAM_WIDTH = 2;
    const BOLT_OFFSET = 4;

    // Colors
    const COL_METAL_BASE = { r: 100, g: 105, b: 110 };
    const COL_RUST_LIGHT = { r: 160, g: 90, b: 40 };
    const COL_RUST_DARK = { r: 90, g: 50, b: 20 };

    // Height Buffer for Normal Calculation
    const heightBuffer = new Float32Array(size * size);

    for (let x = 0; x < size; x++) {
      for (let y = 0; y < size; y++) {
        const index = (x + y * size) * 4;

        // --- GEOMETRY (Plates & Bolts) ---

        // Tile Coordinates
        const tx = x % TILE_SIZE;
        const ty = y % TILE_SIZE;

        // Seams (Edges of tiles)
        const isSeam =
          tx < SEAM_WIDTH ||
          tx >= TILE_SIZE - SEAM_WIDTH ||
          ty < SEAM_WIDTH ||
          ty >= TILE_SIZE - SEAM_WIDTH;

        // Bolts (Corners of tiles)
        const distToCorner = Math.min(
          Math.abs(tx - BOLT_OFFSET) + Math.abs(ty - BOLT_OFFSET),
          Math.abs(tx - (TILE_SIZE - BOLT_OFFSET)) + Math.abs(ty - BOLT_OFFSET),
          Math.abs(tx - BOLT_OFFSET) + Math.abs(ty - (TILE_SIZE - BOLT_OFFSET)),
          Math.abs(tx - (TILE_SIZE - BOLT_OFFSET)) +
            Math.abs(ty - (TILE_SIZE - BOLT_OFFSET))
        );
        const isBolt = distToCorner < 3 && !isSeam;

        // Base Height
        let h = 0.5; // Base Plate
        if (isSeam) h = 0.1; // Recessed
        if (isBolt) h = 0.8; // Protruding

        // --- MATERIAL (Metal & Rust) ---

        // Rust Noise (Fractal)
        const nx = x / size;
        const ny = y / size;
        let rustVal =
          rustNoise.noise2D(nx * 4, ny * 4) +
          0.5 * rustNoise.noise2D(nx * 8, ny * 8);

        // Normalize roughly
        rustVal = (rustVal + 1) / 2; // 0 to 1

        const isRust = rustVal > 0.6; // Threshold

        // Diffuse Color
        let r, g, b;

        if (isSeam) {
          // Dark Seam
          r = 20;
          g = 20;
          b = 25;
        } else if (isRust) {
          // Rust Patch (Vary slightly based on noise)
          const rustMix = (rustVal - 0.6) * 2.5; // Strength
          r = this.lerp(COL_RUST_DARK.r, COL_RUST_LIGHT.r, rustMix);
          g = this.lerp(COL_RUST_DARK.g, COL_RUST_LIGHT.g, rustMix);
          b = this.lerp(COL_RUST_DARK.b, COL_RUST_LIGHT.b, rustMix);

          // Add texture bumps to rust
          h += 0.05 * rustNoise.noise2D(nx * 20, ny * 20);
        } else {
          // Clean Metal
          r = COL_METAL_BASE.r;
          g = COL_METAL_BASE.g;
          b = COL_METAL_BASE.b;

          // Subtle metal grain
          const grain = this.simplex.noise2D(nx * 50, ny * 50) * 10;
          r += grain;
          g += grain;
          b += grain;
        }

        // Roughness (Metal is shiny, Rust is matte)
        let rough = 80; // Metal
        if (isRust) rough = 220; // Rust
        if (isSeam) rough = 255; // Dirt in cracks

        heightBuffer[x + y * size] = h;

        // WRITE BUFFERS
        // 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] = rough;
        datas.roughness.data[index + 1] = rough;
        datas.roughness.data[index + 2] = rough;
        datas.roughness.data[index + 3] = 255;

        // Bump
        const bumpByte = Math.floor(h * 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: Normal Map Generation
    const strength = 4.0;
    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 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;
      }
    }

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

    return maps;
  }

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