<?php
/**
 * Admin settings page.
 *
 * @package KwiroAISalesAgent
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class Kwiro_Admin
 *
 * Handles the plugin admin settings page under WooCommerce menu.
 */
class Kwiro_Admin {

	/**
	 * API Client instance.
	 *
	 * @var Kwiro_API_Client
	 */
	private $api_client;

	/**
	 * Product Sync instance.
	 *
	 * @var Kwiro_Product_Sync
	 */
	private $product_sync;

	/**
	 * Constructor.
	 *
	 * @param Kwiro_API_Client   $api_client   API client instance.
	 * @param Kwiro_Product_Sync $product_sync Product sync instance.
	 */
	public function __construct( $api_client, $product_sync ) {
		$this->api_client   = $api_client;
		$this->product_sync = $product_sync;

		$this->register_hooks();
	}

	/**
	 * Register admin hooks.
	 */
	private function register_hooks() {
		add_action( 'admin_menu', array( $this, 'add_menu_page' ) );
		add_action( 'admin_init', array( $this, 'register_settings' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
		add_action( 'admin_init', array( $this, 'handle_activation_redirect' ) );
		add_action( 'admin_init', array( $this, 'maybe_auto_register' ) );
		add_action( 'wp_ajax_kwiro_test_connection', array( $this, 'ajax_test_connection' ) );
		add_action( 'wp_ajax_kwiro_trigger_sync', array( $this, 'ajax_trigger_sync' ) );
		add_action( 'wp_ajax_kwiro_sync_progress', array( $this, 'ajax_sync_progress' ) );
		add_action( 'wp_ajax_kwiro_begin_link', array( $this, 'ajax_begin_link' ) );
		add_action( 'wp_ajax_kwiro_grant_consent', array( $this, 'ajax_grant_consent' ) );
		add_action( 'wp_ajax_kwiro_disconnect', array( $this, 'ajax_disconnect' ) );
	}

	/**
	 * Auto-register this store with the cloud once the owner has explicitly
	 * consented (see ajax_grant_consent()).
	 *
	 * WP.org Guideline #7 ("phone home") prohibits sending data to remote
	 * services without explicit user consent. Before 1.1.4, this method
	 * fired on every settings-page open and silently registered the store
	 * with api.kwiro.ai — that would now be rejected at review. The
	 * `kwiro_user_consented` option is set only when the owner clicks
	 * "Connect to Kwiro" on the consent disclosure UI rendered by
	 * render_settings_page(). After that, this method runs the actual
	 * registration, populates the API key, and the settings page shows
	 * the post-connect "Connect to Kwiro dashboard" CTA.
	 */
	public function maybe_auto_register() {
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return;
		}

		// Only run on our settings page — avoids a 30-second network hit on
		// every WP admin load, and makes the flow predictable for the user.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page check.
		$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
		if ( 'kwiro-settings' !== $page ) {
			return;
		}

		if ( get_option( 'kwiro_api_key' ) ) {
			return; // Already registered.
		}

		// Hard gate: do not contact api.kwiro.ai until the owner has
		// affirmatively consented via the disclosure UI (DG #7).
		if ( ! get_option( 'kwiro_user_consented' ) ) {
			return;
		}

		// Allow owner to force a retry via query param after a failure.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- manual user action from admin UI.
		if ( isset( $_GET['kwiro_retry'] ) ) {
			delete_transient( 'kwiro_register_backoff' );
		}

		// Heal stale backoffs: if the cached error points at a host that no
		// longer matches the configured API URL (e.g. an old api.kwiro.ai
		// DNS failure from before KWIRO_API_URL was wired in), drop it so
		// we retry cleanly instead of showing a confusing error for 5 min.
		$backoff_msg = get_transient( 'kwiro_register_backoff' );
		if ( $backoff_msg ) {
			$api_host = wp_parse_url( $this->api_client_url(), PHP_URL_HOST );
			if ( $api_host && false === stripos( $backoff_msg, $api_host ) ) {
				delete_transient( 'kwiro_register_backoff' );
				$backoff_msg = false;
			}
		}
		if ( $backoff_msg ) {
			return;
		}

		$home = home_url();
		$host = wp_parse_url( $home, PHP_URL_HOST );
		$port = wp_parse_url( $home, PHP_URL_PORT );
		if ( empty( $host ) ) {
			return;
		}
		$domain = $host . ( $port ? ':' . $port : '' );

		$name     = get_bloginfo( 'name' );
		$currency = function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : 'USD';

		$result = $this->api_client->register_store(
			array(
				'domain'   => $domain,
				'name'     => $name ? $name : $domain,
				'currency' => $currency,
			)
		);

		if ( is_wp_error( $result ) ) {
			// Short TTL (60s) — if it's a transient network blip we retry
			// quickly; if it's a real config problem the owner sees it right
			// away and can hit Retry.
			set_transient( 'kwiro_register_backoff', $result->get_error_message(), 60 );

			// "Domain already linked to a Kwiro account" is a recoverable
			// state — the merchant just needs to retrieve their existing
			// API key from the dashboard. We persist the claim metadata
			// (recoverable flag + redacted email hint) so render_settings_page()
			// can show a help-the-merchant UI instead of a dead-end notice.
			// Other errors clear the option so we don't leave stale state.
			if ( 'kwiro_store_claimed' === $result->get_error_code() ) {
				$err_data = $result->get_error_data();
				update_option( 'kwiro_register_claim_state', is_array( $err_data ) ? $err_data : array( 'recoverable' => true ), false );
			} else {
				delete_option( 'kwiro_register_claim_state' );
			}
		} else {
			// Successful (fresh OR auto-reclaim of an unclaimed store) — drop
			// any stale claim state from a previous attempt.
			delete_option( 'kwiro_register_claim_state' );
			// First successful registration — record it so the dashboard's
			// sync history has a clear "plugin installed/activated" anchor.
			$this->api_client->log_sync_event(
				'activation',
				array(
					'trigger_source' => 'activation',
					'message'        => sprintf(
						/* translators: %s: plugin version */
						__( 'Kwiro plugin %s activated and store registered.', 'kwiro-ai-sales-agent' ),
						KWIRO_VERSION
					),
				)
			);
		}
	}

	/**
	 * Read-only accessor for the API client's current base URL. Delegates to
	 * the client's own lazy resolver so a dev-mode `define(KWIRO_API_URL)`
	 * declared after plugins_loaded still wins.
	 */
	private function api_client_url() {
		return $this->api_client->get_api_url();
	}

	/**
	 * Add submenu page under WooCommerce.
	 */
	public function add_menu_page() {
		add_submenu_page(
			'woocommerce',
			__( 'Kwiro — AI Sales Agent & Chatbot for WooCommerce', 'kwiro-ai-sales-agent' ),
			__( 'Kwiro', 'kwiro-ai-sales-agent' ),
			'manage_woocommerce',
			'kwiro-settings',
			array( $this, 'render_settings_page' )
		);
	}

	/**
	 * Register plugin settings.
	 */
	public function register_settings() {
		register_setting(
			'kwiro_settings_group',
			'kwiro_api_key',
			array(
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			)
		);

		register_setting(
			'kwiro_settings_group',
			'kwiro_settings',
			array(
				'type'              => 'array',
				'sanitize_callback' => array( $this, 'sanitize_settings' ),
			)
		);

		// Connection section.
		add_settings_section(
			'kwiro_connection',
			__( 'Connection', 'kwiro-ai-sales-agent' ),
			array( $this, 'render_connection_section' ),
			'kwiro-settings'
		);

		add_settings_field(
			'kwiro_api_key',
			__( 'API Key', 'kwiro-ai-sales-agent' ),
			array( $this, 'render_api_key_field' ),
			'kwiro-settings',
			'kwiro_connection'
		);

		// Widget section — master on/off only. All appearance fields (color,
		// position, bot name, welcome message, Powered-by badge) are managed
		// from the Kwiro dashboard so they can change live without plugin edits.
		add_settings_section(
			'kwiro_widget',
			__( 'Chat Widget', 'kwiro-ai-sales-agent' ),
			array( $this, 'render_widget_section' ),
			'kwiro-settings'
		);

		add_settings_field(
			'kwiro_widget_enabled',
			__( 'Enable Widget', 'kwiro-ai-sales-agent' ),
			array( $this, 'render_widget_enabled_field' ),
			'kwiro-settings',
			'kwiro_widget'
		);
	}

	/**
	 * Sanitize settings array.
	 *
	 * @param array $input Raw input.
	 * @return array Sanitized settings.
	 */
	public function sanitize_settings( $input ) {
		$sanitized = array();

		$sanitized['widget_enabled'] = ! empty( $input['widget_enabled'] );

		$allowed_positions            = array( 'bottom-right', 'bottom-left' );
		$position                     = sanitize_text_field( $input['widget_position'] ?? 'bottom-right' );
		$sanitized['widget_position'] = in_array( $position, $allowed_positions, true ) ? $position : 'bottom-right';

		$widget_color              = sanitize_hex_color( $input['widget_color'] ?? '#4F46E5' );
		$sanitized['widget_color'] = $widget_color ? $widget_color : '#4F46E5';

		$sanitized['bot_name']        = sanitize_text_field( $input['bot_name'] ?? 'AI Assistant' );
		$sanitized['powered_by']      = ! empty( $input['powered_by'] );
		$sanitized['welcome_message'] = sanitize_textarea_field( $input['welcome_message'] ?? '' );

		$allowed_intervals          = array( 'hourly', 'daily', 'weekly' );
		$interval                   = sanitize_text_field( $input['sync_interval'] ?? 'daily' );
		$sanitized['sync_interval'] = in_array( $interval, $allowed_intervals, true ) ? $interval : 'daily';

		$sanitized['batch_size'] = max( 10, min( 200, absint( $input['batch_size'] ?? 50 ) ) );

		return $sanitized;
	}

	/**
	 * Enqueue admin assets.
	 *
	 * @param string $hook_suffix Current admin page hook.
	 */
	public function enqueue_assets( $hook_suffix ) {
		if ( 'woocommerce_page_kwiro-settings' !== $hook_suffix ) {
			return;
		}

		wp_enqueue_style(
			'kwiro-admin',
			KWIRO_PLUGIN_URL . 'assets/css/admin.css',
			array(),
			KWIRO_VERSION
		);

		wp_enqueue_script(
			'kwiro-admin',
			KWIRO_PLUGIN_URL . 'assets/js/admin.js',
			array( 'jquery' ),
			KWIRO_VERSION,
			true
		);

		// Pass dynamic config (ajax URL + nonce) to the admin script via
		// wp_add_inline_script — the documented best practice for non-string
		// data. `before` position guarantees the global is defined before
		// admin.js runs.
		$admin_data = array(
			'ajaxUrl' => admin_url( 'admin-ajax.php' ),
			'nonce'   => wp_create_nonce( 'kwiro_admin_nonce' ),
		);
		wp_add_inline_script(
			'kwiro-admin',
			'window.kwiroAdmin = ' . wp_json_encode( $admin_data ) . ';',
			'before'
		);

		// Connect / disconnect / link / API-key UI for the settings page.
		// Each block in the file is element-gated so loading it on every render
		// of the settings page is safe — only the blocks whose target buttons
		// exist will attach handlers.
		wp_enqueue_script(
			'kwiro-admin-connect',
			KWIRO_PLUGIN_URL . 'assets/js/admin-connect.js',
			array(),
			KWIRO_VERSION,
			true
		);

		$connect_data = array(
			'ajaxUrl' => admin_url( 'admin-ajax.php' ),
			'nonce'   => wp_create_nonce( 'kwiro_admin_nonce' ),
			'i18n'    => array(
				'connecting'         => __( 'Connecting…', 'kwiro-ai-sales-agent' ),
				'opening'            => __( 'Opening Kwiro…', 'kwiro-ai-sales-agent' ),
				'couldNotStart'      => __( 'Could not start connect flow.', 'kwiro-ai-sales-agent' ),
				'couldNotDisconnect' => __( 'Could not disconnect. Try again.', 'kwiro-ai-sales-agent' ),
				'networkError'       => __( 'Network error. Try again.', 'kwiro-ai-sales-agent' ),
				'disconnectConfirm'  => __( 'Disconnect this store from Kwiro? Your locally stored API key will be deleted. The chat widget will stop appearing on your storefront. You can reconnect any time.', 'kwiro-ai-sales-agent' ),
				'show'               => __( 'Show', 'kwiro-ai-sales-agent' ),
				'hide'               => __( 'Hide', 'kwiro-ai-sales-agent' ),
				'copied'             => __( 'Copied!', 'kwiro-ai-sales-agent' ),
				'copyFailed'         => __( 'Copy failed', 'kwiro-ai-sales-agent' ),
			),
		);
		wp_add_inline_script(
			'kwiro-admin-connect',
			'window.kwiroAdminConnect = ' . wp_json_encode( $connect_data ) . ';',
			'before'
		);
	}

	/**
	 * Redirect to settings page after activation.
	 */
	public function handle_activation_redirect() {
		if ( get_transient( 'kwiro_activated' ) ) {
			delete_transient( 'kwiro_activated' );
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- WordPress core activation redirect pattern.
			if ( ! isset( $_GET['activate-multi'] ) ) {
				wp_safe_redirect( admin_url( 'admin.php?page=kwiro-settings' ) );
				exit;
			}
		}
	}

	/**
	 * Render the settings page.
	 */
	public function render_settings_page() {
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return;
		}

		$api_key       = get_option( 'kwiro_api_key', '' );
		$sync_status   = get_option( 'kwiro_sync_status', array() );
		$register_err  = get_transient( 'kwiro_register_backoff' );
		$dashboard_url = 'https://app.kwiro.ai';
		if ( defined( 'KWIRO_DASHBOARD_URL' ) ) {
			$dashboard_url = KWIRO_DASHBOARD_URL;
		}
		$user_consented = (bool) get_option( 'kwiro_user_consented' );
		$privacy_url    = 'https://kwiro.ai/privacy';
		$terms_url      = 'https://kwiro.ai/terms';

		// Ask cloud whether a user has already claimed this store. On network
		// failure this returns the last cached value or linked=false, so the
		// UI gracefully degrades to "Connect" rather than getting stuck.
		// Force-fresh when the user just returned from the dashboard.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only UX hint, no state change. The query param only triggers a refresh of cached link-status data; nothing it controls is security-sensitive.
		$force_refresh  = isset( $_GET['kwiro_connected'] );
		$link_status    = $api_key ? $this->api_client->get_link_status( $force_refresh ) : array( 'linked' => false );
		$already_linked = ! empty( $link_status['linked'] );
		$linked_email   = $link_status['user_email'] ?? null;
		?>
		<div class="wrap">
			<h1><?php echo esc_html__( 'Kwiro — AI Sales Agent & Chatbot for WooCommerce', 'kwiro-ai-sales-agent' ); ?></h1>

			<?php if ( empty( $api_key ) && ! $user_consented ) : ?>
			<div class="notice" style="border-left-color:#d97757;border-left-width:4px;border-left-style:solid;background:#fff;padding:18px 20px;">
				<p style="font-size:16px;margin:0 0 10px;font-weight:600;color:#1d2327;">
					<?php echo esc_html__( 'Connect this store to Kwiro', 'kwiro-ai-sales-agent' ); ?>
				</p>
				<p style="font-size:13px;margin:0 0 12px;color:#3c434a;line-height:1.55;">
					<?php echo esc_html__( 'Clicking the button below will register this WooCommerce store with Kwiro, a third-party AI service operated by Inteliweave. AI inference is routed through a privacy-preserving gateway with zero data retention — prompts and responses are not stored by the gateway. Until you click, this plugin sends no data anywhere.', 'kwiro-ai-sales-agent' ); ?>
				</p>
				<p style="font-size:13px;margin:0 0 8px;color:#3c434a;line-height:1.55;">
					<strong><?php echo esc_html__( 'What gets sent on first connect:', 'kwiro-ai-sales-agent' ); ?></strong>
					<?php echo esc_html__( 'your store domain, your store name, and your WooCommerce currency code.', 'kwiro-ai-sales-agent' ); ?>
				</p>
				<p style="font-size:13px;margin:0 0 14px;color:#3c434a;line-height:1.55;">
					<strong><?php echo esc_html__( 'What gets sent later:', 'kwiro-ai-sales-agent' ); ?></strong>
					<?php echo esc_html__( 'product catalog data when you click Sync Now, paid-order summaries (totals, products, currency — never customer PII) for ROI tracking, and chat messages while shoppers are using the widget.', 'kwiro-ai-sales-agent' ); ?>
				</p>
				<p style="margin:0 0 14px;font-size:12px;color:#50575e;">
					<a href="<?php echo esc_url( $terms_url ); ?>" target="_blank" rel="noopener"><?php echo esc_html__( 'Terms of Service', 'kwiro-ai-sales-agent' ); ?></a>
					&nbsp;·&nbsp;
					<a href="<?php echo esc_url( $privacy_url ); ?>" target="_blank" rel="noopener"><?php echo esc_html__( 'Privacy Policy', 'kwiro-ai-sales-agent' ); ?></a>
				</p>
				<p style="margin:0;">
					<button type="button" class="button button-primary button-hero" id="kwiro-grant-consent" style="background:#d97757;border-color:#d97757;box-shadow:0 2px 6px rgba(217,119,87,0.25);">
						<?php echo esc_html__( 'Connect to Kwiro →', 'kwiro-ai-sales-agent' ); ?>
					</button>
					<span id="kwiro-consent-status" aria-live="polite" style="margin-left:10px;color:#50575e;font-size:12px;"></span>
				</p>
				<p style="margin:14px 0 0;font-size:12px;color:#50575e;">
					<a href="#kwiro-api-key" id="kwiro-show-manual-pre" style="color:#50575e;text-decoration:none;border-bottom:1px dashed #8c8f94;">
						<?php echo esc_html__( 'Already have a Kwiro API key? Paste it instead', 'kwiro-ai-sales-agent' ); ?>
					</a>
				</p>
			</div>
				<?php
			elseif ( empty( $api_key ) && $register_err ) :
				// "Domain already linked to a Kwiro account" is a recoverable
				// path: signing into the dashboard with the original email
				// gets the merchant their existing API key, and pasting it
				// into the Connection panel below finishes the connect flow.
				// We detect this case via the option set in
				// ajax_grant_consent() / maybe_auto_register() when register
				// returned the STORE_CLAIMED 409. See class-api-client.php
				// register_store() for the cloud-side contract.
				$claim_state = get_option( 'kwiro_register_claim_state', array() );
				$is_claimed  = is_array( $claim_state ) && ! empty( $claim_state['recoverable'] );
				$claim_email = $is_claimed && ! empty( $claim_state['claimed_by_email'] ) ? $claim_state['claimed_by_email'] : '';
				?>
			<div class="notice <?php echo esc_attr( $is_claimed ? 'notice-warning' : 'notice-error' ); ?>" style="<?php echo esc_attr( $is_claimed ? 'border-left-color:#dba617;border-left-width:4px;background:#fff;padding:14px 16px;' : '' ); ?>">
				<?php if ( $is_claimed ) : ?>
					<p style="font-size:15px;margin:0 0 8px;font-weight:600;color:#1d2327;">
						<?php echo esc_html__( 'This domain is already linked to a Kwiro account.', 'kwiro-ai-sales-agent' ); ?>
					</p>
					<p style="margin:0 0 10px;color:#3c434a;line-height:1.55;">
						<?php
						if ( $claim_email ) {
							printf(
								/* translators: %s: redacted email like u***@example.com */
								esc_html__( 'It looks like %s already claimed this WooCommerce install in the Kwiro dashboard. To reconnect: open the dashboard, sign in with that email, copy your existing API key from Settings → API, then paste it into the Connection panel below.', 'kwiro-ai-sales-agent' ),
								'<strong>' . esc_html( $claim_email ) . '</strong>'
							);
						} else {
							echo esc_html__( 'Open the Kwiro dashboard, sign in with the email you used when you first claimed this store, copy your existing API key from Settings → API, then paste it into the Connection panel below.', 'kwiro-ai-sales-agent' );
						}
						?>
					</p>
					<p style="margin:0;">
						<a href="<?php echo esc_url( $dashboard_url ); ?>" target="_blank" rel="noopener" class="button button-primary">
							<?php echo esc_html__( 'Open Kwiro dashboard →', 'kwiro-ai-sales-agent' ); ?>
						</a>
						&nbsp;
						<a href="<?php echo esc_url( add_query_arg( 'kwiro_retry', '1' ) ); ?>" class="button">
							<?php echo esc_html__( 'Try again', 'kwiro-ai-sales-agent' ); ?>
						</a>
						&nbsp;
						<a href="mailto:support@kwiro.ai?subject=Lost%20access%20to%20Kwiro%20account" class="button-link">
							<?php echo esc_html__( 'Lost access to that email? Contact support', 'kwiro-ai-sales-agent' ); ?>
						</a>
					</p>
				<?php else : ?>
					<p>
						<strong><?php echo esc_html__( 'Couldn\'t register this store with Kwiro.', 'kwiro-ai-sales-agent' ); ?></strong>
						<?php echo esc_html( $register_err ); ?>
					</p>
					<p>
						<a href="<?php echo esc_url( add_query_arg( 'kwiro_retry', '1' ) ); ?>" class="button">
							<?php echo esc_html__( 'Retry', 'kwiro-ai-sales-agent' ); ?>
						</a>
					</p>
				<?php endif; ?>
			</div>
			<?php elseif ( ! empty( $api_key ) && $already_linked ) : ?>
			<div class="notice" style="border-left-color:#00a32a;border-left-width:4px;border-left-style:solid;background:#fff;padding:16px 18px;">
				<p style="font-size:15px;margin:0 0 6px;font-weight:600;color:#1d2327;display:flex;align-items:center;gap:8px;">
					<span style="display:inline-flex;width:22px;height:22px;border-radius:99px;background:#00a32a;color:#fff;align-items:center;justify-content:center;font-size:13px;font-weight:700;">✓</span>
					<?php echo esc_html__( 'Connected to your Kwiro account', 'kwiro-ai-sales-agent' ); ?>
				</p>
				<p style="font-size:13px;margin:0 0 14px;color:#50575e;line-height:1.5;">
					<?php
					if ( $linked_email ) {
						printf(
							/* translators: %s = email the store is linked to */
							esc_html__( 'This store is linked to %s. Open the dashboard to review conversations, revenue, and widget settings.', 'kwiro-ai-sales-agent' ),
							'<strong style="color:#1d2327;">' . esc_html( $linked_email ) . '</strong>'
						);
					} else {
						echo esc_html__( 'This store is linked to a Kwiro account. Open the dashboard to review conversations, revenue, and widget settings.', 'kwiro-ai-sales-agent' );
					}
					?>
				</p>
				<p style="margin:0;">
					<a href="<?php echo esc_url( $dashboard_url ); ?>" target="_blank" rel="noopener" class="button button-primary" style="background:#d97757;border-color:#d97757;">
						<?php echo esc_html__( 'Open Kwiro dashboard →', 'kwiro-ai-sales-agent' ); ?>
					</a>
					<button type="button" class="button button-link-delete" id="kwiro-disconnect" style="margin-left:10px;color:#b32d2e;">
						<?php echo esc_html__( 'Disconnect this store', 'kwiro-ai-sales-agent' ); ?>
					</button>
					<span id="kwiro-disconnect-status" aria-live="polite" style="margin-left:10px;color:#50575e;font-size:12px;"></span>
				</p>
				<p style="margin:14px 0 0;font-size:12px;color:#50575e;">
					<a href="<?php echo esc_url( $terms_url ); ?>" target="_blank" rel="noopener"><?php echo esc_html__( 'Terms', 'kwiro-ai-sales-agent' ); ?></a>
					&nbsp;·&nbsp;
					<a href="<?php echo esc_url( $privacy_url ); ?>" target="_blank" rel="noopener"><?php echo esc_html__( 'Privacy Policy', 'kwiro-ai-sales-agent' ); ?></a>
				</p>
			</div>
			<?php elseif ( ! empty( $api_key ) ) : ?>
			<div class="notice" style="border-left-color:#d97757;border-left-width:4px;border-left-style:solid;background:#fff;padding:16px 18px;">
				<p style="font-size:15px;margin:0 0 6px;font-weight:600;color:#1d2327;">
					<?php echo esc_html__( 'Link this store to your Kwiro account', 'kwiro-ai-sales-agent' ); ?>
				</p>
				<p style="font-size:13px;margin:0 0 14px;color:#50575e;line-height:1.5;">
					<?php echo esc_html__( 'One click opens your Kwiro dashboard signed-in, links this store automatically, and brings you back. No copy-paste needed.', 'kwiro-ai-sales-agent' ); ?>
				</p>
				<p style="margin:0;">
					<button type="button" class="button button-primary button-hero" id="kwiro-connect-kwiro" style="background:#d97757;border-color:#d97757;box-shadow:0 2px 6px rgba(217,119,87,0.25);">
						<?php echo esc_html__( 'Connect to Kwiro →', 'kwiro-ai-sales-agent' ); ?>
					</button>
					<span id="kwiro-connect-status" aria-live="polite" style="margin-left:10px;color:#50575e;font-size:12px;"></span>
				</p>
				<p style="margin:12px 0 0;font-size:12px;color:#50575e;">
					<a href="#kwiro-api-key" id="kwiro-show-manual" style="color:#50575e;text-decoration:none;border-bottom:1px dashed #8c8f94;">
						<?php echo esc_html__( 'Or link by pasting the API key manually', 'kwiro-ai-sales-agent' ); ?>
					</a>
				</p>
			</div>
			<?php endif; ?>

			<div class="kwiro-status-bar">
				<span class="kwiro-status-label"><?php echo esc_html__( 'Sync Status:', 'kwiro-ai-sales-agent' ); ?></span>
				<span class="kwiro-status-value kwiro-status-<?php echo esc_attr( $sync_status['status'] ?? 'pending' ); ?>">
					<?php echo esc_html( ucfirst( $sync_status['status'] ?? 'pending' ) ); ?>
				</span>
				<?php if ( ! empty( $sync_status['last_sync'] ) ) : ?>
					<span class="kwiro-last-sync">
						<?php
						printf(
							/* translators: %s: Human-readable time difference */
							esc_html__( 'Last sync: %s ago', 'kwiro-ai-sales-agent' ),
							esc_html( human_time_diff( strtotime( $sync_status['last_sync'] ) ) )
						);
						?>
					</span>
				<?php endif; ?>
				<button type="button" class="button kwiro-sync-btn" id="kwiro-trigger-sync">
					<?php echo esc_html__( 'Sync Now', 'kwiro-ai-sales-agent' ); ?>
				</button>
				<span id="kwiro-sync-status"></span>
			</div>

			<form method="post" action="options.php">
				<?php
				settings_fields( 'kwiro_settings_group' );
				do_settings_sections( 'kwiro-settings' );
				submit_button();
				?>
			</form>
		</div>
		<?php
	}

	/**
	 * Render connection section description.
	 */
	public function render_connection_section() {
		echo '<p>' . esc_html__( 'This API key proves that you own this WordPress install. Paste it into your Kwiro dashboard to link the two.', 'kwiro-ai-sales-agent' ) . '</p>';
	}

	/**
	 * Render widget section description — explains where widget appearance
	 * settings have moved.
	 */
	public function render_widget_section() {
		$url = defined( 'KWIRO_DASHBOARD_URL' ) ? KWIRO_DASHBOARD_URL : 'https://app.kwiro.ai';
		printf(
			'<p>%s <a href="%s" target="_blank" rel="noopener">%s</a></p>',
			esc_html__( 'Widget appearance (color, position, welcome message, branding, bot name) is now managed from your Kwiro dashboard so changes go live without editing WordPress.', 'kwiro-ai-sales-agent' ),
			esc_url( $url ),
			esc_html__( 'Manage widget in Kwiro dashboard →', 'kwiro-ai-sales-agent' )
		);
	}

	/**
	 * Render API key field with Show / Copy buttons.
	 *
	 * The API key is the proof-of-ownership token the store owner pastes into
	 * the Kwiro dashboard to claim their store. Keeping it hidden by default
	 * protects it from shoulder-surfing, but we have to give admins an easy
	 * way to reveal + copy it or onboarding breaks.
	 */
	public function render_api_key_field() {
		$api_key = get_option( 'kwiro_api_key', '' );
		?>
		<div class="kwiro-api-key-group" style="display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap;">
			<input type="password"
					name="kwiro_api_key"
					id="kwiro_api_key"
					value="<?php echo esc_attr( $api_key ); ?>"
					class="regular-text code"
					autocomplete="off"
					readonly
					style="font-family:monospace;min-width:320px;"
			/>
			<button type="button" class="button" id="kwiro-toggle-api-key" aria-pressed="false">
				<?php echo esc_html__( 'Show', 'kwiro-ai-sales-agent' ); ?>
			</button>
			<button type="button" class="button button-primary" id="kwiro-copy-api-key">
				<?php echo esc_html__( 'Copy', 'kwiro-ai-sales-agent' ); ?>
			</button>
			<button type="button" class="button" id="kwiro-test-connection">
				<?php echo esc_html__( 'Test Connection', 'kwiro-ai-sales-agent' ); ?>
			</button>
			<span id="kwiro-connection-status" aria-live="polite"></span>
		</div>
		<p class="description" style="margin-top:8px;">
			<?php echo esc_html__( 'Paste this key into your Kwiro dashboard to link this store.', 'kwiro-ai-sales-agent' ); ?>
			<a href="https://app.kwiro.ai" target="_blank" rel="noopener noreferrer">
				<?php echo esc_html__( 'Open dashboard →', 'kwiro-ai-sales-agent' ); ?>
			</a>
		</p>
		<?php
	}

	/**
	 * Render widget enabled field.
	 */
	public function render_widget_enabled_field() {
		$settings = get_option( 'kwiro_settings', array() );
		$enabled  = $settings['widget_enabled'] ?? true;
		?>
		<label>
			<input type="checkbox"
					name="kwiro_settings[widget_enabled]"
					value="1"
					<?php checked( $enabled ); ?>
			/>
			<?php echo esc_html__( 'Show the chat widget on your store', 'kwiro-ai-sales-agent' ); ?>
		</label>
		<?php
	}


	/**
	 * AJAX handler: test connection to cloud API.
	 */
	public function ajax_test_connection() {
		check_ajax_referer( 'kwiro_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( __( 'Unauthorized.', 'kwiro-ai-sales-agent' ) );
		}

		// Save the API key from the form field so we test the current value, not the old one.
		if ( ! empty( $_POST['api_key'] ) ) {
			update_option( 'kwiro_api_key', sanitize_text_field( wp_unslash( $_POST['api_key'] ) ) );
			delete_transient( 'kwiro_jwt_token' );
		}

		$result = $this->api_client->test_connection();

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( __( 'Connection failed. Please check your API key and try again.', 'kwiro-ai-sales-agent' ) );
		}

		wp_send_json_success( __( 'Connection successful!', 'kwiro-ai-sales-agent' ) );
	}

	/**
	 * AJAX handler: trigger manual sync.
	 *
	 * Runs as many batches inline as fit in a ~25-second time budget so the
	 * sync doesn't depend on WP CRON to finish — typical 100-product
	 * catalogs complete in 1-2 batches inside this AJAX call. Anything
	 * beyond the budget falls back to the scheduled `kwiro_batch_sync` CRON
	 * event for background processing.
	 *
	 * Resume semantics: if a previous sync already pushed N products and
	 * stalled (e.g. CRON didn't fire on a dev VPS / a host that disables
	 * WP CRON), clicking Sync Now picks up at the saved offset instead of
	 * re-pushing everything from 0. The user always has a way to unstick.
	 */
	public function ajax_trigger_sync() {
		check_ajax_referer( 'kwiro_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( __( 'Unauthorized.', 'kwiro-ai-sales-agent' ) );
		}

		// Reset any stuck / errored state so the user can always retry.
		delete_transient( 'kwiro_sync_lock' );

		// Record the manual-sync event in the dashboard's sync history.
		$audit_client = new Kwiro_API_Client();
		$audit_client->log_sync_event( 'manual_sync', array( 'trigger_source' => 'manual' ) );

		$settings   = get_option( 'kwiro_settings', array() );
		$batch_size = max( 10, min( 200, (int) ( $settings['batch_size'] ?? 50 ) ) );

		// Resume from saved offset when a sync is mid-flight; otherwise
		// start from 0. "syncing" with offset>0 means a previous run
		// pushed N products and either CRON failed to fire batches 2..N
		// (common on dev VPS / no-traffic stores / DISABLE_WP_CRON hosts)
		// or the user navigated away mid-sync.
		$prev_status  = get_option( 'kwiro_sync_status', array() );
		$prev_state   = (string) ( $prev_status['status'] ?? '' );
		$prev_offset  = (int) ( $prev_status['offset'] ?? 0 );
		$resume       = ( 'syncing' === $prev_state || 'error' === $prev_state ) && $prev_offset > 0;
		$offset       = $resume ? $prev_offset : 0;
		$synced_total = $resume ? (int) ( $prev_status['products_count'] ?? $prev_offset ) : 0;

		$start_time      = microtime( true );
		$time_budget_sec = 25.0;  // PHP max_execution_time default is 30s; leave 5s headroom.
		$batches_run     = 0;
		$total           = 0;
		$total_batches   = 0;
		$has_more        = false;

		while ( true ) {
			$result        = $this->product_sync->get_products_by_offset( $offset, $batch_size );
			$total         = (int) $result['total'];
			$total_batches = max( 1, (int) ceil( $total / max( 1, $batch_size ) ) );

			if ( empty( $result['products'] ) ) {
				// Nothing left — finalize.
				update_option(
					'kwiro_sync_status',
					array(
						'last_sync'      => current_time( 'mysql', true ),
						'products_count' => $synced_total,
						'status'         => 'complete',
						'total'          => $total ? $total : $synced_total,
					)
				);
				$this->finalize_sync_with_cloud();
				wp_send_json_success(
					array(
						'message'        => $synced_total
							? sprintf(
								/* translators: %d: products synced */
								__( 'Sync complete — %d products.', 'kwiro-ai-sales-agent' ),
								$synced_total
							)
							: __( 'No products to sync. Add some products in WooCommerce first.', 'kwiro-ai-sales-agent' ),
						'products_count' => $synced_total,
						'total'          => $total ? $total : $synced_total,
						'status'         => 'complete',
					)
				);
			}

			$batch_count = count( $result['products'] );
			++$batches_run;
			$batch_number = (int) floor( $offset / max( 1, $batch_size ) ) + 1;

			$sync_result = $this->product_sync->sync_batch(
				$result['products'],
				array(
					'batch_number'   => $batch_number,
					'total_batches'  => $total_batches,
					'trigger_source' => 'manual',
				)
			);

			if ( is_wp_error( $sync_result ) ) {
				update_option(
					'kwiro_sync_status',
					array(
						'last_sync'      => $prev_status['last_sync'] ?? null,
						'products_count' => $synced_total,
						'status'         => 'error',
						'error_message'  => $sync_result->get_error_message(),
						'offset'         => $offset,
						'total'          => $total,
					)
				);
				wp_send_json_error( $sync_result->get_error_message() );
			}

			$synced_total += $batch_count;
			$offset       += $batch_count;
			$has_more      = ! empty( $result['has_more'] );

			update_option(
				'kwiro_sync_status',
				array(
					'last_sync'      => $has_more ? null : current_time( 'mysql', true ),
					'products_count' => $synced_total,
					'status'         => $has_more ? 'syncing' : 'complete',
					'offset'         => $offset,
					'total'          => $total,
					'trigger_source' => 'manual',
				)
			);

			if ( ! $has_more ) {
				break;
			}

			// Budget guard: leave at least one batch's worth of headroom so
			// we don't time out mid-batch and corrupt the offset.
			$elapsed = microtime( true ) - $start_time;
			if ( $elapsed > $time_budget_sec ) {
				break;
			}
		}

		if ( $has_more ) {
			// Schedule the rest via WP CRON. spawn_cron kicks WP's pseudo-cron
			// immediately instead of waiting for the next site visitor; if it
			// fails (host firewall blocking same-origin HTTP, DISABLE_WP_CRON,
			// etc.) the merchant can simply hit Sync Now again — we'll resume
			// from the saved offset.
			wp_schedule_single_event( time(), 'kwiro_batch_sync' );
			if ( function_exists( 'spawn_cron' ) ) {
				spawn_cron();
			}
			wp_send_json_success(
				array(
					'message'        => sprintf(
					/* translators: 1: products synced, 2: total products, 3: batches run */
						__( 'Synced %1$d of %2$d products in %3$d batch(es). Click Sync Now again if progress stalls.', 'kwiro-ai-sales-agent' ),
						$synced_total,
						$total,
						$batches_run
					),
					'products_count' => $synced_total,
					'total'          => $total,
					'status'         => 'syncing',
				)
			);
		}

		// All batches finished inside the budget — nothing to schedule.
		update_option(
			'kwiro_sync_status',
			array(
				'last_sync'      => current_time( 'mysql', true ),
				'products_count' => $synced_total,
				'status'         => 'complete',
				'total'          => $total,
			)
		);
		$this->finalize_sync_with_cloud();

		wp_send_json_success(
			array(
				'message'        => sprintf(
				/* translators: %d: products synced */
					__( 'Synced %d products successfully.', 'kwiro-ai-sales-agent' ),
					$synced_total
				),
				'products_count' => $synced_total,
				'total'          => $total,
				'status'         => 'complete',
			)
		);
	}

	/**
	 * Tell the cloud the inline batch loop has ended so it can flip the
	 * store to complete, run a final embedding pass, and trigger the
	 * `enrichStore` job that builds `store_intelligence` (which the widget
	 * greeting reads). The legacy CRON-driven path called this from
	 * `Kwiro_Batch_Processor::complete_sync()` — the inline-loop refactor
	 * in 1.2.3 inadvertently dropped it, which is why merchants saw "I
	 * know all 0 products here" even after a successful sync.
	 *
	 * Piggybacks the WooCommerce free-shipping threshold so the widget's
	 * MiniCart can render "X away from free shipping" without a separate
	 * round-trip — exactly what `complete_sync()` did before.
	 *
	 * Silent on failure; the cloud also embeds + enriches on schedule, so
	 * the catalog won't stay un-indexed if this one call drops.
	 */
	private function finalize_sync_with_cloud() {
		try {
			$client    = new Kwiro_API_Client();
			$threshold = $this->product_sync->get_free_shipping_threshold();
			$client->sync_finalize( array( 'free_shipping_threshold' => $threshold ) );
		} catch ( \Throwable $e ) {
			// Don't bubble — finalize is best-effort. The cloud's hourly
			// enrichment cron will pick up whatever this call would have
			// pushed, so a network blip here is a UX nit, not data loss.
			unset( $e );
		}
	}

	/**
	 * AJAX handler: start the one-click Connect-to-Kwiro flow. Exchanges the
	 * stored API key for a short-lived link token and returns the dashboard
	 * URL for the browser to open.
	 */
	public function ajax_begin_link() {
		check_ajax_referer( 'kwiro_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( __( 'Unauthorized.', 'kwiro-ai-sales-agent' ) );
		}

		$result = $this->api_client->begin_link();

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( $result->get_error_message() );
		}

		$dashboard_url = defined( 'KWIRO_DASHBOARD_URL' ) ? KWIRO_DASHBOARD_URL : 'https://app.kwiro.ai';
		$connect_url   = add_query_arg( 'link_token', rawurlencode( $result['link_token'] ), $dashboard_url );

		wp_send_json_success(
			array(
				'connect_url' => $connect_url,
				'expires_in'  => $result['expires_in'] ?? 600,
			)
		);
	}

	/**
	 * AJAX handler: get sync progress.
	 */
	public function ajax_sync_progress() {
		check_ajax_referer( 'kwiro_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( __( 'Unauthorized.', 'kwiro-ai-sales-agent' ) );
		}

		$sync_status = get_option( 'kwiro_sync_status', array() );

		wp_send_json_success(
			array(
				'status'         => $sync_status['status'] ?? 'pending',
				'products_count' => $sync_status['products_count'] ?? 0,
				'total'          => $sync_status['total'] ?? 0,
				'last_sync'      => $sync_status['last_sync'] ?? null,
				'error_message'  => $sync_status['error_message'] ?? null,
			)
		);
	}

	/**
	 * AJAX handler: record the owner's explicit consent to send store data
	 * to api.kwiro.ai (DG #7). Once set, maybe_auto_register() runs on the
	 * next settings-page load and the standard connect flow takes over.
	 */
	public function ajax_grant_consent() {
		check_ajax_referer( 'kwiro_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( __( 'Unauthorized.', 'kwiro-ai-sales-agent' ) );
		}

		update_option( 'kwiro_user_consented', current_time( 'mysql', true ), false );
		wp_send_json_success( array( 'consented' => true ) );
	}

	/**
	 * AJAX handler: disconnect this store from Kwiro. Purges the locally
	 * stored API key, store ID, public key, JWT cache, link-status cache,
	 * and the consent flag — leaving the install in the same state as a
	 * fresh activation, so re-clicking Connect re-prompts for consent.
	 *
	 * Account-side cleanup (revoking the API key, unlinking the store)
	 * happens in the Kwiro dashboard; this method intentionally avoids a
	 * round-trip to the cloud so disconnect works even when api.kwiro.ai
	 * is unreachable — owners should always be able to remove the
	 * integration locally without depending on us.
	 */
	public function ajax_disconnect() {
		check_ajax_referer( 'kwiro_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_send_json_error( __( 'Unauthorized.', 'kwiro-ai-sales-agent' ) );
		}

		delete_option( 'kwiro_api_key' );
		delete_option( 'kwiro_store_id' );
		delete_option( 'kwiro_public_key' );
		delete_option( 'kwiro_user_consented' );
		delete_option( 'kwiro_register_claim_state' );
		delete_transient( 'kwiro_jwt_token' );
		delete_transient( 'kwiro_link_status' );
		delete_transient( 'kwiro_register_backoff' );

		wp_send_json_success( array( 'disconnected' => true ) );
	}
}
