package ai

import (
	"math"
	"time"

	"a301_game_server/internal/entity"
	"a301_game_server/pkg/mathutil"
)

// AIState represents the mob's behavioral state.
type AIState int

const (
	StateIdle    AIState = iota // Standing at spawn, doing nothing
	StatePatrol                // Wandering near spawn
	StateChase                 // Moving toward a target
	StateAttack                // In attack range, using skills
	StateReturn                // Walking back to spawn (leash)
	StateDead                  // Dead, waiting for respawn
)

// EntityProvider gives the AI access to the game world.
type EntityProvider interface {
	GetEntity(id uint64) entity.Entity
	GetPlayersInRange(center mathutil.Vec3, radius float32) []entity.Entity
}

// SkillUser allows the AI to use combat skills.
type SkillUser interface {
	UseSkill(casterID uint64, skillID uint32, targetID uint64, targetPos mathutil.Vec3) (bool, string)
}

// UpdateMob advances one mob's AI by one tick.
func UpdateMob(m *Mob, dt time.Duration, provider EntityProvider, skills SkillUser) {
	if !m.IsAlive() {
		m.SetState(StateDead)
		return
	}

	switch m.State() {
	case StateIdle:
		updateIdle(m, provider)
	case StateChase:
		updateChase(m, dt, provider)
	case StateAttack:
		updateAttack(m, provider, skills)
	case StateReturn:
		updateReturn(m, dt)
	case StateDead:
		// handled by spawner
	}
}

func updateIdle(m *Mob, provider EntityProvider) {
	// Scan for players in aggro range.
	target := findNearestPlayer(m, provider, m.Def().AggroRange)
	if target != nil {
		m.SetTargetID(target.EntityID())
		m.SetState(StateChase)
	}
}

func updateChase(m *Mob, dt time.Duration, provider EntityProvider) {
	target := provider.GetEntity(m.TargetID())
	if target == nil || !isAlive(target) {
		m.SetTargetID(0)
		m.SetState(StateReturn)
		return
	}

	// Leash check: too far from spawn?
	if m.Position().DistanceXZ(m.SpawnPos()) > m.Def().LeashRange {
		m.SetTargetID(0)
		m.SetState(StateReturn)
		return
	}

	dist := m.Position().DistanceXZ(target.Position())

	// Close enough to attack?
	if dist <= m.Def().AttackRange {
		m.SetState(StateAttack)
		return
	}

	// Move toward target.
	moveToward(m, target.Position(), dt)
}

func updateAttack(m *Mob, provider EntityProvider, skills SkillUser) {
	target := provider.GetEntity(m.TargetID())
	if target == nil || !isAlive(target) {
		m.SetTargetID(0)
		m.SetState(StateReturn)
		return
	}

	dist := m.Position().DistanceXZ(target.Position())

	// Target moved out of attack range? Chase again.
	if dist > m.Def().AttackRange*1.2 { // 20% buffer to prevent flickering
		m.SetState(StateChase)
		return
	}

	// Leash check.
	if m.Position().DistanceXZ(m.SpawnPos()) > m.Def().LeashRange {
		m.SetTargetID(0)
		m.SetState(StateReturn)
		return
	}

	// Face target.
	dir := target.Position().Sub(m.Position())
	m.SetRotation(float32(math.Atan2(float64(dir.X), float64(dir.Z))))

	// Use attack skill.
	skills.UseSkill(m.EntityID(), m.Def().AttackSkill, target.EntityID(), mathutil.Vec3{})
}

func updateReturn(m *Mob, dt time.Duration) {
	dist := m.Position().DistanceXZ(m.SpawnPos())
	if dist < 0.5 {
		m.SetPosition(m.SpawnPos())
		m.SetState(StateIdle)
		// Heal to full when returning.
		m.SetHP(m.MaxHP())
		return
	}
	moveToward(m, m.SpawnPos(), dt)
}

// moveToward moves the mob toward a target position at its move speed.
func moveToward(m *Mob, target mathutil.Vec3, dt time.Duration) {
	dir := target.Sub(m.Position())
	dir.Y = 0
	dist := dir.Length()
	if dist < 0.01 {
		return
	}

	step := m.Def().MoveSpeed * float32(dt.Seconds())
	if step > dist {
		step = dist
	}

	move := dir.Normalize().Scale(step)
	m.SetPosition(m.Position().Add(move))

	// Face movement direction.
	m.SetRotation(float32(math.Atan2(float64(dir.X), float64(dir.Z))))
}

func findNearestPlayer(m *Mob, provider EntityProvider, radius float32) entity.Entity {
	players := provider.GetPlayersInRange(m.Position(), radius)
	if len(players) == 0 {
		return nil
	}

	var nearest entity.Entity
	minDist := float32(math.MaxFloat32)
	for _, p := range players {
		if !isAlive(p) {
			continue
		}
		d := m.Position().DistanceXZ(p.Position())
		if d < minDist {
			minDist = d
			nearest = p
		}
	}
	return nearest
}

func isAlive(e entity.Entity) bool {
	type aliveChecker interface {
		IsAlive() bool
	}
	if a, ok := e.(aliveChecker); ok {
		return a.IsAlive()
	}
	return true
}
