<?php
/**
 * Simple configuration collector.
 *
 * @package Ealab\Deployer
 */

namespace EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\Config;

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

/**
 * Simple configuration collector.
 *
 * Use this component to get the configuration from configuration provider managers,
 * especially those that collect plugin dependent configuration (config are nested in a plugin slug key),
 * and require merging in a complete configuration.
 * Using this component is not mandatory. E.g. the tracker configuration has a dedicated collector.
 * To use it, inject the configuration provider manager and the configuration collection ID.
 */
class ConfigCollector extends ComponentBasedObject {
	/*
	 * The strategy to use when merging two collected definitions for the same value.
	 *
	 * E.g. "value_handler.option" uses the "replace" strategy because we don't want the
	 * values to cumulate.
	 * In the other hand, we want to cumulate definitions for the "exclusions" config.
	 */
	public const CONFIG_MERGE_STRATEGY__CUMULATE = 'CONFIG_MERGE_STRATEGY__CUMULATE';
	public const CONFIG_MERGE_STRATEGY__REPLACE  = 'CONFIG_MERGE_STRATEGY__REPLACE';

	/**
	 * The collected configuration, keyed by config_collection_id.
	 *
	 * @var array<string,array>
	 */
	private array $collected_config = array();

	/**
	 * Whether the configuration have been collected.
	 *
	 * @var array<string,bool>
	 */
	private array $has_collected_definitions = array();

	/**
	 * Collects the configuration.
	 *
	 * @param ConfigProviderManagerBase $config_provider_manager The configuration provider manager to work with.
	 * @param string                    $config_collection_id The ID that will be used for identifying collection
	 *     requests and allowing alteration mechanism. E.g. the option value handler uses "value_handler__option",
	 *     which will create the "deploy_sync_content__config_collector__value_handler__option" filter. It must contain only
	 *     lowercase letters, numbers and underscores.
	 *
	 * @return array The collected configuration.
	 *
	 * @throws InvalidConfigException If the configuration is invalid.
	 * @throws WpDeployException Error during configuration collection.
	 */
	public function collect( ConfigProviderManagerBase $config_provider_manager, string $config_collection_id ): array {
		if ( $this->has_collected_definitions[ $config_collection_id ] ?? false ) {
			return $this->collected_config[ $config_collection_id ];
		}

		$this->has_collected_definitions[ $config_collection_id ] = true;

		$this->collected_config[ $config_collection_id ] = array();

		// Collect the configuration.
		$active_config = $config_provider_manager->getActiveConfig();

		$config_merge_strategy = $config_provider_manager::getConfigMergeStrategy();

		foreach ( $active_config as $plugin_level_config_value ) {
			// Get rid of the plugin key and deep merge the config into the final config.
			if ( static::CONFIG_MERGE_STRATEGY__CUMULATE === $config_merge_strategy ) {
				// Perform a "CUMULATE" strategy.
				// The config leafs are merged.
				$this->collected_config[ $config_collection_id ] = array_merge_recursive(
					$this->collected_config[ $config_collection_id ],
					$plugin_level_config_value
				);

				continue;
			}

			// Use the "REPLACE" strategy be default.
			// When multiple plugins are enabled and share the same
			// option name, the last plugin's config will overwrite
			// the previous ones. This can look very messy, but let's not
			// forget that this is a very rare case, and that this would
			// break the site anyway.
			$this->collected_config[ $config_collection_id ] = array_replace_recursive(
				$this->collected_config[ $config_collection_id ],
				$plugin_level_config_value
			);
		}

		// Allow third-party script to alter the configuration of the value handler in real-time.
		// The result is not saved in any storage, and is only used for the current request.
		$this->collected_config[ $config_collection_id ] = apply_filters( 'deploy_sync_content__config_collector__' . $config_collection_id, $this->collected_config[ $config_collection_id ] );

		return $this->collected_config[ $config_collection_id ];
	}
}
