// 3D scene rendering with Three.js / 使用 Three.js 进行3D场景渲染
import * as THREE from 'three'
import { PLAYER_SIZE } from '../utils/constants.js'

/**
 * Manages the 3D scene, camera, lighting, player models, zombie models, and wall meshes
 */
export class GameScene {
  constructor(canvas) {
    this.canvas = canvas

    this.scene = new THREE.Scene()
    this.scene.background = new THREE.Color(0x1a1a2e)

    this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200)
    this.cameraOffset = new THREE.Vector3(0, 25, 18)

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

    this.players = new Map()
    this.zombies = new Map()
    this.wallMeshes = []
    this.nutWallMeshes = new Map()

    this._setupLighting()
    this._setupResize()
  }

  _setupLighting() {
    const ambient = new THREE.AmbientLight(0x404060, 0.6)
    this.scene.add(ambient)

    const dirLight = new THREE.DirectionalLight(0xffeedd, 0.8)
    dirLight.position.set(16, 30, 16)
    dirLight.castShadow = true
    dirLight.shadow.mapSize.width = 2048
    dirLight.shadow.mapSize.height = 2048
    dirLight.shadow.camera.near = 0.5
    dirLight.shadow.camera.far = 80
    dirLight.shadow.camera.left = -20
    dirLight.shadow.camera.right = 20
    dirLight.shadow.camera.top = 20
    dirLight.shadow.camera.bottom = -20
    this.scene.add(dirLight)

    const pointLight = new THREE.PointLight(0xff4400, 0.3, 50)
    pointLight.position.set(16, 10, 16)
    this.scene.add(pointLight)
  }

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

  // Build map: floor, static walls (1), player spawns (2), nut walls (4)
  buildMap(mapData) {
    for (const mesh of this.wallMeshes) {
      this.scene.remove(mesh)
      mesh.geometry.dispose()
      mesh.material.dispose()
    }
    this.wallMeshes = []
    for (const [, entry] of this.nutWallMeshes) {
      this.scene.remove(entry.mesh)
      entry.mesh.geometry.dispose()
      entry.mesh.material.dispose()
    }
    this.nutWallMeshes.clear()

    const mapHeight = mapData.length
    const mapWidth = mapData[0]?.length || 0

    const floorGeo = new THREE.PlaneGeometry(mapWidth, mapHeight)
    const floorMat = new THREE.MeshLambertMaterial({ color: 0x2a2a3a })
    const floor = new THREE.Mesh(floorGeo, floorMat)
    floor.rotation.x = -Math.PI / 2
    floor.position.set(mapWidth / 2, 0, mapHeight / 2)
    floor.receiveShadow = true
    this.scene.add(floor)
    this.wallMeshes.push(floor)

    const wallGeo = new THREE.BoxGeometry(1, 1.5, 1)
    const wallMat = new THREE.MeshLambertMaterial({ color: 0x555577 })
    const nutWallGeo = new THREE.BoxGeometry(1, 1.2, 1)
    const nutWallMat = new THREE.MeshLambertMaterial({ color: 0x8B6914 })
    const spawnGeo = new THREE.BoxGeometry(1, 0.1, 1)
    const spawnMat = new THREE.MeshLambertMaterial({ color: 0x00ff88, transparent: true, opacity: 0.5 })
    const zombieSpawnGeo = new THREE.BoxGeometry(1, 0.1, 1)
    const zombieSpawnMat = new THREE.MeshLambertMaterial({ color: 0xff4444, transparent: true, opacity: 0.4 })

    for (let y = 0; y < mapHeight; y++) {
      for (let x = 0; x < mapWidth; x++) {
        const cell = mapData[y] && mapData[y][x]
        if (cell === 1) {
          const wall = new THREE.Mesh(wallGeo, wallMat)
          wall.position.set(x + 0.5, 0.75, y + 0.5)
          wall.castShadow = true
          wall.receiveShadow = true
          this.scene.add(wall)
          this.wallMeshes.push(wall)
        } else if (cell === 2) {
          const spawn = new THREE.Mesh(spawnGeo, spawnMat)
          spawn.position.set(x + 0.5, 0.05, y + 0.5)
          this.scene.add(spawn)
          this.wallMeshes.push(spawn)
        } else if (cell === 3) {
          const zsp = new THREE.Mesh(zombieSpawnGeo, zombieSpawnMat)
          zsp.position.set(x + 0.5, 0.05, y + 0.5)
          this.scene.add(zsp)
          this.wallMeshes.push(zsp)
        } else if (cell === 4) {
          const nutWall = new THREE.Mesh(nutWallGeo, nutWallMat.clone())
          nutWall.position.set(x + 0.5, 0.6, y + 0.5)
          nutWall.castShadow = true
          nutWall.receiveShadow = true
          this.scene.add(nutWall)
          this.wallMeshes.push(nutWall)
          const key = x + ',' + y
          this.nutWallMeshes.set(key, { mesh: nutWall, x, y, maxHealth: 500 })
        }
      }
    }
  }

  // Update nut wall appearance based on health
  updateNutWall(x, y, health, maxHealth) {
    const key = x + ',' + y
    const entry = this.nutWallMeshes.get(key)
    if (!entry) return
    const ratio = health / maxHealth
    const r = 0.55 + (1 - ratio) * 0.45
    const g = 0.41 * ratio
    const b = 0.08 * ratio
    entry.mesh.material.color.setRGB(r, g, b)
    if (health <= 0) {
      this.scene.remove(entry.mesh)
      entry.mesh.geometry.dispose()
      entry.mesh.material.dispose()
      this.nutWallMeshes.delete(key)
    }
  }

  createPlayerModel(color = 0x4488ff) {
    const group = new THREE.Group()

    const bodyGeo = new THREE.CylinderGeometry(PLAYER_SIZE / 2, PLAYER_SIZE / 2, 0.8, 8)
    const bodyMat = new THREE.MeshLambertMaterial({ color })
    const body = new THREE.Mesh(bodyGeo, bodyMat)
    body.position.y = 0.4
    body.castShadow = true
    group.add(body)

    const headGeo = new THREE.SphereGeometry(0.2, 8, 8)
    const headMat = new THREE.MeshLambertMaterial({ color: 0xffcc99 })
    const head = new THREE.Mesh(headGeo, headMat)
    head.position.y = 1.0
    head.castShadow = true
    group.add(head)

    const gunGeo = new THREE.BoxGeometry(0.08, 0.08, 0.5)
    const gunMat = new THREE.MeshLambertMaterial({ color: 0x333333 })
    const gun = new THREE.Mesh(gunGeo, gunMat)
    gun.position.set(0, 0.5, 0.4)
    group.add(gun)

    return group
  }

  addPlayer(id, x, y, color, isLocal = false) {
    const model = this.createPlayerModel(color)
    model.position.set(x, 0, y)
    this.scene.add(model)
    this.players.set(id, { model, isLocal })
  }

  removePlayer(id) {
    const player = this.players.get(id)
    if (player) {
      this.scene.remove(player.model)
      this.players.delete(id)
    }
  }

  updatePlayer(id, x, y, angle, health) {
    const player = this.players.get(id)
    if (player) {
      player.model.position.x = x
      player.model.position.z = y
      player.model.rotation.y = angle
    }
  }

  // --- Zombie rendering ---

  createZombieModel() {
    const group = new THREE.Group()

    // Body - dark green
    const bodyGeo = new THREE.CylinderGeometry(0.35, 0.4, 0.9, 8)
    const bodyMat = new THREE.MeshLambertMaterial({ color: 0x3a5a2a })
    const body = new THREE.Mesh(bodyGeo, bodyMat)
    body.position.y = 0.45
    body.castShadow = true
    group.add(body)

    // Head - slightly green
    const headGeo = new THREE.SphereGeometry(0.22, 8, 8)
    const headMat = new THREE.MeshLambertMaterial({ color: 0x6b8e4e })
    const head = new THREE.Mesh(headGeo, headMat)
    head.position.y = 1.1
    head.castShadow = true
    group.add(head)

    // Arms - thin cylinders pointing forward
    const armGeo = new THREE.CylinderGeometry(0.06, 0.06, 0.6, 6)
    const armMat = new THREE.MeshLambertMaterial({ color: 0x3a5a2a })
    const leftArm = new THREE.Mesh(armGeo, armMat)
    leftArm.position.set(-0.3, 0.7, 0.3)
    leftArm.rotation.x = -Math.PI / 3
    group.add(leftArm)
    const rightArm = new THREE.Mesh(armGeo, armMat)
    rightArm.position.set(0.3, 0.7, 0.3)
    rightArm.rotation.x = -Math.PI / 3
    group.add(rightArm)

    return group
  }

  addZombie(id, x, y) {
    const model = this.createZombieModel()
    model.position.set(x, 0, y)
    this.scene.add(model)
    this.zombies.set(id, { model })
  }

  removeZombie(id) {
    const zombie = this.zombies.get(id)
    if (zombie) {
      this.scene.remove(zombie.model)
      this.zombies.delete(id)
    }
  }

  updateZombie(id, x, y, angle) {
    const zombie = this.zombies.get(id)
    if (zombie) {
      zombie.model.position.x = x
      zombie.model.position.z = y
      zombie.model.rotation.y = angle
    }
  }

  // Camera follows target
  updateCamera(targetX, targetY) {
    const target = new THREE.Vector3(targetX, 0, targetY)
    const desiredPos = target.clone().add(this.cameraOffset)
    this.camera.position.lerp(desiredPos, 0.1)
    this.camera.lookAt(target)
  }

  getMouseGroundPos(mouseX, mouseY) {
    const mouse = new THREE.Vector2(
      (mouseX / window.innerWidth) * 2 - 1,
      -(mouseY / window.innerHeight) * 2 + 1
    )
    const raycaster = new THREE.Raycaster()
    raycaster.setFromCamera(mouse, this.camera)
    const groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
    const intersection = new THREE.Vector3()
    raycaster.ray.intersectPlane(groundPlane, intersection)
    if (intersection) {
      return { x: intersection.x, y: intersection.z }
    }
    return { x: 0, y: 0 }
  }

  render() {
    this.renderer.render(this.scene, this.camera)
  }

  destroy() {
    this.renderer.dispose()
    this.scene.traverse(child => {
      if (child.geometry) child.geometry.dispose()
      if (child.material) {
        if (Array.isArray(child.material)) {
          child.material.forEach(m => m.dispose())
        } else {
          child.material.dispose()
        }
      }
    })
  }
}
