package uint64set

import (
	"fmt"
	"math/bits"
	"slices"
	"sort"
	"sync"
	"sync/atomic"
	"unsafe"

	"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
)

// Set is a fast set for uint64.
//
// It should work faster than map[uint64]struct{} for semi-sparse uint64 values
// such as MetricIDs generated by lib/storage.
//
// It is unsafe calling Set methods from concurrent goroutines.
type Set struct {
	itemsCount int
	buckets    bucket32Sorter

	// Most likely the buckets contains only a single item, so put it here for performance reasons
	// in order to improve memory locality.
	scratchBuckets [1]bucket32
}

type bucket32Sorter []bucket32

func (s *bucket32Sorter) Len() int { return len(*s) }
func (s *bucket32Sorter) Less(i, j int) bool {
	a := *s
	return a[i].hi < a[j].hi
}
func (s *bucket32Sorter) Swap(i, j int) {
	a := *s
	a[i], a[j] = a[j], a[i]
}

// Unmarshal creates an instance of a set from bytes.
//
// The first 8 src bytes contain the set length (number of the elements in the
// set). Since each element is 8-byte long, the number of remaining src bytes
// must be at least 8*length, or else the function will return an error. The
// function will read exactly 8*length bytes and construct an instance of a
// set. The remaining src bytes will be returned along with the set.
func Unmarshal(src []byte) (*Set, []byte, error) {
	if len(src) < 8 {
		return nil, nil, fmt.Errorf("cannot unmarshal uint64set; got %d bytes; want at least 8 bytes", len(src))
	}
	sLen := encoding.UnmarshalUint64(src)
	src = src[8:]
	if uint64(len(src)) < 8*sLen {
		return nil, nil, fmt.Errorf("cannot unmarshal uint64set; got %d bytes; want at least %d bytes", len(src), 8*sLen)
	}
	s := &Set{}
	for range sLen {
		e := encoding.UnmarshalUint64(src)
		src = src[8:]
		s.Add(e)
	}
	return s, src, nil
}

// Marshal encodes the set as a sequence of bytes.
//
// The first 8 bytes contain the length of the set (number of the elements the
// set contains). The subsequent bytes are actual uint64 elements.
//
// The marshaling result is appended to the end of dst, i.e. the initial dst
// content is not overwritten.
func (s *Set) Marshal(dst []byte) []byte {
	dst = encoding.MarshalUint64(dst, uint64(s.Len()))
	s.ForEach(func(part []uint64) bool {
		for _, e := range part {
			dst = encoding.MarshalUint64(dst, e)
		}
		return true
	})
	return dst
}

// Clone returns an independent copy of s.
func (s *Set) Clone() *Set {
	if s == nil || s.itemsCount == 0 {
		// Return an empty set, so data could be added into it later.
		return &Set{}
	}
	var dst Set
	dst.itemsCount = s.itemsCount
	if len(s.buckets) == 1 {
		dst.buckets = dst.scratchBuckets[:]
	} else {
		dst.buckets = make([]bucket32, len(s.buckets))
	}
	for i := range s.buckets {
		s.buckets[i].copyTo(&dst.buckets[i])
	}
	return &dst
}

func (s *Set) fixItemsCount() {
	n := 0
	for i := range s.buckets {
		n += s.buckets[i].getLen()
	}
	s.itemsCount = n
}

func (s *Set) cloneShallow() *Set {
	var dst Set
	dst.itemsCount = s.itemsCount
	if len(s.buckets) == 1 {
		dst.buckets = dst.scratchBuckets[:]
	}
	dst.buckets = append(dst.buckets[:0], s.buckets...)
	return &dst
}

// SizeBytes returns an estimate size of s in RAM.
func (s *Set) SizeBytes() uint64 {
	if s == nil {
		return 0
	}
	n := uint64(unsafe.Sizeof(*s))
	for i := range s.buckets {
		n += s.buckets[i].sizeBytes()
	}
	return n
}

// Len returns the number of distinct uint64 values in s.
func (s *Set) Len() int {
	if s == nil {
		return 0
	}
	return s.itemsCount
}

// Add adds x to s.
func (s *Set) Add(x uint64) {
	hi32 := uint32(x >> 32)
	lo32 := uint32(x)
	bs := s.buckets
	if len(bs) > 0 && bs[0].hi == hi32 {
		// Manually inline bucket32.add for performance reasons.
		hi16 := uint16(lo32 >> 16)
		lo16 := uint16(lo32)
		b32 := &bs[0]
		his := b32.b16his
		if n := b32.getHint(); n < uint32(len(his)) && his[n] == hi16 {
			bs := b32.buckets
			if n < uint32(len(bs)) && bs[n].add(lo16) {
				s.itemsCount++
			}
			return
		}
		if b32.addSlow(hi16, lo16) {
			s.itemsCount++
		}
		return
	}
	for i := range bs {
		b32 := &bs[i]
		if b32.hi == hi32 {
			if b32.add(lo32) {
				s.itemsCount++
			}
			return
		}
	}
	b32 := s.addBucket32()
	b32.hi = hi32
	_ = b32.add(lo32)
	s.itemsCount++
}

// AddMulti adds all the items from a to s.
//
// It is usually faster than calling s.Add() for each item in a.
//
// The caller is responsible for splitting a into items with clustered values.
func (s *Set) AddMulti(a []uint64) {
	if len(a) == 0 {
		return
	}
	hiPrev := uint32(a[0] >> 32)
	i := 0
	for j, x := range a {
		hi := uint32(x >> 32)
		if hi == hiPrev {
			continue
		}
		b32 := s.getOrCreateBucket32(hiPrev)
		s.itemsCount += b32.addMulti(a[i:j])
		hiPrev = hi
		i = j
	}
	b32 := s.getOrCreateBucket32(hiPrev)
	s.itemsCount += b32.addMulti(a[i:])
}

func (s *Set) getOrCreateBucket32(hi uint32) *bucket32 {
	bs := s.buckets
	for i := range bs {
		if bs[i].hi == hi {
			return &bs[i]
		}
	}
	b32 := s.addBucket32()
	b32.hi = hi
	return b32
}

func (s *Set) addBucket32() *bucket32 {
	if len(s.buckets) == 0 {
		s.buckets = s.scratchBuckets[:]
	} else {
		s.buckets = append(s.buckets, bucket32{})
	}
	return &s.buckets[len(s.buckets)-1]
}

// Has verifies whether x exists in s.
func (s *Set) Has(x uint64) bool {
	if s == nil {
		return false
	}
	return s.hasSlow(x)
}

func (s *Set) hasSlow(x uint64) bool {
	hi32 := uint32(x >> 32)
	lo32 := uint32(x)
	bs := s.buckets
	for i := range bs {
		b32 := &bs[i]
		if b32.hi == hi32 {
			return b32.has(lo32)
		}
	}
	return false
}

// Del deletes x from s.
func (s *Set) Del(x uint64) {
	hi := uint32(x >> 32)
	lo := uint32(x)
	bs := s.buckets
	if len(bs) > 0 && bs[0].hi == hi {
		if bs[0].del(lo) {
			s.itemsCount--
		}
		return
	}
	for i := range bs {
		b32 := &bs[i]
		if b32.hi == hi {
			if b32.del(lo) {
				s.itemsCount--
			}
			return
		}
	}
}

// AppendTo appends all the items from the set to dst and returns the result.
//
// The returned items are sorted.
//
// AppendTo can mutate s.
func (s *Set) AppendTo(dst []uint64) []uint64 {
	if s == nil {
		return dst
	}

	// pre-allocate memory for dst
	sLen := s.Len()
	dst = slicesutil.ExtendCapacity(dst, sLen)

	s.sort()
	for i := range s.buckets {
		dst = s.buckets[i].appendTo(dst)
	}
	return dst
}

func (s *Set) sort() {
	// sort s.buckets if it isn't sorted yet
	if !sort.IsSorted(&s.buckets) {
		sort.Sort(&s.buckets)
	}
}

// Union adds all the items from a to s.
func (s *Set) Union(a *Set) {
	s.union(a, false)
}

// UnionMayOwn adds all the items from a to s.
//
// It may own a if s is empty. This means that `a` cannot be used
// after the call to UnionMayOwn.
func (s *Set) UnionMayOwn(a *Set) {
	s.union(a, true)
}

func (s *Set) union(a *Set, mayOwn bool) {
	if a.Len() == 0 {
		// Fast path - nothing to union.
		return
	}
	if s.Len() == 0 {
		// Fast path - copy `a` to `s`.
		if !mayOwn {
			a = a.Clone()
		}
		*s = *a
		return
	}
	// Make shallow copy of `a`, since it can be modified by a.sort().
	if !mayOwn {
		a = a.cloneShallow()
	}
	a.sort()
	s.sort()
	i := 0
	j := 0
	sBucketsLen := len(s.buckets)
	for {
		for i < sBucketsLen && j < len(a.buckets) && s.buckets[i].hi < a.buckets[j].hi {
			i++
		}
		if i >= sBucketsLen {
			for j < len(a.buckets) {
				b32 := s.addBucket32()
				a.buckets[j].copyTo(b32)
				j++
			}
			break
		}
		for j < len(a.buckets) && a.buckets[j].hi < s.buckets[i].hi {
			b32 := s.addBucket32()
			a.buckets[j].copyTo(b32)
			j++
		}
		if j >= len(a.buckets) {
			break
		}
		if s.buckets[i].hi == a.buckets[j].hi {
			s.buckets[i].union(&a.buckets[j], mayOwn)
			i++
			j++
		}
	}
	s.fixItemsCount()
}

// Intersect removes all the items missing in a from s.
func (s *Set) Intersect(a *Set) {
	if s.Len() == 0 || a.Len() == 0 {
		// Fast path - the result is empty.
		*s = Set{}
		return
	}
	// Make shallow copy of `a`, since it can be modified by a.sort().
	a = a.cloneShallow()
	a.sort()
	s.sort()
	i := 0
	j := 0
	for {
		for i < len(s.buckets) && j < len(a.buckets) && s.buckets[i].hi < a.buckets[j].hi {
			s.buckets[i] = bucket32{}
			i++
		}
		if i >= len(s.buckets) {
			break
		}
		for j < len(a.buckets) && a.buckets[j].hi < s.buckets[i].hi {
			j++
		}
		if j >= len(a.buckets) {
			for i < len(s.buckets) {
				s.buckets[i] = bucket32{}
				i++
			}
			break
		}
		if s.buckets[i].hi == a.buckets[j].hi {
			s.buckets[i].intersect(&a.buckets[j])
			i++
			j++
		}
	}
	s.fixItemsCount()
}

// Subtract removes from s all the shared items between s and a.
func (s *Set) Subtract(a *Set) {
	if s.Len() == 0 || a.Len() == 0 {
		// Fast path - nothing to subtract.
		return
	}
	if s.Len() >= a.Len() {
		a.ForEach(func(part []uint64) bool {
			for _, x := range part {
				s.Del(x)
			}
			return true
		})
	} else {
		s.ForEach(func(part []uint64) bool {
			for _, x := range part {
				if a.Has(x) {
					s.Del(x)
				}
			}
			return true
		})
	}
}

// Equal returns true if s contains the same items as a.
func (s *Set) Equal(a *Set) bool {
	if s.Len() != a.Len() {
		return false
	}
	equal := true
	a.ForEach(func(part []uint64) bool {
		for _, x := range part {
			if !s.Has(x) {
				equal = false
				return false
			}
		}
		return true
	})
	return equal
}

// ForEach calls f for all the items stored in s.
//
// Each call to f contains part with arbitrary part of items stored in the set.
// The iteration is stopped if f returns false.
func (s *Set) ForEach(f func(part []uint64) bool) {
	if s == nil {
		return
	}
	for i := range s.buckets {
		if !s.buckets[i].forEach(f) {
			return
		}
	}
}

type bucket32 struct {
	hi uint32

	// hint may contain bucket index for the last successful operation.
	// This allows saving CPU time on subsequent calls to the same bucket.
	hint uint32

	// b16his contains high 16 bits for each bucket in buckets.
	//
	// It is always sorted.
	b16his []uint16

	// buckets are sorted by b16his
	buckets []*bucket16
}

func (b *bucket32) getLen() int {
	n := 0
	for i := range b.buckets {
		n += b.buckets[i].getLen()
	}
	return n
}

func (b *bucket32) union(a *bucket32, mayOwn bool) {
	i := 0
	j := 0
	bBucketsLen := len(b.buckets)
	for {
		for i < bBucketsLen && j < len(a.b16his) && b.b16his[i] < a.b16his[j] {
			i++
		}
		if i >= bBucketsLen {
			for j < len(a.b16his) {
				b16 := b.addBucket16(a.b16his[j])
				if mayOwn {
					*b16 = *a.buckets[j]
				} else {
					a.buckets[j].copyTo(b16)
				}
				j++
			}
			break
		}
		for j < len(a.b16his) && a.b16his[j] < b.b16his[i] {
			b16 := b.addBucket16(a.b16his[j])
			if mayOwn {
				*b16 = *a.buckets[j]
			} else {
				a.buckets[j].copyTo(b16)
			}
			j++
		}
		if j >= len(a.b16his) {
			break
		}
		if b.b16his[i] == a.b16his[j] {
			b.buckets[i].union(a.buckets[j])
			i++
			j++
		}
	}
	// Restore buckets order, which could be violated during the merge above.
	if !sort.IsSorted(b) {
		sort.Sort(b)
	}
}

// This is for sort.Interface used in bucket32.union
func (b *bucket32) Len() int           { return len(b.b16his) }
func (b *bucket32) Less(i, j int) bool { return b.b16his[i] < b.b16his[j] }
func (b *bucket32) Swap(i, j int) {
	his := b.b16his
	buckets := b.buckets
	his[i], his[j] = his[j], his[i]
	buckets[i], buckets[j] = buckets[j], buckets[i]
}

func (b *bucket32) intersect(a *bucket32) {
	i := 0
	j := 0
	for {
		for i < len(b.b16his) && j < len(a.b16his) && b.b16his[i] < a.b16his[j] {
			*b.buckets[i] = bucket16{}
			i++
		}
		if i >= len(b.b16his) {
			break
		}
		for j < len(a.b16his) && a.b16his[j] < b.b16his[i] {
			j++
		}
		if j >= len(a.b16his) {
			for i < len(b.b16his) {
				*b.buckets[i] = bucket16{}
				i++
			}
			break
		}
		if b.b16his[i] == a.b16his[j] {
			b.buckets[i].intersect(a.buckets[j])
			i++
			j++
		}
	}
	// Remove zero buckets
	b16his := b.b16his[:0]
	bs := b.buckets[:0]
	for i, b16 := range b.buckets {
		if b16.isZero() {
			continue
		}
		b16his = append(b16his, b.b16his[i])
		bs = append(bs, b16)
	}
	for i := len(bs); i < len(b.buckets); i++ {
		b.buckets[i] = nil
	}
	b.hint = 0
	b.b16his = b16his
	b.buckets = bs
}

func (b *bucket32) forEach(f func(part []uint64) bool) bool {
	xbuf := partBufPool.Get().(*[]uint64)
	buf := *xbuf
	for i, b16 := range b.buckets {
		hi16 := b.b16his[i]
		buf = b16.appendTo(buf[:0], b.hi, hi16)
		if !f(buf) {
			return false
		}
	}
	*xbuf = buf
	partBufPool.Put(xbuf)
	return true
}

var partBufPool = &sync.Pool{
	New: func() any {
		buf := make([]uint64, 0, bitsPerBucket)
		return &buf
	},
}

func (b *bucket32) sizeBytes() uint64 {
	n := uint64(unsafe.Sizeof(*b))
	n += 2 * uint64(len(b.b16his))
	for _, b16 := range b.buckets {
		n += b16.sizeBytes()
	}
	return n
}

func (b *bucket32) copyTo(dst *bucket32) {
	dst.hi = b.hi
	dst.b16his = append(dst.b16his[:0], b.b16his...)
	// Do not reuse dst.buckets, since it may be used in other places.
	dst.buckets = nil
	if len(b.buckets) > 0 {
		dst.buckets = make([]*bucket16, len(b.buckets))
		for i, b16 := range b.buckets {
			b16Dst := &bucket16{}
			b16.copyTo(b16Dst)
			dst.buckets[i] = b16Dst
		}
	}
}

func (b *bucket32) getHint() uint32 {
	return atomic.LoadUint32(&b.hint)
}

func (b *bucket32) setHint(n int) {
	atomic.StoreUint32(&b.hint, uint32(n))
}

func (b *bucket32) add(x uint32) bool {
	hi := uint16(x >> 16)
	lo := uint16(x)
	his := b.b16his
	if n := b.getHint(); n < uint32(len(his)) && his[n] == hi {
		// Fast path - add to the previously used bucket.
		bs := b.buckets
		return n < uint32(len(bs)) && bs[n].add(lo)
	}
	return b.addSlow(hi, lo)
}

func (b *bucket32) addMulti(a []uint64) int {
	if len(a) == 0 {
		return 0
	}
	count := 0
	hiPrev := uint16(a[0] >> 16)
	i := 0
	for j, x := range a {
		hi := uint16(x >> 16)
		if hi == hiPrev {
			continue
		}
		b16 := b.getOrCreateBucket16(hiPrev)
		count += b16.addMulti(a[i:j])
		hiPrev = hi
		i = j
	}
	b16 := b.getOrCreateBucket16(hiPrev)
	count += b16.addMulti(a[i:])
	return count
}

func (b *bucket32) getOrCreateBucket16(hi uint16) *bucket16 {
	his := b.b16his
	bs := b.buckets
	n := binarySearch16(his, hi)
	if n < 0 || n >= len(his) || his[n] != hi {
		return b.addBucketAtPos(hi, n)
	}
	return bs[n]
}

func (b *bucket32) addSlow(hi, lo uint16) bool {
	his := b.b16his
	n := binarySearch16(his, hi)
	if n < 0 || n >= len(his) || his[n] != hi {
		b16 := b.addBucketAtPos(hi, n)
		b16.add(lo)
		return true
	}
	b.setHint(n)
	bs := b.buckets
	return n < len(bs) && bs[n].add(lo)
}

func (b *bucket32) addBucket16(hi uint16) *bucket16 {
	b.b16his = append(b.b16his, hi)
	b.buckets = append(b.buckets, &bucket16{})
	return b.buckets[len(b.buckets)-1]
}

func (b *bucket32) addBucketAtPos(hi uint16, pos int) *bucket16 {
	if pos < 0 {
		// This is a hint to Go compiler to remove automatic bounds checks below.
		return nil
	}
	if pos >= len(b.b16his) {
		return b.addBucket16(hi)
	}
	b.b16his = append(b.b16his[:pos+1], b.b16his[pos:]...)
	b.b16his[pos] = hi
	b.buckets = append(b.buckets[:pos+1], b.buckets[pos:]...)
	b16 := &bucket16{}
	b.buckets[pos] = b16
	return b16
}

func (b *bucket32) has(x uint32) bool {
	hi := uint16(x >> 16)
	lo := uint16(x)
	his := b.b16his
	n := binarySearch16(his, hi)
	if n < 0 || n >= len(his) || his[n] != hi {
		return false
	}
	// Do not call b.setHint(n) here, since this may trash performance
	// when many concurrent goroutines call b.has() method from many CPU cores.
	bs := b.buckets
	return n < len(bs) && bs[n].has(lo)
}

func (b *bucket32) del(x uint32) bool {
	hi := uint16(x >> 16)
	lo := uint16(x)
	his := b.b16his
	if n := b.getHint(); n < uint32(len(his)) && his[n] == hi {
		// Fast path - use the bucket from the previous operation.
		bs := b.buckets
		return n < uint32(len(bs)) && bs[n].del(lo)
	}
	return b.delSlow(hi, lo)
}

func (b *bucket32) delSlow(hi, lo uint16) bool {
	his := b.b16his
	n := binarySearch16(his, hi)
	if n < 0 || n >= len(his) || his[n] != hi {
		return false
	}
	b.setHint(n)
	bs := b.buckets
	return n < len(bs) && bs[n].del(lo)
}

func (b *bucket32) appendTo(dst []uint64) []uint64 {
	for i := range b.buckets {
		hi16 := b.b16his[i]
		dst = b.buckets[i].appendTo(dst, b.hi, hi16)
	}
	return dst
}

const (
	bitsPerBucket  = 1 << 16
	wordsPerBucket = bitsPerBucket / 64
)

type bucket16 struct {
	bits         *[wordsPerBucket]uint64
	smallPool    [smallPoolSize]uint16
	smallPoolLen int
}

const smallPoolSize = 56

func (b *bucket16) isZero() bool {
	return b.bits == nil && b.smallPoolLen == 0
}

func (b *bucket16) getLen() int {
	if b.bits == nil {
		return b.smallPoolLen
	}
	n := 0
	for _, x := range b.bits {
		if x > 0 {
			n += bits.OnesCount64(x)
		}
	}
	return n
}

func (b *bucket16) union(a *bucket16) {
	if a.bits != nil && b.bits != nil {
		// Fast path - use bitwise ops.
		ab := a.bits
		bb := b.bits
		_ = bb[len(ab)-1]
		for i, ax := range ab {
			bx := bb[i]
			bx |= ax
			bb[i] = bx
		}
		return
	}

	// Slow path
	xbuf := partBufPool.Get().(*[]uint64)
	buf := *xbuf
	buf = a.appendTo(buf[:0], 0, 0)
	for _, x := range buf {
		x16 := uint16(x)
		b.add(x16)
	}
	*xbuf = buf
	partBufPool.Put(xbuf)
}

func (b *bucket16) intersect(a *bucket16) {
	if a.bits != nil && b.bits != nil {
		// Fast path - use bitwise ops
		ab := a.bits
		bb := b.bits
		_ = bb[len(ab)-1]
		for i, ax := range ab {
			bx := bb[i]
			bx &= ax
			bb[i] = bx
		}
		return
	}

	// Slow path
	xbuf := partBufPool.Get().(*[]uint64)
	buf := *xbuf
	buf = b.appendTo(buf[:0], 0, 0)
	for _, x := range buf {
		x16 := uint16(x)
		if !a.has(x16) {
			b.del(x16)
		}
	}
	*xbuf = buf
	partBufPool.Put(xbuf)
}

func (b *bucket16) sizeBytes() uint64 {
	n := unsafe.Sizeof(*b)
	if b.bits != nil {
		n += unsafe.Sizeof(*b.bits)
	}
	return uint64(n)
}

func (b *bucket16) copyTo(dst *bucket16) {
	// Do not reuse dst.bits, since it may be used in other places.
	dst.bits = nil
	if b.bits != nil {
		bits := *b.bits
		dst.bits = &bits
	}
	dst.smallPool = b.smallPool
	dst.smallPoolLen = b.smallPoolLen
}

func (b *bucket16) add(x uint16) bool {
	bits := b.bits
	if bits == nil {
		return b.addToSmallPool(x)
	}
	wordNum, bitMask := getWordNumBitMask(x)
	ok := bits[wordNum]&bitMask == 0
	if ok {
		bits[wordNum] |= bitMask
	}
	return ok
}

func (b *bucket16) addMulti(a []uint64) int {
	if b.bits == nil {
		if b.smallPoolLen+len(a) > len(b.smallPool) {
			b.switchSmallPoolToBits()
			goto fastPath
		}
		// Slow path
		count := 0
		for _, x := range a {
			if b.addToSmallPool(uint16(x)) {
				count++
			}
		}
		return count
	}

fastPath:
	count := 0
	bits := b.bits
	for _, x := range a {
		wordNum, bitMask := getWordNumBitMask(uint16(x))
		if bits[wordNum]&bitMask == 0 {
			bits[wordNum] |= bitMask
			count++
		}
	}
	return count
}

func (b *bucket16) addToSmallPool(x uint16) bool {
	if b.hasInSmallPool(x) {
		return false
	}
	sp := b.smallPool[:]
	if b.smallPoolLen < len(sp) {
		sp[b.smallPoolLen] = x
		b.smallPoolLen++
		return true
	}
	b.switchSmallPoolToBits()
	b.add(x)
	return true
}

func (b *bucket16) switchSmallPoolToBits() {
	smallPoolLen := b.smallPoolLen
	b.smallPoolLen = 0
	var bits [wordsPerBucket]uint64
	b.bits = &bits
	for _, v := range b.smallPool[:smallPoolLen] {
		b.add(v)
	}
}

func (b *bucket16) has(x uint16) bool {
	if b.bits == nil {
		return b.hasInSmallPool(x)
	}
	wordNum, bitMask := getWordNumBitMask(x)
	return b.bits[wordNum]&bitMask != 0
}

func (b *bucket16) hasInSmallPool(x uint16) bool {
	return slices.Contains(b.smallPool[:b.smallPoolLen], x)
}

func (b *bucket16) del(x uint16) bool {
	if b.bits == nil {
		return b.delFromSmallPool(x)
	}
	wordNum, bitMask := getWordNumBitMask(x)
	word := &b.bits[wordNum]
	ok := *word&bitMask != 0
	*word &^= bitMask
	return ok
}

func (b *bucket16) delFromSmallPool(x uint16) bool {
	sp := b.smallPool[:]
	for i, v := range sp[:b.smallPoolLen] {
		if v == x {
			copy(sp[i:], sp[i+1:])
			b.smallPoolLen--
			return true
		}
	}
	return false
}

func (b *bucket16) appendTo(dst []uint64, hi uint32, hi16 uint16) []uint64 {
	hi64 := uint64(hi)<<32 | uint64(hi16)<<16
	if b.bits == nil {
		// Use smallPoolSorter instead of sort.Slice here in order to reduce memory allocations.
		sps := smallPoolSorterPool.Get().(*smallPoolSorter)
		// Sort a copy of b.smallPool, since b must be readonly in order to prevent from data races
		// when b.appendTo is called from concurrent goroutines.
		sps.smallPool = b.smallPool
		sps.a = sps.smallPool[:b.smallPoolLen]
		if len(sps.a) > 1 && !sort.IsSorted(sps) {
			sort.Sort(sps)
		}
		for _, v := range sps.a {
			x := hi64 | uint64(v)
			dst = append(dst, x)
		}
		smallPoolSorterPool.Put(sps)
		return dst
	}
	var wordNum uint64
	for _, word := range b.bits {
		if word == 0 {
			wordNum++
			continue
		}
		x64 := hi64 | (wordNum * 64)
		for {
			tzn := uint64(bits.TrailingZeros64(word))
			if tzn >= 64 {
				break
			}
			word &^= uint64(1) << tzn
			x := x64 | tzn
			dst = append(dst, x)
		}
		wordNum++
	}
	return dst
}

var smallPoolSorterPool = &sync.Pool{
	New: func() any {
		return &smallPoolSorter{}
	},
}

type smallPoolSorter struct {
	smallPool [smallPoolSize]uint16
	a         []uint16
}

func (sps *smallPoolSorter) Len() int { return len(sps.a) }
func (sps *smallPoolSorter) Less(i, j int) bool {
	a := sps.a
	return a[i] < a[j]
}
func (sps *smallPoolSorter) Swap(i, j int) {
	a := sps.a
	a[i], a[j] = a[j], a[i]
}

func getWordNumBitMask(x uint16) (uint16, uint64) {
	wordNum := x / 64
	bitMask := uint64(1) << (x & 63)
	return wordNum, bitMask
}

func binarySearch16(u16 []uint16, x uint16) int {
	// The code has been adapted from sort.Search.
	n := len(u16)
	if n > 0 && u16[n-1] < x {
		// Fast path for values scanned in ascending order.
		return n
	}
	i, j := 0, n
	for i < j {
		h := int(uint(i+j) >> 1)
		if h >= 0 && h < len(u16) && u16[h] < x {
			i = h + 1
		} else {
			j = h
		}
	}
	return i
}
