<?php
/** Explicit, signed cross-site transfer for bounded Spectra One snapshots. */

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

const WPAGENT_SPECTRA_ONE_PORTABLE_SCHEMA_VERSION = 'spectra-one-portable-transfer-v1';
const WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_OPTION  = 'wp_agent_spectra_one_portable_transfer_secret';
const WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_LOCK    = 'wp_agent_spectra_one_portable_transfer_secret_lock';
const WPAGENT_SPECTRA_ONE_PORTABLE_LOCK           = 'wp_agent_spectra_one_portable_transfer_lock';
const WPAGENT_SPECTRA_ONE_PORTABLE_LOCK_TTL       = 300;
const WPAGENT_SPECTRA_ONE_PORTABLE_SNAPSHOT_TTL   = 900;
const WPAGENT_SPECTRA_ONE_PORTABLE_PREFLIGHT_TTL  = 300;
const WPAGENT_SPECTRA_ONE_PORTABLE_MAX_CLOCK_SKEW = 300;

/** @return array<string,mixed> */
function wpagent_ability_configure_spectra_one_transfer_secret(): array {
	return array(
		'label' => __( 'Spectra-One-Transfergeheimnis konfigurieren', 'wp-agent-companion' ),
		'description' => __( 'Speichert ein neues, separat verwaltetes Transfergeheimnis verschlüsselt für portable Spectra-One-Snapshots. Das Geheimnis wird nie wieder ausgegeben.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array(
			'secret' => array( 'type' => 'string', 'minLength' => 32, 'maxLength' => 128, 'format' => 'password' ),
			'expected_key_id' => wpagent_spectra_one_snapshot_hash_schema(),
			'replace_unreadable_secret' => array( 'type' => 'boolean' ),
			'expected_unreadable_record_hash' => wpagent_spectra_one_snapshot_hash_schema(),
		), 'required' => array( 'secret' ), 'additionalProperties' => false, 'maxProperties' => 4 ),
		'output_schema' => array( 'type' => 'object', 'properties' => array(
			'configured' => array( 'type' => 'boolean' ), 'key_id' => wpagent_spectra_one_snapshot_hash_schema(),
		), 'required' => array( 'configured', 'key_id' ), 'additionalProperties' => false, 'maxProperties' => 2 ),
		'execute_callback' => 'wpagent_exec_configure_spectra_one_transfer_secret', 'permission_callback' => 'wpagent_perm_manage_options',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_ability_export_spectra_one_portable_snapshot(): array {
	$base = wpagent_ability_export_spectra_one_design_snapshot();
	$base['label'] = __( 'Portablen Spectra-One-Snapshot exportieren', 'wp-agent-companion' );
	$base['description'] = __( 'Signiert einen begrenzten Spectra-One-Snapshot mit dem separat eingerichteten Transfergeheimnis. Die Zielsite muss ihn mit einer kurzlebigen Preflight-Signatur an ihre eigene Identität, Zielzuordnung und ihren aktuellen Zielzustand binden.', 'wp-agent-companion' );
	$base['output_schema'] = wpagent_spectra_one_portable_snapshot_schema();
	$base['execute_callback'] = 'wpagent_exec_export_spectra_one_portable_snapshot';
	return $base;
}

/** @return array<string,mixed> */
function wpagent_ability_preflight_spectra_one_portable_snapshot(): array {
	return array(
		'label' => __( 'Portablen Spectra-One-Transfer vorprüfen', 'wp-agent-companion' ),
		'description' => __( 'Prüft einen signierten portablen Snapshot auf der Zielsite ohne Schreibvorgang. Jede Global-Styles-, Template-, Template-Teil- und Navigation-Zuordnung wird mit ihrer Kollisionsliste zurückgegeben.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array(
			'portable_snapshot' => wpagent_spectra_one_portable_snapshot_schema(), 'mapping' => wpagent_spectra_one_portable_mapping_schema(),
		), 'required' => array( 'portable_snapshot', 'mapping' ), 'additionalProperties' => false, 'maxProperties' => 2 ),
		'output_schema' => wpagent_spectra_one_portable_preflight_schema(),
		'execute_callback' => 'wpagent_exec_preflight_spectra_one_portable_snapshot', 'permission_callback' => 'wpagent_perm_edit_theme_options',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_ability_import_spectra_one_portable_snapshot(): array {
	return array(
		'label' => __( 'Portablen Spectra-One-Snapshot importieren', 'wp-agent-companion' ),
		'description' => __( 'Importiert ausschließlich einen kollisionsfreien, frisch vorgeprüften portablen Spectra-One-Snapshot. Neue Templates, Teile und Navigationen werden create-only erstellt. Global Styles werden nur ohne vorhandene Zielkollision mit einem Zielhash ergänzt.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array(
			'portable_snapshot' => wpagent_spectra_one_portable_snapshot_schema(), 'preflight' => wpagent_spectra_one_portable_preflight_schema(),
		), 'required' => array( 'portable_snapshot', 'preflight' ), 'additionalProperties' => false, 'maxProperties' => 2 ),
		'output_schema' => wpagent_spectra_one_snapshot_object_schema( array( 'verified' => array( 'type' => 'boolean' ), 'state_hash' => wpagent_spectra_one_snapshot_hash_schema(), 'created' => wpagent_spectra_one_snapshot_created_schema(), 'global_styles' => array( 'type' => array( 'object', 'null' ), 'properties' => wpagent_spectra_one_snapshot_global_result_schema()['properties'], 'required' => wpagent_spectra_one_snapshot_global_result_schema()['required'], 'additionalProperties' => false, 'maxProperties' => 2 ) ), array( 'verified', 'state_hash', 'created', 'global_styles' ) ),
		'execute_callback' => 'wpagent_exec_import_spectra_one_portable_snapshot', 'permission_callback' => 'wpagent_perm_edit_theme_options',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_spectra_one_portable_snapshot_schema(): array {
	return wpagent_spectra_one_snapshot_object_schema( array( 'schema' => array( 'type' => 'string', 'enum' => array( WPAGENT_SPECTRA_ONE_PORTABLE_SCHEMA_VERSION ), 'maxLength' => 64 ), 'transfer_key_id' => wpagent_spectra_one_snapshot_hash_schema(), 'source' => wpagent_spectra_one_snapshot_source_schema(), 'issued_at' => array( 'type' => 'integer', 'minimum' => 1 ), 'expires_at' => array( 'type' => 'integer', 'minimum' => 1 ), 'portable_state_hash' => wpagent_spectra_one_snapshot_hash_schema(), 'signature' => wpagent_spectra_one_snapshot_hash_schema(), 'bytes' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES ) ), array( 'schema', 'transfer_key_id', 'source', 'issued_at', 'expires_at', 'portable_state_hash', 'signature', 'bytes' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_portable_mapping_schema(): array {
	$global = wpagent_spectra_one_snapshot_object_schema( array( 'mode' => array( 'type' => 'string', 'enum' => array( 'create_only', 'skip' ), 'maxLength' => 20 ) ), array( 'mode' ) );
	$template = wpagent_spectra_one_snapshot_object_schema( array( 'source_index' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES - 1 ), 'source_slug' => array( 'type' => 'string', 'maxLength' => 200 ), 'target_slug' => array( 'type' => 'string', 'maxLength' => 200 ) ), array( 'source_index', 'target_slug' ) );
	$navigation = wpagent_spectra_one_snapshot_object_schema( array( 'source_index' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES - 1 ), 'source_id' => array( 'type' => 'integer', 'minimum' => 1 ), 'target_title' => array( 'type' => 'string', 'maxLength' => 200 ) ), array( 'source_index', 'target_title' ) );
	return wpagent_spectra_one_snapshot_object_schema( array( 'global_styles' => $global, 'site_templates' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => $template ), 'template_parts' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => $template ), 'navigations' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => $navigation ) ), array( 'global_styles', 'site_templates', 'template_parts', 'navigations' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_portable_target_global_styles_schema(): array {
	return wpagent_spectra_one_snapshot_object_schema( array( 'expected_target_hash' => wpagent_spectra_one_snapshot_hash_schema(), 'theme' => array( 'type' => 'string', 'maxLength' => 200 ) ), array( 'expected_target_hash', 'theme' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_portable_collision_list_schema(): array {
	$preset = wpagent_spectra_one_snapshot_object_schema( array( 'kind' => array( 'type' => 'string', 'enum' => array( 'preset' ), 'maxLength' => 20 ), 'group' => array( 'type' => 'string', 'enum' => array( 'colors', 'fontSizes', 'spacingSizes', 'fontFamilies' ), 'maxLength' => 20 ), 'slug' => array( 'type' => 'string', 'maxLength' => 40 ) ), array( 'kind', 'group', 'slug' ) );
	$path = wpagent_spectra_one_snapshot_object_schema( array( 'kind' => array( 'type' => 'string', 'enum' => array( 'style_path' ), 'maxLength' => 20 ), 'path' => array( 'type' => 'string', 'maxLength' => 100 ) ), array( 'kind', 'path' ) );
	$template = wpagent_spectra_one_snapshot_object_schema( array( 'source_index' => array( 'type' => 'integer', 'minimum' => 0 ), 'source_slug' => array( 'type' => 'string', 'maxLength' => 200 ), 'target_slug' => array( 'type' => 'string', 'maxLength' => 200 ), 'kind' => array( 'type' => 'string', 'enum' => array( 'target_exists' ), 'maxLength' => 20 ) ), array( 'source_index', 'source_slug', 'target_slug', 'kind' ) );
	$navigation = wpagent_spectra_one_snapshot_object_schema( array( 'source_index' => array( 'type' => 'integer', 'minimum' => 0 ), 'source_id' => array( 'type' => 'integer', 'minimum' => 1 ), 'target_title' => array( 'type' => 'string', 'maxLength' => 200 ), 'kind' => array( 'type' => 'string', 'enum' => array( 'target_exists' ), 'maxLength' => 20 ) ), array( 'source_index', 'source_id', 'target_title', 'kind' ) );
	// Four 60-item preset groups plus the six explicitly supported style paths.
	return wpagent_spectra_one_snapshot_object_schema( array( 'global_styles' => array( 'type' => 'array', 'maxItems' => 246, 'items' => array( 'anyOf' => array( $preset, $path ) ) ), 'site_templates' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => $template ), 'template_parts' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => $template ), 'navigations' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => $navigation ) ), array( 'global_styles', 'site_templates', 'template_parts', 'navigations' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_portable_preflight_schema(): array {
	$target = wpagent_spectra_one_portable_target_global_styles_schema();
	return wpagent_spectra_one_snapshot_object_schema( array( 'schema' => array( 'type' => 'string', 'enum' => array( WPAGENT_SPECTRA_ONE_PORTABLE_SCHEMA_VERSION ), 'maxLength' => 64 ), 'portable_state_hash' => wpagent_spectra_one_snapshot_hash_schema(), 'transfer_key_id' => wpagent_spectra_one_snapshot_hash_schema(), 'target_identity' => wpagent_spectra_one_snapshot_identity_schema(), 'mapping' => wpagent_spectra_one_portable_mapping_schema(), 'target_global_styles' => array( 'type' => array( 'object', 'null' ), 'properties' => $target['properties'], 'required' => $target['required'], 'additionalProperties' => false, 'maxProperties' => 2 ), 'collision_list' => wpagent_spectra_one_portable_collision_list_schema(), 'ready' => array( 'type' => 'boolean' ), 'issued_at' => array( 'type' => 'integer', 'minimum' => 1 ), 'expires_at' => array( 'type' => 'integer', 'minimum' => 1 ), 'preflight_hash' => wpagent_spectra_one_snapshot_hash_schema(), 'signature' => wpagent_spectra_one_snapshot_hash_schema() ), array( 'schema', 'portable_state_hash', 'transfer_key_id', 'target_identity', 'mapping', 'target_global_styles', 'collision_list', 'ready', 'issued_at', 'expires_at', 'preflight_hash', 'signature' ) );
}

/** @return true|WP_Error */
function wpagent_spectra_one_portable_require_admin() {
	return current_user_can( 'manage_options' ) ? true : new WP_Error( 'wpagent_spectra_one_portable_secret_forbidden', __( 'Zum Verwalten des Spectra-One-Transfergeheimnisses ist manage_options erforderlich.', 'wp-agent-companion' ), array( 'status' => 403 ) );
}

/** @return string|WP_Error */
function wpagent_spectra_one_portable_secret_key() {
	if ( ! function_exists( 'openssl_encrypt' ) || ! function_exists( 'openssl_decrypt' ) ) return new WP_Error( 'wpagent_spectra_one_portable_crypto', __( 'OpenSSL ist für das verschlüsselte Spectra-One-Transfergeheimnis nicht verfügbar.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	return hash_hmac( 'sha256', 'wpagently-spectra-one-portable-secret-v1', wp_salt( 'auth' ) . "\0" . wp_salt( 'secure_auth' ), true );
}

/** @return string|WP_Error */
function wpagent_spectra_one_portable_validate_secret( $value ) {
	$secret = is_string( $value ) ? $value : '';
	return preg_match( '/\A[\x21-\x7e]{32,128}\z/D', $secret ) ? $secret : new WP_Error( 'wpagent_spectra_one_portable_secret_invalid', __( 'Das Transfergeheimnis muss 32-128 druckbare ASCII-Zeichen enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
}

function wpagent_spectra_one_portable_key_id( string $secret ): string {
	return hash_hmac( 'sha256', "wpagently-spectra-one-portable-key-id-v1\0", $secret );
}

function wpagent_spectra_one_portable_secret_record_hash( string $raw ): string {
	return hash( 'sha256', "wpagently-spectra-one-portable-secret-record-v1\0{$raw}" );
}

/** @return array<string,mixed>|null */
function wpagent_spectra_one_portable_decode_secret_record( string $raw ): ?array {
	if ( ! is_serialized( $raw, true ) ) return null;
	try {
		$record = @unserialize( $raw, array( 'allowed_classes' => false ) );
	} catch ( Throwable $error ) {
		return null;
	}
	return is_array( $record ) ? $record : null;
}

/** @return WP_Error */
function wpagent_spectra_one_portable_secret_recovery_required( string $raw ): WP_Error {
	return new WP_Error(
		'wpagent_spectra_one_portable_secret_recovery_required',
		__( 'Das gespeicherte Spectra-One-Transfergeheimnis ist nach einer Schlüssel- oder AUTH_SALT-Änderung nicht mehr lesbar. Bestätige den angezeigten Datensatzhash ausdrücklich, um es durch ein neues Geheimnis zu ersetzen.', 'wp-agent-companion' ),
		array( 'status' => 409, 'expected_unreadable_record_hash' => wpagent_spectra_one_portable_secret_record_hash( $raw ) )
	);
}

/** @return true|WP_Error */
function wpagent_spectra_one_portable_validate_expiry( $issued_at, $expires_at, int $maximum_ttl, string $kind ) {
	if ( ! is_int( $issued_at ) || ! is_int( $expires_at ) || $issued_at < 1 || $expires_at <= $issued_at || $expires_at - $issued_at > $maximum_ttl ) {
		return new WP_Error( 'wpagent_spectra_one_portable_expiry_invalid', __( 'Der portable Transfer enthält eine ungültige Ablaufzeit.', 'wp-agent-companion' ), array( 'status' => 400, 'kind' => $kind ) );
	}
	$now = time();
	if ( $issued_at > $now + WPAGENT_SPECTRA_ONE_PORTABLE_MAX_CLOCK_SKEW ) return new WP_Error( 'wpagent_spectra_one_portable_not_yet_valid', __( 'Der portable Transfer liegt zu weit in der Zukunft. Prüfe die Serverzeit beider Websites und erstelle ihn erneut.', 'wp-agent-companion' ), array( 'status' => 409, 'kind' => $kind ) );
	return $expires_at > $now
		? true
		: new WP_Error( 'wpagent_spectra_one_portable_expired', __( 'Der portable Transfer oder sein Preflight ist abgelaufen. Exportiere beziehungsweise prüfe ihn erneut.', 'wp-agent-companion' ), array( 'status' => 409, 'kind' => $kind ) );
}

/** @return array<string,string>|WP_Error */
function wpagent_spectra_one_portable_encrypt_secret( string $secret ) {
	$key = wpagent_spectra_one_portable_secret_key();
	if ( is_wp_error( $key ) ) return $key;
	try { $nonce = random_bytes( 12 ); } catch ( Throwable $error ) { return new WP_Error( 'wpagent_spectra_one_portable_crypto', __( 'Für das Transfergeheimnis konnte kein sicherer Zufallswert erzeugt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) ); }
	$tag = '';
	$ciphertext = openssl_encrypt( $secret, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $tag, 'wpagently-spectra-one-portable-v1' );
	if ( ! is_string( $ciphertext ) || 16 !== strlen( $tag ) ) return new WP_Error( 'wpagent_spectra_one_portable_crypto', __( 'Das Transfergeheimnis konnte nicht sicher verschlüsselt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	return array( 'version' => 'v1', 'nonce' => base64_encode( $nonce ), 'tag' => base64_encode( $tag ), 'ciphertext' => base64_encode( $ciphertext ) );
}

/** @return string|WP_Error */
function wpagent_spectra_one_portable_decrypt_secret( $record ) {
	if ( ! is_array( $record ) || array_diff( array_keys( $record ), array( 'version', 'nonce', 'tag', 'ciphertext' ) ) || 'v1' !== ( $record['version'] ?? '' ) ) return new WP_Error( 'wpagent_spectra_one_portable_secret_unavailable', __( 'Das konfigurierte Spectra-One-Transfergeheimnis ist ungültig oder nicht lesbar.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	foreach ( array( 'nonce', 'tag', 'ciphertext' ) as $field ) if ( ! is_string( $record[ $field ] ?? null ) ) return new WP_Error( 'wpagent_spectra_one_portable_secret_unavailable', __( 'Das konfigurierte Spectra-One-Transfergeheimnis ist ungültig oder nicht lesbar.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	$nonce = base64_decode( $record['nonce'], true ); $tag = base64_decode( $record['tag'], true ); $ciphertext = base64_decode( $record['ciphertext'], true );
	$key = wpagent_spectra_one_portable_secret_key();
	if ( is_wp_error( $key ) ) return $key;
	if ( ! is_string( $nonce ) || 12 !== strlen( $nonce ) || ! is_string( $tag ) || 16 !== strlen( $tag ) || ! is_string( $ciphertext ) ) return new WP_Error( 'wpagent_spectra_one_portable_secret_unavailable', __( 'Das konfigurierte Spectra-One-Transfergeheimnis ist ungültig oder nicht lesbar.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	$secret = openssl_decrypt( $ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $tag, 'wpagently-spectra-one-portable-v1' );
	$valid = wpagent_spectra_one_portable_validate_secret( $secret );
	return is_wp_error( $valid ) ? new WP_Error( 'wpagent_spectra_one_portable_secret_unavailable', __( 'Das konfigurierte Spectra-One-Transfergeheimnis ist ungültig oder nicht lesbar.', 'wp-agent-companion' ), array( 'status' => 500 ) ) : $valid;
}

/** @return array{secret:string,key_id:string}|WP_Error */
function wpagent_spectra_one_portable_transfer_credentials() {
	$row = wpagent_companion_read_option_row_direct( WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_OPTION );
	if ( ! is_array( $row ) ) return new WP_Error( 'wpagent_spectra_one_portable_not_configured', __( 'Auf dieser Website ist noch kein Spectra-One-Transfergeheimnis eingerichtet.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$secret = wpagent_spectra_one_portable_decrypt_secret( wpagent_spectra_one_portable_decode_secret_record( $row['raw'] ) );
	return is_wp_error( $secret ) ? $secret : array( 'secret' => $secret, 'key_id' => wpagent_spectra_one_portable_key_id( $secret ) );
}

/** @return true|WP_Error */
function wpagent_spectra_one_portable_write_secret_cas( ?array $before, array $record ) {
	global $wpdb;
	if ( null === $before ) {
		if ( ! wpagent_companion_add_option_create_only( WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_OPTION, $record, '', false ) ) return new WP_Error( 'wpagent_spectra_one_portable_secret_conflict', __( 'Das Transfergeheimnis wurde gleichzeitig verändert. Lies den Konfigurationszustand erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	} else {
		$updated = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND autoload = %s AND HEX(option_value) = HEX(%s)", maybe_serialize( $record ), WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_OPTION, $before['autoload'], $before['raw'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		wpagent_companion_forget_option_cache( WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_OPTION, true );
		if ( 1 !== $updated ) return new WP_Error( 'wpagent_spectra_one_portable_secret_conflict', __( 'Das Transfergeheimnis wurde gleichzeitig verändert. Lies den Konfigurationszustand erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$after = wpagent_companion_read_option_row_direct( WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_OPTION );
	$autoload_matches = is_array( $after ) && ( null === $before ? ! in_array( $after['autoload'], wp_autoload_values_to_autoload(), true ) : $before['autoload'] === $after['autoload'] );
	return is_array( $after ) && $autoload_matches && wpagent_spectra_one_portable_decode_secret_record( $after['raw'] ) === $record ? true : new WP_Error( 'wpagent_spectra_one_portable_secret_verify', __( 'Das Transfergeheimnis konnte nicht verifiziert gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_configure_spectra_one_transfer_secret( $input ) {
	$capability = wpagent_spectra_one_portable_require_admin();
	if ( is_wp_error( $capability ) ) return $capability;
	$input = is_array( $input ) ? $input : array();
	if ( array_diff( array_keys( $input ), array( 'secret', 'expected_key_id', 'replace_unreadable_secret', 'expected_unreadable_record_hash' ) ) ) return new WP_Error( 'wpagent_spectra_one_portable_secret_invalid', __( 'Die Transfergeheimnis-Konfiguration enthält unbekannte Daten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$secret = wpagent_spectra_one_portable_validate_secret( $input['secret'] ?? null );
	if ( is_wp_error( $secret ) ) return $secret;
	$has_expected_key = array_key_exists( 'expected_key_id', $input );
	$has_recovery = array_key_exists( 'replace_unreadable_secret', $input );
	$has_recovery_hash = array_key_exists( 'expected_unreadable_record_hash', $input );
	if ( ( $has_expected_key && ! is_string( $input['expected_key_id'] ) ) || ( $has_recovery && ! is_bool( $input['replace_unreadable_secret'] ) ) || ( $has_recovery_hash && ! is_string( $input['expected_unreadable_record_hash'] ) ) ) return new WP_Error( 'wpagent_spectra_one_portable_secret_invalid', __( 'Die optionalen Transfergeheimnis-Bestätigungen sind ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$replace_unreadable = $has_recovery && $input['replace_unreadable_secret'];
	$lock = wpagent_companion_acquire_option_lock( WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_LOCK, WPAGENT_SPECTRA_ONE_PORTABLE_LOCK_TTL );
	if ( ! is_string( $lock ) ) return new WP_Error( 'wpagent_spectra_one_portable_secret_busy', __( 'Das Spectra-One-Transfergeheimnis wird gerade verwaltet. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	try {
		$capability = wpagent_spectra_one_portable_require_admin();
		if ( is_wp_error( $capability ) ) return $capability;
		$before = wpagent_companion_read_option_row_direct( WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_OPTION );
		if ( is_array( $before ) ) {
			$current = wpagent_spectra_one_portable_decrypt_secret( wpagent_spectra_one_portable_decode_secret_record( $before['raw'] ) );
			if ( is_wp_error( $current ) ) {
				$expected = is_string( $input['expected_unreadable_record_hash'] ?? null ) ? $input['expected_unreadable_record_hash'] : '';
				if ( ! $replace_unreadable || $has_expected_key || ! preg_match( '/^[a-f0-9]{64}$/', $expected ) || ! hash_equals( wpagent_spectra_one_portable_secret_record_hash( $before['raw'] ), $expected ) ) return wpagent_spectra_one_portable_secret_recovery_required( $before['raw'] );
			} else {
				$expected = is_string( $input['expected_key_id'] ?? null ) ? $input['expected_key_id'] : '';
				if ( $replace_unreadable || $has_recovery_hash || ! preg_match( '/^[a-f0-9]{64}$/', $expected ) || ! hash_equals( wpagent_spectra_one_portable_key_id( $current ), $expected ) ) return new WP_Error( 'wpagent_spectra_one_portable_secret_conflict', __( 'Zum Ersetzen des Transfergeheimnisses ist die aktuelle Schlüsselkennung erforderlich.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
		} elseif ( $has_expected_key || $has_recovery || $has_recovery_hash ) return new WP_Error( 'wpagent_spectra_one_portable_secret_conflict', __( 'Für ein neues Transfergeheimnis darf keine vorherige Schlüsselkennung oder Wiederherstellungsbestätigung gesetzt werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$record = wpagent_spectra_one_portable_encrypt_secret( $secret );
		if ( is_wp_error( $record ) ) return $record;
		$capability = wpagent_spectra_one_portable_require_admin();
		if ( is_wp_error( $capability ) ) return $capability;
		$result = wpagent_spectra_one_portable_write_secret_cas( $before, $record );
		if ( is_wp_error( $result ) ) return $result;
		$verified = wpagent_spectra_one_portable_transfer_credentials();
		return is_wp_error( $verified ) || ! hash_equals( $verified['key_id'], wpagent_spectra_one_portable_key_id( $secret ) ) ? new WP_Error( 'wpagent_spectra_one_portable_secret_verify', __( 'Das Transfergeheimnis konnte nicht verifiziert gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) ) : array( 'configured' => true, 'key_id' => $verified['key_id'] );
	} finally {
		wpagent_companion_release_option_lock( WPAGENT_SPECTRA_ONE_PORTABLE_SECRET_LOCK, $lock );
	}
}

function wpagent_spectra_one_portable_signature( string $scope, string $hash, string $secret ): string {
	return hash_hmac( 'sha256', "wpagently-spectra-one-portable-v1\0{$scope}\0{$hash}", $secret );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_export_spectra_one_portable_snapshot( $input ) {
	$credentials = wpagent_spectra_one_portable_transfer_credentials();
	if ( is_wp_error( $credentials ) ) return $credentials;
	$export = wpagent_exec_export_spectra_one_design_snapshot( $input );
	if ( is_wp_error( $export ) ) return $export;
	$source = wpagent_spectra_one_snapshot_canonical_source( array( 'schema' => $export['schema'], 'identity' => $export['identity'], 'global_styles_state_hash' => $export['global_styles_state_hash'], 'snapshot' => $export['snapshot'], 'state_hash' => $export['state_hash'], 'bytes' => $export['bytes'] ) );
	$issued_at = time();
	$unsigned = array( 'schema' => WPAGENT_SPECTRA_ONE_PORTABLE_SCHEMA_VERSION, 'transfer_key_id' => $credentials['key_id'], 'source' => $source, 'issued_at' => $issued_at, 'expires_at' => $issued_at + WPAGENT_SPECTRA_ONE_PORTABLE_SNAPSHOT_TTL );
	$json = wp_json_encode( $unsigned, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( false === $json || strlen( $json ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES ) return new WP_Error( 'wpagent_spectra_one_portable_size', __( 'Der portable Spectra-One-Snapshot überschreitet die feste Grenze von 512 KiB.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	$hash = hash( 'sha256', "wpagently-spectra-one-portable-snapshot-v1\0{$json}" );
	return array_merge( $unsigned, array( 'portable_state_hash' => $hash, 'signature' => wpagent_spectra_one_portable_signature( 'snapshot', $hash, $credentials['secret'] ), 'bytes' => strlen( $json ) ) );
}

/** @return array{source:array<string,mixed>,plan:array<string,mixed>,credentials:array{secret:string,key_id:string}}|WP_Error */
function wpagent_spectra_one_portable_validate_snapshot( $value ) {
	$credentials = wpagent_spectra_one_portable_transfer_credentials();
	if ( is_wp_error( $credentials ) ) return $credentials;
	if ( ! is_array( $value ) || array_diff( array_keys( $value ), array( 'schema', 'transfer_key_id', 'source', 'issued_at', 'expires_at', 'portable_state_hash', 'signature', 'bytes' ) ) || WPAGENT_SPECTRA_ONE_PORTABLE_SCHEMA_VERSION !== ( $value['schema'] ?? '' ) || ! is_array( $value['source'] ?? null ) || ! is_string( $value['transfer_key_id'] ?? null ) || ! is_int( $value['issued_at'] ?? null ) || ! is_int( $value['expires_at'] ?? null ) || ! is_string( $value['portable_state_hash'] ?? null ) || ! is_string( $value['signature'] ?? null ) || ! is_int( $value['bytes'] ?? null ) ) return new WP_Error( 'wpagent_spectra_one_portable_invalid', __( 'Das portable Spectra-One-Snapshot-Format ist unvollständig oder ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$expiry = wpagent_spectra_one_portable_validate_expiry( $value['issued_at'], $value['expires_at'], WPAGENT_SPECTRA_ONE_PORTABLE_SNAPSHOT_TTL, 'snapshot' );
	if ( is_wp_error( $expiry ) ) return $expiry;
	if ( ! preg_match( '/^[a-f0-9]{64}$/', $value['transfer_key_id'] ) || ! preg_match( '/^[a-f0-9]{64}$/', $value['portable_state_hash'] ) || ! preg_match( '/^[a-f0-9]{64}$/', $value['signature'] ) || ! hash_equals( $credentials['key_id'], $value['transfer_key_id'] ) ) return new WP_Error( 'wpagent_spectra_one_portable_signature', __( 'Der portable Snapshot wurde nicht mit dem hier eingerichteten Transfergeheimnis signiert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$source = $value['source'];
	$unsigned = array( 'schema' => $value['schema'], 'transfer_key_id' => $value['transfer_key_id'], 'source' => wpagent_spectra_one_snapshot_canonical_source( $source ), 'issued_at' => $value['issued_at'], 'expires_at' => $value['expires_at'] ); $json = wp_json_encode( $unsigned, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	$hash = false === $json ? '' : hash( 'sha256', "wpagently-spectra-one-portable-snapshot-v1\0{$json}" );
	if ( false === $json || strlen( $json ) !== $value['bytes'] || strlen( $json ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES || ! hash_equals( $hash, $value['portable_state_hash'] ) || ! hash_equals( wpagent_spectra_one_portable_signature( 'snapshot', $hash, $credentials['secret'] ), $value['signature'] ) ) return new WP_Error( 'wpagent_spectra_one_portable_integrity', __( 'Der portable Spectra-One-Snapshot ist beschädigt, verändert oder zu groß.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( array_diff( array_keys( $source ), array( 'schema', 'identity', 'global_styles_state_hash', 'snapshot', 'state_hash', 'bytes' ) ) || WPAGENT_SPECTRA_ONE_SNAPSHOT_SCHEMA_VERSION !== ( $source['schema'] ?? '' ) || ! is_array( $source['identity'] ?? null ) || ! is_array( $source['snapshot'] ?? null ) || ! is_string( $source['global_styles_state_hash'] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/', $source['global_styles_state_hash'] ) || ! is_string( $source['state_hash'] ?? null ) || ! is_int( $source['bytes'] ?? null ) ) return new WP_Error( 'wpagent_spectra_one_portable_invalid', __( 'Der portable Spectra-One-Snapshot enthält keinen gültigen Quellsnapshot.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$identity = $source['identity'];
	if ( array_diff( array_keys( $identity ), array( 'blog_id', 'site_hash', 'theme' ) ) || ! is_int( $identity['blog_id'] ?? null ) || $identity['blog_id'] < 1 || ! is_string( $identity['site_hash'] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/', $identity['site_hash'] ) || ! is_array( $identity['theme'] ?? null ) || array_diff( array_keys( $identity['theme'] ), array( 'slug', 'version', 'hash' ) ) || 'spectra-one' !== ( $identity['theme']['slug'] ?? '' ) || WPAGENT_SPECTRA_ONE_SNAPSHOT_THEME_VERSION !== ( $identity['theme']['version'] ?? '' ) || ! is_string( $identity['theme']['hash'] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/', $identity['theme']['hash'] ) ) return new WP_Error( 'wpagent_spectra_one_portable_source_theme', __( 'Der portable Snapshot stammt nicht aus einem kompatiblen Spectra-One-1.2.3-Quelltheme.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	if ( $identity === wpagent_spectra_one_snapshot_identity() ) return new WP_Error( 'wpagent_spectra_one_portable_source_target', __( 'Ein portabler Spectra-One-Transfer benötigt unterschiedliche Quell- und Zielsites. Verwende für dieselbe Site den lokalen Snapshot-Import.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$canonical_source = wpagent_spectra_one_snapshot_canonical_source( $source );
	$source_unsigned = array( 'schema' => $canonical_source['schema'], 'identity' => $canonical_source['identity'], 'global_styles_state_hash' => $canonical_source['global_styles_state_hash'], 'snapshot' => $canonical_source['snapshot'] ); $source_json = wp_json_encode( $source_unsigned, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( false === $source_json || strlen( $source_json ) !== $source['bytes'] || strlen( $source_json ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES || ! preg_match( '/^[a-f0-9]{64}$/', $source['state_hash'] ) || ! hash_equals( hash( 'sha256', "wpagently-spectra-one-snapshot-v1\0{$source_json}" ), $source['state_hash'] ) ) return new WP_Error( 'wpagent_spectra_one_portable_integrity', __( 'Der enthaltene Quellsnapshot ist beschädigt oder verändert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$body = $source['snapshot'];
	if ( array_diff( array_keys( $body ), array( 'global_styles', 'site_templates', 'template_parts', 'navigations' ) ) || ! is_array( $body['site_templates'] ?? null ) || ! array_is_list( $body['site_templates'] ) || ! is_array( $body['template_parts'] ?? null ) || ! array_is_list( $body['template_parts'] ) || ! is_array( $body['navigations'] ?? null ) || ! array_is_list( $body['navigations'] ) || count( $body['site_templates'] ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES || count( $body['template_parts'] ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES || count( $body['navigations'] ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES ) return new WP_Error( 'wpagent_spectra_one_portable_invalid', __( 'Die Quellkomponenten des portablen Snapshots sind ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$selection = array( 'global_styles' => is_array( $body['global_styles'] ?? null ), 'site_templates' => array_keys( $body['site_templates'] ), 'template_parts' => array_keys( $body['template_parts'] ), 'navigations' => array_keys( $body['navigations'] ) );
	$plan = array( 'snapshot' => $body, 'selection' => $selection, 'site_templates' => array_values( $body['site_templates'] ), 'template_parts' => array_values( $body['template_parts'] ), 'navigations' => array_values( $body['navigations'] ) );
	if ( $selection['global_styles'] ) { $global = wpagent_spectra_one_snapshot_validate_global_styles( $body['global_styles'] ); if ( is_wp_error( $global ) ) return $global; $plan['snapshot']['global_styles'] = $global; }
	$valid = wpagent_spectra_one_snapshot_validate_records( $plan );
	if ( is_wp_error( $valid ) ) return $valid;
	foreach ( array( 'site_templates', 'template_parts' ) as $kind ) { $slugs = array(); foreach ( $body[ $kind ] as $record ) { $slug = is_array( $record ) ? (string) ( $record['slug'] ?? '' ) : ''; if ( '' === $slug || isset( $slugs[ $slug ] ) ) return new WP_Error( 'wpagent_spectra_one_portable_invalid', __( 'Der portable Snapshot enthält doppelte Quellressourcen.', 'wp-agent-companion' ), array( 'status' => 400 ) ); $slugs[ $slug ] = true; } }
	return array( 'source' => $source, 'plan' => $plan, 'credentials' => $credentials );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_portable_validate_mapping( $value, array $source, array $plan ) {
	if ( ! is_array( $value ) || array_diff( array_keys( $value ), array( 'global_styles', 'site_templates', 'template_parts', 'navigations' ) ) || ! is_array( $value['global_styles'] ?? null ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Für alle portablen Komponenten ist eine explizite Zielzuordnung erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$global = $value['global_styles'];
	if ( array_diff( array_keys( $global ), array( 'mode' ) ) || ! is_string( $global['mode'] ?? null ) || ! in_array( $global['mode'], array( 'create_only', 'skip' ), true ) || ( is_array( $plan['snapshot']['global_styles'] ?? null ) ? 'create_only' !== $global['mode'] : 'skip' !== $global['mode'] ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Die Global-Styles-Zuordnung muss den vorhandenen Quellsnapshot ausdrücklich create-only importieren oder überspringen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$out = array( 'global_styles' => array( 'mode' => $global['mode'] ), 'site_templates' => array(), 'template_parts' => array(), 'navigations' => array() );
	foreach ( array( 'site_templates', 'template_parts' ) as $kind ) {
		$list = $value[ $kind ] ?? null; $records = $source['snapshot'][ $kind ];
		if ( ! is_array( $list ) || ! array_is_list( $list ) || count( $list ) !== count( $records ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Jede Quellvorlage benötigt genau eine explizite Zielzuordnung.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$seen_indexes = array(); $seen_targets = array();
		foreach ( $list as $row ) {
			if ( ! is_array( $row ) || array_diff( array_keys( $row ), array( 'source_index', 'source_slug', 'target_slug' ) ) || ! is_int( $row['source_index'] ?? null ) || ! array_key_exists( $row['source_index'], $records ) || isset( $seen_indexes[ $row['source_index'] ] ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Eine Template-Zuordnung ist ungültig oder doppelt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			$slug = wpagent_site_editor_slug( $row['target_slug'] ?? '' ); if ( is_wp_error( $slug ) || isset( $seen_targets[ $slug ] ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Ein Ziel-Template-Slug ist ungültig oder doppelt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			$source_slug = (string) ( $records[ $row['source_index'] ]['slug'] ?? '' ); if ( isset( $row['source_slug'] ) && ( ! is_string( $row['source_slug'] ) || ! hash_equals( $source_slug, $row['source_slug'] ) ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Eine Template-Zuordnung passt nicht mehr zur Quellressource.', 'wp-agent-companion' ), array( 'status' => 409 ) ); $seen_indexes[ $row['source_index'] ] = true; $seen_targets[ $slug ] = true; $out[ $kind ][] = array( 'source_index' => $row['source_index'], 'source_slug' => $source_slug, 'target_slug' => $slug );
		}
		usort( $out[ $kind ], static fn( array $a, array $b ): int => $a['source_index'] <=> $b['source_index'] );
	}
	$list = $value['navigations'] ?? null; $records = $source['snapshot']['navigations'];
	if ( ! is_array( $list ) || ! array_is_list( $list ) || count( $list ) !== count( $records ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Jede Quellnavigation benötigt genau eine explizite Zielzuordnung.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$seen_indexes = array(); $seen_titles = array();
	foreach ( $list as $row ) {
		if ( ! is_array( $row ) || array_diff( array_keys( $row ), array( 'source_index', 'source_id', 'target_title' ) ) || ! is_int( $row['source_index'] ?? null ) || ! array_key_exists( $row['source_index'], $records ) || isset( $seen_indexes[ $row['source_index'] ] ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Eine Navigation-Zuordnung ist ungültig oder doppelt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$title = wpagent_site_editor_plain_text( $row['target_title'] ?? '', 'title', 200 ); $title_key = is_wp_error( $title ) ? '' : sanitize_title( $title ); if ( is_wp_error( $title ) || isset( $seen_titles[ $title_key ] ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Ein Zielnavigationstitel ist ungültig oder doppelt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$source_id = (int) ( $records[ $row['source_index'] ]['source_id'] ?? 0 ); if ( isset( $row['source_id'] ) && ( ! is_int( $row['source_id'] ) || $source_id !== $row['source_id'] ) ) return new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Eine Navigation-Zuordnung passt nicht mehr zur Quellressource.', 'wp-agent-companion' ), array( 'status' => 409 ) ); $seen_indexes[ $row['source_index'] ] = true; $seen_titles[ $title_key ] = true; $out['navigations'][] = array( 'source_index' => $row['source_index'], 'source_id' => $source_id, 'target_title' => $title );
	}
	usort( $out['navigations'], static fn( array $a, array $b ): int => $a['source_index'] <=> $b['source_index'] );
	return $out;
}

/** @return array<int,string> */
function wpagent_spectra_one_portable_style_paths( $value, string $prefix = '' ): array {
	if ( ! is_array( $value ) || array() === $value ) return '' === $prefix ? array() : array( $prefix );
	$out = array(); foreach ( $value as $key => $child ) { $path = '' === $prefix ? (string) $key : $prefix . '.' . (string) $key; $out = array_merge( $out, wpagent_spectra_one_portable_style_paths( $child, $path ) ); } return $out;
}

/** @return array<string,array<int,array<string,mixed>>>|WP_Error */
function wpagent_spectra_one_portable_collisions( array $plan, array $mapping ) {
	$out = array( 'global_styles' => array(), 'site_templates' => array(), 'template_parts' => array(), 'navigations' => array() );
	if ( 'create_only' === $mapping['global_styles']['mode'] ) {
		$state = wpagent_gs_state(); if ( is_wp_error( $state ) ) return $state;
		$target = wpagent_spectra_one_snapshot_global_styles( $state['snapshot'] ); if ( is_wp_error( $target ) ) return $target;
		foreach ( array( 'colors', 'fontSizes', 'spacingSizes', 'fontFamilies' ) as $kind ) { $existing = array(); foreach ( $target[ $kind ] as $entry ) $existing[ $entry['slug'] ] = true; foreach ( $plan['snapshot']['global_styles'][ $kind ] as $entry ) if ( isset( $existing[ $entry['slug'] ] ) ) $out['global_styles'][] = array( 'kind' => 'preset', 'group' => $kind, 'slug' => $entry['slug'] ); }
		$target_paths = array_fill_keys( wpagent_spectra_one_portable_style_paths( $target['styles'] ), true ); foreach ( wpagent_spectra_one_portable_style_paths( $plan['snapshot']['global_styles']['styles'] ) as $path ) if ( isset( $target_paths[ $path ] ) ) $out['global_styles'][] = array( 'kind' => 'style_path', 'path' => $path );
	}
	foreach ( array( 'site_templates' => 'wp_template', 'template_parts' => 'wp_template_part' ) as $kind => $type ) foreach ( $mapping[ $kind ] as $row ) if ( get_block_template( wpagent_site_editor_template_id( $row['target_slug'] ), $type ) ) $out[ $kind ][] = array( 'source_index' => $row['source_index'], 'source_slug' => $row['source_slug'], 'target_slug' => $row['target_slug'], 'kind' => 'target_exists' );
	foreach ( $mapping['navigations'] as $row ) if ( wpagent_spectra_one_portable_navigation_target_exists( $row['target_title'] ) ) $out['navigations'][] = array( 'source_index' => $row['source_index'], 'source_id' => $row['source_id'], 'target_title' => $row['target_title'], 'kind' => 'target_exists' );
	return $out;
}

function wpagent_spectra_one_portable_collisions_empty( array $collisions ): bool {
	foreach ( $collisions as $list ) if ( is_array( $list ) && array() !== $list ) return false;
	return true;
}

function wpagent_spectra_one_portable_navigation_target_exists( string $title ): bool {
	global $wpdb;
	$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(1) FROM {$wpdb->posts} WHERE post_type = %s AND post_status <> %s AND post_title = %s", 'wp_navigation', 'trash', $title ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
	return (int) $count > 0;
}

function wpagent_spectra_one_portable_navigation_target_conflicts( string $title, int $owned_id ): bool {
	global $wpdb;
	$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(1) FROM {$wpdb->posts} WHERE post_type = %s AND post_status <> %s AND post_title = %s AND ID <> %d", 'wp_navigation', 'trash', $title, $owned_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
	return (int) $count > 0;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_portable_target_state( array $plan, array $mapping ) {
	$collisions = wpagent_spectra_one_portable_collisions( $plan, $mapping );
	if ( is_wp_error( $collisions ) ) return $collisions;
	$global = null;
	if ( 'create_only' === $mapping['global_styles']['mode'] ) { $state = wpagent_gs_state(); if ( is_wp_error( $state ) ) return $state; $global = array( 'expected_target_hash' => $state['state_hash'], 'theme' => $state['theme'] ); }
	return array( 'target_identity' => wpagent_spectra_one_snapshot_identity(), 'target_global_styles' => $global, 'collision_list' => $collisions, 'ready' => wpagent_spectra_one_portable_collisions_empty( $collisions ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_preflight_spectra_one_portable_snapshot( $input ) {
	$capability = wpagent_spectra_one_snapshot_require_capability(); if ( is_wp_error( $capability ) ) return $capability;
	$theme = wpagent_spectra_one_snapshot_require_theme(); if ( is_wp_error( $theme ) ) return $theme;
	$input = is_array( $input ) ? $input : array();
	if ( array_diff( array_keys( $input ), array( 'portable_snapshot', 'mapping' ) ) ) return new WP_Error( 'wpagent_spectra_one_portable_invalid', __( 'Der portable Transfer-Preflight enthält unbekannte Daten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$validated = wpagent_spectra_one_portable_validate_snapshot( $input['portable_snapshot'] ?? null ); if ( is_wp_error( $validated ) ) return $validated;
	$mapping = wpagent_spectra_one_portable_validate_mapping( $input['mapping'] ?? null, $validated['source'], $validated['plan'] ); if ( is_wp_error( $mapping ) ) return $mapping;
	$target = wpagent_spectra_one_portable_target_state( $validated['plan'], $mapping ); if ( is_wp_error( $target ) ) return $target;
	$issued_at = time();
	$unsigned = array( 'schema' => WPAGENT_SPECTRA_ONE_PORTABLE_SCHEMA_VERSION, 'portable_state_hash' => $input['portable_snapshot']['portable_state_hash'], 'transfer_key_id' => $validated['credentials']['key_id'], 'target_identity' => $target['target_identity'], 'mapping' => $mapping, 'target_global_styles' => $target['target_global_styles'], 'collision_list' => $target['collision_list'], 'ready' => $target['ready'], 'issued_at' => $issued_at, 'expires_at' => min( (int) $input['portable_snapshot']['expires_at'], $issued_at + WPAGENT_SPECTRA_ONE_PORTABLE_PREFLIGHT_TTL ) );
	$json = wp_json_encode( $unsigned, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); if ( false === $json || strlen( $json ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES ) return new WP_Error( 'wpagent_spectra_one_portable_size', __( 'Der portable Transfer-Preflight überschreitet die feste Grenze von 512 KiB.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	$hash = hash( 'sha256', "wpagently-spectra-one-portable-preflight-v1\0{$json}" );
	return array_merge( $unsigned, array( 'preflight_hash' => $hash, 'signature' => wpagent_spectra_one_portable_signature( 'preflight', $hash, $validated['credentials']['secret'] ) ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_portable_validate_preflight( $value, array $snapshot, array $validated ) {
	if ( ! is_array( $value ) || array_diff( array_keys( $value ), array( 'schema', 'portable_state_hash', 'transfer_key_id', 'target_identity', 'mapping', 'target_global_styles', 'collision_list', 'ready', 'issued_at', 'expires_at', 'preflight_hash', 'signature' ) ) || WPAGENT_SPECTRA_ONE_PORTABLE_SCHEMA_VERSION !== ( $value['schema'] ?? '' ) || ! is_array( $value['target_identity'] ?? null ) || ! is_array( $value['mapping'] ?? null ) || ! is_array( $value['collision_list'] ?? null ) || ! is_bool( $value['ready'] ?? null ) || ! is_int( $value['issued_at'] ?? null ) || ! is_int( $value['expires_at'] ?? null ) || ! is_string( $value['portable_state_hash'] ?? null ) || ! is_string( $value['transfer_key_id'] ?? null ) || ! is_string( $value['preflight_hash'] ?? null ) || ! is_string( $value['signature'] ?? null ) ) return new WP_Error( 'wpagent_spectra_one_portable_preflight', __( 'Der portable Transfer-Preflight ist unvollständig oder ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$expiry = wpagent_spectra_one_portable_validate_expiry( $value['issued_at'], $value['expires_at'], WPAGENT_SPECTRA_ONE_PORTABLE_PREFLIGHT_TTL, 'preflight' );
	if ( is_wp_error( $expiry ) || $value['expires_at'] > $snapshot['expires_at'] ) return is_wp_error( $expiry ) ? $expiry : new WP_Error( 'wpagent_spectra_one_portable_preflight', __( 'Der portable Transfer-Preflight läuft später ab als sein Snapshot.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$unsigned = array( 'schema' => $value['schema'], 'portable_state_hash' => $value['portable_state_hash'], 'transfer_key_id' => $value['transfer_key_id'], 'target_identity' => $value['target_identity'], 'mapping' => $value['mapping'], 'target_global_styles' => $value['target_global_styles'], 'collision_list' => $value['collision_list'], 'ready' => $value['ready'], 'issued_at' => $value['issued_at'], 'expires_at' => $value['expires_at'] ); $json = wp_json_encode( $unsigned, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); $hash = false === $json ? '' : hash( 'sha256', "wpagently-spectra-one-portable-preflight-v1\0{$json}" );
	if ( false === $json || strlen( $json ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES || ! preg_match( '/^[a-f0-9]{64}$/', $value['preflight_hash'] ) || ! preg_match( '/^[a-f0-9]{64}$/', $value['signature'] ) || ! hash_equals( $hash, $value['preflight_hash'] ) || ! hash_equals( wpagent_spectra_one_portable_signature( 'preflight', $hash, $validated['credentials']['secret'] ), $value['signature'] ) || ! hash_equals( $snapshot['portable_state_hash'], $value['portable_state_hash'] ) || ! hash_equals( $validated['credentials']['key_id'], $value['transfer_key_id'] ) ) return new WP_Error( 'wpagent_spectra_one_portable_preflight', __( 'Der portable Transfer-Preflight wurde verändert oder gehört nicht zu diesem Snapshot.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$mapping = wpagent_spectra_one_portable_validate_mapping( $value['mapping'], $validated['source'], $validated['plan'] ); if ( is_wp_error( $mapping ) || $mapping !== $value['mapping'] ) return new WP_Error( 'wpagent_spectra_one_portable_preflight', __( 'Die signierte Transferzuordnung ist ungültig.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	return $value;
}

/** @return true|WP_Error */
function wpagent_spectra_one_portable_guard( string $lock, array $identity, string $key_id, $global_lock ) {
	if ( ! current_user_can( 'edit_theme_options' ) ) return new WP_Error( 'wpagent_spectra_one_portable_capability_changed', __( 'Die erforderliche Berechtigung ging während des portablen Transfers verloren.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	if ( ! wpagent_companion_refresh_option_lock( WPAGENT_SPECTRA_ONE_PORTABLE_LOCK, $lock, WPAGENT_SPECTRA_ONE_PORTABLE_LOCK_TTL ) ) return new WP_Error( 'wpagent_spectra_one_portable_lock_lost', __( 'Die Transfer-Sperre ging während des Imports verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	if ( is_string( $global_lock ) && ( ! wpagent_gs_refresh_lock( $global_lock ) || ! wpagent_gs_lock_is_owned( $global_lock ) ) ) return new WP_Error( 'wpagent_spectra_one_portable_lock_lost', __( 'Die Global-Styles-Sperre ging während des Imports verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$credentials = wpagent_spectra_one_portable_transfer_credentials(); if ( is_wp_error( $credentials ) || ! hash_equals( $key_id, $credentials['key_id'] ) ) return new WP_Error( 'wpagent_spectra_one_portable_secret_changed', __( 'Das Transfergeheimnis wurde während des Imports verändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	return wpagent_spectra_one_snapshot_identity() === $identity ? true : new WP_Error( 'wpagent_spectra_one_portable_identity_changed', __( 'Blog oder Theme haben sich während des portablen Imports geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

/** @return array<int,array<string,mixed>> */
function wpagent_spectra_one_portable_remap_components( array $components, array $parts, array $navigations ): array {
	$out = array(); foreach ( $components as $component ) { if ( 'template-part' === $component['type'] ) $component['slug'] = $parts[ $component['slug'] ] ?? ''; if ( 'navigation' === $component['type'] ) $component['navigation_id'] = $navigations[ (int) $component['navigation_id'] ] ?? 0; $out[] = $component; } return $out;
}

/** @return WP_Error */
function wpagent_spectra_one_portable_rollback_unjournaled_record( WP_Error $failure, array $record ): WP_Error {
	if ( wpagent_spectra_one_snapshot_rollback_record( $record, '' ) ) return $failure;
	return new WP_Error( 'wpagent_spectra_one_portable_recovery_required', __( 'Der portable Import verlor seine Sicherheitsprüfung nach einer eigenen Änderung. Der Zielzustand muss manuell geprüft werden.', 'wp-agent-companion' ), array( 'status' => 500, 'recovery_required' => true, 'original_error' => $failure->get_error_code() ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_portable_create_template( string $type, array $record, string $target_slug, array $part_map, array $navigation_map, string $lock, array $identity, string $key_id, $global_lock ) {
	$guard = wpagent_spectra_one_portable_guard( $lock, $identity, $key_id, $global_lock ); if ( is_wp_error( $guard ) ) return $guard;
	$components = wpagent_spectra_one_portable_remap_components( $record['components'], $part_map, $navigation_map );
	foreach ( $components as $component ) if ( ( 'template-part' === $component['type'] && '' === $component['slug'] ) || ( 'navigation' === $component['type'] && $component['navigation_id'] < 1 ) ) return new WP_Error( 'wpagent_spectra_one_portable_reference', __( 'Eine Template-Referenz konnte nicht auf eine neu erstellte Zielressource abgebildet werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$input = array( 'slug' => $target_slug, 'title' => $record['title'], 'description' => $record['description'], 'container' => $record['container'], 'components' => $components ); if ( 'wp_template_part' === $type ) $input['area'] = $record['area'];
	$result = 'wp_template_part' === $type ? wpagent_exec_create_template_part( $input ) : wpagent_exec_create_site_template( $input );
	if ( is_wp_error( $result ) || empty( $result['verified'] ) || ! is_array( $result['resource'] ?? null ) ) return is_wp_error( $result ) ? $result : new WP_Error( 'wpagent_spectra_one_portable_verify', __( 'Eine Template-Zielressource wurde nicht vollständig verifiziert.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	$guard = wpagent_spectra_one_portable_guard( $lock, $identity, $key_id, $global_lock ); return is_wp_error( $guard ) ? wpagent_spectra_one_portable_rollback_unjournaled_record( $guard, array( 'kind' => 'template', 'type' => $type, 'slug' => $result['resource']['slug'], 'hash' => $result['resource']['hash'] ) ) : $result;
}

function wpagent_spectra_one_portable_navigation_meta_hash( int $id ): string {
	$meta = get_post_meta( $id );
	if ( ! is_array( $meta ) ) {
		return '';
	}
	ksort( $meta, SORT_STRING );
	return hash( 'sha256', serialize( $meta ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_portable_capture_owned_navigation( int $id, string $title, string $content ) {
	$state = wpagent_site_editor_navigation_state( $id );
	$post  = get_post( $id );
	if ( is_wp_error( $state ) || ! $post instanceof WP_Post || 'wp_navigation' !== $post->post_type || $id !== (int) ( $state['id'] ?? 0 ) || $title !== ( $state['title'] ?? '' ) || $content !== ( $state['content'] ?? '' ) || ! empty( $state['has_disallowed_blocks'] ) || ! is_string( $state['hash'] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/D', $state['hash'] ) ) {
		return new WP_Error( 'wpagent_spectra_one_portable_navigation_candidate_drift', __( 'Die neu angelegte Zielnavigation änderte sich vor der Verifikation und bleibt zur manuellen Prüfung erhalten.', 'wp-agent-companion' ), array( 'status' => 500, 'recovery_required' => true, 'navigation_id' => $id ) );
	}
	$meta_hash = wpagent_spectra_one_portable_navigation_meta_hash( $id );
	if ( ! preg_match( '/^[a-f0-9]{64}$/D', $meta_hash ) ) {
		return new WP_Error( 'wpagent_spectra_one_portable_navigation_candidate_drift', __( 'Die vollständigen Metadaten der neu angelegten Zielnavigation konnten nicht sicher gebunden werden.', 'wp-agent-companion' ), array( 'status' => 500, 'recovery_required' => true, 'navigation_id' => $id ) );
	}
	return array( 'id' => $id, 'hash' => $state['hash'], 'meta_hash' => $meta_hash, 'title' => $title, 'content' => $content );
}

/** @param array<string,mixed> $candidate */
function wpagent_spectra_one_portable_cleanup_owned_navigation( array $candidate, string $lock, array $identity, string $key_id, $global_lock, string $navigation_lock_key, string $navigation_lock ): bool {
	$id            = absint( $candidate['id'] ?? 0 );
	$expected_hash = is_string( $candidate['hash'] ?? null ) ? $candidate['hash'] : '';
	$expected_meta_hash = is_string( $candidate['meta_hash'] ?? null ) ? $candidate['meta_hash'] : '';
	$title         = is_string( $candidate['title'] ?? null ) ? $candidate['title'] : '';
	$content       = is_string( $candidate['content'] ?? null ) ? $candidate['content'] : '';
	$guard         = wpagent_spectra_one_portable_guard( $lock, $identity, $key_id, $global_lock );
	if ( is_wp_error( $guard ) || $id < 1 || ! preg_match( '/^[a-f0-9]{64}$/D', $expected_hash ) || ! preg_match( '/^[a-f0-9]{64}$/D', $expected_meta_hash ) || ! wpagent_site_editor_lock_is_owned( 'wp_navigation', $navigation_lock_key, $navigation_lock ) || ! current_user_can( 'delete_post', $id ) ) {
		return false;
	}
	$state = wpagent_site_editor_navigation_state( $id );
	$post  = get_post( $id );
	if ( is_wp_error( $state ) || ! $post instanceof WP_Post || 'wp_navigation' !== $post->post_type || $id !== (int) ( $state['id'] ?? 0 ) || ! hash_equals( $expected_hash, (string) ( $state['hash'] ?? '' ) ) || ! hash_equals( $expected_meta_hash, wpagent_spectra_one_portable_navigation_meta_hash( $id ) ) || $title !== ( $state['title'] ?? '' ) || $content !== ( $state['content'] ?? '' ) || ! empty( $state['has_disallowed_blocks'] ) ) {
		return false;
	}
	$deleted = wp_delete_post( $id, true );
	return $deleted instanceof WP_Post && null === get_post( $id );
}

/** @return WP_Error */
function wpagent_spectra_one_portable_navigation_recovery_required( int $id, string $message ): WP_Error {
	return new WP_Error( 'wpagent_spectra_one_portable_recovery_required', $message, array( 'status' => 500, 'recovery_required' => true, 'navigation_id' => $id ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_portable_create_navigation( array $record, string $target_title, string $lock, array $identity, string $key_id, $global_lock ) {
	$guard = wpagent_spectra_one_portable_guard( $lock, $identity, $key_id, $global_lock ); if ( is_wp_error( $guard ) ) return $guard;
	$content = wpagent_site_editor_build_navigation_content( $record['items'] ); if ( is_wp_error( $content ) ) return $content;
	// Match the native create-navigation lock key used by the regular writer.
	$lock_key = 'create:' . hash( 'sha256', strtolower( $target_title ) ); $token = wpagent_site_editor_acquire_lock( 'wp_navigation', $lock_key ); if ( is_wp_error( $token ) ) return $token;
	try {
		$guard = wpagent_spectra_one_portable_guard( $lock, $identity, $key_id, $global_lock ); if ( is_wp_error( $guard ) ) return $guard;
		if ( wpagent_spectra_one_portable_navigation_target_exists( $target_title ) ) return new WP_Error( 'wpagent_spectra_one_portable_target_exists', __( 'Eine create-only Zielnavigation existiert bereits.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$id = wp_insert_post( array( 'post_type' => 'wp_navigation', 'post_status' => 'publish', 'post_title' => $target_title, 'post_content' => $content ), true ); if ( is_wp_error( $id ) ) return $id;
		$candidate = wpagent_spectra_one_portable_capture_owned_navigation( (int) $id, $target_title, $content );
		if ( is_wp_error( $candidate ) ) {
			return wpagent_spectra_one_portable_navigation_recovery_required( (int) $id, __( 'Die neu angelegte Zielnavigation konnte nicht als unveränderter eigener Kandidat bestätigt werden. Sie bleibt erhalten und muss manuell geprüft werden.', 'wp-agent-companion' ) );
		}
		if ( wpagent_spectra_one_portable_navigation_target_conflicts( $target_title, (int) $id ) ) {
			if ( ! wpagent_spectra_one_portable_cleanup_owned_navigation( $candidate, $lock, $identity, $key_id, $global_lock, $lock_key, $token ) ) {
				return wpagent_spectra_one_portable_navigation_recovery_required( (int) $id, __( 'Eine externe Navigation kollidierte während des Imports und die eigene Zielnavigation änderte sich. Sie bleibt zur manuellen Prüfung erhalten.', 'wp-agent-companion' ) );
			}
			return new WP_Error( 'wpagent_spectra_one_portable_target_exists', __( 'Eine externe Navigation kollidierte während des create-only Imports mit dem Zielnamen.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$state = wpagent_site_editor_navigation_state( (int) $id ); $post = get_post( (int) $id ); $valid = $post instanceof WP_Post ? wpagent_site_editor_validate_content( wpagent_site_editor_effective_post_content( $post ) ) : new WP_Error( 'wpagent_spectra_one_portable_navigation_missing' );
		if ( is_wp_error( $state ) || is_wp_error( $valid ) || $target_title !== ( $state['title'] ?? '' ) || $content !== ( $state['content'] ?? '' ) || ! empty( $state['has_disallowed_blocks'] ) ) {
			if ( ! wpagent_spectra_one_portable_cleanup_owned_navigation( $candidate, $lock, $identity, $key_id, $global_lock, $lock_key, $token ) ) {
				return wpagent_spectra_one_portable_navigation_recovery_required( (int) $id, __( 'Die Zielnavigation wich beim Read-back ab und änderte sich anschließend oder konnte nicht als eigener Kandidat bestätigt werden. Sie bleibt zur manuellen Prüfung erhalten.', 'wp-agent-companion' ) );
			}
			return new WP_Error( 'wpagent_spectra_one_portable_navigation_verify', __( 'Die Zielnavigation wich beim Read-back ab und wurde als unveränderter eigener Kandidat bereinigt.', 'wp-agent-companion' ), array( 'status' => 500, 'rollback_verified' => true ) );
		}
		$guard = wpagent_spectra_one_portable_guard( $lock, $identity, $key_id, $global_lock ); return is_wp_error( $guard ) ? wpagent_spectra_one_portable_rollback_unjournaled_record( $guard, array( 'kind' => 'navigation', 'id' => (int) $state['id'], 'hash' => (string) $state['hash'] ) ) : array( 'navigation' => $state, 'verified' => true );
	} finally { wpagent_site_editor_release_lock( 'wp_navigation', $lock_key, $token ); }
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_portable_fail_after_mutation( WP_Error $failure, array $applied, string $lock, array $identity, string $key_id, $global_lock ) {
	if ( array() === $applied ) return $failure;
	foreach ( array_reverse( $applied ) as $record ) { $guard = wpagent_spectra_one_portable_guard( $lock, $identity, $key_id, $global_lock ); if ( is_wp_error( $guard ) || ! wpagent_spectra_one_snapshot_rollback_record( $record, is_string( $global_lock ) ? $global_lock : '' ) ) return new WP_Error( 'wpagent_spectra_one_portable_recovery_required', __( 'Der portable Import wurde unterbrochen. Der Zielzustand ist unsicher und muss manuell geprüft werden.', 'wp-agent-companion' ), array( 'status' => 500, 'recovery_required' => true, 'original_error' => $failure->get_error_code() ) ); }
	return new WP_Error( 'wpagent_spectra_one_portable_rolled_back', __( 'Der portable Import wurde nicht vollständig verifiziert. Alle zuvor verifizierten Änderungen wurden zurückgerollt.', 'wp-agent-companion' ), array( 'status' => 500, 'rollback_verified' => true, 'original_error' => $failure->get_error_code() ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_import_spectra_one_portable_snapshot( $input ) {
	$capability = wpagent_spectra_one_snapshot_require_capability(); if ( is_wp_error( $capability ) ) return $capability;
	$theme = wpagent_spectra_one_snapshot_require_theme(); if ( is_wp_error( $theme ) ) return $theme;
	$input = is_array( $input ) ? $input : array(); if ( array_diff( array_keys( $input ), array( 'portable_snapshot', 'preflight' ) ) ) return new WP_Error( 'wpagent_spectra_one_portable_invalid', __( 'Der portable Transfer-Import enthält unbekannte Daten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$validated = wpagent_spectra_one_portable_validate_snapshot( $input['portable_snapshot'] ?? null ); if ( is_wp_error( $validated ) ) return $validated;
	$preflight = wpagent_spectra_one_portable_validate_preflight( $input['preflight'] ?? null, $input['portable_snapshot'], $validated ); if ( is_wp_error( $preflight ) ) return $preflight;
	if ( ! $preflight['ready'] ) return new WP_Error( 'wpagent_spectra_one_portable_collisions', __( 'Der portable Transfer-Preflight enthält Zielkollisionen und ist nicht importierbar.', 'wp-agent-companion' ), array( 'status' => 409, 'collision_list' => $preflight['collision_list'] ) );
	$lock = wpagent_companion_acquire_option_lock( WPAGENT_SPECTRA_ONE_PORTABLE_LOCK, WPAGENT_SPECTRA_ONE_PORTABLE_LOCK_TTL ); if ( ! is_string( $lock ) ) return new WP_Error( 'wpagent_spectra_one_portable_busy', __( 'Ein portabler Spectra-One-Transfer wird bereits verarbeitet. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$identity = wpagent_spectra_one_snapshot_identity(); $global_lock = null; $applied = array();
	try {
		if ( 'create_only' === $preflight['mapping']['global_styles']['mode'] ) { $global_lock = wpagent_gs_acquire_lock(); if ( is_wp_error( $global_lock ) ) return $global_lock; }
		$guard = wpagent_spectra_one_portable_guard( $lock, $identity, $validated['credentials']['key_id'], $global_lock ); if ( is_wp_error( $guard ) ) return $guard;
		$current = wpagent_spectra_one_portable_target_state( $validated['plan'], $preflight['mapping']); if ( is_wp_error( $current ) ) return $current;
		if ( $current['target_identity'] !== $preflight['target_identity'] || $current['target_global_styles'] !== $preflight['target_global_styles'] || $current['collision_list'] !== $preflight['collision_list'] || ! $current['ready'] ) return new WP_Error( 'wpagent_spectra_one_portable_preflight_stale', __( 'Der Zielzustand änderte sich seit dem portablen Transfer-Preflight. Führe den Preflight erneut aus.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$global = null;
		if ( 'create_only' === $preflight['mapping']['global_styles']['mode'] ) {
			$before = wpagent_gs_state();
			if ( is_wp_error( $before ) ) return $before;
			if ( ! hash_equals( $preflight['target_global_styles']['expected_target_hash'], $before['state_hash'] ) ) return new WP_Error( 'wpagent_spectra_one_portable_global_conflict', __( 'Die Ziel-Global-Styles wurden seit dem Preflight verändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			$result = wpagent_exec_set_global_styles_locked(
				wpagent_spectra_one_snapshot_global_styles_write_input( $validated['plan']['snapshot']['global_styles'], $before['state_hash'] ),
				$before['state_hash'],
				$global_lock
			);
			if ( is_wp_error( $result ) || ! is_array( $result ) || empty( $result['verified'] ) || ! is_string( $result['state_hash'] ?? null ) ) {
				$failure = is_wp_error( $result ) ? $result : new WP_Error( 'wpagent_spectra_one_portable_global_verify', __( 'Die Ziel-Global-Styles wurden nicht vollständig verifiziert.', 'wp-agent-companion' ), array( 'status' => 500 ) );
				$fresh   = wpagent_gs_state();
				if ( ! is_wp_error( $fresh ) && hash_equals( $before['state_hash'], $fresh['state_hash'] ) ) return $failure;
				return new WP_Error( 'wpagent_spectra_one_portable_recovery_required', __( 'Die Ziel-Global-Styles lieferten keinen überprüfbaren eigenen Zwischenstand. Ein möglicherweise fremder Zustand wird nicht automatisch überschrieben.', 'wp-agent-companion' ), array( 'status' => 500, 'recovery_required' => true, 'original_error' => $failure->get_error_code() ) );
			}
			$candidate = wpagent_gs_state();
			if ( is_wp_error( $candidate ) || ! hash_equals( $result['state_hash'], $candidate['state_hash'] ) ) {
				return new WP_Error( 'wpagent_spectra_one_portable_recovery_required', __( 'Die Ziel-Global-Styles konnten nicht als eigener exakter Zwischenstand gesichert werden. Der aktuelle Zustand wird nicht automatisch überschrieben.', 'wp-agent-companion' ), array( 'status' => 500, 'recovery_required' => true, 'original_error' => 'wpagent_spectra_one_portable_global_verify' ) );
			}
			$applied[] = array( 'kind' => 'global_styles', 'before' => $before, 'candidate' => $candidate, 'after_hash' => $candidate['state_hash'] );
			$global    = $candidate;
		}
		$navigation_map = array(); $created = array( 'site_templates' => array(), 'template_parts' => array(), 'navigations' => array() );
		foreach ( $validated['plan']['navigations'] as $record ) { $map = null; foreach ( $preflight['mapping']['navigations'] as $row ) if ( $row['source_id'] === $record['source_id'] ) { $map = $row; break; } if ( ! is_array( $map ) ) return wpagent_spectra_one_portable_fail_after_mutation( new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Eine Navigation-Zuordnung fehlt während des Imports.', 'wp-agent-companion' ), array( 'status' => 409 ) ), $applied, $lock, $identity, $validated['credentials']['key_id'], $global_lock ); $result = wpagent_spectra_one_portable_create_navigation( $record, $map['target_title'], $lock, $identity, $validated['credentials']['key_id'], $global_lock ); if ( is_wp_error( $result ) ) return wpagent_spectra_one_portable_fail_after_mutation( $result, $applied, $lock, $identity, $validated['credentials']['key_id'], $global_lock ); $state = $result['navigation']; $navigation_map[ (int) $record['source_id'] ] = (int) $state['id']; $applied[] = array( 'kind' => 'navigation', 'id' => (int) $state['id'], 'hash' => (string) $state['hash'] ); $created['navigations'][] = $state; }
		$part_map = array(); foreach ( $preflight['mapping']['template_parts'] as $row ) $part_map[ $row['source_slug'] ] = $row['target_slug'];
		foreach ( $validated['plan']['template_parts'] as $record ) { $target = $part_map[ $record['slug'] ] ?? ''; $result = '' === $target ? new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Eine Template-Teil-Zuordnung fehlt während des Imports.', 'wp-agent-companion' ), array( 'status' => 409 ) ) : wpagent_spectra_one_portable_create_template( 'wp_template_part', $record, $target, $part_map, $navigation_map, $lock, $identity, $validated['credentials']['key_id'], $global_lock ); if ( is_wp_error( $result ) ) return wpagent_spectra_one_portable_fail_after_mutation( $result, $applied, $lock, $identity, $validated['credentials']['key_id'], $global_lock ); $applied[] = array( 'kind' => 'template', 'type' => 'wp_template_part', 'slug' => $result['resource']['slug'], 'hash' => $result['resource']['hash'] ); $created['template_parts'][] = $result['resource']; }
		$template_map = array(); foreach ( $preflight['mapping']['site_templates'] as $row ) $template_map[ $row['source_slug'] ] = $row['target_slug'];
		foreach ( $validated['plan']['site_templates'] as $record ) { $target = $template_map[ $record['slug'] ] ?? ''; $result = '' === $target ? new WP_Error( 'wpagent_spectra_one_portable_mapping', __( 'Eine Template-Zuordnung fehlt während des Imports.', 'wp-agent-companion' ), array( 'status' => 409 ) ) : wpagent_spectra_one_portable_create_template( 'wp_template', $record, $target, $part_map, $navigation_map, $lock, $identity, $validated['credentials']['key_id'], $global_lock ); if ( is_wp_error( $result ) ) return wpagent_spectra_one_portable_fail_after_mutation( $result, $applied, $lock, $identity, $validated['credentials']['key_id'], $global_lock ); $applied[] = array( 'kind' => 'template', 'type' => 'wp_template', 'slug' => $result['resource']['slug'], 'hash' => $result['resource']['hash'] ); $created['site_templates'][] = $result['resource']; }
		foreach ( $created['navigations'] as $index => $state ) { $fresh = wpagent_site_editor_navigation_state( (int) $state['id'] ); if ( is_wp_error( $fresh ) || wpagent_spectra_one_portable_navigation_target_conflicts( (string) $state['title'], (int) $state['id'] ) || ! hash_equals( $state['hash'], $fresh['hash'] ) ) return wpagent_spectra_one_portable_fail_after_mutation( new WP_Error( 'wpagent_spectra_one_portable_verify', __( 'Eine Zielnavigation änderte sich während des abschließenden Read-Backs.', 'wp-agent-companion' ), array( 'status' => 409 ) ), $applied, $lock, $identity, $validated['credentials']['key_id'], $global_lock ); $created['navigations'][ $index ] = $fresh; }
		foreach ( array( 'template_parts' => 'wp_template_part', 'site_templates' => 'wp_template' ) as $kind => $type ) foreach ( $created[ $kind ] as $state ) { $fresh = wpagent_site_editor_get_template( $type, $state['slug'] ); if ( is_wp_error( $fresh ) || ! hash_equals( $state['hash'], wpagent_site_editor_resource_state( $fresh )['hash'] ) ) return wpagent_spectra_one_portable_fail_after_mutation( new WP_Error( 'wpagent_spectra_one_portable_verify', __( 'Eine Zielvorlage änderte sich während des abschließenden Read-Backs.', 'wp-agent-companion' ), array( 'status' => 409 ) ), $applied, $lock, $identity, $validated['credentials']['key_id'], $global_lock ); }
		$guard = wpagent_spectra_one_portable_guard( $lock, $identity, $validated['credentials']['key_id'], $global_lock ); if ( is_wp_error( $guard ) ) return wpagent_spectra_one_portable_fail_after_mutation( $guard, $applied, $lock, $identity, $validated['credentials']['key_id'], $global_lock );
		return array( 'verified' => true, 'state_hash' => hash( 'sha256', wp_json_encode( array( 'target' => $identity, 'created' => $created, 'global_styles_hash' => is_array( $global ) ? $global['state_hash'] : null ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) ), 'created' => $created, 'global_styles' => is_array( $global ) ? array( 'state_hash' => $global['state_hash'], 'theme' => $global['theme'] ) : null );
	} finally { if ( is_string( $global_lock ) ) wpagent_gs_release_lock( $global_lock ); wpagent_companion_release_option_lock( WPAGENT_SPECTRA_ONE_PORTABLE_LOCK, $lock ); }
}
