package ai

import (
	"sync/atomic"
	"time"

	"a301_game_server/pkg/logger"
	"a301_game_server/pkg/mathutil"
)

// SpawnPoint defines where and what to spawn.
type SpawnPoint struct {
	MobDef       *MobDef
	Position     mathutil.Vec3
	RespawnDelay time.Duration
	MaxCount     int // max alive at this point
}

// spawnEntry tracks a single spawned mob.
type spawnEntry struct {
	mob       *Mob
	alive     bool
	diedAt    time.Time
}

// Spawner manages mob spawning and respawning for a zone.
type Spawner struct {
	points    []SpawnPoint
	mobs      map[uint64]*spawnEntry // mobID -> entry
	pointMobs map[int][]*spawnEntry  // spawnPointIndex -> entries
	nextID    *atomic.Uint64

	// Callbacks
	onSpawn  func(m *Mob)
	onRemove func(mobID uint64)
}

// NewSpawner creates a mob spawner.
func NewSpawner(nextID *atomic.Uint64, onSpawn func(*Mob), onRemove func(uint64)) *Spawner {
	return &Spawner{
		mobs:      make(map[uint64]*spawnEntry),
		pointMobs: make(map[int][]*spawnEntry),
		nextID:    nextID,
		onSpawn:   onSpawn,
		onRemove:  onRemove,
	}
}

// AddSpawnPoint registers a spawn point.
func (s *Spawner) AddSpawnPoint(sp SpawnPoint) {
	s.points = append(s.points, sp)
}

// InitialSpawn spawns all mobs at startup.
func (s *Spawner) InitialSpawn() {
	for i, sp := range s.points {
		for j := 0; j < sp.MaxCount; j++ {
			s.spawnMob(i, &sp)
		}
	}
	logger.Info("initial spawn complete", "totalMobs", len(s.mobs))
}

// Update checks for mobs that need respawning.
func (s *Spawner) Update(now time.Time) {
	for i, sp := range s.points {
		entries := s.pointMobs[i]
		aliveCount := 0
		for _, e := range entries {
			if e.alive {
				aliveCount++
				continue
			}
			// Check if it's time to respawn.
			if !e.diedAt.IsZero() && now.Sub(e.diedAt) >= sp.RespawnDelay {
				s.respawnMob(e)
				aliveCount++
			}
		}
		// Spawn new if below max count.
		for aliveCount < sp.MaxCount {
			s.spawnMob(i, &sp)
			aliveCount++
		}
	}
}

// NotifyDeath marks a mob as dead and starts the respawn timer.
func (s *Spawner) NotifyDeath(mobID uint64) {
	entry, ok := s.mobs[mobID]
	if !ok {
		return
	}
	entry.alive = false
	entry.diedAt = time.Now()

	// Remove from zone (despawn).
	if s.onRemove != nil {
		s.onRemove(mobID)
	}
}

// GetMob returns a mob by ID.
func (s *Spawner) GetMob(id uint64) *Mob {
	if e, ok := s.mobs[id]; ok {
		return e.mob
	}
	return nil
}

// AllMobs returns all tracked mobs.
func (s *Spawner) AllMobs() []*Mob {
	result := make([]*Mob, 0, len(s.mobs))
	for _, e := range s.mobs {
		result = append(result, e.mob)
	}
	return result
}

// AliveMobs returns all alive mobs.
func (s *Spawner) AliveMobs() []*Mob {
	var result []*Mob
	for _, e := range s.mobs {
		if e.alive {
			result = append(result, e.mob)
		}
	}
	return result
}

func (s *Spawner) spawnMob(pointIdx int, sp *SpawnPoint) {
	id := s.nextID.Add(1) + 100000 // offset to avoid collision with player IDs
	mob := NewMob(id, sp.MobDef, sp.Position)

	entry := &spawnEntry{mob: mob, alive: true}
	s.mobs[id] = entry
	s.pointMobs[pointIdx] = append(s.pointMobs[pointIdx], entry)

	if s.onSpawn != nil {
		s.onSpawn(mob)
	}
}

func (s *Spawner) respawnMob(entry *spawnEntry) {
	entry.mob.Reset()
	entry.alive = true
	entry.diedAt = time.Time{}

	if s.onSpawn != nil {
		s.onSpawn(entry.mob)
	}
}
