<?php
/** Öffentliche Live-Editor-Ability-Definitionen und Befehlsadapter. */

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

require_once __DIR__ . '/live-editor-contract.php';

/** @return array<string,mixed>|WP_Error */
function wpagent_live_editor_public_command_output( array $command ) {
	$public = wpagent_companion_live_editor_public_command( $command );
	$result = wpagent_live_editor_json_envelope( is_array( $public['result'] ?? null ) ? $public['result'] : array() );
	if ( is_wp_error( $result ) ) {
		return $result;
	}
	$public['result'] = $result;
	return $public;
}

/** @return array<string,mixed> */
function wpagent_live_editor_public_session_output( array $session ): array {
	$public = wpagent_companion_live_editor_public_session( $session );
	$public['page'] = wpagent_companion_live_editor_normalize_page( is_array( $public['page'] ?? null ) ? $public['page'] : array() );
	return $public;
}

/** @return array<string,mixed> */
function wpagent_ability_get_live_editor_status(): array {
	return array(
		'label' => __( 'Live-Editor-Status lesen', 'wp-agent-companion' ),
		'description' => __( 'Liest die aktive, im WordPress-Backend geöffnete Live-Editor-Sitzung dieses Agent-Nutzers. Öffne den Live-Editor zuerst als Administrator. Verwende danach live-editor-read oder live-editor-write und frage das Ergebnis über get-live-editor-result ab.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array(), 'required' => array(), 'maxProperties' => 0, 'additionalProperties' => false, 'default' => array() ),
		'output_schema' => array(
			'type' => 'object',
			'properties' => array(
				'session_id' => array( 'type' => 'string', 'minLength' => 32, 'maxLength' => 32, 'pattern' => '^[a-f0-9]{32}$' ),
				'status' => array( 'type' => 'string', 'enum' => array( 'active' ), 'maxLength' => 16 ),
				'executor_id' => array( 'type' => 'integer', 'minimum' => 1 ),
				'created_at' => array( 'type' => 'string', 'format' => 'date-time', 'maxLength' => 32 ),
				'expires_at' => array( 'type' => 'string', 'format' => 'date-time', 'maxLength' => 32 ),
				'last_seen_at' => array( 'type' => 'string', 'format' => 'date-time', 'maxLength' => 32 ),
				'pending_commands' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_COMPANION_LIVE_EDITOR_MAX_PENDING ),
				'page' => wpagent_live_editor_page_schema(),
			),
			'required'             => array( 'session_id', 'status', 'executor_id', 'created_at', 'expires_at', 'last_seen_at', 'pending_commands', 'page' ),
			'maxProperties'        => 8,
			'additionalProperties' => false,
		),
		'execute_callback' => 'wpagent_exec_get_live_editor_status',
		'permission_callback' => 'wpagent_perm_live_editor',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_ability_live_editor_read(): array {
	return array(
		'label' => __( 'Im Live-Editor lesen', 'wp-agent-companion' ),
		'description' => __( 'Stellt einen lesenden Befehl in die aktive Browser-Arbeitsfläche. Unterstützt Seiteninspektion, Gutenberg-Struktur, Blocktypen, Blockschema und Blockattribute. Die Antwort enthält eine command_id. Frage den Abschluss über get-live-editor-result ab.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array(
			'type' => 'object',
			'properties' => array(
				'action' => array( 'type' => 'string', 'enum' => array( 'inspect-page', 'gutenberg-structure', 'list-block-types', 'get-block-schema', 'get-block-attributes' ), 'maxLength' => 32 ),
				'block_name' => array( 'type' => 'string', 'minLength' => 3, 'maxLength' => 127, 'pattern' => '^[a-z0-9][a-z0-9-]{0,63}/[a-z0-9][a-z0-9-]{0,63}$' ),
				'client_id' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 100, 'pattern' => '^[A-Za-z0-9_-]{1,100}$' ),
				'include_text' => array( 'type' => 'boolean', 'default' => false ),
				'text_limit' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 10000, 'default' => 3000 ),
				'search' => array( 'type' => 'string', 'maxLength' => 100 ),
			),
			'required' => array( 'action' ),
			'minProperties'        => 1,
			'maxProperties'        => 6,
			'additionalProperties' => false,
		),
		'output_schema' => wpagent_live_editor_command_output_schema(),
		'execute_callback' => 'wpagent_exec_live_editor_read',
		'permission_callback' => 'wpagent_perm_live_editor',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => false ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_ability_live_editor_write(): array {
	return array(
		'label' => __( 'Im Live-Editor bestätigt handeln', 'wp-agent-companion' ),
		'description' => __( 'Stellt eine sichtbare Aktion in die aktive Browser-Arbeitsfläche. Der Administrator muss jeden Befehl dort einzeln bestätigen. Blockänderungen bleiben zunächst ungespeichert. Erst save-post persistiert den sichtbaren Editorzustand. Jede zustandsändernde Aktion verlangt den aktuellen expected_state_hash.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array(
			'type' => 'object',
			'properties' => array(
				'action' => array( 'type' => 'string', 'enum' => array( 'open-post', 'create-block', 'update-block', 'delete-block', 'move-block', 'replace-inner-blocks', 'undo', 'redo', 'save-post', 'finalize-provider-attributes' ), 'maxLength' => 32 ),
				'reason' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 500 ),
				'expected_state_hash' => array( 'type' => 'string', 'minLength' => 64, 'maxLength' => 64, 'pattern' => '^[a-f0-9]{64}$' ),
				'expected_hash' => array( 'type' => 'string', 'minLength' => 64, 'maxLength' => 64, 'pattern' => '^[a-f0-9]{64}$' ),
				'post_id' => array( 'type' => 'integer', 'minimum' => 1 ),
				'client_id' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 100, 'pattern' => '^[A-Za-z0-9_-]{1,100}$' ),
				'parent_client_id' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 100, 'pattern' => '^[A-Za-z0-9_-]{1,100}$' ),
				'block_name' => array( 'type' => 'string', 'minLength' => 3, 'maxLength' => 127, 'pattern' => '^[a-z0-9][a-z0-9-]{0,63}/[a-z0-9][a-z0-9-]{0,63}$' ),
				'index' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 10000 ),
				'attributes' => wpagent_live_editor_json_envelope_schema(),
				'blocks' => wpagent_live_editor_json_envelope_schema(),
			),
			'required' => array( 'action', 'reason' ),
			'minProperties'        => 2,
			'maxProperties'        => 11,
			'additionalProperties' => false,
		),
		'output_schema' => wpagent_live_editor_command_output_schema(),
		'execute_callback' => 'wpagent_exec_live_editor_write',
		'permission_callback' => 'wpagent_perm_live_editor',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => true, 'idempotent' => false ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_ability_live_editor_elementor_write(): array {
	return array(
		'label' => __( 'Elementor im Live-Editor bestätigt ändern', 'wp-agent-companion' ),
		'description' => __( 'Stellt eine einzelne Elementor-Free-Dokumentänderung in die sichtbare Arbeitsfläche. Ein Administrator muss jede Operation einzeln bestätigen. Die Änderung läuft anschließend ausschließlich über Elementors geprüfte Server-API mit Konfliktschutz, Revision, Rückleseprüfung, Frontend-Prüfung und Rollback.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array(
			'type' => 'object',
			'properties' => array(
				'operation' => array( 'type' => 'string', 'enum' => array( 'open-document', 'update-document', 'update-element', 'create-element', 'duplicate-element', 'move-element', 'delete-element', 'restore-revision' ), 'maxLength' => 32 ),
				'reason' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 500 ),
				'post_id' => array( 'type' => 'integer', 'minimum' => 1 ),
				'expected_hash' => array( 'type' => 'string', 'minLength' => 64, 'maxLength' => 64, 'pattern' => '^[a-f0-9]{64}$' ),
				'element_id' => array( 'type' => 'string', 'pattern' => '^[A-Za-z0-9_-]{1,64}$' ),
				'parent_id' => array( 'type' => 'string', 'pattern' => '^[A-Za-z0-9_-]{1,64}$' ),
				'index' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ELEMENTOR_MAX_ELEMENTS ),
				'element' => wpagent_live_editor_json_envelope_schema(),
				'elements' => wpagent_live_editor_json_envelope_schema(),
				'settings' => wpagent_live_editor_json_envelope_schema(),
				'settings_patch' => wpagent_live_editor_json_envelope_schema(),
				'remove_settings' => array( 'type' => 'array', 'maxItems' => 100, 'items' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 191, 'pattern' => '^[A-Za-z0-9_-]{1,191}$' ) ),
				'style_patch' => wpagent_live_editor_json_envelope_schema(),
				'revision_id' => array( 'type' => 'integer', 'minimum' => 1 ),
			),
			'required' => array( 'operation', 'reason', 'post_id' ),
			'minProperties'        => 3,
			'maxProperties'        => 14,
			'additionalProperties' => false,
		),
		'output_schema' => wpagent_live_editor_command_output_schema(),
		'execute_callback' => 'wpagent_exec_live_editor_elementor_write',
		'permission_callback' => 'wpagent_perm_live_editor',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => true, 'idempotent' => false ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_ability_get_live_editor_result(): array {
	return array(
		'label' => __( 'Live-Editor-Ergebnis lesen', 'wp-agent-companion' ),
		'description' => __( 'Liest Status und Ergebnis eines zuvor eingereihten Live-Editor-Befehls. pending, running oder awaiting_confirmation bedeutet, dass du nach kurzer Wartezeit erneut fragen sollst.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array(
			'type' => 'object',
			'properties' => array( 'command_id' => array( 'type' => 'string', 'minLength' => 24, 'maxLength' => 24, 'pattern' => '^[a-f0-9]{24}$' ) ),
			'required' => array( 'command_id' ),
			'minProperties'        => 1,
			'maxProperties'        => 1,
			'additionalProperties' => false,
		),
		'output_schema' => wpagent_live_editor_command_output_schema(),
		'execute_callback' => 'wpagent_exec_get_live_editor_result',
		'permission_callback' => 'wpagent_perm_live_editor',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ) ),
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_get_live_editor_status( $input = array() ) {
	if ( ! wpagent_perm_live_editor() ) {
		return new WP_Error( 'wpagent_live_editor_forbidden', __( 'Der Live-Editor ist nur für den gebundenen Nicht-Administrator mit Inhaltsrechten verfügbar.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$keys = wpagent_live_editor_validate_input_keys( $input, array() );
	if ( is_wp_error( $keys ) ) {
		return $keys;
	}
	$session = wpagent_companion_live_editor_agent_session();
	return is_wp_error( $session ) ? $session : wpagent_live_editor_public_session_output( $session );
}

function wpagent_live_editor_clean_identifier( $value, string $field ) {
	if ( ! is_string( $value ) || ! preg_match( '/^[A-Za-z0-9_-]{1,100}$/D', $value ) ) {
		return new WP_Error(
			'wpagent_live_editor_' . $field,
			sprintf(
				/* translators: %s: machine-readable Live Editor field name. */
				__( '%s ist ungültig.', 'wp-agent-companion' ),
				$field
			),
			array( 'status' => 400 )
		);
	}
	return $value;
}

function wpagent_live_editor_clean_block_name( $value ) {
	if ( ! is_string( $value ) || ! preg_match( '#^[a-z0-9][a-z0-9-]{0,63}/[a-z0-9][a-z0-9-]{0,63}$#D', $value ) ) {
		return new WP_Error( 'wpagent_live_editor_block_name', __( 'block_name muss ein gültiger registrierter Blockname sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return $value;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_live_editor_read( $input ) {
	if ( ! wpagent_perm_live_editor() ) {
		return new WP_Error( 'wpagent_live_editor_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( ! is_array( $input ) || ( $input && array_is_list( $input ) ) ) {
		return new WP_Error( 'wpagent_live_editor_input_fields', __( 'Die Live-Editor-Anfrage muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$action  = is_string( $input['action'] ?? null ) ? $input['action'] : '';
	$allowed = array( 'inspect-page', 'gutenberg-structure', 'list-block-types', 'get-block-schema', 'get-block-attributes' );
	if ( ! in_array( $action, $allowed, true ) ) {
		return new WP_Error( 'wpagent_live_editor_action', __( 'Unbekannter lesender Live-Editor-Befehl.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$fields = array(
		'inspect-page'         => array( 'action', 'include_text', 'text_limit' ),
		'gutenberg-structure'  => array( 'action' ),
		'list-block-types'     => array( 'action', 'search' ),
		'get-block-schema'     => array( 'action', 'block_name' ),
		'get-block-attributes' => array( 'action', 'client_id' ),
	);
	$keys = wpagent_live_editor_validate_input_keys( $input, $fields[ $action ] );
	if ( is_wp_error( $keys ) ) {
		return $keys;
	}
	$payload = wpagent_live_editor_validate_payload_size( $input );
	if ( is_wp_error( $payload ) ) {
		return $payload;
	}
	$args = array();
	if ( 'inspect-page' === $action ) {
		if ( array_key_exists( 'include_text', $input ) && ! is_bool( $input['include_text'] ) ) {
			return new WP_Error( 'wpagent_live_editor_include_text', __( 'include_text muss ein boolescher Wert sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		if ( array_key_exists( 'text_limit', $input ) && ( ! is_int( $input['text_limit'] ) || $input['text_limit'] < 1 || $input['text_limit'] > 10000 ) ) {
			return new WP_Error( 'wpagent_live_editor_text_limit', __( 'text_limit muss zwischen 1 und 10.000 liegen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$args['include_text'] = true === ( $input['include_text'] ?? false );
		$args['text_limit']   = $input['text_limit'] ?? 3000;
	} elseif ( 'list-block-types' === $action ) {
		if ( array_key_exists( 'search', $input ) && ! is_string( $input['search'] ) ) {
			return new WP_Error( 'wpagent_live_editor_search', __( 'search muss ein Textwert sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$search = is_string( $input['search'] ?? null ) ? sanitize_text_field( $input['search'] ) : '';
		if ( strlen( $search ) > 100 ) {
			return new WP_Error( 'wpagent_live_editor_search', __( 'search darf höchstens 100 Byte lang sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$args['search'] = $search;
	} elseif ( 'get-block-schema' === $action ) {
		$name = wpagent_live_editor_clean_block_name( $input['block_name'] ?? null );
		if ( is_wp_error( $name ) ) return $name;
		$args['block_name'] = $name;
	} elseif ( 'get-block-attributes' === $action ) {
		$id = wpagent_live_editor_clean_identifier( $input['client_id'] ?? null, 'client_id' );
		if ( is_wp_error( $id ) ) return $id;
		$args['client_id'] = $id;
	}
	$queued = wpagent_companion_live_editor_queue_command( $action, $args, false );
	return is_wp_error( $queued ) ? $queued : wpagent_live_editor_public_command_output( $queued );
}

function wpagent_live_editor_state_hash( array $input, string $action ) {
	if ( 'open-post' === $action ) return '';
	$value = $input['expected_state_hash'] ?? null;
	if ( ! is_string( $value ) || ! preg_match( '/^[a-f0-9]{64}$/D', $value ) ) {
		return new WP_Error( 'wpagent_live_editor_state_hash', __( 'expected_state_hash muss der aktuelle 64-stellige SHA-256-Zustand des geöffneten Editors sein.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return $value;
}

/** @return string|WP_Error */
function wpagent_live_editor_elementor_identifier( $value, string $field ) {
	if ( ! is_string( $value ) || ! preg_match( '/^[A-Za-z0-9_-]{1,64}$/D', $value ) ) {
		return new WP_Error( 'wpagent_live_editor_elementor_' . $field, sprintf( __( '%s ist ungültig.', 'wp-agent-companion' ), $field ), array( 'status' => 400 ) );
	}
	return $value;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_live_editor_elementor_command_args( array $input ) {
	$keys = wpagent_live_editor_validate_input_keys( $input, array( 'operation', 'reason', 'post_id', 'expected_hash', 'element_id', 'parent_id', 'index', 'element', 'elements', 'settings', 'settings_patch', 'remove_settings', 'style_patch', 'revision_id' ), 'wpagent_live_editor_elementor_fields' );
	if ( is_wp_error( $keys ) ) {
		return $keys;
	}
	$payload = wpagent_live_editor_validate_payload_size( $input );
	if ( is_wp_error( $payload ) ) {
		return $payload;
	}
	$operation = is_string( $input['operation'] ?? null ) ? $input['operation'] : '';
	if ( ! in_array( $operation, wpagent_companion_live_editor_elementor_operations(), true ) ) {
		return new WP_Error( 'wpagent_live_editor_elementor_operation', __( 'Unbekannte Elementor-Live-Editor-Operation.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$reason = is_string( $input['reason'] ?? null ) ? trim( sanitize_text_field( $input['reason'] ) ) : '';
	$post_id = absint( $input['post_id'] ?? 0 );
	if ( '' === $reason || strlen( $reason ) > 500 || $post_id < 1 ) {
		return new WP_Error( 'wpagent_live_editor_elementor_input', __( 'reason und post_id sind erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$allowed = array(
		'open-document' => array( 'operation', 'reason', 'post_id' ),
		'update-document' => array( 'operation', 'reason', 'post_id', 'expected_hash', 'elements', 'settings' ),
		'update-element' => array( 'operation', 'reason', 'post_id', 'expected_hash', 'element_id', 'settings_patch', 'remove_settings', 'style_patch' ),
		'create-element' => array( 'operation', 'reason', 'post_id', 'expected_hash', 'parent_id', 'index', 'element' ),
		'duplicate-element' => array( 'operation', 'reason', 'post_id', 'expected_hash', 'element_id', 'parent_id', 'index' ),
		'move-element' => array( 'operation', 'reason', 'post_id', 'expected_hash', 'element_id', 'parent_id', 'index' ),
		'delete-element' => array( 'operation', 'reason', 'post_id', 'expected_hash', 'element_id' ),
		'restore-revision' => array( 'operation', 'reason', 'post_id', 'expected_hash', 'revision_id' ),
	);
	if ( array_diff( array_keys( $input ), $allowed[ $operation ] ) ) {
		return new WP_Error( 'wpagent_live_editor_elementor_fields', __( 'Die Elementor-Operation enthält nicht erlaubte Felder.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$args = array( 'operation' => $operation, 'reason' => $reason, 'post_id' => $post_id );
	if ( 'open-document' === $operation ) {
		return $args;
	}
	$expected_hash = is_string( $input['expected_hash'] ?? null ) ? $input['expected_hash'] : '';
	if ( ! preg_match( '/^[a-f0-9]{64}$/D', $expected_hash ) ) {
		return new WP_Error( 'wpagent_live_editor_elementor_hash', __( 'expected_hash muss der aktuelle Elementor-SHA-256-Zustand sein.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$args['expected_hash'] = $expected_hash;
	foreach ( array( 'element_id', 'parent_id' ) as $field ) {
		if ( array_key_exists( $field, $input ) ) {
			$value = wpagent_live_editor_elementor_identifier( $input[ $field ], $field );
			if ( is_wp_error( $value ) ) {
				return $value;
			}
			$args[ $field ] = $value;
		}
	}
	if ( in_array( $operation, array( 'update-element', 'duplicate-element', 'move-element', 'delete-element' ), true ) && ! isset( $args['element_id'] ) ) {
		return new WP_Error( 'wpagent_live_editor_elementor_element_id', __( 'element_id ist für diese Elementor-Operation erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( in_array( $operation, array( 'create-element', 'duplicate-element', 'move-element' ), true ) ) {
		if ( array_key_exists( 'index', $input ) && ( ! is_int( $input['index'] ) || $input['index'] < 0 || $input['index'] > WPAGENT_ELEMENTOR_MAX_ELEMENTS ) ) {
			return new WP_Error( 'wpagent_live_editor_elementor_index', __( 'index ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$args['index'] = $input['index'] ?? 0;
	}
	if ( 'create-element' === $operation ) {
		$element = wpagent_live_editor_decode_json_envelope( $input['element'] ?? null, 'element', 'object', WPAGENT_ELEMENTOR_MAX_CONTROLS );
		if ( is_wp_error( $element ) ) {
			return $element;
		}
		$elements_count = 0;
		$valid_element = wpagent_live_editor_validate_elements( array( $element ), $elements_count );
		if ( is_wp_error( $valid_element ) ) {
			return $valid_element;
		}
		$args['element'] = $element;
	}
	if ( 'update-document' === $operation ) {
		if ( ! array_key_exists( 'elements', $input ) && ! array_key_exists( 'settings', $input ) ) {
			return new WP_Error( 'wpagent_live_editor_elementor_input', __( 'elements oder settings ist erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		foreach ( array( 'elements', 'settings' ) as $field ) {
			if ( array_key_exists( $field, $input ) ) {
				$value = wpagent_live_editor_decode_json_envelope( $input[ $field ], $field, 'elements' === $field ? 'array' : 'object', 'settings' === $field ? WPAGENT_ELEMENTOR_MAX_CONTROLS : WPAGENT_LIVE_EDITOR_MAX_JSON_PROPERTIES );
				if ( is_wp_error( $value ) ) {
					return $value;
				}
			$args[ $field ] = $value;
			}
		}
	}
	if ( 'update-element' === $operation ) {
		if ( ! array_key_exists( 'settings_patch', $input ) && ! array_key_exists( 'remove_settings', $input ) && ! array_key_exists( 'style_patch', $input ) ) {
			return new WP_Error( 'wpagent_live_editor_elementor_input', __( 'Mindestens eine Elementor-Elementänderung ist erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		foreach ( array( 'settings_patch', 'remove_settings', 'style_patch' ) as $field ) {
			if ( array_key_exists( $field, $input ) ) {
				if ( 'remove_settings' === $field ) {
					if ( ! is_array( $input[ $field ] ) || ! array_is_list( $input[ $field ] ) || count( $input[ $field ] ) > 100 ) {
						return new WP_Error( 'wpagent_live_editor_elementor_remove_settings', __( 'remove_settings muss eine begrenzte Liste sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
					}
					foreach ( $input[ $field ] as $setting ) {
						if ( ! is_string( $setting ) || ! preg_match( '/^[A-Za-z0-9_-]{1,191}$/D', $setting ) ) {
							return new WP_Error( 'wpagent_live_editor_elementor_remove_settings', __( 'remove_settings enthält einen ungültigen Namen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
						}
					}
					$args[ $field ] = $input[ $field ];
					continue;
				}
				$value = wpagent_live_editor_decode_json_envelope( $input[ $field ], $field, 'object', WPAGENT_ELEMENTOR_MAX_CONTROLS );
				if ( is_wp_error( $value ) ) {
					return $value;
				}
				if ( 'style_patch' === $field ) {
					$valid_style = wpagent_live_editor_validate_style_patch( $value );
					if ( is_wp_error( $valid_style ) ) {
						return $valid_style;
					}
				}
					$args[ $field ] = $value;
			}
		}
	}
	if ( 'restore-revision' === $operation ) {
		$args['revision_id'] = absint( $input['revision_id'] ?? 0 );
		if ( $args['revision_id'] < 1 ) {
			return new WP_Error( 'wpagent_live_editor_elementor_revision', __( 'revision_id ist erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
	}
	$encoded = wp_json_encode( $args );
	if ( ! is_string( $encoded ) || strlen( $encoded ) > WPAGENT_ELEMENTOR_MAX_BYTES ) {
		return new WP_Error( 'wpagent_live_editor_elementor_payload', __( 'Die Elementor-Live-Editor-Anfrage ist zu groß.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	return $args;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_live_editor_elementor_write( $input ) {
	if ( ! wpagent_perm_live_editor() ) {
		return new WP_Error( 'wpagent_live_editor_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( ! is_array( $input ) || ( $input && array_is_list( $input ) ) ) {
		return new WP_Error( 'wpagent_live_editor_elementor_fields', __( 'Die Elementor-Anfrage muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$args = wpagent_live_editor_elementor_command_args( $input );
	if ( is_wp_error( $args ) ) {
		return $args;
	}
	$queued = wpagent_companion_live_editor_queue_command( 'open-document' === $args['operation'] ? 'open-elementor-document' : 'elementor-mutate', $args, true );
	return is_wp_error( $queued ) ? $queued : wpagent_live_editor_public_command_output( $queued );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_live_editor_write( $input ) {
	if ( ! wpagent_perm_live_editor() ) {
		return new WP_Error( 'wpagent_live_editor_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( ! is_array( $input ) || ( $input && array_is_list( $input ) ) ) {
		return new WP_Error( 'wpagent_live_editor_input_fields', __( 'Die Live-Editor-Anfrage muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$action  = is_string( $input['action'] ?? null ) ? $input['action'] : '';
	$allowed = array( 'open-post', 'create-block', 'update-block', 'delete-block', 'move-block', 'replace-inner-blocks', 'undo', 'redo', 'save-post', 'finalize-provider-attributes' );
	if ( ! in_array( $action, $allowed, true ) ) {
		return new WP_Error( 'wpagent_live_editor_action', __( 'Unbekannte schreibende Live-Editor-Aktion.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$fields = array(
		'open-post'                    => array( 'action', 'reason', 'post_id' ),
		'create-block'                 => array( 'action', 'reason', 'expected_state_hash', 'block_name', 'attributes', 'parent_client_id', 'index' ),
		'update-block'                 => array( 'action', 'reason', 'expected_state_hash', 'client_id', 'attributes' ),
		'delete-block'                 => array( 'action', 'reason', 'expected_state_hash', 'client_id' ),
		'move-block'                   => array( 'action', 'reason', 'expected_state_hash', 'client_id', 'parent_client_id', 'index' ),
		'replace-inner-blocks'         => array( 'action', 'reason', 'expected_state_hash', 'client_id', 'blocks' ),
		'undo'                         => array( 'action', 'reason', 'expected_state_hash' ),
		'redo'                         => array( 'action', 'reason', 'expected_state_hash' ),
		'save-post'                    => array( 'action', 'reason', 'expected_state_hash' ),
		'finalize-provider-attributes' => array( 'action', 'reason', 'post_id', 'expected_hash', 'expected_state_hash' ),
	);
	$keys = wpagent_live_editor_validate_input_keys(
		$input,
		$fields[ $action ],
		'finalize-provider-attributes' === $action ? 'wpagent_live_editor_finalize_input' : 'wpagent_live_editor_input_fields'
	);
	if ( is_wp_error( $keys ) ) {
		return $keys;
	}
	$payload = wpagent_live_editor_validate_payload_size( $input );
	if ( is_wp_error( $payload ) ) {
		return $payload;
	}
	if ( wpagent_companion_live_editor_is_provider_finalize_action( $action ) ) {
		$args = wpagent_companion_live_editor_provider_finalize_command_args( $input );
		if ( is_wp_error( $args ) ) {
			return $args;
		}
		$queued = wpagent_companion_live_editor_queue_command( $action, $args, true );
		return is_wp_error( $queued ) ? $queued : wpagent_live_editor_public_command_output( $queued );
	}
	$reason = is_string( $input['reason'] ?? null ) ? trim( sanitize_text_field( $input['reason'] ) ) : '';
	if ( '' === $reason || strlen( $reason ) > 500 ) {
		return new WP_Error( 'wpagent_live_editor_reason', __( 'reason muss zwischen 1 und 500 Byte lang sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$hash = wpagent_live_editor_state_hash( $input, $action );
	if ( is_wp_error( $hash ) ) return $hash;
	$args = array( 'reason' => $reason );
	if ( '' !== $hash ) $args['expected_state_hash'] = $hash;

	if ( 'open-post' === $action ) {
		$post_id = absint( $input['post_id'] ?? 0 );
		if ( 0 === $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
			return new WP_Error( 'wpagent_live_editor_post', __( 'Der Beitrag fehlt oder darf von diesem Agent-Nutzer nicht bearbeitet werden.', 'wp-agent-companion' ), array( 'status' => 403 ) );
		}
		$args['post_id'] = $post_id;
	} elseif ( in_array( $action, array( 'update-block', 'delete-block', 'move-block', 'replace-inner-blocks' ), true ) ) {
		$id = wpagent_live_editor_clean_identifier( $input['client_id'] ?? null, 'client_id' );
		if ( is_wp_error( $id ) ) return $id;
		$args['client_id'] = $id;
	}
	if ( 'create-block' === $action ) {
		$name = wpagent_live_editor_clean_block_name( $input['block_name'] ?? null );
		if ( is_wp_error( $name ) ) return $name;
		$args['block_name'] = $name;
	}
	if ( in_array( $action, array( 'create-block', 'move-block' ), true ) ) {
		if ( isset( $input['parent_client_id'] ) && '' !== $input['parent_client_id'] ) {
			$parent = wpagent_live_editor_clean_identifier( $input['parent_client_id'], 'parent_client_id' );
			if ( is_wp_error( $parent ) ) return $parent;
			$args['parent_client_id'] = $parent;
		}
		if ( array_key_exists( 'index', $input ) && ( ! is_int( $input['index'] ) || $input['index'] < 0 || $input['index'] > 10000 ) ) {
			return new WP_Error( 'wpagent_live_editor_index', __( 'index ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$args['index'] = $input['index'] ?? 0;
	}
	if ( in_array( $action, array( 'create-block', 'update-block' ), true ) ) {
		$attributes = wpagent_live_editor_decode_json_envelope( $input['attributes'] ?? null, 'attributes', 'object' );
		if ( is_wp_error( $attributes ) || ( 'update-block' === $action && empty( $attributes ) ) ) {
			return is_wp_error( $attributes ) ? $attributes : new WP_Error( 'wpagent_live_editor_attributes', __( 'attributes darf bei update-block nicht leer sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$args['attributes'] = $attributes;
	}
	if ( 'replace-inner-blocks' === $action ) {
		$blocks = wpagent_live_editor_decode_json_envelope( $input['blocks'] ?? null, 'blocks', 'array' );
		if ( is_wp_error( $blocks ) ) {
			return $blocks;
		}
		$block_count = 0;
		$valid_blocks = wpagent_live_editor_validate_block_descriptors( $blocks, $block_count );
		if ( is_wp_error( $valid_blocks ) ) {
			return $valid_blocks;
		}
		$args['blocks'] = $blocks;
	}
	$queued = wpagent_companion_live_editor_queue_command( $action, $args, true );
	return is_wp_error( $queued ) ? $queued : wpagent_live_editor_public_command_output( $queued );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_get_live_editor_result( $input ) {
	if ( ! wpagent_perm_live_editor() ) {
		return new WP_Error( 'wpagent_live_editor_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$keys = wpagent_live_editor_validate_input_keys( $input, array( 'command_id' ) );
	if ( is_wp_error( $keys ) ) {
		return $keys;
	}
	$payload = wpagent_live_editor_validate_payload_size( $input );
	if ( is_wp_error( $payload ) ) {
		return $payload;
	}
	$command_id = is_string( $input['command_id'] ?? null ) ? $input['command_id'] : '';
	if ( ! preg_match( '/^[a-f0-9]{24}$/D', $command_id ) ) {
		return new WP_Error( 'wpagent_live_editor_command_missing', __( 'Der Live-Editor-Befehl wurde nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	$option  = wpagent_companion_live_editor_command_option( $command_id );
	$command = get_option( $option, null );
	if ( ! is_array( $command ) ) {
		return new WP_Error( 'wpagent_live_editor_command_missing', __( 'Der Live-Editor-Befehl wurde nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	if ( get_current_user_id() !== absint( $command['executor_id'] ?? 0 ) ) {
		return new WP_Error( 'wpagent_live_editor_command_forbidden', __( 'Dieser Live-Editor-Befehl gehört zu einem anderen Agent-Nutzer.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( absint( $command['expires_at'] ?? 0 ) <= time() && ! in_array( (string) ( $command['status'] ?? '' ), array( 'completed', 'failed', 'denied', 'recovery_required' ), true ) ) {
		$session_id = is_string( $command['session_id'] ?? null ) ? $command['session_id'] : '';
		$lock       = wpagent_companion_live_editor_acquire_lock( $session_id );
		if ( is_wp_error( $lock ) ) {
			return $lock;
		}
		try {
			$session = wpagent_companion_live_editor_get_session( $session_id );
			if ( is_wp_error( $session ) ) {
				return $session;
			}
			$session = wpagent_companion_live_editor_prune_commands( $session );
			if ( is_wp_error( $session ) ) {
				return $session;
			}
			if ( ! wpagent_companion_live_editor_store_record( wpagent_companion_live_editor_session_option( $session_id ), $session ) ) {
				return new WP_Error( 'wpagent_live_editor_session_save', __( 'Die bereinigte Live-Editor-Sitzung konnte nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$command = get_option( $option, null );
			if ( ! is_array( $command ) ) {
				return new WP_Error( 'wpagent_live_editor_command_missing', __( 'Der Live-Editor-Befehl wurde nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
			}
		} finally {
			wpagent_companion_live_editor_release_lock( $session_id, $lock );
		}
	}
	return wpagent_live_editor_public_command_output( $command );
}
