<?php
/**
 * Removes DataStream's traces when the plugin is deleted.
 *
 * This lived in uninstall.php until Freemius rejected the upload, and Freemius
 * is right to: a main-folder uninstall.php takes precedence over every
 * uninstall hook, so the SDK's own cleanup -- deregistering the license,
 * recording the uninstall -- would silently never run. Freemius requires the
 * logic behind its `after_uninstall` action instead.
 *
 * That creates a trap worth naming. `after_uninstall` only fires if the SDK
 * booted, so a build with no credentials, or one where a customer deleted
 * vendor/freemius, would clean up nothing at all. So the plugin registers
 * whichever path is actually available -- see datastream_register_uninstall_
 * cleanup() in the main plugin file.
 *
 * Runtime state (cron events, locks, caches) is ALWAYS removed. Synced records
 * and settings are only deleted when the user opted in via "Delete Data on
 * Uninstall" -- deleting a plugin must never silently destroy data the user
 * spent time syncing.
 *
 * @package DataStream
 */

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

/**
 * Class DataStream_Uninstaller
 */
class DataStream_Uninstaller {

	/**
	 * Options written by the plugin, deleted by name when opted in.
	 *
	 * @return string[]
	 */
	public static function option_names() {
		return array(
			'datastream_api_url',
			'datastream_api_method',
			'datastream_api_timeout',
			'datastream_api_headers',
			'datastream_api_bearer_token',
			'datastream_api_ssl_verify',
			'datastream_json_items_path',
			'datastream_id_field',
			'datastream_content_field',
			'datastream_stream_name',
			'datastream_streams',
			'datastream_cron_schedule',
			'datastream_cron_enabled',
			'datastream_max_records_per_sync',
			'datastream_max_payload_bytes',
			'datastream_max_response_bytes',
			'datastream_max_external_id_len',
			'datastream_stale_record_days',
			'datastream_cache_ttl',
			'datastream_enable_logging',
			'datastream_log_level',
			'datastream_sync_lock_timeout',
			'datastream_delete_on_uninstall',
			'datastream_failure_count',
			'datastream_backoff_until',
			'datastream_last_sync',
			'datastream_last_sync_result',
			'datastream_sync_history',
			'datastream_activity',
			'datastream_db_version',
		);
	}

	/**
	 * Runs the cleanup. Idempotent: both registration routes end here, and
	 * running it twice must be harmless.
	 */
	public static function run() {
		global $wpdb;

		// Always: clear scheduled events, the sync lock, and cached payloads.
		wp_clear_scheduled_hook( 'ds_ingestion_event' );
		delete_transient( 'datastream_sync_lock' );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- prefix delete of our cache transients on uninstall.
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
				$wpdb->esc_like( '_transient_ds_cache_' ) . '%',
				$wpdb->esc_like( '_transient_timeout_ds_cache_' ) . '%'
			)
		);

		if ( ! get_option( 'datastream_delete_on_uninstall', false ) ) {
			return;
		}

		// Opted in: remove the table and every option.
		foreach ( self::option_names() as $option ) {
			delete_option( $option );
		}

		$table = $wpdb->prefix . 'ds_ingestion_table';
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter -- drops only our table, and only when the user opted in. Table name is prefix + a fixed identifier.
		$wpdb->query( "DROP TABLE IF EXISTS {$table}" );
	}
}
