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

/**
 * Generates textures for the Crystal Spires biome.
 * Focus: Clean, geometric, and high-contrast looks.
 */
export class CrystalSpiresTextureGenerator {
  constructor(seed = 12345) {
    this.seed = seed;
    this.simplex = new SimplexNoise(seed);
  }

  /**
   * Helper: Fractal Brownian Motion
   */
  fbm(x, y, octaves, noiseFn = null) {
    let val = 0;
    let freq = 1;
    let amp = 0.5;
    const n = noiseFn || ((nx, ny) => this.simplex.noise2D(nx, ny));

    for (let i = 0; i < octaves; i++) {
      val += n(x * freq, y * freq) * amp;
      freq *= 2;
      amp *= 0.5;
    }
    return val; // -1 to 1 approx
  }

  lerp(a, b, t) {
    return a + (b - a) * t;
  }

  /**
   * Generates a Smooth Marble Texture (for Pillars/Platforms)
   * ID: 1
   */
  generateMarble(size = 64) {
    const maps = this.createMaps(size);
    const { ctxs, datas } = maps;

    const COLOR_BASE = { r: 250, g: 250, b: 255 }; // Almost pure white
    const COLOR_VEIN = { r: 200, g: 200, b: 210 }; // Very faint light grey veins

    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;

        // Warped noise for marble veins
        const warpX = this.fbm(nx * 4, ny * 4, 2);
        const warpY = this.fbm(nx * 4 + 10, ny * 4 + 10, 2);

        let noiseVal = this.simplex.noise2D(
          nx * 4 + warpX * 0.5,
          ny * 4 + warpY * 0.5
        );
        noiseVal = Math.abs(noiseVal); // Sharp veins (0 at center)
        noiseVal = Math.pow(noiseVal, 0.5); // Widen/Soften

        let r = this.lerp(COLOR_VEIN.r, COLOR_BASE.r, noiseVal);
        let g = this.lerp(COLOR_VEIN.g, COLOR_BASE.g, noiseVal);
        let b = this.lerp(COLOR_VEIN.b, COLOR_BASE.b, noiseVal);

        // Smooth surface
        let rough = 20 + noiseVal * 50;

        // Emissive: none
        let er = 0,
          eg = 0,
          eb = 0;

        // Normal/Height
        let h = noiseVal; // Veins are deeper (low val) if we invert?
        // noiseVal is 0 at vein center (darker). So Height 0 = Deep.

        this.setPixel(datas.diffuse, index, r, g, b, 255);
        this.setPixel(datas.emissive, index, er, eg, eb, 255);
        this.setPixel(datas.roughness, index, rough, rough, rough, 255);
        this.setPixel(datas.bump, index, h * 255, h * 255, h * 255, 255);

        // Rough Approx Normal
        this.setPixel(datas.normal, index, 128, 128, 255, 255);
      }
    }

    this.putImages(ctxs, datas);
    return maps.canvases;
  }

  /**
   * Generates a Faceted Crystal Texture (for Shards)
   * ID: 15
   */
  generateCrystal(size = 64) {
    const maps = this.createMaps(size);
    const { ctxs, datas } = maps;

    // Voronoi-like cellular noise logic (simplified manually or usually separate lib)
    // We'll simulate facets with angled gradients or rigid noise

    // Biome Spec: "raw blue Aether crystal"
    const COLOR_CRYSTAL = { r: 0, g: 40, b: 220 }; // Deep Blue
    const COLOR_HIGHLIGHT = { r: 200, g: 220, b: 255 }; // White/Light Blue Highlight

    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;

        // Rigid noise: |noise| * scale
        // Large shapes
        let facet = Math.abs(this.simplex.noise2D(nx * 3, ny * 3));
        // Add smaller facets
        facet += Math.abs(this.simplex.noise2D(nx * 10, ny * 10)) * 0.5;
        facet = Math.min(1, facet);

        // Color
        // Color (Deep Blue Base)
        let r = COLOR_CRYSTAL.r;
        let g = COLOR_CRYSTAL.g;
        let b = COLOR_CRYSTAL.b;

        // Add Facet Highlights (White edges/centers)
        // High facet values get white mix
        if (facet > 0.7) {
          const t = (facet - 0.7) / 0.3; // 0 to 1
          r = this.lerp(r, COLOR_HIGHLIGHT.r, t);
          g = this.lerp(g, COLOR_HIGHLIGHT.g, t);
          b = this.lerp(b, COLOR_HIGHLIGHT.b, t);
        } else {
          // Darker gradients for modulation
          const shade = 0.5 + facet * 0.5;
          r *= shade;
          g *= shade;
          b *= shade;
        }

        // High Emissive
        let er = r;
        let eg = g;
        let eb = b;

        // Very smooth
        let rough = 10;

        this.setPixel(datas.diffuse, index, r, g, b, 255);
        this.setPixel(datas.emissive, index, er, eg, eb, 255);
        this.setPixel(datas.roughness, index, rough, rough, rough, 255);
        this.setPixel(
          datas.bump,
          index,
          facet * 255,
          facet * 255,
          facet * 255,
          255
        );
        this.setPixel(datas.normal, index, 128, 128, 255, 255);
      }
    }

    this.putImages(ctxs, datas);
    return maps.canvases;
  }

  /**
   * Generates Holographic Bridge Texture
   * ID: 20
   */
  generateBridge(size = 64) {
    const maps = this.createMaps(size);
    const { ctxs, datas } = maps;

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

        // Grid pattern
        const gridSpace = 8;
        const lineThick = 1;
        const onGrid = x % gridSpace < lineThick || y % gridSpace < lineThick;

        let r = 0,
          g = 0,
          b = 0;

        if (onGrid) {
          r = 100;
          g = 200;
          b = 255;
        } else {
          // Faint body
          r = 20;
          g = 40;
          b = 50;
        }

        const er = r;
        const eg = g;
        const eb = b;

        this.setPixel(datas.diffuse, index, r, g, b, 200); // Semi-transparent?
        this.setPixel(datas.emissive, index, er, eg, eb, 255);
        this.setPixel(datas.roughness, index, 0, 0, 0, 255); // Glossy
        this.setPixel(datas.bump, index, onGrid ? 255 : 0, 0, 0, 255);
        this.setPixel(datas.normal, index, 128, 128, 255, 255);
      }
    }

    this.putImages(ctxs, datas);
    return maps.canvases;
  }

  createMaps(size) {
    const canvases = {
      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 k in canvases) {
      ctxs[k] = canvases[k].getContext("2d");
      datas[k] = ctxs[k].createImageData(size, size);
    }
    return { canvases, ctxs, datas };
  }

  setPixel(imgData, index, r, g, b, a) {
    imgData.data[index] = r;
    imgData.data[index + 1] = g;
    imgData.data[index + 2] = b;
    imgData.data[index + 3] = a;
  }

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