// Sorted range view over an i64 node property column: the lazily built,
// snapshot-cached primitive behind selective range scans (dates and other
// ordered i64 keys), the ordered complement of the equality property
// index. Entries sort by (value, node id), so any value interval is one
// contiguous window answered by two binary searches and returned as a
// zero-copy id slice.

package chickpeas

import (
	
	
)

// RangeIndex is a sorted view of an i64 node property column: Window
// answers value intervals with a zero-copy slice of node ids. The zero
// value is an empty index.
type RangeIndex struct {
	vals []int64
	ids  []uint32
}

// ColRangeIndex resolves the sorted range view for a node property key,
// building it on first use and caching it on the snapshot for the
// snapshot's lifetime (the posIndex/LabelDense policy: memory stays
// proportional to the column's entry count, ~12 bytes each). ok is false
// when the key has no node column or the column is not i64.
func ( *Snapshot) ( string) (RangeIndex, bool) {
	,  := .PropertyKey()
	if ! {
		return RangeIndex{}, false
	}
	,  := .columns[]
	if ! || .Dtype() != DtypeI64 {
		return RangeIndex{}, false
	}
	.rangeMu.Lock()
	,  := .rangeIndex[]
	.rangeMu.Unlock()
	if  {
		return , true
	}
	 := buildRangeIndex()
	.rangeMu.Lock()
	defer .rangeMu.Unlock()
	if ,  := .rangeIndex[];  {
		return , true
	}
	.rangeIndex[] = 
	return , true
}

// buildRangeIndex collects the column's (value, id) pairs and sorts them
// by value then id, so equal-valued runs enumerate in ascending id order.
func buildRangeIndex( Column) RangeIndex {
	 := .Len()
	 := make([]int64, 0, )
	 := make([]uint32, 0, )
	switch c := .(type) {
	case denseI64Col:
		for ,  := range  {
			 = append(, )
			 = append(, uint32())
		}
	case sparseI64Col:
		 = append(, .vals...)
		 = append(, .ids...)
	default:
		for ,  := range .Entries() {
			if ,  := .I64();  {
				 = append(, )
				 = append(, )
			}
		}
	}
	 := make([]int, len())
	for  := range  {
		[] = 
	}
	slices.SortFunc(, func(,  int) int {
		switch {
		case [] != []:
			if [] < [] {
				return -1
			}
			return 1
		case [] < []:
			return -1
		case [] > []:
			return 1
		}
		return 0
	})
	 := make([]int64, len())
	 := make([]uint32, len())
	for ,  := range  {
		[], [] = [], []
	}
	return RangeIndex{vals: , ids: }
}

// Len is the number of indexed entries (nodes carrying the key).
func ( RangeIndex) () int { return len(.ids) }

// Window is the node ids whose value lies in the interval [lo, hi] with
// per-bound inclusivity, as a zero-copy slice ordered by (value, id).
// Pass math.MinInt64/math.MaxInt64 with inclusive bounds for a half- or
// unbounded interval. An empty or inverted interval is an empty slice.
func ( RangeIndex) (,  int64, ,  bool) []uint32 {
	 := .lowerBound(, )
	 := .upperBound(, )
	if  >=  {
		return nil
	}
	return .ids[:]
}

// lowerBound is the first slot at or above the lower bound.
func ( RangeIndex) ( int64,  bool) int {
	if  {
		,  := slices.BinarySearch(.vals, )
		return 
	}
	if  == math.MaxInt64 {
		return len(.vals)
	}
	,  := slices.BinarySearch(.vals, +1)
	return 
}

// upperBound is the slot just past the upper bound.
func ( RangeIndex) ( int64,  bool) int {
	if ! {
		,  := slices.BinarySearch(.vals, )
		return 
	}
	if  == math.MaxInt64 {
		return len(.vals)
	}
	,  := slices.BinarySearch(.vals, +1)
	return 
}