import Phaser from "phaser";
import { type World, type EntityId, type Vec2, type ActorType } from "../core/types";
import { TILE_SIZE } from "../core/constants";
import { idx, isWall } from "../engine/world/world-logic";
import { GAME_CONFIG } from "../core/config/GameConfig";
import { ITEMS } from "../core/config/Items";
import { FovManager } from "./FovManager";
import { MinimapRenderer } from "./MinimapRenderer";
import { FxRenderer } from "./FxRenderer";

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 itemSprites: Map<EntityId, Phaser.GameObjects.Sprite> = new Map();

  private fovManager: FovManager;
  private minimapRenderer: MinimapRenderer;
  private fxRenderer: FxRenderer;
  
  private world!: World;

  constructor(scene: Phaser.Scene) {
    this.scene = scene;
    this.fovManager = new FovManager();
    this.minimapRenderer = new MinimapRenderer(scene);
    this.fxRenderer = new FxRenderer(scene);
  }

  initializeFloor(world: World, playerId: EntityId) {
    this.world = world;
    this.fovManager.initialize(world);

    // 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("dungeon", "dungeon", 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);
    });

    this.fxRenderer.clearCorpses();
    
    // Ensure player sprite exists
    if (!this.playerSprite) {
      this.playerSprite = this.scene.add.sprite(0, 0, "warrior", 0);
      this.playerSprite.setDepth(100);
      this.playerSprite.play('warrior-idle');
    }

    this.minimapRenderer.positionMinimap();
    
    // Reset player sprite position to prevent tween animation from old floor
    if (this.playerSprite) {
      // Kill any active tweens on the player sprite
      this.scene.tweens.killTweensOf(this.playerSprite);
      
      // Get player position in new world using provided playerId
      const player = world.actors.get(playerId);
      if (player && player.category === "combatant") {
        this.playerSprite.setPosition(
          player.pos.x * TILE_SIZE + TILE_SIZE / 2,
          player.pos.y * TILE_SIZE + TILE_SIZE / 2
        );
      }
    }
  }



  toggleMinimap() {
    this.minimapRenderer.toggle();
  }

  isMinimapVisible(): boolean {
    return this.minimapRenderer.isVisible();
  }

  computeFov(playerId: EntityId) {
    this.fovManager.compute(this.world, playerId);
  }

  isSeen(x: number, y: number): boolean {
    return this.fovManager.isSeen(x, y);
  }

  updateTile(x: number, y: number) {
     if (!this.map || !this.world) return;
     const t = this.world.tiles[idx(this.world, x, y)];
     this.map.putTileAt(t, x, y);
  }

  get seenArray() {
    return this.fovManager.seenArray;
  }

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

    const seen = this.fovManager.seenArray;
    const visible = this.fovManager.visibleArray;

    // Update Tiles
    this.layer.forEachTile(tile => {
      const i = idx(this.world, tile.x, tile.y);
      const worldTile = this.world.tiles[i];
      
      // Sync visual tile with logical tile (e.g. if grass was destroyed)
      if (tile.index !== worldTile) {
        // We can safely update the index property for basic tile switching
        // If we needed to change collision properties, we'd use putTileAt
        tile.index = worldTile;
      }

      const isSeen = seen[i] === 1;
      const isVis = 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 (Combatants)
    const activeEnemyIds = new Set<EntityId>();
    const activeOrbIds = new Set<EntityId>();
    const activeItemIds = new Set<EntityId>();

    for (const a of this.world.actors.values()) {
      const i = idx(this.world, a.pos.x, a.pos.y);
      const isVis = visible[i] === 1;

      if (a.category === "combatant") {
        if (a.isPlayer) {
          if (this.playerSprite) {
            const tx = a.pos.x * TILE_SIZE + TILE_SIZE / 2;
            const ty = a.pos.y * TILE_SIZE + TILE_SIZE / 2;
            
            if (this.playerSprite.x !== tx || this.playerSprite.y !== ty) {
                this.scene.tweens.add({
                    targets: this.playerSprite,
                    x: tx,
                    y: ty,
                    duration: 120,
                    ease: 'Quad.easeOut',
                    overwrite: true
                });
            }
            this.playerSprite.setVisible(true);
          }
          continue;
        }


        if (!isVis) continue;

        activeEnemyIds.add(a.id);
        let sprite = this.enemySprites.get(a.id);
        const textureKey = a.type;
        
        const tx = a.pos.x * TILE_SIZE + TILE_SIZE / 2;
        const ty = a.pos.y * TILE_SIZE + TILE_SIZE / 2;
        
        if (!sprite) {
          sprite = this.scene.add.sprite(tx, ty, textureKey, 0);
          sprite.setDepth(99);
          sprite.play(`${textureKey}-idle`);
          this.enemySprites.set(a.id, sprite);
          sprite.setVisible(true);
        } else {
            if (!sprite.visible) {
                // If it was hidden, snap to new position immediately
                this.scene.tweens.killTweensOf(sprite);
                sprite.setPosition(tx, ty);
                sprite.setVisible(true);
            } else if (sprite.x !== tx || sprite.y !== ty) {
                // Only tween if it was already visible and moved
                this.scene.tweens.add({
                    targets: sprite,
                    x: tx,
                    y: ty,
                    duration: 120,
                    ease: 'Quad.easeOut',
                    overwrite: true
                });
            }
        }

      } else if (a.category === "collectible") {
        if (a.type === "exp_orb") {
          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);
        }
      } else if (a.category === "item_drop") {
          if (!isVis) continue;
          
          activeItemIds.add(a.id);
          let itemSprite = this.itemSprites.get(a.id);
          if (!itemSprite) {
              itemSprite = this.scene.add.sprite(0, 0, a.item.textureKey, a.item.spriteIndex);
              itemSprite.setDepth(40);
              this.itemSprites.set(a.id, itemSprite);
          }
          const tx = a.pos.x * TILE_SIZE + TILE_SIZE / 2;
          const ty = a.pos.y * TILE_SIZE + TILE_SIZE / 2;
          itemSprite.setPosition(tx, ty);
          itemSprite.setVisible(true);
          
          // bobbing effect?
          itemSprite.y += Math.sin(this.scene.time.now / 300) * 2; 
      }
    }

    // Cleanup sprites for removed actors
    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);
        }
      }
    }

    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);
        }
      }
    }

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

    this.minimapRenderer.render(this.world, seen, visible);
  }

  // FX Delegations
  showDamage(x: number, y: number, amount: number, isCrit = false, isBlock = false) {
    this.fxRenderer.showDamage(x, y, amount, isCrit, isBlock);
  }

  showDodge(x: number, y: number) {
    this.fxRenderer.showDodge(x, y);
  }

  showHeal(x: number, y: number, amount: number) {
    this.fxRenderer.showHeal(x, y, amount);
  }

  spawnCorpse(x: number, y: number, type: ActorType) {
    this.fxRenderer.spawnCorpse(x, y, type);
  }

  showWait(x: number, y: number) {
    this.fxRenderer.showWait(x, y);
  }

  spawnOrb(_orbId: EntityId, _x: number, _y: number) {
    // Handled in render()
  }

  collectOrb(actorId: EntityId, amount: number, x: number, y: number) {
    this.fxRenderer.collectOrb(actorId, amount, x, y);
  }

  showLevelUp(x: number, y: number) {
    this.fxRenderer.showLevelUp(x, y);
  }

  showAlert(x: number, y: number) {
    this.fxRenderer.showAlert(x, y);
  }

  showProjectile(from: Vec2, to: Vec2, itemId: string, onComplete: () => void) {
      // World coords
      const startX = from.x * TILE_SIZE + TILE_SIZE / 2;
      const startY = from.y * TILE_SIZE + TILE_SIZE / 2;
      const endX = to.x * TILE_SIZE + TILE_SIZE / 2;
      const endY = to.y * TILE_SIZE + TILE_SIZE / 2;

      // Create sprite
      // Look up sprite index from config
      const itemConfig = ITEMS[itemId];
      const texture = itemConfig?.textureKey ?? "items";
      const frame = itemConfig?.spriteIndex ?? 0;

      // Use 'items' spritesheet
      const sprite = this.scene.add.sprite(startX, startY, texture, frame);
      sprite.setDepth(2000);
      
      // Rotate?
      const angle = Phaser.Math.Angle.Between(startX, startY, endX, endY);
      sprite.setRotation(angle + Math.PI / 4); // Adjust for sprite orientation (diagonal usually)

      const dist = Phaser.Math.Distance.Between(startX, startY, endX, endY);
      const duration = dist * 2; // speed

      this.scene.tweens.add({
          targets: sprite,
          x: endX,
          y: endY,
          rotation: sprite.rotation + 4 * Math.PI, // Spin effect
          duration: duration,
          ease: 'Linear',
          onComplete: () => {
              sprite.destroy();
              onComplete();
          }
      });
  }

  shakeCamera() {
      this.scene.cameras.main.shake(100, 0.01);
  }
}
