package unstable

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"unicode/utf8"
)

// ParserError describes an error relative to the content of the document.
//
// It cannot outlive the instance of Parser it refers to, and may cause panics
// if the parser is reset.
type ParserError struct {
	Highlight []byte
	Message   string
	Key       []string // optional
}

// Error is the implementation of the error interface.
func (e *ParserError) Error() string {
	return e.Message
}

// NewParserError is a convenience function to create a ParserError
//
// Warning: Highlight needs to be a subslice of Parser.data, so only slices
// returned by Parser.Raw are valid candidates.
func NewParserError(highlight []byte, format string, args ...interface{}) error {
	return &ParserError{
		Highlight: highlight,
		Message:   fmt.Errorf(format, args...).Error(),
	}
}

// Parser scans over a TOML-encoded document and generates an iterative AST.
//
// To prime the Parser, first reset it with the contents of a TOML document.
// Then, process all top-level expressions sequentially. See Example.
//
// Don't forget to check Error() after you're done parsing.
//
// Each top-level expression needs to be fully processed before calling
// NextExpression() again. Otherwise, calls to various Node methods may panic
// if the parser has moved on the next expression.
//
// For performance reasons, go-toml doesn't make a copy of the input bytes to
// the parser. Make sure to copy all the bytes you need to outlive the slice
// given to the parser.
type Parser struct {
	KeepComments bool

	data  []byte
	left  []byte
	nodes []Node
	err   error
}

// Data returns the slice provided to the last call to Reset.
func (p *Parser) Data() []byte {
	return p.data
}

// Range returns a range description that corresponds to a given slice of the
// input. If the argument is not a subslice of the parser input, this function
// panics.
func (p *Parser) Range(b []byte) Range {
	// b is a subslice of p.data if and only if they share the same backing
	// array. In that case, because subslicing cannot extend capacity, the
	// number of bytes between the start of b and the end of the backing array
	// (its capacity) identifies the offset of b within data.
	offset := cap(p.data) - cap(b)
	if offset < 0 || offset+len(b) > len(p.data) {
		panic(errors.New("not a slice of the data slice"))
	}
	return Range{
		Offset: uint32(offset), //nolint:gosec // TOML documents are small
		Length: uint32(len(b)), //nolint:gosec // TOML documents are small
	}
}

// Raw returns the slice corresponding to the bytes in the given range.
func (p *Parser) Raw(raw Range) []byte {
	return p.data[raw.Offset : raw.Offset+raw.Length]
}

// Reset brings the parser to its initial state for a given input. It wipes an
// reuses internal storage to reduce allocation.
func (p *Parser) Reset(b []byte) {
	p.data = b
	p.left = b
	p.nodes = p.nodes[:0]
	p.err = nil
}

// Error returns any error that has occurred during parsing.
func (p *Parser) Error() error {
	return p.err
}

// Range of bytes in the document.
type Range struct {
	Offset uint32
	Length uint32
}

// Position describes a position in the input.
type Position struct {
	// Number of bytes from the beginning of the input.
	Offset int
	// Line number, starting at 1.
	Line int
	// Column number, starting at 1.
	Column int
}

// Shape describes the position of a range in the input.
type Shape struct {
	Start Position
	End   Position
}

func (p *Parser) position(offset int) Position {
	pos := Position{
		Offset: offset,
		Line:   1,
		Column: 1,
	}
	b := p.data[:offset]
	for {
		idx := bytes.IndexByte(b, '\n')
		if idx < 0 {
			break
		}
		pos.Line++
		b = b[idx+1:]
	}
	pos.Column = len(b) + 1
	return pos
}

// Shape returns the shape of the given range in the input.  Will
// panic if the range is not a subslice of the input.
func (p *Parser) Shape(r Range) Shape {
	raw := p.Raw(r)
	return Shape{
		Start: p.position(int(r.Offset)),
		End:   p.position(int(r.Offset) + len(raw)),
	}
}

// Expression returns a pointer to the node representing the last successfully
// parsed expression.
func (p *Parser) Expression() *Node {
	if len(p.nodes) == 0 {
		return nil
	}
	return &p.nodes[0]
}

// push appends a node to the arena and returns its handle (1-based index).
func (p *Parser) push(n Node) int32 {
	if len(p.nodes) == cap(p.nodes) {
		// Grow by 2x: large expressions (huge arrays) would otherwise grow
		// the arena in small steps, copying it repeatedly.
		newCap := 2 * cap(p.nodes)
		if newCap < 64 {
			newCap = 64
		}
		nodes := make([]Node, len(p.nodes), newCap)
		copy(nodes, p.nodes)
		p.nodes = nodes
	}
	n.parser = p
	p.nodes = append(p.nodes, n)
	return int32(len(p.nodes)) //nolint:gosec // node counts are bounded by document size
}

// at returns a pointer to the node with the given handle. Only valid until
// the next call to push.
func (p *Parser) at(handle int32) *Node {
	return &p.nodes[handle-1]
}

// offsetOf returns the offset of b within the parser's data. b must be a
// subslice of p.data.
func (p *Parser) offsetOf(b []byte) int {
	return cap(p.data) - cap(b)
}

// rangeFrom returns the Range covering bytes from the start of `from` to the
// start of `to`. Both must be subslices of p.data.
func (p *Parser) rangeFrom(from, to []byte) Range {
	start := p.offsetOf(from)
	end := p.offsetOf(to)
	return Range{
		Offset: uint32(start),       //nolint:gosec // TOML documents are small
		Length: uint32(end - start), //nolint:gosec // TOML documents are small
	}
}

// NextExpression parses the next top-level expression. If an expression was
// successfully parsed, it returns true. If the parser is at the end of the
// document or an error occurred, it returns false.
//
// Retrieve the parsed expression with Expression().
func (p *Parser) NextExpression() bool {
	if p.err != nil {
		return false
	}

	p.nodes = p.nodes[:0]

	for {
		b := skipWhitespace(p.left)
		if len(b) == 0 {
			p.left = b
			return false
		}

		var err error
		switch b[0] {
		case '\n':
			p.left = b[1:]
			continue
		case '\r':
			if len(b) > 1 && b[1] == '\n' {
				p.left = b[2:]
				continue
			}
			err = NewParserError(b[:1], "expected newline but got %#U", b[0])
		case '#':
			var comment, rest []byte
			comment, rest, err = scanComment(b)
			if err == nil {
				rest, err = consumeEOL(rest)
			}
			if err == nil {
				if p.KeepComments {
					p.push(Node{
						Kind: Comment,
						Raw:  p.Range(comment),
						Data: comment,
					})
					p.left = rest
					return true
				}
				p.left = rest
				continue
			}
		case '[':
			var rest []byte
			rest, err = p.parseExprTable(b)
			if err == nil {
				p.left = rest
				return true
			}
		default:
			var rest []byte
			rest, err = p.parseExprKeyval(b)
			if err == nil {
				p.left = rest
				return true
			}
		}

		// Errors at the end of the input have an empty highlight. Extend
		// them to the last byte of the input so that they carry a usable
		// position.
		var perr *ParserError
		if errors.As(err, &perr) && len(perr.Highlight) == 0 {
			if offset := p.offsetOf(perr.Highlight); offset > 0 && offset == len(p.data) {
				perr.Highlight = p.data[offset-1 : offset]
			}
		}

		p.err = err
		return false
	}
}

// consumeEOL consumes a newline (LF or CRLF) or end of input.
func consumeEOL(b []byte) ([]byte, error) {
	if len(b) == 0 {
		return b, nil
	}
	switch b[0] {
	case '\n':
		return b[1:], nil
	case '\r':
		if len(b) > 1 && b[1] == '\n' {
			return b[2:], nil
		}
	}
	return nil, NewParserError(b[:1], "expected newline but got %#U", b[0])
}

// finishLine handles `ws [comment] (newline|eof)` after a top-level
// expression. If a comment is present and KeepComments is set, it is attached
// as the next sibling of the expression's root node.
func (p *Parser) finishLine(root int32, b []byte) ([]byte, error) {
	b = skipWhitespace(b)
	if len(b) > 0 && b[0] == '#' {
		comment, rest, err := scanComment(b)
		if err != nil {
			return nil, err
		}
		if p.KeepComments {
			h := p.push(Node{
				Kind: Comment,
				Raw:  p.Range(comment),
				Data: comment,
			})
			p.at(root).next = h
		}
		b = rest
	}
	return consumeEOL(b)
}

// parseExprKeyval parses a top-level `key = value` expression, including its
// line termination.
func (p *Parser) parseExprKeyval(b []byte) ([]byte, error) {
	root, rest, err := p.parseKeyval(b)
	if err != nil {
		return nil, err
	}
	return p.finishLine(root, rest)
}

// parseExprTable parses a `[table]` or `[[array table]]` expression,
// including its line termination. b starts at '['.
func (p *Parser) parseExprTable(b []byte) ([]byte, error) {
	var root int32
	var err error
	var rest []byte
	if len(b) > 1 && b[1] == '[' {
		root, rest, err = p.parseArrayTableHeader(b)
	} else {
		root, rest, err = p.parseTableHeader(b)
	}
	if err != nil {
		return nil, err
	}
	return p.finishLine(root, rest)
}

// parseTableHeader parses `[ ws key ws ]`. b starts at '['.
func (p *Parser) parseTableHeader(b []byte) (int32, []byte, error) {
	root := p.push(Node{Kind: Table})

	first, b, err := p.parseKey(skipWhitespace(b[1:]))
	if err != nil {
		return 0, nil, err
	}
	p.at(root).child = first

	if len(b) == 0 || b[0] != ']' {
		return 0, nil, NewParserError(highlight1(b), "expected ']' to close table name")
	}
	return root, b[1:], nil
}

// parseArrayTableHeader parses `[[ ws key ws ]]`. b starts at '[['.
func (p *Parser) parseArrayTableHeader(b []byte) (int32, []byte, error) {
	root := p.push(Node{Kind: ArrayTable})

	first, b, err := p.parseKey(skipWhitespace(b[2:]))
	if err != nil {
		return 0, nil, err
	}
	p.at(root).child = first

	if len(b) < 2 || b[0] != ']' || b[1] != ']' {
		return 0, nil, NewParserError(highlight1(b), "expected ']]' to close array table name")
	}
	return root, b[2:], nil
}

// parseKeyval parses `key keyval-sep val`. Returns the handle to the KeyValue
// node.
func (p *Parser) parseKeyval(b []byte) (int32, []byte, error) {
	root := p.push(Node{Kind: KeyValue})
	start := b

	firstKey, b, err := p.parseKey(b)
	if err != nil {
		return 0, nil, err
	}

	if len(b) == 0 || b[0] != '=' {
		return 0, nil, NewParserError(highlight1(b), "expected '=' after key")
	}
	b = skipWhitespace(b[1:])

	value, b, err := p.parseVal(b)
	if err != nil {
		return 0, nil, err
	}

	p.at(root).child = value
	p.at(value).next = firstKey
	p.at(root).Raw = p.rangeFrom(start, b)
	return root, b, nil
}

// parseKey parses a potentially dotted key. It consumes the whitespace
// following the key, so that the caller can directly check for the next
// expected character ('=', ']', ...). Returns the handle of the first Key
// node; subsequent parts are chained via next.
func (p *Parser) parseKey(b []byte) (int32, []byte, error) {
	var first, last int32
	for {
		h, rest, err := p.parseSimpleKey(b)
		if err != nil {
			return 0, nil, err
		}
		if first == 0 {
			first = h
		} else {
			p.at(last).next = h
		}
		last = h

		b = skipWhitespace(rest)
		if len(b) > 0 && b[0] == '.' {
			b = skipWhitespace(b[1:])
			continue
		}
		return first, b, nil
	}
}

func isUnquotedKeyChar(c byte) bool {
	return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_'
}

// parseSimpleKey parses one key part: either a bare key or a quoted key.
func (p *Parser) parseSimpleKey(b []byte) (int32, []byte, error) {
	raw, value, rest, err := p.scanSimpleKey(b)
	if err != nil {
		return 0, nil, err
	}
	h := p.push(Node{Kind: Key, Raw: p.Range(raw), Data: value})
	return h, rest, nil
}

// scanSimpleKey scans one key part (bare or quoted) without building an AST
// node. It returns the raw bytes, the decoded key value, and the rest of the
// input.
func (p *Parser) scanSimpleKey(b []byte) (raw, value, rest []byte, err error) {
	if len(b) == 0 {
		return nil, nil, nil, NewParserError(b, "expected key but reached end of input")
	}

	switch b[0] {
	case '\'':
		return p.parseLiteralString(b)
	case '"':
		return p.parseBasicString(b)
	default:
		i := 0
		for i < len(b) && isUnquotedKeyChar(b[i]) {
			i++
		}
		if i == 0 {
			return nil, nil, nil, NewParserError(b[:1], "invalid character at start of key: %#U", b[0])
		}
		return b[:i], b[:i], b[i:], nil
	}
}

// parseVal parses a TOML value and returns the handle to its node.
func (p *Parser) parseVal(b []byte) (int32, []byte, error) {
	if len(b) == 0 {
		return 0, nil, NewParserError(b, "expected value, not end of input")
	}

	c := b[0]
	switch {
	case c == '"':
		var raw, value, rest []byte
		var err error
		if len(b) > 2 && b[1] == '"' && b[2] == '"' {
			raw, value, rest, err = p.parseMultilineBasicString(b)
		} else {
			raw, value, rest, err = p.parseBasicString(b)
		}
		if err != nil {
			return 0, nil, err
		}
		h := p.push(Node{Kind: String, Raw: p.Range(raw), Data: value})
		return h, rest, nil
	case c == '\'':
		var raw, value, rest []byte
		var err error
		if len(b) > 2 && b[1] == '\'' && b[2] == '\'' {
			raw, value, rest, err = p.parseMultilineLiteralString(b)
		} else {
			raw, value, rest, err = p.parseLiteralString(b)
		}
		if err != nil {
			return 0, nil, err
		}
		h := p.push(Node{Kind: String, Raw: p.Range(raw), Data: value})
		return h, rest, nil
	case c == 't':
		return p.parseKeyword(b, "true", Bool)
	case c == 'f':
		return p.parseKeyword(b, "false", Bool)
	case c == 'i':
		return p.parseKeyword(b, "inf", Float)
	case c == 'n':
		return p.parseKeyword(b, "nan", Float)
	case c == '[':
		return p.parseValArray(b)
	case c == '{':
		return p.parseInlineTable(b)
	case c == '+' || c == '-':
		return p.parseIntOrFloat(b)
	case c >= '0' && c <= '9':
		if isDateTimeStart(b) {
			return p.parseDateTime(b)
		}
		return p.parseIntOrFloat(b)
	default:
		return 0, nil, NewParserError(b[:1], "unexpected character %#U at start of value", c)
	}
}

// scanScalar scans a single scalar TOML value (string, integer, float,
// boolean, or date/time) without building any AST node. It returns the kind of
// the value, its raw bytes, its decoded value bytes (for strings: quotes
// removed and escapes resolved; identical to raw for the other kinds), and the
// rest of the input. Arrays and inline tables are not scalars and produce an
// error: use parseValue for those.
//
// It is exposed to the root toml package through internal/parserbridge for the
// fused generic-decode path; it is not part of the public API.
func (p *Parser) scanScalar(b []byte) (kind Kind, raw, value, rest []byte, err error) {
	if len(b) == 0 {
		return Invalid, nil, nil, nil, NewParserError(b, "expected value, not end of input")
	}

	c := b[0]
	switch {
	case c == '"':
		if len(b) > 2 && b[1] == '"' && b[2] == '"' {
			raw, value, rest, err = p.parseMultilineBasicString(b)
		} else {
			raw, value, rest, err = p.parseBasicString(b)
		}
		return String, raw, value, rest, err
	case c == '\'':
		if len(b) > 2 && b[1] == '\'' && b[2] == '\'' {
			raw, value, rest, err = p.parseMultilineLiteralString(b)
		} else {
			raw, value, rest, err = p.parseLiteralString(b)
		}
		return String, raw, value, rest, err
	case c == 't':
		return scanKeyword(b, "true", Bool)
	case c == 'f':
		return scanKeyword(b, "false", Bool)
	case c == 'i':
		return scanKeyword(b, "inf", Float)
	case c == 'n':
		return scanKeyword(b, "nan", Float)
	case c == '+' || c == '-':
		return scanIntOrFloat(b)
	case c >= '0' && c <= '9':
		if isDateTimeStart(b) {
			return scanDateTime(b)
		}
		return scanIntOrFloat(b)
	default:
		return Invalid, nil, nil, nil, NewParserError(b[:1], "unexpected character %#U at start of value", c)
	}
}

// scanKey scans a potentially dotted key without building AST nodes,
// appending the decoded value of each part to dst (pass dst[:0] to reuse a
// buffer). It consumes the whitespace following the key, so the caller can
// directly check for the next expected character ('=', ']', ...). It returns
// the parts, the raw bytes spanning the whole key (from the first part to the
// end of the last one, excluding trailing whitespace, usable as an error
// highlight), the rest of the input, and any error.
//
// It is exposed to the root toml package through internal/parserbridge for the
// fused generic-decode path; it is not part of the public API.
func (p *Parser) scanKey(b []byte, dst [][]byte) (parts [][]byte, raw, rest []byte, err error) {
	parts = dst
	start := b
	for {
		_, value, r, err := p.scanSimpleKey(b)
		if err != nil {
			return nil, nil, nil, err
		}
		parts = append(parts, value)

		// r points just past the current part: the key spans from start to
		// here, ignoring any whitespace that follows.
		raw = start[:len(start)-len(r)]

		b = skipWhitespace(r)
		if len(b) > 0 && b[0] == '.' {
			b = skipWhitespace(b[1:])
			continue
		}
		return parts, raw, b, nil
	}
}

// parseValue parses a single TOML value, which may be an array or inline table,
// into the parser's arena. It returns the root node of the value and the rest
// of the input. It resets the arena, so any node returned by a previous call to
// parseValue, Expression, or NextExpression is invalidated.
//
// It is exposed to the root toml package through internal/parserbridge for the
// fused generic-decode path; it is not part of the public API.
func (p *Parser) parseValue(b []byte) (*Node, []byte, error) {
	p.nodes = p.nodes[:0]
	h, rest, err := p.parseVal(b)
	if err != nil {
		return nil, nil, err
	}
	return &p.nodes[h-1], rest, nil
}

func (p *Parser) parseKeyword(b []byte, kw string, kind Kind) (int32, []byte, error) {
	k, raw, _, rest, err := scanKeyword(b, kw, kind)
	if err != nil {
		return 0, nil, err
	}
	h := p.push(Node{Kind: k, Raw: p.Range(raw), Data: raw})
	return h, rest, nil
}

// scanKeyword scans a keyword value (true, false, inf, nan) without building
// an AST node. raw and value are identical (the keyword bytes).
func scanKeyword(b []byte, kw string, kind Kind) (Kind, []byte, []byte, []byte, error) {
	if len(b) < len(kw) || string(b[:len(kw)]) != kw {
		n := len(kw)
		if len(b) < n {
			n = len(b)
		}
		return Invalid, nil, nil, nil, NewParserError(b[:n], "expected keyword %q", kw)
	}
	return kind, b[:len(kw)], b[:len(kw)], b[len(kw):], nil
}

// parseValArray parses an array value. b starts at '['.
func (p *Parser) parseValArray(b []byte) (int32, []byte, error) {
	arr := p.push(Node{Kind: Array})
	b = b[1:]

	var lastChild int32
	appendChild := func(h int32) {
		if lastChild == 0 {
			p.at(arr).child = h
		} else {
			p.at(lastChild).next = h
		}
		lastChild = h
	}

	// Comments inside the array are attached as follows: the first comment
	// of a "run" (consecutive comments with no value in between) becomes a
	// child of the array, interleaved with values; subsequent comments of the
	// run are attached as children of the first one.
	var runFirst, runLast int32

	// afterValue is true when a value has been parsed and a comma (or the
	// closing bracket) is expected before the next one.
	afterValue := false
	for {
		b = skipWhitespace(b)
		if len(b) == 0 {
			return 0, nil, NewParserError(b, "array is incomplete")
		}

		switch b[0] {
		case ']':
			return arr, b[1:], nil
		case '\n':
			b = b[1:]
			continue
		case '\r':
			if len(b) > 1 && b[1] == '\n' {
				b = b[2:]
				continue
			}
			return 0, nil, NewParserError(b[:1], "expected newline but got %#U", b[0])
		case '#':
			comment, rest, err := scanComment(b)
			if err != nil {
				return 0, nil, err
			}
			if p.KeepComments {
				h := p.push(Node{Kind: Comment, Raw: p.Range(comment), Data: comment})
				switch {
				case runFirst == 0:
					appendChild(h)
					runFirst = h
				case runLast == runFirst:
					p.at(runFirst).child = h
				default:
					p.at(runLast).next = h
				}
				runLast = h
			}
			b = rest
			continue
		case ',':
			if !afterValue {
				return 0, nil, NewParserError(b[:1], "expected value but got %#U", b[0])
			}
			afterValue = false
			b = b[1:]
			continue
		default:
			if afterValue {
				return 0, nil, NewParserError(b[:1], "expected ',' or ']' after array value")
			}
			h, rest, err := p.parseVal(b)
			if err != nil {
				return 0, nil, err
			}
			appendChild(h)
			afterValue = true
			runFirst, runLast = 0, 0
			b = rest
			continue
		}
	}
}

// parseInlineTable parses an inline table value. b starts at '{'.
//
// Per TOML v1.1.0, inline tables may span multiple lines (whitespace,
// comments and newlines are allowed between elements) and may contain a
// trailing comma.
func (p *Parser) parseInlineTable(b []byte) (int32, []byte, error) {
	tbl := p.push(Node{Kind: InlineTable, Raw: p.Range(b[:1])})
	b = b[1:]

	var lastChild int32
	appendChild := func(h int32) {
		if lastChild == 0 {
			p.at(tbl).child = h
		} else {
			p.at(lastChild).next = h
		}
		lastChild = h
	}

	// Comments are attached as in arrays: the first comment of a "run"
	// (consecutive comments with no key-value in between) becomes a child of
	// the table, interleaved with key-values; subsequent comments of the run
	// hang off the first one.
	var runFirst, runLast int32

	// afterValue is true when a key-value has been parsed and a comma (or the
	// closing brace) is expected before the next one.
	afterValue := false
	for {
		b = skipWhitespace(b)
		if len(b) == 0 {
			return 0, nil, NewParserError(b, "inline table is incomplete")
		}

		switch b[0] {
		case '}':
			return tbl, b[1:], nil
		case '\n':
			b = b[1:]
			continue
		case '\r':
			if len(b) > 1 && b[1] == '\n' {
				b = b[2:]
				continue
			}
			return 0, nil, NewParserError(b[:1], "expected newline but got %#U", b[0])
		case '#':
			comment, rest, err := scanComment(b)
			if err != nil {
				return 0, nil, err
			}
			if p.KeepComments {
				h := p.push(Node{Kind: Comment, Raw: p.Range(comment), Data: comment})
				switch {
				case runFirst == 0:
					appendChild(h)
					runFirst = h
				case runLast == runFirst:
					p.at(runFirst).child = h
				default:
					p.at(runLast).next = h
				}
				runLast = h
			}
			b = rest
			continue
		case ',':
			if !afterValue {
				return 0, nil, NewParserError(b[:1], "unexpected comma in inline table")
			}
			afterValue = false
			b = b[1:]
			continue
		default:
			if afterValue {
				return 0, nil, NewParserError(b[:1], "expected ',' or '}' after inline table key-value")
			}
			h, rest, err := p.parseKeyval(b)
			if err != nil {
				return 0, nil, err
			}
			appendChild(h)
			afterValue = true
			runFirst, runLast = 0, 0
			b = rest
			continue
		}
	}
}

func isDigit(c byte) bool {
	return c >= '0' && c <= '9'
}

func isHexDigit(c byte) bool {
	return isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
}

// isDateTimeStart reports whether b looks like the start of a date or time
// value instead of a number. Values starting with two digits followed by a
// colon are times; values starting with four digits followed by a dash are
// dates.
func isDateTimeStart(b []byte) bool {
	if len(b) >= 3 && isDigit(b[1]) && b[2] == ':' {
		return true
	}
	if len(b) >= 5 && isDigit(b[1]) && isDigit(b[2]) && isDigit(b[3]) && b[4] == '-' {
		return true
	}
	return false
}

// expectDigits checks that the n first bytes of b are digits.
// parseDateTime parses date and/or time values. b starts with a digit.
//
// The parser is lenient: it scans the characters that can be part of a date
// and/or time value to delimit and classify the token, but leaves the
// validation of its contents to the document consumer. This keeps the value
// in one piece, so that errors about its content can point at the right
// place.
func (p *Parser) parseDateTime(b []byte) (int32, []byte, error) {
	kind, raw, _, rest, err := scanDateTime(b)
	if err != nil {
		return 0, nil, err
	}
	h := p.push(Node{Kind: kind, Raw: p.Range(raw), Data: raw})
	return h, rest, nil
}

// scanDateTime classifies and delimits a date/time value without building an
// AST node. raw and value are identical (the token bytes).
func scanDateTime(b []byte) (Kind, []byte, []byte, []byte, error) {
	// Greedily scan the characters that may compose a date/time value. A
	// space is part of the value only when it serves as the delimiter
	// between the date and the time, which is approximated by requiring a
	// digit right after it.
	i := 0
	delim := -1
	for i < len(b) {
		c := b[i]
		if isDigit(c) || c == ':' || c == '-' || c == '+' || c == '.' || c == 'Z' || c == 'z' {
			i++
			continue
		}
		if c == 'T' || c == 't' || (c == ' ' && i+1 < len(b) && isDigit(b[i+1])) {
			if delim < 0 {
				delim = i
			}
			i++
			continue
		}
		break
	}
	tok := b[:i]

	var kind Kind
	switch {
	case tok[2] == ':':
		kind = LocalTime
	case delim < 0:
		kind = LocalDate
	case bytes.ContainsAny(tok[delim+1:], "Zz+-"):
		kind = DateTime
	default:
		kind = LocalDateTime
	}

	return kind, tok, tok, b[i:], nil
}

// scanDigitsWithUnderscores scans a run of digits potentially separated by
// underscores. b starts right after the first digit of the run. isInRange
// selects the kind of digits. Returns the index after the run.
func scanDigitsWithUnderscores(b []byte, i int, isInRange func(byte) bool) (int, error) {
	for i < len(b) {
		c := b[i]
		if isInRange(c) {
			i++
			continue
		}
		if c == '_' {
			if i+1 >= len(b) || !isInRange(b[i+1]) {
				end := i + 2
				if end > len(b) {
					end = len(b)
				}
				return 0, NewParserError(b[i:end], "number must have at least one digit between underscores")
			}
			i += 2
			continue
		}
		break
	}
	return i, nil
}

// parseIntOrFloat parses integer and float values, including the special
// values inf and nan with an optional sign.
func (p *Parser) parseIntOrFloat(b []byte) (int32, []byte, error) {
	kind, raw, _, rest, err := scanIntOrFloat(b)
	if err != nil {
		return 0, nil, err
	}
	h := p.push(Node{Kind: kind, Raw: p.Range(raw), Data: raw})
	return h, rest, nil
}

// scanIntOrFloat delimits and classifies an integer or float value (including
// the special floats inf and nan with an optional sign) without building an
// AST node. raw and value are identical (the token bytes).
func scanIntOrFloat(b []byte) (Kind, []byte, []byte, []byte, error) {
	i := 0
	if b[i] == '+' || b[i] == '-' {
		i++
	}
	if i >= len(b) {
		return Invalid, nil, nil, nil, NewParserError(b, "expected number after sign")
	}

	// special floats
	if b[i] == 'i' || b[i] == 'n' {
		kw := "inf"
		if b[i] == 'n' {
			kw = "nan"
		}
		if len(b) < i+3 || string(b[i:i+3]) != kw {
			return Invalid, nil, nil, nil, NewParserError(b[i:i+1], "expected %q", kw)
		}
		i += 3
		return Float, b[:i], b[:i], b[i:], nil
	}

	if !isDigit(b[i]) {
		return Invalid, nil, nil, nil, NewParserError(b[i:i+1], "expected digit but got %#U", b[i])
	}

	// radix prefixes
	if b[i] == '0' && i+1 < len(b) && (b[i+1] == 'x' || b[i+1] == 'o' || b[i+1] == 'b') {
		if i != 0 {
			return Invalid, nil, nil, nil, NewParserError(b[:2], "sign is not allowed on numbers with a radix prefix")
		}
		var isInRange func(byte) bool
		switch b[1] {
		case 'x':
			isInRange = isHexDigit
		case 'o':
			isInRange = func(c byte) bool { return c >= '0' && c <= '7' }
		case 'b':
			isInRange = func(c byte) bool { return c == '0' || c == '1' }
		}
		i = 2
		if i >= len(b) || !isInRange(b[i]) {
			return Invalid, nil, nil, nil, NewParserError(b[:2], "radix prefix must be followed by at least one digit")
		}
		i++
		var err error
		i, err = scanDigitsWithUnderscores(b, i, isInRange)
		if err != nil {
			return Invalid, nil, nil, nil, err
		}
		return Integer, b[:i], b[:i], b[i:], nil
	}

	// decimal integer part
	leadingZero := b[i] == '0'
	digitsStart := i
	i++
	var err error
	i, err = scanDigitsWithUnderscores(b, i, isDigit)
	if err != nil {
		return Invalid, nil, nil, nil, err
	}
	if leadingZero && i > digitsStart+1 {
		return Invalid, nil, nil, nil, NewParserError(b[digitsStart:digitsStart+2], "integers cannot have leading zeroes")
	}

	kind := Integer

	// fractional part
	if i < len(b) && b[i] == '.' {
		i++
		if i >= len(b) || !isDigit(b[i]) {
			return Invalid, nil, nil, nil, NewParserError(highlight1(b[i:]), "decimal point must be followed by a digit")
		}
		i++
		i, err = scanDigitsWithUnderscores(b, i, isDigit)
		if err != nil {
			return Invalid, nil, nil, nil, err
		}
		kind = Float
	}

	// exponent
	if i < len(b) && (b[i] == 'e' || b[i] == 'E') {
		i++
		if i < len(b) && (b[i] == '+' || b[i] == '-') {
			i++
		}
		if i >= len(b) || !isDigit(b[i]) {
			return Invalid, nil, nil, nil, NewParserError(highlight1(b[i:]), "exponent must contain at least one digit")
		}
		i++
		i, err = scanDigitsWithUnderscores(b, i, isDigit)
		if err != nil {
			return Invalid, nil, nil, nil, err
		}
		kind = Float
	}

	// A letter right after the number means it was meant to be a string that
	// was left unquoted (e.g. "20s"). Report that instead of the misleading
	// "expected newline" raised later (issue #413).
	if i < len(b) {
		if c := b[i]; (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
			return Invalid, nil, nil, nil, NewParserError(b[i:i+1], "strings must be quoted")
		}
	}

	return kind, b[:i], b[:i], b[i:], nil
}

// highlight1 returns a 1-byte highlight at the start of b, or b itself if it
// is empty.
func highlight1(b []byte) []byte {
	if len(b) > 0 {
		return b[:1]
	}
	return b
}

func skipWhitespace(b []byte) []byte {
	for len(b) > 0 && (b[0] == ' ' || b[0] == '\t') {
		b = b[1:]
	}
	return b
}

// Word-at-a-time byte scanning helpers. These detect, within an 8-byte word,
// the presence of bytes that need special handling, so that runs of plain
// ASCII characters can be skipped 8 bytes at a time.
const (
	lsb = 0x0101010101010101
	msb = 0x8080808080808080
)

// hasByteBelow reports whether any byte of the word x is strictly below n.
// Only meaningful when combined with a check that no byte has its high bit
// set.
func hasByteBelow(x uint64, n uint64) uint64 {
	return (x - n*lsb) & ^x & msb
}

// hasByteEqual reports whether any byte of the word x equals c. Only
// meaningful for bytes without their high bit set.
func hasByteEqual(x uint64, c uint64) uint64 {
	y := x ^ (c * lsb)
	return (y - lsb) & ^y & msb
}

// scanComment parses a comment, starting at the '#' character. It returns the
// comment bytes (including '#', excluding the line ending) and the rest of
// the input.
func scanComment(b []byte) ([]byte, []byte, error) {
	i := 1
	for i < len(b) {
		// Fast path: skip 8 bytes at a time as long as they are all plain
		// printable ASCII.
		for i+8 <= len(b) {
			x := binary.LittleEndian.Uint64(b[i:])
			if (x&msb)|hasByteBelow(x, 0x20)|hasByteEqual(x, 0x7f) != 0 {
				break
			}
			i += 8
		}
		if i >= len(b) {
			break
		}

		c := b[i]
		if c >= 0x80 {
			var ok bool
			i, ok = scanUtf8Run(b, i)
			if !ok {
				return nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in comment")
			}
			continue
		}
		switch {
		case c >= 0x20 && c < 0x7f:
			i++
		case c == '\n':
			return b[:i], b[i:], nil
		case c == '\r':
			if i+1 < len(b) && b[i+1] == '\n' {
				return b[:i], b[i:], nil
			}
			return nil, nil, NewParserError(b[i:i+1], "carriage returns are not allowed in comments")
		case c == '\t':
			i++
		default:
			return nil, nil, NewParserError(b[i:i+1], "control characters are not allowed in comments")
		}
	}
	return b[:i], b[i:], nil
}

// parseLiteralString parses a single-line literal string, starting at the
// opening quote. Returns the raw bytes (with quotes), the string value
// (without quotes) and the rest of the input.
func (p *Parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) {
	i := 1
	for {
		// Fast path over plain ASCII.
		for i+8 <= len(b) {
			x := binary.LittleEndian.Uint64(b[i:])
			if (x&msb)|hasByteBelow(x, 0x20)|hasByteEqual(x, '\'')|hasByteEqual(x, 0x7f) != 0 {
				break
			}
			i += 8
		}
		if i >= len(b) {
			return nil, nil, nil, NewParserError(b[len(b):], "unterminated literal string")
		}

		c := b[i]
		switch {
		case c == '\'':
			return b[:i+1], b[1:i], b[i+1:], nil
		case c >= 0x20 && c < 0x7f:
			i++
		case c == '\t':
			i++
		case c == '\n' || c == '\r':
			return nil, nil, nil, NewParserError(b[i:i+1], "literal strings cannot have new lines")
		case c < 0x80:
			return nil, nil, nil, NewParserError(b[i:i+1], "literal strings cannot have control characters")
		default:
			var ok bool
			i, ok = scanUtf8Run(b, i)
			if !ok {
				return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in literal string")
			}
		}
	}
}

// parseMultilineLiteralString parses a multi-line literal string, starting at
// the opening triple quote.
func (p *Parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) {
	i := 3
	// trim the newline right after the opening delimiter
	if i < len(b) && b[i] == '\n' {
		i++
	} else if i+1 < len(b) && b[i] == '\r' && b[i+1] == '\n' {
		i += 2
	}
	contentStart := i

	for i < len(b) {
		c := b[i]
		switch {
		case c == '\'':
			// count consecutive quotes
			j := i
			for j < len(b) && b[j] == '\'' {
				j++
			}
			n := j - i
			if n >= 3 {
				if n > 5 {
					return nil, nil, nil, NewParserError(b[i:j], "too many quotes at the end of a multiline literal string")
				}
				// n-3 quotes belong to the content; the last 3 close the
				// string.
				contentEnd := i + n - 3
				return b[:j], b[contentStart:contentEnd], b[j:], nil
			}
			i = j
		case c >= 0x20 && c < 0x7f:
			i++
		case c == '\t' || c == '\n':
			i++
		case c == '\r':
			if i+1 < len(b) && b[i+1] == '\n' {
				i += 2
				continue
			}
			return nil, nil, nil, NewParserError(b[i:i+1], "carriage returns must be followed by a newline character")
		case c < 0x80:
			return nil, nil, nil, NewParserError(b[i:i+1], "multiline literal strings cannot have control characters")
		default:
			var ok bool
			i, ok = scanUtf8Run(b, i)
			if !ok {
				return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in multiline literal string")
			}
		}
	}
	return nil, nil, nil, NewParserError(b[len(b):], "multiline literal string not terminated by '''")
}

// parseBasicString parses a single-line basic string, starting at the opening
// quote. The value is a subslice of the input if the string contains no
// escape sequence, or a new allocation otherwise.
func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) {
	i := 1
	// First pass: handle strings without escape sequences without allocating.
	for {
		for i+8 <= len(b) {
			x := binary.LittleEndian.Uint64(b[i:])
			if (x&msb)|hasByteBelow(x, 0x20)|hasByteEqual(x, '"')|hasByteEqual(x, '\\')|hasByteEqual(x, 0x7f) != 0 {
				break
			}
			i += 8
		}
		if i >= len(b) {
			return nil, nil, nil, NewParserError(b[len(b):], "unterminated basic string")
		}

		c := b[i]
		switch {
		case c == '"':
			return b[:i+1], b[1:i], b[i+1:], nil
		case c == '\\':
			// switch to the escape-aware parser, copying what has been
			// scanned so far
			return p.parseBasicStringEscaped(b, i)
		case c >= 0x20 && c < 0x7f:
			i++
		case c == '\t':
			i++
		case c == '\n' || c == '\r':
			return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have new lines")
		case c < 0x80:
			return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have control characters")
		default:
			var ok bool
			i, ok = scanUtf8Run(b, i)
			if !ok {
				return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in basic string")
			}
		}
	}
}

// findBasicStringEnd returns the index of the quote closing a basic string,
// or -1 if the string is not terminated. i is the index of the first
// character after the opening quote. It does not validate the content: it
// only skips over escape sequences so that escaped quotes do not terminate
// the string.
func findBasicStringEnd(b []byte, i int) int {
	for i < len(b) {
		switch b[i] {
		case '"':
			return i
		case '\\':
			i += 2
		default:
			i++
		}
	}
	return -1
}

// parseBasicStringEscaped continues parsing a basic string that contains
// escape sequences. i is the index of the first backslash.
func (p *Parser) parseBasicStringEscaped(b []byte, i int) ([]byte, []byte, []byte, error) {
	// Escape sequences only ever shrink, so the content length before
	// unescaping is enough to never reallocate.
	bufCap := len(b) - 1
	if end := findBasicStringEnd(b, i); end >= 0 {
		bufCap = end - 1
	}
	buf := make([]byte, i-1, bufCap)
	copy(buf, b[1:i])

	for i < len(b) {
		c := b[i]
		switch {
		case c == '"':
			return b[:i+1], buf, b[i+1:], nil
		case c == '\\':
			i++
			if i >= len(b) {
				return nil, nil, nil, NewParserError(b[i-1:], `need a character after \`)
			}
			var err error
			buf, i, err = unescape(buf, b, i)
			if err != nil {
				return nil, nil, nil, err
			}
		case c >= 0x20 && c < 0x7f:
			buf = append(buf, c)
			i++
		case c == '\t':
			buf = append(buf, c)
			i++
		case c == '\n' || c == '\r':
			return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have new lines")
		case c < 0x80:
			return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have control characters")
		default:
			j, ok := scanUtf8Run(b, i)
			if !ok {
				return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in basic string")
			}
			buf = append(buf, b[i:j]...)
			i = j
		}
	}
	return nil, nil, nil, NewParserError(b[len(b):], "unterminated basic string")
}

// unescape processes one escape sequence. i is the index of the character
// right after the backslash. It returns the updated buffer and index.
func unescape(buf []byte, b []byte, i int) ([]byte, int, error) {
	c := b[i]
	switch c {
	case '"':
		return append(buf, '"'), i + 1, nil
	case '\\':
		return append(buf, '\\'), i + 1, nil
	case 'b':
		return append(buf, '\b'), i + 1, nil
	case 'f':
		return append(buf, '\f'), i + 1, nil
	case 'n':
		return append(buf, '\n'), i + 1, nil
	case 'r':
		return append(buf, '\r'), i + 1, nil
	case 't':
		return append(buf, '\t'), i + 1, nil
	case 'e':
		// TOML v1.1.0: \e is the escape character (U+001B).
		return append(buf, 0x1B), i + 1, nil
	case 'x':
		// TOML v1.1.0: \xHH is a two-digit hexadecimal code point.
		return unescapeUnicode(buf, b, i+1, 2)
	case 'u':
		return unescapeUnicode(buf, b, i+1, 4)
	case 'U':
		return unescapeUnicode(buf, b, i+1, 8)
	default:
		return nil, 0, NewParserError(b[i-1:i+1], "invalid escape character %#U", c)
	}
}

// unescapeUnicode handles \uXXXX and \UXXXXXXXX escape sequences. i is the
// index of the first hex digit.
func unescapeUnicode(buf []byte, b []byte, i int, n int) ([]byte, int, error) {
	if i+n > len(b) {
		return nil, 0, NewParserError(b[i-2:], "unicode escape sequence is too short")
	}
	var r uint32
	for k := 0; k < n; k++ {
		c := b[i+k]
		var d uint32
		switch {
		case c >= '0' && c <= '9':
			d = uint32(c - '0')
		case c >= 'a' && c <= 'f':
			d = uint32(c-'a') + 10
		case c >= 'A' && c <= 'F':
			d = uint32(c-'A') + 10
		default:
			return nil, 0, NewParserError(b[i+k:i+k+1], "invalid hexadecimal digit in unicode escape sequence")
		}
		r = r<<4 | d
	}
	if r > utf8.MaxRune || (r >= 0xD800 && r <= 0xDFFF) {
		return nil, 0, NewParserError(b[i-2:i+n], "escape sequence is not a valid unicode code point")
	}
	return utf8.AppendRune(buf, rune(r)), i + n, nil
}

// parseMultilineBasicString parses a multi-line basic string, starting at the
// opening triple quote.
func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) {
	i := 3
	// trim the newline right after the opening delimiter
	if i < len(b) && b[i] == '\n' {
		i++
	} else if i+1 < len(b) && b[i] == '\r' && b[i+1] == '\n' {
		i += 2
	}
	contentStart := i

	// First pass without allocating, until an escape sequence is found.
	for i < len(b) {
		c := b[i]
		switch {
		case c == '"':
			j := i
			for j < len(b) && b[j] == '"' {
				j++
			}
			n := j - i
			if n >= 3 {
				if n > 5 {
					return nil, nil, nil, NewParserError(b[i:j], "too many quotes at the end of a multiline basic string")
				}
				contentEnd := i + n - 3
				return b[:j], b[contentStart:contentEnd], b[j:], nil
			}
			i = j
		case c == '\\':
			return p.parseMultilineBasicStringEscaped(b, contentStart, i)
		case c >= 0x20 && c < 0x7f:
			i++
		case c == '\t' || c == '\n':
			i++
		case c == '\r':
			if i+1 < len(b) && b[i+1] == '\n' {
				i += 2
				continue
			}
			return nil, nil, nil, NewParserError(b[i:i+1], "carriage returns must be followed by a newline character")
		case c < 0x80:
			return nil, nil, nil, NewParserError(b[i:i+1], "multiline basic strings cannot have control characters")
		default:
			var ok bool
			i, ok = scanUtf8Run(b, i)
			if !ok {
				return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in multiline basic string")
			}
		}
	}
	return nil, nil, nil, NewParserError(b[len(b):], `multiline basic string not terminated by """`)
}

// findMultilineBasicStringEnd returns the index of the first quote of the
// run of quotes closing a multi-line basic string, or -1 if the string is
// not terminated. It does not validate the content: it only skips over
// escape sequences so that escaped quotes do not terminate the string.
func findMultilineBasicStringEnd(b []byte, i int) int {
	for {
		j := bytes.IndexAny(b[i:], "\"\\")
		if j < 0 {
			return -1
		}
		i += j
		if b[i] == '\\' {
			i += 2
			if i > len(b) {
				return -1
			}
			continue
		}
		j = i
		for j < len(b) && b[j] == '"' {
			j++
		}
		if j-i >= 3 {
			return i
		}
		i = j
	}
}

// parseMultilineBasicStringEscaped continues parsing a multi-line basic
// string that contains escape sequences. i is the index of the first
// backslash; content starts at contentStart.
func (p *Parser) parseMultilineBasicStringEscaped(b []byte, contentStart, i int) ([]byte, []byte, []byte, error) {
	// Escape sequences only ever shrink, so the content length before
	// unescaping is enough to never reallocate. The closing run of quotes
	// can lend up to two quotes to the content.
	bufCap := len(b) - contentStart
	if end := findMultilineBasicStringEnd(b, i); end >= 0 {
		bufCap = end + 2 - contentStart
	}
	buf := make([]byte, i-contentStart, bufCap)
	copy(buf, b[contentStart:i])

	for i < len(b) {
		c := b[i]
		switch {
		case c == '"':
			j := i
			for j < len(b) && b[j] == '"' {
				j++
			}
			n := j - i
			if n >= 3 {
				if n > 5 {
					return nil, nil, nil, NewParserError(b[i:j], "too many quotes at the end of a multiline basic string")
				}
				buf = append(buf, b[i:i+n-3]...)
				return b[:j], buf, b[j:], nil
			}
			buf = append(buf, b[i:j]...)
			i = j
		case c == '\\':
			i++
			if i >= len(b) {
				return nil, nil, nil, NewParserError(b[i-1:], `need a character after \`)
			}
			// Escaped newline: backslash, optional whitespace, newline,
			// then all following whitespace and newlines are trimmed.
			if b[i] == ' ' || b[i] == '\t' || b[i] == '\n' || b[i] == '\r' {
				j := i
				for j < len(b) && (b[j] == ' ' || b[j] == '\t') {
					j++
				}
				if j < len(b) && b[j] == '\r' {
					j++
				}
				if j >= len(b) || b[j] != '\n' {
					return nil, nil, nil, NewParserError(b[i-1:i+1], "invalid escape character %#U", b[i])
				}
				j++
				for j < len(b) && (b[j] == ' ' || b[j] == '\t' || b[j] == '\n' || b[j] == '\r') {
					// note: a lone \r not followed by \n will be caught on
					// the next iteration of the outer loop.
					if b[j] == '\r' {
						if j+1 >= len(b) || b[j+1] != '\n' {
							break
						}
						j++
					}
					j++
				}
				i = j
				continue
			}
			var err error
			buf, i, err = unescape(buf, b, i)
			if err != nil {
				return nil, nil, nil, err
			}
		case c >= 0x20 && c < 0x7f:
			buf = append(buf, c)
			i++
		case c == '\t' || c == '\n':
			buf = append(buf, c)
			i++
		case c == '\r':
			if i+1 < len(b) && b[i+1] == '\n' {
				buf = append(buf, '\r', '\n')
				i += 2
				continue
			}
			return nil, nil, nil, NewParserError(b[i:i+1], "carriage returns must be followed by a newline character")
		case c < 0x80:
			return nil, nil, nil, NewParserError(b[i:i+1], "multiline basic strings cannot have control characters")
		default:
			j, ok := scanUtf8Run(b, i)
			if !ok {
				return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in multiline basic string")
			}
			buf = append(buf, b[i:j]...)
			i = j
		}
	}
	return nil, nil, nil, NewParserError(b[len(b):], `multiline basic string not terminated by """`)
}

// scanUtf8Run consumes a run of valid non-ASCII UTF-8 runes starting at
// b[i]. It returns the index of the first byte after the run, and whether
// the run was entirely valid. Processing whole runs amortizes the cost of
// the call compared to validating rune by rune.
func scanUtf8Run(b []byte, i int) (int, bool) {
	for i < len(b) {
		c := b[i]
		switch {
		case c < 0x80:
			return i, true
		case c < 0xC2:
			return i, false
		case c < 0xE0:
			if i+1 >= len(b) || b[i+1]&0xC0 != 0x80 {
				return i, false
			}
			i += 2
		case c < 0xF0:
			if i+2 >= len(b) || b[i+2]&0xC0 != 0x80 {
				return i, false
			}
			b1 := b[i+1]
			switch c {
			case 0xE0:
				if b1 < 0xA0 || b1 > 0xBF {
					return i, false
				}
			case 0xED:
				// exclude surrogates
				if b1 < 0x80 || b1 > 0x9F {
					return i, false
				}
			default:
				if b1&0xC0 != 0x80 {
					return i, false
				}
			}
			i += 3
		case c < 0xF5:
			if i+3 >= len(b) || b[i+2]&0xC0 != 0x80 || b[i+3]&0xC0 != 0x80 {
				return i, false
			}
			b1 := b[i+1]
			switch c {
			case 0xF0:
				if b1 < 0x90 || b1 > 0xBF {
					return i, false
				}
			case 0xF4:
				if b1 < 0x80 || b1 > 0x8F {
					return i, false
				}
			default:
				if b1&0xC0 != 0x80 {
					return i, false
				}
			}
			i += 4
		default:
			return i, false
		}
	}
	return i, true
}
