<?php
/**
 * Plugin container.
 *
 * @package RewindBrevoFormBridge
 */

namespace RewindBrevoFormBridge;

use RewindBrevoFormBridge\Adapters\Registry;
use RewindBrevoFormBridge\Admin\AdminAssets;
use RewindBrevoFormBridge\Admin\AdminMenu;
use RewindBrevoFormBridge\Admin\Settings;
use RewindBrevoFormBridge\Api\Attributes;
use RewindBrevoFormBridge\Api\Cache;
use RewindBrevoFormBridge\Api\Client;
use RewindBrevoFormBridge\Api\Contacts;
use RewindBrevoFormBridge\Api\Lists;
use RewindBrevoFormBridge\Api\Transactional;
use RewindBrevoFormBridge\Emails\Mailer;
use RewindBrevoFormBridge\Emails\SmtpRelay;
use RewindBrevoFormBridge\Feeds\Processor;
use RewindBrevoFormBridge\Feeds\Repository;
use RewindBrevoFormBridge\Logs\LogRepository;
use RewindBrevoFormBridge\Mapping\Coercer;
use RewindBrevoFormBridge\Mapping\Phone;
use RewindBrevoFormBridge\Rest\ApiRegistry;
use RewindBrevoFormBridge\Rest\Controllers\BrevoController;
use RewindBrevoFormBridge\Rest\Controllers\EmailController;
use RewindBrevoFormBridge\Rest\Controllers\FeedsController;
use RewindBrevoFormBridge\Rest\Controllers\FormsController;
use RewindBrevoFormBridge\Rest\Controllers\LogsController;
use RewindBrevoFormBridge\Rest\Controllers\SettingsController;
use RewindBrevoFormBridge\Rest\Controllers\SystemController;

defined( 'ABSPATH' ) || exit;

/**
 * Singleton container that wires everything together on plugins_loaded.
 *
 * @since 0.1.0
 */
final class Plugin {

	/**
	 * Single instance.
	 *
	 * @var Plugin|null
	 */
	private static ?Plugin $instance = null;

	/**
	 * Settings service.
	 *
	 * @var Settings
	 */
	private Settings $settings;

	/**
	 * Brevo API client.
	 *
	 * @var Client
	 */
	private Client $client;

	/**
	 * Transient cache for API reads.
	 *
	 * @var Cache
	 */
	private Cache $cache;

	/**
	 * Lists resource.
	 *
	 * @var Lists
	 */
	private Lists $lists;

	/**
	 * Attributes resource.
	 *
	 * @var Attributes
	 */
	private Attributes $attributes;

	/**
	 * Contacts resource.
	 *
	 * @var Contacts
	 */
	private Contacts $contacts;

	/**
	 * Transactional email resource.
	 *
	 * @var Transactional
	 */
	private Transactional $transactional;

	/**
	 * wp_mail() over the Brevo API.
	 *
	 * @var Mailer
	 */
	private Mailer $mailer;

	/**
	 * wp_mail() over the Brevo SMTP relay.
	 *
	 * @var SmtpRelay
	 */
	private SmtpRelay $smtp;

	/**
	 * Feed repository.
	 *
	 * @var Repository
	 */
	private Repository $feed_repository;

	/**
	 * Delivery-log repository.
	 *
	 * @var LogRepository
	 */
	private LogRepository $log_repository;

	/**
	 * Feed processor.
	 *
	 * @var Processor
	 */
	private Processor $processor;

	/**
	 * Adapter registry.
	 *
	 * @var Registry
	 */
	private Registry $registry;

	/**
	 * REST controller registry.
	 *
	 * @var ApiRegistry
	 */
	private ApiRegistry $rest;

	/**
	 * Get (and lazily create) the container.
	 *
	 * @since 0.1.0
	 * @return Plugin
	 */
	public static function instance(): Plugin {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Wire services. Private — use instance().
	 */
	private function __construct() {
		Schema::maybe_upgrade();

		$this->settings   = new Settings();
		$this->client     = new Client( $this->settings );
		$this->cache      = new Cache();
		$this->lists      = new Lists( $this->client, $this->cache );
		$this->attributes = new Attributes( $this->client, $this->cache );
		$this->contacts   = new Contacts( $this->client );

		// Site email. Both transports attach on every request — wp_mail()
		// is not an admin-only call — and each one no-ops unless it is the
		// selected, fully configured transport.
		$this->transactional = new Transactional( $this->client, $this->cache );
		$this->mailer        = new Mailer( $this->settings, $this->transactional );
		$this->smtp          = new SmtpRelay( $this->settings );

		$this->mailer->register_hooks();
		$this->smtp->register_hooks();

		$this->feed_repository = new Repository();
		$this->log_repository  = new LogRepository();
		$this->processor       = new Processor(
			$this->feed_repository,
			$this->contacts,
			$this->attributes,
			new Coercer(),
			new Phone(),
			$this->settings
		);

		// Adapters attach on admin too: AJAX submissions arrive through
		// admin-ajax.php.
		$this->registry = new Registry( $this->processor );
		$this->registry->register_hooks();

		// The dashboard talks to the REST API, which must be registered on
		// front-end requests too — rest_api_init does not run under is_admin().
		$this->rest = new ApiRegistry( $this->controllers() );
		$this->rest->register_hooks();

		if ( is_admin() ) {
			$menu = new AdminMenu();
			$menu->register_hooks();

			$assets = new AdminAssets( $this->settings, $this->registry, $menu );
			$assets->register_hooks();
		}
	}

	/**
	 * Build the rbfb/v1 controller map.
	 *
	 * @since 0.4.0
	 * @return \RewindBrevoFormBridge\Rest\BaseController[]
	 */
	private function controllers(): array {
		return array(
			'settings' => new SettingsController( $this->settings ),
			'brevo'    => new BrevoController( $this->client, $this->cache, $this->lists, $this->attributes, $this->settings ),
			'feeds'    => new FeedsController( $this->feed_repository, $this->registry, $this->log_repository ),
			'forms'    => new FormsController( $this->registry ),
			'logs'     => new LogsController( $this->log_repository, $this->contacts, $this->settings ),
			'email'    => new EmailController( $this->settings, $this->mailer, $this->smtp, $this->transactional, $this->cache ),
			'system'   => new SystemController( $this->settings, $this->feed_repository, $this->log_repository, $this->registry ),
		);
	}

	/**
	 * Settings accessor.
	 *
	 * @since 0.1.0
	 * @return Settings
	 */
	public function settings(): Settings {
		return $this->settings;
	}

	/**
	 * API client accessor.
	 *
	 * @since 0.1.0
	 * @return Client
	 */
	public function client(): Client {
		return $this->client;
	}

	/**
	 * Lists resource accessor.
	 *
	 * @since 0.2.0
	 * @return Lists
	 */
	public function lists(): Lists {
		return $this->lists;
	}

	/**
	 * Attributes resource accessor.
	 *
	 * @since 0.2.0
	 * @return Attributes
	 */
	public function attributes(): Attributes {
		return $this->attributes;
	}

	/**
	 * Delivery-log repository accessor.
	 *
	 * @since 0.4.0
	 * @return LogRepository
	 */
	public function logs(): LogRepository {
		return $this->log_repository;
	}

	/**
	 * Cache accessor.
	 *
	 * @since 0.2.0
	 * @return Cache
	 */
	public function cache(): Cache {
		return $this->cache;
	}
}
