package reflectext

import (
	"encoding/json"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"reflect"
)

type ConvertStructToMapOpt struct {
	KeepJsonMarshalTypes bool
}

func ConvertStructToMap(v any, opts ...ConvertStructToMapOpt) any {
	opt := ConvertStructToMapOpt{}
	if len(opts) > 0 {
		opt = opts[0]
	}

	res := reflectToMap(reflect.ValueOf(v), opt)

	if v, ok := res.(map[string]any); ok {
		return v
	} else if langext.IsNil(res) {
		return nil
	} else {
		panic("not an object")
	}
}

func reflectToMap(fv reflect.Value, opt ConvertStructToMapOpt) any {

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

		if fv.IsNil() {
			return nil
		} else {
			return reflectToMap(fv.Elem(), opt)
		}

	}

	if fv.Kind() == reflect.Func {

		// skip
		return nil

	}

	if fv.Kind() == reflect.Array {

		arrlen := fv.Len()
		arr := make([]any, arrlen)
		for i := 0; i < arrlen; i++ {
			arr[i] = reflectToMap(fv.Index(i), opt)
		}
		return arr

	}

	if fv.Kind() == reflect.Slice {

		arrlen := fv.Len()
		arr := make([]any, arrlen)
		for i := 0; i < arrlen; i++ {
			arr[i] = reflectToMap(fv.Index(i), opt)
		}
		return arr

	}

	if fv.Kind() == reflect.Chan {

		// skip
		return nil

	}

	if fv.Kind() == reflect.Struct {

		if opt.KeepJsonMarshalTypes && fv.Type().Implements(reflect.TypeFor[json.Marshaler]()) {
			return fv.Interface()
		}

		res := make(map[string]any)

		for i := 0; i < fv.NumField(); i++ {
			if fv.Type().Field(i).IsExported() {
				res[fv.Type().Field(i).Name] = reflectToMap(fv.Field(i), opt)
			}
		}

		return res

	}

	return fv.Interface()
}