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
	DefaultTransactionTimeout time.Duration
	DefaultContextTimeout     time.Duration

	// 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
	// PrepareStmt cache support LRU expired,
	// default maxsize=int64 Max value and ttl=1h
	PrepareStmtMaxSize int
	PrepareStmtTTL     time.Duration

	// 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
	// PropagateUnscoped propagate Unscoped to every other nested statement
	PropagateUnscoped 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
	PropagateUnscoped        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  && !
	})

	if len() > 0 {
		if ,  := [0].(*Config);  {
			 = 
		} else {
			 = append([]Option{}, ...)
		}
	}

	var  bool
	for ,  := range  {
		if  != nil {
			if  := .Apply();  != nil {
				return nil, 
			}
			defer func( Option) {
				if  {
					return
				}
				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()
			}

			// DB is not initialized, so we skip AfterInitialize
			 = true
			return
		}

		if .TranslateError {
			if ,  := .Dialector.(ErrorTranslator); ! {
				.Logger.Warn(context.Background(), "The TranslateError option is enabled, but the Dialector %s does not implement ErrorTranslator.", .Dialector.Name())
			}
		}
	}

	if .PrepareStmt {
		 := NewPreparedStmtDB(.ConnPool, .PrepareStmtMaxSize, .PrepareStmtTTL)
		.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 .PropagateUnscoped {
		.PropagateUnscoped = 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, .PrepareStmtMaxSize, .PrepareStmtTTL)
			.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,
			}
			if .Config.PropagateUnscoped {
				.Statement.Unscoped = .Statement.Unscoped
			}
		} 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}).getInstance())
	 := .Statement

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