package dataservices

import (
	"errors"
	"fmt"

	perrors "github.com/portainer/portainer/api/dataservices/errors"

	"github.com/rs/zerolog/log"
)

// ErrStop signals the stop of computation when filtering results
var ErrStop = errors.New("stop")

func IsErrObjectNotFound(e error) bool {
	return errors.Is(e, perrors.ErrObjectNotFound)
}

// AppendFn appends elements to the given collection slice
func AppendFn[T any](collection *[]T) func(obj interface{}) (interface{}, error) {
	return func(obj interface{}) (interface{}, error) {
		element, ok := obj.(*T)
		if !ok {
			log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("type assertion failed")
			return nil, fmt.Errorf("failed to convert to %T object: %#v", new(T), obj)
		}

		*collection = append(*collection, *element)

		return new(T), nil
	}
}

// FilterFn appends elements to the given collection when the predicate is true
func FilterFn[T any](collection *[]T, predicate func(T) bool) func(obj interface{}) (interface{}, error) {
	return func(obj interface{}) (interface{}, error) {
		element, ok := obj.(*T)
		if !ok {
			log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("type assertion failed")
			return nil, fmt.Errorf("failed to convert to %T object: %#v", new(T), obj)
		}

		if predicate(*element) {
			*collection = append(*collection, *element)
		}

		return new(T), nil
	}
}

// FirstFn sets the element to the first one that satisfies the predicate and stops the computation, returns ErrStop on
// success
func FirstFn[T any](element *T, predicate func(T) bool) func(obj interface{}) (interface{}, error) {
	return func(obj interface{}) (interface{}, error) {
		e, ok := obj.(*T)
		if !ok {
			log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("type assertion failed")
			return nil, fmt.Errorf("failed to convert to %T object: %#v", new(T), obj)
		}

		if predicate(*e) {
			*element = *e
			return new(T), ErrStop
		}

		return new(T), nil
	}
}