// 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
			omitZero := false
			jsonTag := field.Tag.Get("json")
			if jsonTag != "" {
				for _, tag := range strings.Split(jsonTag, ",") {
					if tag == "omitempty" {
						omitEmpty = true
					} else if tag == "omitzero" {
						omitZero = true
					} else {
						fieldName = tag
					}
				}
			}

			if (omitEmpty || omitZero) && field.Tag.Get("const") == "" {
				// Both omitempty and omitzero skip zero values (including nil)
				if isNil(field.Type, fieldVal) {
					continue
				}

				if omitZero && fieldVal.IsZero() {
					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" && tag != "omitzero" {
						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
}

// CalculateJSONSize returns the byte size of the JSON representation of a value.
// This is used to determine which union type variant has the most data.
func CalculateJSONSize(v interface{}) int {
	data, err := json.Marshal(v)
	if err != nil {
		return 0
	}
	return len(data)
}

// UnionCandidate represents a candidate type during union deserialization
type UnionCandidate struct {
	FieldCount   int
	InexactCount int // Count of fields with unknown/unrecognized enum values
	Size         int
	Type         any // The union type enum value
	Value        any // The unmarshaled value
}

// FieldCounts holds the result of counting fields in a value
type FieldCounts struct {
	Total   int // Total number of populated fields
	Inexact int // Number of fields with unknown/unrecognized open enum values
}

// CountFieldsWithInexact recursively counts fields and tracks inexact matches (unknown open enum values).
func CountFieldsWithInexact(v interface{}) FieldCounts {
	if v == nil {
		return FieldCounts{}
	}

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

	// Dereference pointers
	for typ.Kind() == reflect.Ptr {
		if val.IsNil() {
			return FieldCounts{}
		}
		typ = typ.Elem()
		val = val.Elem()
	}

	return countFieldsRecursive(typ, val)
}

// PickBestCandidate selects the best union type candidate using a multi-stage filtering approach:
// 1. If multiple candidates, filter by field count (keep only those with max field count)
// 2. If still multiple, filter by inexact count (keep only those with min inexact count)
// 3. If still multiple, filter by JSON size (keep only those with max size)
// 4. Return the first remaining candidate
func PickBestCandidate(candidates []UnionCandidate) *UnionCandidate {
	if len(candidates) == 0 {
		return nil
	}

	if len(candidates) == 1 {
		return &candidates[0]
	}

	// Filter by field count if we have multiple candidates
	if len(candidates) > 1 {
		maxFieldCount := -1
		for i := range candidates {
			fieldCounts := CountFieldsWithInexact(candidates[i].Value)
			candidates[i].FieldCount = fieldCounts.Total
			candidates[i].InexactCount = fieldCounts.Inexact
			if candidates[i].FieldCount > maxFieldCount {
				maxFieldCount = candidates[i].FieldCount
			}
		}

		// Keep only candidates with maximum field count
		filtered := make([]UnionCandidate, 0, len(candidates))
		for _, c := range candidates {
			if c.FieldCount == maxFieldCount {
				filtered = append(filtered, c)
			}
		}
		candidates = filtered
	}

	if len(candidates) == 1 {
		return &candidates[0]
	}

	// Filter by inexact count if we still have multiple candidates
	// Prefer candidates with fewer unknown/unrecognized enum values
	if len(candidates) > 1 {
		minInexactCount := int(^uint(0) >> 1) // max int
		for _, c := range candidates {
			if c.InexactCount < minInexactCount {
				minInexactCount = c.InexactCount
			}
		}

		// Keep only candidates with minimum inexact count
		filtered := make([]UnionCandidate, 0, len(candidates))
		for _, c := range candidates {
			if c.InexactCount == minInexactCount {
				filtered = append(filtered, c)
			}
		}
		candidates = filtered
	}

	if len(candidates) == 1 {
		return &candidates[0]
	}

	// Filter by JSON size if we still have multiple candidates
	if len(candidates) > 1 {
		maxSize := -1
		for i := range candidates {
			candidates[i].Size = CalculateJSONSize(candidates[i].Value)
			if candidates[i].Size > maxSize {
				maxSize = candidates[i].Size
			}
		}

		// Keep only candidates with maximum size
		filtered := make([]UnionCandidate, 0, len(candidates))
		for _, c := range candidates {
			if c.Size == maxSize {
				filtered = append(filtered, c)
			}
		}
		candidates = filtered
	}

	// Pick the first remaining candidate
	return &candidates[0]
}

func countFieldsRecursive(typ reflect.Type, val reflect.Value) FieldCounts {
	counts := FieldCounts{}

	// Check if the value has an IsExact() method (for open enums)
	// Try both the value and its pointer
	if val.CanInterface() && val.CanAddr() {
		ptrVal := val.Addr()
		if method := ptrVal.MethodByName("IsExact"); method.IsValid() {
			results := method.Call(nil)
			if len(results) == 1 && results[0].Kind() == reflect.Bool {
				isExact := results[0].Bool()
				counts.Total = 1
				if !isExact {
					counts.Inexact = 1 // Unknown enum value
				}
				return counts
			}
		}
	}

	switch typ.Kind() {
	case reflect.Struct:
		// Handle special types
		switch typ {
		case reflect.TypeOf(time.Time{}):
			if !val.Interface().(time.Time).IsZero() {
				return FieldCounts{Total: 1}
			}
			return FieldCounts{}
		case reflect.TypeOf(big.Int{}):
			b := val.Interface().(big.Int)
			if b.Sign() != 0 {
				return FieldCounts{Total: 1}
			}
			return FieldCounts{}
		case reflect.TypeOf(types.Date{}):
			// Date is always counted if it exists
			return FieldCounts{Total: 1}
		}

		// For regular structs, count non-zero fields
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			fieldVal := val.Field(i)

			// Skip unexported fields and const fields
			if !field.IsExported() || field.Tag.Get("const") != "" {
				continue
			}

			// Skip fields tagged with json:"-"
			jsonTag := field.Tag.Get("json")
			if jsonTag == "-" {
				continue
			}

			fieldTyp := field.Type
			// Dereference pointer types for the field
			for fieldTyp.Kind() == reflect.Ptr {
				if fieldVal.IsNil() {
					break
				}
				fieldTyp = fieldTyp.Elem()
				fieldVal = fieldVal.Elem()
			}

			if !isNil(field.Type, val.Field(i)) {
				fieldCounts := countFieldsRecursive(fieldTyp, fieldVal)
				counts.Total += fieldCounts.Total
				counts.Inexact += fieldCounts.Inexact
			}
		}

	case reflect.Slice, reflect.Array:
		if val.IsNil() || val.Len() == 0 {
			return FieldCounts{}
		}
		// Count each array/slice element
		for i := 0; i < val.Len(); i++ {
			itemVal := val.Index(i)
			itemTyp := itemVal.Type()

			// Dereference pointer types
			for itemTyp.Kind() == reflect.Ptr {
				if itemVal.IsNil() {
					break
				}
				itemTyp = itemTyp.Elem()
				itemVal = itemVal.Elem()
			}

			if !isNil(itemTyp, itemVal) {
				itemCounts := countFieldsRecursive(itemTyp, itemVal)
				counts.Total += itemCounts.Total
				counts.Inexact += itemCounts.Inexact
			}
		}

	case reflect.String:
		if val.String() != "" {
			counts.Total = 1
		}

	case reflect.Bool:
		// Bools always count as a field (even if false)
		counts.Total = 1

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if val.Int() != 0 {
			counts.Total = 1
		}

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if val.Uint() != 0 {
			counts.Total = 1
		}

	case reflect.Float32, reflect.Float64:
		if val.Float() != 0 {
			counts.Total = 1
		}

	default:
		// For any other type, if it's not zero, count it as 1
		if !val.IsZero() {
			counts.Total = 1
		}
	}

	return counts
}
