<?php
/**
 * Admin — registers menus and handles all play CRUD with the new UX.
 *
 * @package CartCue
 */

namespace CartCue\Admin;

use CartCue\DB;
use CartCue\PlayPayloadSanitizer;
use CartCue\PlayValidator;
use CartCue\Plugin;
use CartCue\Recipes;

defined( 'ABSPATH' ) || exit;

class Admin {

	private Plugin $plugin;

	public function __construct( Plugin $plugin ) {
		$this->plugin = $plugin;
		new BuildFeatures( $this->plugin );
		add_action( 'admin_menu',                              [ $this, 'register_menus' ] );
		add_action( 'admin_enqueue_scripts',                   [ $this, 'enqueue_assets' ] );
		add_action( 'admin_post_cartcue_save_play',          [ $this, 'handle_save_play' ] );
		add_action( 'admin_post_cartcue_delete_play',        [ $this, 'handle_delete_play' ] );
		add_action( 'admin_post_cartcue_duplicate_play',     [ $this, 'handle_duplicate_play' ] );
		add_action( 'admin_post_cartcue_toggle_play',        [ $this, 'handle_toggle_play' ] );
		add_action( 'admin_action_cartcue_activate_play',  [ $this, 'handle_activate_play' ] );
		add_action( 'admin_post_cartcue_save_settings',      [ $this, 'handle_save_settings' ] );
		add_action( 'admin_post_cartcue_import_guests',    [ $this, 'handle_import_guests' ] );
		add_action( 'admin_notices',                           [ $this, 'show_notices' ] );
		add_action( 'wp_ajax_cartcue_preview_email',         [ $this, 'handle_preview_email' ] );
		add_action( 'wp_ajax_cartcue_builder_preview',       [ $this, 'handle_builder_preview' ] );
		add_action( 'wp_ajax_cartcue_get_preset_blocks',    [ $this, 'handle_get_preset_blocks' ] );
		add_action( 'wp_ajax_cartcue_preview_audience',      [ $this, 'handle_preview_audience' ] );
		add_action( 'wp_ajax_cartcue_search_picker',         [ $this, 'handle_search_picker' ] );
		add_action( 'wp_ajax_cartcue_customer_journey',      [ $this, 'handle_customer_journey' ] );
		add_action( 'admin_post_cartcue_save_campaign',      [ $this, 'handle_save_campaign' ] );
		add_action( 'admin_post_cartcue_delete_campaign',    [ $this, 'handle_delete_campaign' ] );
		add_action( 'admin_post_cartcue_dismiss_quickstart', [ $this, 'handle_dismiss_quickstart' ] );
		add_action( 'admin_init',                              [ $this, 'show_license_notice' ] );
		add_action( 'admin_init',                              [ $this, 'redirect_legacy_logs_page' ] );
	}

	// -------------------------------------------------------------------------
	// Menus
	// -------------------------------------------------------------------------

	public function register_menus(): void {
		add_menu_page(
			__( 'CartCue', 'cartcue-sales-automation' ),
			__( 'CartCue', 'cartcue-sales-automation' ),
			'manage_woocommerce',
			'cartcue',
			[ $this, 'render_plays_page' ],
			'dashicons-controls-repeat',
			56
		);
		add_submenu_page( 'cartcue', __( 'Automations', 'cartcue-sales-automation' ), __( 'Automations', 'cartcue-sales-automation' ), 'manage_woocommerce', 'cartcue', [ $this, 'render_plays_page' ] );
		add_submenu_page( 'cartcue', __( 'Templates', 'cartcue-sales-automation' ), __( 'Templates', 'cartcue-sales-automation' ), 'manage_woocommerce', 'cartcue-recipes', [ $this, 'render_recipes_page' ] );
		add_submenu_page( 'cartcue', __( 'Reports', 'cartcue-sales-automation' ), __( 'Reports', 'cartcue-sales-automation' ), 'manage_woocommerce', 'cartcue-reports', [ $this, 'render_reports_page' ] );
		add_submenu_page( 'cartcue', __( 'Settings', 'cartcue-sales-automation' ), __( 'Settings', 'cartcue-sales-automation' ), 'manage_woocommerce', 'cartcue-settings', [ $this, 'render_settings_page' ] );
		add_submenu_page( 'cartcue', __( 'Send Now (Manual)', 'cartcue-sales-automation' ), __( 'Send Now (Manual)', 'cartcue-sales-automation' ), 'manage_woocommerce', 'cartcue-campaign', [ $this, 'render_campaign_page' ] );
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			add_submenu_page( 'cartcue', __( 'Queue', 'cartcue-sales-automation' ), __( 'Queue', 'cartcue-sales-automation' ), 'manage_woocommerce', 'cartcue-queue', [ $this, 'render_queue_page' ] );
		}
		add_submenu_page( 'cartcue', __( 'Integrations', 'cartcue-sales-automation' ), __( 'Integrations', 'cartcue-sales-automation' ), 'manage_woocommerce', 'cartcue-integrations', [ $this, 'render_integrations_page' ] );
	}

	/**
	 * Redirect old Logs submenu URL to Reports → Logs tab.
	 */
	public function redirect_legacy_logs_page(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- legacy admin route redirect.
		if ( ! isset( $_GET['page'] ) || 'cartcue-logs' !== $_GET['page'] ) {
			return;
		}
		$args = [
			'page' => 'cartcue-reports',
			'tab'  => 'logs',
		];
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- log filter redirect only.
		if ( isset( $_GET['status'] ) && '' !== $_GET['status'] ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- log filter redirect only.
			$args['status'] = sanitize_key( wp_unslash( $_GET['status'] ) );
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- log filter redirect only.
		if ( isset( $_GET['paged'] ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- log filter redirect only.
			$args['paged'] = max( 1, absint( wp_unslash( $_GET['paged'] ) ) );
		}
		wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
		exit;
	}

	// -------------------------------------------------------------------------
	// Assets
	// -------------------------------------------------------------------------

	public function enqueue_assets( string $hook ): void {
		if ( false === strpos( $hook, 'cartcue' ) ) { return; }

		// Local/some hosts return 502 when core bundles (hoverIntent + wp-hooks) are concatenated via load-scripts.php.
		add_filter( 'js_do_concat', '__return_false' );
		add_filter( 'css_do_concat', '__return_false' );

		wp_enqueue_style( 'cartcue-tabler-icons', CARTCUE_PLUGIN_URL . 'assets/vendor/tabler-icons/tabler-icons.min.css', [], '3.31.0' );
		wp_enqueue_style( 'cartcue-admin', CARTCUE_PLUGIN_URL . 'assets/css/admin.css', [ 'cartcue-tabler-icons' ], CARTCUE_VERSION );
		wp_enqueue_script( 'cartcue-admin', CARTCUE_PLUGIN_URL . 'assets/js/admin.js', [ 'jquery' ], CARTCUE_VERSION, true );

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$page = sanitize_key( $_GET['page'] ?? '' );
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$action = sanitize_key( $_GET['action'] ?? '' );
		$is_play_editor     = 'cartcue' === $page && in_array( $action, [ 'edit', 'new' ], true );
		$use_legacy_stepper = $is_play_editor && ! $this->uses_unified_play_editor_for_request();
		if ( $is_play_editor ) {
			wp_enqueue_style(
				'cartcue-email-builder',
				CARTCUE_PLUGIN_URL . 'assets/css/email-builder.css',
				[ 'cartcue-admin' ],
				CARTCUE_VERSION
			);
			wp_enqueue_script(
				'cartcue-email-builder',
				CARTCUE_PLUGIN_URL . 'assets/js/email-builder.js',
				[ 'cartcue-admin', 'jquery' ],
				CARTCUE_VERSION,
				true
			);
			if ( $use_legacy_stepper ) {
				wp_enqueue_style(
					'cartcue-guided-stepper',
					CARTCUE_PLUGIN_URL . 'assets/css/guided-stepper.css',
					[ 'cartcue-admin' ],
					CARTCUE_VERSION
				);
				wp_enqueue_script(
					'cartcue-guided-stepper',
					CARTCUE_PLUGIN_URL . 'assets/js/guided-stepper.js',
					[ 'cartcue-admin', 'cartcue-play-validation', 'jquery' ],
					CARTCUE_VERSION,
					true
				);
			}
			wp_enqueue_script(
				'cartcue-action-inserter',
				CARTCUE_PLUGIN_URL . 'assets/js/action-inserter.js',
				[ 'cartcue-admin', 'jquery' ],
				CARTCUE_VERSION,
				true
			);
			$play_ux_deps = [ 'cartcue-admin', 'cartcue-action-inserter', 'jquery' ];
			if ( $use_legacy_stepper ) {
				$play_ux_deps[] = 'cartcue-guided-stepper';
			}
			wp_enqueue_script(
				'cartcue-play-ux',
				CARTCUE_PLUGIN_URL . 'assets/js/play-ux.js',
				$play_ux_deps,
				CARTCUE_VERSION,
				true
			);
			wp_enqueue_script(
				'cartcue-play-validation',
				CARTCUE_PLUGIN_URL . 'assets/js/play-validation.js',
				[ 'cartcue-admin', 'jquery' ],
				CARTCUE_VERSION,
				true
			);
			if ( $use_legacy_stepper ) {
				wp_enqueue_style(
					'cartcue-sequence-timeline',
					CARTCUE_PLUGIN_URL . 'assets/css/sequence-timeline.css',
					[ 'cartcue-admin' ],
					CARTCUE_VERSION
				);
				wp_enqueue_script(
					'cartcue-sequence-timeline',
					CARTCUE_PLUGIN_URL . 'assets/js/sequence-timeline.js',
					[ 'cartcue-admin', 'cartcue-guided-stepper', 'cartcue-play-validation', 'jquery' ],
					CARTCUE_VERSION,
					true
				);
			}
			wp_enqueue_script(
				'cartcue-gutenberg-editor',
				CARTCUE_PLUGIN_URL . 'assets/js/gutenberg-editor.js',
				[ 'jquery', 'wp-editor' ],
				CARTCUE_VERSION,
				true
			);
			wp_enqueue_editor();
			wp_enqueue_style( 'wp-edit-blocks' );
			wp_enqueue_media();
		}

		wp_set_script_translations(
			'cartcue-admin',
			'cartcue-sales-automation',
			CARTCUE_PLUGIN_DIR . 'languages'
		);
		if ( $is_play_editor ) {
			wp_set_script_translations(
				'cartcue-guided-stepper',
				'cartcue-sales-automation',
				CARTCUE_PLUGIN_DIR . 'languages'
			);
			wp_set_script_translations(
				'cartcue-email-builder',
				'cartcue-sales-automation',
				CARTCUE_PLUGIN_DIR . 'languages'
			);
		}

		$localize = [
			'ajaxUrl'      => admin_url( 'admin-ajax.php' ),
			'isPro'        => $this->plugin->is_pro(),
			'adminEmail'   => get_option( 'admin_email' ),
			'storeName'    => get_bloginfo( 'name' ),
			'storeUrl'     => home_url( '/' ),
			'storeLogoUrl' => \CartCue\Variables::get_store_logo_url(),
			'nonces'       => [],
		];

		if ( $is_play_editor ) {
			$localize['defaultMode']      = 'guided';
			$localize['emailPresets']     = \CartCue\EmailTemplates::for_js();
			$localize['maxSequenceSteps'] = $this->plugin->max_sequence_steps();
			$localize['canUseBranching']  = $this->plugin->can_use_branching();
			$localize['mailDelivery']     = \CartCue\MailDelivery::get_status();
			$localize['triggers']         = $this->triggers_config();
			$localize['actions']          = $this->actions_config();
			$localize['rules']            = $this->rules_config();
			$localize['timingUnits']      = [
				'minutes' => __( 'minutes', 'cartcue-sales-automation' ),
				'hours'   => __( 'hours',   'cartcue-sales-automation' ),
				'days'    => __( 'days',    'cartcue-sales-automation' ),
			];
			$localize['seqStrings']       = [
				'emptyHint'     => __( 'Add follow-up steps to run one action at a time after each wait period.', 'cartcue-sales-automation' ),
				'needsAction'   => __( 'Choose what should happen first.', 'cartcue-sales-automation' ),
				'thenRun'       => __( 'Then, after', 'cartcue-sales-automation' ),
				'waitLabel'     => __( 'Wait', 'cartcue-sales-automation' ),
				'removeStep'    => __( 'Remove this follow-up step', 'cartcue-sales-automation' ),
				'step'          => $this->plugin->is_pro()
					? __( 'Step', 'cartcue-sales-automation' )
					: __( 'Follow-up', 'cartcue-sales-automation' ),
				'limitReached'  => $this->plugin->is_pro()
					? __( 'Sequence step limit reached.', 'cartcue-sales-automation' )
					: __( 'Follow-up step limit reached. Upgrade to Pro for unlimited follow-up steps.', 'cartcue-sales-automation' ),
				'branchLabel'   => __( 'If / Else branch', 'cartcue-sales-automation' ),
				'branchIf'      => __( 'If condition is true → step', 'cartcue-sales-automation' ),
				'branchElse'    => __( 'Else → step', 'cartcue-sales-automation' ),
				'addBranch'     => __( 'Add If/Else branch', 'cartcue-sales-automation' ),
			];
			$localize['branchRules']      = [
				[ 'slug' => 'email_opened', 'label' => __( 'Email opened (this play)', 'cartcue-sales-automation' ), 'operators' => [ 'is' => __( 'has been opened', 'cartcue-sales-automation' ), 'is_not' => __( 'has not been opened', 'cartcue-sales-automation' ) ] ],
				[ 'slug' => 'email_clicked', 'label' => __( 'Email clicked (this play)', 'cartcue-sales-automation' ), 'operators' => [ 'is' => __( 'has been clicked', 'cartcue-sales-automation' ), 'is_not' => __( 'has not been clicked', 'cartcue-sales-automation' ) ] ],
				[ 'slug' => 'customer_email_engagement', 'label' => __( 'Email engagement', 'cartcue-sales-automation' ), 'operators' => [ 'opened_within' => __( 'opened within (days)', 'cartcue-sales-automation' ), 'not_opened_within' => __( 'NOT opened within (days)', 'cartcue-sales-automation' ) ], 'value' => true ],
				[ 'slug' => 'order_contains_product', 'label' => __( 'Order contains product', 'cartcue-sales-automation' ), 'operators' => [ 'includes' => __( 'includes', 'cartcue-sales-automation' ), 'not_includes' => __( 'does not include', 'cartcue-sales-automation' ) ], 'value' => true ],
			];
			$localize['timelineStrings']  = [
				'railTitle'         => $this->plugin->is_pro()
					? __( 'Sequence', 'cartcue-sales-automation' )
					: __( 'Follow-up journey', 'cartcue-sales-automation' ),
				'railAria'          => $this->plugin->is_pro()
					? __( 'Sequence steps', 'cartcue-sales-automation' )
					: __( 'Follow-up steps', 'cartcue-sales-automation' ),
				'addStep'           => __( 'Add step', 'cartcue-sales-automation' ),
				'triggerFallback'   => __( 'Trigger', 'cartcue-sales-automation' ),
				'triggerMeta'       => __( 'Starts this automation', 'cartcue-sales-automation' ),
				'triggerHint'       => __( 'Edit when this automation runs in the “When…” step.', 'cartcue-sales-automation' ),
				'gotoTrigger'       => __( 'Go to When…', 'cartcue-sales-automation' ),
				'initialWaitMeta'   => __( 'Before the first message', 'cartcue-sales-automation' ),
				'seqWaitMetaFirst'  => __( 'Then send the first message', 'cartcue-sales-automation' ),
				'seqWaitMeta'       => __( 'Before the next message', 'cartcue-sales-automation' ),
				'actionMeta'        => __( 'Edit content on the right', 'cartcue-sales-automation' ),
				'actionFallback'    => __( 'Action', 'cartcue-sales-automation' ),
				/* translators: %1$d: message step number */
				'messageStep'       => __( 'Message %1$d', 'cartcue-sales-automation' ),
				'waitImmediate'     => __( 'Right away', 'cartcue-sales-automation' ),
				/* translators: %1$d: wait duration number, %2$s: unit label (e.g. days, hours) */
				'waitFor'           => __( 'Wait for %1$d %2$s', 'cartcue-sales-automation' ),
				'atTime'            => __( 'At ', 'cartcue-sales-automation' ),
				'missingContent'    => __( 'Missing content', 'cartcue-sales-automation' ),
				'stepReady'         => __( 'Ready', 'cartcue-sales-automation' ),
				'waitHelpInitial'   => __( 'After the trigger fires, wait this long before the first message is sent.', 'cartcue-sales-automation' ),
				'waitHelpFollowUp'  => __( 'If the customer has not completed the action yet, send the next message after this wait.', 'cartcue-sales-automation' ),
				'openingMessage'    => __( 'Opening message', 'cartcue-sales-automation' ),
				'missingSubject'    => __( 'Subject line required', 'cartcue-sales-automation' ),
				'missingBody'       => __( 'Email content required', 'cartcue-sales-automation' ),
				'missingMessage'    => __( 'Message required', 'cartcue-sales-automation' ),
				'reviewIncomplete'  => __( 'Complete these steps before activating:', 'cartcue-sales-automation' ),
			];
			$localize['guided']           = [
				'loading'       => __( 'Loading…', 'cartcue-sales-automation' ),
				'when_fallback' => __( 'something happens', 'cartcue-sales-automation' ),
				'immediately'   => __( 'immediately', 'cartcue-sales-automation' ),
				'at_time'       => __( 'at ', 'cartcue-sales-automation' ),
				'filters'       => __( 'filter(s)', 'cartcue-sales-automation' ),
				'no_filters'    => __( 'Everyone (no filters)', 'cartcue-sales-automation' ),
				'followups'     => __( 'follow-up step(s)', 'cartcue-sales-automation' ),
				'no_followups'  => __( 'None', 'cartcue-sales-automation' ),
				'err_trigger'   => __( 'Please choose a trigger first.', 'cartcue-sales-automation' ),
				'err_name'      => __( 'Give this automation a name.', 'cartcue-sales-automation' ),
				'err_subject'   => __( 'A subject line is required.', 'cartcue-sales-automation' ),
				'next_timing'   => __( 'Next: set the wait →', 'cartcue-sales-automation' ),
				'next_email'    => __( 'Next: write the message →', 'cartcue-sales-automation' ),
				'next_review'   => __( 'Next: review & activate →', 'cartcue-sales-automation' ),
				'save_play'     => __( 'Save & Activate', 'cartcue-sales-automation' ),
			];
			$message_action_slugs = array_values(
				array_filter(
					[ 'send_email', 'send_sms', 'send_whatsapp' ],
					fn( string $slug ): bool => (bool) $this->plugin->get_action( $slug )
				)
			);
			$featured_action_slugs = array_keys( $this->plugin->get_actions() );
			$other_action_slugs    = array_values(
				array_filter(
					$featured_action_slugs,
					fn( string $slug ): bool => ! in_array( $slug, $message_action_slugs, true )
				)
			);
			$localize['featuredActions']   = $featured_action_slugs;
			$localize['messageActionSlugs'] = $message_action_slugs;
			$localize['otherActionSlugs']   = $other_action_slugs;
			$localize['playUx']          = [
				'triggerNames' => [
					'cart_abandoned'           => __( "Bring 'em back — abandoned carts", 'cartcue-sales-automation' ),
					'checkout_started'         => __( 'Nudge checkout starters', 'cartcue-sales-automation' ),
					'order_paid'               => __( 'Thank-you after every order', 'cartcue-sales-automation' ),
					'customer_created'         => __( 'Welcome new customers', 'cartcue-sales-automation' ),
					'customer_win_back'        => __( 'Win back the quiet ones', 'cartcue-sales-automation' ),
					'customer_never_purchased' => __( 'First purchase nudge', 'cartcue-sales-automation' ),
					'order_status_changed'     => __( 'Order Status Update', 'cartcue-sales-automation' ),
					'payment_failed'           => __( 'Payment Failed Recovery', 'cartcue-sales-automation' ),
					'new_review'               => __( 'Review Request', 'cartcue-sales-automation' ),
					'inbound_webhook'          => __( 'Inbound Webhook Flow', 'cartcue-sales-automation' ),
				],
				'timingRecommendations' => [
					'cart_abandoned'           => [ 'type' => 'delay', 'v' => 1, 'u' => 'hours' ],
					'customer_win_back'        => [ 'type' => 'delay', 'v' => 7, 'u' => 'days' ],
					'customer_created'         => [ 'type' => 'immediate', 'v' => 0, 'u' => '' ],
					'order_paid'               => [ 'type' => 'immediate', 'v' => 0, 'u' => '' ],
					'order_status_changed'     => [ 'type' => 'immediate', 'v' => 0, 'u' => '' ],
					'payment_failed'           => [ 'type' => 'delay', 'v' => 30, 'u' => 'minutes' ],
					'new_review'               => [ 'type' => 'delay', 'v' => 1, 'u' => 'days' ],
				],
				'actionIcons' => [
					'send_email'          => 'ti-mail',
					'create_coupon'       => 'ti-ticket',
					'change_order_status' => 'ti-package',
					'add_order_note'      => 'ti-note',
					'send_sms'            => 'ti-message',
					'send_whatsapp'       => 'ti-brand-whatsapp',
					'webhook'             => 'ti-webhook',
				],
				'saveLabels' => [
					'active_new'  => __( 'Save & Activate ✓', 'cartcue-sales-automation' ),
					'active_edit' => __( 'Update & Activate ✓', 'cartcue-sales-automation' ),
					'paused_new'  => __( 'Save (Paused) ✓', 'cartcue-sales-automation' ),
					'paused_edit' => __( 'Update (Paused) ✓', 'cartcue-sales-automation' ),
				],
				'immediately'      => __( 'immediately', 'cartcue-sales-automation' ),
				'atTime'           => __( 'at ', 'cartcue-sales-automation' ),
				'somethingHappens' => __( 'Trigger', 'cartcue-sales-automation' ),
				'actionFallback'   => __( 'Action', 'cartcue-sales-automation' ),
				'sendMessageLabel' => __( 'Send a message', 'cartcue-sales-automation' ),
			];
			$localize['preview']          = [
				'modal_title' => __( 'Email Preview', 'cartcue-sales-automation' ),
				'send_inbox'  => __( 'Send Test to Inbox', 'cartcue-sales-automation' ),
				'close'       => __( 'Close', 'cartcue-sales-automation' ),
				'sample_note' => __( 'Sample data is used for {{ }} variables.', 'cartcue-sales-automation' ),
				'loading'     => __( 'Loading preview…', 'cartcue-sales-automation' ),
			];
			$localize['editor']           = [
				'step4_simple'    => __( 'Choose what happens when this play runs.', 'cartcue-sales-automation' ),
				'step4_sequence'  => __( 'Reminders run one at a time with waits. Keep the actions below ready for each follow-up step.', 'cartcue-sales-automation' ),
				'story_timed'     => __( 'Timed steps:', 'cartcue-sales-automation' ),
				'wait_verb'       => __( 'wait', 'cartcue-sales-automation' ),
				'rules_empty'     => __( 'All customers who match the trigger. Optionally filter by order history, product, or role.', 'cartcue-sales-automation' ),
				'rule_prefix'     => __( 'Customer', 'cartcue-sales-automation' ),
				'draft_saved'     => __( 'Draft saved', 'cartcue-sales-automation' ),
				'timing_singular' => [
					'minutes' => __( 'minute', 'cartcue-sales-automation' ),
					'hours'   => __( 'hour', 'cartcue-sales-automation' ),
					'days'    => __( 'day', 'cartcue-sales-automation' ),
				],
			];
			$localize['nonces']           = [
				'test_play'          => wp_create_nonce( 'cartcue_test_play' ),
				'preview_email'      => wp_create_nonce( 'cartcue_preview_email' ),
				'builder_preview'    => wp_create_nonce( 'cartcue_builder_preview' ),
				'preview_audience'   => wp_create_nonce( 'cartcue_preview_audience' ),
				'search_picker'      => wp_create_nonce( 'cartcue_search_picker' ),
				'test_mail_delivery' => wp_create_nonce( 'cartcue_test_mail_delivery' ),
			];
		} elseif ( 'cartcue-settings' === $page ) {
			$localize['nonces'] = [
				'test_mail_delivery' => wp_create_nonce( 'cartcue_test_mail_delivery' ),
			];
		} elseif ( 'cartcue-reports' === $page ) {
			$localize['nonces'] = [
				'test_mail_delivery' => wp_create_nonce( 'cartcue_test_mail_delivery' ),
				'customer_journey'   => wp_create_nonce( 'cartcue_customer_journey' ),
			];
		}

		wp_localize_script( 'cartcue-admin', 'cartcueAdmin', $localize );
	}

	// -------------------------------------------------------------------------
	// Page renderers
	// -------------------------------------------------------------------------

	public function render_plays_page(): void {
		$plugin = $this->plugin;
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- admin list/navigation only.
		$action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : 'list';

		if ( 'list' === $action && \CartCue\Onboarding\FirstRunWizard::should_show() ) {
			\CartCue\Onboarding\FirstRunWizard::render();
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- recipe auto-activate link only.
		if ( 'new' === $action
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- recipe auto-activate link only.
			&& isset( $_GET['recipe'], $_GET['autoactivate'] )
			&& '1' === sanitize_key( wp_unslash( $_GET['autoactivate'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- recipe auto-activate link only.
		) {
			$this->handle_autoactivate_recipe();
			return;
		}

		if ( 'recipes' === $action ) {
			$recipes = Recipes::all();
			include __DIR__ . '/views/recipes.php';
		} elseif ( 'edit' === $action || 'new' === $action ) {
			if ( 'new' === $action ) {
				// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- editor entry routing only.
				$automation_type_query = sanitize_key( wp_unslash( $_GET['automation_type'] ?? '' ) );
				// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- editor entry routing only.
				$recipe_key_for_route = sanitize_key( wp_unslash( $_GET['recipe'] ?? '' ) );
				if ( '' === $automation_type_query && '' === $recipe_key_for_route ) {
					wp_safe_redirect(
						add_query_arg( [ 'page' => 'cartcue-recipes' ], admin_url( 'admin.php' ) )
					);
					exit;
				}
			}
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- editor entry routing only.
			$automation_type = '';
			if ( 'new' === $action ) {
				// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- editor entry routing only.
				$automation_type = sanitize_key( wp_unslash( $_GET['automation_type'] ?? '' ) );
				if ( 'triggered' === $automation_type ) {
					$automation_type = 'event';
				}
				$valid_types     = [ 'event', 'campaign' ];
				if ( ! in_array( $automation_type, $valid_types, true ) ) {
					$automation_type = 'event';
				}
			}
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- editor entry routing only.
			$play_id  = isset( $_GET['id'] ) ? absint( wp_unslash( $_GET['id'] ) ) : 0;
			$play     = $play_id ? $this->plugin->runner->get_workflow( $play_id ) : null;
			if ( $play && ! empty( $play['automation_type'] ) ) {
				$automation_type = sanitize_key( $play['automation_type'] );
			} elseif ( ! in_array( $automation_type, [ 'event', 'campaign' ], true ) ) {
				$automation_type = 'event';
			}
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- editor entry routing only.
			$recipe_key = sanitize_key( wp_unslash( $_GET['recipe'] ?? '' ) );
			$recipe   = $recipe_key ? Recipes::hydrate_for_editor( $recipe_key ) : null;
			$view_map = [
				'event'    => 'play-edit-event.php',
				'campaign' => 'play-edit-campaign.php',
			];
			$view_file = $view_map[ $automation_type ] ?? 'play-edit-event.php';
			include __DIR__ . '/views/' . $view_file;
		} else {
			include __DIR__ . '/views/plays.php';
		}
	}

	public function render_reports_page(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- export gated below.
		if ( isset( $_GET['cartcue_export_csv'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['cartcue_export_csv'] ) ) ) {
			if ( ! current_user_can( 'manage_woocommerce' ) ) {
				wp_die( esc_html__( 'Permission denied.', 'cartcue-sales-automation' ) );
			}
			check_admin_referer( 'cartcue_export_logs_csv' );
			ReportsRepository::export_logs_csv();
		}

		$plugin = $this->plugin;
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- reports tab navigation.
		$active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'overview';
		if ( ! in_array( $active_tab, [ 'overview', 'logs', 'campaigns', 'unsubscribes', 'journey' ], true ) ) {
			$active_tab = 'overview';
		}
		include __DIR__ . '/views/reports.php';
	}

	public function render_recipes_page(): void {
		$plugin  = $this->plugin;
		$recipes = Recipes::all();
		include __DIR__ . '/views/recipes.php';
	}

	/**
	 * Saves a recipe as an active automation and redirects to the list (template modal).
	 */
	private function handle_autoactivate_recipe(): void {
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_die( esc_html__( 'Permission denied.', 'cartcue-sales-automation' ) );
		}

		if ( ! isset( $_GET['_wpnonce'] )
			|| ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), 'cartcue_autoactivate' )
		) {
			wp_die( esc_html__( 'Security check failed.', 'cartcue-sales-automation' ) );
		}

		$recipe_key = sanitize_key( wp_unslash( $_GET['recipe'] ?? '' ) );
		$recipe     = $recipe_key ? Recipes::hydrate_for_editor( $recipe_key ) : null;
		if ( ! $recipe ) {
			wp_safe_redirect( add_query_arg( [ 'page' => 'cartcue' ], admin_url( 'admin.php' ) ) );
			exit;
		}

		$trigger_slug = sanitize_key( (string) ( $recipe['trigger'] ?? $recipe['trigger_slug'] ?? '' ) );
		$validation   = \CartCue\PlayValidator::validate(
			[
				'name'         => (string) ( $recipe['name'] ?? '' ),
				'trigger_slug' => $trigger_slug,
				'actions'      => is_array( $recipe['actions'] ?? null ) ? $recipe['actions'] : [],
				'sequence'     => is_array( $recipe['sequence'] ?? null ) ? $recipe['sequence'] : [],
			]
		);
		if ( is_wp_error( $validation ) ) {
			wp_die(
				esc_html( $validation->get_error_message() ),
				esc_html__( 'Cannot activate', 'cartcue-sales-automation' ),
				[ 'response' => 400, 'back_link' => true ]
			);
		}

		global $wpdb;
		$payload = \CartCue\RecipeNormalizer::to_workflow_payload( $recipe, 'active' );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->insert( DB::workflows(), $payload );
		$new_id = (int) $wpdb->insert_id;

		if ( $new_id ) {
			$this->plugin->runner->bust_workflow_cache( $new_id );
			$this->plugin->runner->bust_active_workflows_cache( $trigger_slug );
			\CartCue\CampaignRepository::assign_play( $new_id, 0 );
		}

		wp_safe_redirect(
			add_query_arg(
				[
					'page'              => 'cartcue',
					'ap_activated'      => '1',
					'cartcue_highlight' => $new_id,
				],
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	public function render_settings_page(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- settings tab navigation.
		$active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general';
		$allowed    = [ 'general', 'sms', 'integrations', 'advanced' ];
		if ( ! in_array( $active_tab, $allowed, true ) ) {
			$active_tab = 'general';
		}
		include __DIR__ . '/views/settings.php';
	}

	public function render_campaign_page(): void {
		include __DIR__ . '/views/campaign.php';
	}

	public function render_queue_page(): void {
		include __DIR__ . '/views/queue.php';
	}

	public function render_integrations_page(): void {
		include __DIR__ . '/views/integrations.php';
	}

	// -------------------------------------------------------------------------
	// CRUD handlers
	// -------------------------------------------------------------------------

	public function handle_save_play(): void {
		check_admin_referer( 'cartcue_save_play' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'Permission denied.', 'cartcue-sales-automation' ) ); }

		$play_id = isset( $_POST['play_id'] ) ? absint( wp_unslash( $_POST['play_id'] ) ) : 0;

		$name         = sanitize_text_field( wp_unslash( $_POST['name'] ?? '' ) );
		$trigger_slug = sanitize_key( $_POST['trigger_slug'] ?? '' );
		$automation_type_raw = sanitize_key( wp_unslash( $_POST['automation_type'] ?? 'event' ) );
		if ( 'triggered' === $automation_type_raw ) {
			$automation_type_raw = 'event';
		}
		$automation_type     = in_array( $automation_type_raw, [ 'event', 'campaign' ], true )
			? $automation_type_raw
			: 'event';
		if ( 'campaign' === $automation_type && ! $this->plugin->is_pro() ) {
			$automation_type = 'event';
		}
		PlaySaveGuard::reject_ghost_trigger( $this->plugin, $trigger_slug );
		$status = PlaySaveGuard::sanitize_status( wp_unslash( $_POST['status'] ?? 'active' ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized via PlaySaveGuard::sanitize_status().
		PlaySaveGuard::redirect_if_active_limit( $this->plugin, $status, $play_id );

		$timing_type = sanitize_key( $_POST['timing_type'] ?? 'immediate' );
		$timing      = [ 'type' => $timing_type ];
		if ( 'delay' === $timing_type ) {
			$timing['delay_value'] = max( 1, absint( wp_unslash( $_POST['delay_value'] ?? 1 ) ) );
			$delay_unit_raw        = sanitize_key( wp_unslash( (string) ( $_POST['delay_unit'] ?? 'hours' ) ) );
			$timing['delay_unit']  = in_array( $delay_unit_raw, [ 'minutes', 'hours', 'days' ], true ) ? $delay_unit_raw : 'hours';
		} elseif ( 'schedule_time' === $timing_type ) {
			$timing['type'] = 'schedule_time';
			$raw            = sanitize_text_field( wp_unslash( $_POST['schedule_time'] ?? '09:00' ) );
			if ( preg_match( '/^(\d{1,2}):(\d{2})$/', $raw, $m ) ) {
				$timing['schedule_hour']   = min( 23, max( 0, (int) $m[1] ) );
				$timing['schedule_minute'] = min( 59, max( 0, (int) $m[2] ) );
			} else {
				$timing['schedule_hour']   = 9;
				$timing['schedule_minute'] = 0;
			}
		}

		$trigger_settings = [];
		foreach ( (array) wp_unslash( $_POST['trigger_settings'] ?? [] ) as $k => $v ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- keys and values sanitized in loop.
			$trigger_settings[ sanitize_key( $k ) ] = sanitize_text_field( wp_unslash( $v ) );
		}
		if ( 'inbound_webhook' === $trigger_slug && '' === (string) ( $trigger_settings['token'] ?? '' ) ) {
			$trigger_settings['token'] = wp_generate_password( 24, false, false );
		}

		$rules = [];
		foreach ( (array) wp_unslash( $_POST['rules'] ?? [] ) as $r ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- rule fields sanitized below.
			if ( empty( $r['slug'] ) ) { continue; }
			$rules[] = [ 'slug' => sanitize_key( $r['slug'] ), 'operator' => sanitize_key( wp_unslash( $r['operator'] ?? 'equals' ) ), 'value' => sanitize_text_field( wp_unslash( $r['value'] ?? '' ) ) ];
		}

		$actions = [];
		foreach ( (array) wp_unslash( $_POST['actions'] ?? [] ) as $a ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- action fields sanitized below.
			if ( empty( $a['type'] ) ) { continue; }
			$s            = [];
			$settings_raw = (array) ( $a['settings'] ?? [] );
			$body_mode    = isset( $settings_raw['body_mode'] )
				? sanitize_key( wp_unslash( $settings_raw['body_mode'] ) )
				: '';
			foreach ( $settings_raw as $sk => $sv ) {
				$ck = sanitize_key( $sk );
				if ( 'body' === $ck ) {
					$s[ $ck ] = \CartCue\EmailBlockRenderer::sanitize_body_setting(
						(string) wp_unslash( $sv ),
						$body_mode
					);
				} elseif ( in_array( $ck, [ 'message', 'note_content', 'extra_payload' ], true ) ) {
					$s[ $ck ] = wp_kses_post( wp_unslash( $sv ) );
				} elseif ( 'bcc' === $ck ) {
					$s[ $ck ] = sanitize_text_field( wp_unslash( $sv ) );
				} elseif ( 'content_format' === $ck ) {
					$cf       = sanitize_key( wp_unslash( $sv ) );
					$s[ $ck ] = in_array( $cf, [ 'html', 'plain' ], true ) ? $cf : 'html';
				} else {
					$s[ $ck ] = sanitize_text_field( wp_unslash( $sv ) );
				}
			}
			$condition = [];
			if ( isset( $a['condition'] ) && is_array( $a['condition'] ) ) {
				$condition_enabled = ! empty( $a['condition']['enabled'] );
				$condition_field   = sanitize_key( wp_unslash( (string) ( $a['condition']['field'] ?? '' ) ) );
				$condition_op      = sanitize_key( wp_unslash( (string) ( $a['condition']['operator'] ?? 'equals' ) ) );
				$condition_value   = sanitize_text_field( wp_unslash( (string) ( $a['condition']['value'] ?? '' ) ) );
				if ( $condition_enabled && '' !== $condition_field ) {
					$condition = [
						'enabled'  => true,
						'field'    => $condition_field,
						'operator' => $condition_op,
						'value'    => $condition_value,
					];
				}
			}
			$entry = [ 'type' => sanitize_key( $a['type'] ), 'settings' => $s ];
			if ( ! empty( $condition ) ) {
				$entry['condition'] = $condition;
			}
			$actions[] = $entry;
		}
		$actions = PlaySaveGuard::strip_ghost_actions( $this->plugin, $actions );

		$sequence = [];
		if ( ! empty( $_POST['sequence'] ) && is_array( $_POST['sequence'] ) ) {
			foreach ( wp_unslash( $_POST['sequence'] ) as $step ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				if ( ! is_array( $step ) ) {
					continue;
				}
				$step_type = sanitize_key( (string) ( $step['step_type'] ?? 'action' ) );
				if ( 'branch' === $step_type ) {
					if ( ! $this->plugin->can_use_branching() ) {
						continue;
					}
					$branch_rules = [];
					if ( isset( $step['rules'] ) && is_array( $step['rules'] ) ) {
						foreach ( $step['rules'] as $rule ) {
							if ( ! is_array( $rule ) || empty( $rule['slug'] ) ) {
								continue;
							}
							$branch_rules[] = [
								'slug'     => sanitize_key( (string) $rule['slug'] ),
								'operator' => sanitize_key( (string) ( $rule['operator'] ?? 'is' ) ),
								'value'    => sanitize_text_field( (string) ( $rule['value'] ?? '' ) ),
							];
						}
					}
					if ( empty( $branch_rules ) ) {
						continue;
					}
					$sequence[] = [
						'step_type'     => 'branch',
						'rules'         => $branch_rules,
						'next_if_true'  => max( 0, (int) ( $step['next_if_true'] ?? 0 ) ),
						'next_if_false' => max( 0, (int) ( $step['next_if_false'] ?? 0 ) ),
						'delay'         => max( 0, (int) ( $step['delay'] ?? 0 ) ),
						'delay_unit'    => in_array( $step['delay_unit'] ?? '', [ 'minutes', 'hours', 'days' ], true ) ? $step['delay_unit'] : 'hours',
					];
					continue;
				}
				if ( ! isset( $step['action_index'] ) || '' === $step['action_index'] ) {
					continue;
				}
				$sequence[] = [
					'step_type'    => 'action',
					'action_index' => (int) $step['action_index'],
					'delay'        => max( 0, (int) ( $step['delay'] ?? 1 ) ),
					'delay_unit'   => in_array( $step['delay_unit'] ?? '', [ 'minutes', 'hours', 'days' ], true )
						? $step['delay_unit']
						: 'hours',
				];
			}
		}

		$max_runs    = max( 0, absint( wp_unslash( $_POST['max_runs_per_customer'] ?? 0 ) ) );
		$cooldown    = max( 0, absint( wp_unslash( $_POST['cooldown_days'] ?? 0 ) ) );

		$max_seq = $this->plugin->max_sequence_steps();
		if ( $max_seq > 0 && count( $sequence ) > $max_seq ) {
			$sequence = array_slice( $sequence, 0, $max_seq );
		}

		$validation = PlayValidator::validate(
			[
				'name'         => $name,
				'trigger_slug' => $trigger_slug,
				'actions'      => $actions,
				'sequence'     => $sequence,
			]
		);
		if ( is_wp_error( $validation ) ) {
			$redirect_args = [
				'page'       => 'cartcue',
				'action'     => $play_id ? 'edit' : 'new',
				'save_error' => rawurlencode( $validation->get_error_message() ),
			];
			if ( $play_id ) {
				$redirect_args['id'] = $play_id;
			} else {
				$redirect_args['automation_type'] = $automation_type;
			}
			wp_safe_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) );
			exit;
		}

		global $wpdb;
		$now = current_time( 'mysql' );
		$campaign_id = \CartCue\CampaignRepository::resolve_id( max( 0, absint( wp_unslash( $_POST['campaign_id'] ?? 0 ) ) ) );
		$payload = [
			'name'                 => $name,
			'trigger_slug'         => $trigger_slug,
			'automation_type'      => $automation_type,
			'trigger_settings'     => wp_json_encode( $trigger_settings ),
			'rules'                => wp_json_encode( $rules ),
			'actions'              => wp_json_encode( $actions ),
			'sequence'             => wp_json_encode( $sequence ),
			'timing'               => wp_json_encode( $timing ),
			'status'               => $status,
			'max_runs_per_customer' => $max_runs,
			'cooldown_days'        => $cooldown,
			'send_to_past_matches' => ! empty( $_POST['send_to_past_matches'] ) ? 1 : 0,
			'campaign_id'          => $campaign_id,
			'updated_at'           => $now,
		];

		$formats = [
			'%s', // name.
			'%s', // trigger_slug.
			'%s', // automation_type.
			'%s', // trigger_settings.
			'%s', // rules.
			'%s', // actions.
			'%s', // sequence.
			'%s', // timing.
			'%s', // status.
			'%d', // max_runs_per_customer.
			'%d', // cooldown_days.
			'%d', // send_to_past_matches.
			'%d', // campaign_id.
			'%s', // updated_at.
		];

		$old_trigger_slug = '';
		if ( $play_id ) {
			$existing = $this->plugin->runner->get_workflow( $play_id );
			$old_trigger_slug = (string) ( $existing['trigger_slug'] ?? '' );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->update( DB::workflows(), $payload, [ 'id' => $play_id ], $formats, [ '%d' ] );
			$this->plugin->runner->bust_workflow_cache( $play_id );
		} else {
			$payload['created_at'] = $now;
			$payload['run_count']  = 0;
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->insert( DB::workflows(), $payload );
			$play_id = (int) $wpdb->insert_id;
		}
		\CartCue\CampaignRepository::assign_play( (int) $play_id, $campaign_id );

		$this->plugin->runner->bust_active_workflows_cache( $trigger_slug );
		if ( $old_trigger_slug && $old_trigger_slug !== $trigger_slug ) {
			$this->plugin->runner->bust_active_workflows_cache( $old_trigger_slug );
		}
		Plugin::bust_play_count_cache();

		$redirect_args = [ 'page' => 'cartcue' ];
		if ( 'active' === $status ) {
			$redirect_args['ap_activated']        = '1';
			$redirect_args['cartcue_highlight'] = $play_id;
		} else {
			$redirect_args['saved'] = '1';
		}
		wp_safe_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) );
		exit;
	}

	/**
	 * Whether the single-page unified editor is active (event automations).
	 */
	private function uses_unified_play_editor(): bool {
		return 'unified' === apply_filters( 'cartcue_play_editor_view', 'unified' );
	}

	/**
	 * Unified editor applies to the current admin request (not campaign blast editor).
	 */
	private function uses_unified_play_editor_for_request(): bool {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$type_query = sanitize_key( wp_unslash( $_GET['automation_type'] ?? '' ) );
		if ( 'campaign' === $type_query ) {
			return false;
		}
		return $this->uses_unified_play_editor();
	}

	public function handle_delete_play(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- checked via check_admin_referer.
		$id = absint( $_POST['id'] ?? $_GET['id'] ?? 0 );
		check_admin_referer( 'cartcue_delete_' . $id );
		if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die(); }
		global $wpdb;
		$existing = $this->plugin->runner->get_workflow( $id );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->delete( DB::workflows(), [ 'id' => $id ], [ '%d' ] );
		$this->plugin->runner->bust_workflow_cache( $id );
		if ( $existing ) {
			$this->plugin->runner->bust_active_workflows_cache( (string) ( $existing['trigger_slug'] ?? '' ) );
		}
		Plugin::bust_play_count_cache();
		wp_safe_redirect( add_query_arg( [ 'page' => 'cartcue', 'deleted' => '1' ], admin_url( 'admin.php' ) ) );
		exit;
	}

	public function handle_duplicate_play(): void {
		$id = isset( $_GET['id'] ) ? absint( wp_unslash( $_GET['id'] ) ) : 0;
		check_admin_referer( 'cartcue_duplicate_' . $id );
		if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die(); }

		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$orig = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', DB::workflows(), $id ), ARRAY_A );
		if ( $orig ) {
			unset( $orig['id'] );
			$orig['name']       = $orig['name'] . ' ' . __( '(Copy)', 'cartcue-sales-automation' );
			$orig['status']     = 'paused';
			$orig['run_count']  = 0;
			$orig['created_at'] = current_time( 'mysql' );
			$orig['updated_at'] = current_time( 'mysql' );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->insert( DB::workflows(), $orig );
		}

		wp_safe_redirect( add_query_arg( [ 'page' => 'cartcue', 'duplicated' => '1' ], admin_url( 'admin.php' ) ) );
		exit;
	}

	public function handle_toggle_play(): void {
		$id = isset( $_GET['id'] ) ? absint( wp_unslash( $_GET['id'] ) ) : 0;
		check_admin_referer( 'cartcue_toggle_' . $id );
		if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die(); }
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$workflow = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', DB::workflows(), $id ), ARRAY_A );
		if ( ! $workflow ) { wp_die(); }
		$cur = (string) ( $workflow['status'] ?? 'paused' );
		$new = ( 'active' === $cur ) ? 'paused' : 'active';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->update( DB::workflows(), [ 'status' => $new, 'updated_at' => current_time( 'mysql' ) ], [ 'id' => $id ], [ '%s', '%s' ], [ '%d' ] );
		$this->plugin->runner->bust_workflow_cache( $id );
		$this->plugin->runner->bust_active_workflows_cache( (string) ( $workflow['trigger_slug'] ?? '' ) );
		if ( 'active' === $new && ! empty( $workflow['send_to_past_matches'] ) && function_exists( 'as_schedule_single_action' ) ) {
			as_schedule_single_action(
				time() + 5,
				'cartcue_backfill_past_matches',
				[ $id, (string) ( $workflow['trigger_slug'] ?? '' ) ],
				'cartcue'
			);
		}
		Plugin::bust_play_count_cache();
		wp_safe_redirect( add_query_arg( [ 'page' => 'cartcue', 'toggled' => '1' ], admin_url( 'admin.php' ) ) );
		exit;
	}

	/**
	 * Activates a paused automation from the list (one-click).
	 */
	public function handle_activate_play(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$play_id = absint( $_GET['play_id'] ?? 0 );
		check_admin_referer( 'cartcue_activate_play_' . $play_id );

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_die( esc_html__( 'Permission denied.', 'cartcue-sales-automation' ) );
		}

		if ( $play_id && $this->plugin->can_activate_more_plays( $play_id ) ) {
			global $wpdb;
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$workflow = $wpdb->get_row( $wpdb->prepare( 'SELECT trigger_slug FROM %i WHERE id = %d', DB::workflows(), $play_id ), ARRAY_A );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->update(
				DB::workflows(),
				[ 'status' => 'active', 'updated_at' => current_time( 'mysql' ) ],
				[ 'id' => $play_id ],
				[ '%s', '%s' ],
				[ '%d' ]
			);
			$this->plugin->runner->bust_workflow_cache( $play_id );
			$this->plugin->runner->bust_active_workflows_cache( (string) ( $workflow['trigger_slug'] ?? '' ) );
			Plugin::bust_play_count_cache();
			$redirect = [
				'page'              => 'cartcue',
				'ap_activated'      => '1',
				'cartcue_highlight' => $play_id,
			];
		} else {
			$redirect = [
				'page'              => 'cartcue',
				'ap_limit'          => '1',
				'cartcue_highlight' => $play_id,
			];
		}

		wp_safe_redirect( add_query_arg( $redirect, admin_url( 'admin.php' ) ) );
		exit;
	}

	public function handle_save_campaign(): void {
		check_admin_referer( 'cartcue_save_campaign' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die(); }

		$id   = isset( $_POST['campaign_id'] ) ? absint( wp_unslash( $_POST['campaign_id'] ) ) : 0;
		$name = sanitize_text_field( wp_unslash( $_POST['campaign_name'] ?? '' ) );
		$desc = sanitize_textarea_field( wp_unslash( $_POST['campaign_description'] ?? '' ) );
		if ( '' === $name ) {
			wp_safe_redirect( add_query_arg( [ 'page' => 'cartcue-reports', 'tab' => 'campaigns' ], admin_url( 'admin.php' ) ) );
			exit;
		}

		if ( $id > 0 ) {
			\CartCue\CampaignRepository::update( $id, $name, $desc );
		} else {
			$id = \CartCue\CampaignRepository::create( $name, $desc );
		}

		wp_safe_redirect(
			add_query_arg(
				[
					'page'           => 'cartcue-reports',
					'tab'            => 'campaigns',
					'saved_campaign' => $id,
				],
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	public function handle_delete_campaign(): void {
		$id = isset( $_GET['campaign_id'] ) ? absint( wp_unslash( $_GET['campaign_id'] ) ) : 0;
		check_admin_referer( 'cartcue_delete_campaign_' . $id );
		if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die(); }
		if ( $id > 0 ) {
			\CartCue\CampaignRepository::delete( $id );
		}

		wp_safe_redirect(
			add_query_arg(
				[
					'page'             => 'cartcue-reports',
					'tab'              => 'campaigns',
					'deleted_campaign' => '1',
				],
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	public function handle_save_settings(): void {
		check_admin_referer( 'cartcue_save_settings' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die(); }

		$tab = isset( $_POST['settings_tab'] ) ? sanitize_key( wp_unslash( $_POST['settings_tab'] ) ) : 'general';
		$tab_fields = [
			'general'      => [ 'from_name', 'from_email' ],
			'sms'          => [ 'twilio_account_sid', 'twilio_auth_token', 'twilio_from_number', 'vonage_api_key', 'vonage_api_secret', 'vonage_from' ],
			'integrations' => [ 'mailchimp_api_key', 'activecampaign_api_url', 'activecampaign_api_key', 'klaviyo_private_key' ],
			'advanced'     => [ 'weekly_digest_email' ],
		];
		$fields = $tab_fields[ $tab ] ?? $tab_fields['general'];

		$s = array_merge(
			\CartCue\MarketingConsent::default_settings(),
			\CartCue\SmsConsent::default_settings(),
			\CartCue\EnvironmentGuard::default_settings(),
			get_option( 'cartcue_settings', [] )
		);
		foreach ( $fields as $k ) {
			$s[ $k ] = sanitize_text_field( wp_unslash( $_POST[ $k ] ?? $s[ $k ] ?? '' ) );
		}

		if ( 'advanced' === $tab ) {
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$s['email_template_html']  = wp_kses_post( wp_unslash( $_POST['email_template_html'] ?? $s['email_template_html'] ?? '' ) );
			$s['weekly_digest_enabled'] = ! empty( $_POST['weekly_digest_enabled'] ) ? 'yes' : 'no';
		}

		if ( 'general' === $tab ) {
			$s['consent_enabled']          = ! empty( $_POST['consent_enabled'] ) ? '1' : '0';
			$s['consent_enforce']          = ! empty( $_POST['consent_enforce'] ) ? '1' : '0';
			$s['consent_checkout']         = ! empty( $_POST['consent_checkout'] ) ? '1' : '0';
			$s['consent_checkout_blocks']  = ! empty( $_POST['consent_checkout_blocks'] ) ? '1' : '0';
			$s['consent_wp_register']      = ! empty( $_POST['consent_wp_register'] ) ? '1' : '0';
			$s['consent_wc_register']      = ! empty( $_POST['consent_wc_register'] ) ? '1' : '0';
			$s['consent_text']             = sanitize_text_field( wp_unslash( $_POST['consent_text'] ?? $s['consent_text'] ) );
			$s['global_email_cap_enabled'] = ! empty( $_POST['global_email_cap_enabled'] ) ? '1' : '0';
			$s['global_email_cap_count']   = max( 1, absint( wp_unslash( $_POST['global_email_cap_count'] ?? $s['global_email_cap_count'] ?? 3 ) ) );
			$s['global_email_cap_days']    = max( 1, absint( wp_unslash( $_POST['global_email_cap_days'] ?? $s['global_email_cap_days'] ?? 7 ) ) );
			$s['quiet_hours_enabled']      = ! empty( $_POST['quiet_hours_enabled'] ) ? '1' : '0';
			$s['quiet_hours_start']        = sanitize_text_field( wp_unslash( $_POST['quiet_hours_start'] ?? $s['quiet_hours_start'] ?? '21:00' ) );
			$s['quiet_hours_end']          = sanitize_text_field( wp_unslash( $_POST['quiet_hours_end'] ?? $s['quiet_hours_end'] ?? '08:00' ) );
			$s['block_non_production_sends']       = ! empty( $_POST['block_non_production_sends'] ) ? '1' : '0';
			$s['allow_outbound_on_non_production'] = ! empty( $_POST['allow_outbound_on_non_production'] ) ? '1' : '0';
			$s['weekly_digest_email']      = sanitize_email( wp_unslash( $_POST['weekly_digest_email'] ?? $s['weekly_digest_email'] ?? '' ) );
			$s['weekly_digest_enabled']    = ! empty( $_POST['weekly_digest_enabled'] ) ? 'yes' : 'no';
			$s['sms_consent_enabled']          = ! empty( $_POST['sms_consent_enabled'] ) ? '1' : '0';
			$s['sms_consent_enforce']          = ! empty( $_POST['sms_consent_enforce'] ) ? '1' : '0';
			$s['sms_consent_checkout']         = ! empty( $_POST['sms_consent_checkout'] ) ? '1' : '0';
			$s['sms_consent_checkout_blocks']  = ! empty( $_POST['sms_consent_checkout_blocks'] ) ? '1' : '0';
			$s['sms_consent_text']             = sanitize_text_field( wp_unslash( $_POST['sms_consent_text'] ?? $s['sms_consent_text'] ) );
		}

		update_option( 'cartcue_settings', $s, false );
		wp_safe_redirect( add_query_arg( [ 'page' => 'cartcue-settings', 'tab' => $tab, 'saved' => '1' ], admin_url( 'admin.php' ) ) );
		exit;
	}

	public function handle_import_guests(): void {
		check_admin_referer( 'cartcue_import_guests', 'cartcue_import_nonce' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_die( esc_html__( 'Permission denied.', 'cartcue-sales-automation' ) );
		}
		$days_back = isset( $_POST['guest_import_days'] )
			? max( 0, absint( wp_unslash( $_POST['guest_import_days'] ) ) )
			: 0;
		$stats = \CartCue\GuestHistoricalImport::run( $days_back );
		wp_safe_redirect(
			add_query_arg(
				[
					'page'              => 'cartcue-settings',
					'guest_import_done' => '1',
					'imported'          => (int) ( $stats['imported'] ?? 0 ),
					'scanned'           => (int) ( $stats['orders_scanned'] ?? 0 ),
				],
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	public function handle_preview_email(): void {
		check_ajax_referer( 'cartcue_preview_email', 'nonce' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die(); }

		$subject      = sanitize_text_field( wp_unslash( $_POST['subject'] ?? 'Preview' ) );
		$body_raw     = wp_unslash( $_POST['body'] ?? '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below.
		$body         = \CartCue\EmailBlockRenderer::is_builder_content( $body_raw )
			? \CartCue\EmailBlockRenderer::to_html( $body_raw )
			: wp_kses_post( $body_raw );
		$inline       = ! empty( $_POST['inline'] );
		$trigger_slug = sanitize_key( wp_unslash( $_POST['trigger_slug'] ?? 'order_paid' ) );
		$mailer       = $this->plugin->mailer;

		$trigger = $this->plugin->get_trigger( $trigger_slug );
		$data    = $trigger ? $trigger->get_test_data() : [
			'customer_email'      => get_option( 'admin_email' ),
			'customer_first_name' => 'Alex',
			'customer_last_name'  => 'Sample',
		];
		$data['customer_email'] = get_option( 'admin_email' );

		$subject = $this->plugin->variables->process( $subject, $data );
		$body    = $this->plugin->variables->process( $body, $data );
		$html    = $mailer->wrap_for_preview( $body );

		if ( $inline ) {
			wp_send_json_success( [
				'subject' => $subject,
				'html'    => $html,
			] );
		}

		$to     = get_option( 'admin_email' );
		$result = $mailer->send( $to, '[PREVIEW] ' . $subject, $body );
		if ( $result ) {
			wp_send_json_success(
				sprintf(
					/* translators: %s: recipient email address */
					__( 'Preview sent to %s', 'cartcue-sales-automation' ),
					esc_html( $to )
				)
			);
		}
		wp_send_json_error( __( 'Failed to send. Check your WordPress mail settings.', 'cartcue-sales-automation' ) );
	}

	public function handle_builder_preview(): void {
		check_ajax_referer( 'cartcue_builder_preview', 'nonce' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( null, 403 );
		}

		$blocks_raw   = wp_unslash( $_POST['blocks'] ?? '[]' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON decoded and rendered.
		$styles_raw   = wp_unslash( $_POST['styles'] ?? '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON decoded and sanitized.
		$trigger_slug = sanitize_key( wp_unslash( $_POST['trigger_slug'] ?? 'order_paid' ) );

		$payload = $blocks_raw;
		if ( is_string( $styles_raw ) && '' !== trim( $styles_raw ) ) {
			$styles_decoded = json_decode( $styles_raw, true );
			$blocks_decoded = json_decode( $blocks_raw, true );
			if ( is_array( $styles_decoded ) && is_array( $blocks_decoded ) ) {
				$payload = wp_json_encode(
					[
						'v'      => 1,
						'blocks' => $blocks_decoded,
						'styles' => $styles_decoded,
					]
				);
			}
		}

		$inner = \CartCue\EmailBlockRenderer::to_html( $payload );

		$trigger = $this->plugin->get_trigger( $trigger_slug );
		$data    = $trigger ? $trigger->get_test_data() : [];
		$inner   = $this->plugin->variables->process( $inner, $data );

		$doc     = \CartCue\EmailBlockRenderer::parse_document( $payload );
		$styles  = $doc['styles'];
		$accent  = isset( $styles['branding_color'] ) ? sanitize_hex_color( (string) $styles['branding_color'] ) : '';
		$filter  = static function () use ( $accent ) {
			return $accent ?: '#534AB7';
		};
		add_filter( 'cartcue_email_accent_color', $filter, 99 );
		$full_html = $this->plugin->mailer->wrap_for_preview( $inner );
		remove_filter( 'cartcue_email_accent_color', $filter, 99 );
		wp_send_json_success( [ 'html' => $full_html ] );
	}

	public function handle_get_preset_blocks(): void {
		check_ajax_referer( 'cartcue_builder_preview', 'nonce' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( null, 403 );
		}

		$preset  = sanitize_key( wp_unslash( $_POST['preset'] ?? '' ) );
		$blocks  = \CartCue\EmailTemplates::preset_blocks( $preset );
		$presets = \CartCue\EmailTemplates::presets();
		$subject = isset( $presets[ $preset ]['subject'] ) ? (string) $presets[ $preset ]['subject'] : '';
		wp_send_json_success(
			[
				'blocks'  => $blocks,
				'subject' => $subject,
			]
		);
	}

	public function handle_preview_audience(): void {
		check_ajax_referer( 'cartcue_preview_audience', 'nonce' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( __( 'Permission denied.', 'cartcue-sales-automation' ), 403 );
		}

		$raw_rules = isset( $_POST['rules'] ) ? wp_unslash( $_POST['rules'] ) : '[]'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON decoded below.
		$rules     = json_decode( $raw_rules, true );
		if ( ! is_array( $rules ) ) {
			$rules = [];
		}

		$trigger_slug = sanitize_key( wp_unslash( $_POST['trigger_slug'] ?? '' ) );
		$counter      = new \CartCue\AudiencePreview( $this->plugin );
		$result       = $counter->count( $trigger_slug, $rules );

		wp_send_json_success( $result );
	}

	public function handle_customer_journey(): void {
		check_ajax_referer( 'cartcue_customer_journey', 'nonce' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( __( 'Permission denied.', 'cartcue-sales-automation' ), 403 );
		}
		$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) );
		if ( ! is_email( $email ) ) {
			wp_send_json_error( __( 'Enter a valid email address.', 'cartcue-sales-automation' ) );
		}
		wp_send_json_success( \CartCue\CustomerJourney::get_timeline( $email ) );
	}

	public function handle_search_picker(): void {
		check_ajax_referer( 'cartcue_search_picker', 'nonce' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( __( 'Permission denied.', 'cartcue-sales-automation' ), 403 );
		}

		$source = sanitize_key( wp_unslash( $_POST['source'] ?? '' ) );
		$term   = sanitize_text_field( wp_unslash( $_POST['term'] ?? '' ) );
		$limit  = min( 25, max( 5, absint( wp_unslash( $_POST['limit'] ?? 15 ) ) ) );
		$selected_raw = sanitize_text_field( wp_unslash( $_POST['selected'] ?? '' ) );
		$selected     = array_values( array_filter( array_map( 'trim', explode( ',', $selected_raw ) ) ) );
		$items  = [];

		switch ( $source ) {
			case 'products':
				$products = get_posts(
					[
						'post_type'      => [ 'product', 'product_variation' ],
						'post_status'    => 'publish',
						'posts_per_page' => $limit,
						's'              => $term,
						'orderby'        => 'title',
						'order'          => 'ASC',
					]
				);
				if ( ! empty( $selected ) ) {
					$selected_ids = array_map( 'absint', $selected );
					$selected_ids = array_values( array_filter( $selected_ids ) );
					if ( ! empty( $selected_ids ) ) {
						$selected_products = get_posts(
							[
								'post_type'      => [ 'product', 'product_variation' ],
								'post_status'    => 'publish',
								'post__in'       => $selected_ids,
								'posts_per_page' => count( $selected_ids ),
								'orderby'        => 'post__in',
							]
						);
						$products = array_merge( $selected_products, $products );
					}
				}
				foreach ( $products as $product ) {
					$items[] = [
						'value' => (string) $product->ID,
						'label' => sprintf(
							/* translators: 1: product title, 2: product id */
							__( '%1$s (#%2$d)', 'cartcue-sales-automation' ),
							$product->post_title,
							(int) $product->ID
						),
					];
				}
				break;

			case 'customers':
				$users = get_users(
					[
						'number'         => $limit,
						'search'         => '*' . $term . '*',
						'search_columns' => [ 'user_email', 'display_name', 'user_login' ],
						'orderby'        => 'display_name',
						'order'          => 'ASC',
					]
				);
				foreach ( $users as $user ) {
					$items[] = [
						'value' => (string) $user->user_email,
						'label' => sprintf(
							/* translators: 1: user email, 2: user display name */
							__( '%1$s (%2$s)', 'cartcue-sales-automation' ),
							$user->user_email,
							$user->display_name
						),
					];
				}
				break;

			case 'coupons':
				$coupons = get_posts(
					[
						'post_type'      => 'shop_coupon',
						'post_status'    => [ 'publish', 'private' ],
						'posts_per_page' => $limit,
						's'              => $term,
						'orderby'        => 'title',
						'order'          => 'ASC',
					]
				);
				if ( ! empty( $selected ) ) {
					$selected_coupons = get_posts(
						[
							'post_type'      => 'shop_coupon',
							'post_status'    => [ 'publish', 'private' ],
							'posts_per_page' => count( $selected ),
							'post_name__in'  => array_map( 'sanitize_title', $selected ),
						]
					);
					$coupons = array_merge( $selected_coupons, $coupons );
				}
				foreach ( $coupons as $coupon ) {
					$code = wc_format_coupon_code( $coupon->post_title );
					$items[] = [
						'value' => $code,
						'label' => $code,
					];
				}
				if ( ! empty( $selected ) ) {
					foreach ( $selected as $code ) {
						$code = wc_format_coupon_code( $code );
						$items[] = [
							'value' => $code,
							'label' => $code,
						];
					}
				}
				break;

			case 'tags':
				$tag_names = [];
				if ( taxonomy_exists( 'customer_tag' ) ) {
					$terms = get_terms(
						[
							'taxonomy'   => 'customer_tag',
							'hide_empty' => false,
							'number'     => $limit,
							'name__like' => $term,
						]
					);
					if ( ! is_wp_error( $terms ) ) {
						foreach ( $terms as $term_obj ) {
							$tag_names[] = sanitize_text_field( $term_obj->name );
						}
					}
				}
				if ( empty( $tag_names ) && '' !== $term ) {
					$tag_names[] = $term;
				}
				$tag_names = array_values( array_unique( array_filter( $tag_names ) ) );
				foreach ( $tag_names as $tag_name ) {
					$items[] = [
						'value' => $tag_name,
						'label' => $tag_name,
					];
				}
				break;
		}

		wp_send_json_success(
			[
				'items' => array_values(
					array_reduce(
						$items,
						static function ( $carry, $row ) {
							$key = (string) ( $row['value'] ?? '' );
							if ( '' !== $key ) {
								$carry[ $key ] = $row;
							}
							return $carry;
						},
						[]
					)
				),
			]
		);
	}

	public function handle_dismiss_quickstart(): void {
		check_admin_referer( 'cartcue_dismiss_quickstart' );
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_die();
		}
		\CartCue\QuickStart::dismiss();
		wp_safe_redirect( add_query_arg( [ 'page' => 'cartcue' ], admin_url( 'admin.php' ) ) );
		exit;
	}

	public function show_license_notice(): void {
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return;
		}
		if ( $this->plugin->is_pro() ) {
			return;
		}
		if ( function_exists( 'fs_cartcue_is_configured' ) && ! fs_cartcue_is_configured() ) {
			return;
		}
		$screen = get_current_screen();
		if ( ! $screen || false === strpos( $screen->id, 'cartcue' ) ) {
			return;
		}
		echo '<div class="notice notice-warning"><p><strong>'
			. esc_html__( 'CartCue Pro: activate your license to unlock Pro features.', 'cartcue-sales-automation' )
			. '</strong> '
			. esc_html__( 'Campaigns, inbound webhooks, and unlimited active plays require an active subscription.', 'cartcue-sales-automation' )
			. '</p></div>';
	}

	public function show_notices(): void {
		$screen = get_current_screen();
		if ( ! $screen || false === strpos( $screen->id, 'cartcue' ) ) { return; }

		if ( \CartCue\MailDelivery::should_show_failure_notice() ) {
			$status = \CartCue\MailDelivery::get_status();
			echo '<div class="notice notice-warning is-dismissible"><p><strong>'
				. esc_html__( 'CartCue: email delivery may not be working.', 'cartcue-sales-automation' )
				. '</strong> '
				. esc_html( $status['message'] )
				. ' <a href="' . esc_url( admin_url( 'admin.php?page=cartcue-settings' ) ) . '">'
				. esc_html__( 'Run a delivery test in Settings →', 'cartcue-sales-automation' )
				. '</a></p></div>';
		}

		$failed_runs_last_day = $this->plugin->logger->count_recent_failures( 1440 );
		if ( $failed_runs_last_day > 0 ) {
			echo '<div class="notice notice-warning is-dismissible"><p><strong>'
				. esc_html__( 'CartCue: some play runs failed recently.', 'cartcue-sales-automation' )
				. '</strong> '
				. esc_html(
					sprintf(
						/* translators: %d: number of failed runs */
						_n( '%d failed run in the last 24 hours.', '%d failed runs in the last 24 hours.', $failed_runs_last_day, 'cartcue-sales-automation' ),
						$failed_runs_last_day
					)
				)
				. ' <a href="' . esc_url( admin_url( 'admin.php?page=cartcue-reports&tab=logs&status=error' ) ) . '">'
				. esc_html__( 'Review error logs →', 'cartcue-sales-automation' )
				. '</a></p></div>';
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( isset( $_GET['guest_import_done'] ) && '' !== sanitize_text_field( wp_unslash( $_GET['guest_import_done'] ) ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice rendering.
			$imported = isset( $_GET['imported'] ) ? absint( wp_unslash( $_GET['imported'] ) ) : 0;
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice rendering.
			$scanned  = isset( $_GET['scanned'] ) ? absint( wp_unslash( $_GET['scanned'] ) ) : 0;
			$stats    = \CartCue\GuestHistoricalImport::last_stats();
			$class    = $imported > 0 ? 'notice-success' : 'notice-warning';

			echo '<div class="notice ' . esc_attr( $class ) . ' is-dismissible"><p>';
			echo esc_html(
				sprintf(
					/* translators: 1: imported count 2: orders scanned */
					__( 'Guest import finished: %1$d new email(s) added from %2$d orders scanned.', 'cartcue-sales-automation' ),
					$imported,
					$scanned
				)
			);
			if ( ! empty( $stats['message'] ) ) {
				echo ' ';
				echo esc_html( (string) $stats['message'] );
			}
			echo '</p></div>';
		}

		$msgs = [
			'saved'      => __( 'Automation saved.', 'cartcue-sales-automation' ),
			'deleted'    => __( 'Automation deleted.', 'cartcue-sales-automation' ),
			'duplicated' => __( 'Automation duplicated — paused so you can review before switching on.', 'cartcue-sales-automation' ),
			'toggled'    => __( 'Automation status updated.', 'cartcue-sales-automation' ),
		];
		foreach ( $msgs as $p => $t ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice rendering.
			if ( ! empty( $_GET[ $p ] ) ) {
				echo '<div class="notice notice-success is-dismissible"><p>' . esc_html( $t ) . '</p></div>';
			}
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice rendering.
		if ( isset( $_GET['save_error'] ) && '' !== sanitize_text_field( wp_unslash( $_GET['save_error'] ) ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- read-only notice rendering.
			$message = sanitize_text_field( rawurldecode( wp_unslash( $_GET['save_error'] ) ) );
			echo '<div class="notice notice-error is-dismissible"><p>' . esc_html( $message ) . '</p></div>';
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice rendering.
		if ( isset( $_GET['ap_err'] ) && 'upload' === sanitize_key( wp_unslash( $_GET['ap_err'] ) ) ) {
			echo '<div class="notice notice-error is-dismissible"><p>'
				. esc_html__( 'Import failed. Please upload a valid JSON file exported from CartCue.', 'cartcue-sales-automation' )
				. '</p></div>';
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice rendering.
		if ( isset( $_GET['ap_err'] ) && 'invalid' === sanitize_key( wp_unslash( $_GET['ap_err'] ) ) ) {
			echo '<div class="notice notice-error is-dismissible"><p>'
				. esc_html__( 'Import failed. The uploaded JSON format is not valid for CartCue.', 'cartcue-sales-automation' )
				. '</p></div>';
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice rendering.
		if ( isset( $_GET['ap_limit'] ) && '1' === sanitize_key( wp_unslash( $_GET['ap_limit'] ) ) ) {
			$max_active = $this->plugin->max_active_plays();
			echo '<div class="notice notice-warning is-dismissible"><p><strong>'
				. esc_html__( 'Could not activate — active automation limit reached.', 'cartcue-sales-automation' )
				. '</strong> ';
			if ( $max_active > 0 ) {
				echo esc_html(
					sprintf(
						/* translators: %d: maximum active automations on the free plan */
						__( 'You\'re at %d active automations on the free plan. Pause one or upgrade to activate more.', 'cartcue-sales-automation' ),
						$max_active
					)
				);
			} else {
				echo esc_html__( 'Pause another automation or upgrade to activate more.', 'cartcue-sales-automation' );
			}
			echo ' <a href="' . esc_url( Plugin::upgrade_url( 'activate_play_limit' ) ) . '" target="_blank" rel="noopener noreferrer">'
				. esc_html__( 'Upgrade to Pro →', 'cartcue-sales-automation' )
				. '</a></p></div>';
		}
	}

	// -------------------------------------------------------------------------
	// Config arrays for JS localization
	// -------------------------------------------------------------------------

	private function triggers_config(): array {
		$c = [];
		foreach ( $this->plugin->get_triggers() as $slug => $t ) {
			$entry = [
				'label'            => $t->get_label(),
				'sentence'         => $t->get_sentence(),
				'description'      => $t->get_description(),
				'fields'           => $t->get_settings_fields(),
				'supports_recheck' => $t->supports_recheck(),
				'is_pro'           => $t instanceof \CartCue\Triggers\GhostTrigger,
				'is_ghost'         => $t instanceof \CartCue\Triggers\GhostTrigger,
				'upgrade_html'     => '',
			];
			if ( $t instanceof \CartCue\Triggers\GhostTrigger ) {
				$entry['upgrade_html'] = $t->get_upgrade_html();
			}
			$c[ $slug ] = $entry;
		}
		return $c;
	}

	private function actions_config(): array {
		$c = [];
		foreach ( $this->plugin->get_actions() as $slug => $a ) {
			$is_ghost = $a instanceof \CartCue\Actions\GhostAction;
			$entry    = [
				'label'    => $a->get_label(),
				'fields'   => $a->get_settings_fields(),
				'is_pro'   => $is_ghost,
				'is_ghost' => $is_ghost,
			];
			if ( $is_ghost ) {
				$entry['icon']         = $a->get_icon();
				$entry['upgrade_html'] = $a->get_upgrade_html();
			}
			if ( method_exists( $a, 'get_pro_feature_labels' ) ) {
				$entry['pro_feature_labels'] = $a->get_pro_feature_labels();
			}
			$c[ $slug ] = $entry;
		}
		return $c;
	}

	private function rules_config(): array {
		$c = [];
		foreach ( $this->plugin->get_rules() as $slug => $r ) {
			if ( 'order_currency' === $slug && ! \CartCue\StoreMultiCurrency::is_enabled() ) {
				continue;
			}
			$c[ $slug ] = [
				'label'         => $r->get_label(),
				'operators'     => $r->get_operators(),
				'value_type'    => $r->get_value_type(),
				'value_options' => $r->get_value_options(),
				'value_source'  => $r->get_value_source(),
				'value_multiple' => $r->is_value_multiple(),
				'placeholder'   => $r->get_value_placeholder(),
			];
		}
		return $c;
	}
}
