package arrutil

import (
	"errors"
	"reflect"
	"strconv"
	"strings"

	"github.com/gookit/goutil/comdef"
	"github.com/gookit/goutil/mathutil"
	"github.com/gookit/goutil/reflects"
	"github.com/gookit/goutil/strutil"
)

// ErrInvalidType error
var ErrInvalidType = errors.New("the input param type is invalid")

/*************************************************************
 * Join func for slice
 *************************************************************/

// JoinStrings alias of strings.Join
func JoinStrings(sep string, ss ...string) string {
	return strings.Join(ss, sep)
}

// StringsJoin alias of strings.Join
func StringsJoin(sep string, ss ...string) string {
	return strings.Join(ss, sep)
}

// JoinTyped join typed []T slice to string.
//
// Usage:
//
//	JoinTyped(",", 1,2,3) // "1,2,3"
//	JoinTyped(",", "a","b","c") // "a,b,c"
//	JoinTyped[any](",", "a",1,"c") // "a,1,c"
func JoinTyped[T any](sep string, arr ...T) string {
	if arr == nil {
		return ""
	}

	var sb strings.Builder
	for i, v := range arr {
		if i > 0 {
			sb.WriteString(sep)
		}
		sb.WriteString(strutil.QuietString(v))
	}

	return sb.String()
}

// JoinSlice join []any slice to string.
func JoinSlice(sep string, arr ...any) string {
	if arr == nil {
		return ""
	}

	var sb strings.Builder
	for i, v := range arr {
		if i > 0 {
			sb.WriteString(sep)
		}
		sb.WriteString(strutil.QuietString(v))
	}

	return sb.String()
}

/*************************************************************
 * convert func for ints
 *************************************************************/

// IntsToString convert []T to string
func IntsToString[T comdef.Integer](ints []T) string {
	if len(ints) == 0 {
		return "[]"
	}

	var sb strings.Builder
	sb.WriteByte('[')
	for i, v := range ints {
		if i > 0 {
			sb.WriteByte(',')
		}
		sb.WriteString(strconv.FormatInt(int64(v), 10))
	}
	sb.WriteByte(']')
	return sb.String()
}

// ToInt64s convert any(allow: array,slice) to []int64
func ToInt64s(arr any) (ret []int64, err error) {
	rv := reflect.ValueOf(arr)
	if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
		err = ErrInvalidType
		return
	}

	for i := 0; i < rv.Len(); i++ {
		i64, err := mathutil.Int64(rv.Index(i).Interface())
		if err != nil {
			return []int64{}, err
		}

		ret = append(ret, i64)
	}
	return
}

// MustToInt64s convert any(allow: array,slice) to []int64
func MustToInt64s(arr any) []int64 {
	ret, _ := ToInt64s(arr)
	return ret
}

// SliceToInt64s convert []any to []int64
func SliceToInt64s(arr []any) []int64 {
	i64s := make([]int64, len(arr))
	for i, v := range arr {
		i64s[i] = mathutil.QuietInt64(v)
	}
	return i64s
}

// ToMap convert a list to new map.
//
// Example:
//  type User struct {
//		Name string
//		Age  int
//	}
//	users := []User{{"Tom", 18}, {"Jack", 20}}
//	mp := arrutil.ToMap(users, func(u User) (string, int) {
//		return u.Name, u.Age
//	})
//  // mp = map[string]int{"Tom":18, "Jack":20}
func ToMap[T any, K comdef.ScalarType, V any](list []T, mapFn func(T) (K, V)) map[K]V {
	mp := make(map[K]V, len(list))
	for _, item := range list {
		k, v := mapFn(item)
		mp[k] = v
	}
	return mp
}

/*************************************************************
 * convert func for any-slice
 *************************************************************/

// AnyToSlice convert any(allow: array,slice) to []any
func AnyToSlice(sl any) (ls []any, err error) {
	rfKeys := reflect.ValueOf(sl)
	if rfKeys.Kind() != reflect.Slice && rfKeys.Kind() != reflect.Array {
		return nil, ErrInvalidType
	}

	for i := 0; i < rfKeys.Len(); i++ {
		ls = append(ls, rfKeys.Index(i).Interface())
	}
	return
}

// AnyToStrings convert array or slice to []string
func AnyToStrings(arr any) []string {
	ret, _ := ToStrings(arr)
	return ret
}

// MustToStrings convert array or slice to []string
func MustToStrings(arr any) []string {
	ret, err := ToStrings(arr)
	if err != nil {
		panic(err)
	}
	return ret
}

// ToStrings convert any(allow: array,slice) to []string
func ToStrings(arr any) (ret []string, err error) {
	// try direct convert
	switch typVal := arr.(type) {
	case string:
		return []string{typVal}, nil
	case []string:
		return typVal, nil
	case []any:
		return SliceToStrings(typVal), nil
	}

	// try use reflect to convert
	rv := reflect.ValueOf(arr)
	if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
		err = ErrInvalidType
		return
	}

	for i := 0; i < rv.Len(); i++ {
		str, err1 := strutil.ToString(rv.Index(i).Interface())
		if err1 != nil {
			return nil, err1
		}
		ret = append(ret, str)
	}
	return
}

// SliceToStrings safe convert []any to []string
func SliceToStrings(arr []any) []string {
	ss := make([]string, len(arr))
	for i, v := range arr {
		ss[i] = strutil.SafeString(v)
	}
	return ss
}

// QuietStrings safe convert []any to []string
func QuietStrings(arr []any) []string { return SliceToStrings(arr) }

// ConvType convert type of slice elements to new type slice, by the given newElemTyp type.
//
// Supports conversion between []string, []intX, []uintX, []floatX.
//
// Usage:
//
//	ints, _ := arrutil.ConvType([]string{"12", "23"}, 1) // []int{12, 23}
func ConvType[T any, R any](arr []T, newElemTyp R) ([]R, error) {
	newArr := make([]R, len(arr))
	elemTyp := reflect.TypeOf(newElemTyp)

	for i, elem := range arr {
		var anyElem any = elem
		// type is same.
		if _, ok := anyElem.(R); ok {
			newArr[i] = anyElem.(R)
			continue
		}

		// need conv type.
		rfVal, err := reflects.ValueByType(elem, elemTyp)
		if err != nil {
			return nil, err
		}
		newArr[i] = rfVal.Interface().(R)
	}
	return newArr, nil
}

// AnyToString simple and quickly convert any array, slice to string
func AnyToString(arr any) string {
	return NewFormatter(arr).Format()
}

// SliceToString convert []any to string
func SliceToString(arr ...any) string { return ToString(arr) }

// ToString simple and quickly convert []T to string
func ToString[T any](arr []T) string {
	// like fmt.Println([]any(nil))
	if arr == nil {
		return "[]"
	}

	var sb strings.Builder
	sb.WriteByte('[')

	for i, v := range arr {
		if i > 0 {
			sb.WriteByte(',')
		}
		sb.WriteString(strutil.SafeString(v))
	}

	sb.WriteByte(']')
	return sb.String()
}

// CombineToMap combine []K and []V slice to map[K]V.
//
// If keys length is greater than values, the extra keys will be ignored.
func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V {
	ln := len(values)
	mp := make(map[K]V, len(keys))

	for i, key := range keys {
		if i >= ln {
			break
		}
		mp[key] = values[i]
	}
	return mp
}

// CombineToSMap combine two string-slice to map[string]string
func CombineToSMap(keys, values []string) map[string]string {
	ln := len(values)
	mp := make(map[string]string, len(keys))

	for i, key := range keys {
		if ln > i {
			mp[key] = values[i]
		} else {
			mp[key] = ""
		}
	}
	return mp
}
