<?php
/**
 * @package Hafenstudios\Kurato
 */

namespace Hafenstudios\Kurato;

use Hafenstudios\Core\Core;
use Hafenstudios\Core\Events\EventBus;
use Hafenstudios\Core\Support\SelfUpdater;
use Hafenstudios\Kurato\Admin\AiSettingsPage;
use Hafenstudios\Kurato\Admin\BoardPage;
use Hafenstudios\Kurato\Admin\BookingsPage;
use Hafenstudios\Kurato\Admin\CampaignsPage;
use Hafenstudios\Kurato\Admin\HostsPage;
use Hafenstudios\Kurato\Admin\InsightPage;
use Hafenstudios\Kurato\Campaign\CampaignMailBuilder;
use Hafenstudios\Kurato\Campaign\CampaignMaterializer;
use Hafenstudios\Kurato\Campaign\LinkRegistry;
use Hafenstudios\Kurato\Campaign\MailPostType;
use Hafenstudios\Kurato\Campaign\SegmentQuery;
use Hafenstudios\Kurato\Campaign\WelcomeMailService;
use Hafenstudios\Kurato\Cockpit\Connector;
use Hafenstudios\Kurato\Db\CampaignRepository;
use Hafenstudios\Kurato\Admin\ContactPage;
use Hafenstudios\Kurato\Admin\ContactsListPage;
use Hafenstudios\Kurato\Admin\ImportPage;
use Hafenstudios\Kurato\Admin\ListsPage;
use Hafenstudios\Kurato\Admin\MailSettingsPage;
use Hafenstudios\Kurato\Admin\Menu;
use Hafenstudios\Kurato\Admin\ProArea;
use Hafenstudios\Kurato\Admin\SegmentsPage;
use Hafenstudios\Kurato\Booking\BookingMailer;
use Hafenstudios\Kurato\Booking\BookingReminders;
use Hafenstudios\Kurato\Booking\BookingService;
use Hafenstudios\Kurato\Booking\BookingToken;
use Hafenstudios\Kurato\Booking\IcsBuilder;
use Hafenstudios\Kurato\Booking\SlotCalculator;
use Hafenstudios\Kurato\Db\AvailabilityRepository;
use Hafenstudios\Kurato\Db\BookingRepository;
use Hafenstudios\Kurato\Db\BookingTypeRepository;
use Hafenstudios\Kurato\Db\ContactRepository;
use Hafenstudios\Kurato\Db\HostRepository;
use Hafenstudios\Kurato\Db\ListRepository;
use Hafenstudios\Kurato\Db\MailEventRepository;
use Hafenstudios\Kurato\Db\SegmentRepository;
use Hafenstudios\Kurato\Db\StageRepository;
use Hafenstudios\Kurato\Db\SubscriptionRepository;
use Hafenstudios\Kurato\Db\TagRepository;
use Hafenstudios\Kurato\Db\TaskRepository;
use Hafenstudios\Kurato\Db\RecommendationRepository;
use Hafenstudios\Kurato\Doi\DoiService;
use Hafenstudios\Kurato\Insight\FunnelAnalytics;
use Hafenstudios\Kurato\Insight\OutcomeTracker;
use Hafenstudios\Kurato\Insight\RecommendationEngine;
use Hafenstudios\Kurato\Install\Installer;
use Hafenstudios\Kurato\Db\QueueRepository;
use Hafenstudios\Kurato\Intake\LeadIntake;
use Hafenstudios\Kurato\Intake\SubscriptionIntake;
use Hafenstudios\Kurato\Mail\BrevoNormalizer;
use Hafenstudios\Kurato\Mail\DeliveryLog;
use Hafenstudios\Kurato\Mail\DriverFactory;
use Hafenstudios\Kurato\Mail\MailBuilder;
use Hafenstudios\Kurato\Mail\QueueProcessor;
use Hafenstudios\Kurato\Mail\QueueScheduler;
use Hafenstudios\Kurato\Mail\SuppressionService;
use Hafenstudios\Kurato\Preferences\PreferenceCenter;
use Hafenstudios\Kurato\Privacy\PrivacyBridge;
use Hafenstudios\Kurato\Site\BookingBlock;
use Hafenstudios\Kurato\Site\BookingPage;
use Hafenstudios\Kurato\Rest\BrevoWebhookController;
use Hafenstudios\Kurato\Rest\ContactBookingsController;
use Hafenstudios\Kurato\Rest\ContactsController;
use Hafenstudios\Kurato\Rest\FunnelController;
use Hafenstudios\Kurato\Rest\InsightController;
use Hafenstudios\Kurato\Rest\PipelineController;
use Hafenstudios\Kurato\Rest\RecommendationsController;
use Hafenstudios\Kurato\Rest\SegmentsController;
use Hafenstudios\Kurato\Rest\StagesController;
use Hafenstudios\Kurato\Rest\SubscriptionsController;
use Hafenstudios\Kurato\Rest\TagsController;
use Hafenstudios\Kurato\Rest\TasksController;
use Hafenstudios\Kurato\Rest\TrackingController;
use Hafenstudios\Kurato\Rest\UnsubscribeController;
use Hafenstudios\Kurato\Mail\LinkToken;
use Hafenstudios\Kurato\Mail\TrackingToken;
use Hafenstudios\Kurato\Mail\EmailRenderer;

defined( 'ABSPATH' ) || exit;

/**
 * Zentrale Verdrahtung von Kurato (Phase 2, Etappe K1: CRM-Kern).
 *
 * Registriert die Leadlotse-Uebernahme, den Hans-Asset-Pfad und den Migrations-
 * Nachzug. REST-API, Admin-UI (Kontaktliste, Pipeline) und Newsletter/Versand
 * docken in den folgenden Etappen hier an.
 */
final class Plugin {

	private static ?Plugin $instance = null;

	/**
	 * @var array<string, object>
	 */
	private array $services = array();

	private bool $booted = false;

	private function __construct() {}

	public static function instance(): Plugin {
		return self::$instance ??= new self();
	}

	/**
	 * Verdrahtet alle Bestandteile. Idempotent.
	 *
	 * @return void
	 */
	public function boot(): void {
		if ( $this->booted ) {
			return;
		}
		$this->booted = true;

		add_action( 'init', array( $this, 'on_init' ) );

		Core::instance()->register_plugin(
			'kurato',
			array(
				'name'      => __( 'Kurato', 'kurato' ),
				'version'   => KURATO_VERSION,
				'menu_slug' => 'kurato',
			)
		);

		// Hans-Motive aus der gebuendelten Core-Kopie bereitstellen (nur, wenn
		// nicht schon ein anderes Flotten-Plugin die Basis-URL geliefert hat).
		add_filter(
			'hafenstudios/hans_image_base_url',
			static function ( $url ) {
				if ( '' !== (string) $url || ! defined( 'HAFENSTUDIOS_CORE_DIR' ) ) {
					return $url;
				}
				$dir         = wp_normalize_path( HAFENSTUDIOS_CORE_DIR . '/assets/img/hans' );
				$content_dir = wp_normalize_path( WP_CONTENT_DIR );
				if ( 0 === strpos( $dir, $content_dir ) ) {
					return trailingslashit( content_url( substr( $dir, strlen( $content_dir ) ) ) );
				}
				return $url;
			}
		);

		// Selbst-Update gegen das statische Manifest auf hafenstudios.com
		// (Muster Wellenbrecher WB12, seit dem Pro-Launch im Core). Host-gebunden
		// und fail-silent; der WP-Kern feuert den Filter anhand des
		// "Update URI"-Headers. Ohne das haetten zahlende Kunden keinen
		// Update-Pfad - Freemius liefert in der Ein-Paket-Architektur keine ZIPs.
		( new SelfUpdater(
			'kurato',
			plugin_basename( KURATO_FILE ),
			defined( 'KURATO_VERSION' ) ? KURATO_VERSION : '1.0.0'
		) )->register();

		// Pro-Weiche (K-P1, FREEMIUS-LAUNCH Abschnitt 2): haengt die Wirkung von
		// Kurato Pro an ihre Filter (Buchungs-Grenzen unbegrenzt, gemeldeter
		// Cockpit-Plan `pro`) und verdrahtet den Wegklick-Handler der Kauf-Karten.
		// Muss vor der is_admin-Schranke laufen: der gemeldete Plan wird auch
		// ausserhalb des Backends gebraucht.
		ProArea::register();

		// Leadlotse-Anfragen als Kontakte uebernehmen (BAUPLAN Abschnitt 7),
		// sofern der Betreiber die Uebernahme nicht abgeschaltet hat.
		if ( (bool) $this->settings()->get( 'intake.from_leadlotse', true ) ) {
			( new LeadIntake( $this->contacts(), $this->stages() ) )->register();
			( new SubscriptionIntake( $this->contacts(), $this->lists(), $this->subscriptions() ) )->register();
		}

		// REST-API kurato/v1 (D8): Kontakte, Pipeline, Stufen, Abo-Bestaetigung.
		add_action(
			'rest_api_init',
			function () {
				( new ContactsController( $this->contacts(), $this->stages(), $this->subscriptions(), $this->events() ) )->register_routes();
				( new StagesController( $this->stages() ) )->register_routes();
				( new PipelineController( $this->contacts(), $this->stages() ) )->register_routes();
				( new TagsController( $this->contacts(), $this->tags() ) )->register_routes();
				( new TasksController( $this->contacts(), $this->tasks() ) )->register_routes();
				( new SubscriptionsController( $this->doi() ) )->register_routes();
					( new UnsubscribeController( $this->subscriptions(), new LinkToken(), $this->contacts(), $this->events() ) )->register_routes();
					( new SegmentsController( $this->segments(), $this->segment_query() ) )->register_routes();
					( new BrevoWebhookController( new BrevoNormalizer(), $this->suppression(), (string) $this->settings()->get( 'webhook.brevo_secret', '' ) ) )->register_routes();
					// Tracking-Endpoints (K4e-2): Open-Pixel und Click-Redirect hinter dem
					// doppelten Consent-Gate. Der Site-Schalter wird hier frisch gelesen.
					( new TrackingController(
						new TrackingToken(),
						$this->campaigns(),
						$this->contacts(),
						$this->events(),
						new LinkRegistry(),
						(bool) $this->settings()->get( 'tracking.enabled', false )
					) )->register_routes();
					// Termine eines Kontakts fuer den Profil-Abschnitt "Termine" (K5c)
					// und - seit CP4 - die globale Terminliste des Cockpits.
					( new ContactBookingsController( $this->contacts(), $this->bookings(), $this->booking_types(), $this->hosts() ) )->register_routes();
					// Cockpit-Ausbau CP4: Kennzahlen und Hans-Empfehlungen lesend.
					// Beide Controller liefern nur vorhandene Datenbasis aus (H2/K4e),
					// sie rechnen und entscheiden nichts Neues.
					( new InsightController(
						new FunnelAnalytics(),
						$this->campaigns(),
						$this->queue(),
						$this->events(),
						(bool) $this->settings()->get( 'tracking.enabled', false )
					) )->register_routes();
					// Cockpit-Ausbau CP6: die Seekarte (Funnel-Ansicht mit drei
					// Zoom-Stufen). Zwei lesende Routen ueber derselben H2-Datenbasis;
					// die Aggregate rechnet die Datenbank, damit die Antwort bei
					// 10.000 Kontakten so gross bleibt wie bei 20.
					( new FunnelController(
						new FunnelAnalytics(),
						new RecommendationEngine(),
						$this->stages()
					) )->register_routes();
					// Bewusst NICHT am Pro-Gate (Projektlead-Korrektur zu K-P1): die
					// regelbasierten Hans-Empfehlungen bleiben frei, weil die live
					// laufende Cockpit-App sie als eine ihrer drei Bruecken-Spalten
					// zeigt und der Free-Zugang laut Inhaber-Entscheidung (23.07.2026)
					// genau davon lebt - KI-Teaser als Verkaufskanal, mit Kontingent im
					// Proxy. Pro sind in Kurato der Kompass (Funnel + Kampagnen-
					// Statistik) und die Textentwuerfe, nicht die Empfehlungskarte.
					( new RecommendationsController(
						new RecommendationEngine(),
						$this->recommendations(),
						Core::instance()->hans()->is_enabled()
					) )->register_routes();
			}
		);

		// Willkommensmail-Automatik (K4e-2): eine Mail bei Abo-Bestaetigung, direkt
		// ueber den Treiber (nicht durch die Marketing-Queue) und ohne Tracking.
		// Immer verdrahtet; der Handler prueft den welcome.enabled-Schalter selbst.
		// Die Wiederanmeldung im Preference Center feuert bewusst kein
		// subscription_confirmed (K4d-2), es entsteht also keine Doppel-Mail.
		( new WelcomeMailService( $this->contacts(), $this->events(), new EmailRenderer(), new LinkToken() ) )->register();

		// Preference Center light (K4d-2): oeffentliche, token-gesicherte Seite
		// (Query-Var + template_redirect), erreichbar ueber {{preferences_url}}.
		( new PreferenceCenter( $this->subscriptions(), $this->contacts(), $this->lists(), new LinkToken(), $this->events() ) )->register();

		// Zustell-Protokoll (K-M1): lauscht auf das Core-Event `mail_sent` und
		// haelt letzten Erfolg + Ringpuffer der letzten Fehlversande fuer die
		// Versand-Einstellungen bereit (datensparsam, kein Schema-Zuwachs).
		( new DeliveryLog() )->register();

		// Buchungsmodul K5c: oeffentliche Buchungsseite /termin/{slug} + Verwaltung
		// + ICS-Endpoint, Block/Shortcode, Storno-/Erinnerungsmails und der
		// Erinnerungs-Cron.
		$this->booking_page()->register();
		( new BookingBlock( $this->hosts(), $this->booking_types() ) )->register();
		$this->booking_mailer()->register();
		$this->booking_reminders()->register();

		// Cockpit-Connector (CP1): Bearer-Ticket-Auth, CORS, Pairing, agents/sync,
		// Diagnose. Null-Risiko-Default: ohne abgeschlossenes Pairing registriert
		// er weder Auth-Hooks noch REST-Routen noch CORS-Header. Rolle und
		// Admin-Seite duerfen bestehen. Muss vor der is_admin-Schranke laufen,
		// da die Auth-Hooks auch fuer Front-End-REST greifen.
		( new Connector() )->register();

		// Admin-Menue: Kontaktliste (K2), Board/Profil (K3), Listen (K4a).
		if ( is_admin() ) {
			$lists_page = new ListsPage( $this->lists() );
			$lists_page->register();

			$import_page = new ImportPage( $this->contacts(), $this->lists(), $this->subscriptions(), $this->stages() );
			$import_page->register();

			$mail_settings = new MailSettingsPage();
			$mail_settings->register();

			// KI-Einstellungen (H1): Zugang, Treiberwahl, Testaufruf.
			$ai_settings = new AiSettingsPage();
			$ai_settings->register();

			// Kompass light (H2): Funnel-Analytik + Next-Best-Action-Empfehlungen.
			$insight_page = new InsightPage( new FunnelAnalytics(), new RecommendationEngine(), $this->recommendations() );
			$insight_page->register();

			$campaigns_page = new CampaignsPage(
				$this->campaigns(),
				$this->materializer(),
				$this->lists(),
				$this->subscriptions(),
				$this->queue(),
				$this->segments(),
				$this->segment_query(),
				$this->events()
			);
			$campaigns_page->register();

			// Termine-Buchungsmodul (K5b): Buchungsliste und Buchungsseiten-Verwaltung.
			$bookings_page = new BookingsPage( $this->bookings(), $this->hosts(), $this->booking_types(), $this->contacts() );
			$bookings_page->register();

			$hosts_page = new HostsPage( $this->hosts(), $this->booking_types(), $this->availability() );
			$hosts_page->register();

			$menu = new Menu(
				new ContactsListPage( $this->contacts(), $this->stages() ),
				new BoardPage(),
				new ContactPage(),
				$lists_page,
				$import_page,
				$mail_settings,
				$campaigns_page,
				new SegmentsPage(),
				$bookings_page,
				$hosts_page,
				$ai_settings,
				$insight_page
			);
			$menu->register();
		}

		// Privacy: Kontakte an WP-Export/-Loeschung anbinden (DSGVO). Export umfasst
		// jetzt auch Abos, Queue- und Mail-Ereignis-Zeilen (K4e-1).
		( new PrivacyBridge( $this->contacts(), $this->tags(), $this->tasks(), $this->subscriptions(), $this->queue(), $this->events(), $this->bookings(), $this->booking_types() ) )->register();

		// Versand-Queue (K4b): Scheduler planen und Verarbeitungs-Hook verbinden.
		// Der Nachrichten-Builder kommt per Filter aus K4c (Kampagnen); ohne ihn
		// tut die Queue nichts (es gibt noch keine Kampagnen zu versenden).
		$scheduler = new QueueScheduler();
		add_filter( 'cron_schedules', array( $scheduler, 'add_minute_interval' ) );
		add_action( 'init', array( $scheduler, 'register' ) );
		add_action( QueueScheduler::HOOK, array( $this, 'process_queue' ) );

		// Kampagnen (K4c): Nachrichten-Builder an den Queue-Filter haengen. Der
		// Builder wird pro Durchlauf frisch gebaut (aktuelle Absender-Settings).
		EventBus::add_filter(
			'kurato_mail_builder',
			function () {
				return new CampaignMailBuilder(
					$this->campaigns(),
					$this->contacts(),
					new EmailRenderer(),
					new LinkToken(),
					(string) $this->settings()->get( 'mail.sender.from_name', '' ),
					(string) $this->settings()->get( 'mail.sender.from_email', '' ),
					(string) $this->settings()->get( 'mail.sender.address', '' ),
					(bool) $this->settings()->get( 'tracking.enabled', false ),
					new TrackingToken(),
					new LinkRegistry()
				);
			}
		);

		// Taegliche Wartung: Aufbewahrungsfrist der Kontakte.
		add_action( Installer::CRON_HOOK, array( $this, 'run_maintenance' ) );

		// Outcome-Messung der Empfehlungen (H2, HANS-KI-KONZEPT Schicht 4): taeglicher
		// Cron, misst reife angenommene Empfehlungen (T+X Tage). Selbstheilende
		// Planung fuer Bestandsinstallationen, die vor H2 aktiviert wurden.
		( new OutcomeTracker( $this->recommendations() ) )->register();
		if ( ! wp_next_scheduled( OutcomeTracker::HOOK ) ) {
			wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', OutcomeTracker::HOOK );
		}

		// Nach Plugin-Updates ausstehende Migrationen nachziehen.
		add_action(
			'admin_init',
			static function () {
				Installer::migrate();
			}
		);
	}

	/**
	 * Taegliche Wartung.
	 *
	 * @return void
	 */
	public function run_maintenance(): void {
		$this->contacts()->delete_older_than_months( absint( $this->settings()->get( 'privacy.retention_months', 0 ) ) );
		// Mail-Ereignisse nach Ablauf der Aufbewahrungsfrist loeschen (Default 365 Tage).
		$this->events()->delete_older_than_days( absint( $this->settings()->get( 'privacy.event_retention_days', 365 ) ) );
		// Vergangene Buchungen nach Ablauf der Aufbewahrungsfrist loeschen (K5c, Default 365 Tage).
		$this->bookings()->delete_past_older_than_days( absint( $this->settings()->get( 'booking.retention_days', 365 ) ) );
	}

	/**
	 * Verarbeitet einen Queue-Durchlauf (Hook der Versand-Queue).
	 *
	 * @return void
	 */
	public function process_queue(): void {
		/**
		 * Nachrichten-Builder fuer die Queue. K4c (Kampagnen) haengt hier den
		 * CampaignMailBuilder ein. Ohne registrierten Builder passiert nichts.
		 *
		 * @param MailBuilder|null $builder Builder.
		 */
		$builder = EventBus::filter( 'kurato_mail_builder', null );
		if ( ! $builder instanceof MailBuilder ) {
			return;
		}

		$driver = ( new DriverFactory() )->current();
		$rate   = (int) $this->settings()->get( 'mail.rate_per_hour', 200 );
		( new QueueProcessor( $this->queue(), $builder, $this->events() ) )->run( $driver, $rate );
	}

	/**
	 * Queue-Repository.
	 *
	 * @return QueueRepository
	 */
	public function queue(): QueueRepository {
		return $this->services['queue'] ??= new QueueRepository();
	}

	/**
	 * Mail-Ereignis-Log (hs_mail_events, K4e-1).
	 *
	 * @return MailEventRepository
	 */
	public function events(): MailEventRepository {
		return $this->services['events'] ??= new MailEventRepository();
	}

	/**
	 * Empfehlungs-Log (hs_recommendations, HANS-KI-KONZEPT Schicht 4).
	 *
	 * @return RecommendationRepository
	 */
	public function recommendations(): RecommendationRepository {
		return $this->services['recommendations'] ??= new RecommendationRepository();
	}

	/**
	 * Suppression-/Wirkungsdienst fuer Zustell-Ereignisse (K4e-1).
	 *
	 * @return SuppressionService
	 */
	public function suppression(): SuppressionService {
		return $this->services['suppression'] ??= new SuppressionService( $this->contacts(), $this->subscriptions(), $this->events() );
	}

	/**
	 * init-Hook: Textdomain.
	 *
	 * @return void
	 */
	public function on_init(): void {
		load_plugin_textdomain( 'kurato', false, dirname( plugin_basename( KURATO_FILE ) ) . '/languages' );

		// CPT `hs_mail` (Kampagnen-Inhalt, Block-Editor). Registrierung im init-Hook.
		( new MailPostType() )->register();
	}

	/**
	 * Settings-Speicher (Core-Framework, Namespace kurato).
	 *
	 * @return \Hafenstudios\Core\Settings\Settings
	 */
	public function settings() {
		return Core::instance()->settings( 'kurato', Installer::default_settings() );
	}

	/**
	 * Kontakt-Repository.
	 *
	 * @return ContactRepository
	 */
	public function contacts(): ContactRepository {
		return $this->services['contacts'] ??= new ContactRepository();
	}

	/**
	 * Stufen-Repository.
	 *
	 * @return StageRepository
	 */
	public function stages(): StageRepository {
		return $this->services['stages'] ??= new StageRepository();
	}

	/**
	 * Tag-Repository.
	 *
	 * @return TagRepository
	 */
	public function tags(): TagRepository {
		return $this->services['tags'] ??= new TagRepository();
	}

	/**
	 * Listen-Repository.
	 *
	 * @return ListRepository
	 */
	public function lists(): ListRepository {
		return $this->services['lists'] ??= new ListRepository();
	}

	/**
	 * Abo-Repository.
	 *
	 * @return SubscriptionRepository
	 */
	public function subscriptions(): SubscriptionRepository {
		return $this->services['subscriptions'] ??= new SubscriptionRepository();
	}

	/**
	 * Kurato-DOI-Service (Abo-Bestaetigung).
	 *
	 * @return DoiService
	 */
	public function doi(): DoiService {
		return $this->services['doi'] ??= new DoiService( $this->subscriptions() );
	}

	/**
	 * Task-Repository (Wiedervorlagen).
	 *
	 * @return TaskRepository
	 */
	public function tasks(): TaskRepository {
		return $this->services['tasks'] ??= new TaskRepository();
	}

	/**
	 * Kampagnen-Repository (Versandzustand, K4c).
	 *
	 * @return CampaignRepository
	 */
	public function campaigns(): CampaignRepository {
		return $this->services['campaigns'] ??= new CampaignRepository();
	}

	/**
	 * Empfaenger-Materialisierung fuer die spaetere Kampagnen-UI (K4c-3).
	 * Baut den Snapshot einer Kampagne in die Queue.
	 *
	 * @return CampaignMaterializer
	 */
	public function materializer(): CampaignMaterializer {
		return new CampaignMaterializer( $this->campaigns(), $this->subscriptions(), $this->contacts(), $this->queue(), $this->segments(), $this->segment_query() );
	}

	/**
	 * Host-Repository (Ansprechpartner des Buchungsmoduls, K5a).
	 *
	 * @return HostRepository
	 */
	public function hosts(): HostRepository {
		return $this->services['hosts'] ??= new HostRepository();
	}

	/**
	 * Terminarten-Repository (K5a).
	 *
	 * @return BookingTypeRepository
	 */
	public function booking_types(): BookingTypeRepository {
		return $this->services['booking_types'] ??= new BookingTypeRepository();
	}

	/**
	 * Verfuegbarkeits-Repository (Wochenraster + Ausnahmen, K5a).
	 *
	 * @return AvailabilityRepository
	 */
	public function availability(): AvailabilityRepository {
		return $this->services['availability'] ??= new AvailabilityRepository();
	}

	/**
	 * Buchungs-Repository (K5a).
	 *
	 * @return BookingRepository
	 */
	public function bookings(): BookingRepository {
		return $this->services['bookings'] ??= new BookingRepository();
	}

	/**
	 * Transaktionsmailer des Buchungsmoduls (K5c).
	 *
	 * @return BookingMailer
	 */
	public function booking_mailer(): BookingMailer {
		return $this->services['booking_mailer'] ??= new BookingMailer(
			$this->bookings(),
			$this->hosts(),
			$this->booking_types(),
			$this->contacts(),
			new IcsBuilder(),
			new BookingToken()
		);
	}

	/**
	 * Buchungs-Kernlogik inkl. CRM-Verzahnung (K5c).
	 *
	 * @return BookingService
	 */
	public function booking_service(): BookingService {
		return $this->services['booking_service'] ??= new BookingService(
			$this->hosts(),
			$this->booking_types(),
			$this->availability(),
			$this->bookings(),
			$this->contacts(),
			$this->stages(),
			$this->tasks(),
			new SlotCalculator(),
			$this->booking_mailer()
		);
	}

	/**
	 * Oeffentliche Buchungsseite + Verwaltung + ICS-Endpoint (K5c).
	 *
	 * @return BookingPage
	 */
	public function booking_page(): BookingPage {
		return $this->services['booking_page'] ??= new BookingPage(
			$this->hosts(),
			$this->booking_types(),
			$this->availability(),
			$this->bookings(),
			new SlotCalculator(),
			$this->booking_service(),
			$this->booking_mailer(),
			new BookingToken()
		);
	}

	/**
	 * Erinnerungs-Cron des Buchungsmoduls (K5c).
	 *
	 * @return BookingReminders
	 */
	public function booking_reminders(): BookingReminders {
		return $this->services['booking_reminders'] ??= new BookingReminders(
			$this->bookings(),
			$this->hosts(),
			$this->booking_types(),
			$this->contacts(),
			$this->booking_mailer()
		);
	}

	/**
	 * Segment-Repository (gespeicherte Filter, K4d).
	 *
	 * @return SegmentRepository
	 */
	public function segments(): SegmentRepository {
		return $this->services['segments'] ??= new SegmentRepository();
	}

	/**
	 * Segment-Auswertung (SQL-Kompilierung, K4d). Die Tabellennamen werden hier
	 * aus dem Schema aufgeloest, damit der Service ohne Schema-Zugriff testbar bleibt.
	 *
	 * @return SegmentQuery
	 */
	public function segment_query(): SegmentQuery {
		return $this->services['segment_query'] ??= new SegmentQuery(
			\Hafenstudios\Kurato\Db\Schema::table( 'contacts' ),
			\Hafenstudios\Kurato\Db\Schema::table( 'tag_rel' ),
			\Hafenstudios\Kurato\Db\Schema::table( 'subscriptions' )
		);
	}
}
