import { expect } from "@esm-bundle/chai";
import { CaveGenerator } from "../../src/generation/CaveGenerator.js";
import { VoxelGrid } from "../../src/grid/VoxelGrid.js";

// Mock OffscreenCanvas for texture generation
if (typeof OffscreenCanvas === "undefined") {
  class MockCanvas {
    constructor(width, height) {
      this.width = width;
      this.height = height;
    }
    getContext() {
      return {
        createImageData: (w, h) => ({
          data: new Uint8ClampedArray(w * h * 4),
        }),
        putImageData: () => {},
        drawImage: () => {},
        fillStyle: "",
        fillRect: () => {},
      };
    }
  }
  globalThis.OffscreenCanvas = MockCanvas;
}

describe("Generation: CaveGenerator", () => {
  let grid;
  let generator;

  beforeEach(() => {
    grid = new VoxelGrid(20, 10, 20);
    generator = new CaveGenerator(grid, 12345);
  });

  it("CoA 1: Should initialize with texture generators", () => {
    expect(generator.floorGen).to.exist;
    expect(generator.wallGen).to.exist;
    expect(generator.generatedAssets).to.have.property("palette");
  });

  it("CoA 2: preloadTextures should generate texture palette", () => {
    generator.preloadTextures();

    // Should have wall variations (100-109)
    expect(generator.generatedAssets.palette[100]).to.exist;
    expect(generator.generatedAssets.palette[109]).to.exist;

    // Should have floor variations (200-209)
    expect(generator.generatedAssets.palette[200]).to.exist;
    expect(generator.generatedAssets.palette[209]).to.exist;
  });

  it("CoA 3: generate should create foundation layer", () => {
    generator.generate(0.5, 2);

    // Foundation (y=0) should be solid
    for (let x = 0; x < grid.size.x; x++) {
      for (let z = 0; z < grid.size.z; z++) {
        expect(grid.getCell(x, 0, z)).to.not.equal(0);
      }
    }
  });

  it("CoA 4: generate should create extruded walls", () => {
    generator.generate(0.5, 2);

    // Check a spot that is a wall (has block at y=5 for example)
    // It should be solid all the way down to y=0
    let foundWall = false;
    for (let x = 0; x < grid.size.x; x++) {
      for (let z = 0; z < grid.size.z; z++) {
        const midY = Math.floor(grid.size.y / 2);
        if (grid.getCell(x, midY, z) !== 0) {
          foundWall = true;
          // Valid wall stack
          expect(grid.getCell(x, 0, z)).to.not.equal(0);
          expect(grid.getCell(x, 1, z)).to.not.equal(0);
        }
      }
    }
    expect(foundWall).to.be.true;
  });

  it("CoA 5: smoothMap should apply 2D cellular automata", () => {
    // 5x5 Map
    // 1 1 1 0 0
    // 1 0 1 0 0
    // 1 1 1 0 0
    // 0 0 0 0 0
    // 0 0 0 0 0
    // Center (2, 2) has neighbors: (1,1)=0, (1,2)=1, (1,3)=0, (2,1)=1, (2,3)=1, (3,1)=0, (3,2)=0, (3,3)=0
    // Total 1s = 3. Less than 4 -> should become 0.

    // Actually let's just test simple behavior: dense area stays dense, sparse area clears.
    let map = [
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [1, 1, 1, 1, 1],
      [0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0],
    ];
    // Mock dimensions temporarily for this test if smoothMap uses this.width/depth
    // implementation uses map.length so it might be fine or we need to override width/depth
    // My implementation used `this.width`. I should mock it or use the real grid size if I constructed it right.
    // The real grid is 20x10x20.
    // Let's use a full size map initialized to match grid size to avoid bounds errors.

    map = [];
    for (let x = 0; x < 20; x++) {
      map[x] = [];
      for (let z = 0; z < 20; z++) {
        map[x][z] = x < 10 && z < 10 ? 1 : 0; // Block of walls
      }
    }

    // Creating a hole in the wall block
    map[5][5] = 0;
    // Surrounded by 1s (8 neighbors). Should become 1.

    const newMap = generator.smoothMap(map);
    // (5,5) should be filled
    expect(newMap[5][5]).to.equal(1);
  });

  it("CoA 6: applyTextures should assign floor and wall IDs", () => {
    // Setup manual grid state replicating generate() output
    // Wall at 5,5 (Column)
    for (let y = 0; y < grid.size.y; y++) {
      grid.setCell(5, y, 5, 100);
    }

    // Floor at 6,6 (Only y=0)
    grid.setCell(6, 0, 6, 100);
    // y=1 is air
    grid.setCell(6, 1, 6, 0);

    generator.applyTextures();

    // Wall Check
    const wallId = grid.getCell(5, 5, 5);
    expect(wallId).to.be.greaterThanOrEqual(100);
    expect(wallId).to.be.lessThanOrEqual(109);

    // Floor Check
    const floorId = grid.getCell(6, 0, 6);
    expect(floorId).to.be.greaterThanOrEqual(200);
    expect(floorId).to.be.lessThanOrEqual(209);
  });

  it("CoA 7: generate should scatter cover objects", () => {
    generator.generate(0.5, 2);

    // Check for cover objects (ID 10)
    let coverCount = 0;
    for (let x = 0; x < grid.size.x; x++) {
      for (let z = 0; z < grid.size.z; z++) {
        for (let y = 0; y < grid.size.y; y++) {
          if (grid.getCell(x, y, z) === 10) {
            coverCount++;
          }
        }
      }
    }

    // Should have some cover objects
    expect(coverCount).to.be.greaterThan(0);
  });

  it("CoA 8: generate with same seed should produce consistent results", () => {
    const grid1 = new VoxelGrid(20, 10, 20);
    const gen1 = new CaveGenerator(grid1, 12345);
    gen1.generate(0.5, 2);

    const grid2 = new VoxelGrid(20, 10, 20);
    const gen2 = new CaveGenerator(grid2, 12345);
    gen2.generate(0.5, 2);

    // Same seed should produce same results
    expect(grid1.cells).to.deep.equal(grid2.cells);
  });

  it("CoA 9: ensureConnectivity should remove disconnected small regions", () => {
    // Create a map with two disconnected regions
    // Region A: 3x3 (Total 9)
    // Region B: 1x1 (Total 1)
    let map = [];
    for (let x = 0; x < 20; x++) {
      map[x] = [];
      for (let z = 0; z < 20; z++) {
        map[x][z] = 1; // Walls everywhere
      }
    }

    // Region A (Large)
    for (let x = 1; x <= 3; x++) {
      for (let z = 1; z <= 3; z++) {
        map[x][z] = 0;
      }
    }

    // Region B (Small, disconnected)
    map[10][10] = 0;

    const connectedMap = generator.ensureConnectivity(map);

    // Region A should remain
    expect(connectedMap[2][2]).to.equal(0);

    // Region B should be filled
    expect(connectedMap[10][10]).to.equal(1);
  });

  it("CoA 10: scatterWallDetails should place objects on wall faces", () => {
    // Setup a single wall column at 5,5 from y=0 to y=10
    // And air at 6,5 (adjacent)
    for (let y = 0; y < grid.size.y; y++) {
      grid.setCell(5, y, 5, 100);
      grid.setCell(6, y, 5, 0);
    }

    // Force seed/density to ensure placement
    // Or just check that it CAN place
    // Let's rely on randomness with a loop or high density
    generator.scatterWallDetails(11, 1.0, 2); // 100% density for test

    // Check y=2 to y=height-2 (safe range)
    let foundDetail = false;
    for (let y = 2; y < grid.size.y - 1; y++) {
      const id = grid.getCell(6, y, 5);
      if (id === 11) foundDetail = true;
    }
    expect(foundDetail).to.be.true;

    // Ensure it didn't place below minHeight (y=1)
    expect(grid.getCell(6, 1, 5)).to.equal(0);
  });
});
