// Modell-Viewer: rendert Gegner-/Boss-Modelle isoliert auf einer Bühne mit
// heller Beleuchtung und Turntable-Kamera — fuer visuelle Modell-Checks ohne
// Spiel-UI, Cheats oder Bosskaempfe. Siehe AGENTS.md "Modell-Viewer".
//
// URL: viewer.html?m=<id>          Modell waehlen
//      viewer.html?m=<id>&still=1  statische Pose (nur updateAnimation, kein update)
import * as THREE from 'three';
import type { Game } from './Game';
import type { Enemy } from './Enemy';
import { Spider, initSpiderModel } from './Spider';
import { Ghost } from './Ghost';
import { MazeGuard } from './MazeGuard';
import { Frog } from './Frog';
import { Bat } from './Bat';
import { BeeSwarm } from './BeeSwarm';
import { Slime } from './Slime';
import { Brute } from './Brute';
import { Golem } from './Golem';
import { Crab } from './Crab';
import { Turret } from './Turret';
import { Shaman } from './Shaman';
import { Archer } from './Archer';
import { Bomber } from './Bomber';
import { Wisp } from './Wisp';
import { Turtle } from './Turtle';
import { SporeMushroom } from './SporeMushroom';
import { Krake } from './Krake';
import { Minotaurus } from './Minotaurus';
import { SpiderQueen } from './SpiderQueen';
import { FrostGiant } from './FrostGiant';
import { Necromancer } from './Necromancer';
import { Dragon } from './Dragon';
import { Gnome } from './Gnome';

const VIEWER_MODELS: { id: string; create: () => Enemy | Promise<Enemy> }[] = [
  { id: 'spider', create: async () => { await initSpiderModel(); return new Spider(); } },
  { id: 'ghost', create: () => new Ghost() },
  { id: 'frog', create: () => new Frog() },
  { id: 'bat', create: () => new Bat() },
  { id: 'bee', create: () => new BeeSwarm() },
  { id: 'slime', create: () => new Slime() },
  { id: 'brute', create: () => new Brute() },
  { id: 'crab', create: () => new Crab() },
  { id: 'turret', create: () => new Turret() },
  { id: 'shaman', create: () => new Shaman() },
  { id: 'archer', create: () => new Archer() },
  { id: 'bomber', create: () => new Bomber() },
  { id: 'wisp', create: () => new Wisp() },
  { id: 'turtle', create: () => new Turtle() },
  { id: 'spore', create: () => new SporeMushroom() },
  { id: 'golem', create: () => new Golem() },
  { id: 'krake', create: () => new Krake() },
  { id: 'minotaurus', create: () => new Minotaurus() },
  { id: 'spiderqueen', create: async () => { await initSpiderModel(); return new SpiderQueen(); } },
  { id: 'frostgiant', create: () => new FrostGiant() },
  { id: 'necromancer', create: () => new Necromancer() },
  { id: 'dragon', create: () => new Dragon() },
  { id: 'gnome', create: () => new Gnome(250, 90) },
  { id: 'mazeguard', create: () => new MazeGuard() },
];

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

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a2030);

const camera = new THREE.PerspectiveCamera(
  50, window.innerWidth / window.innerHeight, 0.1, 300
);

// Bewusst heller als das Spiel, damit Formen gut sichtbar sind
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(8, 14, 6);
dirLight.castShadow = true;
dirLight.shadow.mapSize.set(2048, 2048);
dirLight.shadow.camera.left = -16;
dirLight.shadow.camera.right = 16;
dirLight.shadow.camera.top = 16;
dirLight.shadow.camera.bottom = -16;
scene.add(dirLight);

const ground = new THREE.Mesh(
  new THREE.CircleGeometry(40, 48),
  new THREE.MeshLambertMaterial({ color: 0x2a3a2a })
);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
const grid = new THREE.GridHelper(80, 80, 0x44608a, 0x334455);
grid.position.y = 0.02;
scene.add(grid);

// --- Stub-Spieler (8 m entfernt: Blick-/Angriffsziel) ---
// Nur ein flacher Boden-Ring als Markierung — eine Kugel wuerde die Sicht
// auf Modelle verdecken, wenn Mobs hineingelaufen kommen.
const stubPlayer = {
  body: new THREE.Group(),
  health: 1000,
  pullTime: 0,
  pullDir: new THREE.Vector3(),
  stunTime: 0,
  rootTime: 0,
  rootCooldown: 0,
  slowTimer: 0,
  damage: () => {},
};
stubPlayer.body.position.set(0, 0, 8);
const playerMarker = new THREE.Mesh(
  new THREE.RingGeometry(0.5, 0.8, 24),
  new THREE.MeshBasicMaterial({ color: 0x44aa44, side: THREE.DoubleSide })
);
playerMarker.rotation.x = -Math.PI / 2;
playerMarker.position.y = 0.05;
stubPlayer.body.add(playerMarker);
scene.add(stubPlayer.body);

// --- Stub-Game: deckt die game.*-Oberflaeche aller Gegner ab (per grep ermittelt) ---
const stubGame = {
  scene,
  player: stubPlayer,
  enemies: [],
  traps: [],
  gasClouds: [],
  fireballs: [],
  slashEffects: [],
  maze: null,
  groundPoint: stubPlayer.body.position,
  clampToArena: (v: THREE.Vector3) => v,
  triggerCameraShake: () => {},
  triggerInkVignette: () => {},
  spawnDamageText: () => {},
  spawnEnemyProjectile: () => {},
  spawnEnemyInstance: () => {},
  removeEnemy: () => {},
  onDamageDealt: () => {},
  onPlayerHurt: () => {},
  destroyProjectilesInArc: () => {},
} as unknown as Game;

// --- Modell-Wechsel + Turntable ---
const params = new URLSearchParams(location.search);
const still = params.has('still');

let current: Enemy | null = null;
let orbitAngle = 0;
let fitRadius = 8;
let fitHeight = 2;
let fitCenterY = 1;
let fitTimer = 0;
let frame = 0;

const nameEl = document.getElementById('name')!;
const uiEl = document.getElementById('ui')!;

function refit() {
  if (!current) return;
  // Auto-Fit nur ueber Modell-Meshes: grosse Effekt-Geometrien (Heil-Ring,
  // Telegrafen) wuerden sonst die Kamera-Distanz dominieren
  const box = new THREE.Box3();
  const tmp = new THREE.Box3();
  current.body.updateMatrixWorld(true);
  current.body.traverse((obj) => {
    if (obj instanceof THREE.Mesh) {
      if (!obj.geometry.boundingSphere) obj.geometry.computeBoundingSphere();
      if ((obj.geometry.boundingSphere?.radius ?? 0) < 5) {
        tmp.setFromObject(obj);
        box.union(tmp);
      }
    }
  });
  if (box.isEmpty()) return;
  const size = box.getSize(new THREE.Vector3());
  const center = box.getCenter(new THREE.Vector3());
  const maxDim = Math.max(size.x, size.y, size.z, 2);
  fitRadius = maxDim * 1.15 + 3;
  // deutlich erhoehte Kamera, damit Fluegel/Flaechen nicht kantenzeitig wirken
  fitHeight = center.y + maxDim * 1.0;
  fitCenterY = center.y;
}

async function show(id: string) {
  const entry = VIEWER_MODELS.find((m) => m.id === id) ?? VIEWER_MODELS[0];
  if (current) {
    try {
      current.dispose();
    } catch {
      // dispose ist nur Kosmetik
    }
    scene.remove(current.body);
    current = null;
  }
  current = await entry.create();
  scene.add(current.body);
  refit();
  nameEl.textContent = current.displayName || entry.id;
  history.replaceState(null, '', `?m=${entry.id}${still ? '&still=1' : ''}`);
  for (const btn of uiEl.querySelectorAll('button')) {
    btn.classList.toggle('active', btn.dataset.id === entry.id);
  }
}

for (const m of VIEWER_MODELS) {
  const btn = document.createElement('button');
  btn.textContent = m.id;
  btn.dataset.id = m.id;
  btn.addEventListener('click', () => show(m.id));
  uiEl.appendChild(btn);
}

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

// --- Loop ---
const clock = new THREE.Clock();
function animate() {
  requestAnimationFrame(animate);
  const dt = Math.min(clock.getDelta(), 0.05);

  if (current) {
    if (!still) current.update(dt, stubGame);
    current.updateAnimation(dt);

    // Auto-Fit alle 30 Frames nachjustieren (States aendern die Ausdehnung)
    fitTimer += dt;
    if (frame % 30 === 0 || fitTimer > 2) {
      fitTimer = 0;
      refit();
    }

    orbitAngle += dt * 0.35;
    const c = current.body.position;
    camera.position.set(
      c.x + Math.sin(orbitAngle) * fitRadius,
      fitHeight,
      c.z + Math.cos(orbitAngle) * fitRadius
    );
    camera.lookAt(c.x, fitCenterY * 0.85, c.z);
  }

  frame++;
  renderer.render(scene, camera);
}

show(params.get('m') ?? 'dragon');
animate();
