<?php
/**
 * PSR-4-like autoloader for the CWPM namespace.
 *
 * Maps CWPM\Dir\Class to includes/Dir/class-{class}.php.
 * Top-level CWPM\Class maps to includes/class-{class}.php.
 * Any sub-namespaced classes that live in an extension subfolder map to
 * that subfolder via the same naming convention.
 *
 * PascalCase class names are converted to kebab-case file names
 * (e.g. TimeoutGuard -> class-timeout-guard.php).
 *
 * The autoloader supports MULTIPLE base paths. WP's activate_plugin() may
 * load another distribution while one is already loaded — each one calls
 * register( $its_plugin_root ) and the lookup tries every registered path
 * in order so namespaced classes resolve from the right tree.
 *
 * @package CoffeeWP_Media_Cleanup
 */

defined( 'ABSPATH' ) || exit;

if ( class_exists( 'CWPM_Autoloader' ) ) {
	return;
}

class CWPM_Autoloader {

	/**
	 * Plugin root directories to search, in order. Each entry is an
	 * absolute path ending in a directory separator.
	 *
	 * @var string[]
	 */
	private static $paths = array();

	/**
	 * Whether the spl_autoload_register has already been wired up.
	 *
	 * @var bool
	 */
	private static $registered = false;

	/**
	 * Register the autoloader and add a base path.
	 *
	 * Called once by each plugin distribution from its main file. The spl
	 * callback is registered exactly once; subsequent calls only append
	 * paths.
	 *
	 * @param string|null $path Plugin root to search. Defaults to CWPM_PATH
	 *                          (set by the calling main file).
	 */
	public static function register( $path = null ) {
		if ( null === $path && defined( 'CWPM_PATH' ) ) {
			$path = CWPM_PATH;
		}

		if ( is_string( $path ) && '' !== $path && ! in_array( $path, self::$paths, true ) ) {
			self::$paths[] = $path;
		}

		if ( ! self::$registered ) {
			spl_autoload_register( array( __CLASS__, 'autoload' ) );
			self::$registered = true;
		}
	}

	/**
	 * Autoload callback.
	 *
	 * @param string $class_name The fully-qualified class name.
	 */
	public static function autoload( $class_name ) {
		if ( 0 !== strpos( $class_name, 'CWPM\\' ) ) {
			return;
		}

		$relative = substr( $class_name, 5 );

		foreach ( self::$paths as $base ) {
			$path = self::resolve( $base, $relative );
			if ( null !== $path && file_exists( $path ) ) {
				require_once $path;
				return;
			}
		}
	}

	/**
	 * Resolve a class to a filesystem path under a given base.
	 *
	 * @param string $base     Plugin root, with trailing slash.
	 * @param string $relative Class name with the CWPM\ prefix stripped.
	 * @return string|null
	 */
	private static function resolve( $base, $relative ) {

		$parts = explode( '\\', $relative );

		if ( count( $parts ) > 1 ) {
			$class = array_pop( $parts );
			$dir   = implode( '/', $parts );
			$file  = 'class-' . self::pascal_to_kebab( $class ) . '.php';
			return $base . 'includes/' . $dir . '/' . $file;
		}

		$file = 'class-' . self::pascal_to_kebab( $parts[0] ) . '.php';
		return $base . 'includes/' . $file;
	}

	/**
	 * Convert a PascalCase string to kebab-case.
	 *
	 * @param string $pascal PascalCase string (e.g. 'TimeoutGuard').
	 * @return string Kebab-case string (e.g. 'timeout-guard').
	 */
	private static function pascal_to_kebab( $pascal ) {
		return strtolower( preg_replace( '/([a-z])([A-Z])/', '$1-$2', $pascal ) );
	}
}
