<?php
/**
 * Transmutes Elementor Pro condition values.
 *
 * @package Ealab\Deployer
 */

namespace Ealab\Deployer\Tracking\ItemValueHandler\ValueProcessor\TransmutationHandler;

use Dakwamine\Component\RootDependencyDefinition;
use Ealab\Deployer\Exception\IgnoredTransmutationException;
use Ealab\Deployer\Exception\InvalidDeploymentDataForImportException;
use Ealab\Deployer\Exception\WpDeployException;
use Ealab\Deployer\Tracking\ItemTracker\ItemTrackerBase;
use Ealab\Deployer\Tracking\ItemValueHandler\ItemValueHandlerBase;
use Ealab\Deployer\Tracking\TrackingManager;
use ElementorPro\Modules\ThemeBuilder\Classes\Conditions_Manager;
use ElementorPro\Modules\ThemeBuilder\Conditions\Author;
use ElementorPro\Modules\ThemeBuilder\Conditions\Post;
use ElementorPro\Modules\ThemeBuilder\Conditions\Taxonomy;
use ElementorPro\Modules\ThemeBuilder\Module;

/**
 * Transmutes Elementor Pro condition values.
 */
class ElementorProConditionTransmutationHandler extends TransmutationHandlerBase {
	/**
	 * 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 getId(): string {
		return 'elementor_pro_condition';
	}

	/**
	 * Gets the ItemTrackerBase instance for the given condition type.
	 *
	 * @param string $condition_type The condition type. E.g. "post", "author", taxonomy".
	 *
	 * @return ItemTrackerBase|null The ItemTrackerBase instance or null if no match was found.
	 *
	 * @throws IgnoredTransmutationException If Elementor Pro is not installed.
	 */
	private function getMatchingTracker( string $condition_type ): ?ItemTrackerBase {
		$tracker_id = static::getMatchingTrackerId( $condition_type );

		if ( ! $tracker_id ) {
			return null;
		}

		return $this->tracking_manager->getItemTrackerByTrackerId( $tracker_id );
	}

	/**
	 * Gets the tracker ID that matches the condition type.
	 *
	 * @param string $condition_type The condition type. E.g. "post", "author", taxonomy".
	 *
	 * @return string|null The tracker name or null if no match was found.
	 *
	 * @throws IgnoredTransmutationException If Elementor Pro is not installed.
	 */
	private static function getMatchingTrackerId( string $condition_type ): ?string {
		if ( ! static::isElementorProInstalled() ) {
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro', 'Elementor Pro is not enabled.' ) );
		}

		/** @var Conditions_Manager $condition_manager */
		$condition_manager = Module::instance()->get_component( 'conditions' );

		$condition = $condition_manager->get_condition( $condition_type );

		if ( ! $condition ) {
			return null;
		}

		// TODO: allow other plugins to augment this mapping with their own classes.
		// This allows inherited conditions to work.
		$class_to_tracker_mapping = array(
			Author::class   => 'users',
			Post::class     => 'posts',
			Taxonomy::class => 'terms',
		);

		foreach ( $class_to_tracker_mapping as $class_candidate => $tracker_id ) {
			if ( ! $condition instanceof $class_candidate ) {
				continue;
			}

			return $tracker_id;
		}

		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	static public function getPublicName(): string {
		return _dpgettext( 'deployer-core', 'Transmutation handler public name', 'Elementor Pro conditions' );
	}

	/**
	 * Whether Elementor Pro is installed.
	 *
	 * @return bool True if Elementor Pro is installed, false otherwise.
	 */
	private static function isElementorProInstalled(): bool {
		return defined( 'ELEMENTOR_PRO_VERSION' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function toExport( $value, ?array $options = array() ) {
		if ( ! static::isElementorProInstalled() ) {
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro', 'Cannot transmute the value because Elementor Pro is not enabled.' ) );
		}

		if ( ! is_string( $value ) ) {
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro', 'Only strings can be transmuted by the Elementor Pro condition processor.' ) );
		}

		/*
		 * Examples of elementor conditions:
		 * - `include/general`
		 * - `include/singular/post`
		 * - `include/archive`
		 * - `exclude/singular/page/6490`
		 * - `include/product`
		 * - `include/product_archive`
		 * - `include/singular/not_found404`
		 *
		 * The ones we are interested in are the singular ones that have a local ID at the end.
		 */
		$condition_parts = explode( '/', $value );

		if ( count( $condition_parts ) < 4 ) {
			// Singular conditions with local IDs have at least 4 parts.
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro', 'Nothing to transmute.' ) );
		}

		if ( ! filter_var( $condition_parts[ count( $condition_parts ) - 1 ], FILTER_VALIDATE_INT ) ) {
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro condition', 'Nothing to transmute.' ) );
		}

		$local_id = array_pop( $condition_parts );

		$condition_type = array_pop( $condition_parts );

		$maybe_singular = array_pop( $condition_parts );

		if ( 'singular' !== $maybe_singular ) {
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro condition', 'Nothing to transmute.' ) );
		}

		// Match the condition type to trackers.
		$tracker = static::getMatchingTracker( $condition_type );

		if ( ! $tracker ) {
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro condition', 'Data to transmute leads to an unknown item tracker.' ) );
		}

		try {
			$item_full_uuid = $tracker->getItemUuidByLocalId( $local_id, true );

			if ( empty( $item_full_uuid ) ) {
				throw new WpDeployException();
			}
		} catch ( WpDeployException $e ) {
			// A technical issue prevented us from getting the UUID.
			// This says that the data is inherently wrong, and we cannot do anything about it.
			// We then allow the data to be exported as it would without transmutation.
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro condition', 'The local ID to transmute has no UUID counterpart.' ) );
		}

		try {
			$referenced_trackable_item = $tracker->loadTrackableItem( $item_full_uuid );

			if ( empty( $referenced_trackable_item ) ) {
				throw new WpDeployException();
			}
		} catch ( WpDeployException $e ) {
			// Cannot find the item by UUID. This is a technical issue that we cannot do anything about.
			throw new IgnoredTransmutationException( sprintf( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro condition', 'The item with UUID %s could not be loaded.' ), $item_full_uuid ) );
		}

		// Add the dependency to the TrackableItem holder.
		$calling_item_value_handler = $options['_item_value_handler'] ?? null;

		if ( $calling_item_value_handler instanceof ItemValueHandlerBase ) {
			$calling_item_value_handler->addItemDependency( $referenced_trackable_item );
		}

		// Recompose the value with the first parts.
		$condition_parts[] = $maybe_singular;
		$condition_parts[] = $condition_type;
		$condition_parts[] = $item_full_uuid;

		$final_value_to_encapsulate = implode( '/', $condition_parts );

		// Encapsulate the transmuted value in the processor placeholder.
		return static::getProcessorPlaceholder( $final_value_to_encapsulate );
	}

	/**
	 * {@inheritDoc}
	 */
	public function toImportFinalize( $value, ?array $options = array() ) {
		// Dev note: we are not checking that Elementor Pro is active because we don't need it here.
		$placeholder_value = static::isProcessorPlaceholder( $value );

		if ( ! is_string( $placeholder_value ) ) {
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro', 'Only strings can be transmuted by the Elementor Pro condition processor.' ) );
		}

		// Replace the last part (a full UUID) by 0, a temporary local ID.
		$condition_parts = explode( '/', $placeholder_value );
		$item_full_uuid  = array_pop( $condition_parts );

		// Get the tracker and local ID.
		$tracker = $this->tracking_manager->getItemTrackerByItemFullUuid( $item_full_uuid );

		if ( empty( $tracker ) ) {
			throw new InvalidDeploymentDataForImportException( sprintf( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro condition', 'An Elementor Pro condition pointed to the item %s, but this item\'s tracker could not be found.' ), $item_full_uuid ) );
		}

		$local_id = $tracker->getLocalIdFromUuid( $item_full_uuid );

		if ( empty( $local_id ) ) {
			throw new InvalidDeploymentDataForImportException( sprintf( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro condition', 'An Elementor Pro condition pointed to the item %s, but this item\'s local ID could not be found.' ), $item_full_uuid ) );
		}

		$condition_parts[] = $local_id;

		return implode( '/', $condition_parts );
	}

	/**
	 * {@inheritDoc}
	 */
	public function toImportPresave( $value, ?array $options = array() ) {
		// Dev note: we are not checking that Elementor Pro is active because we don't need it here.
		$placeholder_value = static::isProcessorPlaceholder( $value );

		if ( ! is_string( $placeholder_value ) ) {
			throw new IgnoredTransmutationException( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro', 'Only strings can be transmuted by the Elementor Pro condition processor.' ) );
		}

		// Replace the last part (a full UUID) by 0, a temporary local ID.
		$condition_parts = explode( '/', $placeholder_value );
		$item_full_uuid  = array_pop( $condition_parts );

		// Get the tracker of the referenced item to make sure the tracker exists.
		$tracker = $this->tracking_manager->getItemTrackerByItemFullUuid( $item_full_uuid );

		if ( empty( $tracker ) ) {
			throw new InvalidDeploymentDataForImportException( sprintf( _dpgettext( 'deployer-core', 'Transmutation handler/Elementor Pro condition', 'An Elementor Pro condition pointed to the item %s, but this item\'s tracker could not be found.' ), $item_full_uuid ) );
		}

		$condition_parts[] = '0';

		return implode( '/', $condition_parts );
	}
}
