package gorm

import (
	
	
	
	
	
	

	
	
)

func initializeCallbacks( *DB) *callbacks {
	return &callbacks{
		processors: map[string]*processor{
			"create": {db: },
			"query":  {db: },
			"update": {db: },
			"delete": {db: },
			"row":    {db: },
			"raw":    {db: },
		},
	}
}

// callbacks gorm callbacks manager
type callbacks struct {
	processors map[string]*processor
}

type processor struct {
	db        *DB
	Clauses   []string
	fns       []func(*DB)
	callbacks []*callback
}

type callback struct {
	name      string
	before    string
	after     string
	remove    bool
	replace   bool
	match     func(*DB) bool
	handler   func(*DB)
	processor *processor
}

func ( *callbacks) () *processor {
	return .processors["create"]
}

func ( *callbacks) () *processor {
	return .processors["query"]
}

func ( *callbacks) () *processor {
	return .processors["update"]
}

func ( *callbacks) () *processor {
	return .processors["delete"]
}

func ( *callbacks) () *processor {
	return .processors["row"]
}

func ( *callbacks) () *processor {
	return .processors["raw"]
}

func ( *processor) ( *DB) *DB {
	// call scopes
	for len(.Statement.scopes) > 0 {
		 = .executeScopes()
	}

	var (
		           = time.Now()
		              = .Statement
		 bool
	)

	if len(.BuildClauses) == 0 {
		.BuildClauses = .Clauses
		 = true
	}

	if ,  := .Statement.Dest.(StatementModifier);  {
		.ModifyStatement()
	}

	// assign model values
	if .Model == nil {
		.Model = .Dest
	} else if .Dest == nil {
		.Dest = .Model
	}

	// parse model values
	if .Model != nil {
		if  := .Parse(.Model);  != nil && (!errors.Is(, schema.ErrUnsupportedDataType) || (.Table == "" && .TableExpr == nil && .SQL.Len() == 0)) {
			if errors.Is(, schema.ErrUnsupportedDataType) && .Table == "" && .TableExpr == nil {
				.AddError(fmt.Errorf("%w: Table not set, please set it like: db.Model(&user) or db.Table(\"users\")", ))
			} else {
				.AddError()
			}
		}
	}

	// assign stmt.ReflectValue
	if .Dest != nil {
		.ReflectValue = reflect.ValueOf(.Dest)
		for .ReflectValue.Kind() == reflect.Ptr {
			if .ReflectValue.IsNil() && .ReflectValue.CanAddr() {
				.ReflectValue.Set(reflect.New(.ReflectValue.Type().Elem()))
			}

			.ReflectValue = .ReflectValue.Elem()
		}
		if !.ReflectValue.IsValid() {
			.AddError(ErrInvalidValue)
		}
	}

	for ,  := range .fns {
		()
	}

	if .SQL.Len() > 0 {
		.Logger.Trace(.Context, , func() (string, int64) {
			,  := .SQL.String(), .Vars
			if ,  := .Logger.(ParamsFilter);  {
				,  = .ParamsFilter(.Context, .SQL.String(), .Vars...)
			}
			return .Dialector.Explain(, ...), .RowsAffected
		}, .Error)
	}

	if !.DB.DryRun {
		.SQL.Reset()
		.Vars = nil
	}

	if  {
		.BuildClauses = nil
	}

	return 
}

func ( *processor) ( string) func(*DB) {
	for  := len(.callbacks) - 1;  >= 0; -- {
		if  := .callbacks[]; .name ==  && !.remove {
			return .handler
		}
	}
	return nil
}

func ( *processor) ( string) *callback {
	return &callback{before: , processor: }
}

func ( *processor) ( string) *callback {
	return &callback{after: , processor: }
}

func ( *processor) ( func(*DB) bool) *callback {
	return &callback{match: , processor: }
}

func ( *processor) ( string,  func(*DB)) error {
	return (&callback{processor: }).Register(, )
}

func ( *processor) ( string) error {
	return (&callback{processor: }).Remove()
}

func ( *processor) ( string,  func(*DB)) error {
	return (&callback{processor: }).Replace(, )
}

func ( *processor) () ( error) {
	var  []*callback
	for ,  := range .callbacks {
		if .match == nil || .match(.db) {
			 = append(, )
		}
	}
	.callbacks = 

	if .fns,  = sortCallbacks(.callbacks);  != nil {
		.db.Logger.Error(context.Background(), "Got error when compile callbacks, got %v", )
	}
	return
}

func ( *callback) ( string) *callback {
	.before = 
	return 
}

func ( *callback) ( string) *callback {
	.after = 
	return 
}

func ( *callback) ( string,  func(*DB)) error {
	.name = 
	.handler = 
	.processor.callbacks = append(.processor.callbacks, )
	return .processor.compile()
}

func ( *callback) ( string) error {
	.processor.db.Logger.Warn(context.Background(), "removing callback `%s` from %s\n", , utils.FileWithLineNum())
	.name = 
	.remove = true
	.processor.callbacks = append(.processor.callbacks, )
	return .processor.compile()
}

func ( *callback) ( string,  func(*DB)) error {
	.processor.db.Logger.Info(context.Background(), "replacing callback `%s` from %s\n", , utils.FileWithLineNum())
	.name = 
	.handler = 
	.replace = true
	.processor.callbacks = append(.processor.callbacks, )
	return .processor.compile()
}

// getRIndex get right index from string slice
func getRIndex( []string,  string) int {
	for  := len() - 1;  >= 0; -- {
		if [] ==  {
			return 
		}
	}
	return -1
}

func sortCallbacks( []*callback) ( []func(*DB),  error) {
	var (
		,  []string
		  func(*callback) error
	)
	sort.SliceStable(, func(,  int) bool {
		if [].before == "*" && [].before != "*" {
			return true
		}
		if [].after == "*" && [].after != "*" {
			return true
		}
		return false
	})

	for ,  := range  {
		// show warning message the callback name already exists
		if  := getRIndex(, .name);  > -1 && !.replace && !.remove && ![].remove {
			.processor.db.Logger.Warn(context.Background(), "duplicated callback `%s` from %s\n", .name, utils.FileWithLineNum())
		}
		 = append(, .name)
	}

	 = func( *callback) error {
		if .before != "" { // if defined before callback
			if .before == "*" && len() > 0 {
				if  := getRIndex(, .name);  == -1 {
					 = append([]string{.name}, ...)
				}
			} else if  := getRIndex(, .before);  != -1 {
				if  := getRIndex(, .name);  == -1 {
					// if before callback already sorted, append current callback just after it
					 = append([:], append([]string{.name}, [:]...)...)
				} else if  >  {
					return fmt.Errorf("conflicting callback %s with before %s", .name, .before)
				}
			} else if  := getRIndex(, .before);  != -1 {
				// if before callback exists
				[].after = .name
			}
		}

		if .after != "" { // if defined after callback
			if .after == "*" && len() > 0 {
				if  := getRIndex(, .name);  == -1 {
					 = append(, .name)
				}
			} else if  := getRIndex(, .after);  != -1 {
				if  := getRIndex(, .name);  == -1 {
					// if after callback sorted, append current callback to last
					 = append(, .name)
				} else if  <  {
					return fmt.Errorf("conflicting callback %s with before %s", .name, .after)
				}
			} else if  := getRIndex(, .after);  != -1 {
				// if after callback exists but haven't sorted
				// set after callback's before callback to current callback
				 := []

				if .before == "" {
					.before = .name
				}

				if  := ();  != nil {
					return 
				}

				if  := ();  != nil {
					return 
				}
			}
		}

		// if current callback haven't been sorted, append it to last
		if getRIndex(, .name) == -1 {
			 = append(, .name)
		}

		return nil
	}

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

	for ,  := range  {
		if  := getRIndex(, ); ![].remove {
			 = append(, [].handler)
		}
	}

	return
}