// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package jsonschema

import (
	"bytes"
	"cmp"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"hash/maphash"
	"math"
	"math/big"
	"reflect"
	"slices"
)

// Equal reports whether two Go values representing JSON values are equal according
// to the JSON Schema spec.
// The values must not contain cycles.
// See https://json-schema.org/draft/2020-12/json-schema-core#section-4.2.2.
// It behaves like reflect.DeepEqual, except that numbers are compared according
// to mathematical equality.
func Equal(x, y any) bool {
	return equalValue(reflect.ValueOf(x), reflect.ValueOf(y))
}

func equalValue(x, y reflect.Value) bool {
	// Copied from src/reflect/deepequal.go, omitting the visited check (because JSON
	// values are trees).
	if !x.IsValid() || !y.IsValid() {
		return x.IsValid() == y.IsValid()
	}

	// Treat numbers specially.
	rx, ok1 := jsonNumber(x)
	ry, ok2 := jsonNumber(y)
	if ok1 && ok2 {
		return rx.Cmp(ry) == 0
	}
	if x.Kind() != y.Kind() {
		return false
	}
	switch x.Kind() {
	case reflect.Array:
		if x.Len() != y.Len() {
			return false
		}
		for i := range x.Len() {
			if !equalValue(x.Index(i), y.Index(i)) {
				return false
			}
		}
		return true
	case reflect.Slice:
		if x.IsNil() != y.IsNil() {
			return false
		}
		if x.Len() != y.Len() {
			return false
		}
		if x.UnsafePointer() == y.UnsafePointer() {
			return true
		}
		// Special case for []byte, which is common.
		if x.Type().Elem().Kind() == reflect.Uint8 && x.Type() == y.Type() {
			return bytes.Equal(x.Bytes(), y.Bytes())
		}
		for i := range x.Len() {
			if !equalValue(x.Index(i), y.Index(i)) {
				return false
			}
		}
		return true
	case reflect.Interface:
		if x.IsNil() || y.IsNil() {
			return x.IsNil() == y.IsNil()
		}
		return equalValue(x.Elem(), y.Elem())
	case reflect.Pointer:
		if x.UnsafePointer() == y.UnsafePointer() {
			return true
		}
		return equalValue(x.Elem(), y.Elem())
	case reflect.Struct:
		t := x.Type()
		if t != y.Type() {
			return false
		}
		for i := range t.NumField() {
			sf := t.Field(i)
			if !sf.IsExported() {
				continue
			}
			if !equalValue(x.FieldByIndex(sf.Index), y.FieldByIndex(sf.Index)) {
				return false
			}
		}
		return true
	case reflect.Map:
		if x.IsNil() != y.IsNil() {
			return false
		}
		if x.Len() != y.Len() {
			return false
		}
		if x.UnsafePointer() == y.UnsafePointer() {
			return true
		}
		iter := x.MapRange()
		for iter.Next() {
			vx := iter.Value()
			vy := y.MapIndex(iter.Key())
			if !vy.IsValid() || !equalValue(vx, vy) {
				return false
			}
		}
		return true
	case reflect.Func:
		if x.Type() != y.Type() {
			return false
		}
		if x.IsNil() && y.IsNil() {
			return true
		}
		panic("cannot compare functions")
	case reflect.String:
		return x.String() == y.String()
	case reflect.Bool:
		return x.Bool() == y.Bool()
	// Ints, uints and floats handled in jsonNumber, at top of function.
	default:
		panic(fmt.Sprintf("unsupported kind: %s", x.Kind()))
	}
}

// hashValue adds v to the data hashed by h. v must not have cycles.
// hashValue panics if the value contains functions or channels, or maps whose
// key type is not string.
// It ignores unexported fields of structs.
// Calls to hashValue with the equal values (in the sense
// of [Equal]) result in the same sequence of values written to the hash.
func hashValue(h *maphash.Hash, v reflect.Value) {
	// TODO: replace writes of basic types with WriteComparable in 1.24.

	writeUint := func(u uint64) {
		var buf [8]byte
		binary.BigEndian.PutUint64(buf[:], u)
		h.Write(buf[:])
	}

	var write func(reflect.Value)
	write = func(v reflect.Value) {
		if r, ok := jsonNumber(v); ok {
			// We want 1.0 and 1 to hash the same.
			// big.Rats are always normalized, so they will be.
			// We could do this more efficiently by handling the int and float cases
			// separately, but that's premature.
			writeUint(uint64(r.Sign() + 1))
			h.Write(r.Num().Bytes())
			h.Write(r.Denom().Bytes())
			return
		}
		switch v.Kind() {
		case reflect.Invalid:
			h.WriteByte(0)
		case reflect.String:
			h.WriteString(v.String())
		case reflect.Bool:
			if v.Bool() {
				h.WriteByte(1)
			} else {
				h.WriteByte(0)
			}
		case reflect.Complex64, reflect.Complex128:
			c := v.Complex()
			writeUint(math.Float64bits(real(c)))
			writeUint(math.Float64bits(imag(c)))
		case reflect.Array, reflect.Slice:
			// Although we could treat []byte more efficiently,
			// JSON values are unlikely to contain them.
			writeUint(uint64(v.Len()))
			for i := range v.Len() {
				write(v.Index(i))
			}
		case reflect.Interface, reflect.Pointer:
			write(v.Elem())
		case reflect.Struct:
			t := v.Type()
			for i := range t.NumField() {
				if sf := t.Field(i); sf.IsExported() {
					write(v.FieldByIndex(sf.Index))
				}
			}
		case reflect.Map:
			if v.Type().Key().Kind() != reflect.String {
				panic("map with non-string key")
			}
			// Sort the keys so the hash is deterministic.
			keys := v.MapKeys()
			// Write the length. That distinguishes between, say, two consecutive
			// maps with disjoint keys from one map that has the items of both.
			writeUint(uint64(len(keys)))
			slices.SortFunc(keys, func(x, y reflect.Value) int { return cmp.Compare(x.String(), y.String()) })
			for _, k := range keys {
				write(k)
				write(v.MapIndex(k))
			}
		// Ints, uints and floats handled in jsonNumber, at top of function.
		default:
			panic(fmt.Sprintf("unsupported kind: %s", v.Kind()))
		}
	}

	write(v)
}

// jsonNumber converts a numeric value or a json.Number to a [big.Rat].
// If v is not a number, it returns nil, false.
func jsonNumber(v reflect.Value) (*big.Rat, bool) {
	r := new(big.Rat)
	switch {
	case !v.IsValid():
		return nil, false
	case v.CanInt():
		r.SetInt64(v.Int())
	case v.CanUint():
		r.SetUint64(v.Uint())
	case v.CanFloat():
		r.SetFloat64(v.Float())
	default:
		jn, ok := v.Interface().(json.Number)
		if !ok {
			return nil, false
		}
		if _, ok := r.SetString(jn.String()); !ok {
			// This can fail in rare cases; for example, "1e9999999".
			// That is a valid JSON number, since the spec puts no limit on the size
			// of the exponent.
			return nil, false
		}
	}
	return r, true
}

// jsonType returns a string describing the type of the JSON value,
// as described in the JSON Schema specification:
// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.1.
// It returns "", false if the value is not valid JSON.
func jsonType(v reflect.Value) (string, bool) {
	if !v.IsValid() {
		// Not v.IsNil(): a nil []any is still a JSON array.
		return "null", true
	}
	if v.CanInt() || v.CanUint() {
		return "integer", true
	}
	if v.CanFloat() {
		if _, f := math.Modf(v.Float()); f == 0 {
			return "integer", true
		}
		return "number", true
	}
	switch v.Kind() {
	case reflect.Bool:
		return "boolean", true
	case reflect.String:
		return "string", true
	case reflect.Slice, reflect.Array:
		return "array", true
	case reflect.Map:
		return "object", true
	default:
		return "", false
	}
}

func assert(cond bool, msg string) {
	if !cond {
		panic(msg)
	}
}
