<?php
/**
 * Cart Recovery — Admin Settings UI
 *
 * Renders the `Niwa AI → Cart Recovery` submenu. Uses the plugin-wide
 * `niwa-wrap` / `niwa-page-header` / `niwa-card` / `niwa-field` markup
 * so the styling matches Leave Stoper, Conversion Busters, Analytics,
 * etc — no standalone look.
 *
 * Settings persist through `Niwa_AI_Cart_Recovery_Settings::sanitize()`
 * (single source of truth registered via `register_setting`).
 *
 * AJAX endpoints:
 *   niwa_ai_cr_preview            Compose preview via Mailer::preview()
 *   niwa_ai_cr_test_send          wp_mail a preview to the current admin
 *   niwa_ai_cr_run_tick_now       Manual scheduler tick (dev tool)
 *   niwa_ai_cr_add_suppression    Insert into suppression list
 *   niwa_ai_cr_remove_suppression Delete from suppression list
 *
 * All AJAX handlers enforce nonce + `manage_options`.
 *
 * @package Niwa_AI
 * @since   0.12.0
 */

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

class Niwa_AI_Cart_Recovery_Settings_UI {

	public const MENU_SLUG    = 'niwa-ai-cart-recovery';
	private const NONCE_ACTION = 'niwa_ai_cr_admin';

	private string $page_hook_suffix = '';

	public function __construct(
		private readonly Niwa_AI_Cart_Recovery_Repository $repository,
		private readonly ?Niwa_AI_Cart_Recovery_Mailer $mailer = null,
		private readonly ?Niwa_AI_Cart_Recovery_Scheduler $scheduler = null
	) {}

	public function register_hooks(): void {
		add_action( 'admin_menu', array( $this, 'register_menu' ), 12 );
		add_action( 'admin_init', array( $this, 'register_settings' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );

		add_action( 'wp_ajax_niwa_ai_cr_preview', array( $this, 'ajax_preview' ) );
		add_action( 'wp_ajax_niwa_ai_cr_test_send', array( $this, 'ajax_test_send' ) );
		add_action( 'wp_ajax_niwa_ai_cr_run_tick_now', array( $this, 'ajax_run_tick_now' ) );
		add_action( 'wp_ajax_niwa_ai_cr_add_suppression', array( $this, 'ajax_add_suppression' ) );
		add_action( 'wp_ajax_niwa_ai_cr_remove_suppression', array( $this, 'ajax_remove_suppression' ) );
	}

	public function register_menu(): void {
		$this->page_hook_suffix = (string) add_submenu_page(
			'niwa-ai',
			__( 'Cart Recovery', 'niwa-ai' ),
			__( 'Cart Recovery', 'niwa-ai' ),
			'manage_options',
			self::MENU_SLUG,
			array( $this, 'render_page' )
		);
	}

	public function register_settings(): void {
		register_setting(
			'niwa_ai_cart_recovery_group',
			Niwa_AI_Constants::OPT_CART_RECOVERY_SETTINGS,
			array(
				'type'              => 'array',
				'sanitize_callback' => array( Niwa_AI_Cart_Recovery_Settings::class, 'sanitize' ),
				'default'           => Niwa_AI_Cart_Recovery_Settings::defaults(),
			)
		);
	}

	public function enqueue_assets( $hook ): void {
		if ( empty( $this->page_hook_suffix ) || $hook !== $this->page_hook_suffix ) {
			return;
		}

		wp_enqueue_style(
			'niwa-ai-cart-recovery-admin',
			NIWA_AI_URL . 'admin/css/cart-recovery-admin.css',
			array(),
			NIWA_AI_VERSION
		);

		wp_enqueue_script(
			'niwa-ai-cart-recovery-admin',
			NIWA_AI_URL . 'admin/js/cart-recovery-admin.js',
			array( 'jquery' ),
			NIWA_AI_VERSION,
			true
		);

		// Chart.js for the metrics tab.
		wp_enqueue_script(
			'niwa-ai-chartjs',
			'https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js',
			array(),
			'4.4.0',
			true
		);

		wp_localize_script(
			'niwa-ai-cart-recovery-admin',
			'NiwaAiCR',
			array(
				'ajaxUrl' => admin_url( 'admin-ajax.php' ),
				'nonce'   => wp_create_nonce( self::NONCE_ACTION ),
				'i18n'    => array(
					'previewError'    => __( 'Preview failed to load.', 'niwa-ai' ),
					'testSent'        => __( 'Test email sent.', 'niwa-ai' ),
					'testFailed'      => __( 'Sending failed. Check SMTP / wp_mail.', 'niwa-ai' ),
					'tickDone'        => __( 'Tick complete.', 'niwa-ai' ),
					'suppressAdded'   => __( 'Email added to suppression list.', 'niwa-ai' ),
					'suppressRemoved' => __( 'Email removed from suppression list.', 'niwa-ai' ),
					'confirmRemove'   => __( 'Remove this suppression entry?', 'niwa-ai' ),
					'shadowAttached'  => __( 'shadow row linked to your email', 'niwa-ai' ),
					'confirmForceLink' => __( 'This attaches your admin email to EVERY pending shadow row (carts that were captured without an email). Use only for local testing — avoid on production. Continue?', 'niwa-ai' ),
				),
			)
		);
	}

	public function render_page(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Insufficient permissions.', 'niwa-ai' ) );
		}

		$tab    = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( (string) $_GET['tab'] ) ) : 'general';
		$tabs   = $this->tabs_definition();
		if ( ! isset( $tabs[ $tab ] ) ) {
			$tab = 'general';
		}

		echo '<div class="wrap niwa-wrap">';

		$this->render_page_header();

		if ( isset( $_GET['settings-updated'] ) && 'true' === $_GET['settings-updated'] ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Settings saved.', 'niwa-ai' ) . '</p></div>';
		}

		$this->render_tab_nav( $tab, $tabs );

		call_user_func( array( $this, 'render_tab_' . $tab ) );

		echo '</div>';
	}

	private function render_page_header(): void {
		echo '<div class="niwa-page-header">';
		echo '<div class="niwa-page-header__icon">';
		// Shopping-cart-return SVG icon.
		echo '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/><path d="M8 10l2 2 4-4"/></svg>';
		echo '</div>';
		echo '<div class="niwa-page-header__text">';
		echo '<h1 class="niwa-page-header__title">' . esc_html__( 'Cart Recovery', 'niwa-ai' ) . '</h1>';
		echo '<p class="niwa-page-header__subtitle">' . esc_html__( 'AI-driven abandoned-cart recovery by email, WhatsApp, or both.', 'niwa-ai' ) . '</p>';
		echo '</div>';
		echo '</div>';
	}

	/** @return array<string, string> */
	private function tabs_definition(): array {
		return array(
			'general'     => __( 'General', 'niwa-ai' ),
			'coupon'      => __( 'Coupon', 'niwa-ai' ),
			'templates'   => __( 'Templates & Preview', 'niwa-ai' ),
			'suppression' => __( 'Suppression List', 'niwa-ai' ),
			'metrics'     => __( 'Metrics', 'niwa-ai' ),
		);
	}

	private function render_tab_nav( string $active, array $tabs ): void {
		echo '<h2 class="nav-tab-wrapper">';
		foreach ( $tabs as $slug => $label ) {
			$url   = add_query_arg(
				array( 'page' => self::MENU_SLUG, 'tab' => $slug ),
				admin_url( 'admin.php' )
			);
			$class = 'nav-tab' . ( $slug === $active ? ' nav-tab-active' : '' );
			echo '<a href="' . esc_url( $url ) . '" class="' . esc_attr( $class ) . '">' . esc_html( $label ) . '</a>';
		}
		echo '</h2>';
	}

	/* ===============================================================
	 * Field helpers — match niwa-field / niwa-toggle pattern
	 * ============================================================ */

	private function toggle( string $name, string $label, array $opts, string $key, string $hint = '' ): void {
		$checked = ! empty( $opts[ $key ] ) ? 'checked' : '';
		echo '<div class="niwa-field">';
		// Hidden "0" companion so an unchecked box still submits a value
		// (otherwise the key would be absent from $_POST and the sanitize
		// merge would preserve the stored "true" state — silent no-op
		// on uncheck). Checkbox comes after, overriding with "1" when
		// checked — PHP's duplicate-key merge takes the later value.
		echo '<input type="hidden" name="' . esc_attr( $name . '[' . $key . ']' ) . '" value="0" />';
		echo '<label class="niwa-toggle">';
		echo '<input type="checkbox" name="' . esc_attr( $name . '[' . $key . ']' ) . '" value="1" ' . esc_attr( $checked ) . ' /> ';
		echo esc_html( $label );
		echo '</label>';
		if ( '' !== $hint ) {
			echo '<span class="niwa-field__hint">' . esc_html( $hint ) . '</span>';
		}
		echo '</div>';
	}

	private function text( string $name, string $label, array $opts, string $key, string $hint = '', string $placeholder = '' ): void {
		$value = (string) ( $opts[ $key ] ?? '' );
		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label" for="' . esc_attr( $name . '-' . $key ) . '">' . esc_html( $label ) . '</label>';
		echo '<input id="' . esc_attr( $name . '-' . $key ) . '" type="text" name="' . esc_attr( $name . '[' . $key . ']' ) . '" value="' . esc_attr( $value ) . '" placeholder="' . esc_attr( $placeholder ) . '" class="regular-text" />';
		if ( '' !== $hint ) {
			echo '<span class="niwa-field__hint">' . esc_html( $hint ) . '</span>';
		}
		echo '</div>';
	}

	private function number( string $name, string $label, array $opts, string $key, int $min = 0, int $max = 9999, string $hint = '' ): void {
		$value = (int) ( $opts[ $key ] ?? 0 );
		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label" for="' . esc_attr( $name . '-' . $key ) . '">' . esc_html( $label ) . '</label>';
		echo '<input id="' . esc_attr( $name . '-' . $key ) . '" type="number" name="' . esc_attr( $name . '[' . $key . ']' ) . '" value="' . esc_attr( $value ) . '" min="' . esc_attr( $min ) . '" max="' . esc_attr( $max ) . '" />';
		if ( '' !== $hint ) {
			echo '<span class="niwa-field__hint">' . esc_html( $hint ) . '</span>';
		}
		echo '</div>';
	}

	private function select( string $name, string $label, array $opts, string $key, array $choices, string $hint = '' ): void {
		$current = (string) ( $opts[ $key ] ?? '' );
		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label" for="' . esc_attr( $name . '-' . $key ) . '">' . esc_html( $label ) . '</label>';
		echo '<select id="' . esc_attr( $name . '-' . $key ) . '" name="' . esc_attr( $name . '[' . $key . ']' ) . '">';
		foreach ( $choices as $val => $text ) {
			echo '<option value="' . esc_attr( (string) $val ) . '" ' . selected( $current, (string) $val, false ) . '>' . esc_html( $text ) . '</option>';
		}
		echo '</select>';
		if ( '' !== $hint ) {
			echo '<span class="niwa-field__hint">' . esc_html( $hint ) . '</span>';
		}
		echo '</div>';
	}

	private function textarea( string $name, string $label, array $opts, string $key, int $rows = 3, string $hint = '', string $placeholder = '' ): void {
		$value = (string) ( $opts[ $key ] ?? '' );
		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label" for="' . esc_attr( $name . '-' . $key ) . '">' . esc_html( $label ) . '</label>';
		echo '<textarea id="' . esc_attr( $name . '-' . $key ) . '" name="' . esc_attr( $name . '[' . $key . ']' ) . '" rows="' . esc_attr( (string) $rows ) . '" class="large-text" placeholder="' . esc_attr( $placeholder ) . '">' . esc_textarea( $value ) . '</textarea>';
		if ( '' !== $hint ) {
			echo '<span class="niwa-field__hint">' . esc_html( $hint ) . '</span>';
		}
		echo '</div>';
	}

	private function card_open( string $title, string $icon_svg = '' ): void {
		echo '<div class="niwa-card">';
		echo '<div class="niwa-card__header">';
		if ( '' !== $icon_svg ) {
			echo '<div class="niwa-card__icon">' . $icon_svg . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		}
		echo '<h2 class="niwa-card__title">' . esc_html( $title ) . '</h2>';
		echo '</div>';
		echo '<div class="niwa-card__body">';
	}

	private function card_close(): void {
		echo '</div></div>';
	}

	private function svg( string $path ): string {
		return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' . $path . '</svg>';
	}

	/* ===============================================================
	 * Tab: General
	 * ============================================================ */

	protected function render_tab_general(): void {
		$opts = Niwa_AI_Cart_Recovery_Settings::get_settings();
		$n    = Niwa_AI_Constants::OPT_CART_RECOVERY_SETTINGS;

		echo '<form method="post" action="' . esc_url( admin_url( 'options.php' ) ) . '">';
		settings_fields( 'niwa_ai_cart_recovery_group' );

		$this->card_open(
			__( 'Feature toggles', 'niwa-ai' ),
			$this->svg( '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>' )
		);
		$this->toggle( $n, __( 'Enable Cart Recovery', 'niwa-ai' ), $opts, 'enabled', __( 'Master switch — when off, no reminders are sent and no rows are promoted.', 'niwa-ai' ) );
		$this->toggle( $n, __( 'Enable first reminder', 'niwa-ai' ), $opts, 'enable_mail1', __( 'Friendly reminder after the configured inactivity delay, without a coupon.', 'niwa-ai' ) );
		$this->toggle( $n, __( 'Enable follow-up', 'niwa-ai' ), $opts, 'enable_mail2', __( 'Second reminder after the configured delay, with an optional coupon.', 'niwa-ai' ) );
		$this->select( $n, __( 'Send cart reminders via', 'niwa-ai' ), $opts, 'delivery_channel', array(
			Niwa_AI_Cart_Recovery_Settings::CHANNEL_EMAIL    => __( 'Email', 'niwa-ai' ),
			Niwa_AI_Cart_Recovery_Settings::CHANNEL_WHATSAPP => __( 'WhatsApp', 'niwa-ai' ),
			Niwa_AI_Cart_Recovery_Settings::CHANNEL_BOTH     => __( 'Email and WhatsApp', 'niwa-ai' ),
		), __( 'Each reminder uses only the selected channels for which the captured contact has valid details.', 'niwa-ai' ) );
		$this->card_close();

		$this->card_open(
			__( 'Drip timing', 'niwa-ai' ),
			$this->svg( '<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>' )
		);
		$this->number( $n, __( 'First reminder delay (minutes)', 'niwa-ai' ), $opts, 'mail1_delay_minutes', 1, 720 );
		$this->number( $n, __( 'Follow-up delay (hours)', 'niwa-ai' ), $opts, 'mail2_delay_hours', 1, 168 );
		$this->number( $n, __( 'Inactivity cap (hours)', 'niwa-ai' ), $opts, 'inactivity_cap_hours', 1, 720, __( 'Rows older than this are auto-expired.', 'niwa-ai' ) );
		$this->number( $n, __( 'Cooldown per contact (days)', 'niwa-ai' ), $opts, 'cooldown_days', 0, 365 );
		$this->card_close();

		$this->card_open(
			__( 'Email sender and store brand', 'niwa-ai' ),
			$this->svg( '<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/>' )
		);
		$this->text( $n, __( 'From name', 'niwa-ai' ), $opts, 'from_name', __( 'Used when delivery includes Email. Leave empty to use the site name.', 'niwa-ai' ), 'Niva' );
		$this->text( $n, __( 'From email', 'niwa-ai' ), $opts, 'from_email', __( 'Used when delivery includes Email. Leave empty to use the admin email.', 'niwa-ai' ), 'noreply@example.com' );
		$this->text( $n, __( 'Store brand name', 'niwa-ai' ), $opts, 'store_brand_name', __( 'Used in reminder content. Defaults to the site name.', 'niwa-ai' ) );
		$this->card_close();

		submit_button();
		echo '</form>';

		// Dev tools — outside the form.
		$this->card_open(
			__( 'Dev tools', 'niwa-ai' ),
			$this->svg( '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>' )
		);
		echo '<p class="description">' . esc_html__( 'Trigger the scheduler tick manually instead of waiting for the next scheduled run.', 'niwa-ai' ) . '</p>';
		echo '<p>';
		echo '<button type="button" class="button button-primary" id="niwa-ai-cr-run-tick">' . esc_html__( 'Run tick now', 'niwa-ai' ) . '</button> ';
		echo '<button type="button" class="button" id="niwa-ai-cr-run-tick-force" title="' . esc_attr__( 'Bulk-attach your admin email to every pending shadow row, then tick. Dev tool — avoid on production data.', 'niwa-ai' ) . '">' . esc_html__( 'Force-link shadow rows → my email, then tick', 'niwa-ai' ) . '</button> ';
		echo '<span id="niwa-ai-cr-run-tick-result" class="niwa-field__hint" style="margin-left:12px;"></span>';
		echo '</p>';

		if ( $this->scheduler ) {
			$runtime = $this->scheduler->active_runtime();
			$badge = 'action_scheduler' === $runtime
				? '<span class="niwa-badge niwa-badge--ok">' . esc_html__( 'Action Scheduler', 'niwa-ai' ) . '</span>'
				: '<span class="niwa-badge niwa-badge--warn">' . esc_html__( 'WP-Cron (fallback)', 'niwa-ai' ) . '</span>';
			echo '<p>' . esc_html__( 'Runtime:', 'niwa-ai' ) . ' ' . $badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			if ( 'action_scheduler' !== $runtime ) {
				echo ' <span class="niwa-field__hint">' . esc_html__( 'Install WooCommerce (or any plugin bundling Action Scheduler) for more reliable ticks on low-traffic sites.', 'niwa-ai' ) . '</span>';
			}
			echo '</p>';
		}

		$this->card_close();
	}

	/* ===============================================================
	 * Tab: Coupon
	 * ============================================================ */

	protected function render_tab_coupon(): void {
		$opts = Niwa_AI_Cart_Recovery_Settings::get_settings();
		$n    = Niwa_AI_Constants::OPT_CART_RECOVERY_SETTINGS;

		echo '<form method="post" action="' . esc_url( admin_url( 'options.php' ) ) . '">';
		settings_fields( 'niwa_ai_cart_recovery_group' );

		$this->card_open(
			__( 'Discount', 'niwa-ai' ),
			$this->svg( '<path d="M20.59 13.41L13.42 20.58a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>' )
		);
		$this->toggle( $n, __( 'Enable coupons in the follow-up reminder', 'niwa-ai' ), $opts, 'coupon_enabled' );
		$this->select( $n, __( 'Discount type', 'niwa-ai' ), $opts, 'discount_type', array(
			Niwa_AI_Cart_Recovery_Coupon_Config::TYPE_PERCENT       => __( 'Percentage', 'niwa-ai' ),
			Niwa_AI_Cart_Recovery_Coupon_Config::TYPE_FIXED_CART    => __( 'Fixed cart amount', 'niwa-ai' ),
			Niwa_AI_Cart_Recovery_Coupon_Config::TYPE_FIXED_PRODUCT => __( 'Fixed product amount', 'niwa-ai' ),
		) );
		$this->number( $n, __( 'Discount amount', 'niwa-ai' ), $opts, 'amount', 0, 100000 );
		$this->number( $n, __( 'Coupon TTL (hours)', 'niwa-ai' ), $opts, 'ttl_hours', 1, 720 );
		$this->text( $n, __( 'Code prefix', 'niwa-ai' ), $opts, 'prefix', __( 'Uppercase alphanumeric + dash/underscore only. Max 32 chars.', 'niwa-ai' ), 'NIWA-BACK-' );
		$this->card_close();

		$this->card_open(
			__( 'Restrictions', 'niwa-ai' ),
			$this->svg( '<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>' )
		);
		$this->select( $n, __( 'Minimum spend', 'niwa-ai' ), $opts, 'minimum_spend_mode', array(
			Niwa_AI_Cart_Recovery_Coupon_Config::MIN_SPEND_NONE       => __( 'No minimum', 'niwa-ai' ),
			Niwa_AI_Cart_Recovery_Coupon_Config::MIN_SPEND_CART_TOTAL => __( 'Require cart subtotal at time of capture', 'niwa-ai' ),
		) );
		$this->toggle( $n, __( 'Restrict to abandoned cart products', 'niwa-ai' ), $opts, 'restrict_to_cart_products' );
		$this->toggle( $n, __( 'Exclude sale items', 'niwa-ai' ), $opts, 'exclude_sale_items' );
		$this->toggle( $n, __( 'Cannot combine with other coupons', 'niwa-ai' ), $opts, 'individual_use' );
		$this->toggle( $n, __( 'Grants free shipping', 'niwa-ai' ), $opts, 'free_shipping' );
		$this->card_close();

		submit_button();
		echo '</form>';
	}

	/* ===============================================================
	 * Tab: Templates & Preview
	 * ============================================================ */

	protected function render_tab_templates(): void {
		$opts = Niwa_AI_Cart_Recovery_Settings::get_settings();
		$n    = Niwa_AI_Constants::OPT_CART_RECOVERY_SETTINGS;
		$rows = $this->repository->recent_rows_for_preview( 20 );
		$template_locale = $this->current_template_admin_locale();

		// ----- Customize templates card -----
		echo '<form method="post" action="' . esc_url( admin_url( 'options.php' ) ) . '">';
		settings_fields( 'niwa_ai_cart_recovery_group' );

		$this->card_open(
			__( 'Customize reminder content', 'niwa-ai' ),
			$this->svg( '<path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>' )
		);

		echo '<p class="description">';
		echo esc_html__( 'Each stage can either use the built-in/custom template default or the AI composer. Supported placeholders:', 'niwa-ai' );
		echo ' ';
		$placeholders = class_exists( 'Niwa_AI_Cart_Recovery_Placeholder_Renderer' )
			? Niwa_AI_Cart_Recovery_Placeholder_Renderer::available_placeholders()
			: array();
		foreach ( $placeholders as $ph ) {
			echo '<code style="margin-right:6px;">' . esc_html( $ph ) . '</code>';
		}
		echo '</p>';

		foreach ( array( 'mail1' => __( 'First reminder', 'niwa-ai' ), 'mail2' => __( 'Follow-up', 'niwa-ai' ) ) as $stage => $label ) {
			$display_opts = $this->apply_default_template_display_values( $opts, $stage, $template_locale );
			echo '<h3 style="margin-top:24px;">' . esc_html( $label ) . '</h3>';
			$this->select( $n, __( 'Content source', 'niwa-ai' ), $display_opts, $stage . '_source', array(
				'template' => __( 'Built-in/custom template (default)', 'niwa-ai' ),
				'ai'       => __( 'AI composer', 'niwa-ai' ),
			), __( 'Template mode uses the locale-aware built-in copy unless you fill custom fields below.', 'niwa-ai' ) );
			$this->text( $n, __( 'Email subject', 'niwa-ai' ), $display_opts, $stage . '_subject_template', __( 'Shows the built-in default when no custom subject is saved.', 'niwa-ai' ) );
			$this->textarea( $n, __( 'Email HTML body', 'niwa-ai' ), $display_opts, $stage . '_html_template', 8, __( 'Shows the built-in default when no custom HTML is saved. Edit and save to customize.', 'niwa-ai' ) );
			$this->textarea( $n, __( 'Plain-text / WhatsApp body', 'niwa-ai' ), $display_opts, $stage . '_text_template', 6, __( 'Used for the plain-text email and as the WhatsApp message content.', 'niwa-ai' ) );
		}

		$this->card_close();

		submit_button();
		echo '</form>';

		// ----- Preview card -----
		$this->card_open(
			__( 'Reminder preview', 'niwa-ai' ),
			$this->svg( '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>' )
		);

		if ( empty( $rows ) ) {
			echo '<p><em>' . esc_html__( 'No captured rows yet. Trigger one on the frontend (add to cart → leave) to see a preview here.', 'niwa-ai' ) . '</em></p>';
			$this->card_close();
			return;
		}

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label" for="niwa-ai-cr-preview-row">' . esc_html__( 'Session', 'niwa-ai' ) . '</label>';
		echo '<select id="niwa-ai-cr-preview-row" class="regular-text">';
		foreach ( $rows as $r ) {
			$contact = trim( (string) ( $r['email'] ?? '' ) );
			if ( '' === $contact ) {
				$contact = trim( (string) ( $r['phone'] ?? '' ) );
			}
			$label = sprintf(
				'#%d — %s (%s)',
				(int) ( $r['id'] ?? 0 ),
				'' !== $contact ? $contact : __( '(no contact)', 'niwa-ai' ),
				(string) ( $r['status'] ?? '' )
			);
			echo '<option value="' . esc_attr( (int) ( $r['id'] ?? 0 ) ) . '">' . esc_html( $label ) . '</option>';
		}
		echo '</select>';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label" for="niwa-ai-cr-preview-stage">' . esc_html__( 'Stage', 'niwa-ai' ) . '</label>';
		echo '<select id="niwa-ai-cr-preview-stage">';
		echo '<option value="mail1">' . esc_html__( 'mail1 (first reminder)', 'niwa-ai' ) . '</option>';
		echo '<option value="mail2">' . esc_html__( 'mail2 (follow-up + coupon)', 'niwa-ai' ) . '</option>';
		echo '</select>';
		echo '</div>';

		echo '<p>';
		echo '<button type="button" class="button button-primary" id="niwa-ai-cr-preview-generate">' . esc_html__( 'Generate email preview', 'niwa-ai' ) . '</button> ';
		echo '<button type="button" class="button" id="niwa-ai-cr-test-send">' . esc_html__( 'Send test email to admin', 'niwa-ai' ) . '</button> ';
		echo '<span id="niwa-ai-cr-preview-status" class="niwa-field__hint" style="margin-left:12px;"></span>';
		echo '</p>';

		echo '<div id="niwa-ai-cr-preview-meta" style="margin:16px 0;"></div>';
		echo '<iframe id="niwa-ai-cr-preview-frame" sandbox="allow-same-origin" title="Preview" style="width:100%;min-height:600px;background:#fff;border:1px solid #ccd0d4;border-radius:6px;"></iframe>';

		$this->card_close();
	}

	private function current_template_admin_locale(): string {
		$ui_settings = function_exists( 'get_option' )
			? get_option( Niwa_AI_Constants::OPT_UI_SETTINGS, array() )
			: array();

		if ( is_array( $ui_settings ) && ! empty( $ui_settings['ui_language'] ) ) {
			return (string) $ui_settings['ui_language'];
		}

		if ( function_exists( 'determine_locale' ) ) {
			return (string) determine_locale();
		}
		if ( function_exists( 'get_locale' ) ) {
			return (string) get_locale();
		}

		return 'en';
	}

	private function apply_default_template_display_values( array $opts, string $stage, string $locale ): array {
		$template = Niwa_AI_Cart_Recovery_Settings::get_default_stage_template( $stage, $locale );
		$map      = array(
			$stage . '_subject_template' => 'subject',
			$stage . '_html_template'    => 'html',
			$stage . '_text_template'    => 'text',
		);

		foreach ( $map as $opt_key => $template_key ) {
			if ( '' === trim( (string) ( $opts[ $opt_key ] ?? '' ) ) ) {
				$opts[ $opt_key ] = (string) ( $template[ $template_key ] ?? '' );
			}
		}

		return $opts;
	}

	/* ===============================================================
	 * Tab: Suppression
	 * ============================================================ */

	protected function render_tab_suppression(): void {
		$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['s'] ) ) : '';
		$page   = max( 1, (int) ( $_GET['paged'] ?? 1 ) );
		$limit  = 25;
		$offset = ( $page - 1 ) * $limit;
		$data   = $this->repository->get_suppression_list( $search, $limit, $offset );
		$total  = (int) ( $data['total'] ?? 0 );
		$items  = is_array( $data['items'] ) ? $data['items'] : array();
		$pages  = max( 1, (int) ceil( $total / $limit ) );

		$this->card_open(
			__( 'Suppression list', 'niwa-ai' ),
			$this->svg( '<circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/>' )
		);
		echo '<p class="description">' . esc_html__( 'These email addresses will not receive cart-recovery reminders. WhatsApp opt-outs are stored separately as protected phone hashes.', 'niwa-ai' ) . '</p>';

		echo '<form method="get" style="margin-bottom:16px; display:flex; gap:8px; align-items:center;">';
		echo '<input type="hidden" name="page" value="' . esc_attr( self::MENU_SLUG ) . '">';
		echo '<input type="hidden" name="tab" value="suppression">';
		echo '<input type="search" name="s" value="' . esc_attr( $search ) . '" placeholder="' . esc_attr__( 'Search email…', 'niwa-ai' ) . '" class="regular-text">';
		echo '<button type="submit" class="button">' . esc_html__( 'Search', 'niwa-ai' ) . '</button>';
		echo '</form>';

		echo '<div class="niwa-field" style="display:flex;gap:8px;align-items:center;">';
		echo '<input type="email" id="niwa-ai-cr-new-suppress" placeholder="email@example.com" class="regular-text"> ';
		echo '<button type="button" class="button" id="niwa-ai-cr-add-suppress">' . esc_html__( 'Add to suppression', 'niwa-ai' ) . '</button>';
		echo '<span id="niwa-ai-cr-suppress-msg" class="niwa-field__hint" style="margin-left:12px;"></span>';
		echo '</div>';

		echo '<table class="wp-list-table widefat striped"><thead><tr>';
		echo '<th>' . esc_html__( 'Email', 'niwa-ai' ) . '</th>';
		echo '<th>' . esc_html__( 'Reason', 'niwa-ai' ) . '</th>';
		echo '<th>' . esc_html__( 'Added', 'niwa-ai' ) . '</th>';
		echo '<th>' . esc_html__( 'Actions', 'niwa-ai' ) . '</th>';
		echo '</tr></thead><tbody>';

		if ( empty( $items ) ) {
			echo '<tr><td colspan="4"><em>' . esc_html__( 'No suppression entries.', 'niwa-ai' ) . '</em></td></tr>';
		} else {
			foreach ( $items as $row ) {
				echo '<tr>';
				echo '<td>' . esc_html( (string) ( $row['email'] ?? '' ) ) . '</td>';
				echo '<td>' . esc_html( (string) ( $row['reason'] ?? '' ) ) . '</td>';
				echo '<td>' . esc_html( (string) ( $row['created_at'] ?? '' ) ) . '</td>';
				echo '<td><button type="button" class="button-link-delete niwa-ai-cr-remove-suppress" data-email="' . esc_attr( (string) ( $row['email'] ?? '' ) ) . '">' . esc_html__( 'Remove', 'niwa-ai' ) . '</button></td>';
				echo '</tr>';
			}
		}

		echo '</tbody></table>';

		if ( $pages > 1 ) {
			echo '<p style="margin-top:12px;">';
			for ( $p = 1; $p <= $pages; $p++ ) {
				$url = add_query_arg(
					array( 'page' => self::MENU_SLUG, 'tab' => 'suppression', 's' => $search, 'paged' => $p ),
					admin_url( 'admin.php' )
				);
				if ( $p === $page ) {
					echo '<strong>[' . esc_html( (string) $p ) . ']</strong> ';
				} else {
					echo '<a href="' . esc_url( $url ) . '">[' . esc_html( (string) $p ) . ']</a> ';
				}
			}
			echo '</p>';
		}

		$this->card_close();
	}

	/* ===============================================================
	 * Tab: Metrics
	 * ============================================================ */

	protected function render_tab_metrics(): void {
		$days    = isset( $_GET['days'] ) ? (int) $_GET['days'] : 30;
		$days    = in_array( $days, array( 7, 30, 90 ), true ) ? $days : 30;
		$metrics = $this->repository->get_metrics( $days );
		$series  = $this->repository->get_time_series_metrics( min( 30, $days ) );

		$this->card_open(
			__( 'Metrics', 'niwa-ai' ),
			$this->svg( '<path d="M3 3v18h18"/><path d="M7 14l3-3 3 2 4-6"/>' )
		);

		echo '<p>';
		foreach ( array( 7, 30, 90 ) as $range ) {
			$url   = add_query_arg( array( 'page' => self::MENU_SLUG, 'tab' => 'metrics', 'days' => $range ), admin_url( 'admin.php' ) );
			$label = sprintf( /* translators: %d = days */ _n( 'Last %d day', 'Last %d days', $range, 'niwa-ai' ), $range );
			if ( $range === $days ) {
				echo '<strong style="margin-right:12px;">[' . esc_html( $label ) . ']</strong>';
			} else {
				echo '<a href="' . esc_url( $url ) . '" style="margin-right:12px;">' . esc_html( $label ) . '</a>';
			}
		}
		echo '</p>';

		echo '<div class="niwa-stats">';
		$this->render_stat( __( 'Reminders sent', 'niwa-ai' ), (string) (int) $metrics['sent'],    sprintf( '%d + %d', (int) $metrics['sent_mail1'], (int) $metrics['sent_mail2'] ) );
		$this->render_stat( __( 'Opens', 'niwa-ai' ),           (string) (int) $metrics['opens'],   $metrics['open_rate'] . '%' );
		$this->render_stat( __( 'Clicks', 'niwa-ai' ),          (string) (int) $metrics['clicks'],  $metrics['click_rate'] . '%' );
		$this->render_stat( __( 'Recovered', 'niwa-ai' ),       (string) (int) $metrics['recovered'], $metrics['recovered_rate'] . '%' );
		$this->render_stat( __( 'Revenue', 'niwa-ai' ),         number_format( (float) $metrics['recovered_revenue'], 2 ), __( 'Sum of recovered orders', 'niwa-ai' ) );
		$this->render_stat( __( 'Pending', 'niwa-ai' ),         (string) (int) $metrics['pending'], __( 'Awaiting tick', 'niwa-ai' ) );
		$this->render_stat( __( 'Suppressed', 'niwa-ai' ),      (string) (int) $metrics['suppression'], __( 'All-time unsubscribes', 'niwa-ai' ) );
		echo '</div>';

		echo '<h3 style="margin-top:24px;">' . esc_html__( 'Sent vs recovered (daily)', 'niwa-ai' ) . '</h3>';
		echo '<canvas id="niwa-ai-cr-metrics-chart" height="120"></canvas>';
		echo '<script type="application/json" id="niwa-ai-cr-metrics-series">' . wp_json_encode( $series ) . '</script>';

		$this->card_close();
	}

	private function render_stat( string $label, string $value, string $subtitle = '' ): void {
		echo '<div class="niwa-stat">';
		echo '<div class="niwa-stat__label">' . esc_html( $label ) . '</div>';
		echo '<div class="niwa-stat__value">' . esc_html( $value ) . '</div>';
		if ( '' !== $subtitle ) {
			echo '<div class="niwa-stat__subtitle">' . esc_html( $subtitle ) . '</div>';
		}
		echo '</div>';
	}

	/* ===============================================================
	 * AJAX handlers
	 * ============================================================ */

	public function ajax_preview(): void {
		$this->verify_ajax();

		$row_id = isset( $_POST['row_id'] ) ? (int) $_POST['row_id'] : 0;
		$stage  = isset( $_POST['stage'] ) ? sanitize_key( wp_unslash( (string) $_POST['stage'] ) ) : 'mail1';

		if ( $row_id <= 0 || ! $this->mailer ) {
			wp_send_json_error( array( 'message' => __( 'Invalid request.', 'niwa-ai' ) ), 400 );
		}

		$row = $this->repository->find_by_id( $row_id );
		if ( ! is_array( $row ) ) {
			wp_send_json_error( array( 'message' => __( 'Row not found.', 'niwa-ai' ) ), 404 );
		}

		$preview = $this->mailer->preview( $row, $stage );
		if ( null === $preview ) {
			wp_send_json_error( array( 'message' => __( 'Composer could not build a preview.', 'niwa-ai' ) ), 500 );
		}

		wp_send_json_success(
			array(
				'subject'     => $preview['subject'],
				'html'        => $preview['html'],
				'text'        => $preview['text'],
				'is_fallback' => $preview['is_fallback'],
				'from_cache'  => $preview['from_cache'],
			)
		);
	}

	public function ajax_test_send(): void {
		$this->verify_ajax();

		$row_id = isset( $_POST['row_id'] ) ? (int) $_POST['row_id'] : 0;
		$stage  = isset( $_POST['stage'] ) ? sanitize_key( wp_unslash( (string) $_POST['stage'] ) ) : 'mail1';

		if ( $row_id <= 0 || ! $this->mailer ) {
			wp_send_json_error( array( 'message' => __( 'Invalid request.', 'niwa-ai' ) ), 400 );
		}

		$row = $this->repository->find_by_id( $row_id );
		if ( ! is_array( $row ) ) {
			wp_send_json_error( array( 'message' => __( 'Row not found.', 'niwa-ai' ) ), 404 );
		}

		$preview = $this->mailer->preview( $row, $stage );
		if ( null === $preview ) {
			wp_send_json_error( array( 'message' => __( 'Composer could not build a preview.', 'niwa-ai' ) ), 500 );
		}

		$admin_email = (string) ( wp_get_current_user()->user_email ?? get_bloginfo( 'admin_email' ) );
		if ( '' === $admin_email ) {
			wp_send_json_error( array( 'message' => __( 'No admin email resolved.', 'niwa-ai' ) ), 500 );
		}

		$subject = '[TEST] ' . (string) $preview['subject'];
		$headers = array( 'Content-Type: text/html; charset=UTF-8' );
		$sent    = (bool) wp_mail( $admin_email, $subject, (string) $preview['html'], $headers );

		if ( ! $sent ) {
			wp_send_json_error( array( 'message' => __( 'wp_mail returned false.', 'niwa-ai' ) ), 500 );
		}

		wp_send_json_success(
			array(
				'to'      => $admin_email,
				'subject' => $subject,
			)
		);
	}

	public function ajax_run_tick_now(): void {
		$this->verify_ajax();

		if ( ! $this->scheduler ) {
			wp_send_json_error( array( 'message' => __( 'Scheduler not available.', 'niwa-ai' ) ), 500 );
		}

		$force_link_all = ! empty( $_POST['force_link_all'] ) && '1' === (string) $_POST['force_link_all'];

		$shadow_before     = $this->repository->count_pending_shadow_rows();
		$with_contact_before = $this->repository->count_pending_with_contact();

		// Pre-tick convenience #1: upgrade the shadow row (if any) that
		// belongs to the admin's own browser session cookie.
		$upgraded = $this->maybe_upgrade_admin_shadow_row();

		// Pre-tick convenience #2 (dev): bulk-attach admin email to every
		// pending shadow row. Only runs when the operator explicitly ticks
		// the "force-link" variant of the button — lets a developer test
		// the drip end-to-end even when the shadow row came from a
		// different browser / private window where the session cookie is
		// not reachable from this admin request.
		$bulk_linked = 0;
		if ( $force_link_all ) {
			$bulk_linked = $this->force_link_all_shadows_to_admin();
		}

		$this->scheduler->tick();

		$shadow_after     = $this->repository->count_pending_shadow_rows();
		$with_contact_after = $this->repository->count_pending_with_contact();

		wp_send_json_success( array(
			'ran'                 => true,
			'force_link_all'      => $force_link_all,
			'shadow_row_upgraded' => $upgraded,
			'bulk_linked'         => $bulk_linked,
			'pending'             => array(
				'shadow_before'     => $shadow_before,
				'with_contact_before' => $with_contact_before,
				'shadow_after'      => $shadow_after,
				'with_contact_after'  => $with_contact_after,
			),
		) );
	}

	/**
	 * If the admin's browser has a niwa_ai session cookie and that session
	 * carries a shadow row (email == ''), attach the current WP user's
	 * email so the scheduler can promote it immediately. Returns the row
	 * id on success, 0 otherwise.
	 */
	private function maybe_upgrade_admin_shadow_row(): int {
		if ( ! current_user_can( 'manage_options' ) ) {
			return 0;
		}

		$cookie_name = defined( 'Niwa_AI_Constants::SESSION_COOKIE_NAME' )
			? Niwa_AI_Constants::SESSION_COOKIE_NAME
			: 'niwa_ai_session';

		$session = isset( $_COOKIE[ $cookie_name ] )
			? sanitize_text_field( wp_unslash( (string) $_COOKIE[ $cookie_name ] ) )
			: '';

		if ( '' === $session ) {
			return 0;
		}

		$shadow = $this->repository->find_by_session_and_email( $session, '' );
		if ( ! is_array( $shadow ) ) {
			return 0;
		}

		$user = wp_get_current_user();
		if ( ! is_object( $user ) || empty( $user->user_email ) ) {
			return 0;
		}

		$email = (string) $user->user_email;
		if ( ! is_email( $email ) ) {
			return 0;
		}

		$linked_id = $this->repository->attach_contact( $session, $email, '' );
		return is_int( $linked_id ) ? $linked_id : 0;
	}

	/**
	 * Developer convenience: attach the current admin's account email to
	 * EVERY pending shadow row, not just the one for the admin's own
	 * session. Used only when the operator explicitly clicks the
	 * "Force-link shadow rows" button — we do NOT want this firing on a
	 * plain tick, otherwise we'd risk mailing customers from addresses
	 * they never gave us. Returns the number of rows upgraded.
	 */
	private function force_link_all_shadows_to_admin(): int {
		if ( ! current_user_can( 'manage_options' ) ) {
			return 0;
		}
		$user = wp_get_current_user();
		if ( ! is_object( $user ) || empty( $user->user_email ) ) {
			return 0;
		}
		$email = (string) $user->user_email;
		if ( ! is_email( $email ) ) {
			return 0;
		}
		return $this->repository->attach_email_to_all_pending_shadows( $email );
	}

	public function ajax_add_suppression(): void {
		$this->verify_ajax();

		$email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( (string) $_POST['email'] ) ) : '';
		if ( '' === $email || ! is_email( $email ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid email.', 'niwa-ai' ) ), 400 );
		}

		$ok = $this->repository->add_suppression( $email, 'admin_manual' );
		if ( ! $ok ) {
			wp_send_json_error( array( 'message' => __( 'Could not add.', 'niwa-ai' ) ), 500 );
		}

		wp_send_json_success( array( 'email' => $email ) );
	}

	public function ajax_remove_suppression(): void {
		$this->verify_ajax();

		$email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( (string) $_POST['email'] ) ) : '';
		if ( '' === $email ) {
			wp_send_json_error( array( 'message' => __( 'Missing email.', 'niwa-ai' ) ), 400 );
		}

		$ok = $this->repository->remove_suppression( $email );
		if ( ! $ok ) {
			wp_send_json_error( array( 'message' => __( 'Email not found in suppression list.', 'niwa-ai' ) ), 404 );
		}

		wp_send_json_success( array( 'email' => $email ) );
	}

	private function verify_ajax(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'niwa-ai' ) ), 403 );
		}
		$nonce = isset( $_POST['nonce'] ) ? (string) $_POST['nonce'] : '';
		if ( ! wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'niwa-ai' ) ), 403 );
		}
	}
}
