<?php
/**
 * Plugin initialization.
 *
 * @package Carticy\Conversions
 */

declare(strict_types=1);

namespace Carticy\Conversions;

use Carticy\Conversions\Core\Container;
use Carticy\Conversions\Capture\OrderCaptureService;
use Carticy\Conversions\Client\ClientEventSerializer;
use Carticy\Conversions\Client\ClientTrackingSettings;
use Carticy\Conversions\Client\GtmImportBuilder;
use Carticy\Conversions\Client\StorefrontTrackingService;
use Carticy\Conversions\Admin\AdminPage;
use Carticy\Conversions\Consent\ConsentPlatformDetector;
use Carticy\Conversions\Consent\ConsentSignalCollector;
use Carticy\Conversions\Consent\GenericConsentPresetRegistry;
use Carticy\Conversions\Diagnostics\DetectionResolutions;
use Carticy\Conversions\Diagnostics\ExistingTagScanner;
use Carticy\Conversions\Diagnostics\GatewayAccuracyReport;
use Carticy\Conversions\Diagnostics\RecentEventsReader;
use Carticy\Conversions\Diagnostics\VerificationStatus;
use Carticy\Conversions\Events\EventToggles;
use Carticy\Conversions\Events\OrderEventService;
use Carticy\Conversions\Privacy\PrivacyService;
use Carticy\Conversions\Rest\ConsentController;
use Carticy\Conversions\Rest\DetectionController;
use Carticy\Conversions\Rest\DiagnosticsController;
use Carticy\Conversions\Rest\GtmImportController;
use Carticy\Conversions\Rest\HomeStatusController;
use Carticy\Conversions\Rest\OnboardingController;
use Carticy\Conversions\Rest\OnboardingStateMachine;
use Carticy\Conversions\Rest\PremiumServerBridge;
use Carticy\Conversions\Rest\ProviderStatusController;
use Carticy\Conversions\Rest\RestApi;
use Carticy\Conversions\Rest\ServerProvidersController;
use Carticy\Conversions\Rest\SettingsController;
use Carticy\Conversions\Rest\VerificationController;
use Carticy\Conversions\Settings\ConsentPostureSettings;
use Carticy\Conversions\Storage\SchemaManager;
use Carticy\Conversions\Storage\OrderMetaStore;

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

/**
 * Main plugin runtime.
 */
final class Init {

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

	/**
	 * Service container.
	 *
	 * @var Container
	 */
	private Container $container;

	/**
	 * Get the runtime instance.
	 *
	 * @return self
	 */
	public static function get_instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Constructor.
	 */
	private function __construct() {
		$this->container = new Container();
		$this->register_services();
		$this->init_hooks();
	}

	/**
	 * Prevent cloning.
	 */
	private function __clone() {}

	/**
	 * Prevent unserialization.
	 *
	 * @throws \Exception Always throws.
	 */
	public function __wakeup(): void {
		throw new \Exception( 'Cannot unserialize singleton.' );
	}

	/**
	 * Register services.
	 */
	private function register_services(): void {
		$this->container->register(
			OrderMetaStore::class,
			static function (): OrderMetaStore {
				return new OrderMetaStore();
			}
		);

		$this->container->register(
			GenericConsentPresetRegistry::class,
			static function (): GenericConsentPresetRegistry {
				return new GenericConsentPresetRegistry();
			}
		);

		$this->container->register(
			ConsentSignalCollector::class,
			function (): ConsentSignalCollector {
				return new ConsentSignalCollector( $this->container->get( GenericConsentPresetRegistry::class ) );
			}
		);

		$this->container->register(
			ConsentPostureSettings::class,
			static function (): ConsentPostureSettings {
				return new ConsentPostureSettings();
			}
		);

		$this->container->register(
			OrderCaptureService::class,
			function (): OrderCaptureService {
				return new OrderCaptureService(
					$this->container->get( OrderMetaStore::class ),
					$this->container->get( ConsentSignalCollector::class ),
					$this->container->get( ConsentPostureSettings::class )
				);
			}
		);

		$this->container->register(
			ClientEventSerializer::class,
			static function (): ClientEventSerializer {
				return new ClientEventSerializer();
			}
		);

		$this->container->register(
			ClientTrackingSettings::class,
			static function (): ClientTrackingSettings {
				return ClientTrackingSettings::from_options();
			}
		);

		$this->container->register(
			StorefrontTrackingService::class,
			function (): StorefrontTrackingService {
				return new StorefrontTrackingService(
					$this->container->get( OrderMetaStore::class ),
					$this->container->get( OrderCaptureService::class ),
					$this->container->get( ClientEventSerializer::class )
				);
			}
		);

		$this->container->register(
			PrivacyService::class,
			function (): PrivacyService {
				return new PrivacyService(
					$this->container->get( OrderMetaStore::class ),
					$this->container->get( ConsentPostureSettings::class )
				);
			}
		);

		$this->container->register(
			OrderEventService::class,
			function (): OrderEventService {
				return new OrderEventService( $this->container->get( OrderMetaStore::class ), $this->order_event_destinations(), new EventToggles() );
			}
		);

		if ( function_exists( 'carticy_conversions_fs' )
			&& is_object( carticy_conversions_fs() )
			&& carticy_conversions_fs()->is__premium_only()
		) {
			// class_exists keeps a build whose premium files were removed by
			// .distignore (instead of the Freemius is_premium rewrite) from
			// fataling on the autoloader miss; the Freemius deploy pipeline
			// remains the canonical premium strip.
			if ( carticy_conversions_fs()->can_use_premium_code()
				&& class_exists( \Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::class )
			) {
				$this->container->register(
					\Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::class,
					static function (): object {
						return \Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::from_options();
					}
				);

				$this->container->register(
					\Carticy\Conversions\ServerSide\Http\HttpClientInterface::class,
					static function (): object {
						return new \Carticy\Conversions\ServerSide\Http\WpHttpClient();
					}
				);

				$this->container->register(
					\Carticy\Conversions\ServerSide\Providers\ServerDispatchQueue::class,
					function (): object {
						$destinations = $this->premium_server_destinations();

						return new \Carticy\Conversions\ServerSide\Providers\ServerDispatchQueue(
							$this->container->get( OrderMetaStore::class ),
							$destinations
						);
					}
				);
			}
		}
	}

	/**
	 * Register runtime hooks.
	 */
	private function init_hooks(): void {
		SchemaManager::install();
		ConsentPostureSettings::add_default_options();

		// Settings, schema, and privacy primitives always register (privacy
		// obligations hold even when tracking is paused); the capture,
		// storefront, and order-event runtime honors the enabled switch.
		$this->container->get( ClientTrackingSettings::class )->register_hooks();
		$this->container->get( PrivacyService::class )->register_hooks();

		// The React admin app always registers so an admin can configure and
		// diagnose tracking even while the storefront runtime is paused.
		if ( is_admin() ) {
			( new AdminPage() )->register_hooks();
		}

		// The admin REST surface always registers: an admin must be able to
		// configure and diagnose tracking even while the runtime is paused.
		// Controllers self-gate the premium server-side routes via the bridge.
		$this->build_rest_api( $this->build_premium_server_bridge() )->register_hooks();

		if ( self::is_enabled() ) {
			$this->container->get( OrderCaptureService::class )->register_hooks();
			$this->container->get( StorefrontTrackingService::class )->register_hooks();
			$this->container->get( OrderEventService::class )->register_hooks();
		}

		if ( function_exists( 'carticy_conversions_fs' )
			&& is_object( carticy_conversions_fs() )
			&& carticy_conversions_fs()->is__premium_only()
		) {
			if ( carticy_conversions_fs()->can_use_premium_code() ) {
				if ( $this->container->has( \Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::class ) ) {
					$this->container->get( \Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::class )->register_hooks();
				}

				if ( $this->container->has( \Carticy\Conversions\ServerSide\Providers\ServerDispatchQueue::class ) ) {
					$this->container->get( \Carticy\Conversions\ServerSide\Providers\ServerDispatchQueue::class )->register_hooks();
				}
			}
		}
	}

	/**
	 * Build event destinations.
	 *
	 * @return array<int, \Carticy\Conversions\Events\EventDestinationInterface>
	 */
	private function order_event_destinations(): array {
		$destinations = array( new \Carticy\Conversions\Events\InternalEventDestination() );

		if ( function_exists( 'carticy_conversions_fs' )
			&& is_object( carticy_conversions_fs() )
			&& carticy_conversions_fs()->is__premium_only()
		) {
			if ( carticy_conversions_fs()->can_use_premium_code() ) {
				return array_merge( $destinations, $this->premium_server_destinations() );
			}
		}

		return $destinations;
	}

	/**
	 * Build premium server-side provider destinations.
	 *
	 * @return array<int, \Carticy\Conversions\ServerSide\Providers\AbstractQueuedProviderDestination>
	 */
	private function premium_server_destinations(): array {
		if ( function_exists( 'carticy_conversions_fs' )
			&& is_object( carticy_conversions_fs() )
			&& carticy_conversions_fs()->is__premium_only()
		) {
			if ( carticy_conversions_fs()->can_use_premium_code()
				&& class_exists( \Carticy\Conversions\ServerSide\Providers\Ga4MeasurementProtocolDestination::class )
				&& $this->container->has( \Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::class )
			) {
				$settings = $this->container->get( \Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::class );
				$http     = $this->container->get( \Carticy\Conversions\ServerSide\Http\HttpClientInterface::class );

				return array(
					new \Carticy\Conversions\ServerSide\Providers\Ga4MeasurementProtocolDestination( $settings, $http ),
					new \Carticy\Conversions\ServerSide\Providers\MetaConversionsApiDestination( $settings, $http ),
				);
			}
		}

		return array();
	}

	/**
	 * Build the admin REST registrar with all controllers wired.
	 *
	 * @param PremiumServerBridge $bridge Neutral premium server bridge.
	 * @return RestApi
	 */
	private function build_rest_api( PremiumServerBridge $bridge ): RestApi {
		$store        = $this->container->get( OrderMetaStore::class );
		$posture      = $this->container->get( ConsentPostureSettings::class );
		$presets      = $this->container->get( GenericConsentPresetRegistry::class );
		$verification = new VerificationStatus( $store, $bridge );
		$accuracy     = new GatewayAccuracyReport( $store );
		$scanner      = new ExistingTagScanner( ClientTrackingSettings::from_options() );
		$resolutions  = new DetectionResolutions();
		$toggles      = new EventToggles();

		return new RestApi(
			array(
				new SettingsController( $posture, $bridge, $toggles ),
				new ConsentController( $posture, $presets, $store, new ConsentPlatformDetector() ),
				new ServerProvidersController( $bridge ),
				new ProviderStatusController( $bridge ),
				new OnboardingController( new OnboardingStateMachine(), $verification ),
				new DiagnosticsController( $store, $accuracy, new RecentEventsReader(), $bridge ),
				new VerificationController( $verification, $bridge ),
				new DetectionController( $scanner, $resolutions ),
				new HomeStatusController( $store, $verification, $accuracy, $scanner, $resolutions, $bridge ),
				new GtmImportController( new GtmImportBuilder() ),
			)
		);
	}

	/**
	 * Build the neutral bridge the shipped REST layer uses to reach the premium
	 * server-side settings and dispatch queue.
	 *
	 * This is the ONLY place the premium namespace may be referenced in shipped
	 * code (the release gate enforces it). The closures are authored here and
	 * invoked opaquely by the free controllers, so no `src/Rest/` file ever names
	 * a premium class. In the free build the guards fail and an unavailable
	 * bridge is returned.
	 *
	 * @return PremiumServerBridge
	 */
	private function build_premium_server_bridge(): PremiumServerBridge {
		$settings_class = '\\Carticy\\Conversions\\ServerSide\\Providers\\ServerProviderSettings';
		if ( ! function_exists( 'carticy_conversions_can_use_premium' )
			|| ! carticy_conversions_can_use_premium()
			|| ! class_exists( $settings_class )
		) {
			return PremiumServerBridge::unavailable();
		}

		$reader = static function (): array {
			$settings = \Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::from_options();

			return array(
				'ga4'  => array(
					'enabled'        => $settings->ga4_enabled(),
					'measurement_id' => $settings->ga4_measurement_id(),
					'api_secret_set' => '' !== $settings->ga4_api_secret(),
					'debug_mode'     => $settings->ga4_debug_mode(),
				),
				'meta' => array(
					'enabled'          => $settings->meta_enabled(),
					'pixel_id'         => $settings->meta_pixel_id(),
					'access_token_set' => '' !== $settings->meta_access_token(),
					'api_version'      => $settings->meta_api_version(),
					'test_event_code'  => $settings->meta_test_event_code(),
				),
			);
		};

		$writer = static function ( array $patch ): void {
			$current = \Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::from_options();
			$ga4     = is_array( $patch['ga4'] ?? null ) ? $patch['ga4'] : array();
			$meta    = is_array( $patch['meta'] ?? null ) ? $patch['meta'] : array();

			// Write-only secrets: a blank/absent secret preserves the stored one.
			$ga4_secret = isset( $ga4['api_secret'] ) && '' !== trim( (string) $ga4['api_secret'] )
				? (string) $ga4['api_secret']
				: $current->ga4_api_secret();
			$meta_token = isset( $meta['access_token'] ) && '' !== trim( (string) $meta['access_token'] )
				? (string) $meta['access_token']
				: $current->meta_access_token();

			$raw = array(
				'ga4'  => array(
					'enabled'        => array_key_exists( 'enabled', $ga4 ) ? ! empty( $ga4['enabled'] ) : $current->ga4_enabled(),
					'measurement_id' => array_key_exists( 'measurement_id', $ga4 ) ? (string) $ga4['measurement_id'] : $current->ga4_measurement_id(),
					'api_secret'     => $ga4_secret,
					'debug_mode'     => array_key_exists( 'debug_mode', $ga4 ) ? ! empty( $ga4['debug_mode'] ) : $current->ga4_debug_mode(),
				),
				'meta' => array(
					'enabled'         => array_key_exists( 'enabled', $meta ) ? ! empty( $meta['enabled'] ) : $current->meta_enabled(),
					'pixel_id'        => array_key_exists( 'pixel_id', $meta ) ? (string) $meta['pixel_id'] : $current->meta_pixel_id(),
					'access_token'    => $meta_token,
					'api_version'     => array_key_exists( 'api_version', $meta ) ? (string) $meta['api_version'] : $current->meta_api_version(),
					'test_event_code' => array_key_exists( 'test_event_code', $meta ) ? (string) $meta['test_event_code'] : $current->meta_test_event_code(),
				),
			);

			update_option(
				\Carticy\Conversions\ServerSide\Providers\ServerProviderSettings::OPTION_NAME,
				$current->sanitize( $raw )
			);
		};

		$kicker = static function (): void {
			\Carticy\Conversions\ServerSide\Providers\ServerDispatchQueue::request_immediate_sweep();
		};

		return new PremiumServerBridge( true, $reader, $writer, $kicker );
	}

	/**
	 * Get the service container.
	 *
	 * @return Container
	 */
	public function get_container(): Container {
		return $this->container;
	}

	/**
	 * Check whether the plugin is enabled.
	 *
	 * @return bool
	 */
	public static function is_enabled(): bool {
		return 'yes' === get_option( 'carticy_conversions_enabled', 'yes' );
	}
}
