<?php
/**
 * PK Core - version-negotiation loader.
 *
 * A deliberately tiny, FROZEN registry shared by every plugin that bundles
 * the PublisherKits Core Library. Each bundle registers its version, root
 * path and requested modules; the loader boots the HIGHEST registered version
 * once. Because this class is guarded by class_exists, the first-loaded
 * bundle's copy of THIS file is the one that runs even when a newer bundle is
 * present - so this class must stay forward-compatible forever.
 *
 * THE FROZEN SURFACE (PK-CORE-SPEC-00 section 3.2 - the complete list, ever):
 *   register(), boot(), is_booted(), active_version(), requested_modules()
 *
 * boot() deliberately requires only ONE payload file from the winning bundle
 * (includes/class-pkcore-core.php) and hands over to it; that file - part of
 * the versioned payload - requires everything else. That is what keeps this
 * loader genuinely frozen: the payload file list can change across versions
 * without ever touching this class.
 *
 * Pattern mirrors Action Scheduler's ActionScheduler_Versions and the
 * AncestorPress ANCP_Core_Loader.
 *
 * @package PublisherKits\Core
 * @since   0.1.0
 */

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

if ( ! class_exists( 'PKCORE_Loader' ) ) {

	/**
	 * Registry + newest-wins boot for the PublisherKits Core Library.
	 *
	 * @since 0.1.0
	 */
	class PKCORE_Loader {

		/**
		 * Registered bundles: version string => absolute core root path.
		 *
		 * @var array<string,string>
		 */
		private static $bundles = array();

		/**
		 * Union of modules requested across all bundles: module => true.
		 *
		 * @var array<string,bool>
		 */
		private static $modules = array();

		/**
		 * Whether the core has been booted this request.
		 *
		 * @var bool
		 */
		private static $booted = false;

		/**
		 * The version that won negotiation and was booted.
		 *
		 * @var string|null
		 */
		private static $active_version = null;

		/**
		 * Register a bundled core version.
		 *
		 * @since 0.1.0
		 * @param string $version Semver-ish version of this bundle's core.
		 * @param string $dir     Absolute path to the bundle's core root (the
		 *                        directory containing includes/).
		 * @param array  $modules Modules the host plugin activates, from
		 *                        {'profile','classify'}. Empty means both.
		 * @return void
		 */
		public static function register( $version, $dir, $modules = array() ) {
			// If two bundles ship the same version, first registration wins;
			// the payload is identical so the path choice is immaterial.
			if ( ! isset( self::$bundles[ $version ] ) ) {
				self::$bundles[ $version ] = $dir;
			}

			$modules = is_array( $modules ) ? $modules : array();
			if ( empty( $modules ) ) {
				$modules = array( 'profile', 'classify' );
			}
			foreach ( $modules as $module ) {
				$module = (string) $module;
				if ( '' !== $module ) {
					self::$modules[ $module ] = true;
				}
			}
		}

		/**
		 * Boot the highest registered core version. Idempotent.
		 *
		 * @since 0.1.0
		 * @return void
		 */
		public static function boot() {
			if ( self::$booted || empty( self::$bundles ) ) {
				return;
			}

			// Highest version wins.
			$versions = array_keys( self::$bundles );
			usort( $versions, 'version_compare' );
			$winner = end( $versions );
			$dir    = self::$bundles[ $winner ];

			// The ONLY payload file the loader ever names. The winning
			// payload's core class requires the rest of its own files.
			require_once $dir . '/includes/class-pkcore-core.php';

			// Mark booted before init so a re-entrant boot() (e.g. an
			// activation-time explicit boot racing plugins_loaded) is a no-op.
			self::$booted         = true;
			self::$active_version = $winner;

			PKCORE_Core::init( $winner, $dir, array_keys( self::$modules ) );
		}

		/**
		 * Whether the core has booted this request.
		 *
		 * @since 0.1.0
		 * @return bool
		 */
		public static function is_booted() {
			return self::$booted;
		}

		/**
		 * The booted (winning) core version, or null before boot.
		 *
		 * @since 0.1.0
		 * @return string|null
		 */
		public static function active_version() {
			return self::$active_version;
		}

		/**
		 * Union of modules requested across all registered bundles.
		 *
		 * @since 0.1.0
		 * @return string[]
		 */
		public static function requested_modules() {
			return array_keys( self::$modules );
		}
	}
}
