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.BindNames[1:], "."))
	}
	for ,  := range .EmbeddedRelations {
		 = append(, ()...)
	}
	return 
}

func preloadEmbedded( *gorm.DB,  *schema.Relationships,  *schema.Schema,  map[string][]interface{},  []interface{}) error {
	if  == nil {
		return nil
	}
	 := parsePreloadMap(, )
	for  := range  {
		if  := .EmbeddedRelations[];  != nil {
			if  := (, , , [], );  != nil {
				return 
			}
		} else if  := .Relations[];  != nil {
			if  := preload(, , append([], ), []);  != nil {
				return 
			}
		} else {
			return fmt.Errorf("%s: %w (embedded) for schema %s", , gorm.ErrUnsupportedRelation, .Name)
		}
	}
	return nil
}

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 {
		for ,  := range  {
			if ,  := .(func(*gorm.DB) *gorm.DB);  {
				 = ()
			} else {
				 = append(, )
			}
		}

		if  := .Where(clause.IN{Column: , Values: }).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
}