<?php
/**
 * Generic widget JSON import/export helpers (SiteOrigin instance ↔ block attributes).
 *
 * @package Rsmspark_Content_Widgets
 */

defined( 'ABSPATH' ) || exit;

/**
 * Rsmspark_Content_Widgets_Widget_Json_Config
 */
class Rsmspark_Content_Widgets_Widget_Json_Config {

	/**
	 * Read a JSON/text POST field without casting the raw $_POST entry.
	 *
	 * @param string $key POST key.
	 * @return string UTF-8 cleaned string (never null).
	 */
	public static function read_post_string( $key ) {
		$key = (string) $key;
		if ( '' === $key || ! isset( $_POST[ $key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Caller verifies nonce.
			return '';
		}

		$value = wp_unslash( $_POST[ $key ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Missing -- Validated below; JSON validated by caller.
		if ( ! is_string( $value ) ) {
			return '';
		}

		$checked = wp_check_invalid_utf8( $value );
		return is_string( $checked ) ? $checked : '';
	}

	/**
	 * @param string               $registry_id Widget registry id.
	 * @param array<string, mixed> $instance    SiteOrigin instance.
	 * @param string               $label       Export label.
	 * @return array<string, mixed>
	 */
	public static function build_export_payload( $registry_id, array $instance, $label = '' ) {
		$registry_id = sanitize_key( (string) $registry_id );
		$config      = self::sanitize_instance( $registry_id, $instance );

		if ( '' === trim( $label ) ) {
			$label = self::default_export_label( $registry_id, $config );
		}

		$payload = array(
			'plugin'      => Rsmspark_Content_Widgets_Widget_Json_Registry::EXPORT_PLUGIN,
			'widget'      => $registry_id,
			'version'     => RSMSPARK_CONTENT_WIDGETS_VERSION,
			'exported_at' => gmdate( DATE_ATOM ),
			'label'       => sanitize_text_field( $label ),
			'config'      => $config,
		);

		$attributes = self::to_block_attributes( $registry_id, $config );
		if ( ! empty( $attributes ) ) {
			$payload['attributes'] = $attributes;
		}

		return $payload;
	}

	/**
	 * @param string $registry_id Widget registry id.
	 * @param mixed  $payload     Raw JSON or array.
	 * @return array{config: array<string, mixed>, label: string, attributes: array<string, mixed>}|null
	 */
	public static function parse_import_payload( $registry_id, $payload ) {
		$registry_id = sanitize_key( (string) $registry_id );

		if ( is_string( $payload ) ) {
			$payload = json_decode( $payload, true );
		}

		if ( ! is_array( $payload ) ) {
			return null;
		}

		$config = self::extract_config_from_payload( $registry_id, $payload );
		if ( null === $config ) {
			return null;
		}

		$config = self::sanitize_instance( $registry_id, $config );

		return array(
			'label'      => sanitize_text_field( (string) ( $payload['label'] ?? '' ) ),
			'config'     => $config,
			'attributes' => self::to_block_attributes( $registry_id, $config ),
		);
	}

	/**
	 * @param string               $registry_id Widget registry id.
	 * @param array<string, mixed> $payload     Decoded export payload.
	 * @return array<string, mixed>|null
	 */
	public static function extract_config_from_payload( $registry_id, array $payload ) {
		$registry_id = sanitize_key( (string) $registry_id );
		$plugin      = (string) ( $payload['plugin'] ?? '' );
		$widget      = sanitize_key( (string) ( $payload['widget'] ?? '' ) );
		$block       = sanitize_key( str_replace( '-', '_', (string) ( $payload['block'] ?? '' ) ) );
		$block_slug  = Rsmspark_Content_Widgets_Widget_Json_Registry::block_slug_for_registry_id( $registry_id );
		$block_id    = sanitize_key( str_replace( '-', '_', $block_slug ) );

		if (
			Rsmspark_Content_Widgets_Widget_Json_Registry::EXPORT_PLUGIN === $plugin
			&& $registry_id === $widget
			&& is_array( $payload['config'] ?? null )
		) {
			return $payload['config'];
		}

		if ( $registry_id === $widget && is_array( $payload['config'] ?? null ) ) {
			return $payload['config'];
		}

		if (
			Rsmspark_Content_Widgets_Widget_Json_Registry::EXPORT_PLUGIN === $plugin
			&& ( $registry_id === $block || $block_id === $block )
			&& is_array( $payload['attributes'] ?? null )
		) {
			return self::from_block_attributes( $registry_id, $payload['attributes'] );
		}

		if (
			( $registry_id === $block || $block_id === $block )
			&& is_array( $payload['attributes'] ?? null )
		) {
			return self::from_block_attributes( $registry_id, $payload['attributes'] );
		}

		if ( self::looks_like_siteorigin_instance( $registry_id, $payload ) ) {
			return $payload;
		}

		if ( self::looks_like_block_attributes( $registry_id, $payload ) ) {
			return self::from_block_attributes( $registry_id, $payload );
		}

		return null;
	}

	/**
	 * @param string               $registry_id Widget registry id.
	 * @param array<string, mixed> $instance    SiteOrigin instance.
	 * @return array<string, mixed>
	 */
	public static function sanitize_instance( $registry_id, array $instance ) {
		unset( $instance['import_export'] );

		$dedicated = Rsmspark_Content_Widgets_Widget_Json_Registry::dedicated_handler_class( $registry_id );
		if ( $dedicated && method_exists( $dedicated, 'sanitize_instance' ) ) {
			return $dedicated::sanitize_instance( $instance );
		}

		$class = Rsmspark_Content_Widgets_Widget_Json_Registry::render_class_for( $registry_id );
		if ( $class && method_exists( $class, 'sanitize_instance' ) ) {
			return $class::sanitize_instance( $instance );
		}

		return $instance;
	}

	/**
	 * @param string               $registry_id Widget registry id.
	 * @param array<string, mixed> $instance    SiteOrigin instance or sanitized config.
	 * @return array<string, mixed>
	 */
	public static function to_block_attributes( $registry_id, array $instance ) {
		$class = Rsmspark_Content_Widgets_Widget_Json_Registry::render_class_for( $registry_id );
		if ( ! $class || ! method_exists( $class, 'block_attributes' ) ) {
			return array();
		}

		$instance = self::sanitize_instance( $registry_id, $instance );
		$flat     = array();

		if ( method_exists( $class, 'prepare_from_siteorigin_instance' ) ) {
			$flat = $class::prepare_from_siteorigin_instance( $instance );
			if ( is_array( $flat ) && ! self::looks_like_block_attributes( $registry_id, $flat ) ) {
				$flat = self::collect_block_attribute_values( $class, $flat );
			}
		} elseif ( method_exists( $class, 'prepare_from_instance' ) ) {
			$normalized = $instance;
			if ( method_exists( $class, 'normalize_instance' ) ) {
				$normalized = $class::normalize_instance( $instance );
			}
			$flat = $class::prepare_from_instance( $normalized );
			if ( is_array( $flat ) && ! self::looks_like_block_attributes( $registry_id, $flat ) ) {
				$flat = self::collect_block_attribute_values( $class, $flat );
			}
		} elseif ( method_exists( $class, 'instance_from_atts' ) ) {
			$structured = $class::instance_from_atts( $instance );
			$flat       = self::collect_block_attribute_values( $class, $structured );
		} else {
			$flat = self::collect_block_attribute_values( $class, $instance );
		}

		return self::pick_block_attributes( $class, is_array( $flat ) ? $flat : array() );
	}

	/**
	 * @param string               $registry_id Widget registry id.
	 * @param array<string, mixed> $attributes  Block attributes.
	 * @return array<string, mixed>
	 */
	public static function from_block_attributes( $registry_id, array $attributes ) {
		$class = Rsmspark_Content_Widgets_Widget_Json_Registry::render_class_for( $registry_id );
		if ( $class && method_exists( $class, 'instance_from_atts' ) ) {
			return $class::instance_from_atts( $attributes );
		}

		return array( 'content' => $attributes );
	}

	/**
	 * @param string               $registry_id Widget registry id.
	 * @param array<string, mixed> $instance    Current SO instance.
	 * @param array<string, mixed> $config      Import config.
	 * @return array<string, mixed>
	 */
	public static function merge_siteorigin_instance( $registry_id, array $instance, array $config ) {
		$registry_id = sanitize_key( (string) $registry_id );
		$dedicated   = Rsmspark_Content_Widgets_Widget_Json_Registry::dedicated_handler_class( $registry_id );

		if ( $dedicated && method_exists( $dedicated, 'merge_siteorigin_instance' ) ) {
			return $dedicated::merge_siteorigin_instance( $instance, $config );
		}

		unset( $config['import_export'] );
		$current = self::sanitize_instance( $registry_id, $instance );
		$config  = self::sanitize_instance( $registry_id, $config );

		if ( self::looks_like_block_attributes( $registry_id, $config ) ) {
			$config = self::distribute_flat_attrs_to_so_instance( $current, $config, $registry_id );
		}

		return array_replace_recursive( $current, $config );
	}

	/**
	 * @param string               $registry_id Widget registry id.
	 * @param array<string, mixed> $config      Sanitized config.
	 * @return string
	 */
	private static function default_export_label( $registry_id, array $config ) {
		$defs = Rsmspark_Content_Widgets_Registry::definitions();
		if ( isset( $defs[ $registry_id ]['label'] ) ) {
			return (string) $defs[ $registry_id ]['label'];
		}

		unset( $config );
		return ucwords( str_replace( '_', ' ', $registry_id ) );
	}

	/**
	 * @param string               $registry_id Widget registry id.
	 * @param array<string, mixed> $payload     Payload fragment.
	 * @return bool
	 */
	private static function looks_like_siteorigin_instance( $registry_id, array $payload ) {
		if ( isset( $payload['import_export'] ) ) {
			return true;
		}

		unset( $registry_id );
		foreach ( $payload as $key => $value ) {
			if ( in_array( $key, array( 'plugin', 'widget', 'block', 'version', 'exported_at', 'label', 'attributes', 'config' ), true ) ) {
				continue;
			}
			if ( is_array( $value ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @param string               $registry_id Widget registry id.
	 * @param array<string, mixed> $payload     Payload fragment.
	 * @return bool
	 */
	private static function looks_like_block_attributes( $registry_id, array $payload ) {
		$class = Rsmspark_Content_Widgets_Widget_Json_Registry::render_class_for( $registry_id );
		if ( ! $class || ! method_exists( $class, 'block_attributes' ) ) {
			return false;
		}

		$keys    = array_keys( $class::block_attributes() );
		$matches = 0;
		foreach ( $keys as $key ) {
			if ( array_key_exists( $key, $payload ) ) {
				++$matches;
			}
		}

		return $matches >= max( 1, (int) floor( count( $keys ) / 4 ) );
	}

	/**
	 * @param class-string         $class Render widget class.
	 * @param array<string, mixed> $data  Nested instance data.
	 * @return array<string, mixed>
	 */
	private static function collect_block_attribute_values( $class, array $data ) {
		$keys  = array_keys( $class::block_attributes() );
		$found = array();
		self::walk_block_attribute_values( $data, $keys, $found );
		return $found;
	}

	/**
	 * @param mixed                $data  Nested data.
	 * @param string[]             $keys  Block attribute keys.
	 * @param array<string, mixed> $found Collected values.
	 * @return void
	 */
	private static function walk_block_attribute_values( $data, array $keys, array &$found ) {
		if ( ! is_array( $data ) ) {
			return;
		}

		foreach ( $data as $key => $value ) {
			if ( in_array( $key, $keys, true ) && ! array_key_exists( $key, $found ) ) {
				$found[ $key ] = $value;
			}
			if ( is_array( $value ) ) {
				self::walk_block_attribute_values( $value, $keys, $found );
			}
		}
	}

	/**
	 * @param class-string         $class Render widget class.
	 * @param array<string, mixed> $flat  Candidate flat values.
	 * @return array<string, mixed>
	 */
	private static function pick_block_attributes( $class, array $flat ) {
		$schema = $class::block_attributes();
		$out    = array();

		foreach ( $schema as $key => $definition ) {
			if ( array_key_exists( $key, $flat ) ) {
				$out[ $key ] = $flat[ $key ];
			} elseif ( array_key_exists( 'default', $definition ) ) {
				$out[ $key ] = $definition['default'];
			}
		}

		return $out;
	}

	/**
	 * @param array<string, mixed> $instance    Current SO instance.
	 * @param array<string, mixed> $attrs       Flat block attributes.
	 * @param string               $registry_id Widget registry id.
	 * @return array<string, mixed>
	 */
	private static function distribute_flat_attrs_to_so_instance( array $instance, array $attrs, $registry_id ) {
		$class = Rsmspark_Content_Widgets_Widget_Json_Registry::render_class_for( $registry_id );
		if ( ! $class || ! method_exists( $class, 'block_attributes' ) ) {
			return array_replace_recursive( $instance, array( 'content' => $attrs ) );
		}

		$keys   = array_keys( $class::block_attributes() );
		$result = $instance;

		foreach ( $attrs as $key => $value ) {
			if ( ! in_array( $key, $keys, true ) ) {
				continue;
			}

			$section = self::guess_section_for_field( $result, $key );
			if ( ! isset( $result[ $section ] ) || ! is_array( $result[ $section ] ) ) {
				$result[ $section ] = array();
			}
			$result[ $section ][ $key ] = $value;
		}

		return $result;
	}

	/**
	 * @param array<string, mixed> $instance SiteOrigin instance.
	 * @param string               $field    Field key.
	 * @return string
	 */
	private static function guess_section_for_field( array $instance, $field ) {
		foreach ( $instance as $section => $data ) {
			if ( 'import_export' === $section || ! is_array( $data ) ) {
				continue;
			}
			if ( array_key_exists( $field, $data ) ) {
				return (string) $section;
			}
		}

		return 'content';
	}
}
