package event_bus

import (
	"fmt"
	"reflect"

	"github.com/pingcap/log"
	"go.uber.org/multierr"
	"go.uber.org/zap"
)

type Event interface {
	Name() string
}

var (
	listeners = map[reflect.Type][]reflect.Value{}

	baseEventType = reflect.TypeOf((*Event)(nil)).Elem()

	errType = reflect.TypeOf((*error)(nil)).Elem()
)

func Register(listener any) {
	rv := reflect.ValueOf(listener)

	for i := 0; i < rv.NumMethod(); i++ {
		method := rv.Method(i)

		if method.Type().NumIn() != 1 {
			continue
		}

		if !method.Type().In(0).Implements(baseEventType) {
			continue
		}

		arr, ok := listeners[method.Type().In(0)]
		if !ok {
			arr = make([]reflect.Value, 0)
		}
		arr = append(arr, method)

		listeners[method.Type().In(0)] = arr
	}
}

func PostSync(event Event) error {
	arr := listeners[reflect.TypeOf(event)]

	if len(arr) == 0 {
		log.Info("empty listener for event", zap.String("event", event.Name()))
		return nil
	}

	defer func() {
		if err := recover(); err != nil {
			log.Error("dispatch event failed", zap.String("event", fmt.Sprintf("%v", err)))
		}
	}()

	var errs error
	for _, fu := range arr {
		results := fu.Call([]reflect.Value{reflect.ValueOf(event)})

		for _, r := range results {
			if !r.Type().Implements(errType) {
				continue
			}

			if !r.IsZero() {
				errs = multierr.Append(errs, r.Interface().(error))
			}
		}
	}

	return errs
}
