package maputil

import (
	"math"
	"strings"

	"github.com/gookit/goutil/arrutil"
	"github.com/gookit/goutil/internal/comfunc"
	"github.com/gookit/goutil/mathutil"
	"github.com/gookit/goutil/strutil"
)

// Map alias of Data
type Map = Data

// Data alias of map[string]any
type Data map[string]any

// Has value on the data map
func (d Data) Has(key string) bool {
	_, ok := d.GetByPath(key)
	return ok
}

// IsEmpty if the data map
func (d Data) IsEmpty() bool {
	return len(d) == 0
}

//
// endregion
// region T: set value(s)
//

// Set value to the data map
func (d Data) Set(key string, val any) {
	d[key] = val
}

// SetByPath sets a value in the map.
// Supports dot syntax to set deep values.
//
// Example:
//
//	d.SetByPath("name.first", "Mat")
func (d Data) SetByPath(path string, value any) error {
	if path == "" {
		return nil
	}
	return d.SetByKeys(strings.Split(path, KeySepStr), value)
}

// SetByKeys sets a value in the map by path keys.
// Supports dot syntax to set deep values.
//
// Example:
//
//	d.SetByKeys([]string{"name", "first"}, "Mat")
func (d Data) SetByKeys(keys []string, value any) error {
	kln := len(keys)
	if kln == 0 {
		return nil
	}

	// special handle d is empty.
	if len(d) == 0 {
		if kln == 1 {
			d.Set(keys[0], value)
		} else {
			d.Set(keys[0], MakeByKeys(keys[1:], value))
		}
		return nil
	}

	return SetByKeys((*map[string]any)(&d), keys, value)
	// It's ok, but use `func (d *Data)`
	// return SetByKeys((*map[string]any)(d), keys, value)
}

//
// endregion
// region T: read value(s)
//

// Value get from the data map
func (d Data) Value(key string) (any, bool) {
	val, ok := d.GetByPath(key)
	return val, ok
}

// Get value from the data map.
// Supports dot syntax to get deep values. eg: top.sub
func (d Data) Get(key string) any {
	if val, ok := d.GetByPath(key); ok {
		return val
	}
	return nil
}

// One get value from the data by multi paths. will return first founded value
func (d Data) One(keys ...string) any {
	if val, ok := d.TryOne(keys...); ok {
		return val
	}
	return nil
}

// TryOne get value from the data by multi paths. will return first founded value
func (d Data) TryOne(keys ...string) (any, bool) {
	for _, path := range keys {
		if val, ok := d.GetByPath(path); ok {
			return val, true
		}
	}
	return nil, false
}

// GetByPath get value from the data map by path. eg: top.sub
// Supports dot syntax to get deep values.
func (d Data) GetByPath(path string) (any, bool) {
	if val, ok := d[path]; ok {
		return val, true
	}

	// is a key path.
	if strings.ContainsRune(path, '.') {
		val, ok := GetByPath(path, d)
		if ok {
			return val, true
		}
	}
	return nil, false
}

// Default get value from the data map with default value
func (d Data) Default(key string, def any) any {
	if val, ok := d.GetByPath(key); ok {
		return val
	}
	return def
}

// Int value get, or default value
func (d Data) Int(key string, defVal ...int) int {
	if val, ok := d.GetByPath(key); ok {
		return mathutil.SafeInt(val)
	}
	if len(defVal) > 0 {
		return defVal[0]
	}
	return 0
}

// Int64 value get, or default value
func (d Data) Int64(key string, defVal ...int64) int64 {
	if val, ok := d.GetByPath(key); ok {
		return mathutil.SafeInt64(val)
	}
	if len(defVal) > 0 {
		return defVal[0]
	}
	return 0
}

// Uint value get, or default value
func (d Data) Uint(key string, defVal ...uint) uint {
	if val, ok := d.GetByPath(key); ok {
		return mathutil.QuietUint(val)
	}
	if len(defVal) > 0 {
		return defVal[0]
	}
	return 0
}

// Uint16 value get, or default value
func (d Data) Uint16(key string, defVal ...uint16) uint16 {
	if val, ok := d.GetByPath(key); ok {
		u64 := mathutil.QuietUint64(val)
		if u64 <= math.MaxUint16 {
			return uint16(u64)
		}
	}

	if len(defVal) > 0 {
		return defVal[0]
	}
	return 0
}

// Uint64 value get, or default value
func (d Data) Uint64(key string, defVal ...uint64) uint64 {
	if val, ok := d.GetByPath(key); ok {
		return mathutil.QuietUint64(val)
	}
	if len(defVal) > 0 {
		return defVal[0]
	}
	return 0
}

// Str value gets by key, or default value
func (d Data) Str(key string, defVal ...string) string {
	if val, ok := d.GetByPath(key); ok {
		return strutil.SafeString(val)
	}
	if len(defVal) > 0 {
		return defVal[0]
	}
	return ""
}

// StrOne value gets by multi keys, will return first value
func (d Data) StrOne(keys ...string) string {
	for _, key := range keys {
		if val, ok := d.GetByPath(key); ok {
			return strutil.SafeString(val)
		}
	}
	return ""
}

// Bool value get
func (d Data) Bool(key string) bool {
	val, ok := d.GetByPath(key)
	if !ok {
		return false
	}
	return comfunc.Bool(val)
}

// BoolOne value gets from multi keys, return first value
func (d Data) BoolOne(keys ...string) bool {
	for _, key := range keys {
		if val, ok := d.GetByPath(key); ok {
			return comfunc.Bool(val)
		}
	}
	return false
}

// StringsOne get []string value by multi keys, return first founded value
func (d Data) StringsOne(keys ...string) []string {
	for _, key := range keys {
		if val, ok := d.GetByPath(key); ok {
			return arrutil.AnyToStrings(val)
		}
	}
	return nil
}

// Strings get []string value by key
func (d Data) Strings(key string) []string {
	if val, ok := d.GetByPath(key); ok {
		return arrutil.AnyToStrings(val)
	}
	return nil
}

// StrSplit get strings by split string value
func (d Data) StrSplit(key, sep string) []string {
	if val, ok := d.GetByPath(key); ok {
		return strings.Split(strutil.SafeString(val), sep)
	}
	return nil
}

// StringsByStr value gets by key, will split string value by ","
func (d Data) StringsByStr(key string) []string {
	return d.StrSplit(key, ",")
}

// StrMap get map[string]string value
func (d Data) StrMap(key string) map[string]string {
	return d.StringMap(key)
}

// StringMap get map[string]string value
func (d Data) StringMap(key string) map[string]string {
	val, ok := d.GetByPath(key)
	if !ok {
		return nil
	}

	switch tv := val.(type) {
	case map[string]string:
		return tv
	case map[string]any:
		return ToStringMap(tv)
	default:
		return nil
	}
}

// Sub get sub value(map[string]any) as new Data
func (d Data) Sub(key string) Data {
	if val, ok := d.GetByPath(key); ok {
		return d.toAnyMap(val)
	}
	return nil
}

// AnyMap get sub value as map[string]any
func (d Data) AnyMap(key string) map[string]any {
	if val, ok := d.GetByPath(key); ok {
		return d.toAnyMap(val)
	}
	return nil
}

// AnyMap get sub value as map[string]any
func (d Data) toAnyMap(val any) map[string]any {
	switch tv := val.(type) {
	case map[string]string:
		return ToAnyMap(tv)
	case map[string]any:
		return tv
	default:
		return nil
	}
}

// Slice get []any value from data map
func (d Data) Slice(key string) ([]any, error) {
	val, ok := d.GetByPath(key)
	if !ok {
		return nil, nil
	}
	return arrutil.AnyToSlice(val)
}

// Keys of the data map
func (d Data) Keys() []string {
	keys := make([]string, 0, len(d))
	for k := range d {
		keys = append(keys, k)
	}
	return keys
}

// ToStringMap convert to map[string]string
func (d Data) ToStringMap() map[string]string {
	return ToStringMap(d)
}

// String data to string
func (d Data) String() string {
	return ToString(d)
}

// Load other data to current data map
func (d Data) Load(sub map[string]any) {
	for name, val := range sub {
		d[name] = val
	}
}

// LoadSMap to data
func (d Data) LoadSMap(smp map[string]string) {
	for name, val := range smp {
		d[name] = val
	}
}
