<?php
/**
 * Trait for components which can have values appended.
 *
 * @package Ealab\Deployer
 */

namespace Ealab\Deployer\Tracking;

use Dakwamine\Component\ComponentBucketType;
use Dakwamine\Component\Exception\UnmetDependencyException;
use Ealab\Deployer\Exception\WpDeployException;
use Ealab\Deployer\Tracking\ItemValueHandler\ItemValueHandlerBase;
use Ealab\Deployer\Tracking\ItemValueHandler\ItemValueHandlerManager;

/**
 * Trait for components which can have values appended.
 */
trait FieldStorageTrait {

	/**
	 * The list of fields for this component, keyed by full field name.
	 *
	 * Each field contains a value which must be handled by a value handler.
	 *
	 * @var array<string, ItemValueHandlerBase>
	 */
	protected array $fields = array();

	/**
	 * Sets a value on this component.
	 *
	 * @param TrackableItem    $holder_item The item holding this value.
	 * @param string           $field_name The full name of the field to append the value to.
	 * @param mixed            $value The value to append.
	 * @param int              $origin The origin of the value.
	 * @param FieldConfig|null $field_config The config of the field. If not given, the default config will be used.
	 *
	 * @return ItemValueHandlerBase|null The value handler instantiated for this value.
	 *   Returns null if the field is excluded from tracking.
	 *
	 * @throws WpDeployException Error on missing dependency.
	 */
	public function setValue( TrackableItem $holder_item, string $field_name, $value, int $origin, ?FieldConfig $field_config = null ): ?ItemValueHandlerBase {
		if ( ! empty( $this->fields[ $field_name ] ) ) {
			// Remove the old value handler.
			$this->removeComponent( $this->fields[ $field_name ] );
		}

		// Use the default field config if none are provided.
		$field_config = $field_config ?? new FieldConfig();

		if ( $field_config->isExcluded() ) {
			// This field is excluded from tracking.
			return null;
		}

		$handler_full_id = $field_config->getFullValueHandlerId();

		// Get the item value handler manager, either from the containing component or from the root component.
		if ( empty( $this->item_value_handler_manager ) || ! $this->item_value_handler_manager instanceof ItemValueHandlerManager ) {
			try {
				$item_value_handler_manager = $this->getComponentByClassName( ItemValueHandlerManager::class, true, ComponentBucketType::ROOT );
			} catch ( UnmetDependencyException $e ) {
				throw new WpDeployException(
					'Error on missing dependency for handler ' . $handler_full_id,
					0,
					$e
				);
			}
		} else {
			$item_value_handler_manager = $this->item_value_handler_manager;
		}

		$handler_class_name = $item_value_handler_manager->getHandlerClassNameById( $handler_full_id );

		try {
			/** @var ItemValueHandlerBase $value_handler */
			$value_handler = $this->addComponentByClassName( $handler_class_name );
		} catch ( UnmetDependencyException $e ) {
			throw new WpDeployException(
				'Error on missing dependency for handler ' . $handler_full_id,
				0,
				$e
			);
		}

		$this->fields[ $field_name ] = $value_handler;
		$value_handler->init( $holder_item, $field_name, $value, $origin, $field_config );

		return $value_handler;
	}
}
