package mo

import (
	"encoding/xml"
	"fmt"
)

type Type byte

// https://docs.mongodb.com/manual/reference/bson-types/
const (
	TypeUndefined Type = 0

	TypeDouble     Type = 0x01 // float64
	TypeString     Type = 0x02 // string
	TypeObject     Type = 0x03 // M
	TypeArray      Type = 0x04 // A
	TypeBinary     Type = 0x05 // Binary reference https://bsonspec.org/spec.html subtype
	TypeObjectId   Type = 0x07 // ObjectID
	TypeBoolean    Type = 0x08 // bool
	TypeDateTime   Type = 0x09 // DateTime
	TypeNull       Type = 0x0A // Null represents the BSON null value.
	TypeRegex      Type = 0x0B // Regex
	TypeJavaScript Type = 0x0D // JavaScript
	TypeInt32      Type = 0x10 // int32
	TypeTimestamp  Type = 0x11 // Timestamp DO NOT USE, for internal MongoDB only: https://docs.mongodb.com/manual/reference/bson-types/#timestamps
	TypeInt64      Type = 0x12 // int64
	TypeDecimal128 Type = 0x13 // Decimal128
	TypeMinKey     Type = 0xFF // MinKey
	TypeMaxKey     Type = 0x7F // MaxKey

	TypeFloat64 = TypeDouble // alias
	TypeMap     = TypeObject
	TypeSlice   = TypeArray
	TypeBool    = TypeBoolean
)

var nameType = map[Type]string{
	TypeDouble:     "double",
	TypeString:     "string",
	TypeObject:     "object",
	TypeArray:      "array",
	TypeBinary:     "binary",
	TypeObjectId:   "objectId",
	TypeBoolean:    "boolean",
	TypeDateTime:   "datetime",
	TypeNull:       "null",
	TypeRegex:      "regex",
	TypeJavaScript: "javascript",
	TypeInt32:      "int32",
	TypeTimestamp:  "timestamp",
	TypeInt64:      "int64",
	TypeDecimal128: "decimal128",
	TypeMinKey:     "minKey",
	TypeMaxKey:     "maxKey",
}

var typeName = map[string]Type{
	"double":     TypeDouble,
	"string":     TypeString,
	"object":     TypeObject,
	"array":      TypeArray,
	"binData":    TypeBinary,
	"objectId":   TypeObjectId,
	"bool":       TypeBoolean,
	"date":       TypeDateTime,
	"null":       TypeNull,
	"regex":      TypeRegex,
	"javascript": TypeJavaScript,
	"int":        TypeInt32,
	"timestamp":  TypeTimestamp,
	"long":       TypeInt64,
	"decimal128": TypeDecimal128,
	"minKey":     TypeMinKey,
	"maxKey":     TypeMaxKey,

	// alias
	"float64":  TypeDouble,
	"float":    TypeDouble,
	"map":      TypeObject,
	"slice":    TypeArray,
	"boolean":  TypeBoolean,
	"binary":   TypeBinary,
	"datetime": TypeDateTime,
	"int32":    TypeInt32,
	"int64":    TypeInt64,
}

func (t *Type) UnmarshalXMLAttr(attr xml.Attr) error {
	if v, ok := typeName[attr.Value]; ok {
		*t = v
		return nil
	}
	return fmt.Errorf("unknown mo.Type(%s)", attr.Value)
}

func (t *Type) String() string {
	if v, ok := nameType[*t]; ok {
		return fmt.Sprintf("mo.Type(%s)", v)
	}
	return fmt.Sprintf("mo.Type(%d)", t)
}

func (t *Type) Default() any {
	switch *t {
	case TypeDouble:
		return float64(0)
	case TypeString:
		return ""
	case TypeObject:
		return M{}
	case TypeArray:
		return A{}
	case TypeBinary:
		return Binary{}
	case TypeObjectId:
		return NilObjectID
	case TypeBoolean:
		return false
	case TypeDateTime:
		return DateTime(0)
	case TypeNull:
		return Null{}
	case TypeRegex:
		return Regex{}
	case TypeJavaScript:
		return JavaScript("")
	case TypeInt32:
		return int32(0)
	case TypeTimestamp:
		return Timestamp{}
	case TypeInt64:
		return int64(0)
	case TypeDecimal128:
		return NewDecimal128(0, 0)
	case TypeMinKey:
		return MinKey{}
	case TypeMaxKey:
		return MaxKey{}
	default:
		panic("unknown type")
	}
}

const (
	DefaultDbName = "test"

	// ISODate 作为 DateTime 字符串时间模板, 来自 time.RFC3339 增加毫秒并移除 +7 偏移量, 见 time/format.go:96
	ISODate = "2006-01-02T15:04:05.000Z"
)

// https://www.mongodb.com/docs/v6.0/reference/operator/aggregation-pipeline/#aggregation-pipeline-stages
const (
	ArgMatch     = "$match"
	ArgLookup    = "$lookup"
	ArgProject   = "$project"
	ArgGroup     = "$group"
	ArgSort      = "$sort"
	ArgLimit     = "$limit"
	ArgSkip      = "$skip"
	ArgSet       = "$set"
	ArgDocuments = "$documents"
)

// https://www.mongodb.com/docs/v6.0/reference/operator/aggregation/#aggregation-pipeline-operators
const (
	OptAdd = "$add"
	OptSum = "$sum"
)

const (
	OptCurrentDate = "$currentDate"
)

const (
	OptSet         = ArgSet
	OptUnset       = "$unset"
	OptSetOnInsert = "$setOnInsert"
)

const (
	OptPush    = "$push"
	OptPull    = "$pull"
	OptPullAll = "$pullAll"
)
