// Copyright (c) 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 dig

import (
	
	

	
	
)

// An InvokeOption modifies the default behavior of Invoke.
type InvokeOption interface {
	applyInvokeOption(*invokeOptions)
}

type invokeOptions struct {
	Info *InvokeInfo
}

// InvokeInfo provides information about an Invoke.
type InvokeInfo struct {
	Inputs []*Input
}

// FillInvokeInfo is an InvokeOption that writes information on the types
// accepted by the Invoke function into the specified InvokeInfo.
// For example:
//
//			var info dig.InvokeInfo
//			err := c.Invoke(func(string, int){}, dig.FillInvokeInfo(&info))
//
//	  info.Inputs[0].String() will be string.
//	  info.Inputs[1].String() will be int.
func ( *InvokeInfo) InvokeOption {
	return fillInvokeInfoOption{info: }
}

type fillInvokeInfoOption struct {
	info *InvokeInfo
}

func ( fillInvokeInfoOption) () string {
	return fmt.Sprintf("FillInvokeInfo(%p)", .info)
}

func ( fillInvokeInfoOption) ( *invokeOptions) {
	.Info = .info
}

// Invoke runs the given function after instantiating its dependencies.
//
// Any arguments that the function has are treated as its dependencies. The
// dependencies are instantiated in an unspecified order along with any
// dependencies that they might have.
//
// The function may return an error to indicate failure. The error will be
// returned to the caller as-is.
//
// If the [RecoverFromPanics] option was given to the container and a panic
// occurs when invoking, a [PanicError] with the panic contained will be
// returned. See [PanicError] for more info.
func ( *Container) ( interface{},  ...InvokeOption) error {
	return .scope.Invoke(, ...)
}

// Invoke runs the given function after instantiating its dependencies.
//
// Any arguments that the function has are treated as its dependencies. The
// dependencies are instantiated in an unspecified order along with any
// dependencies that they might have.
//
// The function may return an error to indicate failure. The error will be
// returned to the caller as-is.
func ( *Scope) ( interface{},  ...InvokeOption) ( error) {
	 := reflect.TypeOf()
	if  == nil {
		return newErrInvalidInput("can't invoke an untyped nil", nil)
	}
	if .Kind() != reflect.Func {
		return newErrInvalidInput(
			fmt.Sprintf("can't invoke non-function %v (type %v)", , ), nil)
	}

	,  := newParamList(, )
	if  != nil {
		return 
	}

	if  := shallowCheckDependencies(, );  != nil {
		return errMissingDependencies{
			Func:   digreflect.InspectFunc(),
			Reason: ,
		}
	}

	if !.isVerifiedAcyclic {
		if ,  := graph.IsAcyclic(.gh); ! {
			return newErrInvalidInput("cycle detected in dependency graph", .cycleDetectedError())
		}
		.isVerifiedAcyclic = true
	}

	,  := .BuildList()
	if  != nil {
		return errArgumentsFailed{
			Func:   digreflect.InspectFunc(),
			Reason: ,
		}
	}
	if .recoverFromPanics {
		defer func() {
			if  := recover();  != nil {
				 = PanicError{
					fn:    digreflect.InspectFunc(),
					Panic: ,
				}
			}
		}()
	}

	var  invokeOptions
	for ,  := range  {
		.applyInvokeOption(&)
	}

	// Record info for the invoke if requested
	if  := .Info;  != nil {
		 := .DotParam()
		.Inputs = make([]*Input, len())
		for ,  := range  {
			.Inputs[] = &Input{
				t:        .Type,
				optional: .Optional,
				name:     .Name,
				group:    .Group,
			}
		}

	}

	 := .invokerFn(reflect.ValueOf(), )
	if len() == 0 {
		return nil
	}
	if  := [len()-1]; isError(.Type()) {
		if ,  := .Interface().(error);  != nil {
			return 
		}
	}

	return nil
}

// Checks that all direct dependencies of the provided parameters are present in
// the container. Returns an error if not.
func shallowCheckDependencies( containerStore,  paramList) error {
	var  errMissingTypes

	 := findMissingDependencies(, .Params...)
	for ,  := range  {
		 = append(, newErrMissingTypes(, key{name: .Name, t: .Type})...)
	}

	if len() > 0 {
		return 
	}
	return nil
}

func findMissingDependencies( containerStore,  ...param) []paramSingle {
	var  []paramSingle

	for ,  := range  {
		switch p := .(type) {
		case paramSingle:
			 := .getAllValueProviders(.Name, .Type)
			,  := .getDecoratedValue(.Name, .Type)
			// This means that there is no provider that provides this value,
			// and it is NOT being decorated and is NOT optional.
			// In the case that there is no providers but there is a decorated value
			// of this type, it can be provided safely so we can safely skip this.
			if len() == 0 && ! && !.Optional {
				 = append(, )
			}
		case paramObject:
			for ,  := range .Fields {
				 = append(, (, .Param)...)
			}
		}
	}
	return 
}