import Phaser from "phaser";
import {
  type EntityId,
  type Vec2,
  type Action,
  type RunState,
  type World
} from "../core/types";
import { TILE_SIZE } from "../core/constants";
import { inBounds, isBlocked, isPlayerOnExit } from "../engine/world/world-logic";
import { findPathAStar } from "../engine/world/pathfinding";
import { applyAction, stepUntilPlayerTurn } from "../engine/simulation/simulation";
import { generateWorld } from "../engine/world/generator";

import { DungeonRenderer } from "../rendering/DungeonRenderer";
import { GAME_CONFIG } from "../core/config/GameConfig";

export class GameScene extends Phaser.Scene {
  private world!: World;
  private playerId!: EntityId;

  private floorIndex = 1;
  private gameState: "playing" | "player-turn" | "enemy-turn" = "player-turn";

  private runState: RunState = {
    stats: { ...GAME_CONFIG.player.initialStats },
    inventory: { gold: 0, items: [] }
  };

  private cursors!: Phaser.Types.Input.Keyboard.CursorKeys;

  private playerPath: Vec2[] = [];
  private awaitingPlayer = false;

  // Sub-systems
  private dungeonRenderer!: DungeonRenderer;
  private isMenuOpen = false;

  constructor() {
    super("GameScene");
  }

  preload() {
    this.load.spritesheet("warrior", "warrior.png", { frameWidth: 12, frameHeight: 15 });
    this.load.spritesheet("rat", "rat.png", { frameWidth: 16, frameHeight: 15 });
    this.load.spritesheet("bat", "bat.png", { frameWidth: 15, frameHeight: 15 });
    this.load.spritesheet("tiles0", "assets/tiles0.png", { frameWidth: 16, frameHeight: 16 });
  }

  create() {
    this.cursors = this.input.keyboard!.createCursorKeys();

    // Camera
    this.cameras.main.setZoom(GAME_CONFIG.rendering.cameraZoom);

    // Initialize Sub-systems
    this.dungeonRenderer = new DungeonRenderer(this);

    // Launch UI Scene
    this.scene.launch("GameUI");

    // Listen for Menu State
    this.events.on("menu-toggled", (isOpen: boolean) => {
      this.isMenuOpen = isOpen;
    });

    // Load initial floor
    this.loadFloor(1);

    // Menu Inputs
    this.input.keyboard?.on("keydown-I", () => {
      // Close minimap if it's open
      if (this.dungeonRenderer.isMinimapVisible()) {
        this.dungeonRenderer.toggleMinimap();
      }
      this.events.emit("toggle-menu");
      // Force update UI in case it opened
      this.emitUIUpdate();
    });
    this.input.keyboard?.on("keydown-ESC", () => {
      this.events.emit("close-menu");
      // Also close minimap
      if (this.dungeonRenderer.isMinimapVisible()) {
        this.dungeonRenderer.toggleMinimap();
      }
    });
    this.input.keyboard?.on("keydown-M", () => {
      // Close menu if it's open
      this.events.emit("close-menu");
      this.dungeonRenderer.toggleMinimap();
    });

    this.input.keyboard?.on("keydown-SPACE", () => {
      if (!this.awaitingPlayer) return;
      if (this.isMenuOpen || this.dungeonRenderer.isMinimapVisible()) return;
      this.commitPlayerAction({ type: "wait" });
    });

    // Listen for Map button click from UI
    this.events.on("toggle-minimap", () => {
      this.dungeonRenderer.toggleMinimap();
    });

    // Listen for UI update requests
    this.events.on("request-ui-update", () => {
      this.emitUIUpdate();
    });

    // Listen for game restart
    this.events.on("restart-game", () => {
      this.restartGame();
    });

    // Mouse click -> compute path (only during player turn, and not while menu/minimap is open)
    this.input.on("pointerdown", (p: Phaser.Input.Pointer) => {
      if (!this.awaitingPlayer) return;
      if (this.isMenuOpen || this.dungeonRenderer.isMinimapVisible()) return;

      const tx = Math.floor(p.worldX / TILE_SIZE);
      const ty = Math.floor(p.worldY / TILE_SIZE);

      if (!inBounds(this.world, tx, ty)) return;

      // Exploration rule: cannot click-to-move into unseen tiles
      if (!this.dungeonRenderer.isSeen(tx, ty)) return;

      // Check if clicking on an enemy
      const isEnemy = [...this.world.actors.values()].some(a => a.pos.x === tx && a.pos.y === ty && !a.isPlayer);

      const player = this.world.actors.get(this.playerId)!;
      const path = findPathAStar(
          this.world, 
          this.dungeonRenderer.seenArray, 
          { ...player.pos }, 
          { x: tx, y: ty },
          { ignoreBlockedTarget: isEnemy }
      );

      if (path.length >= 2) this.playerPath = path;
      this.dungeonRenderer.render(this.playerPath);
    });
  }

  update() {
    if (!this.awaitingPlayer) return;
    if (this.isMenuOpen || this.dungeonRenderer.isMinimapVisible()) return;

    // Auto-walk one step per turn
    if (this.playerPath.length >= 2) {
      const player = this.world.actors.get(this.playerId)!;
      const next = this.playerPath[1];
      const dx = next.x - player.pos.x;
      const dy = next.y - player.pos.y;

      if (Math.abs(dx) + Math.abs(dy) !== 1) {
        this.playerPath = [];
        return;
      }

      if (isBlocked(this.world, next.x, next.y)) {
        // Check if it's an enemy at 'next'
        const targetId = [...this.world.actors.values()].find(
            a => a.pos.x === next.x && a.pos.y === next.y && !a.isPlayer
        )?.id;

        if (targetId !== undefined) {
             this.commitPlayerAction({ type: "attack", targetId });
             this.playerPath = []; // Stop after attack
             return;
        } else {
             // Blocked by something else (friendly?)
             this.playerPath = [];
             return;
        }
      } 
      
      this.commitPlayerAction({ type: "move", dx, dy });
      this.playerPath.shift();
      return;
    }

    // Arrow keys
    let action: Action | null = null;
    let dx = 0;
    let dy = 0;

    if (Phaser.Input.Keyboard.JustDown(this.cursors.left!)) dx = -1;
    else if (Phaser.Input.Keyboard.JustDown(this.cursors.right!)) dx = 1;
    else if (Phaser.Input.Keyboard.JustDown(this.cursors.up!)) dy = -1;
    else if (Phaser.Input.Keyboard.JustDown(this.cursors.down!)) dy = 1;

    if (dx !== 0 || dy !== 0) {
      const player = this.world.actors.get(this.playerId)!;
      const targetX = player.pos.x + dx;
      const targetY = player.pos.y + dy;

      // Check for enemy at target position
      const targetId = [...this.world.actors.values()].find(
        a => a.pos.x === targetX && a.pos.y === targetY && !a.isPlayer
      )?.id;

      if (targetId !== undefined) {
        action = { type: "attack", targetId };
      } else {
        action = { type: "move", dx, dy };
      }
    }

    if (action) {
      this.playerPath = [];
      this.commitPlayerAction(action);
    }
  }

  private emitUIUpdate() {
      this.events.emit("update-ui", {
          world: this.world,
          playerId: this.playerId,
          floorIndex: this.floorIndex
      });
  }

  private commitPlayerAction(action: Action) {
    this.awaitingPlayer = false;

    const playerEvents = applyAction(this.world, this.playerId, action);
    const enemyStep = stepUntilPlayerTurn(this.world, this.playerId);
    this.awaitingPlayer = enemyStep.awaitingPlayerId === this.playerId;

    // Process events for visual fx
    const allEvents = [...playerEvents, ...enemyStep.events];
    for (const ev of allEvents) {
      if (ev.type === "damaged") {
         this.dungeonRenderer.showDamage(ev.x, ev.y, ev.amount);
      } else if (ev.type === "killed") {
         this.dungeonRenderer.spawnCorpse(ev.x, ev.y, ev.victimType || "rat");
      } else if (ev.type === "waited" && ev.actorId === this.playerId) {
         const player = this.world.actors.get(this.playerId);
         if (player) {
           this.dungeonRenderer.showWait(player.pos.x, player.pos.y);
         }
      } else if (ev.type === "orb-spawned") {
         this.dungeonRenderer.spawnOrb(ev.orbId, ev.x, ev.y);
      } else if (ev.type === "exp-collected" && ev.actorId === this.playerId) {
         this.dungeonRenderer.collectOrb(ev.actorId, ev.amount, ev.x, ev.y);
      } else if (ev.type === "leveled-up" && ev.actorId === this.playerId) {
         this.dungeonRenderer.showLevelUp(ev.x, ev.y);
      }
    }


    // Check if player died
    if (!this.world.actors.has(this.playerId)) {
      this.syncRunStateFromPlayer(); // Save final stats for death screen
      const uiScene = this.scene.get("GameUI") as any;
      if (uiScene) {
        uiScene.showDeathScreen({
          floor: this.floorIndex,
          gold: this.runState.inventory.gold,
          stats: this.runState.stats
        });
      }
      return;
    }

    // Level transition
    if (isPlayerOnExit(this.world, this.playerId)) {
      this.syncRunStateFromPlayer();
      this.floorIndex++;
      this.loadFloor(this.floorIndex);
      return;
    }

    this.dungeonRenderer.computeFov(this.playerId);
    this.centerCameraOnPlayer();
    this.dungeonRenderer.render(this.playerPath);
    this.emitUIUpdate();
  }

  private loadFloor(floor: number) {
    this.floorIndex = floor;

    const { world, playerId } = generateWorld(floor, this.runState);
    this.world = world;
    this.playerId = playerId;

    // Reset transient state
    this.playerPath = [];
    this.awaitingPlayer = false;

    // Camera bounds for this level
    this.cameras.main.setBounds(0, 0, this.world.width * TILE_SIZE, this.world.height * TILE_SIZE);

    // Initialize Renderer for new floor
    this.dungeonRenderer.initializeFloor(this.world);

    // Step until player turn
    const enemyStep = stepUntilPlayerTurn(this.world, this.playerId);
    this.awaitingPlayer = enemyStep.awaitingPlayerId === this.playerId;

    this.dungeonRenderer.computeFov(this.playerId);
    this.centerCameraOnPlayer();
    this.dungeonRenderer.render(this.playerPath);
    this.emitUIUpdate();
  }

  private syncRunStateFromPlayer() {
    const p = this.world.actors.get(this.playerId);
    if (!p?.stats || !p.inventory) return;

    this.runState = {
      stats: { ...p.stats },
      inventory: { gold: p.inventory.gold, items: [...p.inventory.items] }
    };
  }

  private restartGame() {
    this.runState = {
      stats: { ...GAME_CONFIG.player.initialStats },
      inventory: { gold: 0, items: [] }
    };
    this.floorIndex = 1;
    this.loadFloor(this.floorIndex);
  }


  private centerCameraOnPlayer() {
    const player = this.world.actors.get(this.playerId)!;
    this.cameras.main.centerOn(
      player.pos.x * TILE_SIZE + TILE_SIZE / 2,
      player.pos.y * TILE_SIZE + TILE_SIZE / 2
    );
  }
}
