<?php
/**
 * Plugin bootstrap coordinator.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Bootstrap;

use ScheduledChanges\Database\Migrator;
use ScheduledChanges\Extension\ModuleRuntime;
use ScheduledChanges\Extension\ModuleRuntimeException;
use ScheduledChanges\Extension\PublicHooks;
use ScheduledChanges\Provider\ProviderManager;
use ScheduledChanges\Recording\RecordingService;
use ScheduledChanges\Rollback\EndVersionCompiler;
use ScheduledChanges\Rollback\RollbackService;
use ScheduledChanges\Scheduler\QueueMonitor;
use ScheduledChanges\Scheduler\SchedulerService;
use ScheduledChanges\Support\Requirements;
use Throwable;

/**
 * Boots services after WordPress is ready.
 */
final class Plugin {
	/**
	 * Shared plugin instance.
	 *
	 * @var self|null
	 */
	private static ?self $instance = null;

	/**
	 * Whether services have been registered.
	 *
	 * @var bool
	 */
	private bool $booted = false;

	/**
	 * Field provider lifecycle service.
	 *
	 * @var ProviderManager|null
	 */
	private ?ProviderManager $provider_manager = null;

	/**
	 * Public scheduling application service.
	 *
	 * @var SchedulerService|null
	 */
	private ?SchedulerService $scheduler_service = null;

	/**
	 * Queue diagnostics.
	 *
	 * @var QueueMonitor|null
	 */
	private ?QueueMonitor $queue_monitor = null;

	/**
	 * Public compensating rollback service.
	 *
	 * @var RollbackService|null
	 */
	private ?RollbackService $rollback_service = null;

	/**
	 * Public immutable end-version compiler.
	 *
	 * @var EndVersionCompiler|null
	 */
	private ?EndVersionCompiler $end_version_compiler = null;

	/**
	 * Public Record Changes service.
	 *
	 * @var RecordingService|null
	 */
	private ?RecordingService $recording_service = null;

	/**
	 * Optional notification delivery service.
	 *
	 * @var object|null
	 */
	private ?object $notification_service = null;

	/**
	 * Optional approval workflow service.
	 *
	 * @var object|null
	 */
	private ?object $approval_service = null;

	/**
	 * Resolved module runtime.
	 *
	 * @var ModuleRuntime|null
	 */
	private ?ModuleRuntime $module_runtime = null;

	/**
	 * Returns the shared bootstrap instance.
	 */
	public static function instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Registers plugin services or an actionable plugin-row status.
	 */
	public function boot(): void {
		if ( $this->booted ) {
			return;
		}

		$this->booted = true;

		$requirements = new Requirements();

		if ( ! $requirements->satisfied() ) {
			self::registerPluginRowStatus(
				static function () use ( $requirements ): array {
					return $requirements->errors();
				}
			);

			return;
		}

		$migrator = new Migrator();

		if ( $migrator->currentVersion() < $migrator->latestVersion() ) {
			self::registerPluginRowStatus(
				static function (): array {
					return array(
						__(
							'JJ\'s Post Scheduler database migrations are pending. Reactivate the plugin to complete the database update before scheduling changes.',
							'jjs-post-scheduler'
						),
					);
				}
			);

			return;
		}

		$this->module_runtime = new ModuleRuntime();

		try {
			$this->module_runtime->assertHealthy();
		} catch ( ModuleRuntimeException $exception ) {
			self::registerPluginRowStatus(
				static function () use ( $exception ): array {
					return array(
						sprintf(
							/* translators: %s: module graph diagnostic. */
							__( 'JJ\'s Post Scheduler module graph is not safe to boot: %s', 'jjs-post-scheduler' ),
							$exception->getMessage()
						),
					);
				}
			);

			return;
		}

		try {
			$provider = PublicHooks::coreModuleProvider( new CoreModuleProvider() );
			if ( ! method_exists( $provider, 'boot' ) ) {
				throw new \RuntimeException( 'Module provider must expose a boot method.' );
			}

			$services = $provider->boot( $this->module_runtime );
			if ( ! $services instanceof CoreModuleServices ) {
				throw new \RuntimeException( 'Module provider must return core module services.' );
			}
		} catch ( Throwable $exception ) {
			$this->resetApplicationServices();
			self::registerPluginRowStatus(
				static function () use ( $exception ): array {
					return array( self::bootFailureMessage( $exception ) );
				}
			);

			return;
		}

		$this->provider_manager     = $services->providers();
		$this->scheduler_service    = $services->scheduler();
		$this->queue_monitor        = $services->queue();
		$this->rollback_service     = $services->rollback();
		$this->end_version_compiler = $services->endVersionCompiler();
		$this->recording_service    = $services->recording();
		$this->notification_service = $services->notifications();
		$this->approval_service     = $services->approvals();

		\ScheduledChanges\Extension\PublicHooks::coreServicesReady( $services );
	}

	/**
	 * Returns the provider service after a successful plugin boot.
	 */
	public function providerManager(): ?ProviderManager {
		return $this->provider_manager;
	}

	/**
	 * Returns the scheduling service after a successful plugin boot.
	 */
	public function schedulerService(): ?SchedulerService {
		return $this->scheduler_service;
	}

	/**
	 * Returns queue diagnostics after a successful plugin boot.
	 */
	public function queueMonitor(): ?QueueMonitor {
		return $this->queue_monitor;
	}

	/**
	 * Returns the rollback service after a successful plugin boot.
	 */
	public function rollbackService(): ?RollbackService {
		return $this->rollback_service;
	}

	/**
	 * Returns the end-version compiler after a successful plugin boot.
	 */
	public function endVersionCompiler(): ?EndVersionCompiler {
		return $this->end_version_compiler;
	}

	/**
	 * Returns the Record Changes service after a successful plugin boot.
	 */
	public function recordingService(): ?RecordingService {
		return $this->recording_service;
	}

	/**
	 * Returns notification delivery after a successful plugin boot.
	 */
	public function notificationService(): ?object {
		return $this->notification_service;
	}

	/**
	 * Returns approval workflows after a successful plugin boot.
	 */
	public function approvalService(): ?object {
		return $this->approval_service;
	}

	/**
	 * Returns the module runtime after a successful plugin boot.
	 */
	public function moduleRuntime(): ?ModuleRuntime {
		return $this->module_runtime;
	}

	/**
	 * Prevents direct construction.
	 */
	private function __construct() {
	}

	/**
	 * Clears public services after an incomplete module composition.
	 */
	private function resetApplicationServices(): void {
		$this->provider_manager     = null;
		$this->scheduler_service    = null;
		$this->queue_monitor        = null;
		$this->rollback_service     = null;
		$this->end_version_compiler = null;
		$this->recording_service    = null;
		$this->notification_service = null;
		$this->approval_service     = null;
	}

	/**
	 * Formats a bounded module composition diagnostic for administrators.
	 */
	private static function bootFailureMessage( Throwable $exception ): string {
		$message = sanitize_text_field( $exception->getMessage() );
		$roots   = array();

		if ( defined( 'SCHEDULED_CHANGES_PLUGIN_DIR' ) ) {
			$roots[] = SCHEDULED_CHANGES_PLUGIN_DIR;
		}
		if ( defined( 'WP_CONTENT_DIR' ) ) {
			$roots[] = WP_CONTENT_DIR;
		}
		if ( defined( 'ABSPATH' ) ) {
			$roots[] = ABSPATH;
		}

		foreach ( $roots as $root ) {
			if ( '' !== $root ) {
				$message = str_replace( $root, '', $message );
			}
		}

		if ( '' === $message ) {
			$message = __( 'Unexpected bootstrap failure.', 'jjs-post-scheduler' );
		}

		/* translators: %s: module composition diagnostic. */
		$format             = __( 'JJ\'s Post and Product Scheduler could not finish booting: %s', 'jjs-post-scheduler' );
		$max_total_length   = 285;
		$max_message_length = max( 0, $max_total_length - strlen( sprintf( $format, '' ) ) );

		if ( strlen( $message ) > $max_message_length ) {
			$message = substr( $message, 0, max( 0, $max_message_length - 3 ) ) . '...';
		}

		return sprintf(
			$format,
			$message
		);
	}

	/**
	 * Registers an operational status row on the Plugins screen only.
	 *
	 * @param callable():array<int,string> $messages Callback that returns messages to render.
	 */
	private static function registerPluginRowStatus( callable $messages ): void {
		add_action(
			'after_plugin_row_' . SCHEDULED_CHANGES_PLUGIN_BASENAME,
			static function () use ( $messages ): void {
				if ( ! current_user_can( 'activate_plugins' ) ) {
					return;
				}

				foreach ( $messages() as $message ) {
					self::renderPluginRowStatus( (string) $message );
				}
			}
		);
	}

	/**
	 * Renders an operational status row on the Plugins screen only.
	 */
	private static function renderPluginRowStatus( string $message ): void {
		printf(
			'<tr class="plugin-update-tr active" data-plugin="%1$s"><td colspan="4" class="plugin-update colspanchange"><div class="notice inline notice-error notice-alt"><p>%2$s</p></div></td></tr>',
			esc_attr( SCHEDULED_CHANGES_PLUGIN_BASENAME ),
			esc_html( $message )
		);
	}
}
