import * as THREE from 'three';
import { Player } from './Player';
import { Enemy, initSpiderModel } from './Enemy';
import { Sword } from './Sword';
import { Staff } from './Staff';
import { Fireball } from './Fireball';
import { Cross, initCrossModel } from './Cross';
import { Arena } from './Arena';
import { initSounds, playSound, resumeContext } from './SoundManager';

const SPAWN_TIME = 10;

export class Game {
  scene: THREE.Scene;
  camera: THREE.PerspectiveCamera;
  renderer: THREE.WebGLRenderer;
  clock: THREE.Clock;

  player!: Player;
  enemies: Enemy[] = [];
  fireballs: Fireball[] = [];
  crosses: Cross[] = [];

  keys: Set<string> = new Set();
  mouseButtons: Set<number> = new Set();
  private prevMouseButtons: Set<number> = new Set();
  mouseX = 0;
  mouseY = 0;
  groundPoint = new THREE.Vector3();

  spawnTimer = 0;
  spawns = 0;
  killed = 0;
  gameOver = false;

  // Debug hitzone visualization
  private showHitzones = false;
  private playerHitRing!: THREE.Mesh;
  private enemyHitRings: Map<Enemy, THREE.Mesh> = new Map();
  private fireballHitRings: Map<Fireball, THREE.Mesh> = new Map();
  private crossHitRings: Map<Cross, THREE.Mesh> = new Map();

  // UI elements
  private uiHealthBar!: HTMLElement;
  private uiHealthText!: HTMLElement;
  private uiWaveLabel!: HTMLElement;
  private uiKillsLabel!: HTMLElement;
  private uiFireballCD!: HTMLElement;
  private uiSwordCD!: HTMLElement;
  private uiTeleportCD!: HTMLElement;
  private uiWaveBarFill!: HTMLElement;
  private uiWaveBarLabel!: HTMLElement;
  private uiGameOver!: HTMLElement;
  private mouseOnCanvas = false;

  constructor() {
    this.scene = new THREE.Scene();
    this.scene.background = new THREE.Color(0x0d1117);
    this.scene.fog = new THREE.Fog(0x0d1117, 25, 70);

    this.camera = new THREE.PerspectiveCamera(
      60, window.innerWidth / window.innerHeight, 1, 150
    );
    this.camera.position.set(0, 25, -15);
    this.camera.lookAt(0, 0, 0);

    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    this.renderer.shadowMap.enabled = true;
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    document.body.prepend(this.renderer.domElement);

    this.clock = new THREE.Clock();

    this.setupUI();
    this.setupInput();
    this.setupLighting();
  }

  async init() {
    this.setupArena();
    this.setupPlayer();
    this.player.setGame(this);

    initSounds();

    await Promise.all([
      this.player.init(),
      this.player.rightHandWeapon.init(),
      this.player.leftHandWeapon.init(),
      initSpiderModel(),
      initCrossModel(),
    ]);
  }

  start() {
    window.addEventListener('resize', () => {
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
    });

    this.animate();
  }

  private setupUI() {
    this.uiHealthBar = document.getElementById('health-bar')!;
    this.uiHealthText = document.getElementById('health-text')!;
    this.uiWaveLabel = document.getElementById('wave-label')!;
    this.uiKillsLabel = document.getElementById('kills-label')!;
    this.uiFireballCD = document.querySelector('#fireball-cooldown .cooldown-fill')!;
    this.uiSwordCD = document.querySelector('#sword-cooldown .cooldown-fill')!;
    this.uiTeleportCD = document.querySelector('#teleport-cooldown .cooldown-fill')!;
    this.uiWaveBarFill = document.getElementById('wave-bar-fill')!;
    this.uiWaveBarLabel = document.getElementById('wave-bar-label')!;
    this.uiGameOver = document.getElementById('game-over')!;

    const canvas = this.renderer.domElement;
    canvas.addEventListener('mouseenter', () => this.mouseOnCanvas = true);
    canvas.addEventListener('mouseleave', () => this.mouseOnCanvas = false);
    canvas.addEventListener('contextmenu', e => e.preventDefault());
  }

  private setupInput() {
    window.addEventListener('keydown', (e) => {
      this.keys.add(e.code);
      if (e.code === 'Space') {
        this.player.jump();
        e.preventDefault();
      }
      if (e.code === 'KeyH') {
        this.showHitzones = !this.showHitzones;
      }
    });
    window.addEventListener('keyup', (e) => {
      this.keys.delete(e.code);
      if (e.code === 'KeyE' && !this.gameOver) {
        this.player.leftHandWeapon.skill2(this.groundPoint, this.player);
      }
    });

    window.addEventListener('mousedown', (e) => {
      resumeContext();
      this.mouseButtons.add(e.button);
      this.prevMouseButtons.add(e.button);
    });
    window.addEventListener('mouseup', (e) => {
      resumeContext();
      this.mouseButtons.delete(e.button);
      this.handleMouseRelease(e.button);
    });
    window.addEventListener('mousemove', (e) => {
      this.mouseX = (e.clientX / window.innerWidth) * 2 - 1;
      this.mouseY = -(e.clientY / window.innerHeight) * 2 + 1;
    });
  }

  private handleMouseRelease(button: number) {
    if (this.gameOver) return;
    if (button === 0) {
      this.player.leftHandWeapon.skill1(this.groundPoint, this.player);
    } else if (button === 1) {
      this.player.switchHands();
    } else if (button === 2) {
      this.player.rightHandWeapon.skill1(this.groundPoint, this.player);
    }
  }

  private setupLighting() {
    const ambient = new THREE.AmbientLight(0x556688, 0.35);
    this.scene.add(ambient);

    const dirLight = new THREE.DirectionalLight(0xccddff, 0.55);
    dirLight.position.set(30, 40, 20);
    dirLight.castShadow = true;
    dirLight.shadow.mapSize.width = 1024;
    dirLight.shadow.mapSize.height = 1024;
    dirLight.shadow.camera.near = 1;
    dirLight.shadow.camera.far = 150;
    dirLight.shadow.camera.left = -60;
    dirLight.shadow.camera.right = 60;
    dirLight.shadow.camera.top = 60;
    dirLight.shadow.camera.bottom = -60;
    this.scene.add(dirLight);
  }

  private setupArena() {
    const arena = new Arena();
    this.scene.add(arena);
  }

  private setupPlayer() {
    this.player = new Player();
    this.player.body.position.set(0, 0.5, 0);
    this.scene.add(this.player.body);

    // Player hitzone ring (contact damage radius = 2)
    this.playerHitRing = this.createHitRing(2, 0xff0000);
    this.scene.add(this.playerHitRing);
  }

  private createHitRing(radius: number, color: number): THREE.Mesh {
    const geom = new THREE.RingGeometry(radius - 0.05, radius, 48);
    const mat = new THREE.MeshBasicMaterial({
      color,
      transparent: true,
      opacity: 0.5,
      side: THREE.DoubleSide,
      depthWrite: false,
    });
    const ring = new THREE.Mesh(geom, mat);
    ring.rotation.x = -Math.PI / 2;
    ring.position.y = 0.05;
    ring.renderOrder = 999;
    return ring;
  }

  private updateHitzones() {
    this.playerHitRing.position.x = this.player.body.position.x;
    this.playerHitRing.position.z = this.player.body.position.z;
    this.playerHitRing.visible = this.showHitzones;

    // Enemy rings
    for (const enemy of this.enemies) {
      let ring = this.enemyHitRings.get(enemy);
      if (!ring) {
        ring = this.createHitRing(2, 0xff8800);
        this.scene.add(ring);
        this.enemyHitRings.set(enemy, ring);
      }
      ring.position.x = enemy.body.position.x;
      ring.position.z = enemy.body.position.z;
      ring.visible = this.showHitzones && !enemy.dead;
    }
    // Clean up removed enemies
    for (const [enemy, ring] of this.enemyHitRings) {
      if (!this.enemies.includes(enemy)) {
        this.scene.remove(ring);
        ring.geometry.dispose();
        (ring.material as THREE.Material).dispose();
        this.enemyHitRings.delete(enemy);
      }
    }

    // Fireball rings (impact radius 1.5 / AoE 5)
    for (const fb of this.fireballs) {
      let ring = this.fireballHitRings.get(fb);
      if (!ring) {
        ring = this.createHitRing(fb.exploding ? 5 : 1.5, fb.exploding ? 0xffff00 : 0xff6600);
        this.scene.add(ring);
        this.fireballHitRings.set(fb, ring);
      }
      ring.position.x = fb.body.position.x;
      ring.position.z = fb.body.position.z;
      ring.visible = this.showHitzones;
    }
    for (const [fb, ring] of this.fireballHitRings) {
      if (!this.fireballs.includes(fb)) {
        this.scene.remove(ring);
        ring.geometry.dispose();
        (ring.material as THREE.Material).dispose();
        this.fireballHitRings.delete(fb);
      }
    }

    // Cross rings (pickup radius 2)
    for (const cross of this.crosses) {
      let ring = this.crossHitRings.get(cross);
      if (!ring) {
        ring = this.createHitRing(2, 0x00ff00);
        this.scene.add(ring);
        this.crossHitRings.set(cross, ring);
      }
      ring.position.x = cross.body.position.x;
      ring.position.z = cross.body.position.z;
      ring.visible = this.showHitzones;
    }
    for (const [cross, ring] of this.crossHitRings) {
      if (!this.crosses.includes(cross)) {
        this.scene.remove(ring);
        ring.geometry.dispose();
        (ring.material as THREE.Material).dispose();
        this.crossHitRings.delete(cross);
      }
    }
  }

  private spawnEnemy() {
    const x = (Math.random() - 0.5) * 80;
    const z = (Math.random() - 0.5) * 80;
    const enemy = new Enemy(this.player.body.position);
    enemy.body.position.set(x, 0.1, z);
    this.scene.add(enemy.body);
    this.enemies.push(enemy);
  }

  private spawnCross(position: THREE.Vector3) {
    if (Math.random() > 0.9) {
      const cross = new Cross(position.clone());
      this.scene.add(cross.body);
      this.crosses.push(cross);
    }
  }

  private updateGroundPoint() {
    const raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(new THREE.Vector2(this.mouseX, this.mouseY), this.camera);
    const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
    const intersection = new THREE.Vector3();
    if (raycaster.ray.intersectPlane(plane, intersection)) {
      intersection.y = 0;
      intersection.clamp(
        new THREE.Vector3(-48, 0, -48),
        new THREE.Vector3(48, 0, 48)
      );
      this.groundPoint.copy(intersection);
    }
  }

  private updatePlayerMovement(dt: number) {
    if (this.gameOver) {
      this.player.body.position.y = 0.5;
      return;
    }

    const camFwd = new THREE.Vector3();
    this.camera.getWorldDirection(camFwd);
    camFwd.y = 0;
    camFwd.normalize();

    const camLeft = new THREE.Vector3();
    camLeft.crossVectors(new THREE.Vector3(0, 1, 0), camFwd).normalize();

    const moveDir = new THREE.Vector3();
    if (this.keys.has('KeyW') || this.keys.has('ArrowUp')) moveDir.add(camFwd);
    if (this.keys.has('KeyS') || this.keys.has('ArrowDown')) moveDir.sub(camFwd);
    if (this.keys.has('KeyA') || this.keys.has('ArrowLeft')) moveDir.add(camLeft);
    if (this.keys.has('KeyD') || this.keys.has('ArrowRight')) moveDir.sub(camLeft);

    let speed = 7.5;
    let isMoving = false;
    if (moveDir.length() > 0) {
      moveDir.normalize();
      this.player.velocity.set(moveDir.x * speed, 0, moveDir.z * speed);
      isMoving = true;
    } else {
      this.player.velocity.set(0, 0, 0);
    }

    // Play walk/stand animations
    if (isMoving && this.player.getCurrentAnimation() !== 'walk') {
      this.player.playAnimation('walk');
    } else if (!isMoving && this.player.getCurrentAnimation() !== 'stand') {
      this.player.playAnimation('stand');
    }

    // Jump / gravity
    if (!this.player.onGround) {
      this.player.jumpVelocity -= 20 * dt;
      this.player.body.position.y += this.player.jumpVelocity * dt;
      if (this.player.body.position.y <= 0.5) {
        this.player.body.position.y = 0.5;
        this.player.jumpVelocity = 0;
        this.player.onGround = true;
      }
    }

    // Apply horizontal movement
    this.player.body.position.x += this.player.velocity.x * dt;
    this.player.body.position.z += this.player.velocity.z * dt;
    this.clampToArena(this.player.body.position);

    // Look at ground point
    this.player.body.lookAt(
      this.groundPoint.x,
      this.player.body.position.y,
      this.groundPoint.z
    );
  }

  private clampToArena(pos: THREE.Vector3) {
    pos.x = Math.max(-47, Math.min(47, pos.x));
    pos.z = Math.max(-47, Math.min(47, pos.z));
  }

  private updateEnemies(dt: number) {
    for (const enemy of this.enemies) {
      if (enemy.dead) {
        enemy.updateAnimation(dt);
        continue;
      }

      const toPlayer = new THREE.Vector3()
        .subVectors(this.player.body.position, enemy.body.position);
      toPlayer.y = 0;

      const dist = toPlayer.length();
      if (dist > 0.1) {
        const dir = toPlayer.normalize();
        enemy.body.lookAt(
          enemy.body.position.x + dir.x,
          enemy.body.position.y,
          enemy.body.position.z + dir.z
        );

        enemy.playAnim('walk');

        let moveDir = dir.multiplyScalar(6);
        if (enemy.knockback > 0) {
          moveDir = dir.multiplyScalar(-6);
          enemy.knockback -= dt;
        }
        enemy.body.position.x += moveDir.x * dt;
        enemy.body.position.z += moveDir.z * dt;
        this.clampToArena(enemy.body.position);
      } else {
        enemy.playAnim('stand');
      }

      enemy.updateAnimation(dt);
    }
  }

  private updateFireballs(dt: number) {
    for (let i = this.fireballs.length - 1; i >= 0; i--) {
      const fb = this.fireballs[i];
      fb.lifetime -= dt;

      if (fb.exploding) {
        fb.timer += dt;
        if (fb.timer > 0.2 && !fb.damageDone) {
          fb.damageDone = true;
          for (const enemy of this.enemies) {
            if (enemy.dead) continue;
            const dist = enemy.body.position.distanceTo(fb.body.position);
            if (dist < 8) {
              enemy.takeDamage(100, this);
            }
          }
        }
        if (fb.timer > 2) {
          this.scene.remove(fb.body);
          this.fireballs.splice(i, 1);
        }
        continue;
      }

      fb.body.position.x += fb.direction.x * dt;
      fb.body.position.z += fb.direction.z * dt;
      fb.updateTrail(dt, fb.body.position);

      for (const enemy of this.enemies) {
        if (enemy.dead) continue;
        const dist = enemy.body.position.distanceTo(fb.body.position);
        if (dist < 1.8) {
          fb.explode(this.scene);
          break;
        }
      }

      if (fb.lifetime <= 0) {
        fb.explode(this.scene);
      }
    }
  }

  private updateUI() {
    const healthPct = this.player.health / this.player.MAX_HEALTH;
    this.uiHealthBar.style.width = `${Math.max(0, healthPct * 250)}px`;
    this.uiHealthText.textContent = `${Math.ceil(this.player.health)}%`;

    this.uiWaveLabel.textContent = `Wave ${this.spawns}`;
    this.uiKillsLabel.textContent = `Kills: ${this.killed}`;

    const sword = this.player.rightHandWeapon as Sword;
    const staff = this.player.leftHandWeapon as Staff;

    const fbPct = Math.max(0, (staff.COOLDOWN1_TIME - staff.cooldown1) / staff.COOLDOWN1_TIME);
    this.uiFireballCD.style.width = `${fbPct * 100}%`;

    const swordPct = Math.max(0, (sword.COOLDOWN1_TIME - sword.cooldown1) / sword.COOLDOWN1_TIME);
    this.uiSwordCD.style.width = `${swordPct * 100}%`;

    const tpPct = Math.max(0, (staff.COOLDOWN2_TIME - staff.cooldown2) / staff.COOLDOWN2_TIME);
    this.uiTeleportCD.style.width = `${tpPct * 100}%`;

    const wavePct = this.spawnTimer / SPAWN_TIME;
    this.uiWaveBarFill.style.width = `${wavePct * 100}%`;
    this.uiWaveBarLabel.textContent = `Next Wave: ${Math.ceil(SPAWN_TIME - this.spawnTimer)}s`;

    if (this.player.health <= 0 && !this.gameOver) {
      this.gameOver = true;
      this.player.die();
      this.uiGameOver.style.opacity = '1';
      playSound('gameover', 0.8);
    }
  }

  private animate() {
    requestAnimationFrame(() => this.animate());

    const dt = Math.min(this.clock.getDelta(), 0.1);

    this.updateGroundPoint();
    this.updatePlayerMovement(dt);
    this.updateEnemies(dt);
    this.updateFireballs(dt);
    this.updateHitzones();

    // Update animations
    this.player.updateAnimations(dt);
    this.player.rightHandWeapon.updateAnimation(dt);

    // Spawn enemies in waves
    if (!this.gameOver) {
      this.spawnTimer += dt;
      if (this.spawnTimer >= SPAWN_TIME) {
        this.spawns++;
        for (let i = 0; i < this.spawns; i++) {
          this.spawnEnemy();
        }
        this.spawnTimer = 0;
      }
    }

    // Check close encounters (spider touching player)
    for (const enemy of this.enemies) {
      if (enemy.dead) continue;
      const dist = enemy.body.position.distanceTo(this.player.body.position);
      if (dist < 2) {
        this.player.health -= 25 * dt;
      }
    }

    // Cross collisions
    for (let i = this.crosses.length - 1; i >= 0; i--) {
      const cross = this.crosses[i];
      cross.update(dt);
      const dist = cross.body.position.distanceTo(this.player.body.position);
      if (dist < 2) {
        this.player.heal(25);
        this.scene.remove(cross.body);
        this.crosses.splice(i, 1);
      }
    }

    // Update cooldowns
    this.player.rightHandWeapon.reduceCooldowns(dt);
    this.player.leftHandWeapon.reduceCooldowns(dt);

    // Camera follows player
    this.camera.position.set(
      this.player.body.position.x,
      25,
      this.player.body.position.z - 15
    );
    this.camera.lookAt(
      this.player.body.position.x,
      0,
      this.player.body.position.z
    );

    // Clean dead enemies
    for (let i = this.enemies.length - 1; i >= 0; i--) {
      const enemy = this.enemies[i];
      if (enemy.dead) {
        enemy.deathTimer += dt;
        if (enemy.deathTimer > 10) {
          this.scene.remove(enemy.body);
          this.enemies.splice(i, 1);
        }
      }
    }

    this.updateUI();
    this.renderer.render(this.scene, this.camera);
  }

  removeEnemy(enemy: Enemy) {
    enemy.dead = true;
    this.killed++;
    this.spawnCross(enemy.body.position);
  }
}
