// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

// Package jsonx contains helper types and functionality to use with
// [github.com/go-json-experiment/json], which is positioned to be
// merged into the Go standard library as [encoding/json/v2].
//
// See https://go.dev/issues/71497
package jsonx

import (
	"errors"
	"fmt"
	"reflect"

	"github.com/go-json-experiment/json"
	"github.com/go-json-experiment/json/jsontext"
)

var (
	errUnknownTypeName  = errors.New("unknown type name")
	errNonSingularValue = errors.New("dynamic value must only have exactly one member")
)

// MakeInterfaceCoders constructs a pair of marshal and unmarshal functions
// to serialize a Go interface type T. A bijective mapping for the set
// of concrete types that implement T is provided,
// where the key is a stable type name to use in the JSON representation,
// while the value is any value of a concrete type that implements T.
// By convention, only the zero value of concrete types is passed.
//
// The JSON representation for a dynamic value is a JSON object
// with a single member, where the member name is the type name,
// and the value is the JSON representation for the Go value.
// For example, the JSON serialization for a concrete type named Foo
// would be {"Foo": ...}, where ... is the JSON representation
// of the concrete value of the Foo type.
//
// Example instantiation:
//
//	// Interface is a union type implemented by [FooType] and [BarType].
//	type Interface interface { ... }
//
//	var interfaceCoders = MakeInterfaceCoders(map[string]Interface{
//		"FooType": FooType{},
//		"BarType": (*BarType)(nil),
//	})
//
// The pair of Marshal and Unmarshal functions can be used with the [json]
// package with either type-specified or caller-specified serialization.
// The result of this constructor is usually stored into a global variable.
//
// Example usage with type-specified serialization:
//
//	// InterfaceWrapper is a concrete type that wraps [Interface].
//	// It extends [Interface] to implement
//	// [json.MarshalerTo] and [json.UnmarshalerFrom].
//	type InterfaceWrapper struct{ Interface }
//
//	func (w InterfaceWrapper) MarshalJSONTo(enc *jsontext.Encoder) error {
//		return interfaceCoders.Marshal(enc, &w.Interface)
//	}
//
//	func (w *InterfaceWrapper) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
//		return interfaceCoders.Unmarshal(dec, &w.Interface)
//	}
//
// Example usage with caller-specified serialization:
//
//	var opts json.Options = json.JoinOptions(
//		json.WithMarshalers(json.MarshalToFunc(interfaceCoders.Marshal)),
//		json.WithUnmarshalers(json.UnmarshalFromFunc(interfaceCoders.Unmarshal)),
//	)
//
//	var v Interface
//	... := json.Marshal(v, opts)
//	... := json.Unmarshal(&v, opts)
//
// The function panics if T is not a named interface kind,
// or if valuesByName contains distinct entries with the same concrete type.
func MakeInterfaceCoders[T any](valuesByName map[string]T) (c struct {
	Marshal   func(*jsontext.Encoder, *T) error
	Unmarshal func(*jsontext.Decoder, *T) error
}) {
	// Verify that T is a named interface.
	switch t := reflect.TypeFor[T](); {
	case t.Kind() != reflect.Interface:
		panic(fmt.Sprintf("%v must be an interface kind", t))
	case t.Name() == "":
		panic(fmt.Sprintf("%v must be a named type", t))
	}

	// Construct a bijective mapping of names to types.
	typesByName := make(map[string]reflect.Type)
	namesByType := make(map[reflect.Type]string)
	for name, value := range valuesByName {
		t := reflect.TypeOf(value)
		if t == nil {
			panic(fmt.Sprintf("nil value for %s", name))
		}
		if name2, ok := namesByType[t]; ok {
			panic(fmt.Sprintf("type %v cannot have multiple names %s and %v", t, name, name2))
		}
		typesByName[name] = t
		namesByType[t] = name
	}

	// Construct the marshal and unmarshal functions.
	c.Marshal = func(enc *jsontext.Encoder, val *T) error {
		t := reflect.TypeOf(*val)
		if t == nil {
			return enc.WriteToken(jsontext.Null)
		}
		name := namesByType[t]
		if name == "" {
			return fmt.Errorf("Go type %v: %w", t, errUnknownTypeName)
		}

		if err := enc.WriteToken(jsontext.BeginObject); err != nil {
			return err
		}
		if err := enc.WriteToken(jsontext.String(name)); err != nil {
			return err
		}
		if err := json.MarshalEncode(enc, *val); err != nil {
			return err
		}
		if err := enc.WriteToken(jsontext.EndObject); err != nil {
			return err
		}
		return nil
	}
	c.Unmarshal = func(dec *jsontext.Decoder, val *T) error {
		switch tok, err := dec.ReadToken(); {
		case err != nil:
			return err
		case tok.Kind() == 'n':
			var zero T
			*val = zero // store nil interface value for JSON null
			return nil
		case tok.Kind() != '{':
			return &json.SemanticError{JSONKind: tok.Kind(), GoType: reflect.TypeFor[T]()}
		}
		var v reflect.Value
		switch tok, err := dec.ReadToken(); {
		case err != nil:
			return err
		case tok.Kind() != '"':
			return errNonSingularValue
		default:
			t := typesByName[tok.String()]
			if t == nil {
				return errUnknownTypeName
			}
			v = reflect.New(t)
		}
		if err := json.UnmarshalDecode(dec, v.Interface()); err != nil {
			return err
		}
		*val = v.Elem().Interface().(T)
		switch tok, err := dec.ReadToken(); {
		case err != nil:
			return err
		case tok.Kind() != '}':
			return errNonSingularValue
		}
		return nil
	}

	return c
}
