<?php
/**
 * BaseCloud Boost — Plugin Singleton
 *
 * @package BaseCloud\Boost
 */

declare( strict_types=1 );

namespace BaseCloud\Boost;

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

use BaseCloud\Boost\CDN\CDN_Integration;
use BaseCloud\Boost\CDN\BunnyCDN_Integration;
use BaseCloud\Boost\CDN\BunnyCDN_Rewriter;
use BaseCloud\Boost\Core\Cache_Engine;
use BaseCloud\Boost\Core\Cache_Preloader;
use BaseCloud\Boost\Core\Fleet_Ops;
use BaseCloud\Boost\Core\Heartbeat_Control;
use BaseCloud\Boost\Core\Security_Headers;
use BaseCloud\Boost\Core\Webhook_Sender;
use BaseCloud\Boost\Core\Lighthouse_Sender;
use BaseCloud\Boost\Optimizer\Asset_Optimizer;
use BaseCloud\Boost\Optimizer\Critical_CSS;
use BaseCloud\Boost\Optimizer\Elementor_Optimizer;
use BaseCloud\Boost\Optimizer\Lazy_Loader;
use BaseCloud\Boost\Optimizer\LCP_Optimizer;
use BaseCloud\Boost\Optimizer\Image_Optimizer;
use BaseCloud\Boost\Optimizer\Image_Compressor;
use BaseCloud\Boost\Optimizer\Misc_Optimizer;
use BaseCloud\Boost\Optimizer\Resource_Hints;
use BaseCloud\Boost\Optimizer\HTML_Minifier;
use BaseCloud\Boost\Optimizer\CSS_Minifier;
use BaseCloud\Boost\Optimizer\JS_Minifier;
use BaseCloud\Boost\Optimizer\Link_Preloader;
use BaseCloud\Boost\Optimizer\Video_Facade;
use BaseCloud\Boost\Optimizer\Google_Fonts;
use BaseCloud\Boost\Optimizer\Self_Host_Fonts;
use BaseCloud\Boost\Optimizer\Speculation_Rules;
use BaseCloud\Boost\Admin\Admin_Controller;
use BaseCloud\Boost\Compatibility\WooCommerce_Compat;
use BaseCloud\Boost\Compatibility\Multisite_Compat;
use BaseCloud\Boost\Compatibility\Plugin_Compat;
use BaseCloud\Boost\Compatibility\Host_Detector;
use BaseCloud\Boost\Compatibility\Object_Cache_Compat;
use BaseCloud\Boost\Compatibility\Varnish;

/**
 * Main plugin class — boots all modules.
 */
final class Plugin {

	/** @var Plugin|null Singleton instance. */
	private static ?Plugin $instance = null;

	/** Private constructor — use instance(). */
	private function __construct() {}

	/**
	 * Return (and lazily create) the singleton instance.
	 *
	 * @return Plugin
	 */
	public static function instance(): Plugin {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Boot all plugin modules.
	 *
	 * @return void
	 */
	public function boot(): void {
		// Self-healing upgrade: refresh the drop-in / .htaccess when the plugin is
		// updated without reactivation (admin context only).
		add_action( 'admin_init', [ Activator::class, 'maybe_upgrade' ] );

		// One-time option migrations (run at admin_init priority 5, i.e. before
		// maybe_upgrade refreshes .htaccess / the options cache at priority 10).
		( new \BaseCloud\Boost\Core\Upgrade() )->boot();

		// Compatibility layers (always).
		( new Plugin_Compat() )->boot();

		// Object cache (Redis / Object Cache Pro / Memcached): register our
		// non-persistent group so we never bloat or clash with it.
		( new Object_Cache_Compat() )->boot();

		// Hosting environment: applies host-specific tweaks and, on hosts that run
		// their own full-page cache (WP Engine, Kinsta…), disables ours via the
		// BCBOOST_page_cache_enabled filter so the two don't double-cache.
		( new Host_Detector() )->boot();

		// Varnish / reverse-proxy: mirror local purges to the edge over HTTP.
		( new Varnish() )->boot();

		if ( is_multisite() ) {
			( new Multisite_Compat() )->boot();
		}

		// Core caching engine.
		( new Cache_Engine() )->boot();

		// Cache preloader — MUST boot on every request (incl. wp-cron) so the
		// batch-processing cron handler and the BCBOOST_1min schedule are
		// registered; without this the preloader queued URLs but never crawled them.
		( new Cache_Preloader() )->boot();

		// Fleet Ops REST API (bcboost/v1) — boots in EVERY context because REST
		// requests run their own context (is_admin() is false, front-end
		// optimizers don't apply). The class self-gates per request on the
		// fleet_ops_enabled kill-switch + pairing key; registration alone adds
		// only a rest_api_init hook. Held behind BCBOOST_FLEET_OPS_READY until
		// the BaseCloud CRM (the calling side) goes live — the Advanced screen
		// shows the feature as "Unavailable Right Now" meanwhile.
		if ( defined( 'BCBOOST_FLEET_OPS_READY' ) && BCBOOST_FLEET_OPS_READY ) {
			( new Fleet_Ops() )->boot();
		}

		// Cloudflare CDN — mirrors local purge events to the edge.
		if ( BCBOOST_get_option( 'cdn_enabled', false ) ) {
			( new CDN_Integration() )->boot();
		}

		// BunnyCDN — mirrors local purge events to the BunnyCDN edge.
		if ( BCBOOST_get_option( 'bunnycdn_enabled', false ) ) {
			( new BunnyCDN_Integration() )->boot();
		}

		// BunnyCDN URL rewriter — rewrites asset URLs to the CDN hostname on the front end.
		// Gated out of builder editors/previews: editors XHR-fetch and manipulate
		// asset URLs, and cross-origin CDN URLs break that (CORS) — origin assets
		// are always safe inside an editor iframe.
		if ( BCBOOST_get_option( 'bunnycdn_enabled', false ) && ! is_admin() && BCBOOST_is_optimizable_request() ) {
			( new BunnyCDN_Rewriter() )->boot();
		}

		// Critical CSS — per-template/per-URL store, <head> injector, stale-flag
		// listeners and admin/CLI endpoints. Boots in EVERY context (builder saves
		// and theme switches fire in admin/AJAX; the injector self-gates to the
		// front end). This is also what BCBOOST_async_css_active() resolves against.
		( new Critical_CSS() )->boot();

		// Elementor Speed — inline per-page post-CSS, unused-icon CSS removal,
		// DOM-size advisory (the read-only Settings Advisor is invoked directly
		// by the settings view). Boots in EVERY context like Critical_CSS: its
		// cache-freshness listeners (Elementor CSS regeneration / Library
		// template saves) fire in admin/AJAX, while the buffer passes self-gate
		// to optimizable front-end requests. No-ops unless Elementor is active
		// AND the elementor_speed master toggle is on.
		( new Elementor_Optimizer() )->boot();

		// Heartbeat control.
		( new Heartbeat_Control() )->boot();

		// Security headers (all requests including admin for non-admin pages).
		( new Security_Headers() )->boot();

		// Image compression — hooks the upload pipeline, so it must boot in admin
		// (and async upload) context, not just on the front end.
		( new Image_Compressor() )->boot();

		// Front-end optimizations only — and NEVER inside a page-builder editor or
		// preview (Elementor, Divi ?et_fb=1, Beaver, WPBakery, Bricks, Oxygen,
		// OptimizePress, Thrive) or the Customizer. Builders render the front end
		// inside their editor iframe; minifying/deferring/stripping there breaks
		// the editor. BCBOOST_is_optimizable_request() (basecloud-boost.php) is the
		// single source of truth for this — each buffer also re-checks it at
		// template_redirect time to catch is_preview()/Customizer states that are
		// not knowable this early (plugins_loaded).
		if ( ! is_admin() && BCBOOST_is_optimizable_request() ) {
			if ( class_exists( 'WooCommerce' ) ) {
				( new WooCommerce_Compat() )->boot();
			}

			// Resource hints (preconnect/dns-prefetch).
			( new Resource_Hints() )->boot();

			// HTML minification (output buffer, inner layer).
			( new HTML_Minifier() )->boot();

			// Asset optimization: defer/delay JS, remove query strings.
			( new Asset_Optimizer() )->boot();

			// CSS minification + combining (enqueued files + inline styles).
			( new CSS_Minifier() )->boot();

			// JS minification + combining (enqueued files + inline scripts).
			( new JS_Minifier() )->boot();

			// Critical CSS: the GLOBAL fallback textarea is inlined by Resource_Hints
			// (priority 2); Critical_CSS (booted above, injector at priority 3)
			// supersedes it whenever a per-URL/template entry covers the request.

			// Lazy loading images, iframes, and videos.
			( new Lazy_Loader() )->boot();

			// Image optimization (WebP + AVIF serving).
			( new Image_Optimizer() )->boot();

			// LCP hero detection + preload (img / inline-bg / slider / combined-CSS).
			( new LCP_Optimizer() )->boot();

			// Misc optimizations (emojis, embeds, dashicons, head cleanup).
			( new Misc_Optimizer() )->boot();

			// Link prefetch on hover.
			( new Link_Preloader() )->boot();

			// Video facade — YouTube/Vimeo lite embed.
			( new Video_Facade() )->boot();

			// Self-host Google Fonts (download + serve locally). Runs before
			// Google_Fonts so remote font links are localised first.
			( new Self_Host_Fonts() )->boot();

			// Google Fonts optimization (font-display: swap, async load).
			( new Google_Fonts() )->boot();

			// Speculation Rules API — instant navigation for Chrome 109+.
			( new Speculation_Rules() )->boot();

		}

		// Daily webhook cron.
		add_action( 'BCBOOST_webhook_daily', function () {
			if ( BCBOOST_get_option( 'webhook_enabled', false ) ) {
				( new Webhook_Sender() )->send();
			}
		} );

		// Daily Lighthouse cron — audits mobile + desktop and sends both to the Lighthouse webhook.
		add_action( 'BCBOOST_lighthouse_daily', function () {
			if ( BCBOOST_get_option( 'lighthouse_webhook_enabled', false ) ) {
				( new Lighthouse_Sender() )->run_and_send_daily();
			}
		} );

		// Admin panel.
		if ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) {
			( new Admin_Controller() )->boot();
			( new \BaseCloud\Boost\Admin\Media_Actions() )->boot();
		}

		// WP-CLI commands.
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			require_once BCBOOST_DIR . 'includes/CLI/CLI_Commands.php';
		}
	}

}
