package xflags

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

// This function returns the value of a field in a struct, given a path to the field.
func getValueFrom[D any](obj D, keyWithDots string) (interface{}, error) {
	keySlice := strings.Split(keyWithDots, ".")
	v := reflect.ValueOf(obj)

	for _, key := range keySlice[0 : len(keySlice)-1] {
		for v.Kind() == reflect.Ptr {
			v = v.Elem()
		}

		if v.Kind() != reflect.Struct {
			return nil, newUnsupportedTypePathError(keyWithDots, v.Type())
		}

		v = v.FieldByName(key)
	}

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

	// non-supporter type at the top of the path
	if v.Kind() != reflect.Struct {
		return nil, newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type())
	}

	v = v.FieldByName(keySlice[len(keySlice)-1])
	if !v.IsValid() {
		return nil, newInvalidPathError(keyWithDots)
	}

	return v.Interface(), nil

}

// This function sets the value of a field in a struct, given a path to the field.
func setValueUsingPath[D any](obj D, keyWithDots string, newValue string) error {

	keySlice := strings.Split(keyWithDots, ".")
	v := reflect.ValueOf(obj)

	for _, key := range keySlice[0 : len(keySlice)-1] {
		for v.Kind() != reflect.Ptr {
			v = v.Addr()
		}

		if v.Kind() != reflect.Ptr {
			return newUnsupportedTypePathError(keyWithDots, v.Type())
		}

		elem := v.Elem()
		if elem.Kind() != reflect.Struct {
			return newUnsupportedTypePathError(keyWithDots, v.Type())
		}

		v = elem.FieldByName(key)

	}

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

	// non-supporter type at the top of the path
	if v.Kind() != reflect.Struct {
		return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type())
	}

	v = v.FieldByName(keySlice[len(keySlice)-1])
	if !v.IsValid() {
		return newInvalidPathError(keyWithDots)
	}

	if !v.CanSet() {
		return newCannotSetError(keyWithDots)
	}

	switch v.Kind() {
	case reflect.String:
		v.SetString(newValue)
	case reflect.Int:

		s, err := strconv.Atoi(newValue)
		if err != nil {
			return err
		}

		v.SetInt(int64(s))
	case reflect.Bool:
		v.SetBool(newValue == "true")
	case reflect.Float64:

		s, err := strconv.ParseFloat(newValue, 64)
		if err != nil {
			return err
		}

		v.SetFloat(s)
	default:
		return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type())
	}

	return nil

}