package surf

import (
	"github.com/enetx/g"
	"github.com/enetx/g/cmp"
)

// mw represents a middleware wrapper that holds the function itself,
// its execution priority, and the order in which it was added.
type mw[T any] struct {
	fn       func(T) error // Middleware function
	priority g.Int         // Execution priority (lower is higher priority)
	order    g.Int         // Insertion order (used to stabilize sorting)
}

// middleware is a generic middleware chain manager for type T.
// It uses a priority heap to manage the execution order of middleware functions.
type middleware[T any] struct {
	heap    *g.Heap[mw[T]] // Heap-ordered middleware functions
	counter g.Int          // Monotonic counter to track insertion order
}

// newMiddleware creates a new middleware manager for type T.
//
// Returns:
//   - *middleware[T]: a pointer to a new middleware instance
//
// Lower priority values execute earlier. For equal priorities, insertion order is preserved.
func newMiddleware[T any]() *middleware[T] {
	return &middleware[T]{
		heap: g.NewHeap(func(a, b mw[T]) cmp.Ordering {
			return a.priority.Cmp(b.priority).
				Then(a.order.Cmp(b.order))
		}),
	}
}

// add adds a middleware function to the chain with the specified priority.
//
// Parameters:
//   - priority int: determines execution order (lower means earlier execution)
//   - fn func(T) error: the middleware function that receives a context of type T
//
// Functions with the same priority are executed in the order they were added.
func (m *middleware[T]) add(priority g.Int, fn func(T) error) {
	m.heap.Push(mw[T]{fn, priority, m.counter})
	m.counter++
}

// run executes all middleware functions in priority order.
//
// Parameters:
//   - ctx T: the context value passed to each middleware function
//
// Returns:
//   - error: the first error encountered in the chain, or nil if all passed
//
// Note:
//   - A clone of the heap is used during execution to avoid mutating the original chain.
//   - Execution stops immediately if any middleware returns a non-nil error.
func (m *middleware[T]) run(ctx T) error {
	clone := m.heap.Clone()

	for !clone.Empty() {
		item := clone.Pop().Some()
		if err := item.fn(ctx); err != nil {
			return err
		}
	}

	return nil
}
