package application

import (
	"slices"

	"github.com/wailsapp/wails/v3/pkg/events"
)

// EventManager manages event-related operations
type EventManager struct {
	app *App
}

// newEventManager creates a new EventManager instance
func newEventManager(app *App) *EventManager {
	return &EventManager{
		app: app,
	}
}

// Emit emits a custom event with the specified name and associated data.
// It returns a boolean indicating whether the event was cancelled by a hook.
//
// If no data argument is provided, Emit emits an event with nil data.
// When there is exactly one data argument, it will be used as the custom event's data field.
// When more than one argument is provided, the event's data field will be set to the argument slice.
//
// If the given event name is registered, Emit validates the data parameter
// against the expected data type. In case of a mismatch, Emit reports an error
// to the registered error handler for the application and cancels the event.
func (em *EventManager) Emit(name string, data ...any) bool {
	event := &CustomEvent{Name: name}

	if len(data) == 1 {
		event.Data = data[0]
	} else if len(data) > 1 {
		event.Data = data
	}

	if err := em.app.customEventProcessor.Emit(event); err != nil {
		globalApplication.handleError(err)
	}

	return event.IsCancelled()
}

// EmitEvent emits a custom event object (internal use)
// It returns a boolean indicating whether the event was cancelled by a hook.
//
// If the given event name is registered, emitEvent validates the data parameter
// against the expected data type. In case of a mismatch, emitEvent reports an error
// to the registered error handler for the application and cancels the event.
func (em *EventManager) EmitEvent(event *CustomEvent) bool {
	if err := em.app.customEventProcessor.Emit(event); err != nil {
		globalApplication.handleError(err)
	}

	return event.IsCancelled()
}

// On registers a listener for custom events
func (em *EventManager) On(name string, callback func(event *CustomEvent)) func() {
	return em.app.customEventProcessor.On(name, callback)
}

// Off removes all listeners for a custom event
func (em *EventManager) Off(name string) {
	em.app.customEventProcessor.Off(name)
}

// OnMultiple registers a listener for custom events that will be called N times
func (em *EventManager) OnMultiple(name string, callback func(event *CustomEvent), counter int) {
	em.app.customEventProcessor.OnMultiple(name, callback, counter)
}

// Reset removes all custom event listeners
func (em *EventManager) Reset() {
	em.app.customEventProcessor.OffAll()
}

// OnApplicationEvent registers a listener for application events
func (em *EventManager) OnApplicationEvent(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() {
	eventID := uint(eventType)
	em.app.applicationEventListenersLock.Lock()
	defer em.app.applicationEventListenersLock.Unlock()
	listener := &EventListener{
		callback: callback,
	}
	em.app.applicationEventListeners[eventID] = append(em.app.applicationEventListeners[eventID], listener)
	if em.app.impl != nil {
		go func() {
			defer handlePanic()
			em.app.impl.on(eventID)
		}()
	}

	return func() {
		// lock the map
		em.app.applicationEventListenersLock.Lock()
		defer em.app.applicationEventListenersLock.Unlock()
		// Remove listener
		em.app.applicationEventListeners[eventID] = slices.DeleteFunc(em.app.applicationEventListeners[eventID], func(l *EventListener) bool {
			return l == listener
		})
	}
}

// RegisterApplicationEventHook registers an application event hook
func (em *EventManager) RegisterApplicationEventHook(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() {
	eventID := uint(eventType)
	em.app.applicationEventHooksLock.Lock()
	defer em.app.applicationEventHooksLock.Unlock()
	thisHook := &eventHook{
		callback: callback,
	}
	em.app.applicationEventHooks[eventID] = append(em.app.applicationEventHooks[eventID], thisHook)

	return func() {
		em.app.applicationEventHooksLock.Lock()
		em.app.applicationEventHooks[eventID] = slices.DeleteFunc(em.app.applicationEventHooks[eventID], func(h *eventHook) bool {
			return h == thisHook
		})
		em.app.applicationEventHooksLock.Unlock()
	}
}

// Dispatch dispatches an event to listeners (internal use)
func (em *EventManager) dispatch(event *CustomEvent) {
	// Snapshot listeners under Lock
	em.app.wailsEventListenerLock.Lock()
	listeners := slices.Clone(em.app.wailsEventListeners)
	em.app.wailsEventListenerLock.Unlock()

	for _, listener := range listeners {
		if event.IsCancelled() {
			return
		}
		listener.DispatchWailsEvent(event)
	}
}

// HandleApplicationEvent handles application events (internal use)
func (em *EventManager) handleApplicationEvent(event *ApplicationEvent) {
	defer handlePanic()
	em.app.applicationEventListenersLock.RLock()
	listeners, ok := em.app.applicationEventListeners[event.Id]
	em.app.applicationEventListenersLock.RUnlock()
	if !ok {
		return
	}

	// Process Hooks
	em.app.applicationEventHooksLock.RLock()
	hooks, ok := em.app.applicationEventHooks[event.Id]
	em.app.applicationEventHooksLock.RUnlock()
	if ok {
		for _, thisHook := range hooks {
			thisHook.callback(event)
			if event.IsCancelled() {
				return
			}
		}
	}

	for _, listener := range listeners {
		go func() {
			if event.IsCancelled() {
				return
			}
			defer handlePanic()
			listener.callback(event)
		}()
	}
}
