<?php
/**
 * Dependency injection container with lazy singleton resolution.
 *
 * @package MonitorCat
 */

declare(strict_types=1);

namespace MonitorCat;

defined( 'ABSPATH' ) || exit;

use RuntimeException;

/**
 * Simple DI container that resolves services as lazy singletons.
 */
final class Container {

	/**
	 * Service factory closures.
	 *
	 * @var array<string, \Closure>
	 */
	private array $factories = array();

	/**
	 * Resolved service instances.
	 *
	 * @var array<string, object>
	 */
	private array $instances = array();

	/**
	 * Register a service factory as a singleton.
	 *
	 * @param string   $id      Service identifier (typically a class-string).
	 * @param \Closure $factory Factory closure receiving this container.
	 */
	public function singleton( string $id, \Closure $factory ): void {
		$this->factories[ $id ] = $factory;
	}

	/**
	 * Resolve and return a service instance.
	 *
	 * @param string $id Service identifier.
	 * @return object Resolved service.
	 *
	 * @throws RuntimeException If service is not registered.
	 */
	public function get( string $id ): object {
		if ( ! isset( $this->instances[ $id ] ) ) {
			if ( ! isset( $this->factories[ $id ] ) ) {
				throw new RuntimeException(
					// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception, not rendered to browser.
					sprintf( 'Service not registered: %s', $id )
				);
			}

			$instance = ( $this->factories[ $id ] )( $this );

			if ( ! is_object( $instance ) ) {
				throw new RuntimeException(
					// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception, not rendered to browser.
					sprintf( 'Factory for %s must return an object.', $id )
				);
			}

			$this->instances[ $id ] = $instance;
		}

		return $this->instances[ $id ];
	}

	/**
	 * Check whether a service is registered.
	 *
	 * @param string $id Service identifier.
	 * @return bool
	 */
	public function has( string $id ): bool {
		return isset( $this->factories[ $id ] ) || isset( $this->instances[ $id ] );
	}
}
