// Lazy, range-fetched RCPG loading: parse the small front matter (header +
// directory), plan the byte ranges a given ParseOptions needs, and fetch only
// those — so a topology-only load over a range-capable transport never
// transfers the property columns. Most of the CSR-skeleton/working-set
// machinery the Rust codec layers on top of this is deliberately deferred;
// SectionFetch and the directory planner lock the interface it builds on,
// and atoms_lazy.go's block-lazy AtomReader is its first landed slice.

package rcpg

// SectionFetch is a byte-range provider for lazy RCPG loading: it returns
// [offset, offset+length) of an RCPG file. Implementations must return
// exactly length bytes or an error. Callers plug in files (io.ReaderAt),
// HTTP Range requests, or object-store reads.
type SectionFetch interface {
	Fetch(offset, length uint64) ([]byte, error)
}

// MemoryFetch is a SectionFetch over an in-memory RCPG slice, returning
// sub-slices with no copy — the lazy-load equivalent of eager Parse, useful
// for embedding and for parity tests of the lazy path.
type MemoryFetch []byte

// Fetch implements SectionFetch.
func ( MemoryFetch) (,  uint64) ([]byte, error) {
	 :=  + 
	if  <  ||  > uint64(len()) {
		return nil, corruptf("range %d+%d past end of file (%d)", , , len())
	}
	return [:], nil
}

// DirEntry is one section directory entry: (id, offset, length) in file
// order.
type DirEntry struct {
	ID     uint32
	Offset uint64
	Length uint64
}

// ByteRange is a half-open [Start, End) byte range within a file.
type ByteRange struct {
	Start uint64
	End   uint64
}

// Directory is the RCPG header + section directory — the small, resident
// front matter of a file. Parse it from the leading bytes (16-byte header +
// section_count x 24-byte entries), then PlanSections to get the byte ranges
// a given ParseOptions needs, so the bodies can be range-fetched without
// transferring the whole file.
type Directory struct {
	entries []DirEntry
}

// FrontMatterLen is the number of front-matter bytes (header + directory)
// for a file with sectionCount sections — what to fetch before the directory
// can be parsed.
func ( uint32) uint64 {
	return headerLen + uint64()*dirEntryLen
}

// SectionCount reads the section count from a header prefix (at least 16
// bytes), validating the magic and version. Use with FrontMatterLen to size
// the directory fetch.
func ( []byte) (uint32, error) {
	 := newCursor()
	,  := .take(4)
	if  != nil {
		return 0, 
	}
	if [4]byte() != Magic {
		return 0, corruptf("bad magic, not an RCPG file")
	}
	,  := .u16()
	if  != nil {
		return 0, 
	}
	// Only v1 exists; reject anything else (including the invalid v0),
	// matching RRSR's strict != Version check. Widen this to a range when a
	// v2 layout is actually defined.
	if  != Version {
		return 0, unsupportedVersionf("RCPG", )
	}
	if ,  := .u16();  != nil { // flags
		return 0, 
	}
	,  := .u32()
	if  != nil {
		return 0, 
	}
	if  > 1024 {
		return 0, corruptf("implausible section count %d", )
	}
	return , nil
}

// ParseDirectory parses the header + full directory from front (which must
// hold at least FrontMatterLen bytes). Body offsets are recorded but not
// validated against any file length — the bodies live elsewhere and are
// checked when fetched.
func ( []byte) (*Directory, error) {
	,  := SectionCount()
	if  != nil {
		return nil, 
	}
	 := newCursor()
	if ,  := .take(headerLen);  != nil {
		return nil, 
	}
	 := make([]DirEntry, 0, preallocCap(int()))
	for range  {
		,  := .u32()
		if  != nil {
			return nil, 
		}
		if ,  := .u32();  != nil { // reserved
			return nil, 
		}
		,  := .u64()
		if  != nil {
			return nil, 
		}
		,  := .u64()
		if  != nil {
			return nil, 
		}
		 = append(, DirEntry{ID: , Offset: , Length: })
	}
	return &Directory{entries: }, nil
}

// Entries returns the directory entries in file order.
func ( *Directory) () []DirEntry {
	return .entries
}

// PlanSections returns the byte ranges to fetch for opts: the topology
// sections always, the property columns only when their option is set.
// Unknown sections are skipped. Order follows the directory, so the ranges
// are ascending and can be coalesced for batched fetches.
func ( *Directory) ( ParseOptions) []ByteRange {
	var  []ByteRange
	for ,  := range .entries {
		if sectionWanted(.ID, ) {
			 = append(, ByteRange{Start: .Offset, End: .Offset + .Length})
		}
	}
	return 
}

// LoadPrefixLen is the smallest prefix length [0, len) that contains every
// section needed under opts — fetch this many bytes and ParseLazy over a
// MemoryFetch of them to load without the trailing (unwanted) sections.
// Because the topology sections precede the property columns in the file, a
// topology-only prefix omits the columns entirely; a full load returns the
// file length.
func ( *Directory) ( ParseOptions) uint64 {
	var  uint64
	 := false
	for ,  := range .entries {
		if !sectionWanted(.ID, ) {
			continue
		}
		 = true
		if  := .Offset + .Length;  >  {
			 = 
		}
	}
	if ! {
		return FrontMatterLen(uint32(len(.entries)))
	}
	return 
}

// ParseLazy parses an RCPG file through a SectionFetch, transferring only
// the sections opts needs. It reads the header, then the directory, then
// fetches and decodes each wanted section body — so a topology-only load
// over a range-capable transport never transfers the property columns.
// Produces a GraphSection identical to ParseWith for the same opts.
func ( SectionFetch,  ParseOptions) (*GraphSection, error) {
	,  := .Fetch(0, headerLen)
	if  != nil {
		return nil, 
	}
	,  := SectionCount()
	if  != nil {
		return nil, 
	}
	,  := .Fetch(0, FrontMatterLen())
	if  != nil {
		return nil, 
	}
	,  := ParseDirectory()
	if  != nil {
		return nil, 
	}
	 := &GraphSection{}
	for ,  := range .Entries() {
		if !sectionWanted(.ID, ) {
			continue
		}
		,  := .Fetch(.Offset, .Length)
		if  != nil {
			return nil, 
		}
		if uint64(len()) != .Length {
			return nil, corruptf("section %d fetch returned %d bytes, expected %d",
				.ID, len(), .Length)
		}
		if  := decodeSection(.ID, , , );  != nil {
			return nil, 
		}
	}
	return , nil
}