// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// This file implements JSON Pointers.
// A JSON Pointer is a path that refers to one JSON value within another.
// If the path is empty, it refers to the root value.
// Otherwise, it is a sequence of slash-prefixed strings, like "/points/1/x",
// selecting successive properties (for JSON objects) or items (for JSON arrays).
// For example, when applied to this JSON value:
//    {
// 	     "points": [
// 	 	    {"x": 1, "y": 2},
// 	 	    {"x": 3, "y": 4}
// 		 ]
//    }
//
// the JSON Pointer "/points/1/x" refers to the number 3.
// See the spec at https://datatracker.ietf.org/doc/html/rfc6901.

package jsonschema

import (
	
	
	
	
	
)

var (
	jsonPointerEscaper   = strings.NewReplacer("~", "~0", "/", "~1")
	jsonPointerUnescaper = strings.NewReplacer("~0", "~", "~1", "/")
)

func escapeJSONPointerSegment( string) string {
	return jsonPointerEscaper.Replace()
}

func unescapeJSONPointerSegment( string) string {
	return jsonPointerUnescaper.Replace()
}

// parseJSONPointer splits a JSON Pointer into a sequence of segments. It doesn't
// convert strings to numbers, because that depends on the traversal: a segment
// is treated as a number when applied to an array, but a string when applied to
// an object. See section 4 of the spec.
func parseJSONPointer( string) ( []string,  error) {
	if  == "" {
		return nil, nil
	}
	if [0] != '/' {
		return nil, fmt.Errorf("JSON Pointer %q does not begin with '/'", )
	}
	// Unlike file paths, consecutive slashes are not coalesced.
	// Split is nicer than Cut here, because it gets a final "/" right.
	 = strings.Split([1:], "/")
	if strings.Contains(, "~") {
		// Undo the simple escaping rules that allow one to include a slash in a segment.
		for  := range  {
			[] = unescapeJSONPointerSegment([])
		}
	}
	return , nil
}

// dereferenceJSONPointer returns the Schema that sptr points to within s,
// or an error if none.
// This implementation suffices for JSON Schema: pointers are applied only to Schemas,
// and refer only to Schemas.
func dereferenceJSONPointer( *Schema,  string) ( *Schema,  error) {
	defer wrapf(&, "JSON Pointer %q", )

	,  := parseJSONPointer()
	if  != nil {
		return nil, 
	}
	 := reflect.ValueOf()
	for ,  := range  {
		switch .Kind() {
		case reflect.Pointer:
			 = .Elem()
			if !.IsValid() {
				return nil, errors.New("navigated to nil reference")
			}
			fallthrough // if valid, can only be a pointer to a Schema

		case reflect.Struct:
			// The segment must refer to a field in a Schema.
			if .Type() != reflect.TypeFor[Schema]() {
				return nil, fmt.Errorf("navigated to non-Schema %s", .Type())
			}
			 = lookupSchemaField(, )
			if !.IsValid() {
				return nil, fmt.Errorf("no schema field %q", )
			}
		case reflect.Slice, reflect.Array:
			// The segment must be an integer without leading zeroes that refers to an item in the
			// slice or array.
			if  == "-" {
				return nil, errors.New("the JSON Pointer array segment '-' is not supported")
			}
			if len() > 1 && [0] == '0' {
				return nil, fmt.Errorf("segment %q has leading zeroes", )
			}
			,  := strconv.Atoi()
			if  != nil {
				return nil, fmt.Errorf("invalid int: %q", )
			}
			if  < 0 ||  >= .Len() {
				return nil, fmt.Errorf("index %d is out of bounds for array of length %d", , .Len())
			}
			 = .Index()
			// Cannot be invalid.
		case reflect.Map:
			// The segment must be a key in the map.
			 = .MapIndex(reflect.ValueOf())
			if !.IsValid() {
				return nil, fmt.Errorf("no key %q in map", )
			}
		default:
			return nil, fmt.Errorf("value %s (%s) is not a schema, slice or map", , .Type())
		}
	}
	if ,  := .Interface().(*Schema);  {
		return , nil
	}
	return nil, fmt.Errorf("does not refer to a schema, but to a %s", .Type())
}

// lookupSchemaField returns the value of the field with the given name in v,
// or the zero value if there is no such field or it is not of type Schema or *Schema.
func lookupSchemaField( reflect.Value,  string) reflect.Value {
	if  == "type" {
		// The "type" keyword may refer to Type or Types.
		// At most one will be non-zero.
		if  := .FieldByName("Type"); !.IsZero() {
			return 
		}
		return .FieldByName("Types")
	}
	if  == "items" {
		// The "items" keyword refers to the "union type" that is either a schema or a schema array.
		// Implemented using the Items representing the schema and ItemsArray for the schema array.
		if  := .FieldByName("Items"); .IsValid() && !.IsNil() {
			return 
		}
		return .FieldByName("ItemsArray")
	}
	if  == "dependencies" {
		// The "dependencies" keyword refers to both DependencyStrings and DependencySchemas maps.
		// The value on schemaFieldMap is not garanteed to be DependencySchemas which we want
		// for pointer dereference. So we use FieldByName to get the DependencySchemas map.
		return .FieldByName("DependencySchemas")
	}
	if ,  := schemaFieldMap[];  {
		return .FieldByIndex(.Index)
	}
	return reflect.Value{}
}