<?php
/**
 * Manages the value processors.
 *
 * @package Ealab\Deployer
 */

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

use EaLab\WP_Deployer\vendor\Dakwamine\Component\ComponentBasedObject;
use EaLab\WP_Deployer\vendor\Dakwamine\Component\Exception\UnmetDependencyException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Exception\WpDeployException;

/**
 * Manages the value processors.
 */
class ValueProcessorManager extends ComponentBasedObject {
	/**
	 * List of processor classes.
	 *
	 * @var array<string,string> The processor classes, keyed by processor ID.
	 */
	private static array $class_names;

	/**
	 * Appends a processor instance to the given component-based object.
	 *
	 * @param array                $processor_config The processor config. Should contain at least an "id" key.
	 * @param ComponentBasedObject $cbo The component-based object to append the processor instance to.
	 *   Usually an ItemValueHandler instance.
	 *
	 * @return ValueProcessorBase The processor instance.
	 *
	 * @throws WpDeployException If the given config is invalid.
	 */
	public function appendProcessorInstanceTo( array $processor_config, ComponentBasedObject $cbo ): ValueProcessorBase {
		if ( empty( $processor_config['id'] ) ) {
			throw new WpDeployException( 'Invalid processor options: missing ID.' );
		}

		$processor_class = $this->getProcessorClassById( $processor_config['id'] );

		try {
			/** @var ValueProcessorBase $instance */
			$instance = $cbo->addComponentByClassName( $processor_class );
		} catch ( UnmetDependencyException $e ) {
			throw new WpDeployException( 'Cannot instantiate value processor for ID: ' . $processor_config['id'], 0, $e );
		}

		$instance->init( $processor_config, $cbo );

		return $instance;
	}

	/**
	 * Gets the processor class by its ID.
	 *
	 * To register a new processor, use the "deploy_sync_content__value_processor_manager__get_class_names" filter.
	 *
	 * @param string $processor_id The processor ID.
	 *
	 * @return string The processor class name. It extends ValueProcessorBase (checks are performed before the return).
	 *
	 * @throws WpDeployException If the processor ID is invalid.
	 */
	private function getProcessorClassById( string $processor_id ): string {
		if ( empty( $processor_id ) ) {
			throw new WpDeployException( 'The processor ID cannot be empty.' );
		}

		if ( empty( static::$class_names ) ) {
			static::$class_names = apply_filters( 'deploy_sync_content__value_processor_manager__get_class_names', array() );
		}

		$class_name = static::$class_names[ $processor_id ] ?? null;

		if ( empty( $class_name ) ) {
			throw new WpDeployException( sprintf( 'The value processor "%s" does not exist.', $processor_id ) );
		}

		if ( ! is_a( $class_name, ValueProcessorBase::class, true ) ) {
			throw new WpDeployException( 'Invalid processor class for ID: ' . $processor_id );
		}

		return $class_name;
	}

	/**
	 * Prepends a processor instance to the given component-based object.
	 *
	 * @param array                $processor_config The processor config. Should contain at least an "id" key.
	 * @param ComponentBasedObject $cbo The component-based object to prepend the processor instance to.
	 *
	 * @return ValueProcessorBase The processor instance.
	 *
	 * @throws WpDeployException If the given config is invalid, or if we could not prepend the processor.
	 */
	public function prependProcessorInstanceTo( array $processor_config, ComponentBasedObject $cbo ): ValueProcessorBase {
		if ( empty( $processor_config['id'] ) ) {
			throw new WpDeployException( 'Invalid processor options: missing ID.' );
		}

		$processor_class = $this->getProcessorClassById( $processor_config['id'] );

		try {
			// Get the old instances.
			$current_instances = $cbo->getComponentsByClassName( ValueProcessorBase::class );

			// Dereference the old instances.
			$cbo->removeComponentsByClassName( ValueProcessorBase::class );

			// Because we removed the old instances, we need to add them back later,
			// even if the new instance fails to be added.
			$needs_to_add_old = true;

			/** @var ValueProcessorBase $instance */
			$instance = $cbo->addComponentByClassName( $processor_class );
		} catch ( UnmetDependencyException $e ) {
			throw new WpDeployException( 'Cannot instantiate value processor for ID: ' . $processor_config['id'], 0, $e );
		} finally {
			if ( ! empty( $needs_to_add_old ) ) {
				// Add the old instances back.
				foreach ( $current_instances as $current_instance ) {
					$cbo->subComponents[] = $current_instance;
				}
			}
		}

		$instance->init( $processor_config, $cbo );

		return $instance;
	}
}
