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

/**
 * Procedural Texture Generator for the Rusting Wastes Walls.
 * Generates horizontal metal plating, rivets, and vertical grime streaks.
 * Dependency Free. Uses OffscreenCanvas.
 */
export class RustedWallTextureGenerator {
  constructor(seed) {
    this.simplex = new SimplexNoise(seed);
  }

  /**
   * Generates the Wall Textures (Industrial Plating).
   * Returns an object containing diffuse, normal, roughness, and bump maps.
   * @param {number} size - Resolution
   * @param {number|null} variantSeed - Optional seed for rust/grime variation
   */
  generateWall(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 detailNoise = this.simplex;
    if (variantSeed !== null) {
      detailNoise = new SimplexNoise(variantSeed);
    }

    // Configuration
    const PANEL_HEIGHT = 16; // Horizontal stripes
    const BOLT_SPACING = 16;

    // Colors
    const COL_METAL_BASE = { r: 90, g: 95, b: 100 };
    const COL_RUST = { r: 140, g: 70, b: 30 };
    const COL_GRIME = { r: 30, g: 30, b: 30 };

    // Height Buffer
    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;
        const nx = x / size;
        const ny = y / size;

        // --- STRUCTURE (Panels & Bolts) ---

        // Panel Logic (Horizontal strips)
        const panelY = y % PANEL_HEIGHT;
        // Seam is at the bottom of the panel
        const isSeam = panelY >= PANEL_HEIGHT - 2;

        // Bolt Logic (Regular interval along top of panel)
        const boltX = x % BOLT_SPACING;
        // Bolts appear near the top of the panel (panelY < 4) centered on X spacing
        const isBolt = panelY < 4 && Math.abs(boltX - BOLT_SPACING / 2) < 2;

        // Base Height
        let h = 0.5;
        if (isSeam) h = 0.2; // Recessed gap
        if (isBolt) h = 0.85; // Protruding bolt

        // --- MATERIAL (Rust & Grime) ---

        // Rust (Large patches)
        let rustVal = detailNoise.noise2D(nx * 3, ny * 3);
        const isRust = rustVal > 0.4;

        // Grime (Vertical streaks - stretched Y frequency)
        let grimeVal = detailNoise.noise2D(nx * 10, ny * 2);
        const isGrime = grimeVal > 0.6 && !isRust; // Rust covers grime

        // Diffuse Color
        let r, g, b;

        if (isSeam) {
          r = 15;
          g = 15;
          b = 20;
        } else if (isBolt) {
          r = 120;
          g = 125;
          b = 130; // Shiny bolt
        } else if (isRust) {
          const mix = (rustVal - 0.4) * 2.0;
          r = this.lerp(COL_RUST.r, 60, mix); // Fade to dark
          g = this.lerp(COL_RUST.g, 30, mix);
          b = this.lerp(COL_RUST.b, 10, mix);

          // Texture bump for rust
          h += 0.05 * detailNoise.noise2D(nx * 20, ny * 20);
        } else if (isGrime) {
          r = COL_GRIME.r;
          g = COL_GRIME.g;
          b = COL_GRIME.b;
        } else {
          r = COL_METAL_BASE.r;
          g = COL_METAL_BASE.g;
          b = COL_METAL_BASE.b;
          // Subtle vertical brushing
          h += 0.02 * Math.random();
        }

        // Roughness
        let rough = 100; // Base Metal
        if (isRust) rough = 240; // Rough
        if (isBolt) rough = 50; // Shiny
        if (isGrime) rough = 180; // Dirty

        heightBuffer[x + y * size] = h;

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

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

        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 = 5.0; // Stronger normal map for industrial look
    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;
  }
}
