<?php

namespace SugarCalendarBookings;

use Exception;
use SugarCalendarBookings\SetupWizard\SetupWizard;
use SugarCalendarBookings\Support\AbstractLoader;
use SugarCalendarBookings\Support\Container;
use Throwable;

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

/**
 * Sugar Calendar Bookings Core.
 *
 * @since 1.0.0
 */
class Plugin {

	/**
	 * Core instance.
	 *
	 * @since 1.0.0
	 *
	 * @var Plugin
	 */
	private static $instance;

	/**
	 * Container.
	 *
	 * @since 1.0.0
	 *
	 * @var Container
	 */
	private $container;

	/**
	 * Loader instances.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $loaders = [];

	/**
	 * Paid returns true, free (Lite) returns false.
	 *
	 * @since 1.0.0
	 *
	 * @var bool
	 */
	private $is_pro = false;

	/**
	 * Main Sugar Calendar Bookings Instance.
	 *
	 * @since 1.0.0
	 *
	 * @return Plugin
	 */
	public static function instance(): Plugin {

		if ( self::$instance === null ) {
			self::$instance = new self();

			self::$instance->init();
		}

		return self::$instance;
	}

	/**
	 * Constructor.
	 *
	 * Private constructor to prevent direct instantiation.
	 *
	 * @since 1.0.0
	 */
	private function __construct() {

		$this->container = new Container();
	}

	/**
	 * Initialize the plugin.
	 *
	 * We have two steps initialization:
	 * 1. Bind all classes to the container during the loader registration.
	 * 2. Initialize all bootable classes in the bootstrap step.
	 *
	 * It is important to do this in two steps to avoid circular dependencies issue.
	 * So, in the bootstrap/initialization step, we already have all classes bound to
	 * the container.
	 *
	 * @since 1.0.0
	 */
	private function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		$this->register_loader( Loader::class );
		$this->load_action_scheduler();

		if ( $this->is_pro_allowed() ) {
			$this->is_pro = true;

			$this->register_loader( Pro\Loader::class );
		}

		add_action( 'plugins_loaded', fn() => $this->bootstrap(), 15 );

		// Register activation hook.
		register_activation_hook( SCBOOKINGS_PLUGIN_FILE, [ $this, 'activate' ] );
	}

	/**
	 * Plugin activation hook.
	 *
	 * @since 1.0.0
	 */
	public function activate() {

		// Store temporarily that the plugin was just activated for translation downloads.
		set_transient( 'scb_just_activated', true, 60 );

		// Store the plugin version when the initial installation occurred.
		// add_option returns false if the option already exists.
		$is_first_activation = add_option( 'sugar_calendar_bookings_initial_version', SCBOOKINGS_PLUGIN_VERSION, '', false );

		// Store the timestamp of first plugin activation.
		add_option( 'sugar_calendar_bookings_initial_activation_time', time(), '', false );

		// Store the timestamp of the first plugin activation by license type.
		$license_type = is_readable( SCBOOKINGS_PLUGIN_PATH . 'src/Pro/Pro.php' ) ? 'pro' : 'lite';
		$activated    = get_option( 'sugar_calendar_bookings_activated', [] );

		if ( empty( $activated[ $license_type ] ) ) {

			$activated[ $license_type ] = time();

			update_option( 'sugar_calendar_bookings_activated', $activated, false );
		}

		// Set default options.
		$options = new Options();

		if ( empty( $options->get_main_options() ) ) {
			$options->update( $options->get_default() );
		}

		// Set transient to trigger Setup Wizard only on first activation.
		if ( $is_first_activation ) {
			set_transient( SetupWizard::TRANSIENT_FIRST_RUN, 1, HOUR_IN_SECONDS );
		}
	}

	/**
	 * Register a loader.
	 *
	 * @since 1.0.0
	 *
	 * @param class-string<AbstractLoader> $loader_class Loader class name.
	 */
	public function register_loader( $loader_class ) {

		if ( ! is_subclass_of( $loader_class, AbstractLoader::class ) ) {
			return;
		}

		$loader = new $loader_class( $this->container );

		$loader->register_all();

		$this->loaders[] = $loader;
	}

	/**
	 * Bootstrap all of the loaders.
	 *
	 * Initialize all of the registered classes.
	 *
	 * @since 1.0.0
	 */
	private function bootstrap() {

		foreach ( $this->loaders as $loader ) {
			$loader->boot();
		}

		foreach ( $this->loaders as $loader ) {
			$loader->boot_providers();
		}
	}

	/**
	 * Whether the Pro part of the plugin is allowed to be loaded.
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	private function is_pro_allowed() {

		$is_allowed = true;

		if ( ! is_readable( SCBOOKINGS_PLUGIN_PATH . 'src/Pro/Pro.php' ) ) {
			$is_allowed = false;
		}

		/**
		 * Whether the Pro version of the plugin is allowed to be loaded.
		 *
		 * @since 1.0.0
		 *
		 * @param bool $is_allowed Whether the Pro version is allowed.
		 */
		return apply_filters( 'sugar_calendar_bookings_plugin_is_pro_allowed', $is_allowed );
	}

	/**
	 * Whether the current instance of the plugin is a paid version, or free.
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	public function is_pro(): bool {

		return $this->is_pro;
	}

	/**
	 * Get the object from the container.
	 *
	 * @since 1.0.0
	 *
	 * @template T
	 *
	 * @param class-string<T> $id Class name.
	 *
	 * @return T|null
	 */
	public static function get( $id ) {

		try {
			return self::instance()->container->get( $id );
		} catch ( Exception $e ) {
			return null;
		}
	}

	/**
	 * Make a new instance of a class with constructor parameters.
	 *
	 * Unlike get(), this always creates a fresh instance (not cached).
	 * Use for classes that require runtime parameters.
	 *
	 * @since 1.3.0
	 *
	 * @template T
	 *
	 * @param class-string<T> $id      Class name.
	 * @param mixed           ...$args Constructor arguments.
	 *
	 * @return T|null
	 */
	public static function make( $id, ...$args ) {

		try {
			return self::instance()->container->make( $id, ...$args );
		} catch ( Throwable $e ) {
			return null;
		}
	}

	/**
	 * Load Action Scheduler.
	 *
	 * @since 1.0.0
	 */
	private function load_action_scheduler() {

		require_once SCBOOKINGS_PLUGIN_PATH . '/vendor/woocommerce/action-scheduler/action-scheduler.php';
	}
}
