<?php
/** Bounded, integrity-checked JSON envelopes for dynamic builder state. */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

define( 'WPAGENT_BUILDER_JSON_DOCUMENT_MAX_DEPTH', 64 );

/** @return array<string,mixed> */
function wpagent_builder_json_document_schema( int $max_bytes ): array {
	return array(
		'type'                 => 'object',
		'properties'           => array(
			'json'   => array( 'type' => 'string', 'minLength' => 2, 'maxLength' => $max_bytes ),
			'sha256' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			'bytes'  => array( 'type' => 'integer', 'minimum' => 2, 'maximum' => $max_bytes ),
		),
		'required'             => array( 'json', 'sha256', 'bytes' ),
		'additionalProperties' => false,
	);
}

/** @param mixed $value */
function wpagent_builder_json_document_is_envelope( $value ): bool {
	return is_array( $value )
		&& 3 === count( $value )
		&& ! array_diff( array_keys( $value ), array( 'json', 'sha256', 'bytes' ) );
}

/**
 * @param mixed $value
 * @return array{json:string,sha256:string,bytes:int}|WP_Error
 */
function wpagent_builder_json_document_from_value( $value, int $max_bytes, callable $validator, string $error_prefix ) {
	$valid = $validator( $value );
	if ( is_wp_error( $valid ) ) {
		return $valid;
	}
	try {
		$json = json_encode( $value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR );
	} catch ( JsonException ) {
		return new WP_Error( $error_prefix . '_json_encode', __( 'Die Builder-Daten konnten nicht sicher als JSON gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$bytes = strlen( $json );
	if ( $bytes < 2 || $bytes > $max_bytes ) {
		return new WP_Error( $error_prefix . '_json_size', __( 'Das Builder-JSON überschreitet die sichere Größenbegrenzung.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	return array( 'json' => $json, 'sha256' => hash( 'sha256', $json ), 'bytes' => $bytes );
}

/**
 * @return mixed|WP_Error
 */
function wpagent_builder_json_document_to_value( $document, int $max_bytes, callable $validator, string $error_prefix ) {
	if ( ! wpagent_builder_json_document_is_envelope( $document )
		|| ! is_string( $document['json'] )
		|| ! is_string( $document['sha256'] )
		|| ! is_int( $document['bytes'] ) ) {
		return new WP_Error( $error_prefix . '_json_document', __( 'Das Builder-JSON muss den geschlossenen Integritätsumschlag json, sha256 und bytes verwenden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$json  = $document['json'];
	$bytes = strlen( $json );
	if ( $bytes < 2 || $bytes > $max_bytes || $document['bytes'] !== $bytes || ! preg_match( '/^[a-f0-9]{64}$/', $document['sha256'] ) || ! hash_equals( $document['sha256'], hash( 'sha256', $json ) ) ) {
		return new WP_Error( $error_prefix . '_json_integrity', __( 'Das Builder-JSON ist zu groß oder seine Integritätsdaten stimmen nicht überein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	try {
		$value = json_decode( $json, true, WPAGENT_BUILDER_JSON_DOCUMENT_MAX_DEPTH, JSON_THROW_ON_ERROR );
	} catch ( JsonException ) {
		return new WP_Error( $error_prefix . '_json_decode', __( 'Das Builder-JSON ist nicht gültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$valid = $validator( $value );
	return is_wp_error( $valid ) ? $valid : $value;
}
