<?php

namespace WeDevs\AppBridge\Core\Notifications;

/**
 * Runtime view over the registered event catalogs.
 *
 * Catalogs are owned by their integrations (Vendor App ships with Dokan,
 * Customer App ships with WooCommerce) and contributed via
 * `IntegrationInterface::event_catalogs()`, which AppBridge feeds into the
 * `app_bridge_event_catalogs` filter.
 *
 * On top of those declarations we overlay per-event user edits saved at
 *   `<integration_option>.push_events.<event_id>.{enabled,title,body}`.
 * The integration declares which wp_option holds its overrides via
 * `IntegrationInterface::event_option_keys()`; AppBridge feeds those into
 * the `app_bridge_notification_option_keys` filter so vendor and customer
 * toggles can be resolved independently.
 *
 * The dispatcher consumes this to resolve a `<app>.<event_id>` key into
 * its final title/body/channel/route, honoring any admin-side toggles.
 *
 * Use {@see EventRegistry::flush()} after settings are saved if you need
 * the next dispatch to see the new values within the same request.
 */
class EventRegistry {

	private const FALLBACK_OPTION = 'app_bridge_event_settings';

	/** @var array<string, array<string, mixed>>|null */
	private static ?array $cache = null;

	/**
	 * Resolve a single event by app + id, with user overrides applied.
	 *
	 * @return array<string, mixed>|null
	 */
	public static function get( string $app, string $event ): ?array {
		$catalog = self::all();

		return $catalog[ self::key( $app, $event ) ] ?? null;
	}

	public static function exists( string $app, string $event ): bool {
		return null !== self::get( $app, $event );
	}

	public static function key( string $app, string $event ): string {
		return $app . '.' . $event;
	}

	/**
	 * Flat map of every registered event keyed by `<app>.<event_id>`.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	public static function all(): array {
		if ( null === self::$cache ) {
			/**
			 * Catalogs contributed by active integrations.
			 *
			 * Shape: [ '<app>' => <category-grouped events>, ... ].
			 *
			 * @param array<string, array<string, array{label:string, events:array<int, array<string, mixed>>}>> $catalogs
			 */
			$catalogs = apply_filters( 'app_bridge_event_catalogs', [] );

			$entries = [];
			foreach ( $catalogs as $app => $categories ) {
				$app_name  = (string) $app;
				$overrides = self::saved_overrides_for_app( $app_name );
				$entries   = array_merge( $entries, self::flatten( $app_name, (array) $categories, $overrides ) );
			}

			self::$cache = apply_filters( 'app_bridge_notification_events', $entries );
		}

		return self::$cache;
	}

	public static function flush(): void {
		self::$cache = null;
	}

	/**
	 * Walk a category-grouped catalog and emit a flat keyed map.
	 *
	 * @param array<string, array{label:string, events:array<int, array<string, mixed>>}> $categories
	 * @param array<string, array<string, mixed>>                                          $overrides
	 *
	 * @return array<string, array<string, mixed>>
	 */
	private static function flatten( string $app, array $categories, array $overrides ): array {
		$flat = [];

		foreach ( $categories as $cat_id => $cat ) {
			foreach ( ( $cat['events'] ?? [] ) as $event ) {
				$id = (string) ( $event['id'] ?? '' );
				if ( '' === $id ) {
					continue;
				}
				$key         = self::key( $app, $id );
				$user_edits  = $overrides[ $id ] ?? [];

				$flat[ $key ] = [
					'app'                     => $app,
					'category'                => $cat_id,
					'id'                      => $id,
					'label'                   => (string) ( $event['label'] ?? $id ),
					'enabled'                 => (bool) ( $user_edits['enabled'] ?? true ),
					'title'                   => self::pick_string( $user_edits['title'] ?? null, $event['title'] ?? '' ),
					'body'                    => self::pick_string( $user_edits['body'] ?? null, $event['body'] ?? '' ),
					'notification_channel_id' => (string) ( $event['channel'] ?? 'general' ),
					'screen_route'            => (string) ( $event['screen_route'] ?? '' ),
				];
			}
		}

		return $flat;
	}

	/**
	 * Empty user-supplied strings fall back to the catalog default — protects
	 * us from a saved blank wiping out the message.
	 */
	private static function pick_string( $user, $fallback ): string {
		$user = is_string( $user ) ? trim( $user ) : '';

		return '' !== $user ? $user : (string) $fallback;
	}

	/**
	 * Resolve the wp_option key that stores per-event overrides for $app.
	 * Integrations declare this via {@see IntegrationInterface::event_option_keys()};
	 * AppBridge feeds the map into the filter below.
	 */
	private static function option_key_for_app( string $app ): string {
		/**
		 * Map of `app => wp_option name` for override storage.
		 *
		 * @param array<string, string> $keys
		 */
		$keys = (array) apply_filters( 'app_bridge_notification_option_keys', [] );

		$key = isset( $keys[ $app ] ) ? (string) $keys[ $app ] : '';

		return '' !== $key ? $key : self::FALLBACK_OPTION;
	}

	/**
	 * @return array<string, array<string, mixed>>
	 */
	private static function saved_overrides_for_app( string $app ): array {
		$settings = get_option( self::option_key_for_app( $app ), [] );

		if ( ! is_array( $settings ) || empty( $settings['push_events'] ) || ! is_array( $settings['push_events'] ) ) {
			return [];
		}

		return $settings['push_events'];
	}
}
