<?php
/**
 * Helper for the complete deployer config from the repository.
 *
 * @package Ealab\Deployer
 */

namespace Ealab\Deployer\Utility\DeployerConfigRepository;

use Dakwamine\Component\ComponentBasedObject;
use Ealab\Deployer\Exception\EaLabWebServiceException;
use Ealab\Deployer\Exception\LicenceException;
use Ealab\Deployer\Exception\LicenceKeyUnsetException;
use Ealab\Deployer\Exception\WpDeployException;

/**
 * Helper for the complete deployer config from the repository, at /api/v1/deployer/config.
 */
class DeployerConfig {
	/**
	 * The configuration if already retrieved.
	 *
	 * @var array|null
	 */
	private static ?array $config = null;

	/**
	 * The licence status attached to the config on response.
	 *
	 * @var array|null
	 */
	private static ?array $licence_status = null;

	/**
	 * Filters the config.
	 *
	 * @param array|mixed                  $to_filter The config to filter.
	 * @param array<ConfigFilterInterface> $filters The filters to apply to the configuration.
	 *     See getConfig() for more details.
	 */
	private static function filterConfig( &$to_filter, array $filters = array() ): void {
		foreach ( $filters as $filter ) {
			if ( is_array( $filter ) ) {
				// Class + args array.
				$filter_class = $filter[0];
				$filter_args  = $filter[1] ?? null;
			} else {
				// Class only.
				$filter_class = $filter;
				$filter_args  = null;
			}

			/** @var ConfigFilterInterface $filter_class */
			$to_filter = $filter_class::filter( $to_filter, $filter_args );
		}
	}

	/**
	 * Gets the complete deployer configuration from the repository, at /api/v1/deployer/config.
	 *
	 * @param string                                                                       $config_type The
	 *     configuration type. For example, 'tracker', 'value_handler.option'.
	 * @param array<ConfigFilterInterface|array<ConfigFilterInterface,array<string,mixed>> $filters The filters to
	 *     apply to the configuration. Two types of values are accepted:
	 *     - string: the filter class name (implementing ConfigFilterInterface), used with the default options.
	 *     - array: array containing the filter class name on index 0 and the filter options as array on index 1.
	 *
	 * @return array<string,mixed> The configuration for the given $config_type and filtered by $filters.
	 *     Each config type has its own structure.
	 *
	 * @throws EaLabWebServiceException Any error due to EA Lab web services.
	 * @throws LicenceException If the licence is invalid.
	 * @throws LicenceKeyUnsetException If the licence key is not set.
	 * @throws WpDeployException If the repository could not be reached or the response is invalid.
	 */
	public static function getConfig( string $config_type, array $filters = array() ): array {
		if ( null !== static::$config ) {
			// Already retrieved.
			$to_filter = static::$config[ $config_type ] ?? array();

			static::filterConfig( $to_filter, $filters );

			return $to_filter;
		}

		// Get the repository configuration.
		$config_provider_class = apply_filters( 'deploy_sync_content__get_config_repository', null );

		$error_message = esc_html__('No config repository available. Please enable a config provider, or enable EA Lab\'s automatic configuration service.');

		if ( empty( $config_provider_class ) ) {
			throw new WpDeployException( $error_message );
		}

		$config_provider = ComponentBasedObject::getRootComponentByClassName( $config_provider_class, true );

		if ( empty( $config_provider ) ) {
			throw new WpDeployException( $error_message );
		}

		$decoded = $config_provider->getConfig();

		// We are not doing any deep validation here, because the repository is supposed to be trusted.
		static::$config = $decoded['deployerConfig'];

		// The licence status comes with the config; let's keep this for later use by the licence manager.
		static::$licence_status = $decoded['licenceStatus'] ?? null;

		$to_filter = static::$config[ $config_type ] ?? array();

		static::filterConfig( $to_filter, $filters );

		return $to_filter;
	}

	/**
	 * Gets the licence status.
	 *
	 * @return array|null The licence status.
	 *
	 * @throws EaLabWebServiceException Web service error.
	 * @throws LicenceException Problem on licence.
	 * @throws LicenceKeyUnsetException Unset licence key.
	 * @throws WpDeployException Other plugin errors.
	 */
	public static function getLicenceStatus(): ?array {
		// Call the web service at least once.
		static::getConfig( '' );

		return static::$licence_status;
	}
}
