package gorm

import (
	
	
	

	
	
	
)

// Association Mode contains some helper methods to handle relationship things easily.
type Association struct {
	DB           *DB
	Relationship *schema.Relationship
	Unscope      bool
	Error        error
}

func ( *DB) ( string) *Association {
	 := &Association{DB: }
	 := .Statement.Table

	if  := .Statement.Parse(.Statement.Model);  == nil {
		.Statement.Table = 
		.Relationship = .Statement.Schema.Relationships.Relations[]

		if .Relationship == nil {
			.Error = fmt.Errorf("%w: %s", ErrUnsupportedRelation, )
		}

		.Statement.ReflectValue = reflect.ValueOf(.Statement.Model)
		for .Statement.ReflectValue.Kind() == reflect.Ptr {
			.Statement.ReflectValue = .Statement.ReflectValue.Elem()
		}
	} else {
		.Error = 
	}

	return 
}

func ( *Association) () *Association {
	return &Association{
		DB:           .DB,
		Relationship: .Relationship,
		Error:        .Error,
		Unscope:      true,
	}
}

func ( *Association) ( interface{},  ...interface{}) error {
	if .Error == nil {
		.Error = .buildCondition().Find(, ...).Error
	}
	return .Error
}

func ( *Association) ( ...interface{}) error {
	if .Error == nil {
		switch .Relationship.Type {
		case schema.HasOne, schema.BelongsTo:
			if len() > 0 {
				.Error = .Replace(...)
			}
		default:
			.saveAssociation( /*clear*/ false, ...)
		}
	}

	return .Error
}

func ( *Association) ( ...interface{}) error {
	if .Error == nil {
		 := .DB.Statement.ReflectValue
		 := .Relationship

		var  clause.Expression
		// we have to record the old BelongsTo value
		if .Unscope && .Type == schema.BelongsTo {
			var  []*schema.Field
			for ,  := range .References {
				if !.OwnPrimaryKey {
					 = append(, .ForeignKey)
				}
			}
			if ,  := schema.GetIdentityFieldValuesMap(.DB.Statement.Context, , ); len() > 0 {
				,  := schema.ToQueryValues(.FieldSchema.Table, .FieldSchema.PrimaryFieldDBNames, )
				 = clause.IN{Column: , Values: }
			}
		}

		// save associations
		if .saveAssociation( /*clear*/ true, ...); .Error != nil {
			return .Error
		}

		// set old associations's foreign key to null
		switch .Type {
		case schema.BelongsTo:
			if len() == 0 {
				 := map[string]interface{}{}
				switch .Kind() {
				case reflect.Slice, reflect.Array:
					for  := 0;  < .Len(); ++ {
						.Error = .Field.Set(.DB.Statement.Context, .Index(), reflect.Zero(.Field.FieldType).Interface())
					}
				case reflect.Struct:
					.Error = .Field.Set(.DB.Statement.Context, , reflect.Zero(.Field.FieldType).Interface())
				}

				for ,  := range .References {
					[.ForeignKey.DBName] = nil
				}

				.Error = .DB.UpdateColumns().Error
			}
			if .Unscope &&  != nil {
				.Error = .DB.Model(nil).Where().Delete(reflect.New(.FieldSchema.ModelType).Interface()).Error
			}
		case schema.HasOne, schema.HasMany:
			var (
				 []*schema.Field
				   []string
				     = map[string]interface{}{}
				     = schema.GetRelationsValues(.DB.Statement.Context, , []*schema.Relationship{})
				    = reflect.New(.FieldSchema.ModelType).Interface()
				            = .DB.Model()
			)

			if ,  := schema.GetIdentityFieldValuesMap(.DB.Statement.Context, , .FieldSchema.PrimaryFields); len() > 0 {
				if ,  := schema.ToQueryValues(.FieldSchema.Table, .FieldSchema.PrimaryFieldDBNames, ); len() > 0 {
					.Not(clause.IN{Column: , Values: })
				}
			}

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

			if ,  := schema.GetIdentityFieldValuesMap(.DB.Statement.Context, , ); len() > 0 {
				,  := schema.ToQueryValues(.FieldSchema.Table, , )
				if .Unscope {
					.Error = .Where(clause.IN{Column: , Values: }).Delete().Error
				} else {
					.Error = .Where(clause.IN{Column: , Values: }).UpdateColumns().Error
				}
			}
		case schema.Many2Many:
			var (
				,      []*schema.Field
				,  []string
				                          = reflect.New(.JoinTable.ModelType).Interface()
				                                  = .DB.Model()
			)

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

			,  := schema.GetIdentityFieldValuesMap(.DB.Statement.Context, , )
			if ,  := schema.ToQueryValues(.JoinTable.Table, , ); len() > 0 {
				.Where(clause.IN{Column: , Values: })
			} else {
				return ErrPrimaryKeyRequired
			}

			,  := schema.GetIdentityFieldValuesMapFromValues(.DB.Statement.Context, , )
			if ,  := schema.ToQueryValues(.JoinTable.Table, , ); len() > 0 {
				.Where(clause.Not(clause.IN{Column: , Values: }))
			}

			.Error = .Delete().Error
		}
	}
	return .Error
}

func ( *Association) ( ...interface{}) error {
	if .Error == nil {
		var (
			  = .DB.Statement.ReflectValue
			           = .Relationship
			 []*schema.Field
			   []string
			   = map[string]interface{}{}
			         []clause.Expression
		)

		for ,  := range .References {
			if .PrimaryValue == "" {
				 = append(, .PrimaryKey)
				 = append(, .ForeignKey.DBName)
				[.ForeignKey.DBName] = nil
			} else {
				 = append(, clause.Eq{Column: .ForeignKey.DBName, Value: .PrimaryValue})
			}
		}

		switch .Type {
		case schema.BelongsTo:
			 := .DB.Session(&Session{})
			 := .Model(reflect.New(.Schema.ModelType).Interface())

			,  := schema.GetIdentityFieldValuesMap(.DB.Statement.Context, , .Schema.PrimaryFields)
			if ,  := schema.ToQueryValues(.Schema.Table, .Schema.PrimaryFieldDBNames, ); len() > 0 {
				 = append(, clause.IN{Column: , Values: })
			} else {
				return ErrPrimaryKeyRequired
			}

			,  := schema.GetIdentityFieldValuesMapFromValues(.DB.Statement.Context, , )
			,  := schema.ToQueryValues(.Schema.Table, , )
			 = append(, clause.IN{Column: , Values: })

			.Error = .Clauses(...).UpdateColumns().Error
			if .Unscope {
				var  []*schema.Field
				for ,  := range .References {
					if !.OwnPrimaryKey {
						 = append(, .ForeignKey)
					}
				}
				if ,  := schema.GetIdentityFieldValuesMap(.DB.Statement.Context, , ); len() > 0 {
					,  := schema.ToQueryValues(.FieldSchema.Table, .FieldSchema.PrimaryFieldDBNames, )
					.Error = .Model(nil).Where(clause.IN{Column: , Values: }).Delete(reflect.New(.FieldSchema.ModelType).Interface()).Error
				}
			}
		case schema.HasOne, schema.HasMany:
			 := reflect.New(.FieldSchema.ModelType).Interface()
			 := .DB.Model()

			,  := schema.GetIdentityFieldValuesMap(.DB.Statement.Context, , )
			if ,  := schema.ToQueryValues(.FieldSchema.Table, , ); len() > 0 {
				 = append(, clause.IN{Column: , Values: })
			} else {
				return ErrPrimaryKeyRequired
			}

			,  := schema.GetIdentityFieldValuesMapFromValues(.DB.Statement.Context, , .FieldSchema.PrimaryFields)
			,  := schema.ToQueryValues(.FieldSchema.Table, .FieldSchema.PrimaryFieldDBNames, )
			 = append(, clause.IN{Column: , Values: })

			if .Unscope {
				.Error = .Clauses(...).Delete().Error
			} else {
				.Error = .Clauses(...).UpdateColumns().Error
			}
		case schema.Many2Many:
			var (
				,      []*schema.Field
				,  []string
				                           = reflect.New(.JoinTable.ModelType).Interface()
			)

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

			,  := schema.GetIdentityFieldValuesMap(.DB.Statement.Context, , )
			if ,  := schema.ToQueryValues(.JoinTable.Table, , ); len() > 0 {
				 = append(, clause.IN{Column: , Values: })
			} else {
				return ErrPrimaryKeyRequired
			}

			,  := schema.GetIdentityFieldValuesMapFromValues(.DB.Statement.Context, , )
			,  := schema.ToQueryValues(.JoinTable.Table, , )
			 = append(, clause.IN{Column: , Values: })

			.Error = .DB.Where(clause.Where{Exprs: }).Model(nil).Delete().Error
		}

		if .Error == nil {
			// clean up deleted values's foreign key
			,  := schema.GetIdentityFieldValuesMapFromValues(.DB.Statement.Context, , .FieldSchema.PrimaryFields)

			 := func( reflect.Value) {
				if ,  := .Field.ValueOf(.DB.Statement.Context, ); ! {
					 := reflect.Indirect(.Field.ReflectValueOf(.DB.Statement.Context, ))
					 := make([]interface{}, len(.FieldSchema.PrimaryFields))

					switch .Kind() {
					case reflect.Slice, reflect.Array:
						 := reflect.Zero(.Field.IndirectFieldType)
						for  := 0;  < .Len(); ++ {
							for ,  := range .FieldSchema.PrimaryFields {
								[], _ = .ValueOf(.DB.Statement.Context, .Index())
							}

							if ,  := [utils.ToStringKey(...)]; ! {
								 = reflect.Append(, .Index())
							}
						}

						.Error = .Field.Set(.DB.Statement.Context, , .Interface())
					case reflect.Struct:
						for ,  := range .FieldSchema.PrimaryFields {
							[], _ = .ValueOf(.DB.Statement.Context, )
						}

						if ,  := [utils.ToStringKey(...)];  {
							if .Error = .Field.Set(.DB.Statement.Context, , reflect.Zero(.FieldSchema.ModelType).Interface()); .Error != nil {
								break
							}

							if .JoinTable == nil {
								for ,  := range .References {
									if .OwnPrimaryKey || .PrimaryValue != "" {
										.Error = .ForeignKey.Set(.DB.Statement.Context, , reflect.Zero(.ForeignKey.FieldType).Interface())
									} else {
										.Error = .ForeignKey.Set(.DB.Statement.Context, , reflect.Zero(.ForeignKey.FieldType).Interface())
									}
								}
							}
						}
					}
				}
			}

			switch .Kind() {
			case reflect.Slice, reflect.Array:
				for  := 0;  < .Len(); ++ {
					(reflect.Indirect(.Index()))
				}
			case reflect.Struct:
				()
			}
		}
	}

	return .Error
}

func ( *Association) () error {
	return .Replace()
}

func ( *Association) () ( int64) {
	if .Error == nil {
		.Error = .buildCondition().Count(&).Error
	}
	return
}

type assignBack struct {
	Source reflect.Value
	Index  int
	Dest   reflect.Value
}

func ( *Association) ( bool,  ...interface{}) {
	var (
		 = .DB.Statement.ReflectValue
		  []assignBack // assign association values back to arguments after save
	)

	 := func(,  reflect.Value,  bool) {
		switch .Relationship.Type {
		case schema.HasOne, schema.BelongsTo:
			switch .Kind() {
			case reflect.Slice, reflect.Array:
				if .Len() > 0 {
					.Error = .Relationship.Field.Set(.DB.Statement.Context, , .Index(0).Addr().Interface())

					if .Relationship.Field.FieldType.Kind() == reflect.Struct {
						 = append(, assignBack{Source: , Dest: .Index(0)})
					}
				}
			case reflect.Struct:
				.Error = .Relationship.Field.Set(.DB.Statement.Context, , .Addr().Interface())

				if .Relationship.Field.FieldType.Kind() == reflect.Struct {
					 = append(, assignBack{Source: , Dest: })
				}
			}
		case schema.HasMany, schema.Many2Many:
			 := .Relationship.Field.IndirectFieldType.Elem()
			 := reflect.Indirect(.Relationship.Field.ReflectValueOf(.DB.Statement.Context, ))
			var  reflect.Value
			if  {
				 = reflect.MakeSlice(.Type(), 0, .Cap())
			} else {
				 = reflect.MakeSlice(.Type(), .Len(), .Cap())
				reflect.Copy(, )
			}

			 := func( reflect.Value) {
				if .Type().AssignableTo() {
					 = reflect.Append(, )
				} else if .Type().Elem().AssignableTo() {
					 = reflect.Append(, .Elem())
				} else {
					.Error = fmt.Errorf("unsupported data type: %v for relation %s", .Type(), .Relationship.Name)
				}

				if .Kind() == reflect.Struct {
					 = append(, assignBack{Source: , Dest: , Index: .Len()})
				}
			}

			switch .Kind() {
			case reflect.Slice, reflect.Array:
				for  := 0;  < .Len(); ++ {
					(reflect.Indirect(.Index()).Addr())
				}
			case reflect.Struct:
				(.Addr())
			}

			if .Error == nil {
				.Error = .Relationship.Field.Set(.DB.Statement.Context, , .Interface())
			}
		}
	}

	 := []string{.Relationship.Name}
	 := []string{}
	,  := .DB.Statement.SelectAndOmitColumns(true, false)
	for ,  := range  {
		 := ""
		if strings.HasPrefix(, .Relationship.Name) {
			if  = strings.TrimPrefix(, .Relationship.Name);  == ".*" {
				 = 
			}
		} else if strings.HasPrefix(, clause.Associations) {
			 = 
		}

		if  != "" {
			if  {
				 = append(, )
			} else {
				 = append(, )
			}
		}
	}

	for ,  := range .Relationship.References {
		if !.OwnPrimaryKey {
			 = append(, .ForeignKey.Name)
		}
	}

	 := .DB.Session(&Session{}).Model(nil)
	if !.DB.FullSaveAssociations {
		.Select()
	}
	if len() > 0 {
		.Omit(...)
	}
	 = .Session(&Session{})

	switch .Kind() {
	case reflect.Slice, reflect.Array:
		if len() != .Len() {
			// clear old data
			if  && len() == 0 {
				for  := 0;  < .Len(); ++ {
					if  := .Relationship.Field.Set(.DB.Statement.Context, .Index(), reflect.New(.Relationship.Field.IndirectFieldType).Interface());  != nil {
						.Error = 
						break
					}

					if .Relationship.JoinTable == nil {
						for ,  := range .Relationship.References {
							if !.OwnPrimaryKey && .PrimaryValue == "" {
								if  := .ForeignKey.Set(.DB.Statement.Context, .Index(), reflect.Zero(.ForeignKey.FieldType).Interface());  != nil {
									.Error = 
									break
								}
							}
						}
					}
				}
				break
			}

			.Error = ErrInvalidValueOfLength
			return
		}

		for  := 0;  < .Len(); ++ {
			(.Index(), reflect.Indirect(reflect.ValueOf([])), )

			// TODO support save slice data, sql with case?
			.Error = .Updates(.Index().Addr().Interface()).Error
		}
	case reflect.Struct:
		// clear old data
		if  && len() == 0 {
			.Error = .Relationship.Field.Set(.DB.Statement.Context, , reflect.New(.Relationship.Field.IndirectFieldType).Interface())

			if .Relationship.JoinTable == nil && .Error == nil {
				for ,  := range .Relationship.References {
					if !.OwnPrimaryKey && .PrimaryValue == "" {
						.Error = .ForeignKey.Set(.DB.Statement.Context, , reflect.Zero(.ForeignKey.FieldType).Interface())
					}
				}
			}
		}

		for ,  := range  {
			 := reflect.Indirect(reflect.ValueOf())
			(, ,  &&  == 0)
		}

		if len() > 0 {
			.Error = .Updates(.Addr().Interface()).Error
		}
	}

	for ,  := range  {
		 := reflect.Indirect(.Relationship.Field.ReflectValueOf(.DB.Statement.Context, .Source))
		if .Index > 0 {
			reflect.Indirect(.Dest).Set(.Index(.Index - 1))
		} else {
			reflect.Indirect(.Dest).Set()
		}
	}
}

func ( *Association) () *DB {
	var (
		 = .Relationship.ToQueryConditions(.DB.Statement.Context, .DB.Statement.ReflectValue)
		 = reflect.New(.Relationship.FieldSchema.ModelType).Interface()
		         = .DB.Model()
	)

	if .Relationship.JoinTable != nil {
		if !.Statement.Unscoped && len(.Relationship.JoinTable.QueryClauses) > 0 {
			 := Statement{DB: , Context: .Statement.Context, Schema: .Relationship.JoinTable, Table: .Relationship.JoinTable.Table, Clauses: map[string]clause.Clause{}}
			for ,  := range .Relationship.JoinTable.QueryClauses {
				.AddClause()
			}
			.Build("WHERE")
			if len(.SQL.String()) > 0 {
				.Clauses(clause.Expr{SQL: strings.Replace(.SQL.String(), "WHERE ", "", 1), Vars: .Vars})
			}
		}

		 = .Session(&Session{QueryFields: true}).Clauses(clause.From{Joins: []clause.Join{{
			Table: clause.Table{Name: .Relationship.JoinTable.Table},
			ON:    clause.Where{Exprs: },
		}}})
	} else {
		.Clauses(clause.Where{Exprs: })
	}

	return 
}