// Package strictjson owns fail-closed JSON token validation shared by pack
// ingestion and immutable App component canonicalization.
package strictjson

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"unicode/utf16"
	"unicode/utf8"
)

// RejectDuplicateKeys rejects duplicate keys at every object depth and any
// trailing JSON value. encoding/json alone silently accepts both shapes.
func RejectDuplicateKeys(raw []byte, source string) error {
	if err := validateUnicode(raw); err != nil {
		return err
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	if err := scanValue(decoder, source); err != nil {
		return err
	}
	if _, err := decoder.Token(); err == io.EOF {
		return nil
	} else if err != nil {
		return err
	}
	return fmt.Errorf("unexpected trailing JSON token")
}

// encoding/json replaces malformed UTF-8 and unpaired UTF-16 surrogate
// escapes with U+FFFD. Identity-bearing JSON must reject those inputs before
// decoding so distinct invalid bytes cannot acquire one canonical identity.
func validateUnicode(raw []byte) error {
	if !utf8.Valid(raw) {
		return fmt.Errorf("JSON must contain valid UTF-8")
	}
	inString := false
	for index := 0; index < len(raw); index++ {
		if !inString {
			if raw[index] == '"' {
				inString = true
			}
			continue
		}
		if raw[index] == '"' {
			inString = false
			continue
		}
		if raw[index] != '\\' || index+1 >= len(raw) {
			continue
		}
		if raw[index+1] != 'u' {
			index++
			continue
		}
		first, ok := parseHexUTF16(raw, index+2)
		if !ok {
			continue
		}
		if utf16.IsSurrogate(rune(first)) {
			if first >= 0xdc00 {
				return fmt.Errorf("JSON contains an unpaired UTF-16 low surrogate escape")
			}
			if index+12 > len(raw) || raw[index+6] != '\\' || raw[index+7] != 'u' {
				return fmt.Errorf("JSON contains an unpaired UTF-16 high surrogate escape")
			}
			second, valid := parseHexUTF16(raw, index+8)
			if !valid || second < 0xdc00 || second > 0xdfff {
				return fmt.Errorf("JSON contains an unpaired UTF-16 high surrogate escape")
			}
			index += 11
			continue
		}
		index += 5
	}
	return nil
}

func parseHexUTF16(raw []byte, offset int) (uint16, bool) {
	if offset+4 > len(raw) {
		return 0, false
	}
	var value uint16
	for _, char := range raw[offset : offset+4] {
		value <<= 4
		switch {
		case char >= '0' && char <= '9':
			value |= uint16(char - '0')
		case char >= 'a' && char <= 'f':
			value |= uint16(char-'a') + 10
		case char >= 'A' && char <= 'F':
			value |= uint16(char-'A') + 10
		default:
			return 0, false
		}
	}
	return value, true
}

func scanValue(decoder *json.Decoder, path string) error {
	token, err := decoder.Token()
	if err != nil {
		return err
	}
	delimiter, ok := token.(json.Delim)
	if !ok {
		return nil
	}
	switch delimiter {
	case '{':
		seen := map[string]struct{}{}
		for decoder.More() {
			keyToken, err := decoder.Token()
			if err != nil {
				return err
			}
			key, ok := keyToken.(string)
			if !ok {
				return fmt.Errorf("%s: object key is not a string", path)
			}
			if _, duplicate := seen[key]; duplicate {
				return fmt.Errorf("%s: duplicate object key %q", path, key)
			}
			seen[key] = struct{}{}
			childPath := path + "." + key
			if path == "" {
				childPath = key
			}
			if err := scanValue(decoder, childPath); err != nil {
				return err
			}
		}
		end, err := decoder.Token()
		if err != nil {
			return err
		}
		if end != json.Delim('}') {
			return fmt.Errorf("%s: expected object end", path)
		}
	case '[':
		for index := 0; decoder.More(); index++ {
			if err := scanValue(decoder, fmt.Sprintf("%s[%d]", path, index)); err != nil {
				return err
			}
		}
		end, err := decoder.Token()
		if err != nil {
			return err
		}
		if end != json.Delim(']') {
			return fmt.Errorf("%s: expected array end", path)
		}
	default:
		return fmt.Errorf("%s: unexpected delimiter %q", path, delimiter)
	}
	return nil
}
