<?php
/**
 * Uninstall: Aura leaves without a trace in the database.
 *
 * It runs ONLY on "Delete" from the plugins screen — never on deactivate, never on update. That is
 * the distinction that matters: whoever deactivates is running a test or hunting a conflict, and
 * expects to find everything again on reactivating; whoever deletes has decided.
 *
 * WHAT WE DELETE
 *  - the plugin's options (provider, encrypted keys, brand tokens, font cache);
 *  - the transients (site context, bridge health, per-IP limits);
 *  - our own post types: the components (ai_section) and the form entries (aipb_entry), the latter a
 *    legacy of the forms feature removed in 0.38.0 — whoever used it still has the entries in the
 *    database, and uninstalling has to take them away as it always did;
 *  - the _aipb_* meta left on pages and posts, which mean nothing without the plugin;
 *  - the self-hosted fonts downloaded into uploads/aipb-fonts/.
 *
 * WHAT WE DO *NOT* DELETE, on purpose
 *  The post_content of pages and posts. Every save from the Studio has written the page's HTML
 *  snapshot there (see AIPB_Render::sync_post_content): that is exactly why the snapshot exists — with
 *  the plugin gone the pages stay up and visible. Deleting the meta takes away the ability to edit
 *  them with Aura again, not the pages.
 *
 * @package Aura
 */

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

/**
 * Cleans up a single site. On multisite it is called once for each one.
 */
function aipb_uninstall_site() {
	global $wpdb;

	// 1. Options. Listed by hand instead of with a LIKE 'aipb_%': an explicit list cannot swallow
	//    another plugin's option that happens to share the prefix.
	$options = array(
		'aipb_version',
		// Options from the versions before the service (provider and keys lived on the site): they
		// stay on the list because uninstalling has to clean up whoever comes from there too.
		'aipb_provider',
		'aipb_api_key',
		'aipb_model',
		'aipb_server_url',
		'aipb_server_key',
		// The custom field definitions. The VALUES stay in the contents' meta: they belong to the
		// client, not to us, and uninstalling a builder must not empty the product sheets. It is the
		// same reason the values are saved as ordinary meta.
		'aipb_fields',
		// The type and taxonomy definitions. Removing them leaves the CONTENTS in the database: they
		// stop being reachable, they are not deleted.
		'aipb_types',
		'aipb_types_hash',
		// The local usage log: it is this site's data and it goes with the plugin. On the service the
		// history stays, and rightly so — there it is what billing is made of.
		'aipb_usage_totals',
		'aipb_usage_recent',
		'aipb_brand_tokens',
		'aipb_brand_palette',
		'aipb_brand_fonts',
		'aipb_brand_direction',
		'aipb_fonts_cache',
	);
	foreach ( $options as $option ) {
		delete_option( $option );
	}

	// 2. Transients. Some have a variable key (a hash of the bridge URL, the IP of whoever submits a
	//    form): those can only be caught by prefix, straight from the table. With a persistent object
	//    cache the transients do not live in the options: delete_transient() on the fixed names covers
	//    that case, the query covers the rest.
	delete_transient( 'aipb_site_context' );
	delete_transient( 'aipb_credits_removed' );

	$wpdb->query(
		"DELETE FROM {$wpdb->options}
		 WHERE option_name LIKE '\_transient\_aipb\_%'
		    OR option_name LIKE '\_transient\_timeout\_aipb\_%'"
	);

	// 3. Our own contents: components and form entries (a removed feature, whose data may still be
	//    there for whoever used it: it goes away all the same).
	//    force_delete = true, because ending up in the trash of a plugin that is no longer there is
	//    worse than disappearing: nobody could open them again.
	$ids = $wpdb->get_col(
		"SELECT ID FROM {$wpdb->posts} WHERE post_type IN ( 'ai_section', 'aipb_entry' )"
	);
	foreach ( $ids as $id ) {
		wp_delete_post( (int) $id, true );
	}

	// 4. The meta left on pages, posts, patterns and FSE templates. Revisions carry a copy of them
	//    (see AIPB_Render::save_revision_meta): that goes too, because the query looks at the
	//    meta_key and not at the post type.
	$meta_keys = array(
		'_aipb_template',
		'_aipb_schema',
		'_aipb_values',
		'_aipb_blocks',
		'_aipb_canvas',
		'_aipb_excerpt',
		'_aipb_editor',
		'_aipb_entry_fields',
		'_aipb_entry_form',
		'_aipb_entry_page',
		'_aipb_entry_meta',
	);
	foreach ( $meta_keys as $key ) {
		$wpdb->delete( $wpdb->postmeta, array( 'meta_key' => $key ) );
	}

	// 5. The self-hosted fonts downloaded from Google (uploads/aipb-fonts/): .woff2 files and the
	//    rewritten CSS. One level only, no recursion: the folder is flat by construction.
	$uploads = wp_upload_dir();
	if ( empty( $uploads['error'] ) && ! empty( $uploads['basedir'] ) ) {
		$dir = trailingslashit( $uploads['basedir'] ) . 'aipb-fonts';
		if ( is_dir( $dir ) ) {
			$files = glob( $dir . '/*' );
			if ( is_array( $files ) ) {
				foreach ( $files as $file ) {
					if ( is_file( $file ) ) {
						@unlink( $file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged — odd permissions must not stop the uninstall
					}
				}
			}
			@rmdir( $dir ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged — if something is left inside, the folder stays: harmless
		}
	}
}

if ( is_multisite() ) {
	// One site at a time, so each cleanup sees its own tables. get_sites() with a high but finite
	// ceiling: on a huge network the uninstall must not time out halfway through.
	$sites = get_sites( array( 'number' => 10000, 'fields' => 'ids' ) );
	foreach ( $sites as $site_id ) {
		switch_to_blog( (int) $site_id );
		aipb_uninstall_site();
		restore_current_blog();
	}
} else {
	aipb_uninstall_site();
}
