package run

import (
	"encoding/json"
	"errors"
	"fmt"
	"unicode/utf8"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/workflow/plan"
	"github.com/tidbcloud/AgentTeam/internal/workflow/runlimit"
)

const (
	workItemResourceLimitFailureCode = "work_item.resource_limit_exceeded"
	workItemResourceLimitMessage     = "WorkItem exceeded the durable payload or state budget"
)

type fanOutResourceLimitError struct {
	path    string
	detail  string
	maximum int
}

func (failure *fanOutResourceLimitError) Error() string {
	limitDescription := "fan-out " + failure.detail + " limit"
	return fmt.Sprintf("%s exceeds the %s (%d)", failure.path, limitDescription, failure.maximum)
}

// validateFanOutPayloadBudget counts the JSON wire representation without
// materializing it. In particular, a dependency view can point to one shared
// predecessor Value from many envelopes; the counter stops at the first
// over-budget occurrence instead of deep-cloning the complete expanded DAG.
func validateFanOutPayloadBudget(path string, value any) error {
	counter := fanOutJSONCounter{
		path:      path,
		maxBytes:  runlimit.MaxRunPayloadBytes,
		maxDepth:  runlimit.MaxRunPayloadJSONNesting,
		maxValues: runlimit.MaxRunPayloadJSONValues,
	}
	return counter.count(value, 0)
}

// validateFanOutIntentBudget mirrors the dynamic portion of the durable
// orchestrator EffectRequest. A send_message request persists Intent and repeats
// the rendered message fields in MessageRequest, most importantly Text as
// Content. Projecting only State would therefore admit a 2+ MiB rendered text
// that the 4 MiB effect-request boundary rejects. Static Run/workspace/plan
// identity fields are bounded elsewhere; an 8 KiB reserve covers their keys
// and worst-case JSON escaping without importing orchestrator back into the pure
// reducer. The durable columns cap workspace/Plan/Run/identity IDs at 64
// characters, node IDs at 128, and the workflow-name snapshot at 255. Even if
// every character needs a six-byte JSON escape, the omitted envelope stays
// below this reserve.
func validateFanOutIntentBudget(path string, intent Intent) error {
	type projectedMessage struct {
		TargetRef           string                     `json:"target,omitempty"`
		Scope               any                        `json:"scope,omitempty"`
		Content             string                     `json:"content"`
		Callback            bool                       `json:"callback"`
		ResponderAgentRef   string                     `json:"responderAgentRef,omitempty"`
		ResponderCandidates []string                   `json:"responderCandidates,omitempty"`
		SelectOneStrategy   plan.SelectOneStrategy     `json:"selectOneStrategy,omitempty"`
		StartThread         bool                       `json:"startThread,omitempty"`
		WorkScopeMode       WorkScopeMode              `json:"workScopeMode,omitempty"`
		WorkScope           any                        `json:"workScope,omitempty"`
		RelatedScopes       []domain.MessageScopeValue `json:"relatedScopes,omitempty"`
		TaskWatchHandoff    *TaskWatchHandoff          `json:"taskWatchHandoff,omitempty"`
	}
	type projectedRequest struct {
		Intent  Intent            `json:"intent"`
		Message *projectedMessage `json:"message,omitempty"`
	}
	request := projectedRequest{Intent: intent}
	identityReserveBytes := 0
	identityReserveValues := 0
	if intent.Kind == IntentSendMessage {
		request.Message = &projectedMessage{
			TargetRef:           intent.Target,
			Scope:               intent.Scope,
			Content:             intent.Text,
			Callback:            intent.Callback,
			ResponderAgentRef:   intent.Responder,
			ResponderCandidates: append([]string(nil), intent.ResponderCandidates...),
			SelectOneStrategy:   intent.SelectOneStrategy,
			StartThread:         intent.StartThread,
			WorkScopeMode:       intent.WorkScopeMode,
			WorkScope:           intent.WorkScope,
			RelatedScopes:       intent.RelatedScopes,
			TaskWatchHandoff:    intent.TaskWatchHandoff,
		}
		const effectIdentityReserve = 8 << 10
		identityReserveBytes = effectIdentityReserve
		identityReserveValues = 64
	}
	payload, err := json.Marshal(request)
	if err != nil {
		return fmt.Errorf("encode %s: %w", path, err)
	}
	if _, err := measureDeterministicJSONBudget(
		payload,
		runlimit.MaxRunPayloadBytes-identityReserveBytes,
		runlimit.MaxRunPayloadJSONNesting,
		runlimit.MaxRunPayloadJSONValues-identityReserveValues,
	); err != nil {
		return &fanOutResourceLimitError{
			path: path, detail: "durable effect request", maximum: runlimit.MaxRunPayloadBytes,
		}
	}
	return nil
}

func validateFanOutIntentRange(path string, intents []Intent, first int) error {
	if first < 0 {
		first = 0
	}
	for index := first; index < len(intents); index++ {
		if err := validateFanOutIntentBudget(
			fmt.Sprintf("%s intent[%d]", path, index),
			intents[index],
		); err != nil {
			return err
		}
	}
	return nil
}

func validateFanOutHardStateBudget(path string, state *State) error {
	_, err := measureFanOutStateExact(path, state)
	return err
}

// validateFanOutStateExact is the pure reducer mirror of the Store boundary:
// the deterministic snapshot must satisfy the same byte, nesting, and value-count
// budgets. Going through the shared deterministic encoder also catches number
// expansion (for example 1e100) that a raw json.Marshal length check misses.
func validateFanOutStateExact(path string, state *State) error {
	_, err := measureFanOutStateExact(path, state)
	return err
}

type deterministicJSONMeasurement struct {
	bytes, values, maxDepth int
}

func measureFanOutStateExact(path string, state *State) (deterministicJSONMeasurement, error) {
	payload, err := json.Marshal(state)
	if err != nil {
		return deterministicJSONMeasurement{}, fmt.Errorf("encode %s: %w", path, err)
	}
	measurement, err := measureDeterministicJSONBudget(
		payload,
		runlimit.MaxRunStateBytes,
		runlimit.MaxRunStateJSONNesting,
		runlimit.MaxRunStateJSONValues,
	)
	if err != nil {
		return deterministicJSONMeasurement{}, &fanOutResourceLimitError{
			path: path, detail: "canonical Run state", maximum: runlimit.MaxRunStateBytes,
		}
	}
	return measurement, nil
}

// validateDeterministicJSONBudget measures the exact bytes the Store's
// deterministic encoder will emit for json.Marshal-produced reducer state. Such input
// is already compact, has unique keys, deterministic string escaping, and stable
// key ordering, so only Store number normalization can change its length. A
// streaming traversal counts that delta plus the exact Store depth/value
// semantics without materializing a second map tree and deterministic byte slice.
func validateDeterministicJSONBudget(payload []byte, maxBytes, maxNesting, maxValues int) error {
	_, err := measureDeterministicJSONBudget(payload, maxBytes, maxNesting, maxValues)
	return err
}

type deterministicJSONBudgetScanner struct {
	payload                       []byte
	index                         int
	maxBytes, maxDepth, maxValues int
	measurement                   deterministicJSONMeasurement
}

func measureDeterministicJSONBudget(
	payload []byte,
	maxBytes, maxNesting, maxValues int,
) (deterministicJSONMeasurement, error) {
	if len(payload) > maxBytes {
		return deterministicJSONMeasurement{}, fmt.Errorf("JSON exceeds %d bytes", maxBytes)
	}
	scanner := deterministicJSONBudgetScanner{
		payload:     payload,
		maxBytes:    maxBytes,
		maxDepth:    maxNesting,
		maxValues:   maxValues,
		measurement: deterministicJSONMeasurement{bytes: len(payload)},
	}
	if err := scanner.scanValue(0); err != nil {
		return deterministicJSONMeasurement{}, err
	}
	scanner.skipWhitespace()
	if scanner.index != len(payload) {
		return deterministicJSONMeasurement{}, fmt.Errorf("JSON has trailing data")
	}
	if scanner.measurement.bytes > maxBytes {
		return deterministicJSONMeasurement{}, fmt.Errorf("canonical JSON exceeds %d bytes", maxBytes)
	}
	return scanner.measurement, nil
}

func (scanner *deterministicJSONBudgetScanner) skipWhitespace() {
	for scanner.index < len(scanner.payload) {
		switch scanner.payload[scanner.index] {
		case ' ', '\t', '\n', '\r':
			scanner.index++
		default:
			return
		}
	}
}

func (scanner *deterministicJSONBudgetScanner) scanString() error {
	if scanner.index >= len(scanner.payload) || scanner.payload[scanner.index] != '"' {
		return fmt.Errorf("expected JSON string")
	}
	scanner.index++
	for scanner.index < len(scanner.payload) {
		switch scanner.payload[scanner.index] {
		case '"':
			scanner.index++
			return nil
		case '\\':
			scanner.index += 2
		default:
			scanner.index++
		}
	}
	return fmt.Errorf("unterminated JSON string")
}

func (scanner *deterministicJSONBudgetScanner) scanValue(depth int) error {
	scanner.skipWhitespace()
	if depth > scanner.maxDepth {
		return fmt.Errorf("JSON nesting exceeds %d", scanner.maxDepth)
	}
	if depth > scanner.measurement.maxDepth {
		scanner.measurement.maxDepth = depth
	}
	scanner.measurement.values++
	if scanner.measurement.values > scanner.maxValues {
		return fmt.Errorf("JSON value count exceeds %d", scanner.maxValues)
	}
	if scanner.index >= len(scanner.payload) {
		return fmt.Errorf("unexpected end of JSON")
	}
	switch scanner.payload[scanner.index] {
	case '{':
		return scanner.scanObject(depth)
	case '[':
		return scanner.scanArray(depth)
	case '"':
		return scanner.scanString()
	case 't':
		return scanner.scanLiteral("true")
	case 'f':
		return scanner.scanLiteral("false")
	case 'n':
		return scanner.scanLiteral("null")
	default:
		return scanner.scanNumber()
	}
}

func (scanner *deterministicJSONBudgetScanner) scanObject(depth int) error {
	scanner.index++
	scanner.skipWhitespace()
	if scanner.index < len(scanner.payload) && scanner.payload[scanner.index] == '}' {
		scanner.index++
		return nil
	}
	for {
		if err := scanner.scanString(); err != nil {
			return err
		}
		scanner.skipWhitespace()
		if scanner.index >= len(scanner.payload) || scanner.payload[scanner.index] != ':' {
			return fmt.Errorf("object key has no value")
		}
		scanner.index++
		if err := scanner.scanValue(depth + 1); err != nil {
			return err
		}
		scanner.skipWhitespace()
		if scanner.index < len(scanner.payload) && scanner.payload[scanner.index] == '}' {
			scanner.index++
			return nil
		}
		if scanner.index >= len(scanner.payload) || scanner.payload[scanner.index] != ',' {
			return fmt.Errorf("unterminated object")
		}
		scanner.index++
		scanner.skipWhitespace()
	}
}

func (scanner *deterministicJSONBudgetScanner) scanArray(depth int) error {
	scanner.index++
	scanner.skipWhitespace()
	if scanner.index < len(scanner.payload) && scanner.payload[scanner.index] == ']' {
		scanner.index++
		return nil
	}
	for {
		if err := scanner.scanValue(depth + 1); err != nil {
			return err
		}
		scanner.skipWhitespace()
		if scanner.index < len(scanner.payload) && scanner.payload[scanner.index] == ']' {
			scanner.index++
			return nil
		}
		if scanner.index >= len(scanner.payload) || scanner.payload[scanner.index] != ',' {
			return fmt.Errorf("unterminated array")
		}
		scanner.index++
	}
}

func (scanner *deterministicJSONBudgetScanner) scanLiteral(literal string) error {
	end := scanner.index + len(literal)
	if end > len(scanner.payload) || string(scanner.payload[scanner.index:end]) != literal {
		return fmt.Errorf("invalid JSON literal")
	}
	scanner.index = end
	return nil
}

func (scanner *deterministicJSONBudgetScanner) scanNumber() error {
	start := scanner.index
	for scanner.index < len(scanner.payload) {
		switch scanner.payload[scanner.index] {
		case ',', '}', ']', ' ', '\t', '\n', '\r':
			goto numberDone
		default:
			scanner.index++
		}
	}
numberDone:
	if scanner.index == start {
		return fmt.Errorf("invalid JSON value")
	}
	source := string(scanner.payload[start:scanner.index])
	normalizedNum, err := runlimit.NormalizedNumberString(source)
	if err != nil {
		return err
	}
	scanner.measurement.bytes += len(normalizedNum) - len(source)
	if scanner.measurement.bytes > scanner.maxBytes {
		return fmt.Errorf("canonical JSON exceeds %d bytes", scanner.maxBytes)
	}
	return nil
}

// validateFanOutStateAdmission checks both the candidate and an exact compact
// fallback projection. The fallback converts every still-live item (plus an
// optionally forced just-succeeded item) to the fixed resource failure. This
// guarantees room for deterministic settlement metadata without relying on a
// guessed byte reserve that can be defeated by dense long-key DAGs.
func (kernel *Kernel) validateFanOutStateAdmission(path string, state *State, forceItemID string) error {
	measurement, err := measureFanOutStateExact(path, state)
	if err != nil {
		return err
	}
	addedBytes, addedValues := fanOutFallbackMaximumAdditions(state, forceItemID)
	// Every other fallback mutation clears payloads or shortens metadata. When
	// the actual state has enough room for this complete fixed-size addition
	// bound, the exact fallback is proven to fit without another 32 MiB encode.
	if measurement.bytes <= runlimit.MaxRunStateBytes-addedBytes &&
		measurement.values <= runlimit.MaxRunStateJSONValues-addedValues &&
		measurement.maxDepth <= runlimit.MaxRunStateJSONNesting {
		return nil
	}
	return kernel.validateFanOutFallbackState(path+" fallback", state, forceItemID)
}

func fanOutFallbackMaximumAdditions(state *State, forceItemID string) (bytes, values int) {
	if state == nil {
		return 0, 0
	}
	type target struct{ scopeID, itemID string }
	targets := make([]target, 0)
	for scopeIndex := range state.Scopes {
		scope := &state.Scopes[scopeIndex]
		if scope.FanOut == nil || scope.Status != ScopeActive {
			continue
		}
		for itemIndex := range scope.FanOut.Items {
			item := &scope.FanOut.Items[itemIndex]
			if item.ID != forceItemID && item.Status.Terminal() {
				continue
			}
			targets = append(targets, target{scopeID: scope.ID, itemID: item.ID})
			bytes += len(`,"failure":{"code":"work_item.resource_limit_exceeded","message":"WorkItem exceeded the durable payload or state budget"}`)
			values += 3 // Failure object, code, message.
		}
	}
	const activationFailureBytes = len(`,"failure":{"code":"work_item.resource_limit_exceeded"}`) + 3
	const nestedItemFailureBytes = len(`,"failure":{"code":"work_item.resource_limit_exceeded"}`) + 1
	for _, fallback := range targets {
		for activationIndex := range state.Activations {
			activation := &state.Activations[activationIndex]
			if (activation.Status == ActivationReady || activation.Status == ActivationWaiting) &&
				activationBelongsToFanOutItem(activation, fallback.scopeID, fallback.itemID) {
				bytes += activationFailureBytes
				values += 2 // Failure object and code.
			}
		}
		for scopeIndex := range state.Scopes {
			nested := &state.Scopes[scopeIndex]
			if nested.ID == fallback.scopeID || nested.Status != ScopeActive ||
				!scopeLineageContainsFanOutItem(nested.ParentLineage, fallback.scopeID, fallback.itemID) {
				continue
			}
			bytes += 2 // active -> canceled
			if nested.FanOut == nil {
				continue
			}
			for itemIndex := range nested.FanOut.Items {
				status := nested.FanOut.Items[itemIndex].Status
				if status == WorkItemPending || status == WorkItemRunning {
					bytes += nestedItemFailureBytes
					values += 2
				}
			}
		}
	}
	return bytes, values
}

func (kernel *Kernel) validateFanOutFallbackState(path string, state *State, forceItemID string) error {
	// Only WorkItem outcome metadata changes in this projection. Copy those
	// envelopes structurally while sharing immutable activation inputs/outputs
	// and item Values; a full State.Clone here would deep-copy up to 32 MiB for
	// every admission attempt and turn bounded fan-out into quadratic memory
	// traffic.
	fallback := cloneFanOutStateForFallbackBudget(state)
	type compactTarget struct{ scopeID, itemID string }
	targets := make([]compactTarget, 0)
	for scopeIndex := range fallback.Scopes {
		scope := &fallback.Scopes[scopeIndex]
		if scope.FanOut == nil || scope.Status != ScopeActive {
			continue
		}
		for itemIndex := range scope.FanOut.Items {
			item := &scope.FanOut.Items[itemIndex]
			if item.ID != forceItemID && item.Status.Terminal() {
				continue
			}
			targets = append(targets, compactTarget{scopeID: scope.ID, itemID: item.ID})
		}
	}
	if len(targets) == 0 {
		return nil
	}
	for _, target := range targets {
		scope, err := fallback.scope(target.scopeID)
		if err != nil || scope.FanOut == nil {
			return fmt.Errorf("%s fallback scope %q is invalid", path, target.scopeID)
		}
		kernel.compactFanOutItemResourceState(
			fallback,
			target.scopeID,
			fanOutItemByID(scope, target.itemID),
		)
	}
	return validateFanOutStateExact(path, fallback)
}

func cloneFanOutStateForFallbackBudget(state *State) *State {
	if state == nil {
		return nil
	}
	copyState := *state
	copyState.Activations = append([]Activation(nil), state.Activations...)
	copyState.Scopes = append([]Scope(nil), state.Scopes...)
	for index := range copyState.Scopes {
		if state.Scopes[index].Parallel != nil {
			parallel := *state.Scopes[index].Parallel
			parallel.Branches = append([]BranchState(nil), state.Scopes[index].Parallel.Branches...)
			copyState.Scopes[index].Parallel = &parallel
		}
		if state.Scopes[index].Loop != nil {
			loop := *state.Scopes[index].Loop
			copyState.Scopes[index].Loop = &loop
		}
		if state.Scopes[index].FanOut != nil {
			fanOut := *state.Scopes[index].FanOut
			fanOut.Items = append([]WorkItemState(nil), state.Scopes[index].FanOut.Items...)
			copyState.Scopes[index].FanOut = &fanOut
		}
	}
	return &copyState
}

func projectCompactFanOutResourceFailure(item *WorkItemState) {
	if item == nil {
		return
	}
	item.Status = WorkItemFailed
	item.Value = nil
	item.BlockedBy = []string{}
	item.Failure = &Failure{
		Code:    workItemResourceLimitFailureCode,
		Message: workItemResourceLimitMessage,
		Stage:   "work_item",
		Problem: FailureProblem(workItemResourceLimitFailureCode),
		Hint:    FailureHint(workItemResourceLimitFailureCode),
	}
}

// validateFanOutCompletionBudget admits a callback/effect result before
// finishPortsInPlace writes activation.Output. This is essential for many
// concurrent root WorkItems: aggregate terminal result admission alone cannot
// prevent one full callback output from accumulating on every source
// activation.
func (kernel *Kernel) validateFanOutCompletionBudget(
	state *State,
	activation *Activation,
	port string,
	output Values,
) (bool, error) {
	if state == nil || activation == nil {
		return false, nil
	}
	reference := activeFailureScopeRef(state, activation.Lineage)
	inFanOut := reference != nil && reference.Kind == plan.ArtifactScopeFanOut
	if !inFanOut && !activation.FanOutResourceCausal {
		return false, nil
	}
	if err := validateFanOutPayloadBudget("fan_out completion output", output); err != nil {
		return true, err
	}
	savedStatus := activation.Status
	savedOutput := activation.Output
	savedPorts := activation.SelectedPorts
	activation.Status = ActivationSucceeded
	activation.Output = output
	activation.SelectedPorts = []string{port}
	var err error
	if inFanOut {
		itemID := ""
		for index := len(activation.Lineage) - 1; index >= 0; index-- {
			if activation.Lineage[index].Kind == plan.ArtifactScopeFanOut {
				itemID = activation.Lineage[index].ItemID
				break
			}
		}
		err = kernel.validateFanOutStateAdmission("fan_out completion", state, itemID)
	} else {
		err = validateFanOutHardStateBudget("fan_out causal completion", state)
	}
	activation.Status = savedStatus
	activation.Output = savedOutput
	activation.SelectedPorts = savedPorts
	return true, err
}

func isFanOutResourceLimit(err error) bool {
	var failure *fanOutResourceLimitError
	return errors.As(err, &failure)
}

type fanOutJSONCounter struct {
	path string
	// bytes is the normalized Store representation while rawBytes is the compact
	// encoding/json representation presented to the Store. The durable boundary
	// rejects either representation above maxBytes: it checks raw bytes before
	// normalizing numbers, then checks the deterministic result again.
	bytes, rawBytes    int
	values             int
	maxBytes, maxDepth int
	maxValues          int
}

func (counter *fanOutJSONCounter) addBytes(size int) error {
	if err := counter.addRawBytes(size); err != nil {
		return err
	}
	return counter.addDeterministicBytes(size)
}

func (counter *fanOutJSONCounter) addRawBytes(size int) error {
	if size < 0 || counter.rawBytes > counter.maxBytes-size {
		return &fanOutResourceLimitError{path: counter.path, detail: "raw payload byte", maximum: counter.maxBytes}
	}
	counter.rawBytes += size
	return nil
}

func (counter *fanOutJSONCounter) addDeterministicBytes(size int) error {
	if size < 0 || counter.bytes > counter.maxBytes-size {
		return &fanOutResourceLimitError{path: counter.path, detail: "payload byte", maximum: counter.maxBytes}
	}
	counter.bytes += size
	return nil
}

func (counter *fanOutJSONCounter) count(value any, depth int) error {
	if depth > counter.maxDepth {
		return &fanOutResourceLimitError{path: counter.path, detail: "JSON nesting", maximum: counter.maxDepth}
	}
	counter.values++
	if counter.values > counter.maxValues {
		return &fanOutResourceLimitError{path: counter.path, detail: "JSON value count", maximum: counter.maxValues}
	}
	switch typed := value.(type) {
	case nil:
		return counter.addBytes(4)
	case bool:
		if typed {
			return counter.addBytes(4)
		}
		return counter.addBytes(5)
	case string:
		return counter.countString(typed)
	case json.Number:
		return counter.countNumber(typed.String())
	case Values:
		return counter.countObject(map[string]any(typed), depth)
	case map[string]any:
		return counter.countObject(typed, depth)
	case []any:
		if err := counter.addBytes(2); err != nil {
			return err
		}
		for index, item := range typed {
			if index != 0 {
				if err := counter.addBytes(1); err != nil {
					return err
				}
			}
			if err := counter.count(item, depth+1); err != nil {
				return err
			}
		}
		return nil
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
		encoded, err := json.Marshal(typed)
		if err != nil {
			return fmt.Errorf("%s contains an invalid number: %w", counter.path, err)
		}
		return counter.countNumber(string(encoded))
	default:
		return fmt.Errorf("%s contains unsupported JSON value %T", counter.path, value)
	}
}

func (counter *fanOutJSONCounter) countNumber(source string) error {
	// Store normalization has an authoritative raw-input gate before number
	// normalization. A spelling such as 1.000... may normalize to one byte but
	// must still be rejected when its compact raw JSON exceeds the payload cap.
	if err := counter.addRawBytes(len(source)); err != nil {
		return err
	}
	normalizedNum, err := runlimit.NormalizedNumberString(source)
	if err != nil {
		return &fanOutResourceLimitError{
			path: counter.path, detail: "canonical payload number", maximum: counter.maxBytes,
		}
	}
	return counter.addDeterministicBytes(len(normalizedNum))
}

func (counter *fanOutJSONCounter) countObject(object map[string]any, depth int) error {
	if err := counter.addBytes(2); err != nil {
		return err
	}
	index := 0
	for key, value := range object {
		if index != 0 {
			if err := counter.addBytes(1); err != nil {
				return err
			}
		}
		index++
		if err := counter.countString(key); err != nil {
			return err
		}
		if err := counter.addBytes(1); err != nil {
			return err
		}
		if err := counter.count(value, depth+1); err != nil {
			return err
		}
	}
	return nil
}

// countString matches encoding/json's default HTML-escaping byte count.
func (counter *fanOutJSONCounter) countString(value string) error {
	if err := counter.addBytes(2); err != nil { // quotes
		return err
	}
	for index := 0; index < len(value); {
		character := value[index]
		if character < utf8.RuneSelf {
			index++
			switch character {
			case '\\', '"', '\b', '\f', '\n', '\r', '\t':
				if err := counter.addBytes(2); err != nil {
					return err
				}
			case '<', '>', '&':
				if err := counter.addBytes(6); err != nil {
					return err
				}
			default:
				size := 1
				if character < 0x20 {
					size = 6
				}
				if err := counter.addBytes(size); err != nil {
					return err
				}
			}
			continue
		}
		runeValue, consumed := utf8.DecodeRuneInString(value[index:])
		emitted := consumed
		if runeValue == utf8.RuneError && consumed == 1 {
			emitted = 6 // encoding/json emits \ufffd for invalid UTF-8.
		} else if runeValue == '\u2028' || runeValue == '\u2029' {
			emitted = 6
		}
		if err := counter.addBytes(emitted); err != nil {
			return err
		}
		index += consumed
	}
	return nil
}
