package rdf

import (
	
	
	
)

// SyntaxError reports a line that is neither blank, nor a comment, nor a valid
// N-Triples/N-Quads statement. Line is 1-based; Text is the offending line,
// trimmed and truncated for the message.
//
// It exists so that a truncated document is an error rather than a smaller graph.
// A parser that skips what it cannot read turns a half-written dump into a
// well-formed, wrong answer, and no caller downstream can tell.
//
// Line counts from the start of the input actually handed to the parser, which is
// not the document when a caller feeds it one chunk at a time: a bad line in the
// third 1MB chunk of a large dump is reported at its offset within that chunk, and
// the number looks entirely plausible. Parse a chunked document through
// [NewDecoder] instead -- it reads across an [io.Reader] and numbers lines
// continuously -- or add the chunk's line base to Line before showing it to
// anyone.
type SyntaxError struct {
	Line int    // 1-based, relative to the input given to the parser; see the type doc
	Text string // the offending line, trimmed
}

func ( *SyntaxError) () string {
	 := .Text
	if len() > 64 {
		 = [:61] + "..."
	}
	return "rdf: line " + strconv.Itoa(.Line) + ": malformed N-Triples/N-Quads statement: " + strconv.Quote()
}

// lineKind classifies one line of an N-Triples/N-Quads document. Blank and
// comment lines carry no statement but are not errors; a malformed line is.
// Collapsing the two into one "not a statement" bool is what let a truncated
// document parse clean.
type lineKind uint8

const (
	lineStatement lineKind = iota // a well-formed statement
	lineIgnorable                 // blank or comment: no statement, no error
	lineMalformed                 // not parseable as a statement
)

// ParseNTriples parses an N-Triples document (one "subject predicate object ."
// statement per line) into a Graph. It also accepts N-Quads, ignoring any fourth
// (graph) term. Blank and comment lines are skipped; a malformed line is a
// *SyntaxError naming the line number, so a truncated document does not parse as
// a smaller graph. One private copy of the input backs every term, so data is
// free for reuse once it returns.
//
// Use [NewDecoder] with [Decoder.SkipMalformed] to tolerate the trailing noise
// some real-world dumps carry.
func ( []byte) (*Graph, error) {
	return parseNTriples(string())
}

// ParseNTriplesShared is ParseNTriples without the private input copy: terms
// are zero-copy views into data itself, saving one input-sized allocation. In
// exchange the caller must not modify data while the Graph, or any Term drawn
// from it, remains in use.
func ( []byte) (*Graph, error) {
	return parseNTriples(bytesView())
}

func parseNTriples( string) (*Graph, error) {
	// Zero-copy substrings of data back every term. Preallocate the triple slice
	// at full length from the line count and fill each statement in place through
	// a write index, so the 168-byte Triple is assembled directly in the slice
	// rather than returned and appended by value -- that struct copy (runtime.
	// duffcopy) otherwise dominates parse CPU. Ignorable/malformed lines leave the
	// index where it is; the tail of untouched slots is truncated at the end.
	 := &Graph{Triples: make([]Triple, strings.Count(, "\n")+1)}
	var  arena
	,  := 0, 0
	for  := range strings.SplitSeq(, "\n") {
		++
		switch parseNTLineInto(, &, &.Triples[]) {
		case lineStatement:
			++
		case lineMalformed:
			.Triples = .Triples[:]
			return , &SyntaxError{Line: , Text: strings.TrimSpace()}
		}
	}
	.Triples = .Triples[:]
	return , nil
}

// parseNTLineInto parses one N-Triples/N-Quads line, writing subject, predicate
// and object directly into t (dropping any fourth, graph, term) so the Triple is
// never copied by value. It mirrors parseNQuadLine's leniency: a trailing graph
// term or terminator is not required, and content past the object is ignored.
func parseNTLineInto( string,  *arena,  *Triple) lineKind {
	 := strings.TrimSpace()
	if  == "" || [0] == '#' {
		return lineIgnorable
	}
	var  bool
	if .S, ,  = readNTTerm(, ); ! || .S.IsLiteral() { // a literal subject is not valid RDF
		return lineMalformed
	}
	if .P, ,  = readNTTerm(strings.TrimLeft(, " \t"), ); ! || !.P.IsIRI() {
		return lineMalformed
	}
	if .O, _,  = readNTTerm(strings.TrimLeft(, " \t"), ); ! {
		return lineMalformed
	}
	return lineStatement
}

// parseNQuadLine parses one N-Triples/N-Quads line into a quad, keeping the
// optional fourth (graph) term; a three-term line falls in the default graph
// (zero-value G).
func parseNQuadLine( string,  *arena) (Quad, lineKind) {
	 := strings.TrimSpace()
	if  == "" || [0] == '#' {
		return Quad{}, lineIgnorable
	}
	, ,  := readNTTerm(, )
	if ! || .IsLiteral() { // a literal subject is not valid RDF
		return Quad{}, lineMalformed
	}
	, ,  := readNTTerm(strings.TrimLeft(, " \t"), )
	if ! || !.IsIRI() {
		return Quad{}, lineMalformed
	}
	, ,  := readNTTerm(strings.TrimLeft(, " \t"), )
	if ! {
		return Quad{}, lineMalformed
	}
	// The optional graph label is an IRI or blank node before the terminating
	// '.'; a literal there is not a valid graph name and is ignored.
	var  Term
	if  := strings.TrimLeft(, " \t"); len() > 0 && [0] != '.' {
		if , ,  := readNTTerm(, );  && !.IsLiteral() {
			 = 
		}
	}
	return Quad{, , , }, lineStatement
}

// readNTTerm reads one term from the front of s, returning the term and the
// remaining string.
func readNTTerm( string,  *arena) (Term, string, bool) {
	switch {
	case strings.HasPrefix(, "<"):
		 := strings.IndexByte(, '>')
		if  < 0 {
			return Term{}, , false
		}
		return NewIRI(unescapeRDF([1:])), [+1:], true
	case strings.HasPrefix(, "_:"):
		 := strings.IndexAny(, " \t")
		if  < 0 {
			 = len()
		}
		// A trailing "." is the statement terminator, not part of the label
		// (a label may contain "." but not end with one), so strip it.
		 := 
		for  > 2 && [-1] == '.' {
			--
		}
		return NewBlank([2:]), [:], true
	case strings.HasPrefix(, `"`):
		return readNTLiteral(, )
	}
	return Term{}, , false
}

// readNTLiteral reads a quoted literal and any ^^<datatype> or @lang suffix. The
// lexical form is a zero-copy slice of the input when it has no escapes, and is
// unescaped only when needed.
func readNTLiteral( string,  *arena) (Term, string, bool) {
	 := false
	 := 1
	for  < len() {
		switch [] {
		case '\\':
			 = true
			 += 2 // skip the escaped character so an escaped quote is not the close
		case '"':
			 := [1:]
			if  {
				if  != nil {
					 = .unescape() // arena-backed (whole-document parse)
				} else {
					 = unescapeRDF() // streaming: each triple owns its strings
				}
			}
			return ntLiteralSuffix(, [+1:])
		default:
			++
		}
	}
	return Term{}, , false // unterminated
}

// ntLiteralSuffix attaches a ^^<datatype> or @lang suffix to a literal value.
func ntLiteralSuffix(,  string) (Term, string, bool) {
	switch {
	case strings.HasPrefix(, "^^<"):
		 := strings.IndexByte(, '>')
		if  < 0 {
			return Term{}, , false
		}
		return NewLiteral(, "", unescapeRDF([3:])), [+1:], true
	case strings.HasPrefix(, "@"):
		 := langTagLen([1:])
		return NewLiteral(, [1:1+], ""), [1+:], true
	}
	return NewLiteral(, "", ""), , true
}

// langTagLen returns the length of a well-formed language tag at the start of s
// (the text after the '@'): [a-zA-Z]+ ('-' [a-zA-Z0-9]+)*. It returns 0 when s does
// not begin with a letter, so an invalid tag like "0" yields no language rather
// than a value the Turtle grammar would reject on a round trip.
func langTagLen( string) int {
	 := 0
	for  < len() && isAlpha([]) {
		++
	}
	if  == 0 {
		return 0
	}
	for  < len() && [] == '-' {
		 :=  + 1
		for  < len() && (isAlpha([]) || ([] >= '0' && [] <= '9')) {
			++
		}
		if  == +1 { // a '-' must be followed by at least one alphanumeric
			break
		}
		 = 
	}
	return 
}

// NTriples serializes the graph as N-Triples.
func ( *Graph) () []byte {
	var  Encoder
	return .AppendNTriples(nil, )
}

// appendNTTerm writes a term in N-Triples syntax.
func appendNTTerm( []byte,  Term,  *blankNamer) []byte {
	switch .Kind {
	case IRI:
		 = 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(, "^^<"...)
			 = appendEscapedIRI(, .Datatype)
			return append(, '>')
		}
		return 
	}
}

// ---- shared escaping ----

// iriNeedsEscape[c] reports whether byte c must be \u-escaped in an IRI
// reference — the ASCII controls and space plus the delimiters IRIs forbid bare.
// A table lookup replaces a per-byte chain of comparisons on the write path.
var iriNeedsEscape = func() ( [256]bool) {
	for  := 0;  <= 0x20; ++ {
		[] = true
	}
	for ,  := range []byte{'<', '>', '"', '{', '}', '|', '^', '`', '\\'} {
		[] = true
	}
	return
}()

// appendEscapedIRI escapes the characters not allowed bare in an IRI reference.
// Clean runs — the overwhelming common case, since real IRIs need no escaping —
// are bulk-copied in one append rather than byte by byte.
func appendEscapedIRI( []byte,  string) []byte {
	 := 0
	for  := 0;  < len(); ++ {
		if iriNeedsEscape[[]] {
			 = append(, [:]...)
			 = appendUnicodeEscape(, rune([]))
			 =  + 1
		}
	}
	return append(, [:]...)
}

// literalEscape parameterizes appendLiteralEscaped for the two output profiles
// that share its body. The N-Triples/Turtle serializer uses the zero value; the
// canonical N-Quads form additionally emits \b and \f as named escapes and escapes
// U+007F, as RDFC-1.0 requires.
type literalEscape struct {
	namedBF   bool // emit \b and \f as named escapes (else the \u00XX control rule)
	escapeDEL bool // escape U+007F (DEL) as \u007F rather than passing it through
}

// appendLiteralEscaped escapes a literal's lexical form per cfg: always the named
// escapes \\ \" \n \r \t and \uXXXX for C0 controls, with \b \f and U+007F handled
// per cfg; valid UTF-8 passes through and a lone invalid byte is dropped. It is the
// single escaper both the N-Triples serializer and the canonicalizer call, so the
// two cannot drift.
func appendLiteralEscaped( []byte,  string,  literalEscape) []byte {
	for  := 0;  < len(); {
		 := []
		if  < 0x80 {
			switch {
			case  == '\\':
				 = append(, '\\', '\\')
			case  == '"':
				 = append(, '\\', '"')
			case  == '\n':
				 = append(, '\\', 'n')
			case  == '\r':
				 = append(, '\\', 'r')
			case  == '\t':
				 = append(, '\\', 't')
			case  == '\b' && .namedBF:
				 = append(, '\\', 'b')
			case  == '\f' && .namedBF:
				 = append(, '\\', 'f')
			case  < 0x20 || ( == 0x7f && .escapeDEL):
				 = appendUnicodeEscape(, rune())
			default:
				 = append(, )
			}
			++
			continue
		}
		,  := utf8.DecodeRuneInString([:])
		if  == utf8.RuneError &&  == 1 {
			++
			continue
		}
		 = append(, [:+]...)
		 += 
	}
	return 
}

// appendEscapedLiteral escapes a literal's lexical form for Turtle/N-Triples.
func appendEscapedLiteral( []byte,  string) []byte {
	return appendLiteralEscaped(, , literalEscape{})
}

// blankNamer maps blank-node labels to fresh, always-valid identifiers (b1, b2, …)
// during one serialization. The mapping is injective, so two distinct blank nodes
// never collapse to the same output label — which a character-sanitizing scheme
// could (e.g. "!x" and "?x" both becoming "_x"), silently merging nodes.
type blankNamer struct {
	m map[string]string
	n int
}

func ( *blankNamer) ( string) string {
	if ,  := .m[];  {
		return 
	}
	if .m == nil {
		.m = make(map[string]string)
	}
	.n++
	 := "b" + strconv.Itoa(.n)
	.m[] = 
	return 
}

// newScope drops the label→name mappings while keeping the running counter, so
// the next labels begin a fresh blank-node scope yet still receive globally
// unique output names. It lets independently-labeled graphs (e.g. one per
// record) serialize into a single N-Quads document without their blank nodes
// merging.
func ( *blankNamer) () { .m = nil }

func appendUnicodeEscape( []byte,  rune) []byte {
	const  = "0123456789ABCDEF"
	return append(, '\\', 'u', [(>>12)&0xf], [(>>8)&0xf], [(>>4)&0xf], [&0xf])
}

// unescapeRDF unescapes \uXXXX, \UXXXXXXXX and the string escapes in an IRI or
// literal lexical form.
func unescapeRDF( string) string {
	if !strings.ContainsRune(, '\\') {
		return 
	}
	var  strings.Builder
	.Grow(len()) // the unescaped form is never longer, so one allocation suffices
	for  := 0;  < len(); {
		if [] == '\\' && +1 < len() {
			,  := unescapeRune([:])
			.WriteRune()
			 += 
			continue
		}
		.WriteByte([])
		++
	}
	return .String()
}

// unescapeRune decodes one backslash escape at the front of s, returning the rune
// and the number of bytes consumed.
func unescapeRune( string) (rune, int) {
	if len() < 2 {
		return '\\', 1
	}
	switch [1] {
	case 'n':
		return '\n', 2
	case 'r':
		return '\r', 2
	case 't':
		return '\t', 2
	case 'b':
		return '\b', 2
	case 'f':
		return '\f', 2
	case '"':
		return '"', 2
	case '\'':
		return '\'', 2
	case '\\':
		return '\\', 2
	case 'u':
		if len() >= 6 {
			if ,  := parseHex([2:6]);  {
				return , 6
			}
		}
	case 'U':
		if len() >= 10 {
			if ,  := parseHex([2:10]);  {
				return , 10
			}
		}
	}
	return rune([1]), 2
}

func parseHex( string) (rune, bool) {
	var  rune
	for  := 0;  < len(); ++ {
		 := []
		switch {
		case  >= '0' &&  <= '9':
			 = <<4 | rune(-'0')
		case  >= 'a' &&  <= 'f':
			 = <<4 | rune(-'a'+10)
		case  >= 'A' &&  <= 'F':
			 = <<4 | rune(-'A'+10)
		default:
			return 0, false
		}
	}
	return , true
}