package callbacks

import (
	
	
	
	

	
	
	
	
)

// parsePreloadMap extracts nested preloads. e.g.
//
//	// schema has a "k0" relation and a "k7.k8" embedded relation
//	parsePreloadMap(schema, map[string][]interface{}{
//		clause.Associations: {"arg1"},
//		"k1":                {"arg2"},
//		"k2.k3":             {"arg3"},
//		"k4.k5.k6":          {"arg4"},
//	})
//	// preloadMap is
//	map[string]map[string][]interface{}{
//		"k0": {},
//		"k7": {
//			"k8": {},
//		},
//		"k1": {},
//		"k2": {
//			"k3": {"arg3"},
//		},
//		"k4": {
//			"k5.k6": {"arg4"},
//		},
//	}
func parsePreloadMap( *schema.Schema,  map[string][]interface{}) map[string]map[string][]interface{} {
	 := map[string]map[string][]interface{}{}
	 := func(,  string,  []interface{}) {
		if ,  := []; ! {
			[] = map[string][]interface{}{}
		}
		if  != "" {
			[][] = 
		}
	}

	for ,  := range  {
		 := strings.Split(, ".")
		 := strings.TrimPrefix(strings.TrimPrefix(, [0]), ".")
		if [0] == clause.Associations {
			for ,  := range .Relationships.Relations {
				if .Schema ==  {
					(.Name, , )
				}
			}

			for ,  := range .Relationships.EmbeddedRelations {
				for ,  := range embeddedValues() {
					(, , )
				}
			}
		} else {
			([0], , )
		}
	}
	return 
}

func embeddedValues( *schema.Relationships) []string {
	if  == nil {
		return nil
	}
	 := make([]string, 0, len(.Relations)+len(.EmbeddedRelations))
	for ,  := range .Relations {
		// skip first struct name
		 = append(, strings.Join(.Field.EmbeddedBindNames[1:], "."))
	}
	for ,  := range .EmbeddedRelations {
		 = append(, ()...)
	}
	return 
}

// preloadEntryPoint enters layer by layer. It will call real preload if it finds the right entry point.
// If the current relationship is embedded or joined, current query will be ignored.
//
//nolint:cyclop
func preloadEntryPoint( *gorm.DB,  []string,  *schema.Relationships,  map[string][]interface{},  []interface{}) error {
	 := parsePreloadMap(.Statement.Schema, )

	// avoid random traversal of the map
	 := make([]string, 0, len())
	for  := range  {
		 = append(, )
	}
	sort.Strings()

	 := func( string) ( bool,  []string) {
		for ,  := range  {
			if ,  := .Relations[];  &&  ==  {
				 = true
				continue
			}
			, ,  := strings.Cut(, ".")
			if  {
				if ,  := .Relations[];  &&  ==  {
					 = true
					 = append(, )
				}
			}
		}
		return , 
	}

	for ,  := range  {
		if  := .EmbeddedRelations[];  != nil {
			if  := (, , , [], );  != nil {
				return 
			}
		} else if  := .Relations[];  != nil {
			if ,  := ();  {
				switch  := .Statement.ReflectValue; .Kind() {
				case reflect.Slice, reflect.Array:
					if .Len() > 0 {
						 := .FieldSchema.MakeSlice().Elem()
						for  := 0;  < .Len(); ++ {
							 := .Field.ReflectValueOf(.Statement.Context, .Index())
							if .Kind() != reflect.Ptr {
								 = reflect.Append(, .Addr())
							} else {
								if .IsNil() {
									continue
								}
								 = reflect.Append(, )
							}
						}

						 := preloadDB(, , .Interface())
						if  := (, , &.Statement.Schema.Relationships, [], );  != nil {
							return 
						}
					}
				case reflect.Struct, reflect.Pointer:
					 := .Field.ReflectValueOf(.Statement.Context, )
					 := preloadDB(, , .Interface())
					if  := (, , &.Statement.Schema.Relationships, [], );  != nil {
						return 
					}
				default:
					return gorm.ErrInvalidData
				}
			} else {
				 := .Table("").Session(&gorm.Session{Context: .Statement.Context, SkipHooks: .Statement.SkipHooks})
				.Statement.ReflectValue = .Statement.ReflectValue
				.Statement.Unscoped = .Statement.Unscoped
				if  := preload(, , append([], ...), []);  != nil {
					return 
				}
			}
		} else {
			return fmt.Errorf("%s: %w for schema %s", , gorm.ErrUnsupportedRelation, .Statement.Schema.Name)
		}
	}
	return nil
}

func preloadDB( *gorm.DB,  reflect.Value,  interface{}) *gorm.DB {
	 := .Session(&gorm.Session{Context: .Statement.Context, NewDB: true, SkipHooks: .Statement.SkipHooks, Initialized: true})
	.Statement.Settings.Range(func(,  interface{}) bool {
		.Statement.Settings.Store(, )
		return true
	})

	if  := .Statement.Parse();  != nil {
		.AddError()
		return 
	}
	.Statement.ReflectValue = 
	.Statement.Unscoped = .Statement.Unscoped
	return 
}

func preload( *gorm.DB,  *schema.Relationship,  []interface{},  map[string][]interface{}) error {
	var (
		     = .Statement.ReflectValue
		   []string
		 []*schema.Field
		    []*schema.Field
		    [][]interface{}
		      = map[string][]reflect.Value{}
		      []interface{}
	)

	if .JoinTable != nil {
		var (
			    = make([]*schema.Field, 0, len(.References))
			 = make([]*schema.Field, 0, len(.References))
			      = make([]string, 0, len(.References))
		)

		for ,  := range .References {
			if .OwnPrimaryKey {
				 = append(, .ForeignKey.DBName)
				 = append(, .ForeignKey)
				 = append(, .PrimaryKey)
			} else if .PrimaryValue != "" {
				 = .Where(clause.Eq{Column: .ForeignKey.DBName, Value: .PrimaryValue})
			} else {
				 = append(, .ForeignKey)
				 = append(, .PrimaryKey.DBName)
				 = append(, .PrimaryKey)
			}
		}

		,  := schema.GetIdentityFieldValuesMap(.Statement.Context, , )
		if len() == 0 {
			return nil
		}

		 := .JoinTable.MakeSlice().Elem()
		,  := schema.ToQueryValues(clause.CurrentTable, , )
		if  := .Where(clause.IN{Column: , Values: }).Find(.Addr().Interface()).Error;  != nil {
			return 
		}

		// convert join identity map to relation identity map
		 := make([]interface{}, len())
		 := make([]interface{}, len())
		for  := 0;  < .Len(); ++ {
			 := .Index()
			for ,  := range  {
				[], _ = .ValueOf(.Statement.Context, )
			}

			for ,  := range  {
				[], _ = .ValueOf(.Statement.Context, )
			}

			if ,  := [utils.ToStringKey(...)];  {
				 := utils.ToStringKey(...)
				[] = append([], ...)
			}
		}

		_,  = schema.GetIdentityFieldValuesMap(.Statement.Context, , )
	} else {
		for ,  := range .References {
			if .OwnPrimaryKey {
				 = append(, .ForeignKey.DBName)
				 = append(, .ForeignKey)
				 = append(, .PrimaryKey)
			} else if .PrimaryValue != "" {
				 = .Where(clause.Eq{Column: .ForeignKey.DBName, Value: .PrimaryValue})
			} else {
				 = append(, .PrimaryKey.DBName)
				 = append(, .PrimaryKey)
				 = append(, .ForeignKey)
			}
		}

		,  = schema.GetIdentityFieldValuesMap(.Statement.Context, , )
		if len() == 0 {
			return nil
		}
	}

	// nested preload
	for ,  := range  {
		 = .Preload(, ...)
	}

	 := .FieldSchema.MakeSlice().Elem()
	,  := schema.ToQueryValues(clause.CurrentTable, , )

	if len() != 0 {
		 = .Model(.Addr().Interface()).Where(clause.IN{Column: , Values: })

		for ,  := range  {
			if ,  := .(func(*gorm.DB) *gorm.DB);  {
				 = ()
			} else {
				 = append(, )
			}
		}

		if len() > 0 {
			 = .Where([0], [1:]...)
		}

		if  := .Find(.Addr().Interface()).Error;  != nil {
			return 
		}
	}

	 := make([]interface{}, len())

	// clean up old values before preloading
	switch .Kind() {
	case reflect.Struct:
		switch .Type {
		case schema.HasMany, schema.Many2Many:
			.AddError(.Field.Set(.Statement.Context, , reflect.MakeSlice(.Field.IndirectFieldType, 0, 10).Interface()))
		default:
			.AddError(.Field.Set(.Statement.Context, , reflect.New(.Field.FieldType).Interface()))
		}
	case reflect.Slice, reflect.Array:
		for  := 0;  < .Len(); ++ {
			switch .Type {
			case schema.HasMany, schema.Many2Many:
				.AddError(.Field.Set(.Statement.Context, .Index(), reflect.MakeSlice(.Field.IndirectFieldType, 0, 10).Interface()))
			default:
				.AddError(.Field.Set(.Statement.Context, .Index(), reflect.New(.Field.FieldType).Interface()))
			}
		}
	}

	for  := 0;  < .Len(); ++ {
		 := .Index()
		for ,  := range  {
			[], _ = .ValueOf(.Statement.Context, )
		}

		,  := [utils.ToStringKey(...)]
		if ! {
			return fmt.Errorf("failed to assign association %#v, make sure foreign fields exists", .Interface())
		}

		for ,  := range  {
			 := .Field.ReflectValueOf(.Statement.Context, )
			if .Kind() == reflect.Ptr && .IsNil() {
				.Set(reflect.New(.Field.FieldType.Elem()))
			}

			 = reflect.Indirect()
			switch .Kind() {
			case reflect.Struct:
				.AddError(.Field.Set(.Statement.Context, , .Interface()))
			case reflect.Slice, reflect.Array:
				if .Type().Elem().Kind() == reflect.Ptr {
					.AddError(.Field.Set(.Statement.Context, , reflect.Append(, ).Interface()))
				} else {
					.AddError(.Field.Set(.Statement.Context, , reflect.Append(, .Elem()).Interface()))
				}
			}
		}
	}

	return .Error
}