<?php
/**
 * Immutable provider version requirements.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Provider;

// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are escaped at UI boundaries.

/**
 * Captures and revalidates exact provider versions used to build a plan.
 */
final class ProviderRequirementSet {
	/**
	 * @var array<string, string>
	 */
	private array $versions;
	/**
	 * @param array<string,string> $versions Required versions keyed by provider ID.
	 */
	public function __construct( array $versions ) {
		$this->versions = $versions;
	}

	public static function capture( ProviderRegistry $providers ): self {
		return new self( $providers->versions() );
	}

	/**
	 * Captures only providers referenced by an immutable plan.
	 *
	 * @param string[] $providerIds Referenced provider IDs.
	 */
	public static function captureIds( ProviderRegistry $providers, array $providerIds ): self {
		$installed   = $providers->versions();
		$required    = array();
		$providerIds = array_values( array_unique( $providerIds ) );
		sort( $providerIds );

		foreach ( $providerIds as $providerId ) {
			if ( ! isset( $installed[ $providerId ] ) ) {
				throw new \InvalidArgumentException( 'Cannot capture missing provider: ' . $providerId );
			}

			$required[ $providerId ] = $installed[ $providerId ];
		}

		return new self( $required );
	}

	/**
	 * @return array<string,string>
	 */
	public function toArray(): array {
		return $this->versions;
	}

	public function validate( ProviderRegistry $providers ): ProviderRequirementReport {
		$installed = $providers->versions();
		$missing   = array();
		$changed   = array();

		foreach ( $this->versions as $id => $required ) {
			if ( ! isset( $installed[ $id ] ) ) {
				$missing[] = $id;
				continue;
			}

			if ( $required !== $installed[ $id ] ) {
				$changed[ $id ] = array(
					'required'  => $required,
					'installed' => $installed[ $id ],
				);
			}
		}

		sort( $missing );
		ksort( $changed );

		return new ProviderRequirementReport( $missing, $changed );
	}
}
