<?php

namespace Antimanual;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Selective plugin uninstall cleanup.
 *
 * Allows administrators to choose which module-specific database data should
 * be removed when the plugin is uninstalled.
 */
class Uninstall {
	/**
	 * Option key that stores per-module uninstall cleanup preferences.
	 */
	const MODULE_UNINSTALL_OPTION_KEY = 'antimanual_module_uninstall_prefs';

	/**
	 * Default module cleanup preferences.
	 *
	 * @return array<string, bool>
	 */
	public static function get_module_uninstall_defaults(): array {
		return array(
			'knowledge_base'   => false,
			'chatbot'          => false,
			'search_block'     => false,
			'generate_post'    => false,
			'auto_posting'     => false,
			'auto_update'      => false,
			'bulk_rewrite'     => false,
			'repurpose_studio' => false,
			'faq_generator'    => false,
			'generate_docs'    => false,
			'forum_automation' => false,
			'translation'      => false,
			'seo_agent'         => false,
			'google_integration' => false,
			'internal_linking' => false,
		);
	}

	/**
	 * Get saved module uninstall preferences merged with defaults.
	 *
	 * @return array<string, bool>
	 */
	public static function get_module_uninstall_preferences(): array {
		$saved = \get_option( self::MODULE_UNINSTALL_OPTION_KEY, array() );

		return \wp_parse_args(
			is_array( $saved ) ? $saved : array(),
			self::get_module_uninstall_defaults()
		);
	}

	/**
	 * UI metadata for each module cleanup toggle.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	public static function get_module_uninstall_ui_data(): array {
		global $wpdb;
		$p = $wpdb->prefix;

		return array(
			'knowledge_base' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes all Knowledge Base embeddings and settings. Features that depend on the Knowledge Base (Chatbot, AI Search Block) will lose AI context until the Knowledge Base is rebuilt.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'      => [ "{$p}antimanual_embeddings" ],
					'post_types'  => [],
					'meta_keys'   => [],
					'option_keys' => [ 'antimanual_kb_preferences' ],
				),
			),
			'chatbot' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes chatbot conversations, feedback votes, the assistant landing page, and chatbot-specific settings. Shared Knowledge Base embeddings are kept.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'     => [ "{$p}antimanual_query_votes" ],
					'post_types' => [ 'atml_conversation' ],
				),
			),
			'search_block' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes AI Search Block usage history. Shared Knowledge Base embeddings are kept so other AI features do not lose context unexpectedly.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'      => [ "{$p}antimanual_usage" ],
					'post_types'  => [],
					'meta_keys'   => [],
					'option_keys' => [],
				),
			),
			'generate_post' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes Antimanual generation metadata from posts, but keeps the generated posts themselves because they are your site content.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'      => [],
					'post_types'  => [],
					'meta_keys'   => [ '_atml_generated_post', '_atml_generated_at', '_atml_generation_prompt' ],
					'option_keys' => [],
				),
			),
			'auto_posting' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes Auto Posting schedules, queue records, runtime transients, and Antimanual auto-post tracking meta.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'     => [ "{$p}antimanual_auto_posting_queue" ],
					'post_types' => [ 'atml_auto_posting' ],
				),
			),
			'auto_update' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes Auto Update schedules, runtime transients, and Antimanual auto-update tracking meta.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'     => [],
					'post_types' => [ 'atml_auto_update' ],
				),
			),
			'bulk_rewrite' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes Bulk Rewrite usage tracking stored by Antimanual.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'      => [ "{$p}antimanual_usage" ],
					'post_types'  => [],
					'meta_keys'   => [],
					'option_keys' => [],
				),
			),
			'repurpose_studio' => array(
				'supports_cleanup' => false,
				'summary'          => __( 'Repurpose Studio does not store dedicated module data in the WordPress database.', 'antimanual' ),
				'supports_reset'   => false,
				'db_info'          => array(
					'tables'     => [],
					'post_types' => [],
				),
			),
			'faq_generator' => array(
				'supports_cleanup' => false,
				'summary'          => __( 'FAQ Generator creates site content, but does not keep dedicated module-only database records that are safe to purge automatically.', 'antimanual' ),
				'supports_reset'   => false,
				'db_info'          => array(
					'tables'     => [],
					'post_types' => [],
				),
			),
			'generate_docs' => array(
				'supports_cleanup' => false,
				'summary'          => __( 'Generate Docs creates documentation content, but does not keep dedicated module-only database records that are safe to purge automatically.', 'antimanual' ),
				'supports_reset'   => false,
				'db_info'          => array(
					'tables'     => [],
					'post_types' => [],
				),
			),
			'forum_automation' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes Forum Automation settings and usage tracking. Existing forum topics and replies are kept because they are site content.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'      => [ "{$p}antimanual_usage" ],
					'post_types'  => [],
					'meta_keys'   => [],
					'option_keys' => [],
				),
			),
			'translation' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes stored translations and translation settings created by Antimanual Translation.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'     => [ "{$p}atml_translations" ],
					'post_types' => [],
				),
			),
			'seo_agent' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes SEO Agent monitoring settings, cached analyses, and Antimanual SEO meta fields.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'      => [],
					'post_types'  => [],
					'meta_keys'   => [ '_atml_meta_description', '_atml_focus_keyword', '_atml_seo_force_lazy_loading', '_atml_seo_force_noopener' ],
					'option_keys' => [],
				),
			),
			'google_integration' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes stored Google API credentials (PSI key, service account JSON), cached tokens, Indexing API logs, and Core Web Vitals history.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'      => [],
					'post_types'  => [],
					'meta_keys'   => [],
					'option_keys' => [
						'antimanual_google_psi_api_key',
						'antimanual_google_sa_json',
						'antimanual_indexing_api_log',
						'antimanual_indexing_api_auto_submit',
						'antimanual_seo_cwv_history_v1',
					],
				),
			),
			'internal_linking' => array(
				'supports_cleanup' => true,
				'summary'          => __( 'Deletes Internal Linking cached reports and cache-version settings.', 'antimanual' ),
				'supports_reset'   => true,
				'db_info'          => array(
					'tables'      => [],
					'post_types'  => [],
					'meta_keys'   => [],
					'option_keys' => [ 'atml_il_report_cache_version' ],
				),
			),
		);
	}

	/**
	 * Calculate the effective used size of a table in bytes.
	 *
	 * MySQL/InnoDB often keeps freed pages allocated after DELETE operations and
	 * reports them via DATA_FREE. Subtracting that reusable space gives the UI a
	 * size value that better matches the current live data footprint.
	 *
	 * @param mixed $data_length  Reported data length in bytes.
	 * @param mixed $index_length Reported index length in bytes.
	 * @param mixed $data_free    Reported reusable free space in bytes.
	 * @return int Effective used size in bytes.
	 */
	public static function calculate_effective_table_size_bytes( $data_length, $index_length, $data_free = 0 ): int {
		$total     = (int) $data_length + (int) $index_length;
		$reusable  = max( 0, (int) $data_free );

		return max( 0, $total - $reusable );
	}

	/**
	 * Get the exact row count for a plugin-owned table.
	 *
	 * information_schema.TABLES and SHOW TABLE STATUS may return approximate row
	 * counts for InnoDB tables, so we query the table directly when the admin UI
	 * needs an authoritative answer.
	 *
	 * @param string $table_name Fully-qualified table name including prefix.
	 * @return int Exact row count, or 0 when the table is missing/unreadable.
	 */
	public static function get_table_row_count( string $table_name ): int {
		global $wpdb;

		$count = $wpdb->get_var(
			$wpdb->prepare( 'SELECT COUNT(*) FROM %i', $table_name )
		);

		return max( 0, (int) $count );
	}

	/**
	 * Get the live content footprint for a table in bytes.
	 *
	 * This sums the byte length of the current values in each column rather than
	 * relying on InnoDB allocation metadata, which can remain inflated long after
	 * rows are deleted or tables are truncated.
	 *
	 * @param string $table_name Fully-qualified table name including prefix.
	 * @return int Live data footprint in bytes.
	 */
	public static function get_table_content_size_bytes( string $table_name ): int {
		global $wpdb;

		$columns = $wpdb->get_col(
			$wpdb->prepare(
				'SELECT column_name
				 FROM information_schema.COLUMNS
				 WHERE table_schema = DATABASE()
				   AND table_name = %s
				 ORDER BY ordinal_position',
				$table_name
			)
		);

		if ( empty( $columns ) || ! is_array( $columns ) ) {
			return 0;
		}

		$expressions = array();
		foreach ( $columns as $column_name ) {
			$safe_column   = str_replace( '`', '``', (string) $column_name );
			$expressions[] = "COALESCE(SUM(OCTET_LENGTH(`{$safe_column}`)), 0)";
		}

		$safe_table_name = str_replace( '`', '``', $table_name );
		$size            = $wpdb->get_var(
			sprintf(
				'SELECT %s AS size_bytes FROM `%s`',
				implode( ' + ', $expressions ),
				$safe_table_name
			)
		);

		return max( 0, (int) $size );
	}

	/**
	 * Get the combined data + index size for each plugin-owned database table.
	 *
	 * Returns a flat map of full table name (including WP prefix) => size in bytes.
	 * Tables that have not been created yet or contain no data return 0.
	 *
	 * @return array<string, int>
	 */
	public static function get_module_table_sizes(): array {
		global $wpdb;

		// Collect every declared table name across all modules.
		$all_tables = array();
		foreach ( self::get_module_uninstall_ui_data() as $module_data ) {
			if ( ! empty( $module_data['db_info']['tables'] ) && is_array( $module_data['db_info']['tables'] ) ) {
				foreach ( $module_data['db_info']['tables'] as $table_name ) {
					$all_tables[] = (string) $table_name;
				}
			}
		}

		$all_tables = array_values( array_unique( $all_tables ) );

		if ( empty( $all_tables ) ) {
			return array();
		}

		$sizes = array_fill_keys( $all_tables, 0 );

		foreach ( $all_tables as $table_name ) {
			$row_count            = self::get_table_row_count( $table_name );
			$sizes[ $table_name ] = $row_count > 0
				? self::get_table_content_size_bytes( $table_name )
				: 0;
		}

		return $sizes;
	}

	/**
	 * Get data sizes for individual postmeta keys and option keys used by modules.
	 *
	 * Returns a flat map of key_name => size_bytes (0 if no data exists).
	 * Used by the admin UI to show per-key data footprint.
	 *
	 * @return array<string, int>
	 */
	public static function get_wp_key_sizes(): array {
		global $wpdb;

		$all_meta_keys   = array();
		$all_option_keys = array();

		foreach ( self::get_module_uninstall_ui_data() as $module_data ) {
			if ( ! empty( $module_data['db_info']['meta_keys'] ) && is_array( $module_data['db_info']['meta_keys'] ) ) {
				foreach ( $module_data['db_info']['meta_keys'] as $key ) {
					$all_meta_keys[] = (string) $key;
				}
			}
			if ( ! empty( $module_data['db_info']['option_keys'] ) && is_array( $module_data['db_info']['option_keys'] ) ) {
				foreach ( $module_data['db_info']['option_keys'] as $key ) {
					$all_option_keys[] = (string) $key;
				}
			}
		}

		$all_meta_keys   = array_values( array_unique( $all_meta_keys ) );
		$all_option_keys = array_values( array_unique( $all_option_keys ) );

		$sizes = array();

		if ( ! empty( $all_meta_keys ) ) {
			foreach ( $all_meta_keys as $key ) {
				$sizes[ $key ] = 0;
			}
			$placeholders = implode( ', ', array_fill( 0, count( $all_meta_keys ), '%s' ) );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT meta_key, SUM(LENGTH(meta_value)) AS size_bytes FROM {$wpdb->postmeta} WHERE meta_key IN ({$placeholders}) GROUP BY meta_key",
					...$all_meta_keys
				)
			);
			if ( $rows ) {
				foreach ( $rows as $row ) {
					$sizes[ $row->meta_key ] = (int) $row->size_bytes;
				}
			}
		}

		if ( ! empty( $all_option_keys ) ) {
			foreach ( $all_option_keys as $key ) {
				$sizes[ $key ] = 0;
			}
			$placeholders = implode( ', ', array_fill( 0, count( $all_option_keys ), '%s' ) );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT option_name, LENGTH(option_value) AS size_bytes FROM {$wpdb->options} WHERE option_name IN ({$placeholders})",
					...$all_option_keys
				)
			);
			if ( $rows ) {
				foreach ( $rows as $row ) {
					$sizes[ $row->option_name ] = (int) $row->size_bytes;
				}
			}
		}

		return $sizes;
	}

	/**
	 * Execute uninstall cleanup.
	 *
	 * @return void
	 */
	public static function run(): void {
		$cleanup_prefs = self::get_module_uninstall_preferences();

		if ( ! empty( $cleanup_prefs['chatbot'] ) ) {
			self::cleanup_chatbot();
		}

		if ( ! empty( $cleanup_prefs['search_block'] ) ) {
			self::cleanup_search_block();
		}

		if ( ! empty( $cleanup_prefs['generate_post'] ) ) {
			self::cleanup_generate_post();
		}

		if ( ! empty( $cleanup_prefs['auto_posting'] ) ) {
			self::cleanup_auto_posting();
		}

		if ( ! empty( $cleanup_prefs['auto_update'] ) ) {
			self::cleanup_auto_update();
		}

		if ( ! empty( $cleanup_prefs['bulk_rewrite'] ) ) {
			self::cleanup_bulk_rewrite();
		}

		if ( ! empty( $cleanup_prefs['forum_automation'] ) ) {
			self::cleanup_forum_automation();
		}

		if ( ! empty( $cleanup_prefs['translation'] ) ) {
			self::cleanup_translation();
		}

		if ( ! empty( $cleanup_prefs['seo_agent'] ) ) {
			self::cleanup_seo_agent();
		}

		if ( ! empty( $cleanup_prefs['google_integration'] ) ) {
			self::cleanup_google_integration();
		}

		if ( ! empty( $cleanup_prefs['internal_linking'] ) ) {
			self::cleanup_internal_linking();
		}

		if ( ! empty( $cleanup_prefs['knowledge_base'] ) ) {
			self::cleanup_knowledge_base();
		}

		\delete_option( self::MODULE_UNINSTALL_OPTION_KEY );
	}

	/**
	 * Reset all module-owned data without dropping tables.
	 *
	 * Used for the "Reset Data" action on the Modules settings page. Unlike
	 * uninstall cleanup, this keeps table structures intact and preserves
	 * module settings so the administrator's configuration is not lost.
	 *
	 * @param string $module_key Module key.
	 * @return bool True if the module key was recognised, false otherwise.
	 */
	public static function reset_module( string $module_key ): bool {
		switch ( $module_key ) {
			case 'knowledge_base':
				self::truncate_tables( [ 'antimanual_embeddings' ] );
				return true;

			case 'chatbot':
				self::truncate_tables( [ 'antimanual_query_votes' ] );
				self::delete_posts_by_type( [ 'atml_conversation' ] );
				self::delete_post_meta_keys( [
					'_antimanual_assistant_page',
					'_atml_provider_conversation_id',
					'_atml_conversation_provider',
					'_atml_conversation_messages',
					'_atml_lead_email',
					'_atml_lead_name',
					'_atml_lead_ip',
					'_atml_lead_source',
					'_atml_lead_note',
					'_atml_lead_status',
					'_atml_unresolved',
				] );
				self::delete_usage_rows( [ 'chatbot' ] );
				return true;

			case 'search_block':
				self::delete_usage_rows( [ 'search_block' ] );
				return true;

			case 'generate_post':
				self::delete_post_meta_keys( [
					'_atml_generated_post',
					'_atml_generated_at',
					'_atml_generation_prompt',
				] );
				return true;

			case 'auto_posting':
				self::delete_posts_by_type( [ 'atml_auto_posting' ] );
				self::delete_post_meta_keys( [
					'_atml_auto_post_status',
					'_atml_auto_posting_id',
					'_atml_auto_post_topic',
					'_atml_auto_post_error',
					'_atml_auto_post_scheduled',
				] );
				self::truncate_tables( [ 'antimanual_auto_posting_queue' ] );
				self::delete_transients( [ 'atml_auto_posting_last_check', 'atml_auto_posting_running' ] );
				return true;

			case 'auto_update':
				self::delete_posts_by_type( [ 'atml_auto_update' ] );
				self::delete_post_meta_keys( [
					'_atml_auto_update_status',
					'_atml_auto_update_id',
					'_atml_auto_update_error',
					'_atml_auto_updated_at',
				] );
				self::delete_transients( [ 'atml_auto_update_last_check', 'atml_auto_update_running' ] );
				return true;

			case 'bulk_rewrite':
				self::delete_usage_rows( [ 'bulk_rewrite' ] );
				return true;

			case 'forum_automation':
				self::delete_usage_rows( [ 'forum_answer', 'forum_conversion' ] );
				return true;

			case 'translation':
				self::truncate_tables( [ 'atml_translations' ] );
				return true;

			case 'seo_agent':
				self::delete_post_meta_keys( [
					'_atml_meta_description',
					'_atml_focus_keyword',
					'_atml_seo_force_lazy_loading',
					'_atml_seo_force_noopener',
				] );
				self::delete_transients_by_prefixes( [ 'atml_seo_agent_cache_' ] );
				return true;

			case 'google_integration':
				self::delete_exact_options( [
					'antimanual_google_psi_api_key',
					'antimanual_google_sa_json',
					'antimanual_indexing_api_log',
					'antimanual_indexing_api_auto_submit',
					'antimanual_seo_cwv_history_v1',
				] );
				self::delete_transients_by_prefixes( [ 'atml_google_token_', 'atml_psi_', 'atml_cwv_fixes_' ] );
				return true;

			case 'internal_linking':
				self::delete_exact_options( [ 'atml_il_report_cache_version' ] );
				self::delete_transients_by_prefixes( [ 'atml_il_report_' ] );
				return true;

			default:
				return false;
		}
	}

	/**
	 * Cleanup AI Chatbot data.
	 */
	private static function cleanup_chatbot(): void {
		self::delete_posts_by_type( array( 'atml_conversation' ) );
		self::delete_posts_by_meta( '_antimanual_assistant_page', '1' );
		self::delete_post_meta_keys( array(
			'_antimanual_assistant_page',
			'_atml_provider_conversation_id',
			'_atml_conversation_provider',
			'_atml_conversation_messages',
			'_atml_lead_email',
			'_atml_lead_name',
			'_atml_lead_ip',
			'_atml_lead_source',
			'_atml_lead_note',
			'_atml_lead_status',
			'_atml_unresolved',
		) );
		self::delete_options_by_prefixes( array( 'antimanual_chatbot_' ) );
		self::drop_tables( array( 'antimanual_query_votes' ) );
		self::delete_usage_rows( array( 'chatbot' ) );
	}

	/**
	 * Cleanup AI Search Block data.
	 */
	private static function cleanup_search_block(): void {
		self::delete_usage_rows( array( 'search_block' ) );
	}

	/**
	 * Cleanup Generate Post data.
	 */
	private static function cleanup_generate_post(): void {
		self::delete_post_meta_keys( array(
			'_atml_generated_post',
			'_atml_generated_at',
			'_atml_generation_prompt',
		) );
	}

	/**
	 * Cleanup Auto Posting data.
	 */
	private static function cleanup_auto_posting(): void {
		self::delete_posts_by_type( array( 'atml_auto_posting' ) );
		self::delete_post_meta_keys( array(
			'_atml_auto_post_status',
			'_atml_auto_posting_id',
			'_atml_auto_post_topic',
			'_atml_auto_post_error',
			'_atml_auto_post_scheduled',
		) );
		self::drop_tables( array( 'antimanual_auto_posting_queue' ) );
		self::delete_transients( array( 'atml_auto_posting_last_check', 'atml_auto_posting_running' ) );
	}

	/**
	 * Cleanup Auto Update data.
	 */
	private static function cleanup_auto_update(): void {
		self::delete_posts_by_type( array( 'atml_auto_update' ) );
		self::delete_post_meta_keys( array(
			'_atml_auto_update_status',
			'_atml_auto_update_id',
			'_atml_auto_update_error',
			'_atml_auto_updated_at',
		) );
		self::delete_transients( array( 'atml_auto_update_last_check', 'atml_auto_update_running' ) );
	}

	/**
	 * Cleanup Bulk Rewrite data.
	 */
	private static function cleanup_bulk_rewrite(): void {
		self::delete_usage_rows( array( 'bulk_rewrite' ) );
	}

	/**
	 * Cleanup Forum Automation data.
	 */
	private static function cleanup_forum_automation(): void {
		self::delete_options_by_prefixes( array( 'antimanual_bbp_' ) );
		self::delete_usage_rows( array( 'forum_answer', 'forum_conversion' ) );
	}

	/**
	 * Cleanup Translation data.
	 */
	private static function cleanup_translation(): void {
		self::drop_tables( array( 'atml_translations' ) );
		self::delete_options_by_prefixes( array( 'antimanual_translation_' ) );
		self::delete_exact_options( array( 'atml_translations_db_version' ) );
	}

	/**
	 * Cleanup SEO Agent data.
	 */
	private static function cleanup_seo_agent(): void {
		self::delete_exact_options( array( 'antimanual_seo_monitoring_prefs' ) );
		self::delete_post_meta_keys( array(
			'_atml_meta_description',
			'_atml_focus_keyword',
			'_atml_seo_force_lazy_loading',
			'_atml_seo_force_noopener',
		) );
		self::delete_transients_by_prefixes( array( 'atml_seo_agent_cache_' ) );
	}

	/**
	 * Cleanup Google Integrations data.
	 */
	private static function cleanup_google_integration(): void {
		self::delete_exact_options( array(
			'antimanual_google_psi_api_key',
			'antimanual_google_sa_json',
			'antimanual_indexing_api_log',
			'antimanual_indexing_api_auto_submit',
			'antimanual_seo_cwv_history_v1',
		) );
		self::delete_transients_by_prefixes( array( 'atml_google_token_', 'atml_psi_', 'atml_cwv_fixes_' ) );
	}

	/**
	 * Cleanup Internal Linking data.
	 */
	private static function cleanup_internal_linking(): void {
		self::delete_exact_options( array( 'atml_il_report_cache_version' ) );
		self::delete_transients_by_prefixes( array( 'atml_il_report_' ) );
	}

	/**
	 * Cleanup Knowledge Base data.
	 */
	private static function cleanup_knowledge_base(): void {
		self::drop_tables( array( 'antimanual_embeddings' ) );
		self::delete_exact_options( array( 'antimanual_kb_preferences' ) );
	}

	/**
	 * Delete exact options.
	 *
	 * @param array<int, string> $option_names Option names.
	 */
	private static function delete_exact_options( array $option_names ): void {
		foreach ( $option_names as $option_name ) {
			\delete_option( $option_name );
		}
	}

	/**
	 * Delete options by name prefix.
	 *
	 * @param array<int, string> $prefixes Prefixes.
	 */
	private static function delete_options_by_prefixes( array $prefixes ): void {
		global $wpdb;

		foreach ( $prefixes as $prefix ) {
			$like = $wpdb->esc_like( $prefix ) . '%';
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $like ) );
		}
	}

	/**
	 * Delete post meta keys globally.
	 *
	 * @param array<int, string> $meta_keys Meta keys.
	 */
	private static function delete_post_meta_keys( array $meta_keys ): void {
		foreach ( $meta_keys as $meta_key ) {
			\delete_post_meta_by_key( $meta_key );
		}
	}

	/**
	 * Delete all posts for the given post types.
	 *
	 * @param array<int, string> $post_types Post types.
	 */
	private static function delete_posts_by_type( array $post_types ): void {
		$post_ids = \get_posts(
			array(
				'post_type'      => $post_types,
				'post_status'    => 'any',
				'posts_per_page' => -1,
				'fields'         => 'ids',
			)
		);

		foreach ( $post_ids as $post_id ) {
			\wp_delete_post( (int) $post_id, true );
		}
	}

	/**
	 * Delete posts by meta key/value pair.
	 *
	 * @param string $meta_key   Meta key.
	 * @param string $meta_value Meta value.
	 */
	private static function delete_posts_by_meta( string $meta_key, string $meta_value ): void {
		$post_ids = \get_posts(
			array(
				'post_type'      => 'any',
				'post_status'    => 'any',
				'posts_per_page' => -1,
				'fields'         => 'ids',
				'meta_key'       => $meta_key,
				'meta_value'     => $meta_value,
			)
		);

		foreach ( $post_ids as $post_id ) {
			\wp_delete_post( (int) $post_id, true );
		}
	}

	/**
	 * Drop custom tables by unprefixed base table name.
	 *
	 * @param array<int, string> $table_names Table names without WP prefix.
	 */
	private static function drop_tables( array $table_names ): void {
		global $wpdb;

		foreach ( $table_names as $table_name ) {
			$full_table_name = $wpdb->prefix . $table_name;
			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
			$wpdb->query( "DROP TABLE IF EXISTS `{$full_table_name}`" );
		}
	}

	/**
	 * Truncate custom tables (delete all rows while keeping the table structure).
	 *
	 * Used during module reset so the plugin remains functional after clearing data.
	 *
	 * @param array<int, string> $table_names Table names without WP prefix.
	 */
	private static function truncate_tables( array $table_names ): void {
		global $wpdb;

		foreach ( $table_names as $table_name ) {
			$full_table_name = $wpdb->prefix . $table_name;
			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
			$wpdb->query( "TRUNCATE TABLE `{$full_table_name}`" );
		}
	}

	/**
	 * Delete exact transients.
	 *
	 * @param array<int, string> $transient_keys Transient keys.
	 */
	private static function delete_transients( array $transient_keys ): void {
		foreach ( $transient_keys as $transient_key ) {
			\delete_transient( $transient_key );
		}
	}

	/**
	 * Delete transients by key prefix.
	 *
	 * @param array<int, string> $prefixes Transient key prefixes.
	 */
	private static function delete_transients_by_prefixes( array $prefixes ): void {
		global $wpdb;

		foreach ( $prefixes as $prefix ) {
			$transient_like = $wpdb->esc_like( '_transient_' . $prefix ) . '%';
			$timeout_like   = $wpdb->esc_like( '_transient_timeout_' . $prefix ) . '%';

			$wpdb->query(
				$wpdb->prepare(
					"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
					$transient_like,
					$timeout_like
				)
			);
		}
	}

	/**
	 * Delete usage tracker rows for specific features.
	 *
	 * @param array<int, string> $features Feature slugs.
	 */
	private static function delete_usage_rows( array $features ): void {
		global $wpdb;

		$table_name = $wpdb->prefix . 'antimanual_usage';
		$found      = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) );

		if ( $found !== $table_name || empty( $features ) ) {
			return;
		}

		$features     = array_values( array_filter( array_map( 'sanitize_key', $features ) ) );
		$placeholders = implode( ', ', array_fill( 0, count( $features ), '%s' ) );
		$query        = $wpdb->prepare( "DELETE FROM {$table_name} WHERE feature IN ({$placeholders})", ...$features );
		$wpdb->query( $query );
	}
}
