// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1

package syncmap

import "sync"

// SyncMap implements a map similar to sync.Map, but with generics and with an equality
// in the values specified by an "ID()" method.
type SyncMap[K comparable, V IDer] struct {
	// lock is used to synchronize access to the map
	lock sync.RWMutex
	// data holds the actual data
	data map[K]V
}

// NewSyncMap returns a new, empty SyncMap.
func NewSyncMap[K comparable, V IDer]() *SyncMap[K, V] {
	return &SyncMap[K, V]{
		data: make(map[K]V),
	}
}

// Get returns the value for the given key.
func (m *SyncMap[K, V]) Get(k K) V {
	m.lock.RLock()
	defer m.lock.RUnlock()
	return m.data[k]
}

// Pop deletes and returns the value for the given key, if it exists.
func (m *SyncMap[K, V]) Pop(k K) V {
	m.lock.Lock()
	defer m.lock.Unlock()
	v, ok := m.data[k]
	if ok {
		delete(m.data, k)
	}
	return v
}

// PopIfEqual deletes and returns the value for the given key, if it exists
// and only if the ID is equal to the provided string.
func (m *SyncMap[K, V]) PopIfEqual(k K, id string) V {
	m.lock.Lock()
	defer m.lock.Unlock()
	v, ok := m.data[k]
	if ok && v.ID() == id {
		delete(m.data, k)
		return v
	}
	var zero V
	return zero
}

// Put adds the given key-value pair to the map and returns the previous value, if any.
func (m *SyncMap[K, V]) Put(k K, v V) V {
	m.lock.Lock()
	defer m.lock.Unlock()
	oldV := m.data[k]
	m.data[k] = v
	return oldV
}

// PutIfEmpty adds the given key-value pair to the map only if there is no value already in it,
// and returns the new value and true if so.
// If there is already a value, it returns the existing value and false.
func (m *SyncMap[K, V]) PutIfEmpty(k K, v V) (V, bool) {
	m.lock.Lock()
	defer m.lock.Unlock()
	oldV, ok := m.data[k]
	if ok {
		return oldV, false
	}
	m.data[k] = v
	return v, true
}

// Clear deletes all entries from the map, and returns the previous map.
func (m *SyncMap[K, V]) Clear() map[K]V {
	m.lock.Lock()
	defer m.lock.Unlock()
	old := m.data
	m.data = make(map[K]V)
	return old
}

// Values returns a copy of all values in the map.
func (m *SyncMap[K, V]) Values() []V {
	m.lock.RLock()
	defer m.lock.RUnlock()

	values := make([]V, 0, len(m.data))
	for _, v := range m.data {
		values = append(values, v)
	}
	return values
}

// IDer is used to extract an ID that SyncMap uses for equality checking.
type IDer interface {
	ID() string
}
