package runner

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"unicode/utf8"
)

const maxJSONDepth = 128

// DecodeUniqueJSONDocument decodes one bounded, finite JSON value while
// rejecting invalid UTF-8, trailing values, and duplicate keys at any depth.
// It also returns the original document in compact form.
func DecodeUniqueJSONDocument(
	raw json.RawMessage,
) (json.RawMessage, any, error) {
	if len(raw) == 0 || len(raw) > MaxResponseBytes || !utf8.Valid(raw) {
		return nil, nil, errors.New("JSON document is invalid")
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoded, err := decodeUniqueJSONValue(decoder, 0)
	if err != nil {
		return nil, nil, err
	}
	if _, err := decoder.Token(); err != io.EOF {
		return nil, nil, errors.New("JSON document has trailing data")
	}
	var compact bytes.Buffer
	if err := json.Compact(&compact, raw); err != nil ||
		compact.Len() == 0 ||
		compact.Len() > MaxResponseBytes {
		return nil, nil, errors.New("JSON document is invalid")
	}
	return json.RawMessage(compact.Bytes()), decoded, nil
}

func decodeUniqueJSONValue(
	decoder *json.Decoder,
	depth int,
) (any, error) {
	if depth > maxJSONDepth {
		return nil, errors.New("JSON nesting is too deep")
	}
	token, err := decoder.Token()
	if err != nil {
		return nil, err
	}
	delimiter, composite := token.(json.Delim)
	if !composite {
		return token, nil
	}
	switch delimiter {
	case '{':
		object := make(map[string]any)
		for decoder.More() {
			keyToken, err := decoder.Token()
			if err != nil {
				return nil, err
			}
			key, ok := keyToken.(string)
			if !ok {
				return nil, errors.New("JSON object key is invalid")
			}
			if _, duplicate := object[key]; duplicate {
				return nil, errors.New("JSON object key is duplicated")
			}
			value, err := decodeUniqueJSONValue(decoder, depth+1)
			if err != nil {
				return nil, err
			}
			object[key] = value
		}
		end, err := decoder.Token()
		if err != nil || end != json.Delim('}') {
			return nil, errors.New("JSON object is incomplete")
		}
		return object, nil
	case '[':
		array := make([]any, 0)
		for decoder.More() {
			value, err := decodeUniqueJSONValue(decoder, depth+1)
			if err != nil {
				return nil, err
			}
			array = append(array, value)
		}
		end, err := decoder.Token()
		if err != nil || end != json.Delim(']') {
			return nil, errors.New("JSON array is incomplete")
		}
		return array, nil
	default:
		return nil, errors.New("JSON delimiter is invalid")
	}
}
