<?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 Schnitt zwischen einer persistenten ACF-Feldgruppe und einer neuen ACPT-Lite-Metagruppe. Es werden nur Definitionen erstellt, 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' );
	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' );
	}
	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,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'] );
	$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 ): bool {
			$provider_file = str_ends_with( wp_normalize_path( $file ), '/src/Core/Repository/MetaRepository.php' );
			if ( E_WARNING !== $severity ) return false;
			if ( $provider_file && 963 === $line && str_starts_with( $message, 'Undefined array key 0' ) ) { ++$warnings['missing_field']; return true; }
			if ( $provider_file && 964 === $line && str_starts_with( $message, 'Attempt to read property "meta_box_id" on null' ) ) { ++$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 ) {
	$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 );
	}
}
