package zone

import (
	"fmt"
	"log"
	"time"

	"eq2emu/internal/common"
	"eq2emu/internal/spawn"
)

// ZoneServerConfig holds configuration for creating a zone server
type ZoneServerConfig struct {
	ZoneName          string
	ZoneFile          string
	ZoneSkyFile       string
	ZoneDescription   string
	ZoneID            int32
	InstanceID        int32
	InstanceType      InstanceType
	DatabasePath      string
	MaxPlayers        int32
	MinLevel          int16
	MaxLevel          int16
	SafeX             float32
	SafeY             float32
	SafeZ             float32
	SafeHeading       float32
	LoadMaps          bool
	EnableWeather     bool
	EnablePathfinding bool
}

// Initialize initializes the zone server with all required systems
func (zs *ZoneServer) Initialize(config *ZoneServerConfig) error {
	zs.masterZoneLock.Lock()
	defer zs.masterZoneLock.Unlock()

	if zs.isInitialized.Load() {
		return fmt.Errorf("zone server already initialized")
	}

	log.Printf("%s Initializing zone server '%s' (ID: %d)", LogPrefixZone, config.ZoneName, config.ZoneID)

	// Set basic configuration
	zs.zoneName = config.ZoneName
	zs.zoneFile = config.ZoneFile
	zs.zoneSkyFile = config.ZoneSkyFile
	zs.zoneDescription = config.ZoneDescription
	zs.zoneID = config.ZoneID
	zs.instanceID = config.InstanceID
	zs.instanceType = config.InstanceType
	zs.isInstance = config.InstanceType != InstanceTypeNone

	// Set zone limits
	zs.minimumLevel = config.MinLevel
	zs.maximumLevel = config.MaxLevel

	// Set safe coordinates
	zs.safeX = config.SafeX
	zs.safeY = config.SafeY
	zs.safeZ = config.SafeZ
	zs.safeHeading = config.SafeHeading

	// Initialize timers
	if err := zs.initializeTimers(); err != nil {
		return fmt.Errorf("failed to initialize timers: %v", err)
	}

	// Load zone data from database
	if err := zs.loadZoneData(); err != nil {
		return fmt.Errorf("failed to load zone data: %v", err)
	}

	// Initialize pathfinding if enabled
	if config.EnablePathfinding {
		if err := zs.initializePathfinding(); err != nil {
			log.Printf("%s Warning: failed to initialize pathfinding: %v", LogPrefixZone, err)
			// Don't fail initialization, just log warning
		}
	}

	// Load maps if enabled
	if config.LoadMaps {
		if err := zs.loadZoneMaps(); err != nil {
			log.Printf("%s Warning: failed to load zone maps: %v", LogPrefixZone, err)
			// Don't fail initialization, just log warning
		}
	}

	// Initialize weather if enabled
	if config.EnableWeather {
		zs.initializeWeather()
	}

	// Initialize movement manager
	zs.movementMgr = NewMobMovementManager(zs)

	// Start processing threads
	zs.startProcessingThreads()

	zs.loadingData = false
	zs.isInitialized.Store(true)

	log.Printf("%s Zone server '%s' initialized successfully", LogPrefixZone, zs.zoneName)
	return nil
}

// Process performs the main zone processing loop
func (zs *ZoneServer) Process() error {
	if zs.zoneShuttingDown.Load() {
		return fmt.Errorf("zone is shutting down")
	}

	if !zs.isInitialized.Load() {
		return fmt.Errorf("zone not initialized")
	}

	// Update watchdog timestamp
	zs.watchdogTimestamp = int32(time.Now().Unix())

	// Process clients
	zs.processClients()

	// Process spawns
	zs.processSpawns()

	// Process timers
	zs.processTimers()

	// Process movement
	if zs.movementMgr != nil {
		zs.movementMgr.Process()
	}

	// Process spell effects
	if zs.spellProcess != nil {
		zs.spellProcess.ProcessSpellEffects()
	}

	// Process pending spawn changes
	zs.processSpawnChanges()

	// Process proximity checks
	zs.processProximityChecks()

	// Process weather updates
	zs.processWeather()

	// Clean up expired data
	zs.cleanupExpiredData()

	return nil
}

// SpawnProcess performs spawn-specific processing
func (zs *ZoneServer) SpawnProcess() error {
	if zs.zoneShuttingDown.Load() {
		return fmt.Errorf("zone is shutting down")
	}

	// Process spawn locations for respawns
	zs.processSpawnLocations()

	// Process spawn movement
	zs.processSpawnMovement()

	// Process NPC AI
	zs.processNPCAI()

	// Process combat
	zs.processCombat()

	// Check for dead spawns to remove
	zs.checkDeadSpawnRemoval()

	// Process spawn script timers
	zs.processSpawnScriptTimers()

	return nil
}

// AddClient adds a client to the zone
func (zs *ZoneServer) AddClient(client Client) error {
	if zs.zoneShuttingDown.Load() {
		return fmt.Errorf("zone is shutting down")
	}

	zs.clientListLock.Lock()
	defer zs.clientListLock.Unlock()

	// Check zone capacity
	if len(zs.clients) >= int(zs.GetMaxPlayers()) {
		return fmt.Errorf("zone is full")
	}

	// Check client requirements
	if !zs.canClientEnter(client) {
		return fmt.Errorf("client does not meet zone requirements")
	}

	// Add client to list
	zs.clients = append(zs.clients, client)
	zs.numPlayers = int32(len(zs.clients))
	zs.lifetimeClientCount++

	log.Printf("%s Client %s entered zone '%s' (%d/%d players)",
		LogPrefixZone, client.GetPlayerName(), zs.zoneName, zs.numPlayers, zs.GetMaxPlayers())

	// Initialize client in zone
	go zs.initializeClientInZone(client)

	return nil
}

// RemoveClient removes a client from the zone
func (zs *ZoneServer) RemoveClient(client Client) {
	zs.clientListLock.Lock()
	defer zs.clientListLock.Unlock()

	// Find and remove client
	for i, c := range zs.clients {
		if c.GetID() == client.GetID() {
			// Remove from slice
			zs.clients = append(zs.clients[:i], zs.clients[i+1:]...)
			zs.numPlayers = int32(len(zs.clients))

			log.Printf("%s Client %s left zone '%s' (%d/%d players)",
				LogPrefixZone, client.GetPlayerName(), zs.zoneName, zs.numPlayers, zs.GetMaxPlayers())

			// Clean up client-specific data
			zs.cleanupClientData(client)
			break
		}
	}
}

// AddSpawn adds a spawn to the zone
func (zs *ZoneServer) AddSpawn(spawn *spawn.Spawn) error {
	if zs.zoneShuttingDown.Load() {
		return fmt.Errorf("zone is shutting down")
	}

	zs.spawnListLock.Lock()
	defer zs.spawnListLock.Unlock()

	// Add to spawn list
	zs.spawnList[spawn.GetID()] = spawn

	// Add to appropriate grid
	zs.addSpawnToGrid(spawn)

	// Mark as changed for client updates
	zs.markSpawnChanged(spawn.GetID())

	log.Printf("%s Added spawn '%s' (ID: %d) to zone '%s'",
		LogPrefixZone, spawn.GetName(), spawn.GetID(), zs.zoneName)

	return nil
}

// RemoveSpawn removes a spawn from the zone
func (zs *ZoneServer) RemoveSpawn(spawnID int32, deleteSpawn bool) error {
	zs.spawnListLock.Lock()
	defer zs.spawnListLock.Unlock()

	spawn, exists := zs.spawnList[spawnID]
	if !exists {
		return fmt.Errorf("spawn %d not found", spawnID)
	}

	// Remove from grids
	zs.removeSpawnFromGrid(spawn)

	// Clean up spawn data
	zs.cleanupSpawnData(spawn)

	// Remove from spawn list
	delete(zs.spawnList, spawnID)

	// Mark for client removal
	zs.markSpawnForRemoval(spawnID)

	log.Printf("%s Removed spawn '%s' (ID: %d) from zone '%s'",
		LogPrefixZone, spawn.GetName(), spawn.GetID(), zs.zoneName)

	return nil
}

// GetSpawn retrieves a spawn by ID
func (zs *ZoneServer) GetSpawn(spawnID int32) *spawn.Spawn {
	zs.spawnListLock.RLock()
	defer zs.spawnListLock.RUnlock()

	return zs.spawnList[spawnID]
}

// GetSpawnsByRange retrieves all spawns within range of a position
func (zs *ZoneServer) GetSpawnsByRange(x, y, z, maxRange float32) []*spawn.Spawn {
	zs.spawnListLock.RLock()
	defer zs.spawnListLock.RUnlock()

	var nearbySpawns []*spawn.Spawn
	maxRangeSquared := maxRange * maxRange

	for _, spawn := range zs.spawnList {
		spawnX, spawnY, spawnZ, _ := spawn.GetPosition()
		distSquared := Distance3DSquared(x, y, z, spawnX, spawnY, spawnZ)

		if distSquared <= maxRangeSquared {
			nearbySpawns = append(nearbySpawns, spawn)
		}
	}

	return nearbySpawns
}

// GetGridsByLocation retrieves grid IDs that contain the specified location
func (zs *ZoneServer) GetGridsByLocation(x, y, z, distance float32) []int32 {
	// Calculate grid boundaries
	minGridX := int32((x - distance) / DefaultGridSize)
	maxGridX := int32((x + distance) / DefaultGridSize)
	minGridY := int32((y - distance) / DefaultGridSize)
	maxGridY := int32((y + distance) / DefaultGridSize)

	var gridIDs []int32

	for gridX := minGridX; gridX <= maxGridX; gridX++ {
		for gridY := minGridY; gridY <= maxGridY; gridY++ {
			gridID := gridX*1000 + gridY // Simple grid ID calculation
			if gridID >= 0 && gridID <= MaxGridID {
				gridIDs = append(gridIDs, gridID)
			}
		}
	}

	return gridIDs
}

// SendZoneSpawns sends all visible spawns to a client
func (zs *ZoneServer) SendZoneSpawns(client Client) error {
	playerX, playerY, playerZ, _, _ := client.GetPosition()

	// Get spawns in range
	spawns := zs.GetSpawnsByRange(playerX, playerY, playerZ, SendSpawnDistance)

	log.Printf("%s Sending %d spawns to client %s", LogPrefixZone, len(spawns), client.GetPlayerName())

	// Send each spawn
	for _, spawn := range spawns {
		if client.CanSeeSpawn(spawn) {
			zs.sendSpawnToClient(client, spawn)
		}
	}

	return nil
}

// ProcessWeather handles zone-wide weather changes
func (zs *ZoneServer) ProcessWeather() {
	if !zs.weatherEnabled || !zs.weatherAllowed {
		return
	}

	currentTime := int32(time.Now().Unix())

	// Check if it's time for a weather change
	if currentTime-zs.weatherLastChangedTime < zs.weatherFrequency {
		return
	}

	// Roll for weather change
	if zs.weatherChangeChance > 0 {
		// Simple random roll (0-100)
		roll := currentTime % 100
		if int8(roll) > zs.weatherChangeChance {
			return
		}
	}

	// Calculate weather change
	var change float32
	switch zs.weatherType {
	case WeatherTypeNormal:
		change = zs.weatherChangeAmount
	case WeatherTypeDynamic:
		// Dynamic weather with random offset
		change = zs.weatherChangeAmount + (float32(currentTime%100)/100.0-0.5)*zs.weatherDynamicOffset
	case WeatherTypeRandom:
		// Completely random change
		change = (float32(currentTime%100)/100.0 - 0.5) * 2.0 * zs.weatherMaxSeverity
	case WeatherTypeChaotic:
		// Chaotic weather with large swings
		change = (float32(currentTime%100)/100.0 - 0.5) * 4.0 * zs.weatherMaxSeverity
	}

	// Apply pattern
	switch zs.weatherPattern {
	case WeatherPatternDecreasing:
		change = -abs(change)
	case WeatherPatternIncreasing:
		change = abs(change)
		// WeatherPatternRandom uses calculated change as-is
	}

	// Update weather severity
	newSeverity := zs.weatherCurrentSeverity + change

	// Clamp to bounds
	if newSeverity < zs.weatherMinSeverity {
		newSeverity = zs.weatherMinSeverity
	}
	if newSeverity > zs.weatherMaxSeverity {
		newSeverity = zs.weatherMaxSeverity
	}

	// Check if severity actually changed
	if newSeverity != zs.weatherCurrentSeverity {
		zs.weatherCurrentSeverity = newSeverity
		zs.rain = newSeverity

		// Send weather update to all clients
		zs.sendWeatherUpdate()

		log.Printf("%s Weather changed to %.2f in zone '%s'", LogPrefixWeather, newSeverity, zs.zoneName)
	}

	zs.weatherLastChangedTime = currentTime
}

// SetRain sets the rain level in the zone
func (zs *ZoneServer) SetRain(val float32) {
	zs.rain = val
	zs.weatherCurrentSeverity = val
	zs.sendWeatherUpdate()
}

// GetZoneID returns the zone ID
func (zs *ZoneServer) GetZoneID() int32 {
	return zs.zoneID
}

// GetZoneName returns the zone name
func (zs *ZoneServer) GetZoneName() string {
	return zs.zoneName
}

// GetInstanceID returns the instance ID
func (zs *ZoneServer) GetInstanceID() int32 {
	return zs.instanceID
}

// GetInstanceType returns the instance type
func (zs *ZoneServer) GetInstanceType() InstanceType {
	return zs.instanceType
}

// IsInstanceZone returns whether this is an instance zone
func (zs *ZoneServer) IsInstanceZone() bool {
	return zs.isInstance
}

// GetNumPlayers returns the current number of players
func (zs *ZoneServer) GetNumPlayers() int32 {
	return zs.numPlayers
}

// GetMaxPlayers returns the maximum number of players allowed
func (zs *ZoneServer) GetMaxPlayers() int32 {
	if zs.isInstance {
		// Instance zones have different limits based on type
		switch zs.instanceType {
		case InstanceTypeGroupLockout, InstanceTypeGroupPersist:
			return 6
		case InstanceTypeRaidLockout, InstanceTypeRaidPersist:
			return 24
		case InstanceTypeSoloLockout, InstanceTypeSoloPersist:
			return 1
		case InstanceTypeTradeskill, InstanceTypePublic:
			return DefaultMaxPlayers
		case InstanceTypePersonalHouse:
			return 10
		case InstanceTypeGuildHouse:
			return 50
		case InstanceTypeQuest:
			return 6
		}
	}
	return DefaultMaxPlayers
}

// GetSafePosition returns the safe position coordinates
func (zs *ZoneServer) GetSafePosition() (x, y, z, heading float32) {
	return zs.safeX, zs.safeY, zs.safeZ, zs.safeHeading
}

// SetSafePosition sets the safe position coordinates
func (zs *ZoneServer) SetSafePosition(x, y, z, heading float32) {
	zs.safeX = x
	zs.safeY = y
	zs.safeZ = z
	zs.safeHeading = heading
}

// IsLocked returns whether the zone is locked
func (zs *ZoneServer) IsLocked() bool {
	return zs.locked
}

// SetLocked sets the zone lock state
func (zs *ZoneServer) SetLocked(locked bool) {
	zs.locked = locked
	if locked {
		log.Printf("%s Zone '%s' has been locked", LogPrefixZone, zs.zoneName)
	} else {
		log.Printf("%s Zone '%s' has been unlocked", LogPrefixZone, zs.zoneName)
	}
}

// GetWatchdogTime returns the last watchdog timestamp
func (zs *ZoneServer) GetWatchdogTime() int32 {
	return zs.watchdogTimestamp
}

// Private helper methods

func (zs *ZoneServer) initializeTimers() error {
	zs.aggroTimer = common.NewTimer(AggroCheckInterval)
	zs.charsheetChanges = common.NewTimer(CharsheetUpdateInterval)
	zs.clientSave = common.NewTimer(ClientSaveInterval)
	zs.locationProxTimer = common.NewTimer(LocationProximityInterval)
	zs.movementTimer = common.NewTimer(MovementUpdateInterval)
	zs.regenTimer = common.NewTimer(DefaultTimerInterval)
	zs.respawnTimer = common.NewTimer(RespawnCheckInterval)
	zs.shutdownTimer = common.NewTimer(0) // Disabled by default
	zs.spawnRangeTimer = common.NewTimer(SpawnRangeUpdateInterval)
	zs.spawnUpdateTimer = common.NewTimer(DefaultTimerInterval)
	zs.syncGameTimer = common.NewTimer(DefaultTimerInterval)
	zs.trackingTimer = common.NewTimer(TrackingUpdateInterval)
	zs.weatherTimer = common.NewTimer(WeatherUpdateInterval)
	zs.widgetTimer = common.NewTimer(WidgetUpdateInterval)

	return nil
}

func (zs *ZoneServer) loadZoneData() error {
	// TODO: Load zone data from database
	// This would include spawn locations, NPCs, objects, etc.
	log.Printf("%s Loading zone data for '%s'", LogPrefixZone, zs.zoneName)
	return nil
}

func (zs *ZoneServer) initializePathfinding() error {
	// TODO: Initialize pathfinding system
	log.Printf("%s Initializing pathfinding for zone '%s'", LogPrefixPathfind, zs.zoneName)
	return nil
}

func (zs *ZoneServer) loadZoneMaps() error {
	// TODO: Load zone maps and collision data
	log.Printf("%s Loading maps for zone '%s'", LogPrefixMap, zs.zoneName)
	return nil
}

func (zs *ZoneServer) initializeWeather() {
	zs.weatherEnabled = true
	zs.weatherType = WeatherTypeNormal
	zs.weatherFrequency = DefaultWeatherFrequency
	zs.weatherMinSeverity = DefaultWeatherMinSeverity
	zs.weatherMaxSeverity = DefaultWeatherMaxSeverity
	zs.weatherChangeAmount = DefaultWeatherChangeAmount
	zs.weatherDynamicOffset = DefaultWeatherDynamicOffset
	zs.weatherChangeChance = DefaultWeatherChangeChance
	zs.weatherPattern = WeatherPatternRandom
	zs.weatherCurrentSeverity = 0.0
	zs.weatherLastChangedTime = int32(time.Now().Unix())

	log.Printf("%s Weather system initialized for zone '%s'", LogPrefixWeather, zs.zoneName)
}

func (zs *ZoneServer) startProcessingThreads() {
	zs.spawnThreadActive = true
	zs.combatThreadActive = true
	zs.clientThreadActive = true

	log.Printf("%s Started processing threads for zone '%s'", LogPrefixZone, zs.zoneName)
}

func (zs *ZoneServer) canClientEnter(client Client) bool {
	// Check level requirements
	player := client.GetPlayer()
	if player != nil {
		level := player.GetLevel()
		if zs.minimumLevel > 0 && level < zs.minimumLevel {
			return false
		}
		if zs.maximumLevel > 0 && level > zs.maximumLevel {
			return false
		}
	}

	// Check client version
	version := client.GetClientVersion()
	if zs.minimumVersion > 0 && int16(version) < zs.minimumVersion {
		return false
	}

	// Check if zone is locked
	if zs.locked {
		return false
	}

	return true
}

func (zs *ZoneServer) initializeClientInZone(client Client) {
	// Send zone information
	zs.sendZoneInfo(client)

	// Send all visible spawns
	zs.SendZoneSpawns(client)

	// Send weather update
	if zs.weatherEnabled {
		zs.sendWeatherUpdateToClient(client)
	}

	// Send time update
	zs.sendTimeUpdateToClient(client)
}

func (zs *ZoneServer) processClients() {
	// Process each client
	zs.clientListLock.RLock()
	clients := make([]Client, len(zs.clients))
	copy(clients, zs.clients)
	zs.clientListLock.RUnlock()

	for _, client := range clients {
		if client.IsLoadingZone() {
			continue
		}

		// Update spawn visibility
		zs.updateClientSpawnVisibility(client)
	}
}

func (zs *ZoneServer) processSpawns() {
	// Process spawn updates and changes
	zs.spawnListLock.RLock()
	spawns := make([]*spawn.Spawn, 0, len(zs.spawnList))
	for _, spawn := range zs.spawnList {
		spawns = append(spawns, spawn)
	}
	zs.spawnListLock.RUnlock()

	for _, spawn := range spawns {
		// Process spawn logic here
		_ = spawn // Placeholder
	}
}

func (zs *ZoneServer) processTimers() {
	// Check and process all timers
	if zs.aggroTimer.Check() {
		zs.processAggroChecks()
	}

	if zs.respawnTimer.Check() {
		zs.processRespawns()
	}

	if zs.widgetTimer.Check() {
		zs.processWidgets()
	}

	// Add other timer checks...
}

func (zs *ZoneServer) processSpawnChanges() {
	zs.changedSpawnsLock.Lock()
	defer zs.changedSpawnsLock.Unlock()

	if len(zs.changedSpawns) == 0 {
		return
	}

	// Send changes to all clients
	zs.clientListLock.RLock()
	clients := make([]Client, len(zs.clients))
	copy(clients, zs.clients)
	zs.clientListLock.RUnlock()

	for spawnID := range zs.changedSpawns {
		spawn := zs.GetSpawn(spawnID)
		if spawn != nil {
			for _, client := range clients {
				if client.CanSeeSpawn(spawn) {
					zs.sendSpawnUpdateToClient(client, spawn)
				}
			}
		}
	}

	// Clear changed spawns
	zs.changedSpawns = make(map[int32]bool)
}

func (zs *ZoneServer) processProximityChecks() {
	// Process player and location proximity
	if zs.locationProxTimer.Check() {
		zs.checkLocationProximity()
		zs.checkPlayerProximity()
	}
}

func (zs *ZoneServer) processWeather() {
	if zs.weatherTimer.Check() {
		zs.ProcessWeather()
	}
}

func (zs *ZoneServer) cleanupExpiredData() {
	// Clean up dead spawns, expired timers, etc.
	zs.cleanupDeadSpawns()
	zs.cleanupExpiredTimers()
}

// Helper functions for various processing tasks
func (zs *ZoneServer) processSpawnLocations() {
	// TODO: Process spawn location respawns
}

func (zs *ZoneServer) processSpawnMovement() {
	// TODO: Process NPC movement
}

func (zs *ZoneServer) processNPCAI() {
	// TODO: Process NPC AI
}

func (zs *ZoneServer) processCombat() {
	// TODO: Process combat
}

func (zs *ZoneServer) checkDeadSpawnRemoval() {
	// TODO: Check for dead spawns to remove
}

func (zs *ZoneServer) processSpawnScriptTimers() {
	// TODO: Process spawn script timers
}

func (zs *ZoneServer) processAggroChecks() {
	// TODO: Process aggro checks
}

func (zs *ZoneServer) processRespawns() {
	// TODO: Process respawns
}

func (zs *ZoneServer) processWidgets() {
	// TODO: Process widget timers
}

func (zs *ZoneServer) checkLocationProximity() {
	// TODO: Check location proximity
}

func (zs *ZoneServer) checkPlayerProximity() {
	// TODO: Check player proximity
}

func (zs *ZoneServer) cleanupDeadSpawns() {
	// TODO: Clean up dead spawns
}

func (zs *ZoneServer) cleanupExpiredTimers() {
	// TODO: Clean up expired timers
}

func (zs *ZoneServer) cleanupClientData(client Client) {
	// TODO: Clean up client-specific data
}

func (zs *ZoneServer) cleanupSpawnData(spawn *spawn.Spawn) {
	// TODO: Clean up spawn-specific data
}

func (zs *ZoneServer) addSpawnToGrid(spawn *spawn.Spawn) {
	// TODO: Add spawn to grid system
}

func (zs *ZoneServer) removeSpawnFromGrid(spawn *spawn.Spawn) {
	// TODO: Remove spawn from grid system
}

func (zs *ZoneServer) markSpawnChanged(spawnID int32) {
	zs.changedSpawnsLock.Lock()
	defer zs.changedSpawnsLock.Unlock()
	zs.changedSpawns[spawnID] = true
}

func (zs *ZoneServer) markSpawnForRemoval(spawnID int32) {
	zs.pendingSpawnRemoveLock.Lock()
	defer zs.pendingSpawnRemoveLock.Unlock()
	zs.pendingSpawnRemove[spawnID] = true
}

func (zs *ZoneServer) sendSpawnToClient(client Client, spawn *spawn.Spawn) {
	// TODO: Send spawn packet to client
}

func (zs *ZoneServer) sendSpawnUpdateToClient(client Client, spawn *spawn.Spawn) {
	// TODO: Send spawn update packet to client
}

func (zs *ZoneServer) sendZoneInfo(client Client) {
	// TODO: Send zone info packet to client
}

func (zs *ZoneServer) sendWeatherUpdate() {
	// TODO: Send weather update to all clients
}

func (zs *ZoneServer) sendWeatherUpdateToClient(client Client) {
	// TODO: Send weather update to specific client
}

func (zs *ZoneServer) sendTimeUpdateToClient(client Client) {
	// TODO: Send time update to client
}

func (zs *ZoneServer) updateClientSpawnVisibility(client Client) {
	// TODO: Update spawn visibility for client
}

func abs(x float32) float32 {
	if x < 0 {
		return -x
	}
	return x
}
