import { type World, type EntityId, type RunState, type Tile, type Actor, type Vec2 } from "../../core/types";
import { idx } from "./world-logic";
import { GAME_CONFIG } from "../../core/config/GameConfig";
import { seededRandom } from "../../core/math";

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

/**
 * Generates a procedural dungeon world with rooms and corridors
 * @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(GAME_CONFIG.terrain.wall);

  const random = seededRandom(floor * 12345);

  const rooms = generateRooms(width, height, tiles, random);
  
  // 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,
    energy: 0,
    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[], random: () => number): Room[] {
  const rooms: Room[] = [];
  const numRooms = GAME_CONFIG.map.minRooms + Math.floor(random() * (GAME_CONFIG.map.maxRooms - GAME_CONFIG.map.minRooms + 1));
  
  const fakeWorldForIdx = { width, height };

  for (let i = 0; i < numRooms; i++) {
    const roomWidth = GAME_CONFIG.map.roomMinWidth + Math.floor(random() * (GAME_CONFIG.map.roomMaxWidth - GAME_CONFIG.map.roomMinWidth + 1));
    const roomHeight = GAME_CONFIG.map.roomMinHeight + Math.floor(random() * (GAME_CONFIG.map.roomMaxHeight - GAME_CONFIG.map.roomMinHeight + 1));
    const roomX = 1 + Math.floor(random() * (width - roomWidth - 2));
    const roomY = 1 + Math.floor(random() * (height - roomHeight - 2));
    
    const newRoom: Room = { x: roomX, y: roomY, width: roomWidth, height: roomHeight };
    
    if (!doesOverlap(newRoom, rooms)) {
      carveRoom(newRoom, tiles, fakeWorldForIdx);
      
      if (rooms.length > 0) {
        carveCorridor(rooms[rooms.length - 1], newRoom, tiles, fakeWorldForIdx, random);
      }
      
      rooms.push(newRoom);
    }
  }
  return rooms;
}

function doesOverlap(newRoom: Room, rooms: Room[]): boolean {
  for (const room of rooms) {
    if (
      newRoom.x < room.x + room.width + 1 &&
      newRoom.x + newRoom.width + 1 > room.x &&
      newRoom.y < room.y + room.height + 1 &&
      newRoom.y + newRoom.height + 1 > room.y
    ) {
      return true;
    }
  }
  return false;
}

function carveRoom(room: Room, tiles: Tile[], world: any): void {
  for (let x = room.x; x < room.x + room.width; x++) {
    for (let y = room.y; y < room.y + room.height; y++) {
      tiles[idx(world, x, y)] = GAME_CONFIG.terrain.empty;
    }
  }
}

function carveCorridor(room1: Room, room2: Room, tiles: Tile[], world: any, random: () => number): void {
  const x1 = Math.floor(room1.x + room1.width / 2);
  const y1 = Math.floor(room1.y + room1.height / 2);
  const x2 = Math.floor(room2.x + room2.width / 2);
  const y2 = Math.floor(room2.y + room2.height / 2);

  if (random() < 0.5) {
    // Horizontal then vertical
    for (let x = Math.min(x1, x2); x <= Math.max(x1, x2); x++) {
      tiles[idx(world, x, y1)] = GAME_CONFIG.terrain.empty;
    }
    for (let y = Math.min(y1, y2); y <= Math.max(y1, y2); y++) {
      tiles[idx(world, x2, y)] = GAME_CONFIG.terrain.empty;
    }
  } else {
    // Vertical then horizontal
    for (let y = Math.min(y1, y2); y <= Math.max(y1, y2); y++) {
      tiles[idx(world, x1, y)] = GAME_CONFIG.terrain.empty;
    }
    for (let x = Math.min(x1, x2); x <= Math.max(x1, x2); x++) {
      tiles[idx(world, x, y2)] = GAME_CONFIG.terrain.empty;
    }
  }
}

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)] = GAME_CONFIG.terrain.exit;

  // Add water patches (similar to PD Sewers)
  const waterMask = generatePatch(width, height, 0.45, 5, random);
  for (let i = 0; i < tiles.length; i++) {
    if (tiles[i] === GAME_CONFIG.terrain.empty && waterMask[i]) {
      tiles[i] = GAME_CONFIG.terrain.water;
    }
  }

  // Wall decorations
  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] === GAME_CONFIG.terrain.wall && 
          tiles[nextY] === GAME_CONFIG.terrain.water &&
          random() < 0.25) {
        tiles[i] = GAME_CONFIG.terrain.wallDeco;
      }
    }
  }

  // Floor decorations (moss)
  for (let y = 1; y < height - 1; y++) {
    for (let x = 1; x < width - 1; x++) {
      const i = idx(world as any, x, y);
      if (tiles[i] === GAME_CONFIG.terrain.empty) {
        let wallCount = 0;
        if (tiles[idx(world as any, x + 1, y)] === GAME_CONFIG.terrain.wall) wallCount++;
        if (tiles[idx(world as any, x - 1, y)] === GAME_CONFIG.terrain.wall) wallCount++;
        if (tiles[idx(world as any, x, y + 1)] === GAME_CONFIG.terrain.wall) wallCount++;
        if (tiles[idx(world as any, x, y - 1)] === GAME_CONFIG.terrain.wall) wallCount++;

        if (random() * 16 < wallCount * wallCount) {
          tiles[i] = GAME_CONFIG.terrain.emptyDeco;
        }
      }
    }
  }
}

/**
 * Simple cellular automata for generating patches of terrain
 */
function generatePatch(width: number, height: number, fillChance: number, iterations: number, random: () => number): boolean[] {
  let map = new Array(width * height).fill(false).map(() => random() < fillChance);

  for (let step = 0; step < iterations; step++) {
    const nextMap = new Array(width * height).fill(false);
    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        let neighbors = 0;
        for (let dy = -1; dy <= 1; dy++) {
          for (let dx = -1; dx <= 1; dx++) {
            if (map[(y + dy) * width + (x + dx)]) neighbors++;
          }
        }
        if (neighbors > 4) nextMap[y * width + x] = true;
        else if (neighbors < 4) nextMap[y * width + x] = false;
        else nextMap[y * width + x] = map[y * width + x];
      }
    }
    map = nextMap;
  }
  return map;
}

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)),
          energy: 0,
          stats: {
            maxHp: scaledHp + Math.floor(random() * 4),
            hp: scaledHp + Math.floor(random() * 4),
            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;
