// Copyright 2019 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 label

import (
	"fmt"
	"io"
	"slices"
	"unsafe"
)

// Key is used as the identity of a Label.
// Keys are intended to be compared by pointer only, the name should be unique
// for communicating with external systems, but it is not required or enforced.
type Key interface {
	// Name returns the key name.
	Name() string
	// Description returns a string that can be used to describe the value.
	Description() string

	// Format is used in formatting to append the value of the label to the
	// supplied buffer.
	// The formatter may use the supplied buf as a scratch area to avoid
	// allocations.
	Format(w io.Writer, buf []byte, l Label)
}

// Label holds a key and value pair.
// It is normally used when passing around lists of labels.
type Label struct {
	key     Key
	packed  uint64
	untyped any
}

// Map is the interface to a collection of Labels indexed by key.
type Map interface {
	// Find returns the label that matches the supplied key.
	Find(key Key) Label
}

// List is the interface to something that provides an iterable
// list of labels.
// Iteration should start from 0 and continue until Valid returns false.
type List interface {
	// Valid returns true if the index is within range for the list.
	// It does not imply the label at that index will itself be valid.
	Valid(index int) bool
	// Label returns the label at the given index.
	Label(index int) Label
}

// list implements LabelList for a list of Labels.
type list struct {
	labels []Label
}

// filter wraps a LabelList filtering out specific labels.
type filter struct {
	keys       []Key
	underlying List
}

// listMap implements LabelMap for a simple list of labels.
type listMap struct {
	labels []Label
}

// mapChain implements LabelMap for a list of underlying LabelMap.
type mapChain struct {
	maps []Map
}

// OfValue creates a new label from the key and value.
// This method is for implementing new key types, label creation should
// normally be done with the Of method of the key.
func OfValue(k Key, value any) Label { return Label{key: k, untyped: value} }

// UnpackValue assumes the label was built using LabelOfValue and returns the value
// that was passed to that constructor.
// This method is for implementing new key types, for type safety normal
// access should be done with the From method of the key.
func (t Label) UnpackValue() any { return t.untyped }

// Of64 creates a new label from a key and a uint64. This is often
// used for non uint64 values that can be packed into a uint64.
// This method is for implementing new key types, label creation should
// normally be done with the Of method of the key.
func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} }

// Unpack64 assumes the label was built using LabelOf64 and returns the value that
// was passed to that constructor.
// This method is for implementing new key types, for type safety normal
// access should be done with the From method of the key.
func (t Label) Unpack64() uint64 { return t.packed }

type stringptr unsafe.Pointer

// OfString creates a new label from a key and a string.
// This method is for implementing new key types, label creation should
// normally be done with the Of method of the key.
func OfString(k Key, v string) Label {
	return Label{
		key:     k,
		packed:  uint64(len(v)),
		untyped: stringptr(unsafe.StringData(v)),
	}
}

// UnpackString assumes the label was built using LabelOfString and returns the
// value that was passed to that constructor.
// This method is for implementing new key types, for type safety normal
// access should be done with the From method of the key.
func (t Label) UnpackString() string {
	return unsafe.String((*byte)(t.untyped.(stringptr)), int(t.packed))
}

// Valid returns true if the Label is a valid one (it has a key).
func (t Label) Valid() bool { return t.key != nil }

// Key returns the key of this Label.
func (t Label) Key() Key { return t.key }

// Format is used for debug printing of labels.
func (t Label) Format(f fmt.State, r rune) {
	if !t.Valid() {
		io.WriteString(f, `nil`)
		return
	}
	io.WriteString(f, t.Key().Name())
	io.WriteString(f, "=")
	var buf [128]byte
	t.Key().Format(f, buf[:0], t)
}

func (l *list) Valid(index int) bool {
	return index >= 0 && index < len(l.labels)
}

func (l *list) Label(index int) Label {
	return l.labels[index]
}

func (f *filter) Valid(index int) bool {
	return f.underlying.Valid(index)
}

func (f *filter) Label(index int) Label {
	l := f.underlying.Label(index)
	if slices.Contains(f.keys, l.Key()) {
		return Label{}
	}
	return l
}

func (lm listMap) Find(key Key) Label {
	for _, l := range lm.labels {
		if l.Key() == key {
			return l
		}
	}
	return Label{}
}

func (c mapChain) Find(key Key) Label {
	for _, src := range c.maps {
		l := src.Find(key)
		if l.Valid() {
			return l
		}
	}
	return Label{}
}

var emptyList = &list{}

func NewList(labels ...Label) List {
	if len(labels) == 0 {
		return emptyList
	}
	return &list{labels: labels}
}

func Filter(l List, keys ...Key) List {
	if len(keys) == 0 {
		return l
	}
	return &filter{keys: keys, underlying: l}
}

func NewMap(labels ...Label) Map {
	return listMap{labels: labels}
}

func MergeMaps(srcs ...Map) Map {
	var nonNil []Map
	for _, src := range srcs {
		if src != nil {
			nonNil = append(nonNil, src)
		}
	}
	if len(nonNil) == 1 {
		return nonNil[0]
	}
	return mapChain{maps: nonNil}
}
