<?php
/** Bounded, native Spectra One 1.2.3 design snapshot import/export. */

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

const WPAGENT_SPECTRA_ONE_SNAPSHOT_SCHEMA_VERSION = 'spectra-one-design-snapshot-v1';
const WPAGENT_SPECTRA_ONE_SNAPSHOT_THEME_VERSION  = '1.2.3';
const WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES  = 20;
const WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES      = 524288;
const WPAGENT_SPECTRA_ONE_SNAPSHOT_LOCK           = 'wp_agent_spectra_one_snapshot_lock';
const WPAGENT_SPECTRA_ONE_SNAPSHOT_LOCK_TTL       = 300;

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_identity(): array {
	$theme = wp_get_theme();
	$blog  = (int) get_current_blog_id();
	$site  = (string) get_option( 'siteurl', '' );
	$home  = (string) get_option( 'home', '' );
	$slug  = (string) get_stylesheet();
	$version = $theme->exists() ? (string) $theme->get( 'Version' ) : '';
	return array(
		'blog_id'   => $blog,
		'site_hash' => hash( 'sha256', "wpagently-spectra-one-site-v1\0{$blog}\0{$site}\0{$home}" ),
		'theme'     => array(
			'slug'    => $slug,
			'version' => $version,
			'hash'    => hash( 'sha256', "wpagently-spectra-one-theme-v1\0" . (string) get_template() . "\0{$slug}\0{$version}" ),
		),
	);
}

/** @return true|WP_Error */
function wpagent_spectra_one_snapshot_require_theme() {
	$identity = wpagent_spectra_one_snapshot_identity();
	if (
		'spectra-one' !== $identity['theme']['slug']
		|| 'spectra-one' !== (string) get_template()
		|| WPAGENT_SPECTRA_ONE_SNAPSHOT_THEME_VERSION !== $identity['theme']['version']
		|| ! function_exists( 'wp_is_block_theme' )
		|| ! wp_is_block_theme()
	) {
		return new WP_Error( 'wpagent_spectra_one_snapshot_theme', __( 'Dieser Design-Snapshot ist ausschließlich für das aktive Spectra One 1.2.3 Blocktheme verfügbar.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return true;
}

/** @return true|WP_Error */
function wpagent_spectra_one_snapshot_require_capability() {
	return current_user_can( 'edit_theme_options' )
		? true
		: new WP_Error( 'wpagent_spectra_one_snapshot_forbidden', __( 'Für Spectra-One-Design-Snapshots ist edit_theme_options erforderlich.', 'wp-agent-companion' ), array( 'status' => 403 ) );
}

/** @return string|WP_Error */
function wpagent_spectra_one_snapshot_acquire_lock() {
	$token = wpagent_companion_acquire_option_lock( WPAGENT_SPECTRA_ONE_SNAPSHOT_LOCK, WPAGENT_SPECTRA_ONE_SNAPSHOT_LOCK_TTL );
	return is_string( $token ) ? $token : new WP_Error( 'wpagent_spectra_one_snapshot_busy', __( 'Ein Spectra-One-Design-Snapshot wird bereits verarbeitet. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_spectra_one_snapshot_release_lock( string $token ): void {
	wpagent_companion_release_option_lock( WPAGENT_SPECTRA_ONE_SNAPSHOT_LOCK, $token );
}

function wpagent_spectra_one_snapshot_signature( string $state_hash ): string {
	return hash_hmac( 'sha256', WPAGENT_SPECTRA_ONE_SNAPSHOT_SCHEMA_VERSION . "\0{$state_hash}", wp_salt( 'auth' ) );
}

/** @return true|WP_Error */
function wpagent_spectra_one_snapshot_guard( string $token, array $identity ) {
	if ( ! current_user_can( 'edit_theme_options' ) ) {
		return new WP_Error( 'wpagent_spectra_one_snapshot_capability_changed', __( 'Die erforderliche Berechtigung ging während des Imports verloren.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( ! wpagent_companion_refresh_option_lock( WPAGENT_SPECTRA_ONE_SNAPSHOT_LOCK, $token, WPAGENT_SPECTRA_ONE_SNAPSHOT_LOCK_TTL ) ) {
		return new WP_Error( 'wpagent_spectra_one_snapshot_lock_lost', __( 'Die Snapshot-Sperre ging während des Imports verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$current = wpagent_spectra_one_snapshot_identity();
	return $current === $identity
		? true
		: new WP_Error( 'wpagent_spectra_one_snapshot_identity_changed', __( 'Blog oder Theme haben sich während des Imports geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_object_schema( array $properties, array $required = [] ): array {
	return array( 'type' => 'object', 'properties' => $properties, 'required' => $required, 'additionalProperties' => false, 'maxProperties' => count( $properties ) );
}

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

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_identity_schema(): array {
	$theme = wpagent_spectra_one_snapshot_object_schema( array( 'slug' => array( 'type' => 'string', 'enum' => array( 'spectra-one' ), 'maxLength' => 200 ), 'version' => array( 'type' => 'string', 'enum' => array( WPAGENT_SPECTRA_ONE_SNAPSHOT_THEME_VERSION ), 'maxLength' => 20 ), 'hash' => wpagent_spectra_one_snapshot_hash_schema() ), array( 'slug', 'version', 'hash' ) );
	return wpagent_spectra_one_snapshot_object_schema( array( 'blog_id' => array( 'type' => 'integer', 'minimum' => 1 ), 'site_hash' => wpagent_spectra_one_snapshot_hash_schema(), 'theme' => $theme ), array( 'blog_id', 'site_hash', 'theme' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_styles_schema(): array {
	$color = wpagent_spectra_one_snapshot_object_schema( array( 'background' => array( 'type' => 'string', 'maxLength' => 64 ), 'text' => array( 'type' => 'string', 'maxLength' => 64 ) ) );
	$spacing = wpagent_spectra_one_snapshot_object_schema( array( 'blockGap' => array( 'type' => 'string', 'maxLength' => 64 ) ) );
	$button = wpagent_spectra_one_snapshot_object_schema( array( 'color' => $color ), array( 'color' ) );
	$link = wpagent_spectra_one_snapshot_object_schema( array( 'color' => wpagent_spectra_one_snapshot_object_schema( array( 'text' => array( 'type' => 'string', 'maxLength' => 64 ) ), array( 'text' ) ) ), array( 'color' ) );
	return wpagent_spectra_one_snapshot_object_schema( array( 'color' => $color, 'spacing' => $spacing, 'elements' => wpagent_spectra_one_snapshot_object_schema( array( 'button' => $button, 'link' => $link ) ) ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_preset_schema( string $value_key, int $max_length ): array {
	return wpagent_spectra_one_snapshot_object_schema( array( 'slug' => array( 'type' => 'string', 'maxLength' => 40 ), 'name' => array( 'type' => 'string', 'maxLength' => 100 ), $value_key => array( 'type' => 'string', 'maxLength' => $max_length ) ), array( 'slug', 'name', $value_key ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_global_styles_schema(): array {
	return wpagent_spectra_one_snapshot_object_schema( array(
		'colors' => array( 'type' => 'array', 'maxItems' => 60, 'items' => wpagent_spectra_one_snapshot_preset_schema( 'color', 64 ) ),
		'fontSizes' => array( 'type' => 'array', 'maxItems' => 60, 'items' => wpagent_spectra_one_snapshot_preset_schema( 'size', 64 ) ),
		'spacingSizes' => array( 'type' => 'array', 'maxItems' => 60, 'items' => wpagent_spectra_one_snapshot_preset_schema( 'size', 64 ) ),
		'fontFamilies' => array( 'type' => 'array', 'maxItems' => 60, 'items' => wpagent_spectra_one_snapshot_preset_schema( 'fontFamily', 200 ) ), 'styles' => wpagent_spectra_one_snapshot_styles_schema(),
	), array( 'colors', 'fontSizes', 'spacingSizes', 'fontFamilies', 'styles' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_component_schema(): array {
	return wpagent_spectra_one_snapshot_object_schema( array( 'type' => array( 'type' => 'string', 'enum' => array( 'site-title', 'post-title', 'site-logo', 'template-part', 'navigation', 'post-featured-image', 'post-excerpt', 'post-date', 'post-author-name', 'post-content', 'post-terms', 'query-title', 'separator', 'spacer' ), 'maxLength' => 32 ), 'slug' => array( 'type' => 'string', 'maxLength' => 200 ), 'navigation_id' => array( 'type' => 'integer', 'minimum' => 1 ), 'level' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 6 ), 'is_link' => array( 'type' => 'boolean' ), 'width' => array( 'type' => 'integer', 'minimum' => 16, 'maximum' => 600 ), 'taxonomy' => array( 'type' => 'string', 'maxLength' => 32 ), 'query_title_type' => array( 'type' => 'string', 'enum' => array( 'archive', 'search' ), 'maxLength' => 10 ), 'height' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 400 ) ), array( 'type' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_navigation_item_schema( int $depth = 0 ): array {
	$properties = array( 'type' => array( 'type' => 'string', 'enum' => array( 'custom', 'home', 'page', 'post', 'category', 'tag', 'submenu' ), 'maxLength' => 20 ), 'label' => array( 'type' => 'string', 'maxLength' => 200 ), 'url' => array( 'type' => 'string', 'maxLength' => 2048 ), 'id' => array( 'type' => 'integer', 'minimum' => 1 ), 'opens_in_new_tab' => array( 'type' => 'boolean' ), 'description' => array( 'type' => 'string', 'maxLength' => 500 ) );
	if ( $depth < 2 ) $properties['items'] = array( 'type' => 'array', 'minItems' => 1, 'maxItems' => 50, 'items' => wpagent_spectra_one_snapshot_navigation_item_schema( $depth + 1 ) );
	return wpagent_spectra_one_snapshot_object_schema( $properties, array( 'type' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_resource_schema( bool $part ): array {
	$properties = array(
		'slug' => array( 'type' => 'string', 'maxLength' => 200 ), 'title' => array( 'type' => 'string', 'maxLength' => 200 ), 'description' => array( 'type' => 'string', 'maxLength' => 1000 ),
		'container' => array( 'type' => 'string', 'enum' => array( 'none', 'group', 'main', 'header', 'footer' ), 'maxLength' => 10 ),
		'components' => array( 'type' => 'array', 'minItems' => 1, 'maxItems' => WPAGENT_SITE_EDITOR_MAX_COMPONENTS, 'items' => wpagent_spectra_one_snapshot_component_schema() ), 'source_hash' => wpagent_spectra_one_snapshot_hash_schema(),
	);
	if ( $part ) $properties['area'] = array( 'type' => 'string', 'maxLength' => 20 );
	return wpagent_spectra_one_snapshot_object_schema( $properties, array_keys( $properties ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_navigation_schema(): array {
	return wpagent_spectra_one_snapshot_object_schema( array(
		'source_id' => array( 'type' => 'integer', 'minimum' => 1 ), 'title' => array( 'type' => 'string', 'maxLength' => 200 ),
		'items' => array( 'type' => 'array', 'minItems' => 1, 'maxItems' => WPAGENT_SITE_EDITOR_MAX_NAV_ITEMS, 'items' => wpagent_spectra_one_snapshot_navigation_item_schema() ), 'source_hash' => wpagent_spectra_one_snapshot_hash_schema(),
	), array( 'source_id', 'title', 'items', 'source_hash' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_body_schema(): array {
	return wpagent_spectra_one_snapshot_object_schema( array( 'global_styles' => array( 'type' => array( 'object', 'null' ), 'properties' => wpagent_spectra_one_snapshot_global_styles_schema()['properties'], 'required' => wpagent_spectra_one_snapshot_global_styles_schema()['required'], 'additionalProperties' => false, 'maxProperties' => 5 ), 'site_templates' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => wpagent_spectra_one_snapshot_resource_schema( false ) ), 'template_parts' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => wpagent_spectra_one_snapshot_resource_schema( true ) ), 'navigations' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => wpagent_spectra_one_snapshot_navigation_schema() ) ), array( 'global_styles', 'site_templates', 'template_parts', 'navigations' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_source_schema(): array {
	return wpagent_spectra_one_snapshot_object_schema( array( 'schema' => array( 'type' => 'string', 'enum' => array( WPAGENT_SPECTRA_ONE_SNAPSHOT_SCHEMA_VERSION ), 'maxLength' => 64 ), 'identity' => wpagent_spectra_one_snapshot_identity_schema(), 'global_styles_state_hash' => wpagent_spectra_one_snapshot_hash_schema(), 'snapshot' => wpagent_spectra_one_snapshot_body_schema(), 'state_hash' => wpagent_spectra_one_snapshot_hash_schema(), 'bytes' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES ) ), array( 'schema', 'identity', 'global_styles_state_hash', 'snapshot', 'state_hash', 'bytes' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_created_resource_schema(): array {
	$string_list = static fn( int $max_items ): array => array( 'type' => 'array', 'maxItems' => $max_items, 'items' => array( 'type' => 'string', 'maxLength' => 100 ) );
	return wpagent_spectra_one_snapshot_object_schema( array(
		'id' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 220 ),
		'wp_id' => array( 'type' => 'integer', 'minimum' => 1 ),
		'theme' => array( 'type' => 'string', 'maxLength' => 200 ),
		'slug' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 200 ),
		'type' => array( 'type' => 'string', 'enum' => array( 'wp_template', 'wp_template_part' ), 'maxLength' => 20 ),
		'source' => array( 'type' => 'string', 'maxLength' => 32 ),
		'origin' => array( 'type' => array( 'string', 'null' ), 'maxLength' => 200 ),
		'title' => array( 'type' => 'string', 'maxLength' => 200 ),
		'description' => array( 'type' => 'string', 'maxLength' => 1000 ),
		'status' => array( 'type' => 'string', 'maxLength' => 20 ),
		'content' => array( 'type' => 'string', 'maxLength' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES ),
		'area' => array( 'type' => array( 'string', 'null' ), 'maxLength' => 20 ),
		'has_theme_file' => array( 'type' => 'boolean' ),
		'is_custom' => array( 'type' => 'boolean' ),
		'modified' => array( 'type' => array( 'string', 'null' ), 'maxLength' => 30 ),
		'block_count' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_SITE_EDITOR_MAX_COMPONENTS + 1 ),
		'freeform_count' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_SITE_EDITOR_MAX_COMPONENTS + 1 ),
		'block_types' => $string_list( WPAGENT_SITE_EDITOR_MAX_COMPONENTS ),
		'disallowed_blocks' => $string_list( WPAGENT_SITE_EDITOR_MAX_COMPONENTS ),
		'has_disallowed_blocks' => array( 'type' => 'boolean' ),
		'hash' => wpagent_spectra_one_snapshot_hash_schema(),
	), array( 'id', 'wp_id', 'theme', 'slug', 'type', 'source', 'origin', 'title', 'description', 'status', 'content', 'area', 'has_theme_file', 'is_custom', 'modified', 'block_count', 'freeform_count', 'block_types', 'disallowed_blocks', 'has_disallowed_blocks', 'hash' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_created_navigation_schema(): array {
	$string_list = static fn( int $max_items ): array => array( 'type' => 'array', 'maxItems' => $max_items, 'items' => array( 'type' => 'string', 'maxLength' => 100 ) );
	return wpagent_spectra_one_snapshot_object_schema( array(
		'id' => array( 'type' => 'integer', 'minimum' => 1 ),
		'slug' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 200 ),
		'title' => array( 'type' => 'string', 'maxLength' => 200 ),
		'status' => array( 'type' => 'string', 'maxLength' => 20 ),
		'content' => array( 'type' => 'string', 'maxLength' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES ),
		'modified' => array( 'type' => 'string', 'maxLength' => 30 ),
		'item_count' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_SITE_EDITOR_MAX_NAV_ITEMS ),
		'block_count' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_SITE_EDITOR_MAX_NAV_ITEMS ),
		'freeform_count' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_SITE_EDITOR_MAX_NAV_ITEMS ),
		'block_types' => $string_list( WPAGENT_SITE_EDITOR_MAX_NAV_ITEMS ),
		'disallowed_blocks' => $string_list( WPAGENT_SITE_EDITOR_MAX_NAV_ITEMS ),
		'has_disallowed_blocks' => array( 'type' => 'boolean' ),
		'hash' => wpagent_spectra_one_snapshot_hash_schema(),
	), array( 'id', 'slug', 'title', 'status', 'content', 'modified', 'item_count', 'block_count', 'freeform_count', 'block_types', 'disallowed_blocks', 'has_disallowed_blocks', 'hash' ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_created_schema(): array {
	$resource = wpagent_spectra_one_snapshot_created_resource_schema();
	$navigation = wpagent_spectra_one_snapshot_created_navigation_schema();
	return wpagent_spectra_one_snapshot_object_schema( array( 'site_templates' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => $resource ), 'template_parts' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => $resource ), 'navigations' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => $navigation ) ), array( 'site_templates', 'template_parts', 'navigations' ) );
}

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

function wpagent_ability_export_spectra_one_design_snapshot(): array {
	return array(
		'label' => __( 'Spectra-One-Design-Snapshot exportieren', 'wp-agent-companion' ),
		'description' => __( 'Exportiert einen begrenzten, typisierten Spectra-One-1.2.3-Snapshot aus erlaubten Global-Styles-Tokens, kanonischen eigenen Site-Editor-Ressourcen und Blocknavigationen. Rohmarkup, Theme-Dateien und nicht verlustfrei darstellbare Entitäten werden abgelehnt.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array(
			'global_styles' => array( 'type' => 'boolean', 'default' => true ),
			'site_templates' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => array( 'type' => 'string', 'maxLength' => 200 ) ),
			'template_parts' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => array( 'type' => 'string', 'maxLength' => 200 ) ),
			'navigation_ids' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => array( 'type' => 'integer', 'minimum' => 1 ) ),
		), 'required' => array(), 'additionalProperties' => false, 'maxProperties' => 4, 'default' => array() ),
		'output_schema' => wpagent_spectra_one_snapshot_export_schema(),
		'execute_callback' => 'wpagent_exec_export_spectra_one_design_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_spectra_one_snapshot_export_schema(): array {
	return wpagent_spectra_one_snapshot_object_schema( array( 'schema' => array( 'type' => 'string', 'enum' => array( WPAGENT_SPECTRA_ONE_SNAPSHOT_SCHEMA_VERSION ), 'maxLength' => 64 ), 'identity' => wpagent_spectra_one_snapshot_identity_schema(), 'global_styles_state_hash' => wpagent_spectra_one_snapshot_hash_schema(), 'snapshot' => wpagent_spectra_one_snapshot_body_schema(), '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', 'identity', 'global_styles_state_hash', 'snapshot', 'state_hash', 'signature', 'bytes' ) );
}

function wpagent_ability_import_spectra_one_design_snapshot(): array {
	return array(
		'label' => __( 'Spectra-One-Design-Snapshot importieren', 'wp-agent-companion' ),
		'description' => __( 'Importiert bewusst ausgewählte Snapshot-Komponenten ausschließlich als neue native Ressourcen. Vor jedem Schritt prüft der Import Blog-, Theme-, Hash-, Sperr- und Berechtigungszustand; bei Unsicherheit stoppt er und meldet erforderliche Wiederherstellung.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array(
			'snapshot' => wpagent_spectra_one_snapshot_export_schema(),
			'selection' => array( 'type' => 'object', 'properties' => array(
				'global_styles' => array( 'type' => 'boolean' ), 'expected_global_styles_hash' => wpagent_spectra_one_snapshot_hash_schema(),
				'site_templates' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => array( 'type' => 'integer', 'minimum' => 0 ) ),
				'template_parts' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => array( 'type' => 'integer', 'minimum' => 0 ) ),
				'navigations' => array( 'type' => 'array', 'maxItems' => WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES, 'items' => array( 'type' => 'integer', 'minimum' => 0 ) ),
			), 'required' => array( 'global_styles', 'site_templates', 'template_parts', 'navigations' ), 'additionalProperties' => false, 'maxProperties' => 5 ),
		), 'required' => array( 'snapshot', 'selection' ), '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_design_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>|WP_Error */
function wpagent_exec_export_spectra_one_design_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( 'global_styles', 'site_templates', 'template_parts', 'navigation_ids' ) ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_selection', __( 'Die Snapshot-Auswahl enthält unbekannte Daten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$state = wpagent_gs_state();
	if ( is_wp_error( $state ) ) return $state;
	$selection = wpagent_spectra_one_snapshot_export_selection( $input );
	if ( is_wp_error( $selection ) ) return $selection;
	$snapshot = array( 'global_styles' => null, 'site_templates' => array(), 'template_parts' => array(), 'navigations' => array() );
	if ( $selection['global_styles'] ) {
		$snapshot['global_styles'] = wpagent_spectra_one_snapshot_global_styles( $state['snapshot'] );
		if ( is_wp_error( $snapshot['global_styles'] ) ) return $snapshot['global_styles'];
	}
	foreach ( $selection['site_templates'] as $slug ) {
		$record = wpagent_spectra_one_snapshot_template_record( 'wp_template', $slug, $selection['template_parts'], $selection['navigation_ids'] );
		if ( is_wp_error( $record ) ) return $record;
		$snapshot['site_templates'][] = $record;
	}
	foreach ( $selection['template_parts'] as $slug ) {
		$record = wpagent_spectra_one_snapshot_template_record( 'wp_template_part', $slug, $selection['template_parts'], $selection['navigation_ids'] );
		if ( is_wp_error( $record ) ) return $record;
		$snapshot['template_parts'][] = $record;
	}
	foreach ( $selection['navigation_ids'] as $id ) {
		$record = wpagent_spectra_one_snapshot_navigation_record( $id );
		if ( is_wp_error( $record ) ) return $record;
		$snapshot['navigations'][] = $record;
	}
	$snapshot = wpagent_spectra_one_snapshot_canonical_body( $snapshot );
	$identity = wpagent_spectra_one_snapshot_identity();
	$payload = array( 'schema' => WPAGENT_SPECTRA_ONE_SNAPSHOT_SCHEMA_VERSION, 'identity' => $identity, 'global_styles_state_hash' => $state['state_hash'], 'snapshot' => $snapshot );
	$json = wp_json_encode( $payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( false === $json || strlen( $json ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES ) return new WP_Error( 'wpagent_spectra_one_snapshot_size', __( 'Der ausgewählte Spectra-One-Snapshot überschreitet die feste Grenze von 512 KiB.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	$payload['state_hash'] = hash( 'sha256', "wpagently-spectra-one-snapshot-v1\0{$json}" );
	$payload['signature']  = wpagent_spectra_one_snapshot_signature( $payload['state_hash'] );
	$payload['bytes'] = strlen( $json );
	return $payload;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_snapshot_export_selection( array $input ) {
	if ( array_key_exists( 'global_styles', $input ) && ! is_bool( $input['global_styles'] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_selection', __( 'global_styles muss ein boolescher Wert sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$result = array( 'global_styles' => ! array_key_exists( 'global_styles', $input ) || true === $input['global_styles'], 'site_templates' => array(), 'template_parts' => array(), 'navigation_ids' => array() );
	foreach ( array( 'site_templates', 'template_parts' ) as $key ) {
		$list = $input[ $key ] ?? array();
		if ( ! is_array( $list ) || ! array_is_list( $list ) || count( $list ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES ) return new WP_Error( 'wpagent_spectra_one_snapshot_selection', __( 'Die Snapshot-Auswahl ist ungültig oder zu groß.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		foreach ( $list as $value ) {
			$slug = wpagent_site_editor_slug( $value );
			if ( is_wp_error( $slug ) || isset( $result[ $key ][ $slug ] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_selection', __( 'Jeder Template-Slug darf höchstens einmal ausgewählt werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			$result[ $key ][ $slug ] = true;
		}
		$result[ $key ] = array_keys( $result[ $key ] );
	}
	$list = $input['navigation_ids'] ?? array();
	if ( ! is_array( $list ) || ! array_is_list( $list ) || count( $list ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES ) return new WP_Error( 'wpagent_spectra_one_snapshot_selection', __( 'Die Navigation-Auswahl ist ungültig oder zu groß.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	foreach ( $list as $value ) {
		$id = is_int( $value ) ? $value : 0;
		if ( $id < 1 || isset( $result['navigation_ids'][ $id ] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_selection', __( 'Jede Navigation darf höchstens einmal ausgewählt werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$result['navigation_ids'][ $id ] = true;
	}
	$result['navigation_ids'] = array_map( 'intval', array_keys( $result['navigation_ids'] ) );
	if ( ! $result['global_styles'] && array() === $result['site_templates'] && array() === $result['template_parts'] && array() === $result['navigation_ids'] ) return new WP_Error( 'wpagent_spectra_one_snapshot_empty', __( 'Mindestens eine Spectra-One-Snapshot-Komponente muss ausgewählt werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	return $result;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_snapshot_global_styles( array $source ) {
	$settings = isset( $source['settings'] ) && is_array( $source['settings'] ) ? $source['settings'] : array();
	$styles = isset( $source['styles'] ) && is_array( $source['styles'] ) ? $source['styles'] : array();
	$map = array(
		'colors' => array( $settings['color']['palette'] ?? array(), 'color', 'colors' ),
		'fontSizes' => array( $settings['typography']['fontSizes'] ?? array(), 'size', 'fontSizes' ),
		'spacingSizes' => array( $settings['spacing']['spacingSizes'] ?? array(), 'size', 'spacingSizes' ),
		'fontFamilies' => array( $settings['typography']['fontFamilies'] ?? array(), 'fontFamily', 'fontFamilies' ),
	);
	$out = array();
	foreach ( $map as $key => $item ) {
		$entries = wpagent_gs_preset_entries_by_slug( $item[0] );
		$list = array();
		foreach ( $entries as $entry ) {
			if ( ! is_array( $entry ) || array_diff( array_keys( $entry ), array( 'slug', 'name', $item[1] ) ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_global_styles', __( 'Ein Global-Styles-Preset enthält nicht unterstützte Daten und wird nicht exportiert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			$list[] = $entry;
		}
		$validated = wpagent_gs_validate_preset_list( $list, $item[1], 'wpagent_gs_validate_' . ( 'color' === $item[1] ? 'color' : ( 'size' === $item[1] ? 'size' : 'font_family' ) ), $item[2] );
		if ( is_wp_error( $validated ) ) return $validated;
		$out[ $key ] = $validated;
	}
	$allowed_styles = array();
	foreach ( array( 'color', 'spacing', 'elements' ) as $key ) if ( array_key_exists( $key, $styles ) ) $allowed_styles[ $key ] = $styles[ $key ];
	// A valid user theme JSON can intentionally have no direct styles. Preserve
	// that exact empty object in the signed snapshot instead of treating it as
	// a malformed update payload.
	$validated_styles = array() === $allowed_styles ? array() : wpagent_gs_validate_styles( $allowed_styles );
	return is_wp_error( $validated_styles ) ? $validated_styles : array_merge( $out, array( 'styles' => $validated_styles ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_canonical_body( array $body ): array {
	// PHP serializes an empty array as JSON [], but styles is always an object in
	// this API. Canonicalize only the valid empty styles object before signing or
	// verifying so export, replay and portable transfer use identical bytes.
	if ( is_array( $body['global_styles'] ?? null ) && array_key_exists( 'styles', $body['global_styles'] ) && array() === $body['global_styles']['styles'] ) {
		$body['global_styles']['styles'] = (object) array();
	}
	return $body;
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_canonical_source( array $source ): array {
	if ( is_array( $source['snapshot'] ?? null ) ) {
		$source['snapshot'] = wpagent_spectra_one_snapshot_canonical_body( $source['snapshot'] );
	}
	return $source;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_snapshot_template_record( string $type, string $slug, array $parts, array $navigation_ids ) {
	$template = wpagent_site_editor_get_template( $type, $slug );
	if ( is_wp_error( $template ) ) return $template;
	if ( 'custom' !== $template->source || ! $template->wp_id ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Nur eigene Datenbank-Templates und Template-Teile können create-only exportiert werden.', 'wp-agent-companion' ), array( 'status' => 409, 'slug' => $slug ) );
	$state = wpagent_site_editor_resource_state( $template );
	$parsed = wpagent_spectra_one_snapshot_components_from_content( wpagent_site_editor_template_raw_content( $template ), $parts, $navigation_ids );
	if ( is_wp_error( $parsed ) ) return $parsed;
	$record = array( 'slug' => $slug, 'title' => $state['title'], 'description' => $state['description'], 'container' => $parsed['container'], 'components' => $parsed['components'], 'source_hash' => $state['hash'] );
	if ( 'wp_template_part' === $type ) $record['area'] = $state['area'];
	return $record;
}

/** @return array{container:string,components:array<int,array<string,mixed>>}|WP_Error */
function wpagent_spectra_one_snapshot_components_from_content( string $raw, array $parts, array $navigation_ids ) {
	if ( '' === $raw || serialize_blocks( parse_blocks( $raw ) ) !== $raw ) return new WP_Error( 'wpagent_spectra_one_snapshot_markup', __( 'Der Snapshot akzeptiert nur kanonisches, strukturiertes WordPress-Blockmaterial.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$blocks = parse_blocks( $raw );
	$container = 'none';
	if ( 1 === count( $blocks ) && 'core/group' === ( $blocks[0]['blockName'] ?? '' ) ) {
		$attrs = is_array( $blocks[0]['attrs'] ?? null ) ? $blocks[0]['attrs'] : array();
		$tag = isset( $attrs['tagName'] ) ? (string) $attrs['tagName'] : 'div';
		if ( array_diff( array_keys( $attrs ), array( 'layout', 'tagName' ) ) || array( 'type' => 'constrained' ) !== ( $attrs['layout'] ?? null ) || ! in_array( $tag, array( 'div', 'main', 'header', 'footer' ), true ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Der Template-Container verwendet nicht unterstützte Eigenschaften.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$container = 'div' === $tag ? 'group' : $tag;
		$blocks = $blocks[0]['innerBlocks'] ?? array();
	}
	$components = array();
	foreach ( $blocks as $block ) {
		$component = wpagent_spectra_one_snapshot_component_from_block( $block, $parts, $navigation_ids );
		if ( is_wp_error( $component ) ) return $component;
		$components[] = $component;
	}
	if ( array() === $components || count( $components ) > WPAGENT_SITE_EDITOR_MAX_COMPONENTS ) return new WP_Error( 'wpagent_spectra_one_snapshot_components', __( 'Die Template-Ressource enthält keine exportierbaren Komponenten.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$rebuilt = wpagent_site_editor_build_content( $components, $container );
	return is_wp_error( $rebuilt ) || ! hash_equals( $raw, $rebuilt )
		? new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Die Template-Ressource kann nicht bytegenau aus erlaubten Komponenten wiederhergestellt werden.', 'wp-agent-companion' ), array( 'status' => 409 ) )
		: array( 'container' => $container, 'components' => $components );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_snapshot_component_from_block( array $block, array $parts, array $navigation_ids ) {
	$name = (string) ( $block['blockName'] ?? '' ); $attrs = is_array( $block['attrs'] ?? null ) ? $block['attrs'] : array();
	if ( ! empty( $block['innerBlocks'] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Verschachtelte freie Blockstrukturen werden nicht exportiert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$allowed = array(); $out = array();
	if ( 'core/site-title' === $name || 'core/post-title' === $name ) {
		$allowed = array( 'level', 'isLink' ); $out = array( 'type' => 'core/site-title' === $name ? 'site-title' : 'post-title', 'level' => absint( $attrs['level'] ?? ( 'core/site-title' === $name ? 2 : 1 ) ) );
		if ( isset( $attrs['isLink'] ) ) $out['is_link'] = (bool) $attrs['isLink'];
	} elseif ( 'core/site-logo' === $name ) { $allowed = array( 'width' ); $out = array( 'type' => 'site-logo' ); if ( isset( $attrs['width'] ) ) $out['width'] = absint( $attrs['width'] );
	} elseif ( 'core/template-part' === $name ) { $allowed = array( 'slug', 'theme' ); $slug = wpagent_site_editor_slug( $attrs['slug'] ?? '' ); if ( is_wp_error( $slug ) || ! in_array( $slug, $parts, true ) || (string) ( $attrs['theme'] ?? '' ) !== get_stylesheet() ) return new WP_Error( 'wpagent_spectra_one_snapshot_reference', __( 'Jede Template-Teil-Referenz muss im selben Snapshot ausgewählt sein.', 'wp-agent-companion' ), array( 'status' => 409 ) ); $out = array( 'type' => 'template-part', 'slug' => $slug );
	} elseif ( 'core/navigation' === $name ) { $allowed = array( 'ref' ); $id = absint( $attrs['ref'] ?? 0 ); if ( $id < 1 || ! in_array( $id, $navigation_ids, true ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_reference', __( 'Jede Navigation-Referenz muss im selben Snapshot ausgewählt sein.', 'wp-agent-companion' ), array( 'status' => 409 ) ); $out = array( 'type' => 'navigation', 'navigation_id' => $id );
	} elseif ( 'core/post-featured-image' === $name || 'core/post-excerpt' === $name || 'core/post-date' === $name || 'core/post-author-name' === $name ) { $allowed = array( 'isLink' ); $out = array( 'type' => str_replace( 'core/', '', $name ) ); if ( isset( $attrs['isLink'] ) ) $out['is_link'] = (bool) $attrs['isLink'];
	} elseif ( 'core/post-content' === $name ) { $allowed = array( 'layout' ); if ( array( 'type' => 'constrained' ) !== ( $attrs['layout'] ?? null ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'post-content enthält nicht unterstützte Attribute.', 'wp-agent-companion' ), array( 'status' => 409 ) ); $out = array( 'type' => 'post-content' );
	} elseif ( 'core/post-terms' === $name ) { $allowed = array( 'term' ); $out = array( 'type' => 'post-terms', 'taxonomy' => (string) ( $attrs['term'] ?? '' ) );
	} elseif ( 'core/query-title' === $name ) { $allowed = array( 'type', 'showPrefix' ); if ( false !== ( $attrs['showPrefix'] ?? false ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'query-title enthält nicht unterstützte Attribute.', 'wp-agent-companion' ), array( 'status' => 409 ) ); $out = array( 'type' => 'query-title', 'query_title_type' => (string) ( $attrs['type'] ?? 'archive' ) );
	} elseif ( 'core/separator' === $name ) { $out = array( 'type' => 'separator' );
	} elseif ( 'core/spacer' === $name ) { $allowed = array( 'height' ); if ( ! preg_match( '/^(\d{1,3})px$/', (string) ( $attrs['height'] ?? '' ), $match ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'spacer enthält keine unterstützte Pixelhöhe.', 'wp-agent-companion' ), array( 'status' => 409 ) ); $out = array( 'type' => 'spacer', 'height' => (int) $match[1] );
	} else return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Der Snapshot unterstützt diesen Blocktyp nicht.', 'wp-agent-companion' ), array( 'status' => 409, 'block' => $name ) );
	return array_diff( array_keys( $attrs ), $allowed ) ? new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Ein Block enthält nicht unterstützte Attribute.', 'wp-agent-companion' ), array( 'status' => 409, 'block' => $name ) ) : $out;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_snapshot_navigation_record( int $id ) {
	$state = wpagent_site_editor_navigation_state( $id );
	if ( is_wp_error( $state ) || 'publish' !== $state['status'] ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Nur veröffentlichte Blocknavigationen können exportiert werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$items = wpagent_spectra_one_snapshot_navigation_items_from_blocks( parse_blocks( (string) get_post( $id )->post_content ) );
	if ( is_wp_error( $items ) ) return $items;
	$rebuilt = wpagent_site_editor_build_navigation_content( $items );
	if ( is_wp_error( $rebuilt ) || ! hash_equals( (string) get_post( $id )->post_content, $rebuilt ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Die Navigation kann nicht bytegenau aus erlaubten Einträgen wiederhergestellt werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	return array( 'source_id' => $id, 'title' => $state['title'], 'items' => $items, 'source_hash' => $state['hash'] );
}

/** @return array<int,array<string,mixed>>|WP_Error */
function wpagent_spectra_one_snapshot_navigation_items_from_blocks( array $blocks, int $depth = 0 ) {
	if ( $depth > 2 || array() === $blocks ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Die Navigation ist leer oder zu tief verschachtelt.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$items = array();
	foreach ( $blocks as $block ) {
		$name = (string) ( $block['blockName'] ?? '' ); $attrs = is_array( $block['attrs'] ?? null ) ? $block['attrs'] : array(); $submenu = 'core/navigation-submenu' === $name;
		if ( ! in_array( $name, array( 'core/navigation-link', 'core/navigation-submenu' ), true ) || array_diff( array_keys( $attrs ), array( 'label', 'url', 'kind', 'type', 'id', 'opensInNewTab', 'description' ) ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Die Navigation enthält einen nicht unterstützten Eintrag.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$type = 'custom'; $kind = (string) ( $attrs['kind'] ?? '' ); $entity = (string) ( $attrs['type'] ?? '' );
		if ( 'post-type' === $kind && in_array( $entity, array( 'page', 'post' ), true ) ) $type = $entity;
		elseif ( 'taxonomy' === $kind && in_array( $entity, array( 'category', 'post_tag' ), true ) ) $type = 'post_tag' === $entity ? 'tag' : 'category';
		elseif ( 'custom' !== $kind || 'custom' !== $entity ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Die Navigation enthält einen unbekannten Entitätstyp.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$item = array( 'type' => $submenu ? 'submenu' : $type, 'label' => (string) ( $attrs['label'] ?? '' ), 'url' => (string) ( $attrs['url'] ?? '' ) );
		if ( ! $submenu && 'custom' !== $type ) { $item['id'] = absint( $attrs['id'] ?? 0 ); unset( $item['url'] ); }
		if ( ! empty( $attrs['opensInNewTab'] ) ) $item['opens_in_new_tab'] = true;
		if ( isset( $attrs['description'] ) ) $item['description'] = (string) $attrs['description'];
		if ( $submenu ) { $children = wpagent_spectra_one_snapshot_navigation_items_from_blocks( $block['innerBlocks'] ?? array(), $depth + 1 ); if ( is_wp_error( $children ) ) return $children; $item['items'] = $children; }
		elseif ( ! empty( $block['innerBlocks'] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Ein Navigationseintrag enthält unerwartete Kindblöcke.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$items[] = $item;
	}
	return $items;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_import_spectra_one_design_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( 'snapshot', 'selection' ) ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Der Snapshot-Import enthält unbekannte Daten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$plan = wpagent_spectra_one_snapshot_import_plan( $input );
	if ( is_wp_error( $plan ) ) return $plan;
	$lock = wpagent_spectra_one_snapshot_acquire_lock();
	if ( is_wp_error( $lock ) ) return $lock;
	$identity = wpagent_spectra_one_snapshot_identity();
	$global_lock = null;
	$applied = array();
	try {
		$guard = wpagent_spectra_one_snapshot_guard( $lock, $identity );
		if ( is_wp_error( $guard ) ) return $guard;
		$preflight = wpagent_spectra_one_snapshot_preflight_targets( $plan );
		if ( is_wp_error( $preflight ) ) return $preflight;
		if ( $plan['selection']['global_styles'] ) {
			$global_lock = wpagent_gs_acquire_lock();
			if ( is_wp_error( $global_lock ) ) return $global_lock;
			$guard = wpagent_spectra_one_snapshot_guard( $lock, $identity );
			if ( is_wp_error( $guard ) ) return $guard;
			$before = wpagent_gs_state();
			if ( is_wp_error( $before ) ) return $before;
			if ( ! hash_equals( $before['state_hash'], $plan['selection']['expected_global_styles_hash'] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_global_styles_conflict', __( 'Die Global Styles wurden seit der Zielauswahl geändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['state_hash'] ) );
			$result = wpagent_exec_set_global_styles_locked( wpagent_spectra_one_snapshot_global_styles_write_input( $plan['snapshot']['global_styles'], $before['state_hash'] ), $before['state_hash'], $global_lock );
			if ( is_wp_error( $result ) || ! is_array( $result ) || ! is_string( $result['state_hash'] ?? null ) ) {
				$failure = is_wp_error( $result ) ? $result : new WP_Error( 'wpagent_spectra_one_snapshot_global_styles_verify', __( 'Die 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_snapshot_recovery_required', __( 'Die Global-Styles-Änderung lieferte 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_snapshot_recovery_required', __( 'Die Global-Styles-Änderung konnte 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_snapshot_global_styles_verify' ) );
			}
			$applied[] = array( 'kind' => 'global_styles', 'before' => $before, 'candidate' => $candidate, 'after_hash' => $candidate['state_hash'] );
			$guard = wpagent_spectra_one_snapshot_guard( $lock, $identity );
			if ( is_wp_error( $guard ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( $guard, $applied, $lock, $identity, $global_lock );
		}
		$navigation_map = array();
		foreach ( $plan['navigations'] as $record ) {
			$guard = wpagent_spectra_one_snapshot_guard( $lock, $identity );
			if ( is_wp_error( $guard ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( $guard, $applied, $lock, $identity, $global_lock );
			$result = wpagent_exec_create_block_navigation( array( 'title' => $record['title'], 'items' => $record['items'] ) );
			if ( is_wp_error( $result ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( $result, $applied, $lock, $identity, $global_lock );
			$state = $result['navigation'] ?? null;
			if ( ! is_array( $state ) || empty( $result['verified'] ) || ! isset( $state['id'], $state['hash'] ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( new WP_Error( 'wpagent_spectra_one_snapshot_navigation_verify', __( 'Die importierte Navigation wurde nicht vollständig verifiziert.', 'wp-agent-companion' ), array( 'status' => 500 ) ), $applied, $lock, $identity, $global_lock );
			$navigation_map[ (int) $record['source_id'] ] = (int) $state['id'];
			$applied[] = array( 'kind' => 'navigation', 'id' => (int) $state['id'], 'hash' => (string) $state['hash'] );
			$guard = wpagent_spectra_one_snapshot_guard( $lock, $identity );
			if ( is_wp_error( $guard ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( $guard, $applied, $lock, $identity, $global_lock );
		}
		$created = array( 'site_templates' => array(), 'template_parts' => array(), 'navigations' => array() );
		foreach ( $plan['template_parts'] as $record ) {
			$result = wpagent_spectra_one_snapshot_create_template( 'wp_template_part', $record, $navigation_map, $lock, $identity );
			if ( is_wp_error( $result ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( $result, $applied, $lock, $identity, $global_lock );
			$applied[] = array( 'kind' => 'template', 'type' => 'wp_template_part', 'slug' => $result['resource']['slug'], 'hash' => $result['resource']['hash'] );
			$created['template_parts'][] = $result['resource'];
		}
		foreach ( $plan['site_templates'] as $record ) {
			$result = wpagent_spectra_one_snapshot_create_template( 'wp_template', $record, $navigation_map, $lock, $identity );
			if ( is_wp_error( $result ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( $result, $applied, $lock, $identity, $global_lock );
			$applied[] = array( 'kind' => 'template', 'type' => 'wp_template', 'slug' => $result['resource']['slug'], 'hash' => $result['resource']['hash'] );
			$created['site_templates'][] = $result['resource'];
		}
		foreach ( $applied as $record ) if ( 'navigation' === $record['kind'] ) $created['navigations'][] = wpagent_site_editor_navigation_state( $record['id'] );
		if ( in_array( true, array_map( 'is_wp_error', $created['navigations'] ), true ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( new WP_Error( 'wpagent_spectra_one_snapshot_navigation_verify', __( 'Eine importierte Navigation konnte abschließend nicht gelesen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) ), $applied, $lock, $identity, $global_lock );
		$guard = wpagent_spectra_one_snapshot_guard( $lock, $identity );
		if ( is_wp_error( $guard ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( $guard, $applied, $lock, $identity, $global_lock );
		$global = $plan['selection']['global_styles'] ? wpagent_gs_state() : null;
		if ( is_wp_error( $global ) ) return wpagent_spectra_one_snapshot_fail_after_mutation( $global, $applied, $lock, $identity, $global_lock );
		return array( 'verified' => true, 'state_hash' => wpagent_spectra_one_snapshot_import_hash( $created, $global ), 'created' => $created, 'global_styles' => $global ? array( 'state_hash' => $global['state_hash'], 'theme' => $global['theme'] ) : null );
	} finally {
		if ( is_string( $global_lock ) ) wpagent_gs_release_lock( $global_lock );
		wpagent_spectra_one_snapshot_release_lock( $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_snapshot_import_plan( array $input ) {
	$snapshot = $input['snapshot'] ?? null; $selection = $input['selection'] ?? null;
	if ( ! is_array( $snapshot ) || ! is_array( $selection ) || array_diff( array_keys( $snapshot ), array( 'schema', 'identity', 'global_styles_state_hash', 'snapshot', 'state_hash', 'signature', 'bytes' ) ) || array_diff( array_keys( $selection ), array( 'global_styles', 'expected_global_styles_hash', 'site_templates', 'template_parts', 'navigations' ) ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Das Snapshot-Format oder die Importauswahl ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( WPAGENT_SPECTRA_ONE_SNAPSHOT_SCHEMA_VERSION !== ( $snapshot['schema'] ?? '' ) || ! is_array( $snapshot['identity'] ?? null ) || ! is_array( $snapshot['snapshot'] ?? null ) || ! is_string( $snapshot['state_hash'] ?? null ) || ! is_string( $snapshot['signature'] ?? null ) || ! is_int( $snapshot['bytes'] ?? null ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Das Snapshot-Format ist unvollständig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$unsigned = array( 'schema' => $snapshot['schema'], 'identity' => $snapshot['identity'], 'global_styles_state_hash' => $snapshot['global_styles_state_hash'] ?? '', 'snapshot' => wpagent_spectra_one_snapshot_canonical_body( $snapshot['snapshot'] ) );
	$json = wp_json_encode( $unsigned, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( false === $json || strlen( $json ) !== $snapshot['bytes'] || ! hash_equals( hash( 'sha256', "wpagently-spectra-one-snapshot-v1\0{$json}" ), $snapshot['state_hash'] ) || ! hash_equals( wpagent_spectra_one_snapshot_signature( $snapshot['state_hash'] ), $snapshot['signature'] ) || strlen( $json ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_BYTES ) return new WP_Error( 'wpagent_spectra_one_snapshot_integrity', __( 'Der Snapshot ist beschädigt, verändert oder zu groß.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( $snapshot['identity'] !== wpagent_spectra_one_snapshot_identity() ) return new WP_Error( 'wpagent_spectra_one_snapshot_identity', __( 'Der Snapshot stammt nicht aus demselben Blog, derselben Site oder demselben Spectra-One-Theme.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$body = $snapshot['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'] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Die Snapshot-Komponenten sind ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	foreach ( array( 'site_templates', 'template_parts', 'navigations' ) as $key ) if ( count( $body[ $key ] ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES ) return new WP_Error( 'wpagent_spectra_one_snapshot_size', __( 'Der Snapshot enthält zu viele Ressourcen.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	$plan = array( 'snapshot' => $body, 'selection' => $selection, 'site_templates' => array(), 'template_parts' => array(), 'navigations' => array() );
	$plan['navigations'] = wpagent_spectra_one_snapshot_selected_records( $body['navigations'], $selection['navigations'] ?? array(), 'navigation' );
	$plan['template_parts'] = wpagent_spectra_one_snapshot_selected_records( $body['template_parts'], $selection['template_parts'] ?? array(), 'part' );
	$plan['site_templates'] = wpagent_spectra_one_snapshot_selected_records( $body['site_templates'], $selection['site_templates'] ?? array(), 'template' );
	foreach ( array( 'navigations', 'template_parts', 'site_templates' ) as $key ) if ( is_wp_error( $plan[ $key ] ) ) return $plan[ $key ];
	if ( ! is_bool( $selection['global_styles'] ?? null ) || ( $selection['global_styles'] && ( ! is_string( $selection['expected_global_styles_hash'] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/', $selection['expected_global_styles_hash'] ) || ! is_array( $body['global_styles'] ?? null ) ) ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_selection', __( 'Die Global-Styles-Auswahl benötigt einen frischen vollständigen Ziel-Hash.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	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;
	return $plan;
}

/** @return array<int,array<string,mixed>>|WP_Error */
function wpagent_spectra_one_snapshot_selected_records( array $records, $indexes, string $kind ) {
	if ( ! is_array( $indexes ) || ! array_is_list( $indexes ) || count( $indexes ) > WPAGENT_SPECTRA_ONE_SNAPSHOT_MAX_RESOURCES ) return new WP_Error( 'wpagent_spectra_one_snapshot_selection', __( 'Die ausgewählten Snapshot-Indizes sind ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$out = array();
	foreach ( $indexes as $index ) { if ( ! is_int( $index ) || $index < 0 || ! array_key_exists( $index, $records ) || isset( $out[ $index ] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_selection', __( 'Ein Snapshot-Index ist ungültig oder doppelt.', 'wp-agent-companion' ), array( 'status' => 400 ) ); $out[ $index ] = $records[ $index ]; }
	return array_values( $out );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_snapshot_validate_global_styles( array $value ) {
	if ( array_diff( array_keys( $value ), array( 'colors', 'fontSizes', 'spacingSizes', 'fontFamilies', 'styles' ) ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_global_styles', __( 'Der Global-Styles-Snapshot enthält unbekannte Daten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$map = array( 'colors' => array( 'color', 'wpagent_gs_validate_color' ), 'fontSizes' => array( 'size', 'wpagent_gs_validate_size' ), 'spacingSizes' => array( 'size', 'wpagent_gs_validate_size' ), 'fontFamilies' => array( 'fontFamily', 'wpagent_gs_validate_font_family' ) );
	$out = array();
	foreach ( $map as $key => $config ) { $result = wpagent_gs_validate_preset_list( $value[ $key ] ?? null, $config[0], $config[1], $key ); if ( is_wp_error( $result ) ) return $result; $out[ $key ] = $result; }
	$styles = array_key_exists( 'styles', $value ) && array() === $value['styles']
		? array()
		: wpagent_gs_validate_styles( $value['styles'] ?? null );
	return is_wp_error( $styles ) ? $styles : array_merge( $out, array( 'styles' => $styles ) );
}

/** @return array<string,mixed> */
function wpagent_spectra_one_snapshot_global_styles_write_input( array $global_styles, string $expected_hash ): array {
	// The generic writer treats an explicit empty styles object as invalid. For a
	// snapshot it means exactly “no direct style changes”, so omit only this
	// semantically empty update while retaining it in the signed payload.
	if ( array_key_exists( 'styles', $global_styles ) && array() === $global_styles['styles'] ) {
		unset( $global_styles['styles'] );
	}
	return array_merge( $global_styles, array( 'expected_hash' => $expected_hash, 'replace' => false ) );
}

/** @return true|WP_Error */
function wpagent_spectra_one_snapshot_validate_records( array &$plan ) {
	$selected_parts = array(); $selected_navigation = array();
	foreach ( $plan['navigations'] as $record ) {
		if ( ! is_array( $record ) || array_diff( array_keys( $record ), array( 'source_id', 'title', 'items', 'source_hash' ) ) || ! is_int( $record['source_id'] ?? null ) || isset( $selected_navigation[ $record['source_id'] ] ) || ! is_string( $record['source_hash'] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/', $record['source_hash'] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Ein Navigation-Snapshot ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$title = wpagent_site_editor_plain_text( $record['title'] ?? '', 'title', 200 ); $items = wpagent_site_editor_build_navigation_content( $record['items'] ?? null );
		if ( is_wp_error( $title ) || is_wp_error( $items ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Ein Navigation-Snapshot enthält ungültige Einträge.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$record['title'] = $title; $selected_navigation[ $record['source_id'] ] = true;
	}
	foreach ( array( 'template_parts' => 'wp_template_part', 'site_templates' => 'wp_template' ) as $key => $type ) {
		foreach ( $plan[ $key ] as &$record ) {
			$valid = wpagent_spectra_one_snapshot_validate_resource_record( $record, 'wp_template_part' === $type );
			if ( is_wp_error( $valid ) ) return $valid;
			if ( 'wp_template_part' === $type ) { if ( isset( $selected_parts[ $record['slug'] ] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Ein Template-Teil-Slug ist doppelt vorhanden.', 'wp-agent-companion' ), array( 'status' => 400 ) ); $selected_parts[ $record['slug'] ] = true; }
		}
		unset( $record );
	}
	foreach ( array( 'template_parts', 'site_templates' ) as $key ) foreach ( $plan[ $key ] as $record ) foreach ( $record['components'] as $component ) {
		if ( 'template-part' === $component['type'] && ! isset( $selected_parts[ $component['slug'] ] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_reference', __( 'Jede Template-Teil-Referenz muss ebenfalls importiert werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		if ( 'navigation' === $component['type'] && ! isset( $selected_navigation[ $component['navigation_id'] ] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_reference', __( 'Jede Navigation-Referenz muss ebenfalls importiert werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	};
	$ordered = wpagent_spectra_one_snapshot_order_parts( $plan['template_parts'] );
	if ( is_wp_error( $ordered ) ) return $ordered;
	$plan['template_parts'] = $ordered;
	return true;
}

/** @return true|WP_Error */
function wpagent_spectra_one_snapshot_validate_resource_record( array &$record, bool $part ) {
	$keys = array( 'slug', 'title', 'description', 'container', 'components', 'source_hash' ); if ( $part ) $keys[] = 'area';
	if ( array_diff( array_keys( $record ), $keys ) || ! is_string( $record['source_hash'] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/', $record['source_hash'] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Eine Template-Ressource ist unvollständig oder enthält unbekannte Daten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$slug = wpagent_site_editor_slug( $record['slug'] ?? '' ); $title = wpagent_site_editor_plain_text( $record['title'] ?? '', 'title', 200 ); $description = wpagent_site_editor_plain_text( $record['description'] ?? '', 'description', 1000, true );
	if ( is_wp_error( $slug ) || is_wp_error( $title ) || is_wp_error( $description ) || ! in_array( $record['container'] ?? '', array( 'none', 'group', 'main', 'header', 'footer' ), true ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Eine Template-Ressource enthält ungültige Metadaten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( $part ) { $area = wpagent_site_editor_validate_area( $record['area'] ?? '' ); if ( is_wp_error( $area ) ) return $area; $record['area'] = $area; }
	$components = wpagent_spectra_one_snapshot_validate_components( $record['components'] ?? null );
	if ( is_wp_error( $components ) ) return $components;
	$record['components'] = $components;
	// Import preflight must not manufacture target references. It uses the
	// native structural builder without resolving references, then the regular
	// native writer resolves and verifies each remapped reference after creation.
	$content = wpagent_site_editor_build_content( $record['components'], $record['container'], false );
	if ( is_wp_error( $content ) || strlen( $content ) > WPAGENT_SITE_EDITOR_MAX_CONTENT_BYTES ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Eine Template-Ressource enthält nicht unterstützte Komponenten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$record['slug'] = $slug; $record['title'] = $title; $record['description'] = $description;
	return true;
}

/** @return array<int,array<string,mixed>>|WP_Error */
function wpagent_spectra_one_snapshot_validate_components( $components ) {
	if ( ! is_array( $components ) || ! array_is_list( $components ) || array() === $components || count( $components ) > WPAGENT_SITE_EDITOR_MAX_COMPONENTS ) return new WP_Error( 'wpagent_spectra_one_snapshot_invalid', __( 'Eine Template-Ressource enthält keine begrenzte Komponentenliste.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$keys = array(
		'site-title' => array( 'type', 'level', 'is_link' ), 'post-title' => array( 'type', 'level', 'is_link' ), 'site-logo' => array( 'type', 'width' ),
		'template-part' => array( 'type', 'slug' ), 'navigation' => array( 'type', 'navigation_id' ),
		'post-featured-image' => array( 'type', 'is_link' ), 'post-excerpt' => array( 'type', 'is_link' ), 'post-date' => array( 'type', 'is_link' ), 'post-author-name' => array( 'type', 'is_link' ),
		'post-content' => array( 'type' ), 'post-terms' => array( 'type', 'taxonomy' ), 'query-title' => array( 'type', 'query_title_type' ),
		'separator' => array( 'type' ), 'spacer' => array( 'type', 'height' ),
	);
	foreach ( $components as $component ) {
		$type = is_array( $component ) && is_string( $component['type'] ?? null ) ? $component['type'] : '';
		if ( ! isset( $keys[ $type ] ) || array_diff( array_keys( $component ), $keys[ $type ] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_unsupported_entity', __( 'Ein Snapshot enthält eine unbekannte oder nicht erlaubte Template-Komponente.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return $components;
}

/** @return array<int,array<string,mixed>>|WP_Error */
function wpagent_spectra_one_snapshot_order_parts( array $parts ) {
	$by_slug = array(); foreach ( $parts as $part ) $by_slug[ $part['slug'] ] = $part;
	$ordered = array(); $visiting = array(); $visited = array();
	$visit = static function ( string $slug ) use ( &$visit, &$by_slug, &$ordered, &$visiting, &$visited ) {
		if ( isset( $visited[ $slug ] ) ) return true;
		if ( isset( $visiting[ $slug ] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_cycle', __( 'Die importierten Template-Teile enthalten einen Zyklus.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$visiting[ $slug ] = true;
		foreach ( $by_slug[ $slug ]['components'] as $component ) if ( 'template-part' === $component['type'] ) { $result = $visit( $component['slug'] ); if ( is_wp_error( $result ) ) return $result; }
		unset( $visiting[ $slug ] ); $visited[ $slug ] = true; $ordered[] = $by_slug[ $slug ]; return true;
	};
	foreach ( array_keys( $by_slug ) as $slug ) { $result = $visit( $slug ); if ( is_wp_error( $result ) ) return $result; }
	return $ordered;
}

/** @return true|WP_Error */
function wpagent_spectra_one_snapshot_preflight_targets( array $plan ) {
	foreach ( array( 'template_parts' => 'wp_template_part', 'site_templates' => 'wp_template' ) as $key => $type ) foreach ( $plan[ $key ] as $record ) if ( get_block_template( wpagent_site_editor_template_id( $record['slug'] ), $type ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_target_exists', __( 'Eine create-only Zielressource existiert bereits.', 'wp-agent-companion' ), array( 'status' => 409, 'slug' => $record['slug'] ) );
	foreach ( $plan['navigations'] as $record ) if ( wpagent_spectra_one_snapshot_navigation_title_exists( $record['title'] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_target_exists', __( 'Eine create-only Zielnavigation existiert bereits.', 'wp-agent-companion' ), array( 'status' => 409, 'title' => $record['title'] ) );
	return true;
}

function wpagent_spectra_one_snapshot_navigation_title_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', 'publish', $title ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
	return (int) $count > 0;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_snapshot_create_template( string $type, array $record, array $navigation_map, string $lock, array $identity ) {
	$guard = wpagent_spectra_one_snapshot_guard( $lock, $identity );
	if ( is_wp_error( $guard ) ) return $guard;
	$components = wpagent_spectra_one_snapshot_remap_components( $record['components'], $navigation_map );
	if ( is_wp_error( $components ) ) return $components;
	$input = array( 'slug' => $record['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_snapshot_template_verify', __( 'Die importierte Template-Ressource wurde nicht verifiziert.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	$guard = wpagent_spectra_one_snapshot_guard( $lock, $identity );
	return is_wp_error( $guard ) ? $guard : $result;
}

/** @return array<int,array<string,mixed>>|WP_Error */
function wpagent_spectra_one_snapshot_remap_components( array $components, array $navigation_map ) {
	$out = array();
	foreach ( $components as $component ) {
		if ( 'navigation' === $component['type'] ) { $source = (int) $component['navigation_id']; if ( ! isset( $navigation_map[ $source ] ) ) return new WP_Error( 'wpagent_spectra_one_snapshot_reference', __( 'Eine Navigation-Referenz konnte nicht auf die neue Ressource abgebildet werden.', 'wp-agent-companion' ), array( 'status' => 409 ) ); $component['navigation_id'] = $navigation_map[ $source ]; }
		$out[] = $component;
	}
	return $out;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_spectra_one_snapshot_fail_after_mutation( WP_Error $failure, array $applied, string $lock, array $identity, $global_lock ) {
	if ( array() === $applied ) return $failure;
	foreach ( array_reverse( $applied ) as $record ) {
		$guard = wpagent_spectra_one_snapshot_guard( $lock, $identity );
		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_snapshot_recovery_required', __( 'Der Import wurde unterbrochen. Der Zustand ist unsicher und darf nicht automatisch weiter verändert werden. Prüfe die bereits angelegten Ressourcen manuell.', 'wp-agent-companion' ), array( 'status' => 500, 'recovery_required' => true, 'original_error' => $failure->get_error_code() ) );
	}
	return new WP_Error( 'wpagent_spectra_one_snapshot_import_rolled_back', __( 'Der 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() ) );
}

function wpagent_spectra_one_snapshot_rollback_record( array $record, string $global_lock ): bool {
	if ( ! current_user_can( 'edit_theme_options' ) ) return false;
	if ( 'global_styles' === $record['kind'] ) {
		if ( '' === $global_lock || ! wpagent_gs_refresh_lock( $global_lock ) || ! wpagent_gs_lock_is_owned( $global_lock ) || ! is_array( $record['candidate'] ?? null ) ) return false;
		$current = wpagent_gs_state();
		if ( is_wp_error( $current ) || ! hash_equals( $record['after_hash'], $current['state_hash'] ) || ! wpagent_gs_restore_snapshot( $record['before'], $record['candidate'], $global_lock ) ) return false;
		$fresh = wpagent_gs_state(); return ! is_wp_error( $fresh ) && hash_equals( $record['before']['state_hash'], $fresh['state_hash'] );
	}
	if ( 'template' === $record['kind'] ) {
		$token = wpagent_site_editor_acquire_lock( $record['type'], $record['slug'] ); if ( is_wp_error( $token ) ) return false;
		try { return wpagent_site_editor_delete_created_template_if_current( $record['type'], $record['slug'], $record['hash'], $token ); } finally { wpagent_site_editor_release_lock( $record['type'], $record['slug'], $token ); }
	}
	if ( 'navigation' === $record['kind'] ) {
		$token = wpagent_site_editor_acquire_lock( 'wp_navigation', (string) $record['id'] ); if ( is_wp_error( $token ) ) return false;
		try { return wpagent_site_editor_delete_created_navigation_if_current( (int) $record['id'], $record['hash'], $token ); } finally { wpagent_site_editor_release_lock( 'wp_navigation', (string) $record['id'], $token ); }
	}
	return false;
}

function wpagent_spectra_one_snapshot_import_hash( array $created, $global ): string {
	return hash( 'sha256', wp_json_encode( array( 'blog_id' => get_current_blog_id(), 'theme' => wpagent_spectra_one_snapshot_identity()['theme'], 'created' => $created, 'global_styles_hash' => is_array( $global ) ? $global['state_hash'] : null ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
}
