import Phaser from "phaser";
import { FOV } from "rot-js";
import { type World, type EntityId, type Vec2 } from "../core/types";
import { TILE_SIZE } from "../core/constants";
import { idx, inBounds, isWall } from "../engine/world/world-logic";
import { GAME_CONFIG } from "../core/config/GameConfig";

export class DungeonRenderer {
  private scene: Phaser.Scene;
  private map?: Phaser.Tilemaps.Tilemap;
  private layer?: Phaser.Tilemaps.TilemapLayer;
  
  private playerSprite?: Phaser.GameObjects.Sprite;
  private enemySprites: Map<EntityId, Phaser.GameObjects.Sprite> = new Map();
  private orbSprites: Map<EntityId, Phaser.GameObjects.Arc> = new Map();
  private corpseSprites: Phaser.GameObjects.Sprite[] = [];

  
  // FOV
  private fov!: any;
  private seen!: Uint8Array;
  private visible!: Uint8Array;
  private visibleStrength!: Float32Array;
  
  // State refs
  private world!: World;

  // Minimap
  private minimapGfx!: Phaser.GameObjects.Graphics;
  private minimapContainer!: Phaser.GameObjects.Container;
  private minimapBg!: Phaser.GameObjects.Rectangle;
  private minimapVisible = false;

  constructor(scene: Phaser.Scene) {
    this.scene = scene;
    this.initMinimap();
  }

  private initMinimap() {
    this.minimapContainer = this.scene.add.container(0, 0);
    this.minimapContainer.setScrollFactor(0);
    this.minimapContainer.setDepth(1001);
    
    this.minimapBg = this.scene.add
      .rectangle(0, 0, GAME_CONFIG.ui.minimapPanelWidth, GAME_CONFIG.ui.minimapPanelHeight, 0x000000, 0.8)
      .setStrokeStyle(1, 0xffffff, 0.9)
      .setInteractive();
    
    this.minimapGfx = this.scene.add.graphics();
    
    this.minimapContainer.add(this.minimapBg);
    this.minimapContainer.add(this.minimapGfx);
    this.positionMinimap();
    this.minimapContainer.setVisible(false);
  }

  initializeFloor(world: World) {

    this.world = world;
    this.seen = new Uint8Array(this.world.width * this.world.height);
    this.visible = new Uint8Array(this.world.width * this.world.height);
    this.visibleStrength = new Float32Array(this.world.width * this.world.height);

    // Setup Tilemap
    if (this.map) this.map.destroy();
    this.map = this.scene.make.tilemap({
      data: Array.from({ length: world.height }, (_, y) => 
        Array.from({ length: world.width }, (_, x) => this.world.tiles[idx(this.world, x, y)])
      ),
      tileWidth: 16,
      tileHeight: 16
    });

    const tileset = this.map.addTilesetImage("tiles0", "tiles0", 16, 16, 0, 0)!;
    this.layer = this.map.createLayer(0, tileset, 0, 0)!;
    this.layer.setDepth(0);

    // Initial tile states (hidden)
    this.layer.forEachTile(tile => {
      tile.setVisible(false);
    });

    // Clear old corpses
    for (const sprite of this.corpseSprites) {
      sprite.destroy();
    }
    this.corpseSprites = [];

    // Setup player sprite
    if (!this.playerSprite) {
      this.playerSprite = this.scene.add.sprite(0, 0, "warrior", 0);
      this.playerSprite.setDepth(100);
      
      // Calculate scale to fit 15px high sprite into 16px tile
      const scale = 1.0; 
      this.playerSprite.setScale(scale);
      
      this.scene.anims.create({
        key: 'warrior-idle',
        frames: this.scene.anims.generateFrameNumbers('warrior', { frames: [0, 0, 0, 1, 0, 0, 1, 1] }),
        frameRate: 2,
        repeat: -1
      });

      this.scene.anims.create({
        key: 'warrior-run',
        frames: this.scene.anims.generateFrameNumbers('warrior', { frames: [2, 3, 4, 5, 6, 7] }),
        frameRate: 15,
        repeat: -1
      });

      this.scene.anims.create({
        key: 'warrior-die',
        frames: this.scene.anims.generateFrameNumbers('warrior', { frames: [8, 9, 10, 11, 12] }),
        frameRate: 10,
        repeat: 0
      });

      this.playerSprite.play('warrior-idle');
    }

    // Enemy animations
    if (!this.scene.anims.exists('rat-idle')) {
      this.scene.anims.create({
        key: 'rat-idle',
        frames: this.scene.anims.generateFrameNumbers('rat', { frames: [0, 0, 0, 1] }),
        frameRate: 4,
        repeat: -1
      });
      this.scene.anims.create({
        key: 'rat-run',
        frames: this.scene.anims.generateFrameNumbers('rat', { frames: [6, 7, 8, 9, 10] }),
        frameRate: 10,
        repeat: -1
      });
      this.scene.anims.create({
        key: 'rat-die',
        frames: this.scene.anims.generateFrameNumbers('rat', { frames: [11, 12, 13, 14] }),
        frameRate: 10,
        repeat: 0
      });
    }

    if (!this.scene.anims.exists('bat-idle')) {
      this.scene.anims.create({
        key: 'bat-idle',
        frames: this.scene.anims.generateFrameNumbers('bat', { frames: [0, 1] }),
        frameRate: 8,
        repeat: -1
      });
      this.scene.anims.create({
        key: 'bat-run',
        frames: this.scene.anims.generateFrameNumbers('bat', { frames: [0, 1] }),
        frameRate: 12,
        repeat: -1
      });
      this.scene.anims.create({
        key: 'bat-die',
        frames: this.scene.anims.generateFrameNumbers('bat', { frames: [4, 5, 6] }),
        frameRate: 10,
        repeat: 0
      });
    }

    this.fov = new FOV.PreciseShadowcasting((x: number, y: number) => {
      if (!inBounds(this.world, x, y)) return false;
      return !isWall(this.world, x, y);
    });

    this.positionMinimap();
  }

  private positionMinimap() {
    const cam = this.scene.cameras.main;
    this.minimapContainer.setPosition(cam.width / 2, cam.height / 2);
  }

  toggleMinimap() {
    this.minimapVisible = !this.minimapVisible;
    this.minimapContainer.setVisible(this.minimapVisible);
  }

  isMinimapVisible(): boolean {
    return this.minimapVisible;
  }

  computeFov(playerId: EntityId) {
    this.visible.fill(0);
    this.visibleStrength.fill(0);

    const player = this.world.actors.get(playerId)!;
    const ox = player.pos.x;
    const oy = player.pos.y;

    this.fov.compute(ox, oy, GAME_CONFIG.player.viewRadius, (x: number, y: number, r: number, v: number) => {
      if (!inBounds(this.world, x, y)) return;

      const i = idx(this.world, x, y);
      this.visible[i] = 1;
      this.seen[i] = 1;

      const radiusT = Phaser.Math.Clamp(r / GAME_CONFIG.player.viewRadius, 0, 1);
      const falloff = 1 - radiusT * 0.6;
      const strength = Phaser.Math.Clamp(v * falloff, 0, 1);

      if (strength > this.visibleStrength[i]) this.visibleStrength[i] = strength;
    });
  }

  isSeen(x: number, y: number): boolean {
    if (!this.world || !inBounds(this.world, x, y)) return false;
    return this.seen[idx(this.world, x, y)] === 1;
  }

  get seenArray() {
    return this.seen;
  }

  render(_playerPath: Vec2[]) {
    if (!this.world || !this.layer) return;

    // Update Tiles
    this.layer.forEachTile(tile => {
      const i = idx(this.world, tile.x, tile.y);
      const isSeen = this.seen[i] === 1;
      const isVis = this.visible[i] === 1;

      if (!isSeen) {
        tile.setVisible(false);
      } else {
        tile.setVisible(true);
        if (isVis) {
          tile.alpha = 1.0;
          tile.tint = 0xffffff;
        } else {
          tile.alpha = isWall(this.world, tile.x, tile.y) ? 0.4 : 0.2;
          tile.tint = 0x888888;
        }
      }
    });

    // Actors
    const activeEnemyIds = new Set<EntityId>();
    for (const a of this.world.actors.values()) {
      const i = idx(this.world, a.pos.x, a.pos.y);
      const isVis = this.visible[i] === 1;
      
      if (a.isPlayer) {
        if (this.playerSprite) {
          this.playerSprite.setPosition(a.pos.x * TILE_SIZE + TILE_SIZE / 2, a.pos.y * TILE_SIZE + TILE_SIZE / 2);
          this.playerSprite.setVisible(true);
        }
        continue;
      }

      if (!isVis) continue;

      activeEnemyIds.add(a.id);
      let sprite = this.enemySprites.get(a.id);
      const textureKey = a.type === "bat" ? "bat" : "rat";
      
      if (!sprite) {
        sprite = this.scene.add.sprite(0, 0, textureKey, 0);
        sprite.setDepth(99);
        sprite.play(`${textureKey}-idle`);
        this.enemySprites.set(a.id, sprite);
      }

      sprite.setPosition(a.pos.x * TILE_SIZE + TILE_SIZE / 2, a.pos.y * TILE_SIZE + TILE_SIZE / 2);
      sprite.setVisible(true);
    }

    for (const [id, sprite] of this.enemySprites.entries()) {
      if (!activeEnemyIds.has(id)) {
        sprite.setVisible(false);
        if (!this.world.actors.has(id)) {
          sprite.destroy();
          this.enemySprites.delete(id);
        }
      }
    }

    // Orbs
    const activeOrbIds = new Set<EntityId>();
    for (const a of this.world.actors.values()) {
      if (a.type !== "exp_orb") continue;
      
      const i = idx(this.world, a.pos.x, a.pos.y);
      // PD usually shows items only when visible or seen. Let's do visible.
      const isVis = this.visible[i] === 1;

      
      if (!isVis) continue;

      activeOrbIds.add(a.id);
      let orb = this.orbSprites.get(a.id);
      if (!orb) {
        orb = this.scene.add.circle(0, 0, 4, GAME_CONFIG.rendering.expOrbColor);
        orb.setStrokeStyle(1, 0xffffff, 0.5);
        orb.setDepth(45);
        this.orbSprites.set(a.id, orb);
      }
      orb.setPosition(a.pos.x * TILE_SIZE + TILE_SIZE / 2, a.pos.y * TILE_SIZE + TILE_SIZE / 2);
      orb.setVisible(true);
    }

    for (const [id, orb] of this.orbSprites.entries()) {
      if (!activeOrbIds.has(id)) {
        orb.setVisible(false);
        if (!this.world.actors.has(id)) {
          orb.destroy();
          this.orbSprites.delete(id);
        }
      }
    }


    this.renderMinimap();
  }

  private renderMinimap() {
    this.minimapGfx.clear();
    if (!this.world) return;

    const padding = GAME_CONFIG.ui.minimapPadding;
    const availableWidth = GAME_CONFIG.ui.minimapPanelWidth - padding * 2;
    const availableHeight = GAME_CONFIG.ui.minimapPanelHeight - padding * 2;
    
    const scaleX = availableWidth / this.world.width;
    const scaleY = availableHeight / this.world.height;
    const tileSize = Math.floor(Math.min(scaleX, scaleY));
    
    const mapPixelWidth = this.world.width * tileSize;
    const mapPixelHeight = this.world.height * tileSize;
    const offsetX = -mapPixelWidth / 2;
    const offsetY = -mapPixelHeight / 2;

    for (let y = 0; y < this.world.height; y++) {
      for (let x = 0; x < this.world.width; x++) {
        const i = idx(this.world, x, y);
        if (this.seen[i] !== 1) continue;

        const wall = isWall(this.world, x, y);
        const color = wall ? 0x666666 : 0x333333;

        this.minimapGfx.fillStyle(color, 1);
        this.minimapGfx.fillRect(offsetX + x * tileSize, offsetY + y * tileSize, tileSize, tileSize);
      }
    }

    const ex = this.world.exit.x;
    const ey = this.world.exit.y;
    if (this.seen[idx(this.world, ex, ey)] === 1) {
      this.minimapGfx.fillStyle(0xffd166, 1);
      this.minimapGfx.fillRect(offsetX + ex * tileSize, offsetY + ey * tileSize, tileSize, tileSize);
    }

    const player = [...this.world.actors.values()].find(a => a.isPlayer);
    if (player) {
      this.minimapGfx.fillStyle(0x66ff66, 1);
      this.minimapGfx.fillRect(offsetX + player.pos.x * tileSize, offsetY + player.pos.y * tileSize, tileSize, tileSize);
    }

    for (const a of this.world.actors.values()) {
      if (a.isPlayer) continue;
      const i = idx(this.world, a.pos.x, a.pos.y);
      if (this.visible[i] === 1) {
        this.minimapGfx.fillStyle(0xff6666, 1);
        this.minimapGfx.fillRect(offsetX + a.pos.x * tileSize, offsetY + a.pos.y * tileSize, tileSize, tileSize);
      }
    }
  }

  showDamage(x: number, y: number, amount: number) {
    const screenX = x * TILE_SIZE + TILE_SIZE / 2;
    const screenY = y * TILE_SIZE;

    const text = this.scene.add.text(screenX, screenY, amount.toString(), {
      fontSize: "16px",
      color: "#ff3333",
      stroke: "#000",
      strokeThickness: 2,
      fontStyle: "bold"
    }).setOrigin(0.5, 1).setDepth(200);

    this.scene.tweens.add({
      targets: text,
      y: screenY - 24,
      alpha: 0,
      duration: 800,
      ease: "Power1",
      onComplete: () => text.destroy()
    });
  }

  spawnCorpse(x: number, y: number, type: "player" | "rat" | "bat" | "exp_orb") {
    if (type === "exp_orb") return;
    const textureKey = type === "player" ? "warrior" : type;

    const corpse = this.scene.add.sprite(
      x * TILE_SIZE + TILE_SIZE / 2,
      y * TILE_SIZE + TILE_SIZE / 2,
      textureKey,
      0
    );
    corpse.setDepth(50);
    corpse.play(`${textureKey}-die`);
    this.corpseSprites.push(corpse);
  }

  showWait(x: number, y: number) {
    const screenX = x * TILE_SIZE + TILE_SIZE / 2;
    const screenY = y * TILE_SIZE;

    const text = this.scene.add.text(screenX, screenY, "zZz", {
      fontSize: "14px",
      color: "#aaaaff",
      stroke: "#000",
      strokeThickness: 2,
      fontStyle: "bold"
    }).setOrigin(0.5, 1).setDepth(200);

    this.scene.tweens.add({
      targets: text,
      y: screenY - 20,
      alpha: 0,
      duration: 600,
      ease: "Power1",
      onComplete: () => text.destroy()
    });
  }

  spawnOrb(_orbId: EntityId, _x: number, _y: number) {
    // Just to trigger a render update if needed, but render() handles it
  }

  collectOrb(_actorId: EntityId, amount: number, x: number, y: number) {

    const screenX = x * TILE_SIZE + TILE_SIZE / 2;
    const screenY = y * TILE_SIZE;

    const text = this.scene.add.text(screenX, screenY, `+${amount} EXP`, {
      fontSize: "14px",
      color: "#" + GAME_CONFIG.rendering.expTextColor.toString(16),
      stroke: "#000",
      strokeThickness: 2,
      fontStyle: "bold"
    }).setOrigin(0.5, 1).setDepth(200);

    this.scene.tweens.add({
      targets: text,
      y: screenY - 32,
      alpha: 0,
      duration: 1000,
      ease: "Power1",
      onComplete: () => text.destroy()
    });
  }

  showLevelUp(x: number, y: number) {
    const screenX = x * TILE_SIZE + TILE_SIZE / 2;
    const screenY = y * TILE_SIZE;

    const text = this.scene.add.text(screenX, screenY - 16, "+1 LVL", {
      fontSize: "20px",
      color: "#" + GAME_CONFIG.rendering.levelUpColor.toString(16),
      stroke: "#000",
      strokeThickness: 3,
      fontStyle: "bold"
    }).setOrigin(0.5, 1).setDepth(210);

    this.scene.tweens.add({
      targets: text,
      y: screenY - 60,
      alpha: 0,
      duration: 1500,
      ease: "Cubic.out",
      onComplete: () => text.destroy()
    });
  }
}

