package dirty

import (
	"maps"
	"sync"

	"github.com/microsoft/typescript-go/internal/collections"
)

type lockedEntry[K comparable, V Cloneable[V]] struct {
	e *SyncMapEntry[K, V]
}

func (e *lockedEntry[K, V]) Value() V {
	return e.e.valueLocked()
}

func (e *lockedEntry[K, V]) Original() V {
	return e.e.original
}

func (e *lockedEntry[K, V]) Dirty() bool {
	return e.e.dirty
}

func (e *lockedEntry[K, V]) Change(apply func(V)) {
	e.e.changeLocked(apply)
}

func (e *lockedEntry[K, V]) ChangeIf(cond func(V) bool, apply func(V)) bool {
	if cond(e.e.valueLocked()) {
		e.e.changeLocked(apply)
		return true
	}
	return false
}

func (e *lockedEntry[K, V]) Delete() {
	e.e.deleteLocked()
}

func (e *lockedEntry[K, V]) Locked(fn func(Value[V])) {
	fn(e)
}

type SyncMapEntry[K comparable, V Cloneable[V]] struct {
	m  *SyncMap[K, V]
	mu sync.Mutex
	mapEntry[K, V]
	// proxyFor is set when this entry loses a race to become the dirty entry
	// for a value. Since two goroutines hold a reference to two entries that
	// may try to mutate the same underlying value, all mutations are routed
	// through the one that actually exists in the dirty map.
	proxyFor *SyncMapEntry[K, V]
}

func (e *SyncMapEntry[K, V]) Value() V {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.proxyFor != nil {
		return e.proxyFor.Value()
	}
	return e.valueLocked()
}

func (e *SyncMapEntry[K, V]) valueLocked() V {
	if e.delete {
		var zero V
		return zero
	}
	return e.value
}

func (e *SyncMapEntry[K, V]) Dirty() bool {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.proxyFor != nil {
		return e.proxyFor.Dirty()
	}
	return e.dirty
}

func (e *SyncMapEntry[K, V]) Locked(fn func(Value[V])) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.proxyFor != nil {
		e.proxyFor.Locked(fn)
		return
	}
	fn(&lockedEntry[K, V]{e: e})
}

func (e *SyncMapEntry[K, V]) Change(apply func(V)) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.proxyFor != nil {
		e.proxyFor.Change(apply)
		return
	}
	e.changeLocked(apply)
}

func (e *SyncMapEntry[K, V]) changeLocked(apply func(V)) {
	if e.dirty {
		apply(e.value)
		return
	}

	entry, loaded := e.m.dirty.LoadOrStore(e.key, e)
	if loaded {
		entry.mu.Lock()
		defer entry.mu.Unlock()
	}
	if !entry.dirty {
		entry.value = entry.value.Clone()
		entry.dirty = true
	}
	if loaded {
		e.proxyFor = entry
		e.value = entry.value
		e.dirty = true
		e.delete = entry.delete
	}
	apply(entry.value)
}

func (e *SyncMapEntry[K, V]) ChangeIf(cond func(V) bool, apply func(V)) bool {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.proxyFor != nil {
		return e.proxyFor.ChangeIf(cond, apply)
	}

	if cond(e.value) {
		e.changeLocked(apply)
		return true
	}
	return false
}

func (e *SyncMapEntry[K, V]) Delete() {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.proxyFor != nil {
		e.proxyFor.Delete()
		return
	}

	if e.dirty {
		e.delete = true
		return
	}
	entry, loaded := e.m.dirty.LoadOrStore(e.key, e)
	if loaded {
		entry.mu.Lock()
		defer entry.mu.Unlock()
		e.delete = true
	} else {
		entry.delete = true
	}
}

func (e *SyncMapEntry[K, V]) deleteLocked() {
	if e.dirty {
		e.delete = true
		return
	}
	entry, loaded := e.m.dirty.LoadOrStore(e.key, e)
	if loaded {
		entry.mu.Lock()
		defer entry.mu.Unlock()
		e.proxyFor = entry
		e.value = entry.value
		e.delete = true
		e.dirty = entry.dirty
	}
	entry.delete = true
}

func (e *SyncMapEntry[K, V]) DeleteIf(cond func(V) bool) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.proxyFor != nil {
		e.proxyFor.DeleteIf(cond)
		return
	}
	if cond(e.value) {
		e.deleteLocked()
	}
}

type SyncMap[K comparable, V Cloneable[V]] struct {
	base  map[K]V
	dirty collections.SyncMap[K, *SyncMapEntry[K, V]]
}

func NewSyncMap[K comparable, V Cloneable[V]](base map[K]V) *SyncMap[K, V] {
	return &SyncMap[K, V]{
		base:  base,
		dirty: collections.SyncMap[K, *SyncMapEntry[K, V]]{},
	}
}

func (m *SyncMap[K, V]) Load(key K) (*SyncMapEntry[K, V], bool) {
	if entry, ok := m.dirty.Load(key); ok {
		entry.mu.Lock()
		defer entry.mu.Unlock()
		if entry.delete {
			return nil, false
		}
		return entry, true
	}
	if val, ok := m.base[key]; ok {
		return &SyncMapEntry[K, V]{
			m: m,
			mapEntry: mapEntry[K, V]{
				key:      key,
				original: val,
				value:    val,
				dirty:    false,
				delete:   false,
			},
		}, true
	}
	return nil, false
}

func (m *SyncMap[K, V]) LoadOrStore(key K, value V) (*SyncMapEntry[K, V], bool) {
	// Check for existence in the base map first so the sync map access is atomic.
	if baseValue, ok := m.base[key]; ok {
		if dirty, ok := m.dirty.Load(key); ok {
			dirty.mu.Lock()
			defer dirty.mu.Unlock()
			if dirty.delete {
				return nil, false
			}
			return dirty, true
		}
		return &SyncMapEntry[K, V]{
			m: m,
			mapEntry: mapEntry[K, V]{
				key:      key,
				original: baseValue,
				value:    baseValue,
				dirty:    false,
				delete:   false,
			},
		}, true
	}
	entry, loaded := m.dirty.LoadOrStore(key, &SyncMapEntry[K, V]{
		m: m,
		mapEntry: mapEntry[K, V]{
			key:   key,
			value: value,
			dirty: true,
		},
	})
	if loaded {
		entry.mu.Lock()
		defer entry.mu.Unlock()
		if entry.delete {
			return nil, false
		}
	}
	return entry, loaded
}

func (m *SyncMap[K, V]) Delete(key K) {
	entry, loaded := m.dirty.LoadOrStore(key, &SyncMapEntry[K, V]{
		m: m,
		mapEntry: mapEntry[K, V]{
			key:      key,
			original: m.base[key],
			delete:   true,
		},
	})
	if loaded {
		entry.Delete()
	}
}

func (m *SyncMap[K, V]) Range(fn func(*SyncMapEntry[K, V]) bool) {
	seenInDirty := make(map[K]struct{})
	m.dirty.Range(func(key K, entry *SyncMapEntry[K, V]) bool {
		seenInDirty[key] = struct{}{}
		entry.mu.Lock()
		deleted := entry.delete
		entry.mu.Unlock()
		if !deleted && !fn(entry) {
			return false
		}
		return true
	})
	for key, value := range m.base {
		if _, ok := seenInDirty[key]; ok {
			continue // already processed in dirty entries
		}
		if !fn(&SyncMapEntry[K, V]{m: m, mapEntry: mapEntry[K, V]{
			key:      key,
			original: value,
			value:    value,
			dirty:    false,
		}}) {
			break
		}
	}
}

type FinalizationHooks[K comparable, V any] struct {
	OnDelete func(key K, value V)
	OnChange func(key K, oldValue V, newValue V)
	OnAdd    func(key K, value V)
}

func (m *SyncMap[K, V]) finalize(hooks FinalizationHooks[K, V]) (map[K]V, bool) {
	var changed bool
	result := m.base
	ensureCloned := func() {
		if !changed {
			if m.base == nil {
				result = make(map[K]V)
			} else {
				result = maps.Clone(m.base)
			}
			changed = true
		}
	}

	m.dirty.Range(func(key K, entry *SyncMapEntry[K, V]) bool {
		entry.mu.Lock()
		defer entry.mu.Unlock()
		if entry.delete {
			ensureCloned()
			if hooks.OnDelete != nil {
				hooks.OnDelete(key, entry.value)
			}
			delete(result, key)
		} else if entry.dirty {
			ensureCloned()
			if hooks.OnChange != nil || hooks.OnAdd != nil {
				if _, ok := m.base[key]; ok {
					if hooks.OnChange != nil {
						hooks.OnChange(key, entry.original, entry.value)
					}
				} else if hooks.OnAdd != nil {
					hooks.OnAdd(key, entry.value)
				}
			}
			result[key] = entry.value
		}
		return true
	})
	return result, changed
}

func (m *SyncMap[K, V]) Finalize() (map[K]V, bool) {
	return m.finalize(FinalizationHooks[K, V]{})
}

func (m *SyncMap[K, V]) FinalizeWith(hooks FinalizationHooks[K, V]) (map[K]V, bool) {
	return m.finalize(hooks)
}
