<?php
/**
 * Simple dependency injection container.
 *
 * @package Carticy\CheckoutShield\Core
 */

declare(strict_types=1);

namespace Carticy\CheckoutShield\Core;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Simple DI container for managing service instances.
 */
final class Container {

	/**
	 * Service factory callbacks.
	 *
	 * @var array<string, callable>
	 */
	private array $factories = array();

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

	/**
	 * Register a service factory.
	 *
	 * @param string   $class_name The fully qualified class name.
	 * @param callable $factory    Factory callback that returns the service instance.
	 */
	public function register( string $class_name, callable $factory ): void {
		$this->factories[ $class_name ] = $factory;
	}

	/**
	 * Get a service instance.
	 *
	 * @template T
	 * @param class-string<T> $class_name The service class name.
	 * @return T
	 * @throws \InvalidArgumentException If service is not registered.
	 */
	public function get( string $class_name ): object {
		// Return cached instance if exists.
		if ( isset( $this->instances[ $class_name ] ) ) {
			return $this->instances[ $class_name ];
		}

		// Check if factory is registered.
		if ( ! isset( $this->factories[ $class_name ] ) ) {
			throw new \InvalidArgumentException(
				// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not HTML output.
				sprintf( 'Service "%s" is not registered in the container.', $class_name )
			);
		}

		// Create and cache the instance.
		$this->instances[ $class_name ] = ( $this->factories[ $class_name ] )();

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

	/**
	 * Check if a service is registered.
	 *
	 * @param string $class_name The service class name.
	 * @return bool
	 */
	public function has( string $class_name ): bool {
		return isset( $this->factories[ $class_name ] );
	}
}
