package autocomplete

import (
	"bytes"
	"cmp"
	"slices"
	"sync"

	"github.com/junegunn/fzf/src/algo"
	"github.com/junegunn/fzf/src/util"
)

// Set is an autocomplete set.
type Set[V any] struct {
	keys []util.Chars
	vals []V
}

// Add associates a value with a key in the autocomplete set.
// The behavior is undefined if the key already has a value.
func (s *Set[V]) Add(key string, val V) {
	k := util.ToChars([]byte(key))
	i, _ := slices.BinarySearchFunc(s.keys, k, func(a, b util.Chars) int {
		return bytes.Compare(a.Bytes(), b.Bytes())
	})
	s.keys = slices.Insert(s.keys, i, k)
	s.vals = slices.Insert(s.vals, i, val)
}

// Find appends to r all values in the set with keys that key matches.
func (s *Set[V]) Find(r []V, key string) []V {
	initFzf()
	var (
		p = []rune(key)

		got  []V
		t    []algo.Result
		slab util.Slab
	)
	for i := range s.keys {
		res, _ := algo.FuzzyMatchV2(false, true, true, &s.keys[i], p, false, &slab)
		if res.Score <= 0 {
			continue
		}
		j, _ := slices.BinarySearchFunc(t, res, func(a, b algo.Result) int { return -cmp.Compare(a.Score, b.Score) })
		// Insert after all other matches with the same score for stability.
		for j < len(t) && t[j].Score == res.Score {
			j++
		}
		t = slices.Insert(t, j, res)
		got = slices.Insert(got, j, s.vals[i])
	}
	return append(r, got...)
}

var initFzf = sync.OnceFunc(func() { algo.Init("default") })
