// Copyright (c) 2020-2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package fx

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"strings"

	"go.uber.org/dig"
	"go.uber.org/fx/internal/fxreflect"
)

// Annotated annotates a constructor provided to Fx with additional options.
//
// For example,
//
//	func NewReadOnlyConnection(...) (*Connection, error)
//
//	fx.Provide(fx.Annotated{
//	  Name: "ro",
//	  Target: NewReadOnlyConnection,
//	})
//
// Is equivalent to,
//
//	type result struct {
//	  fx.Out
//
//	  Connection *Connection `name:"ro"`
//	}
//
//	fx.Provide(func(...) (result, error) {
//	  conn, err := NewReadOnlyConnection(...)
//	  return result{Connection: conn}, err
//	})
//
// Annotated cannot be used with constructors which produce fx.Out objects.
// When used with [Supply], Target is a value instead of a constructor.
//
// This type represents a less powerful version of the [Annotate] construct;
// prefer [Annotate] where possible.
type Annotated struct {
	// If specified, this will be used as the name for all non-error values returned
	// by the constructor. For more information on named values, see the documentation
	// for the fx.Out type.
	//
	// A name option may not be provided if a group option is provided.
	Name string

	// If specified, this will be used as the group name for all non-error values returned
	// by the constructor. For more information on value groups, see the package documentation.
	//
	// A group option may not be provided if a name option is provided.
	//
	// Similar to group tags, the group name may be followed by a `,flatten`
	// option to indicate that each element in the slice returned by the
	// constructor should be injected into the value group individually.
	Group string

	// Target is the constructor or value being annotated with fx.Annotated.
	Target interface{}
}

func (a Annotated) String() string {
	var fields []string
	if len(a.Name) > 0 {
		fields = append(fields, fmt.Sprintf("Name: %q", a.Name))
	}
	if len(a.Group) > 0 {
		fields = append(fields, fmt.Sprintf("Group: %q", a.Group))
	}
	if a.Target != nil {
		fields = append(fields, fmt.Sprintf("Target: %v", fxreflect.FuncName(a.Target)))
	}
	return fmt.Sprintf("fx.Annotated{%v}", strings.Join(fields, ", "))
}

var (
	// field used for embedding fx.In type in generated struct.
	_inAnnotationField = reflect.StructField{
		Name:      "In",
		Type:      reflect.TypeOf(In{}),
		Anonymous: true,
	}
	// field used for embedding fx.Out type in generated struct.
	_outAnnotationField = reflect.StructField{
		Name:      "Out",
		Type:      reflect.TypeOf(Out{}),
		Anonymous: true,
	}
)

// Annotation specifies how to wrap a target for [Annotate].
// It can be used to set up additional options for a constructor,
// or with [Supply], for a value.
type Annotation interface {
	apply(*annotated) error
	build(*annotated) (interface{}, error)
}

var (
	_typeOfError = reflect.TypeOf((*error)(nil)).Elem()
	_nilError    = reflect.Zero(_typeOfError)
)

// annotationError is a wrapper for an error that was encountered while
// applying annotation to a function. It contains the specific error
// that it encountered as well as the target interface that was attempted
// to be annotated.
type annotationError struct {
	target interface{}
	err    error
}

func (e *annotationError) Error() string {
	return e.err.Error()
}

// Unwrap the wrapped error.
func (e *annotationError) Unwrap() error {
	return e.err
}

type paramTagsAnnotation struct {
	tags []string
}

var _ Annotation = paramTagsAnnotation{}
var (
	errTagSyntaxSpace            = errors.New(`multiple tags are not separated by space`)
	errTagKeySyntax              = errors.New("tag key is invalid, Use group, name or optional as tag keys")
	errTagValueSyntaxQuote       = errors.New(`tag value should start with double quote. i.e. key:"value" `)
	errTagValueSyntaxEndingQuote = errors.New(`tag value should end in double quote. i.e. key:"value" `)
)

// Collections of key value pairs within a tag should be separated by a space.
// Eg: `group:"some" optional:"true"`.
func verifyTagsSpaceSeparated(tagIdx int, tag string) error {
	if tagIdx > 0 && tag != "" && tag[0] != ' ' {
		return errTagSyntaxSpace
	}
	return nil
}

// verify tag values are delimited with double quotes.
func verifyValueQuote(value string) (string, error) {
	// starting quote should be a double quote
	if value[0] != '"' {
		return "", errTagValueSyntaxQuote
	}
	// validate tag value is within quotes
	i := 1
	for i < len(value) && value[i] != '"' {
		if value[i] == '\\' {
			i++
		}
		i++
	}
	if i >= len(value) {
		return "", errTagValueSyntaxEndingQuote
	}
	return value[i+1:], nil
}

// Check whether the tag follows valid struct.
// format and returns an error if it's invalid. (i.e. not following
// tag:"value" space-separated list )
// Currently dig accepts only 'name', 'group', 'optional' as valid tag keys.
func verifyAnnotateTag(tag string) error {
	tagIdx := 0
	validKeys := map[string]struct{}{"group": {}, "optional": {}, "name": {}}
	for ; tag != ""; tagIdx++ {
		if err := verifyTagsSpaceSeparated(tagIdx, tag); err != nil {
			return err
		}
		i := 0
		if strings.TrimSpace(tag) == "" {
			return nil
		}
		// parsing the key i.e. till reaching colon :
		for i < len(tag) && tag[i] != ':' {
			i++
		}
		key := strings.TrimSpace(tag[:i])
		if _, ok := validKeys[key]; !ok {
			return errTagKeySyntax
		}
		value, err := verifyValueQuote(tag[i+1:])
		if err != nil {
			return err
		}
		tag = value
	}
	return nil
}

// Given func(T1, T2, T3, ..., TN), this generates a type roughly
// equivalent to,
//
//   struct {
//     fx.In
//
//     Field1 T1 `$tags[0]`
//     Field2 T2 `$tags[1]`
//     ...
//     FieldN TN `$tags[N-1]`
//   }
//
// If there has already been a ParamTag that was applied, this
// will return an error.
//
// If the tag is invalid and has mismatched quotation for example,
// (`tag_name:"tag_value') , this will return an error.

func (pt paramTagsAnnotation) apply(ann *annotated) error {
	if len(ann.ParamTags) > 0 {
		return errors.New("cannot apply more than one line of ParamTags")
	}
	for _, tag := range pt.tags {
		if err := verifyAnnotateTag(tag); err != nil {
			return err
		}
	}
	ann.ParamTags = pt.tags
	return nil
}

// build builds and returns a constructor after applying a ParamTags annotation
func (pt paramTagsAnnotation) build(ann *annotated) (interface{}, error) {
	paramTypes, remap := pt.parameters(ann)
	resultTypes, _ := ann.currentResultTypes()

	origFn := reflect.ValueOf(ann.Target)
	newFnType := reflect.FuncOf(paramTypes, resultTypes, false)
	newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
		args = remap(args)
		return origFn.Call(args)
	})
	return newFn.Interface(), nil
}

// parameters returns the type for the parameters of the annotated function,
// and a function that maps the arguments of the annotated function
// back to the arguments of the target function.
func (pt paramTagsAnnotation) parameters(ann *annotated) (
	types []reflect.Type,
	remap func([]reflect.Value) []reflect.Value,
) {
	ft := reflect.TypeOf(ann.Target)
	types = make([]reflect.Type, ft.NumIn())
	for i := 0; i < ft.NumIn(); i++ {
		types[i] = ft.In(i)
	}

	// No parameter annotations. Return the original types
	// and an identity function.
	if len(pt.tags) == 0 {
		return types, func(args []reflect.Value) []reflect.Value {
			return args
		}
	}

	// Turn parameters into an fx.In struct.
	inFields := []reflect.StructField{_inAnnotationField}

	// there was a variadic argument, so it was pre-transformed
	if len(types) > 0 && isIn(types[0]) {
		paramType := types[0]

		for i := 1; i < paramType.NumField(); i++ {
			origField := paramType.Field(i)
			field := reflect.StructField{
				Name: origField.Name,
				Type: origField.Type,
				Tag:  origField.Tag,
			}
			if i-1 < len(pt.tags) {
				field.Tag = reflect.StructTag(pt.tags[i-1])
			}

			inFields = append(inFields, field)
		}

		types = []reflect.Type{reflect.StructOf(inFields)}
		return types, func(args []reflect.Value) []reflect.Value {
			param := args[0]
			args[0] = reflect.New(paramType).Elem()
			for i := 1; i < paramType.NumField(); i++ {
				args[0].Field(i).Set(param.Field(i))
			}
			return args
		}
	}

	for i, t := range types {
		field := reflect.StructField{
			Name: fmt.Sprintf("Field%d", i),
			Type: t,
		}
		if i < len(pt.tags) {
			field.Tag = reflect.StructTag(pt.tags[i])
		}

		inFields = append(inFields, field)
	}

	types = []reflect.Type{reflect.StructOf(inFields)}
	return types, func(args []reflect.Value) []reflect.Value {
		params := args[0]
		args = args[:0]
		for i := 0; i < ft.NumIn(); i++ {
			args = append(args, params.Field(i+1))
		}
		return args
	}
}

// ParamTags is an Annotation that annotates the parameter(s) of a function.
//
// When multiple tags are specified, each tag is mapped to the corresponding
// positional parameter.
// For example, the following will refer to a named database connection,
// and the default, unnamed logger:
//
//	fx.Annotate(func(log *log.Logger, conn *sql.DB) *Handler {
//		// ...
//	}, fx.ParamTags("", `name:"ro"`))
//
// ParamTags cannot be used in a function that takes an fx.In struct as a
// parameter.
func ParamTags(tags ...string) Annotation {
	return paramTagsAnnotation{tags}
}

type resultTagsAnnotation struct {
	tags []string
}

var _ Annotation = resultTagsAnnotation{}

// Given func(T1, T2, T3, ..., TN), this generates a type roughly
// equivalent to,
//
//	struct {
//	  fx.Out
//
//	  Field1 T1 `$tags[0]`
//	  Field2 T2 `$tags[1]`
//	  ...
//	  FieldN TN `$tags[N-1]`
//	}
//
// If there has already been a ResultTag that was applied, this
// will return an error.
//
// If the tag is invalid and has mismatched quotation for example,
// (`tag_name:"tag_value') , this will return an error.
func (rt resultTagsAnnotation) apply(ann *annotated) error {
	if len(ann.ResultTags) > 0 {
		return errors.New("cannot apply more than one line of ResultTags")
	}
	for _, tag := range rt.tags {
		if err := verifyAnnotateTag(tag); err != nil {
			return err
		}
	}
	ann.ResultTags = rt.tags
	return nil
}

// build builds and returns a constructor after applying a ResultTags annotation
func (rt resultTagsAnnotation) build(ann *annotated) (interface{}, error) {
	paramTypes := ann.currentParamTypes()
	resultTypes, remapResults := rt.results(ann)
	origFn := reflect.ValueOf(ann.Target)
	newFnType := reflect.FuncOf(paramTypes, resultTypes, false)
	newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
		results := origFn.Call(args)
		return remapResults(results)
	})
	return newFn.Interface(), nil
}

// results returns the types of the results of the annotated function,
// and a function that maps the results of the target function,
// into a result compatible with the annotated function.
func (rt resultTagsAnnotation) results(ann *annotated) (
	types []reflect.Type,
	remap func([]reflect.Value) []reflect.Value,
) {
	types, hasError := ann.currentResultTypes()

	if hasError {
		types = types[:len(types)-1]
	}

	// No result annotations. Return the original types
	// and an identity function.
	if len(rt.tags) == 0 {
		return types, func(results []reflect.Value) []reflect.Value {
			return results
		}
	}

	// if there's no Out struct among the return types, there was no As annotation applied
	// just replace original result types with an Out struct and apply tags
	var (
		newOut       outStructInfo
		existingOuts []reflect.Type
	)

	newOut.Fields = []reflect.StructField{_outAnnotationField}
	newOut.Offsets = []int{}

	for i, t := range types {
		if !isOut(t) {
			// this must be from the original function.
			// apply the tags
			field := reflect.StructField{
				Name: fmt.Sprintf("Field%d", i),
				Type: t,
			}
			if i < len(rt.tags) {
				field.Tag = reflect.StructTag(rt.tags[i])
			}
			newOut.Offsets = append(newOut.Offsets, len(newOut.Fields))
			newOut.Fields = append(newOut.Fields, field)
			continue
		}
		// this must be from an As annotation
		// apply the tags to the existing type
		taggedFields := make([]reflect.StructField, t.NumField())
		taggedFields[0] = _outAnnotationField
		for j, tag := range rt.tags {
			if j+1 < t.NumField() {
				field := t.Field(j + 1)
				taggedFields[j+1] = reflect.StructField{
					Name: field.Name,
					Type: field.Type,
					Tag:  reflect.StructTag(tag),
				}
			}
		}
		existingOuts = append(existingOuts, reflect.StructOf(taggedFields))
	}

	resType := reflect.StructOf(newOut.Fields)

	outTypes := []reflect.Type{resType}
	// append existing outs back to outTypes
	outTypes = append(outTypes, existingOuts...)
	if hasError {
		outTypes = append(outTypes, _typeOfError)
	}

	return outTypes, func(results []reflect.Value) []reflect.Value {
		var (
			outErr     error
			outResults []reflect.Value
		)
		outResults = append(outResults, reflect.New(resType).Elem())

		tIdx := 0
		for i, r := range results {
			if i == len(results)-1 && hasError {
				// If hasError and this is the last item,
				// we are guaranteed that this is an error
				// object.
				if err, _ := r.Interface().(error); err != nil {
					outErr = err
				}
				continue
			}
			if i < len(newOut.Offsets) {
				if fieldIdx := newOut.Offsets[i]; fieldIdx > 0 {
					// fieldIdx 0 is an invalid index
					// because it refers to uninitialized
					// outs and would point to fx.Out in the
					// struct definition. We need to check this
					// to prevent panic from setting fx.Out to
					// a value.
					outResults[0].Field(fieldIdx).Set(r)
				}
				continue
			}
			if isOut(r.Type()) {
				tIdx++
				if tIdx < len(outTypes) {
					newResult := reflect.New(outTypes[tIdx]).Elem()
					for j := 1; j < outTypes[tIdx].NumField(); j++ {
						newResult.Field(j).Set(r.Field(j))
					}
					outResults = append(outResults, newResult)
				}
			}
		}

		if hasError {
			if outErr != nil {
				outResults = append(outResults, reflect.ValueOf(outErr))
			} else {
				outResults = append(outResults, _nilError)
			}
		}

		return outResults
	}
}

// ResultTags is an Annotation that annotates the result(s) of a function.
// When multiple tags are specified, each tag is mapped to the corresponding
// positional result.
//
// For example, the following will produce a named database connection.
//
//	fx.Annotate(func() (*sql.DB, error) {
//		// ...
//	}, fx.ResultTags(`name:"ro"`))
//
// ResultTags cannot be used on a function that returns an fx.Out struct.
func ResultTags(tags ...string) Annotation {
	return resultTagsAnnotation{tags}
}

type outStructInfo struct {
	Fields  []reflect.StructField // fields of the struct
	Offsets []int                 // Offsets[i] is the index of result i in Fields
}

type _lifecycleHookAnnotationType int

const (
	_unknownHookType _lifecycleHookAnnotationType = iota
	_onStartHookType
	_onStopHookType
)

type lifecycleHookAnnotation struct {
	Type   _lifecycleHookAnnotationType
	Target interface{}
}

var _ Annotation = (*lifecycleHookAnnotation)(nil)

func (la *lifecycleHookAnnotation) String() string {
	name := "UnknownHookAnnotation"
	switch la.Type {
	case _onStartHookType:
		name = _onStartHook
	case _onStopHookType:
		name = _onStopHook
	}
	return name
}

func (la *lifecycleHookAnnotation) apply(ann *annotated) error {
	if la.Target == nil {
		return fmt.Errorf(
			"cannot use nil function for %q hook annotation",
			la,
		)
	}

	for _, h := range ann.Hooks {
		if la.Type == h.Type {
			return fmt.Errorf(
				"cannot apply more than one %q hook annotation",
				la,
			)
		}
	}

	ft := reflect.TypeOf(la.Target)

	if ft.Kind() != reflect.Func {
		return fmt.Errorf(
			"must provide function for %q hook, got %v (%T)",
			la,
			la.Target,
			la.Target,
		)
	}

	if n := ft.NumOut(); n > 0 {
		if n > 1 || ft.Out(0) != _typeOfError {
			return fmt.Errorf(
				"optional hook return may only be an error, got %v (%T)",
				la.Target,
				la.Target,
			)
		}
	}

	if ft.IsVariadic() {
		return fmt.Errorf(
			"hooks must not accept variadic parameters, got %v (%T)",
			la.Target,
			la.Target,
		)
	}

	ann.Hooks = append(ann.Hooks, la)
	return nil
}

// build builds and returns a constructor after applying a lifecycle hook annotation.
func (la *lifecycleHookAnnotation) build(ann *annotated) (interface{}, error) {
	resultTypes, hasError := ann.currentResultTypes()
	if !hasError {
		resultTypes = append(resultTypes, _typeOfError)
	}

	hookInstaller, paramTypes, remapParams := la.buildHookInstaller(ann)

	origFn := reflect.ValueOf(ann.Target)
	newFnType := reflect.FuncOf(paramTypes, resultTypes, false)
	newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
		// copy the original arguments before remapping the parameters
		// so that we can apply them to the hookInstaller.
		origArgs := make([]reflect.Value, len(args))
		copy(origArgs, args)
		args = remapParams(args)
		results := origFn.Call(args)
		if hasError {
			errVal := results[len(results)-1]
			results = results[:len(results)-1]
			if err, _ := errVal.Interface().(error); err != nil {
				// if constructor returned error, do not call hook installer
				return append(results, errVal)
			}
		}
		hookInstallerResults := hookInstaller.Call(append(results, origArgs...))
		results = append(results, hookInstallerResults[0])
		return results
	})
	return newFn.Interface(), nil
}

var (
	_typeOfLifecycle = reflect.TypeOf((*Lifecycle)(nil)).Elem()
	_typeOfContext   = reflect.TypeOf((*context.Context)(nil)).Elem()
)

// buildHookInstaller returns a function that appends a hook to Lifecycle when called,
// along with the new parameter types and a function that maps arguments to the annotated constructor
func (la *lifecycleHookAnnotation) buildHookInstaller(ann *annotated) (
	hookInstaller reflect.Value,
	paramTypes []reflect.Type,
	remapParams func([]reflect.Value) []reflect.Value, // function to remap parameters to function being annotated
) {
	paramTypes = ann.currentParamTypes()
	paramTypes, remapParams = injectLifecycle(paramTypes)

	resultTypes, hasError := ann.currentResultTypes()
	if hasError {
		resultTypes = resultTypes[:len(resultTypes)-1]
	}

	// look for the context.Context type from the original hook function
	// and then exclude it from the paramTypes of invokeFn because context.Context
	// will be injected by the lifecycle
	ctxPos := -1
	ctxStructPos := -1
	origHookFn := reflect.ValueOf(la.Target)
	origHookFnT := reflect.TypeOf(la.Target)
	invokeParamTypes := []reflect.Type{
		_typeOfLifecycle,
	}
	for i := 0; i < origHookFnT.NumIn(); i++ {
		t := origHookFnT.In(i)
		if t == _typeOfContext && ctxPos < 0 {
			ctxPos = i
			continue
		}
		if !isIn(t) {
			invokeParamTypes = append(invokeParamTypes, origHookFnT.In(i))
			continue
		}
		fields := []reflect.StructField{_inAnnotationField}
		for j := 1; j < t.NumField(); j++ {
			field := t.Field(j)
			if field.Type == _typeOfContext && ctxPos < 0 {
				ctxStructPos = i
				ctxPos = j
				continue
			}
			fields = append(fields, field)
		}
		invokeParamTypes = append(invokeParamTypes, reflect.StructOf(fields))

	}
	invokeFnT := reflect.FuncOf(invokeParamTypes, []reflect.Type{}, false)
	invokeFn := reflect.MakeFunc(invokeFnT, func(args []reflect.Value) (results []reflect.Value) {
		lc := args[0].Interface().(Lifecycle)
		args = args[1:]
		hookArgs := make([]reflect.Value, origHookFnT.NumIn())

		hookFn := func(ctx context.Context) (err error) {
			// If the hook function has multiple parameters, and the first
			// parameter is a context, inject the provided context.
			if ctxStructPos < 0 {
				offset := 0
				for i := 0; i < len(hookArgs); i++ {
					if i == ctxPos {
						hookArgs[i] = reflect.ValueOf(ctx)
						offset = 1
						continue
					}
					if i-offset >= 0 && i-offset < len(args) {
						hookArgs[i] = args[i-offset]
					}
				}
			} else {
				for i := 0; i < origHookFnT.NumIn(); i++ {
					if i != ctxStructPos {
						hookArgs[i] = args[i]
						continue
					}
					t := origHookFnT.In(i)
					v := reflect.New(t).Elem()
					for j := 1; j < t.NumField(); j++ {
						if j < ctxPos {
							v.Field(j).Set(args[i].Field(j))
						} else if j == ctxPos {
							v.Field(j).Set(reflect.ValueOf(ctx))
						} else {
							v.Field(j).Set(args[i].Field(j - 1))
						}
					}
					hookArgs[i] = v
				}
			}
			hookResults := origHookFn.Call(hookArgs)
			if len(hookResults) > 0 && hookResults[0].Type() == _typeOfError {
				err, _ = hookResults[0].Interface().(error)
			}
			return err
		}
		lc.Append(la.buildHook(hookFn))
		return results
	})

	installerType := reflect.FuncOf(append(resultTypes, paramTypes...), []reflect.Type{_typeOfError}, false)
	hookInstaller = reflect.MakeFunc(installerType, func(args []reflect.Value) (results []reflect.Value) {
		// build a private scope for hook function
		var scope *dig.Scope
		switch la.Type {
		case _onStartHookType:
			scope = ann.container.Scope("onStartHookScope")
		case _onStopHookType:
			scope = ann.container.Scope("onStopHookScope")
		}

		// provide the private scope with the current dependencies and results of the annotated function
		results = []reflect.Value{_nilError}
		ctor := makeHookScopeCtor(paramTypes, resultTypes, args)
		if err := scope.Provide(ctor); err != nil {
			results[0] = reflect.ValueOf(fmt.Errorf("error providing possible parameters for hook installer: %w", err))
			return results
		}

		// invoking invokeFn appends the hook function to lifecycle
		if err := scope.Invoke(invokeFn.Interface()); err != nil {
			results[0] = reflect.ValueOf(fmt.Errorf("error invoking hook installer: %w", err))
			return results
		}
		return results
	})
	return hookInstaller, paramTypes, remapParams
}

var (
	_nameTag  = "name"
	_groupTag = "group"
)

// makeHookScopeCtor makes a constructor that provides all possible parameters
// that the lifecycle hook being appended can depend on. It also deduplicates
// duplicate param and result types, which is possible when using fx.Decorate,
// and uses values from results for providing the deduplicated types.
func makeHookScopeCtor(paramTypes []reflect.Type, resultTypes []reflect.Type, args []reflect.Value) interface{} {
	type key struct {
		t     reflect.Type
		name  string
		group string
	}
	seen := map[key]struct{}{}
	outTypes := make([]reflect.Type, len(resultTypes))
	for i, t := range resultTypes {
		outTypes[i] = t
		if isOut(t) {
			for j := 1; j < t.NumField(); j++ {
				field := t.Field(j)
				seen[key{
					t:     field.Type,
					name:  field.Tag.Get(_nameTag),
					group: field.Tag.Get(_groupTag),
				}] = struct{}{}
			}
			continue
		}
		seen[key{t: t}] = struct{}{}
	}

	fields := []reflect.StructField{_outAnnotationField}

	skippedParams := make([][]int, len(paramTypes))

	for i, t := range paramTypes {
		skippedParams[i] = []int{}
		if isIn(t) {
			for j := 1; j < t.NumField(); j++ {
				origField := t.Field(j)
				k := key{
					t:     origField.Type,
					name:  origField.Tag.Get(_nameTag),
					group: origField.Tag.Get(_groupTag),
				}

				if _, ok := seen[k]; ok {
					skippedParams[i] = append(skippedParams[i], j)
					continue
				}

				field := reflect.StructField{
					Name: fmt.Sprintf("Field%d", j-1),
					Type: origField.Type,
					Tag:  origField.Tag,
				}
				fields = append(fields, field)
			}
			continue
		}
		k := key{t: t}

		if _, ok := seen[k]; ok {
			skippedParams[i] = append(skippedParams[i], i)
			continue
		}
		field := reflect.StructField{
			Name: fmt.Sprintf("Field%d", i),
			Type: t,
		}
		fields = append(fields, field)
	}

	outTypes = append(outTypes, reflect.StructOf(fields))
	ctorType := reflect.FuncOf([]reflect.Type{}, outTypes, false)
	ctor := reflect.MakeFunc(ctorType, func(_ []reflect.Value) []reflect.Value {
		nOut := len(outTypes)
		results := make([]reflect.Value, nOut)
		for i := 0; i < nOut-1; i++ {
			results[i] = args[i]
		}

		v := reflect.New(outTypes[nOut-1]).Elem()
		fieldIdx := 1
		for i := nOut - 1; i < len(args); i++ {
			paramIdx := i - (nOut - 1)
			if isIn(paramTypes[paramIdx]) {
				skippedIdx := 0
				for j := 1; j < paramTypes[paramIdx].NumField(); j++ {
					if len(skippedParams[paramIdx]) > 0 && skippedParams[paramIdx][skippedIdx] == j {
						// skip
						skippedIdx++
						continue
					}
					v.Field(fieldIdx).Set(args[i].Field(j))
					fieldIdx++
				}
			} else {
				if len(skippedParams[paramIdx]) > 0 && skippedParams[paramIdx][0] == paramIdx {
					continue
				}
				v.Field(fieldIdx).Set(args[i])
				fieldIdx++
			}
		}
		results[nOut-1] = v

		return results
	})
	return ctor.Interface()
}

func injectLifecycle(paramTypes []reflect.Type) ([]reflect.Type, func([]reflect.Value) []reflect.Value) {
	// since lifecycle already exists in param types, no need to inject again
	if lifecycleExists(paramTypes) {
		return paramTypes, func(args []reflect.Value) []reflect.Value {
			return args
		}
	}
	// If params are tagged or there's an untagged variadic argument,
	// add a Lifecycle field to the param struct
	if len(paramTypes) > 0 && isIn(paramTypes[0]) {
		taggedParam := paramTypes[0]
		fields := []reflect.StructField{
			taggedParam.Field(0),
			{
				Name: "Lifecycle",
				Type: _typeOfLifecycle,
			},
		}
		for i := 1; i < taggedParam.NumField(); i++ {
			fields = append(fields, taggedParam.Field(i))
		}
		newParamType := reflect.StructOf(fields)
		return []reflect.Type{newParamType}, func(args []reflect.Value) []reflect.Value {
			param := args[0]
			args[0] = reflect.New(taggedParam).Elem()
			for i := 1; i < taggedParam.NumField(); i++ {
				args[0].Field(i).Set(param.Field(i + 1))
			}
			return args
		}
	}

	return append([]reflect.Type{_typeOfLifecycle}, paramTypes...), func(args []reflect.Value) []reflect.Value {
		return args[1:]
	}
}

func lifecycleExists(paramTypes []reflect.Type) bool {
	for _, t := range paramTypes {
		if t == _typeOfLifecycle {
			return true
		}
		if isIn(t) {
			for i := 1; i < t.NumField(); i++ {
				if t.Field(i).Type == _typeOfLifecycle {
					return true
				}
			}
		}
	}
	return false
}

func (la *lifecycleHookAnnotation) buildHook(fn func(context.Context) error) (hook Hook) {
	switch la.Type {
	case _onStartHookType:
		hook.OnStart = fn
	case _onStopHookType:
		hook.OnStop = fn
	}
	return hook
}

// OnStart is an Annotation that appends an OnStart Hook to the application
// Lifecycle when that function is called. This provides a way to create
// Lifecycle OnStart (see Lifecycle type documentation) hooks without building a
// function that takes a dependency on the Lifecycle type.
//
//	fx.Provide(
//		fx.Annotate(
//			NewServer,
//			fx.OnStart(func(ctx context.Context, server Server) error {
//				return server.Listen(ctx)
//			}),
//		)
//	)
//
// Which is functionally the same as:
//
//	 fx.Provide(
//	   func(lifecycle fx.Lifecycle, p Params) Server {
//	     server := NewServer(p)
//	     lifecycle.Append(fx.Hook{
//		      OnStart: func(ctx context.Context) error {
//			    return server.Listen(ctx)
//		      },
//	     })
//		 return server
//	   }
//	 )
//
// It is also possible to use OnStart annotation with other parameter and result
// annotations, provided that the parameter of the function passed to OnStart
// matches annotated parameters and results.
//
// For example, the following is possible:
//
//	fx.Provide(
//		fx.Annotate(
//			func (a A) B {...},
//			fx.ParamTags(`name:"A"`),
//			fx.ResultTags(`name:"B"`),
//			fx.OnStart(func (p OnStartParams) {...}),
//		),
//	)
//
// As long as OnStartParams looks like the following and has no other dependencies
// besides Context or Lifecycle:
//
//	type OnStartParams struct {
//		fx.In
//		FieldA A `name:"A"`
//		FieldB B `name:"B"`
//	}
//
// Only one OnStart annotation may be applied to a given function at a time,
// however functions may be annotated with other types of lifecycle Hooks, such
// as OnStop. The hook function passed into OnStart cannot take any arguments
// outside of the annotated constructor's existing dependencies or results, except
// a context.Context.
func OnStart(onStart interface{}) Annotation {
	return &lifecycleHookAnnotation{
		Type:   _onStartHookType,
		Target: onStart,
	}
}

// OnStop is an Annotation that appends an OnStop Hook to the application
// Lifecycle when that function is called. This provides a way to create
// Lifecycle OnStop (see Lifecycle type documentation) hooks without building a
// function that takes a dependency on the Lifecycle type.
//
//	fx.Provide(
//		fx.Annotate(
//			NewServer,
//			fx.OnStop(func(ctx context.Context, server Server) error {
//				return server.Shutdown(ctx)
//			}),
//		)
//	)
//
// Which is functionally the same as:
//
//	 fx.Provide(
//	   func(lifecycle fx.Lifecycle, p Params) Server {
//	     server := NewServer(p)
//	     lifecycle.Append(fx.Hook{
//		      OnStop: func(ctx context.Context) error {
//			    return server.Shutdown(ctx)
//		      },
//	     })
//		 return server
//	   }
//	 )
//
// It is also possible to use OnStop annotation with other parameter and result
// annotations, provided that the parameter of the function passed to OnStop
// matches annotated parameters and results.
//
// For example, the following is possible:
//
//	fx.Provide(
//		fx.Annotate(
//			func (a A) B {...},
//			fx.ParamTags(`name:"A"`),
//			fx.ResultTags(`name:"B"`),
//			fx.OnStop(func (p OnStopParams) {...}),
//		),
//	)
//
// As long as OnStopParams looks like the following and has no other dependencies
// besides Context or Lifecycle:
//
//	type OnStopParams struct {
//		fx.In
//		FieldA A `name:"A"`
//		FieldB B `name:"B"`
//	}
//
// Only one OnStop annotation may be applied to a given function at a time,
// however functions may be annotated with other types of lifecycle Hooks, such
// as OnStart. The hook function passed into OnStop cannot take any arguments
// outside of the annotated constructor's existing dependencies or results, except
// a context.Context.
func OnStop(onStop interface{}) Annotation {
	return &lifecycleHookAnnotation{
		Type:   _onStopHookType,
		Target: onStop,
	}
}

type asAnnotation struct {
	targets []interface{}
	types   []asType
}

type asType struct {
	self bool
	typ  reflect.Type // May be nil if self is true.
}

func (a asType) String() string {
	if a.self {
		return "self"
	}
	return a.typ.String()
}

func isOut(t reflect.Type) bool {
	return (t.Kind() == reflect.Struct &&
		dig.IsOut(reflect.New(t).Elem().Interface()))
}

func isIn(t reflect.Type) bool {
	return (t.Kind() == reflect.Struct &&
		dig.IsIn(reflect.New(t).Elem().Interface()))
}

var _ Annotation = (*asAnnotation)(nil)

// As is an Annotation that annotates the result of a function (i.e. a
// constructor) to be provided as another interface.
//
// For example, the following code specifies that the return type of
// bytes.NewBuffer (bytes.Buffer) should be provided as io.Writer type:
//
//	fx.Provide(
//	  fx.Annotate(bytes.NewBuffer, fx.As(new(io.Writer)))
//	)
//
// In other words, the code above is equivalent to:
//
//	fx.Provide(func() io.Writer {
//	  return bytes.NewBuffer()
//	  // provides io.Writer instead of *bytes.Buffer
//	})
//
// Note that the bytes.Buffer type is provided as an io.Writer type, so this
// constructor does NOT provide both bytes.Buffer and io.Writer type; it just
// provides io.Writer type.
//
// When multiple values are returned by the annotated function, each type
// gets mapped to corresponding positional result of the annotated function.
//
// For example,
//
//	func a() (bytes.Buffer, bytes.Buffer) {
//	  ...
//	}
//	fx.Provide(
//	  fx.Annotate(a, fx.As(new(io.Writer), new(io.Reader)))
//	)
//
// Is equivalent to,
//
//	fx.Provide(func() (io.Writer, io.Reader) {
//	  w, r := a()
//	  return w, r
//	}
//
// As entirely replaces the default return types of a function. In order
// to maintain the original return types when using As, see [Self].
//
// As annotation cannot be used in a function that returns an [Out] struct as a return type.
func As(interfaces ...interface{}) Annotation {
	return &asAnnotation{targets: interfaces}
}

// Self returns a special value that can be passed to [As] to indicate
// that a type should be provided as its original type, in addition to whatever other
// types it gets provided as via other [As] annotations.
//
// For example,
//
//	fx.Provide(
//	  fx.Annotate(
//	    bytes.NewBuffer,
//	    fx.As(new(io.Writer)),
//	    fx.As(fx.Self()),
//	  )
//	)
//
// Is equivalent to,
//
//	fx.Provide(
//	  bytes.NewBuffer,
//	  func(b *bytes.Buffer) io.Writer {
//	    return b
//	  },
//	)
//
// in that it provides the same *bytes.Buffer instance
// as both a *bytes.Buffer and an io.Writer.
func Self() any {
	return &self{}
}

type self struct{}

func (at *asAnnotation) apply(ann *annotated) error {
	at.types = make([]asType, len(at.targets))
	for i, typ := range at.targets {
		if _, ok := typ.(*self); ok {
			at.types[i] = asType{self: true}
			continue
		}
		t := reflect.TypeOf(typ)
		if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Interface {
			return fmt.Errorf("fx.As: argument must be a pointer to an interface: got %v", t)
		}
		t = t.Elem()
		at.types[i] = asType{typ: t}
	}

	ann.As = append(ann.As, at.types)
	return nil
}

// build implements Annotation
func (at *asAnnotation) build(ann *annotated) (interface{}, error) {
	paramTypes := ann.currentParamTypes()

	resultTypes, remapResults, err := at.results(ann)
	if err != nil {
		return nil, err
	}

	origFn := reflect.ValueOf(ann.Target)
	newFnType := reflect.FuncOf(paramTypes, resultTypes, false)
	newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
		results := origFn.Call(args)
		return remapResults(results)
	})
	return newFn.Interface(), nil
}

func (at *asAnnotation) results(ann *annotated) (
	types []reflect.Type,
	remap func([]reflect.Value) []reflect.Value,
	err error,
) {
	types, hasError := ann.currentResultTypes()
	fields := []reflect.StructField{_outAnnotationField}
	if hasError {
		types = types[:len(types)-1]
	}
	resultFields, getResult := extractResultFields(types)

	for i, f := range resultFields {
		t := f.Type
		field := reflect.StructField{
			Name: fmt.Sprintf("Field%d", i),
			Type: t,
			Tag:  f.Tag,
		}

		if i >= len(at.types) || at.types[i].self {
			fields = append(fields, field)
			continue
		}

		if !t.Implements(at.types[i].typ) {
			return nil, nil, fmt.Errorf("invalid fx.As: %v does not implement %v", t, at.types[i])
		}
		field.Type = at.types[i].typ
		fields = append(fields, field)
	}
	resType := reflect.StructOf(fields)

	var outTypes []reflect.Type
	outTypes = append(types, resType)
	if hasError {
		outTypes = append(outTypes, _typeOfError)
	}

	return outTypes, func(results []reflect.Value) []reflect.Value {
		var (
			outErr     error
			outResults []reflect.Value
		)

		for i, r := range results {
			if i == len(results)-1 && hasError {
				// If hasError and this is the last item,
				// we are guaranteed that this is an error
				// object.
				if err, _ := r.Interface().(error); err != nil {
					outErr = err
				}
				continue
			}
			outResults = append(outResults, r)
		}

		newOutResult := reflect.New(resType).Elem()
		for i := 1; i < resType.NumField(); i++ {
			newOutResult.Field(i).Set(getResult(i, results))
		}
		outResults = append(outResults, newOutResult)

		if hasError {
			if outErr != nil {
				outResults = append(outResults, reflect.ValueOf(outErr))
			} else {
				outResults = append(outResults, _nilError)
			}
		}

		return outResults
	}, nil
}

func extractResultFields(types []reflect.Type) ([]reflect.StructField, func(int, []reflect.Value) reflect.Value) {
	var resultFields []reflect.StructField
	if len(types) > 0 && isOut(types[0]) {
		for i := 1; i < types[0].NumField(); i++ {
			resultFields = append(resultFields, types[0].Field(i))
		}
		return resultFields, func(idx int, results []reflect.Value) reflect.Value {
			return results[0].Field(idx)
		}
	}
	for i, t := range types {
		if isOut(t) {
			continue
		}
		field := reflect.StructField{
			Name: fmt.Sprintf("Field%d", i),
			Type: t,
		}
		resultFields = append(resultFields, field)
	}
	return resultFields, func(idx int, results []reflect.Value) reflect.Value {
		return results[idx-1]
	}
}

type fromAnnotation struct {
	targets []interface{}
	types   []reflect.Type
}

var _ Annotation = (*fromAnnotation)(nil)

// From is an [Annotation] that annotates the parameter(s) for a function (i.e. a
// constructor) to be accepted from other provided types. It is analogous to the
// [As] for parameter types to the constructor.
//
// For example,
//
//	type Runner interface { Run() }
//	func NewFooRunner() *FooRunner // implements Runner
//	func NewRunnerWrap(r Runner) *RunnerWrap
//
//	fx.Provide(
//	  fx.Annotate(
//	    NewRunnerWrap,
//	    fx.From(new(*FooRunner)),
//	  ),
//	)
//
// Is equivalent to,
//
//	fx.Provide(func(r *FooRunner) *RunnerWrap {
//	  // need *FooRunner instead of Runner
//	  return NewRunnerWrap(r)
//	})
//
// When the annotated function takes in multiple parameters, each type gets
// mapped to corresponding positional parameter of the annotated function
//
// For example,
//
//	func NewBarRunner() *BarRunner // implements Runner
//	func NewRunnerWraps(r1 Runner, r2 Runner) *RunnerWraps
//
//	fx.Provide(
//	  fx.Annotate(
//	    NewRunnerWraps,
//	    fx.From(new(*FooRunner), new(*BarRunner)),
//	  ),
//	)
//
// Is equivalent to,
//
//	fx.Provide(func(r1 *FooRunner, r2 *BarRunner) *RunnerWraps {
//	  return NewRunnerWraps(r1, r2)
//	})
//
// From annotation cannot be used in a function that takes an [In] struct as a
// parameter.
func From(interfaces ...interface{}) Annotation {
	return &fromAnnotation{targets: interfaces}
}

func (fr *fromAnnotation) apply(ann *annotated) error {
	if len(ann.From) > 0 {
		return errors.New("cannot apply more than one line of From")
	}
	ft := reflect.TypeOf(ann.Target)
	fr.types = make([]reflect.Type, len(fr.targets))
	for i, typ := range fr.targets {
		if ft.IsVariadic() && i == ft.NumIn()-1 {
			return errors.New("fx.From: cannot annotate a variadic argument")
		}
		t := reflect.TypeOf(typ)
		if t == nil || t.Kind() != reflect.Ptr {
			return fmt.Errorf("fx.From: argument must be a pointer to a type that implements some interface: got %v", t)
		}
		fr.types[i] = t.Elem()
	}
	ann.From = fr.types
	return nil
}

// build builds and returns a constructor after applying a From annotation
func (fr *fromAnnotation) build(ann *annotated) (interface{}, error) {
	paramTypes, remap, err := fr.parameters(ann)
	if err != nil {
		return nil, err
	}
	resultTypes, _ := ann.currentResultTypes()

	origFn := reflect.ValueOf(ann.Target)
	newFnType := reflect.FuncOf(paramTypes, resultTypes, false)
	newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
		args = remap(args)
		return origFn.Call(args)
	})
	return newFn.Interface(), nil
}

// parameters returns the type for the parameters of the annotated function,
// and a function that maps the arguments of the annotated function
// back to the arguments of the target function.
func (fr *fromAnnotation) parameters(ann *annotated) (
	types []reflect.Type,
	remap func([]reflect.Value) []reflect.Value,
	err error,
) {
	ft := reflect.TypeOf(ann.Target)
	types = make([]reflect.Type, ft.NumIn())
	for i := 0; i < ft.NumIn(); i++ {
		types[i] = ft.In(i)
	}

	// No parameter annotations. Return the original types
	// and an identity function.
	if len(fr.targets) == 0 {
		return types, func(args []reflect.Value) []reflect.Value {
			return args
		}, nil
	}

	// Turn parameters into an fx.In struct.
	inFields := []reflect.StructField{_inAnnotationField}

	// The following situations may occur:
	// 1. there was a variadic argument, so it was pre-transformed.
	// 2. another parameter annotation was transformed (ex: ParamTags).
	// so need to visit fields of the fx.In struct.
	if len(types) > 0 && isIn(types[0]) {
		paramType := types[0]

		for i := 1; i < paramType.NumField(); i++ {
			origField := paramType.Field(i)
			field := reflect.StructField{
				Name: origField.Name,
				Type: origField.Type,
				Tag:  origField.Tag,
			}
			if i-1 < len(fr.types) {
				t := fr.types[i-1]
				if !t.Implements(field.Type) {
					return nil, nil, fmt.Errorf("invalid fx.From: %v does not implement %v", t, field.Type)
				}
				field.Type = t
			}

			inFields = append(inFields, field)
		}

		types = []reflect.Type{reflect.StructOf(inFields)}
		return types, func(args []reflect.Value) []reflect.Value {
			param := args[0]
			args[0] = reflect.New(paramType).Elem()
			for i := 1; i < paramType.NumField(); i++ {
				args[0].Field(i).Set(param.Field(i))
			}
			return args
		}, nil
	}

	for i, t := range types {
		field := reflect.StructField{
			Name: fmt.Sprintf("Field%d", i),
			Type: t,
		}
		if i < len(fr.types) {
			t := fr.types[i]
			if !t.Implements(field.Type) {
				return nil, nil, fmt.Errorf("invalid fx.From: %v does not implement %v", t, field.Type)
			}
			field.Type = t
		}

		inFields = append(inFields, field)
	}

	types = []reflect.Type{reflect.StructOf(inFields)}
	return types, func(args []reflect.Value) []reflect.Value {
		params := args[0]
		args = args[:0]
		for i := 0; i < ft.NumIn(); i++ {
			args = append(args, params.Field(i+1))
		}
		return args
	}, nil
}

type annotated struct {
	Target      interface{}
	Annotations []Annotation
	ParamTags   []string
	ResultTags  []string
	As          [][]asType
	From        []reflect.Type
	FuncPtr     uintptr
	Hooks       []*lifecycleHookAnnotation
	// container is used to build private scopes for lifecycle hook functions
	// added via fx.OnStart and fx.OnStop annotations.
	container *dig.Container
}

func (ann annotated) String() string {
	var sb strings.Builder
	sb.WriteString("fx.Annotate(")
	sb.WriteString(fxreflect.FuncName(ann.Target))
	if tags := ann.ParamTags; len(tags) > 0 {
		fmt.Fprintf(&sb, ", fx.ParamTags(%q)", tags)
	}
	if tags := ann.ResultTags; len(tags) > 0 {
		fmt.Fprintf(&sb, ", fx.ResultTags(%q)", tags)
	}
	if as := ann.As; len(as) > 0 {
		fmt.Fprintf(&sb, ", fx.As(%v)", as)
	}
	if from := ann.From; len(from) > 0 {
		fmt.Fprintf(&sb, ", fx.From(%v)", from)
	}
	return sb.String()
}

// Build builds and returns a constructor based on fx.In/fx.Out params and
// results wrapping the original constructor passed to fx.Annotate.
func (ann *annotated) Build() (interface{}, error) {
	ann.container = dig.New()
	ft := reflect.TypeOf(ann.Target)
	if ft.Kind() != reflect.Func {
		return nil, fmt.Errorf("must provide constructor function, got %v (%T)", ann.Target, ann.Target)
	}

	if err := ann.typeCheckOrigFn(); err != nil {
		return nil, fmt.Errorf("invalid annotation function %T: %w", ann.Target, err)
	}

	ann.applyOptionalTag()

	var (
		err        error
		lcHookAnns []*lifecycleHookAnnotation
	)
	for _, annotation := range ann.Annotations {
		if lcHookAnn, ok := annotation.(*lifecycleHookAnnotation); ok {
			lcHookAnns = append(lcHookAnns, lcHookAnn)
			continue
		}
		if ann.Target, err = annotation.build(ann); err != nil {
			return nil, err
		}
	}

	// need to call cleanUpAsResults before applying lifecycle annotations
	// to exclude the original results from the hook's scope if any
	// fx.As annotations were applied
	ann.cleanUpAsResults()

	for _, la := range lcHookAnns {
		if ann.Target, err = la.build(ann); err != nil {
			return nil, err
		}
	}
	return ann.Target, nil
}

// applyOptionalTag checks if function being annotated is variadic
// and applies optional tag to the variadic argument before
// applying any other annotations
func (ann *annotated) applyOptionalTag() {
	ft := reflect.TypeOf(ann.Target)
	if !ft.IsVariadic() {
		return
	}

	resultTypes, _ := ann.currentResultTypes()

	fields := []reflect.StructField{_inAnnotationField}
	for i := 0; i < ft.NumIn(); i++ {
		field := reflect.StructField{
			Name: fmt.Sprintf("Field%d", i),
			Type: ft.In(i),
		}
		if i == ft.NumIn()-1 {
			// Mark a variadic argument optional by default
			// so that just wrapping a function in fx.Annotate does not
			// suddenly introduce a required []arg dependency.
			field.Tag = reflect.StructTag(`optional:"true"`)
		}
		fields = append(fields, field)
	}
	paramType := reflect.StructOf(fields)
	origFn := reflect.ValueOf(ann.Target)
	newFnType := reflect.FuncOf([]reflect.Type{paramType}, resultTypes, false)
	newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
		params := args[0]
		args = args[:0]
		for i := 0; i < ft.NumIn(); i++ {
			args = append(args, params.Field(i+1))
		}
		return origFn.CallSlice(args)
	})
	ann.Target = newFn.Interface()
}

// cleanUpAsResults does a check to see if an As annotation was applied.
// If there was any fx.As annotation applied, cleanUpAsResults wraps the
// function one more time to remove the results from the original function.
func (ann *annotated) cleanUpAsResults() {
	// clean up orig function results if there were any As annotations
	if len(ann.As) < 1 {
		return
	}
	paramTypes := ann.currentParamTypes()
	resultTypes, hasError := ann.currentResultTypes()
	numRes := len(ann.As)
	if hasError {
		numRes++
	}
	newResultTypes := resultTypes[len(resultTypes)-numRes:]
	origFn := reflect.ValueOf(ann.Target)
	newFnType := reflect.FuncOf(paramTypes, newResultTypes, false)
	newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) (results []reflect.Value) {
		results = origFn.Call(args)
		results = results[len(results)-numRes:]
		return
	})
	ann.Target = newFn.Interface()
}

// checks and returns a non-nil error if the target function:
// - returns an fx.Out struct as a result and has either a ResultTags or an As annotation
// - takes in an fx.In struct as a parameter and has either a ParamTags or a From annotation
// - has an error result not as the last result.
func (ann *annotated) typeCheckOrigFn() error {
	ft := reflect.TypeOf(ann.Target)
	numOut := ft.NumOut()
	for i := 0; i < numOut; i++ {
		ot := ft.Out(i)
		if ot == _typeOfError && i != numOut-1 {
			return fmt.Errorf(
				"only the last result can be an error: "+
					"%v (%v) returns error as result %d",
				fxreflect.FuncName(ann.Target), ft, i)
		}
		if ot.Kind() != reflect.Struct {
			continue
		}
		if !dig.IsOut(reflect.New(ft.Out(i)).Elem().Interface()) {
			continue
		}
		if len(ann.ResultTags) > 0 || len(ann.As) > 0 {
			return errors.New("fx.Out structs cannot be annotated with fx.ResultTags or fx.As")
		}
	}
	for i := 0; i < ft.NumIn(); i++ {
		it := ft.In(i)
		if it.Kind() != reflect.Struct {
			continue
		}
		if !dig.IsIn(reflect.New(ft.In(i)).Elem().Interface()) {
			continue
		}
		if len(ann.ParamTags) > 0 || len(ann.From) > 0 {
			return errors.New("fx.In structs cannot be annotated with fx.ParamTags or fx.From")
		}
	}
	return nil
}

func (ann *annotated) currentResultTypes() (resultTypes []reflect.Type, hasError bool) {
	ft := reflect.TypeOf(ann.Target)
	numOut := ft.NumOut()
	resultTypes = make([]reflect.Type, numOut)

	for i := 0; i < numOut; i++ {
		resultTypes[i] = ft.Out(i)
		if resultTypes[i] == _typeOfError && i == numOut-1 {
			hasError = true
		}
	}
	return resultTypes, hasError
}

func (ann *annotated) currentParamTypes() []reflect.Type {
	ft := reflect.TypeOf(ann.Target)
	paramTypes := make([]reflect.Type, ft.NumIn())

	for i := 0; i < ft.NumIn(); i++ {
		paramTypes[i] = ft.In(i)
	}
	return paramTypes
}

// Annotate lets you annotate a function's parameters and returns
// without you having to declare separate struct definitions for them.
//
// For example,
//
//	func NewGateway(ro, rw *db.Conn) *Gateway { ... }
//	fx.Provide(
//	  fx.Annotate(
//	    NewGateway,
//	    fx.ParamTags(`name:"ro" optional:"true"`, `name:"rw"`),
//	    fx.ResultTags(`name:"foo"`),
//	  ),
//	)
//
// Is equivalent to,
//
//	type params struct {
//	  fx.In
//
//	  RO *db.Conn `name:"ro" optional:"true"`
//	  RW *db.Conn `name:"rw"`
//	}
//
//	type result struct {
//	  fx.Out
//
//	  GW *Gateway `name:"foo"`
//	}
//
//	fx.Provide(func(p params) result {
//	   return result{GW: NewGateway(p.RO, p.RW)}
//	})
//
// Using the same annotation multiple times is invalid.
// For example, the following will fail with an error:
//
//	fx.Provide(
//	  fx.Annotate(
//	    NewGateWay,
//	    fx.ParamTags(`name:"ro" optional:"true"`),
//	    fx.ParamTags(`name:"rw"), // ERROR: ParamTags was already used above
//	    fx.ResultTags(`name:"foo"`)
//	  )
//	)
//
// If more tags are given than the number of parameters/results, only
// the ones up to the number of parameters/results will be applied.
//
// # Variadic functions
//
// If the provided function is variadic, Annotate treats its parameter as a
// slice. For example,
//
//	fx.Annotate(func(w io.Writer, rs ...io.Reader) {
//	  // ...
//	}, ...)
//
// Is equivalent to,
//
//	fx.Annotate(func(w io.Writer, rs []io.Reader) {
//	  // ...
//	}, ...)
//
// You can use variadic parameters with Fx's value groups.
// For example,
//
//	fx.Annotate(func(mux *http.ServeMux, handlers ...http.Handler) {
//	  // ...
//	}, fx.ParamTags(``, `group:"server"`))
//
// If we provide the above to the application,
// any constructor in the Fx application can inject its HTTP handlers
// by using [Annotate], [Annotated], or [Out].
//
//	fx.Annotate(
//	  func(..) http.Handler { ... },
//	  fx.ResultTags(`group:"server"`),
//	)
//
//	fx.Annotated{
//	  Target: func(..) http.Handler { ... },
//	  Group:  "server",
//	}
func Annotate(t interface{}, anns ...Annotation) interface{} {
	result := annotated{Target: t}
	for _, ann := range anns {
		if err := ann.apply(&result); err != nil {
			return annotationError{
				target: t,
				err:    err,
			}
		}
	}
	result.Annotations = anns
	return result
}
