<?php
/**
 * Main plugin orchestrator.
 *
 * Wires up every subsystem: CPTs, taxonomies, migrations, REST,
 * admin/frontend hooks, and the Pro extension point.
 *
 * @package ThemeREX\AutoDealer
 */

declare(strict_types=1);

namespace ThemeREX\AutoDealer\Core;

defined( 'ABSPATH' ) || exit;
use ThemeREX\AutoDealer\Admin\AdminListTable;
use ThemeREX\AutoDealer\Admin\AdminSettings;
use ThemeREX\AutoDealer\Admin\DealerMetaBox;
use ThemeREX\AutoDealer\Admin\ListingQualityScore;
use ThemeREX\AutoDealer\Admin\QRCodeMetaBox;
use ThemeREX\AutoDealer\Admin\TaxonomyIconFields;
use ThemeREX\AutoDealer\Admin\TemplatesAdminPage;
use ThemeREX\AutoDealer\Admin\UpgradePage;
use ThemeREX\AutoDealer\Admin\VehicleDataMetaBox;
use ThemeREX\AutoDealer\Admin\AdminAssets;
use ThemeREX\AutoDealer\Blocks\BindingsSource;
use ThemeREX\AutoDealer\Blocks\BlockRegistry;
use ThemeREX\AutoDealer\Blocks\PatternRegistry;
use ThemeREX\AutoDealer\Forms\FormHandler;
use ThemeREX\AutoDealer\Database\MigrationRunner;
use ThemeREX\AutoDealer\Cache\AggregationCache;
use ThemeREX\AutoDealer\Database\SyncErrorVisibility;
use ThemeREX\AutoDealer\Database\VehicleSpecsSync;
use ThemeREX\AutoDealer\Frontend\ArchiveFilters;
use ThemeREX\AutoDealer\Frontend\Assets;
use ThemeREX\AutoDealer\Frontend\CompareHandler;
use ThemeREX\AutoDealer\Frontend\DealerDashboard;
use ThemeREX\AutoDealer\Frontend\FavoritesHandler;
use ThemeREX\AutoDealer\Frontend\FrontendForms;
use ThemeREX\AutoDealer\Frontend\PlainPermalinkArchive;
use ThemeREX\AutoDealer\Frontend\TemplateLoader;
use ThemeREX\AutoDealer\PostTypes\DealerPostType;
use ThemeREX\AutoDealer\PostTypes\VehiclePostType;
use ThemeREX\AutoDealer\REST\DealersController;
use ThemeREX\AutoDealer\REST\LeadsController;
use ThemeREX\AutoDealer\REST\PdfController;
use ThemeREX\AutoDealer\REST\SearchController;
use ThemeREX\AutoDealer\REST\VehiclesController;
use ThemeREX\AutoDealer\Taxonomies\BodyStyleTaxonomy;
use ThemeREX\AutoDealer\Taxonomies\ColorTaxonomy;
use ThemeREX\AutoDealer\Taxonomies\ConditionTaxonomy;
use ThemeREX\AutoDealer\Taxonomies\DrivetrainTaxonomy;
use ThemeREX\AutoDealer\Taxonomies\FeatureTaxonomy;
use ThemeREX\AutoDealer\Taxonomies\FuelTypeTaxonomy;
use ThemeREX\AutoDealer\Taxonomies\MakeTaxonomy;
use ThemeREX\AutoDealer\Taxonomies\ModelTaxonomy;
use ThemeREX\AutoDealer\Taxonomies\TransmissionTaxonomy;
use ThemeREX\AutoDealer\Taxonomies\UserTaxonomyManager;
use ThemeREX\AutoDealer\Taxonomies\VehicleTypeTaxonomy;

class Plugin {

	private Loader $loader;

	/**
	 * All taxonomy instances, kept for activation seeding.
	 *
	 * @var array<int, MakeTaxonomy|ModelTaxonomy|BodyStyleTaxonomy|FuelTypeTaxonomy|TransmissionTaxonomy|DrivetrainTaxonomy|ColorTaxonomy|FeatureTaxonomy|VehicleTypeTaxonomy|ConditionTaxonomy>
	 */
	private array $taxonomies = array();

	/**
	 * Bootstrap every subsystem and register all hooks.
	 *
	 * @return void
	 */
	public function run(): void {
		$this->loader = new Loader();

		// Run idempotent upgrades — re-seeds default terms / caps if the
		// stored seed-version is behind the current shipped version.
		// Hooks `init` priority 11 so post types + taxonomies are registered.
		add_action( 'init', array( '\ThemeREX\AutoDealer\Core\Activator', 'ensureUpgraded' ), 11 );

		$this->loadTextDomain();
		$this->registerPostTypes();
		$this->registerTaxonomies();
		$this->registerMigrations();
		$this->registerRestApi();
		$this->registerForms();
		$this->registerBlocks();

		// Asset registration runs in both contexts: `wp_enqueue_scripts`
		// fires only on the front end, `enqueue_block_editor_assets` only
		// inside the block editor — each hook self-gates, so registering
		// both unconditionally is correct.
		( new Assets() )->register();

		// Opt-in SVG icon uploads (tester feedback #15). Runs in both contexts:
		// media uploads arrive via admin AND async/REST endpoints.
		( new SvgUploads() )->register();

		if ( is_admin() ) {
			$this->registerAdminHooks();
		} else {
			$this->registerFrontendHooks();
		}

		$this->registerElementor();
		$this->registerSchema();
		$this->registerI18n();
		$this->registerMaps();

		/**
		 * Fires after the free plugin has registered all its modules.
		 *
		 * The Pro add-on (and any third-party extension) hooks here
		 * to register additional CPTs, taxonomies, REST routes, etc.
		 *
		 * @param Loader $loader The hook registration manager.
		 */
		do_action( 'trx_ad_register_modules', $this->loader );

		$this->loader->run();
	}

	/**
	 * Load the plugin text domain for translations.
	 *
	 * @return void
	 */
	private function loadTextDomain(): void {
		$this->loader->addAction(
			'init',
			static function (): void {
				load_plugin_textdomain(
					'trx-auto-dealer',
					false,
					dirname( TRX_AD_BASENAME ) . '/languages'
				);
			},
			1
		);
	}

	/**
	 * Register Vehicle and Dealer custom post types.
	 *
	 * @return void
	 */
	private function registerPostTypes(): void {
		$vehicle = new VehiclePostType();
		$dealer  = new DealerPostType();

		// Taxonomies must be registered before CPTs that reference them,
		// but WordPress also accepts forward-declared taxonomy slugs,
		// so we register taxonomies at priority 9, CPTs at priority 10.
		$this->loader->addAction( 'init', array( $vehicle, 'register' ), 10 );
		$this->loader->addAction( 'init', array( $dealer, 'register' ), 10 );

		$this->loader->addFilter( 'use_block_editor_for_post_type', array( $vehicle, 'disableBlockEditor' ), 10, 2 );
	}

	/**
	 * Instantiate and register all 10 vehicle taxonomies.
	 *
	 * @return void
	 */
	private function registerTaxonomies(): void {
		$this->taxonomies = array(
			new MakeTaxonomy(),
			new ModelTaxonomy(),
			new BodyStyleTaxonomy(),
			new FuelTypeTaxonomy(),
			new TransmissionTaxonomy(),
			new DrivetrainTaxonomy(),
			new ColorTaxonomy(),
			new FeatureTaxonomy(),
			new VehicleTypeTaxonomy(),
			new ConditionTaxonomy(),
		);

		foreach ( $this->taxonomies as $taxonomy ) {
			// Priority 9 so taxonomies exist before CPTs register at 10.
			$this->loader->addAction( 'init', array( $taxonomy, 'register' ), 9 );
		}

		// Hide disabled core taxonomies from UI (still registered internally).
		$this->loader->addAction( 'init', array( $this, 'hideDisabledTaxonomies' ), 11 );

		// Register user-defined taxonomies.
		$userTaxonomies = new UserTaxonomyManager();
		$userTaxonomies->register();

		// Cross-taxonomy Model → Make linker (admin UI + helpers).
		( new \ThemeREX\AutoDealer\Taxonomies\ModelMakeLink() )->register();
	}

	/**
	 * Override visibility args for disabled core taxonomies.
	 *
	 * The taxonomy stays registered so all internal code (sync, REST,
	 * templates) continues to work — it just becomes invisible in the UI.
	 *
	 * Hooked to `init` at priority 11 (after taxonomies register at 9).
	 *
	 * @return void
	 */
	public function hideDisabledTaxonomies(): void {
		$disabled = UserTaxonomyManager::getDisabled();

		// Remove Gutenberg sidebar panels for ALL vehicle taxonomies
		// (core + user-defined) since they live in our Vehicle Data tab now.
		$allSlugs = array_merge(
			UserTaxonomyManager::CORE_TAXONOMIES,
			UserTaxonomyManager::getSlugs()
		);

		foreach ( $allSlugs as $slug ) {
			$taxonomy = get_taxonomy( $slug );

			if ( ! $taxonomy instanceof \WP_Taxonomy ) {
				continue;
			}

			// Suppress the default meta box in both classic and block editor.
			$taxonomy->meta_box_cb = false;

			// Disabled taxonomies get fully hidden from UI.
			if ( in_array( $slug, $disabled, true ) ) {
				$taxonomy->show_ui            = false;
				$taxonomy->show_in_menu       = false;
				$taxonomy->show_admin_column  = false;
				$taxonomy->show_in_nav_menus  = false;
				$taxonomy->show_tagcloud      = false;
				$taxonomy->publicly_queryable = false;
				$taxonomy->public             = false;
			}
		}
	}

	/**
	 * Wire up the database migration runner and specs sync.
	 *
	 * @return void
	 */
	private function registerMigrations(): void {
		$runner = new MigrationRunner();
		$runner->register();

		$specsSync = new VehicleSpecsSync();
		$specsSync->register();

		// Aggregation cache: hooks save_post / deleted_post / specs_synced for invalidation.
		( new AggregationCache() )->register();
	}

	/**
	 * Register REST API routes.
	 *
	 * @return void
	 */
	private function registerRestApi(): void {
		$this->loader->addAction(
			'rest_api_init',
			static function (): void {
				( new VehiclesController() )->register_routes();
				( new SearchController() )->register_routes();
				( new DealersController() )->register_routes();
				( new PdfController() )->register_routes();
				( new LeadsController() )->register_routes();

				do_action( 'trx_ad_rest_api_init' );
			}
		);
	}

	/**
	 * Register form submission handler.
	 *
	 * @return void
	 */
	private function registerForms(): void {
		$handler = new FormHandler();
		$handler->register();
	}

	/**
	 * Register Gutenberg blocks via {@see BlockRegistry}.
	 *
	 * @return void
	 */
	private function registerBlocks(): void {
		( new BlockRegistry() )->register();
		( new PatternRegistry() )->register();
		( new BindingsSource() )->register();
	}

	/**
	 * Register admin-only hooks.
	 *
	 * @return void
	 */
	private function registerAdminHooks(): void {
		$specsSync = new VehicleSpecsSync();

		// Admin-side surface for vehicle-specs sync errors recorded by $specsSync.
		( new SyncErrorVisibility() )->register();

		$vehicleData = new VehicleDataMetaBox();
		$vehicleData->register();

		( new \ThemeREX\AutoDealer\Admin\DealerAssignmentMetaBox() )->register();
		( new \ThemeREX\AutoDealer\Admin\ElementorStarterTemplate() )->register();
		( new \ThemeREX\AutoDealer\Admin\GutenbergStarterTemplate() )->register();

		$dealerMetaBox = new DealerMetaBox();
		$dealerMetaBox->register();

		$dealerListTable = new \ThemeREX\AutoDealer\Admin\DealerListTable();
		$dealerListTable->register();

		$adminListTable = new AdminListTable();
		$adminListTable->register();

		$qualityScore = new ListingQualityScore( $specsSync );
		$qualityScore->register();

		$adminSettings = new AdminSettings();
		$adminSettings->register();

		$adminAssets = new AdminAssets();
		$adminAssets->register();

		$taxonomyIcons = new TaxonomyIconFields();
		$taxonomyIcons->register();

		$qrCode = new QRCodeMetaBox();
		$qrCode->register();

		( new \ThemeREX\AutoDealer\Admin\FeaturedMetaBox() )->register();

		// Phase 19 — Layouts & Templates admin page. (The former per-listing
		// layout upsell stub was removed — wp.org G5: no locked-looking UI.)
		( new TemplatesAdminPage() )->register();

		// Informational "Pro Version" page (hidden when Pro is active).
		( new UpgradePage() )->register();

		// ADR-09 — block deactivating free while Pro is active (WP 6.4 fallback;
		// WP 6.5+ enforces this from Pro's "Requires Plugins" header).
		( new \ThemeREX\AutoDealer\Admin\ProDependencyGuard() )->register();

		do_action( 'trx_ad_admin_hooks_register', $this->loader );
	}

	/**
	 * Register frontend-only hooks.
	 *
	 * @return void
	 */
	private function registerFrontendHooks(): void {
		$templateLoader = new TemplateLoader();
		$templateLoader->register();

		// Route /inventory/ (and /dealers/) to the post-type archive even
		// when the site is on Plain permalinks (rewrite rules inactive).
		( new PlainPermalinkArchive() )->register();

		$archiveFilters = new ArchiveFilters();
		$archiveFilters->register();

		$dealerDashboard = new DealerDashboard();
		$dealerDashboard->register();

		$frontendForms = new FrontendForms();
		$frontendForms->register();

		$compareHandler = new CompareHandler();
		$compareHandler->register();

		$favoritesHandler = new FavoritesHandler();
		$favoritesHandler->register();

		do_action( 'trx_ad_frontend_hooks_register', $this->loader );
	}

	/**
	 * Bootstrap Elementor integration when Elementor is active.
	 *
	 * @return void
	 */
	private function registerElementor(): void {
		if ( ! did_action( 'elementor/loaded' ) ) {
			return;
		}

		$elementor = new \ThemeREX\AutoDealer\Elementor\ElementorIntegration();
		$elementor->register();
	}

	/**
	 * Wire the schema/SEO surface (ADR-19). The orchestrator decides which
	 * mode integration to load on `wp_loaded`, then hooks `wp_head` (or the
	 * relevant SEO plugin's filter) to emit JSON-LD.
	 */
	private function registerSchema(): void {
		( new \ThemeREX\AutoDealer\Integrations\Schema\SchemaGenerator() )->register();
		( new \ThemeREX\AutoDealer\Frontend\LlmsTxt() )->register();
	}

	/**
	 * Register WPML and Polylang compatibility (ADR-07). Both integrations
	 * are no-ops when their respective plugin isn't active — detection
	 * happens inside `register()`.
	 */
	private function registerI18n(): void {
		( new \ThemeREX\AutoDealer\Integrations\WPML\WpmlIntegration() )->register();
		( new \ThemeREX\AutoDealer\Integrations\Polylang\PolylangIntegration() )->register();
	}

	/**
	 * Register the map provider abstraction (ADR-11).
	 *
	 * The active provider is selected by the `map_provider` setting; the
	 * geocoder always registers (no-ops on dealer save when the address
	 * is empty or coords already pinned).
	 */
	private function registerMaps(): void {
		( new \ThemeREX\AutoDealer\Integrations\Maps\Geocoder() )->register();

		/**
		 * Filter the active map provider instance. Returning a custom
		 * MapProviderInterface implementation overrides the setting.
		 *
		 * @param \ThemeREX\AutoDealer\Integrations\Maps\MapProviderInterface|null $provider Default null.
		 * @param string $slug Slug from settings: 'none' | 'leaflet' | 'google'.
		 */
		$slug     = (string) \ThemeREX\AutoDealer\Admin\AdminSettings::get( 'map_provider', 'none' );
		$provider = apply_filters( 'trx_ad_map_provider', null, $slug );

		if ( null === $provider ) {
			$provider = match ( $slug ) {
				'google'  => new \ThemeREX\AutoDealer\Integrations\Maps\GoogleMapsProvider(),
				'leaflet' => new \ThemeREX\AutoDealer\Integrations\Maps\LeafletProvider(),
				default   => null,
			};
		}

		if ( $provider instanceof \ThemeREX\AutoDealer\Integrations\Maps\MapProviderInterface ) {
			// Stash for later retrieval by render-time consumers.
			$GLOBALS['trx_ad_map_provider'] = $provider;
		}
	}
}
