// Typed column readers: the key -> column lookup is done once via
// Snapshot.Col / Snapshot.RelCol, then narrowed to a typed reader -- the
// polars column(name).i64() shape. Dense columns read through their raw
// slice fast path; sparse columns binary-search, or read O(1) through a
// lazily built position index (Snapshot.ColIndexed / RelColIndexed).

package chickpeas

import (
	

	
)

// posIndex accelerates a sparse column's random reads: a presence bitmap
// with block ranks resolves pos -> pair slot in O(1) -- one rank read plus
// a bounded popcount, no hashing or per-probe branching. A sparse column's
// pair array is position-sorted (its own Get binary-searches it), so the
// rank IS the slot. nil means unindexed (dense and rank layouts are
// already O(1)).
type posIndex = *rankIndex

func buildPosIndex( Column) posIndex {
	 := make([]uint32, 0, .Len())
	 := 0
	for  := range .Entries() {
		 = append(, )
		if int()+1 >  {
			 = int() + 1
		}
	}
	 := buildRankIndex(, )
	return &
}

// readIndexed reads a position through the resolved index when present,
// else the column's own lookup.
func readIndexed( Column,  posIndex,  uint32) (Value, bool) {
	if  == nil {
		return .Get()
	}
	,  := .slot()
	if ! {
		return Value{}, false
	}
	return valueAtSlot(, uint32())
}

// Col is a resolved property column, narrowed to a typed reader with I64 /
// F64 / Bool / Str. Build with Snapshot.Col (node columns, indexed by node
// id) or Snapshot.RelCol (rel columns, indexed by outgoing-CSR position).
type Col struct {
	col Column
	idx posIndex
}

// Dtype is the column's logical element type, for picking a typed reader
// without narrowing.
func ( Col) () Dtype { return .col.Dtype() }

// Column is the raw generic reader.
func ( Col) () Column { return .col }

// I64 narrows to a typed integer reader; a non-integer column reads back as
// absent, like a mistyped Prop.
func ( Col) () I64Col {
	 := I64Col{col: .col, idx: .idx}
	switch d := .col.(type) {
	case denseI64Col:
		.dense = 
	case denseI64NarrowCol:
		.nv = &i64NarrowView{narrow: .b, w: .w, min: .min}
	case denseI64BitCol:
		.nv = &i64NarrowView{bits: .bits, min: .min}
	}
	return 
}

// F64 narrows to a typed float reader.
func ( Col) () F64Col {
	,  := .col.(denseF64Col)
	return F64Col{dense: , col: .col, idx: .idx}
}

// Bool narrows to a typed boolean reader.
func ( Col) () BoolCol {
	var  *bitset.Bits
	if ,  := .col.(denseBoolCol);  {
		 = .bits
	}
	return BoolCol{dense: , col: .col, idx: .idx}
}

// Str narrows to a typed string reader exposing interned atom ids; resolve
// a comparison string to its atom once and compare ids.
func ( Col) () StrCol {
	,  := .col.(denseStrCol)
	return StrCol{dense: , col: .col, idx: .idx}
}

// i64NarrowView is the unpacked read state for the narrow and bit
// storage classes, held behind one pointer so I64Col itself stays small:
// Get has a value receiver, so every field added to I64Col is copied on
// every call -- unpacking these six words inline made each read pay a
// duffcopy that cost more than the decode it saved (task 300).
type i64NarrowView struct {
	// narrow is the byte-class delta vector (value = min + delta at
	// width w); bits is the two-valued bit class's vector (value =
	// min + bit).
	narrow []byte
	w      uint8
	min    int64
	bits   *bitset.Bits
}

// I64Col is a resolved integer column reader.
type I64Col struct {
	dense denseI64Col
	// nv is the narrow/bit-class read state (nil for other layouts),
	// one pointer so the value receiver stays cheap to copy.
	nv  *i64NarrowView
	col Column
	idx posIndex
}

// Get returns the value at pos (a node id for node columns, a CSR position
// for rel columns); ok is false when absent.
func ( I64Col) ( uint32) (int64, bool) {
	if .dense != nil {
		if int() < len(.dense) {
			return .dense[], true
		}
		return 0, false
	}
	if .nv != nil {
		return .nv.get()
	}
	,  := readIndexed(.col, .idx, )
	if ! {
		return 0, false
	}
	return .I64()
}

// get decodes pos from the narrow byte classes; the bit class splits out
// so this stays inlinable into Get -- the split plus the u48 single-load
// keeps a narrow read one call level flatter than the fallback path. The
// u48 arm bounds on i+8 against the 2-byte-padded buffer -- exact, since
// a valid slot satisfies i <= len-8 and the first out-of-range slot lands
// at i = len-2; narrower widths divide the unpadded length evenly.
func ( *i64NarrowView) ( uint32) (int64, bool) {
	if .narrow == nil {
		return .getBits()
	}
	 := int() * int(.w)
	switch .w {
	case 1:
		if  < len(.narrow) {
			return .min + int64(.narrow[]), true
		}
	case 2:
		if +2 <= len(.narrow) {
			return .min + int64(binary.LittleEndian.Uint16(.narrow[:])), true
		}
	case 4:
		if +4 <= len(.narrow) {
			return .min + int64(binary.LittleEndian.Uint32(.narrow[:])), true
		}
	default:
		if +8 <= len(.narrow) {
			return .min + int64(binary.LittleEndian.Uint64(.narrow[:])&narrowU48Mask), true
		}
	}
	return 0, false
}

// getBits decodes pos from the two-valued bit class.
func ( *i64NarrowView) ( uint32) (int64, bool) {
	if int() >= .bits.Len() {
		return 0, false
	}
	if .bits.Get(int()) {
		return .min + 1, true
	}
	return .min, true
}

// Slice is the dense value slice indexed directly by position; ok is false
// for a sparse column (fall back to Get).
func ( I64Col) () ([]int64, bool) {
	return .dense, .dense != nil
}

// GetMany reads the values at ids into vals with a presence mask -- the
// bulk form of Get for candidate-batch sweeps: the representation switch
// and the receiver hoist out of the loop, so a chunk pays one dispatch
// instead of a call chain per id. vals and present must be at least
// len(ids) long.
func ( I64Col) ( []uint32,  []int64,  []bool) {
	switch {
	case .dense != nil:
		 := .dense
		for ,  := range  {
			if int() < len() {
				[], [] = [], true
			} else {
				[] = false
			}
		}
	case .nv != nil && .nv.narrow != nil:
		, ,  := .nv, .nv.narrow, .nv.min
		switch .w {
		case 1:
			for ,  := range  {
				if int() < len() {
					[], [] = +int64([]), true
				} else {
					[] = false
				}
			}
		case 2:
			for ,  := range  {
				if  := int() * 2; +2 <= len() {
					[], [] = +int64(binary.LittleEndian.Uint16([:])), true
				} else {
					[] = false
				}
			}
		case 4:
			for ,  := range  {
				if  := int() * 4; +4 <= len() {
					[], [] = +int64(binary.LittleEndian.Uint32([:])), true
				} else {
					[] = false
				}
			}
		default:
			// u48: the buffer carries a 2-byte pad, so the padded 8-byte
			// masked load bounds exactly like Get's arm.
			for ,  := range  {
				if  := int() * 6; +8 <= len() {
					[], [] = +int64(binary.LittleEndian.Uint64([:])&narrowU48Mask), true
				} else {
					[] = false
				}
			}
		}
	default:
		for ,  := range  {
			[], [] = .Get()
		}
	}
}

// SliceRange is the dense-speed window of a column whose presence is one
// contiguous position run: values read as vals[pos-start], and a
// position outside [start, start+len) is absent. LDBC-shaped loaders
// assign each label a contiguous id block, so a label-scoped column
// stored sparse still reads at hoisted-slice speed through its window
// (the rcp twin's as_i64_slice_range, tasks rcp-265/072; a sparse
// column's pair array is position-sorted, so contiguous presence makes
// the value array itself the window -- the check is O(1) and nothing is
// built). ok=false for gapped presence or a non-integer column.
func ( I64Col) () ( uint32,  []int64,  bool) {
	if .dense != nil {
		return 0, .dense, true
	}
	,  := .col.(sparseI64Col)
	 := len(.ids)
	if ! ||  == 0 || int(.ids[-1]-.ids[0])+1 !=  {
		return 0, nil, false
	}
	return .ids[0], .vals, true
}

// F64Col is a resolved float column reader.
type F64Col struct {
	dense denseF64Col
	col   Column
	idx   posIndex
}

// Get returns the value at pos; ok is false when absent.
func ( F64Col) ( uint32) (float64, bool) {
	if .dense != nil {
		if int() < len(.dense) {
			return .dense[], true
		}
		return 0, false
	}
	,  := readIndexed(.col, .idx, )
	if ! {
		return 0, false
	}
	return .F64()
}

// Slice is the dense value slice; ok is false for a sparse column.
func ( F64Col) () ([]float64, bool) {
	return .dense, .dense != nil
}

// SliceRange is I64Col.SliceRange for float columns.
func ( F64Col) () ( uint32,  []float64,  bool) {
	if .dense != nil {
		return 0, .dense, true
	}
	,  := .col.(sparseF64Col)
	 := len(.ids)
	if ! ||  == 0 || int(.ids[-1]-.ids[0])+1 !=  {
		return 0, nil, false
	}
	return .ids[0], .vals, true
}

// BoolCol is a resolved boolean column reader.
type BoolCol struct {
	dense *bitset.Bits
	col   Column
	idx   posIndex
}

// Get returns the value at pos; ok is false when absent.
func ( BoolCol) ( uint32) (bool, bool) {
	if .dense != nil {
		if int() < .dense.Len() {
			return .dense.Get(int()), true
		}
		return false, false
	}
	,  := readIndexed(.col, .idx, )
	if ! {
		return false, false
	}
	return .Bool()
}

// Bits is the dense bit vector indexed directly by position; ok is false
// for a sparse column.
func ( BoolCol) () (*bitset.Bits, bool) {
	return .dense, .dense != nil
}

// StrCol is a resolved string column reader exposing interned atom ids.
type StrCol struct {
	dense denseStrCol
	col   Column
	idx   posIndex
}

// ID returns the atom id at pos; ok is false when absent. Note atom 0 ("")
// in a dense column means missing -- Prop.Str folds that in; this raw
// reader does not.
func ( StrCol) ( uint32) (uint32, bool) {
	if .dense != nil {
		if int() < len(.dense) {
			return .dense[], true
		}
		return 0, false
	}
	,  := readIndexed(.col, .idx, )
	if ! {
		return 0, false
	}
	return .StrID()
}

// IDs is the dense atom-id slice; ok is false for a sparse column.
func ( StrCol) () ([]uint32, bool) {
	return .dense, .dense != nil
}