package maputils

import (
	"errors"
	"strconv"
	"strings"
)

const (
	pathSeparator = "."
)

// GetValueByPath 通过路径查询map的值,常用查找unmarshal到map的json或者yaml中的值
// 参数:
// m: key是string类型的map,值可以是任意类型,如果map下的值还是map,则map的key必须为string类型
// path: 路径,如foo1.foo1_sub.[0],key为foo1的值是一个map[string]any,foo1_sub对应的值是一个slice,如果需要取slice的某一个值,可以用[n]获取
// 返回值:
// 对应的值和错误
func GetValueByPath(m map[string]any, path string) (any, error) {
	if m == nil || strings.TrimSpace(path) == "" {
		return nil, errors.New("没有传递需要查找的数据或路径")
	}

	var retValue any
	var currentFind any

	currentFind = m
	pathParts := strings.Split(path, pathSeparator)
	for _, pathPart := range pathParts {
		if strings.HasPrefix(pathPart, "[") && strings.HasSuffix(pathPart, "]") {
			indexStr := strings.TrimLeft(strings.TrimRight(pathPart, "]"), "[")

			index, err := strconv.Atoi(strings.Trim(indexStr, " "))
			if err != nil {
				return nil, err
			}

			findSlice, ok := currentFind.([]any)
			if ok {
				if index > len(findSlice)-1 {
					retValue = nil
				} else {
					retValue = findSlice[index]
				}
			} else {
				findMapSlice, ok := currentFind.([]map[string]any)
				if !ok {
					return nil, errors.New("对应slice路径的值不是slice: " + pathPart)
				}

				if index > len(findMapSlice)-1 {
					retValue = nil
				} else {
					retValue = findMapSlice[index]
				}
			}
		} else {
			findMap, ok := currentFind.(map[string]any)
			if !ok {
				return nil, errors.New("对应map路径的值不是map[string]any: " + pathPart)
			}

			value, ok := findMap[pathPart]
			if !ok {
				return nil, nil
			}

			retValue = value
		}

		if retValue == nil {
			return nil, nil
		}

		currentFind = retValue
	}

	return retValue, nil
}

// GetValueByKey 通过key查询map的值
// 模板参数:
// K是map的key的类型,V是map值的类型
// 参数:
// m: 从这个map获取值
// key: map的key
// 返回值:
// 对应的值和获取是否成功,失败可能有两个原因:对应的key不存在或者V类型转换失败
func GetValueByKey[K comparable, V any](m map[K]any, key K) (V, bool) {
	var zeroValue V

	mapValue, ok := m[key]
	if !ok {
		return zeroValue, false
	}

	v, ok := mapValue.(V)
	if !ok {
		return zeroValue, false
	}

	return v, true
}