<?php

declare(strict_types=1);

namespace LolaCore\Marketing;

/**
 * Commercial addon catalog for passive discovery surfaces (Expansions, Addons page).
 *
 * Core ships default entries for the four launch addons. Additional (or overriding)
 * entries are registered via the {@see AddonCatalog::FILTER_HOOK} filter.
 */
class AddonCatalog {

	public const FILTER_HOOK = 'lolacore_addon_catalog';

	public const STATE_NONE      = 'none';
	public const STATE_INSTALLED = 'installed';
	public const STATE_LICENSED  = 'licensed';

	/** @var string[] Required keys on every catalog entry (filter or core). */
	private const REQUIRED_KEYS = [
		'addon_key',
		'domain_slug',
		'name',
		'tagline',
		'ability_count',
		'marketing_url',
		'addon_plugin_files',
		'base_plugin_files',
		'catalog_order',
	];

	/**
	 * Merged catalog: core defaults + filter extensions.
	 *
	 * @return array<string, array{
	 *     addon_key: string,
	 *     domain_slug: string,
	 *     name: string,
	 *     tagline: string,
	 *     ability_count: int,
	 *     is_free: bool,
	 *     marketing_url: string,
	 *     addon_plugin_files: string[],
	 *     base_plugin_files: string[],
	 *     catalog_order: int
	 * }>
	 */
	public static function getDefinitions(): array {
		/** @var array<string, array<string, mixed>> $catalog */
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Constant value is prefixed: 'lolacore_addon_catalog'.
		$catalog = \apply_filters( self::FILTER_HOOK, self::getCoreDefinitions() );

		if ( ! \is_array( $catalog ) ) {
			return self::getCoreDefinitions();
		}

		return self::normalizeDefinitions( $catalog );
	}

	/**
	 * Built-in catalog entries shipped with LolaCore core.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	public static function getCoreDefinitions(): array {
		return [
			'woocommerce_pro' => [
				'addon_key'          => 'woocommerce_pro',
				'domain_slug'        => 'woocommerce_pro',
				'name'               => 'WooCommerce Pro',
				'tagline'            => \__(
					'Lola runs your store operations: orders, refunds, customers, and analytics.',
					'lolacore'
				),
				'ability_count'      => 29,
				'marketing_url'      => 'https://lolacore.com/addons/woocommerce',
				'addon_plugin_files' => [ 'lolacore-woocommerce-pro/lolacore-woocommerce-pro.php' ],
				'base_plugin_files'  => [ 'woocommerce/woocommerce.php' ],
				'catalog_order'      => 1,
			],
			'wpcode_snippets' => [
				'addon_key'          => 'wpcode_snippets',
				'domain_slug'        => 'wpcode',
				'name'               => 'WPCode Snippets',
				'tagline'            => \__(
					'Lola writes, tests, and manages code snippets on your site.',
					'lolacore'
				),
				'ability_count'      => 8,
				'marketing_url'      => 'https://lolacore.com/addons/wpcode',
				'addon_plugin_files' => [ 'lolacore-wpcode/lolacore-wpcode.php' ],
				'base_plugin_files'  => [
					'wpcode-premium/wpcode.php',
					'insert-headers-and-footers/ihaf.php',
				],
				'catalog_order'      => 2,
			],
			'fluent_support' => [
				'addon_key'          => 'fluent_support',
				'domain_slug'        => 'fluent_support',
				'name'               => 'Lola for Fluent Support',
				'tagline'            => \__(
					'Lola reads your support tickets, spots the patterns, and drafts the replies.',
					'lolacore'
				),
				'ability_count'      => 98,
				'marketing_url'      => 'https://lolacore.com/addons/fluent-support',
				'addon_plugin_files' => [ 'lolacore-fluent-support/lolacore-fluent-support.php' ],
				'base_plugin_files'  => [ 'fluent-support/fluent-support.php' ],
				'catalog_order'      => 3,
			],
			'fluent_crm' => [
				'addon_key'          => 'fluent_crm',
				'domain_slug'        => 'fluent_crm',
				'name'               => 'Lola for Fluent CRM',
				'tagline'            => \__(
					'Lola manages your contacts, lists, and email campaigns.',
					'lolacore'
				),
				'ability_count'      => 51,
				'marketing_url'      => 'https://lolacore.com/addons/fluent-crm',
				'addon_plugin_files' => [ 'lolacore-fluent-crm/lolacore-fluent-crm.php' ],
				'base_plugin_files'  => [ 'fluent-crm/fluent-crm.php' ],
				'catalog_order'      => 4,
			],
			'elementor'       => [
				'addon_key'          => 'elementor',
				'domain_slug'        => 'elementor',
				'name'               => 'LolaCore for Elementor',
				'tagline'            => \__(
					'Lola builds and edits your Elementor pages: layouts, widgets, templates, and global styles.',
					'lolacore'
				),
				'ability_count'      => 91,
				'is_free'            => true,
				'marketing_url'      => 'https://lolacore.com/lolacore-for-elementor/',
				'addon_plugin_files' => [ 'lolacore-for-elementor/lolacore-for-elementor.php' ],
				'base_plugin_files'  => [ 'elementor/elementor.php' ],
				'catalog_order'      => 5,
			],
			'acf'             => [
				'addon_key'          => 'acf',
				'domain_slug'        => 'acf',
				'name'               => 'LolaCore for ACF',
				'tagline'            => \__(
					'Lola builds and fills your custom fields: field groups, fields, post types, and values.',
					'lolacore'
				),
				'ability_count'      => 26,
				'is_free'            => true,
				'wporg_slug'         => 'lolacore-for-acf',
				'marketing_url'      => 'https://lolacore.com/lolacore-for-acf/',
				'addon_plugin_files' => [ 'lolacore-for-acf/lolacore-for-acf.php' ],
				'base_plugin_files'  => [
					'advanced-custom-fields/acf.php',
					'advanced-custom-fields-pro/acf.php',
				],
				'catalog_order'      => 6,
			],
			'seo'             => [
				'addon_key'          => 'seo',
				'domain_slug'        => 'seo',
				'name'               => 'LolaCore for SEO',
				'tagline'            => \__(
					'Lola writes and audits your SEO: titles, meta descriptions, keyphrases, redirects, and site-wide fixes.',
					'lolacore'
				),
				'ability_count'      => 22,
				'is_free'            => true,
				'wporg_slug'         => 'lolacore-for-seo',
				'marketing_url'      => 'https://lolacore.com/lolacore-for-seo/',
				'addon_plugin_files' => [ 'lolacore-for-seo/lolacore-for-seo.php' ],
				'base_plugin_files'  => [
					'wordpress-seo/wp-seo.php',
					'wordpress-seo-premium/wp-seo-premium.php',
				],
				'catalog_order'      => 7,
			],
			'statistics'      => [
				'addon_key'          => 'statistics',
				'domain_slug'        => 'statistics',
				'name'               => 'LolaCore for Statistics',
				'tagline'            => \__(
					'Ask Lola how your site is doing: visits, sources, content, and unusual patterns worth a look.',
					'lolacore'
				),
				'ability_count'      => 18,
				'is_free'            => true,
				'wporg_slug'         => 'lolacore-for-statistics',
				'marketing_url'      => 'https://lolacore.com/lolacore-for-statistics/',
				'addon_plugin_files' => [ 'lolacore-for-statistics/lolacore-for-statistics.php' ],
				'base_plugin_files'  => [ 'wp-statistics/wp-statistics.php' ],
				'catalog_order'      => 8,
			],
			'wpforms'         => [
				'addon_key'          => 'wpforms',
				'domain_slug'        => 'wpforms',
				'name'               => 'LolaCore for Forms',
				'tagline'            => \__(
					'Build forms and check submissions by talking to Lola — no drag-and-drop builder required.',
					'lolacore'
				),
				'ability_count'      => 15,
				'is_free'            => true,
				'wporg_slug'         => 'lolacore-for-forms',
				'marketing_url'      => 'https://lolacore.com/lolacore-for-forms/',
				'addon_plugin_files' => [ 'lolacore-for-forms/lolacore-for-forms.php' ],
				'base_plugin_files'  => [
					'wpforms-lite/wpforms.php',
					'wpforms/wpforms.php',
				],
				'catalog_order'      => 9,
			],
			'fse'             => [
				'addon_key'          => 'fse',
				'domain_slug'        => 'fse',
				'name'               => 'LolaCore for FSE',
				'tagline'            => \__(
					'Edit your block theme by talking to Lola: templates, parts, patterns, navigation, and global styles in one reviewed change.',
					'lolacore'
				),
				'ability_count'      => 21,
				'is_free'            => true,
				'wporg_slug'         => 'lolacore-for-fse',
				'marketing_url'      => 'https://lolacore.com/lolacore-for-fse/',
				'addon_plugin_files' => [ 'lolacore-for-fse/lolacore-for-fse.php' ],
				'base_plugin_files'  => [],
				'catalog_order'      => 10,
			],
		];
	}

	/**
	 * @return array<int, array<string, mixed>>
	 */
	public static function getAll(): array {
		return array_values( self::getDefinitions() );
	}

	/**
	 * @return array<string, mixed>|null
	 */
	public static function getByKey( string $addonKey ): ?array {
		$definitions = self::getDefinitions();
		return $definitions[ $addonKey ] ?? null;
	}

	public static function getInstalledState( string $addonKey ): string {
		$definition = self::getByKey( $addonKey );
		if ( $definition === null ) {
			return self::STATE_NONE;
		}

		if ( ! self::isAnyPluginFileActive( $definition['addon_plugin_files'] ) ) {
			return self::STATE_NONE;
		}

		$registered = self::getRegisteredAddonDomains();
		$domainSlug = $definition['domain_slug'];

		if ( isset( $registered[ $domainSlug ] ) && ! empty( $registered[ $domainSlug ]['license_active'] ) ) {
			return self::STATE_LICENSED;
		}

		return self::STATE_INSTALLED;
	}

	/**
	 * Core ability domains plus addons with an active license.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	public static function getAvailableDomains(): array {
		$settings = new \LolaCore\Admin\Settings();
		$summary  = $settings->getAbilityDomainSummary( 'disabled_domains', true );

		return array_filter(
			$summary,
			static function ( array $domain ): bool {
				if ( ! empty( $domain['is_premium'] ) ) {
					return ! empty( $domain['license_active'] );
				}
				return true;
			}
		);
	}

	/**
	 * Catalog cards for the Expansions panel (not licensed).
	 *
	 * @return array<int, array<string, mixed>>
	 */
	public static function getSortedForExpansionsPanel( string $utmMedium = MarketingUrlBuilder::MEDIUM_SETTINGS_EXPANSIONS ): array {
		$cards = [];

		foreach ( self::getDefinitions() as $definition ) {
			$state = self::getInstalledState( $definition['addon_key'] );
			if ( $state === self::STATE_LICENSED ) {
				continue;
			}

			$cards[] = self::buildCard( $definition, $state, $utmMedium );
		}

		return self::sortExpansionCards( $cards );
	}

	/**
	 * Full catalog for the Addons admin page.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	public static function getCatalogForAddonsPage(): array {
		$cards = [];

		foreach ( self::getDefinitions() as $definition ) {
			$state = self::getInstalledState( $definition['addon_key'] );
			$cards[] = self::buildCard(
				$definition,
				$state,
				MarketingUrlBuilder::MEDIUM_SUBMENU_ADDONS
			);
		}

		return self::sortExpansionCards( $cards );
	}

	/**
	 * @param array<string, mixed> $definition
	 * @return array<string, mixed>
	 */
	private static function buildCard( array $definition, string $state, string $utmMedium ): array {
		$registered = self::getRegisteredAddonDomains();
		$domainSlug = $definition['domain_slug'];
		$domainData = $registered[ $domainSlug ] ?? [];
		$baseActive = self::isAnyPluginFileActive( $definition['base_plugin_files'] );

		return [
			'addon_key'          => $definition['addon_key'],
			'domain_slug'        => $domainSlug,
			'name'               => $definition['name'],
			'tagline'            => $definition['tagline'],
			'ability_count'      => $definition['ability_count'],
			'is_free'            => ! empty( $definition['is_free'] ),
			'wporg_slug'         => (string) ( $definition['wporg_slug'] ?? '' ),
			'state'              => $state,
			'base_plugin_active' => $baseActive,
			'learn_more_url'     => MarketingUrlBuilder::build(
				$definition['marketing_url'],
				$utmMedium,
				$definition['addon_key']
			),
			'management_url'     => $domainData['license_url'] ?? ( $domainData['settings_url'] ?? '' ),
			'catalog_order'      => $definition['catalog_order'],
		];
	}

	/**
	 * Base-plugin-active entries first, then catalog order.
	 *
	 * @param array<int, array<string, mixed>> $cards
	 * @return array<int, array<string, mixed>>
	 */
	public static function sortExpansionCards( array $cards ): array {
		usort(
			$cards,
			static function ( array $a, array $b ): int {
				$aPriority = ! empty( $a['base_plugin_active'] ) ? 0 : 1;
				$bPriority = ! empty( $b['base_plugin_active'] ) ? 0 : 1;

				if ( $aPriority !== $bPriority ) {
					return $aPriority <=> $bPriority;
				}

				return (int) $a['catalog_order'] <=> (int) $b['catalog_order'];
			}
		);

		return $cards;
	}

	/**
	 * @param string[] $pluginFiles
	 */
	public static function isAnyPluginFileActive( array $pluginFiles ): bool {
		if ( ! \function_exists( 'is_plugin_active' ) ) {
			@include_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		if ( ! \function_exists( 'is_plugin_active' ) ) {
			return false;
		}

		foreach ( $pluginFiles as $pluginFile ) {
			if ( \is_plugin_active( $pluginFile ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @param array<string, array<string, mixed>> $definitions
	 * @return array<string, array<string, mixed>>
	 */
	public static function normalizeDefinitions( array $definitions ): array {
		$normalized = [];

		foreach ( $definitions as $arrayKey => $entry ) {
			if ( ! \is_array( $entry ) ) {
				self::warnInvalidEntry( 'Catalog entry must be an array.' );
				continue;
			}

			$sanitized = self::sanitizeDefinition( \is_string( $arrayKey ) ? $arrayKey : '', $entry );
			if ( $sanitized === null ) {
				continue;
			}

			$normalized[ $sanitized['addon_key'] ] = $sanitized;
		}

		return $normalized;
	}

	/**
	 * @param array<string, mixed> $entry
	 * @return array<string, mixed>|null
	 */
	public static function sanitizeDefinition( string $arrayKey, array $entry ): ?array {
		$addonKey = \sanitize_key( (string) ( $entry['addon_key'] ?? $arrayKey ) );
		if ( $addonKey === '' ) {
			self::warnInvalidEntry( 'Missing addon_key.' );
			return null;
		}

		foreach ( self::REQUIRED_KEYS as $required ) {
			if ( ! \array_key_exists( $required, $entry ) ) {
				self::warnInvalidEntry( "Entry '{$addonKey}' is missing required field '{$required}'." );
				return null;
			}
		}

		$domainSlug = \sanitize_key( (string) $entry['domain_slug'] );
		if ( $domainSlug === '' ) {
			self::warnInvalidEntry( "Entry '{$addonKey}' has an empty domain_slug." );
			return null;
		}

		$name = \is_string( $entry['name'] ) ? \wp_strip_all_tags( $entry['name'] ) : '';
		$tagline = \is_string( $entry['tagline'] ) ? \wp_strip_all_tags( $entry['tagline'] ) : '';
		if ( $name === '' || $tagline === '' ) {
			self::warnInvalidEntry( "Entry '{$addonKey}' must include non-empty name and tagline strings." );
			return null;
		}

		$marketingUrl = \is_string( $entry['marketing_url'] ) ? $entry['marketing_url'] : '';
		if ( $marketingUrl === '' || ! \filter_var( $marketingUrl, FILTER_VALIDATE_URL ) ) {
			self::warnInvalidEntry( "Entry '{$addonKey}' has an invalid marketing_url." );
			return null;
		}

		$addonFiles = self::sanitizePluginFileList( $entry['addon_plugin_files'], $addonKey, 'addon_plugin_files' );
		if ( $addonFiles === null ) {
			return null;
		}

		$baseFiles = self::sanitizePluginFileList( $entry['base_plugin_files'], $addonKey, 'base_plugin_files', true );
		if ( $baseFiles === null ) {
			return null;
		}

		return [
			'addon_key'          => $addonKey,
			'domain_slug'        => $domainSlug,
			'name'               => $name,
			'tagline'            => $tagline,
			'ability_count'      => max( 0, (int) $entry['ability_count'] ),
			'is_free'            => ! empty( $entry['is_free'] ),
			'wporg_slug'         => isset( $entry['wporg_slug'] ) ? \sanitize_key( (string) $entry['wporg_slug'] ) : '',
			'marketing_url'      => $marketingUrl,
			'addon_plugin_files' => $addonFiles,
			'base_plugin_files'  => $baseFiles,
			'catalog_order'      => (int) $entry['catalog_order'],
		];
	}

	/**
	 * @param mixed $files
	 * @return string[]|null
	 */
	private static function sanitizePluginFileList( $files, string $addonKey, string $field, bool $allowEmpty = false ): ?array {
		if ( ! \is_array( $files ) || ( ! $allowEmpty && $files === [] ) ) {
			self::warnInvalidEntry( "Entry '{$addonKey}' must include a non-empty {$field} array." );
			return null;
		}

		if ( $files === [] ) {
			return [];
		}

		$sanitized = [];
		foreach ( $files as $file ) {
			if ( ! \is_string( $file ) || $file === '' ) {
				self::warnInvalidEntry( "Entry '{$addonKey}' has an invalid path in {$field}." );
				return null;
			}
			$sanitized[] = $file;
		}

		return $sanitized;
	}

	private static function warnInvalidEntry( string $message ): void {
		if ( \function_exists( '_doing_it_wrong' ) ) {
			\_doing_it_wrong( __METHOD__, \esc_html( $message ), '1.8.0' );
		}
	}

	/**
	 * @return array<string, array<string, mixed>>
	 */
	private static function getRegisteredAddonDomains(): array {
		$domains = \apply_filters( 'lolacore_addon_domains', [] );
		return \is_array( $domains ) ? $domains : [];
	}
}
