package rdf

import 

// This file gives Dataset a zero-copy read path. Dataset.Graph materializes one
// Triple per matching quad, which at corpus scale dominates the allocation
// profile of any consumer that splits a dataset into its named graphs and then
// only reads them. GraphView answers the same queries over positions into the
// dataset's own Quads slice, so a view costs a few bytes per statement instead
// of a whole Triple (three Terms) per statement.

// GraphQuery is the read-only query surface shared by *Graph and *GraphView, so
// a consumer can write one function over a materialized graph and a view alike.
type GraphQuery interface {
	SubjectsOfType(typeIRI string) []Term
	Objects(subject Term, predicate string) []Term
	Object(subject Term, predicate string) (Term, bool)
	Literal(subject Term, predicate string) (string, bool)
	HasType(subject Term, typeIRI string) bool
}

var (
	_ GraphQuery = (*Graph)(nil)
	_ GraphQuery = (*GraphView)(nil)
)

// GraphView is a read-only view of the statements in one named graph of a
// Dataset. It answers the Graph query surface without copying any triple: its
// lazy index holds int32 positions into the dataset's Quads, the same shape as
// Graph's subject index. Pass a zero-value graph term to view the default graph.
//
// A view borrows the dataset, so the dataset must outlive it. Appending to the
// dataset invalidates the view's index; the next query rebuilds it, so a view
// stays correct across Add at the cost of one reindex. Views are not safe for
// concurrent use with a writer, and the first query on a view builds the index,
// so concurrent readers must either share a view whose index is already built
// or hold one view each.
type GraphView struct {
	d     *Dataset
	graph Term

	spo   map[Term][]int32 // subject -> positions in d.Quads, carved from one shared arena
	gen   int              // len(d.Quads) when spo was built, to detect appends
	spoOK bool
}

// GraphView returns a read-only, zero-copy view of the given graph term; pass a
// zero-value term for the default graph. It is the allocation-free counterpart
// to Graph, which materializes the same statements as a []Triple copy.
func ( *Dataset) ( Term) *GraphView {
	return &GraphView{d: , graph: }
}

// subjectIndex builds the view's lazy subject index on first subject-keyed query,
// in two passes mirroring Graph.index: count this graph's quads per subject, then
// carve each subject's bucket from one shared arena at exactly its final size.
// Buckets hold int32 positions into the dataset's Quads, so the index costs a few
// bytes per statement and copies no triple. Both passes skip quads belonging to
// other graphs, so the index is sized to the view rather than to the dataset.
//
// The whole-graph scans (Len, Triples, SubjectsOfType) deliberately do not go
// through here: they need no subject keying, and building the map for them would
// cost more than the scan itself.
func ( *GraphView) () map[Term][]int32 {
	if .spoOK && .gen == len(.d.Quads) {
		return .spo
	}
	 := .d.Quads
	 := 0
	 := make(map[Term]int32, len()/8+1)
	for  := range  {
		if [].G == .graph {
			++
			[[].S]++
		}
	}
	 := make([]int32, 0, )
	 := make(map[Term][]int32, len())
	for  := range  {
		if [].G != .graph {
			continue
		}
		 := [].S
		,  := []
		if ! {
			 := len() + int([])
			 = [len():len():]
			 = [:]
		}
		[] = append(, int32())
	}
	.spo, .gen, .spoOK = , len(), true
	return 
}

// Len returns the number of statements in the view's graph. It reads the
// dataset's cached per-graph counts, so it costs no scan after the first call on
// that dataset, however many graphs are asked about.
func ( *GraphView) () int { return .d.GraphLen(.graph) }

// Empty reports whether the view's graph holds no statements. Like Len it answers
// from the dataset's cached counts, so a consumer that reads a graph only when it
// is populated — an editorial overlay, say — pays nothing to skip an absent one.
func ( *GraphView) () bool { return !.d.HasGraph(.graph) }

// GraphTerm returns the graph term this view is scoped to.
func ( *GraphView) () Term { return .graph }

// Triples iterates the view's statements in document order, projecting each quad
// onto its triple. It builds no index and materializes no slice, yielding one
// Triple value at a time from the dataset's quads; the only allocation is the
// iterator closure itself, a fixed ~56 bytes per call regardless of graph size.
//
// The cost that matters is the pass, not the yield. Triples filters the whole
// dataset, so walking it is one full-dataset pass per view — reading N graphs out
// of one dataset costs N passes, even for graphs that turn out to be empty. Code
// that consumes several graphs at once can beat that by fusing the dispatch into
// a single hand-written pass over Dataset.Quads switching on each quad's graph
// term. Use Empty to skip a graph without walking it at all: it reads the
// dataset's cached counts and never scans.
//
// Per-triple overhead, by contrast, is not worth avoiding: in this package's
// Corpus/Grain/SingleGraph Triples benchmarks the iterator beats an equivalent
// hand-written single-graph loop at both corpus and per-grain scale, even against
// a single-graph dataset whose loop needs no filter at all.
func ( *GraphView) () iter.Seq[Triple] {
	return func( func(Triple) bool) {
		for  := range .d.Quads {
			if  := &.d.Quads[]; .G == .graph {
				if !(.Triple()) {
					return
				}
			}
		}
	}
}

// Objects returns the distinct objects of every statement in this graph with the
// given subject and predicate IRI, in document order. As with [Graph.Objects], a
// repeated statement yields its object once.
func ( *GraphView) ( Term,  string) []Term {
	 := .subjectIndex()[]
	var  []Term
	 := objectSet{hint: len()}
	for ,  := range  {
		if  := &.d.Quads[]; .P.Kind == IRI && .P.Value ==  {
			 = .append(, .O)
		}
	}
	return 
}

// Object returns the first object for (subject, predicate) in this graph, or
// false. It scans the index without allocating an intermediate slice.
func ( *GraphView) ( Term,  string) (Term, bool) {
	for ,  := range .subjectIndex()[] {
		if  := &.d.Quads[]; .P.Kind == IRI && .P.Value ==  {
			return .O, true
		}
	}
	return Term{}, false
}

// HasType reports whether the subject has rdf:type typeIRI in this graph. It
// scans the index without allocating.
func ( *GraphView) ( Term,  string) bool {
	for ,  := range .subjectIndex()[] {
		if  := &.d.Quads[]; .P.Kind == IRI && .P.Value == TypeIRI && .O.Kind == IRI && .O.Value ==  {
			return true
		}
	}
	return false
}

// Literal returns the value of the subject's first literal object for the
// predicate in this graph, or false. It scans the index without allocating an
// intermediate slice, serving the frequent single-value field reads.
func ( *GraphView) ( Term,  string) (string, bool) {
	for ,  := range .subjectIndex()[] {
		if  := &.d.Quads[]; .P.Kind == IRI && .P.Value ==  && .O.Kind == Literal {
			return .O.Value, true
		}
	}
	return "", false
}

// SubjectsOfType returns every subject in this graph with rdf:type typeIRI, in
// document order (deduplicated). Like Graph.SubjectsOfType it scans rather than
// keying off the subject index, so it never triggers an index build.
func ( *GraphView) ( string) []Term {
	var  []Term
	 := map[Term]bool{}
	for  := range .d.Quads {
		 := &.d.Quads[]
		if .G == .graph && .P.Kind == IRI && .P.Value == TypeIRI && .O.Kind == IRI && .O.Value ==  {
			if ![.S] {
				[.S] = true
				 = append(, .S)
			}
		}
	}
	return 
}