package testutil

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
)

// LazyObj is a function that returns an object of type T.
type LazyObj[T any] func() T

// LazyObjT is an interface that provides access to the [testing.T] object.
type LazyObjT interface {
	T() *testing.T
}

// LazyObjNew is a function that creates and returns an object of type T and an error if any.
type LazyObjNew[T any] func() (T, error)

// LazyObjDrop is a callback that is called when [LazyObj] is reset.
// It receives a pointer to the object being dropped.
// If the object was not yet initialized, the callback is not called.
type LazyObjDrop[T any] func(T) error

// newLazyObj creates a new [LazyObj] that initializes the object on the first call.
// It returns a function that can be called to get the object and a cancel function
// that can be called to reset the object.
func newLazyObj[T any](
	s LazyObjT,
	newFn LazyObjNew[T],
	dropFn ...LazyObjDrop[T],
) (LazyObj[T], context.CancelFunc) {
	s.T().Helper()

	var obj *T

	init := func() T {
		if obj != nil {
			return *obj
		}

		o, err := newFn()
		require.NoError(s.T(), err, "Failed to initialize lazy object")

		obj = &o
		return o
	}

	cancel := func() {
		if obj == nil {
			return
		}

		for _, fn := range dropFn {
			if fn == nil {
				continue
			}

			require.NoError(s.T(), fn(*obj), "Failed to reset lazy object")
		}

		obj = nil
	}

	return init, cancel
}
