import type { World, EntityId, Action, SimEvent, Actor } from "../../core/types";

import { isBlocked } from "../world/world-logic";
import { GAME_CONFIG } from "../../core/config/GameConfig";


export function applyAction(w: World, actorId: EntityId, action: Action): SimEvent[] {
  const actor = w.actors.get(actorId);
  if (!actor) return [];

  const events: SimEvent[] = [];

  switch (action.type) {
    case "move":
      events.push(...handleMove(w, actor, action));
      break;
    case "attack":
      events.push(...handleAttack(w, actor, action));
      break;
    case "wait":
    default:
      events.push({ type: "waited", actorId });
      break;
  }

  // Spend energy for any action (move/wait/attack)
  actor.energy -= GAME_CONFIG.gameplay.actionCost;

  return events;
}

function handleExpCollection(w: World, player: Actor, events: SimEvent[]) {
  const orbs = [...w.actors.values()].filter(a => a.type === "exp_orb" && a.pos.x === player.pos.x && a.pos.y === player.pos.y);
  
  for (const orb of orbs) {
    const amount = (orb as any).expAmount || 0;
    if (player.stats) {
      player.stats.exp += amount;
      events.push({ 
        type: "exp-collected", 
        actorId: player.id, 
        amount, 
        x: player.pos.x, 
        y: player.pos.y 
      });
      
      checkLevelUp(player, events);
    }
    w.actors.delete(orb.id);
  }
}

function checkLevelUp(player: Actor, events: SimEvent[]) {
  if (!player.stats) return;
  const s = player.stats;
  
  while (s.exp >= s.expToNextLevel) {
    s.level++;
    s.exp -= s.expToNextLevel;
    
    // Growth
    s.maxHp += GAME_CONFIG.leveling.hpGainPerLevel;
    s.hp = s.maxHp; // Heal on level up
    s.attack += GAME_CONFIG.leveling.attackGainPerLevel;
    
    // Scale requirement
    s.expToNextLevel = Math.floor(s.expToNextLevel * GAME_CONFIG.leveling.expMultiplier);
    
    events.push({ 
      type: "leveled-up", 
      actorId: player.id, 
      level: s.level, 
      x: player.pos.x, 
      y: player.pos.y 
    });
  }
}


function handleMove(w: World, actor: Actor, action: { dx: number; dy: number }): SimEvent[] {
  const from = { ...actor.pos };
  const nx = actor.pos.x + action.dx;
  const ny = actor.pos.y + action.dy;

  if (!isBlocked(w, nx, ny)) {
    actor.pos.x = nx;
    actor.pos.y = ny;
    const to = { ...actor.pos };
    const events: SimEvent[] = [{ type: "moved", actorId: actor.id, from, to }];
    
    if (actor.isPlayer) {
      handleExpCollection(w, actor, events);
    }
    
    return events;
  } else {
    return [{ type: "waited", actorId: actor.id }];
  }
}


function handleAttack(w: World, actor: Actor, action: { targetId: EntityId }): SimEvent[] {
  const target = w.actors.get(action.targetId);
  if (target && target.stats && actor.stats) {
    const events: SimEvent[] = [{ type: "attacked", attackerId: actor.id, targetId: action.targetId }];
    
    const dmg = Math.max(1, actor.stats.attack - target.stats.defense);
    target.stats.hp -= dmg;
    
    events.push({ 
      type: "damaged", 
      targetId: action.targetId, 
      amount: dmg, 
      hp: target.stats.hp,
      x: target.pos.x,
      y: target.pos.y
    });

    if (target.stats.hp <= 0) {
      events.push({ 
        type: "killed", 
        targetId: target.id, 
        killerId: actor.id, 
        x: target.pos.x, 
        y: target.pos.y, 
        victimType: target.type 
      });
      w.actors.delete(target.id);

      // Spawn EXP Orb
      const expAmount = target.type === "rat" ? GAME_CONFIG.enemy.ratExp : GAME_CONFIG.enemy.batExp;
      const orbId = Math.max(0, ...w.actors.keys(), target.id) + 1;
      w.actors.set(orbId, {

        id: orbId,
        isPlayer: false,
        type: "exp_orb",
        pos: { ...target.pos },
        speed: 0,
        energy: 0,
        expAmount // Hidden property for simulation
      } as any);

      events.push({ type: "orb-spawned", orbId, x: target.pos.x, y: target.pos.y });
    }
    return events;
  }
  return [{ type: "waited", actorId: actor.id }];
}


/**
 * Very basic enemy AI:
 * - if adjacent to player, attack
 * - else step toward player using greedy Manhattan
 */
export function decideEnemyAction(w: World, enemy: Actor, player: Actor): Action {
  const dx = player.pos.x - enemy.pos.x;
  const dy = player.pos.y - enemy.pos.y;
  const dist = Math.abs(dx) + Math.abs(dy);

  if (dist === 1) {
    return { type: "attack", targetId: player.id };
  }

  const options: { dx: number; dy: number }[] = [];
  if (Math.abs(dx) >= Math.abs(dy)) {
    options.push({ dx: Math.sign(dx), dy: 0 });
    options.push({ dx: 0, dy: Math.sign(dy) });
  } else {
    options.push({ dx: 0, dy: Math.sign(dy) });
    options.push({ dx: Math.sign(dx), dy: 0 });
  }

  options.push({ dx: -options[0].dx, dy: -options[0].dy });

  for (const o of options) {
    if (o.dx === 0 && o.dy === 0) continue;
    const nx = enemy.pos.x + o.dx;
    const ny = enemy.pos.y + o.dy;
    if (!isBlocked(w, nx, ny)) return { type: "move", dx: o.dx, dy: o.dy };
  }
  return { type: "wait" };
}

/**
 * Energy/speed scheduler: runs until it's the player's turn and the game needs input.
 * Returns enemy events accumulated along the way.
 */
export function stepUntilPlayerTurn(w: World, playerId: EntityId): { awaitingPlayerId: EntityId; events: SimEvent[] } {
  const player = w.actors.get(playerId);
  if (!player) throw new Error("Player missing");

  const events: SimEvent[] = [];

  while (true) {
    while (![...w.actors.values()].some(a => a.energy >= GAME_CONFIG.gameplay.energyThreshold)) {
      for (const a of w.actors.values()) a.energy += a.speed;
    }

    const ready = [...w.actors.values()].filter(a => a.energy >= GAME_CONFIG.gameplay.energyThreshold);

    ready.sort((a, b) => (b.energy - a.energy) || (a.id - b.id));
    const actor = ready[0];

    if (actor.isPlayer) {
      return { awaitingPlayerId: actor.id, events };
    }

    const action = decideEnemyAction(w, actor, player);
    events.push(...applyAction(w, actor.id, action));

    // Check if player was killed by this action
    if (!w.actors.has(playerId)) {
      return { awaitingPlayerId: null as any, events };
    }
  }
}
