package rdf

import (
	
	
	
	
	
	
)

// ErrCanonComplexity is returned by the canonicalization functions when a graph's
// blank-node structure would require more work than maxCanonWork permits — the
// guard against the algorithm's exponential worst case on adversarial "poison"
// datasets. Well-formed data never approaches the limit.
var ErrCanonComplexity = errors.New("rdf: canonicalization exceeded complexity budget")

// maxCanonWork bounds the total permutations examined across the n-degree hashing
// recursion, and maxCanonDepth bounds that recursion's depth. Real datasets finish
// in near-linear work at shallow depth (the whole W3C rdf-canon suite peaks at
// ~2880 permutations and depth 6); only graphs crafted to be maximally symmetric —
// or a long chain of indistinguishable blank nodes, which would otherwise recurse
// one frame per link and overflow the goroutine stack — approach these limits, and
// are rejected quickly with ErrCanonComplexity rather than hanging or crashing.
const (
	maxCanonWork  = 1 << 18
	maxCanonDepth = 1 << 10
)

// Canonical returns the dataset serialized as canonical N-Quads per the W3C RDF
// Dataset Canonicalization algorithm RDFC-1.0 (URDNA2015): blank nodes are
// relabeled to isomorphism-invariant _:c14nN identifiers and the quads are sorted,
// so two datasets that differ only in statement order or blank-node labels produce
// byte-identical output. It returns ErrCanonComplexity for a graph whose symmetry
// exceeds the work budget.
func ( *Dataset) () ([]byte, error) {
	, ,  := canonicalize(.Quads)
	return , 
}

// Canonicalize returns a new Dataset with blank nodes relabeled to their canonical
// _:c14nN identifiers and quads in canonical order, leaving d untouched.
func ( *Dataset) (*Dataset, error) {
	, ,  := canonicalize(.Quads)
	if  != nil {
		return nil, 
	}
	return &Dataset{Quads: }, nil
}

// Canonical returns the graph as canonical N-Quads (its triples in the default
// graph), per RDFC-1.0. See Dataset.Canonical.
func ( *Graph) () ([]byte, error) {
	 := make([]Quad, len(.Triples))
	for ,  := range .Triples {
		[] = Quad{S: .S, P: .P, O: .O}
	}
	, ,  := canonicalize()
	return , 
}

// identifierIssuer issues canonical or temporary blank-node labels (c14n0, c14n1,
// … or b0, b1, …) in first-issue order, per RDFC-1.0's Issue Identifier algorithm.
type identifierIssuer struct {
	prefix string
	issued map[string]string
	order  []string
}

func newIssuer( string) *identifierIssuer {
	return &identifierIssuer{prefix: , issued: map[string]string{}}
}

func ( *identifierIssuer) () *identifierIssuer {
	 := &identifierIssuer{
		prefix: .prefix,
		issued: make(map[string]string, len(.issued)),
		order:  append([]string(nil), .order...),
	}
	maps.Copy(.issued, .issued)
	return 
}

// issue returns the label for id, minting a new one on first sight.
func ( *identifierIssuer) ( string) string {
	if ,  := .issued[];  {
		return 
	}
	 := .prefix + strconv.Itoa(len(.order))
	.issued[] = 
	.order = append(.order, )
	return 
}

func ( *identifierIssuer) ( string) (string, bool) { ,  := .issued[]; return ,  }

type canonicalizer struct {
	quads       []Quad
	blankQuads  map[string][]int // blank id -> indices of quads it occurs in (once per occurrence)
	canonical   *identifierIssuer
	firstDegree map[string]string // memoized first-degree hashes (a pure function of the quad set)
	work        int
}

// spend charges n units of work against the complexity budget, panicking with
// ErrCanonComplexity when the budget is exhausted. Charges are proportional to the
// real cost of a step (quads serialized, issuer entries cloned) so an adversarial
// graph that is cheap per permutation but expensive per step still fails fast.
func ( *canonicalizer) ( int) {
	.work += 
	if .work > maxCanonWork {
		panic(ErrCanonComplexity)
	}
}

// canonicalize runs RDFC-1.0 over quads, returning the relabeled+sorted quads and
// the canonical N-Quads bytes.
func canonicalize( []Quad) ( []Quad,  []byte,  error) {
	 := &canonicalizer{
		quads:       ,
		blankQuads:  map[string][]int{},
		canonical:   newIssuer("c14n"),
		firstDegree: map[string]string{},
	}
	for ,  := range  {
		for ,  := range [3]Term{.S, .O, .G} {
			if .Kind == Blank {
				.blankQuads[.Value] = append(.blankQuads[.Value], )
			}
		}
	}

	defer func() {
		if  := recover();  != nil {
			if  == ErrCanonComplexity {
				, ,  = nil, nil, ErrCanonComplexity
				return
			}
			panic()
		}
	}()

	// First-degree hashing: group blank nodes by their neighbourhood hash.
	 := map[string][]string{}
	for  := range .blankQuads {
		 := .hashFirstDegree()
		[] = append([], )
	}
	 := make([]string, 0, len())
	for  := range  {
		 = append(, )
	}
	sort.Strings()

	// Unique first-degree hashes get canonical ids immediately, in hash order.
	var  []string
	for ,  := range  {
		if len([]) == 1 {
			.canonical.issue([][0])
		} else {
			 = append(, )
		}
	}

	// Remaining (hash-colliding) blank nodes are disambiguated by n-degree hashing.
	for ,  := range  {
		var  []ndegreeResult
		for ,  := range [] {
			if ,  := .canonical.get();  {
				continue
			}
			 := newIssuer("b")
			.issue()
			,  := .hashNDegree(, , 0)
			 = append(, ndegreeResult{hash: , issuer: })
		}
		sort.Slice(, func(,  int) bool { return [].hash < [].hash })
		for ,  := range  {
			for ,  := range .issuer.order {
				.canonical.issue()
			}
		}
	}

	return .serialize()
}

// relabel maps a blank term to its canonical form; non-blank terms pass through.
func ( *canonicalizer) ( Term) Term {
	if .Kind == Blank {
		if ,  := .canonical.get(.Value);  {
			return NewBlank()
		}
	}
	return 
}

// serialize relabels every quad's blank nodes, sorts the quads by their canonical
// N-Quads line, and returns both the sorted quads and the concatenated bytes.
func ( *canonicalizer) () ([]Quad, []byte, error) {
	type  struct {
		    Quad
		 []byte
	}
	 := make([], len(.quads))
	for ,  := range .quads {
		 := Quad{S: .relabel(.S), P: .relabel(.P), O: .relabel(.O), G: .relabel(.G)}
		[] = {: , : canonLine(nil, , canonAppendTerm)}
	}
	sort.Slice(, func(,  int) bool { return string([].) < string([].) })
	 := make([]Quad, 0, len())
	var  []byte
	var  string
	for ,  := range  {
		if  > 0 && string(.) ==  { // an RDF dataset is a set: drop duplicate quads
			continue
		}
		 = string(.)
		 = append(, .)
		 = append(, ....)
	}
	return , , nil
}

// hashFirstDegree returns the hash of a blank node's immediate neighbourhood: each
// of its quads serialized with the node itself as _:a and any other blank as _:z,
// sorted and hashed.
func ( *canonicalizer) ( string) string {
	if ,  := .firstDegree[];  {
		return 
	}
	 := func( []byte,  Term) []byte {
		if .Kind == Blank {
			if .Value ==  {
				return append(, "_:a"...)
			}
			return append(, "_:z"...)
		}
		return canonAppendTerm(, )
	}
	 := make([]string, 0, len(.blankQuads[]))
	for ,  := range .blankQuads[] {
		 = append(, string(canonLine(nil, .quads[], )))
	}
	sort.Strings()
	 := sha256.New()
	for ,  := range  {
		.Write([]byte())
	}
	 := hex.EncodeToString(.Sum(nil))
	.firstDegree[] = 
	return 
}

// hashRelated hashes a related blank node's contribution from one quad position,
// preferring an already-issued canonical or temporary id and otherwise its
// first-degree hash.
func ( *canonicalizer) ( string,  Quad,  *identifierIssuer,  byte) string {
	 := []byte{}
	if  != 'g' {
		 = append(, '<')
		 = append(, .P.Value...)
		 = append(, '>')
	}
	if ,  := .canonical.get();  {
		 = append(, "_:"...)
		 = append(, ...)
	} else if ,  := .get();  {
		 = append(, "_:"...)
		 = append(, ...)
	} else {
		 = append(, .hashFirstDegree()...)
	}
	 := sha256.Sum256()
	return hex.EncodeToString([:])
}

type ndegreeResult struct {
	hash   string
	issuer *identifierIssuer
}

// hashNDegree computes the n-degree hash of a blank node, distinguishing it from
// others that share a first-degree hash by exploring its blank-node neighbourhood
// (with permutations) — RDFC-1.0's Hash N-Degree Quads.
func ( *canonicalizer) ( string,  *identifierIssuer,  int) (string, *identifierIssuer) {
	if  > maxCanonDepth {
		panic(ErrCanonComplexity)
	}
	 := map[string][]string{}
	for ,  := range .blankQuads[] {
		 := .quads[]
		.addRelated(, .S, , , 's', )
		.addRelated(, .O, , , 'o', )
		.addRelated(, .G, , , 'g', )
	}

	var  []byte
	 := make([]string, 0, len())
	for  := range  {
		 = append(, )
	}
	sort.Strings()

	for ,  := range  {
		 = append(, ...)
		var  []byte
		var  *identifierIssuer

		permute([], func( []string) {
			// Each visit clones the issuer (O(|issued|)) and may serialize quads, so
			// charge for that real cost rather than a flat unit per permutation --
			// otherwise a graph cheap in permutations but expensive per permutation
			// evades the budget.
			.spend(len(.issued) + 1)
			 := .clone()
			var  []byte
			var  []string
			 := false
			for ,  := range  {
				if ,  := .canonical.get();  {
					 = append(, "_:"...)
					 = append(, ...)
				} else {
					if ,  := .get(); ! {
						 = append(, )
					}
					 = append(, "_:"...)
					 = append(, .issue()...)
				}
				if  != nil && len() >= len() && string() > string() {
					 = true
					break
				}
			}
			if ! {
				for ,  := range  {
					,  := .(, , +1)
					 = append(, "_:"...)
					 = append(, .issue()...)
					 = append(, '<')
					 = append(, ...)
					 = append(, '>')
					 = 
					if  != nil && len() >= len() && string() > string() {
						 = true
						break
					}
				}
			}
			if ! && ( == nil || string() < string()) {
				 = 
				 = 
			}
		})

		 = append(, ...)
		 = 
	}
	 := sha256.Sum256()
	return hex.EncodeToString([:]), 
}

// addRelated records a blank node related to bn through one quad component.
func ( *canonicalizer) ( string,  Term,  Quad,  *identifierIssuer,  byte,  map[string][]string) {
	if .Kind != Blank || .Value ==  {
		return
	}
	 := .hashRelated(.Value, , , )
	[] = append([], .Value)
}

// permute calls visit for every permutation of list (Heap's algorithm). visit must
// not retain the slice: it is mutated in place between calls.
func permute( []string,  func([]string)) {
	if len() == 0 {
		()
		return
	}
	 := append([]string(nil), ...)
	var  func( int)
	 = func( int) {
		if  == 1 {
			()
			return
		}
		for  := range  {
			( - 1)
			if %2 == 0 {
				[], [-1] = [-1], []
			} else {
				[0], [-1] = [-1], [0]
			}
		}
	}
	(len())
}

// ---- canonical N-Quads term serialization ----

// canonLine appends one quad as a canonical N-Quads line (terms rendered by term),
// terminated by " .\n". A zero-value graph term is the default graph (three terms).
func canonLine( []byte,  Quad,  func([]byte, Term) []byte) []byte {
	 = (, .S)
	 = append(, ' ')
	 = (, .P)
	 = append(, ' ')
	 = (, .O)
	if .G != (Term{}) {
		 = append(, ' ')
		 = (, .G)
	}
	return append(, ' ', '.', '\n')
}

// canonAppendTerm writes a term in canonical N-Quads form.
func canonAppendTerm( []byte,  Term) []byte {
	switch .Kind {
	case IRI:
		 = append(, '<')
		 = appendEscapedIRI(, .Value)
		return append(, '>')
	case Blank:
		 = append(, '_', ':')
		return append(, .Value...)
	default:
		 = append(, '"')
		 = canonAppendLiteral(, .Value)
		 = append(, '"')
		if .Lang != "" {
			 = append(, '@')
			return append(, .Lang...)
		}
		if .Datatype != "" && .Datatype != XSDString {
			 = append(, '^', '^', '<')
			 = appendEscapedIRI(, .Datatype)
			return append(, '>')
		}
		return 
	}
}

// canonAppendLiteral escapes a literal's lexical form for canonical N-Quads: the
// named escapes \b \t \n \f \r \" \\, and \u00XX (uppercase) for other C0 controls
// and U+007F; all other bytes pass through as UTF-8. It shares the one literal
// escaper with the N-Triples serializer (see appendLiteralEscaped), configured for
// the RDFC-1.0 profile.
func canonAppendLiteral( []byte,  string) []byte {
	return appendLiteralEscaped(, , literalEscape{namedBF: true, escapeDEL: true})
}