// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package maps

import (
	"slices"

	"github.com/gohugoio/hugo/common/hashing"
)

// Ordered is a map that can be iterated in the order of insertion.
// Note that insertion order is not affected if a key is re-inserted into the map.
// In a nil map, all operations are no-ops.
// This is not thread safe.
type Ordered[K comparable, T any] struct {
	// The keys in the order they were added.
	keys []K
	// The values.
	values map[K]T
}

// NewOrdered creates a new Ordered map.
func NewOrdered[K comparable, T any]() *Ordered[K, T] {
	return &Ordered[K, T]{values: make(map[K]T)}
}

// Set sets the value for the given key.
// Note that insertion order is not affected if a key is re-inserted into the map.
func (m *Ordered[K, T]) Set(key K, value T) {
	if m == nil {
		return
	}
	// Check if key already exists.
	if _, found := m.values[key]; !found {
		m.keys = append(m.keys, key)
	}
	m.values[key] = value
}

// Get gets the value for the given key.
func (m *Ordered[K, T]) Get(key K) (T, bool) {
	if m == nil {
		var v T
		return v, false
	}
	value, found := m.values[key]
	return value, found
}

// Has returns whether the given key exists in the map.
func (m *Ordered[K, T]) Has(key K) bool {
	if m == nil {
		return false
	}
	_, found := m.values[key]
	return found
}

// Delete deletes the value for the given key.
func (m *Ordered[K, T]) Delete(key K) {
	if m == nil {
		return
	}
	delete(m.values, key)
	for i, k := range m.keys {
		if k == key {
			m.keys = slices.Delete(m.keys, i, i+1)
			break
		}
	}
}

// Clone creates a shallow copy of the map.
func (m *Ordered[K, T]) Clone() *Ordered[K, T] {
	if m == nil {
		return nil
	}
	clone := NewOrdered[K, T]()
	for _, k := range m.keys {
		clone.Set(k, m.values[k])
	}
	return clone
}

// Keys returns the keys in the order they were added.
func (m *Ordered[K, T]) Keys() []K {
	if m == nil {
		return nil
	}
	return m.keys
}

// Values returns the values in the order they were added.
func (m *Ordered[K, T]) Values() []T {
	if m == nil {
		return nil
	}
	var values []T
	for _, k := range m.keys {
		values = append(values, m.values[k])
	}
	return values
}

// Len returns the number of items in the map.
func (m *Ordered[K, T]) Len() int {
	if m == nil {
		return 0
	}
	return len(m.keys)
}

// Range calls f sequentially for each key and value present in the map.
// If f returns false, range stops the iteration.
// TODO(bep) replace with iter.Seq2 when we bump go Go 1.24.
func (m *Ordered[K, T]) Range(f func(key K, value T) bool) {
	if m == nil {
		return
	}
	for _, k := range m.keys {
		if !f(k, m.values[k]) {
			return
		}
	}
}

// Hash calculates a hash from the values.
func (m *Ordered[K, T]) Hash() (uint64, error) {
	if m == nil {
		return 0, nil
	}
	return hashing.Hash(m.values)
}
