<?php
/**
 * Connector Registry
 *
 * Central registry where connector plugins self-register and where any
 * AmbassadorHub module can discover available and active connectors.
 *
 * @package AmbassadorHub\Connectors
 */

namespace AmbassadorHub\Connectors;

use function get_option;

/**
 * Singleton registry for AmbassadorHub connectors.
 *
 * ## Registration (connector plugin side)
 *
 * Connector plugins call ConnectorRegistry::register() on the `ambh_loaded`
 * action, which fires after all core components (including the registry) are initialised:
 *
 * ```php
 * add_action( 'ambh_loaded', function() {
 *     \AmbassadorHub\Connectors\ConnectorRegistry::register( new MollieConnector() );
 * } );
 * ```
 *
 * ## Active-connector slots
 *
 * Each connector type has exactly one "active" slot, chosen by the admin on the
 * Integrations settings tab and persisted in `wp_options`:
 *
 * | Slot               | Option key                           |
 * |--------------------|--------------------------------------|
 * | PSP                | `ambh_active_connector_psp`          |
 * | CRM                | `ambh_active_connector_crm`          |
 * | Marketing automation | `ambh_active_connector_marketing_automation` |
 *
 * The value stored is the connector's `get_id()` string, or an empty string
 * for "None". `get_active_psp()` / `get_active_crm()` / `get_active_marketing()`
 * return null when no connector is active or the active connector is unavailable.
 */
final class ConnectorRegistry {

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

	/** @var array<string, ConnectorInterface> Registered connectors keyed by get_id(). */
	private array $connectors = array();

	/**
	 * Private constructor — use instance().
	 */
	private function __construct() {}

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

		return self::$instance;
	}

	// -------------------------------------------------------------------------
	// Registration
	// -------------------------------------------------------------------------

	/**
	 * Register a connector and immediately wire its automation hooks.
	 *
	 * Called by connector plugins on `ambh_loaded`. The connector's
	 * register_triggers() and register_actions() methods are called here so their
	 * `ambh_automation_triggers` / `ambh_automation_actions` filter callbacks are
	 * added before the rule editor or execution engine queries them.
	 *
	 * @param AbstractConnector $connector
	 *
	 * @throws \LogicException If a connector with the same ID is already registered.
	 *
	 * @return void
	 */
	public static function register( AbstractConnector $connector ): void {

		$id = $connector->get_id();

		if ( isset( self::instance()->connectors[ $id ] ) ) {
			throw new \LogicException(
				sprintf( 'A connector with ID "%s" is already registered.', esc_html( $id ) )
			);
		}

		self::instance()->connectors[ $id ] = $connector;
		$connector::register();
		$connector::on_registration();
	}

	// -------------------------------------------------------------------------
	// Boot
	// -------------------------------------------------------------------------

	/**
	 * Initialise the connector-automations bridge.
	 *
	 * Must be called after all connectors have self-registered so that the late
	 * `ambh_automation_actions` filter added by ConnectorAutomations sees the
	 * full connector list. Plugin::load_components() schedules this on
	 * `ambh_loaded` at priority 30 (connectors register at priority 20).
	 *
	 * @return void
	 */
	public static function boot(): void {
		ConnectorAutomations::init();
	}

	// -------------------------------------------------------------------------
	// Discovery
	// -------------------------------------------------------------------------

	/**
	 * Return all registered connectors.
	 *
	 * @return array<string, ConnectorInterface> Keyed by connector ID.
	 */
	public static function get_all(): array {
		return self::instance()->connectors;
	}

	/**
	 * Return all registered connectors of a given type.
	 *
	 * @param string $type One of 'psp', 'crm', 'marketing_automation'.
	 * @return array<string, ConnectorInterface>
	 */
	public static function get_by_type( string $type ): array {
		return array_filter(
			self::instance()->connectors,
			static fn( ConnectorInterface $c ) => $c->get_type() === $type,
		);
	}

	/**
	 * Return a registered connector by ID, or null if not found.
	 *
	 * @param string $id Connector ID (e.g. 'mollie', 'hubspot').
	 * @return ConnectorInterface|null
	 */
	public static function get( string $id ): ?ConnectorInterface {
		return self::instance()->connectors[ $id ] ?? null;
	}

	// -------------------------------------------------------------------------
	// Active-connector slots
	// -------------------------------------------------------------------------

	/**
	 * Return the currently active PSP connector, or null if none is configured
	 * or the configured connector is not registered / not available.
	 *
	 * @return PspConnectorInterface|null
	 */
	public static function get_active_psp(): ?PspConnectorInterface {
		$connector = self::get_active_for_type( 'psp' );

		return $connector instanceof PspConnectorInterface ? $connector : null;
	}

	/**
	 * Return the currently active CRM connector, or null if none is configured
	 * or the configured connector is not registered / not available.
	 *
	 * @return CrmConnectorInterface|null
	 */
	public static function get_active_crm(): ?CrmConnectorInterface {
		$connector = self::get_active_for_type( 'crm' );

		return $connector instanceof CrmConnectorInterface ? $connector : null;
	}

	/**
	 * Return the currently active marketing automation connector, or null if
	 * none is configured or the configured connector is not registered / not available.
	 *
	 * @return MarketingConnectorInterface|null
	 */
	public static function get_active_marketing(): ?MarketingConnectorInterface {
		$connector = self::get_active_for_type( 'marketing_automation' );

		return $connector instanceof MarketingConnectorInterface ? $connector : null;
	}

	// -------------------------------------------------------------------------
	// Internals
	// -------------------------------------------------------------------------

	/**
	 * Resolve the active connector for a given type from wp_options.
	 *
	 * @param string $type Connector type slug.
	 * @return ConnectorInterface|null
	 */
	private static function get_active_for_type( string $type ): ?ConnectorInterface {
		$option_key = 'ambh_active_connector_' . $type;
		$active_id  = (string) get_option( $option_key, '' );

		if ( '' === $active_id ) {
			return null;
		}

		$connector = self::get( $active_id );

		if ( null === $connector || ! $connector->is_available() ) {
			return null;
		}

		return $connector;
	}
}
