<?php
/**
 * Plugin utility class.
 *
 * @package Ealab\Deployer
 */

namespace Ealab\Deployer\Utility;

use Dakwamine\Component\ComponentBasedObject;
use wpdb;

/**
 * Plugin utility class.
 */
class Plugin extends ComponentBasedObject {
	/**
	 * The table plugin map.
	 *
	 * @var array<string,string>|null
	 */
	private static ?array $table_plugin_map = null;

	/**
	 * Get the list of active plugins names.
	 *
	 * @param array<string,mixed> $options The options. Available options:
	 *     - "include_deployer" (bool): Whether to include Deploy & Sync Content in the list. Default is false.
	 *
	 * @return array<array<string,mixed>> The list of active plugins names.
	 */
	public static function getActivePlugins( array $options = array() ): array {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
		$include_deployer  = $options['include_deployer'] ?? false;
		$active_plugins    = get_option( 'active_plugins' );
		$installed_plugins = get_plugins();
		$activated_plugins = array();

		foreach ( $active_plugins as $p ) {
			if ( ! isset( $installed_plugins[ $p ] ) ) {
				// An active plugin has not been found in the installed plugins.
				// Ignore this invalid entry.
				continue;
			}

			$plugin_slug = static::getPluginSlugFromBasename( $p );

			if ( ! $include_deployer && ( DEPLOY_SYNC_CONTENT__SLUG === $plugin_slug ) ) {
				// Skip Deploy & Sync Content plugin.
				continue;
			}

			$activated_plugins[ $plugin_slug ] = array(
				'Name'      => $installed_plugins[ $p ]['Name'],
				'Version'   => $installed_plugins[ $p ]['Version'] ?? null,
				'_Basename' => $p,
				'_Slug'     => $plugin_slug,
			);
		}

		return $activated_plugins;
	}

	/**
	 * Gets the list of the database tables of active plugins.
	 *
	 * @return array<array<string,mixed>> The list of the database tables of active plugins.
	 */
	public static function getActivePluginsNamesWithTables(): array {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
		$active_plugins    = get_option( 'active_plugins' );
		$installed_plugins = get_plugins();
		$activated_plugins = array();

		foreach ( $active_plugins as $p ) {
			if ( ! isset( $installed_plugins[ $p ] ) ) {
				// An active plugin has not been found in the installed plugins.
				// Ignore this invalid entry.
				continue;
			}

			$plugin_tables = static::getTablesByPlugin( $installed_plugins[ $p ]['TextDomain'] );

			// TODO: do not use text domain as plugin identifier.
			if ( ( 'deploy-sync-content' !== $installed_plugins[ $p ]['TextDomain'] ) && ! empty( $plugin_tables ) ) {
				$activated_plugins[] = array(
					'Name'       => $installed_plugins[ $p ]['Name'],
					'TextDomain' => $installed_plugins[ $p ]['TextDomain'],
					'Tables'     => $plugin_tables,
				);
			}
		}

		return $activated_plugins;
	}

	/**
	 * Gets the list of installed plugins, disabled included, without Deploy & Sync Content.
	 *
	 * @param array<string,mixed> $options The options. Available options:
	 *     - "include_deployer" (bool): Whether to include Deploy & Sync Content in the list. Default is false.
	 *
	 * @return array<string,array<string,mixed>> The list of installed plugins names, indexed by plugin slug.
	 *   Structure of an item:
	 *   - Name: The plugin name.
	 *   - Version: The plugin version.
	 *   - _Basename: The plugin basename.
	 *   - _Slug: The plugin slug.
	 */
	public static function getInstalledPlugins( array $options = array() ): array {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
		$include_deployer = $options['include_deployer'] ?? false;

		$installed_plugins = get_plugins();

		$installed_plugins_names = array();

		foreach ( $installed_plugins as $basename => $plugin ) {
			$plugin_slug = static::getPluginSlugFromBasename( $basename );

			if ( ! $include_deployer && ( DEPLOY_SYNC_CONTENT__SLUG === $plugin_slug ) ) {
				// Skip Deploy & Sync Content plugin.
				continue;
			}

			$installed_plugins_names[ $plugin_slug ] = array(
				'Name'      => $plugin['Name'],
				'Version'   => $plugin['Version'] ?? null,
				'_Basename' => $basename,
				'_Slug'     => $plugin_slug,
			);
		}

		return $installed_plugins_names;
	}

	/**
	 * Gets the plugin basename from the plugin slug.
	 *
	 * For example, "deploy-sync-content" will return "deploy-sync-content/deploy-sync-content.php".
	 *
	 * @param string $slug The plugin slug.
	 *
	 * @return string|null The plugin basename, or null if not found.
	 */
	public static function getPluginBasenameFromSlug( string $slug ): ?string {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
		$plugins = get_plugins();

		foreach ( $plugins as $plugin_basename => $plugin_data ) {
			if ( static::getPluginSlugFromBasename( $plugin_basename ) === $slug ) {
				return $plugin_basename;
			}
		}

		return null;
	}

	/**
	 * Gets which plugins handle which database tables (trackers).
	 *
	 * @param string              $tracker_id The tracker ID.
	 * @param array<string,mixed> $options The options. Available options:
	 *     - first_only (bool): Whether to return only the first plugin found. Default: false.
	 *     - activated_only (bool): Whether to return only active plugins. Default: false.
	 *
	 * @return array<string,array<string,mixed>> The list of plugins handling the given tracker ID.
	 *     Structure of an item:
	 *     - basename (string): The plugin basename, for example "deploy-sync-content/deploy-sync-content.php".
	 *     - slug (string): The plugin slug, for example "deploy-sync-content".
	 *     - isActive (bool): Whether the plugin is active.
	 */
	public static function getPluginsByTrackerId( string $tracker_id, array $options = array() ): array {
		$map = static::getTablePluginMap();

		$plugins = $map[ $tracker_id ] ?? array();

		$plugin_candidates = array();

		foreach ( $plugins as $plugin_slug ) {
			$plugin_basename = static::getPluginBasenameFromSlug( $plugin_slug );

			if ( ! $plugin_basename ) {
				continue;
			}

			$plugin_candidates[ $plugin_slug ]['basename'] = $plugin_basename;
			$plugin_candidates[ $plugin_slug ]['slug']     = $plugin_slug;
			$plugin_candidates[ $plugin_slug ]['isActive'] = static::isPluginActive( $plugin_basename );
		}

		$first_only     = $options['first_only'] ?? false;
		$activated_only = $options['activated_only'] ?? false;

		// Search for activated plugins first.
		$activated_plugin_candidates = array_filter( $plugin_candidates, fn( $plugin ) => $plugin['isActive'] );

		if ( ! empty( $activated_plugin_candidates ) ) {
			// One or more activated plugins may handle the tracker.
			return $first_only ? array_slice( $activated_plugin_candidates, 0, 1, true ) : $activated_plugin_candidates;
		}

		if ( $activated_only ) {
			return array();
		}

		// Search for installed but not activated plugins.
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
		$installed_plugins = get_plugins();

		$installed_plugin_candidates = array_filter( $plugin_candidates, fn( $plugin ) => in_array( $plugin['basename'], $installed_plugins, true ) );

		if ( ! empty( $installed_plugin_candidates ) ) {
			// One or more installed plugins may handle the tracker.
			return $first_only ? array_slice( $installed_plugin_candidates, 0, 1, true ) : $installed_plugin_candidates;
		}

		// We don't know which plugins handle the tracker.
		// TODO: perform a deeper search, for example in the plugin code (WPD-37).
		return array();
	}

	/**
	 * Gets the plugin slug from the plugin basename.
	 *
	 * For example, "deploy-sync-content/deploy-sync-content.php" will return "deploy-sync-content".
	 * This is useful to identify a plugin wherever it is installed.
	 *
	 * @param string $basename The plugin basename, as returned by plugin_basename().
	 *
	 * @return string The plugin slug.
	 */
	public static function getPluginSlugFromBasename( string $basename ): string {
		// This implementation is based on the one of WP-CLI.
		if ( false === strpos( $basename, '/' ) ) {
			$slug = basename( $basename, '.php' );
		} else {
			$slug = dirname( $basename );
		}

		return $slug;
	}

	/**
	 * Gets the mapping of tables to plugins.
	 *
	 * @return array|string[] The mapping of tables to plugins.
	 *     The key is the table name, the value is an array of plugin slugs.
	 */
	public static function getTablePluginMap(): array {
		// TODO: Améliorer la détection du plugin qui maintient un tracker.
		//     Faire que quand il y a une action en DB (query),
		//     on regarde si la table est dans la liste des tables
		//     connues, et si non, on enregistre le backtrace,
		//     pour qu'enfin, un service mette à jour la configuration.
		if ( null === static::$table_plugin_map ) {
			$raw_map = file_get_contents( DEPLOY_SYNC_CONTENT__PLUGIN_DIR . '/plugin.json' );

			if ( false === $raw_map ) {
				// Could not load the map.
				static::$table_plugin_map = array();

				return static::$table_plugin_map;
			}

			static::$table_plugin_map = json_decode( $raw_map, true );
		}

		return static::$table_plugin_map;
	}

	/**
	 * Function to determine the associated tables for a given plugin
	 *
	 * @param string $plugin_slug The plugin slug.
	 *
	 * @return array<string> Tables names.
	 */
	public static function getTablesByPlugin( string $plugin_slug ): array {
		global $wpdb;

		$json_data   = static::getTablePluginMap();
		$table_names = array();

		foreach ( $json_data as $table => $plugin ) {
			$table_with_prefix = $wpdb->prefix . $table;

			if ( $plugin[0] === $plugin_slug && in_array( $table_with_prefix, Database::getPluginsTables(), true ) ) {
				$table_names[] = $table_with_prefix;
			}
		}

		return $table_names;
	}

	/**
	 * Checks if a plugin is active.
	 *
	 * @param string $plugin_basename The plugin basename.
	 *
	 * @return bool True if the plugin is active, false otherwise.
	 */
	public static function isPluginActive( string $plugin_basename ): bool {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';

		return is_plugin_active( $plugin_basename );
	}

	/**
	 * Gets the list of plugins database tables without name.
	 *
	 * @return array The list of the full plugins database tables.
	 */
	public static function get_plugins_tables_without_name(): array {

		$tables_names = array();

		$plugins_tables_with_name = static::getActivePluginsNamesWithTables();

		foreach ( $plugins_tables_with_name as $pt ) {
			foreach ( $pt["Tables"] as $t ) {
				array_push( $tables_names, $t );
			}
		}

		return $tables_names;
	}

	/**
	 * Gets the list of others database tables with no plugin name related.
	 *
	 * @return array The list of the others database tables.
	 */
	public static function getOthersPluginsTables(): array {

		$all_plugins_tables = Database::getPluginsTables();

		$plugins_tables_without_name = static::get_plugins_tables_without_name();

		return array_diff(
			$all_plugins_tables,
			$plugins_tables_without_name
		);
	}
}
