<?php
/**
 * Parses the given value to translate local values to their exported representation, and vice versa.
 *
 * @package Ealab\Deployer
 */

namespace EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor;

use EaLab\WP_Deployer\vendor\Dakwamine\Component\Exception\UnmetDependencyException;
use EaLab\WP_Deployer\vendor\Dakwamine\Component\RootDependencyDefinition;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Exception\IgnoredTransmutationException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Exception\WpDeployException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ItemValueHandlerBase;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler\TransmutationHandlerBase;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler\TransmutationHandlerManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\TrackingManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\DataWalker;
use EaLab\WP_Deployer\vendor\Symfony\Component\Yaml\Yaml;

/**
 * Parses the given value to translate local values to their exported representation, and vice versa.
 */
class TransmutationProcessor extends ValueProcessorBase {
	/**
	 * Tracking manager.
	 *
	 * @var TrackingManager|null
	 */
	private ?TrackingManager $tracking_manager = null;

	/**
	 * {@inheritDoc}
	 */
	public function getDependencyDefinitions(): array {
		$definitions   = parent::getDependencyDefinitions();
		$definitions[] = new RootDependencyDefinition( TrackingManager::class, $this->tracking_manager );

		return $definitions;
	}

	/**
	 * {@inheritDoc}
	 */
	public static function getFormBuild(): array {
		$build = parent::getFormBuild();

		$raw_build = <<<YAML
buttons:
  load: processor_item_action_buttons_wrapper
  keepTemplateContext: true
  content:
    - load: processor_item_edit_button
      keepTemplateContext: true
    - load: processor_item_three_action_buttons
      keepTemplateContext: true
modal:
  type: Modal
  attributes:
    style:
      zIndex: 1050 # Modal backdrop fix.
  closeButton: true
  showBoolPath: ~._openEditModal
  headerTitle:
    - Edit this processor
  body:
    data_walker_pattern:
      label:
        load: field_label
        content: Data walker pattern
      field:
        type: TextField
        dataLocation: ~.data_walker_pattern
    transmutation_handler:
      label:
        load: field_label
        content: Transmutation handler
      field:
        type: SelectField
        dataLocation: ~.transmutation_handler
        dynamicOptions: ~~.transmutationHandlers
        actions:
          - # Remove the previous handler options.
            what: setData
            on: change
            path: ~.transmutation_handler_options
YAML;

		$parsed = Yaml::parse( $raw_build );

		$build[1]['content'] = $parsed;

		return $build;
	}

	/**
	 * {@inheritDoc}
	 */
	public static function getId(): string {
		return 'transmutation';
	}

	/**
	 * {@inheritDoc}
	 */
	public static function getLabel(): string {
		return _dpgettext( 'deployer-core', 'processor label', 'Transmutation' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function process( $value, int $operation, ?callable $progress_callback = null ) {
		$transmutation_handler_class = TransmutationHandlerManager::getClassById( $this->config['transmutation_handler'] ?? '' );

		/** @var array<string,mixed> $transmutation_handler_options */
		$transmutation_handler_options = $this->config['transmutation_handler_options'] ?? array();

		// Pattern to use to walk the data.
		$data_walker_pattern = $this->config['data_walker_pattern'] ?? '';

		if ( empty( $data_walker_pattern ) ) {
			return $value;
		}

		// Send context info that may be useful for handlers.
		$transmutation_handler_options['_value_processor']    = $this;
		$transmutation_handler_options['_item_value_handler'] = $this->holder;

		try {
			/** @var TransmutationHandlerBase $transmutation_handler */
			$transmutation_handler = $this->getComponentByClassName( $transmutation_handler_class, true );
		} catch ( UnmetDependencyException $e ) {
			// The handler class is not available. We cannot transmute the value.
			throw new WpDeployException( 'The transmutation handler is not available.' );
		}

		if ( ! $transmutation_handler instanceof TransmutationHandlerBase ) {
			throw new WpDeployException( 'The given transmutation handler class name is not an instance of TransmutationHandlerBase.' );
		}

		// Deserialize the item. We expect the incoming value to be safe.
		// The WP core function `maybe_unserialize` is used to deserialize the value if serialized.
		$deserialized = maybe_unserialize( $value );

		$serialization_method = $deserialized !== $value ? 'php' : 'none';

		// Try with the JSON method.
		if ( 'none' === $serialization_method ) {
			// Do not decode to associative array to preserve the original data structure.
			$deserialized_json = json_decode( $value );

			if ( JSON_ERROR_NONE === json_last_error() ) {
				$serialization_method = 'json';
				$deserialized         = $deserialized_json;
			}

			// TODO: allow other serialization methods (XML?). Serialization methods must be reversible.
		}

		// This is also the operation to use when we want to compare the values.
		DataWalker::walkAndApply(
			$deserialized,
			$data_walker_pattern,
			function ( &$found_value ) use ( $transmutation_handler, $transmutation_handler_options, $operation ) {
				try {
					if ( ItemValueHandlerBase::OPERATION__EXPORT === $operation ) {
						$found_value = $transmutation_handler->toExport( $found_value, $transmutation_handler_options );
					} elseif ( ItemValueHandlerBase::OPERATION__IMPORT__PRESAVE === $operation ) {
						$found_value = $transmutation_handler->toImportPresave( $found_value, $transmutation_handler_options );
					} elseif ( ItemValueHandlerBase::OPERATION__IMPORT__FINALIZE === $operation ) {
						$found_value = $transmutation_handler->toImportFinalize( $found_value, $transmutation_handler_options );
					} else {
						// The operation is not supported.
						throw new WpDeployException( 'The operation is not supported.' );
					}
				} catch ( IgnoredTransmutationException $e ) {
					// The transmutation handler doesn't want to transmute the value.
					// We will keep the original value.
					return;
				}
			}
		);

		switch ( $serialization_method ) {
			case 'php':
				$value = serialize( $deserialized );
				break;

			case 'json':
				$value = json_encode( $deserialized );
				break;

			default:
				// The value was not serialized.
				$value = $deserialized;
				break;
		}

		return $value;
	}
}
