import { type World, type EntityId, type RunState, type Tile, type Actor, type Vec2 } from "../../core/types";
import { TileType } from "../../core/terrain";
import { idx } from "./world-logic";
import { GAME_CONFIG } from "../../core/config/GameConfig";
import { seededRandom } from "../../core/math";
import * as ROT from "rot-js";

interface Room {
  x: number;
  y: number;
  width: number;
  height: number;
}

/**
 * Generates a procedural dungeon world with rooms and corridors using rot-js Uniform algorithm
 * @param floor The floor number (affects difficulty)
 * @param runState Player's persistent state across floors
 * @returns Generated world and player ID
 */
export function generateWorld(floor: number, runState: RunState): { world: World; playerId: EntityId } {
  const width = GAME_CONFIG.map.width;
  const height = GAME_CONFIG.map.height;
  const tiles: Tile[] = new Array(width * height).fill(TileType.WALL);

  const random = seededRandom(floor * 12345);

  // Set ROT's RNG seed for consistent dungeon generation
  ROT.RNG.setSeed(floor * 12345);

  const rooms = generateRooms(width, height, tiles, floor);
  
  // Place player in first room
  const firstRoom = rooms[0];
  const playerX = firstRoom.x + Math.floor(firstRoom.width / 2);
  const playerY = firstRoom.y + Math.floor(firstRoom.height / 2);
  
  const actors = new Map<EntityId, Actor>();
  const playerId = 1;
  
  actors.set(playerId, {
    id: playerId,
    category: "combatant",
    isPlayer: true,
    type: "player",
    pos: { x: playerX, y: playerY },
    speed: GAME_CONFIG.player.speed,
    stats: { ...runState.stats },
    inventory: { gold: runState.inventory.gold, items: [...runState.inventory.items] }
  });

  // Place exit in last room
  const lastRoom = rooms[rooms.length - 1];
  const exit: Vec2 = {
    x: lastRoom.x + Math.floor(lastRoom.width / 2),
    y: lastRoom.y + Math.floor(lastRoom.height / 2)
  };

  placeEnemies(floor, rooms, actors, random);
  decorate(width, height, tiles, random, exit);
  
  return { 
    world: { width, height, tiles, actors, exit }, 
    playerId 
  };
}


function generateRooms(width: number, height: number, tiles: Tile[], floor: number): Room[] {
  const rooms: Room[] = [];
  
  // Choose dungeon algorithm based on floor depth
  let dungeon: any;
  
  if (floor <= 4) {
    // Floors 1-4: Uniform (organic, irregular rooms)
    dungeon = new ROT.Map.Uniform(width, height, {
      roomWidth: [GAME_CONFIG.map.roomMinWidth, GAME_CONFIG.map.roomMaxWidth],
      roomHeight: [GAME_CONFIG.map.roomMinHeight, GAME_CONFIG.map.roomMaxHeight],
      roomDugPercentage: 0.3,
    });
  } else if (floor <= 9) {
    // Floors 5-9: Digger (traditional rectangular rooms + corridors)
    dungeon = new ROT.Map.Digger(width, height, {
      roomWidth: [GAME_CONFIG.map.roomMinWidth, GAME_CONFIG.map.roomMaxWidth],
      roomHeight: [GAME_CONFIG.map.roomMinHeight, GAME_CONFIG.map.roomMaxHeight],
      corridorLength: [2, 6],
    });
  } else {
    // Floors 10+: Cellular (natural cave systems)
    dungeon = new ROT.Map.Cellular(width, height, {
      born: [4, 5, 6, 7, 8],
      survive: [2, 3, 4, 5],
    });
    
    // Cellular needs randomization and smoothing
    dungeon.randomize(0.5);
    for (let i = 0; i < 4; i++) {
      dungeon.create();
    }
  }

  // Generate the dungeon
  dungeon.create((x: number, y: number, value: number) => {
    if (value === 0) {
      // 0 = floor, 1 = wall
      tiles[y * width + x] = TileType.EMPTY;
    }
  });

  // Extract room information from the generated dungeon
  const roomData = (dungeon as any).getRooms?.();
  
  if (roomData && roomData.length > 0) {
    // Traditional dungeons (Uniform/Digger) have explicit rooms
    for (const room of roomData) {
      rooms.push({
        x: room.getLeft(),
        y: room.getTop(),
        width: room.getRight() - room.getLeft() + 1,
        height: room.getBottom() - room.getTop() + 1
      });
    }
  } else {
    // Cellular caves don't have explicit rooms, so find connected floor areas
    rooms.push(...extractRoomsFromCave(width, height, tiles));
  }

  // Ensure we have at least 2 rooms for player/exit placement
  if (rooms.length < 2) {
    // Fallback: create two basic rooms
    rooms.push(
      { x: 5, y: 5, width: 5, height: 5 },
      { x: width - 10, y: height - 10, width: 5, height: 5 }
    );
  }

  return rooms;
}

/**
 * For cellular/cave maps, find clusters of floor tiles to use as "rooms"
 */
function extractRoomsFromCave(width: number, height: number, tiles: Tile[]): Room[] {
  const rooms: Room[] = [];
  const visited = new Set<number>();
  
  // Find large connected floor areas
  for (let y = 1; y < height - 1; y++) {
    for (let x = 1; x < width - 1; x++) {
      const idx = y * width + x;
      if (tiles[idx] === TileType.EMPTY && !visited.has(idx)) {
        const cluster = floodFill(width, height, tiles, x, y, visited);
        
        // Only consider clusters larger than 20 tiles
        if (cluster.length > 20) {
          // Create bounding box for this cluster
          let minX = width, maxX = 0, minY = height, maxY = 0;
          for (const pos of cluster) {
            const cx = pos % width;
            const cy = Math.floor(pos / width);
            minX = Math.min(minX, cx);
            maxX = Math.max(maxX, cx);
            minY = Math.min(minY, cy);
            maxY = Math.max(maxY, cy);
          }
          
          rooms.push({
            x: minX,
            y: minY,
            width: maxX - minX + 1,
            height: maxY - minY + 1
          });
        }
      }
    }
  }
  
  return rooms;
}

/**
 * Flood fill to find connected floor tiles
 */
function floodFill(width: number, height: number, tiles: Tile[], startX: number, startY: number, visited: Set<number>): number[] {
  const cluster: number[] = [];
  const queue: number[] = [startY * width + startX];
  
  while (queue.length > 0) {
    const idx = queue.shift()!;
    if (visited.has(idx)) continue;
    
    visited.add(idx);
    cluster.push(idx);
    
    const x = idx % width;
    const y = Math.floor(idx / width);
    
    // Check 4 directions
    const neighbors = [
      { nx: x + 1, ny: y },
      { nx: x - 1, ny: y },
      { nx: x, ny: y + 1 },
      { nx: x, ny: y - 1 },
    ];
    
    for (const { nx, ny } of neighbors) {
      if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
        const nIdx = ny * width + nx;
        if (tiles[nIdx] === TileType.EMPTY && !visited.has(nIdx)) {
          queue.push(nIdx);
        }
      }
    }
  }
  
  return cluster;
}

function decorate(width: number, height: number, tiles: Tile[], random: () => number, exit: Vec2): void {
  const world = { width, height };

  // Set exit tile
  tiles[idx(world as any, exit.x, exit.y)] = TileType.EXIT;

  // Use Simplex noise for natural-looking grass distribution
  const grassNoise = new ROT.Noise.Simplex();
  const decorationNoise = new ROT.Noise.Simplex();
  
  // Offset noise to get different patterns for grass vs decorations
  const grassOffset = random() * 1000;
  const decorOffset = random() * 1000;
  
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const i = idx(world as any, x, y);
      
      if (tiles[i] === TileType.EMPTY) {
        // Grass patches: use noise to create organic shapes
        const grassValue = grassNoise.get((x + grassOffset) / 15, (y + grassOffset) / 15);
        
        // Create grass patches where noise is above threshold
        if (grassValue > 0.35) {
          tiles[i] = TileType.GRASS;
        } else if (grassValue > 0.25) {
          // Transition zone: Saplings around grass clumps
          tiles[i] = TileType.GRASS_SAPLINGS;
        } else {
          // Floor decorations (moss/rocks): clustered distribution
          const decoValue = decorationNoise.get((x + decorOffset) / 8, (y + decorOffset) / 8);
          
          // Dense clusters where noise is high
          if (decoValue > 0.5) {
            tiles[i] = TileType.EMPTY_DECO;
          } else if (decoValue > 0.3 && random() < 0.3) {
            // Sparse decorations at medium noise levels
            tiles[i] = TileType.EMPTY_DECO;
          }
        }
      }
    }
  }

  // Wall decorations (moss near grass)
  for (let y = 0; y < height - 1; y++) {
    for (let x = 0; x < width; x++) {
      const i = idx(world as any, x, y);
      const nextY = idx(world as any, x, y + 1);

      if (tiles[i] === TileType.WALL && 
          tiles[nextY] === TileType.GRASS &&
          random() < 0.25) {
        tiles[i] = TileType.WALL_DECO;
      }
    }
  }
}

function placeEnemies(floor: number, rooms: Room[], actors: Map<EntityId, Actor>, random: () => number): void {
  let enemyId = 2;
  const numEnemies = GAME_CONFIG.enemyScaling.baseCount + floor * GAME_CONFIG.enemyScaling.baseCountPerFloor; 
  
  const enemyTypes = Object.keys(GAME_CONFIG.enemies);
  const occupiedPositions = new Set<string>();

  for (let i = 0; i < numEnemies; i++) {
    // Pick a random room (not the starting room 0)
    const roomIdx = 1 + Math.floor(random() * (rooms.length - 1));
    const room = rooms[roomIdx];
    
    // Try to find an empty spot in the room
    for (let attempts = 0; attempts < 5; attempts++) {

      const ex = room.x + 1 + Math.floor(random() * (room.width - 2));
      const ey = room.y + 1 + Math.floor(random() * (room.height - 2));
      const k = `${ex},${ey}`;

      if (!occupiedPositions.has(k)) {
        const type = enemyTypes[Math.floor(random() * enemyTypes.length)] as keyof typeof GAME_CONFIG.enemies;
        const enemyDef = GAME_CONFIG.enemies[type];

        const scaledHp = enemyDef.baseHp + floor * GAME_CONFIG.enemyScaling.hpPerFloor;
        const scaledAttack = enemyDef.baseAttack + Math.floor(floor / 2) * GAME_CONFIG.enemyScaling.attackPerTwoFloors;
        
        actors.set(enemyId, {
          id: enemyId,
          category: "combatant",
          isPlayer: false,
          type,
          pos: { x: ex, y: ey },
          speed: enemyDef.minSpeed + Math.floor(random() * (enemyDef.maxSpeed - enemyDef.minSpeed)),
          stats: {
            maxHp: scaledHp + Math.floor(random() * 4),
            hp: scaledHp + Math.floor(random() * 4),
            maxMana: 0,
            mana: 0,
            attack: scaledAttack + Math.floor(random() * 2),
            defense: enemyDef.baseDefense,
            level: 0,
            exp: 0,
            expToNextLevel: 0,
            statPoints: 0,
            skillPoints: 0,
            strength: 0,
            dexterity: 0,
            intelligence: 0,
            critChance: 0,
            critMultiplier: 100,
            accuracy: 80,
            lifesteal: 0,
            evasion: 0,
            blockChance: 0,
            luck: 0,
            passiveNodes: []
          }
        });
        
        occupiedPositions.add(k);
        enemyId++;

        break;
      }
    }
  }
}



export const makeTestWorld = generateWorld;
