package errors

import (
	"errors"
)

// See: errors.Is().
func Is(err error, target error) bool { return errors.Is(err, target) }

// IsV2 calls Is(err, target) for each target within targets.
func IsV2(err error, targets ...error) bool {
	for _, target := range targets {
		if Is(err, target) {
			return true
		}
	}
	return false
}

// See: errors.As().
func As(err error, target any) bool { return errors.As(err, target) }

// AsV2 is functionally similar to As(), instead
// leveraging generics to handle allocation and
// returning of a concrete generic parameter type.
func AsV2[Type any](err error) Type {
	var t Type
	var ok bool
	errs := []error{err}
	for len(errs) > 0 {
		// Pop next error to check.
		err := errs[len(errs)-1]
		errs = errs[:len(errs)-1]

		// Check direct type.
		t, ok = err.(Type)
		if ok {
			return t
		}

		// Look for .As() support.
		as, ok := err.(interface {
			As(target any) bool
		})

		if ok {
			// Attempt .As().
			if as.As(&t) {
				return t
			}
		}

		// Try unwrap errors.
		switch u := err.(type) {
		case interface{ Unwrap() error }:
			errs = append(errs, u.Unwrap())
		case interface{ Unwrap() []error }:
			errs = append(errs, u.Unwrap()...)
		}
	}
	return t
}

// See: errors.Unwrap().
func Unwrap(err error) error { return errors.Unwrap(err) }

// UnwrapV2 is functionally similar to Unwrap(), except that
// it also handles the case of interface{ Unwrap() []error }.
func UnwrapV2(err error) []error {
	switch u := err.(type) {
	case interface{ Unwrap() error }:
		if e := u.Unwrap(); err != nil {
			return []error{e}
		}
	case interface{ Unwrap() []error }:
		return u.Unwrap()
	}
	return nil
}

// See: errors.Join().
func Join(errs ...error) error { return errors.Join(errs...) }