import { BaseGenerator } from "./BaseGenerator.js";
import { DampCaveTextureGenerator } from "./textures/DampCaveTextureGenerator.js";
import { BioluminescentCaveWallTextureGenerator } from "./textures/BioluminescentCaveWallTextureGenerator.js";
import { SimplexNoise } from "../utils/SimplexNoise.js";

/**
 * Generates organic, open-topped caves using Cellular Automata.
 * Designed to create "Canyons" rather than "Tunnels" for better camera visibility.
 * Integrates procedural textures for floors and walls with variation.
 */
export class CaveGenerator extends BaseGenerator {
  constructor(grid, seed) {
    super(grid, seed);

    this.floorGen = new DampCaveTextureGenerator(seed);
    this.wallGen = new BioluminescentCaveWallTextureGenerator(seed);

    // Container for assets generated by this biome logic.
    // We preload 10 variations of floors and walls.
    // The VoxelManager will need to read this 'palette' to create materials.
    this.generatedAssets = {
      palette: {}, // Maps Voxel ID -> Texture Asset Config
      spawnZones: { player: [], enemy: [] },
    };

    this.preloadTextures();
  }

  /**
   * Pre-generates texture variations so we don't regenerate them per voxel.
   * Assigns them to specific Voxel IDs.
   */
  preloadTextures() {
    const VARIATIONS = 10;
    const TEXTURE_SIZE = 128;

    // 4. Generate Wall Textures
    // IDs: 100-107 (Plain), 108-109 (Veined)
    const wallGen = new BioluminescentCaveWallTextureGenerator(this.seed);

    // Plain Walls (Majority)
    for (let i = 0; i < 8; i++) {
      // disable veins for plain walls
      this.generatedAssets.palette[100 + i] = wallGen.generateWall(
        64,
        i,
        false
      );
    }
    // Veined Walls (Sparse)
    for (let i = 0; i < 2; i++) {
      // enable veins
      this.generatedAssets.palette[108 + i] = wallGen.generateWall(
        64,
        i + 10,
        true
      );
    }

    // 2. Preload Floor Variations (IDs 200 - 209)
    for (let i = 0; i < VARIATIONS; i++) {
      const floorSeed = this.rng.next() * 10000 + i;
      // Using the variantSeed support added previously to generateFloor
      this.generatedAssets.palette[200 + i] = this.floorGen.generateFloor(
        TEXTURE_SIZE,
        floorSeed
      );
    }
  }

  generate(fillPercent = 0.45, iterations = 4) {
    // 0. Decide Layout Topology
    // "Tunnel" = Open Z-min and Z-max
    // "Dead End" = Open Z-min only
    this.isTunnel = this.rng.next() > 0.5;

    // 1. Initialize 2D Map
    // 1 = Wall, 0 = Floor
    let map = [];
    for (let x = 0; x < this.width; x++) {
      map[x] = [];
      for (let z = 0; z < this.depth; z++) {
        // Border Logic
        const isSideBorder = x === 0 || x === this.width - 1;
        const isBackBorder = z === this.depth - 1;
        const isFrontBorder = z === 0;

        let isSolid = false;

        if (isSideBorder) {
          isSolid = true; // Sides are always walls (but low)
        } else if (isFrontBorder) {
          isSolid = false; // Entrance always open
        } else if (isBackBorder) {
          isSolid = !this.isTunnel; // Back open if tunnel, closed if dead end
        } else {
          // Internal
          isSolid = this.rng.chance(fillPercent);
        }

        map[x][z] = isSolid ? 1 : 0;
      }
    }

    // 2. Smooth 2D Map
    for (let i = 0; i < iterations; i++) {
      map = this.smoothMap(map);
    }

    // 3. Post-Process: Ensure Connectivity
    // Note: ensureConnectivity respects 0s, so open entrances remain open if connected
    map = this.ensureConnectivity(map);

    // 4. Extrude to 3D Voxel Grid
    for (let x = 0; x < this.width; x++) {
      for (let z = 0; z < this.depth; z++) {
        if (map[x][z] === 1) {
          // Wall Logic
          // Cap all walls to height 6 for visibility (cutoff view)
          const MAX_WALL_HEIGHT = 6;

          // Vary height slightly for "voxel aesthetic"
          // height-1 or height, but capped at MAX_WALL_HEIGHT
          const baseHeight = Math.min(this.height, MAX_WALL_HEIGHT);
          const wallHeight = baseHeight - (this.rng.next() > 0.5 ? 1 : 0);

          for (let y = 0; y < wallHeight; y++) {
            this.grid.setCell(x, y, z, 100);
          }
        } else {
          // Floor Logic
          // y=0 is foundation (always solid)
          this.grid.setCell(x, 0, z, 100);
        }
      }
    }

    // 5. Apply Texture/Material Logic
    this.applyTextures();

    // 6. Scatter Cover (Post-Texturing)
    // ID 10 = Destructible Cover (Mushrooms/Rocks)
    // 5% Density on floor tiles
    this.scatterCover(10, 0.05);

    // 7. Scatter Wall Details (Spores/Moss)
    // ID 11 = Wall Detail
    // 5% Density on wall surfaces above y=2
    this.scatterWallDetails(11, 0.05, 2);

    // 8. Define Deployment Zones
    // Player Z-min, Enemy Z-max
    this.defineSpawnZones();
  }

  defineSpawnZones() {
    // Scan Z < 5 for Player
    for (let x = 1; x < this.width - 1; x++) {
      for (let z = 1; z < 5; z++) {
        if (
          this.grid.getCell(x, 0, z) !== 0 &&
          this.grid.getCell(x, 1, z) === 0
        ) {
          this.generatedAssets.spawnZones.player.push({ x, y: 1, z });
        }
      }
    }

    // Scan Z > Depth - 5 for Enemy
    for (let x = 1; x < this.width - 1; x++) {
      for (let z = this.depth - 6; z < this.depth - 1; z++) {
        if (
          this.grid.getCell(x, 0, z) !== 0 &&
          this.grid.getCell(x, 1, z) === 0
        ) {
          this.generatedAssets.spawnZones.enemy.push({ x, y: 1, z });
        }
      }
    }
  }

  ensureConnectivity(map) {
    const regions = [];
    const visited = new Set();
    const floorCells = [];

    // 1. Identify all regions
    for (let x = 0; x < this.width; x++) {
      for (let z = 0; z < this.depth; z++) {
        if (map[x][z] === 0 && !visited.has(`${x},${z}`)) {
          const region = [];
          this.floodFill(map, x, z, visited, region);
          regions.push(region);
        }
      }
    }

    // If no regions (all walls?), return map as is or clear a center room.
    if (regions.length === 0) return map;

    // 2. Find Largest Region
    regions.sort((a, b) => {
      if (b.length !== a.length) {
        return b.length - a.length;
      }
      // Deterministic tie-breaker using first cell coordinates
      if (a.length > 0 && b.length > 0) {
        const keyA = a[0].x * 1000 + a[0].z;
        const keyB = b[0].x * 1000 + b[0].z;
        return keyB - keyA;
      }
      return 0;
    });
    const mainRegion = regions[0];

    // 3. Fill all others with walls
    // (We construct a new map where only mainRegion is 0, rest is 1)
    const newMap = [];
    for (let x = 0; x < this.width; x++) {
      newMap[x] = [];
      for (let z = 0; z < this.depth; z++) {
        newMap[x][z] = 1; // Default to wall
      }
    }

    for (const pos of mainRegion) {
      newMap[pos.x][pos.z] = 0;
    }

    return newMap;
  }

  floodFill(map, startX, startZ, visited, region) {
    const stack = [{ x: startX, z: startZ }];
    visited.add(`${startX},${startZ}`);

    while (stack.length > 0) {
      const { x, z } = stack.pop();
      region.push({ x, z });

      const neighbors = [
        { x: x + 1, z: z },
        { x: x - 1, z: z },
        { x: x, z: z + 1 },
        { x: x, z: z - 1 },
      ];

      for (const n of neighbors) {
        if (n.x >= 0 && n.x < this.width && n.z >= 0 && n.z < this.depth) {
          const key = `${n.x},${n.z}`;
          if (map[n.x][n.z] === 0 && !visited.has(key)) {
            visited.add(key);
            stack.push(n);
          }
        }
      }
    }
  }

  /**
   * Spreads details on wall surfaces.
   * Target: Air block that has at least one solid neighbor at the same Y level.
   * @param {number} objectId
   * @param {number} density
   * @param {number} minHeight
   */
  scatterWallDetails(objectId, density, minHeight = 2) {
    const validSpots = [];

    for (let x = 1; x < this.width - 1; x++) {
      for (let z = 1; z < this.depth - 1; z++) {
        for (let y = minHeight; y < this.height - 1; y++) {
          // Must be empty to place an object
          if (this.grid.getCell(x, y, z) !== 0) continue;

          // Check 4 horizontal neighbors for a wall
          // We only care if IT IS touching a wall
          if (
            this.grid.isSolid({ x: x + 1, y, z }) ||
            this.grid.isSolid({ x: x - 1, y, z }) ||
            this.grid.isSolid({ x, y, z: z + 1 }) ||
            this.grid.isSolid({ x, y, z: z - 1 })
          ) {
            validSpots.push({ x, y, z });
          }
        }
      }
    }

    for (const pos of validSpots) {
      if (this.rng.chance(density)) {
        this.grid.setCell(pos.x, pos.y, pos.z, objectId);
      }
    }
  }

  smoothMap(map) {
    const newMap = [];
    for (let x = 0; x < this.width; x++) {
      newMap[x] = [];
      for (let z = 0; z < this.depth; z++) {
        // Border Logic (Must match generate loop to avoid closing entrances)
        const isSideBorder = x === 0 || x === this.width - 1;
        const isFrontBorder = z === 0;
        const isBackBorder = z === this.depth - 1;

        if (isSideBorder) {
          newMap[x][z] = 1;
          continue;
        }
        if (isFrontBorder) {
          newMap[x][z] = 0;
          continue;
        }
        if (isBackBorder) {
          newMap[x][z] = this.isTunnel ? 0 : 1;
          continue;
        }

        const neighbors = this.get2DNeighbors(map, x, z);
        if (neighbors > 4) newMap[x][z] = 1;
        else if (neighbors < 4) newMap[x][z] = 0;
        else newMap[x][z] = map[x][z];
      }
    }
    return newMap;
  }

  get2DNeighbors(map, gridX, gridZ) {
    let count = 0;
    for (let i = -1; i <= 1; i++) {
      for (let j = -1; j <= 1; j++) {
        if (i === 0 && j === 0) continue;
        const checkX = gridX + i;
        const checkZ = gridZ + j;
        // Check bounds, out of bounds counts as wall
        if (
          checkX < 0 ||
          checkX >= this.width ||
          checkZ < 0 ||
          checkZ >= this.depth
        ) {
          count++;
        } else if (map[checkX][checkZ] === 1) {
          count++;
        }
      }
    }
    return count;
  }

  /**
   * Iterates through the grid to identify Floor and Wall voxels,
   * assigning randomized IDs from our preloaded palette.
   */
  applyTextures() {
    // Noise for vein distribution
    // Low frequency for large coherent patches
    const veinNoise = new SimplexNoise(this.seed + 999);

    for (let x = 0; x < this.width; x++) {
      for (let z = 0; z < this.depth; z++) {
        // Determine if this column is a "Vein Area"
        // Scale 0.1 gives features around 10 blocks wide, typical for "sections"
        const veinVal = veinNoise.noise2D(x * 0.1, z * 0.1);

        // Threshold: Only top 30% are veins (sparse)
        const isVeinArea = veinVal > 0.4;

        let variant;
        if (isVeinArea) {
          // Veined IDs: 108-109
          variant = 8 + this.rng.rangeInt(0, 1);
        } else {
          // Plain IDs: 100-107
          variant = this.rng.rangeInt(0, 7);
        }

        const isWallColumn = this.grid.getCell(x, 1, z) !== 0;
        if (isWallColumn) {
          for (let y = 0; y < this.height; y++) {
            if (this.grid.getCell(x, y, z) !== 0) {
              // Use the selected variant for the whole column
              this.grid.setCell(x, y, z, 100 + variant);
            }
          }
        } else {
          // Floors
          const floorVariant = this.rng.rangeInt(0, 9);
          this.grid.setCell(x, 0, z, 200 + floorVariant);
        }
      }
    }
  }
}
