<?php
/**
 * Canonical Divi Supreme Pro module toggle mapping and dashboard field builder.
 *
 * Add new toggleable modules in {@see ModuleRegistry::get_modules_mapping()} (one entry).
 * Rich labels/descriptions (optional): extend {@see ModuleDashboardLegacyRows::get_rows()}
 * keyed by option slug, or rely on {@see ModuleRegistry::fallback_module_settings_row()}.
 * Always-on helper modules: add to {@see ModuleRegistry::get_always_loaded_definitions()}.
 *
 * @package DiviSupreme
 */

declare(strict_types=1);

namespace DiviSupreme\Core;

if ( ! defined( 'ABSPATH' ) ) {
	die( 'Direct access forbidden.' );
}

final class ModuleRegistry {

	/**
	 * Option keys (`dsm_modules[ key ]`) mapped to DependencyInterface PHP classes (parent plus children).
	 *
	 * Order here defines Modules settings list order — keep child classes on the same line as parents.
	 *
	 * @return array<string, array<int, class-string>>
	 */
	public static function get_modules_mapping(): array {
		return array(
			'AdvancedTabs'            => array( \DiviSupreme\Modules\AdvancedTabs::class, \DiviSupreme\Modules\AdvancedTabsChild::class ),
			'AnimatedGradientText'    => array( \DiviSupreme\Modules\AnimatedGradientText::class ),
			'Badges'                  => array( \DiviSupreme\Modules\TextBadge::class ),
			'BeforeAfterImage'        => array( \DiviSupreme\Modules\BeforeAfterSlider::class ),
			'BlobImage'               => array( \DiviSupreme\Modules\BlobImage::class ),
			'BlockRevealImage'        => array( \DiviSupreme\Modules\BlockRevealImage::class ),
			'BlockRevealText'         => array( \DiviSupreme\Modules\BlockRevealText::class ),
			'BlogCarousel'            => array( \DiviSupreme\Modules\BlogCarousel::class ),
			'Breadcrumbs'             => array( \DiviSupreme\Modules\Breadcrumbs::class ),
			'BusinessHours'           => array( \DiviSupreme\Modules\BusinessHours::class, \DiviSupreme\Modules\BusinessHoursChild::class ),
			'Buttons'                 => array( \DiviSupreme\Modules\Buttons::class ),
			'Card'                    => array( \DiviSupreme\Modules\Card::class ),
			'CardCarousel'            => array( \DiviSupreme\Modules\CardCarousel::class, \DiviSupreme\Modules\CardCarouselChild::class ),
			'CircleInfo'              => array( \DiviSupreme\Modules\CircleInfo::class, \DiviSupreme\Modules\CircleInfoChild::class ),
			'ContactForm7'            => array( \DiviSupreme\Modules\ContactForm7::class ),
			'ContentTimeLine'         => array( \DiviSupreme\Modules\ContentTimeline::class, \DiviSupreme\Modules\ContentTimelineChild::class ),
			'ContentToggle'           => array( \DiviSupreme\Modules\ContentToggle::class ),
			'DualHeading'             => array( \DiviSupreme\Modules\DualHeading::class ),
			'EmbedGoogleMap'          => array( \DiviSupreme\Modules\EmbedGoogleMap::class ),
			'FacebookEmbed'           => array( \DiviSupreme\Modules\FacebookEmbed::class ),
			'FacebookSimpleFeed'      => array( \DiviSupreme\Modules\FacebookSimpleFeed::class ),
			'Faq'                     => array( \DiviSupreme\Modules\Faq::class, \DiviSupreme\Modules\FaqChild::class ),
			'FilterableGallery'       => array( \DiviSupreme\Modules\FilterableGallery::class ),
			'FlipBoxPerk'             => array( \DiviSupreme\Modules\FlipboxPerk::class, \DiviSupreme\Modules\FlipboxChild::class ),
			'FloatingMultiImages'     => array( \DiviSupreme\Modules\FloatingMultiImages::class, \DiviSupreme\Modules\FloatingMultiImagesChild::class ),
			'GlitchText'              => array( \DiviSupreme\Modules\GlitchText::class ),
			'GradientText'            => array( \DiviSupreme\Modules\GradientText::class ),
			'IconDivider'             => array( \DiviSupreme\Modules\IconDivider::class ),
			'IconList'                => array( \DiviSupreme\Modules\IconList::class, \DiviSupreme\Modules\IconListChild::class ),
			'ImageAccordion'          => array( \DiviSupreme\Modules\ImageAccordion::class, \DiviSupreme\Modules\ImageAccordionChild::class ),
			'ImageCarousel'           => array( \DiviSupreme\Modules\ImageCarousel::class ),
			'ImageHotSpots'           => array( \DiviSupreme\Modules\ImageHotspots::class, \DiviSupreme\Modules\ImageHotspotsChild::class ),
			'ImageHoverReveal'        => array( \DiviSupreme\Modules\ImageHoverReveal::class ),
			'ImageReveal'             => array( \DiviSupreme\Modules\ImageReveal::class ),
			'Lottie'                  => array( \DiviSupreme\Modules\Lottie::class ),
			'LineHover'               => array( \DiviSupreme\Modules\LineHover::class ),
			'MaskText'                => array( \DiviSupreme\Modules\MaskText::class ),
			'MasonryGallery'          => array( \DiviSupreme\Modules\MasonryGallery::class ),
			'Menu'                    => array( \DiviSupreme\Modules\Menu::class ),
			'PerspectiveImage'        => array( \DiviSupreme\Modules\PerspectiveImage::class ),
			'PostCarousel'            => array( \DiviSupreme\Modules\PostCarousel::class ),
			'PriceList'               => array( \DiviSupreme\Modules\PriceList::class, \DiviSupreme\Modules\PriceListChild::class ),
			'ProgressBar'             => array( \DiviSupreme\Modules\ProgressBar::class ),
			'RandomImage'             => array( \DiviSupreme\Modules\RandomImage::class ),
			'ScrollImage'             => array( \DiviSupreme\Modules\ScrollImage::class ),
			'Shapes'                  => array( \DiviSupreme\Modules\Shapes::class ),
			'ShuffleLetters'          => array( \DiviSupreme\Modules\ShuffleLetters::class ),
			'SocialShareButtons'      => array( \DiviSupreme\Modules\SocialShareButtons::class, \DiviSupreme\Modules\SocialShareButtonsChild::class ),
			'StarRating'              => array( \DiviSupreme\Modules\StarRating::class ),
			'StepFlow'                => array( \DiviSupreme\Modules\StepFlow::class ),
			'SvgAnimation'            => array( \DiviSupreme\Modules\SvgAnimation::class ),
			'TextDivider'             => array( \DiviSupreme\Modules\TextDivider::class ),
			'TextNotation'            => array( \DiviSupreme\Modules\TextNotation::class ),
			'TextPath'                => array( \DiviSupreme\Modules\TextPath::class ),
			'TextRotator'             => array( \DiviSupreme\Modules\TextRotator::class ),
			'TiltImage'               => array( \DiviSupreme\Modules\TiltImage::class ),
			'TypingEffect'            => array( \DiviSupreme\Modules\TypingEffect::class ),
			'AnimatedHeadline'        => array( \DiviSupreme\Modules\AnimatedHeadline::class ),
			'OnScrollHeaderAnimation' => array( \DiviSupreme\Modules\OnScrollHeaderAnimation::class ),
			'Readmore'                => array( \DiviSupreme\Modules\Readmore::class ),
			'StackingCard'            => array( \DiviSupreme\Modules\StackingCard::class, \DiviSupreme\Modules\StackingCardChild::class ),
			'TableOfContents'        => array( \DiviSupreme\Modules\TableOfContents::class ),
		);
	}

	/**
	 * Groups registered without toggles (`dsm_modules`): extra keys appended after mapped keys on the Modules screen.
	 *
	 * @return array<int, array{setting_key:string,always_enabled?:bool,classes:array<int, class-string>}>
	 */
	public static function get_always_loaded_definitions(): array {
		return array();
	}

	/**
	 * Flat class list appended after enabled mapped classes when building `$this->modules`.
	 *
	 * @return array<int, class-string>
	 */
	public static function get_always_loaded_module_classes(): array {
		$out = array();

		foreach ( self::get_always_loaded_definitions() as $group ) {
			foreach ( $group['classes'] as $class ) {
				$out[] = $class;
			}
		}

		return array_values( array_unique( $out ) );
	}

	/**
	 * Stable order for Modules settings controls and AJAX merge.
	 *
	 * @return list<string>
	 */
	public static function get_ordered_module_setting_keys(): array {
		$keys   = array_keys( self::get_modules_mapping() );
		$mapped = array_flip( $keys );

		foreach ( self::get_always_loaded_definitions() as $group ) {
			$sk = isset( $group['setting_key'] ) ? (string) $group['setting_key'] : '';

			if ( '' === $sk || isset( $mapped[ $sk ] ) ) {
				continue;
			}

			$keys[]            = $sk;
			$mapped[ $sk ] = true;
		}

		/** @var list<string> $keys */
		return $keys;
	}

	/**
	 * PascalCase / camelCase slug to `kebab-case` basename for `assets/static/modules/{slug}.svg`.
	 */
	public static function setting_key_to_icon_basename( string $setting_key ): string {
		$with_hyphens = (string) preg_replace( '/([a-z0-9])([A-Z])/', '$1-$2', $setting_key );
		$with_hyphens = strtolower( str_replace( '_', '-', $with_hyphens ) );

		return $with_hyphens;
	}

	/**
	 * Maps each `dsm_modules` option key to a dashboard filter chip slug.
	 *
	 * Slugs: layout, typography, creative_text, media, carousels, content, contact_forms, interaction,
	 * social_embeds. Unmapped keys use utilities via {@see self::get_module_dashboard_category_slug()}.
	 *
	 * @return array<string, string>
	 */
	private static function dashboard_category_by_key(): array {
		return array(
			'AdvancedTabs'            => 'content',
			'AnimatedGradientText'    => 'creative_text',
			'AnimatedHeadline'        => 'creative_text',
			'Badges'                  => 'typography',
			'BeforeAfterImage'        => 'media',
			'BlobImage'               => 'media',
			'BlockRevealImage'        => 'media',
			'BlockRevealText'         => 'creative_text',
			'BlogCarousel'            => 'carousels',
			'Breadcrumbs'             => 'layout',
			'BusinessHours'           => 'content',
			'Buttons'                 => 'layout',
			'Card'                    => 'content',
			'CardCarousel'            => 'carousels',
			'CircleInfo'              => 'content',
			'ContactForm7'            => 'contact_forms',
			'ContentTimeLine'         => 'content',
			'ContentToggle'           => 'content',
			'DualHeading'             => 'typography',
			'EmbedGoogleMap'          => 'content',
			'FacebookEmbed'           => 'social_embeds',
			'FacebookSimpleFeed'      => 'social_embeds',
			'Faq'                     => 'content',
			'FilterableGallery'       => 'media',
			'FlipBoxPerk'             => 'interaction',
			'FloatingMultiImages'     => 'media',
			'GlitchText'              => 'creative_text',
			'GradientText'            => 'typography',
			'IconDivider'             => 'layout',
			'LineHover'               => 'typography',
			'IconList'                => 'content',
			'ImageAccordion'          => 'media',
			'ImageCarousel'           => 'media',
			'ImageHotSpots'           => 'media',
			'ImageHoverReveal'        => 'media',
			'ImageReveal'             => 'media',
			'Lottie'                  => 'media',
			'MaskText'                => 'creative_text',
			'MasonryGallery'          => 'media',
			'Menu'                    => 'layout',
			'OnScrollHeaderAnimation' => 'creative_text',
			'PerspectiveImage'        => 'media',
			'PostCarousel'            => 'carousels',
			'PriceList'               => 'content',
			'ProgressBar'             => 'interaction',
			'RandomImage'             => 'media',
			'Readmore'                => 'content',
			'ScrollImage'             => 'media',
			'Shapes'                  => 'interaction',
			'ShuffleLetters'          => 'creative_text',
			'SocialShareButtons'      => 'social_embeds',
			'StarRating'              => 'interaction',
			'StackingCard'            => 'interaction',
			'StepFlow'                => 'content',
			'SvgAnimation'            => 'media',
			'TableOfContents'         => 'content',
			'TextDivider'             => 'layout',
			'TextNotation'            => 'creative_text',
			'TextPath'                => 'typography',
			'TextRotator'             => 'creative_text',
			'TiltImage'               => 'media',
			'TypingEffect'            => 'creative_text',
		);
	}

	/**
	 * Translated labels for {@see self::dashboard_category_by_key()} slugs (chip UI).
	 *
	 * @return array<string, string>
	 */
	public static function get_module_dashboard_category_labels(): array {
		return array(
			'layout'         => __( 'Layout & navigation', 'dsm-supreme-modules-pro-for-divi' ),
			'typography'     => __( 'Typography & text', 'dsm-supreme-modules-pro-for-divi' ),
			'creative_text'  => __( 'Creative text & headlines', 'dsm-supreme-modules-pro-for-divi' ),
			'media'          => __( 'Media & galleries', 'dsm-supreme-modules-pro-for-divi' ),
			'carousels'      => __( 'Carousels & feeds', 'dsm-supreme-modules-pro-for-divi' ),
			'content'        => __( 'Content blocks', 'dsm-supreme-modules-pro-for-divi' ),
			'contact_forms'  => __( 'Contact forms', 'dsm-supreme-modules-pro-for-divi' ),
			'interaction'    => __( 'Interaction & effects', 'dsm-supreme-modules-pro-for-divi' ),
			'social_embeds'  => __( 'Social & embeds', 'dsm-supreme-modules-pro-for-divi' ),
			'utilities'      => __( 'Other', 'dsm-supreme-modules-pro-for-divi' ),
		);
	}

	/**
	 * Category slug for a `dsm_modules` option key.
	 */
	public static function get_module_dashboard_category_slug( string $setting_key ): string {
		$map = self::dashboard_category_by_key();

		return isset( $map[ $setting_key ] ) ? $map[ $setting_key ] : 'utilities';
	}

	/**
	 * Ordered category slugs for consistent chip order in the React dashboard.
	 *
	 * @return list<string>
	 */
	public static function get_module_dashboard_category_order(): array {
		return array(
			'layout',
			'typography',
			'creative_text',
			'media',
			'carousels',
			'content',
			'contact_forms',
			'interaction',
			'social_embeds',
			'utilities',
		);
	}

	/**
	 * Auto row when a mapping key has no legacy dashboard definition yet.
	 *
	 * @return array<string, mixed>
	 */
	public static function fallback_module_settings_row( string $setting_key ): array {
		$slug     = self::setting_key_to_icon_basename( $setting_key );
		$relative = 'modules/' . $slug . '.svg';

		if ( defined( 'DSM_DIR_PATH' ) ) {
			$abs = DSM_DIR_PATH . 'divi-5/assets/static/' . $relative;

			if ( ! is_readable( $abs ) ) {
				$relative = 'modules/card.svg';
			}
		}

		$words = (string) preg_replace( '/(?<!^)([A-Z])/', ' $1', $setting_key );
		$words = trim( str_replace( '_', ' ', $words ) );
		/* translators: %s: module name derived from internal setting key (e.g. "Advanced Tabs"). */
		$title = sprintf( __( 'Divi %s', 'dsm-supreme-modules-pro-for-divi' ), $words );

		return array(
			'name'          => $setting_key,
			'class'         => 'dsm-settings-checkbox dsm-settings-column',
			'label'         => '<div class="dsm-settings-modules-icon"><img src="' . Assets::getAssetPath( $relative ) . '" alt=""></div><h4>' . esc_html( $title ) . '</h4>',
			'desc'          => __( 'Supreme module for Divi. Enable or disable its appearance in the Divi Builder module list.', 'dsm-supreme-modules-pro-for-divi' ),
			'documentation' => 'https://docs.divisupreme.com/',
			'type'          => 'checkbox',
			'default'       => 'on',
		);
	}

	/**
	 * Builds `dsm_modules` fields: mapping order + always-enabled metadata; supports `dsm_module_settings_fields` filter.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	public static function build_dsm_modules_settings_fields(): array {
		$by_name = array();

		foreach ( ModuleDashboardLegacyRows::get_rows() as $row ) {
			if ( isset( $row['name'] ) && is_string( $row['name'] ) && '' !== $row['name'] ) {
				$by_name[ $row['name'] ] = $row;
			}
		}

		$always = array();

		foreach ( self::get_always_loaded_definitions() as $group ) {
			$sk = isset( $group['setting_key'] ) ? (string) $group['setting_key'] : '';

			if ( '' !== $sk && ! empty( $group['always_enabled'] ) ) {
				$always[ $sk ] = true;
			}
		}

		$fields = array();

		foreach ( self::get_ordered_module_setting_keys() as $key ) {
			$row = isset( $by_name[ $key ] ) ? $by_name[ $key ] : self::fallback_module_settings_row( $key );
			$row['name'] = $key;

			$map_classes = self::get_modules_mapping();
			$row['dashboard_category']  = self::get_module_dashboard_category_slug( $key );
			$row['dashboard_has_child'] = isset( $map_classes[ $key ] ) && \count( $map_classes[ $key ] ) > 1;

			if ( isset( $always[ $key ] ) ) {
				$row['always_enabled']         = true;
				$row['default']               = 'on';
				$row['always_enabled_caption'] = __( 'Always enabled', 'dsm-supreme-modules-pro-for-divi' );
				$row['sanitize_callback']       = static function (): string {
					return 'on';
				};
			}

			$fields[] = $row;
		}

		/**
		 * Filter merged module settings rows before registration (order is already canonical).
		 *
		 * @param array<int, array<string, mixed>> $fields Field definitions for `dsm_modules`.
		 */
		return apply_filters( 'dsm_module_settings_fields', $fields );
	}
}
