<?php
namespace ActiriseAdmin\Includes;

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

use WP_Error;
use Actirise\Includes\AdFormat;
use Actirise\Includes\Api;
use Actirise\Includes\Cron;
use Actirise\Includes\EventAnnotation;
use Actirise\Includes\Helpers;
use Actirise\Includes\Options;
use Actirise\Includes\Logger;
use ActirisePublic\Includes\AdsTxt;

/**
 * The admin-specific functionality of the plugin.
 *
 * Ajax request manager
 *
 * @link       https://actirise.com
 * @since      2.0.0
 * @package    actirise
 * @subpackage actirise/admin/includes
 * @author     actirise <wordpress@actirise.com>
 */
final class Ajax {
	/**
	 * The ID of this plugin.
	 *
	 * @since    2.0.0
	 * @access   private
	 * @var      string    $plugin_name    The ID of this plugin.
	 */
	private $plugin_name;

	/**
	 * All ajax events
	 *
	 * @since    2.0.0
	 * @access   private
	 * @var      array<string>    $events    All ajax events.
	 */
	private $events;

	/**
	 * Api class
	 *
	 * @since    2.0.0
	 * @access   private
	 * @var      Api    $api    Api class.
	 */
	private $api;

	/**
	 * Cron class
	 *
	 * @since    2.0.0
	 * @access   private
	 * @var      Cron    $cron    Cron class.
	 */
	private $cron;

	/**
	 * Initialize the class and set its properties.
	 *
	 * @since    2.0.0
	 * @param    string $plugin_name          The name of this plugin.
	 */
	public function __construct( $plugin_name ) {
		$this->plugin_name = $plugin_name;

		$this->api  = new Api();
		$this->cron = new Cron();

		$this->events = array(
			'set_custom_var'                => 'set_custom_var',
			'toggle_feature'                => 'toggle_feature',
			'get_adstxt'                    => 'get_adstxt',
			'get_adstxt_custom'             => 'get_adstxt_custom',
			'set_adstxt'                    => 'set_adstxt',
			'check_adstxt'                  => 'check_adstxt',
			'set_presized_div'              => 'set_presized_div',
			'get_articles_by_keywords'      => 'get_articles_by_keywords',
			'add_no_pub'                    => 'add_no_pub',
			'remove_no_pub'                 => 'remove_no_pub',
			'check_url_no_pub'              => 'check_url_no_pub',
			'url_exists'                    => 'url_exists',
			'enabled_debug'                 => 'enabled_debug',
			'options_fast_cmp'              => 'options_fast_cmp',
			'save_cmp_vendors'              => 'save_cmp_vendors',
			'login'                         => 'login',
			'logout'                        => 'logout',
			'create_account'                => 'create_account',
			'resend_registration_email'     => 'resend_registration_email',
			'create_website'                => 'create_website',
			'create_organization'           => 'create_organization',
			'update_currency'               => 'update_currency',
			'get_logs'                      => 'get_logs',
			'set_auto_update'               => 'set_auto_update',
			'save_billing_status'           => 'save_billing_status',
			'update_onboarding_status'      => 'update_onboarding_status',
			'get_contract_preview'          => 'get_contract_preview',
			'sign_contract'                 => 'sign_contract',
			'sync_data'                     => 'sync_data',
			'get_slots'                     => 'get_slots',
			'update_slots'                  => 'update_slots',
			'get_has_cmp'                   => 'get_has_cmp',
			'get_xpath_check_results'       => 'get_xpath_check_results',
			'save_hosted_adstxt'            => 'save_hosted_adstxt',
			'update_plugin'                 => 'update_plugin',
			'check_adstxt_file_for_hosted'  => 'check_adstxt_file_for_hosted',
			'check_hosted_adstxt_redirect'  => 'check_hosted_adstxt_redirect',
			'get_health_issues'             => 'get_health_issues',
			'force_xpath_check'             => 'force_xpath_check',
			'save_gitbook_consent'          => 'save_gitbook_consent',
			'toggle_optimization_checklist' => 'toggle_optimization_checklist',
			'get_event_annotations'         => 'get_event_annotations',
		);

		add_filter( 'posts_where', array( $this, 'search_post_title' ), 10, 2 );
	}

	/**
	 * Dispatch ajax request
	 *
	 * @since    2.0.0
	 * @return void
	 */
	public function dispatch() {
		if ( $this->security_check() !== true ) {
			return;
		}

		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$action = isset( $_POST['action_actirise'] ) ? sanitize_text_field( wp_unslash( $_POST['action_actirise'] ) ) : null;

		if ( is_null( $action ) ) {
			$error = new WP_Error( '404', 'Action empty' );

			wp_send_json_error( $error );
		}

		if ( ! array_key_exists( $action, $this->events ) ) {
			$error = new WP_Error( '404', 'Action not found' );

			wp_send_json_error( $error );
		}

		$method = $this->events[ $action ];

		if ( ! method_exists( $this, $method ) ) {
			$error = new WP_Error( '404', 'Method not found' );

			wp_send_json_error( $error );
		}

		$this->$method();
	}

	/**
	 * Filter for search post title by LIKE
	 *
	 * @since    2.0.0
	 * @param string    $where
	 * @param \WP_Query $wp_query
	 * @return string
	 */
	public function search_post_title( $where, $wp_query ) {
		/** @var \wpdb $wpdb */
		global $wpdb;

		$title = $wp_query->get( 'search_title' );

		if ( $title ) {
			/** @var string $title_sql */
			$title_sql = esc_sql( $wpdb->esc_like( $title ) ); // @phpstan-ignore argument.type
			$where    .= ' AND ' . $wpdb->posts . ".post_title LIKE '%" . $title_sql . "%'";
		}

		return $where;
	}

	/**
	 * Save custom var
	 *
	 * @since    2.0.0
	 * @return void
	 */
	private function set_custom_var() {
		$post_data = $this->get_sanitized_post_data( array( 'customVar' ) );

		if ( in_array( null, $post_data, true ) ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		/** @var string $custom_var */
		$custom_var      = $post_data['customVar'];
		$json_custom_var = json_decode( stripslashes( $custom_var ) );

		if ( ! is_array( $json_custom_var ) ) {
			$error = new WP_Error( '500', 'Need array' );

			wp_send_json_error( $error );
		}

		/** @var array<\stdClass> $json_custom_var */
		foreach ( $json_custom_var as $custom ) {
			Options::update( $custom->name, $custom->value ); // @phpstan-ignore argument.type
		}

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Enabled or disabled feature
	 *
	 * @since    2.0.0
	 * @return void
	 */
	private function toggle_feature() {
		$post_data = $this->get_sanitized_post_data( array( 'feature', 'feature_value' ) );

		if ( in_array( null, $post_data, true ) ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		if ( ! in_array( $post_data['feature'], array( 'adstxt', 'presizeddiv' ), true ) ) {
			$error = new WP_Error( '500', 'Feature not found' );

			wp_send_json_error( $error );
		}

		if ( $post_data['feature'] === 'adstxt' ) {
			$path_ads_txt = ABSPATH . 'ads.txt';

			if ( $post_data['feature_value'] === 'true' ) {
				AdsTxt::update_file();
				$this->cron->update_adstxt();

				// Don't block if hosted mode is enabled
				$is_hosted = Options::get( 'hosted-adstxt-enabled', false ) === true;
				if ( ! $is_hosted && Options::get( 'adstxt-existing', false ) ) {
					wp_send_json(
						array(
							'success' => false,
						)
					);
				}
			} else {
				$this->cron->check_adstxt();
			}

			EventAnnotation::add_event(
				'Ads.txt Manager',
				'Ads.txt feature set to ' . ( $post_data['feature_value'] === 'true' ? 'enabled' : 'disabled' ),
				'website_update',
				Options::get( 'adstxt-active', false ),
				$post_data['feature_value'] === 'true' ? 'true' : 'false'
			);
		}

		if ( $post_data['feature'] === 'presizeddiv' ) {
			EventAnnotation::add_event(
				'Core web vitals',
				'Presized Div feature set to ' . ( $post_data['feature_value'] === 'true' ? 'enabled' : 'disabled' ),
				'website_update',
				Options::get( 'presizeddiv-active', false ) === true ? 'true' : 'false',
				$post_data['feature_value'] === 'true' ? 'true' : 'false'
			);
		}

		Logger::add_log_action(
			'Feature ' . $post_data['feature'] . ' set to ' . $post_data['feature_value']
		);

		Options::update( $post_data['feature'] . '-active', filter_var( $post_data['feature_value'], FILTER_VALIDATE_BOOLEAN ) );

		// Get updated health issues
		$health_check  = new HealthCheck();
		$health_issues = $health_check->get_issues();

		wp_send_json(
			array(
				'success'       => true,
				'health_issues' => $health_issues,
			)
		);
	}

	/**
	 * Get ads.txt
	 *
	 * @since    2.0.0
	 * @return void
	 */
	private function get_adstxt() {
		if ( ! Options::get( 'settings-uuid' ) || ! Options::get( 'settings-uuid-type', 'boot' ) ) {
			$error = new WP_Error( '500', 'Error' );

			wp_send_json_error( $error );
		}

		$response = AdsTxt::get_from_api();

		if ( false !== $response ) {
			wp_send_json(
				array(
					'success' => true,
					'data'    => $response,
				)
			);
		} else {
			Logger::add_log( 'get_adstxt false', 'admin/include/ajax', 'error' );
			$error = new WP_Error( '500', 'Error' );

			wp_send_json_error( $error );
		}
	}

	/**
	 * Get ads.txt custom
	 *
	 * @since    2.0.0
	 * @return void
	 */
	private function get_adstxt_custom() {
		$adstxt_custom = Options::get( 'adstxt-custom', array() );

		wp_send_json(
			array(
				'success' => true,
				'data'    => ! $adstxt_custom ? array() : $adstxt_custom,
			)
		);
	}

	/**
	 * Set ads.txt
	 *
	 * @since    2.0.0
	 * @return void
	 */
	private function set_adstxt() {
		$post_data = $this->get_sanitized_post_data( array( 'adsTxtCustom' ) );

		if ( in_array( null, $post_data, true ) || empty( $post_data['adsTxtCustom'] ) ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		$old_adstxt = Options::get( 'adstxt-custom', '' );

		Options::update( 'adstxt-custom', $post_data['adsTxtCustom'] );

		Logger::add_log_action(
			'Ads.txt custom update'
		);

		EventAnnotation::add_event(
			'Custom Ads.txt modified',
			'The custom ads.txt file content has been updated',
			'website_update',
			$old_adstxt,
			$post_data['adsTxtCustom']
		);

		AdsTxt::update_file();

		$health_check  = new HealthCheck();
		$health_issues = $health_check->get_issues();

		wp_send_json(
			array(
				'success'       => true,
				'health_issues' => $health_issues,
			)
		);
	}

	/**
	 * Check ads.txt lines
	 *
	 * @since 3.0.0
	 * @return void
	 */
	private function check_adstxt() {
		$post_data = $this->get_sanitized_post_data( array( 'force' ) );
		/** @var string $force */
		$force    = $post_data['force'];
		$response = AdsTxt::check_from_api( $force );

		wp_send_json(
			array(
				'success' => true,
				'data'    => $response,
			)
		);
	}

	/**
	 * Get settings
	 *
	 * @since    2.0.0
	 * @return void
	 */
	private function set_presized_div() {
		$post_data = $this->get_sanitized_post_data( array( 'presizedDiv' ) );
		/** @var string $presized_div_data */
		$presized_div_data = $post_data['presizedDiv'];

		Logger::add_log_action(
			'Presized div ' . wp_json_encode( stripslashes( $presized_div_data ) )
		);

		if ( in_array( null, $post_data, true ) || empty( $presized_div_data ) ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		/** @var array<object{slotName: string, active: boolean}> $presized_div_post */
		$presized_div_post = json_decode( stripslashes( $presized_div_data ) );
		$presized_div      = array();

		$old_presized_div     = Options::get( 'presizeddiv-selected', array() );
		$old_presized_div_map = array();

		// Create a map of old settings for comparison
		if ( is_array( $old_presized_div ) ) {
			foreach ( $old_presized_div as $old_div ) {
				if ( isset( $old_div['slotName'] ) ) {
					/** @var string $slot_name */
					$slot_name                          = $old_div['slotName'];
					$old_presized_div_map[ $slot_name ] = isset( $old_div['active'] ) ? $old_div['active'] : false;
				}
			}
		}

		foreach ( $presized_div_post as $key => $value ) {
			$presized_div[] = array(
				'slotName' => $value->slotName,
				'active'   => $value->active,
			);

			// Track changes for each presized div slot
			$old_status = isset( $old_presized_div_map[ $value->slotName ] ) ? $old_presized_div_map[ $value->slotName ] : false;
			$new_status = $value->active;

			if ( $old_status !== $new_status ) {
				EventAnnotation::add_event(
					'Presized div `' . $value->slotName . '`',
					'Presized div `' . $value->slotName . '` feature set to ' . ( $new_status ? 'enabled' : 'disabled' ),
					'website_update',
					$old_status,
					$new_status
				);
			}
		}

		Options::update( 'presizeddiv-selected', $presized_div );

		// Get updated health issues
		$health_check  = new HealthCheck();
		$health_issues = $health_check->get_issues();

		wp_send_json(
			array(
				'success'       => true,
				'health_issues' => $health_issues,
			)
		);
	}

	/**
	 * Get page, post, category, tag by keywords
	 *
	 * @since    2.0.0
	 * @return void
	 */
	private function get_articles_by_keywords() {
		$post_data = $this->get_sanitized_post_data( array( 'keywords' ) );

		if ( in_array( null, $post_data, true ) ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		/** @var string $keywords */
		$keywords = $post_data['keywords'];

		$query_page = new \WP_Query(
			array(
				'post_type'    => 'any',
				'search_title' => $keywords,
				'cache_result' => false,
				'orderby'      => 'post_date',
			)
		);

		/** @var array<\WP_Post> $post */
		$post   = $query_page->posts;
		$result = array();

		foreach ( $post as $page ) {
			if ( $page->post_type !== 'attachment' ) {
				$image = get_the_post_thumbnail_url( $page->ID );

				if ( has_excerpt( $page ) ) {
					$page->post_content = wp_strip_all_tags( get_the_excerpt( $page ), true );
				}

				if ( $page->post_type === 'page' ) {
					$page->post_content = 'article';
				}

				$result[] = array(
					'id'            => $page->ID,
					'guid'          => get_permalink( $page->ID ),
					'title'         => $page->post_title,
					'publishedDate' => $page->post_date,
					'summary'       => substr( $page->post_content, 0, 100 ),
					'image'         => $image,
					'type'          => $page->post_type,
				);
			}
		}

		$count = count( $result );

		$query_terms = get_terms(
			array(
				'taxonomy'   => array( 'post_tag', 'category' ),
				'name__like' => $keywords,
			)
		);

		$result_terms = array();
		/** @var array<\WP_Term> $query_terms */
		foreach ( $query_terms as &$terms ) {
			$term_link = get_term_link( $terms );

			if ( is_wp_error( $term_link ) ) {
				continue;
			}

			$result_terms[] = array(
				'id'      => $terms->term_id,
				'title'   => $terms->name,
				'summary' => substr( $terms->description, 0, 100 ),
				'type'    => $terms->taxonomy,
				'url'     => $term_link,
			);
		}

		$count += count( $result_terms );

		wp_send_json(
			array(
				'page'     => $result,
				'taxonomy' => $result_terms,
				'count'    => $count,
				'keywords' => $keywords,
				'success'  => true,
			)
		);
	}

	/**
	 * Add no pub item
	 *
	 * @since    2.0.0
	 * @return void
	 */
	private function add_no_pub() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'article' => isset( $_POST['article'] ) ? sanitize_text_field( wp_unslash( $_POST['article'] ) ) : null,
			'summary' => isset( $_POST['summary'] ) ? sanitize_text_field( wp_unslash( $_POST['summary'] ) ) : null,
		);

		if ( is_null( $post_data['article'] ) || is_null( $post_data['summary'] ) ) {
			if ( is_null( $post_data['article'] ) ) {
				Logger::add_log( 'add_no_pub post_data is null for article', 'admin/include/ajax', 'error' );
			}

			if ( is_null( $post_data['summary'] ) ) {
				Logger::add_log( 'add_no_pub post_data is null for summary', 'admin/include/ajax', 'error' );
			}

			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		/** @var string $article */
		$article = urldecode( $post_data['article'] );

		/** @var \stdClass $article */
		$article = json_decode( $article );

		if ( ! is_object( $article ) ) {
			Logger::add_log( 'add_no_pub article is not object', 'admin/include/ajax', 'error' );

			$error = new WP_Error( '500', 'Need object' );

			wp_send_json_error( $error );
		}

		/** @var string $summary */
		$summary = urldecode( $post_data['summary'] );

		$article->summary = $summary;

		/** @var array<\stdClass> $no_pub */
		$no_pub = Options::get( 'nopub', array() );

		// Check for duplicates by ID and type, or by URL
		foreach ( $no_pub as $existing_item ) {
			if ( ! is_object( $existing_item ) ) {
				continue;
			}

			// Check by ID and type
			if ( isset( $existing_item->id ) && isset( $article->id ) &&
				isset( $existing_item->type ) && isset( $article->type ) &&
				$existing_item->id === $article->id &&
				$existing_item->type === $article->type ) {
				wp_send_json(
					array(
						'success' => false,
						'error'   => 'duplicate',
						'message' => 'URL already exists in the list',
					)
				);
			}

			// Check by URL
			if ( isset( $existing_item->url ) && isset( $article->url ) &&
				$existing_item->url === $article->url ) {
				wp_send_json(
					array(
						'success' => false,
						'error'   => 'duplicate',
						'message' => 'URL already exists in the list',
					)
				);
			}
		}

		$no_pub[] = $article;

		foreach ( $no_pub as $key => $value ) {
			if ( ! isset( $value->url ) ) {
				unset( $no_pub[ $key ] );
			}
		}

		$add_item_url = null;

		if ( isset( $article->url ) && is_string( $article->url ) ) {
			$add_item_url = strlen( $article->url ) > 145 ? substr( $article->url, 0, 142 ) . '...' : $article->url;
		}

		Options::update( 'nopub', $no_pub );

		EventAnnotation::add_event(
			'No ad - page added',
			'URL: ' . ( isset( $add_item_url ) ? $add_item_url : 'Unknown' ),
			'website_update',
			count( $no_pub ) - 1,
			count( $no_pub )
		);

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Remove no pub item
	 *
	 * @since 2.0.0
	 * @return void
	 */
	private function remove_no_pub() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'id'   => isset( $_POST['id'] ) ? sanitize_text_field( wp_unslash( $_POST['id'] ) ) : null,
			'type' => isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : null,
		);

		if ( is_null( $post_data['id'] ) || is_null( $post_data['type'] ) ) {
			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		/** @var int $id */
		$id = intval( $post_data['id'] );
		/** @var string $type */
		$type = $post_data['type'];

		/** @var array<\stdClass> $no_pub */
		$no_pub = Options::get( 'nopub', array() );

		/** @var array<object{id: int, type: string, url: string}> $no_pub */
		foreach ( $no_pub as $key => $value ) {
			if ( ! isset( $value->url ) ) {
				unset( $no_pub[ $key ] );
			}
		}

		$removed_item_url = null;
		$removed_item     = null;

		/** @var array<object{id: int, type: string, url: string}> $no_pub */
		foreach ( $no_pub as $key => $value ) {
			if ( $value->id === $id && $value->type === $type ) {
				$removed_item = $value;
				unset( $no_pub[ $key ] );
			}
		}

		if ( $removed_item !== null && isset( $removed_item->url ) && is_string( $removed_item->url ) ) {
			$removed_item_url = strlen( $removed_item->url ) > 145 ? substr( $removed_item->url, 0, 142 ) . '...' : $removed_item->url;
		}

		Options::update( 'nopub', $no_pub );

		if ( $removed_item !== null ) {
			EventAnnotation::add_event(
				'No ad - page removed',
				'URL: ' . ( isset( $removed_item_url ) ? $removed_item_url : 'Unknown' ),
				'website_update',
				$removed_item,
				null
			);
		}

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Check if url is valid and get informations (post, page, tag, category)
	 *
	 * @since    2.0.0
	 * @return void
	 */
	private function check_url_no_pub() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'url' => isset( $_POST['url'] ) ? sanitize_text_field( wp_unslash( $_POST['url'] ) ) : null,
		);

		if ( is_null( $post_data['url'] ) ) {
			Logger::add_log( 'check_url_no_pub post_data is null for url', 'admin/include/ajax', 'error' );

			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		$url    = $post_data['url'];
		$result = Helpers::resolve_url_to_content( $url );

		wp_send_json(
			array(
				'success' => true,
				'result'  => $result,
			)
		);
	}

	/**
	 * Check if url exist
	 *
	 * @since 2.0.0
	 * @return void
	 */
	private function url_exists() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'id'   => isset( $_POST['id'] ) ? sanitize_text_field( wp_unslash( $_POST['id'] ) ) : null,
			'type' => isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : null,
		);

		if ( is_null( $post_data['id'] ) || is_null( $post_data['type'] ) ) {
			if ( is_null( $post_data['id'] ) ) {
				Logger::add_log( 'url_exists post_data is null for id', 'admin/include/ajax', 'error' );
			}

			if ( is_null( $post_data['type'] ) ) {
				Logger::add_log( 'url_exists post_data is null for type', 'admin/include/ajax', 'error' );
			}

			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		/** @var int $id */
		$id   = $post_data['id'];
		$type = $post_data['type'];

		$exist = false;

		if ( $type === 'category' || $type === 'post_tag' ) {
			$terms = get_term_by( 'ID', $id, $type );

			if ( $terms !== false ) {
				$exist = true;
			}
		} else {
			$post = get_post( $id );

			if ( $post !== 0 && $post !== null ) {
				if ( $post->post_status !== 'trash' ) {
					$exist = true;
				}
			}
		}

		wp_send_json(
			array(
				'success' => true,
				'exist'   => $exist,
			)
		);
	}

	/**
	 * Set debug mode
	 *
	 * @since 2.0.0
	 * @return void
	 */
	private function enabled_debug() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'debug' => isset( $_POST['debug'] ) ? sanitize_text_field( wp_unslash( $_POST['debug'] ) ) : null,
		);

		if ( is_null( $post_data['debug'] ) ) {
			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		$debug = $post_data['debug'];

		$old_debug = Options::get( 'debug-enabled', false );

		Options::update( 'debug-enabled', $debug );

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Update options for fast cmp
	 *
	 * @since 2.2.0
	 * @return void
	 */
	private function options_fast_cmp() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'options' => isset( $_POST['options'] ) ? sanitize_text_field( wp_unslash( $_POST['options'] ) ) : null,
		);

		if ( is_null( $post_data['options'] ) ) {
			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		/** @var array<mixed> $options */
		$options        = json_decode( $post_data['options'] );
		$enabled_change = false;

		$old_options = array();
		foreach ( $options as $name => $value ) {
			$old_options[ $name ] = Options::get( 'fastcmp-' . $name, null );
		}

		foreach ( $options as $name => $value ) {
			if ( $name === 'consentV2' ) {
				Logger::add_log_action(
					'Change consent mode V2: ' . ( $value ? 'true' : 'false' )
				);
			}

			if ( $name === 'enabled' ) {
				$enabled_change = $old_options[$name] !== ( $value === '1' );
			}

			/** @var string $value */
			Options::update( 'fastcmp-' . $name, $value );
		}

		if ( $enabled_change ) {
			$enabled = Options::get( 'fastcmp-enabled', null );

			EventAnnotation::add_event(
				'FastCMP',
				'FastCMP feature set to ' . ( $enabled ? 'enabled' : 'disabled' ),
				'website_update',
				$old_options['enabled'] ? 'enabled' : 'disabled',
				$enabled ? 'enabled' : 'disabled'
			);

			Logger::add_log_action(
				'Feature CMP set to ' . ( $enabled ? 'true' : 'false' )
			);
		} else {
			EventAnnotation::add_event(
				'FastCMP settings updated',
				'',
				'website_update',
				$old_options,
				$options
			);
		}

		$health_check  = new HealthCheck();
		$health_issues = $health_check->get_issues();

		wp_send_json(
			array(
				'success'       => true,
				'health_issues' => $health_issues,
			)
		);
	}

	/**
	 * Login to API v2
	 *
	 * @since 2.3.15
	 * @return void
	 */
	private function login() {
		$post_data = $this->get_sanitized_post_data( array( 'email', 'password' ) );

		if ( in_array( null, $post_data, true ) ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		$response = $this->api->post( 'api', 'authentication_token', $post_data );

		if ( $this->is_email_not_validated_response( $response ) ) {
			wp_send_json(
				array(
					'success' => true,
					'data'    => array(
						'emailNotValidated' => true,
					),
				)
			);
		}

		$validated_response = $this->validate_api_response( $response, 'Error while login' );

		/** @var string $token */
		$token = $validated_response['token'];
		/** @var string $user */
		$user = $validated_response['user'];
		/** @var string $user_id */
		$user_id = Helpers::extract_id( $user );

		Options::update( 'settings-analytics-token', $token );
		Options::update( 'settings-analytics-userid', $user_id );

		$user_role = Helpers::get_user_role( $user_id, $token );
		$currency  = $validated_response['currency'];

		$api_token_response = $this->api->post( 'api', 'api_tokens', array( 'name' => 'wordpress' ), $token );
		$api_token          = $this->validate_api_response( $api_token_response, 'Error while login api token', false );

		$website       = Helpers::get_website_info();
		$uuid          = '';
		$organizations = array();

		if ( empty( $website ) ) {
			if ( $user_role !== 'organization_manager' ) {
				wp_send_json(
					array(
						'success' => false,
						'message' => 'Access denied. Please try to log in with an account associated with this website.',
					)
				);
			}
			$organizations = $this->get_organizations( $token );
		} else {
			Core::check_website_status( $website );
			$uuid = $this->get_organization_uuid( $token );
			Options::update( 'settings-uuid', $uuid, true );
			Options::update( 'settings-uuid-type', 'universal', true );
			Options::update( 'settings-analytics-token', $api_token['token'], true );
			Options::update( 'website-exists', true );

			$this->cron->update_adstxt();
			$this->cron->check_presized_div();

			if ( ACTIRISE_CRON === 'true' ) {
				$this->cron->schedule();
			}
		}

		wp_send_json(
			array(
				'success' => true,
				'data'    => array(
					'auth_token'       => $token,
					'token'            => $api_token['token'],
					'uuid'             => $uuid,
					'userId'           => $user_id,
					'userRole'         => $user_role,
					'currency'         => $currency,
					'organizations'    => $organizations,
					'onboardingStatus' => Options::get( 'onboarding-status' ),
					'contractType'     => Options::get( 'contract-type', 'essential' ),
					'contractStatus'   => Options::get( 'contract-status' ),
					'trialEndDate'     => Options::get( 'trial-end-date' ),
					'adsServing'       => Options::get( 'ads-serving' ),
					'type'             => Options::get( 'settings-uuid-type' ),
					'presizedDiv'      => Options::get( 'presizeddiv-selected', array() ),
					'adstxt'           => Options::get( 'adstxt-actirise', '' ),
					'customv'          => array(
						'custom1' => Options::get( 'custom1', 'author_ID' ),
						'custom2' => Options::get( 'custom2', 'category_0_slug' ),
						'custom3' => Options::get( 'custom3', 'post_ID' ),
						'custom4' => Options::get( 'custom4', '' ),
						'custom5' => Options::get( 'custom5', '' ),
					),
				),
			)
		);
	}

	/**
	 * Logout from API v2
	 *
	 * @since 2.0.0
	 * @return void
	 */
	private function logout() {
		Options::delete( 'settings-analytics-token' );
		Options::delete( 'settings-analytics-userid' );
		Options::delete( 'gitbook-consent' );

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Create a lightweight account through the centralized registration workflow.
	 *
	 * Only creates the user (firstname, lastname, email, password) via the new
	 * `/user_registrations` endpoint. The organization, website and NDA steps are
	 * completed on the central Actirise platform. The API answers 204 (no token):
	 * a validation email is sent and the user must confirm it before logging in.
	 *
	 * @since 2.5.0
	 * @return void
	 */
	private function create_account() {
		$post_data = $this->get_sanitized_post_data(
			array(
				'firstname',
				'lastname',
				'email',
				'password',
			)
		);

		if ( in_array( null, $post_data, true ) ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		$args = array(
			'workEmail'          => $post_data['email'],
			'firstName'          => $post_data['firstname'],
			'lastName'           => $post_data['lastname'],
			'password'           => $post_data['password'],
			'registrationOrigin' => 'wordpress_plugin',
			// Full URL of the Actirise plugin page in the WP admin dashboard,
			// used as the fallback link in the welcome email.
			'wordpressFallback'  => Helpers::get_page_url(),
		);

		$response = $this->api->post( 'api', 'user_registrations', $args );

		if ( is_wp_error( $response ) ) {
			$error_code = (string) $response->get_error_code();

			// 409: an account with this email address already exists.
			if ( '409' === $error_code ) {
				wp_send_json(
					array(
						'success' => true,
						'data'    => array(
							'emailAlreadyExists' => true,
						),
					)
				);
			}

			// 422: validation error (invalid email, weak/compromised password, ...).
			if ( '422' === $error_code ) {
				wp_send_json_error(
					array(
						'response' => $response->get_error_data(),
						'status'   => 422,
					)
				);
			}

			Logger::add_log( 'Error while user registration', 'admin/include/ajax', 'error' );

			wp_send_json_error(
				array(
					'response' => $response,
					'status'   => $error_code,
				)
			);
		}

		wp_send_json(
			array(
				'success' => true,
				'data'    => array(
					'email' => $post_data['email'],
				),
			)
		);
	}

	/**
	 * Resend the registration validation email.
	 *
	 * Calls the public `/user_registrations/resend` endpoint (202 Accepted). The
	 * endpoint is idempotent: the same answer is returned whether the email is
	 * unknown or already validated, to avoid account enumeration.
	 *
	 * @since 4.2.0
	 * @return void
	 */
	private function resend_registration_email() {
		$post_data = $this->get_sanitized_post_data( array( 'email' ) );

		if ( $post_data['email'] === null ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		$response = $this->api->post(
			'api',
			'user_registrations/resend',
			array( 'workEmail' => $post_data['email'] )
		);

		if ( is_wp_error( $response ) ) {
			$error_code = (string) $response->get_error_code();

			// 429: too many resend attempts.
			if ( '429' === $error_code ) {
				wp_send_json_error(
					array(
						'response' => 'rate_limited',
						'status'   => 429,
					)
				);
			}

			Logger::add_log( 'Error while resending registration email', 'admin/include/ajax', 'error' );

			wp_send_json_error(
				array(
					'response' => $response,
					'status'   => $error_code,
				)
			);
		}

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Create new website
	 *
	 * @since 2.5.0
	 * @return void
	 */
	private function create_website() {
		$post_data = $this->get_sanitized_post_data( array( 'organization' ) );

		if ( $post_data['organization'] === null ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		$website_domain = rawurlencode( Helpers::get_server_details()['host'] );

		$args = array(
			'domain'         => $website_domain,
			'name'           => get_bloginfo( 'name' ),
			'websiteProduct' => '/v2.0/website_products/3',
			'organization'   => $post_data['organization'],
		);

		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );
		/** @var string $organization_iri */
		$organization_iri = $post_data['organization'];
		$organization_id  = Helpers::extract_id( $organization_iri );

		$response = $this->api->post( 'api', 'websites', $args, $token );

		if ( is_wp_error( $response ) ) {
			Logger::add_log( 'Error while register wp_e', 'admin/include/ajax', 'error' );
			$error = new WP_Error( '500', 'Error while register wp_e' );

			wp_send_json_error( array( $error, $response, $args ) );
		}

		if ( ! is_array( $response ) ) {
			Logger::add_log( 'Error while register not_a', 'admin/include/ajax', 'error' );
			$error = new WP_Error( '500', 'Error while register not_a' );

			wp_send_json_error( array( $error, $response ) );
		}

		$api_token_response = $this->api->post( 'api', 'api_tokens', array( 'name' => 'wordpress' ), $token );
		$api_token          = $this->validate_api_response( $api_token_response, 'Error while login api token', false );
		$uuid               = $this->get_organization_uuid( $token, $organization_id );

		Options::update( 'settings-uuid', $uuid, true );
		Options::update( 'settings-uuid-type', 'universal', true );
		Options::update( 'settings-analytics-token', $api_token['token'] );
		Options::update( 'website-exists', true );
		Options::update( 'onboarding-status', 'approval' );

		if ( Options::get( 'settings-analytics-token', '' ) !== '' && Options::get( 'settings-analytics-userid', '' ) === '' ) {
			Helpers::get_user_id();
		}

		$this->cron->update_adstxt();

		if ( ACTIRISE_CRON === 'true' ) {
			$this->cron->schedule();
		}

		wp_send_json(
			array(
				'success' => true,
				'data'    => $response,
			)
		);
	}

	/**
	 * Create new organization
	 *
	 * @since 2.5.0
	 * @return void
	 */
	private function create_organization() {
		$post_data = $this->get_sanitized_post_data(
			array(
				'name',
				'address',
				'city',
				'country',
				'registrationID',
				'website',
				'zipCode',
			)
		);

		if ( in_array( null, $post_data, true ) ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		$organization_type_iri = $this->get_organization_type_iri( 'company' );
		$country_iri           = $this->get_country_iri( $post_data['country'] );
		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );

		$args = array(
			'name'                => $post_data['name'],
			'type'                => $organization_type_iri,
			'address'             => $post_data['address'],
			'city'                => $post_data['city'],
			'country'             => $country_iri,
			'localRegistrationId' => $post_data['registrationID'],
			'website'             => $post_data['website'],
			'postalCode'          => $post_data['zipCode'],
		);

		$response_organization = $this->api->post( 'api', 'organizations', $args, $token, true );

		if ( is_wp_error( $response_organization ) ) {
			Logger::add_log( 'Error while create_organization wp_e', 'admin/include/ajax', 'error' );

			wp_send_json_error(
				array(
					'response' => $response_organization,
					'status'   => $response_organization->get_error_code(),
				)
			);
		}

		if ( ! is_array( $response_organization ) ) {
			Logger::add_log( 'Error while create organization not_a', 'admin/include/ajax', 'error' );
			$error = new WP_Error( '500', 'Error while create organization not_a' );

			wp_send_json_error( array( $error, $response_organization ) );
		}

		$website_domain = rawurlencode( Helpers::get_server_details()['host'] );

		$args = array(
			'domain'         => $website_domain,
			'name'           => get_bloginfo( 'name' ),
			'websiteProduct' => '/v2.0/website_products/3',
			'organization'   => $response_organization['@id'],
		);

		$response = $this->api->post( 'api', 'websites', $args, $token );

		$api_token_response = $this->api->post( 'api', 'api_tokens', array( 'name' => 'wordpress' ), $token );
		$api_token          = $this->validate_api_response( $api_token_response, 'Error while login api token', false );

		Options::update( 'settings-uuid', $response_organization['uuid'] );
		Options::update( 'settings-uuid-type', 'universal', true );
		Options::update( 'settings-analytics-token', $api_token['token'] );
		Options::update( 'website-exists', true );
		Options::update( 'onboarding-status', 'approval' );
		Options::update( 'organization-country', $post_data['country'], true );

		if ( Options::get( 'settings-analytics-token', '' ) !== '' && Options::get( 'settings-analytics-userid', '' ) === '' ) {
			Helpers::get_user_id();
		}

		$this->cron->update_adstxt();

		if ( ACTIRISE_CRON === 'true' ) {
			$this->cron->schedule();
		}

		$response['organizationCountry'] = $post_data['country'];

		wp_send_json(
			array(
				'success' => true,
				'data'    => $response,
			)
		);
	}

	/**
	 * Update currency settings
	 *
	 * @since 2.5.0
	 * @return void
	 */
	private function update_currency() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'currency' => isset( $_POST['currency'] ) ? sanitize_text_field( wp_unslash( $_POST['currency'] ) ) : null,
		);

		if ( $post_data['currency'] === null ) {
			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		/** @var string $user_id */
		$user_id = Options::get( 'settings-analytics-userid', '' );
		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );

		if ( $user_id !== '' && $token !== '' ) {
			$response = $this->api->patch(
				'api',
				'users/' . $user_id,
				array(
					'currency' => $post_data['currency'],
				),
				$token
			);

			if ( is_wp_error( $response ) ) {
				Logger::add_log( 'Error while updating currency ' . $response->get_error_code(), 'admin/include/ajax', 'error' );
				$error = new WP_Error( '500', 'Error while updating currency' );

				wp_send_json_error( $error );
			}
		}

		Options::update( 'currency', $post_data['currency'] );

		Logger::add_log_action(
			'Currency set to ' . $post_data['currency']
		);

		wp_send_json(
			array(
				'success' => true,
				'data'    => $post_data['currency'],
			)
		);
	}

	/**
	 * Get logs
	 *
	 * @since 2.5.0
	 * @return void
	 */
	private function get_logs() {
		$logs         = Logger::get_logs();
		$logs_actions = Logger::get_logs_action();

		wp_send_json(
			array(
				'success' => true,
				'data'    => array(
					'logs'        => $logs,
					'logs_action' => $logs_actions,
				),
			)
		);
	}

	/**
	 * Set auto update settings
	 *
	 * @since 2.6.5
	 * @return void
	 */
	private function set_auto_update() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'autoUpdate' => isset( $_POST['autoUpdate'] ) ? sanitize_text_field( wp_unslash( $_POST['autoUpdate'] ) ) : null,
		);

		if ( is_null( $post_data['autoUpdate'] ) ) {
			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		$this->cron->set_auto_update( $post_data['autoUpdate'] === 'true' );

		Logger::add_log_action(
			'Auto update set to ' . $post_data['autoUpdate']
		);

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Update onboarding status
	 *
	 * @since 2.5.0
	 * @return void
	 */
	private function update_onboarding_status() {
		$post_data = $this->get_sanitized_post_data( array( 'status' ) );

		if ( $post_data['status'] === null ) {
			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		$statuses = array(
			'premium_io',
			'ads',
			'cmp',
			'activation',
			'congrats',
		);

		$status_index = array_search( $post_data['status'], $statuses, true );

		if ( $status_index === false || $status_index === count( $statuses ) - 1 ) {
			$error = new WP_Error( '400', 'Invalid or final status' );
			wp_send_json_error( $error );
		}

		$next_status    = $statuses[$status_index + 1];
		$trial_end_date = null;

		if ( $next_status === 'ads' ) {
			$this->cron->update_adstxt();
		}

		if ( $post_data['status'] === 'activation' ) {
			Options::update( 'activation-time', time() );
			$website = Helpers::get_website_info();

			if ( $website['adsServing'] === false ) {
				/** @var string $token */
				$token           = Options::get( 'settings-analytics-token', '' );
				$response_config = $this->api->post( 'api', 'default_configs', array( 'website' => $website['@id'] ), $token );

				if ( is_wp_error( $response_config ) ) {
					Logger::add_log( 'update_onboarding_status default_configs error', 'admin/include/ajax', 'error' );
					Helpers::send_error( '500', 'Error while activating Actirise' );
					return;
				}
			}

			if ( isset( $website['trialEndDate'] ) ) {
				$trial_end_date = $website['trialEndDate'];
			}
		}

		Options::update( 'onboarding-status', $next_status );

		wp_send_json(
			array(
				'success'      => true,
				'status'       => $next_status,
				'trialEndDate' => $trial_end_date,
			)
		);
	}

	/**
	 * Get contract preview
	 *
	 * @since 3.0.0
	 * @return void
	 */
	private function get_contract_preview() {
		$website = Helpers::get_website_info();

		/** @var string $website_id */
		$website_id = $website['@id'];
		$endpoint   = str_replace( '/v2.0/', '', $website_id ) . '/contract';

		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );

		$response = $this->api->get( 'api', $endpoint, array(), $token );

		if ( is_wp_error( $response ) ) {
			Helpers::send_error( '500', 'Error while get_contract_preview' );
		}

		wp_send_json(
			array(
				'success' => true,
				'data'    => $response,
			)
		);
	}

	/**
	 * Sign contract
	 *
	 * @since 3.0.0
	 * @return void
	 */
	private function sign_contract() {
		$website = Helpers::get_website_info();
		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );

		$response = $this->api->post( 'api', 'websites_contracts_signed', array( 'websites' => array( $website['@id'] ) ), $token );

		if ( is_wp_error( $response ) ) {
			Helpers::send_error( '500', 'Error while signing contract' );
		}

		wp_send_json(
			array(
				'success' => true,
				'data'    => $response,
			)
		);
	}

	/**
	 * Save CMP vendors snapshot for comparison
	 *
	 * @since 3.3.0
	 * @return void
	 */
	private function save_cmp_vendors() {
		$post_data = $this->get_sanitized_post_data( array( 'vendors' ) );

		if ( in_array( null, $post_data, true ) ) {
			Helpers::send_error( '500', 'Post empty' );
		}

		/** @var string $vendors_json */
		$vendors_json = $post_data['vendors'];
		$vendors      = json_decode( stripslashes( $vendors_json ), true );

		if ( ! is_array( $vendors ) ) {
			Helpers::send_error( '400', 'Invalid vendors data' );
		}

		/** @var array<mixed> $vendors */
		// Extract only vendor IDs for comparison
		$vendor_ids = array_map(
			function ( $vendor ) {
				return isset( $vendor['id'] ) ? $vendor['id'] : null;
			},
			$vendors
		);

		// Remove null values
		$vendor_ids = array_filter( $vendor_ids );

		// Sort for consistent comparison
		sort( $vendor_ids );

		Options::update( 'cmp_vendors_snapshot', $vendor_ids );

		wp_send_json(
			array(
				'success' => true,
				'data'    => $vendor_ids,
			)
		);
	}

	/**
	 * Save platform billing status
	 *
	 * @since 3.0.0
	 * @return void
	 */
	private function save_billing_status() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'billingStatus' => isset( $_POST['billingStatus'] ) ? sanitize_text_field( wp_unslash( $_POST['billingStatus'] ) ) : null,
		);

		if ( $post_data['billingStatus'] === null ) {
			$error = new WP_Error( '500', 'Billing status is null' );

			wp_send_json_error( $error );
		}

		$billing_status = $post_data['billingStatus'];

		Options::update( 'billing-status', $billing_status );

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Sync data
	 *
	 * @since 2.6.8
	 * @return void
	 */
	private function sync_data() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$post_data = array(
			'feature' => isset( $_POST['feature'] ) ? sanitize_text_field( wp_unslash( $_POST['feature'] ) ) : null,
		);

		if ( is_null( $post_data['feature'] ) ) {
			$error = new WP_Error( '500', 'Post empty' );

			wp_send_json_error( $error );
		}

		$response = null;

		if ( $post_data['feature'] === 'adstxt' ) {
			$this->cron->update_adstxt();

			$response = AdsTxt::get_adstxt();
		}

		if ( $post_data['feature'] === 'presized_div' ) {
			$this->cron->check_presized_div();

			$response = Options::get( 'presizeddiv-selected', array() );
		}

		if ( $post_data['feature'] === 'fastcmp' ) {
			$this->cron->get_fast_cmp();

			$response = Options::get( 'fastcmp-vendors', array() );
		}

		wp_send_json(
			array(
				$response,
			)
		);
	}


	/**
	 * Security check for ajax request
	 *
	 * @since 2.4.0
	 * @return bool
	 */
	private function security_check() {
		if ( false === check_ajax_referer( $this->plugin_name . '-settings', 'nonce', true ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		return true;
	}

	/**
	 * Get sanitized post data for every requests
	 *
	 * @since 3.0.0
	 * @param array<mixed> $keys
	 * @return array<mixed>
	 */
	private function get_sanitized_post_data( $keys ) {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			$error = new WP_Error( '403', 'Security check is not valid' );

			wp_send_json_error( $error );
		}

		$data = array();
		foreach ( $keys as $key ) {
			/** @var string $key_string */
			$key_string        = $key;
			$data[$key_string] = isset( $_POST[$key_string] ) ? sanitize_text_field( wp_unslash( $_POST[$key_string] ) ) : null;
		}
		return $data;
	}

	/**
	 * Validate api response on auth routes
	 *
	 * @since 3.0.0
	 * @param mixed  $response
	 * @param string $error_message
	 * @param bool   $return_error
	 * @return array<mixed>
	 */
	private function validate_api_response( $response, $error_message, $return_error = true ) {
		$is_error = is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['token'] );

		if ( $is_error && $return_error === true ) {
			Helpers::send_error( '500', $error_message );
		} elseif ( $is_error && $return_error === false ) {
			$response = array( 'token' => '' );
		}

		if ( is_array( $response ) ) {
			return $response;
		}

		return array();
	}

	/**
	 * Detect whether an authentication response failed because the account email
	 * has not been validated yet.
	 *
	 * Both invalid credentials and a not-yet-activated account answer 401, so the
	 * only discriminator the API exposes is the message returned by the security
	 * UserChecker (`user_checker.not_activated`). The known translations are matched
	 * explicitly. Keep this list in sync with the krull-api `validators` catalog.
	 *
	 * @since 4.2.0
	 * @param mixed $response The raw API response.
	 * @return bool
	 */
	private function is_email_not_validated_response( $response ) {
		if ( ! is_wp_error( $response ) || (string) $response->get_error_code() !== '401' ) {
			return false;
		}

		$error_data = $response->get_error_data();

		if ( ! is_array( $error_data ) || ! isset( $error_data['message'] ) || ! is_string( $error_data['message'] ) ) {
			return false;
		}

		$not_activated_messages = array(
			'Your account is not activated.',
			"Votre compte n'est pas activé.",
		);

		return in_array( $error_data['message'], $not_activated_messages, true );
	}

	/**
	 * Get organization uuid if website exists
	 *
	 * @since 3.0.0
	 * @param string $token
	 * @param string $organization_id
	 * @return string
	 */
	private function get_organization_uuid( $token, $organization_id = '' ) {
		if ( empty( $organization_id ) ) {
			$organization_id = Options::get( 'organization-id' );
		}

		if ( ! empty( $organization_id ) && is_string( $organization_id ) ) {
			$response = $this->api->get(
				'api',
				'organizations/' . $organization_id,
				array(),
				$token
			);

			if ( is_wp_error( $response ) || ! is_array( $response ) || empty( $response ) ) {
				return '';
			}

			if ( isset( $response['uuid'] ) && is_string( $response['uuid'] ) ) {
				return $response['uuid'];
			}
		}

		return '';
	}

	/**
	 * Get organization IRI
	 *
	 * @since 3.0.0
	 * @param string $type
	 * @return string
	 */
	private function get_organization_type_iri( $type ) {
		$organization_type_iri = '';
		$response              = $this->api->get( 'api', 'organization_types', array(), false, true );

		if ( is_wp_error( $response ) || ! is_array( $response ) || ! is_array( $response['hydra:member'] ) ) {
			Helpers::send_error( '500', 'Error while get organization_types' );
		}

		if ( is_array( $response ) && is_array( $response['hydra:member'] ) ) {
			foreach ( $response['hydra:member'] as $organization_type ) {
				if (
					is_array( $organization_type ) &&
					is_string( $organization_type['value'] ) &&
					strtolower( $organization_type['value'] ) === $type ) {
					if ( is_string( $organization_type['@id'] ) ) {
						$organization_type_iri = $organization_type['@id'];
					}
				}
			}
		}

		return $organization_type_iri;
	}

	/**
	 * Get country IRI
	 *
	 * @since 3.0.0
	 * @param mixed $country_code
	 * @return string
	 */
	private function get_country_iri( $country_code ) {
		$country_iri = '';
		$response    = $this->api->get( 'api', 'countries', array( 'code' => $country_code ), false, true );

		if ( is_wp_error( $response ) || ! is_array( $response ) || ! is_array( $response['hydra:member'] ) ) {
			Helpers::send_error( '500', 'Error while get_country_iri' );
		}

		if ( is_array( $response ) && is_array( $response['hydra:member'] ) ) {
			foreach ( $response['hydra:member'] as $country ) {
				if ( is_array( $country ) && $country['code'] === $country_code ) {
					if ( is_string( $country['@id'] ) ) {
						$country_iri = $country['@id'];
					}
				}
			}
		}

		return $country_iri;
	}

	/**
	 * Fetches organizations
	 *
	 * @since 3.0.0
	 * @param string $token
	 * @return array<array<string, mixed>>
	 */
	private function get_organizations( $token ) {
		$response = $this->api->get( 'api', 'organizations', array(), $token, true );

		if ( ! is_array( $response ) || empty( $response['hydra:member'] ) ) {
			return array();
		}

		if ( is_array( $response['hydra:member'] ) ) {
			return array_map(
				function ( $org ) {
					// `ndaRequired` is flipped to false by Krull once the NDA is signed
					// (or when no NDA is required), so it doubles as a "signed" flag.
					return array(
						'name'      => $org['name'],
						'value'     => $org['@id'],
						'ndaSigned' => isset( $org['ndaRequired'] ) ? ! $org['ndaRequired'] : false,
					);
				},
				$response['hydra:member']
			);
		}

		return array();
	}

	/**
	 * Get website enabled slots
	 *
	 * @since 3.1.0
	 * @return void
	 */
	private function get_slots() {
		$ad_format = new AdFormat();
		$response  = $ad_format->get_slots();

		if ( is_wp_error( $response ) ) {
			Helpers::send_error( '500', $response->get_error_message() );
		}

		if ( ! is_wp_error( $response ) ) {
			AdFormat::cache_enabled_slugs( $response );
		}

		wp_send_json(
			array(
				'success' => true,
				'data'    => $response,
			)
		);
	}


	/**
	 * Update website slots
	 *
	 * @since 3.1.0
	 * @return void
	 */
	private function update_slots() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			Helpers::send_error( '403', 'Security check is not valid' );
		}

		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );

		$ad_format = new AdFormat();
		$slots_raw = isset( $_POST['slots'] ) ? wp_unslash( $_POST['slots'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$slots     = is_string( $slots_raw ) ? sanitize_textarea_field( $slots_raw ) : '';

		if ( empty( $slots ) ) {
			Helpers::send_error( '400', 'Slots data is required' );
		}

		$in_image_raw = isset( $_POST['inImage'] ) ? wp_unslash( $_POST['inImage'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$in_image     = is_string( $in_image_raw ) ? sanitize_textarea_field( $in_image_raw ) : '';

		$note_raw = isset( $_POST['note'] ) ? wp_unslash( $_POST['note'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$note     = is_string( $note_raw ) ? sanitize_textarea_field( $note_raw ) : '';

		$response = $ad_format->update_slots( $slots, $in_image, $note );

		if ( is_wp_error( $response ) ) {
			Helpers::send_error( '500', $response->get_error_message() );
		}

		if ( ! is_wp_error( $response ) ) {
			AdFormat::cache_enabled_slugs( $response );
		}

		EventAnnotation::add_event(
			'New Ad formats configuration',
			'A new Ad formats configuration was published',
			'config_changes'
		);

		// Get updated health issues
		$health_check  = new HealthCheck();
		$health_issues = $health_check->get_issues();

		wp_send_json(
			array(
				'success'                 => true,
				'data'                    => $response,
				'message'                 => 'Slots updated successfully',
				'health_issues'           => $health_issues,
				'ad_format_enabled_slugs' => Options::get( 'ad-format-enabled-slugs', array() ),
			)
		);
	}

	/**
	 * Check whether the website has an active CMP detected by Krull.
	 *
	 * Krull centralizes the analytics-based detection and works for unsigned websites too,
	 * whereas the analytics dashboard endpoint filters them out.
	 *
	 * @since 4.1.2
	 * @return void
	 */
	private function get_has_cmp() {
		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );

		$website_id = Options::get( 'website-id', '' );

		if ( $website_id === '' ) {
			$website = Helpers::get_website_info();

			if ( isset( $website['@id'] ) && is_string( $website['@id'] ) ) {
				$website_id = basename( $website['@id'] );
				Options::update( 'website-id', $website_id );
			}
		}

		if ( $website_id === '' ) {
			Helpers::send_error( '500', 'Unable to retrieve website ID' );
		}

		$endpoint = 'websites/' . $website_id . '/has_cmp';
		$response = $this->api->get( 'api', $endpoint, array(), $token );

		if ( is_wp_error( $response ) ) {
			Helpers::send_error( '500', $response->get_error_message() );
		}

		wp_send_json(
			array(
				'success' => true,
				'data'    => $response,
			)
		);
	}

	/**
	 * Get XPath check results from the last check
	 *
	 * @since 3.1.0
	 * @return void
	 */
	private function get_xpath_check_results() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			Helpers::send_error( '403', 'Security check is not valid' );
		}

		$ad_format = new AdFormat();
		$results   = $ad_format->get_xpath_check_results();

		wp_send_json(
			array(
				'success' => true,
				'data'    => $results,
			)
		);
	}

	/**
	 * Save hosted ads.txt setting
	 *
	 * @since 3.3.1
	 * @return void
	 */
	private function save_hosted_adstxt() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			Helpers::send_error( '403', 'Security check is not valid' );
		}

		$post_data = array(
			'enabled' => isset( $_POST['enabled'] ) ? sanitize_text_field( wp_unslash( $_POST['enabled'] ) ) : 'false',
		);

		$enabled = $post_data['enabled'] === 'true' || $post_data['enabled'] === '1' || $post_data['enabled'] === 1;

		Options::update( 'hosted-adstxt-enabled', $enabled );
		Logger::add_log( 'Hosted ads.txt ' . ( $enabled ? 'enabled' : 'disabled' ), 'admin/include/ajax', 'info' );

		// Clear redirect error if disabling
		if ( ! $enabled ) {
			Options::delete( 'hosted-adstxt-redirect-error' );
		}

		// If enabling, verify the redirect works correctly
		if ( $enabled ) {
			$uuid = Options::get( 'website-uuid', '' );

			if ( empty( $uuid ) ) {
				wp_send_json(
					array(
						'success' => false,
						'error'   => 'redirect_verification_failed',
						'message' => 'Website UUID is not configured.',
					)
				);
			}

			$expected_url = 'https://gateway.actirise.com/services/adstxt/' . $uuid;
			$site_url     = get_site_url();
			$ads_txt_url  = trailingslashit( $site_url ) . 'ads.txt';

			$response = wp_remote_get(
				$ads_txt_url,
				array(
					'timeout'     => 10,
					'redirection' => 0,
					'sslverify'   => false,
					'user-agent'  => 'Actirise-Redirect-Check/1.0',
				)
			);

			$status_code = is_wp_error( $response ) ? 0 : wp_remote_retrieve_response_code( $response );
			$headers     = is_wp_error( $response ) ? array() : wp_remote_retrieve_headers( $response );
			$location    = isset( $headers['location'] ) ? $headers['location'] : '';
			$body        = is_wp_error( $response ) ? '' : wp_remote_retrieve_body( $response );

			// Case 1: No redirect at all (status 200) - third-party config or file served directly
			if ( $status_code === 200 ) {
				// Check if it's the Actirise file being served (should contain specific marker)
				$is_actirise_file = strpos( $body, 'actirise.com' ) !== false || strpos( $body, 'sparteo.com' ) !== false;

				if ( ! $is_actirise_file ) {
					wp_send_json(
						array(
							'success'      => false,
							'error'        => 'redirect_verification_failed',
							'error_type'   => 'third_party',
							'flashbid_url' => $expected_url,
							'message'      => 'Third-party configuration detected blocking redirection.',
						)
					);
				}
			}

			// Case 2: Redirect to wrong URL
			if ( ( $status_code === 302 || $status_code === 301 ) && ! empty( $location ) ) {
				// Ignore WordPress trailing slash redirects (ads.txt -> ads.txt/)
				$is_trailing_slash_redirect = ( $location === trailingslashit( $ads_txt_url ) );

				if ( ! $is_trailing_slash_redirect && $location !== $expected_url ) {
					$location_str = is_string( $location ) ? $location : '';
					wp_send_json(
						array(
							'success'      => false,
							'error'        => 'redirect_verification_failed',
							'error_type'   => 'wrong_url',
							'flashbid_url' => $expected_url,
							'message'      => sprintf( 'Redirect points to wrong URL: %s instead of %s', $location_str, $expected_url ),
						)
					);
				}
			}

			// Case 3: Other errors (timeout, no redirect, etc.)
			if ( $status_code !== 302 && $status_code !== 200 ) {
				wp_send_json(
					array(
						'success'      => false,
						'error'        => 'redirect_verification_failed',
						'error_type'   => 'manual_required',
						'flashbid_url' => $expected_url,
						'message'      => 'Server-side rule or security layer preventing automatic redirection.',
					)
				);
			}

			Logger::add_log( 'Hosted ads.txt redirect verified successfully', 'admin/include/ajax', 'info' );
			// Clear any previous redirect error
			Options::delete( 'hosted-adstxt-redirect-error' );
			// Ensure adstxt-active is false so the right option defaults to manual
			Options::update( 'adstxt-active', false );
		}

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Check if ads.txt file exists and can be renamed for hosted mode
	 *
	 * @since 3.3.5
	 * @return void
	 */
	private function check_adstxt_file_for_hosted() {
		$nonce = isset( $_REQUEST['nonce'] ) ?
			sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) :
			'';

		if ( ! wp_verify_nonce( $nonce, 'actirise-settings' ) ) {
			Helpers::send_error( '403', 'Security check is not valid' );
			return;
		}

		$path_ads_txt = ABSPATH . 'ads.txt';
		$wp_fs        = Helpers::get_wp_fs();

		if ( is_wp_error( $wp_fs ) ) {
			wp_send_json(
				array(
					'success'     => false,
					'file_exists' => false,
					'error'       => 'filesystem_error',
				)
			);
		}

		$file_exists = $wp_fs->exists( $path_ads_txt );

		// Check if a redirect exists (not ours)
		$has_third_party_redirect = false;
		$uuid                     = Options::get( 'website-uuid', '' );
		$expected_url             = 'https://gateway.actirise.com/services/adstxt/' . $uuid;
		$site_url                 = get_site_url();
		$ads_txt_url              = trailingslashit( $site_url ) . 'ads.txt';

		$response = wp_remote_get(
			$ads_txt_url,
			array(
				'timeout'     => 10,
				'redirection' => 0,
				'sslverify'   => false,
			)
		);

		if ( ! is_wp_error( $response ) ) {
			$status_code = wp_remote_retrieve_response_code( $response );
			if ( $status_code === 302 || $status_code === 301 ) {
				$redirect_location = wp_remote_retrieve_header( $response, 'location' );
				// Ignore WordPress trailing slash redirects (ads.txt -> ads.txt/)
				$is_trailing_slash_redirect = ( $redirect_location === trailingslashit( $ads_txt_url ) );
				// If there's a redirect but not to our URL and not a trailing slash redirect, it's a third-party redirect
				if ( ! empty( $redirect_location ) && $redirect_location !== $expected_url && ! $is_trailing_slash_redirect ) {
					$has_third_party_redirect = true;
				}
			}
		}

		// Persist or clear redirect error in DB so health check reflects current state
		if ( ! $file_exists && ! $has_third_party_redirect ) {
			Options::update( 'hosted-adstxt-redirect-error', false );
		} elseif ( $file_exists ) {
			Options::update(
				'hosted-adstxt-redirect-error',
				array( 'error_type' => 'file_exists' )
			);
		} elseif ( $has_third_party_redirect ) {
			Options::update(
				'hosted-adstxt-redirect-error',
				array( 'error_type' => 'third_party' )
			);
		}

		wp_send_json(
			array(
				'success'                  => true,
				'file_exists'              => $file_exists,
				'has_third_party_redirect' => $has_third_party_redirect,
			)
		);
	}

	/**
	 * Get health check issues
	 *
	 * @since 4.0.0
	 * @return void
	 */
	private function get_health_issues() {
		$health_check  = new HealthCheck();
		$health_issues = $health_check->get_issues();

		wp_send_json(
			array(
				'success'       => true,
				'health_issues' => $health_issues,
			)
		);
	}

	/**
	 * Force XPath check and return updated health issues
	 *
	 * @since 4.0.0
	 * @return void
	 */
	private function force_xpath_check() {
		$ad_format = new AdFormat();
		$ad_format->validate_xpath_and_notify();

		// Get updated health issues
		$health_check  = new HealthCheck();
		$health_issues = $health_check->get_issues();

		wp_send_json(
			array(
				'success'       => true,
				'health_issues' => $health_issues,
			)
		);
	}

	/**
	 * Save GitBook AI assistant consent for this website.
	 *
	 * @since 4.0.0
	 * @return void
	 */
	private function save_gitbook_consent() {
		Options::update( 'gitbook-consent', true );

		wp_send_json(
			array(
				'success' => true,
			)
		);
	}

	/**
	 * Toggle an optimization checklist manual step.
	 *
	 * @since 4.1.0
	 * @return void
	 */
	private function toggle_optimization_checklist() {
		$allowed_steps = array( 'cache_exclusion', 'editorial_best_practices' );

		$step  = isset( $_POST['step'] ) ? sanitize_text_field( wp_unslash( $_POST['step'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in dispatch()
		$value = isset( $_POST['value'] ) ? filter_var( wp_unslash( $_POST['value'] ), FILTER_VALIDATE_BOOLEAN ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in dispatch()

		if ( ! in_array( $step, $allowed_steps, true ) ) {
			Helpers::send_error( '400', 'Invalid step' );
			return;
		}

		$manual_steps = Options::get( 'optimization-checklist-manual', array() );
		if ( ! is_array( $manual_steps ) ) {
			$manual_steps = array();
		}

		$manual_steps[ $step ] = $value;
		Options::update( 'optimization-checklist-manual', $manual_steps );

		$health_check  = new HealthCheck();
		$health_issues = $health_check->get_issues();

		wp_send_json(
			array(
				'success'       => true,
				'health_issues' => $health_issues,
			)
		);
	}

	/**
	 * Proxy request for event annotations list to avoid CORS issues.
	 *
	 * @since 4.1.0
	 * @return void
	 */
	private function get_event_annotations() {
		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );

		if ( empty( $token ) ) {
			Helpers::send_error( '401', 'Missing API token' );
			return;
		}

		$website_iri = '/v2.0/websites/' . Options::get( 'website-id' );

		$body = array(
			'timezone'     => 'Europe/Paris',
			'startDate'    => '2026-01-20',
			'scope'        => array( 'public' ),
			'page'         => 1,
			'itemsPerPage' => 5,
			'sort'         => 'desc',
			'websites'     => array( $website_iri ),
		);

		$response = $this->api->post( 'api', 'event_annotations/list', $body, $token, true );

		if ( is_wp_error( $response ) ) {
			Helpers::send_error( '500', 'Error fetching event annotations' );
			return;
		}

		wp_send_json(
			array(
				'success' => true,
				'data'    => $response,
			)
		);
	}
}
