// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley
// SPDX-License-Identifier: MIT

package validator

import (
	"net/http"
	"sort"
	"sync"

	"github.com/pb33f/libopenapi"
	"github.com/pb33f/libopenapi/datamodel/high/base"

	v3 "github.com/pb33f/libopenapi/datamodel/high/v3"

	"github.com/pb33f/libopenapi-validator/cache"
	"github.com/pb33f/libopenapi-validator/config"
	"github.com/pb33f/libopenapi-validator/errors"
	"github.com/pb33f/libopenapi-validator/helpers"
	"github.com/pb33f/libopenapi-validator/internal/bodycodec"
	"github.com/pb33f/libopenapi-validator/internal/requeststate"
	"github.com/pb33f/libopenapi-validator/parameters"
	"github.com/pb33f/libopenapi-validator/paths"
	"github.com/pb33f/libopenapi-validator/radix"
	"github.com/pb33f/libopenapi-validator/requests"
	"github.com/pb33f/libopenapi-validator/responses"
	"github.com/pb33f/libopenapi-validator/router"
	"github.com/pb33f/libopenapi-validator/schema_validation"
)

// Validator provides a coarse grained interface for validating an OpenAPI 3+ documents.
// There are three primary use-cases for validation
//
// Validating *http.Request objects against and OpenAPI 3+ document
// Validating *http.Response objects against an OpenAPI 3+ document
// Validating an OpenAPI 3+ document against the OpenAPI 3+ specification
type Validator interface {
	// ValidateHttpRequest will validate an *http.Request object against an OpenAPI 3+ document.
	// The path, query, cookie and header parameters and request body are validated.
	ValidateHttpRequest(request *http.Request) (bool, []*errors.ValidationError)
	// ValidateHttpRequestSync will validate an *http.Request object against an OpenAPI 3+ document synchronously and without spawning any goroutines.
	// The path, query, cookie and header parameters and request body are validated.
	ValidateHttpRequestSync(request *http.Request) (bool, []*errors.ValidationError)

	// ValidateHttpRequestWithPathItem will validate an *http.Request object against an OpenAPI 3+ document.
	// The path, query, cookie and header parameters and request body are validated.
	ValidateHttpRequestWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)

	// ValidateHttpRequestSyncWithPathItem will validate an *http.Request object against an OpenAPI 3+ document synchronously and without spawning any goroutines.
	// The path, query, cookie and header parameters and request body are validated.
	ValidateHttpRequestSyncWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)

	// ValidateHttpResponse will an *http.Response object against an OpenAPI 3+ document.
	// The response body is validated. The request is only used to extract the correct response from the spec.
	ValidateHttpResponse(request *http.Request, response *http.Response) (bool, []*errors.ValidationError)

	// ValidateHttpRequestResponse will validate both the *http.Request and *http.Response objects against an OpenAPI 3+ document.
	// The path, query, cookie and header parameters and request and response body are validated.
	ValidateHttpRequestResponse(request *http.Request, response *http.Response) (bool, []*errors.ValidationError)

	// ValidateDocument validates an OpenAPI document against the embedded 3.0, 3.1, or 3.2 specification schema.
	ValidateDocument() (bool, []*errors.ValidationError)

	// GetParameterValidator will return a parameters.ParameterValidator instance used to validate parameters
	GetParameterValidator() parameters.ParameterValidator

	// GetRequestBodyValidator will return a parameters.RequestBodyValidator instance used to validate request bodies
	GetRequestBodyValidator() requests.RequestBodyValidator

	// GetResponseBodyValidator will return a parameters.ResponseBodyValidator instance used to validate response bodies
	GetResponseBodyValidator() responses.ResponseBodyValidator

	// SetDocument will set the OpenAPI 3+ document to be validated
	SetDocument(document libopenapi.Document)

	// Release clears validator-owned caches and drops references to the model, document, and child validators.
	Release()
}

// NewValidator will create a new Validator from an OpenAPI 3+ document
func NewValidator(document libopenapi.Document, opts ...config.Option) (Validator, []error) {
	m, errs := document.BuildV3Model()
	if errs != nil {
		return nil, []error{errs}
	}
	v := NewValidatorFromV3Model(&m.Model, opts...)
	v.(*validator).document = document
	return v, nil
}

// NewValidatorFromV3Model will create a new Validator from an OpenAPI Model
func NewValidatorFromV3Model(m *v3.Document, opts ...config.Option) Validator {
	options := config.NewValidationOptions(opts...)
	bodycodec.Apply(options)

	// Build radix tree for O(k) path lookup (where k = path depth)
	// Skip if path tree is disabled or a custom tree was provided
	if options.PathTree == nil && !options.IsPathTreeDisabled() {
		options.PathTree = radix.BuildPathTree(m)
	}
	var routerOptions []router.Option
	if options.PathTree != nil {
		routerOptions = append(routerOptions, router.WithPathLookup(options.PathTree))
	}
	if options.RegexCache != nil {
		routerOptions = append(routerOptions, router.WithRegexCache(options.RegexCache))
	}
	if !options.StrictServerMatching {
		routerOptions = append(routerOptions, router.WithPathOnlyMatching())
	}
	routeFinder := router.NewRouter(m, routerOptions...)
	options.Router = routeFinder
	options.BodyRegistry = options.BodyRegistry.Precompute(declaredMediaTypes(m))

	// warm the schema caches by pre-compiling all schemas in the document
	// (warmSchemaCaches checks for nil cache and skips if disabled)
	warmSchemaCaches(m, options)

	v := &validator{options: options, v3Model: m, router: routeFinder}

	// create a new parameter validator
	v.paramValidator = parameters.NewParameterValidator(m, config.WithExistingOpts(options))

	// create aq new request body validator
	v.requestValidator = requests.NewRequestBodyValidator(m, config.WithExistingOpts(options))

	// create a response body validator
	v.responseValidator = responses.NewResponseBodyValidator(m, config.WithExistingOpts(options))

	return v
}

func declaredMediaTypes(document *v3.Document) []string {
	if document == nil || document.Paths == nil || document.Paths.PathItems == nil {
		return nil
	}
	seen := make(map[string]struct{})
	var mediaTypes []string
	add := func(mediaType string) {
		if _, ok := seen[mediaType]; !ok {
			seen[mediaType] = struct{}{}
			mediaTypes = append(mediaTypes, mediaType)
		}
	}
	for path := document.Paths.PathItems.First(); path != nil; path = path.Next() {
		pathItem := path.Value()
		for operation := pathItem.GetOperations().First(); operation != nil; operation = operation.Next() {
			op := operation.Value()
			if op.RequestBody != nil && op.RequestBody.Content != nil {
				for media := op.RequestBody.Content.First(); media != nil; media = media.Next() {
					add(media.Key())
				}
			}
			if op.Responses != nil {
				if op.Responses.Codes != nil {
					for response := op.Responses.Codes.First(); response != nil; response = response.Next() {
						if response.Value() != nil && response.Value().Content != nil {
							for media := response.Value().Content.First(); media != nil; media = media.Next() {
								add(media.Key())
							}
						}
					}
				}
				if op.Responses.Default != nil && op.Responses.Default.Content != nil {
					for media := op.Responses.Default.Content.First(); media != nil; media = media.Next() {
						add(media.Key())
					}
				}
			}
			for _, parameter := range op.Parameters {
				if parameter != nil && parameter.Content != nil {
					for media := parameter.Content.First(); media != nil; media = media.Next() {
						add(media.Key())
					}
				}
			}
		}
		for _, parameter := range pathItem.Parameters {
			if parameter != nil && parameter.Content != nil {
				for media := parameter.Content.First(); media != nil; media = media.Next() {
					add(media.Key())
				}
			}
		}
	}
	return mediaTypes
}

func (v *validator) SetDocument(document libopenapi.Document) {
	v.document = document
}

func (v *validator) Release() {
	if v == nil {
		return
	}
	if v.router != nil {
		v.router.Release()
		v.router = nil
	}
	releaseIfSupported(v.paramValidator)
	releaseIfSupported(v.requestValidator)
	releaseIfSupported(v.responseValidator)
	if v.options != nil {
		v.options.Release()
		v.options = nil
	}
	v.v3Model = nil
	v.document = nil
	v.paramValidator = nil
	v.requestValidator = nil
	v.responseValidator = nil
}

func releaseIfSupported(value any) {
	if r, ok := value.(interface{ Release() }); ok {
		r.Release()
	}
}

func (v *validator) GetParameterValidator() parameters.ParameterValidator {
	return v.paramValidator
}

func (v *validator) GetRequestBodyValidator() requests.RequestBodyValidator {
	return v.requestValidator
}

func (v *validator) GetResponseBodyValidator() responses.ResponseBodyValidator {
	return v.responseValidator
}

func (v *validator) ValidateDocument() (bool, []*errors.ValidationError) {
	if v.document == nil {
		return false, []*errors.ValidationError{{
			ValidationType:    helpers.DocumentValidation,
			ValidationSubType: helpers.ValidationMissing,
			Message:           "Document is not set",
			Reason:            "The document cannot be validated as it is not set",
			SpecLine:          1,
			SpecCol:           1,
			HowToFix:          "Set the document via `SetDocument` before validating",
		}}
	}
	var validationOpts []config.Option
	if v.options != nil {
		validationOpts = append(validationOpts, config.WithRegexEngine(v.options.RegexEngine))
	}
	return schema_validation.ValidateOpenAPIDocument(v.document, validationOpts...)
}

func (v *validator) ValidateHttpResponse(
	request *http.Request,
	response *http.Response,
) (bool, []*errors.ValidationError) {
	var pathItem *v3.PathItem
	var pathValue string
	var errs []*errors.ValidationError

	pathItem, errs, pathValue = paths.FindPath(request, v.v3Model, v.options)
	if pathItem == nil || errs != nil {
		return false, errs
	}

	responseBodyValidator := v.responseValidator

	// validate response
	_, responseErrors := responseBodyValidator.ValidateResponseBodyWithPathItem(request, response, pathItem, pathValue)

	if len(responseErrors) > 0 {
		return false, responseErrors
	}
	return true, nil
}

func (v *validator) ValidateHttpRequestResponse(
	request *http.Request,
	response *http.Response,
) (bool, []*errors.ValidationError) {
	route, errs := paths.ResolveRoute(request, v.v3Model, v.options)
	if route == nil || errs != nil {
		return false, errs
	}
	restoreRoute := requeststate.AttachRoute(request, route)
	defer restoreRoute()

	responseBodyValidator := v.responseValidator

	// validate request and response
	_, requestErrors := v.ValidateHttpRequestWithPathItem(request, route.PathItem, route.Path)
	_, responseErrors := responseBodyValidator.ValidateResponseBodyWithPathItem(request, response, route.PathItem, route.Path)

	if len(requestErrors) > 0 || len(responseErrors) > 0 {
		return false, append(requestErrors, responseErrors...)
	}
	return true, nil
}

func (v *validator) ValidateHttpRequest(request *http.Request) (bool, []*errors.ValidationError) {
	route, errs := paths.ResolveRoute(request, v.v3Model, v.options)
	if len(errs) > 0 {
		return false, errs
	}
	restoreRoute := requeststate.AttachRoute(request, route)
	defer restoreRoute()
	return v.ValidateHttpRequestWithPathItem(request, route.PathItem, route.Path)
}

func (v *validator) ValidateHttpRequestWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
	restoreRoute := v.attachRoute(request, pathItem, pathValue)
	defer restoreRoute()
	if v.options != nil && v.options.RequestDefaults {
		return v.validateWithRequestDefaults(request, pathItem, pathValue, v.validateHttpRequestWithPathItem)
	}
	return v.validateHttpRequestWithPathItem(request, pathItem, pathValue)
}

func (v *validator) validateHttpRequestWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
	if v.options != nil && v.options.AuthenticationFunc != nil {
		if _, err := requeststate.Snapshot(request); err != nil {
			return false, []*errors.ValidationError{{
				ValidationType:    helpers.RequestBodyValidation,
				ValidationSubType: helpers.Schema,
				Message:           "request body could not be read for authentication",
				Reason:            err.Error(),
				HowToFix:          "provide a readable request body",
			}}
		}
		return v.ValidateHttpRequestSyncWithPathItem(request, pathItem, pathValue)
	}
	// create a new parameter validator
	paramValidator := v.paramValidator

	// create a new request body validator
	reqBodyValidator := v.requestValidator

	// create some channels to handle async validation
	doneChan := make(chan struct{})
	errChan := make(chan []*errors.ValidationError)
	controlChan := make(chan struct{})

	// async param validation function.
	parameterValidationFunc := func(control chan struct{}, errorChan chan []*errors.ValidationError) {
		paramErrs := make(chan []*errors.ValidationError)
		paramControlChan := make(chan struct{})
		paramFunctionControlChan := make(chan struct{})
		var paramValidationErrors []*errors.ValidationError

		validations := []validationFunction{
			paramValidator.ValidatePathParamsWithPathItem,
			paramValidator.ValidateCookieParamsWithPathItem,
			paramValidator.ValidateHeaderParamsWithPathItem,
			paramValidator.ValidateSecurityWithPathItem,
		}
		if v.options.ValidateRequestQuery {
			validations = append(validations, paramValidator.ValidateQueryParamsWithPathItem)
		}

		// listen for validation errors on parameters. everything will run async.
		paramListener := func(control chan struct{}, errorChan chan []*errors.ValidationError) {
			completedValidations := 0
			for {
				select {
				case vErrs := <-errorChan:
					paramValidationErrors = append(paramValidationErrors, vErrs...)
				case <-control:
					completedValidations++
					if completedValidations == len(validations) {
						paramFunctionControlChan <- struct{}{}
						return
					}
				}
			}
		}

		validateParamFunction := func(
			control chan struct{},
			errorChan chan []*errors.ValidationError,
			validatorFunc validationFunction,
		) {
			valid, pErrs := validatorFunc(request, pathItem, pathValue)
			if !valid {
				errorChan <- pErrs
			}
			control <- struct{}{}
		}
		go paramListener(paramControlChan, paramErrs)
		for i := range validations {
			go validateParamFunction(paramControlChan, paramErrs, validations[i])
		}

		// wait for all the validations to complete
		<-paramFunctionControlChan
		if len(paramValidationErrors) > 0 {
			errorChan <- paramValidationErrors
		}

		// let runValidation know we are done with this part.
		controlChan <- struct{}{}
	}

	requestBodyValidationFunc := func(control chan struct{}, errorChan chan []*errors.ValidationError) {
		valid, pErrs := reqBodyValidator.ValidateRequestBodyWithPathItem(request, pathItem, pathValue)
		if !valid {
			errorChan <- pErrs
		}
		control <- struct{}{}
	}

	// build async functions
	asyncFunctions := []validationFunctionAsync{
		parameterValidationFunc,
	}
	if v.options.ValidateRequestBody {
		asyncFunctions = append(asyncFunctions, requestBodyValidationFunc)
	}

	var validationErrors []*errors.ValidationError

	// sit and wait for everything to report back.
	go runValidation(controlChan, doneChan, errChan, &validationErrors, len(asyncFunctions))

	// run async functions
	for i := range asyncFunctions {
		go asyncFunctions[i](controlChan, errChan)
	}

	// wait for all the validations to complete
	<-doneChan

	// sort errors for deterministic ordering (async validation can return errors in any order)
	sortValidationErrors(validationErrors)

	return len(validationErrors) == 0, validationErrors
}

func (v *validator) ValidateHttpRequestSync(request *http.Request) (bool, []*errors.ValidationError) {
	route, errs := paths.ResolveRoute(request, v.v3Model, v.options)
	if len(errs) > 0 {
		return false, errs
	}
	restoreRoute := requeststate.AttachRoute(request, route)
	defer restoreRoute()
	return v.ValidateHttpRequestSyncWithPathItem(request, route.PathItem, route.Path)
}

func (v *validator) ValidateHttpRequestSyncWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
	restoreRoute := v.attachRoute(request, pathItem, pathValue)
	defer restoreRoute()
	if v.options != nil && v.options.RequestDefaults {
		return v.validateWithRequestDefaults(request, pathItem, pathValue, v.validateHttpRequestSyncWithPathItem)
	}
	return v.validateHttpRequestSyncWithPathItem(request, pathItem, pathValue)
}

func (v *validator) attachRoute(request *http.Request, pathItem *v3.PathItem, pathValue string) func() {
	if requeststate.Route(request) != nil {
		return func() {}
	}
	route := &router.Route{
		Document: v.v3Model, Path: pathValue, PathItem: pathItem, Method: request.Method,
		Operation: helpers.ExtractOperation(request, pathItem),
	}
	if v.options != nil && v.options.Router != nil {
		if resolved, err := v.options.Router.FindRoute(request); err == nil && resolved != nil && resolved.PathItem == pathItem {
			route = resolved
		}
	}
	return requeststate.AttachRoute(request, route)
}

func (v *validator) validateHttpRequestSyncWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
	// create a new parameter validator
	paramValidator := v.paramValidator

	// create a new request body validator
	reqBodyValidator := v.requestValidator

	validationErrors := make([]*errors.ValidationError, 0)

	paramValidationErrors := make([]*errors.ValidationError, 0)
	validations := []validationFunction{
		paramValidator.ValidatePathParamsWithPathItem,
		paramValidator.ValidateCookieParamsWithPathItem,
		paramValidator.ValidateHeaderParamsWithPathItem,
		paramValidator.ValidateSecurityWithPathItem,
	}
	if v.options.ValidateRequestQuery {
		validations = append(validations, paramValidator.ValidateQueryParamsWithPathItem)
	}
	for _, validateFunc := range validations {
		valid, pErrs := validateFunc(request, pathItem, pathValue)
		if !valid {
			paramValidationErrors = append(paramValidationErrors, pErrs...)
		}
	}

	if v.options.ValidateRequestBody {
		valid, pErrs := reqBodyValidator.ValidateRequestBodyWithPathItem(request, pathItem, pathValue)
		if !valid {
			paramValidationErrors = append(paramValidationErrors, pErrs...)
		}
	}

	validationErrors = append(validationErrors, paramValidationErrors...)
	return len(validationErrors) == 0, validationErrors
}

type validator struct {
	options           *config.ValidationOptions
	v3Model           *v3.Document
	document          libopenapi.Document
	paramValidator    parameters.ParameterValidator
	requestValidator  requests.RequestBodyValidator
	responseValidator responses.ResponseBodyValidator
	router            router.Router
}

func runValidation(control, doneChan chan struct{},
	errorChan chan []*errors.ValidationError,
	validationErrors *[]*errors.ValidationError,
	total int,
) {
	var validationLock sync.Mutex
	completedValidations := 0
	for {
		select {
		case vErrs := <-errorChan:
			validationLock.Lock()
			*validationErrors = append(*validationErrors, vErrs...)
			validationLock.Unlock()
		case <-control:
			completedValidations++
			if completedValidations == total {
				doneChan <- struct{}{}
				return
			}
		}
	}
}

type (
	validationFunction      func(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
	validationFunctionAsync func(control chan struct{}, errorChan chan []*errors.ValidationError)
)

// sortValidationErrors sorts validation errors for deterministic ordering.
// Errors are sorted by validation type first, then by message.
func sortValidationErrors(errs []*errors.ValidationError) {
	sort.Slice(errs, func(i, j int) bool {
		if errs[i].ValidationType != errs[j].ValidationType {
			return errs[i].ValidationType < errs[j].ValidationType
		}
		return errs[i].Message < errs[j].Message
	})
}

// warmSchemaCaches pre-compiles all schemas in the OpenAPI document and stores them in the validator caches.
// This frontloads the compilation cost so that runtime validation doesn't need to compile schemas.
func warmSchemaCaches(
	doc *v3.Document,
	options *config.ValidationOptions,
) {
	// Skip warming if cache is nil (explicitly disabled via WithSchemaCache(nil))
	if doc == nil || doc.Paths == nil || doc.Paths.PathItems == nil || options.SchemaCache == nil {
		return
	}

	schemaCache := options.SchemaCache
	version := helpers.VersionToFloat(doc.Version)

	// Walk through all paths and operations
	for pathPair := doc.Paths.PathItems.First(); pathPair != nil; pathPair = pathPair.Next() {
		pathItem := pathPair.Value()

		// Get all operations for this path (handles all HTTP methods including OpenAPI 3.2+ extensions)
		operations := pathItem.GetOperations()

		for opPair := operations.First(); opPair != nil; opPair = opPair.Next() {
			operation := opPair.Value()

			// Warm request body schemas
			if operation.RequestBody != nil && operation.RequestBody.Content != nil {
				for contentPair := operation.RequestBody.Content.First(); contentPair != nil; contentPair = contentPair.Next() {
					mediaType := contentPair.Value()
					if mediaType.Schema != nil {
						warmMediaTypeSchema(mediaType, schemaCache, options, version,
							schema_validation.SchemaValidationPurposeRequestBody)
					}
				}
			}

			// Warm response body schemas
			if operation.Responses != nil {
				// Warm status code responses
				if operation.Responses.Codes != nil {
					for codePair := operation.Responses.Codes.First(); codePair != nil; codePair = codePair.Next() {
						response := codePair.Value()
						if response != nil && response.Content != nil {
							for contentPair := response.Content.First(); contentPair != nil; contentPair = contentPair.Next() {
								mediaType := contentPair.Value()
								if mediaType.Schema != nil {
									warmMediaTypeSchema(mediaType, schemaCache, options, version,
										schema_validation.SchemaValidationPurposeResponseBody)
								}
							}
						}
					}
				}

				// Warm default response schemas
				if operation.Responses.Default != nil && operation.Responses.Default.Content != nil {
					for contentPair := operation.Responses.Default.Content.First(); contentPair != nil; contentPair = contentPair.Next() {
						mediaType := contentPair.Value()
						if mediaType.Schema != nil {
							warmMediaTypeSchema(mediaType, schemaCache, options, version,
								schema_validation.SchemaValidationPurposeResponseBody)
						}
					}
				}
			}

			// Warm parameter schemas
			if operation.Parameters != nil {
				for _, param := range operation.Parameters {
					if param != nil {
						warmParameterSchema(param, schemaCache, options, version)
					}
				}
			}
		}

		// Warm path-level parameters
		if pathItem.Parameters != nil {
			for _, param := range pathItem.Parameters {
				if param != nil {
					warmParameterSchema(param, schemaCache, options, version)
				}
			}
		}
	}
}

// warmMediaTypeSchema warms the cache for a media type schema
func warmMediaTypeSchema(
	mediaType *v3.MediaType,
	schemaCache cache.SchemaCache,
	options *config.ValidationOptions,
	version float32,
	purpose schema_validation.SchemaValidationPurpose,
) {
	if mediaType != nil && mediaType.Schema != nil {
		schema := mediaType.Schema.Schema()
		if schema == nil || schema.GoLow() == nil {
			return
		}
		hash := schema_validation.SchemaCacheKey(schema.GoLow().Hash(), version, purpose)

		if _, exists := schemaCache.Load(hash); !exists {
			compiled, err := schema_validation.CompileSchemaForValidation(
				schema,
				purpose,
				options,
				version,
			)
			if err != nil || compiled == nil || compiled.CompiledSchema == nil {
				return
			}

			schemaCache.Store(hash, compiled.ToCacheEntry(schema))
		}
	}
}

// warmParameterSchema warms the cache for a parameter schema
func warmParameterSchema(param *v3.Parameter, schemaCache cache.SchemaCache, options *config.ValidationOptions, version float32) {
	if param != nil {
		var schema *base.Schema

		// Parameters can have schemas in two places: schema property or content property
		if param.Schema != nil {
			schema = param.Schema.Schema()
		} else if param.Content != nil {
			// Check content for schema
			for contentPair := param.Content.First(); contentPair != nil; contentPair = contentPair.Next() {
				mediaType := contentPair.Value()
				if mediaType.Schema != nil {
					schema = mediaType.Schema.Schema()
					break // Only process first content type
				}
			}
		}

		if schema != nil && schema.GoLow() != nil {
			hash := schema_validation.SchemaCacheKey(schema.GoLow().Hash(), version,
				schema_validation.SchemaValidationPurposeGeneric)
			if _, exists := schemaCache.Load(hash); !exists {
				compiled, err := schema_validation.CompileSchemaForValidation(
					schema,
					schema_validation.SchemaValidationPurposeGeneric,
					options,
					version,
				)
				if err != nil || compiled == nil || compiled.CompiledSchema == nil {
					return
				}

				// Store in cache using the shared SchemaCache type
				schemaCache.Store(hash, compiled.ToCacheEntry(schema))
			}
		}
	}
}
