<?php
/**
 * Deterministic module runtime.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Extension;

/**
 * Resolves modules before side-effectful WordPress hooks are registered.
 */
final class ModuleRuntime {
	private ModuleRegistry $registry;
	private ?ModuleCatalogReport $report = null;

	private const PHASES = array(
		ModuleDefinition::PHASE_FOUNDATION,
		ModuleDefinition::PHASE_PERSISTENCE,
		ModuleDefinition::PHASE_PROVIDERS,
		ModuleDefinition::PHASE_APPLICATION,
		ModuleDefinition::PHASE_DELIVERY,
	);

	public function __construct( ?ModuleRegistry $registry = null ) {
		$this->registry = $registry ?? new ModuleRegistry();
	}

	public function catalogReport(): ModuleCatalogReport {
		if ( null === $this->report ) {
			$this->report = $this->registry->catalogReport();
		}

		return $this->report;
	}

	/**
	 * Throws when the graph is not safe to boot.
	 */
	public function assertHealthy(): void {
		$report = $this->catalogReport();

		if ( $report->healthy() ) {
			return;
		}

		// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception text is escaped at the UI boundary.
		throw new ModuleRuntimeException( implode( '; ', $report->errors() ) );
	}

	/**
	 * Returns the deterministic module boot order.
	 *
	 * @return ModuleDefinition[]
	 */
	public function bootOrder(): array {
		$ordered = array();

		foreach ( self::PHASES as $phase ) {
			foreach ( $this->catalogReport()->definitions() as $definition ) {
				if ( $phase === $definition->registrationPhase() ) {
					$ordered[] = $definition;
				}
			}
		}

		return $ordered;
	}

	/**
	 * Returns the deterministic module boot order as IDs.
	 *
	 * @return string[]
	 */
	public function bootOrderIds(): array {
		return array_map(
			static fn ( ModuleDefinition $definition ): string => $definition->slug(),
			$this->bootOrder()
		);
	}

	/**
	 * Returns the supported registration phases in boot order.
	 *
	 * @return string[]
	 */
	public function registrationPhases(): array {
		return self::PHASES;
	}

	/**
	 * Returns modules assigned to a registration phase.
	 *
	 * @return ModuleDefinition[]
	 */
	public function definitionsForPhase( string $phase ): array {
		if ( ! in_array( $phase, self::PHASES, true ) ) {
			return array();
		}

		return array_values(
			array_filter(
				$this->bootOrder(),
				static fn ( ModuleDefinition $definition ): bool => $phase === $definition->registrationPhase()
			)
		);
	}
}
