package mlrval

import (
	"bytes"
	"fmt"
	"slices"
	"strconv"

	"github.com/johnkerl/miller/v6/pkg/lib"
)

// IsEmpty determines if a map is empty.
func (mlrmap *Mlrmap) IsEmpty() bool {
	return mlrmap.Head == nil
}

func (mlrmap *Mlrmap) Has(key string) bool {
	return mlrmap.findEntry(key) != nil
}

func (mlrmap *Mlrmap) Get(key string) *Mlrval {
	pe := mlrmap.findEntry(key)
	if pe == nil {
		return nil
	}
	return pe.Value
}

// PutReference copies the key but not the value. This is not safe for DSL use,
// where we could create undesired references between different objects.  Only
// intended to be used at callsites which allocate a mlrval on the spot, solely
// for the purpose of putting into the map.
func (mlrmap *Mlrmap) PutReference(key string, value *Mlrval) {
	pe := mlrmap.findEntry(key)
	if pe == nil {
		mlrmap.putReferenceNewAux(key, value)
	} else {
		pe.Value = value
	}
}

// putReferenceNewAux is a helper function for code shared between PutReference
// and PutReferenceMaybeDedupe. It should not be invoked from anywhere else --
// it doesn't do its own check if the key already exists in the record or not.
func (mlrmap *Mlrmap) putReferenceNewAux(key string, value *Mlrval) {
	mlrmap.linkNewEntry(newMlrmapEntry(key, value))
}

// linkNewEntry appends an already-constructed entry to the tail of the list and
// updates the index if present. The entry must not already be in the map. It is
// shared by the normal put path and by the batch-arena reader fast path.
func (mlrmap *Mlrmap) linkNewEntry(pe *MlrmapEntry) {
	if mlrmap.Head == nil {
		mlrmap.Head = pe
		mlrmap.Tail = pe
	} else {
		pe.Prev = mlrmap.Tail
		pe.Next = nil
		mlrmap.Tail.Next = pe
		mlrmap.Tail = pe
	}
	if mlrmap.keysToEntries != nil {
		mlrmap.keysToEntries[pe.Key] = pe
	}
	mlrmap.FieldCount++
}

// PutReferenceMaybeDedupe is the default inserter for key-value pairs in input records --
// if the input is 'x=8,x=9` then we make a record with x=8 and x_2=9. This can be suppressed
// via a command-line flag which this method's dedupe flag respects.
func (mlrmap *Mlrmap) PutReferenceMaybeDedupe(key string, value *Mlrval, dedupe bool) (string, error) {
	if !dedupe {
		mlrmap.PutReference(key, value)
		return key, nil
	}

	pe := mlrmap.findEntry(key)
	if pe == nil {
		mlrmap.putReferenceNewAux(key, value)
		return key, nil
	}

	for i := 2; ; i++ {
		newKey := key + "_" + strconv.Itoa(i)
		pe := mlrmap.findEntry(newKey)
		if pe == nil {
			mlrmap.putReferenceNewAux(newKey, value)
			return newKey, nil
		}
	}
}

// PutCopy copies the key and value (deep-copying in case the value is array/map).
// This is safe for DSL use. See also PutReference.
func (mlrmap *Mlrmap) PutCopy(key string, value *Mlrval) {
	pe := mlrmap.findEntry(key)
	if pe == nil {
		pe = newMlrmapEntry(key, value.Copy())
		if mlrmap.Head == nil {
			mlrmap.Head = pe
			mlrmap.Tail = pe
		} else {
			pe.Prev = mlrmap.Tail
			pe.Next = nil
			mlrmap.Tail.Next = pe
			mlrmap.Tail = pe
		}
		if mlrmap.keysToEntries != nil {
			mlrmap.keysToEntries[key] = pe
		}
		mlrmap.FieldCount++
	} else {
		pe.Value = value.Copy()
	}
}

// PrependReference is the same as PutReference, but puts a new entry first, not last.
func (mlrmap *Mlrmap) PrependReference(key string, value *Mlrval) {
	pe := mlrmap.findEntry(key)
	if pe == nil {
		pe = newMlrmapEntry(key, value)
		if mlrmap.Tail == nil {
			mlrmap.Head = pe
			mlrmap.Tail = pe
		} else {
			pe.Prev = nil
			pe.Next = mlrmap.Head
			mlrmap.Head.Prev = pe
			mlrmap.Head = pe
		}
		if mlrmap.keysToEntries != nil {
			mlrmap.keysToEntries[key] = pe
		}
		mlrmap.FieldCount++
	} else {
		pe.Value = value
	}
}

// TODO: COMMENT
func (mlrmap *Mlrmap) PutReferenceAfter(
	pe *MlrmapEntry,
	key string,
	value *Mlrval,
) *MlrmapEntry {
	if pe == nil || pe.Next == nil {
		// New entry is supposed to go 'after' old, but there is no such old
		// entry.  Or, the old entry exists and is at the tail. In either case,
		// add the new entry at the end of the record (new tail).

		// TODO: make a helper method for code-dedupe
		pf := newMlrmapEntry(key, value)
		if mlrmap.Head == nil { // First entry into empty map
			mlrmap.Head = pf
			mlrmap.Tail = pf
		} else {
			// Before: ... pc pd
			// After:  ... pc pd pf
			pf.Prev = mlrmap.Tail
			pf.Next = nil
			mlrmap.Tail.Next = pf
			mlrmap.Tail = pf
		}

		if mlrmap.keysToEntries != nil {
			mlrmap.keysToEntries[key] = pf
		}
		mlrmap.FieldCount++
		return pf

	}
	// Before: ... pe pg ...
	// After:  ... pe pf pg ...
	//
	// New entry is neither the new head (pe != nil) nor the new tail
	// (pe.Next != nil, otherwise we'd be in the if-branch above).

	pf := newMlrmapEntry(key, value)
	pg := pe.Next

	pe.Next = pf
	pf.Next = pg
	pf.Prev = pe
	if pg != nil {
		pg.Prev = pf
	}

	if mlrmap.keysToEntries != nil {
		mlrmap.keysToEntries[key] = pf
	}
	mlrmap.FieldCount++
	return pf
}

// findEntry is the basic hash-map accessor for Has, Put, Get, Remove, etc.
// Hashmaps in Go are a built-in type; Mlrmap is (a)
// insertion-order-preserving, and (b) supports either hashed or non-hashed
// access.
func (mlrmap *Mlrmap) findEntry(key string) *MlrmapEntry {
	if mlrmap.keysToEntries != nil {
		return mlrmap.keysToEntries[key]
	}
	// Lazily build the key-to-entry index when a lookup happens on a record
	// wide enough to benefit. Narrow records (the common case) and records
	// that are never looked up stay on the linear-search path and never pay
	// for a map. See mlrmapHashThreshold.
	if mlrmap.autoHash && mlrmap.FieldCount >= mlrmapHashThreshold {
		mlrmap.buildIndex()
		return mlrmap.keysToEntries[key]
	}
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		if pe.Key == key {
			return pe
		}
	}
	return nil
}

// buildIndex populates keysToEntries from the linked list. Once built, all
// mutators keep it in sync (each guards on keysToEntries != nil). On duplicate
// keys the last entry wins, matching the linear-search semantics of findEntry
// (which returns the first match), since records are deduped on insert.
func (mlrmap *Mlrmap) buildIndex() {
	m := make(map[string]*MlrmapEntry, mlrmap.FieldCount)
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		if _, ok := m[pe.Key]; !ok {
			m[pe.Key] = pe
		}
	}
	mlrmap.keysToEntries = m
}

// findEntryByPositionalIndex is for '$[1]' etc. in the DSL.
//
// Notes:
//   - This is a linear search.
//   - Indices are 1-up not 0-up
//   - Indices -n..-1 are aliases for 1..n. In particular, it will be faster to
//     get the -1st field than the nth.
//   - Returns 0 on invalid index: 0, or < -n, or > n where n is the number of
//     fields.
func (mlrmap *Mlrmap) findEntryByPositionalIndex(position int64) *MlrmapEntry {
	if position > mlrmap.FieldCount || position < -mlrmap.FieldCount || position == 0 {
		return nil
	}
	if position > 0 {
		var i int64 = 1
		for pe := mlrmap.Head; pe != nil; pe = pe.Next {
			if i == position {
				return pe
			}
			i++
		}
		lib.InternalCodingErrorIf(true)
	} else {
		var i int64 = -1
		for pe := mlrmap.Tail; pe != nil; pe = pe.Prev {
			if i == position {
				return pe
			}
			i--
		}
		lib.InternalCodingErrorIf(true)
	}
	lib.InternalCodingErrorIf(true)
	return nil
}

func (mlrmap *Mlrmap) PutCopyWithMlrvalIndex(key *Mlrval, value *Mlrval) error {
	// $[3] acts as $["3"]
	if key.IsStringOrInt() {
		mlrmap.PutCopy(key.String(), value)
		return nil
	}
	return fmt.Errorf(
		"record/map indices must be string, int, or array thereof; got %s", key.GetTypeName(),
	)
}

func (mlrmap *Mlrmap) PrependCopy(key string, value *Mlrval) {
	mlrmap.PrependReference(key, value.Copy())
}

// Merges that into mlrmap.
func (mlrmap *Mlrmap) Merge(other *Mlrmap) {
	for pe := other.Head; pe != nil; pe = pe.Next {
		mlrmap.PutCopy(pe.Key, pe.Value)
	}
}

// Exposed for the 'nest' verb
func (mlrmap *Mlrmap) GetEntry(key string) *MlrmapEntry {
	return mlrmap.findEntry(key)
}

func (mlrmap *Mlrmap) GetKeys() []string {
	keys := make([]string, mlrmap.FieldCount)
	i := 0
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		keys[i] = pe.Key
		i++
	}
	return keys
}

// Returns an array of keys, not including the ones specified. The ones
// specified are to be passed in as a map from string to bool, as Go
// doesn't have hash-sets.
func (mlrmap *Mlrmap) GetKeysExcept(exceptions map[string]bool) []string {
	keys := []string{}
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		if _, present := exceptions[pe.Key]; !present {
			keys = append(keys, pe.Key)
		}
	}
	return keys
}

// TODO: put error-return into this API
func (mlrmap *Mlrmap) PutNameWithPositionalIndex(position int64, name *Mlrval) {
	positionalEntry := mlrmap.findEntryByPositionalIndex(position)

	if positionalEntry == nil {
		// TODO: handle out-of-bounds accesses
		return
	}

	s := ""
	if name.IsString() || name.IsInt() {
		s = name.String()
	} else {
		// TODO: return MlrvalFromError()
		return
	}

	// E.g. there are fields named 'a' and 'b', as positions 1 and 2,
	// and the user does '$[[1]] = $[[2]]'. Then there would be two b's.
	mapEntry := mlrmap.findEntry(s)
	if mapEntry != nil && mapEntry != positionalEntry {
		if mlrmap.keysToEntries != nil {
			delete(mlrmap.keysToEntries, positionalEntry.Key)
		}
		mlrmap.Unlink(mapEntry)
	}

	lib.InternalCodingErrorIf(s == "")
	positionalEntry.Key = s

	if mlrmap.keysToEntries != nil {
		mlrmap.keysToEntries[s] = positionalEntry
	}
}

func (mlrmap *Mlrmap) GetWithPositionalIndex(position int64) *Mlrval {
	mapEntry := mlrmap.findEntryByPositionalIndex(position)
	if mapEntry == nil {
		return nil
	}
	return mapEntry.Value
}

func (mlrmap *Mlrmap) GetWithMlrvalIndex(index *Mlrval) (*Mlrval, error) {
	if index.IsArray() {
		return mlrmap.getWithMlrvalArrayIndex(index)
	}
	return mlrmap.getWithMlrvalSingleIndex(index)
}

// This lets the user do '$y = $x[ ["a", "b", "c"] ]' in lieu of
// '$y = $x["a"]["b"]["c"]'.
func (mlrmap *Mlrmap) getWithMlrvalArrayIndex(index *Mlrval) (*Mlrval, error) {
	current := mlrmap
	var retval *Mlrval = nil
	lib.InternalCodingErrorIf(!index.IsArray())
	array := index.intf.([]*Mlrval)
	n := len(array)
	for i, piece := range array {
		next, err := current.GetWithMlrvalIndex(piece)
		if err != nil {
			return nil, err
		}
		if next == nil {
			return nil, nil
		}
		if i < n-1 {
			if !next.IsMap() {
				return nil, fmt.Errorf("cannot multi-index non-map")
			}
			current = next.intf.(*Mlrmap)
		} else {
			retval = next.Copy()
		}
	}
	return retval, nil
}

func (mlrmap *Mlrmap) getWithMlrvalSingleIndex(index *Mlrval) (*Mlrval, error) {
	if index.IsString() {
		return mlrmap.Get(index.printrep), nil
	} else if index.IsInt() {
		// $[3] acts as $["3"]
		return mlrmap.Get(index.String()), nil
	}
	return nil, fmt.Errorf(
		"record/map indices must be string, int, or array thereof; got %s", index.GetTypeName(),
	)
}

// For '$[[1]]' etc. in the DSL.
//
// Notes:
// * This is a linear search.
// * Indices are 1-up not 0-up
// * Indices -n..-1 are aliases for 1..n. In particular, it will be faster to
//   get the -1st field than the nth.
// * Returns 0 on invalid index: 0, or < -n, or > n where n is the number of
//   fields.

func (mlrmap *Mlrmap) GetNameAtPositionalIndex(position int64) (string, bool) {
	mapEntry := mlrmap.findEntryByPositionalIndex(position)
	if mapEntry == nil {
		return "", false
	}
	return mapEntry.Key, true
}

// Copies the key and value (deep-copying in case the value is array/map).
// This is safe for DSL use. See also PutReference.

// TODO: put error-return into this API
func (mlrmap *Mlrmap) PutCopyWithPositionalIndex(position int64, value *Mlrval) {
	mapEntry := mlrmap.findEntryByPositionalIndex(position)

	if mapEntry != nil {
		mapEntry.Value = value.Copy()
	} else {
		return
	}
}

func (mlrmap *Mlrmap) RemoveWithPositionalIndex(position int64) {
	mapEntry := mlrmap.findEntryByPositionalIndex(position)
	if mapEntry != nil {
		mlrmap.Unlink(mapEntry)
	}
}

func (mlrmap *Mlrmap) Equals(other *Mlrmap) bool {
	if mlrmap.FieldCount != other.FieldCount {
		return false
	}
	if !mlrmap.Contains(other) {
		return false
	}
	if !other.Contains(mlrmap) {
		return false
	}
	return true
}

// True if this contains other, i.e. if other is contained by mlrmap.
// * If any key of other is not a key of this, return false.
// * If any key of other has a value unequal to this' value at the same key, return false.
// * Else return true
func (mlrmap *Mlrmap) Contains(other *Mlrmap) bool {
	for pe := other.Head; pe != nil; pe = pe.Next {
		if !mlrmap.Has(pe.Key) {
			return false
		}
		thisval := mlrmap.Get(pe.Key)
		thatval := pe.Value
		if !Equals(thisval, thatval) {
			return false
		}
	}
	return true
}

func (mlrmap *Mlrmap) Clear() {
	mlrmap.FieldCount = 0
	// Assuming everything unreferenced is getting GC'ed by the Go runtime
	mlrmap.Head = nil
	mlrmap.Tail = nil
	if mlrmap.keysToEntries != nil {
		mlrmap.keysToEntries = make(map[string]*MlrmapEntry)
	}
}

func (mlrmap *Mlrmap) Copy() *Mlrmap {
	var other *Mlrmap
	if mlrmap.autoHash {
		// Preserve lazy-hashing semantics: don't force an eager index on the
		// copy just because the source happens to have built one.
		other = newMlrmapLazyHashed()
	} else {
		other = NewMlrmapMaybeHashed(mlrmap.isHashed())
	}
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		other.PutCopy(pe.Key, pe.Value)
	}
	return other
}

// Returns true if it was found and removed
func (mlrmap *Mlrmap) Remove(key string) bool {
	pe := mlrmap.findEntry(key)
	if pe == nil {
		return false
	}
	mlrmap.Unlink(pe)
	return true
}

func (mlrmap *Mlrmap) MoveToHead(key string) {
	pe := mlrmap.findEntry(key)
	if pe != nil {
		mlrmap.Unlink(pe)
		mlrmap.linkAtHead(pe)
	}
}

func (mlrmap *Mlrmap) MoveToTail(key string) {
	pe := mlrmap.findEntry(key)
	if pe != nil {
		mlrmap.Unlink(pe)
		mlrmap.linkAtTail(pe)
	}
}

// E.g. '$name[1]["foo"] = "bar"' or '$*["foo"][1] = "bar"'
// In the former case the indices are ["name", 1, "foo"] and in the latter case
// the indices are ["foo", 1]. See also indexed-lvalues.md.
//
// This is a Mlrmap (from string to Mlrval) so we handle the first level of
// indexing here, then pass the remaining indices to the Mlrval at the desired
// slot.

func (mlrmap *Mlrmap) PutIndexed(indices []*Mlrval, rvalue *Mlrval) error {
	return putIndexedOnMap(mlrmap, indices, rvalue)
}

func (mlrmap *Mlrmap) RemoveIndexed(indices []*Mlrval) error {
	return removeIndexedOnMap(mlrmap, indices)
}

func (mlrmap *Mlrmap) GetKeysJoined() string {
	var buffer bytes.Buffer
	i := 0
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		if i > 0 {
			buffer.WriteString(",")
		}
		i++
		buffer.WriteString(pe.Key)
	}
	return buffer.String()
}

// For mlr reshape
func (mlrmap *Mlrmap) GetValuesJoined() string {
	var buffer bytes.Buffer
	i := 0
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		if i > 0 {
			buffer.WriteString(",")
		}
		i++
		buffer.WriteString(pe.Value.String())
	}
	return buffer.String()
}

// For group-by in several transformers.  If the record is 'a=x,b=y,c=3,d=4,e=5' and
// selectedFieldNames is 'a,b,c' then values are 'x,y,3'. This is returned as a
// comma-joined string.  The boolean ok is false if not all selected field
// names were present in the record.
//
// It's OK for the selected-field-namees list to be empty. This happens for
// transformers which support a -g option but are invoked without it (e.g. 'mlr tail
// -n 1' vs 'mlr tail -n 1 -g a,b,c'). In this case the return value is simply
// the empty string.

func (mlrmap *Mlrmap) GetSelectedValuesJoined(selectedFieldNames []string) (string, bool) {
	if len(selectedFieldNames) == 0 {
		// The fall-through is functionally correct, but this is quicker with
		// skipping setting up an empty bytes-buffer and stringifying it. The
		// non-grouped case is quite normal and is worth optimizing for.
		return "", true
	}

	var buffer bytes.Buffer
	for i, selectedFieldName := range selectedFieldNames {
		entry := mlrmap.findEntry(selectedFieldName)
		if entry == nil {
			return "", false
		}
		if i > 0 {
			buffer.WriteString(",")
		}
		// This may be an array or map, or just a string/int/etc. Regardless we
		// stringify it.
		buffer.WriteString(entry.Value.String())
	}
	return buffer.String(), true
}

// As with GetSelectedValuesJoined but also returning the array of mlrvals.
// For sort.
// TODO: put 'Copy' into the method name
func (mlrmap *Mlrmap) GetSelectedValuesAndJoined(selectedFieldNames []string) (
	string,
	[]*Mlrval,
	bool,
) {
	mlrvals := make([]*Mlrval, 0, len(selectedFieldNames))

	if len(selectedFieldNames) == 0 {
		// The fall-through is functionally correct, but this is quicker with
		// skipping setting up an empty bytes-buffer and stringifying it. The
		// non-grouped case is quite normal and is worth optimizing for.
		return "", mlrvals, true
	}

	var buffer bytes.Buffer
	for i, selectedFieldName := range selectedFieldNames {
		entry := mlrmap.findEntry(selectedFieldName)
		if entry == nil {
			return "", mlrvals, false
		}
		if i > 0 {
			buffer.WriteString(",")
		}
		// This may be an array or map, or just a string/int/etc. Regardless we
		// stringify it.
		buffer.WriteString(entry.Value.String())
		mlrvals = append(mlrvals, entry.Value.Copy())
	}
	return buffer.String(), mlrvals, true
}

// As above but only returns the array. Also, these are references, NOT copies.
// For step and join.
func (mlrmap *Mlrmap) ReferenceSelectedValues(selectedFieldNames []string) ([]*Mlrval, bool) {
	allFound := true
	mlrvals := make([]*Mlrval, 0, len(selectedFieldNames))

	for _, selectedFieldName := range selectedFieldNames {
		entry := mlrmap.findEntry(selectedFieldName)
		if entry != nil {
			mlrvals = append(mlrvals, entry.Value)
		} else {
			mlrvals = append(mlrvals, nil)
			allFound = false
		}
	}
	return mlrvals, allFound
}

// TODO: rename to CopySelectedValues
// As previous but with copying. For stats1.
func (mlrmap *Mlrmap) GetSelectedValues(selectedFieldNames []string) ([]*Mlrval, bool) {
	allFound := true
	mlrvals := make([]*Mlrval, 0, len(selectedFieldNames))

	for _, selectedFieldName := range selectedFieldNames {
		entry := mlrmap.findEntry(selectedFieldName)
		if entry != nil {
			mlrvals = append(mlrvals, entry.Value.Copy())
		} else {
			mlrvals = append(mlrvals, nil)
			allFound = false
		}
	}
	return mlrvals, allFound
}

// Similar to the above but only checks availability. For join.
func (mlrmap *Mlrmap) HasSelectedKeys(selectedFieldNames []string) bool {
	for _, selectedFieldName := range selectedFieldNames {
		entry := mlrmap.findEntry(selectedFieldName)
		if entry == nil {
			return false
		}
	}
	return true
}

// For mlr nest implode across records.
func (mlrmap *Mlrmap) GetKeysJoinedExcept(px *MlrmapEntry) string {
	var buffer bytes.Buffer
	i := 0
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		if pe == px {
			continue
		}
		if i > 0 {
			buffer.WriteString(",")
		}
		i++
		buffer.WriteString(pe.Key)
	}
	return buffer.String()
}

// For mlr nest implode across records.
func (mlrmap *Mlrmap) GetValuesJoinedExcept(px *MlrmapEntry) string {
	var buffer bytes.Buffer
	i := 0
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		if pe == px {
			continue
		}
		if i > 0 {
			buffer.WriteString(",")
		}
		i++
		// This may be an array or map, or just a string/int/etc. Regardless we
		// stringify it.
		buffer.WriteString(pe.Value.String())
	}
	return buffer.String()
}

func (mlrmap *Mlrmap) Rename(oldKey string, newKey string) bool {
	entry := mlrmap.findEntry(oldKey)
	if entry == nil {
		// Rename field from 'a' to 'b' where there is no 'a': no-op
		return false
	}

	existing := mlrmap.findEntry(newKey)
	if existing == nil {
		// Rename field from 'a' to 'b' where there is no 'b': simple update
		entry.Key = newKey

		if mlrmap.keysToEntries != nil {
			delete(mlrmap.keysToEntries, oldKey)
			mlrmap.keysToEntries[newKey] = entry
		}
	} else {
		// Rename field from 'a' to 'b' where there are both 'a' and 'b':
		// remove old 'a' and put its value into the slot of 'b'.
		existing.Value = entry.Value
		if mlrmap.keysToEntries != nil {
			delete(mlrmap.keysToEntries, oldKey)
		}
		mlrmap.Unlink(entry)
	}

	return true
}

func (mlrmap *Mlrmap) Label(newNames []string) {
	other := NewMlrmapAsRecord()

	i := 0
	numNewNames := len(newNames)
	for i < numNewNames {
		pe := mlrmap.pop()
		if pe == nil {
			break
		}
		// Old record will be GC'ed: just move pointers
		other.PutReference(newNames[i], pe.Value)
		i++
	}

	for {
		pe := mlrmap.pop()
		if pe == nil {
			break
		}
		// Example:
		// * Input record has keys a,b,i,x,y
		// * Requested labeling is d,x,f
		// * The first three records a,b,i should be renamed to d,x,f
		// * The old x needs to disappear (for key-uniqueness)
		// * The y field is carried through
		if other.Has(pe.Key) {
			continue
		}
		other.PutReference(pe.Key, pe.Value)
	}

	*mlrmap = *other
}

func (mlrmap *Mlrmap) SortByKey() {
	keys := mlrmap.GetKeys()

	slices.Sort(keys)

	other := NewMlrmapAsRecord()

	for _, key := range keys {
		// Old record will be GC'ed: just move pointers
		other.PutReference(key, mlrmap.Get(key))
	}

	*mlrmap = *other
}

func (mlrmap *Mlrmap) SortByKeyRecursively() {
	keys := mlrmap.GetKeys()

	slices.Sort(keys)

	other := NewMlrmapAsRecord()

	for _, key := range keys {
		// Old record will be GC'ed: just move pointers
		val := mlrmap.Get(key)
		if val.IsMap() {
			val.intf.(*Mlrmap).SortByKeyRecursively()
		}
		other.PutReference(key, val)
	}

	*mlrmap = *other
}

// Only checks to see if the first entry is a map. For emit/emitp.
func (mlrmap *Mlrmap) IsNested() bool {
	if mlrmap.Head == nil {
		return false
	} else if mlrmap.Head.Value.GetMap() == nil {
		//TODO: check IsArrayOrMap()
		return false
	}
	return true
}

// PRIVATE METHODS

func (mlrmap *Mlrmap) Unlink(pe *MlrmapEntry) {
	if pe == mlrmap.Head {
		if pe == mlrmap.Tail {
			mlrmap.Head = nil
			mlrmap.Tail = nil
		} else {
			mlrmap.Head = pe.Next
			pe.Next.Prev = nil
		}
	} else {
		pe.Prev.Next = pe.Next
		if pe == mlrmap.Tail {
			mlrmap.Tail = pe.Prev
		} else {
			pe.Next.Prev = pe.Prev
		}
	}
	if mlrmap.keysToEntries != nil {
		delete(mlrmap.keysToEntries, pe.Key)
	}
	mlrmap.FieldCount--
}

// Does not check for duplicate keys
func (mlrmap *Mlrmap) linkAtHead(pe *MlrmapEntry) {
	if mlrmap.Head == nil {
		pe.Prev = nil
		pe.Next = nil
		mlrmap.Head = pe
		mlrmap.Tail = pe
	} else {
		pe.Prev = nil
		pe.Next = mlrmap.Head
		mlrmap.Head.Prev = pe
		mlrmap.Head = pe
	}
	if mlrmap.keysToEntries != nil {
		mlrmap.keysToEntries[pe.Key] = pe
	}
	mlrmap.FieldCount++
}

// Does not check for duplicate keys
func (mlrmap *Mlrmap) linkAtTail(pe *MlrmapEntry) {
	if mlrmap.Head == nil {
		pe.Prev = nil
		pe.Next = nil
		mlrmap.Head = pe
		mlrmap.Tail = pe
	} else {
		pe.Prev = mlrmap.Tail
		pe.Next = nil
		mlrmap.Tail.Next = pe
		mlrmap.Tail = pe
	}
	if mlrmap.keysToEntries != nil {
		mlrmap.keysToEntries[pe.Key] = pe
	}
	mlrmap.FieldCount++
}

func (mlrmap *Mlrmap) pop() *MlrmapEntry {
	if mlrmap.Head == nil {
		return nil
	}
	pe := mlrmap.Head
	mlrmap.Unlink(pe)
	return pe
}

// ToPairsArray is used for sorting maps by key/value/etc, e.g. the sortmf DSL function.
func (mlrmap *Mlrmap) ToPairsArray() []MlrmapPair {
	pairsArray := make([]MlrmapPair, mlrmap.FieldCount)
	i := 0
	for pe := mlrmap.Head; pe != nil; pe = pe.Next {
		pairsArray[i].Key = pe.Key
		pairsArray[i].Value = pe.Value.Copy()
		i++
	}

	return pairsArray
}

// MlrmapFromPairsArray is used for sorting maps by key/value/etc, e.g. the sortmf DSL function.
func MlrmapFromPairsArray(pairsArray []MlrmapPair) *Mlrmap {
	mlrmap := NewMlrmap()
	for i := range pairsArray {
		mlrmap.PutCopy(pairsArray[i].Key, pairsArray[i].Value)
	}

	return mlrmap
}

// GetFirstPair returns the first key-value pair as its own map.  If the map is
// empty (i.e. there is no first pair) it returns nil.
func (mlrmap *Mlrmap) GetFirstPair() *Mlrmap {
	if mlrmap.Head == nil {
		return nil
	}
	pair := NewMlrmap()
	pair.PutCopy(mlrmap.Head.Key, mlrmap.Head.Value)
	return pair
}
