package gorm

import (
	
	
	
	
	
	
	

	
	
	
)

// for Config.cacheStore store PreparedStmtDB key
const preparedStmtDBKey = "preparedStmt"

// Config GORM config
type Config struct {
	// GORM perform single create, update, delete operations in transactions by default to ensure database data integrity
	// You can disable it by setting `SkipDefaultTransaction` to true
	SkipDefaultTransaction bool
	// NamingStrategy tables, columns naming strategy
	NamingStrategy schema.Namer
	// FullSaveAssociations full save associations
	FullSaveAssociations bool
	// Logger
	Logger logger.Interface
	// NowFunc the function to be used when creating a new timestamp
	NowFunc func() time.Time
	// DryRun generate sql without execute
	DryRun bool
	// PrepareStmt executes the given query in cached statement
	PrepareStmt bool
	// DisableAutomaticPing
	DisableAutomaticPing bool
	// DisableForeignKeyConstraintWhenMigrating
	DisableForeignKeyConstraintWhenMigrating bool
	// IgnoreRelationshipsWhenMigrating
	IgnoreRelationshipsWhenMigrating bool
	// DisableNestedTransaction disable nested transaction
	DisableNestedTransaction bool
	// AllowGlobalUpdate allow global update
	AllowGlobalUpdate bool
	// QueryFields executes the SQL query with all fields of the table
	QueryFields bool
	// CreateBatchSize default create batch size
	CreateBatchSize int
	// TranslateError enabling error translation
	TranslateError bool

	// ClauseBuilders clause builder
	ClauseBuilders map[string]clause.ClauseBuilder
	// ConnPool db conn pool
	ConnPool ConnPool
	// Dialector database dialector
	Dialector
	// Plugins registered plugins
	Plugins map[string]Plugin

	callbacks  *callbacks
	cacheStore *sync.Map
}

// Apply update config to new config
func ( *Config) ( *Config) error {
	if  !=  {
		* = *
	}
	return nil
}

// AfterInitialize initialize plugins after db connected
func ( *Config) ( *DB) error {
	if  != nil {
		for ,  := range .Plugins {
			if  := .Initialize();  != nil {
				return 
			}
		}
	}
	return nil
}

// Option gorm option interface
type Option interface {
	Apply(*Config) error
	AfterInitialize(*DB) error
}

// DB GORM DB definition
type DB struct {
	*Config
	Error        error
	RowsAffected int64
	Statement    *Statement
	clone        int
}

// Session session config when create session with Session() method
type Session struct {
	DryRun                   bool
	PrepareStmt              bool
	NewDB                    bool
	Initialized              bool
	SkipHooks                bool
	SkipDefaultTransaction   bool
	DisableNestedTransaction bool
	AllowGlobalUpdate        bool
	FullSaveAssociations     bool
	QueryFields              bool
	Context                  context.Context
	Logger                   logger.Interface
	NowFunc                  func() time.Time
	CreateBatchSize          int
}

// Open initialize db session based on dialector
func ( Dialector,  ...Option) ( *DB,  error) {
	 := &Config{}

	sort.Slice(, func(,  int) bool {
		,  := [].(*Config)
		,  := [].(*Config)
		return  && !
	})

	for ,  := range  {
		if  != nil {
			if  := .Apply();  != nil {
				return nil, 
			}
			defer func( Option) {
				if  := .AfterInitialize();  != nil {
					 = 
				}
			}()
		}
	}

	if ,  := .(interface{ (*Config) error });  {
		if  = .();  != nil {
			return
		}
	}

	if .NamingStrategy == nil {
		.NamingStrategy = schema.NamingStrategy{IdentifierMaxLength: 64} // Default Identifier length is 64
	}

	if .Logger == nil {
		.Logger = logger.Default
	}

	if .NowFunc == nil {
		.NowFunc = func() time.Time { return time.Now().Local() }
	}

	if  != nil {
		.Dialector = 
	}

	if .Plugins == nil {
		.Plugins = map[string]Plugin{}
	}

	if .cacheStore == nil {
		.cacheStore = &sync.Map{}
	}

	 = &DB{Config: , clone: 1}

	.callbacks = initializeCallbacks()

	if .ClauseBuilders == nil {
		.ClauseBuilders = map[string]clause.ClauseBuilder{}
	}

	if .Dialector != nil {
		 = .Dialector.Initialize()

		if  != nil {
			if ,  := .DB();  != nil {
				_ = .Close()
			}
		}
	}

	if .PrepareStmt {
		 := NewPreparedStmtDB(.ConnPool)
		.cacheStore.Store(preparedStmtDBKey, )
		.ConnPool = 
	}

	.Statement = &Statement{
		DB:       ,
		ConnPool: .ConnPool,
		Context:  context.Background(),
		Clauses:  map[string]clause.Clause{},
	}

	if  == nil && !.DisableAutomaticPing {
		if ,  := .ConnPool.(interface{ () error });  {
			 = .()
		}
	}

	if  != nil {
		.Logger.Error(context.Background(), "failed to initialize database, got error %v", )
	}

	return
}

// Session create new db session
func ( *DB) ( *Session) *DB {
	var (
		 = *.Config
		       = &DB{
			Config:    &,
			Statement: .Statement,
			Error:     .Error,
			clone:     1,
		}
	)
	if .CreateBatchSize > 0 {
		.Config.CreateBatchSize = .CreateBatchSize
	}

	if .SkipDefaultTransaction {
		.Config.SkipDefaultTransaction = true
	}

	if .AllowGlobalUpdate {
		.AllowGlobalUpdate = true
	}

	if .FullSaveAssociations {
		.FullSaveAssociations = true
	}

	if .Context != nil || .PrepareStmt || .SkipHooks {
		.Statement = .Statement.clone()
		.Statement.DB = 
	}

	if .Context != nil {
		.Statement.Context = .Context
	}

	if .PrepareStmt {
		var  *PreparedStmtDB

		if ,  := .cacheStore.Load(preparedStmtDBKey);  {
			 = .(*PreparedStmtDB)
		} else {
			 = NewPreparedStmtDB(.ConnPool)
			.cacheStore.Store(preparedStmtDBKey, )
		}

		switch t := .Statement.ConnPool.(type) {
		case Tx:
			.Statement.ConnPool = &PreparedStmtTX{
				Tx:             ,
				PreparedStmtDB: ,
			}
		default:
			.Statement.ConnPool = &PreparedStmtDB{
				ConnPool: .Config.ConnPool,
				Mux:      .Mux,
				Stmts:    .Stmts,
			}
		}
		.ConnPool = .Statement.ConnPool
		.PrepareStmt = true
	}

	if .SkipHooks {
		.Statement.SkipHooks = true
	}

	if .DisableNestedTransaction {
		.DisableNestedTransaction = true
	}

	if !.NewDB {
		.clone = 2
	}

	if .DryRun {
		.Config.DryRun = true
	}

	if .QueryFields {
		.Config.QueryFields = true
	}

	if .Logger != nil {
		.Config.Logger = .Logger
	}

	if .NowFunc != nil {
		.Config.NowFunc = .NowFunc
	}

	if .Initialized {
		 = .getInstance()
	}

	return 
}

// WithContext change current instance db's context to ctx
func ( *DB) ( context.Context) *DB {
	return .Session(&Session{Context: })
}

// Debug start debug mode
func ( *DB) () ( *DB) {
	 = .getInstance()
	return .Session(&Session{
		Logger: .Logger.LogMode(logger.Info),
	})
}

// Set store value with key into current db instance's context
func ( *DB) ( string,  interface{}) *DB {
	 := .getInstance()
	.Statement.Settings.Store(, )
	return 
}

// Get get value with key from current db instance's context
func ( *DB) ( string) (interface{}, bool) {
	return .Statement.Settings.Load()
}

// InstanceSet store value with key into current db instance's context
func ( *DB) ( string,  interface{}) *DB {
	 := .getInstance()
	.Statement.Settings.Store(fmt.Sprintf("%p", .Statement)+, )
	return 
}

// InstanceGet get value with key from current db instance's context
func ( *DB) ( string) (interface{}, bool) {
	return .Statement.Settings.Load(fmt.Sprintf("%p", .Statement) + )
}

// Callback returns callback manager
func ( *DB) () *callbacks {
	return .callbacks
}

// AddError add error to db
func ( *DB) ( error) error {
	if  != nil {
		if .Config.TranslateError {
			if ,  := .Dialector.(ErrorTranslator);  {
				 = .Translate()
			}
		}

		if .Error == nil {
			.Error = 
		} else {
			.Error = fmt.Errorf("%v; %w", .Error, )
		}
	}
	return .Error
}

// DB returns `*sql.DB`
func ( *DB) () (*sql.DB, error) {
	 := .ConnPool
	if .Statement != nil && .Statement.ConnPool != nil {
		 = .Statement.ConnPool
	}
	if ,  := .(*sql.Tx);  &&  != nil {
		return (*sql.DB)(reflect.ValueOf().Elem().FieldByName("db").UnsafePointer()), nil
	}

	if ,  := .(GetDBConnector);  &&  != nil {
		if ,  := .GetDBConn();  != nil ||  != nil {
			return , 
		}
	}

	if ,  := .(*sql.DB);  &&  != nil {
		return , nil
	}

	return nil, ErrInvalidDB
}

func ( *DB) () *DB {
	if .clone > 0 {
		 := &DB{Config: .Config, Error: .Error}

		if .clone == 1 {
			// clone with new statement
			.Statement = &Statement{
				DB:        ,
				ConnPool:  .Statement.ConnPool,
				Context:   .Statement.Context,
				Clauses:   map[string]clause.Clause{},
				Vars:      make([]interface{}, 0, 8),
				SkipHooks: .Statement.SkipHooks,
			}
		} else {
			// with clone statement
			.Statement = .Statement.clone()
			.Statement.DB = 
		}

		return 
	}

	return 
}

// Expr returns clause.Expr, which can be used to pass SQL expression as params
func ( string,  ...interface{}) clause.Expr {
	return clause.Expr{SQL: , Vars: }
}

// SetupJoinTable setup join table schema
func ( *DB) ( interface{},  string,  interface{}) error {
	var (
		                      = .getInstance()
		                    = .Statement
		,  *schema.Schema
	)

	 := .Parse()
	if  != nil {
		return 
	}
	 = .Schema

	 = .Parse()
	if  != nil {
		return 
	}
	 = .Schema

	,  := .Relationships.Relations[]
	 :=  && .JoinTable != nil
	if ! {
		return fmt.Errorf("failed to find relation: %s", )
	}

	for ,  := range .References {
		 := .LookUpField(.ForeignKey.DBName)
		if  == nil {
			return fmt.Errorf("missing field %s for join table", .ForeignKey.DBName)
		}

		.DataType = .ForeignKey.DataType
		.GORMDataType = .ForeignKey.GORMDataType
		if .Size == 0 {
			.Size = .ForeignKey.Size
		}
		.ForeignKey = 
	}

	for ,  := range .JoinTable.Relationships.Relations {
		if ,  := .Relationships.Relations[]; ! {
			.Schema = 
			.Relationships.Relations[] = 
		}
	}
	.JoinTable = 

	return nil
}

// Use use plugin
func ( *DB) ( Plugin) error {
	 := .Name()
	if ,  := .Plugins[];  {
		return ErrRegistered
	}
	if  := .Initialize();  != nil {
		return 
	}
	.Plugins[] = 
	return nil
}

// ToSQL for generate SQL string.
//
//	db.ToSQL(func(tx *gorm.DB) *gorm.DB {
//			return tx.Model(&User{}).Where(&User{Name: "foo", Age: 20})
//				.Limit(10).Offset(5)
//				.Order("name ASC")
//				.First(&User{})
//	})
func ( *DB) ( func( *DB) *DB) string {
	 := (.Session(&Session{DryRun: true, SkipDefaultTransaction: true}))
	 := .Statement

	return .Dialector.Explain(.SQL.String(), .Vars...)
}