<?php
/** Native Beaver Builder creation, settings and structure abilities. */

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

/** @return array<string,mixed> */
function wpagent_beaver_mutation_output_schema(): array {
	$schema                              = wpagent_beaver_document_output_schema();
	$schema['properties']['changed']     = array( 'type' => 'boolean' );
	$schema['properties']['verified']    = array( 'type' => 'boolean' );
	$schema['properties']['revision_id'] = array( 'type' => 'integer' );
	$schema['properties']['node']        = wpagent_beaver_node_output_schema();
	return $schema;
}

/** @return array<string,mixed> */
function wpagent_beaver_hash_input_properties(): array {
	return array(
		'post_id'       => array( 'type' => 'integer' ),
		'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
	);
}

function wpagent_ability_create_beaver_page(): array {
	$settings_schema = array( 'type' => 'object', 'additionalProperties' => true );
	$module_schema   = array(
		'type'       => 'object',
		'properties' => array(
			'type'     => array( 'type' => 'string' ),
			'settings' => $settings_schema,
		),
		'required'   => array( 'type' ),
	);
	$column_schema   = array(
		'type'       => 'object',
		'properties' => array(
			'settings' => $settings_schema,
			'modules'  => array( 'type' => 'array', 'items' => $module_schema ),
		),
	);
	return array(
		'label'               => __( 'Beaver-Builder-Seite erstellen', 'wp-agent-companion' ),
		'description'         => __( 'Erstellt über Beaver Builders Modell-API eine verifizierte Seite aus begrenzten Zeilen, Spalten und sicheren Modulen.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'       => 'object',
			'properties' => array(
				'title'  => array( 'type' => 'string' ),
				'status' => array( 'type' => 'string', 'enum' => array( 'draft', 'pending', 'private', 'publish' ) ),
				'rows'   => array(
					'type'     => 'array',
					'minItems' => 1,
					'maxItems' => WPAGENT_BEAVER_MAX_ROWS,
					'items'    => array(
						'type'       => 'object',
						'properties' => array(
							'layout'   => array( 'type' => 'string' ),
							'settings' => $settings_schema,
							'columns'  => array( 'type' => 'array', 'items' => $column_schema ),
						),
						'required'   => array( 'layout' ),
					),
				),
			),
			'required'   => array( 'title', 'rows' ),
		),
		'output_schema'       => wpagent_beaver_mutation_output_schema(),
		'execute_callback'    => 'wpagent_exec_create_beaver_page',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta'                => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

/** @return true|WP_Error */
function wpagent_beaver_apply_settings_to_node( int $post_id, string $node_id, array $settings ) {
	if ( array() === $settings ) {
		return true;
	}
	$node = wpagent_beaver_node( $post_id, $node_id );
	if ( is_wp_error( $node ) ) {
		return $node;
	}
	$prepared = wpagent_beaver_prepare_node_settings( $node, $settings );
	if ( is_wp_error( $prepared ) ) {
		return $prepared;
	}
	$data                        = FLBuilderModel::get_layout_data( 'published', $post_id );
	$data[ $node_id ]->settings = $prepared;
	FLBuilderModel::update_layout_data( $data, 'published', $post_id );
	return true;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_create_beaver_page( $input ) {
	if ( ! wpagent_beaver_available() ) {
		return wpagent_beaver_unavailable_error();
	}
	$input = is_array( $input ) ? $input : array();
	$title = sanitize_text_field( is_string( $input['title'] ?? null ) ? $input['title'] : '' );
	$rows  = is_array( $input['rows'] ?? null ) ? $input['rows'] : array();
	$status = sanitize_key( is_string( $input['status'] ?? null ) ? $input['status'] : 'draft' );
	if ( '' === $title || strlen( $title ) > 500 ) {
		return new WP_Error( 'wpagent_beaver_bad_title', __( 'Ein gültiger Seitentitel ist erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! current_user_can( get_post_type_object( 'page' )->cap->create_posts ) ) {
		return new WP_Error( 'wpagent_beaver_create_forbidden', __( 'Keine Berechtigung zum Erstellen von Seiten.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( ! in_array( $status, array( 'draft', 'pending', 'private', 'publish' ), true ) ) {
		return new WP_Error( 'wpagent_beaver_bad_status', __( 'Ungültiger Beitragsstatus.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( 'publish' === $status && ! current_user_can( get_post_type_object( 'page' )->cap->publish_posts ) ) {
		return new WP_Error( 'wpagent_beaver_publish_forbidden', __( 'Keine Berechtigung zum Veröffentlichen von Seiten.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( count( $rows ) < 1 || count( $rows ) > WPAGENT_BEAVER_MAX_ROWS ) {
		return new WP_Error( 'wpagent_beaver_row_limit', __( 'Die Anzahl der Beaver-Builder-Zeilen liegt außerhalb der sicheren Grenze.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	$module_count        = 0;
	$expected_node_count = 0;
	foreach ( $rows as $row ) {
		if ( ! is_array( $row ) ) {
			return new WP_Error( 'wpagent_beaver_bad_row', __( 'Ungültige Beaver-Builder-Zeile.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$layout = sanitize_key( is_string( $row['layout'] ?? null ) ? $row['layout'] : '' );
		if ( ! isset( FLBuilderModel::$row_layouts[ $layout ] ) || ! is_array( FLBuilderModel::$row_layouts[ $layout ] ) ) {
			return new WP_Error( 'wpagent_beaver_bad_layout', __( 'Unbekanntes Beaver-Builder-Spaltenlayout.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$expected_node_count += 2 + count( FLBuilderModel::$row_layouts[ $layout ] );
		$columns = is_array( $row['columns'] ?? null ) ? $row['columns'] : array();
		if ( count( $columns ) > count( FLBuilderModel::$row_layouts[ $layout ] ) ) {
			return new WP_Error( 'wpagent_beaver_column_count', __( 'Das Spaltenlayout enthält mehr Spaltenkonfigurationen als vorgesehen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		foreach ( $columns as $column ) {
			if ( ! is_array( $column ) ) {
				return new WP_Error( 'wpagent_beaver_bad_column', __( 'Ungültige Beaver-Builder-Spalte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
			$modules = is_array( $column['modules'] ?? null ) ? $column['modules'] : array();
			$module_count += count( $modules );
			foreach ( $modules as $module_config ) {
				if ( ! is_array( $module_config ) || is_wp_error( wpagent_beaver_module( sanitize_key( is_string( $module_config['type'] ?? null ) ? $module_config['type'] : '' ) ) ) ) {
					return new WP_Error( 'wpagent_beaver_bad_module', __( 'Die Seite enthält einen unbekannten oder gesperrten Beaver-Builder-Modultyp.', 'wp-agent-companion' ), array( 'status' => 400 ) );
				}
			}
		}
	}
	if ( $module_count > WPAGENT_BEAVER_MAX_MODULES ) {
		return new WP_Error( 'wpagent_beaver_module_limit', __( 'Die Seite enthält zu viele Beaver-Builder-Module.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	$expected_node_count += $module_count;
	if ( $expected_node_count > WPAGENT_BEAVER_MAX_NODES ) {
		return new WP_Error( 'wpagent_beaver_node_limit', __( 'Das Beaver-Builder-Layout überschreitet die sichere Knotengrenze.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	$post_id = wp_insert_post( array( 'post_type' => 'page', 'post_title' => $title, 'post_status' => $status, 'post_content' => '' ), true );
	if ( is_wp_error( $post_id ) ) {
		return $post_id;
	}
	$failure = null;
	try {
		$expected_nodes = array();
		FLBuilderModel::update_layout_data( array(), 'published', $post_id );
		FLBuilderModel::set_post_id( $post_id );
		try {
			foreach ( $rows as $row_index => $row_config ) {
				$layout = sanitize_key( $row_config['layout'] );
				$row    = FLBuilderModel::add_row( $layout );
				if ( ! is_object( $row ) || empty( $row->node ) ) {
					throw new RuntimeException( 'row' );
				}
				$applied = wpagent_beaver_apply_settings_to_node( $post_id, $row->node, is_array( $row_config['settings'] ?? null ) ? $row_config['settings'] : array() );
				if ( is_wp_error( $applied ) ) {
					$failure = $applied;
					throw new RuntimeException( 'row-settings' );
				}
				$layout_data = FLBuilderModel::get_layout_data( 'published', $post_id );
				$expected_nodes[ $row->node ] = array(
					'type'     => 'row',
					'parent'   => '',
					'position' => $row_index,
					'settings' => $layout_data[ $row->node ]->settings ?? new stdClass(),
				);
				$groups = FLBuilderModel::get_nodes( 'column-group', $row->node );
				$group  = reset( $groups );
				if ( 1 !== count( $groups ) || ! is_object( $group ) || $row->node !== (string) ( $group->parent ?? '' ) ) {
					throw new RuntimeException( 'group' );
				}
				$columns = array_values( FLBuilderModel::get_nodes( 'column', $group->node ) );
				if ( count( FLBuilderModel::$row_layouts[ $layout ] ) !== count( $columns ) ) {
					throw new RuntimeException( 'columns' );
				}
				$layout_data = FLBuilderModel::get_layout_data( 'published', $post_id );
				$expected_nodes[ $group->node ] = array(
					'type'     => 'column-group',
					'parent'   => $row->node,
					'position' => max( 0, (int) ( $group->position ?? 0 ) ),
					'settings' => $layout_data[ $group->node ]->settings ?? new stdClass(),
				);
				foreach ( $columns as $column_index => $column ) {
					if ( ! is_object( $column ) || empty( $column->node ) || $group->node !== (string) ( $column->parent ?? '' ) || $column_index !== (int) ( $column->position ?? -1 ) ) {
						throw new RuntimeException( 'column-structure' );
					}
					$expected_nodes[ $column->node ] = array(
						'type'     => 'column',
						'parent'   => $group->node,
						'position' => $column_index,
						'settings' => $layout_data[ $column->node ]->settings ?? new stdClass(),
					);
				}
				foreach ( is_array( $row_config['columns'] ?? null ) ? $row_config['columns'] : array() as $index => $column_config ) {
					if ( ! isset( $columns[ $index ] ) ) {
						throw new RuntimeException( 'column' );
					}
					$column_id = $columns[ $index ]->node;
					$applied   = wpagent_beaver_apply_settings_to_node( $post_id, $column_id, is_array( $column_config['settings'] ?? null ) ? $column_config['settings'] : array() );
					if ( is_wp_error( $applied ) ) {
						$failure = $applied;
						throw new RuntimeException( 'column-settings' );
					}
					$layout_data = FLBuilderModel::get_layout_data( 'published', $post_id );
					$expected_nodes[ $column_id ]['settings'] = $layout_data[ $column_id ]->settings ?? new stdClass();
					$module_position = 0;
					foreach ( is_array( $column_config['modules'] ?? null ) ? $column_config['modules'] : array() as $module_config ) {
						$module_type = sanitize_key( $module_config['type'] );
						$module      = FLBuilderModel::add_default_module( $column_id, $module_type );
						if ( ! is_object( $module ) || empty( $module->node ) ) {
							throw new RuntimeException( 'module' );
						}
						$applied = wpagent_beaver_apply_settings_to_node( $post_id, $module->node, is_array( $module_config['settings'] ?? null ) ? $module_config['settings'] : array() );
						if ( is_wp_error( $applied ) ) {
							$failure = $applied;
							throw new RuntimeException( 'module-settings' );
						}
						$layout_data = FLBuilderModel::get_layout_data( 'published', $post_id );
						$expected_nodes[ $module->node ] = array(
							'type'     => 'module',
							'parent'   => $column_id,
							'position' => $module_position,
							'settings' => $layout_data[ $module->node ]->settings ?? new stdClass(),
						);
						$module_position++;
					}
				}
			}
		} finally {
			FLBuilderModel::reset_post_id();
		}
		$finalized = wpagent_beaver_finalize_layout( $post_id );
		if ( is_wp_error( $finalized ) ) {
			$failure = $finalized;
			throw new RuntimeException( 'finalize' );
		}
		$state = wpagent_beaver_document_state( $post_id );
		if ( is_wp_error( $state ) ) {
			$failure = $state;
			throw new RuntimeException( 'readback' );
		}
		$actual_node_count = count( $state['nodes'] ?? array() );
		if ( $actual_node_count < $expected_node_count || $actual_node_count > WPAGENT_BEAVER_MAX_NODES ) {
			$failure = new WP_Error( 'wpagent_beaver_verify_failed', __( 'Die erstellte Beaver-Builder-Struktur stimmt nicht mit der angeforderten Struktur überein.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			throw new RuntimeException( 'readback' );
		}
		$final_data          = FLBuilderModel::get_layout_data( 'published', $post_id );
		$safe_created_layout = wpagent_beaver_validate_safe_subtrees(
			$final_data,
			array_keys( $final_data )
		);
		if ( is_wp_error( $safe_created_layout ) ) {
			$failure = $safe_created_layout;
			throw new RuntimeException( 'readback-safety' );
		}
		$verified_contract = wpagent_beaver_verify_mutation_contract(
			array( 'nodes' => $expected_nodes ),
			$final_data,
			FLBuilderModel::get_layout_settings( 'published', $post_id )
		);
		if ( is_wp_error( $verified_contract ) || $title !== (string) ( $state['title'] ?? '' ) || $status !== (string) ( $state['status'] ?? '' ) ) {
			$failure = is_wp_error( $verified_contract )
				? $verified_contract
				: new WP_Error( 'wpagent_beaver_verify_failed', __( 'Die erstellte Beaver-Builder-Seite entspricht nicht dem angeforderten Zielzustand.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			throw new RuntimeException( 'readback-contract' );
		}
		return array_merge( $state, array( 'changed' => true, 'verified' => true, 'revision_id' => 0 ) );
	} catch ( Throwable $error ) {
		wp_delete_post( $post_id, true );
		return $failure instanceof WP_Error
			? $failure
			: new WP_Error( 'wpagent_beaver_create_failed', __( 'Die Beaver-Builder-Seite konnte nicht verifiziert erstellt werden und wurde entfernt.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
}

function wpagent_ability_update_beaver_node(): array {
	$properties                    = wpagent_beaver_hash_input_properties();
	$properties['node_id']         = array( 'type' => 'string' );
	$properties['settings_patch']  = array( 'type' => 'object', 'additionalProperties' => true );
	$properties['remove_settings'] = array( 'type' => 'array', 'items' => array( 'type' => 'string' ) );
	return array(
		'label'               => __( 'Beaver-Builder-Knoten aktualisieren', 'wp-agent-companion' ),
		'description'         => __( 'Ändert schema-validierte Einstellungen eines Beaver-Builder-Knotens konfliktgeschützt und mit Revision, Frontend-Prüfung und Rollback.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array( 'type' => 'object', 'properties' => $properties, 'required' => array( 'post_id', 'node_id', 'expected_hash', 'settings_patch' ) ),
		'output_schema'       => wpagent_beaver_mutation_output_schema(),
		'execute_callback'    => 'wpagent_exec_update_beaver_node',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta'                => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => true ) ),
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_update_beaver_node( $input ) {
	$input         = is_array( $input ) ? $input : array();
	$post_id       = absint( $input['post_id'] ?? 0 );
	$node_id       = sanitize_key( is_string( $input['node_id'] ?? null ) ? $input['node_id'] : '' );
	$expected_hash = is_string( $input['expected_hash'] ?? null ) ? strtolower( $input['expected_hash'] ) : '';
	$patch         = is_array( $input['settings_patch'] ?? null ) ? $input['settings_patch'] : array();
	$remove        = is_array( $input['remove_settings'] ?? null ) ? array_values( array_filter( $input['remove_settings'], 'is_string' ) ) : array();
	$state         = wpagent_beaver_document_state( $post_id );
	if ( is_wp_error( $state ) ) {
		return $state;
	}
	if ( ! hash_equals( (string) $state['hash'], $expected_hash ) ) {
		return new WP_Error( 'wpagent_beaver_conflict', __( 'Das Beaver-Builder-Layout wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $state['hash'] ) );
	}
	$node = wpagent_beaver_node( $post_id, $node_id );
	if ( is_wp_error( $node ) ) {
		return $node;
	}
	$prepared = wpagent_beaver_prepare_node_settings( $node, $patch, $remove );
	if ( is_wp_error( $prepared ) ) {
		return $prepared;
	}
	if ( wpagent_beaver_canonicalize( $node->settings ?? array() ) === wpagent_beaver_canonicalize( $prepared ) ) {
		return array_merge( $state, array( 'node' => wpagent_beaver_public_node_by_id( $state, $node_id ), 'changed' => false, 'verified' => true, 'revision_id' => 0 ) );
	}
	$result = wpagent_beaver_mutate(
		$post_id,
		$expected_hash,
		static function () use ( $post_id, $node_id, $prepared ): array {
			$data                        = FLBuilderModel::get_layout_data( 'published', $post_id );
			$data[ $node_id ]->settings = $prepared;
			FLBuilderModel::update_layout_data( $data, 'published', $post_id );
			return array(
				'target_node_id' => $node_id,
				'_verification'   => array(
					'nodes' => array(
						$node_id => array(
							'type'     => sanitize_key( (string) ( $data[ $node_id ]->type ?? '' ) ),
							'parent'   => sanitize_key( (string) ( $data[ $node_id ]->parent ?? '' ) ),
							'position' => max( 0, (int) ( $data[ $node_id ]->position ?? 0 ) ),
							'settings' => $prepared,
						),
					),
				),
			);
		}
	);
	return wpagent_beaver_attach_target_node( $result );
}

function wpagent_ability_create_beaver_module(): array {
	$properties                  = wpagent_beaver_hash_input_properties();
	$properties['parent_id']     = array( 'type' => 'string' );
	$properties['module_type']   = array( 'type' => 'string' );
	$properties['position']      = array( 'type' => 'integer', 'minimum' => 0 );
	$properties['settings']      = array( 'type' => 'object', 'additionalProperties' => true );
	return wpagent_beaver_write_ability_definition(
		__( 'Beaver-Builder-Modul erstellen', 'wp-agent-companion' ),
		__( 'Erstellt ein sicheres registriertes Modul in einer vorhandenen Beaver-Builder-Spalte.', 'wp-agent-companion' ),
		$properties,
		array( 'post_id', 'parent_id', 'module_type', 'expected_hash' ),
		'wpagent_exec_create_beaver_module',
		false
	);
}

/** @return array<string,mixed> */
function wpagent_beaver_write_ability_definition( string $label, string $description, array $properties, array $required, string $callback, bool $destructive ): array {
	return array(
		'label' => $label, 'description' => $description, 'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => $properties, 'required' => $required ),
		'output_schema' => wpagent_beaver_mutation_output_schema(), 'execute_callback' => $callback,
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => $destructive, 'idempotent' => false ) ),
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_beaver_attach_target_node( $result ) {
	if ( is_wp_error( $result ) ) {
		return $result;
	}
	$target_id = sanitize_key( (string) ( $result['target_node_id'] ?? '' ) );
	$result['node'] = wpagent_beaver_public_node_by_id( $result, $target_id );
	unset( $result['target_node_id'] );
	if ( ! is_array( $result['node'] ) ) {
		return new WP_Error( 'wpagent_beaver_verify_failed', __( 'Der geänderte Beaver-Builder-Knoten fehlt im Read-back.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	return $result;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_create_beaver_module( $input ) {
	$input         = is_array( $input ) ? $input : array();
	$post_id       = absint( $input['post_id'] ?? 0 );
	$parent_id     = sanitize_key( is_string( $input['parent_id'] ?? null ) ? $input['parent_id'] : '' );
	$module_type   = sanitize_key( is_string( $input['module_type'] ?? null ) ? $input['module_type'] : '' );
	$expected_hash = is_string( $input['expected_hash'] ?? null ) ? strtolower( $input['expected_hash'] ) : '';
	$position      = isset( $input['position'] ) ? max( 0, absint( $input['position'] ) ) : null;
	$settings      = is_array( $input['settings'] ?? null ) ? $input['settings'] : array();
	$post          = wpagent_beaver_editable_post( $post_id );
	if ( is_wp_error( $post ) ) {
		return $post;
	}
	$module        = wpagent_beaver_module( $module_type );
	if ( is_wp_error( $module ) ) {
		return $module;
	}
	$parent = wpagent_beaver_node( $post_id, $parent_id );
	if ( is_wp_error( $parent ) ) {
		return $parent;
	}
	if ( 'column' !== ( $parent->type ?? '' ) ) {
		return new WP_Error( 'wpagent_beaver_bad_parent', __( 'Neue Module dürfen im sicheren Pfad nur in Beaver-Builder-Spalten eingefügt werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$temp           = new stdClass();
	$temp->node     = 'wpagently-preview';
	$temp->type     = 'module';
	$temp->parent   = $parent_id;
	$temp->position = 0;
	$temp->settings = FLBuilderModel::get_module_defaults( $module_type );
	$temp->settings->type = $module_type;
	$prepared = wpagent_beaver_prepare_node_settings( $temp, $settings );
	if ( is_wp_error( $prepared ) ) {
		return $prepared;
	}
	$data = FLBuilderModel::get_layout_data( 'published', $post_id );
	$sibling_count = count(
		array_filter(
			$data,
			static fn( $candidate ): bool => is_object( $candidate ) && $parent_id === sanitize_key( (string) ( $candidate->parent ?? '' ) )
		)
	);
	if ( null !== $position && $position > $sibling_count ) {
		return new WP_Error( 'wpagent_beaver_bad_position', __( 'Die angeforderte Beaver-Builder-Position liegt außerhalb der Zielspalte.', 'wp-agent-companion' ), array( 'status' => 400, 'maximum' => $sibling_count ) );
	}
	$expected_position = null === $position ? $sibling_count : $position;
	$result = wpagent_beaver_mutate(
		$post_id,
		$expected_hash,
		static function () use ( $parent_id, $module_type, $position, $prepared, $expected_position ) {
			$created = FLBuilderModel::add_default_module( $parent_id, $module_type, $position );
			if ( ! is_object( $created ) || empty( $created->node ) ) {
				return new WP_Error( 'wpagent_beaver_create_module_failed', __( 'Beaver Builder konnte das Modul nicht erstellen.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$data                              = FLBuilderModel::get_layout_data();
			$data[ $created->node ]->settings = $prepared;
			FLBuilderModel::update_layout_data( $data );
			return array(
				'target_node_id' => $created->node,
				'_verification'   => array(
					'nodes' => array(
						$created->node => array(
							'type'     => 'module',
							'parent'   => $parent_id,
							'position' => $expected_position,
							'settings' => $prepared,
						),
					),
					'safe_subtree_roots' => array( $created->node ),
				),
			);
		}
	);
	return wpagent_beaver_attach_target_node( $result );
}

function wpagent_ability_duplicate_beaver_node(): array {
	$properties            = wpagent_beaver_hash_input_properties();
	$properties['node_id'] = array( 'type' => 'string' );
	return wpagent_beaver_write_ability_definition( __( 'Beaver-Builder-Knoten duplizieren', 'wp-agent-companion' ), __( 'Dupliziert eine Zeile, Spalte oder ein sicheres Modul über Beaver Builders Modell-API.', 'wp-agent-companion' ), $properties, array( 'post_id', 'node_id', 'expected_hash' ), 'wpagent_exec_duplicate_beaver_node', false );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_duplicate_beaver_node( $input ) {
	$input         = is_array( $input ) ? $input : array();
	$post_id       = absint( $input['post_id'] ?? 0 );
	$node_id       = sanitize_key( is_string( $input['node_id'] ?? null ) ? $input['node_id'] : '' );
	$expected_hash = is_string( $input['expected_hash'] ?? null ) ? strtolower( $input['expected_hash'] ) : '';
	$post          = wpagent_beaver_editable_post( $post_id );
	if ( is_wp_error( $post ) ) {
		return $post;
	}
	$node           = wpagent_beaver_node( $post_id, $node_id );
	if ( is_wp_error( $node ) ) {
		return $node;
	}
	if ( 'module' === ( $node->type ?? '' ) ) {
		$module_type = sanitize_key( (string) ( $node->settings->type ?? '' ) );
		if ( is_wp_error( wpagent_beaver_module( $module_type ) ) ) {
			return new WP_Error( 'wpagent_beaver_unsafe_module', __( 'Dieses Beaver-Builder-Modul darf nicht dupliziert werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
	}
	if ( ! in_array( $node->type ?? '', array( 'row', 'column', 'module' ), true ) || ! empty( $node->global ) || ! empty( $node->template_id ) ) {
		return new WP_Error( 'wpagent_beaver_node_not_writable', __( 'Dieser Beaver-Builder-Knoten darf nicht dupliziert werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$safe_subtree = wpagent_beaver_validate_safe_subtrees( FLBuilderModel::get_layout_data( 'published', $post_id ), array( $node_id ) );
	if ( is_wp_error( $safe_subtree ) ) {
		return $safe_subtree;
	}
	$source_data      = FLBuilderModel::get_layout_data( 'published', $post_id );
	$source_signature = wpagent_beaver_subtree_signature( $source_data, $node_id );
	if ( is_wp_error( $source_signature ) ) {
		return $source_signature;
	}
	$expected_parent   = sanitize_key( (string) ( $node->parent ?? '' ) );
	$expected_position = max( 0, (int) ( $node->position ?? 0 ) + 1 );
	$result = wpagent_beaver_mutate(
		$post_id,
		$expected_hash,
		static function () use ( $node_id, $node, $source_signature, $expected_parent, $expected_position ) {
			if ( 'row' === $node->type ) {
				$copy = FLBuilderModel::copy_row( $node_id, null, null, false );
			} elseif ( 'column' === $node->type ) {
				$copy = FLBuilderModel::copy_col( $node_id, null, null, false );
			} else {
				$copy = FLBuilderModel::copy_module( $node_id, null, null, null, false );
			}
			if ( ! is_object( $copy ) || empty( $copy->node ) ) {
				return new WP_Error( 'wpagent_beaver_duplicate_failed', __( 'Beaver Builder konnte den Knoten nicht duplizieren.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$verification = array(
				'nodes'           => array(
					$copy->node => array(
						'type'     => sanitize_key( (string) $node->type ),
						'parent'   => $expected_parent,
						'position' => $expected_position,
					),
				),
				'subtree_matches' => array( array( 'source_id' => $node_id, 'target_id' => $copy->node ) ),
			);
			if ( 'column' !== $node->type ) {
				$verification['subtree_signatures'] = array( $node_id => $source_signature );
			}
			return array(
				'target_node_id' => $copy->node,
				'_verification'   => $verification,
			);
		}
	);
	return wpagent_beaver_attach_target_node( $result );
}

function wpagent_ability_move_beaver_node(): array {
	$properties              = wpagent_beaver_hash_input_properties();
	$properties['node_id']   = array( 'type' => 'string' );
	$properties['parent_id'] = array( 'type' => 'string' );
	$properties['position']  = array( 'type' => 'integer', 'minimum' => 0 );
	return wpagent_beaver_write_ability_definition( __( 'Beaver-Builder-Knoten verschieben', 'wp-agent-companion' ), __( 'Verschiebt ein sicheres Modul konfliktgeschützt in eine vorhandene Beaver-Builder-Spalte.', 'wp-agent-companion' ), $properties, array( 'post_id', 'node_id', 'parent_id', 'expected_hash' ), 'wpagent_exec_move_beaver_node', false );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_move_beaver_node( $input ) {
	$input         = is_array( $input ) ? $input : array();
	$post_id       = absint( $input['post_id'] ?? 0 );
	$node_id       = sanitize_key( is_string( $input['node_id'] ?? null ) ? $input['node_id'] : '' );
	$parent_id     = sanitize_key( is_string( $input['parent_id'] ?? null ) ? $input['parent_id'] : '' );
	$expected_hash = is_string( $input['expected_hash'] ?? null ) ? strtolower( $input['expected_hash'] ) : '';
	$position      = max( 0, absint( $input['position'] ?? 0 ) );
	$post          = wpagent_beaver_editable_post( $post_id );
	if ( is_wp_error( $post ) ) {
		return $post;
	}
	$node           = wpagent_beaver_node( $post_id, $node_id );
	$parent         = wpagent_beaver_node( $post_id, $parent_id );
	if ( is_wp_error( $node ) ) {
		return $node;
	}
	if ( is_wp_error( $parent ) ) {
		return $parent;
	}
	if ( 'module' !== ( $node->type ?? '' ) || 'column' !== ( $parent->type ?? '' ) || ! empty( $node->global ) || ! empty( $node->template_id ) ) {
		return new WP_Error( 'wpagent_beaver_bad_move', __( 'Im sicheren Pfad können nur lokale Module zwischen Beaver-Builder-Spalten verschoben werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( is_wp_error( wpagent_beaver_module( sanitize_key( (string) ( $node->settings->type ?? '' ) ) ) ) ) {
		return new WP_Error( 'wpagent_beaver_unsafe_module', __( 'Dieses Beaver-Builder-Modul darf nicht verschoben werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$data = FLBuilderModel::get_layout_data( 'published', $post_id );
	$destination_count = count(
		array_filter(
			$data,
			static fn( $candidate, $candidate_id ): bool => is_object( $candidate )
				&& $node_id !== sanitize_key( (string) $candidate_id )
				&& $parent_id === sanitize_key( (string) ( $candidate->parent ?? '' ) ),
			ARRAY_FILTER_USE_BOTH
		)
	);
	if ( $position > $destination_count ) {
		return new WP_Error( 'wpagent_beaver_bad_position', __( 'Die angeforderte Beaver-Builder-Position liegt außerhalb der Zielspalte.', 'wp-agent-companion' ), array( 'status' => 400, 'maximum' => $destination_count ) );
	}
	if ( $parent_id === (string) ( $node->parent ?? '' ) && $position === (int) ( $node->position ?? 0 ) ) {
		$state = wpagent_beaver_document_state( $post_id );
		if ( is_wp_error( $state ) ) {
			return $state;
		}
		if ( ! hash_equals( (string) $state['hash'], $expected_hash ) ) {
			return new WP_Error( 'wpagent_beaver_conflict', __( 'Das Beaver-Builder-Layout wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		return array_merge( $state, array( 'node' => wpagent_beaver_public_node_by_id( $state, $node_id ), 'changed' => false, 'verified' => true, 'revision_id' => 0 ) );
	}
	$result = wpagent_beaver_mutate(
		$post_id,
		$expected_hash,
		static function () use ( $node_id, $parent_id, $position, $node ): array {
			FLBuilderModel::move_node( $node_id, $parent_id, $position );
			return array(
				'target_node_id' => $node_id,
				'_verification'   => array(
					'nodes' => array(
						$node_id => array(
							'type'     => 'module',
							'parent'   => $parent_id,
							'position' => $position,
							'settings' => $node->settings ?? new stdClass(),
						),
					),
				),
			);
		}
	);
	return wpagent_beaver_attach_target_node( $result );
}

function wpagent_ability_delete_beaver_node(): array {
	$properties             = wpagent_beaver_hash_input_properties();
	$properties['node_id']  = array( 'type' => 'string' );
	$properties['confirm']  = array( 'type' => 'boolean' );
	return wpagent_beaver_write_ability_definition( __( 'Beaver-Builder-Knoten löschen', 'wp-agent-companion' ), __( 'Löscht nach ausdrücklicher Bestätigung einen lokalen Beaver-Builder-Knoten samt Nachfahren.', 'wp-agent-companion' ), $properties, array( 'post_id', 'node_id', 'expected_hash', 'confirm' ), 'wpagent_exec_delete_beaver_node', true );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_delete_beaver_node( $input ) {
	$input         = is_array( $input ) ? $input : array();
	$post_id       = absint( $input['post_id'] ?? 0 );
	$node_id       = sanitize_key( is_string( $input['node_id'] ?? null ) ? $input['node_id'] : '' );
	$expected_hash = is_string( $input['expected_hash'] ?? null ) ? strtolower( $input['expected_hash'] ) : '';
	if ( true !== ( $input['confirm'] ?? false ) ) {
		return new WP_Error( 'wpagent_beaver_confirmation_required', __( 'Das Löschen erfordert confirm=true.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$post = wpagent_beaver_editable_post( $post_id );
	if ( is_wp_error( $post ) ) {
		return $post;
	}
	$node = wpagent_beaver_node( $post_id, $node_id );
	if ( is_wp_error( $node ) ) {
		return $node;
	}
	if ( ! empty( $node->global ) || ! empty( $node->template_id ) ) {
		return new WP_Error( 'wpagent_beaver_global_node', __( 'Globale oder verknüpfte Beaver-Builder-Knoten werden nicht gelöscht.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$safe_subtree = wpagent_beaver_validate_safe_subtrees( FLBuilderModel::get_layout_data( 'published', $post_id ), array( $node_id ) );
	if ( is_wp_error( $safe_subtree ) ) {
		return $safe_subtree;
	}
	$data       = FLBuilderModel::get_layout_data( 'published', $post_id );
	$deleted_ids = array();
	$queue       = array( $node_id );
	while ( array() !== $queue ) {
		$current_id = array_pop( $queue );
		if ( isset( $deleted_ids[ $current_id ] ) ) {
			continue;
		}
		$deleted_ids[ $current_id ] = true;
		foreach ( $data as $candidate_id => $candidate ) {
			if ( is_object( $candidate ) && $current_id === sanitize_key( (string) ( $candidate->parent ?? '' ) ) ) {
				$queue[] = sanitize_key( (string) $candidate_id );
			}
		}
	}
	$result = wpagent_beaver_mutate(
		$post_id,
		$expected_hash,
		static function () use ( $node_id, $deleted_ids ): array {
			FLBuilderModel::delete_node( $node_id );
			return array(
				'deleted_node_id' => $node_id,
				'_verification'   => array( 'absent_ids' => array_keys( $deleted_ids ) ),
			);
		}
	);
	if ( is_wp_error( $result ) ) {
		return $result;
	}
	$result['node'] = array( 'id' => $node_id, 'type' => '', 'module_type' => '', 'parent' => '', 'position' => 0, 'writable' => false, 'settings' => array() );
	return $result;
}

function wpagent_ability_restore_beaver_revision(): array {
	$properties                = wpagent_beaver_hash_input_properties();
	$properties['revision_id'] = array( 'type' => 'integer' );
	$properties['confirm']     = array( 'type' => 'boolean' );
	return wpagent_beaver_write_ability_definition( __( 'Beaver-Builder-Revision wiederherstellen', 'wp-agent-companion' ), __( 'Stellt eine zugehörige Beaver-Builder-Revision konfliktgeschützt wieder her und legt vorher eine neue Rückfallrevision an.', 'wp-agent-companion' ), $properties, array( 'post_id', 'revision_id', 'expected_hash', 'confirm' ), 'wpagent_exec_restore_beaver_revision', true );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_restore_beaver_revision( $input ) {
	$input         = is_array( $input ) ? $input : array();
	$post_id       = absint( $input['post_id'] ?? 0 );
	$revision_id   = absint( $input['revision_id'] ?? 0 );
	$expected_hash = is_string( $input['expected_hash'] ?? null ) ? strtolower( $input['expected_hash'] ) : '';
	if ( true !== ( $input['confirm'] ?? false ) ) {
		return new WP_Error( 'wpagent_beaver_confirmation_required', __( 'Das Wiederherstellen erfordert confirm=true.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$post = wpagent_beaver_editable_post( $post_id );
	if ( is_wp_error( $post ) ) {
		return $post;
	}
	$revision = get_post( $revision_id );
	if ( ! $revision || 'revision' !== $revision->post_type || $post_id !== (int) $revision->post_parent ) {
		return new WP_Error( 'wpagent_beaver_revision_not_found', __( 'Die Beaver-Builder-Revision gehört nicht zu diesem Beitrag.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	$revision_data = FLBuilderModel::get_layout_data( 'published', $revision_id );
	if ( empty( $revision_data ) ) {
		return new WP_Error( 'wpagent_beaver_revision_empty', __( 'Die Revision enthält keine Beaver-Builder-Layoutdaten.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$safe_revision = wpagent_beaver_validate_safe_subtrees( $revision_data, array_keys( $revision_data ), 'wpagent_beaver_unsafe_revision' );
	if ( is_wp_error( $safe_revision ) ) {
		return $safe_revision;
	}
	$revision_settings = FLBuilderModel::get_layout_settings( 'published', $revision_id );
	$revision_hash     = wpagent_beaver_state_hash( $revision_data, $revision_settings );
	$result = wpagent_beaver_mutate(
		$post_id,
		$expected_hash,
		static function () use ( $post_id, $revision_id, $revision_hash ): array {
			FLBuilderModel::restore_revision( $post_id, $revision_id );
			return array(
				'restored_revision_id' => $revision_id,
				'_verification'        => array( 'state_hash' => $revision_hash ),
			);
		}
	);
	if ( is_wp_error( $result ) ) {
		return $result;
	}
	$result['node'] = array( 'id' => '', 'type' => '', 'module_type' => '', 'parent' => '', 'position' => 0, 'writable' => false, 'settings' => array() );
	return $result;
}
