package object

import (
	"fmt"
	"sync"
)

// ObjectManager manages all objects in the game world
type ObjectManager struct {
	objects map[int32]*Object // Objects by database ID

	// Zone-based indexing
	objectsByZone map[string][]*Object // Objects grouped by zone

	// Type-based indexing
	interactiveObjects []*Object // Objects that can be interacted with
	transportObjects   []*Object // Objects that provide transport
	merchantObjects    []*Object // Objects that are merchants
	collectorObjects   []*Object // Objects that are collectors

	// Thread safety
	mutex sync.RWMutex
}

// NewObjectManager creates a new object manager
func NewObjectManager() *ObjectManager {
	return &ObjectManager{
		objects:            make(map[int32]*Object),
		objectsByZone:      make(map[string][]*Object),
		interactiveObjects: make([]*Object, 0),
		transportObjects:   make([]*Object, 0),
		merchantObjects:    make([]*Object, 0),
		collectorObjects:   make([]*Object, 0),
	}
}

// AddObject adds an object to the manager
func (om *ObjectManager) AddObject(object *Object) error {
	if object == nil {
		return fmt.Errorf("cannot add nil object")
	}

	om.mutex.Lock()
	defer om.mutex.Unlock()

	databaseID := object.GetDatabaseID()
	if databaseID == 0 {
		return fmt.Errorf("object must have a valid database ID")
	}

	// Check if object already exists
	if _, exists := om.objects[databaseID]; exists {
		return fmt.Errorf("object with database ID %d already exists", databaseID)
	}

	// Add to main collection
	om.objects[databaseID] = object

	// Add to zone collection
	zoneName := object.GetZoneName()
	if zoneName != "" {
		om.objectsByZone[zoneName] = append(om.objectsByZone[zoneName], object)
	}

	// Add to type-based collections
	om.updateObjectIndices(object, true)

	return nil
}

// RemoveObject removes an object from the manager
func (om *ObjectManager) RemoveObject(databaseID int32) error {
	om.mutex.Lock()
	defer om.mutex.Unlock()

	object, exists := om.objects[databaseID]
	if !exists {
		return fmt.Errorf("object with database ID %d not found", databaseID)
	}

	// Remove from main collection
	delete(om.objects, databaseID)

	// Remove from zone collection
	zoneName := object.GetZoneName()
	if zoneName != "" {
		if zoneObjects, exists := om.objectsByZone[zoneName]; exists {
			for i, obj := range zoneObjects {
				if obj.GetDatabaseID() == databaseID {
					om.objectsByZone[zoneName] = append(zoneObjects[:i], zoneObjects[i+1:]...)
					break
				}
			}

			// Clean up empty zone collection
			if len(om.objectsByZone[zoneName]) == 0 {
				delete(om.objectsByZone, zoneName)
			}
		}
	}

	// Remove from type-based collections
	om.updateObjectIndices(object, false)

	return nil
}

// GetObject retrieves an object by database ID
func (om *ObjectManager) GetObject(databaseID int32) *Object {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	return om.objects[databaseID]
}

// GetObjectsByZone retrieves all objects in a specific zone
func (om *ObjectManager) GetObjectsByZone(zoneName string) []*Object {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	if objects, exists := om.objectsByZone[zoneName]; exists {
		// Return a copy to prevent external modification
		result := make([]*Object, len(objects))
		copy(result, objects)
		return result
	}

	return make([]*Object, 0)
}

// GetInteractiveObjects returns all objects that can be interacted with
func (om *ObjectManager) GetInteractiveObjects() []*Object {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	result := make([]*Object, len(om.interactiveObjects))
	copy(result, om.interactiveObjects)
	return result
}

// GetTransportObjects returns all objects that provide transport
func (om *ObjectManager) GetTransportObjects() []*Object {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	result := make([]*Object, len(om.transportObjects))
	copy(result, om.transportObjects)
	return result
}

// GetMerchantObjects returns all merchant objects
func (om *ObjectManager) GetMerchantObjects() []*Object {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	result := make([]*Object, len(om.merchantObjects))
	copy(result, om.merchantObjects)
	return result
}

// GetCollectorObjects returns all collector objects
func (om *ObjectManager) GetCollectorObjects() []*Object {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	result := make([]*Object, len(om.collectorObjects))
	copy(result, om.collectorObjects)
	return result
}

// GetObjectCount returns the total number of objects
func (om *ObjectManager) GetObjectCount() int {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	return len(om.objects)
}

// GetZoneCount returns the number of zones with objects
func (om *ObjectManager) GetZoneCount() int {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	return len(om.objectsByZone)
}

// GetObjectsByType returns objects filtered by specific criteria
func (om *ObjectManager) GetObjectsByType(objectType string) []*Object {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	switch objectType {
	case "interactive":
		result := make([]*Object, len(om.interactiveObjects))
		copy(result, om.interactiveObjects)
		return result
	case "transport":
		result := make([]*Object, len(om.transportObjects))
		copy(result, om.transportObjects)
		return result
	case "merchant":
		result := make([]*Object, len(om.merchantObjects))
		copy(result, om.merchantObjects)
		return result
	case "collector":
		result := make([]*Object, len(om.collectorObjects))
		copy(result, om.collectorObjects)
		return result
	default:
		return make([]*Object, 0)
	}
}

// FindObjectsInZone finds objects in a zone matching specific criteria
func (om *ObjectManager) FindObjectsInZone(zoneName string, filter func(*Object) bool) []*Object {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	zoneObjects, exists := om.objectsByZone[zoneName]
	if !exists {
		return make([]*Object, 0)
	}

	result := make([]*Object, 0)
	for _, obj := range zoneObjects {
		if filter == nil || filter(obj) {
			result = append(result, obj)
		}
	}

	return result
}

// FindObjectByName finds the first object with a matching name (placeholder)
func (om *ObjectManager) FindObjectByName(name string) *Object {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	// TODO: Implement name searching when spawn name system is integrated
	// For now, return nil
	return nil
}

// UpdateObject updates an existing object's properties
func (om *ObjectManager) UpdateObject(databaseID int32, updateFn func(*Object)) error {
	om.mutex.Lock()
	defer om.mutex.Unlock()

	object, exists := om.objects[databaseID]
	if !exists {
		return fmt.Errorf("object with database ID %d not found", databaseID)
	}

	// Store old zone for potential reindexing
	oldZone := object.GetZoneName()

	// Apply updates
	updateFn(object)

	// Check if zone changed and reindex if necessary
	newZone := object.GetZoneName()
	if oldZone != newZone {
		// Remove from old zone
		if oldZone != "" {
			if zoneObjects, exists := om.objectsByZone[oldZone]; exists {
				for i, obj := range zoneObjects {
					if obj.GetDatabaseID() == databaseID {
						om.objectsByZone[oldZone] = append(zoneObjects[:i], zoneObjects[i+1:]...)
						break
					}
				}

				// Clean up empty zone collection
				if len(om.objectsByZone[oldZone]) == 0 {
					delete(om.objectsByZone, oldZone)
				}
			}
		}

		// Add to new zone
		if newZone != "" {
			om.objectsByZone[newZone] = append(om.objectsByZone[newZone], object)
		}
	}

	// Update type-based indices
	om.rebuildIndicesForObject(object)

	return nil
}

// ClearZone removes all objects from a specific zone
func (om *ObjectManager) ClearZone(zoneName string) int {
	om.mutex.Lock()
	defer om.mutex.Unlock()

	zoneObjects, exists := om.objectsByZone[zoneName]
	if !exists {
		return 0
	}

	count := len(zoneObjects)

	// Remove objects from main collection and indices
	for _, obj := range zoneObjects {
		databaseID := obj.GetDatabaseID()
		delete(om.objects, databaseID)
		om.updateObjectIndices(obj, false)
	}

	// Clear zone collection
	delete(om.objectsByZone, zoneName)

	return count
}

// GetStatistics returns statistics about objects in the manager
func (om *ObjectManager) GetStatistics() map[string]interface{} {
	om.mutex.RLock()
	defer om.mutex.RUnlock()

	stats := make(map[string]interface{})
	stats["total_objects"] = len(om.objects)
	stats["zones_with_objects"] = len(om.objectsByZone)
	stats["interactive_objects"] = len(om.interactiveObjects)
	stats["transport_objects"] = len(om.transportObjects)
	stats["merchant_objects"] = len(om.merchantObjects)
	stats["collector_objects"] = len(om.collectorObjects)

	// Zone breakdown
	zoneStats := make(map[string]int)
	for zoneName, objects := range om.objectsByZone {
		zoneStats[zoneName] = len(objects)
	}
	stats["objects_by_zone"] = zoneStats

	return stats
}

// Shutdown clears all objects and prepares for shutdown
func (om *ObjectManager) Shutdown() {
	om.mutex.Lock()
	defer om.mutex.Unlock()

	om.objects = make(map[int32]*Object)
	om.objectsByZone = make(map[string][]*Object)
	om.interactiveObjects = make([]*Object, 0)
	om.transportObjects = make([]*Object, 0)
	om.merchantObjects = make([]*Object, 0)
	om.collectorObjects = make([]*Object, 0)
}

// Private helper methods

// updateObjectIndices updates type-based indices for an object
func (om *ObjectManager) updateObjectIndices(object *Object, add bool) {
	if add {
		// Add to type-based collections
		if object.IsClickable() || len(object.GetPrimaryCommands()) > 0 || len(object.GetSecondaryCommands()) > 0 {
			om.interactiveObjects = append(om.interactiveObjects, object)
		}

		if object.GetTransporterID() > 0 {
			om.transportObjects = append(om.transportObjects, object)
		}

		if object.GetMerchantID() > 0 {
			om.merchantObjects = append(om.merchantObjects, object)
		}

		if object.IsCollector() {
			om.collectorObjects = append(om.collectorObjects, object)
		}
	} else {
		// Remove from type-based collections
		databaseID := object.GetDatabaseID()

		om.interactiveObjects = removeObjectFromSlice(om.interactiveObjects, databaseID)
		om.transportObjects = removeObjectFromSlice(om.transportObjects, databaseID)
		om.merchantObjects = removeObjectFromSlice(om.merchantObjects, databaseID)
		om.collectorObjects = removeObjectFromSlice(om.collectorObjects, databaseID)
	}
}

// rebuildIndicesForObject rebuilds type-based indices for an object (used after updates)
func (om *ObjectManager) rebuildIndicesForObject(object *Object) {
	databaseID := object.GetDatabaseID()

	// Remove from all type-based collections
	om.interactiveObjects = removeObjectFromSlice(om.interactiveObjects, databaseID)
	om.transportObjects = removeObjectFromSlice(om.transportObjects, databaseID)
	om.merchantObjects = removeObjectFromSlice(om.merchantObjects, databaseID)
	om.collectorObjects = removeObjectFromSlice(om.collectorObjects, databaseID)

	// Re-add based on current properties
	om.updateObjectIndices(object, true)
}

// removeObjectFromSlice removes an object from a slice by database ID
func removeObjectFromSlice(slice []*Object, databaseID int32) []*Object {
	for i, obj := range slice {
		if obj.GetDatabaseID() == databaseID {
			return append(slice[:i], slice[i+1:]...)
		}
	}
	return slice
}

// Global object manager instance
var globalObjectManager *ObjectManager
var initObjectManagerOnce sync.Once

// GetGlobalObjectManager returns the global object manager (singleton)
func GetGlobalObjectManager() *ObjectManager {
	initObjectManagerOnce.Do(func() {
		globalObjectManager = NewObjectManager()
	})
	return globalObjectManager
}
