package enum

import (
	
	
	
	
)

// Member is an enum member, a specific value bound to a variable.
type Member[ comparable] struct {
	Value 
}

// Equaler check if the two values of the same type are equal.
type Equaler[ comparable] interface {
	Equal(other ) bool
	comparable
}

// iMember is the type constraint for Member used by Enum.
//
// We can't use Member directly in type constraints
// because the users create a new subtype from Member
// instead of using it directly.
//
// We also can't use a normal interface because new types
// don't inherit methods of their base type.
type iMember[ comparable] interface {
	~struct{ Value  }
}

// Enum is a collection of enum members.
//
// Use [New] to construct a new Enum from a list of members.
type Enum[ iMember[],  comparable] struct {
	members []
	v2m     map[]*
}

// New constructs a new [Enum] wrapping the given enum members.
func [ comparable,  iMember[]]( ...) Enum[, ] {
	 := Enum[, ]{, nil}
	.v2m = make(map[]*)
	for ,  := range .members {
		 := .Value()
		.v2m[] = &.members[]
	}
	return 
}

// TypeName is a string representation of the wrapped type.
func (Enum[, ]) () string {
	return fmt.Sprintf("%T", *new())
}

// Empty returns true if the enum doesn't have any members.
func ( Enum[, ]) () bool {
	return len(.members) == 0
}

// Len returns how many members the enum has.
func ( Enum[, ]) () int {
	return len(.members)
}

// Contains returns true if the enum has the given member.
func ( Enum[, ]) ( ) bool {
	for ,  := range .members {
		if  ==  {
			return true
		}
	}
	return false
}

// Parse converts a raw value into a member of the enum.
//
// If none of the enum members has the given value, nil is returned.
func ( Enum[, ]) ( ) * {
	return .v2m[]
}

// Value returns the wrapped value of the given enum member.
func ( Enum[, ]) ( )  {
	return Member[]().Value
}

// Index returns the index of the given member in the enum.
//
// If the given member is not in the enum, it panics.
// Use [Enum.Contains] first if you don't know for sure
// if the member belongs to the enum.
func ( Enum[, ]) ( ) int {
	for ,  := range .members {
		if .Value() == .Value() {
			return 
		}
	}
	panic("the given Member does not belong to this Enum")
}

// Members returns a slice of the members in the enum.
func ( Enum[, ]) () [] {
	return .members
}

// Choice returns a randomly selected member of the enum.
//
// A random seed can be given (or be 0 to use time.Now().UnixNano() as the seed).
// nil is returned only if the Enum contains no members.
func ( Enum[, ]) ( int64) * {
	 := len(.members)
	// Enum is empty
	if  == 0 {
		return nil
	}
	if  == 0 {
		 = time.Now().UnixNano()
	}
	// nolint: gosec
	 := rand.New(rand.NewSource())
	return &(.members[.Intn()])
}

// Values returns a slice of values of all members of the enum.
func ( Enum[, ]) () [] {
	 := make([], 0, len(.members))
	for ,  := range .members {
		 = append(, .Value())
	}
	return 
}

// String implements [fmt.Stringer] interface.
//
// It returns a comma-separated list of values of the enum members.
func ( Enum[, ]) () string {
	 := make([]string, 0, len(.members))
	for ,  := range .members {
		 = append(, fmt.Sprintf("%v", .Value()))
	}
	return strings.Join(, ", ")
}

// GoString implements [fmt.GoStringer] interface.
//
// When you print a member using "%#v" format,
// it will show the enum representation as a valid Go syntax.
func ( Enum[, ]) () string {
	 := make([]string, 0, len(.members))
	for ,  := range .members {
		 = append(, fmt.Sprintf("%T{%#v}", , .Value()))
	}
	 := strings.Join(, ", ")
	return fmt.Sprintf("enum.New(%s)", )
}

// Parse is like [Enum.Parse] but finds the member for the value using [Equaler] comparator.
func [ iMember[],  Equaler[]]( Enum[, ],  ) * {
	for ,  := range .v2m {
		if .Equal() {
			return 
		}
	}
	return nil
}

// Builder is a constructor for an [Enum].
//
// Use [Builder.Add] to add new members to the future enum
// and then call [Builder.Enum] to create a new [Enum] with all added members.
//
// Builder is useful for when you have lots of enum members, and new ones
// are added over time, as the project grows. In such scenario, it's easy to forget
// to add in the [Enum] a newly created [Member].
// The builder is designed to prevent that.
type Builder[ iMember[],  comparable] struct {
	members  []
	finished bool
}

// NewBuilder creates a new [Builder], a constructor for an [Enum].
func [ comparable,  iMember[]]() Builder[, ] {
	return Builder[, ]{make([], 0), false}
}

// Add registers a new [Member] in the builder.
func ( *Builder[, ]) ( )  {
	.members = append(.members, )
	return 
}

// Enum creates a new [Enum] with all members registered using [Builder.Add].
func ( *Builder[, ]) () Enum[, ] {
	.finished = true
	 := New(.members...)
	return 
}