// Copyright 2022 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0

package pathfinder

import (
	"reflect"
	"strconv"
	"strings"
)

// GetValue returns the value of a field in a struct, given a path to the field.
// The path can contain dots to access nested fields.
// The object must be a pointer to a struct, a struct, a map, a slice or an array,
// otherwise an error is returned.
func GetValue[D any](obj D, keyWithDots string) (any, error) {
	keySlice := strings.Split(keyWithDots, ".")
	v := reflect.ValueOf(obj)

	for _, key := range keySlice[0:] {

		if !v.IsValid() {
			return nil, newInvalidPathError(keyWithDots)
		}

		switch v.Kind() {
		case reflect.Ptr, reflect.Interface:
			v = v.Elem()
		}

		switch v.Kind() {
		case reflect.Map:
			v = v.MapIndex(reflect.ValueOf(key))
			if !v.IsValid() {
				return nil, newInvalidPathError(keyWithDots)
			}

		case reflect.Slice, reflect.Array:
			index, err := strconv.Atoi(key)
			if err != nil {
				return nil, newInvalidPathError(keyWithDots)
			}
			// check if index is in range
			if index >= v.Len() {
				return nil, newInvalidPathError(keyWithDots)
			}
			v = v.Index(index)
		case reflect.Struct:
			v = v.FieldByName(key)
			if !v.IsValid() {
				return nil, newInvalidPathError(keyWithDots)
			}
		default:
			return nil, newInvalidPathError(keyWithDots)
		}

	}

	if v.Kind() == reflect.Invalid {
		return nil, newInvalidPathError(keyWithDots)
	}

	//for v.Kind() == reflect.Ptr {
	//	v = v.Elem()
	//}

	// check if v can interface
	if !v.CanInterface() {
		return nil, newInvalidPathError(keyWithDots)
	}

	return v.Interface(), nil

}