<?php
/**
 * Data Registry
 *
 * Tracks all data storage locations used by modules for accurate cleanup
 * during uninstall. Centralized registry for comprehensive
 * data lifecycle management.
 *
 * @package WP_PowerSuite\Core
 * @since 1.0.0
 */

namespace WP_PowerSuite\Core;

/**
 * DataRegistry Class
 *
 * Provides a centralized list of custom tables, options, and meta keys
 * used by modules. Used by UninstallHandler and uninstall.php.
 *
 * @since 1.0.0
 */
class DataRegistry {

	/**
	 * Custom table names (without wpdb prefix) created by modules.
	 * Keep in sync when adding new modules that create tables.
	 *
	 * @var array<string>
	 */
	private static array $custom_tables = [
		'wpps_activity_log',           // advanced-user-activity-log
		'wpps_short_links',            // link-shortener
		'wpps_short_links_analytics',  // link-shortener
		'wpps_form_entries',           // basic-forms
		'tl_temporary_logins',         // temporary-login
		'tl_activity_logs',            // temporary-login (activity log)
		'wpps_404_logs',               // track-404s
		'wpps_login_attempts',         // limit-login-attempts
		'wpps_magic_links',            // magic-login
		'wpps_revisions',              // code-snippet (revisions)
		'wpps_redirects',              // redirect-manager
		'wpps_email_logs',             // email-logs
		'wpps_ai_alt_log',             // ai-alt-text-generator
		'wpps_backup_logs',            // legacy: retired backup-restore table name
	];

	/**
	 * Post types owned by the plugin (snippets, etc.). Deleted on "Clear All Data".
	 *
	 * @var array<string>
	 */
	private static array $post_types_to_delete = [
		'wpps_code_snippet',  // code-snippet
		'wpps_form',          // basic-forms
	];

	/**
	 * Option name patterns for plugin data (LIKE patterns).
	 *
	 * @var array<string>
	 */
	private static array $option_patterns = [
		'wp_powersuite_%',
		'_wpps_%',
		'wpps_%',
	];

	/**
	 * Additional option patterns removed only by full data wipe (Clear All Data / uninstall).
	 *
	 * @return list<string>
	 */
	public static function getFullWipeOptionPatterns(): array {
		return array_merge(
			self::$option_patterns,
			[
				'WPPowerSuitePremium%',
			]
		);
	}

	/**
	 * Standalone module options (not in wp_powersuite_settings).
	 * Deleted on "Clear All Data" and "Reset to defaults" even when module is not loaded.
	 *
	 * @var array<string>
	 */
	private static array $standalone_module_options = [
		'wp_powersuite_system_email_settings',   // custom-wp-emails
		'wp_powersuite_email_logs_settings',     // email-logs
		'wp_powersuite_admin_menu_organizer_config', // admin-menu-organizer
		'tl_secret_key',                         // temporary-login
		'wpps_tl_db_ready',                      // temporary-login
		'wpps_ml_db_ready',                      // magic-login
		'wp_powersuite_login_as_audit',          // login-as-user
		'wpps_cs_global_safe_mode',              // code-snippet
		'wpps_ai_alt_db_version',                // ai-alt-text-generator
	];

	/**
	 * User meta key patterns for plugin data.
	 *
	 * @var array<string>
	 */
	private static array $user_meta_patterns = [
		'wp_powersuite_%',
		'_wpps_%',
		'wpps_%',
		'facebook_sign_in_id',
	];

	/**
	 * Post meta key patterns for plugin data.
	 *
	 * @var array<string>
	 */
	private static array $post_meta_patterns = [
		'wp_powersuite_%',
		'_wpps_%',
		'wpps_%',
	];

	/**
	 * Transient name patterns for plugin data.
	 * Includes wp_powersuite_%, wpps_% (module transients), tl_% (temporary-login).
	 *
	 * @var array<string>
	 */
	private static array $transient_patterns = [
		'wp_powersuite_%',
		'wpps_%',
		'tl_%',
	];

	/**
	 * Scheduled hooks owned by modules.
	 *
	 * @var array<string>
	 */
	private static array $cron_hooks = [
		'wpps_dml_cleanup_expired_zips',
		'wpps_content_expiration_check',
		'wpps_br_scheduled_backup',
		'tl_cleanup_expired_logins',
		'tl_cleanup_activity_logs',
		'wpps_login_as_cleanup_sessions',
		'wpps_magic_links_cleanup',
		'wpps_track_404s_cleanup',
		'wpps_activity_log_cleanup',
		'wp_powersuite_license_recheck',
		'wpps_email_reports_daily',
		'wpps_email_reports_weekly',
		'wpps_email_reports_monthly',
		'wpps_email_logs_prune',
		'wpps_login_attempts_prune',
		'wpps_link_shortener_analytics_cleanup',
		'wpps_author_slugs_backfill_batch',
		'wpps_ai_alt_purge_logs',
		'wpps_ai_alt_process_queue',
		'wpps_ai_alt_scheduled_scan',
		'wpps_ai_alt_content_scan_batch',
		'wpps_dc_scheduled_cleanup',
		'wpps_dc_scheduled_cleanup_continue',
		'wpps_auto_publish_missed_schedule',
		'wp_powersuite_avif_process_attachment',
	];

	/**
	 * Module-registered data locations (for audit/debugging).
	 *
	 * @var array<string, array>
	 */
	private static array $module_data_locations = [];

	/**
	 * Get full custom table names (with wpdb prefix).
	 *
	 * @return array<string>
	 */
	public static function getCustomTables(): array {
		global $wpdb;
		$tables = [];
		foreach ( self::getCustomTableNames() as $table ) {
			$tables[] = $wpdb->prefix . $table;
		}
		return $tables;
	}

	/**
	 * Custom table base names (without wpdb prefix), including tables declared by loaded modules.
	 *
	 * @return list<string>
	 */
	public static function getCustomTableNames(): array {
		$names = self::$custom_tables;

		foreach ( self::$module_data_locations as $locations ) {
			$tables = $locations['tables'] ?? [];
			if ( ! is_array( $tables ) ) {
				continue;
			}
			foreach ( $tables as $table ) {
				if ( is_string( $table ) && $table !== '' && ! in_array( $table, $names, true ) ) {
					$names[] = $table;
				}
			}
		}

		return $names;
	}

	/**
	 * Get option name patterns.
	 *
	 * @return array<string>
	 */
	public static function getOptionPatterns(): array {
		return self::$option_patterns;
	}

	/**
	 * Get standalone module options (deleted on reset/clear).
	 *
	 * @return array<string>
	 */
	public static function getStandaloneModuleOptions(): array {
		return self::$standalone_module_options;
	}

	/**
	 * Get user meta key patterns.
	 *
	 * @return array<string>
	 */
	public static function getUserMetaPatterns(): array {
		return self::$user_meta_patterns;
	}

	/**
	 * Get post meta key patterns.
	 *
	 * @return array<string>
	 */
	public static function getPostMetaPatterns(): array {
		return self::$post_meta_patterns;
	}

	/**
	 * Get transient name patterns.
	 *
	 * @return array<string>
	 */
	public static function getTransientPatterns(): array {
		return self::$transient_patterns;
	}

	/**
	 * @return array<string>
	 */
	public static function getCronHooks(): array {
		return self::$cron_hooks;
	}

	/**
	 * Cron hooks owned by the plugin core (kernel/licensing), not individual modules.
	 *
	 * Scheduled on activation and cleared on deactivation.
	 *
	 * @return list<string>
	 */
	public static function getCoreCronHooks(): array {
		$hooks = [
			'wp_powersuite_license_recheck',
		];

		/**
		 * Filter core plugin cron hooks registered on activation.
		 *
		 * @since 1.0.0
		 * @param list<string> $hooks Cron hook names.
		 */
		$hooks = apply_filters( 'wp_powersuite_core_cron_hooks', $hooks );

		return array_values( array_unique( array_filter( array_map( 'strval', $hooks ) ) ) );
	}

	/**
	 * Transient key used by {@see ModuleRegistry} for manifest metadata.
	 */
	public const MODULE_MANIFEST_TRANSIENT = 'wp_powersuite_modules_v3_manifest';

	/**
	 * Option storing a signature of modules-manifest.json so stale transients
	 * are dropped when modules are added/removed without a plugin version bump.
	 */
	public const MODULE_MANIFEST_SIG_OPTION = 'wp_powersuite_modules_manifest_sig';

	/**
	 * Module metadata transient keys (current + legacy) cleared when cache is invalidated.
	 *
	 * @return list<string>
	 */
	public static function getModuleCacheTransientKeys(): array {
		return [
			self::MODULE_MANIFEST_TRANSIENT,
			'wp_powersuite_modules',
			'wp_powersuite_modules_v2_categories',
			'wp_powersuite_modules_cache',
		];
	}

	/**
	 * Delete module metadata transients from object cache and database.
	 */
	public static function clearCaches(): void {
		foreach ( self::getModuleCacheTransientKeys() as $key ) {
			delete_transient( $key );
			if ( is_multisite() ) {
				delete_site_transient( $key );
			}
		}
		delete_option( self::MODULE_MANIFEST_SIG_OPTION );
		if ( is_multisite() ) {
			delete_site_option( self::MODULE_MANIFEST_SIG_OPTION );
		}
	}

	/**
	 * Register a module's data locations (for cleanup integration and audit).
	 *
	 * @param string $module_id Module ID.
	 * @param array  $locations Data locations from getDataLocations().
	 * @return void
	 */
	public static function registerModuleDataLocations( string $module_id, array $locations ): void {
		self::$module_data_locations[ $module_id ] = $locations;

		$tables = $locations['tables'] ?? [];
		if ( is_array( $tables ) ) {
			foreach ( $tables as $table ) {
				if ( is_string( $table ) && $table !== '' ) {
					self::registerTable( $table );
				}
			}
		}
	}

	/**
	 * Get module-registered data locations.
	 *
	 * @return array<string, array>
	 */
	public static function getModuleDataLocations(): array {
		return self::$module_data_locations;
	}

	/**
	 * Register a custom table (for modules to add their tables dynamically).
	 *
	 * @param string $table_name Table name without prefix (e.g. 'wpps_my_table').
	 * @return void
	 */
	public static function registerTable( string $table_name ): void {
		if ( ! in_array( $table_name, self::$custom_tables, true ) ) {
			self::$custom_tables[] = $table_name;
		}
	}

	/**
	 * Get all registered data locations for debugging/admin display.
	 *
	 * @return array<string, mixed>
	 */
	public static function getSummary(): array {
		return [
			'tables'   => self::getCustomTableNames(),
			'patterns' => [
				'options'    => self::$option_patterns,
				'user_meta'  => self::$user_meta_patterns,
				'post_meta'  => self::$post_meta_patterns,
				'transients' => self::$transient_patterns,
			],
			'module_locations' => self::$module_data_locations,
		];
	}

	/**
	 * Delete all plugin data from the database.
	 * Used by Settings "Clear All Data" and uninstall.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public static function deleteAllPluginData(): void {
		global $wpdb;

		foreach ( self::getCronHooks() as $hook ) {
			wp_clear_scheduled_hook( $hook );
		}

		delete_option( 'wp_powersuite_settings' );
		if ( is_multisite() ) {
			delete_site_option( 'wp_powersuite_settings' );
		}

		self::clearCaches();

		$transient_patterns = [
			'_transient_wp_powersuite_%', '_transient_timeout_wp_powersuite_%',
			'_transient_wpps_%', '_transient_timeout_wpps_%',
			'_transient_tl_%', '_transient_timeout_tl_%',
		];
		foreach ( $transient_patterns as $pat ) {
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $pat ) );
		}
		if ( is_multisite() ) {
			$site_transient_patterns = [
				'_site_transient_wp_powersuite_%', '_site_transient_timeout_wp_powersuite_%',
				'_site_transient_wpps_%', '_site_transient_timeout_wpps_%',
				'_site_transient_tl_%', '_site_transient_timeout_tl_%',
			];
			foreach ( $site_transient_patterns as $pat ) {
				$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->sitemeta} WHERE meta_key LIKE %s", $pat ) );
			}
		}

		$plugin_options = [ 'wp_powersuite_delete_on_uninstall', 'wp_powersuite_version', 'wp_powersuite_db_version' ];
		foreach ( $plugin_options as $opt ) {
			if ( class_exists( NetworkScope::class ) ) {
				NetworkScope::deletePluginOption( $opt );
				continue;
			}

			delete_option( $opt );
			if ( is_multisite() ) {
				delete_site_option( $opt );
			}
		}

		foreach ( self::getStandaloneModuleOptions() as $opt ) {
			delete_option( $opt );
			if ( is_multisite() ) {
				delete_site_option( $opt );
			}
		}

		foreach ( self::getModuleDataLocations() as $locations ) {
			$opts = $locations['options'] ?? [];
			if ( is_array( $opts ) ) {
				foreach ( $opts as $opt ) {
					if ( is_string( $opt ) && $opt !== '' ) {
						delete_option( $opt );
						if ( is_multisite() ) {
							delete_site_option( $opt );
						}
					}
				}
			}
		}

		foreach ( self::getCustomTables() as $table ) {
			$wpdb->query( "DROP TABLE IF EXISTS `{$table}`" );
		}

		$post_types = array_merge(
			self::$post_types_to_delete,
			array_filter( array_column( self::getModuleDataLocations(), 'post_type' ) )
		);
		$post_types = array_unique( array_filter( array_map( 'sanitize_key', $post_types ) ) );
		foreach ( $post_types as $post_type ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$ids = $wpdb->get_col( $wpdb->prepare(
				"SELECT ID FROM {$wpdb->posts} WHERE post_type = %s",
				$post_type
			) );
			if ( is_array( $ids ) ) {
				foreach ( $ids as $post_id ) {
					wp_delete_post( (int) $post_id, true );
				}
			}
		}

		$wpdb->query( self::buildMetaPatternDeleteSql( $wpdb->usermeta, 'meta_key', self::$user_meta_patterns ) );
		$wpdb->query( self::buildMetaPatternDeleteSql( $wpdb->postmeta, 'meta_key', self::$post_meta_patterns ) );
		$wpdb->query( self::buildMetaPatternDeleteSql( $wpdb->commentmeta, 'meta_key', self::$post_meta_patterns ) );
		$wpdb->query( self::buildOptionPatternDeleteSql( $wpdb->options, 'option_name', self::getFullWipeOptionPatterns() ) );
		if ( is_multisite() ) {
			$wpdb->query( self::buildMetaPatternDeleteSql( $wpdb->sitemeta, 'meta_key', self::getFullWipeOptionPatterns() ) );
		}

		wp_cache_flush();
	}

	/**
	 * @param list<string> $patterns SQL LIKE patterns.
	 */
	private static function buildOptionPatternDeleteSql( string $table, string $column, array $patterns ): string {
		if ( $patterns === [] ) {
			return '';
		}

		global $wpdb;

		$clauses = array_map(
			static fn( string $pattern ): string => $wpdb->prepare( "{$column} LIKE %s", $pattern ),
			$patterns
		);

		return "DELETE FROM {$table} WHERE " . implode( ' OR ', $clauses );
	}

	/**
	 * @param list<string> $patterns SQL LIKE patterns.
	 */
	private static function buildMetaPatternDeleteSql( string $table, string $column, array $patterns ): string {
		return self::buildOptionPatternDeleteSql( $table, $column, $patterns );
	}
}
