<?php
/**
 * TamRank SEO — Uninstall cleanup
 *
 * Runs when the plugin is deleted via the WordPress admin (not just deactivated).
 * Required for wp.org submission and GDPR/AVG compliance.
 *
 * Two cleanup tiers:
 *   - Always:  transients (caches), scheduled cron hooks (no-op once gone)
 *   - Opt-in:  options, post meta, custom database tables — user data, only
 *              wiped when tam_rank_general_settings['delete_all_data_on_uninstall']
 *              is explicitly enabled. Default is off, so a "Delete plugin"
 *              click preserves license, settings, audit scores, focus
 *              keywords, schema configs and custom tables.
 */

if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
	exit;
}

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Run cleanup for a single site (called once per site on multisite).
 */
function tam_rank_uninstall_cleanup_site() {
	global $wpdb;

	$general_settings           = get_option( 'tam_rank_general_settings', array() );
	$delete_all_data            = ! empty( $general_settings['delete_all_data_on_uninstall'] );

	// --- Transients (always) ---
	// Transients are caches, not user data. Always safe to drop.
	// Live in wp_options with _transient_ and _transient_timeout_ prefixes.
	$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\\_transient\\_tam_rank\\_%' OR option_name LIKE '\\_transient\\_timeout\\_tam_rank\\_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- direct query on core options table; uninstall-context write, caching not applicable
	$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\\_transient\\_tamrank\\_%' OR option_name LIKE '\\_transient\\_timeout\\_tamrank\\_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- direct query on core options table; uninstall-context write, caching not applicable

	// --- Site transients (multisite, always) ---
	if ( is_multisite() ) {
		$wpdb->query( "DELETE FROM {$wpdb->sitemeta} WHERE meta_key LIKE '\\_site\\_transient\\_tam_rank\\_%' OR meta_key LIKE '\\_site\\_transient\\_timeout\\_tam_rank\\_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- direct query on core sitemeta table; uninstall-context write, caching not applicable
		$wpdb->query( "DELETE FROM {$wpdb->sitemeta} WHERE meta_key LIKE '\\_site\\_transient\\_tamrank\\_%' OR meta_key LIKE '\\_site\\_transient\\_timeout\\_tamrank\\_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- direct query on core sitemeta table; uninstall-context write, caching not applicable
	}

	// --- Scheduled cron hooks (always) ---
	// Hooks become no-ops once the plugin is gone, but clearing them keeps the
	// cron table tidy — especially recurring events, which never self-expire.
	// PRO has no uninstall.php of its own and only clears one of its own hooks
	// on deactivation, so FREE's uninstall defensively clears the known PRO
	// hooks too; wp_unschedule_hook() on an absent hook is a harmless no-op.
	$cron_hooks = array(
		// FREE.
		'tamrank_weekly_learning',          // recurring weekly  (class-redirect-suggestions.php)
		'tamrank_daily_cleanup',            // recurring daily   (class-redirect-suggestions.php)
		'tamrank_link_graph_scan',          // recurring weekly  (class-tamrank-link-graph.php)
		'tamrank_run_schema_upgrade',       // single-event      (class-schema-manager.php)
		'tamrank_request_indexing',         // single-event WITH post-ID arg (class-tamrank-indexing.php)
		// PRO (defensive — PRO ships no uninstall.php).
		'tamrank_pro_update_check',         // recurring twicedaily (pro update-checker)
		'tam_rank_pagespeed_queue_process', // single-event      (pro pagespeed-queue)
		'tamrank_daily_index_check',        // recurring daily   (pro index-checker)
		'tamrank_change_log_cleanup',       // recurring weekly  (pro change-tracker)
		'tamrank_notify_post_created',      // single-event WITH arg (pro event-dispatcher)
		'tamrank_notify_post_updated',      // single-event WITH arg (pro event-dispatcher)
		'tamrank_notify_post_deleted',      // single-event WITH arg (pro event-dispatcher)
		'tamrank_notify_status_changed',    // single-event WITH arg (pro event-dispatcher)
	);
	foreach ( $cron_hooks as $hook ) {
		// wp_unschedule_hook() removes ALL instances regardless of args;
		// wp_clear_scheduled_hook() without an $args param would miss the
		// arg-bearing single-events (request_indexing, notify_*).
		wp_unschedule_hook( $hook );
	}

	// --- User data (opt-in only) ---
	// Options (settings, license key, GSC tokens, scan stats), post meta
	// (focus keywords, audit scores, meta titles, schema configs) and custom
	// tables (redirects, topical maps, change log, 404 log, etc.) are all
	// user data. Preserved by default so that "Delete plugin" + reinstall
	// keeps everything intact, matching Yoast/Rank Math behaviour. Wiped
	// only when the user explicitly opts in via the settings toggle.
	if ( $delete_all_data ) {
		// Options: covers every tam_rank_* and tamrank_* option, including
		// future additions, without needing to maintain an explicit list.
		$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE 'tam_rank\\_%' OR option_name LIKE 'tamrank\\_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- direct query on core options table; uninstall-context write, caching not applicable

		// Post meta: _tam_rank_*, _tamrank_*, and tamrank_schema_* keys. The
		// _tamrank_* half covers the alt/title autofill markers
		// (_tamrank_alt_autofilled, _tamrank_title_autofilled) and the bulk
		// marker (_tamrank_alt_bulkfilled) as well. Note that this removes the
		// MARKERS, not the alt texts themselves: those are core's own
		// _wp_attachment_image_alt and stay exactly where they are.
		$wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '\\_tam_rank\\_%' OR meta_key LIKE '\\_tamrank\\_%' OR meta_key LIKE 'tamrank_schema_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- direct query on core postmeta table; uninstall-context write, caching not applicable

		// User meta: per-user state (e.g. tam_rank_last_seen_404 — 404 Monitor
		// last-seen timestamp). Prefix-based to cover any future user-meta keys.
		$wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE 'tam_rank\\_%' OR meta_key LIKE 'tamrank\\_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- direct query on core usermeta table; uninstall-context write, caching not applicable

		// Custom tables. The first 5 are created by FREE; the last 4 are
		// created by the PRO add-on (topical_maps + change_log by the topical
		// authority and change tracker, and the two agent_* tables by the MCP
		// layer). PRO ships no uninstall.php and never drops its own tables, so
		// FREE clears them defensively here on a full opt-in wipe — important
		// because tamrank_agent_tokens holds PAT token secrets. DROP TABLE IF
		// EXISTS is a no-op when a table is absent.
		$custom_tables = array(
			$wpdb->prefix . 'tam_rank_404_log',
			$wpdb->prefix . 'tam_rank_redirects',
			$wpdb->prefix . 'tam_rank_redirect_stats',
			$wpdb->prefix . 'tam_rank_redirect_suggestions',
			$wpdb->prefix . 'tam_rank_slug_synonyms',
			$wpdb->prefix . 'tam_rank_topical_maps',   // PRO: topical authority
			$wpdb->prefix . 'tamrank_change_log',      // PRO: change tracker
			$wpdb->prefix . 'tamrank_agent_tokens',    // PRO: MCP PAT secrets
			$wpdb->prefix . 'tamrank_agent_actions',   // PRO: MCP action log
		);
		foreach ( $custom_tables as $table ) {
			$wpdb->query( "DROP TABLE IF EXISTS `{$table}`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table schema removal on plugin uninstall; only a table identifier (wpdb->prefix + hardcoded name) is interpolated, no user values
		}
	}
}

// --- Entry point: single-site vs multisite ---
if ( is_multisite() ) {
	$tam_rank_site_ids = get_sites( array( 'fields' => 'ids', 'number' => 0 ) );
	foreach ( $tam_rank_site_ids as $tam_rank_site_id ) {
		switch_to_blog( $tam_rank_site_id );
		tam_rank_uninstall_cleanup_site();
		restore_current_blog();
	}
} else {
	tam_rank_uninstall_cleanup_site();
}
