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

import { isBlocked, inBounds, tryDestructTile } from "../world/world-logic";
import { isDestructibleByWalk, blocksSight } from "../../core/terrain";
import { findPathAStar } from "../world/pathfinding";
import { GAME_CONFIG } from "../../core/config/GameConfig";
import { type EntityManager } from "../EntityManager";
import { FOV } from "rot-js";
import * as ROT from "rot-js";


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

  const events: SimEvent[] = [];

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

  // Note: Energy is now managed by ROT.Scheduler, no need to deduct manually

  return events;
}

function handleExpCollection(w: World, player: Actor, events: SimEvent[], em?: EntityManager) {
  if (player.category !== "combatant") return;

  const orbs = [...w.actors.values()].filter(a => 
    a.category === "collectible" && 
    a.type === "exp_orb" && 
    a.pos.x === player.pos.x && 
    a.pos.y === player.pos.y
  ) as CollectibleActor[];
  
  for (const orb of orbs) {
    const amount = orb.expAmount || 0;
    player.stats.exp += amount;
    events.push({ 
      type: "exp-collected", 
      actorId: player.id, 
      amount, 
      x: player.pos.x, 
      y: player.pos.y 
    });
    
    checkLevelUp(player, events);
    if (em) em.removeActor(orb.id);
    else w.actors.delete(orb.id);
  }
}

function checkLevelUp(player: CombatantActor, events: SimEvent[]) {
  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.maxMana += GAME_CONFIG.leveling.manaGainPerLevel;
    s.mana = s.maxMana; // Restore mana on level up
    s.attack += GAME_CONFIG.leveling.attackGainPerLevel;

    s.statPoints += GAME_CONFIG.leveling.statPointsPerLevel;
    s.skillPoints += GAME_CONFIG.leveling.skillPointsPerLevel;
    
    // 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 }, em?: EntityManager): SimEvent[] {
  const from = { ...actor.pos };
  const nx = actor.pos.x + action.dx;
  const ny = actor.pos.y + action.dy;

  if (!isBlocked(w, nx, ny, em)) {
    if (em) {
      em.moveActor(actor.id, from, { x: nx, y: ny });
    } else {
      actor.pos.x = nx;
      actor.pos.y = ny;
    }
    const to = { ...actor.pos };
    const events: SimEvent[] = [{ type: "moved", actorId: actor.id, from, to }];
    
    // Check for "destructible by walk" tiles (e.g. grass)
    // We check the tile at the *new* position
    const tileIdx = ny * w.width + nx;
    const tile = w.tiles[tileIdx];
    if (isDestructibleByWalk(tile)) {
      tryDestructTile(w, nx, ny);
      // Optional: Add an event if we want visual feedback immediately,
      // but the renderer usually handles map updates automatically or next frame
    }

    if (actor.category === "combatant" && actor.isPlayer) {
      handleExpCollection(w, actor, events, em);
    }
    
    return events;
  } else {
    return [{ type: "waited", actorId: actor.id }];
  }
}



function handleAttack(w: World, actor: Actor, action: { targetId: EntityId }, em?: EntityManager): SimEvent[] {
  const target = w.actors.get(action.targetId);
  if (target && target.category === "combatant" && actor.category === "combatant") {
    const events: SimEvent[] = [{ type: "attacked", attackerId: actor.id, targetId: action.targetId }];
    
    // 1. Accuracy vs Evasion Check
    const hitChance = actor.stats.accuracy - target.stats.evasion;
    const hitRoll = Math.random() * 100;
    
    if (hitRoll > hitChance) {
      // Miss!
      events.push({ 
        type: "dodged", 
        targetId: action.targetId, 
        x: target.pos.x,
        y: target.pos.y
      });
      return events;
    }

    // 2. Base Damage Calculation
    let dmg = Math.max(1, actor.stats.attack - target.stats.defense);
    
    // 3. Critical Strike Check
    const critRoll = Math.random() * 100;
    const isCrit = critRoll < actor.stats.critChance;
    if (isCrit) {
      dmg = Math.floor(dmg * (actor.stats.critMultiplier / 100));
    }

    // 4. Block Chance Check
    const blockRoll = Math.random() * 100;
    let isBlock = false;
    if (blockRoll < target.stats.blockChance) {
      dmg = Math.floor(dmg * 0.5); // Block reduces damage by 50%
      isBlock = true;
    }

    target.stats.hp -= dmg;
    
    // 5. Lifesteal Logic
    if (actor.stats.lifesteal > 0 && dmg > 0) {
      const healAmount = Math.floor(dmg * (actor.stats.lifesteal / 100));
      if (healAmount > 0) {
        actor.stats.hp = Math.min(actor.stats.maxHp, actor.stats.hp + healAmount);
        events.push({
            type: "healed",
            actorId: actor.id,
            amount: healAmount,
            x: actor.pos.x,
            y: actor.pos.y
        });
      }
    }

    events.push({ 
      type: "damaged", 
      targetId: action.targetId, 
      amount: dmg, 
      hp: target.stats.hp,
      x: target.pos.x,
      y: target.pos.y,
      isCrit,
      isBlock
    });

    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 as ActorType
      });
      if (em) em.removeActor(target.id);
      else w.actors.delete(target.id);



      // Spawn EXP Orb
      const enemyDef = (GAME_CONFIG.enemies as any)[target.type || ""];
      const expAmount = enemyDef?.expValue || 0;
      const orbId = em ? em.getNextId() : Math.max(0, ...w.actors.keys(), target.id) + 1;
      
      const orb: CollectibleActor = {
        id: orbId,
        category: "collectible",
        type: "exp_orb",
        pos: { ...target.pos },
        expAmount
      };

      if (em) em.addActor(orb);
      else w.actors.set(orbId, orb);


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


/**
 * Check if an enemy can see the player using FOV calculation
 */
function canEnemySeePlayer(w: World, enemy: CombatantActor, player: CombatantActor): boolean {
  const viewRadius = 8; // Enemy vision range
  let canSee = false;
  
  
  const fov = new FOV.PreciseShadowcasting((x: number, y: number) => {
    if (!inBounds(w, x, y)) return false;
    const idx = y * w.width + x;
    return !blocksSight(w.tiles[idx]);
  });
  
  fov.compute(enemy.pos.x, enemy.pos.y, viewRadius, (x: number, y: number) => {
    if (x === player.pos.x && y === player.pos.y) {
      canSee = true;
    }
  });
  
  return canSee;
}

/**
 * Get a random wander move for an enemy
 */
function getRandomWanderMove(w: World, enemy: CombatantActor, em?: EntityManager): Action {
  const directions = [
    { dx: 0, dy: -1 }, // up
    { dx: 0, dy: 1 },  // down
    { dx: -1, dy: 0 }, // left
    { dx: 1, dy: 0 },  // right
  ];
  
  // Shuffle directions
  for (let i = directions.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [directions[i], directions[j]] = [directions[j], directions[i]];
  }
  
  // Try each direction, return first valid one
  for (const dir of directions) {
    const nx = enemy.pos.x + dir.dx;
    const ny = enemy.pos.y + dir.dy;
    if (!isBlocked(w, nx, ny, em)) {
      return { type: "move", ...dir };
    }
  }
  
  // If no valid move, wait
  return { type: "wait" };
}

/**
 * Enemy AI with state machine:
 * - Wandering: Random movement when can't see player
 * - Alerted: Brief period after spotting player (shows "!")
 * - Pursuing: Chase player while in FOV or toward last known position
 */
export function decideEnemyAction(w: World, enemy: CombatantActor, player: CombatantActor, em?: EntityManager): { action: Action; justAlerted: boolean } {
  // Initialize AI state if not set
  if (!enemy.aiState) {
    enemy.aiState = "wandering";
  }
  
  const canSee = canEnemySeePlayer(w, enemy, player);
  const dx = player.pos.x - enemy.pos.x;
  const dy = player.pos.y - enemy.pos.y;
  const dist = Math.abs(dx) + Math.abs(dy);
  
  // State transitions
  let justAlerted = false;
  if (canSee && enemy.aiState === "wandering") {
    // Spotted player! Transition to alerted state
    enemy.aiState = "alerted";
    enemy.alertedAt = Date.now();
    enemy.lastKnownPlayerPos = { ...player.pos };
    justAlerted = true;
  } else if (enemy.aiState === "alerted") {
    // Check if alert period is over (1 second = 1000ms)
    const alertDuration = 1000;
    if (Date.now() - (enemy.alertedAt || 0) > alertDuration) {
      enemy.aiState = "pursuing";
    }
  } else if (enemy.aiState === "pursuing") {
    if (canSee) {
      // Update last known position
      enemy.lastKnownPlayerPos = { ...player.pos };
    } else {
      // Lost sight - check if we've reached last known position
      if (enemy.lastKnownPlayerPos) {
        const distToLastKnown = Math.abs(enemy.pos.x - enemy.lastKnownPlayerPos.x) + 
                                Math.abs(enemy.pos.y - enemy.lastKnownPlayerPos.y);
        if (distToLastKnown <= 1) {
          // Reached last known position, return to wandering
          enemy.aiState = "wandering";
          enemy.lastKnownPlayerPos = undefined;
        }
      } else {
        // No last known position, return to wandering
        enemy.aiState = "wandering";
      }
    }
  }
  
  // Behavior based on current state
  if (enemy.aiState === "wandering") {
    return { action: getRandomWanderMove(w, enemy, em), justAlerted };
  }
  
  if (enemy.aiState === "alerted") {
    // During alert, stay still (or could do small movement)
    return { action: { type: "wait" }, justAlerted };
  }
  
  // Pursuing state - chase player or last known position
  const targetPos = canSee ? player.pos : (enemy.lastKnownPlayerPos || player.pos);
  const targetDx = targetPos.x - enemy.pos.x;
  const targetDy = targetPos.y - enemy.pos.y;
  
  // If adjacent to player, attack
  if (dist === 1 && canSee) {
    return { action: { type: "attack", targetId: player.id }, justAlerted };
  }

  // Use A* for smarter pathfinding to target
  const dummySeen = new Uint8Array(w.width * w.height).fill(1);
  const path = findPathAStar(w, dummySeen, enemy.pos, targetPos, { ignoreBlockedTarget: true, ignoreSeen: true, em });

  if (path.length >= 2) {
    const next = path[1];
    const adx = next.x - enemy.pos.x;
    const ady = next.y - enemy.pos.y;
    return { action: { type: "move", dx: adx, dy: ady }, justAlerted };
  }

  // Fallback to greedy if no path found
  const options: { dx: number; dy: number }[] = [];

  if (Math.abs(targetDx) >= Math.abs(targetDy)) {
    options.push({ dx: Math.sign(targetDx), dy: 0 });
    options.push({ dx: 0, dy: Math.sign(targetDy) });
  } else {
    options.push({ dx: 0, dy: Math.sign(targetDy) });
    options.push({ dx: Math.sign(targetDx), 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, em)) return { action: { type: "move", dx: o.dx, dy: o.dy }, justAlerted };
  }
  
  return { action: { type: "wait" }, justAlerted };
}

/**
 * Speed-based scheduler using rot-js: 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, em?: EntityManager): { awaitingPlayerId: EntityId; events: SimEvent[] } {
  const player = w.actors.get(playerId) as CombatantActor;
  if (!player || player.category !== "combatant") throw new Error("Player missing or invalid");

  const events: SimEvent[] = [];

  // Create scheduler and add all combatants
  const scheduler = new ROT.Scheduler.Speed();
  
  for (const actor of w.actors.values()) {
    if (actor.category === "combatant") {
      // ROT.Scheduler.Speed expects actors to have a getSpeed() method
      // Add it dynamically if it doesn't exist
      const actorWithGetSpeed = actor as any;
      if (!actorWithGetSpeed.getSpeed) {
        actorWithGetSpeed.getSpeed = function() { return this.speed; };
      }
      scheduler.add(actorWithGetSpeed, true);
    }
  }

  while (true) {
    // Get next actor from scheduler
    const actor = scheduler.next() as CombatantActor | null;
    
    if (!actor || !w.actors.has(actor.id)) {
      // Actor was removed (died), continue to next
      continue;
    }

    if (actor.isPlayer) {
      // Player's turn - return control to the user
      return { awaitingPlayerId: actor.id, events };
    }

    // Enemy turn - decide action and apply it
    const decision = decideEnemyAction(w, actor, player, em);
    
    // Emit alert event if enemy just spotted player
    if (decision.justAlerted) {
      events.push({ 
        type: "enemy-alerted", 
        enemyId: actor.id, 
        x: actor.pos.x, 
        y: actor.pos.y 
      });
    }
    
    events.push(...applyAction(w, actor.id, decision.action, em));

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


