// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.

package utils

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"math/big"
	"reflect"
	"strconv"
	"strings"
	"time"
	"unsafe"

	"github.com/LukeHagar/plexgo/types"
)

func MarshalJSON(v interface{}, tag reflect.StructTag, topLevel bool) ([]byte, error) {
	typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v))

	switch {
	case isModelType(typ):
		if topLevel {
			return json.Marshal(v)
		}

		if isNil(typ, val) {
			return []byte("null"), nil
		}

		out := map[string]json.RawMessage{}

		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			fieldVal := val.Field(i)

			fieldName := field.Name

			omitEmpty := false
			jsonTag := field.Tag.Get("json")
			if jsonTag != "" {
				for _, tag := range strings.Split(jsonTag, ",") {
					if tag == "omitempty" {
						omitEmpty = true
					} else {
						fieldName = tag
					}
				}
			}

			if isNil(field.Type, fieldVal) && field.Tag.Get("const") == "" {
				if omitEmpty {
					continue
				}
			}

			if !field.IsExported() && field.Tag.Get("const") == "" {
				continue
			}

			additionalProperties := field.Tag.Get("additionalProperties")
			if fieldName == "-" && additionalProperties == "" {
				continue
			}

			if additionalProperties == "true" {
				if isNil(field.Type, fieldVal) {
					continue
				}
				fieldVal := trueReflectValue(fieldVal)
				if fieldVal.Type().Kind() != reflect.Map {
					return nil, fmt.Errorf("additionalProperties must be a map")
				}

				for _, key := range fieldVal.MapKeys() {
					r, err := marshalValue(fieldVal.MapIndex(key).Interface(), field.Tag)
					if err != nil {
						return nil, err
					}

					out[key.String()] = r
				}

				continue
			}

			var fv interface{}

			if field.IsExported() {
				fv = fieldVal.Interface()
			} else {
				pt := reflect.New(typ).Elem()
				pt.Set(val)

				pf := pt.Field(i)

				fv = reflect.NewAt(pf.Type(), unsafe.Pointer(pf.UnsafeAddr())).Elem().Interface()
			}

			r, err := marshalValue(fv, field.Tag)
			if err != nil {
				return nil, err
			}

			out[fieldName] = r
		}

		return json.Marshal(out)
	default:
		return marshalValue(v, tag)
	}
}

func UnmarshalJSON(b []byte, v interface{}, tag reflect.StructTag, topLevel bool, requiredFields []string) error {
	if reflect.TypeOf(v).Kind() != reflect.Ptr {
		return errors.New("v must be a pointer")
	}

	typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v))

	switch {
	case isModelType(typ):
		if topLevel || bytes.Equal(b, []byte("null")) {
			return json.Unmarshal(b, v)
		}

		var unmarshaled map[string]json.RawMessage

		if err := json.Unmarshal(b, &unmarshaled); err != nil {
			return err
		}

		missingFields := []string{}
		for _, requiredField := range requiredFields {
			if _, ok := unmarshaled[requiredField]; !ok {
				missingFields = append(missingFields, requiredField)
			}
		}
		if len(missingFields) > 0 {
			return fmt.Errorf("missing required fields: %s", strings.Join(missingFields, ", "))
		}

		var additionalPropertiesField *reflect.StructField
		var additionalPropertiesValue *reflect.Value

		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			fieldVal := val.Field(i)

			fieldName := field.Name

			jsonTag := field.Tag.Get("json")
			if jsonTag != "" {
				for _, tag := range strings.Split(jsonTag, ",") {
					if tag != "omitempty" {
						fieldName = tag
					}
				}
			}

			if field.Tag.Get("additionalProperties") == "true" {
				additionalPropertiesField = &field
				additionalPropertiesValue = &fieldVal
				continue
			}

			// If we receive a value for a const field ignore it but mark it as unmarshaled
			if field.Tag.Get("const") != "" {
				if r, ok := unmarshaled[fieldName]; ok {
					val := string(r)

					if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) {
						var err error
						val, err = strconv.Unquote(val)
						if err != nil {
							return fmt.Errorf("failed to unquote const field `%s` value `%s`: %w", fieldName, val, err)
						}
					}
					constValue := field.Tag.Get("const")
					if val != constValue {
						return fmt.Errorf("const field `%s` does not match expected value `%s` got `%s`", fieldName, constValue, val)
					}

					delete(unmarshaled, fieldName)
				}
			} else if !field.IsExported() {
				continue
			}

			value, ok := unmarshaled[fieldName]
			if !ok {
				defaultTag, defaultOk := field.Tag.Lookup("default")
				if defaultOk {
					value = handleDefaultConstValue(defaultTag, fieldVal.Interface(), field.Tag)
					ok = true
				}
			} else {
				delete(unmarshaled, fieldName)
			}

			if ok {
				if err := unmarshalValue(value, fieldVal, field.Tag); err != nil {
					return err
				}
			}
		}

		keys := make([]string, 0, len(unmarshaled))
		for k := range unmarshaled {
			keys = append(keys, k)
		}

		if len(keys) > 0 {
			if additionalPropertiesField != nil && additionalPropertiesValue != nil {
				typeOfMap := additionalPropertiesField.Type
				if additionalPropertiesValue.Type().Kind() == reflect.Interface {
					typeOfMap = reflect.TypeOf(map[string]interface{}{})
				} else if additionalPropertiesValue.Type().Kind() != reflect.Map {
					return fmt.Errorf("additionalProperties must be a map")
				}

				mapValue := reflect.MakeMap(typeOfMap)

				for key, value := range unmarshaled {
					val := reflect.New(typeOfMap.Elem())

					if err := unmarshalValue(value, val, additionalPropertiesField.Tag); err != nil {
						return err
					}

					if val.Elem().Type().String() == typeOfMap.Elem().String() {
						mapValue.SetMapIndex(reflect.ValueOf(key), val.Elem())
					} else {
						mapValue.SetMapIndex(reflect.ValueOf(key), trueReflectValue(val))
					}

				}
				if additionalPropertiesValue.Type().Kind() == reflect.Interface {
					additionalPropertiesValue.Set(mapValue)
				} else {
					additionalPropertiesValue.Set(mapValue)
				}
			}
		}
	default:
		return unmarshalValue(b, reflect.ValueOf(v), tag)
	}

	return nil
}

func marshalValue(v interface{}, tag reflect.StructTag) (json.RawMessage, error) {
	constTag := tag.Get("const")
	if constTag != "" {
		return handleDefaultConstValue(constTag, v, tag), nil
	}

	if isNil(reflect.TypeOf(v), reflect.ValueOf(v)) {
		defaultTag, ok := tag.Lookup("default")
		if ok {
			return handleDefaultConstValue(defaultTag, v, tag), nil
		}

		return []byte("null"), nil
	}

	typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v))
	switch typ.Kind() {
	case reflect.Int64:
		format := tag.Get("integer")
		if format == "string" {
			b := val.Interface().(int64)
			return []byte(fmt.Sprintf(`"%d"`, b)), nil
		}
	case reflect.Float64:
		format := tag.Get("number")
		if format == "string" {
			b := val.Interface().(float64)
			return []byte(fmt.Sprintf(`"%g"`, b)), nil
		}
	case reflect.Map:
		if isNil(typ, val) {
			return []byte("null"), nil
		}

		// Check if the map implements json.Marshaler (like optionalnullable.OptionalNullable[T])
		if marshaler, ok := val.Interface().(json.Marshaler); ok {
			return marshaler.MarshalJSON()
		}

		out := map[string]json.RawMessage{}

		for _, key := range val.MapKeys() {
			itemVal := val.MapIndex(key)

			if isNil(itemVal.Type(), itemVal) {
				out[key.String()] = []byte("null")
				continue
			}

			r, err := marshalValue(itemVal.Interface(), tag)
			if err != nil {
				return nil, err
			}

			out[key.String()] = r
		}

		return json.Marshal(out)
	case reflect.Slice, reflect.Array:
		if isNil(typ, val) {
			return []byte("null"), nil
		}

		out := []json.RawMessage{}

		for i := 0; i < val.Len(); i++ {
			itemVal := val.Index(i)

			if isNil(itemVal.Type(), itemVal) {
				out = append(out, []byte("null"))
				continue
			}

			r, err := marshalValue(itemVal.Interface(), tag)
			if err != nil {
				return nil, err
			}

			out = append(out, r)
		}

		return json.Marshal(out)
	case reflect.Struct:
		switch typ {
		case reflect.TypeOf(time.Time{}):
			return []byte(fmt.Sprintf(`"%s"`, val.Interface().(time.Time).Format(time.RFC3339Nano))), nil
		case reflect.TypeOf(big.Int{}):
			format := tag.Get("bigint")
			if format == "string" {
				b := val.Interface().(big.Int)
				return []byte(fmt.Sprintf(`"%s"`, (&b).String())), nil
			}
		}
	}

	return json.Marshal(v)
}

func handleDefaultConstValue(tagValue string, val interface{}, tag reflect.StructTag) json.RawMessage {
	if tagValue == "null" {
		return []byte("null")
	}

	typ := dereferenceTypePointer(reflect.TypeOf(val))
	switch typ {
	case reflect.TypeOf(time.Time{}):
		return []byte(fmt.Sprintf(`"%s"`, tagValue))
	case reflect.TypeOf(big.Int{}):
		bigIntTag := tag.Get("bigint")
		if bigIntTag == "string" {
			return []byte(fmt.Sprintf(`"%s"`, tagValue))
		}
	case reflect.TypeOf(int64(0)):
		format := tag.Get("integer")
		if format == "string" {
			return []byte(fmt.Sprintf(`"%s"`, tagValue))
		}
	case reflect.TypeOf(float64(0)):
		format := tag.Get("number")
		if format == "string" {
			return []byte(fmt.Sprintf(`"%s"`, tagValue))
		}
	case reflect.TypeOf(types.Date{}):
		return []byte(fmt.Sprintf(`"%s"`, tagValue))
	default:
		if typ.Kind() == reflect.String {
			return []byte(fmt.Sprintf("%q", tagValue))
		}
	}

	return []byte(tagValue)
}

func unmarshalValue(value json.RawMessage, v reflect.Value, tag reflect.StructTag) error {
	if bytes.Equal(value, []byte("null")) {
		if v.CanAddr() {
			return json.Unmarshal(value, v.Addr().Interface())
		} else {
			return json.Unmarshal(value, v.Interface())
		}
	}

	typ := dereferenceTypePointer(v.Type())

	switch typ.Kind() {
	case reflect.Int64:
		var b int64

		format := tag.Get("integer")
		if format == "string" {
			var s string
			if err := json.Unmarshal(value, &s); err != nil {
				return err
			}

			var err error
			b, err = strconv.ParseInt(s, 10, 64)
			if err != nil {
				return fmt.Errorf("failed to parse string as int64: %w", err)
			}
			if v.Kind() == reflect.Ptr {
				if v.IsNil() {
					v.Set(reflect.New(typ))
				}
				v = v.Elem()
			}

			v.Set(reflect.ValueOf(b))
			return nil
		}
	case reflect.Float64:
		var b float64

		format := tag.Get("number")
		if format == "string" {
			var s string
			if err := json.Unmarshal(value, &s); err != nil {
				return err
			}

			var err error
			b, err = strconv.ParseFloat(s, 64)
			if err != nil {
				return fmt.Errorf("failed to parse string as float64: %w", err)
			}

			if v.Kind() == reflect.Ptr {
				if v.IsNil() {
					v.Set(reflect.New(typ))
				}
				v = v.Elem()
			}

			v.Set(reflect.ValueOf(b))
			return nil
		}
	case reflect.Map:
		if bytes.Equal(value, []byte("null")) || !isComplexValueType(dereferenceTypePointer(typ.Elem())) {
			if v.CanAddr() {
				return json.Unmarshal(value, v.Addr().Interface())
			} else {
				return json.Unmarshal(value, v.Interface())
			}
		}

		var unmarshaled map[string]json.RawMessage

		if err := json.Unmarshal(value, &unmarshaled); err != nil {
			return err
		}

		m := reflect.MakeMap(typ)

		for k, value := range unmarshaled {
			itemVal := reflect.New(typ.Elem())

			if err := unmarshalValue(value, itemVal, tag); err != nil {
				return err
			}

			m.SetMapIndex(reflect.ValueOf(k), itemVal.Elem())
		}

		v.Set(m)
		return nil
	case reflect.Slice, reflect.Array:
		var unmarshaled []json.RawMessage

		if err := json.Unmarshal(value, &unmarshaled); err != nil {
			return err
		}

		arrVal := reflect.MakeSlice(typ, len(unmarshaled), len(unmarshaled))

		for index, value := range unmarshaled {
			itemVal := reflect.New(typ.Elem())

			if err := unmarshalValue(value, itemVal, tag); err != nil {
				return err
			}

			arrVal.Index(index).Set(itemVal.Elem())
		}

		if v.Kind() == reflect.Pointer {
			if v.IsNil() {
				v.Set(reflect.New(typ))
			}
			v = v.Elem()
		}

		v.Set(arrVal)
		return nil
	case reflect.Struct:
		switch typ {
		case reflect.TypeOf(time.Time{}):
			var s string
			if err := json.Unmarshal(value, &s); err != nil {
				return err
			}

			t, err := time.Parse(time.RFC3339Nano, s)
			if err != nil {
				return fmt.Errorf("failed to parse string as time.Time: %w", err)
			}

			if v.Kind() == reflect.Ptr {
				if v.IsNil() {
					v.Set(reflect.New(typ))
				}
				v = v.Elem()
			}

			v.Set(reflect.ValueOf(t))
			return nil
		case reflect.TypeOf(big.Int{}):
			var b *big.Int

			format := tag.Get("bigint")
			if format == "string" {
				var s string
				if err := json.Unmarshal(value, &s); err != nil {
					return err
				}

				var ok bool
				b, ok = new(big.Int).SetString(s, 10)
				if !ok {
					return fmt.Errorf("failed to parse string as big.Int")
				}
			} else {
				if err := json.Unmarshal(value, &b); err != nil {
					return err
				}
			}

			if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Ptr {
				v = v.Elem()
			}

			v.Set(reflect.ValueOf(b))
			return nil
		case reflect.TypeOf(types.Date{}):
			var s string

			if err := json.Unmarshal(value, &s); err != nil {
				return err
			}

			d, err := types.DateFromString(s)
			if err != nil {
				return fmt.Errorf("failed to parse string as types.Date: %w", err)
			}

			if v.Kind() == reflect.Ptr {
				if v.IsNil() {
					v.Set(reflect.New(typ))
				}
				v = v.Elem()
			}

			v.Set(reflect.ValueOf(d))
			return nil
		}
	}

	var val interface{}

	if v.CanAddr() {
		val = v.Addr().Interface()
	} else {
		val = v.Interface()
	}

	return json.Unmarshal(value, val)
}

func dereferencePointers(typ reflect.Type, val reflect.Value) (reflect.Type, reflect.Value) {
	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		val = val.Elem()
	} else {
		return typ, val
	}

	return dereferencePointers(typ, val)
}

func dereferenceTypePointer(typ reflect.Type) reflect.Type {
	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
	} else {
		return typ
	}

	return dereferenceTypePointer(typ)
}

func isComplexValueType(typ reflect.Type) bool {
	switch typ.Kind() {
	case reflect.Struct:
		switch typ {
		case reflect.TypeOf(time.Time{}):
			fallthrough
		case reflect.TypeOf(big.Int{}):
			fallthrough
		case reflect.TypeOf(types.Date{}):
			return true
		}
	}

	return false
}

func isModelType(typ reflect.Type) bool {
	if isComplexValueType(typ) {
		return false
	}

	if typ.Kind() == reflect.Struct {
		return true
	}

	return false
}
