<?php
/**
 * PersonalizeWP
 *
 * @link    https://personalizewp.com/
 * @since   2.5.0
 *
 * @package PersonalizeWP
 */

namespace PersonalizeWP;

use PersonalizeWP\Traits\SingletonTrait;
use PersonalizeWP\Model\Contact;
use PersonalizeWP\Integrations\WooCommerce\WooCommerce_Memberships_Teams;

defined( 'ABSPATH' ) || exit; // Exit if accessed directly.

/**
 * Class for managing the database schema, initial data, upgrade migrations and uninstall.
 */
class DB_Manager {

	use SingletonTrait;

	/**
	 * Option key to store database version
	 *
	 * @var string
	 */
	const OPTION_NAME = '_pwp_db_version';

	/**
	 * DB Schema version.
	 *
	 * @since 1.0.0 started with version string 100.
	 * @since 2.3.0 switched to using direct int.
	 *
	 * @var int
	 */
	const DB_VERSION = 350; // Increment with each change in db

	/**
	 * Holds version of database from database itself
	 *
	 * @var int
	 */
	protected int $db_version;

	/**
	 * Returns the database version for the plugin.
	 *
	 * @return int
	 */
	public function get_db_version() {
		return absint( get_option( self::OPTION_NAME, 0 ) );
	}

	/**
	 * Class constructor, adding required hooks
	 */
	protected function __construct() {

		$this->db_version = $this->get_db_version();

		// Immediately check for possible database migrations, since already within `init` hook.
		$this->check_migrate();

		add_action( 'personalizewp_batch_user_memberships_team_backfill', array( $this, 'batch_user_memberships_team_backfill' ) );
	}

	/**
	 * Checks db version, run potential installations.
	 *
	 * Runs via activation hook.
	 *
	 * @return void
	 */
	public function check_install() {

		// We are up to date. Nothing to do.
		if ( self::DB_VERSION === $this->db_version ) {
			return;
		}

		// Already installed as some db version exists.
		if ( $this->db_version > 0 ) {
			return;
		}

		// Install all the tables
		$this->install();
	}

	/**
	 * Checks db version, run potential installations, populations and migrations.
	 *
	 * @return void
	 */
	public function check_migrate() {

		// We are up to date. Nothing to do.
		if ( self::DB_VERSION === $this->db_version ) {
			return;
		}

		// Activation may have skipped the installation.
		$this->check_install();

		$current_db_version = $this->db_version;

		// Run through various upgrade functions, depending on current version.
		if ( $current_db_version < 230 ) {
			$this->upgrade_230( $current_db_version );
		}

		if ( $current_db_version < 240 ) {
			$this->upgrade_240( $current_db_version );
		}

		if ( $current_db_version < 260 ) {
			$this->upgrade_260( $current_db_version );
		}

		if ( $current_db_version < 280 ) {
			$this->upgrade_280( $current_db_version );
		}

		if ( $current_db_version < 310 ) {
			$this->upgrade_310( $current_db_version );
		}

		// Re-trigger 320 migration, even on a already migrated 320 site.
		if ( $current_db_version < 320 ) {
			$this->upgrade_320( $current_db_version );
		}

		if ( $current_db_version < 330 ) {
			$this->upgrade_330( $current_db_version );
		}

		if ( $current_db_version < 342 ) {
			$this->upgrade_342( $current_db_version );
		}

		if ( $current_db_version < 350 ) {
			$this->upgrade_350( $current_db_version );
		}

		// Mark db updated.
		update_option( self::OPTION_NAME, self::DB_VERSION, true );
	}

	/**
	 * Returns true if we're installing.
	 *
	 * @return bool
	 */
	private function is_installing() {
		return 'yes' === get_transient( 'pwp_installing' );
	}

	/**
	 * Executes changes made in 2.3.0.
	 *
	 * @since 2.3.0
	 *
	 * @param int $db_version The old (current) database version.
	 *
	 * @return void
	 */
	private function upgrade_230( $db_version ) {
		global $wpdb;

		if ( $db_version < 230 ) {
			// Re-run install tables to ensure up to date schema.
			// Relies upon core dbDelta() to diff and apply the changes.
			$this->install_tables();

			// Ensure the default condition operator is set for all Rules.
			$wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->prefix}pwp_rules` SET `operator` = %s", 'ALL' ) );
		}
	}

	/**
	 * Executes changes made in 2.4.0.
	 *
	 * @since 2.4.0
	 *
	 * @param int $db_version The old (current) database version.
	 *
	 * @return void
	 */
	private function upgrade_240( $db_version ) {
		global $wpdb;

		if ( $db_version < 240 ) {
			// Back fill the default condition operator for Rules which might have a blank operator, due to creation bug.
			$wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->prefix}pwp_rules` SET `operator` = %s WHERE `operator` = ''", 'ALL' ) );
		}
	}

	/**
	 * Executes changes made in 2.6.0.
	 *
	 * @since 2.6.0
	 *
	 * @param int $db_version The old (current) database version.
	 *
	 * @return void
	 */
	private function upgrade_260( $db_version ) {
		global $wpdb;

		if ( $db_version < 260 ) {

			// @since 3.1 - Removed column change to db table no longer used.

			// Re-run install tables to ensure up to date schema.
			// Relies upon core dbDelta() to diff and apply the changes.
			$this->install_tables();
		}
	}

	/**
	 * Executes changes made in 2.8.0.
	 *
	 * @since 2.8.0
	 *
	 * @param int $db_version The old (current) database version.
	 *
	 * @return void
	 */
	private function upgrade_280( $db_version ) {

		if ( $db_version < 280 ) {

			// Delete old options in case they still exist
			delete_option( 'pwp_admin_notices' );
			delete_option( 'pwp_flash_messages' );
			delete_option( 'personalizewp_editor_role_values' );
			delete_option( 'dxp_newsletter_signup' );
			delete_option( 'pwp_admin_dashboard_message' );
			delete_option( 'pwp_admin_onboarding_message' );

		}
	}

	/**
	 * Executes changes made in 3.1.0.
	 *
	 * @since 3.1.0
	 *
	 * @param int $db_version The old (current) database version.
	 *
	 * @return void
	 */
	private function upgrade_310( $db_version ) {
		global $wpdb;

		if ( $db_version < 310 ) {

			$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_active_blocks" );
		}
	}

	/**
	 * Executes changes made in 3.2.0.
	 *
	 * @since 3.2.0
	 *
	 * @param int $db_version The old (current) database version.
	 *
	 * @return void
	 */
	private function upgrade_320( $db_version ) {
		global $wpdb;

		if ( $db_version < 320 ) {

			// Migrations from Pro.
			$pro_db_version = absint( get_option( '_pwp_pro_db', 0 ) );
			if ( ! empty( $pro_db_version ) ) {

				// Migrate Pro prefixed tables to non-pro prefixed names
				$pro_db_tablename_mappings = [
					'pwp_pro_activity'                 => 'pwp_activity',
					'pwp_pro_contacts'                 => 'pwp_contacts',
					'pwp_pro_contacts_meta'            => 'pwp_contacts_meta',
					'pwp_pro_scoring_rules'            => 'pwp_scoring_rules',
					'pwp_pro_segments'                 => 'pwp_segments',
					'pwp_pro_segments_relationships'   => 'pwp_segments_relationships',
					'pwp_pro_variation_block_mappings' => 'pwp_variation_block_mappings',
				];
				foreach ( $pro_db_tablename_mappings as $old_tablename => $new_tablename ) {
					// Double check table existence.
					$suppress    = $wpdb->suppress_errors();
					$tablefields = $wpdb->get_results( "DESCRIBE {$wpdb->prefix}{$old_tablename};" );
					$wpdb->suppress_errors( $suppress );

					if ( ! $tablefields ) {
						continue;
					}
					$wpdb->query( "RENAME TABLE {$wpdb->prefix}{$old_tablename} TO {$wpdb->prefix}{$new_tablename}" );
				}

				// Re-run install tables to ensure up to date schema.
				// Relies upon core dbDelta() to diff and apply the changes.
				// Should be empty, but dbDelta can return content even though nothing changed.
				$this->install_tables();

				if ( $pro_db_version < 230 ) {
					$this->upgrade_from_pro_230( $pro_db_version );
				}

				if ( $pro_db_version < 280 ) {
					$this->upgrade_from_pro_280( $pro_db_version );
				}

				if ( $pro_db_version < 301 ) {
					$this->upgrade_from_pro_301( $pro_db_version );
				}
			}

			// Re-run install tables to ensure up to date schema.
			// Relies upon core dbDelta() to diff and apply the changes.
			$this->install_tables();

			// Add new initial content, if not already there.
			$this->install_expanded_rules();

			$this->install_segment_defaults();

			$this->install_scoring_defaults();

			// Merge Variations Blocks mapping table into main block mappings table, if the table exists
			$suppress           = $wpdb->suppress_errors();
			$variation_mappings = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}pwp_variation_block_mappings", ARRAY_A );
			$wpdb->suppress_errors( $suppress );
			if ( ! empty( $variation_mappings ) ) {
				// Loop through all variation blocks.
				foreach ( $variation_mappings as $variation_mapping ) {
					// Load the original post...
					$wp_post = get_post( $variation_mapping['post_ref'], ARRAY_A );
					if ( $wp_post ) {
						// Then just 'update', the normal triggers in Admin_Blocks will run and ensure
						// uniqueness of refs, storage of refs in the correct table, and update caches.
						wp_update_post( $wp_post );
					}
				}
			}

			delete_option( 'personalizewp_license_key' );
			delete_option( 'personalizewp_license' );
			delete_option( '_pwp_pro_db' );
		}
	}

	/**
	 * Executes changes made in 3.3.0.
	 *
	 * @since 3.3.0
	 *
	 * @param int $db_version The old (current) database version.
	 *
	 * @return void
	 */
	private function upgrade_330( $db_version ) {

		if ( $db_version < 330 ) {
			// Re-run install tables to ensure up to date schema.
			$this->install_tables();
		}
	}

	/**
	 * Executes changes made in 3.4.2.
	 *
	 * @since 3.4.2
	 *
	 * @param int $db_version The old (current) database version.
	 *
	 * @return void
	 */
	private function upgrade_342( $db_version ) {
		global $wpdb;

		if ( $db_version < 342 ) {

			// Check for use of Teams, no need to run if not. Cannot check for our Teams class as this upgrade runs early.
			if ( ! class_exists( 'WC_Memberships_For_Teams_Loader' ) ) {
				return;
			}
			// Safety check for Action Scheduler, which is core within WooCommerce and needed for backfilling.
			if ( ! function_exists( 'as_enqueue_async_action' ) ) {
				return;
			}

			// 0: Remove any potential invalid zero IDs on membership teams.
			$num_deleted = $wpdb->query(
				$wpdb->prepare(
					'DELETE FROM %i WHERE meta_key = %s AND meta_value = 0',
					$wpdb->prefix . 'pwp_contacts_meta',
					'wc_memberships_team_id'
				)
			);

			// 1: Get all Visitor IDs with corresponding WP User ID meta, without a teams meta field ID.
			$visitor_ids = $wpdb->get_col(
				$wpdb->prepare(
					'SELECT meta1.contact_id
					FROM %i as meta1
					LEFT JOIN %i as meta2 ON ( meta1.contact_id = meta2.contact_id AND meta2.meta_key = %s )
					WHERE meta1.meta_key = %s AND meta2.contact_id IS NULL',
					$wpdb->prefix . 'pwp_contacts_meta',
					$wpdb->prefix . 'pwp_contacts_meta',
					'wc_memberships_team_id',
					'wp_user_id'
				)
			);

			$action_ids = array();
			// 2: Add new AS action for each found Visitor ID.
			foreach ( $visitor_ids as $visitor_id ) {
				$action_ids[] = as_enqueue_async_action(
					'personalizewp_batch_user_memberships_team_backfill',
					array( $visitor_id ),
					'personalizewp_memberships_teams'
				);
			}
		}
	}

	/**
	 * Executes changes made in 3.5.0.
	 *
	 * Removes legacy geo transients that were replaced by object cache.
	 *
	 * @since 3.5.0
	 *
	 * @param int $db_version The old (current) database version.
	 *
	 * @return void
	 */
	private function upgrade_350( $db_version ) {
		global $wpdb;

		if ( $db_version < 350 ) {
			// Clean up legacy geo transients now replaced by wp_cache.
			$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_transient\_pwp\_geo\_%' OR option_name LIKE '\_transient\_timeout\_pwp\_geo\_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		}
	}

	/**
	 * Backfills a visitors Team ID for WooCommerce Memberships.
	 *
	 * @param int $visitor_id Visitor ID
	 *
	 * @return int|bool|WP_Error|void Returns early if the user doesn't exist, or the team is already set
	 */
	public function batch_user_memberships_team_backfill( $visitor_id ) {

		// Safety check
		$teams = WooCommerce_Memberships_Teams::instance();
		if ( empty( $teams ) ) {
			return new \WP_Error( 'personalizewp_teams_backfill_teams_not_available', __( 'Teams for WooCommerce Memberships not available', 'personalizewp' ) );
		}

		// Check visitor ID validity.
		$visitor = Contact::get_instance( $visitor_id );
		if ( ! $visitor ) {
			return new \WP_Error( 'personalizewp_teams_backfill_invalid_visitor', __( 'Invalid visitor ID.', 'personalizewp' ) );
		}

		// Double check for pre-existing memberships team.
		$_team_id = $visitor->get_metadata( 'wc_memberships_team_id', true );
		if ( ! empty( $_team_id ) ) {
			return;
		}

		// Get WP User ID, *should* be valid.
		$wp_user_id = $visitor->get_metadata( 'wp_user_id', true );
		$team_id    = $teams->get_membership_team_id_by_user_id( $wp_user_id );
		if ( empty( $team_id ) ) {
			// No valid team, nothing to do.
			return;
		}

		// Add/update new meta data
		return $visitor->update_metadata( 'wc_memberships_team_id', $team_id );
	}

	/**
	 * Install database tables and populate with initial defaults.
	 *
	 * @return void
	 */
	public function install() {

		// Check if we are not already running this routine.
		if ( $this->is_installing() ) {
			return;
		}

		// Double check for current db version, don't run if already exists.
		if ( ! empty( $this->get_db_version() ) ) {
			return;
		}

		// If we made it till here nothing is running yet, lets set the transient now.
		set_transient( 'pwp_installing', 'yes', MINUTE_IN_SECONDS * 10 );

		// Ensure all the tables exist.
		$results = $this->install_tables();

		// If tables successfully installed as new.
		// Should be empty, but dbDelta can return content even though nothing changed.
		if ( ! empty( $results ) ) {
			// Setup out of the box content.
			$this->install_legacy_rules();

			$this->install_expanded_rules();

			$this->install_segment_defaults();

			$this->install_scoring_defaults();
		}

		// Add the current plugin db version, so this function isn't run again.
		update_option( self::OPTION_NAME, self::DB_VERSION, true );

		delete_transient( 'pwp_installing' );
	}

	/**
	 * Initial database install routine
	 * Note: dbDelta can return content due to mixed case on db columns, even when nothing changed.
	 *
	 * @return array Strings containing the results of the various creation queries. Empty if tables already there.
	 */
	private function install_tables() {
		global $wpdb;

		static $db_delta_results;

		// Only run once, return what the results were.
		if ( isset( $db_delta_results ) ) {
			return $db_delta_results;
		}

		$wpdb->hide_errors();

		// Ensure we have access to dbDelta()
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';

		$sql             = array();
		$charset_collate = $wpdb->get_charset_collate();

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_categories (
				`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
				`name` varchar(255) NOT NULL DEFAULT '',
				`created_at` datetime NOT NULL,
				`modified_at` datetime NOT NULL,
				PRIMARY KEY  (`id`)
			) {$charset_collate};";

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_rules (
				`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
				`name` varchar(255) NOT NULL DEFAULT '',
				`category_id` int(11) NOT NULL,
				`type` varchar(30) NOT NULL,
				`conditions_json` mediumtext NOT NULL,
				`operator` varchar(10) NOT NULL DEFAULT 'ALL',
				`created_by` bigint(20) NOT NULL,
				`created_at` datetime NOT NULL,
				`modified_at` datetime NOT NULL,
				PRIMARY KEY  (`id`),
				KEY category_id (`category_id`),
				KEY `type` (`type`)
			) {$charset_collate};";

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_block_mappings (
				`block_ref` varchar(255) NOT NULL,
				`post_ref` varchar(255) NOT NULL,
				`map_type` varchar(255) NOT NULL DEFAULT '',
				PRIMARY KEY  (`block_ref`),
				KEY post_ref (`post_ref`),
				KEY map_type (`map_type`)
			) {$charset_collate};";

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_activity (
			`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			`activity_type` varchar(20) NOT NULL,
			`url` text NOT NULL,
			`object_id` bigint(20) unsigned NULL,
			`object_type` varchar(100) NOT NULL DEFAULT '',
			`contact_id` bigint(20) unsigned NOT NULL,
			`lead_score` INT(11) NOT NULL DEFAULT 0,
			`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
			`ip` varchar(45) NULL,
			`referrer` VARCHAR(255) NOT NULL DEFAULT '',
			PRIMARY KEY  (ID),
			KEY contact_id (contact_id),
			KEY activity_type (activity_type)
		) $charset_collate;";

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_activity_meta (
			`meta_id` bigint(20) unsigned NOT NULL auto_increment,
			`activity_id` bigint(20) unsigned NOT NULL,
			`meta_key` varchar(200) default NULL,
			`meta_value` longtext,
			PRIMARY KEY  (meta_id),
			KEY activity_id (activity_id),
			KEY meta_key (meta_key(191))
		) $charset_collate;";

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_contacts (
			`ID` bigint(20) NOT NULL AUTO_INCREMENT,
			`uid` VARCHAR(200) NOT NULL,
			`first_name` VARCHAR(200) NOT NULL DEFAULT '',
			`last_name` VARCHAR(200) NOT NULL DEFAULT '',
			`email` VARCHAR(200) NOT NULL DEFAULT '',
			`lead_score` INT(11) NOT NULL DEFAULT 0,
			`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
			`last_seen` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
			`is_known` TINYINT(1) NOT NULL DEFAULT 0,
			`is_verified` TINYINT(1) NOT NULL DEFAULT 0,
			PRIMARY KEY  (ID),
			KEY uid (uid),
			KEY is_known (is_known)
		) $charset_collate;";

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_contacts_meta (
			`meta_id` bigint(20) unsigned NOT NULL auto_increment,
			`contact_id` bigint(20) unsigned NOT NULL,
			`meta_key` varchar(200) default NULL,
			`meta_value` longtext,
			PRIMARY KEY  (meta_id),
			KEY contact_id (contact_id),
			KEY meta_key (meta_key(191))
		) $charset_collate;";

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_segments (
			`ID` INT(11) NOT NULL AUTO_INCREMENT,
			`name` VARCHAR(255) NOT NULL DEFAULT '',
			`type` varchar(20) NOT NULL,
			`conditions` mediumtext NOT NULL,
			`operator` varchar(10) NOT NULL DEFAULT 'ANY',
			`is_active` TINYINT(1) NOT NULL DEFAULT 0,
			`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
			`contact_count` int(11) NOT NULL default '0',
			PRIMARY KEY  (ID),
			KEY type (type),
			KEY is_active (is_active),
			KEY active_type (type, is_active)
		) $charset_collate;";

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_segments_relationships (
			`segment_id` INT(11) unsigned NOT NULL default 0,
			`contact_id` INT(11) unsigned NOT NULL default 0,
			PRIMARY KEY  (segment_id, contact_id)
			) $charset_collate;";

		$sql[] = "CREATE TABLE {$wpdb->prefix}pwp_scoring_rules (
			`ID` INT(11) NOT NULL AUTO_INCREMENT,
			`name` VARCHAR(255) NOT NULL DEFAULT '',
			`type` varchar(20) NOT NULL,
			`conditions` mediumtext NOT NULL,
			`operator` varchar(10) NOT NULL DEFAULT 'ANY',
			`lead_score` INT(11) NOT NULL DEFAULT 0,
			`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY  (ID),
			KEY type (type)
		) $charset_collate;";

		$db_delta_results = \dbDelta( $sql );

		$wpdb->show_errors();

		return $db_delta_results;
	}

	/**
	 * Populate database tables with initial content
	 *
	 * @since 1.0.0
	 *
	 * @return array Strings containing the results of the various delta queries.
	 */
	private function install_legacy_rules() {
		global $wpdb;

		static $has_run;

		if ( isset( $has_run ) ) {
			return;
		}

		// Ensure we have access to dbDelta()
		require_once ABSPATH . '/wp-admin/includes/upgrade.php';

		$sql   = array();
		$sql[] = "INSERT INTO `{$wpdb->prefix}pwp_categories` (`id`, `name`, `created_at`, `modified_at`)
            VALUES
                (NULL, 'Location', NOW(), NOW()),
                (NULL, 'Purchases', NOW(), NOW()),
                (NULL, 'User Types', NOW(), NOW()),
                (NULL, 'Other', NOW(), NOW()),
				(NULL, 'Device Types', NOW(), NOW()),
                (NULL, 'Time', NOW(), NOW());
			";

		$sql[] = "INSERT INTO `{$wpdb->prefix}pwp_rules` (`id`, `name`, `category_id`, `type`, `conditions_json`, `created_by`, `created_at`, `modified_at`)
			VALUES
				(NULL, 'User is currently logged in', 3, 'standard', '[{\"measure\":\"core_is_logged_in_user\",\"comparator\":\"equals\",\"value\":\"true\"}]', 0, NOW(), NOW()),
				(NULL, 'User is not logged in', 3, 'standard', '[{\"measure\":\"core_is_logged_in_user\",\"comparator\":\"equals\",\"value\":\"false\"}]', 0, NOW(), NOW()),
				(NULL, 'UK-based visitor', 1, 'standard', '[{\"measure\":\"core_visitor_country\",\"comparator\":\"equals\",\"value\":\"GB\"}]', 0, NOW(), NOW()),
				(NULL, 'New visitor', 3, 'standard', '[{\"measure\":\"core_new_visitor\",\"comparator\":\"equals\",\"value\":\"true\"}]', 0, NOW(), NOW()),
				(NULL, 'Returning visitor', 3, 'standard', '[{\"measure\":\"core_new_visitor\",\"comparator\":\"equals\",\"value\":\"false\"}]', 0, NOW(), NOW()),
				(NULL, 'Device Type - mobile', 5, 'standard', '[{\"measure\":\"core_users_device_type\",\"comparator\":\"equals\",\"value\":\"mobile\"}]', 0, NOW(), NOW()),
				(NULL, 'Device Type - desktop', 5, 'standard', '[{\"measure\":\"core_users_device_type\",\"comparator\":\"equals\",\"value\":\"desktop\"}]', 0, NOW(), NOW()),
				(NULL, '10 secs spent on page', 6, 'standard', '[{\"measure\":\"core_time_elapsed\",\"comparator\":\"equals\",\"value\":\"10\"}]', 0, NOW(), NOW()),
				(NULL, '30 secs spent on page', 6, 'standard', '[{\"measure\":\"core_time_elapsed\",\"comparator\":\"equals\",\"value\":\"30\"}]', 0, NOW(), NOW()),
				(NULL, '1 min spent on page', 6, 'standard', '[{\"measure\":\"core_time_elapsed\",\"comparator\":\"equals\",\"value\":\"60\"}]', 0, NOW(), NOW()),
				(NULL, 'US-based visitor', 1, 'standard', '[{\"measure\":\"core_visitor_country\",\"comparator\":\"equals\",\"value\":\"US\"}]', 0, NOW(), NOW()),
				(NULL, 'Time is morning (6am - 12pm)', 6, 'standard', '[{\"measure\":\"core_users_visiting_time\",\"comparator\":\"equals\",\"value\":\"morning\"}]', 0, NOW(), NOW()),
				(NULL, 'Time is afternoon (12pm - 6pm)', 6, 'standard', '[{\"measure\":\"core_users_visiting_time\",\"comparator\":\"equals\",\"value\":\"afternoon\"}]', 0, NOW(), NOW()),
				(NULL, 'Time is evening (6pm - 12am)', 6, 'standard', '[{\"measure\":\"core_users_visiting_time\",\"comparator\":\"equals\",\"value\":\"evening\"}]', 0, NOW(), NOW());
			";

		$has_run = true;
		return \dbDelta( $sql );
	}

	/**
	 * Executes changes made in Pro 2.3.0.
	 *
	 * @since 3.2.0 Merged in PWP, using non pro prefixed tablenames
	 * @since 2.3.0
	 *
	 * @param int $db_version The old Pro database version.
	 *
	 * @return void
	 */
	private function upgrade_from_pro_230( $db_version ) {
		global $wpdb;

		if ( $db_version < 230 ) {

			// Ensure the default condition operator is set for all Segments and Scoring Rules, unless already set.
			$wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->prefix}pwp_segments` SET `operator` = %s WHERE `operator` = '' ", 'ANY' ) );
			$wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->prefix}pwp_scoring_rules` SET `operator` = %s WHERE `operator` = '' ", 'ANY' ) );

			// Delete Segment relationships with non-existing contacts.
			$num_deleted = $wpdb->query(
				"DELETE {$wpdb->prefix}pwp_segments_relationships
				FROM {$wpdb->prefix}pwp_segments_relationships
				LEFT JOIN {$wpdb->prefix}pwp_contacts ON {$wpdb->prefix}pwp_contacts.ID = {$wpdb->prefix}pwp_segments_relationships.contact_id
				WHERE {$wpdb->prefix}pwp_contacts.ID IS NULL"
			);

			if ( ! is_wp_error( $num_deleted ) && is_int( $num_deleted ) && 0 < $num_deleted ) {
				// Additional join on all remaining Segments, joined as count with relationships.
				$rows = $wpdb->get_results(
					"SELECT {$wpdb->prefix}pwp_segments.ID, count({$wpdb->prefix}pwp_segments_relationships.contact_id) as new_count
					FROM {$wpdb->prefix}pwp_segments
					LEFT JOIN {$wpdb->prefix}pwp_segments_relationships ON {$wpdb->prefix}pwp_segments.ID = {$wpdb->prefix}pwp_segments_relationships.segment_id
					GROUP BY {$wpdb->prefix}pwp_segments.ID",
					ARRAY_A
				);

				foreach ( $rows as $row ) {
					// Update each Segment with fresh, correct count
					$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}pwp_segments SET contact_count = %d WHERE ID = %d", $row['new_count'], $row['ID'] ) );
				}
			} // Re-count the contacts within Segments
		}
	}

	/**
	 * Executes changes made in Pro 2.8.0.
	 *
	 * @since 3.2.0 Merged in PWP, using non pro prefixed tablenames
	 * @since 2.8.0
	 *
	 * @param int $db_version The old Pro database version.
	 *
	 * @return void
	 */
	private function upgrade_from_pro_280( $db_version ) {
		global $wpdb;

		if ( $db_version < 280 ) {

			// Update all contacts with a non-empty first/last/email/is_verified as `is_known`
			$wpdb->query(
				"UPDATE {$wpdb->prefix}pwp_contacts SET is_known = 1
					WHERE first_name != '' OR last_name != '' OR email != '' OR is_verified = 1"
			);

			// Get all contacts who have at least a single meta data field, such as `_has-completed-form`.
			$rows = $wpdb->get_col(
				"SELECT DISTINCT ID FROM {$wpdb->prefix}pwp_contacts
					INNER JOIN {$wpdb->prefix}pwp_contacts_meta
					ON {$wpdb->prefix}pwp_contacts.ID = {$wpdb->prefix}pwp_contacts_meta.contact_id"
			);
			if ( is_array( $rows ) && 0 < count( $rows ) ) {
				// Update all contacts whom have stored meta data as `is_known`
				$wpdb->query(
					$wpdb->prepare(
						sprintf(
							"UPDATE {$wpdb->prefix}pwp_contacts SET is_known = 1 WHERE ID IN (%s)",
							implode( ',', array_fill( 0, count( $rows ), '%d' ) )
						),
						$rows
					)
				);
			}

			// Find latest activity entry for each contact.
			$rows = $wpdb->get_results(
				"SELECT {$wpdb->prefix}pwp_contacts.ID, MAX( {$wpdb->prefix}pwp_activity.created ) AS last_seen
					FROM {$wpdb->prefix}pwp_contacts
					INNER JOIN {$wpdb->prefix}pwp_activity
					ON {$wpdb->prefix}pwp_contacts.ID = {$wpdb->prefix}pwp_activity.contact_id
					GROUP BY {$wpdb->prefix}pwp_contacts.ID",
				ARRAY_A
			);
			if ( is_array( $rows ) && 0 < count( $rows ) ) {
				// Update each contact with their latest last_seen date.
				foreach ( $rows as $row ) {
					$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}pwp_contacts SET last_seen = %s WHERE ID = %d", $row['last_seen'], $row['ID'] ) );
				}
			}

			// For any remaining contacts, set their last_seen to their creation date, as per during creation.
			$wpdb->query( "UPDATE {$wpdb->prefix}pwp_contacts SET last_seen = created WHERE last_seen = '0000-00-00 00:00:00'" );

			// Clear out the Contacts caches since data has changed.
			wp_cache_flush_group( 'pwp_pro_contacts' );

			// Clear out some old hooks/options.
			wp_clear_scheduled_hook( 'personalisewp_daily_scheduled_events' );
			wp_clear_scheduled_hook( 'pwp_weekly_scheduled_events' );
			delete_option( 'personalizewp_license_status' );
			delete_option( 'personalisewp_license_notice' );
		}
	}

	/**
	 * Executes changes made in Pro 3.0.1.
	 *
	 * @since 3.2.0 Merged in PWP, updated to use merged Block_Mappings class
	 * @since 3.0.1
	 *
	 * @param int $db_version The old Pro database version.
	 *
	 * @return void
	 */
	private function upgrade_from_pro_301( $db_version ) {
		global $wpdb;

		// Content Variations were introduced in Pro 2.8, so check between those versions
		if ( $db_version >= 280 && $db_version < 301 ) {

			// Fix missing Content Variations entries within Block Mappings table.

			// Get public CPTs.
			$post_types = get_post_types( [ 'public' => true ] );
			// Add in specific hidden WP ones.
			$post_types = array_merge( $post_types, array( 'wp_template', 'wp_block' ) );
			// But exclude attachment.
			unset( $post_types['attachment'] );

			// Search for any content variations blocks within the known post types. No limit.
			$variation_rows = $wpdb->get_results(
				$wpdb->prepare(
					sprintf(
						"SELECT ID, post_content, post_type
							FROM {$wpdb->posts}
							WHERE post_content LIKE '%%<!-- wp:personalizewp-pro/variations-block%%' AND post_type IN (%s)",
						implode( ',', array_fill( 0, count( $post_types ), '%s' ) )
					),
					$post_types
				),
				ARRAY_A
			);

			$block_mappings      = null;
			$variations_backfill = [];
			if ( is_array( $variation_rows ) && 0 < count( $variation_rows ) ) {
				$block_mappings = Block_Mappings::instance(); // Directly use the combined Block Mappings class.

				// Loop through each returned database entry.
				foreach ( $variation_rows as $post_row ) {
					// Get block refs out of content.
					preg_match_all( '/"variationsBlockId":"([^"]+)"}/', $post_row['post_content'], $block_refs );
					if ( ! empty( $block_refs[1] ) && is_array( $block_refs[1] ) ) {
						// Loop through each block ref.
						foreach ( $block_refs[1] as $block_ref ) {

							// Check for existance via get_block_mapping.
							$block = $block_mappings->get_block_mapping( $block_ref );
							if ( empty( $block ) ) {
								// If it doesn't exist, it needs backfilling.
								$variations_backfill[] = array(
									'block_ref' => $block_ref,
									'post_ref'  => $post_row['ID'],
									'map_type'  => 'wp_template' === $post_row['post_type'] ? 'site-editor' : 'block-editor',
								);
							}
						}
					}
				}
			}
			if ( ! empty( $variations_backfill ) ) {
				// Bulk save, triggers cache population.
				$block_mappings->save_mappings( $variations_backfill );
			}
		}
	}

	/**
	 * Adds expanded rules. These are rules that Pro originally added,
	 * when initially installed or during Pro upgrades.
	 *
	 * Checks for existence of a rule with same condition so as not to duplicate itself.
	 *
	 * @since 3.2.0
	 *
	 * @return void
	 */
	private function install_expanded_rules() {
		global $wpdb;

		static $has_run;

		if ( isset( $has_run ) ) {
			return;
		}

		$now = current_time( 'mysql' );

		$measure_total_products_ordered = 'woocommerce_total_products_purchased';
		// Check if any rule with this condition already exists
		if ( ! $this->has_existing_rule( $measure_total_products_ordered ) ) {
			$wpdb->insert(
				"{$wpdb->prefix}pwp_rules",
				array(
					'name'            => __( 'Total products purchased greater than one', 'personalizewp' ),
					'category_id'     => 2,
					'type'            => 'standard', // Meaning immutable
					'conditions_json' => wp_json_encode(
						array(
							(object) array(
								'measure'    => $measure_total_products_ordered,
								'meta'       => array(),
								'comparator' => 'more_than',
								'value'      => '1',
							),
						)
					),
					'created_by'      => 0,
					'created_at'      => $now,
					'modified_at'     => $now,
				)
			);
		}

		$measure_time_since_last_order = 'woocommerce_time_since_last_order';
		// Check if any rule with this condition already exists
		if ( ! $this->has_existing_rule( $measure_time_since_last_order ) ) {
			$wpdb->insert(
				$wpdb->prefix . 'pwp_rules',
				array(
					'name'            => __( 'Time Since Last Order > 90 days', 'personalizewp' ),
					'category_id'     => 2, // WooCommerce category
					'type'            => 'standard',
					'conditions_json' => wp_json_encode(
						array(
							// Create a condition for Time Since Last Order > 90 days
							(object) array(
								'measure'    => $measure_time_since_last_order,
								'meta'       => array(),
								'comparator' => 'more_than',
								'value'      => '90',
							),
						),
					),
					'operator'        => 'ALL',
					'created_by'      => 0, // System created
					'created_at'      => $now,
					'modified_at'     => $now,
				)
			);
		}

		$measure_cart_total = 'woocommerce_cart_total';
		// Check if any rule with this condition already exists
		if ( ! $this->has_existing_rule( $measure_cart_total ) ) {
			$wpdb->insert(
				$wpdb->prefix . 'pwp_rules',
				array(
					'name'            => __( 'Total Cart Value > $100', 'personalizewp' ),
					'category_id'     => 2, // WooCommerce category
					'type'            => 'standard',
					'conditions_json' => wp_json_encode(
						array(
							// Create a condition for Total Cart Value > £100
							(object) array(
								'measure'    => $measure_cart_total,
								'meta'       => array(),
								'comparator' => 'more_than',
								'value'      => '100',
							),
						),
					),
					'operator'        => 'ALL',
					'created_by'      => 0, // System created
					'created_at'      => $now,
					'modified_at'     => $now,
				)
			);
		}

		// Total Spend rule
		$measure_total_spend = 'woocommerce_total_spend';
		// Check if any rule with this condition already exists
		if ( ! $this->has_existing_rule( $measure_total_spend ) ) {
			$wpdb->insert(
				$wpdb->prefix . 'pwp_rules',
				array(
					'name'            => __( 'Customer Total Spend > $100', 'personalizewp' ),
					'category_id'     => 2, // WooCommerce category
					'type'            => 'standard',
					'conditions_json' => wp_json_encode(
						array(
							// Create a condition for Customer Total Spend > £100
							(object) array(
								'measure'    => $measure_total_spend,
								'meta'       => array(),
								'comparator' => 'more_than',
								'value'      => '100',
							),
						),
					),
					'operator'        => 'ALL',
					'created_by'      => 0, // System created
					'created_at'      => $now,
					'modified_at'     => $now,
				)
			);
		}

		// Total Orders rule
		$measure_total_orders = 'woocommerce_total_orders';
		// Check if any rule with this condition already exists
		if ( ! $this->has_existing_rule( $measure_total_orders ) ) {
			$wpdb->insert(
				$wpdb->prefix . 'pwp_rules',
				array(
					'name'            => __( 'Customer Total Orders > 5', 'personalizewp' ),
					'category_id'     => 2, // WooCommerce category
					'type'            => 'standard',
					'conditions_json' => wp_json_encode(
						array(
							// Create a condition for Customer Total Orders > 5
							(object) array(
								'measure'    => $measure_total_orders,
								'meta'       => array(),
								'comparator' => 'more_than',
								'value'      => '5',
							),
						),
					),
					'operator'        => 'ALL',
					'created_by'      => 0, // System created
					'created_at'      => $now,
					'modified_at'     => $now,
				)
			);
		}

		// AOV rule
		$measure_aov = 'woocommerce_average_order_value';
		// Check if any rule with this condition already exists
		if ( ! $this->has_existing_rule( $measure_aov ) ) {
			$wpdb->insert(
				$wpdb->prefix . 'pwp_rules',
				array(
					'name'            => __( 'Customer Average Order Value > $25', 'personalizewp' ),
					'category_id'     => 2, // WooCommerce category
					'type'            => 'standard',
					'conditions_json' => wp_json_encode(
						array(
							// Create a condition for Customer AOV > £25
							(object) array(
								'measure'    => $measure_aov,
								'meta'       => array(),
								'comparator' => 'more_than',
								'value'      => '25',
							),
						),
					),
					'operator'        => 'ALL',
					'created_by'      => 0, // System created
					'created_at'      => $now,
					'modified_at'     => $now,
				)
			);
		}

		// Add Product Stock Level rules
		$measure_product_stock_level = 'woocommerce_product_stock_level';
		// Check if any rule with this condition already exists
		if ( ! $this->has_existing_rule( $measure_product_stock_level ) ) {
			$wpdb->insert(
				$wpdb->prefix . 'pwp_rules',
				array(
					'name'            => __( 'Product In Stock', 'personalizewp' ),
					'category_id'     => 2, // WooCommerce category
					'type'            => 'standard',
					'conditions_json' => wp_json_encode(
						array(
							// Create a condition for Product In Stock
							(object) array(
								'measure'    => $measure_product_stock_level,
								'meta'       => array(),
								'comparator' => 'in_stock',
								'value'      => 'true',
							),
						),
					),
					'operator'        => 'ALL',
					'created_by'      => 0, // System created
					'created_at'      => $now,
					'modified_at'     => $now,
				)
			);

			// Assume no out of stock rule, if no in stock rule
			$wpdb->insert(
				$wpdb->prefix . 'pwp_rules',
				array(
					'name'            => __( 'Product Out of Stock', 'personalizewp' ),
					'category_id'     => 2, // WooCommerce category
					'type'            => 'standard',
					'conditions_json' => wp_json_encode(
						array(
							// Create a condition for Product Out of Stock
							(object) array(
								'measure'    => $measure_product_stock_level,
								'meta'       => array(),
								'comparator' => 'out_of_stock',
								'value'      => 'true',
							),
						),
					),
					'operator'        => 'ALL',
					'created_by'      => 0, // System created
					'created_at'      => $now,
					'modified_at'     => $now,
				)
			);
		}

		$has_run = true;
	}

	/**
	 * Check for a rule with an existing condition.
	 *
	 * @since 3.2.0
	 *
	 * @param string $condition Existing condition to check against.
	 *
	 * @return bool
	 */
	private function has_existing_rule( $condition ): bool {
		global $wpdb;

		// Any existing rule with this condition will have an ID
		$result = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT id FROM {$wpdb->prefix}pwp_rules
					WHERE conditions_json LIKE %s
					LIMIT 1",
				'%' . $wpdb->esc_like( '"measure":"' . $condition . '"' ) . '%'
			)
		);

		return (bool) $result;
	}

	/**
	 * Creates the initial Segments content for a newly-installed site.
	 *
	 * @since 3.2.0
	 *
	 * @return void
	 */
	private function install_segment_defaults() {
		global $wpdb;

		static $has_run;

		if ( isset( $has_run ) ) {
			return;
		}

		// Check for any existing segments, skip if there are.
		$existing_entries = $wpdb->get_var( "SELECT COUNT(name) FROM {$wpdb->prefix}pwp_segments" );
		if ( ! empty( $existing_entries ) ) {
			return;
		}

		$now = current_time( 'mysql' );

		$wpdb->insert(
			"{$wpdb->prefix}pwp_segments",
			array(
				'name'       => __( 'Visited homepage', 'personalizewp' ),
				'type'       => 'page',
				'conditions' => wp_json_encode( array( wp_parse_url( home_url( '/' ), PHP_URL_PATH ) ) ),
				'is_active'  => '1',
				'created'    => $now,
			)
		);
		$wpdb->insert(
			"{$wpdb->prefix}pwp_segments",
			array(
				'name'      => __( 'Submitted Form', 'personalizewp' ),
				'type'      => 'form',
				'is_active' => '1',
				'created'   => $now,
			)
		);
		$wpdb->insert(
			"{$wpdb->prefix}pwp_segments",
			array(
				'name'      => __( 'Ordered product from WooCommerce', 'personalizewp' ),
				'type'      => 'product',
				'is_active' => class_exists( '\WooCommerce' ),
				'created'   => $now,
			)
		);
		$wpdb->insert(
			"{$wpdb->prefix}pwp_segments",
			array(
				'name'       => __( 'US Visitors', 'personalizewp' ),
				'type'       => 'location',
				'conditions' => wp_json_encode( array( 'US' ) ),
				'is_active'  => '1',
				'created'    => $now,
			)
		);
		$wpdb->insert(
			"{$wpdb->prefix}pwp_segments",
			array(
				'name'       => __( 'UK Visitors', 'personalizewp' ),
				'type'       => 'location',
				'conditions' => wp_json_encode( array( 'GB' ) ),
				'is_active'  => '1',
				'created'    => $now,
			)
		);
		$wpdb->insert(
			"{$wpdb->prefix}pwp_segments",
			array(
				'name'       => __( 'Search Visitors from Google', 'personalizewp' ),
				'type'       => 'referer',
				'conditions' => wp_json_encode( array( '\.google\..*' => 'regex' ) ),
				'is_active'  => '1',
				'created'    => $now,
			)
		);
		$wpdb->insert(
			"{$wpdb->prefix}pwp_segments",
			array(
				'name'       => __( 'Referred from Facebook', 'personalizewp' ),
				'type'       => 'referer',
				'conditions' => wp_json_encode( array( 'facebook.com' => 'url' ) ),
				'is_active'  => '1',
				'created'    => $now,
			)
		);
		$has_run = true;
	}

	/**
	 * Adds initial Lead Scoring based content.
	 *
	 * @since 3.2.0
	 *
	 * @return void
	 */
	private function install_scoring_defaults() {
		global $wpdb;

		static $has_run;

		if ( isset( $has_run ) ) {
			return;
		}

		$now = current_time( 'mysql' );

		// Check for any existing scoring rules, skip if there are.
		$existing_entries = $wpdb->get_var( "SELECT COUNT(name) FROM {$wpdb->prefix}pwp_scoring_rules" );
		if ( empty( $existing_entries ) ) {
			$wpdb->insert(
				"{$wpdb->prefix}pwp_scoring_rules",
				array(
					'name'       => __( 'Visited homepage', 'personalizewp' ),
					'type'       => 'page',
					'conditions' => wp_json_encode( array( wp_parse_url( home_url( '/' ), PHP_URL_PATH ) ) ),
					'lead_score' => '1',
					'created'    => $now,
				)
			);
		}

		// Check specifically for a lead_score type Segment, which won't yet exist before Scoring Rules were introduced.
		$existing_entries = $wpdb->get_var(
			$wpdb->prepare( "SELECT COUNT(name) FROM {$wpdb->prefix}pwp_segments WHERE type = %s", 'lead_score' )
		);
		if ( empty( $existing_entries ) ) {
			$wpdb->insert(
				"{$wpdb->prefix}pwp_segments",
				array(
					'name'       => __( 'Highly Interested', 'personalizewp' ),
					'type'       => 'lead_score',
					'conditions' => wp_json_encode(
						array(
							'min' => 20,
							'max' => 0,
						)
					),
					'is_active'  => '1',
					'created'    => $now,
				)
			);
		}
		$has_run = true;
	}

	/**
	 * Remove all the PersonalizeWP database tables when uninstalling
	 */
	public static function uninstall() {
		global $wpdb;

		// Remove all custom tables.
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- We are uninstalling the plugin
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_rules" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_categories" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_active_blocks" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_block_mappings" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_activity" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_contacts" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_contacts_meta" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_scoring_rules" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_segments" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_segments_relationships" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_variation_block_mappings" );
		// Include older Pro prefixed tables, just for completeness.
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_pro_activity" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_pro_contacts" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_pro_contacts_meta" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_pro_scoring_rules" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_pro_segments" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_pro_segments_relationships" );
		$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}pwp_pro_variation_block_mappings" );
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange

		// Remove Legacy Pro Options
		delete_option( '_pwp_pro_db' );
		delete_option( 'personalizewp_license_key' );
		delete_option( 'personalizewp_license' );

		// Clear events
		wp_clear_scheduled_hook( 'personalisewp_weekly_scheduled_events' );
		wp_clear_scheduled_hook( 'personalizewp_daily_purge_expired_activity_logs' );
		wp_clear_scheduled_hook( 'personalizewp_daily_purge_expired_anonymous_profiles' );
		wp_clear_scheduled_hook( 'personalizewp_daily_purge_expired_known_profiles' );
		wp_clear_scheduled_hook( 'personalizewp_update_geo_database' );

		// Remove all current options.
		delete_option( self::OPTION_NAME );
		delete_option( 'pwp_editor_role_values' );
		delete_option( 'pwp_admin_onboarding_dismissed' );

		// Reset the onboarding
		delete_option( 'pwp_pending_onboarding' );
		delete_option( 'pwp_onboarding' );
	}
}
