<?php
/**
 * Service container + boot orchestrator.
 *
 * Services are resolved lazily via {@see Plugin::get()}.
 * Use the `backreload_services` filter to swap implementations.
 *
 * @package Backreload
 */

namespace Backreload;

use Backreload\Core\Logger;
use Backreload\Core\Options;
use Backreload\Core\Encryption;
use Backreload\Core\BackupEngine;
use Backreload\Core\RestoreEngine;
use Backreload\Core\Scheduler;
use Backreload\Core\MaintenanceMode;
use Backreload\Core\MigrationPushEngine;
use Backreload\Destinations\DestinationManager;
use Backreload\Notifiers\NotifierManager;
use Backreload\Api\RestRouter;

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

final class Plugin {

	/** @var array<string,callable> */
	private $factories = [];

	/** @var array<string,object> */
	private $instances = [];

	public function __construct() {
		$this->register_default_services();
	}

	public function boot(): void {
		$this->maybe_upgrade();
		( new Admin() )->init();
		( new DashboardWidget( $this ) )->init();
		$this->get( 'destinations' )->boot();
		$this->get( 'notifiers' )->boot();
		$this->get( 'scheduler' )->boot();
		$this->get( 'maintenance' )->boot();
		// Eagerly resolve so their constructors wire the relevant cron
		// hooks. Without this, the cron callback is never registered and
		// scheduled events fire into the void.
		//   migration_push: backreload_migration_tick
		//   restore:        backreload_restore_async (used by migration auto-restore)
		$this->get( 'migration_push' );
		$this->get( 'restore' );
		( new RestRouter( $this ) )->register();
	}

	/** Run installer if DB version doesn't match (covers sites where activation hook was skipped). */
	private function maybe_upgrade(): void {
		$installed = get_option( 'backreload_db_version' );
		if ( $installed !== BACKRELOAD_DB_VER ) {
			Installer::activate();
			return;
		}
		// Safety net: even if version matches, double-check that recently-added
		// columns + composite indices are actually present. Repairs sites where
		// dbDelta silently skipped the ALTER on a previous upgrade. Each of
		// these issues a SHOW COLUMNS / SHOW INDEX query, so we gate them
		// behind a stored marker — they run once after a successful repair and
		// stay quiet thereafter, rather than running on every page request.
		$repair_marker = 'backreload_schema_repaired_at';
		if ( BACKRELOAD_DB_VER !== get_option( $repair_marker ) ) {
			Installer::ensure_columns();
			Installer::ensure_indices();
			update_option( $repair_marker, BACKRELOAD_DB_VER, false );
		}

		// Self-heal the .htaccess WAF exemption if it goes missing (cleared by
		// another plugin, manually removed, or never installed because this
		// plugin was activated before that feature shipped). Throttled to once
		// a day so we touch the filesystem at most once per server-day.
		if ( false === get_transient( 'backreload_waf_check' ) ) {
			if ( \Backreload\Core\WafExemption::uses_htaccess() && ! \Backreload\Core\WafExemption::is_installed() ) {
				\Backreload\Core\WafExemption::install();
			}
			set_transient( 'backreload_waf_check', 1, DAY_IN_SECONDS );
		}
	}

	/**
	 * Resolve a service by id (lazy singleton).
	 */
	public function get( string $id ) {
		if ( isset( $this->instances[ $id ] ) ) {
			return $this->instances[ $id ];
		}
		if ( ! isset( $this->factories[ $id ] ) ) {
			throw new \RuntimeException( esc_html(  "Unknown service: {$id}"  )  );
		}
		return $this->instances[ $id ] = ( $this->factories[ $id ] )( $this );
	}

	/**
	 * Register / override a service.
	 */
	public function set( string $id, callable $factory ): void {
		$this->factories[ $id ] = $factory;
		unset( $this->instances[ $id ] );
	}

	private function register_default_services(): void {
		$this->factories['options']      = fn() => new Options();
		$this->factories['logger']       = fn() => new Logger();
		$this->factories['encryption']   = fn( $c ) => new Encryption( $c->get( 'options' ) );
		$this->factories['destinations'] = fn( $c ) => new DestinationManager( $c );
		$this->factories['notifiers']    = fn( $c ) => new NotifierManager( $c );
		$this->factories['engine']         = fn( $c ) => new BackupEngine( $c );
		$this->factories['restore']        = fn( $c ) => new RestoreEngine( $c );
		$this->factories['scheduler']      = fn( $c ) => new Scheduler( $c );
		$this->factories['maintenance']    = fn()      => new MaintenanceMode();
		$this->factories['migration_push'] = fn( $c ) => new MigrationPushEngine( $c );

		// Allow third-party overrides:
		// add_filter( 'backreload_services', function ( $plugin ) { $plugin->set( 'logger', fn() => new MyLogger() ); return $plugin; } );
		apply_filters( 'backreload_services', $this );
	}
}
