<?php
/** Read-only, version-bound Elementor v3-to-v4 migration preview. */

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

define( 'WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_VERSION', '4.2.1' );
define( 'WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_SETTINGS', 64 );
define( 'WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_REPORT_ITEMS', 4096 );

/** @return array<string,array<string,string[]>> */
function wpagent_elementor_migration_preview_supported_controls(): array {
	return array(
		'container'   => array( 'container' => array( 'content_width', 'width', 'min_height', 'flex_direction', 'flex_justify_content', 'flex_align_items', 'padding', 'margin', 'background_background', 'background_color' ) ),
		'heading'     => array( 'widget:heading' => array( 'title', 'header_size', 'align', 'title_color', 'typography_typography', 'typography_font_size' ) ),
		'text-editor' => array( 'widget:text-editor' => array( 'editor', 'align', 'text_color', 'typography_typography', 'typography_font_size' ) ),
		'button'      => array( 'widget:button' => array( 'text', 'link', 'align', 'size', 'button_text_color', 'button_background_color' ) ),
		'image'       => array( 'widget:image' => array( 'image', 'image_size', 'caption_source', 'align' ) ),
	);
}

/** @return true|WP_Error */
function wpagent_elementor_migration_preview_runtime() {
	if ( ! wpagent_elementor_available() ) {
		return wpagent_elementor_unavailable_error();
	}
	$actual = defined( 'ELEMENTOR_VERSION' ) ? (string) ELEMENTOR_VERSION : '';
	if ( ! hash_equals( WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_VERSION, $actual ) ) {
		return new WP_Error(
			'wpagent_elementor_migration_preview_version',
			__( 'Der Elementor-Migrations-Preview ist ausschließlich für Elementor 4.2.1 geprüft.', 'wp-agent-companion' ),
			array( 'status' => 409, 'expected_version' => WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_VERSION, 'actual_version' => $actual )
		);
	}
	return true;
}

/** @return array<string,mixed> */
function wpagent_ability_preview_elementor_v3_v4_migration(): array {
	$change = array(
		'type' => 'object', 'properties' => array(
			'element_id' => array( 'type' => 'string' ), 'path' => array( 'type' => 'string' ), 'reason' => array( 'type' => 'string' ),
		), 'required' => array( 'element_id', 'path', 'reason' ), 'additionalProperties' => false,
	);
	$mapping = array(
		'type' => 'object', 'properties' => array(
			'element_id' => array( 'type' => 'string' ), 'source_type' => array( 'type' => 'string' ), 'target_type' => array( 'type' => 'string' ),
			'supported_settings' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ),
		), 'required' => array( 'element_id', 'source_type', 'target_type', 'supported_settings' ), 'additionalProperties' => false,
	);
	$provider = array(
		'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string', 'const' => 'elementor' ), 'runtime_version' => array( 'type' => 'string', 'const' => WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_VERSION ) ),
		'required' => array( 'name', 'runtime_version' ), 'additionalProperties' => false,
	);
	$target = array(
		'type' => 'object', 'properties' => array( 'provider' => array( 'type' => 'string', 'const' => 'elementor' ), 'runtime_version' => array( 'type' => 'string', 'const' => WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_VERSION ), 'checked' => array( 'type' => 'boolean', 'const' => true ) ),
		'required' => array( 'provider', 'runtime_version', 'checked' ), 'additionalProperties' => false,
	);
	$capabilities = array(
		'type' => 'object', 'properties' => array(
			'preview' => array( 'type' => 'boolean', 'const' => true ), 'source_write' => array( 'type' => 'boolean', 'const' => false ), 'target_write' => array( 'type' => 'boolean', 'const' => false ),
			'template_import' => array( 'type' => 'boolean', 'const' => false ), 'pro_simulation' => array( 'type' => 'boolean', 'const' => false ),
		), 'required' => array( 'preview', 'source_write', 'target_write', 'template_import', 'pro_simulation' ), 'additionalProperties' => false,
	);
	return array(
		'label' => __( 'Elementor-v3-zu-v4-Migrations-Preview', 'wp-agent-companion' ),
		'description' => __( 'Analysiert ein Elementor-Dokument ausschließlich lesend für den eng geprüften 4.2.1-Zielvertrag. Es schreibt nie, importiert keine Templates und simuliert keine Pro-Funktionen.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array(
			'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'minimum' => 1 ) ),
			'required' => array( 'post_id' ), 'additionalProperties' => false,
		),
		'output_schema' => array(
			'type' => 'object', 'properties' => array(
				'post_id' => array( 'type' => 'integer' ), 'source_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'blog_binding' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'provider' => $provider, 'target' => $target, 'capabilities' => $capabilities,
				'analysis' => array( 'type' => 'object', 'properties' => array( 'nodes' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ELEMENTOR_MAX_ELEMENTS ), 'max_depth' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ELEMENTOR_MAX_DEPTH ), 'bytes' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ELEMENTOR_MAX_BYTES ) ), 'required' => array( 'nodes', 'max_depth', 'bytes' ), 'additionalProperties' => false ), 'mappings' => array( 'type' => 'array', 'maxItems' => WPAGENT_ELEMENTOR_MAX_ELEMENTS + 1, 'items' => $mapping ),
				'losses' => array( 'type' => 'array', 'maxItems' => WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_REPORT_ITEMS, 'items' => $change ), 'conflicts' => array( 'type' => 'array', 'maxItems' => WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_REPORT_ITEMS, 'items' => $change ),
				'redactions' => array( 'type' => 'object', 'properties' => array( 'count' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_REPORT_ITEMS ), 'values_emitted' => array( 'type' => 'boolean', 'const' => false ) ), 'required' => array( 'count', 'values_emitted' ), 'additionalProperties' => false ),
			),
			'required' => array( 'post_id', 'source_hash', 'blog_binding', 'provider', 'target', 'capabilities', 'analysis', 'mappings', 'losses', 'conflicts', 'redactions' ),
			'additionalProperties' => false,
		),
		'execute_callback' => 'wpagent_exec_preview_elementor_v3_v4_migration',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ) ),
	);
}

/** @return string */
function wpagent_elementor_migration_preview_blog_binding(): string {
	return hash( 'sha256', get_current_blog_id() . "\0" . home_url( '/' ) . "\0" . site_url( '/' ) );
}

/** @return true|WP_Error */
function wpagent_elementor_migration_preview_validate_value( $value, int $depth = 0 ) {
	if ( $depth > WPAGENT_ELEMENTOR_MAX_DEPTH ) {
		return new WP_Error( 'wpagent_elementor_migration_preview_depth', __( 'Die Elementor-Struktur überschreitet die erlaubte Verschachtelungstiefe.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	if ( null === $value || is_bool( $value ) || is_int( $value ) || is_float( $value ) || is_string( $value ) ) {
		return true;
	}
	if ( ! is_array( $value ) ) {
		return new WP_Error( 'wpagent_elementor_migration_preview_value', __( 'Die Elementor-Struktur enthält keinen JSON-kompatiblen Wert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	foreach ( $value as $key => $item ) {
		if ( ! is_int( $key ) && ! is_string( $key ) ) {
			return new WP_Error( 'wpagent_elementor_migration_preview_value', __( 'Die Elementor-Struktur enthält einen ungültigen Schlüssel.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$valid = wpagent_elementor_migration_preview_validate_value( $item, $depth + 1 );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
	}
	return true;
}

/**
 * Validates the provider tree without applying the write-path safety policy.
 * Unsafe and unknown widgets are reported as losses, never executed or emitted.
 *
 * @param array<int,mixed> $elements
 * @return array{nodes:int,max_depth:int,bytes:int}|WP_Error
 */
function wpagent_elementor_migration_preview_validate_elements( array $elements ) {
	$encoded = wp_json_encode( $elements, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( false === $encoded || strlen( $encoded ) > WPAGENT_ELEMENTOR_MAX_BYTES ) {
		return new WP_Error( 'wpagent_elementor_migration_preview_bytes', __( 'Das Elementor-Dokument überschreitet die erlaubte Größe für den Migrations-Preview.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	$ids = array();
	$nodes = 0;
	$max_depth = 0;
	$walk = function ( array $items, int $depth ) use ( &$walk, &$ids, &$nodes, &$max_depth ) {
		if ( $depth > WPAGENT_ELEMENTOR_MAX_DEPTH ) {
			return new WP_Error( 'wpagent_elementor_migration_preview_depth', __( 'Die Elementor-Struktur überschreitet die erlaubte Verschachtelungstiefe.', 'wp-agent-companion' ), array( 'status' => 413 ) );
		}
		$max_depth = max( $max_depth, $depth );
		foreach ( $items as $element ) {
			if ( ! is_array( $element ) || array_is_list( $element ) ) {
				return new WP_Error( 'wpagent_elementor_migration_preview_element', __( 'Jedes Elementor-Element muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
			++$nodes;
			if ( $nodes > WPAGENT_ELEMENTOR_MAX_ELEMENTS ) {
				return new WP_Error( 'wpagent_elementor_migration_preview_nodes', __( 'Das Elementor-Dokument enthält zu viele Elemente für den Migrations-Preview.', 'wp-agent-companion' ), array( 'status' => 413 ) );
			}
			$id = $element['id'] ?? null;
			$type = $element['elType'] ?? null;
			$settings = $element['settings'] ?? array();
			$children = $element['elements'] ?? array();
			if ( ! is_string( $id ) || ! preg_match( '/^[A-Za-z0-9_-]{1,64}$/D', $id ) || isset( $ids[ $id ] ) || ! is_string( $type ) || ! preg_match( '/^[A-Za-z0-9_-]{1,64}$/D', $type ) || ! is_array( $settings ) || ( array() !== $settings && array_is_list( $settings ) ) || count( $settings ) > WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_SETTINGS || ! is_array( $children ) || ! array_is_list( $children ) ) {
				return new WP_Error( 'wpagent_elementor_migration_preview_shape', __( 'Das Elementor-Element liegt außerhalb des erwarteten Providerformats.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
			if ( 'widget' === $type && ( ! is_string( $element['widgetType'] ?? null ) || ! preg_match( '/^[A-Za-z0-9_-]{1,128}$/D', $element['widgetType'] ) ) ) {
				return new WP_Error( 'wpagent_elementor_migration_preview_widget', __( 'Ein Elementor-Widget enthält keinen gültigen Provider-Typ.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
			$ids[ $id ] = true;
			$settings_valid = wpagent_elementor_migration_preview_validate_value( $settings, $depth + 1 );
			if ( is_wp_error( $settings_valid ) ) {
				return $settings_valid;
			}
			$children_valid = $walk( $children, $depth + 1 );
			if ( is_wp_error( $children_valid ) ) {
				return $children_valid;
			}
		}
		return true;
	};
	$valid = $walk( $elements, 0 );
	if ( is_wp_error( $valid ) ) {
		return $valid;
	}
	return array( 'nodes' => $nodes, 'max_depth' => $max_depth, 'bytes' => strlen( $encoded ) );
}

function wpagent_elementor_migration_preview_secret_key( string $key ): bool {
	$normalized = preg_replace( '/(?<=[a-z0-9])(?=[A-Z])/', '_', $key );
	$normalized = is_string( $normalized ) ? strtolower( $normalized ) : '';
	if ( 1 === preg_match( '/(?:^|[_-])(?:api[_-]?key|access[_-]?key|secret|token|password|authorization|bearer)(?:$|[_-])/', $normalized ) ) {
		return true;
	}
	return in_array( str_replace( array( '_', '-' ), '', $normalized ), array( 'apikey', 'accesskey', 'secret', 'token', 'password', 'authorization', 'bearer' ), true );
}

function wpagent_elementor_migration_preview_dynamic_key( string $key ): bool {
	return in_array( $key, array( '__dynamic__', 'dynamic_tags', 'e_interactions', 'interactions' ), true );
}

/** @param array<string,mixed> $settings @return array{supported:string[],losses:array<int,array<string,string>>,redacted:int} */
function wpagent_elementor_migration_preview_settings( array $settings, array $allowed, string $element_id, string $path ): array {
	$supported = array();
	$losses = array();
	$redacted = 0;
	foreach ( array_keys( $settings ) as $key ) {
		$key = is_string( $key ) ? $key : '';
		if ( '' === $key ) {
			$losses[] = array( 'element_id' => $element_id, 'path' => $path, 'reason' => 'invalid_setting_key' );
			continue;
		}
		if ( wpagent_elementor_migration_preview_secret_key( $key ) ) {
			++$redacted;
			$losses[] = array( 'element_id' => $element_id, 'path' => $path . '.<redacted>', 'reason' => 'secret_redacted' );
			continue;
		}
		if ( wpagent_elementor_migration_preview_dynamic_key( $key ) ) {
			$losses[] = array( 'element_id' => $element_id, 'path' => $path . '.' . $key, 'reason' => in_array( $key, array( 'e_interactions', 'interactions' ), true ) ? 'interaction' : 'dynamic_tag' );
			continue;
		}
		if ( in_array( $key, $allowed, true ) ) {
			$supported[] = $key;
			continue;
		}
		$losses[] = array( 'element_id' => $element_id, 'path' => $path . '.' . $key, 'reason' => 'unsupported_setting' );
	}
	sort( $supported, SORT_STRING );
	usort( $losses, static fn( array $left, array $right ): int => strcmp( $left['path'] . "\0" . $left['reason'], $right['path'] . "\0" . $right['reason'] ) );
	return array( 'supported' => $supported, 'losses' => $losses, 'redacted' => $redacted );
}

/** @param array<int,array<string,mixed>> $elements @param array<string,array<string,string[]>> $supported */
function wpagent_elementor_migration_preview_report_elements( array $elements, array $supported, array &$mappings, array &$losses, array &$conflicts, int &$redacted, array $path = array() ): void {
	foreach ( $elements as $index => $element ) {
		$id = (string) $element['id'];
		$type = (string) $element['elType'];
		$node_path = implode( '.', array_merge( $path, array( (string) $index ) ) );
		$settings = $element['settings'];
		$source_type = 'widget' === $type ? 'widget:' . (string) $element['widgetType'] : $type;
		$provider_key = 'widget' === $type ? (string) $element['widgetType'] : $type;
		if ( 'section' === $type || 'column' === $type ) {
			$conflicts[] = array( 'element_id' => $id, 'path' => $node_path, 'reason' => 'section' === $type ? 'legacy_section_layout' : 'legacy_column_layout' );
			$details = wpagent_elementor_migration_preview_settings( $settings, array(), $id, $node_path );
			$losses = array_merge( $losses, $details['losses'] );
			$redacted += $details['redacted'];
		} elseif ( isset( $supported[ $provider_key ] ) ) {
			$target = array_key_first( $supported[ $provider_key ] );
			$details = wpagent_elementor_migration_preview_settings( $settings, $supported[ $provider_key ][ $target ], $id, $node_path );
			$mappings[] = array( 'element_id' => $id, 'source_type' => $source_type, 'target_type' => $target, 'supported_settings' => $details['supported'] );
			$losses = array_merge( $losses, $details['losses'] );
			$redacted += $details['redacted'];
		} else {
			$losses[] = array( 'element_id' => $id, 'path' => $node_path, 'reason' => 'widget' === $type && wpagent_elementor_widget_type_is_unsafe( (string) $element['widgetType'] ) ? 'unsafe_or_unsupported_widget' : 'unsupported_element' );
			$details = wpagent_elementor_migration_preview_settings( $settings, array(), $id, $node_path );
			$losses = array_merge( $losses, $details['losses'] );
			$redacted += $details['redacted'];
		}
		wpagent_elementor_migration_preview_report_elements( $element['elements'], $supported, $mappings, $losses, $conflicts, $redacted, array_merge( $path, array( (string) $index ) ) );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_preview_elementor_v3_v4_migration( $input ) {
	$input = is_array( $input ) ? $input : array();
	$post_id = absint( $input['post_id'] ?? 0 );
	if ( $post_id < 1 ) {
		return new WP_Error( 'wpagent_elementor_migration_preview_post', __( 'post_id muss eine positive Ganzzahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$runtime = wpagent_elementor_migration_preview_runtime();
	if ( is_wp_error( $runtime ) ) {
		return $runtime;
	}
	$loaded = wpagent_elementor_editable_document( $post_id );
	if ( is_wp_error( $loaded ) ) {
		return $loaded;
	}
	try {
		$elements = $loaded['document']->get_elements_data();
		$settings = method_exists( $loaded['document'], 'get_db_document_settings' ) ? $loaded['document']->get_db_document_settings() : array();
	} catch ( Throwable ) {
		return new WP_Error( 'wpagent_elementor_migration_preview_read', __( 'Elementor konnte das Dokument nicht sicher für den Migrations-Preview lesen.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	$elements = is_array( $elements ) ? $elements : array();
	$settings = is_array( $settings ) ? $settings : array();
	$analysis = wpagent_elementor_migration_preview_validate_elements( $elements );
	if ( is_wp_error( $analysis ) ) {
		return $analysis;
	}
	$settings_encoded = wp_json_encode( $settings, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( false === $settings_encoded || strlen( $settings_encoded ) > WPAGENT_ELEMENTOR_MAX_BYTES - $analysis['bytes'] ) {
		return new WP_Error( 'wpagent_elementor_migration_preview_bytes', __( 'Die vollständigen Elementor-Dokumentdaten überschreiten die erlaubte Größe für den Migrations-Preview.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	$analysis['bytes'] += strlen( $settings_encoded );
	if ( count( $settings ) > WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_SETTINGS ) {
		return new WP_Error( 'wpagent_elementor_migration_preview_settings', __( 'Die Elementor-Dokumenteinstellungen überschreiten die erlaubte Größe für den Migrations-Preview.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	$settings_valid = wpagent_elementor_migration_preview_validate_value( $settings );
	if ( is_wp_error( $settings_valid ) ) {
		return $settings_valid;
	}
	$state = array( 'elements' => $elements, 'settings' => $settings );
	$source_hash = wpagent_elementor_state_hash( $state );
	$mappings = array();
	$losses = array();
	$conflicts = array();
	$redacted = 0;
	wpagent_elementor_migration_preview_report_elements( $elements, wpagent_elementor_migration_preview_supported_controls(), $mappings, $losses, $conflicts, $redacted );
	$document_settings = wpagent_elementor_migration_preview_settings( $settings, array( 'hide_title' ), 'document', 'document.settings' );
	$losses = array_merge( $losses, $document_settings['losses'] );
	$redacted += $document_settings['redacted'];
	$mappings[] = array( 'element_id' => 'document', 'source_type' => 'document', 'target_type' => 'document', 'supported_settings' => $document_settings['supported'] );
	if ( count( $mappings ) > WPAGENT_ELEMENTOR_MAX_ELEMENTS + 1 || count( $losses ) > WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_REPORT_ITEMS || count( $conflicts ) > WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_REPORT_ITEMS || $redacted > WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_MAX_REPORT_ITEMS ) {
		return new WP_Error( 'wpagent_elementor_migration_preview_report', __( 'Der Elementor-Migrations-Preview würde seinen begrenzten Berichtsumfang überschreiten.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	usort( $losses, static fn( array $left, array $right ): int => strcmp( $left['element_id'] . "\0" . $left['path'] . "\0" . $left['reason'], $right['element_id'] . "\0" . $right['path'] . "\0" . $right['reason'] ) );
	usort( $conflicts, static fn( array $left, array $right ): int => strcmp( $left['element_id'] . "\0" . $left['path'], $right['element_id'] . "\0" . $right['path'] ) );
	return array(
		'post_id' => $post_id,
		'source_hash' => $source_hash,
		'blog_binding' => wpagent_elementor_migration_preview_blog_binding(),
		'provider' => array( 'name' => 'elementor', 'runtime_version' => (string) ELEMENTOR_VERSION ),
		'target' => array( 'provider' => 'elementor', 'runtime_version' => WPAGENT_ELEMENTOR_MIGRATION_PREVIEW_VERSION, 'checked' => true ),
		'capabilities' => array( 'preview' => true, 'source_write' => false, 'target_write' => false, 'template_import' => false, 'pro_simulation' => false ),
		'analysis' => $analysis,
		'mappings' => $mappings,
		'losses' => $losses,
		'conflicts' => $conflicts,
		'redactions' => array( 'count' => $redacted, 'values_emitted' => false ),
	);
}
