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

	
	
	
	
)

// A container represents a set of constructors to provide
// dependencies, and a set of functions to invoke once all the
// dependencies have been initialized.
//
// This definition corresponds to the dig.Container and dig.Scope.
type container interface {
	Invoke(interface{}, ...dig.InvokeOption) error
	Provide(interface{}, ...dig.ProvideOption) error
	Decorate(interface{}, ...dig.DecorateOption) error
}

// Module is a named group of zero or more fx.Options.
//
// A Module scopes the effect of certain operations to within the module.
// For more information, see [Decorate], [Replace], or [Invoke].
//
// Module allows packages to bundle sophisticated functionality into easy-to-use
// logical units.
// For example, a logging package might export a simple option like this:
//
//	package logging
//
//	var Module = fx.Module("logging",
//		fx.Provide(func() *log.Logger {
//			return log.New(os.Stdout, "", 0)
//		}),
//		// ...
//	)
//
// A shared all-in-one microservice package could use Module to bundle
// all required components of a microservice:
//
//	package server
//
//	var Module = fx.Module("server",
//		logging.Module,
//		metrics.Module,
//		tracing.Module,
//		rpc.Module,
//	)
//
// When new global functionality is added to the service ecosystem,
// it can be added to the shared module with minimal churn for users.
//
// Use the all-in-one pattern sparingly.
// It limits the flexibility available to the application.
func ( string,  ...Option) Option {
	 := moduleOption{
		name:     ,
		location: fxreflect.CallerStack(1, 2)[0],
		options:  ,
	}
	return 
}

type moduleOption struct {
	name     string
	location fxreflect.Frame
	options  []Option
}

func ( moduleOption) () string {
	return fmt.Sprintf("fx.Module(%q, %v)", .name, .options)
}

func ( moduleOption) ( *module) {
	// This get called on any submodules' that are declared
	// as part of another module.

	// 1. Create a new module with the parent being the specified
	// module.
	// 2. Apply child Options on the new module.
	// 3. Append it to the parent module.

	// Create trace as parent's trace with this module's location pre-pended.
	 := append([]string{fmt.Sprintf("%v (%v)", .location, .name)}, .trace...)
	 := &module{
		name:   .name,
		parent: ,
		trace:  ,
		app:    .app,
	}
	for ,  := range .options {
		.apply()
	}
	.modules = append(.modules, )
}

type module struct {
	parent         *module
	name           string
	trace          []string
	scope          scope
	provides       []provide
	invokes        []invoke
	decorators     []decorator
	modules        []*module
	app            *App
	log            fxevent.Logger
	fallbackLogger fxevent.Logger
	logConstructor *provide
}

// scope is a private wrapper interface for dig.Container and dig.Scope.
// We can consider moving this into Fx using type constraints after Go 1.20
// is released and 1.17 is deprecated.
type scope interface {
	Decorate(f interface{}, opts ...dig.DecorateOption) error
	Invoke(f interface{}, opts ...dig.InvokeOption) error
	Provide(f interface{}, opts ...dig.ProvideOption) error
	Scope(name string, opts ...dig.ScopeOption) *dig.Scope
	String() string
}

// builds the Scopes using the App's Container. Note that this happens
// after applyModules' are called because the App's Container needs to
// be built for any Scopes to be initialized, and applys' should be called
// before the Container can get initialized.
func ( *module) ( *App,  *dig.Container) {
	if .parent == nil {
		.scope = 
	} else {
		 := .parent.scope
		.scope = .Scope(.name)
		// use parent module's logger by default
		.log = .parent.log
	}

	if .logConstructor != nil {
		// Since user supplied a custom logger, use a buffered logger
		// to hold all messages until user supplied logger is
		// instantiated. Then we flush those messages after fully
		// constructing the custom logger.
		.fallbackLogger, .log = .log, new(logBuffer)
	}

	for ,  := range .modules {
		.(, )
	}
}

func ( *module) () {
	for ,  := range .provides {
		.provide()
	}

	for ,  := range .modules {
		.()
	}
}

func ( *module) ( provide) {
	if .app.err != nil {
		return
	}

	if .IsSupply {
		.supply()
		return
	}

	 := fxreflect.FuncName(.Target)
	var  dig.ProvideInfo
	 := []dig.ProvideOption{
		dig.FillProvideInfo(&),
		dig.Export(!.Private),
		dig.WithProviderBeforeCallback(func( dig.BeforeCallbackInfo) {
			.log.LogEvent(&fxevent.BeforeRun{
				Name:       ,
				Kind:       "provide",
				ModuleName: .name,
			})
		}),
		dig.WithProviderCallback(func( dig.CallbackInfo) {
			.log.LogEvent(&fxevent.Run{
				Name:       ,
				Kind:       "provide",
				ModuleName: .name,
				Runtime:    .Runtime,
				Err:        .Error,
			})
		}),
	}

	if  := runProvide(.scope, , ...);  != nil {
		.app.err = 
	}
	 := make([]string, len(.Outputs))
	for ,  := range .Outputs {
		[] = .String()
	}

	.log.LogEvent(&fxevent.Provided{
		ConstructorName: ,
		StackTrace:      .Stack.Strings(),
		ModuleTrace:     append([]string{.Stack[0].String()}, .trace...),
		ModuleName:      .name,
		OutputTypeNames: ,
		Err:             .app.err,
		Private:         .Private,
	})
}

func ( *module) ( provide) {
	 := .SupplyType.String()
	 := []dig.ProvideOption{
		dig.Export(!.Private),
		dig.WithProviderBeforeCallback(func( dig.BeforeCallbackInfo) {
			.log.LogEvent(&fxevent.BeforeRun{
				Name:       fmt.Sprintf("stub(%v)", ),
				Kind:       "supply",
				ModuleName: .name,
			})
		}),
		dig.WithProviderCallback(func( dig.CallbackInfo) {
			.log.LogEvent(&fxevent.Run{
				Name:       fmt.Sprintf("stub(%v)", ),
				Kind:       "supply",
				Runtime:    .Runtime,
				ModuleName: .name,
			})
		}),
	}

	if  := runProvide(.scope, , ...);  != nil {
		.app.err = 
	}

	.log.LogEvent(&fxevent.Supplied{
		TypeName:    ,
		StackTrace:  .Stack.Strings(),
		ModuleTrace: append([]string{.Stack[0].String()}, .trace...),
		ModuleName:  .name,
		Err:         .app.err,
	})
}

// Constructs custom loggers for all modules in the tree
func ( *module) () {
	if .logConstructor != nil {
		if ,  := .log.(*logBuffer);  {
			// default to parent's logger if custom logger constructor fails
			if  := .installEventLogger();  != nil {
				.app.err = multierr.Append(.app.err, )
				.log = .fallbackLogger
				.Connect(.log)
			}
		}
		.fallbackLogger = nil
	} else if .parent != nil {
		.log = .parent.log
	}

	for ,  := range .modules {
		.()
	}
}

func ( *module) ( *logBuffer) ( error) {
	 := .logConstructor
	 := fxreflect.FuncName(.Target)
	defer func() {
		.log.LogEvent(&fxevent.LoggerInitialized{
			Err:             ,
			ConstructorName: ,
		})
	}()

	// TODO: Use dig.FillProvideInfo to inspect the provided constructor
	// and fail the application if its signature didn't match.
	if  := .scope.Provide(.Target);  != nil {
		return fmt.Errorf("fx.WithLogger(%v) from:\n%+v\nin Module: %q\nFailed: %w",
			, .Stack, .name, )
	}

	return .scope.Invoke(func( fxevent.Logger) {
		.log = 
		.Connect()
	})
}

func ( *module) () error {
	for ,  := range .modules {
		if  := .();  != nil {
			return 
		}
	}

	for ,  := range .invokes {
		if  := .invoke();  != nil {
			return 
		}
	}

	return nil
}

func ( *module) ( invoke) ( error) {
	 := fxreflect.FuncName(.Target)
	.log.LogEvent(&fxevent.Invoking{
		FunctionName: ,
		ModuleName:   .name,
	})
	 = runInvoke(.scope, )
	.log.LogEvent(&fxevent.Invoked{
		FunctionName: ,
		ModuleName:   .name,
		Err:          ,
		Trace:        fmt.Sprintf("%+v", .Stack), // format stack trace as multi-line
	})
	return 
}

func ( *module) () error {
	for ,  := range .decorators {
		if  := .decorate();  != nil {
			return 
		}
	}

	for ,  := range .modules {
		if  := .();  != nil {
			return 
		}
	}
	return nil
}

func ( *module) ( decorator) ( error) {
	if .IsReplace {
		return .replace()
	}

	 := fxreflect.FuncName(.Target)
	var  dig.DecorateInfo
	 := []dig.DecorateOption{
		dig.FillDecorateInfo(&),
		dig.WithDecoratorBeforeCallback(func( dig.BeforeCallbackInfo) {
			.log.LogEvent(&fxevent.BeforeRun{
				Name:       ,
				Kind:       "decorate",
				ModuleName: .name,
			})
		}),
		dig.WithDecoratorCallback(func( dig.CallbackInfo) {
			.log.LogEvent(&fxevent.Run{
				Name:       ,
				Kind:       "decorate",
				ModuleName: .name,
				Runtime:    .Runtime,
				Err:        .Error,
			})
		}),
	}

	 = runDecorator(.scope, , ...)
	 := make([]string, len(.Outputs))
	for ,  := range .Outputs {
		[] = .String()
	}

	.log.LogEvent(&fxevent.Decorated{
		DecoratorName:   ,
		StackTrace:      .Stack.Strings(),
		ModuleTrace:     append([]string{.Stack[0].String()}, .trace...),
		ModuleName:      .name,
		OutputTypeNames: ,
		Err:             ,
	})

	return 
}

func ( *module) ( decorator) error {
	 := .ReplaceType.String()
	 := []dig.DecorateOption{
		dig.WithDecoratorBeforeCallback(func( dig.BeforeCallbackInfo) {
			.log.LogEvent(&fxevent.BeforeRun{
				Name:       fmt.Sprintf("stub(%v)", ),
				Kind:       "replace",
				ModuleName: .name,
			})
		}),
		dig.WithDecoratorCallback(func( dig.CallbackInfo) {
			.log.LogEvent(&fxevent.Run{
				Name:       fmt.Sprintf("stub(%v)", ),
				Kind:       "replace",
				ModuleName: .name,
				Runtime:    .Runtime,
				Err:        .Error,
			})
		}),
	}

	 := runDecorator(.scope, , ...)
	.log.LogEvent(&fxevent.Replaced{
		ModuleName:      .name,
		StackTrace:      .Stack.Strings(),
		ModuleTrace:     append([]string{.Stack[0].String()}, .trace...),
		OutputTypeNames: []string{},
		Err:             ,
	})
	return 
}