<?php
/** Verified create, duplicate, move and delete operations for Elementor subtrees. */

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

/** @return array<string,mixed> */
function wpagent_elementor_tree_target_input_properties(): array {
	return array(
		'post_id'       => array( 'type' => 'integer' ),
		'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
		'parent_id'     => array( 'type' => 'string', 'pattern' => '^[A-Za-z0-9_-]{1,64}$' ),
		'index'         => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ELEMENTOR_MAX_ELEMENTS ),
	);
}

/** @return array<string,mixed> */
function wpagent_elementor_tree_write_output_schema(): array {
	$schema = wpagent_elementor_write_output_schema();
	$schema['properties']['operation'] = array( 'type' => 'string' );
	$schema['properties']['changed'] = array( 'type' => 'boolean' );
	$schema['properties']['element_id'] = array( 'type' => 'string' );
	$schema['properties']['path'] = array( 'type' => 'array', 'maxItems' => WPAGENT_ELEMENTOR_MAX_DEPTH, 'items' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ELEMENTOR_MAX_ELEMENTS ) );
	$schema['properties']['previous_path'] = array( 'type' => 'array', 'maxItems' => WPAGENT_ELEMENTOR_MAX_DEPTH, 'items' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ELEMENTOR_MAX_ELEMENTS ) );
	$schema['properties']['element'] = wpagent_elementor_json_document_schema();
	$schema['properties']['deleted_element'] = wpagent_elementor_json_document_schema();
	return $schema;
}

function wpagent_ability_create_elementor_element(): array {
	$properties = wpagent_elementor_tree_target_input_properties();
	$properties['element'] = wpagent_elementor_json_document_schema();
	return array(
		'label' => __( 'Elementor-Element erstellen', 'wp-agent-companion' ),
		'description' => __( 'Erstellt einen sicheren Elementor-Teilbaum an einer bestimmten Position. Alle Element-IDs werden neu erzeugt; Konfliktschutz, Revision sowie Rücklese- und Frontend-Prüfung sind verpflichtend. Unklare Providerzustände bleiben zur manuellen Prüfung erhalten.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => $properties, 'required' => array( 'post_id', 'expected_hash', 'element' ), 'additionalProperties' => false ),
		'output_schema' => wpagent_elementor_tree_write_output_schema(),
		'execute_callback' => 'wpagent_exec_create_elementor_element',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

function wpagent_ability_duplicate_elementor_element(): array {
	$properties = wpagent_elementor_tree_target_input_properties();
	$properties['element_id'] = array( 'type' => 'string', 'pattern' => '^[A-Za-z0-9_-]{1,64}$' );
	return array(
		'label' => __( 'Elementor-Element duplizieren', 'wp-agent-companion' ),
		'description' => __( 'Dupliziert einen vorhandenen Elementor-Teilbaum mit neuen IDs und fügt ihn an einer bestimmten Position ein. Die Quelle bleibt unverändert.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => $properties, 'required' => array( 'post_id', 'element_id', 'expected_hash' ), 'additionalProperties' => false ),
		'output_schema' => wpagent_elementor_tree_write_output_schema(),
		'execute_callback' => 'wpagent_exec_duplicate_elementor_element',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

function wpagent_ability_move_elementor_element(): array {
	$properties = wpagent_elementor_tree_target_input_properties();
	$properties['element_id'] = array( 'type' => 'string', 'pattern' => '^[A-Za-z0-9_-]{1,64}$' );
	return array(
		'label' => __( 'Elementor-Element verschieben', 'wp-agent-companion' ),
		'description' => __( 'Verschiebt einen Elementor-Teilbaum ohne seine ID oder Einstellungen zu verändern. Selbst- und Nachfahrenziele werden abgelehnt.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => $properties, 'required' => array( 'post_id', 'element_id', 'expected_hash' ), 'additionalProperties' => false ),
		'output_schema' => wpagent_elementor_tree_write_output_schema(),
		'execute_callback' => 'wpagent_exec_move_elementor_element',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

function wpagent_ability_delete_elementor_element(): array {
	return array(
		'label' => __( 'Elementor-Element löschen', 'wp-agent-companion' ),
		'description' => __( 'Prüft einen Elementor-Teilbaum per Hash und verweist für die Löschung in den Elementor-Editor. Remote-Löschungen sind deaktiviert, weil Elementor keine atomare Compare-and-Delete-Operation gegen parallele Editor-Änderungen bietet.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array(
			'type' => 'object',
			'properties' => array(
				'post_id' => array( 'type' => 'integer' ),
				'element_id' => array( 'type' => 'string', 'pattern' => '^[A-Za-z0-9_-]{1,64}$' ),
				'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'confirm' => array( 'type' => 'boolean' ),
			),
			'required' => array( 'post_id', 'element_id', 'expected_hash', 'confirm' ),
			'additionalProperties' => false,
		),
		'output_schema' => array(
			'type'                 => 'object',
			'properties'           => array(),
			'additionalProperties' => false,
		),
		'execute_callback' => 'wpagent_exec_delete_elementor_element',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => true, 'idempotent' => false, 'manual_only' => true ) ),
	);
}

/** @return array{post_id:int,expected_hash:string}|WP_Error */
function wpagent_elementor_parse_tree_write_request( array $input ) {
	$post_id = absint( $input['post_id'] ?? 0 );
	$expected_hash = is_string( $input['expected_hash'] ?? null ) ? $input['expected_hash'] : '';
	if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected_hash ) ) {
		return new WP_Error( 'wpagent_elementor_hash', __( 'expected_hash muss ein SHA-256-Hash sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$loaded = wpagent_elementor_editable_document( $post_id );
	if ( is_wp_error( $loaded ) ) {
		return $loaded;
	}
	$current = wpagent_elementor_document_state( $post_id, $loaded['document'] );
	if ( ! hash_equals( $current['hash'], $expected_hash ) ) {
		return new WP_Error( 'wpagent_elementor_conflict', __( 'Das Elementor-Dokument wurde seit dem Lesen geändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $current['hash'] ) );
	}
	return array( 'post_id' => $post_id, 'expected_hash' => $expected_hash );
}

/**
 * @param callable(array<int,array<string,mixed>>,array<string,mixed>):array<string,mixed>|WP_Error $mutation Receives the mutable element tree and the immutable pre-write state.
 * @return array<string,mixed>|WP_Error
 */
function wpagent_elementor_commit_tree_mutation( int $post_id, string $expected_hash, string $operation, callable $mutation ) {
	$lock = wpagent_elementor_acquire_lock( $post_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		if ( ! wpagent_elementor_lock_checkpoint( $post_id, $lock ) ) {
			return wpagent_elementor_recovery_required( $post_id, $expected_hash, 'lock_lost_before_initial_read' );
		}
		$loaded = wpagent_elementor_editable_document( $post_id );
		if ( is_wp_error( $loaded ) ) {
			return $loaded;
		}
		$before = wpagent_elementor_document_state( $post_id, $loaded['document'] );
		if ( ! hash_equals( $before['hash'], $expected_hash ) ) {
			return new WP_Error( 'wpagent_elementor_conflict', __( 'Das Elementor-Dokument wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['hash'] ) );
		}
		$current_valid = wpagent_elementor_validate_elements( $before['elements'] );
		if ( is_wp_error( $current_valid ) ) {
			return $current_valid;
		}
		try {
			$mutation_result = $mutation( $before['elements'], $before );
		} catch ( Throwable ) {
			return new WP_Error( 'wpagent_elementor_mutation_failed', __( 'Die Elementor-Strukturänderung konnte nicht vorbereitet werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		if ( is_wp_error( $mutation_result ) ) {
			return $mutation_result;
		}
		$elements = $mutation_result['elements'] ?? null;
		if ( ! is_array( $elements ) || ! array_is_list( $elements ) ) {
			return new WP_Error( 'wpagent_elementor_mutation_failed', __( 'Die Elementor-Strukturänderung lieferte keine gültige Elementliste.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		$normalized = wpagent_elementor_normalize_elements( $loaded['document'], $elements );
		if ( is_wp_error( $normalized ) ) {
			return $normalized;
		}
		if ( $normalized === $before['elements'] ) {
			$frontend = wpagent_elementor_verify_frontend( $post_id, $before['element_count'] );
			if ( is_wp_error( $frontend ) ) {
				return $frontend;
			}
			$result = $before;
			$result['verified'] = true;
			$result['frontend_verified'] = true;
			$result['previous_hash'] = $before['hash'];
			$result['revision_id'] = 0;
			$result['operation'] = $operation;
			$result['changed'] = false;
			return wpagent_elementor_publicize_result( array_merge( $result, $mutation_result['meta'] ?? array() ) );
		}
		if ( ! wpagent_elementor_lock_checkpoint( $post_id, $lock ) ) {
			return wpagent_elementor_recovery_required( $post_id, $before['hash'], 'lock_lost_before_revision' );
		}
		$revision_id = wpagent_elementor_save_revision( $post_id, $before['hash'] );
		if ( is_wp_error( $revision_id ) ) {
			return $revision_id;
		}
		if ( ! wpagent_elementor_lock_checkpoint( $post_id, $lock ) ) {
			return wpagent_elementor_recovery_required( $post_id, $before['hash'], 'lock_lost_after_revision' );
		}
		$prewrite = wpagent_elementor_editable_document( $post_id );
		if ( is_wp_error( $prewrite ) ) {
			return wpagent_elementor_recovery_required( $post_id, $before['hash'], 'capability_lost_before_provider_write' );
		}
		$prewrite_state = wpagent_elementor_document_state( $post_id, $prewrite['document'] );
		if ( ! hash_equals( $before['hash'], $prewrite_state['hash'] ) ) {
			return new WP_Error( 'wpagent_elementor_conflict', __( 'Das Elementor-Dokument wurde vor dem Speichern verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $prewrite_state['hash'] ) );
		}
		if ( ! wpagent_elementor_lock_checkpoint( $post_id, $lock ) ) {
			return wpagent_elementor_recovery_required( $post_id, $before['hash'], 'lock_lost_before_provider_write' );
		}
		try {
			$saved = wpagent_elementor_save_document( $prewrite['document'], array( 'elements' => $elements ), false );
		} catch ( Throwable ) {
			return wpagent_elementor_recovery_required( $post_id, $before['hash'], 'provider_write_exception' );
		}
		if ( ! wpagent_elementor_lock_checkpoint( $post_id, $lock ) ) {
			return wpagent_elementor_recovery_required( $post_id, $before['hash'], 'lock_lost_after_provider_write' );
		}
		$fresh_loaded = wpagent_elementor_editable_document( $post_id );
		if ( is_wp_error( $fresh_loaded ) ) {
			return wpagent_elementor_recovery_required( $post_id, $before['hash'], 'capability_lost_before_readback' );
		}
		$after = wpagent_elementor_document_state( $post_id, $fresh_loaded['document'] );
		$verified = true === $saved && is_array( $after ) && wpagent_elementor_elements_contain_requested( $after['elements'], $normalized );
		$element_id = is_string( $mutation_result['meta']['element_id'] ?? null ) ? $mutation_result['meta']['element_id'] : '';
		$fresh_element = null;
		if ( $verified && '' !== $element_id ) {
			$fresh_element = wpagent_elementor_locate_element( $after['elements'], $element_id );
			$expect_absent = ! empty( $mutation_result['expect_absent'] );
			$verified = $expect_absent
				? is_wp_error( $fresh_element ) && 'wpagent_elementor_element_not_found' === $fresh_element->get_error_code()
				: is_array( $fresh_element );
		}
		$frontend = $verified ? wpagent_elementor_verify_frontend( $post_id, $after['element_count'] ) : false;
		if ( ! wpagent_elementor_lock_checkpoint( $post_id, $lock ) ) {
			return wpagent_elementor_recovery_required( $post_id, $before['hash'], 'lock_lost_after_readback' );
		}
		if ( ! $verified || is_wp_error( $frontend ) ) {
			return wpagent_elementor_recovery_required( $post_id, $before['hash'], 'provider_readback_or_frontend_verification_failed' );
		}
		$after['verified'] = true;
		$after['frontend_verified'] = true;
		$after['previous_hash'] = $before['hash'];
		$after['revision_id'] = $revision_id;
		$after['operation'] = $operation;
		$after['changed'] = true;
		$after = array_merge( $after, $mutation_result['meta'] ?? array() );
		if ( is_array( $fresh_element ) ) {
			$after['path'] = $fresh_element['path'];
			$after['element'] = $fresh_element['element'];
		}
		return wpagent_elementor_publicize_result( $after );
	} finally {
		wpagent_elementor_release_lock( $post_id, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_create_elementor_element( $input ) {
	$input = is_array( $input ) ? $input : array();
	$target = wpagent_elementor_parse_tree_target( $input );
	if ( is_wp_error( $target ) ) {
		return $target;
	}
	// Compare the document before parsing a potentially expensive subtree so a
	// stale caller receives the established conflict response and cannot infer
	// validation details about a newer document state.
	$request = wpagent_elementor_parse_tree_write_request( $input );
	if ( is_wp_error( $request ) ) {
		return $request;
	}
	$element = wpagent_elementor_json_document_input( $input['element'] ?? null, 'wpagent_elementor_validate_new_subtree_input' );
	if ( is_wp_error( $element ) ) {
		return $element;
	}
	$encoded = wp_json_encode( $element );
	if ( false === $encoded || strlen( $encoded ) > WPAGENT_ELEMENTOR_MAX_BYTES ) {
		return new WP_Error( 'wpagent_elementor_payload_size', __( 'Der neue Elementor-Teilbaum darf höchstens 2 MiB groß sein.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	return wpagent_elementor_commit_tree_mutation(
		$request['post_id'],
		$request['expected_hash'],
		'create',
		static function ( array $elements, array $_before ) use ( $target, $element ) {
			$ids = wpagent_elementor_tree_ids( $elements );
			if ( is_wp_error( $ids ) ) {
				return $ids;
			}
			$count = 0;
			$prepared = wpagent_elementor_prepare_new_subtree( $element, $ids, 0, $count );
			if ( is_wp_error( $prepared ) ) {
				return $prepared;
			}
			$parent_path = wpagent_elementor_parent_path( $elements, $target['parent_id'] );
			if ( is_wp_error( $parent_path ) ) {
				return $parent_path;
			}
			$path = wpagent_elementor_insert_under_parent( $elements, $parent_path, $target['index'], $prepared );
			if ( is_wp_error( $path ) ) {
				return $path;
			}
			return array( 'elements' => $elements, 'meta' => array( 'element_id' => $prepared['id'], 'path' => $path ) );
		}
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_duplicate_elementor_element( $input ) {
	$input = is_array( $input ) ? $input : array();
	$element_id = is_string( $input['element_id'] ?? null ) ? $input['element_id'] : '';
	if ( ! preg_match( '/^[A-Za-z0-9_-]{1,64}$/', $element_id ) ) {
		return new WP_Error( 'wpagent_elementor_bad_id', __( 'element_id ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$target = wpagent_elementor_parse_tree_target( $input );
	if ( is_wp_error( $target ) ) {
		return $target;
	}
	$request = wpagent_elementor_parse_tree_write_request( $input );
	if ( is_wp_error( $request ) ) {
		return $request;
	}
	return wpagent_elementor_commit_tree_mutation(
		$request['post_id'],
		$request['expected_hash'],
		'duplicate',
		static function ( array $elements, array $_before ) use ( $element_id, $target ) {
			$source = wpagent_elementor_locate_element( $elements, $element_id );
			if ( is_wp_error( $source ) ) {
				return $source;
			}
			$ids = wpagent_elementor_tree_ids( $elements );
			if ( is_wp_error( $ids ) ) {
				return $ids;
			}
			$copy = wpagent_elementor_duplicate_subtree( $source['element'], $ids );
			if ( is_wp_error( $copy ) ) {
				return $copy;
			}
			$parent_path = wpagent_elementor_parent_path( $elements, $target['parent_id'] );
			if ( is_wp_error( $parent_path ) ) {
				return $parent_path;
			}
			$path = wpagent_elementor_insert_under_parent( $elements, $parent_path, $target['index'], $copy );
			if ( is_wp_error( $path ) ) {
				return $path;
			}
			return array( 'elements' => $elements, 'meta' => array( 'element_id' => $copy['id'], 'path' => $path, 'previous_path' => $source['path'] ) );
		}
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_move_elementor_element( $input ) {
	$input = is_array( $input ) ? $input : array();
	$element_id = is_string( $input['element_id'] ?? null ) ? $input['element_id'] : '';
	if ( ! preg_match( '/^[A-Za-z0-9_-]{1,64}$/', $element_id ) ) {
		return new WP_Error( 'wpagent_elementor_bad_id', __( 'element_id ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$target = wpagent_elementor_parse_tree_target( $input );
	if ( is_wp_error( $target ) ) {
		return $target;
	}
	$request = wpagent_elementor_parse_tree_write_request( $input );
	if ( is_wp_error( $request ) ) {
		return $request;
	}
	return wpagent_elementor_commit_tree_mutation(
		$request['post_id'],
		$request['expected_hash'],
		'move',
		static function ( array $elements, array $_before ) use ( $element_id, $target ) {
			$source = wpagent_elementor_locate_element( $elements, $element_id );
			if ( is_wp_error( $source ) ) {
				return $source;
			}
			$parent_path = wpagent_elementor_parent_path( $elements, $target['parent_id'] );
			if ( is_wp_error( $parent_path ) ) {
				return $parent_path;
			}
			if ( null !== $parent_path && wpagent_elementor_path_is_prefix( $source['path'], $parent_path ) ) {
				return new WP_Error( 'wpagent_elementor_cycle', __( 'Ein Elementor-Element kann nicht in sich selbst oder einen Nachfahren verschoben werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
			$removed = wpagent_elementor_remove_at_path( $elements, $source['path'] );
			if ( is_wp_error( $removed ) ) {
				return $removed;
			}
			$updated_parent_path = wpagent_elementor_parent_path( $elements, $target['parent_id'] );
			if ( is_wp_error( $updated_parent_path ) ) {
				return $updated_parent_path;
			}
			$path = wpagent_elementor_insert_under_parent( $elements, $updated_parent_path, $target['index'], $removed );
			if ( is_wp_error( $path ) ) {
				return $path;
			}
			return array( 'elements' => $elements, 'meta' => array( 'element_id' => $element_id, 'path' => $path, 'previous_path' => $source['path'] ) );
		}
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_delete_elementor_element( $input ) {
	$input = is_array( $input ) ? $input : array();
	$element_id = is_string( $input['element_id'] ?? null ) ? $input['element_id'] : '';
	if ( ! preg_match( '/^[A-Za-z0-9_-]{1,64}$/', $element_id ) ) {
		return new WP_Error( 'wpagent_elementor_bad_id', __( 'element_id ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( true !== ( $input['confirm'] ?? null ) ) {
		return new WP_Error( 'wpagent_elementor_confirmation_required', __( 'Das Löschen eines Elementor-Teilbaums erfordert confirm=true.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$request = wpagent_elementor_parse_tree_write_request( $input );
	if ( is_wp_error( $request ) ) {
		return $request;
	}
	$loaded = wpagent_elementor_editable_document( $request['post_id'] );
	if ( is_wp_error( $loaded ) ) {
		return $loaded;
	}
	$before = wpagent_elementor_document_state( $request['post_id'], $loaded['document'] );
	if ( ! hash_equals( $before['hash'], $request['expected_hash'] ) ) {
		return new WP_Error( 'wpagent_elementor_conflict', __( 'Das Elementor-Dokument wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['hash'] ) );
	}
	$valid = wpagent_elementor_validate_elements( $before['elements'] );
	if ( is_wp_error( $valid ) ) {
		return $valid;
	}
	$source = wpagent_elementor_locate_element( $before['elements'], $element_id );
	if ( is_wp_error( $source ) ) {
		return $source;
	}
	return new WP_Error(
		'wpagent_elementor_manual_delete_required',
		__( 'Der Elementor-Teilbaum wird nicht remote gelöscht, weil Elementor keine atomare Compare-and-Delete-Operation gegen parallele Editor-Änderungen bietet. Prüfe den aktuellen Zustand erneut und lösche den Teilbaum anschließend manuell im Elementor-Editor.', 'wp-agent-companion' ),
		array( 'status' => 409, 'current_hash' => $before['hash'], 'post_id' => $request['post_id'], 'element_id' => $element_id, 'path' => $source['path'], 'manual_action_required' => true )
	);
}
