package rdf

import (
	
	
)

// ---- Turtle serialization ----

// Turtle serializes the graph as Turtle, declaring the given namespace prefixes
// (prefix label -> namespace IRI) and compacting IRIs against them. Triples are
// grouped by subject in first-seen order, using `a` for rdf:type.
func ( *Graph) ( map[string]string) []byte {
	return append(TurtleHeader(), .TurtleBody()...)
}

// TurtleHeader returns the @prefix declaration block for the given prefixes,
// terminated by a blank line. Collection writers emit it once.
func ( map[string]string) []byte {
	if len() == 0 {
		return nil
	}
	 := make([]string, 0, len())
	for  := range  {
		 = append(, )
	}
	sort.Strings()
	var  []byte
	for ,  := range  {
		 = append(, "@prefix "...)
		 = append(, ...)
		 = append(, ": <"...)
		 = appendEscapedIRI(, [])
		 = append(, "> .\n"...)
	}
	return append(, '\n')
}

// TurtleBody serializes the triples grouped by subject, without a prefix header.
func ( *Graph) ( map[string]string) []byte {
	var  Encoder
	return .AppendTurtle(nil, , )
}

// AppendTurtle appends g's triples to b as a Turtle body (no @prefix header),
// grouped by subject, in a fresh blank-node scope. It groups without a
// per-subject map: subjects are ranked by first appearance, then a stable sort
// of triple indices by that rank makes each subject's triples contiguous in
// document order — turning thousands of small allocations into a handful.
func ( *Encoder) ( []byte,  *Graph,  map[string]string) []byte {
	 := len(.Triples)
	if  == 0 {
		return 
	}
	.bn.newScope()
	 := make(map[Term]int, )
	 := make([]int, )
	 := 0
	for ,  := range .Triples {
		,  := [.S]
		if ! {
			,  = , +1
			[.S] = 
		}
		[] = 
	}
	 := make([]int, )
	for  := range  {
		[] = 
	}
	sort.SliceStable(, func(,  int) bool { return [[]] < [[]] })

	var  []bool // reused across subjects
	for  := 0;  < ; {
		 :=  + 1
		for  <  && .Triples[[]].S == .Triples[[]].S {
			++
		}
		 = appendTurtleSubject(, .Triples, [:], , &, &.bn)
		 = 
	}
	return 
}

// appendTurtleSubject writes one subject's predicate-object list, grouping objects
// by predicate with a linear scan over the subject's (contiguous) triples and a
// caller-reused scratch buffer.
func appendTurtleSubject( []byte,  []Triple,  []int,  map[string]string,  *[]bool,  *blankNamer) []byte {
	 = appendTurtleTerm(, [[0]].S, , false, )

	 := (*)[:0]
	for range  {
		 = append(, false)
	}
	* = 

	 := true
	for  := range  {
		if [] {
			continue
		}
		 := [[]]
		if  {
			 = append(, ' ')
			 = false
		} else {
			 = append(, " ;\n    "...)
		}
		 = appendTurtleTerm(, .P, , true, )
		 = append(, ' ')
		 = appendTurtleTerm(, .O, , false, )
		[] = true
		for  :=  + 1;  < len(); ++ {
			if ![] && [[]].P == .P {
				 = append(, ", "...)
				 = appendTurtleTerm(, [[]].O, , false, )
				[] = true
			}
		}
	}
	return append(, " .\n"...)
}

// appendTurtleTerm writes a term in Turtle syntax. In predicate position rdf:type
// is written as `a`.
func appendTurtleTerm( []byte,  Term,  map[string]string,  bool,  *blankNamer) []byte {
	switch .Kind {
	case IRI:
		if  && .Value == TypeIRI {
			return append(, 'a')
		}
		if ,  := appendCompactIRI(, .Value, );  {
			return 
		}
		 = append(, '<')
		 = appendEscapedIRI(, .Value)
		return append(, '>')
	case Blank:
		 = append(, '_', ':')
		return append(, .name(.Value)...)
	default:
		 = append(, '"')
		 = appendEscapedLiteral(, .Value)
		 = append(, '"')
		if .Lang != "" {
			 = append(, '@')
			return append(, .Lang...)
		}
		if .Datatype != "" && .Datatype != XSDString {
			 = append(, "^^"...)
			if ,  := appendCompactIRI(, .Datatype, );  {
				return 
			}
			 = append(, '<')
			 = appendEscapedIRI(, .Datatype)
			return append(, '>')
		}
		return 
	}
}

// appendCompactIRI appends iri as a prefixed name against the longest matching
// namespace when the remaining local part is a valid bare local name, reporting
// whether it compacted. It writes straight to b, allocating no intermediate
// string.
func appendCompactIRI( []byte,  string,  map[string]string) ([]byte, bool) {
	,  := "", ""
	for ,  := range  {
		if len() > len() && strings.HasPrefix(, ) {
			if  := [len():]; validLocal() {
				,  = , 
			}
		}
	}
	if  == "" {
		return , false
	}
	 = append(, ...)
	 = append(, ':')
	 = append(, [len():]...)
	return , true
}

// validLocal reports whether s is a non-empty local name that can be written bare
// (matching what the reader accepts), so a round-trip is lossless.
func validLocal( string) bool {
	if  == "" || [len()-1] == '.' {
		return false
	}
	for  := 0;  < len(); ++ {
		if !isNameChar([]) {
			return false
		}
	}
	return true
}