<?php

namespace WPDRMS\ASP\Core;

use WPDRMS\ASP\BlockEditor\ASPBlock;
use WPDRMS\ASP\BlockEditor\UserSearchesBlock;
use WPDRMS\ASP\Cache\CacheHooks;
use WPDRMS\ASP\Cache\CacheRoute;
use WPDRMS\ASP\Cli\CliRegistrable;
use WPDRMS\ASP\Hooks\DeprecatedHooks;
use WPDRMS\ASP\Integration\Imagely\NextGenGallery;
use WPDRMS\ASP\Options\OptionAssets;
use WPDRMS\AdminUI\Routes\DirectoriesRoute;
use WPDRMS\AdminUI\Routes\PluginLanguagesRoute;
use WPDRMS\AdminUI\Routes\PostMetaKeysRoute;
use WPDRMS\AdminUI\Routes\PostsRoute;
use WPDRMS\AdminUI\Routes\PostTypesRoute;
use WPDRMS\AdminUI\Routes\SitesRoute;
use WPDRMS\AdminUI\Routes\TaxonomyTermsRoute;
use WPDRMS\AdminUI\Routes\UsersRoute;
use WPDRMS\ASP\Index\IndexTableHooks;
use WPDRMS\ASP\Index\IndexTableRoute;
use WPDRMS\ASP\Options\Routes\SearchOptionsRoute;
use WPDRMS\ASP\Options\Routes\StatisticsComponentsRoute;
use WPDRMS\ASP\Modal\TimedModalRoutes;
use WPDRMS\PluginCore\Traits\SingletonTrait;
use WPDRMS\ASP\Analytics\AnalyticsRoute;
use WPDRMS\ASP\Statistics\StatisticsRoute;
use WPDRMS\ASP\Maintenance\MaintenanceRoute;
use WPDRMS\ASP\Compatibility\CompatibilityRoute;
use WPDRMS\ASP\Synonyms\SynonymsRoute;
use WPDRMS\ASP\Misc\DemoMode;
use WPDRMS\ASP\Misc\UpdatesHelpRoute;
use WPDRMS\ASP\Priorities\PrioritiesRoute;
use WPDRMS\ASP\Updates\UpgradeGuard;
use WPDRMS\ASP\AI\AIRoute;
use WPDRMS\ASP\AI\AIHooks;
use WPDRMS\ASP\Suggest\SuggestHooks;

/**
 * Returns all class instances for a given interface name
 *
 * @see .phpstorm.meta.php for corrected type hints
 */
class Factory {
	use SingletonTrait;

	const SUPPORTED_INTERFACES = array(
		'Rest'        => array(
			TimedModalRoutes::class,
			DirectoriesRoute::class,
			PluginLanguagesRoute::class,
			PostMetaKeysRoute::class,
			SitesRoute::class,
			TaxonomyTermsRoute::class,
			UsersRoute::class,
			PostsRoute::class,
			PostTypesRoute::class,
			StatisticsComponentsRoute::class,
			SearchOptionsRoute::class,
			IndexTableRoute::class,
			StatisticsRoute::class,
			CacheRoute::class,
			AnalyticsRoute::class,
			SynonymsRoute::class,
			MaintenanceRoute::class,
			CompatibilityRoute::class,
			UpdatesHelpRoute::class,
			PrioritiesRoute::class,
			AIRoute::class,
		),
		'Asset'       => array(
			OptionAssets::class,
		),
		'Block'       => array(
			ASPBlock::class,
			UserSearchesBlock::class,
		),
		'Integration' => array(
			NextGenGallery::class,
		),
		'Hook'        => array(
			DeprecatedHooks::class,
			CliRegistrable::class,
			IndexTableHooks::class,
			UpgradeGuard::class,
			AIHooks::class,
			SuggestHooks::class,
			CacheHooks::class,
			DemoMode::class,
		),
	);

	/**
	 * Get all the objects array for a given interface
	 *
	 * @param key-of<self::SUPPORTED_INTERFACES> $interface_name
	 * @param mixed[]                            $args
	 */
	public function get( string $interface_name, ?array $args = null ): array {
		if ( !isset(self::SUPPORTED_INTERFACES[ $interface_name ]) ) {
			return array();
		}
		return self::instantiate(self::SUPPORTED_INTERFACES[ $interface_name ], $args);
	}

	/**
	 * Instantiates a list of class names, skipping any that cannot be loaded.
	 *
	 * A listed class may be missing if it was removed in a newer version while
	 * an older Factory (still referencing it) is in memory during the plugin
	 * file-swap on an update. Skipping it degrades gracefully (one hook/route
	 * not registered for that request) instead of fataling the whole admin /
	 * REST request in that narrow window.
	 *
	 * @param string[]     $classes
	 * @param mixed[]|null $args
	 * @return object[]
	 */
	public static function instantiate( array $classes, ?array $args = null ): array {
		$objects = array();
		foreach ( $classes as $class_name ) {
			if ( !class_exists($class_name) ) {
				if ( defined('WP_DEBUG') && WP_DEBUG ) {
					// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
					error_log(sprintf('Ajax Search Pro: skipped missing class "%s" during Factory instantiation.', $class_name));
				}
				continue;
			}
			if ( method_exists($class_name, 'instance') ) {
				$objects[] = is_array($args) ? $class_name::instance(...$args) : $class_name::instance();
			} else {
				$objects[] = is_array($args) ? new $class_name(...$args) : new $class_name(); // @phpstan-ignore-line
			}
		}
		return $objects;
	}
}
