<?php
/** Mutating Site Editor content abilities. */

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

/** @return array<string,mixed> */
function wpagent_site_editor_write_ability( string $label, string $description, array $input, array $output, string $callback, bool $destructive = false, bool $manual_only = false ): array {
	$annotations = array( 'readonly' => false, 'destructive' => $destructive, 'idempotent' => false );
	if ( $manual_only ) {
		$annotations['manual_only'] = true;
	}
	return array(
		'label' => $label, 'description' => $description, 'category' => 'wp-agent',
		'input_schema' => $input, 'output_schema' => $output, 'execute_callback' => $callback,
		'permission_callback' => 'wpagent_perm_edit_theme_options',
		'meta' => array( 'show_in_rest' => true, 'annotations' => $annotations ),
	);
}

/** @return array<string,mixed> */
function wpagent_site_editor_template_write_properties( bool $part ): array {
	$properties = array(
		'slug'        => array( 'type' => 'string', 'maxLength' => 200 ),
		'title'       => array( 'type' => 'string', 'maxLength' => 200 ),
		'description' => array( 'type' => 'string', 'maxLength' => 1000 ),
		'container'   => array( 'type' => 'string', 'enum' => array( 'none', 'group', 'main', 'header', 'footer' ), 'default' => 'none' ),
		'components'  => wpagent_site_editor_components_schema(),
	);
	if ( $part ) $properties['area'] = array( 'type' => 'string', 'maxLength' => 50, 'default' => 'uncategorized' );
	return $properties;
}

/** @return array<string,mixed> */
function wpagent_site_editor_template_create_schema( bool $part ): array {
	return array(
		'type' => 'object', 'properties' => wpagent_site_editor_template_write_properties( $part ),
		'required' => $part ? array( 'slug', 'title', 'area', 'components' ) : array( 'slug', 'title', 'components' ),
		'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_site_editor_template_update_schema( bool $part ): array {
	$properties = wpagent_site_editor_template_write_properties( $part );
	unset( $properties['container']['default'], $properties['area']['default'] );
	$properties['expected_hash']         = array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' );
	$properties['confirm_theme_override'] = array( 'type' => 'boolean', 'default' => false );
	return array( 'type' => 'object', 'properties' => $properties, 'required' => array( 'slug', 'expected_hash' ), 'additionalProperties' => false );
}

/** @return array<string,mixed> */
function wpagent_site_editor_template_write_output_schema(): array {
	return array(
		'type' => 'object', 'properties' => array( 'resource' => wpagent_site_editor_resource_schema(), 'verified' => array( 'type' => 'boolean' ) ),
		'required' => array( 'resource', 'verified' ), 'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_site_editor_reference_schema(): array {
	return array(
		'type' => 'object', 'properties' => array(
			'items' => array( 'type' => 'array', 'maxItems' => 100, 'items' => array(
				'type' => 'object', 'properties' => array(
					'post_id' => array( 'type' => 'integer' ), 'post_type' => array( 'type' => 'string', 'maxLength' => 20 ), 'title' => array( 'type' => 'string', 'maxLength' => 500 ),
				), 'required' => array( 'post_id', 'post_type', 'title' ), 'additionalProperties' => false,
			) ),
			'truncated' => array( 'type' => 'boolean' ),
		), 'required' => array( 'items', 'truncated' ), 'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_site_editor_template_delete_schema(): array {
	return array(
		'type' => 'object', 'properties' => array(
			'slug' => array( 'type' => 'string', 'maxLength' => 200 ),
			'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			'confirm' => array( 'type' => 'boolean' ), 'confirm_references' => array( 'type' => 'boolean', 'default' => false ),
		), 'required' => array( 'slug', 'expected_hash', 'confirm' ), 'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_site_editor_template_delete_output_schema(): array {
	return array(
		'type' => 'object', 'properties' => array(
			'deleted' => array( 'type' => 'boolean' ), 'reverted_to_theme' => array( 'type' => 'boolean' ),
			'reverted_to_source' => array( 'type' => 'boolean' ),
			'resource' => array( 'type' => array( 'object', 'null' ), 'properties' => wpagent_site_editor_resource_schema()['properties'], 'additionalProperties' => false ),
			'references' => wpagent_site_editor_reference_schema(),
		), 'required' => array( 'deleted', 'reverted_to_theme', 'reverted_to_source', 'resource', 'references' ), 'additionalProperties' => false,
	);
}

function wpagent_ability_create_site_template(): array {
	return wpagent_site_editor_write_ability( __( 'Site-Template erstellen', 'wp-agent-companion' ), __( 'Erstellt ein eigenes Blocktemplate aus strukturierten, serverseitig serialisierten Core-Komponenten und verifiziert den gespeicherten Zustand.', 'wp-agent-companion' ), wpagent_site_editor_template_create_schema( false ), wpagent_site_editor_template_write_output_schema(), 'wpagent_exec_create_site_template' );
}
function wpagent_ability_update_site_template(): array {
	return wpagent_site_editor_write_ability( __( 'Site-Template aktualisieren', 'wp-agent-companion' ), __( 'Aktualisiert ein Blocktemplate mit CAS-Schutz. Theme- und Plugin-Vorlagen benötigen eine ausdrückliche Überschreibungsbestätigung.', 'wp-agent-companion' ), wpagent_site_editor_template_update_schema( false ), wpagent_site_editor_template_write_output_schema(), 'wpagent_exec_update_site_template' );
}
function wpagent_ability_delete_site_template(): array {
	return wpagent_site_editor_write_ability( __( 'Site-Template löschen oder zurücksetzen', 'wp-agent-companion' ), __( 'Prüft ein Site-Template per Hash und verweist für die Löschung in den Website-Editor. Remote-Löschungen sind deaktiviert, weil WordPress keine atomare Compare-and-Delete-Operation gegen parallele Editor-Änderungen bietet.', 'wp-agent-companion' ), wpagent_site_editor_template_delete_schema(), wpagent_site_editor_template_delete_output_schema(), 'wpagent_exec_delete_site_template', true, true );
}
function wpagent_ability_create_template_part(): array {
	return wpagent_site_editor_write_ability( __( 'Template-Teil erstellen', 'wp-agent-companion' ), __( 'Erstellt ein Template-Teil aus strukturierten Core-Komponenten, ordnet seinen Bereich zu und verifiziert den gespeicherten Zustand.', 'wp-agent-companion' ), wpagent_site_editor_template_create_schema( true ), wpagent_site_editor_template_write_output_schema(), 'wpagent_exec_create_template_part' );
}
function wpagent_ability_update_template_part(): array {
	return wpagent_site_editor_write_ability( __( 'Template-Teil aktualisieren', 'wp-agent-companion' ), __( 'Aktualisiert ein Template-Teil mit CAS-Schutz und sicherer Theme-Überschreibung.', 'wp-agent-companion' ), wpagent_site_editor_template_update_schema( true ), wpagent_site_editor_template_write_output_schema(), 'wpagent_exec_update_template_part' );
}
function wpagent_ability_delete_template_part(): array {
	return wpagent_site_editor_write_ability( __( 'Template-Teil löschen oder zurücksetzen', 'wp-agent-companion' ), __( 'Prüft ein Template-Teil und seine Referenzen per Hash und verweist für die Löschung in den Website-Editor. Remote-Löschungen sind deaktiviert, weil WordPress keine atomare Compare-and-Delete-Operation gegen parallele Editor-Änderungen bietet.', 'wp-agent-companion' ), wpagent_site_editor_template_delete_schema(), wpagent_site_editor_template_delete_output_schema(), 'wpagent_exec_delete_template_part', true, true );
}

/** @return array<string,mixed> */
function wpagent_site_editor_navigation_items_schema(): array {
	return array( 'type' => 'array', 'minItems' => 1, 'maxItems' => WPAGENT_SITE_EDITOR_MAX_NAV_ITEMS, 'items' => wpagent_site_editor_nav_item_schema() );
}

function wpagent_ability_create_block_navigation(): array {
	return wpagent_site_editor_write_ability(
		__( 'Blocknavigation erstellen', 'wp-agent-companion' ), __( 'Erstellt eine veröffentlichte Blocknavigation aus sicheren internen, externen und verschachtelten Einträgen.', 'wp-agent-companion' ),
		array( 'type' => 'object', 'properties' => array( 'title' => array( 'type' => 'string', 'maxLength' => 200 ), 'items' => wpagent_site_editor_navigation_items_schema() ), 'required' => array( 'title', 'items' ), 'additionalProperties' => false ),
		array( 'type' => 'object', 'properties' => array( 'navigation' => wpagent_site_editor_navigation_schema(), 'verified' => array( 'type' => 'boolean' ) ), 'required' => array( 'navigation', 'verified' ), 'additionalProperties' => false ),
		'wpagent_exec_create_block_navigation'
	);
}

function wpagent_ability_update_block_navigation(): array {
	return wpagent_site_editor_write_ability(
		__( 'Blocknavigation aktualisieren', 'wp-agent-companion' ), __( 'Aktualisiert Titel oder Einträge einer Blocknavigation mit CAS-Schutz, Sperre, Revision und Read-Back-Verifikation.', 'wp-agent-companion' ),
		array( 'type' => 'object', 'properties' => array(
			'id' => array( 'type' => 'integer', 'minimum' => 1 ), 'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			'title' => array( 'type' => 'string', 'maxLength' => 200 ), 'items' => wpagent_site_editor_navigation_items_schema(),
		), 'required' => array( 'id', 'expected_hash' ), 'additionalProperties' => false ),
		array( 'type' => 'object', 'properties' => array( 'navigation' => wpagent_site_editor_navigation_schema(), 'verified' => array( 'type' => 'boolean' ) ), 'required' => array( 'navigation', 'verified' ), 'additionalProperties' => false ),
		'wpagent_exec_update_block_navigation'
	);
}

function wpagent_ability_delete_block_navigation(): array {
	return wpagent_site_editor_write_ability(
		__( 'Blocknavigation löschen', 'wp-agent-companion' ), __( 'Prüft eine Blocknavigation und ihre Referenzen per Hash und verweist für die Löschung in den Website-Editor. Remote-Löschungen sind deaktiviert, weil WordPress keine atomare Compare-and-Delete-Operation gegen parallele Editor-Änderungen bietet.', 'wp-agent-companion' ),
		array( 'type' => 'object', 'properties' => array(
			'id' => array( 'type' => 'integer', 'minimum' => 1 ), 'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			'confirm' => array( 'type' => 'boolean' ), 'confirm_references' => array( 'type' => 'boolean', 'default' => false ),
		), 'required' => array( 'id', 'expected_hash', 'confirm' ), 'additionalProperties' => false ),
		array( 'type' => 'object', 'properties' => array( 'deleted' => array( 'type' => 'boolean' ), 'references' => wpagent_site_editor_reference_schema() ), 'required' => array( 'deleted', 'references' ), 'additionalProperties' => false ),
		'wpagent_exec_delete_block_navigation', true, true
	);
}

function wpagent_exec_create_site_template( $input ) { return wpagent_site_editor_create_template( 'wp_template', is_array( $input ) ? $input : array() ); }
function wpagent_exec_update_site_template( $input ) { return wpagent_site_editor_update_template( 'wp_template', is_array( $input ) ? $input : array() ); }
function wpagent_exec_delete_site_template( $input ) { return wpagent_site_editor_delete_template( 'wp_template', is_array( $input ) ? $input : array() ); }
function wpagent_exec_create_template_part( $input ) { return wpagent_site_editor_create_template( 'wp_template_part', is_array( $input ) ? $input : array() ); }
function wpagent_exec_update_template_part( $input ) { return wpagent_site_editor_update_template( 'wp_template_part', is_array( $input ) ? $input : array() ); }
function wpagent_exec_delete_template_part( $input ) { return wpagent_site_editor_delete_template( 'wp_template_part', is_array( $input ) ? $input : array() ); }

/** @return array<string,mixed>|WP_Error */
function wpagent_site_editor_create_template( string $type, array $input ) {
	$block_theme = wpagent_site_editor_require_block_theme();
	if ( is_wp_error( $block_theme ) ) return $block_theme;
	$slug = wpagent_site_editor_slug( $input['slug'] ?? '' );
	$title = wpagent_site_editor_plain_text( $input['title'] ?? '', 'title', 200 );
	$description = wpagent_site_editor_plain_text( $input['description'] ?? '', 'description', 1000, true );
	foreach ( array( $slug, $title, $description ) as $value ) if ( is_wp_error( $value ) ) return $value;
	$area = null;
	if ( 'wp_template_part' === $type ) {
		$area = wpagent_site_editor_validate_area( $input['area'] ?? '' );
		if ( is_wp_error( $area ) ) return $area;
	}
	$content = wpagent_site_editor_build_content( $input['components'] ?? null, (string) ( $input['container'] ?? 'none' ) );
	if ( is_wp_error( $content ) ) return $content;
	$valid = wpagent_site_editor_validate_content( $content, wpagent_site_editor_validation_root( $type, $slug ) );
	if ( is_wp_error( $valid ) ) return $valid;
	// Creation, update, and deletion must serialize on the same resource key.
	// A separate "create" key would allow an update to race the create read-back.
	$lock_key = $slug;
	$lock = wpagent_site_editor_acquire_lock( $type, $lock_key );
	if ( is_wp_error( $lock ) ) return $lock;
	try {
		if ( get_block_template( wpagent_site_editor_template_id( $slug ), $type ) ) {
			return new WP_Error( 'wpagent_site_editor_exists', __( 'Unter diesem Slug existiert bereits eine Theme-, Plugin- oder Datenbankvorlage.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$request = new WP_REST_Request( 'POST', '' );
		$request->set_body_params( array( 'slug' => $slug, 'theme' => get_stylesheet(), 'title' => $title, 'description' => $description, 'content' => $content, 'area' => $area ) );
		$prepared = (object) array(
			'post_name' => $slug, 'post_type' => $type, 'post_status' => 'publish', 'post_author' => get_current_user_id(),
			'post_title' => $title, 'post_excerpt' => $description, 'post_content' => $content,
			'tax_input' => array_filter(
				array( 'wp_theme' => get_stylesheet(), 'wp_template_part_area' => $area ),
				static fn( $value ): bool => null !== $value
			),
		);
		$prepared = apply_filters( "rest_pre_insert_{$type}", $prepared, $request );
		if ( is_wp_error( $prepared ) ) return $prepared;
		if ( ! is_object( $prepared ) ) {
			return new WP_Error( 'wpagent_site_editor_filter_result', __( 'Der WordPress-Vorbereitungsfilter lieferte keinen gültigen Beitragsdatensatz.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		$content = (string) ( $prepared->post_content ?? '' );
		$title = wpagent_site_editor_plain_text( $prepared->post_title ?? '', 'title', 200 );
		$description = wpagent_site_editor_plain_text( $prepared->post_excerpt ?? '', 'description', 1000, true );
		foreach ( array( $title, $description ) as $value ) if ( is_wp_error( $value ) ) return $value;
		$valid = wpagent_site_editor_validate_content( $content, wpagent_site_editor_validation_root( $type, $slug ) );
		if ( is_wp_error( $valid ) ) return $valid;
			$insert = array(
			'post_name' => $slug, 'post_type' => $type, 'post_status' => 'publish', 'post_author' => get_current_user_id(),
			'post_title' => $title, 'post_excerpt' => $description, 'post_content' => $content,
			);
			$owner_id = get_current_user_id();
			$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
			if ( is_wp_error( $guard ) ) return $guard;
			$post_id = wp_insert_post( wp_slash( $insert ), true );
			if ( is_wp_error( $post_id ) ) return $post_id;
			$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
			if ( is_wp_error( $guard ) ) return $guard;
			$candidate = wpagent_core_mutator_created_post_candidate( $post_id, $type, $owner_id );
			if ( ! is_array( $candidate ) ) return wpagent_core_mutator_created_post_recovery_required( $post_id, $type, $owner_id, 'site_editor_template_create_candidate_unreadable' );
			$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
			if ( is_wp_error( $guard ) ) return $guard;
			$theme_terms = wp_set_object_terms( $post_id, get_stylesheet(), 'wp_theme', false );
			if ( ! is_wp_error( $theme_terms ) ) {
				$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
				if ( is_wp_error( $guard ) ) return $guard;
				$candidate = wpagent_core_mutator_created_post_candidate( $post_id, $type, $owner_id );
				if ( ! is_array( $candidate ) ) return wpagent_core_mutator_created_post_recovery_required( $post_id, $type, $owner_id, 'site_editor_template_theme_candidate_unreadable' );
			}
			$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
			if ( is_wp_error( $guard ) ) return $guard;
			$area_terms = null === $area ? array() : wp_set_object_terms( $post_id, $area, 'wp_template_part_area', false );
			if ( is_wp_error( $theme_terms ) || is_wp_error( $area_terms ) ) {
				$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
				if ( is_wp_error( $guard ) ) return $guard;
				if ( wpagent_core_mutator_delete_created_post_if_current( $candidate, $type, $owner_id ) ) {
				return is_wp_error( $theme_terms ) ? $theme_terms : $area_terms;
			}
			return wpagent_core_mutator_created_post_recovery_required( $post_id, $type, $owner_id, 'site_editor_template_terms_cleanup_drift' );
			}
			$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
			if ( is_wp_error( $guard ) ) return $guard;
			$candidate = wpagent_core_mutator_created_post_candidate( $post_id, $type, $owner_id );
		if ( ! is_array( $candidate ) ) return wpagent_core_mutator_created_post_recovery_required( $post_id, $type, $owner_id, 'site_editor_template_final_terms_candidate_unreadable' );
			$post = get_post( $post_id );
			if ( $post instanceof WP_Post ) {
				$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
				if ( is_wp_error( $guard ) ) return $guard;
				do_action( "rest_after_insert_{$type}", $post, $request, true );
			}
			$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
			if ( is_wp_error( $guard ) ) return $guard;
			$fresh = wpagent_site_editor_get_template( $type, $slug );
			$verified = ! is_wp_error( $fresh ) && wpagent_site_editor_template_matches( $fresh, $title, $description, $content, $area );
			if ( ! $verified ) {
				$guard = wpagent_site_editor_update_guard( $type, $lock_key, $lock );
				if ( is_wp_error( $guard ) ) return $guard;
				if ( wpagent_core_mutator_delete_created_post_if_current( $candidate, $type, $owner_id ) ) {
				return new WP_Error( 'wpagent_site_editor_create_verify', __( 'Die Site-Editor-Ressource wich beim Read-Back ab. Der unveränderte Create-Kandidat wurde entfernt.', 'wp-agent-companion' ), array( 'status' => 500, 'rollback_verified' => true ) );
			}
			return wpagent_core_mutator_created_post_recovery_required( $post_id, $type, $owner_id, 'site_editor_template_create_readback_mismatch' );
		}
		return array( 'resource' => wpagent_site_editor_resource_state( $fresh ), 'verified' => true );
	} finally {
		wpagent_site_editor_release_lock( $type, $lock_key, $lock );
	}
}

/** @return true|WP_Error */
function wpagent_site_editor_update_guard( string $kind, string $identity, string $lock ) {
	if ( ! current_user_can( 'edit_theme_options' ) ) {
		return new WP_Error( 'wpagent_site_editor_capability_changed', __( 'Die Berechtigung edit_theme_options ging während der Site-Editor-Aktualisierung verloren.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( ! wpagent_site_editor_refresh_lock( $kind, $identity, $lock ) || ! wpagent_site_editor_lock_is_owned( $kind, $identity, $lock ) ) {
		return new WP_Error( 'wpagent_site_editor_lock_lost', __( 'Die Site-Editor-Sperre ging während der Aktualisierung verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return true;
}

/** @return WP_Error */
function wpagent_site_editor_candidate_recovery_required( string $reason, $original_error = null ): WP_Error {
	$data = array( 'status' => 500, 'recovery_required' => true, 'rollback_verified' => false, 'reason' => $reason );
	if ( $original_error instanceof WP_Error ) {
		$data['original_error'] = $original_error->get_error_code();
	}
	return new WP_Error( 'wpagent_site_editor_recovery_required', __( 'Die Site-Editor-Aktualisierung konnte nicht vollständig verifiziert oder sicher zurückgesetzt werden. Der aktuelle Zustand wird bewahrt und muss manuell geprüft werden.', 'wp-agent-companion' ), $data );
}

/** @return array{resource_hash:string,post:array<string,mixed>,post_candidate:array{post_id:int,hash:string}}|null */
function wpagent_site_editor_template_update_candidate( WP_Block_Template $template ): ?array {
	$post_id = (int) $template->wp_id;
	$post    = $post_id > 0 ? get_post( $post_id, ARRAY_A ) : null;
	if ( ! is_array( $post ) || $post_id !== (int) ( $post['ID'] ?? 0 ) || (string) $template->type !== (string) ( $post['post_type'] ?? '' ) ) {
		return null;
	}
	$post_candidate = wpagent_core_mutator_created_post_candidate( $post_id, (string) $template->type, (int) ( $post['post_author'] ?? 0 ) );
	if ( ! is_array( $post_candidate ) ) {
		return null;
	}
	return array( 'resource_hash' => wpagent_site_editor_resource_state( $template )['hash'], 'post' => $post, 'post_candidate' => $post_candidate );
}

/** @return array{state_hash:string,post:array<string,mixed>,post_candidate:array{post_id:int,hash:string}}|null */
function wpagent_site_editor_navigation_update_candidate( int $post_id ) {
	$post  = get_post( $post_id, ARRAY_A );
	$state = wpagent_site_editor_navigation_state( $post_id );
	if ( ! is_array( $post ) || is_wp_error( $state ) || 'wp_navigation' !== (string) ( $post['post_type'] ?? '' ) ) {
		return null;
	}
	$post_candidate = wpagent_core_mutator_created_post_candidate( $post_id, 'wp_navigation', (int) ( $post['post_author'] ?? 0 ) );
	if ( ! is_array( $post_candidate ) ) {
		return null;
	}
	return array( 'state_hash' => $state['hash'], 'post' => $post, 'post_candidate' => $post_candidate );
}

/** @return array<int,string> */
function wpagent_site_editor_post_record_fields(): array {
	return array( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_excerpt', 'post_status', 'comment_status', 'ping_status', 'post_password', 'post_name', 'to_ping', 'pinged', 'post_modified', 'post_modified_gmt', 'post_content_filtered', 'post_parent', 'guid', 'menu_order', 'post_type', 'post_mime_type', 'comment_count' );
}

/**
 * Write a complete post row only when the persisted row still exactly matches
 * the expected snapshot. wp_update_post() has no compare-and-swap predicate
 * and could overwrite a wp-admin edit that arrived after the hash preflight.
 *
 * @param array<string,mixed> $expected
 * @param array<string,mixed> $next
 */
function wpagent_site_editor_conditional_post_update_if_current( array $expected, array $next ): bool {
	$id = absint( $expected['ID'] ?? 0 );
	if ( ! $id || $id !== absint( $next['ID'] ?? 0 ) ) {
		return false;
	}
	$fields = wpagent_site_editor_post_record_fields();
	foreach ( $fields as $field ) {
		if ( ! array_key_exists( $field, $expected ) || ! array_key_exists( $field, $next ) ) {
			return false;
		}
	}
	global $wpdb;
	$sets   = array();
	$where  = array( 'ID = %d' );
	$params = array();
	foreach ( $fields as $field ) {
		$sets[]    = "`{$field}` = %s";
		$params[]  = (string) $next[ $field ];
	}
	$params[] = $id;
	foreach ( $fields as $field ) {
		$where[]   = "HEX(CAST(`{$field}` AS CHAR)) = HEX(CAST(%s AS CHAR))";
		$params[]  = (string) $expected[ $field ];
	}
	$updated = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET " . implode( ', ', $sets ) . ' WHERE ' . implode( ' AND ', $where ), ...$params ) );
	if ( false === $updated ) {
		return false;
	}
	clean_post_cache( $id );
	$restored = get_post( $id, ARRAY_A );
	if ( ! is_array( $restored ) ) {
		return false;
	}
	foreach ( $fields as $field ) {
		if ( (string) $next[ $field ] !== (string) ( $restored[ $field ] ?? '' ) ) {
			return false;
		}
	}
	return true;
}

/**
 * Restore only an exact candidate. This is a directionally named wrapper for
 * the shared conditional writer used by both recovery and normal updates.
 *
 * @param array<string,mixed> $before
 * @param array<string,mixed> $candidate
 */
function wpagent_site_editor_restore_post_record_if_current( array $before, array $candidate ): bool {
	return wpagent_site_editor_conditional_post_update_if_current( $candidate, $before );
}

/** @return WP_Error */
function wpagent_site_editor_manual_update_error( string $current_hash ): WP_Error {
	return new WP_Error(
		'wpagent_site_editor_manual_update_required',
		__( 'Diese Site-Editor-Ressource kann nicht atomar gegen parallele Website-Editor-Änderungen aktualisiert werden. Prüfe den aktuellen Zustand erneut und nimm die Änderung manuell im Website-Editor vor.', 'wp-agent-companion' ),
		array( 'status' => 409, 'current_hash' => $current_hash, 'manual_action_required' => true )
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_site_editor_update_template( string $type, array $input ) {
	$block_theme = wpagent_site_editor_require_block_theme();
	if ( is_wp_error( $block_theme ) ) return $block_theme;
	$slug = wpagent_site_editor_slug( $input['slug'] ?? '' );
	if ( is_wp_error( $slug ) ) return $slug;
	$expected = wpagent_site_editor_expected_hash( $input['expected_hash'] ?? '' );
	if ( is_wp_error( $expected ) ) return $expected;
	$lock = wpagent_site_editor_acquire_lock( $type, $slug );
	if ( is_wp_error( $lock ) ) return $lock;
	try {
		$template = wpagent_site_editor_get_template( $type, $slug );
		if ( is_wp_error( $template ) ) return $template;
		$before = wpagent_site_editor_resource_state( $template );
		if ( ! hash_equals( $before['hash'], $expected ) ) return new WP_Error( 'wpagent_site_editor_conflict', __( 'Die Site-Editor-Ressource wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['hash'] ) );
		if ( 'custom' !== $template->source && empty( $input['confirm_theme_override'] ) ) return new WP_Error( 'wpagent_site_editor_override_confirmation', __( 'Eine Theme- oder Plugin-Vorlage darf nur mit confirm_theme_override=true überschrieben werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$mutable = array_intersect( array_keys( $input ), array( 'title', 'description', 'area', 'components' ) );
		if ( array() === $mutable ) return new WP_Error( 'wpagent_site_editor_no_changes', __( 'Es wurde keine Änderung angegeben.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$title = isset( $input['title'] ) ? wpagent_site_editor_plain_text( $input['title'], 'title', 200 ) : $before['title'];
		$description = isset( $input['description'] ) ? wpagent_site_editor_plain_text( $input['description'], 'description', 1000, true ) : $before['description'];
		foreach ( array( $title, $description ) as $value ) if ( is_wp_error( $value ) ) return $value;
		$area = 'wp_template_part' === $type ? $before['area'] : null;
		if ( 'wp_template_part' === $type && isset( $input['area'] ) ) {
			$area = wpagent_site_editor_validate_area( $input['area'] );
			if ( is_wp_error( $area ) ) return $area;
		}
		if ( isset( $input['container'] ) && ! isset( $input['components'] ) ) return new WP_Error( 'wpagent_site_editor_container_without_components', __( 'container kann nur zusammen mit components geändert werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$content = isset( $input['components'] ) ? wpagent_site_editor_build_content( $input['components'], (string) ( $input['container'] ?? 'none' ) ) : $before['content'];
		if ( is_wp_error( $content ) ) return $content;
		$valid = wpagent_site_editor_validate_content( $content, wpagent_site_editor_validation_root( $type, $slug ) );
		if ( is_wp_error( $valid ) ) return $valid;
		$fresh_before_write = wpagent_site_editor_get_template( $type, $slug );
		if ( is_wp_error( $fresh_before_write ) ) return $fresh_before_write;
		$fresh_before_state = wpagent_site_editor_resource_state( $fresh_before_write );
		if ( ! hash_equals( $expected, $fresh_before_state['hash'] ) ) {
			return new WP_Error( 'wpagent_site_editor_conflict', __( 'Die Site-Editor-Ressource wurde unmittelbar vor dem Schreiben verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $fresh_before_state['hash'] ) );
		}
		if ( 'custom' !== $fresh_before_write->source || ! $fresh_before_write->wp_id || ( 'wp_template_part' === $type && $area !== $fresh_before_state['area'] ) ) {
			return wpagent_site_editor_manual_update_error( $fresh_before_state['hash'] );
		}
		$snapshot = wpagent_site_editor_template_snapshot( $fresh_before_write );
		if ( ! is_array( $snapshot ) || ! is_array( $snapshot['post'] ?? null ) ) return new WP_Error( 'wpagent_site_editor_snapshot_failed', __( 'Der vorherige Template-Zustand konnte nicht für einen sicheren Compare-and-Swap gelesen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		$next_post                         = $snapshot['post'];
		$next_post['post_title']           = $title;
		$next_post['post_excerpt']         = $description;
		$next_post['post_content']         = $content;
		$next_post['post_modified']        = current_time( 'mysql' );
		$next_post['post_modified_gmt']    = current_time( 'mysql', true );
		if ( $next_post['post_title'] === $snapshot['post']['post_title'] && $next_post['post_excerpt'] === $snapshot['post']['post_excerpt'] && $next_post['post_content'] === $snapshot['post']['post_content'] ) {
			return array( 'resource' => $fresh_before_state, 'verified' => true );
		}
		$guard = wpagent_site_editor_update_guard( $type, $slug, $lock );
		if ( is_wp_error( $guard ) ) return $guard;
		do_action( 'wpagent_site_editor_before_conditional_update', 'template', (int) $fresh_before_write->wp_id );
		if ( ! wpagent_site_editor_conditional_post_update_if_current( $snapshot['post'], $next_post ) ) {
			$current = wpagent_site_editor_get_template( $type, $slug );
			$current_hash = is_wp_error( $current ) ? '' : wpagent_site_editor_resource_state( $current )['hash'];
			return new WP_Error( 'wpagent_site_editor_conflict', __( 'Die Site-Editor-Ressource wurde während des atomaren Schreibens verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $current_hash ) );
		}
		$post_after = get_post( (int) $fresh_before_write->wp_id );
		if ( $post_after instanceof WP_Post ) {
			do_action( 'wp_after_insert_post', (int) $post_after->ID, $post_after, true, null );
			do_action( "rest_after_insert_{$type}", $post_after, new WP_REST_Request( 'PUT', '' ), false );
		}
		$fresh = wpagent_site_editor_get_template( $type, $slug );
		$guard = wpagent_site_editor_update_guard( $type, $slug, $lock );
		if ( is_wp_error( $guard ) || is_wp_error( $fresh ) ) {
			return wpagent_site_editor_candidate_recovery_required( 'template_readback_guard_or_candidate_failed' );
		}
		if ( ! wpagent_site_editor_template_matches( $fresh, $title, $description, $content, $area ) ) {
			// A mismatching readback is not an owned candidate: it may include a
			// wp-admin or provider mutation between the write and our first read.
			// Preserve it for manual recovery instead of guessing a rollback.
			return wpagent_site_editor_candidate_recovery_required( 'template_readback_mismatch' );
		}
		return array( 'resource' => wpagent_site_editor_resource_state( $fresh ), 'verified' => true );
	} finally {
		wpagent_site_editor_release_lock( $type, $slug, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_site_editor_delete_template( string $type, array $input ) {
	$block_theme = wpagent_site_editor_require_block_theme();
	if ( is_wp_error( $block_theme ) ) return $block_theme;
	$slug = wpagent_site_editor_slug( $input['slug'] ?? '' );
	if ( is_wp_error( $slug ) ) return $slug;
	$expected = wpagent_site_editor_expected_hash( $input['expected_hash'] ?? '' );
	if ( is_wp_error( $expected ) ) return $expected;
	if ( true !== ( $input['confirm'] ?? false ) ) return new WP_Error( 'wpagent_site_editor_delete_confirmation', __( 'Zum Löschen ist confirm=true erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$template = wpagent_site_editor_get_template( $type, $slug );
	if ( is_wp_error( $template ) ) return $template;
	$before = wpagent_site_editor_resource_state( $template );
	if ( ! hash_equals( $before['hash'], $expected ) ) return new WP_Error( 'wpagent_site_editor_conflict', __( 'Die Site-Editor-Ressource wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['hash'] ) );
	$references = 'wp_template_part' === $type ? wpagent_site_editor_find_references( 'template_part', $slug ) : array( 'items' => array(), 'truncated' => false );
	if ( is_wp_error( $references ) ) return $references;
	return wpagent_site_editor_manual_delete_error( $before['hash'], $references );
}

/** @param array<string,mixed> $references */
function wpagent_site_editor_manual_delete_error( string $current_hash, array $references = array( 'items' => array(), 'truncated' => false ) ): WP_Error {
	return new WP_Error(
		'wpagent_site_editor_manual_delete_required',
		__( 'Die Ressource wird nicht remote gelöscht, weil WordPress keine atomare Compare-and-Delete-Operation gegen parallele Änderungen im Website-Editor bietet. Prüfe den aktuellen Zustand erneut und lösche die Ressource anschließend manuell im Website-Editor.', 'wp-agent-companion' ),
		array( 'status' => 409, 'current_hash' => $current_hash, 'manual_action_required' => true, 'references' => $references )
	);
}

/** @return string|WP_Error */
function wpagent_site_editor_validate_area( $value ) {
	$area = is_string( $value ) ? sanitize_key( $value ) : '';
	$allowed = array( WP_TEMPLATE_PART_AREA_UNCATEGORIZED );
	foreach ( get_allowed_block_template_part_areas() as $item ) if ( is_string( $item['area'] ?? null ) ) $allowed[] = $item['area'];
	if ( '' === $area || ! in_array( $area, array_unique( $allowed ), true ) ) return new WP_Error( 'wpagent_site_editor_area', __( 'area ist kein registrierter Template-Teil-Bereich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	return $area;
}

/** @return string|WP_Error */
function wpagent_site_editor_expected_hash( $value ) {
	$hash = is_string( $value ) ? $value : '';
	return preg_match( '/^[a-f0-9]{64}$/', $hash ) ? $hash : new WP_Error( 'wpagent_site_editor_hash', __( 'expected_hash muss ein SHA-256-Hash sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
}

function wpagent_site_editor_template_matches( WP_Block_Template $template, string $title, string $description, string $content, ?string $area ): bool {
	$raw_valid = wpagent_site_editor_validate_content(
		wpagent_site_editor_template_raw_content( $template ),
		wpagent_site_editor_validation_root( (string) $template->type, (string) $template->slug )
	);
	$effective_valid = wpagent_site_editor_validate_content(
		(string) $template->content,
		wpagent_site_editor_validation_root( (string) $template->type, (string) $template->slug )
	);
	return 'custom' === $template->source && $title === (string) $template->title && $description === (string) $template->description
		&& $content === wpagent_site_editor_template_raw_content( $template ) && ! is_wp_error( $raw_valid ) && ! is_wp_error( $effective_valid )
		&& ( 'wp_template_part' !== $template->type || $area === (string) $template->area );
}

/** @return array<string,mixed>|null */
function wpagent_site_editor_template_snapshot( WP_Block_Template $template ): ?array {
	if ( ! $template->wp_id ) return null;
	$post = get_post( $template->wp_id, ARRAY_A );
	if ( ! is_array( $post ) ) return null;
	$theme = wp_get_object_terms( $template->wp_id, 'wp_theme', array( 'fields' => 'slugs' ) );
	$area = wp_get_object_terms( $template->wp_id, 'wp_template_part_area', array( 'fields' => 'slugs' ) );
	if ( is_wp_error( $theme ) || is_wp_error( $area ) ) return null;
	sort( $theme, SORT_STRING );
	sort( $area, SORT_STRING );
	return array( 'post' => $post, 'theme' => $theme, 'area' => $area, 'origin' => get_post_meta( $template->wp_id, 'origin', true ), 'state_hash' => wpagent_site_editor_resource_state( $template )['hash'] );
}

/** @return array<int,string>|null */
function wpagent_site_editor_template_term_slugs( int $post_id, string $taxonomy ): ?array {
	$terms = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'slugs' ) );
	if ( is_wp_error( $terms ) || ! is_array( $terms ) || array_filter( $terms, 'is_string' ) !== $terms ) return null;
	$terms = array_values( $terms );
	sort( $terms, SORT_STRING );
	return $terms;
}

/**
 * A recovery must not reapply a stale snapshot. It may only restore the exact
 * complete provider candidate just written by this request. Taxonomy and origin
 * changes deliberately decline automatic recovery because Core exposes no
 * atomic compare-and-replace primitive for those records.
 *
 * @param array<string,mixed>|null $snapshot
 * @param array{resource_hash:string,post:array<string,mixed>,post_candidate:array{post_id:int,hash:string}} $candidate
 */
function wpagent_site_editor_restore_template_snapshot_if_current( string $type, string $slug, ?array $snapshot, array $candidate, string $lock ): bool {
	if ( ! is_array( $snapshot ) || ! is_array( $snapshot['post'] ?? null ) || ! is_string( $snapshot['state_hash'] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/', $snapshot['state_hash'] ) ) {
		return false;
	}
	$guard = wpagent_site_editor_update_guard( $type, $slug, $lock );
	if ( is_wp_error( $guard ) ) return false;
	$current = wpagent_site_editor_get_template( $type, $slug );
	if ( is_wp_error( $current ) || ! $current->wp_id || ! hash_equals( $candidate['resource_hash'], wpagent_site_editor_resource_state( $current )['hash'] ) ) return false;
	$current_candidate = wpagent_site_editor_template_update_candidate( $current );
	if ( ! is_array( $current_candidate ) || ! hash_equals( $candidate['post_candidate']['hash'], $current_candidate['post_candidate']['hash'] ) ) return false;
	$post_id = (int) $current->wp_id;
	if ( wpagent_site_editor_template_term_slugs( $post_id, 'wp_theme' ) !== $snapshot['theme'] ) return false;
	if ( 'wp_template_part' === $type && wpagent_site_editor_template_term_slugs( $post_id, 'wp_template_part_area' ) !== $snapshot['area'] ) return false;
	if ( (string) get_post_meta( $post_id, 'origin', true ) !== (string) $snapshot['origin'] ) return false;
	$guard = wpagent_site_editor_update_guard( $type, $slug, $lock );
	if ( is_wp_error( $guard ) || ! wpagent_site_editor_restore_post_record_if_current( $snapshot['post'], $candidate['post'] ) ) return false;
	$restored = wpagent_site_editor_get_template( $type, $slug );
	return ! is_wp_error( $restored ) && hash_equals( $snapshot['state_hash'], wpagent_site_editor_resource_state( $restored )['hash'] );
}

/**
 * The Spectra import only compensates resources it created in the same request.
 * The re-read state and full Core candidate must still match before deletion.
 */
function wpagent_site_editor_delete_created_template_if_current( string $type, string $slug, string $expected_hash, string $lock ): bool {
	$guard = wpagent_site_editor_update_guard( $type, $slug, $lock );
	if ( is_wp_error( $guard ) ) return false;
	$template = wpagent_site_editor_get_template( $type, $slug );
	if ( is_wp_error( $template ) || 'custom' !== $template->source || ! $template->wp_id || ! hash_equals( $expected_hash, wpagent_site_editor_resource_state( $template )['hash'] ) ) return false;
	$post = get_post( $template->wp_id );
	if ( ! $post instanceof WP_Post ) return false;
	$candidate = wpagent_core_mutator_created_post_candidate( (int) $template->wp_id, $type, (int) $post->post_author );
	$guard = wpagent_site_editor_update_guard( $type, $slug, $lock );
	return ! is_wp_error( $guard ) && wpagent_core_mutator_delete_created_post_if_current( $candidate, $type, (int) $post->post_author );
}

/**
 * Navigation has no taxonomy restore. A conditional post-row CAS is sufficient
 * after the complete state and lock/capability have been freshly proven.
 *
 * @param array<string,mixed> $snapshot
 * @param array{state_hash:string,post:array<string,mixed>,post_candidate:array{post_id:int,hash:string}} $candidate
 */
function wpagent_site_editor_restore_navigation_snapshot_if_current( int $post_id, array $snapshot, array $candidate, string $lock ): bool {
	$guard = wpagent_site_editor_update_guard( 'wp_navigation', (string) $post_id, $lock );
	if ( is_wp_error( $guard ) || ! is_array( $snapshot['post'] ?? null ) || ! is_string( $snapshot['state_hash'] ?? null ) ) return false;
	$current = wpagent_site_editor_navigation_state( $post_id );
	if ( is_wp_error( $current ) || ! hash_equals( $candidate['state_hash'], $current['hash'] ) ) return false;
	$current_candidate = wpagent_site_editor_navigation_update_candidate( $post_id );
	if ( ! is_array( $current_candidate ) || ! hash_equals( $candidate['post_candidate']['hash'], $current_candidate['post_candidate']['hash'] ) ) return false;
	$guard = wpagent_site_editor_update_guard( 'wp_navigation', (string) $post_id, $lock );
	if ( is_wp_error( $guard ) || ! wpagent_site_editor_restore_post_record_if_current( $snapshot['post'], $candidate['post'] ) ) return false;
	$restored = wpagent_site_editor_navigation_state( $post_id );
	return ! is_wp_error( $restored ) && hash_equals( $snapshot['state_hash'], $restored['hash'] );
}

function wpagent_site_editor_delete_created_navigation_if_current( int $post_id, string $expected_hash, string $lock ): bool {
	$guard = wpagent_site_editor_update_guard( 'wp_navigation', (string) $post_id, $lock );
	if ( is_wp_error( $guard ) ) return false;
	$state = wpagent_site_editor_navigation_state( $post_id );
	$post = get_post( $post_id );
	if ( is_wp_error( $state ) || ! $post instanceof WP_Post || ! hash_equals( $expected_hash, $state['hash'] ) ) return false;
	$candidate = wpagent_core_mutator_created_post_candidate( $post_id, 'wp_navigation', (int) $post->post_author );
	$guard = wpagent_site_editor_update_guard( 'wp_navigation', (string) $post_id, $lock );
	return ! is_wp_error( $guard ) && wpagent_core_mutator_delete_created_post_if_current( $candidate, 'wp_navigation', (int) $post->post_author );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_create_block_navigation( $input ) {
	$input = is_array( $input ) ? $input : array();
	$title = wpagent_site_editor_plain_text( $input['title'] ?? '', 'title', 200 );
	if ( is_wp_error( $title ) ) return $title;
	$content = wpagent_site_editor_build_navigation_content( $input['items'] ?? null );
	if ( is_wp_error( $content ) ) return $content;
	$identity = 'create:' . hash( 'sha256', strtolower( $title ) );
	$lock = wpagent_site_editor_acquire_lock( 'wp_navigation', $identity );
	if ( is_wp_error( $lock ) ) return $lock;
	try {
		$owner_id = get_current_user_id();
		$guard = wpagent_site_editor_update_guard( 'wp_navigation', $identity, $lock );
		if ( is_wp_error( $guard ) ) return $guard;
		$id = wp_insert_post( array( 'post_type' => 'wp_navigation', 'post_status' => 'publish', 'post_title' => $title, 'post_content' => $content ), true );
		if ( is_wp_error( $id ) ) return $id;
		$guard = wpagent_site_editor_update_guard( 'wp_navigation', $identity, $lock );
		if ( is_wp_error( $guard ) ) return $guard;
		$candidate = wpagent_core_mutator_created_post_candidate( (int) $id, 'wp_navigation', $owner_id );
		if ( ! is_array( $candidate ) ) return wpagent_core_mutator_created_post_recovery_required( (int) $id, 'wp_navigation', $owner_id, 'site_editor_navigation_create_candidate_unreadable' );
		$guard = wpagent_site_editor_update_guard( 'wp_navigation', $identity, $lock );
		if ( is_wp_error( $guard ) ) return $guard;
		$fresh = wpagent_site_editor_navigation_state( (int) $id );
		$post = get_post( (int) $id );
		$effective_valid = $post instanceof WP_Post ? wpagent_site_editor_validate_content( wpagent_site_editor_effective_post_content( $post ) ) : new WP_Error( 'wpagent_site_editor_navigation_missing' );
		if ( is_wp_error( $fresh ) || $title !== $fresh['title'] || $content !== $fresh['content'] || $fresh['has_disallowed_blocks'] || is_wp_error( $effective_valid ) ) {
			$guard = wpagent_site_editor_update_guard( 'wp_navigation', $identity, $lock );
			if ( is_wp_error( $guard ) ) return $guard;
			if ( wpagent_core_mutator_delete_created_post_if_current( $candidate, 'wp_navigation', $owner_id ) ) {
				return new WP_Error( 'wpagent_site_editor_navigation_create_verify', __( 'Die Blocknavigation wich beim Read-Back ab. Der unveränderte Create-Kandidat wurde entfernt.', 'wp-agent-companion' ), array( 'status' => 500, 'rollback_verified' => true ) );
			}
			return wpagent_core_mutator_created_post_recovery_required( (int) $id, 'wp_navigation', $owner_id, 'site_editor_navigation_create_cleanup_drift' );
		}
		return array( 'navigation' => $fresh, 'verified' => true );
	} finally {
		wpagent_site_editor_release_lock( 'wp_navigation', $identity, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_update_block_navigation( $input ) {
	$input = is_array( $input ) ? $input : array();
	$id = absint( $input['id'] ?? 0 );
	$expected = wpagent_site_editor_expected_hash( $input['expected_hash'] ?? '' );
	if ( is_wp_error( $expected ) ) return $expected;
	if ( ! isset( $input['title'] ) && ! isset( $input['items'] ) ) return new WP_Error( 'wpagent_site_editor_no_changes', __( 'Es wurde keine Änderung angegeben.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$lock = wpagent_site_editor_acquire_lock( 'wp_navigation', (string) $id );
	if ( is_wp_error( $lock ) ) return $lock;
	try {
		$before = wpagent_site_editor_navigation_state( $id );
		if ( is_wp_error( $before ) ) return $before;
		if ( ! hash_equals( $before['hash'], $expected ) ) return new WP_Error( 'wpagent_site_editor_conflict', __( 'Die Blocknavigation wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['hash'] ) );
		$title = isset( $input['title'] ) ? wpagent_site_editor_plain_text( $input['title'], 'title', 200 ) : $before['title'];
		if ( is_wp_error( $title ) ) return $title;
		$content = isset( $input['items'] ) ? wpagent_site_editor_build_navigation_content( $input['items'] ) : $before['content'];
		if ( is_wp_error( $content ) ) return $content;
		$fresh_before_write = wpagent_site_editor_navigation_state( $id );
		if ( is_wp_error( $fresh_before_write ) ) return $fresh_before_write;
		if ( ! hash_equals( $expected, $fresh_before_write['hash'] ) ) return new WP_Error( 'wpagent_site_editor_conflict', __( 'Die Blocknavigation wurde unmittelbar vor dem Schreiben verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $fresh_before_write['hash'] ) );
		$snapshot_post = get_post( $id, ARRAY_A );
		if ( ! is_array( $snapshot_post ) || 'wp_navigation' !== (string) ( $snapshot_post['post_type'] ?? '' ) ) return new WP_Error( 'wpagent_site_editor_snapshot_failed', __( 'Der vorherige Navigationszustand konnte nicht für einen sicheren Compare-and-Swap gelesen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		if ( $title === $fresh_before_write['title'] && $content === $fresh_before_write['content'] ) return array( 'navigation' => $fresh_before_write, 'verified' => true );
		$next_post                      = $snapshot_post;
		$next_post['post_title']        = $title;
		$next_post['post_content']      = $content;
		$next_post['post_modified']     = current_time( 'mysql' );
		$next_post['post_modified_gmt'] = current_time( 'mysql', true );
		$guard = wpagent_site_editor_update_guard( 'wp_navigation', (string) $id, $lock );
		if ( is_wp_error( $guard ) ) return $guard;
		do_action( 'wpagent_site_editor_before_conditional_update', 'navigation', $id );
		if ( ! wpagent_site_editor_conditional_post_update_if_current( $snapshot_post, $next_post ) ) {
			$current = wpagent_site_editor_navigation_state( $id );
			$current_hash = is_wp_error( $current ) ? '' : $current['hash'];
			return new WP_Error( 'wpagent_site_editor_conflict', __( 'Die Blocknavigation wurde während des atomaren Schreibens verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $current_hash ) );
		}
		$post_after = get_post( $id );
		if ( $post_after instanceof WP_Post ) do_action( 'wp_after_insert_post', $id, $post_after, true, null );
		$fresh = wpagent_site_editor_navigation_state( $id );
		$guard = wpagent_site_editor_update_guard( 'wp_navigation', (string) $id, $lock );
		$post = get_post( $id );
		$effective_valid = $post instanceof WP_Post ? wpagent_site_editor_validate_content( wpagent_site_editor_effective_post_content( $post ) ) : new WP_Error( 'wpagent_site_editor_navigation_missing' );
		if ( is_wp_error( $guard ) || is_wp_error( $fresh ) ) {
			return wpagent_site_editor_candidate_recovery_required( 'navigation_readback_guard_or_candidate_failed' );
		}
		if ( $title !== $fresh['title'] || $content !== $fresh['content'] || $fresh['has_disallowed_blocks'] || is_wp_error( $effective_valid ) ) {
			// The mismatch may be a competing native editor write. It is not safe
			// to label that state as ours or to overwrite it automatically.
			return wpagent_site_editor_candidate_recovery_required( 'navigation_readback_mismatch' );
		}
		return array( 'navigation' => $fresh, 'verified' => true );
	} finally {
		wpagent_site_editor_release_lock( 'wp_navigation', (string) $id, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_delete_block_navigation( $input ) {
	$input = is_array( $input ) ? $input : array();
	$id = absint( $input['id'] ?? 0 );
	if ( ! $id ) return new WP_Error( 'wpagent_site_editor_navigation_id', __( 'id muss eine positive Navigations-ID sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$expected = wpagent_site_editor_expected_hash( $input['expected_hash'] ?? '' );
	if ( is_wp_error( $expected ) ) return $expected;
	if ( true !== ( $input['confirm'] ?? false ) ) return new WP_Error( 'wpagent_site_editor_delete_confirmation', __( 'Zum Löschen ist confirm=true erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$before = wpagent_site_editor_navigation_state( $id );
	if ( is_wp_error( $before ) ) return $before;
	if ( ! hash_equals( $before['hash'], $expected ) ) return new WP_Error( 'wpagent_site_editor_conflict', __( 'Die Blocknavigation wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['hash'] ) );
	$references = wpagent_site_editor_find_references( 'navigation', (string) $id );
	if ( is_wp_error( $references ) ) return $references;
	return wpagent_site_editor_manual_delete_error( $before['hash'], $references );
}
