package reflectext

import (
	"errors"
	"fmt"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"reflect"
)

// PrimitiveStringSerializer is used to serialize primitive types (and a few more) from and to string
// This is not really intended to be user facing, and more as a simple building block for other mechanisms
// supports:
//   - golang primitives (ints, uints, floats, bool, string)
//   - type aliases
//   - time.Time
//   - primitive.ObjectID
type PrimitiveStringSerializer struct{}

func (pss PrimitiveStringSerializer) ValueToString(v any) (string, error) {

	inType := reflect.TypeOf(v)

	if inType.Kind() == reflect.Ptr && langext.IsNil(v) {
		return "", nil
	}

	if inType.Kind() == reflect.Ptr {
		rval1 := reflect.ValueOf(v)
		rval2 := rval1.Elem()
		rval3 := rval2.Interface()
		return pss.ValueToString(rval3)
	}

	if conv, ok := primitiveSerializer[inType]; ok {
		return conv.ToString(v)
	}

	for convType, conv := range primitiveSerializer {
		if castV, ok := TryCastType(v, convType); ok {
			return conv.ToString(castV)
		}
	}

	return "", errors.New(fmt.Sprintf("failed to find a matching generic <toString> conversion fo type %T", v))
}

func (pss PrimitiveStringSerializer) ValueFromString(str string, outType reflect.Type) (any, error) {

	if outType.Kind() == reflect.Ptr && str == "" {
		return reflect.Zero(outType).Interface(), nil // = nil.(outType), nil
	}

	if str == "" {
		return reflect.Zero(outType).Interface(), nil // = <default>(outType), nil
	}

	if outType.Kind() == reflect.Ptr {

		innerValue, err := pss.ValueFromString(str, outType.Elem())
		if err != nil {
			return nil, err
		}

		// this weird piece of shit converts innerValue to &innerValue  (while keeping types)

		rval1 := reflect.ValueOf(innerValue)
		rval2 := rval1.Convert(outType.Elem())
		rval3 := reflect.New(outType.Elem())
		rval3.Elem().Set(rval2)
		rval4 := rval3.Interface()

		return rval4, nil
	}

	if conv, ok := primitiveSerializer[outType]; ok {
		return conv.FromString(str)
	}

	emptyResultVal := reflect.Zero(outType).Interface()

	for convType, conv := range primitiveSerializer {
		if _, ok := TryCastType(emptyResultVal, convType); ok {
			if convVal, err := conv.FromString(str); err == nil {
				if resVal, ok := TryCastType(convVal, outType); ok {
					return resVal, nil
				}
			}
		}
	}

	return "", errors.New(fmt.Sprintf("failed to find a matching generic <toString> conversion fo type %s", outType.String()))
}
