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

package prefs

import (
	"fmt"

	jsonv2 "github.com/go-json-experiment/json"
	"github.com/go-json-experiment/json/jsontext"
	"tailscale.com/types/opt"
	"tailscale.com/types/ptr"
	"tailscale.com/types/views"
	"tailscale.com/util/must"
)

// Item is a single preference item that can be configured.
// T must either be an immutable type or implement the [views.ViewCloner] interface.
type Item[T any] struct {
	preference[T]
}

// ItemOf returns an [Item] configured with the specified value and [Options].
func ItemOf[T any](v T, opts ...Options) Item[T] {
	return Item[T]{preferenceOf(opt.ValueOf(must.Get(deepClone(v))), opts...)}
}

// ItemWithOpts returns an unconfigured [Item] with the specified [Options].
func ItemWithOpts[T any](opts ...Options) Item[T] {
	return Item[T]{preferenceOf(opt.Value[T]{}, opts...)}
}

// SetValue configures the preference with the specified value.
// It fails and returns [ErrManaged] if p is a managed preference,
// and [ErrReadOnly] if p is a read-only preference.
func (i *Item[T]) SetValue(val T) error {
	return i.preference.SetValue(must.Get(deepClone(val)))
}

// SetManagedValue configures the preference with the specified value
// and marks the preference as managed.
func (i *Item[T]) SetManagedValue(val T) {
	i.preference.SetManagedValue(must.Get(deepClone(val)))
}

// Clone returns a copy of i that aliases no memory with i.
// It is a runtime error to call [Item.Clone] if T contains pointers
// but does not implement [views.Cloner].
func (i Item[T]) Clone() *Item[T] {
	res := ptr.To(i)
	if v, ok := i.ValueOk(); ok {
		res.s.Value.Set(must.Get(deepClone(v)))
	}
	return res
}

// Equal reports whether i and i2 are equal.
// If the template type T implements an Equal(T) bool method, it will be used
// instead of the == operator for value comparison.
// If T is not comparable, it reports false.
func (i Item[T]) Equal(i2 Item[T]) bool {
	if i.s.Metadata != i2.s.Metadata {
		return false
	}
	return i.s.Value.Equal(i2.s.Value)
}

func deepClone[T any](v T) (T, error) {
	if c, ok := any(v).(views.Cloner[T]); ok {
		return c.Clone(), nil
	}
	if !views.ContainsPointers[T]() {
		return v, nil
	}
	var zero T
	return zero, fmt.Errorf("%T contains pointers, but does not implement Clone", v)
}

// ItemView is a read-only view of an [Item][T], where T is a mutable type
// implementing [views.ViewCloner].
type ItemView[T views.ViewCloner[T, V], V views.StructView[T]] struct {
	// ж is the underlying mutable value, named with a hard-to-type
	// character that looks pointy like a pointer.
	// It is named distinctively to make you think of how dangerous it is to escape
	// to callers. You must not let callers be able to mutate it.
	ж *Item[T]
}

// ItemViewOf returns a read-only view of i.
// It is used by [tailscale.com/cmd/viewer].
func ItemViewOf[T views.ViewCloner[T, V], V views.StructView[T]](i *Item[T]) ItemView[T, V] {
	return ItemView[T, V]{i}
}

// Valid reports whether the underlying [Item] is non-nil.
func (iv ItemView[T, V]) Valid() bool {
	return iv.ж != nil
}

// AsStruct implements [views.StructView] by returning a clone of the preference
// which aliases no memory with the original.
func (iv ItemView[T, V]) AsStruct() *Item[T] {
	if iv.ж == nil {
		return nil
	}
	return iv.ж.Clone()
}

// IsSet reports whether the preference has a value set.
func (iv ItemView[T, V]) IsSet() bool {
	return iv.ж.IsSet()
}

// Value returns a read-only view of the value if the preference has a value set.
// Otherwise, it returns a read-only view of its default value.
func (iv ItemView[T, V]) Value() V {
	return iv.ж.Value().View()
}

// ValueOk returns a read-only view of the value and true if the preference has a value set.
// Otherwise, it returns an invalid view and false.
func (iv ItemView[T, V]) ValueOk() (val V, ok bool) {
	if val, ok := iv.ж.ValueOk(); ok {
		return val.View(), true
	}
	return val, false
}

// DefaultValue returns a read-only view of the default value of the preference.
func (iv ItemView[T, V]) DefaultValue() V {
	return iv.ж.DefaultValue().View()
}

// IsManaged reports whether the preference is managed via MDM, Group Policy, or similar means.
func (iv ItemView[T, V]) IsManaged() bool {
	return iv.ж.IsManaged()
}

// IsReadOnly reports whether the preference is read-only and cannot be changed by user.
func (iv ItemView[T, V]) IsReadOnly() bool {
	return iv.ж.IsReadOnly()
}

// Equal reports whether iv and iv2 are equal.
func (iv ItemView[T, V]) Equal(iv2 ItemView[T, V]) bool {
	if !iv.Valid() && !iv2.Valid() {
		return true
	}
	if iv.Valid() != iv2.Valid() {
		return false
	}
	return iv.ж.Equal(*iv2.ж)
}

// MarshalJSONTo implements [jsonv2.MarshalerTo].
func (iv ItemView[T, V]) MarshalJSONTo(out *jsontext.Encoder) error {
	return iv.ж.MarshalJSONTo(out)
}

// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
func (iv *ItemView[T, V]) UnmarshalJSONFrom(in *jsontext.Decoder) error {
	var x Item[T]
	if err := x.UnmarshalJSONFrom(in); err != nil {
		return err
	}
	iv.ж = &x
	return nil
}

// MarshalJSON implements [json.Marshaler].
func (iv ItemView[T, V]) MarshalJSON() ([]byte, error) {
	return jsonv2.Marshal(iv) // uses MarshalJSONTo
}

// UnmarshalJSON implements [json.Unmarshaler].
func (iv *ItemView[T, V]) UnmarshalJSON(b []byte) error {
	return jsonv2.Unmarshal(b, iv) // uses UnmarshalJSONFrom
}
