<?php
/**
 * Schedules module initialization by boot tier.
 *
 * @package WP_PowerSuite\Core
 * @since 1.0.0
 */

namespace WP_PowerSuite\Core;

use WP_PowerSuite\Bootstrap\Kernel;

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

/**
 * Defers module hook registration until the appropriate WordPress lifecycle hook.
 */
final class ModuleBootScheduler {

	private static ?self $instance = null;

	/** @var array<string, true> */
	private array $scheduled = [];

	public static function getInstance(): self {
		if ( self::$instance === null ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Schedule or run module initialization.
	 *
	 * @return bool True when init ran immediately; false when deferred.
	 */
	public function schedule( string $module_id, string $boot_tier, callable $init_callback, bool $force_immediate = false ): bool {
		$boot_tier = $this->normalizeTier( $boot_tier );

		if ( $force_immediate || $boot_tier === 'critical' || ! $this->shouldDefer( $boot_tier ) ) {
			$init_callback();
			return true;
		}

		[ $hook ] = $this->resolveHook( $boot_tier );
		if ( did_action( $hook ) ) {
			$init_callback();
			return true;
		}

		if ( isset( $this->scheduled[ $module_id ] ) ) {
			return false;
		}

		$this->scheduled[ $module_id ] = true;

		[ $hook, $priority ] = $this->resolveHook( $boot_tier );

		add_action(
			$hook,
			static function () use ( $init_callback ): void {
				$init_callback();
			},
			$priority
		);

		return false;
	}

	private function normalizeTier( string $boot_tier ): string {
		$boot_tier = sanitize_key( $boot_tier );
		$valid     = [ 'critical', 'standard', 'display', 'admin' ];

		return in_array( $boot_tier, $valid, true ) ? $boot_tier : 'critical';
	}

	/**
	 * @return array{0: string, 1: int}
	 */
	private function resolveHook( string $boot_tier ): array {
		return match ( $boot_tier ) {
			'standard' => [ 'init', 10 ],
			'display'  => [ 'wp', 1 ],
			'admin'    => [ 'admin_menu', 0 ],
			default    => [ 'init', 10 ],
		};
	}

	/**
	 * Whether deferred boot is appropriate for the current request context.
	 */
	private function shouldDefer( string $boot_tier ): bool {
		try {
			$kernel = Kernel::getInstance();
		} catch ( \Throwable $e ) {
			return $boot_tier !== 'critical';
		}

		// admin-ajax.php and admin-post.php do not run admin_menu, wp, or other deferred lifecycle hooks.
		if ( $kernel->isAjax() || $kernel->isAdminPost() ) {
			return false;
		}

		return match ( $boot_tier ) {
			'admin'    => $kernel->isAdmin(),
			'display'  => $kernel->isFrontend(),
			'standard' => true,
			default    => true,
		};
	}
}
