package gorm

import (
	
	
	
	
	
	
	
	
	
	

	
	
	
	
)

// Statement statement
type Statement struct {
	*DB
	TableExpr            *clause.Expr
	Table                string
	Model                interface{}
	Unscoped             bool
	Dest                 interface{}
	ReflectValue         reflect.Value
	Clauses              map[string]clause.Clause
	BuildClauses         []string
	Distinct             bool
	Selects              []string // selected columns
	Omits                []string // omit columns
	Joins                []join
	Preloads             map[string][]interface{}
	Settings             sync.Map
	ConnPool             ConnPool
	Schema               *schema.Schema
	Context              context.Context
	RaiseErrorOnNotFound bool
	SkipHooks            bool
	SQL                  strings.Builder
	Vars                 []interface{}
	CurDestIndex         int
	attrs                []interface{}
	assigns              []interface{}
	scopes               []func(*DB) *DB
}

type join struct {
	Name     string
	Conds    []interface{}
	On       *clause.Where
	Selects  []string
	Omits    []string
	JoinType clause.JoinType
}

// StatementModifier statement modifier interface
type StatementModifier interface {
	ModifyStatement(*Statement)
}

// WriteString write string
func ( *Statement) ( string) (int, error) {
	return .SQL.WriteString()
}

// WriteByte write byte
func ( *Statement) ( byte) error {
	return .SQL.WriteByte()
}

// WriteQuoted write quoted value
func ( *Statement) ( interface{}) {
	.QuoteTo(&.SQL, )
}

// QuoteTo write quoted value to writer
func ( *Statement) ( clause.Writer,  interface{}) {
	 := func( bool,  string) {
		if  {
			.WriteString()
		} else {
			.DB.Dialector.QuoteTo(, )
		}
	}

	switch v := .(type) {
	case clause.Table:
		if .Name == clause.CurrentTable {
			if .TableExpr != nil {
				.TableExpr.Build()
			} else {
				(.Raw, .Table)
			}
		} else {
			(.Raw, .Name)
		}

		if .Alias != "" {
			.WriteByte(' ')
			(.Raw, .Alias)
		}
	case clause.Column:
		if .Table != "" {
			if .Table == clause.CurrentTable {
				(.Raw, .Table)
			} else {
				(.Raw, .Table)
			}
			.WriteByte('.')
		}

		if .Name == clause.PrimaryKey {
			if .Schema == nil {
				.DB.AddError(ErrModelValueRequired)
			} else if .Schema.PrioritizedPrimaryField != nil {
				(.Raw, .Schema.PrioritizedPrimaryField.DBName)
			} else if len(.Schema.DBNames) > 0 {
				(.Raw, .Schema.DBNames[0])
			} else {
				.DB.AddError(ErrModelAccessibleFieldsRequired) //nolint:typecheck,errcheck
			}
		} else {
			(.Raw, .Name)
		}

		if .Alias != "" {
			.WriteString(" AS ")
			(.Raw, .Alias)
		}
	case []clause.Column:
		.WriteByte('(')
		for ,  := range  {
			if  > 0 {
				.WriteByte(',')
			}
			.(, )
		}
		.WriteByte(')')
	case clause.Expr:
		.Build()
	case string:
		.DB.Dialector.QuoteTo(, )
	case []string:
		.WriteByte('(')
		for ,  := range  {
			if  > 0 {
				.WriteByte(',')
			}
			.DB.Dialector.QuoteTo(, )
		}
		.WriteByte(')')
	default:
		.DB.Dialector.QuoteTo(, fmt.Sprint())
	}
}

// Quote returns quoted value
func ( *Statement) ( interface{}) string {
	var  strings.Builder
	.QuoteTo(&, )
	return .String()
}

// AddVar add var
func ( *Statement) ( clause.Writer,  ...interface{}) {
	for ,  := range  {
		if  > 0 {
			.WriteByte(',')
		}

		switch v := .(type) {
		case sql.NamedArg:
			.Vars = append(.Vars, .Value)
		case clause.Column, clause.Table:
			.QuoteTo(, )
		case Valuer:
			 := reflect.ValueOf()
			if .Kind() == reflect.Ptr && .IsNil() {
				.(, nil)
			} else {
				.(, .GormValue(.Context, .DB))
			}
		case clause.Interface:
			 := clause.Clause{Name: .Name()}
			.MergeClause(&)
			.Build()
		case clause.Expression:
			.Build()
		case driver.Valuer:
			.Vars = append(.Vars, )
			.DB.Dialector.BindVarTo(, , )
		case []byte:
			.Vars = append(.Vars, )
			.DB.Dialector.BindVarTo(, , )
		case []interface{}:
			if len() > 0 {
				.WriteByte('(')
				.(, ...)
				.WriteByte(')')
			} else {
				.WriteString("(NULL)")
			}
		case *DB:
			 := .Session(&Session{Logger: logger.Discard, DryRun: true}).getInstance()
			if .Statement.SQL.Len() > 0 {
				var (
					 = .Statement.Vars
					  = .Statement.SQL.String()
				)

				.Statement.Vars = make([]interface{}, 0, len())
				for ,  := range  {
					.Statement.Vars = append(.Statement.Vars, )
					 := strings.Builder{}
					.Dialector.BindVarTo(&, .Statement, )
					 = strings.Replace(, .String(), "?", 1)
				}

				.Statement.SQL.Reset()
				.Statement.Vars = .Vars
				if strings.Contains(, "@") {
					clause.NamedExpr{SQL: , Vars: }.Build(.Statement)
				} else {
					clause.Expr{SQL: , Vars: }.Build(.Statement)
				}
			} else {
				.Statement.Vars = append(.Vars, .Statement.Vars...)
				.callbacks.Query().Execute()
			}

			.WriteString(.Statement.SQL.String())
			.Vars = .Statement.Vars
		default:
			switch  := reflect.ValueOf(); .Kind() {
			case reflect.Slice, reflect.Array:
				if .Len() == 0 {
					.WriteString("(NULL)")
				} else if .Type().Elem() == reflect.TypeOf(uint8(0)) {
					.Vars = append(.Vars, )
					.DB.Dialector.BindVarTo(, , )
				} else {
					.WriteByte('(')
					for  := 0;  < .Len(); ++ {
						if  > 0 {
							.WriteByte(',')
						}
						.(, .Index().Interface())
					}
					.WriteByte(')')
				}
			default:
				.Vars = append(.Vars, )
				.DB.Dialector.BindVarTo(, , )
			}
		}
	}
}

// AddClause add clause
func ( *Statement) ( clause.Interface) {
	if ,  := .(StatementModifier);  {
		.ModifyStatement()
	} else {
		 := .Name()
		 := .Clauses[]
		.Name = 
		.MergeClause(&)
		.Clauses[] = 
	}
}

// AddClauseIfNotExists add clause if not exists
func ( *Statement) ( clause.Interface) {
	if ,  := .Clauses[.Name()]; ! || .Expression == nil {
		.AddClause()
	}
}

// BuildCondition build condition
func ( *Statement) ( interface{},  ...interface{}) []clause.Expression {
	if ,  := .(string);  {
		// if it is a number, then treats it as primary key
		if ,  := strconv.Atoi();  != nil {
			if  == "" && len() == 0 {
				return nil
			}

			if len() == 0 || (len() > 0 && strings.Contains(, "?")) {
				// looks like a where condition
				return []clause.Expression{clause.Expr{SQL: , Vars: }}
			}

			if len() > 0 && strings.Contains(, "@") {
				// looks like a named query
				return []clause.Expression{clause.NamedExpr{SQL: , Vars: }}
			}

			if strings.Contains(strings.TrimSpace(), " ") {
				// looks like a where condition
				return []clause.Expression{clause.Expr{SQL: , Vars: }}
			}

			if len() == 1 {
				return []clause.Expression{clause.Eq{Column: , Value: [0]}}
			}
		}
	}

	 := make([]clause.Expression, 0, 4)
	 = append([]interface{}{}, ...)
	for ,  := range  {
		if  == nil {
			continue
		}
		if ,  := .(driver.Valuer);  {
			, _ = .Value()
		}

		switch v := .(type) {
		case clause.Expression:
			 = append(, )
		case *DB:
			.executeScopes()

			if ,  := .Statement.Clauses["WHERE"];  && .Expression != nil {
				if ,  := .Expression.(clause.Where);  {
					if len(.Exprs) == 1 {
						if ,  := .Exprs[0].(clause.OrConditions);  {
							.Exprs[0] = clause.AndConditions()
						}
					}
					 = append(, clause.And(.Exprs...))
				} else {
					 = append(, .Expression)
				}
				if .Statement ==  {
					.Expression = nil
					.Statement.Clauses["WHERE"] = 
				}
			}
		case map[interface{}]interface{}:
			for ,  := range  {
				 = append(, clause.Eq{Column: , Value: })
			}
		case map[string]string:
			 := make([]string, 0, len())
			for  := range  {
				 = append(, )
			}
			sort.Strings()

			for ,  := range  {
				 = append(, clause.Eq{Column: , Value: []})
			}
		case map[string]interface{}:
			 := make([]string, 0, len())
			for  := range  {
				 = append(, )
			}
			sort.Strings()

			for ,  := range  {
				 := reflect.Indirect(reflect.ValueOf([]))
				switch .Kind() {
				case reflect.Slice, reflect.Array:
					if ,  := [].(driver.Valuer);  {
						 = append(, clause.Eq{Column: , Value: []})
					} else if ,  := [].(Valuer);  {
						 = append(, clause.Eq{Column: , Value: []})
					} else {
						// optimize reflect value length
						 := .Len()
						 := make([]interface{}, )
						for  := 0;  < ; ++ {
							[] = .Index().Interface()
						}

						 = append(, clause.IN{Column: , Values: })
					}
				default:
					 = append(, clause.Eq{Column: , Value: []})
				}
			}
		default:
			 := reflect.Indirect(reflect.ValueOf())
			for .Kind() == reflect.Ptr {
				 = .Elem()
			}

			if ,  := schema.Parse(, .DB.cacheStore, .DB.NamingStrategy);  == nil {
				 := map[string]bool{}
				if  == 0 {
					for ,  := range [1:] {
						if ,  := .(string);  {
							[] = true
						}
					}
				}
				 := len() != 0

				switch .Kind() {
				case reflect.Struct:
					for ,  := range .Fields {
						 := [.DBName] || [.Name]
						if  || (! && .Readable) {
							if ,  := .ValueOf(.Context, ); ! ||  {
								if .DBName != "" {
									 = append(, clause.Eq{Column: clause.Column{Table: clause.CurrentTable, Name: .DBName}, Value: })
								} else if .DataType != "" {
									 = append(, clause.Eq{Column: clause.Column{Table: clause.CurrentTable, Name: .Name}, Value: })
								}
							}
						}
					}
				case reflect.Slice, reflect.Array:
					for  := 0;  < .Len(); ++ {
						for ,  := range .Fields {
							 := [.DBName] || [.Name]
							if  || (! && .Readable) {
								if ,  := .ValueOf(.Context, .Index()); ! ||  {
									if .DBName != "" {
										 = append(, clause.Eq{Column: clause.Column{Table: clause.CurrentTable, Name: .DBName}, Value: })
									} else if .DataType != "" {
										 = append(, clause.Eq{Column: clause.Column{Table: clause.CurrentTable, Name: .Name}, Value: })
									}
								}
							}
						}
					}
				}

				if  {
					break
				}
			} else if !.IsValid() {
				.AddError(ErrInvalidData)
			} else if len() == 0 {
				if len() == 1 {
					switch .Kind() {
					case reflect.Slice, reflect.Array:
						// optimize reflect value length
						 := .Len()
						 := make([]interface{}, )
						for  := 0;  < ; ++ {
							[] = .Index().Interface()
						}

						if len() > 0 {
							 = append(, clause.IN{Column: clause.PrimaryColumn, Values: })
						}
						return 
					}
				}

				 = append(, clause.IN{Column: clause.PrimaryColumn, Values: })
			}
		}
	}

	return 
}

// Build build sql with clauses names
func ( *Statement) ( ...string) {
	var  bool

	for ,  := range  {
		if ,  := .Clauses[];  {
			if  {
				.WriteByte(' ')
			}

			 = true
			if ,  := .DB.ClauseBuilders[];  {
				(, )
			} else {
				.Build()
			}
		}
	}
}

func ( *Statement) ( interface{}) ( error) {
	return .ParseWithSpecialTableName(, "")
}

func ( *Statement) ( interface{},  string) ( error) {
	if .Schema,  = schema.ParseWithSpecialTableName(, .DB.cacheStore, .DB.NamingStrategy, );  == nil && .Table == "" {
		if  := strings.Split(.Schema.Table, "."); len() == 2 {
			.TableExpr = &clause.Expr{SQL: .Quote(.Schema.Table)}
			.Table = [1]
			return
		}

		.Table = .Schema.Table
	}
	return 
}

func ( *Statement) () *Statement {
	 := &Statement{
		TableExpr:            .TableExpr,
		Table:                .Table,
		Model:                .Model,
		Unscoped:             .Unscoped,
		Dest:                 .Dest,
		ReflectValue:         .ReflectValue,
		Clauses:              map[string]clause.Clause{},
		Distinct:             .Distinct,
		Selects:              .Selects,
		Omits:                .Omits,
		Preloads:             map[string][]interface{}{},
		ConnPool:             .ConnPool,
		Schema:               .Schema,
		Context:              .Context,
		RaiseErrorOnNotFound: .RaiseErrorOnNotFound,
		SkipHooks:            .SkipHooks,
	}

	if .SQL.Len() > 0 {
		.SQL.WriteString(.SQL.String())
		.Vars = make([]interface{}, 0, len(.Vars))
		.Vars = append(.Vars, .Vars...)
	}

	for ,  := range .Clauses {
		.Clauses[] = 
	}

	for ,  := range .Preloads {
		.Preloads[] = 
	}

	if len(.Joins) > 0 {
		.Joins = make([]join, len(.Joins))
		copy(.Joins, .Joins)
	}

	if len(.scopes) > 0 {
		.scopes = make([]func(*DB) *DB, len(.scopes))
		copy(.scopes, .scopes)
	}

	.Settings.Range(func(,  interface{}) bool {
		.Settings.Store(, )
		return true
	})

	return 
}

// SetColumn set column's value
//
//	stmt.SetColumn("Name", "jinzhu") // Hooks Method
//	stmt.SetColumn("Name", "jinzhu", true) // Callbacks Method
func ( *Statement) ( string,  interface{},  ...bool) {
	if ,  := .Dest.(map[string]interface{});  {
		[] = 
	} else if ,  := .Dest.([]map[string]interface{});  {
		for ,  := range  {
			[] = 
		}
	} else if .Schema != nil {
		if  := .Schema.LookUpField();  != nil {
			 := reflect.ValueOf(.Dest)
			for .Kind() == reflect.Ptr {
				 = .Elem()
			}

			if .ReflectValue !=  {
				if !.CanAddr() {
					 := reflect.New(.Type())
					.Elem().Set()
					.Dest = .Interface()
					 = .Elem()
				}

				switch .Kind() {
				case reflect.Struct:
					.AddError(.Set(.Context, , ))
				default:
					.AddError(ErrInvalidData)
				}
			}

			switch .ReflectValue.Kind() {
			case reflect.Slice, reflect.Array:
				if len() > 0 {
					for  := 0;  < .ReflectValue.Len(); ++ {
						.AddError(.Set(.Context, .ReflectValue.Index(), ))
					}
				} else {
					.AddError(.Set(.Context, .ReflectValue.Index(.CurDestIndex), ))
				}
			case reflect.Struct:
				if !.ReflectValue.CanAddr() {
					.AddError(ErrInvalidValue)
					return
				}

				.AddError(.Set(.Context, .ReflectValue, ))
			}
		} else {
			.AddError(ErrInvalidField)
		}
	} else {
		.AddError(ErrInvalidField)
	}
}

// Changed check model changed or not when updating
func ( *Statement) ( ...string) bool {
	 := .ReflectValue
	switch .Kind() {
	case reflect.Slice, reflect.Array:
		 = .ReflectValue.Index(.CurDestIndex)
	}

	,  := .SelectAndOmitColumns(false, true)
	 := func( *schema.Field) bool {
		,  := .ValueOf(.Context, )
		if ,  := [.DBName]; ( && ) || (! && !) {
			if ,  := .Dest.(map[string]interface{});  {
				if ,  := [.Name];  {
					return !utils.AssertEqual(, )
				} else if ,  := [.DBName];  {
					return !utils.AssertEqual(, )
				}
			} else {
				 := reflect.ValueOf(.Dest)
				for .Kind() == reflect.Ptr {
					 = .Elem()
				}

				,  := .ValueOf(.Context, )
				if  {
					return !utils.AssertEqual(, )
				}
				return ! && !utils.AssertEqual(, )
			}
		}
		return false
	}

	if len() == 0 {
		for ,  := range .Schema.FieldsByDBName {
			if () {
				return true
			}
		}
	} else {
		for ,  := range  {
			if  := .Schema.LookUpField();  != nil {
				if () {
					return true
				}
			}
		}
	}

	return false
}

var nameMatcher = regexp.MustCompile(`^(?:\W?(\w+?)\W?\.)?\W?(\w+?)\W?$`)

// SelectAndOmitColumns get select and omit columns, select -> true, omit -> false
func ( *Statement) (,  bool) (map[string]bool, bool) {
	 := map[string]bool{}
	 := false

	 := func( string,  bool) {
		if .Schema == nil {
			[] = 
		} else if  == "*" {
			 = 
			for ,  := range .Schema.DBNames {
				[] = 
			}
		} else if  == clause.Associations {
			for ,  := range .Schema.Relationships.Relations {
				[.Name] = 
			}
		} else if  := .Schema.LookUpField();  != nil && .DBName != "" {
			[.DBName] = 
		} else if  := nameMatcher.FindStringSubmatch(); len() == 3 && ([1] == .Table || [1] == "") {
			if [2] == "*" {
				for ,  := range .Schema.DBNames {
					[] = 
				}
			} else {
				[[2]] = 
			}
		} else {
			[] = 
		}
	}

	// select columns
	for ,  := range .Selects {
		(, true)
	}

	// omit columns
	for ,  := range .Omits {
		(, false)
	}

	if .Schema != nil {
		for ,  := range .Schema.FieldsByName {
			 := .DBName
			if  == "" {
				 = .Name
			}

			if  && !.Creatable {
				[] = false
			} else if  && !.Updatable {
				[] = false
			}
		}
	}

	return , ! && len(.Selects) > 0
}