<?php
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Integration Manager
 *
 * Centralized management for WooCommerce Appointments integrations.
 * Handles loading, instantiation, and registration of all integrations.
 *
 * @since 5.1.0
 */
class WC_Appointments_Integration_Manager {

	/**
	 * Registered integrations.
	 *
	 * @var array<string, array{file: string, class: string, check: string|callable|null, instantiate: bool|callable}>
	 */
	private array $integrations = [];

	/**
	 * Instantiated integration instances.
	 *
	 * @var array<string, WC_Appointments_Integration_Interface>
	 */
	private array $instances = [];

	/**
	 * Integration directory path.
	 *
	 * @var string
	 */
	private string $integration_dir;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->integration_dir = WC_APPOINTMENTS_ABSPATH . 'includes/integrations/';
		$this->register_integrations();
	}

	/**
	 * Register all available integrations.
	 *
	 * @return void
	 */
	private function register_integrations(): void {
		// Add-ons integration (always loaded, part of core plugin).
		$this->register(
			'addons',
			'woocommerce-product-addons/class-wc-appointments-integration-addons.php',
			'WC_Appointments_Integration_Addons',
			null, // Always load.
			true
		);

		// Third-party integrations (conditional loading).
		// WCML requires special instantiation with constructor parameters.
		$this->register(
			'wcml',
			'class-wc-appointments-integration-wcml.php',
			'WC_Appointments_Integration_WCML',
			[ 'SitePress', 'woocommerce_wpml', 'WPML_Element_Translation_Package' ],
			[ $this, 'instantiate_wcml' ] // Special instantiation callback.
		);

		$this->register(
			'follow_up_emails',
			'woocommerce-follow-up-emails/class-wc-appointments-integration-follow-ups.php',
			'WC_Appointments_Integration_WCFUE',
			'Follow_Up_Emails'
		);

		$this->register(
			'twilio_sms',
			'woocommerce-twilio-sms-notifications/class-wc-appointments-integration-twilio-sms.php',
			'WC_Appointments_Integration_Twilio_SMS',
			'WC_Twilio_SMS_Notification',
			false // Special case: doesn't implement interface, handles its own instantiation.
		);

		$this->register(
			'point_of_sale',
			'woocommerce-point-of-sale/class-wc-appointments-integration-point-of-sale.php',
			'WC_Appointments_Integration_POS',
			'WC_POS'
		);

		$this->register(
			'crm',
			'class-wc-appointments-integration-customer-relationship-manager.php',
			'WC_Appointments_Integration_CRM',
			'WC_CRM'
		);

		$this->register(
			'product_vendors',
			'class-wc-appointments-integration-product-vendors.php',
			'WC_Appointments_Integration_Product_Vendors',
			'WC_Product_Vendors'
		);

		$this->register(
			'memberships',
			'class-wc-appointments-integration-memberships.php',
			'WC_Appointments_Integration_Memberships',
			'WC_Memberships'
		);

		$this->register(
			'invoices',
			'class-wc-appointments-integration-invoices.php',
			'WC_Appointments_Integration_Invoices',
			'WC_PIP'
		);

		$this->register(
			'pdf_invoices',
			'class-wc-appointments-integration-pdf-invoices.php',
			'WC_Appointments_Integration_PDF_Invoices',
			'WPO_WCPDF'
		);

		$this->register(
			'deposits',
			'class-wc-appointments-integration-deposits.php',
			'WC_Appointments_Integration_Deposits',
			'WC_Deposits'
		);

		$this->register(
			'polylang',
			'class-wc-appointments-integration-polylang.php',
			'WC_Appointments_Integration_Polylang',
			[ 'Polylang', 'Polylang_Woocommerce' ]
		);

		$this->register(
			'wcopc',
			'class-wc-appointments-integration-wcopc.php',
			'WC_Appointments_Integration_WCOPC',
			'PP_One_Page_Checkout'
		);

		$this->register(
			'wcpbc',
			'class-wc-appointments-integration-wcpbc.php',
			'WC_Appointments_Integration_WCPBC',
			'WC_Product_Price_Based_Country'
		);

		$this->register(
			'kadence_woomail',
			'class-wc-appointments-integration-kadence-woomail.php',
			'WC_Appointments_Integration_Kadence_Woomail',
			'Kadence_Woomail_Designer'
		);

		$this->register(
			'wc_box_office',
			'class-wc-appointments-integration-wc-box-office.php',
			'WC_Appointments_Integration_WC_Box_Office',
			'WC_Box_Office'
		);

		$this->register(
			'webtomizer_deposits',
			'class-wc-appointments-integration-webtomizer-deposits.php',
			'WC_Appointments_Integration_Webtomizer_Deposits',
			'Webtomizer\WCDP\WC_Deposits'
		);

		$this->register(
			'multi_currency',
			'class-wc-appointments-integration-multi-currency.php',
			'WC_Appointments_Integration_Multi_Currency',
			'WOOMULTI_CURRENCY'
		);

		$this->register(
			'wc_payments',
			'class-wc-appointments-integration-wc-payments.php',
			'WC_Appointments_Integration_WC_Payments',
			'WC_Payments'
		);

		$this->register(
			'wc_square',
			'class-wc-appointments-integration-wc-square.php',
			'WC_Appointments_Integration_WC_Square',
			'WooCommerce_Square_Loader'
		);

		$this->register(
			'automatewoo',
			'woocommerce-automatewoo/class-wc-appointments-integration-automatewoo.php',
			'WC_Appointments_Integration_AutomateWoo',
			'AutomateWoo_Loader'
		);

		$this->register(
			'stripe',
			'class-wc-appointments-integration-stripe.php',
			'WC_Appointments_Integration_Stripe',
			'WC_Stripe'
		);

		// PayPal Payments (official WooCommerce PayPal plugin).
		// Disables smart buttons on appointment product pages.
		$this->register(
			'paypal_payments',
			'class-wc-appointments-integration-paypal-payments.php',
			'WC_Appointments_Integration_PayPal_Payments',
			fn() => class_exists( 'WooCommerce\PayPalCommerce\Plugin' ) || defined( 'PPCP_FLAG_SUBSCRIPTION' )
		);

		// Legacy Twilio SMS integration.
		$this->register(
			'twilio_sms_legacy',
			'woocommerce-twilio-sms-notifications/class-wc-appointments-integration-twilio-sms-legacy.php',
			'WC_Appointments_Integration_WCTSN',
			null, // Check handled in file itself.
			false // Handles its own instantiation.
		);
	}

	/**
	 * Register an integration.
	 *
	 * @param string                $key        Integration key.
	 * @param string                $file       Relative file path from integrations directory.
	 * @param string                $class      Class name.
	 * @param string|array|callable|null $check Dependency check (class name(s) or callable). Null = always load.
	 * @param bool|callable         $instantiate Whether to instantiate (true) or callable for custom instantiation.
	 * @return void
	 */
	private function register( string $key, string $file, string $class, $check = null, $instantiate = true ): void {
		$this->integrations[ $key ] = [
			'file'        => $file,
			'class'       => $class,
			'check'       => $check,
			'instantiate' => $instantiate,
		];
	}

	/**
	 * Load and initialize all registered integrations.
	 *
	 * @return void
	 */
	public function load_integrations(): void {
		foreach ( $this->integrations as $key => $integration ) {
			// Check dependencies.
			if ( ! $this->check_dependencies( $integration['check'] ) ) {
				continue;
			}

			// Load file.
			$file_path = $this->integration_dir . $integration['file'];
			if ( ! file_exists( $file_path ) ) {
				continue;
			}

			include_once $file_path;

			// Instantiate if class exists and instantiation is enabled.
			if ( $integration['instantiate'] && class_exists( $integration['class'] ) ) {
				if ( is_callable( $integration['instantiate'] ) ) {
					// Custom instantiation callback.
					call_user_func( $integration['instantiate'], $key, $integration['class'] );
				} else {
					// Standard instantiation.
					$this->instantiate_integration( $key, $integration['class'] );
				}
			}
		}
	}

	/**
	 * Check if integration dependencies are met.
	 *
	 * @param string|array|callable|null $check Dependency check.
	 * @return bool
	 */
	private function check_dependencies( $check ): bool {
		if ( null === $check ) {
			return true; // Always load.
		}

		if ( is_callable( $check ) ) {
			return (bool) call_user_func( $check );
		}

		if ( is_array( $check ) ) {
			// All classes must exist.
			foreach ( $check as $class ) {
				if ( ! class_exists( $class ) ) {
					return false;
				}
			}
			return true;
		}

		// Single class check.
		return class_exists( $check );
	}

	/**
	 * Instantiate an integration class.
	 *
	 * @param string $key   Integration key.
	 * @param string $class Class name.
	 * @return void
	 */
	private function instantiate_integration( string $key, string $class ): void {
		// Skip if already instantiated.
		if ( isset( $this->instances[ $key ] ) ) {
			return;
		}

		// Check if class implements the interface.
		if ( ! in_array( 'WC_Appointments_Integration_Interface', class_implements( $class ), true ) ) {
			// Class doesn't implement interface, skip automatic instantiation.
			// It may handle its own instantiation (e.g., Twilio SMS).
			return;
		}

		// Instantiate and store.
		try {
			$instance = new $class();
			$this->instances[ $key ] = $instance;

			// Set global for backward compatibility.
			$this->set_global( $key, $instance );
		} catch ( Exception $e ) {
			// Log error but don't break plugin.
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( sprintf( 'Failed to instantiate integration %s: %s', $class, $e->getMessage() ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			}
		}
	}

	/**
	 * Set global variable for backward compatibility.
	 *
	 * @param string                              $key      Integration key.
	 * @param WC_Appointments_Integration_Interface $instance Integration instance.
	 * @return void
	 */
	private function set_global( string $key, WC_Appointments_Integration_Interface $instance ): void {
		// Map integration keys to their legacy global variable names.
		// These globals are set for backward compatibility with code that may reference them.
		$global_map = [
			'addons'            => 'Appointments_Integration_Addons', // Special case: different naming.
			'wcml'              => 'wc_appointments_integration_wcml',
			'follow_up_emails'  => 'wc_appointments_integration_wcfue',
			'twilio_sms_legacy' => 'wc_appointments_integration_wctsn',
			'point_of_sale'     => 'wc_appointments_integration_pos',
			'crm'               => 'wc_appointments_integration_crm',
			'product_vendors'   => 'wc_appointments_integration_product_vendors',
			'memberships'       => 'wc_appointments_integration_memberships',
			'invoices'          => 'wc_appointments_integration_invoices',
			'pdf_invoices'      => 'wc_appointments_integration_pdf_invoices',
			'deposits'          => 'wc_appointments_integration_deposits',
			'polylang'          => 'wc_appointments_integration_polylang',
			'wcopc'             => 'wc_appointments_integration_wcopc',
			'wcpbc'             => 'wc_appointments_integration_wcpbc',
			'kadence_woomail'   => 'wc_appointments_integration_kadence_woomail',
			'wc_box_office'     => 'wc_appointments_integration_wc_box_office',
			'webtomizer_deposits' => 'wc_appointments_integration_webtomizer_deposits',
			'multi_currency'    => 'wc_appointments_integration_multi_currency',
			'wc_payments'       => 'wc_appointments_integration_wc_payments',
			'wc_square'         => 'wc_appointments_integration_wc_square',
			'automatewoo'       => 'wc_appointments_integration_automatewoo',
			'stripe'            => 'wc_appointments_integration_stripe',
			'paypal_payments'   => 'wc_appointments_integration_paypal_payments',
		];

		// Set global if mapping exists.
		if ( isset( $global_map[ $key ] ) ) {
			$GLOBALS[ $global_map[ $key ] ] = $instance;
		}
	}

	/**
	 * Get an integration instance.
	 *
	 * @param string $key Integration key.
	 * @return WC_Appointments_Integration_Interface|null
	 */
	public function get_integration( string $key ): ?WC_Appointments_Integration_Interface {
		return $this->instances[ $key ] ?? null;
	}

	/**
	 * Get all loaded integration instances.
	 *
	 * @return array<string, WC_Appointments_Integration_Interface>
	 */
	public function get_integrations(): array {
		return $this->instances;
	}

	/**
	 * Check if an integration is loaded.
	 *
	 * @param string $key Integration key.
	 * @return bool
	 */
	public function is_loaded( string $key ): bool {
		return isset( $this->instances[ $key ] );
	}

	/**
	 * Special instantiation for WCML integration (requires constructor parameters).
	 * WCML needs to be loaded after wpml_loaded action fires.
	 *
	 * @param string $key   Integration key.
	 * @param string $class Class name.
	 * @return void
	 */
	private function instantiate_wcml( string $key, string $class ): void {
		// Defer instantiation until wpml_loaded action fires.
		if ( ! did_action( 'wpml_loaded' ) ) {
			add_action( 'wpml_loaded', [ $this, 'instantiate_wcml_deferred' ], 10001 );
			return;
		}

		$this->instantiate_wcml_deferred();
	}

	/**
	 * Deferred WCML instantiation (called after wpml_loaded action).
	 *
	 * @return void
	 */
	public function instantiate_wcml_deferred(): void {
		global $sitepress, $woocommerce, $woocommerce_wpml, $wpdb, $wpml_post_translations;

		if ( ! $sitepress || ! $woocommerce ) {
			return;
		}

		if ( ! $woocommerce_wpml ) {
			$woocommerce_wpml = new woocommerce_wpml();
		}

		if ( ! $woocommerce_wpml ) {
			return;
		}

		$key   = 'wcml';
		$class = 'WC_Appointments_Integration_WCML';

		// Skip if already instantiated.
		if ( isset( $this->instances[ $key ] ) ) {
			return;
		}

		try {
			$instance = new $class( $sitepress, $woocommerce, $woocommerce_wpml, $wpdb, new WPML_Element_Translation_Package(), $wpml_post_translations );
			$this->instances[ $key ] = $instance;

			// Set global for backward compatibility.
			$this->set_global( $key, $instance );
		} catch ( Exception $e ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( sprintf( 'Failed to instantiate WCML integration: %s', $e->getMessage() ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			}
		}
	}
}

