<?php
/** Read abilities for the live native WordPress block registry and documents. */

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

/**
 * Dynamic provider schemas are transported as a small, integrity-bound JSON
 * document rather than an open-ended MCP object. This keeps MCP transforms
 * finite while retaining the provider's scalar/array schema information.
 */
const WPAGENT_NATIVE_BLOCK_JSON_ENVELOPE_FORMAT = 'wpagently.native-block-json.v1';
const WPAGENT_NATIVE_BLOCK_JSON_MAX_BYTES       = 65536;
const WPAGENT_NATIVE_BLOCK_JSON_MAX_DEPTH       = 8;
const WPAGENT_NATIVE_BLOCK_JSON_MAX_ITEMS       = 100;
const WPAGENT_NATIVE_BLOCK_JSON_MAX_NODES       = 1000;
const WPAGENT_NATIVE_BLOCK_JSON_MAX_STRING      = 4096;

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

/** @param mixed $value */
function wpagent_native_block_public_schema_within_json_limits( $value, int $depth = 0, ?int &$nodes = null, int $root_max_items = WPAGENT_NATIVE_BLOCK_JSON_MAX_ITEMS ): bool {
	$nodes = null === $nodes ? 0 : $nodes;
	++$nodes;
	if ( $nodes > WPAGENT_NATIVE_BLOCK_JSON_MAX_NODES || $depth > WPAGENT_NATIVE_BLOCK_JSON_MAX_DEPTH ) {
		return false;
	}
	if ( null === $value || is_bool( $value ) || is_int( $value ) ) {
		return true;
	}
	if ( is_float( $value ) ) {
		return is_finite( $value );
	}
	if ( is_string( $value ) ) {
		return strlen( $value ) <= WPAGENT_NATIVE_BLOCK_JSON_MAX_STRING && 1 === preg_match( '//u', $value );
	}
	if ( ! is_array( $value ) ) {
		return false;
	}
	$limit = 0 === $depth ? $root_max_items : WPAGENT_NATIVE_BLOCK_JSON_MAX_ITEMS;
	if ( count( $value ) > $limit ) {
		return false;
	}
	$is_list = wp_is_numeric_array( $value );
	foreach ( $value as $key => $child ) {
		if ( is_string( $key ) && wpagent_native_block_sensitive_key( $key ) ) {
			continue;
		}
		if ( $is_list ? ! is_int( $key ) : ( ! is_string( $key ) || '' === $key || strlen( $key ) > 128 || 1 !== preg_match( '//u', $key ) ) ) {
			return false;
		}
		// These values are deliberately omitted from public output by the shared
		// sanitizer, so they cannot consume the public JSON envelope budget.
		if ( is_string( $child ) && wpagent_native_block_local_path_value( $child ) ) {
			continue;
		}
		if ( ! wpagent_native_block_public_schema_within_json_limits( $child, $depth + 1, $nodes, WPAGENT_NATIVE_BLOCK_JSON_MAX_ITEMS ) ) {
			return false;
		}
	}
	return true;
}

/** @param mixed $value @return array<mixed>|WP_Error */
function wpagent_native_block_canonical_json_value( $value, int $depth = 0, ?int &$nodes = null, int $root_max_items = WPAGENT_NATIVE_BLOCK_JSON_MAX_ITEMS ) {
	$nodes = null === $nodes ? 0 : $nodes;
	++$nodes;
	if ( $nodes > WPAGENT_NATIVE_BLOCK_JSON_MAX_NODES || $depth > WPAGENT_NATIVE_BLOCK_JSON_MAX_DEPTH ) {
		return new WP_Error( 'wpagent_native_block_json_bounds', __( 'Die JSON-Struktur überschreitet die erlaubte Tiefe oder Größe.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( null === $value || is_bool( $value ) || is_int( $value ) ) {
		return $value;
	}
	if ( is_float( $value ) ) {
		return is_finite( $value ) ? $value : new WP_Error( 'wpagent_native_block_json_value', __( 'Die JSON-Struktur enthält einen nicht endlichen Zahlenwert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( is_string( $value ) ) {
		if ( strlen( $value ) > WPAGENT_NATIVE_BLOCK_JSON_MAX_STRING || 1 !== preg_match( '//u', $value ) ) {
			return new WP_Error( 'wpagent_native_block_json_string', __( 'Die JSON-Struktur enthält einen ungültigen oder zu langen Textwert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		return $value;
	}
	if ( ! is_array( $value ) ) {
		return new WP_Error( 'wpagent_native_block_json_value', __( 'Die JSON-Struktur enthält einen nicht unterstützten Wert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$limit = 0 === $depth ? $root_max_items : WPAGENT_NATIVE_BLOCK_JSON_MAX_ITEMS;
	if ( count( $value ) > $limit ) {
		return new WP_Error( 'wpagent_native_block_json_items', __( 'Die JSON-Struktur enthält zu viele Einträge.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$is_list = wp_is_numeric_array( $value );
	$out = array();
	foreach ( $value as $key => $child ) {
		if ( $is_list && ! is_int( $key ) ) {
			return new WP_Error( 'wpagent_native_block_json_shape', __( 'Die JSON-Struktur enthält eine mehrdeutige Array-Form.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		if ( ! $is_list && ( ! is_string( $key ) || '' === $key || strlen( $key ) > 128 || 1 !== preg_match( '//u', $key ) ) ) {
			return new WP_Error( 'wpagent_native_block_json_key', __( 'Die JSON-Struktur enthält einen ungültigen Objektschlüssel.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$canonical = wpagent_native_block_canonical_json_value( $child, $depth + 1, $nodes, WPAGENT_NATIVE_BLOCK_JSON_MAX_ITEMS );
		if ( is_wp_error( $canonical ) ) {
			return $canonical;
		}
		$out[ $key ] = $canonical;
	}
	if ( ! $is_list ) {
		ksort( $out, SORT_STRING );
	}
	return $out;
}

/** @param mixed $value @return array<string,mixed>|WP_Error */
function wpagent_native_block_json_envelope( $value, int $root_max_items = WPAGENT_NATIVE_BLOCK_JSON_MAX_ITEMS ) {
	$nodes = 0;
	$canonical = wpagent_native_block_canonical_json_value( $value, 0, $nodes, $root_max_items );
	if ( is_wp_error( $canonical ) ) {
		return $canonical;
	}
	$json = wp_json_encode( $canonical, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( ! is_string( $json ) || strlen( $json ) < 2 || strlen( $json ) > WPAGENT_NATIVE_BLOCK_JSON_MAX_BYTES ) {
		return new WP_Error( 'wpagent_native_block_json_bytes', __( 'Die JSON-Struktur überschreitet die erlaubte Bytegröße.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return array(
		'format' => WPAGENT_NATIVE_BLOCK_JSON_ENVELOPE_FORMAT,
		'json' => $json,
		'sha256' => hash( 'sha256', $json ),
		'bytes' => strlen( $json ),
	);
}

/** @param mixed $envelope @return array<mixed>|WP_Error */
function wpagent_native_block_decode_json_envelope( $envelope, int $root_max_items = WPAGENT_NATIVE_BLOCK_JSON_MAX_ITEMS ) {
	$required = array( 'format', 'json', 'sha256', 'bytes' );
	if ( ! is_array( $envelope ) || wp_is_numeric_array( $envelope ) || array_diff_key( $envelope, array_flip( $required ) ) || array_diff_key( array_flip( $required ), $envelope ) ) {
		return new WP_Error( 'wpagent_native_block_json_envelope', __( 'attributes muss ein vollständiger, integritätsgebundener JSON-Umschlag sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$format = $envelope['format'] ?? null;
	$json = $envelope['json'] ?? null;
	$sha256 = $envelope['sha256'] ?? null;
	$bytes = $envelope['bytes'] ?? null;
	if ( WPAGENT_NATIVE_BLOCK_JSON_ENVELOPE_FORMAT !== $format || ! is_string( $json ) || ! is_string( $sha256 ) || ! is_int( $bytes )
		|| $bytes < 2 || $bytes > WPAGENT_NATIVE_BLOCK_JSON_MAX_BYTES || $bytes !== strlen( $json )
		|| ! preg_match( '/^[a-f0-9]{64}$/D', $sha256 ) || ! hash_equals( $sha256, hash( 'sha256', $json ) ) || 1 !== preg_match( '//u', $json ) ) {
		return new WP_Error( 'wpagent_native_block_json_integrity', __( 'Der JSON-Umschlag ist ungültig oder seine Integrität stimmt nicht.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	try {
		$decoded = json_decode( $json, true, WPAGENT_NATIVE_BLOCK_JSON_MAX_DEPTH + 2, JSON_THROW_ON_ERROR );
	} catch ( JsonException $exception ) {
		return new WP_Error( 'wpagent_native_block_json_parse', __( 'Der JSON-Umschlag enthält kein gültiges JSON.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! is_array( $decoded ) || ( array() !== $decoded && wp_is_numeric_array( $decoded ) ) ) {
		return new WP_Error( 'wpagent_native_block_json_root', __( 'Der JSON-Umschlag muss ein Objekt mit Attributnamen enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$nodes = 0;
	$canonical = wpagent_native_block_canonical_json_value( $decoded, 0, $nodes, $root_max_items );
	return is_wp_error( $canonical ) ? $canonical : $canonical;
}

/** @return array<string,mixed> */
function wpagent_native_block_type_summary_schema(): array {
	$properties = array(
		'name' => array( 'type' => 'string' ), 'namespace' => array( 'type' => 'string' ),
		'title' => array( 'type' => 'string' ), 'category' => array( 'type' => 'string' ),
		'api_version' => array( 'type' => 'integer' ), 'dynamic' => array( 'type' => 'boolean' ),
		'attribute_count' => array( 'type' => 'integer' ),
	);
	return array( 'type' => 'object', 'properties' => $properties, 'required' => array_keys( $properties ), 'additionalProperties' => false );
}

/** @return array<string,mixed> */
function wpagent_native_block_document_schema(): array {
	$properties = array(
		'post_id' => array( 'type' => 'integer' ), 'post_type' => array( 'type' => 'string' ),
		'status' => array( 'type' => 'string' ), 'modified' => array( 'type' => 'string' ),
		'content' => array( 'type' => 'string' ), 'block_count' => array( 'type' => 'integer' ),
		'freeform_count' => array( 'type' => 'integer' ),
		'block_types' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ),
		'primary' => array( 'type' => 'string' ),
		'hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
	);
	return array( 'type' => 'object', 'properties' => $properties, 'required' => array_keys( $properties ), 'additionalProperties' => false );
}

function wpagent_ability_list_block_types(): array {
	return array(
		'label' => __( 'Native Blocktypen auflisten', 'wp-agent-companion' ),
		'description' => __( 'Liest alle tatsächlich registrierten Blocktypen ohne Callbacks, Quellpfade oder Geheimnisattribute.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array(
			'type' => 'object', 'properties' => array(
				'search' => array( 'type' => 'string', 'maxLength' => 100 ),
				'namespace' => array( 'type' => 'string', 'pattern' => '^(?:all|[a-z0-9-]+)$', 'maxLength' => 64, 'default' => 'all' ),
				'page' => array( 'type' => 'integer', 'minimum' => 1, 'default' => 1 ),
				'per_page' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 500, 'default' => 100 ),
			), 'additionalProperties' => false,
		),
		'output_schema' => array(
			'type' => 'object', 'properties' => array(
				'items' => array( 'type' => 'array', 'items' => wpagent_native_block_type_summary_schema() ),
				'total' => array( 'type' => 'integer' ), 'page' => array( 'type' => 'integer' ),
				'per_page' => array( 'type' => 'integer' ), 'pages' => array( 'type' => 'integer' ),
			), 'required' => array( 'items', 'total', 'page', 'per_page', 'pages' ), 'additionalProperties' => false,
		),
		'execute_callback' => 'wpagent_exec_list_block_types', 'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ) ),
	);
}

function wpagent_ability_get_block_type(): array {
	$properties = wpagent_native_block_type_summary_schema()['properties'];
	$properties['description'] = array( 'type' => 'string' );
	$properties['parent'] = array( 'type' => 'array', 'items' => array( 'type' => 'string' ) );
	$properties['ancestor'] = array( 'type' => 'array', 'items' => array( 'type' => 'string' ) );
	$properties['allowed_blocks'] = array( 'type' => 'array', 'items' => array( 'type' => 'string' ) );
	$properties['attributes'] = wpagent_native_block_json_envelope_schema();
	$properties['supports'] = wpagent_native_block_json_envelope_schema();
	return array(
		'label' => __( 'Nativen Blocktyp lesen', 'wp-agent-companion' ),
		'description' => __( 'Liest das serverseitig registrierte Schema eines Blocktyps in einer JSON-sicheren Darstellung.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string', 'pattern' => '^[a-z0-9-]+/[a-z0-9-]+$', 'maxLength' => 200 ) ), 'required' => array( 'name' ), 'additionalProperties' => false ),
		'output_schema' => array( 'type' => 'object', 'properties' => $properties, 'required' => array_keys( $properties ), 'additionalProperties' => false ),
		'execute_callback' => 'wpagent_exec_get_block_type', 'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ) ),
	);
}

function wpagent_ability_get_native_block_document(): array {
	return array(
		'label' => __( 'Natives Blockdokument lesen', 'wp-agent-companion' ),
		'description' => __( 'Liest ein serialisiertes Gutenberg-, Kadence-, GenerateBlocks- oder Spectra-Dokument samt Konflikt-Hash und rekursivem Blockbericht.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'minimum' => 1 ) ), 'required' => array( 'post_id' ), 'additionalProperties' => false ),
		'output_schema' => wpagent_native_block_document_schema(),
		'execute_callback' => 'wpagent_exec_get_native_block_document', 'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ) ),
	);
}

/** @param mixed $input @return array<string,mixed>|WP_Error */
function wpagent_exec_list_block_types( $input ) {
	if ( ! is_array( $input ) || array_diff_key( $input, array_flip( array( 'search', 'namespace', 'page', 'per_page' ) ) ) ) {
		return new WP_Error( 'wpagent_native_block_input', __( 'Ungültiger Block-Namespace.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$search    = $input['search'] ?? '';
	$namespace = $input['namespace'] ?? 'all';
	$page      = $input['page'] ?? 1;
	$per_page  = $input['per_page'] ?? 100;
	if (
		! is_string( $search )
		|| ! is_string( $namespace )
		|| ! is_int( $page )
		|| ! is_int( $per_page )
	) {
		return new WP_Error( 'wpagent_native_block_input', __( 'Ungültiger Block-Namespace.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if (
		wpagent_native_block_string_length( $search ) > 100
		|| $page < 1
		|| $per_page < 1
		|| $per_page > 500
	) {
		return new WP_Error( 'wpagent_native_block_input', __( 'Ungültiger Block-Namespace.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! preg_match( '/^(?:all|[a-z0-9-]+)$/', $namespace ) || strlen( $namespace ) > 64 ) {
		return new WP_Error( 'wpagent_native_block_namespace', __( 'Ungültiger Block-Namespace.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$search     = trim( $search );
	$items      = array();
	foreach ( WP_Block_Type_Registry::get_instance()->get_all_registered() as $type ) {
		if ( ! $type instanceof WP_Block_Type ) continue;
		$summary = wpagent_native_block_type_summary( $type );
		if ( 'all' !== $namespace && $namespace !== $summary['namespace'] ) continue;
		if ( '' !== $search && false === stripos( $summary['name'] . ' ' . $summary['title'], $search ) ) continue;
		$items[] = $summary;
	}
	usort( $items, static fn( array $a, array $b ): int => strcmp( $a['name'], $b['name'] ) );
	$total = count( $items );
	$pages = max( 1, (int) ceil( $total / $per_page ) );
	return array( 'items' => array_slice( $items, ( $page - 1 ) * $per_page, $per_page ), 'total' => $total, 'page' => $page, 'per_page' => $per_page, 'pages' => $pages );
}

/** @param mixed $input @return array<string,mixed>|WP_Error */
function wpagent_exec_get_block_type( $input ) {
	if ( ! is_array( $input ) || array_diff_key( $input, array_flip( array( 'name' ) ) ) || ! isset( $input['name'] ) || ! is_string( $input['name'] ) ) {
		return new WP_Error( 'wpagent_native_block_name', __( 'Ungültiger Blocktyp.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$name = $input['name'];
	if ( strlen( $name ) > 200 || ! preg_match( '/^[a-z0-9-]+\/[a-z0-9-]+$/', $name ) ) {
		return new WP_Error( 'wpagent_native_block_name', __( 'Ungültiger Blocktyp.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$type = WP_Block_Type_Registry::get_instance()->get_registered( $name );
	if ( ! $type instanceof WP_Block_Type ) {
		return new WP_Error( 'wpagent_native_block_type_not_found', __( 'Blocktyp ist auf dieser Website nicht registriert.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	$nodes = 0;
	$raw_attributes = $type->get_attributes();
	$raw_supports = (array) $type->supports;
	if ( ! wpagent_native_block_public_schema_within_json_limits( $raw_attributes, 0, $nodes, 512 )
		|| ! wpagent_native_block_public_schema_within_json_limits( $raw_supports, 0, $nodes, 512 ) ) {
		return new WP_Error( 'wpagent_native_block_schema_bounds', __( 'Der öffentliche Blockvertrag überschreitet die sicheren JSON-Grenzen.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$result = wpagent_native_block_type_summary( $type );
	$result['description'] = wpagent_native_block_public_string( wp_strip_all_tags( (string) $type->description ) ) ?? '';
	$result['parent'] = wpagent_native_block_public_block_names( $type->parent );
	$result['ancestor'] = wpagent_native_block_public_block_names( $type->ancestor );
	$result['allowed_blocks'] = wpagent_native_block_public_block_names( $type->allowed_blocks );
	$attributes = wpagent_native_block_json_envelope( wpagent_native_block_public_attributes( $type ), 512 );
	$supports = wpagent_native_block_json_envelope( (array) wpagent_native_block_public_schema_value( $raw_supports ), 512 );
	if ( is_wp_error( $attributes ) || is_wp_error( $supports ) ) {
		return new WP_Error( 'wpagent_native_block_schema_bounds', __( 'Der öffentliche Blockvertrag überschreitet die sicheren JSON-Grenzen.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$result['attributes'] = $attributes;
	$result['supports'] = $supports;
	return $result;
}

/** @param mixed $input @return array<string,mixed>|WP_Error */
function wpagent_exec_get_native_block_document( $input ) {
	return wpagent_native_block_document_state( is_array( $input ) ? (int) ( $input['post_id'] ?? 0 ) : 0 );
}
