// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package collections

// UniqueKey represents a value that is comparable and uniquely identifies
// another value of type T.
//
// The Go type system offers no way to guarantee at compile time that
// implementations of this type are comparable, but if this interface is
// implemented by an uncomparable type then that will cause runtime panics
// when inserting elements into collection types that use unique keys.
//
// We use this to help with correctness of the unique-key-generator callbacks
// used with the collection types in this package, so help with type parameter
// inference and to raise compile-time errors if an inappropriate callback
// is used as the key generator for a particular collection.
type UniqueKey[T any] interface {
	// Implementations must include an IsUniqueKey method with an empty body
	// just as a compile-time assertion that they are intended to behave as
	// unique keys for a particular other type.
	//
	// This method is never actually called by the collection types. Other
	// callers could potentially call it, but it would be strange and pointless
	// to do so.
	IsUniqueKey(T)
}

// A UniqueKeyer is a type that knows how to calculate a unique key itself.
type UniqueKeyer[T any] interface {
	// UniqueKey returns the unique key of the receiver.
	//
	// A correct implementation of UniqueKey must return a distinct value
	// for each unique value of T, where the uniqueness of T values is decided
	// by the implementer. See [UniqueKey] for more information.
	//
	// Although not enforced directly by the Go type system, it doesn't make
	// sense for a type to implement [UniqueKeyer] for any type other than
	// itself. Such a nonsensical implementation will not be accepted by
	// functions like [NewSet] and [NewMap].
	UniqueKey() UniqueKey[T]
}

// cmpUniqueKey is an annoying little adapter used to make arbitrary
// comparable types usable with [Set] and [Map].
//
// It just wraps a single-element array of T around the value, so it
// remains exactly as comparable as T. However, it does unfortunately
// mean redundantly storing T twice -- both as the unique key and the
// value -- in our collections.
type cmpUniqueKey[T comparable] [1]T

func (cmpUniqueKey[T]) IsUniqueKey(T) {}

func cmpUniqueKeyFunc[T comparable](v T) UniqueKey[T] {
	return cmpUniqueKey[T]{v}
}
