import * as THREE from 'three';
import { Sword } from './Sword';
import { Staff } from './Staff';
import { ChainLightning } from './ChainLightning';
import { loadGLB } from './MeshLoader';
import type { Game } from './Game';
import type { SkillSystem } from './Skills';

export interface PlayerStats {
  maxHealth: number;
  regen: number;
  lifesteal: number;
  shield: number;
  shieldRechargeDelay: number;
  moveSpeed: number;
  swordDamage: number;
  swordRange: number;
  thornDamage: number;
}

export class Player {
  body: THREE.Group;
  leftHandWeapon: Staff;
  rightHandWeapon: Sword;
  skillLightning: ChainLightning;
  velocity = new THREE.Vector3();
  health = 100;
  game!: Game;
  jumpVelocity = 0;
  onGround = true;
  shieldHp = 0;
  shieldTimer = 0;
  slowTimer = 0;
  slowFactor = 0.6;
  pullTime = 0;
  pullDir = new THREE.Vector3();
  stunTime = 0;
  rootTime = 0;
  rootCooldown = 0;
  stats: PlayerStats = {
    maxHealth: 100,
    regen: 0,
    lifesteal: 0,
    shield: 0,
    shieldRechargeDelay: 8,
    moveSpeed: 7.5,
    swordDamage: 50,
    swordRange: 2.2,
    thornDamage: 0,
  };
  mixer!: THREE.AnimationMixer;
  private clips: Map<string, THREE.AnimationAction> = new Map();
  private currentAnim = '';
  private backShield: THREE.Group | null = null;
  private bodyMaterials: THREE.MeshToonMaterial[] = [];
  private hurtCooldown = 0;
  private flashTime = 0;
  private stunStars: THREE.Group;
  private static readonly BASE_COLOR = 0x44aa44;
  private static readonly HURT_COLOR = 0xff5555;
  private static readonly SLOW_COLOR = 0x9944cc;
  private static readonly HURT_FLASH_TIME = 0.3;

  constructor() {
    this.body = new THREE.Group();

    // Stun-Sterne: kreisen um den Kopf, solange der Spieler gestunnt ist
    this.stunStars = new THREE.Group();
    const starGeom = new THREE.OctahedronGeometry(0.13);
    const starMat = new THREE.MeshBasicMaterial({ color: 0xffdd33 });
    for (let i = 0; i < 3; i++) {
      const a = (i / 3) * Math.PI * 2;
      const star = new THREE.Mesh(starGeom, starMat);
      star.position.set(Math.cos(a) * 0.55, 1.35, Math.sin(a) * 0.55);
      this.stunStars.add(star);
    }
    this.stunStars.visible = false;
    this.body.add(this.stunStars);

    // Helmet
    const helmetGeom = new THREE.SphereGeometry(0.45, 8, 8, 0, Math.PI * 2, 0, Math.PI / 2);
    const helmetMat = new THREE.MeshToonMaterial({ color: 0x888888 });
    const helmet = new THREE.Mesh(helmetGeom, helmetMat);
    helmet.position.y = 0.9;
    helmet.castShadow = true;
    this.body.add(helmet);

    // Try loading the real helmet model (replaces the placeholder sphere)
    loadGLB('helmet').then((gltf) => {
      const realHelmet = gltf.scene;
      const helmetTexture = new THREE.TextureLoader().load('./textures/helmet.png');
      helmetTexture.flipY = false;
      helmetTexture.colorSpace = THREE.SRGBColorSpace;
      realHelmet.traverse((child) => {
        if (child instanceof THREE.Mesh) {
          child.castShadow = true;
          child.material = new THREE.MeshToonMaterial({ map: helmetTexture });
        }
      });
      realHelmet.scale.set(0.35, 0.35, 0.35);
      realHelmet.position.set(0, 0.9, -0.2);
      realHelmet.rotation.set(-0.4, 0, 0);
      this.body.remove(helmet);
      this.body.add(realHelmet);
    }).catch(() => {
      // Keep placeholder helmet
    });

    // Shadow disc
    const shadowGeom = new THREE.CircleGeometry(0.4, 16);
    const shadowMat = new THREE.MeshBasicMaterial({
      color: 0x000000, transparent: true, opacity: 0.3,
    });
    const shadow = new THREE.Mesh(shadowGeom, shadowMat);
    shadow.rotation.x = -Math.PI / 2;
    shadow.position.y = -0.48;
    shadow.renderOrder = 999;
    this.body.add(shadow);

    // Shield on the back while the shield skill is charged
    loadGLB('shield').then((gltf) => {
      const shieldModel = gltf.scene;
      const shieldTexture = new THREE.TextureLoader().load('./textures/shield.png');
      shieldTexture.flipY = false;
      shieldTexture.colorSpace = THREE.SRGBColorSpace;
      shieldModel.traverse((child) => {
        if (child instanceof THREE.Mesh) {
          const geo = child.geometry;
          // The converted model has no UVs; project them from the
          // front plane so the shield texture maps onto the face
          if (!geo.attributes.uv) {
            geo.computeBoundingBox();
            const bb = geo.boundingBox!;
            const pos = geo.attributes.position;
            const uv = new Float32Array(pos.count * 2);
            for (let i = 0; i < pos.count; i++) {
              uv[i * 2] = (pos.getX(i) - bb.min.x) / (bb.max.x - bb.min.x);
              uv[i * 2 + 1] = (pos.getY(i) - bb.min.y) / (bb.max.y - bb.min.y);
            }
            geo.setAttribute('uv', new THREE.BufferAttribute(uv, 2));
          }
          child.castShadow = true;
          child.material = new THREE.MeshToonMaterial({ map: shieldTexture });
        }
      });
      // The geometry is offset from the model origin; recenter it so
      // rotation and scale act on the shield's center
      const box = new THREE.Box3().setFromObject(shieldModel);
      const center = box.getCenter(new THREE.Vector3());
      shieldModel.position.sub(center);
      const shield = new THREE.Group();
      shield.add(shieldModel);
      shield.scale.set(0.25, 0.25, 0.25);
      shield.position.set(0, 0.85, -0.5);
      shield.rotation.set(-0.15, Math.PI, 0);
      shield.visible = this.stats.shield > 0 && this.shieldHp > 0;
      this.backShield = shield;
      this.body.add(shield);
    }).catch(() => {
      // No shield model available
    });

    // Weapons
    this.leftHandWeapon = new Staff();
    this.rightHandWeapon = new Sword();
    this.skillLightning = new ChainLightning();
    this.leftHandWeapon.mesh.visible = false;
    this.body.add(this.leftHandWeapon.mesh);
    this.body.add(this.rightHandWeapon.mesh);
  }

  async init() {
    try {
      const gltf = await loadGLB('blob');
      const model = gltf.scene;
      model.scale.set(0.5, 0.5, 0.5);
      model.traverse((child) => {
        if (child instanceof THREE.Mesh) {
          child.castShadow = true;
          child.receiveShadow = true;
          const mat = new THREE.MeshToonMaterial({
            color: Player.BASE_COLOR,
          });
          child.material = mat;
          this.bodyMaterials.push(mat);
        }
      });
      this.body.add(model);

      if (gltf.animations.length > 0) {
        this.mixer = new THREE.AnimationMixer(model);
        for (const clip of gltf.animations) {
          const action = this.mixer.clipAction(clip);
          this.clips.set(clip.name, action);
        }
        this.playAnimation('stand');
      }
    } catch (e) {
      console.warn('Failed to load blob model, using fallback', e);
      // Fallback capsule
      const bodyGeom = new THREE.CapsuleGeometry(0.5, 0.5, 8, 16);
      const bodyMat = new THREE.MeshToonMaterial({ color: Player.BASE_COLOR });
      const bodyMesh = new THREE.Mesh(bodyGeom, bodyMat);
      bodyMesh.position.y = 0.75;
      bodyMesh.castShadow = true;
      this.body.add(bodyMesh);
      this.bodyMaterials.push(bodyMat);
    }
  }

  playAnimation(name: string, loop = true, speed = 1) {
    if (!this.clips.has(name)) return;
    if (this.currentAnim === name) return;
    // Stop the previous animation so it doesn't blend with the new one
    if (this.currentAnim) {
      const prev = this.clips.get(this.currentAnim);
      if (prev) prev.stop();
    }
    this.currentAnim = name;
    const action = this.clips.get(name)!;
    action.reset();
    action.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce, loop ? Infinity : 1);
    action.clampWhenFinished = !loop;
    action.setEffectiveTimeScale(speed);
    action.weight = 1;
    action.play();
    return action;
  }

  stopAnimation(name: string) {
    const action = this.clips.get(name);
    if (action) action.stop();
  }

  getCurrentAnimation(): string {
    return this.currentAnim;
  }

  updateAnimations(dt: number) {
    if (this.mixer) this.mixer.update(dt);
    if (this.hurtCooldown > 0) this.hurtCooldown -= dt;
    if (this.slowTimer > 0) this.slowTimer -= dt;
    if (this.stunTime > 0) this.stunTime -= dt;
    if (this.rootTime > 0) this.rootTime -= dt;
    if (this.rootCooldown > 0) this.rootCooldown -= dt;

    // Stun-Sterne animieren
    this.stunStars.visible = this.stunTime > 0;
    if (this.stunStars.visible) {
      this.stunStars.rotation.y += dt * 5;
      for (const star of this.stunStars.children) {
        star.rotation.x += dt * 8;
        star.rotation.y += dt * 6;
        star.position.y = 1.35 + Math.sin(this.stunTime * 10) * 0.08;
      }
    }

    let color: THREE.Color;
    if (this.flashTime > 0) {
      this.flashTime -= dt;
      const t = Math.max(0, this.flashTime) / Player.HURT_FLASH_TIME;
      color = new THREE.Color(Player.BASE_COLOR).lerp(
        new THREE.Color(Player.HURT_COLOR), t
      );
    } else if (this.slowTimer > 0) {
      color = new THREE.Color(Player.BASE_COLOR).lerp(
        new THREE.Color(Player.SLOW_COLOR), 0.55
      );
    } else {
      color = new THREE.Color(Player.BASE_COLOR);
    }
    for (const mat of this.bodyMaterials) mat.color.copy(color);
  }

  setGame(game: Game) {
    this.game = game;
  }

  jump() {
    if (this.onGround) {
      this.jumpVelocity = 8;
      this.onGround = false;
      this.playAnimation('jump', false);
    }
  }

  heal(amount: number) {
    this.health = Math.min(this.health + amount, this.stats.maxHealth);
  }

  damage(amount: number, sourcePos?: THREE.Vector3) {
    if (this.shieldHp > 0) {
      const absorbed = Math.min(this.shieldHp, amount);
      this.shieldHp -= absorbed;
      amount -= absorbed;
      this.shieldTimer = this.stats.shieldRechargeDelay;
    }
    this.health -= amount;
    if (amount > 0 && this.hurtCooldown <= 0) {
      this.hurtCooldown = 0.25;
      this.flashTime = Player.HURT_FLASH_TIME;
      if (this.game) {
        this.game.onPlayerHurt(sourcePos ?? this.body.position);
      }
    }
  }

  updatePassives(dt: number) {
    if (this.stats.regen > 0) {
      this.heal(this.stats.regen * dt);
    }
    if (this.stats.shield > 0) {
      if (this.shieldHp < this.stats.shield) {
        this.shieldTimer -= dt;
        if (this.shieldTimer <= 0) {
          this.shieldHp = this.stats.shield;
        }
      } else {
        this.shieldTimer = 0;
      }
    }
    if (this.backShield) {
      this.backShield.visible = this.stats.shield > 0 && this.shieldHp > 0;
    }
  }

  applySkillSystem(skills: SkillSystem) {
    const oldMax = this.stats.maxHealth;
    const l = (id: string) => skills.getLevel(id);

    this.stats = {
      maxHealth: 100 + 25 * l('maxHp'),
      regen: 1.5 * l('regen'),
      lifesteal: 0.05 * l('lifesteal'),
      shield: 25 * l('shield'),
      shieldRechargeDelay: 8,
      moveSpeed: 7.5 + l('speed'),
      swordDamage: 50 + 25 * l('swordDamage'),
      swordRange: 2.2 + 0.6 * l('swordRange'),
      thornDamage: 15 * l('thorns'),
    };

    if (this.stats.maxHealth > oldMax) {
      this.health = this.stats.maxHealth;
    } else {
      this.health = Math.min(this.health, this.stats.maxHealth);
    }
    if (this.stats.shield > 0 && this.shieldHp <= 0) {
      this.shieldHp = this.stats.shield;
    }
    if (this.backShield) {
      this.backShield.visible = this.stats.shield > 0 && this.shieldHp > 0;
    }
  }

  die() {
    this.playAnimation('die', false);
  }

  reset() {
    this.health = 100;
    this.shieldHp = 0;
    this.shieldTimer = 0;
    this.slowTimer = 0;
    this.pullTime = 0;
    this.stunTime = 0;
    this.rootTime = 0;
    this.rootCooldown = 0;
    this.body.position.set(0, 0.5, 0);
    this.velocity.set(0, 0, 0);
    this.jumpVelocity = 0;
    this.onGround = true;
    this.playAnimation('stand');
  }
}
