package datatypes

import (
	
	
	
	
	
	
	
	
	

	
	
	
	
)

// JSON defined JSON data type, need to implements driver.Valuer, sql.Scanner interface
type JSON json.RawMessage

// Value return json value, implement driver.Valuer interface
func ( JSON) () (driver.Value, error) {
	if len() == 0 {
		return nil, nil
	}
	return string(), nil
}

// Scan scan value into Jsonb, implements sql.Scanner interface
func ( *JSON) ( interface{}) error {
	if  == nil {
		* = JSON("null")
		return nil
	}
	var  []byte
	if ,  := .(fmt.Stringer);  {
		 = []byte(.String())
	} else {
		switch v := .(type) {
		case []byte:
			if len() > 0 {
				 = make([]byte, len())
				copy(, )
			}
		case string:
			 = []byte()
		default:
			return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", ))
		}
	}

	 := json.RawMessage()
	* = JSON()
	return nil
}

// MarshalJSON to output non base64 encoded []byte
func ( JSON) () ([]byte, error) {
	return json.RawMessage().MarshalJSON()
}

// UnmarshalJSON to deserialize []byte
func ( *JSON) ( []byte) error {
	 := json.RawMessage{}
	 := .UnmarshalJSON()
	* = JSON()
	return 
}

func ( JSON) () string {
	return string()
}

// GormDataType gorm common data type
func (JSON) () string {
	return "json"
}

// GormDBDataType gorm db data type
func (JSON) ( *gorm.DB,  *schema.Field) string {
	switch .Dialector.Name() {
	case "sqlite":
		return "JSON"
	case "mysql":
		return "JSON"
	case "postgres":
		return "JSONB"
	}
	return ""
}

func ( JSON) ( context.Context,  *gorm.DB) clause.Expr {
	if len() == 0 {
		return gorm.Expr("NULL")
	}

	,  := .MarshalJSON()

	switch .Dialector.Name() {
	case "mysql":
		if ,  := .Dialector.(*mysql.Dialector);  && !strings.Contains(.ServerVersion, "MariaDB") {
			return gorm.Expr("CAST(? AS JSON)", string())
		}
	}

	return gorm.Expr("?", string())
}

// JSONQueryExpression json query expression, implements clause.Expression interface to use as querier
type JSONQueryExpression struct {
	column      string
	keys        []string
	hasKeys     bool
	equals      bool
	likes       bool
	equalsValue interface{}
	extract     bool
	path        string
}

// JSONQuery query column as json
func ( string) *JSONQueryExpression {
	return &JSONQueryExpression{column: }
}

// Extract extract json with path
func ( *JSONQueryExpression) ( string) *JSONQueryExpression {
	.extract = true
	.path = 
	return 
}

// HasKey returns clause.Expression
func ( *JSONQueryExpression) ( ...string) *JSONQueryExpression {
	.keys = 
	.hasKeys = true
	return 
}

// Keys returns clause.Expression
func ( *JSONQueryExpression) ( interface{},  ...string) *JSONQueryExpression {
	.keys = 
	.equals = true
	.equalsValue = 
	return 
}

// Likes return clause.Expression
func ( *JSONQueryExpression) ( interface{},  ...string) *JSONQueryExpression {
	.keys = 
	.likes = true
	.equalsValue = 
	return 
}

// Build implements clause.Expression
func ( *JSONQueryExpression) ( clause.Builder) {
	if ,  := .(*gorm.Statement);  {
		switch .Dialector.Name() {
		case "mysql", "sqlite":
			switch {
			case .extract:
				.WriteString("JSON_EXTRACT(")
				.WriteQuoted(.column)
				.WriteByte(',')
				.AddVar(, prefix+.path)
				.WriteString(")")
			case .hasKeys:
				if len(.keys) > 0 {
					.WriteString("JSON_EXTRACT(")
					.WriteQuoted(.column)
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
					.WriteString(") IS NOT NULL")
				}
			case .equals:
				if len(.keys) > 0 {
					.WriteString("JSON_EXTRACT(")
					.WriteQuoted(.column)
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
					.WriteString(") = ")
					if ,  := .equalsValue.(bool);  {
						.WriteString(strconv.FormatBool())
					} else {
						.AddVar(, .equalsValue)
					}
				}
			case .likes:
				if len(.keys) > 0 {
					.WriteString("JSON_EXTRACT(")
					.WriteQuoted(.column)
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
					.WriteString(") LIKE ")
					if ,  := .equalsValue.(bool);  {
						.WriteString(strconv.FormatBool())
					} else {
						.AddVar(, .equalsValue)
					}
				}
			}
		case "postgres":
			switch {
			case .extract:
				.WriteString(fmt.Sprintf("json_extract_path_text(%v::json,", .Quote(.column)))
				.AddVar(, .path)
				.WriteByte(')')
			case .hasKeys:
				if len(.keys) > 0 {
					.WriteQuoted(.column)
					.WriteString("::jsonb")
					for ,  := range .keys[0 : len(.keys)-1] {
						.WriteString(" -> ")
						.AddVar(, )
					}

					.WriteString(" ? ")
					.AddVar(, .keys[len(.keys)-1])
				}
			case .equals:
				if len(.keys) > 0 {
					.WriteString(fmt.Sprintf("json_extract_path_text(%v::json,", .Quote(.column)))

					for ,  := range .keys {
						if  > 0 {
							.WriteByte(',')
						}
						.AddVar(, )
					}
					.WriteString(") = ")

					if ,  := .equalsValue.(string);  {
						.AddVar(, .equalsValue)
					} else {
						.AddVar(, fmt.Sprint(.equalsValue))
					}
				}
			case .likes:
				if len(.keys) > 0 {
					.WriteString(fmt.Sprintf("json_extract_path_text(%v::json,", .Quote(.column)))

					for ,  := range .keys {
						if  > 0 {
							.WriteByte(',')
						}
						.AddVar(, )
					}
					.WriteString(") LIKE ")

					if ,  := .equalsValue.(string);  {
						.AddVar(, .equalsValue)
					} else {
						.AddVar(, fmt.Sprint(.equalsValue))
					}
				}
			}
		}
	}
}

// JSONOverlapsExpression JSON_OVERLAPS expression, implements clause.Expression interface to use as querier
type JSONOverlapsExpression struct {
	column clause.Expression
	val    string
}

// JSONOverlaps query column as json
func ( clause.Expression,  string) *JSONOverlapsExpression {
	return &JSONOverlapsExpression{
		column: ,
		val:    ,
	}
}

// Build implements clause.Expression
// only mysql support JSON_OVERLAPS
func ( *JSONOverlapsExpression) ( clause.Builder) {
	if ,  := .(*gorm.Statement);  {
		switch .Dialector.Name() {
		case "mysql":
			.WriteString("JSON_OVERLAPS(")
			.column.Build()
			.WriteString(",")
			.AddVar(, .val)
			.WriteString(")")
		}
	}
}

type columnExpression string

func ( string) columnExpression {
	return columnExpression()
}

func ( columnExpression) ( clause.Builder) {
	if ,  := .(*gorm.Statement);  {
		switch .Dialector.Name() {
		case "mysql", "sqlite", "postgres":
			.WriteString(.Quote(string()))
		}
	}
}

const prefix = "$."

func jsonQueryJoin( []string) string {
	if len() == 1 {
		return prefix + [0]
	}

	 := len(prefix)
	 += len() - 1
	for  := 0;  < len(); ++ {
		 += len([])
	}

	var  strings.Builder
	.Grow()
	.WriteString(prefix)
	.WriteString([0])
	for ,  := range [1:] {
		.WriteString(".")
		.WriteString()
	}
	return .String()
}

// JSONSetExpression json set expression, implements clause.Expression interface to use as updater
type JSONSetExpression struct {
	column     string
	path2value map[string]interface{}
	mutex      sync.RWMutex
}

// JSONSet update fields of json column
func ( string) *JSONSetExpression {
	return &JSONSetExpression{column: , path2value: make(map[string]interface{})}
}

// Set return clause.Expression.
//
//	{
//		"age": 20,
//		"name": "json-1",
//		"orgs": {"orga": "orgv"},
//		"tags": ["tag1", "tag2"]
//	}
//
//	// In MySQL/SQLite, path is `age`, `name`, `orgs.orga`, `tags[0]`, `tags[1]`.
//	DB.UpdateColumn("attr", JSONSet("attr").Set("orgs.orga", 42))
//
//	// In PostgreSQL, path is `{age}`, `{name}`, `{orgs,orga}`, `{tags, 0}`, `{tags, 1}`.
//	DB.UpdateColumn("attr", JSONSet("attr").Set("{orgs, orga}", "bar"))
func ( *JSONSetExpression) ( string,  interface{}) *JSONSetExpression {
	.mutex.Lock()
	.path2value[] = 
	.mutex.Unlock()
	return 
}

// Build implements clause.Expression
// support mysql, sqlite and postgres
func ( *JSONSetExpression) ( clause.Builder) {
	if ,  := .(*gorm.Statement);  {
		switch .Dialector.Name() {
		case "mysql":

			var  bool
			if ,  := .Dialector.(*mysql.Dialector);  {
				 = strings.Contains(.ServerVersion, "MariaDB")
			}

			.WriteString("JSON_SET(")
			.WriteQuoted(.column)
			for ,  := range .path2value {
				.WriteByte(',')
				.AddVar(, prefix+)
				.WriteByte(',')

				if ,  := .(clause.Expression);  {
					.AddVar(, )
					continue
				}

				 := reflect.ValueOf()
				if .Kind() == reflect.Ptr {
					 = .Elem()
				}
				switch .Kind() {
				case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map:
					,  := json.Marshal()
					if  {
						.AddVar(, string())
						break
					}
					.AddVar(, gorm.Expr("CAST(? AS JSON)", string()))
				case reflect.Bool:
					.WriteString(strconv.FormatBool(.Bool()))
				default:
					.AddVar(, )
				}
			}
			.WriteString(")")

		case "sqlite":
			.WriteString("JSON_SET(")
			.WriteQuoted(.column)
			for ,  := range .path2value {
				.WriteByte(',')
				.AddVar(, prefix+)
				.WriteByte(',')

				if ,  := .(clause.Expression);  {
					.AddVar(, )
					continue
				}

				 := reflect.ValueOf()
				if .Kind() == reflect.Ptr {
					 = .Elem()
				}
				switch .Kind() {
				case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map:
					,  := json.Marshal()
					.AddVar(, gorm.Expr("JSON(?)", string()))
				default:
					.AddVar(, )
				}
			}
			.WriteString(")")

		case "postgres":
			var  clause.Expression = columnExpression(.column)
			for ,  := range .path2value {
				if _,  = .(clause.Expression);  {
					 = gorm.Expr("JSONB_SET(?,?,?)", , , )
					continue
				} else {
					,  := json.Marshal()
					 = gorm.Expr("JSONB_SET(?,?,?)", , , string())
				}
			}
			.AddVar(, )
		}
	}
}

func ( string) *JSONArrayExpression {
	return &JSONArrayExpression{
		column: ,
	}
}

type JSONArrayExpression struct {
	contains    bool
	in          bool
	column      string
	keys        []string
	equalsValue interface{}
}

// Contains checks if column[keys] contains the value given. The keys parameter is only supported for MySQL and SQLite.
func ( *JSONArrayExpression) ( interface{},  ...string) *JSONArrayExpression {
	.contains = true
	.equalsValue = 
	.keys = 
	return 
}

// In checks if columns[keys] is in the array value given. This method is only supported for MySQL and SQLite.
func ( *JSONArrayExpression) ( interface{},  ...string) *JSONArrayExpression {
	.in = true
	.keys = 
	.equalsValue = 
	return 
}

// Build implements clause.Expression
func ( *JSONArrayExpression) ( clause.Builder) {
	if ,  := .(*gorm.Statement);  {
		switch .Dialector.Name() {
		case "mysql":
			switch {
			case .contains:
				.WriteString("JSON_CONTAINS(" + .Quote(.column) + ",JSON_ARRAY(")
				.AddVar(, .equalsValue)
				.WriteByte(')')
				if len(.keys) > 0 {
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
				}
				.WriteByte(')')
			case .in:
				.WriteString("JSON_CONTAINS(JSON_ARRAY")
				.AddVar(, .equalsValue)
				.WriteByte(',')
				if len(.keys) > 0 {
					.WriteString("JSON_EXTRACT(")
				}
				.WriteQuoted(.column)
				if len(.keys) > 0 {
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
					.WriteByte(')')
				}
				.WriteByte(')')
			}
		case "sqlite":
			switch {
			case .contains:
				.WriteString("EXISTS(SELECT 1 FROM json_each(")
				.WriteQuoted(.column)
				if len(.keys) > 0 {
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
				}
				.WriteString(") WHERE value = ")
				.AddVar(, .equalsValue)
				.WriteString(") AND json_array_length(")
				.WriteQuoted(.column)
				if len(.keys) > 0 {
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
				}
				.WriteString(") > 0")
			case .in:
				.WriteString("CASE WHEN json_type(")
				.WriteQuoted(.column)
				if len(.keys) > 0 {
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
				}
				.WriteString(") = 'array' THEN NOT EXISTS(SELECT 1 FROM json_each(")
				.WriteQuoted(.column)
				if len(.keys) > 0 {
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
				}
				.WriteString(") WHERE value NOT IN ")
				.AddVar(, .equalsValue)
				.WriteString(") ELSE ")
				if len(.keys) > 0 {
					.WriteString("json_extract(")
				}
				.WriteQuoted(.column)
				if len(.keys) > 0 {
					.WriteByte(',')
					.AddVar(, jsonQueryJoin(.keys))
					.WriteByte(')')
				}
				.WriteString(" IN ")
				.AddVar(, .equalsValue)
				.WriteString(" END")
			}
		case "postgres":
			switch {
			case .contains:
				.WriteString(.Quote(.column))
				.WriteString(" ? ")
				.AddVar(, .equalsValue)
			}
		}
	}
}