<?php
/**
 * Deliberately small, verified ACF/ACPT Lite field-group definition migrations.
 * Pods and Meta Box remain snapshot-and-compare only: their native contracts
 * do not yet prove this exact create/readback/compensating-delete intersection.
 */

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

define( 'WPAGENT_CONTENT_MODEL_MIGRATION_TTL', 300 );
define( 'WPAGENT_CONTENT_MODEL_MIGRATION_RECORD_VERSION', 1 );

/** @return array<string,mixed> */
function wpagent_content_model_migration_input_schema(): array {
	return array(
		'type' => 'object', 'properties' => array(
			'source_provider' => array( 'type' => 'string', 'enum' => wpagent_content_model_providers() ),
			'source_identifier' => array( 'type' => 'string', 'maxLength' => 121 ),
			'target_provider' => array( 'type' => 'string', 'enum' => wpagent_content_model_providers() ),
			'target_identifier' => array( 'type' => 'string', 'minLength' => 3, 'maxLength' => 40 ),
			'target_box_identifier' => array( 'type' => 'string', 'minLength' => 3, 'maxLength' => 40 ),
			'expected_source_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			'expected_target_empty_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			'idempotency_key' => array( 'type' => 'string', 'minLength' => 16, 'maxLength' => 128, 'pattern' => '^[A-Za-z0-9_-]+$' ),
		),
		'required' => array( 'source_provider', 'source_identifier', 'target_provider', 'target_identifier', 'expected_source_hash', 'expected_target_empty_hash', 'idempotency_key' ), 'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_content_model_migration_output_schema(): array {
	return array(
		'type' => 'object', 'properties' => array(
			'source' => wpagent_content_model_snapshot_schema(), 'target' => wpagent_content_model_snapshot_schema(),
			'verified' => array( 'type' => 'boolean' ), 'idempotent_replay' => array( 'type' => 'boolean' ), 'rollback_verified' => array( 'type' => 'boolean' ),
			'migration_id' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
		),
		'required' => array( 'source', 'target', 'verified', 'idempotent_replay', 'rollback_verified', 'migration_id' ), 'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_ability_migrate_content_model(): array {
	return array(
		'label' => __( 'Datenmodell migrieren', 'wp-agent-companion' ),
		'description' => __( 'Migriert ausschließlich den verifizierten verlustfreien Definitionsschnitt zwischen ACF Free und ACPT Lite in beide Richtungen. Es werden niemals Feldwerte migriert. Bestehende Zielmodelle werden nie überschrieben oder gelöscht.', 'wp-agent-companion' ),
		'category' => 'wp-agent', 'input_schema' => wpagent_content_model_migration_input_schema(), 'output_schema' => wpagent_content_model_migration_output_schema(),
		'execute_callback' => 'wpagent_exec_migrate_content_model', 'permission_callback' => 'wpagent_perm_manage_options',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => true ) ),
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_migration_validate_input( $input ) {
	$input = is_array( $input ) && ! array_is_list( $input ) ? $input : array();
	$source_provider = is_string( $input['source_provider'] ?? null ) ? $input['source_provider'] : '';
	$target_provider = is_string( $input['target_provider'] ?? null ) ? $input['target_provider'] : '';
	$pairs = array( 'acf' => 'acpt-lite', 'acpt-lite' => 'acf' );
	if ( ! isset( $pairs[ $source_provider ] ) || $target_provider !== $pairs[ $source_provider ] ) return new WP_Error( 'wpagent_content_model_migration_pair', __( 'Dieser Anbieterpfad besitzt keinen verifizierten verlustfreien Definitionsmigrationsvertrag.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$source_identifier = wpagent_content_model_identifier( $source_provider, $input['source_identifier'] ?? null );
	$target_identifier = wpagent_content_model_identifier( $target_provider, $input['target_identifier'] ?? null );
	$target_box = '';
	if ( 'acpt-lite' === $target_provider ) {
		$target_box = wpagent_acpt_meta_identifier( $input['target_box_identifier'] ?? '', 'target_box_identifier' );
	} elseif ( ! wpagent_content_model_migration_empty_value( $input['target_box_identifier'] ?? null ) ) {
		return new WP_Error( 'wpagent_content_model_migration_target_box', __( 'target_box_identifier ist nur für ein ACPT-Lite-Ziel zulässig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	foreach ( array( $source_identifier, $target_identifier, $target_box ) as $value ) if ( is_wp_error( $value ) ) return $value;
	foreach ( array( 'expected_source_hash', 'expected_target_empty_hash' ) as $field ) if ( ! is_string( $input[ $field ] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/D', $input[ $field ] ) ) return new WP_Error( 'wpagent_content_model_migration_hash', __( 'Die erwarteten Datenmodell-Hashes müssen SHA-256-Werte sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$key = is_string( $input['idempotency_key'] ?? null ) ? $input['idempotency_key'] : '';
	if ( ! preg_match( '/^[A-Za-z0-9_-]{16,128}$/D', $key ) ) return new WP_Error( 'wpagent_content_model_migration_idempotency', __( 'idempotency_key muss 16 bis 128 sichere ASCII-Zeichen enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	return array( 'source_provider' => $source_provider, 'source_identifier' => $source_identifier, 'target_provider' => $target_provider, 'target_identifier' => $target_identifier, 'target_box_identifier' => $target_box, 'expected_source_hash' => $input['expected_source_hash'], 'expected_target_empty_hash' => $input['expected_target_empty_hash'], 'idempotency_key' => $key );
}

function wpagent_content_model_migration_lock_name( string $key ): string {
	return 'wp_agent_content_model_migration_lock_' . substr( hash( 'sha256', get_current_blog_id() . "\0" . $key ), 0, 32 );
}

function wpagent_content_model_migration_record_name( string $key ): string {
	return 'wp_agent_content_model_migration_result_' . substr( hash( 'sha256', get_current_blog_id() . "\0" . $key ), 0, 32 );
}

function wpagent_content_model_migration_input_hash( array $input ): string {
	return wpagent_content_model_hash( $input );
}

/** @return array<string,mixed>|null */
function wpagent_content_model_migration_record( string $key, string $input_hash ): ?array {
	$record = wpagent_companion_read_option_record_direct( wpagent_content_model_migration_record_name( $key ) );
	if ( ! is_array( $record ) ) return null;
	if ( WPAGENT_CONTENT_MODEL_MIGRATION_RECORD_VERSION !== (int) ( $record['version'] ?? 0 ) || get_current_blog_id() !== (int) ( $record['blog_id'] ?? 0 ) || ! is_string( $record['input_hash'] ?? null ) || ! hash_equals( $record['input_hash'], $input_hash ) || ! is_array( $record['result'] ?? null ) ) return array( 'invalid' => true );
	return $record;
}

/** @param mixed $value */
function wpagent_content_model_migration_empty_value( $value ): bool {
	return in_array( $value, array( null, '', false, 0, '0', array() ), true );
}

/** @return bool|WP_Error */
function wpagent_content_model_migration_acf_boolean( $value ) {
	if ( is_bool( $value ) ) return $value;
	if ( 0 === $value || '0' === $value ) return false;
	if ( 1 === $value || '1' === $value ) return true;
	return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Ein ACF-Feld enthält keinen eindeutig booleschen Pflichtwert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_acf_to_acpt_definition( string $group_key, string $target_name, string $target_box_name ) {
	$group = wpagent_acf_group_get_persistent( $group_key );
	if ( is_wp_error( $group ) ) return $group;
	$state = wpagent_acf_group_state( $group );
	$fields = acf_get_fields( $group ); $fields = is_array( $fields ) ? $fields : array();
	$expected_group = array(
		'key' => $state['key'], 'title' => $state['title'], 'active' => true,
		'location' => wpagent_acf_group_location_for_post_types( $state['post_types'] ),
		'menu_order' => 0, 'position' => 'normal', 'style' => 'default',
		'label_placement' => 'top', 'instruction_placement' => 'label',
		'hide_on_screen' => array(), 'description' => '',
	);
	if ( ! $state['active'] || 1 !== count( $state['post_types'] ) || 1 !== $state['location_rules'] || count( $fields ) < 1 || count( $fields ) > WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_FIELDS || ! wpagent_acf_group_definition_matches( $group, $expected_group ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Die ACF-Feldgruppe liegt außerhalb des kleinen verlustfreien Migrationsvertrags.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$title = wpagent_acpt_meta_text( $state['title'], 'label', 100 );
	if ( is_wp_error( $title ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Der ACF-Feldgruppentitel kann nicht verlustfrei als ACPT-Lite-Beschriftung gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$target = wpagent_acpt_meta_target( array( 'type' => 'post_type', 'name' => $state['post_types'][0] ) );
	if ( is_wp_error( $target ) ) return $target;
	$acpt_fields = array(); $seen = array();
	foreach ( $fields as $index => $field ) {
		if ( ! is_array( $field ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'ACF lieferte eine beschädigte Felddefinition.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$type = (string) ( $field['type'] ?? '' );
		if ( ! in_array( $type, array( 'text', 'email', 'select' ), true ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Nur ACF-Text-, E-Mail- und einfache Auswahlfelder sind in diesem verlustfreien Pfad zugelassen.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$name = wpagent_acpt_meta_identifier( $field['name'] ?? '', 'field.name' ); $label = wpagent_acpt_meta_text( $field['label'] ?? '', 'field.label', 100 ); $required = wpagent_content_model_migration_acf_boolean( $field['required'] ?? false );
		if ( is_wp_error( $name ) || is_wp_error( $label ) || is_wp_error( $required ) || isset( $seen[ $name ] ) || ! wpagent_content_model_migration_empty_value( $field['instructions'] ?? '' ) || ! wpagent_content_model_migration_empty_value( $field['default_value'] ?? '' ) || ! wpagent_content_model_migration_empty_value( $field['placeholder'] ?? '' ) || ! wpagent_content_model_migration_empty_value( $field['conditional_logic'] ?? 0 ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Ein ACF-Feld enthält Einstellungen, die ACPT Lite nicht exakt darstellen kann.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$portable = array( 'label' => $label, 'name' => $name, 'type' => $type, 'instructions' => '', 'required' => $required, 'default_value' => '', 'placeholder' => '' );
		$options = array();
		if ( 'select' === $type ) {
			if ( true === ( $field['multiple'] ?? false ) || true === ( $field['allow_null'] ?? false ) || true === ( $field['ui'] ?? false ) || true === ( $field['ajax'] ?? false ) || ! in_array( $field['return_format'] ?? 'value', array( 'value', '' ), true ) || ! is_array( $field['choices'] ?? null ) || array() === $field['choices'] || count( $field['choices'] ) > 20 ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Das ACF-Auswahlfeld enthält nicht übertragbare Auswahl-Einstellungen.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			$portable['choices'] = $field['choices']; $portable['multiple'] = false;
			$values = array(); foreach ( $field['choices'] as $value => $option_label ) { $option_label = wpagent_acpt_meta_text( $option_label, 'option.label', 100 ); $option_value = wpagent_acpt_meta_text( $value, 'option.value', 100 ); if ( is_wp_error( $option_label ) || is_wp_error( $option_value ) || isset( $values[ $option_value ] ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Das ACF-Auswahlfeld enthält nicht übertragbare Auswahlwerte.', 'wp-agent-companion' ), array( 'status' => 409 ) ); $values[ $option_value ] = true; $options[] = array( 'label' => $option_label, 'value' => $option_value ); }
		}
		$portable = wpagent_acf_group_validate_field( $portable );
		if ( is_wp_error( $portable ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Ein ACF-Feld liegt außerhalb der verlustfreien Feldgrenze.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		if ( 'select' === $type && false === ( $field['default_value'] ?? null ) ) $portable['default_value'] = false;
		$expected = array_merge( $portable, array( 'ID' => absint( $field['ID'] ?? 0 ), 'key' => (string) ( $field['key'] ?? '' ), 'parent' => absint( $field['parent'] ?? 0 ), 'menu_order' => $index, 'conditional_logic' => 0, 'wrapper' => array( 'width' => '', 'class' => '', 'id' => '' ) ) );
		if ( wp_json_encode( wpagent_acf_group_field_definition_state( $field ) ) !== wp_json_encode( wpagent_acf_group_field_definition_state( $expected ) ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Das ACF-Feld enthält eine nicht explizit geprüfte Provider-Einstellung.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$seen[ $name ] = true; $acpt_fields[] = array( 'name' => $name, 'label' => $label, 'type' => $type, 'required' => $required, 'options' => $options );
	}
	return array( 'name' => $target_name, 'label' => $title, 'target' => $target, 'context' => 'normal', 'priority' => 'default', 'boxes' => array( array( 'name' => $target_box_name, 'label' => $title, 'fields' => $acpt_fields ) ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_acpt_to_acf_definition( string $name ) {
	$state = wpagent_acpt_meta_group_state( $name );
	if ( is_wp_error( $state ) ) return $state;
	if ( 'post_type' !== ( $state['target']['type'] ?? '' ) || 'normal' !== ( $state['context'] ?? '' ) || 'default' !== ( $state['priority'] ?? '' ) || 1 !== count( $state['boxes'] ?? array() ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Die ACPT-Lite-Metagruppe liegt außerhalb des kleinen verlustfreien Migrationsvertrags.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$box = $state['boxes'][0] ?? array();
	if ( ! is_array( $box ) || ( $state['label'] ?? '' ) !== ( $box['label'] ?? '' ) || ! is_array( $box['fields'] ?? null ) || count( $box['fields'] ) < 1 || count( $box['fields'] ) > WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_FIELDS ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Die ACPT-Lite-Metagruppe enthält eine nicht abbildbare Box oder Feldmenge.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$fields = array(); $seen = array();
	foreach ( $box['fields'] as $field ) {
		if ( ! is_array( $field ) || ! in_array( $field['type'] ?? '', array( 'text', 'email', 'select' ), true ) || ! is_string( $field['name'] ?? null ) || ! is_string( $field['label'] ?? null ) || ! is_bool( $field['required'] ?? null ) || isset( $seen[ $field['name'] ] ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Ein ACPT-Lite-Feld liegt außerhalb der verlustfreien ACF-Free-Feldgrenze.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$portable = array( 'label' => $field['label'], 'name' => $field['name'], 'type' => $field['type'], 'instructions' => '', 'required' => $field['required'], 'default_value' => '', 'placeholder' => '' );
		if ( 'select' === $field['type'] ) {
			if ( ! is_array( $field['options'] ?? null ) || array() === $field['options'] || count( $field['options'] ) > 20 ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Das ACPT-Lite-Auswahlfeld enthält keine einfache verlustfreie Optionsliste.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			$choices = array();
			foreach ( $field['options'] as $option ) {
				if ( ! is_array( $option ) || ! is_string( $option['value'] ?? null ) || ! is_string( $option['label'] ?? null ) || isset( $choices[ $option['value'] ] ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Das ACPT-Lite-Auswahlfeld enthält mehrdeutige Optionen.', 'wp-agent-companion' ), array( 'status' => 409 ) );
				$choices[ $option['value'] ] = $option['label'];
			}
			$portable['choices'] = $choices; $portable['multiple'] = false;
		} elseif ( array() !== ( $field['options'] ?? null ) ) {
			return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Nur einfache ACPT-Lite-Auswahlfelder dürfen Optionen enthalten.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$portable = wpagent_acf_group_validate_field( $portable );
		if ( is_wp_error( $portable ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Ein ACPT-Lite-Feld kann nicht verlustfrei als ACF-Free-Feld gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$seen[ $portable['name'] ] = true; $fields[] = $portable;
	}
	$title = wpagent_acf_group_validate_title( $state['label'] ?? null ); $post_types = wpagent_acf_group_validate_post_types( array( $state['target']['name'] ?? '' ) );
	if ( is_wp_error( $title ) || is_wp_error( $post_types ) || 1 !== count( $post_types ) ) return new WP_Error( 'wpagent_content_model_migration_loss', __( 'Die ACPT-Lite-Zielbindung kann nicht verlustfrei als ACF-Free-Feldgruppe gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	return array( 'title' => $title, 'active' => true, 'post_types' => $post_types, 'fields' => $fields );
}

function wpagent_content_model_migration_target_record_name( string $provider, string $identifier ): string {
	return 'wp_agent_content_model_migration_target_' . substr( hash( 'sha256', get_current_blog_id() . "\0" . $provider . "\0" . $identifier ), 0, 32 );
}

/** @return bool */
function wpagent_content_model_rollback_created_acf_group( string $group_key, int $created_id, string $created_hash ): bool {
	if ( $created_id < 1 || ! preg_match( '/^[a-f0-9]{64}$/D', $created_hash ) ) return false;
	$available = wpagent_acf_group_require_api();
	if ( is_wp_error( $available ) ) return false;
	$token = wpagent_acf_group_acquire_lock( $group_key );
	if ( is_wp_error( $token ) ) return false;
	try {
		/* This is compensating cleanup for an immediately preceding, internally
		 * created group. The public endpoint remains manual only because it must
		 * not delete an arbitrary administrator-selected ACF field group. */
		$group = wpagent_acf_group_get_persistent( $group_key );
		if ( is_wp_error( $group ) ) return false;
		$state = wpagent_acf_group_state( $group );
		if ( $created_id !== (int) $state['id'] || ! hash_equals( $created_hash, (string) $state['hash'] ) || ! current_user_can( 'manage_options' ) ) return false;
		try {
			$deleted = true === acf_delete_field_group( $created_id );
		} catch ( Throwable ) {
			return false;
		}
		$after = wpagent_acf_group_get_persistent( $group_key );
		return $deleted && is_wp_error( $after ) && in_array( 'wpagent_acf_group_not_found', $after->get_error_codes(), true );
	} finally {
		wpagent_acf_group_release_lock( $group_key, $token );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_create_acf_with_verified_absence_lookup( array $definition ) {
	$title      = wpagent_acf_group_validate_title( $definition['title'] ?? null );
	$post_types = wpagent_acf_group_validate_post_types( $definition['post_types'] ?? null );
	$fields     = wpagent_acf_group_validate_fields( $definition['fields'] ?? null );
	$active     = wpagent_acf_group_boolean( $definition['active'] ?? true );
	foreach ( array( $title, $post_types, $fields ) as $validated ) {
		if ( is_wp_error( $validated ) ) return $validated;
	}
	/* ACF 6.8 persists an empty simple select as false. The generic input
	 * normalizer keeps JSON's empty string, so apply only this provider's
	 * canonical stored value after the bounded migration definition was checked. */
	foreach ( $fields as &$field ) {
		if ( 'select' === $field['type'] && '' === $field['default_value'] ) $field['default_value'] = false;
	}
	unset( $field );
	try {
		return wpagent_acf_group_create_internal( $title, $post_types, $fields, $active );
	} catch ( Throwable ) {
		return new WP_Error( 'wpagent_content_model_migration_target_uncertain', __( 'Der ACF-Free-Providerwrite wurde unterbrochen. Der Zielzustand bleibt zur manuellen Prüfung unverändert.', 'wp-agent-companion' ), array( 'status' => 409, 'recovery_required' => true ) );
	}
}

function wpagent_content_model_migration_refresh_acf_lock( string $group_key, string $token ): bool {
	return wpagent_companion_refresh_option_lock( 'wp_agent_acf_group_lock_' . hash( 'sha256', $group_key ), $token, WPAGENT_CONTENT_MODEL_MIGRATION_TTL );
}

function wpagent_content_model_migration_remove_owned_record( string $name, array $record ): bool {
	global $wpdb;
	$row = wpagent_companion_read_option_row_direct( $name );
	$stored = is_array( $row ) ? maybe_unserialize( $row['raw'] ) : null;
	if ( ! is_array( $row ) || ! is_array( $stored ) || ! hash_equals( wpagent_content_model_hash( $record ), wpagent_content_model_hash( $stored ) ) ) return false;
	$deleted = $wpdb->query( $wpdb->prepare(
		"DELETE FROM {$wpdb->options} WHERE option_name = %s AND HEX(option_value) = HEX(%s) AND HEX(autoload) = HEX(%s)",
		$name, $row['raw'], $row['autoload']
	) );
	wpagent_companion_forget_option_cache( $name, in_array( $row['autoload'], wp_autoload_values_to_autoload(), true ) );
	return 1 === $deleted && null === wpagent_companion_read_option_row_direct( $name );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_migrate_content_model_acpt_to_acf( $input ) {
	$input = wpagent_content_model_migration_validate_input( $input );
	if ( is_wp_error( $input ) ) return $input;
	$source_capabilities = wpagent_content_model_capabilities( 'acpt-lite' ); $target_capabilities = wpagent_content_model_capabilities( 'acf' );
	if ( ! $source_capabilities['migration_source'] || ! $target_capabilities['migration_target'] ) return new WP_Error( 'wpagent_content_model_migration_provider', __( 'Die erforderlichen nativen Anbieterfähigkeiten sind nicht aktiv.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$source = wpagent_content_model_snapshot( 'acpt-lite', $input['source_identifier'] );
	if ( is_wp_error( $source ) ) return $source;
	if ( ! $source['exists'] || ! hash_equals( $input['expected_source_hash'], $source['snapshot_hash'] ) ) return new WP_Error( 'wpagent_content_model_migration_source_conflict', __( 'Der ACPT-Lite-Quell-Snapshot wurde nicht gefunden oder hat sich seit der Vorprüfung geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$lock_name = wpagent_content_model_migration_lock_name( $input['idempotency_key'] ); $token = wpagent_companion_acquire_option_lock( $lock_name, WPAGENT_CONTENT_MODEL_MIGRATION_TTL );
	if ( ! is_string( $token ) ) return new WP_Error( 'wpagent_content_model_migration_busy', __( 'Diese Datenmodellmigration läuft bereits.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$input_hash = wpagent_content_model_migration_input_hash( $input ); $source_token = false; $reservation_token = null;
	try {
		$source_token = wpagent_acpt_meta_acquire_lock( 'definitions' );
		if ( false === $source_token ) return new WP_Error( 'wpagent_content_model_migration_busy', __( 'Eine andere ACPT-Lite-Definitionsmigration läuft bereits.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$reservation_token = wpagent_acf_group_acquire_lock( $input['target_identifier'] );
		if ( is_wp_error( $reservation_token ) ) return $reservation_token;
		if ( ! current_user_can( 'manage_options' ) || ! wpagent_companion_refresh_option_lock( $lock_name, $token, WPAGENT_CONTENT_MODEL_MIGRATION_TTL ) || ! wpagent_acpt_meta_refresh_lock( 'definitions', $source_token ) || ! wpagent_content_model_migration_refresh_acf_lock( $input['target_identifier'], $reservation_token ) ) return new WP_Error( 'wpagent_content_model_migration_lock', __( 'Die Datenmodellmigrationssperre, Anbietersperre oder Berechtigung ging verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$reservation_name = wpagent_content_model_migration_target_record_name( 'acf', $input['target_identifier'] );
		$record = wpagent_content_model_migration_record( $input['idempotency_key'], $input_hash );
		if ( is_array( $record ) ) {
			if ( ! empty( $record['invalid'] ) ) return new WP_Error( 'wpagent_content_model_migration_idempotency_conflict', __( 'Der Idempotenzschlüssel ist bereits mit einer anderen oder beschädigten Migration verbunden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			$result = $record['result']; $result_target = is_array( $result['target'] ?? null ) ? (string) ( $result['target']['identifier'] ?? '' ) : '';
			$fresh_target = wpagent_content_model_snapshot( 'acf', $result_target );
			$stored_reservation = wpagent_companion_read_option_record_direct( $reservation_name );
			$expected_reservation = array( 'version' => WPAGENT_CONTENT_MODEL_MIGRATION_RECORD_VERSION, 'blog_id' => get_current_blog_id(), 'input_hash' => $input_hash, 'result' => $result );
			if ( is_wp_error( $fresh_target ) || ! $fresh_target['exists'] || ! is_array( $result['target'] ?? null ) || ! hash_equals( (string) $result['target']['snapshot_hash'], $fresh_target['snapshot_hash'] ) || ! is_array( $stored_reservation ) || ! hash_equals( wpagent_content_model_hash( $expected_reservation ), wpagent_content_model_hash( $stored_reservation ) ) ) return new WP_Error( 'wpagent_content_model_migration_replay_conflict', __( 'Das zuvor migrierte Zielmodell oder seine Zielreservierung wurde verändert oder entfernt und wird nicht erneut erstellt.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			$result['target'] = $fresh_target; $result['idempotent_replay'] = true; return $result;
		}
		if ( null !== wpagent_companion_read_option_row_direct( $reservation_name ) ) return new WP_Error( 'wpagent_content_model_migration_target_conflict', __( 'Die angeforderte ACF-Free-Zielreservierung wurde bereits verwendet und wird nicht erneut beschrieben.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$source = wpagent_content_model_snapshot( 'acpt-lite', $input['source_identifier'] );
		if ( is_wp_error( $source ) || ! $source['exists'] || ! hash_equals( $input['expected_source_hash'], $source['snapshot_hash'] ) ) return new WP_Error( 'wpagent_content_model_migration_source_conflict', __( 'Der ACPT-Lite-Quell-Snapshot hat sich während der Migration geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		if ( is_array( acf_get_field_group( $input['target_identifier'] ) ) ) return new WP_Error( 'wpagent_content_model_migration_target_conflict', __( 'Die angeforderte ACF-Free-Zielreservierung verweist auf eine persistente oder Local-JSON-Feldgruppe und wird nicht verwendet.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$reservation = wpagent_content_model_snapshot( 'acf', $input['target_identifier'] );
		if ( is_wp_error( $reservation ) || $reservation['exists'] || ! hash_equals( $input['expected_target_empty_hash'], $reservation['snapshot_hash'] ) ) return new WP_Error( 'wpagent_content_model_migration_target_conflict', __( 'Die angeforderte ACF-Free-Zielreservierung ist nicht mehr leer oder stimmt nicht mit ihrer Vorprüfung überein.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$definition = wpagent_content_model_acpt_to_acf_definition( $input['source_identifier'] );
		if ( is_wp_error( $definition ) ) return $definition;
		$source = wpagent_content_model_snapshot( 'acpt-lite', $input['source_identifier'] );
		if ( is_wp_error( $source ) || ! $source['exists'] || ! hash_equals( $input['expected_source_hash'], $source['snapshot_hash'] ) ) return new WP_Error( 'wpagent_content_model_migration_source_conflict', __( 'Die ACPT-Lite-Quelle hat sich während der Konvertierung geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		if ( ! wpagent_companion_refresh_option_lock( $lock_name, $token, WPAGENT_CONTENT_MODEL_MIGRATION_TTL ) || ! wpagent_acpt_meta_refresh_lock( 'definitions', $source_token ) || ! wpagent_content_model_migration_refresh_acf_lock( $input['target_identifier'], $reservation_token ) ) return new WP_Error( 'wpagent_content_model_migration_lock', __( 'Eine Migrations- oder Anbietersperre ging vor dem Providerwrite verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$created = wpagent_content_model_create_acf_with_verified_absence_lookup( $definition );
		if ( is_wp_error( $created ) ) {
			$data = $created->get_error_data(); $uncertain_key = is_array( $data ) && is_string( $data['group_key'] ?? null ) ? $data['group_key'] : '';
			if ( '' !== $uncertain_key ) {
				$after_error = wpagent_content_model_snapshot( 'acf', $uncertain_key );
				if ( is_wp_error( $after_error ) || $after_error['exists'] ) return wpagent_content_model_migration_failure( __( 'Der ACF-Free-Providerwrite meldete einen Fehler; der Zielzustand ist nicht sicher leer nachweisbar.', 'wp-agent-companion' ), false, true );
			}
			return $created;
		}
		$created_group = $created['group'] ?? array(); $created_key = is_array( $created_group ) ? (string) ( $created_group['key'] ?? '' ) : ''; $created_id = is_array( $created_group ) ? (int) ( $created_group['id'] ?? 0 ) : 0; $created_hash = is_array( $created_group ) ? (string) ( $created_group['hash'] ?? '' ) : '';
		if ( ! preg_match( '/^group_[A-Za-z0-9_-]{1,120}$/D', $created_key ) || $created_id < 1 || ! preg_match( '/^[a-f0-9]{64}$/D', $created_hash ) ) return wpagent_content_model_migration_failure( __( 'Der ACF-Free-Providerwrite lieferte keine nachweislich eigene Zielgruppe.', 'wp-agent-companion' ), false, true );
		$target_token = wpagent_acf_group_acquire_lock( $created_key );
		if ( is_wp_error( $target_token ) ) {
			$rolled_back = wpagent_content_model_rollback_created_acf_group( $created_key, $created_id, $created_hash );
			return wpagent_content_model_migration_failure( __( 'Die neu angelegte ACF-Free-Zielgruppe konnte nicht für den Readback gesperrt werden.', 'wp-agent-companion' ), $rolled_back, ! $rolled_back );
		}
		$failure = ''; $recovery_required = false; $result = array();
		try {
			$source_readback = wpagent_content_model_snapshot( 'acpt-lite', $input['source_identifier'] ); $target = wpagent_content_model_snapshot( 'acf', $created_key );
			$comparison = is_wp_error( $source_readback ) || is_wp_error( $target ) ? new WP_Error( 'wpagent_content_model_migration_readback', __( 'Ein Datenmodell-Readback ist fehlgeschlagen.', 'wp-agent-companion' ), array( 'status' => 409 ) ) : wpagent_content_model_compare( $source_readback, $target );
			if ( is_wp_error( $comparison ) || ! $source_readback['exists'] || ! hash_equals( $input['expected_source_hash'], $source_readback['snapshot_hash'] ) || ! $target['exists'] || ! $comparison['lossless'] || ! current_user_can( 'manage_options' ) || ! wpagent_companion_refresh_option_lock( $lock_name, $token, WPAGENT_CONTENT_MODEL_MIGRATION_TTL ) || ! wpagent_acpt_meta_refresh_lock( 'definitions', $source_token ) || ! wpagent_content_model_migration_refresh_acf_lock( $input['target_identifier'], $reservation_token ) || ! wpagent_content_model_migration_refresh_acf_lock( $created_key, $target_token ) ) {
				$failure = __( 'Das neue ACF-Free-Zielmodell konnte nicht vollständig verlustfrei bestätigt werden.', 'wp-agent-companion' );
			} else {
				$result = array( 'source' => $source_readback, 'target' => $target, 'verified' => true, 'idempotent_replay' => false, 'rollback_verified' => false, 'migration_id' => hash( 'sha256', get_current_blog_id() . "\0" . $input_hash ) );
				$reservation_record = array( 'version' => WPAGENT_CONTENT_MODEL_MIGRATION_RECORD_VERSION, 'blog_id' => get_current_blog_id(), 'input_hash' => $input_hash, 'result' => $result );
				$idempotency_record = $reservation_record;
				if ( ! wpagent_companion_add_option_create_only( $reservation_name, $reservation_record, '', false ) ) {
					$failure = __( 'Die ACF-Free-Zielreservierung konnte nicht atomar gespeichert werden.', 'wp-agent-companion' );
				} elseif ( ! wpagent_companion_add_option_create_only( wpagent_content_model_migration_record_name( $input['idempotency_key'] ), $idempotency_record, '', false ) ) {
					if ( ! wpagent_content_model_migration_remove_owned_record( $reservation_name, $reservation_record ) ) {
						$failure = __( 'Der Idempotenzbeleg konnte nicht gespeichert werden; die verifizierte Zielreservierung bleibt zur manuellen Wiederherstellung erhalten.', 'wp-agent-companion' ); $recovery_required = true;
					} else {
						$failure = __( 'Der Idempotenzbeleg konnte nicht atomar gespeichert werden.', 'wp-agent-companion' );
					}
				} else {
					$final_source = wpagent_content_model_snapshot( 'acpt-lite', $input['source_identifier'] );
					$final_target = wpagent_content_model_snapshot( 'acf', $created_key );
					$stored = wpagent_content_model_migration_record( $input['idempotency_key'], $input_hash ); $stored_reservation = wpagent_companion_read_option_record_direct( $reservation_name );
					$final_verified = current_user_can( 'manage_options' )
						&& wpagent_companion_refresh_option_lock( $lock_name, $token, WPAGENT_CONTENT_MODEL_MIGRATION_TTL )
						&& wpagent_acpt_meta_refresh_lock( 'definitions', $source_token )
						&& wpagent_content_model_migration_refresh_acf_lock( $input['target_identifier'], $reservation_token )
						&& wpagent_content_model_migration_refresh_acf_lock( $created_key, $target_token )
						&& is_array( $final_source ) && $final_source['exists'] && hash_equals( $input['expected_source_hash'], $final_source['snapshot_hash'] )
						&& is_array( $final_target ) && $final_target['exists'] && hash_equals( $target['snapshot_hash'], $final_target['snapshot_hash'] );
					if ( ! $final_verified || ! is_array( $stored ) || ! empty( $stored['invalid'] ) || ! is_array( $stored['result'] ?? null ) || wpagent_content_model_hash( $stored['result'] ) !== wpagent_content_model_hash( $result ) || ! is_array( $stored_reservation ) || wpagent_content_model_hash( $stored_reservation ) !== wpagent_content_model_hash( $reservation_record ) ) {
						$records_removed = wpagent_content_model_migration_remove_owned_record( wpagent_content_model_migration_record_name( $input['idempotency_key'] ), $idempotency_record ) && wpagent_content_model_migration_remove_owned_record( $reservation_name, $reservation_record );
						$failure = __( 'Die gespeicherten Migrationsbelege konnten nicht exakt zurückgelesen werden.', 'wp-agent-companion' ); $recovery_required = ! $records_removed;
					}
				}
			}
		} finally {
			wpagent_acf_group_release_lock( $created_key, $target_token );
		}
		if ( '' !== $failure ) {
			if ( $recovery_required ) return wpagent_content_model_migration_failure( $failure, false, true );
			$rolled_back = wpagent_content_model_rollback_created_acf_group( $created_key, $created_id, $created_hash );
			return wpagent_content_model_migration_failure( $failure, $rolled_back, ! $rolled_back );
		}
		return $result;
	} finally {
		if ( is_string( $reservation_token ) ) wpagent_acf_group_release_lock( $input['target_identifier'], $reservation_token );
		if ( is_string( $source_token ) ) wpagent_acpt_meta_release_lock( 'definitions', $source_token );
		wpagent_companion_release_option_lock( $lock_name, $token );
	}
}

/** @return array<string,string>|false */
function wpagent_content_model_acpt_owned_tables( array $provider ) {
	$database = $provider['database'] ?? '';
	if ( ! is_string( $database ) || ! is_callable( array( $database, 'startTransaction' ) ) || ! is_callable( array( $database, 'commitTransaction' ) ) || ! is_callable( array( $database, 'rollbackTransaction' ) ) || ! is_callable( array( $database, 'executeQueryOrThrowException' ) ) ) return false;
	try {
		$tables = array(
			'option' => $database::prefixedTableName( $database::TABLE_META_OPTION ),
			'field' => $database::prefixedTableName( $database::TABLE_META_FIELD ),
			'box' => $database::prefixedTableName( $database::TABLE_META_BOX ),
			'group_belong' => $database::prefixedTableName( $database::TABLE_META_GROUP_BELONG ),
			'belong' => $database::prefixedTableName( $database::TABLE_BELONG ),
			'group' => $database::prefixedTableName( $database::TABLE_META_GROUP ),
		);
	} catch ( Throwable ) {
		return false;
	}
	foreach ( $tables as $table ) if ( ! is_string( $table ) || ! preg_match( '/^[A-Za-z0-9_]+$/D', $table ) ) return false;
	return $tables;
}

/**
 * Deletes only a proven operation-owned group through ACPT Lite's native DB API.
 * ACPT Lite 2.0.11's repository delete path emits warnings after deleting a group
 * before its child reads, so the explicit child-first transaction is required.
 */
function wpagent_content_model_delete_owned_acpt_group( array $provider, string $group_id ): bool {
	$tables = wpagent_content_model_acpt_owned_tables( $provider );
	if ( false === $tables ) return false;
	$database = $provider['database'];
	try {
		$database::startTransaction();
		$database::executeQueryOrThrowException( "DELETE o FROM `{$tables['option']}` o INNER JOIN `{$tables['box']}` b ON b.id = o.meta_box_id WHERE b.group_id = %s", array( $group_id ) );
		$database::executeQueryOrThrowException( "DELETE f FROM `{$tables['field']}` f INNER JOIN `{$tables['box']}` b ON b.id = f.meta_box_id WHERE b.group_id = %s", array( $group_id ) );
		$database::executeQueryOrThrowException( "DELETE FROM `{$tables['box']}` WHERE group_id = %s", array( $group_id ) );
		/* A concurrent native edit can attach a freshly-created belong row to another
		 * group. The rollback owns only this group's link, never that other group. */
		$database::executeQueryOrThrowException( "DELETE b FROM `{$tables['belong']}` b INNER JOIN `{$tables['group_belong']}` own_group ON own_group.belong_id = b.id LEFT JOIN `{$tables['group_belong']}` other_group ON other_group.belong_id = b.id AND other_group.group_id <> %s WHERE own_group.group_id = %s AND other_group.group_id IS NULL", array( $group_id, $group_id ) );
		$database::executeQueryOrThrowException( "DELETE FROM `{$tables['group_belong']}` WHERE group_id = %s", array( $group_id ) );
		$database::executeQueryOrThrowException( "DELETE FROM `{$tables['group']}` WHERE id = %s", array( $group_id ) );
		$database::commitTransaction();
		return true;
	} catch ( Throwable ) {
		try { $database::rollbackTransaction(); } catch ( Throwable ) {}
		return false;
	}
}

/** @return bool */
function wpagent_content_model_rollback_created_acpt_group( string $name, string $created_id, string $created_hash ): bool {
	if ( '' === $created_id || ! preg_match( '/^[a-f0-9]{64}$/D', $created_hash ) ) return false;
	$scope = 'definitions'; $token = wpagent_acpt_meta_acquire_lock( $scope );
	if ( false === $token ) return false;
	try {
		$current = wpagent_acpt_meta_group_state( $name );
		if ( is_wp_error( $current ) || ! hash_equals( $created_id, (string) $current['id'] ) || ! hash_equals( $created_hash, (string) $current['definition_hash'] ) || ! current_user_can( 'manage_options' ) || ! wpagent_acpt_meta_refresh_lock( $scope, $token ) ) return false;
		$provider = wpagent_acpt_meta_provider();
		if ( is_wp_error( $provider ) || ! wpagent_content_model_delete_owned_acpt_group( $provider, $created_id ) || ! wpagent_acpt_meta_refresh_lock( $scope, $token ) || is_wp_error( wpagent_acpt_flush_provider_cache( $provider ) ) ) return false;
		$after = wpagent_acpt_meta_group_state( $name );
		return is_wp_error( $after ) && in_array( 'wpagent_acpt_meta_not_found', $after->get_error_codes(), true ) && current_user_can( 'manage_options' );
	} catch ( Throwable ) {
		return false;
	} finally {
		wpagent_acpt_meta_release_lock( $scope, $token );
	}
}

/** @return WP_Error */
function wpagent_content_model_migration_failure( string $message, bool $rollback_verified, bool $recovery_required = false ): WP_Error {
	return new WP_Error( 'wpagent_content_model_migration_failed', $message, array( 'status' => 409, 'rollback_verified' => $rollback_verified, 'recovery_required' => $recovery_required ) );
}

/** @return array{result:array<string,mixed>|WP_Error,known_absence_warnings:bool} */
function wpagent_content_model_create_acpt_with_verified_absence_lookup( array $definition ): array {
	$expected_fields = 0;
	foreach ( $definition['boxes'] as $box ) $expected_fields += count( $box['fields'] );
	$provider_version = defined( 'ACPT_LITE_PLUGIN_VERSION' ) ? (string) ACPT_LITE_PLUGIN_VERSION : '';
	$warnings = array( 'missing_field' => 0, 'missing_box' => 0 );
	$unexpected_warning = false;
	set_error_handler(
		static function ( int $severity, string $message, string $file, int $line ) use ( &$warnings, &$unexpected_warning, $provider_version ): bool {
			$warning_kind = wpagent_acpt_meta_audited_absence_warning_kind( $provider_version, $severity, $message, $file, $line );
			if ( 'missing_field' === $warning_kind ) {
				++$warnings['missing_field'];
				return true;
			}
			if ( 'missing_box' === $warning_kind ) {
				++$warnings['missing_box'];
				return true;
			}
			$unexpected_warning = true;
			return false;
		}
	);
	try {
		$result = wpagent_exec_create_acpt_meta_group( $definition );
	} catch ( Throwable ) {
		$result = new WP_Error( 'wpagent_content_model_migration_target_uncertain', __( 'Der ACPT-Lite-Providerwrite wurde unterbrochen. Der Zielzustand bleibt zur manuellen Prüfung unverändert.', 'wp-agent-companion' ), array( 'status' => 409, 'recovery_required' => true ) );
	} finally {
		restore_error_handler();
	}
	$known_absence_warnings = ! $unexpected_warning && ( ( 0 === $warnings['missing_field'] && 0 === $warnings['missing_box'] ) || ( $expected_fields === $warnings['missing_field'] && $expected_fields === $warnings['missing_box'] ) );
	return array( 'result' => $result, 'known_absence_warnings' => $known_absence_warnings );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_migrate_content_model( $input ) {
	$validated = wpagent_content_model_migration_validate_input( $input );
	if ( is_wp_error( $validated ) ) return $validated;
	return 'acpt-lite' === $validated['source_provider'] ? wpagent_exec_migrate_content_model_acpt_to_acf( $validated ) : wpagent_exec_migrate_content_model_acf_to_acpt( $validated );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_migrate_content_model_acf_to_acpt( $input ) {
	$input = wpagent_content_model_migration_validate_input( $input );
	if ( is_wp_error( $input ) ) return $input;
	$source_capabilities = wpagent_content_model_capabilities( 'acf' ); $target_capabilities = wpagent_content_model_capabilities( 'acpt-lite' );
	if ( ! $source_capabilities['migration_source'] || ! $target_capabilities['migration_target'] ) return new WP_Error( 'wpagent_content_model_migration_provider', __( 'Die erforderlichen nativen Anbieterfähigkeiten sind nicht aktiv.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$source = wpagent_content_model_snapshot( 'acf', $input['source_identifier'] );
	if ( is_wp_error( $source ) ) return $source;
	if ( ! $source['exists'] || ! hash_equals( $input['expected_source_hash'], $source['snapshot_hash'] ) ) return new WP_Error( 'wpagent_content_model_migration_source_conflict', __( 'Der ACF-Quell-Snapshot wurde nicht gefunden oder hat sich seit der Vorprüfung geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$lock_name = wpagent_content_model_migration_lock_name( $input['idempotency_key'] ); $token = wpagent_companion_acquire_option_lock( $lock_name, WPAGENT_CONTENT_MODEL_MIGRATION_TTL );
	if ( ! is_string( $token ) ) return new WP_Error( 'wpagent_content_model_migration_busy', __( 'Diese Datenmodellmigration läuft bereits.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$input_hash = wpagent_content_model_migration_input_hash( $input );
	$source_token = null;
	try {
		$source_token = wpagent_acf_group_acquire_lock( $input['source_identifier'] );
		if ( is_wp_error( $source_token ) ) return $source_token;
		if ( ! current_user_can( 'manage_options' ) || ! wpagent_companion_refresh_option_lock( $lock_name, $token, WPAGENT_CONTENT_MODEL_MIGRATION_TTL ) ) return new WP_Error( 'wpagent_content_model_migration_lock', __( 'Die Datenmodellmigrationssperre oder die Berechtigung ging verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$record = wpagent_content_model_migration_record( $input['idempotency_key'], $input_hash );
		if ( is_array( $record ) ) {
			if ( ! empty( $record['invalid'] ) ) return new WP_Error( 'wpagent_content_model_migration_idempotency_conflict', __( 'Der Idempotenzschlüssel ist bereits mit einer anderen oder beschädigten Migration verbunden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			$result = $record['result']; $fresh_target = wpagent_content_model_snapshot( 'acpt-lite', $input['target_identifier'] );
			if ( is_wp_error( $fresh_target ) || ! $fresh_target['exists'] || ! is_array( $result['target'] ?? null ) || ! hash_equals( (string) $result['target']['snapshot_hash'], $fresh_target['snapshot_hash'] ) ) return new WP_Error( 'wpagent_content_model_migration_replay_conflict', __( 'Das zuvor migrierte Zielmodell wurde verändert oder entfernt und wird nicht erneut erstellt.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			$result['target'] = $fresh_target; $result['idempotent_replay'] = true; return $result;
		}
		$source = wpagent_content_model_snapshot( 'acf', $input['source_identifier'] );
		if ( is_wp_error( $source ) || ! $source['exists'] || ! hash_equals( $input['expected_source_hash'], $source['snapshot_hash'] ) ) return new WP_Error( 'wpagent_content_model_migration_source_conflict', __( 'Der ACF-Quell-Snapshot hat sich während der Migration geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$target = wpagent_content_model_snapshot( 'acpt-lite', $input['target_identifier'] );
		if ( is_wp_error( $target ) || $target['exists'] || ! hash_equals( $input['expected_target_empty_hash'], $target['snapshot_hash'] ) ) return new WP_Error( 'wpagent_content_model_migration_target_conflict', __( 'Das ACPT-Lite-Zielmodell ist nicht mehr leer oder stimmt nicht mit seiner Vorprüfung überein.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$definition = wpagent_content_model_acf_to_acpt_definition( $input['source_identifier'], $input['target_identifier'], $input['target_box_identifier'] );
		if ( is_wp_error( $definition ) ) return $definition;
		$source = wpagent_content_model_snapshot( 'acf', $input['source_identifier'] );
		if ( is_wp_error( $source ) || ! $source['exists'] || ! hash_equals( $input['expected_source_hash'], $source['snapshot_hash'] ) ) return new WP_Error( 'wpagent_content_model_migration_source_conflict', __( 'Die ACF-Quelle hat sich während der Konvertierung geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		if ( ! wpagent_companion_refresh_option_lock( $lock_name, $token, WPAGENT_CONTENT_MODEL_MIGRATION_TTL ) ) return new WP_Error( 'wpagent_content_model_migration_lock', __( 'Die Datenmodellmigrationssperre ging vor dem Providerwrite verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$write = wpagent_content_model_create_acpt_with_verified_absence_lookup( $definition ); $created = $write['result'];
		if ( is_wp_error( $created ) ) {
			$after_error = wpagent_content_model_snapshot( 'acpt-lite', $input['target_identifier'] );
			if ( is_wp_error( $after_error ) || $after_error['exists'] || ! hash_equals( $input['expected_target_empty_hash'], $after_error['snapshot_hash'] ) ) return wpagent_content_model_migration_failure( __( 'Der ACPT-Lite-Providerwrite meldete einen Fehler; der Zielzustand ist nicht sicher leer nachweisbar.', 'wp-agent-companion' ), false, true );
			return $created;
		}
		$created_group = $created['group'] ?? array(); $created_id = is_array( $created_group ) ? (string) ( $created_group['id'] ?? '' ) : ''; $created_hash = is_array( $created_group ) ? (string) ( $created_group['definition_hash'] ?? '' ) : '';
		$source_readback = wpagent_content_model_snapshot( 'acf', $input['source_identifier'] );
		$target = wpagent_content_model_snapshot( 'acpt-lite', $input['target_identifier'] ); $comparison = is_wp_error( $source_readback ) || is_wp_error( $target ) ? new WP_Error( 'wpagent_content_model_migration_readback', __( 'Ein Datenmodell-Readback ist fehlgeschlagen.', 'wp-agent-companion' ), array( 'status' => 409 ) ) : wpagent_content_model_compare( $source_readback, $target );
		if ( ! $write['known_absence_warnings'] || is_wp_error( $comparison ) || ! $source_readback['exists'] || ! hash_equals( $input['expected_source_hash'], $source_readback['snapshot_hash'] ) || ! $target['exists'] || ! $comparison['lossless'] || ! current_user_can( 'manage_options' ) || ! wpagent_companion_refresh_option_lock( $lock_name, $token, WPAGENT_CONTENT_MODEL_MIGRATION_TTL ) ) {
			$rolled_back = wpagent_content_model_rollback_created_acpt_group( $input['target_identifier'], $created_id, $created_hash );
			return wpagent_content_model_migration_failure( __( 'Das neue ACPT-Lite-Zielmodell konnte nicht vollständig verlustfrei bestätigt werden.', 'wp-agent-companion' ), $rolled_back, ! $rolled_back );
		}
		$result = array( 'source' => $source, 'target' => $target, 'verified' => true, 'idempotent_replay' => false, 'rollback_verified' => false, 'migration_id' => hash( 'sha256', get_current_blog_id() . "\0" . $input_hash ) );
		$record = array( 'version' => WPAGENT_CONTENT_MODEL_MIGRATION_RECORD_VERSION, 'blog_id' => get_current_blog_id(), 'input_hash' => $input_hash, 'result' => $result );
		if ( ! wpagent_companion_add_option_create_only( wpagent_content_model_migration_record_name( $input['idempotency_key'] ), $record, '', false ) ) {
			$rolled_back = wpagent_content_model_rollback_created_acpt_group( $input['target_identifier'], $created_id, $created_hash );
			return wpagent_content_model_migration_failure( __( 'Das verifizierte Migrationsresultat konnte nicht atomar als Idempotenzbeleg gespeichert werden.', 'wp-agent-companion' ), $rolled_back, ! $rolled_back );
		}
		$stored = wpagent_content_model_migration_record( $input['idempotency_key'], $input_hash );
		if ( ! is_array( $stored ) || ! empty( $stored['invalid'] ) || ! is_array( $stored['result'] ?? null ) || wpagent_content_model_hash( $stored['result'] ) !== wpagent_content_model_hash( $result ) ) {
			$rolled_back = wpagent_content_model_rollback_created_acpt_group( $input['target_identifier'], $created_id, $created_hash );
			return wpagent_content_model_migration_failure( __( 'Der gespeicherte Idempotenzbeleg konnte nicht exakt zurückgelesen werden.', 'wp-agent-companion' ), $rolled_back, ! $rolled_back );
		}
		return $result;
	} finally {
		if ( is_string( $source_token ) ) wpagent_acf_group_release_lock( $input['source_identifier'], $source_token );
		wpagent_companion_release_option_lock( $lock_name, $token );
	}
}
