<?php
/**
 * Admin UI: menus, session list, variance report, live count screen.
 *
 * @package Stocktake_For_WooCommerce
 */

defined( 'ABSPATH' ) || exit;

class STWC_Admin {

	const CAP  = 'manage_woocommerce';
	const SLUG = 'stwc-sessions';

	private $hook = '';

	public function __construct() {
		add_action( 'admin_menu', array( $this, 'menu' ) );
		add_action( 'admin_init', array( $this, 'handle_actions' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'assets' ) );
		// `current_screen`, not `in_admin_header`: the body tag is printed by
		// admin-header.php, so a filter added from in_admin_header arrives after
		// the class list is already rendered and never lands. The old code got
		// away with it because remove_all_actions() acted on output that comes
		// later; hiding needs the class, and the class needs to be early.
		add_action( 'current_screen', array( $this, 'clean_notices' ) );
		add_filter( 'admin_body_class', array( $this, 'body_class' ) );
		add_filter( 'submenu_file', array( $this, 'submenu_highlight' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'menu_icon_css' ) );
	}

	/**
	 * Full-colour brand icon for the admin menu. Registered as 'none' and
	 * painted via CSS with a URL-ENCODED (not base64) SVG data URI on purpose:
	 * core's svg-painter.js repaints base64 menu icons to the admin colour
	 * scheme, which flattens the gradient into a solid white square.
	 */
	public function menu_icon_css() {
		wp_enqueue_style( 'stwc-menu-icon', STWC_URL . 'assets/css/menu.css', array(), $this->asset_ver( 'assets/css/menu.css' ) );
	}

	/**
	 * Cache-busting asset version: the file's mtime, so a stale browser or
	 * proxy cache can never serve last release's CSS/JS after an update.
	 *
	 * @param string $rel Path relative to the plugin root.
	 * @return string
	 */
	private function asset_ver( $rel ) {
		$mtime = @filemtime( STWC_PATH . $rel ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- missing file falls back to the plugin version.
		return $mtime ? STWC_VERSION . '.' . $mtime : STWC_VERSION;
	}

	/**
	 * Flag our screen when the effective plugin language is right-to-left so the
	 * UI can mirror itself (without affecting the rest of wp-admin).
	 *
	 * @param string $classes
	 * @return string
	 */
	public function body_class( $classes ) {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( $screen && $this->hook === $screen->id && stwc_is_rtl_locale( stwc_effective_locale() ) ) {
			$classes .= ' stwc-locale-rtl';
		}
		return $classes;
	}

	/**
	 * On our own screens, remove third-party admin notices (theme nags, cache
	 * messages, etc.). WordPress relocates notices after the first H1 — inside
	 * our hero — so they'd otherwise break the UI. They remain visible on every
	 * other admin page; our own messages are rendered inline and unaffected.
	 */
	public function clean_notices() {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( ! $screen || $this->hook !== $screen->id ) {
			return;
		}
		// Hidden with CSS, never unhooked. remove_all_actions() on the notice hooks
		// takes WordPress core's update and PHP-version warnings and WooCommerce's
		// "database upgrade required" with it - a shopkeeper mid-count is exactly
		// who must not silently miss "your site needs a security update", and the
		// notice is gone for that request whether or not they ever look at ours.
		// Hiding on our own body class keeps every callback intact: the notices
		// stay in the DOM, stay reachable to screen readers, and come back the
		// moment the user leaves this screen. (Ledger R4-4 - this exact pattern
		// has already cost one wp.org review; Retail POS carries the same fix.)
		add_filter(
			'admin_body_class',
			static function ( $classes ) {
				return trim( $classes . ' stwc-quiet-notices' );
			}
		);
	}

	public function menu() {
		// Managers get everything; users holding only "stwc_count" (Pro team
		// counting) still get the menu, but route() limits them to counting.
		$is_manager = current_user_can( self::CAP );
		$is_counter = current_user_can( 'stwc_count' );
		$check_only = false;
		$cap        = $is_manager ? self::CAP : ( $is_counter ? 'stwc_count' : ( $check_only ? 'read' : self::CAP ) );
		// Icon is 'none' — menu_icon_css() paints the full-colour brand SVG.
		$this->hook = add_menu_page(
			__( 'Stocktake', 'stocktake-for-woocommerce' ),
			__( 'Stocktake', 'stocktake-for-woocommerce' ),
			$cap,
			self::SLUG,
			array( $this, 'route' ),
			'none',
			56
		);

		// Direct entries into the main areas. Query-string slugs land on route().
		if ( $is_manager ) {
			add_submenu_page( self::SLUG, __( 'Stocktakes', 'stocktake-for-woocommerce' ), __( 'Stocktakes', 'stocktake-for-woocommerce' ), self::CAP, self::SLUG, array( $this, 'route' ) );
			add_submenu_page( self::SLUG, __( 'Reports', 'stocktake-for-woocommerce' ), __( 'Reports', 'stocktake-for-woocommerce' ), self::CAP, 'admin.php?page=' . self::SLUG . '&view=report' );
			add_submenu_page( self::SLUG, __( 'Help & FAQ', 'stocktake-for-woocommerce' ), __( 'Help & FAQ', 'stocktake-for-woocommerce' ), self::CAP, 'admin.php?page=' . self::SLUG . '&view=help' );
			if ( ! stwc_is_pro() ) {
				// Our own localized upgrade screen — the SDK's pricing page is
				// English-only, so its submenu is hidden (main file) in favour
				// of this one; its checkout route stays in use for purchases.
				add_submenu_page( self::SLUG, __( 'Upgrade to Pro', 'stocktake-for-woocommerce' ), __( 'Upgrade', 'stocktake-for-woocommerce' ) . ' ➤', self::CAP, 'admin.php?page=' . self::SLUG . '&view=upgrade' );
			}
		} elseif ( $is_counter ) {
			// Count staff: their stocktakes plus — when granted — the quick
			// stock check.
			add_submenu_page( self::SLUG, __( 'Stocktakes', 'stocktake-for-woocommerce' ), __( 'Stocktakes', 'stocktake-for-woocommerce' ), 'stwc_count', self::SLUG, array( $this, 'route' ) );
		}
	}

	/**
	 * Keep the right submenu item highlighted for our query-string views.
	 *
	 * @param string|null $submenu_file
	 * @return string|null
	 */
	public function submenu_highlight( $submenu_file ) {
		if ( self::SLUG !== ( $_GET['page'] ?? '' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput -- strict comparison against a literal; value never used.
			return $submenu_file;
		}
		$view = sanitize_key( $_GET['view'] ?? 'list' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$map = array(
			'help'    => 'help',
			'report'  => 'report',
			'upgrade' => 'upgrade',
		);
		if ( isset( $map[ $view ] ) ) {
			return 'admin.php?page=' . self::SLUG . '&view=' . $map[ $view ];
		}
		return $submenu_file;
	}

	/**
	 * Enqueue assets only on our screen.
	 *
	 * @param string $hook
	 */
	public function assets( $hook ) {
		// The Freemius-rendered pricing page runs on its own hook; give it the
		// premium skin and stop — none of our screen assets belong there.
		if ( isset( $_GET['page'] ) && 'stwc-sessions-pricing' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen check.
			wp_enqueue_style( 'stwc-pricing', STWC_URL . 'assets/css/pricing.css', array(), STWC_VERSION );
			return;
		}

		if ( $hook !== $this->hook ) {
			return;
		}

		wp_enqueue_style( 'stwc-admin', STWC_URL . 'assets/css/admin.css', array(), $this->asset_ver( 'assets/css/admin.css' ) );
		wp_enqueue_script( 'stwc-admin', STWC_URL . 'assets/js/admin.js', array(), $this->asset_ver( 'assets/js/admin.js' ), true );

		if ( 'upgrade' === ( $_GET['view'] ?? '' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput -- strict comparison against a literal; value never used.
			wp_enqueue_style( 'stwc-upgrade', STWC_URL . 'assets/css/upgrade.css', array(), $this->asset_ver( 'assets/css/upgrade.css' ) );
		}


		if ( 'count' === ( $_GET['view'] ?? '' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput -- strict comparison against a literal; value never used.
			$session_id = absint( $_GET['session'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$session    = STWC_Sessions::get_session( $session_id );

			// ZXing: robust 1D/2D decoding for the phone camera (iOS fallback;
			// Android uses the native BarcodeDetector first). Bundled locally
			// (MIT licensed) — never loaded from a CDN, per WP.org guidelines.
			wp_enqueue_script( 'stwc-zxing', STWC_URL . 'assets/js/vendor/zxing.min.js', array(), '0.23.0', true );
			wp_enqueue_script( 'stwc-count', STWC_URL . 'assets/js/count.js', array( 'stwc-zxing' ), STWC_VERSION, true );
			wp_localize_script(
				'stwc-count',
				'STWC',
				array(
					'root'      => esc_url_raw( rest_url( 'stwc/v1/' ) ),
					'nonce'     => wp_create_nonce( 'wp_rest' ),
					'sessionId' => $session_id,
					'maxQty'    => STWC_REST::MAX_QTY,
					'mode'      => $session ? $session['count_mode'] : 'partial',
					'blind'     => $session ? (int) $session['blind'] : 0,
					'i18n'      => array(
						'notFound'  => __( 'No product matches this code.', 'stocktake-for-woocommerce' ),
						'addedOutside' => __( 'This product wasn’t in your count list — added and counted.', 'stocktake-for-woocommerce' ),
						'counted'   => __( 'Counted', 'stocktake-for-woocommerce' ),
						'expected'  => __( 'Expected', 'stocktake-for-woocommerce' ),
						'variance'  => __( 'Variance', 'stocktake-for-woocommerce' ),
						'saveError' => __( 'Could not save. Try again.', 'stocktake-for-woocommerce' ),
						'countedQty'     => __( 'Counted quantity', 'stocktake-for-woocommerce' ),
						'alreadyCounted' => __( 'Already counted — added on top:', 'stocktake-for-woocommerce' ),
						'savesAuto'      => __( 'Edits save automatically.', 'stocktake-for-woocommerce' ),
						'remove'        => __( 'Remove from count', 'stocktake-for-woocommerce' ),
						'confirmRemove' => __( 'Remove?', 'stocktake-for-woocommerce' ),
						'emptyList'     => __( 'Scanned items will appear here.', 'stocktake-for-woocommerce' ),
						'inCount'   => __( 'In count', 'stocktake-for-woocommerce' ),
						'addNew'    => __( 'Add', 'stocktake-for-woocommerce' ),
						'noResults' => __( 'No matching products.', 'stocktake-for-woocommerce' ),
						'camTitle'  => __( 'Scan with camera', 'stocktake-for-woocommerce' ),
						'camClose'  => __( 'Close', 'stocktake-for-woocommerce' ),
						'camStart'  => __( 'Starting camera…', 'stocktake-for-woocommerce' ),
						'camAlign'  => __( 'Align the barcode with the line — or use Photo scan.', 'stocktake-for-woocommerce' ),
						'camPhoto'  => __( 'Photo scan', 'stocktake-for-woocommerce' ),
						'camZoomIn' => __( 'Zoom', 'stocktake-for-woocommerce' ),
						'camLight'  => __( 'Light', 'stocktake-for-woocommerce' ),
						'camNoCam'  => __( 'Camera could not start. Use a handheld scanner or type instead.', 'stocktake-for-woocommerce' ),
						'camNoDec'  => __( 'This browser cannot decode barcodes from the camera. Use a handheld scanner or type instead.', 'stocktake-for-woocommerce' ),
						'beta'      => __( 'Beta', 'stocktake-for-woocommerce' ),
						'camNote'   => __( 'Beta — some barcode types may not scan on every phone. Use a handheld scanner or type the SKU if a code won’t read.', 'stocktake-for-woocommerce' ),
					),
				)
			);
		}
	}

	/**
	 * Route to the correct view.
	 */
	public function route() {
		$view = sanitize_key( $_GET['view'] ?? 'list' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		// Non-managers: count staff pick a session and count; anyone granted
		// the quick stock check (by role or by person) gets that screen.
		// Everything else is managers only.
		if ( ! current_user_can( self::CAP ) ) {
			if ( 'count' === $view && current_user_can( 'stwc_count' ) ) {
				$this->render_count();
			} else {
				$this->render_list();
			}
			return;
		}

		switch ( $view ) {
			case 'session':
				$this->render_session();
				break;
			case 'count':
				$this->render_count();
				break;
			case 'help':
				$this->render_help();
				break;
			case 'upgrade':
				$this->render_upgrade();
				break;
			case 'report':
				$this->render_report();
				break;
			// The Pro-only screens simply do not exist in the free build — an
			// old bookmark lands on the stocktake list, not on a locked page.
			default:
				$this->render_list();
		}
	}

	/* ---------------------------------------------------------------------
	 * Action handling
	 * ------------------------------------------------------------------- */

	public function handle_actions() {
		// Per-user language preference — available to count-only staff too.
		$stwc_locale_ok = current_user_can( self::CAP ) || current_user_can( 'stwc_count' );
		if ( isset( $_POST['stwc_action'] ) && 'set_locale' === $_POST['stwc_action'] && $stwc_locale_ok ) {
			check_admin_referer( 'stwc_set_locale' );
			$loc = sanitize_text_field( wp_unslash( $_POST['stwc_locale'] ?? 'auto' ) );
			if ( array_key_exists( $loc, stwc_available_locales() ) ) {
				update_user_meta( get_current_user_id(), 'stwc_locale', $loc );
			}
			$back = array( 'view' => sanitize_key( $_POST['back_view'] ?? 'list' ) );
			$sess = absint( $_POST['back_session'] ?? 0 );
			if ( $sess ) {
				$back['session'] = $sess;
			}
			wp_safe_redirect( $this->url( $back ) );
			exit;
		}

		if ( ! current_user_can( self::CAP ) ) {
			return;
		}


		if ( isset( $_POST['stwc_action'] ) && 'create' === $_POST['stwc_action'] ) {
			check_admin_referer( 'stwc_create' );
			$create_args = array(
				'name'        => sanitize_text_field( wp_unslash( $_POST['name'] ?? '' ) ),
				'scope_type'  => sanitize_key( $_POST['scope_type'] ?? 'all' ),
				'scope_value' => array_map( 'absint', (array) ( $_POST['categories'] ?? array() ) ),
				'count_mode'  => sanitize_key( $_POST['count_mode'] ?? 'partial' ),
				'blind'       => ! empty( $_POST['blind'] ),
				'note'        => sanitize_textarea_field( wp_unslash( $_POST['note'] ?? '' ) ),
			);
			$session_id = STWC_Sessions::create_session( $create_args );

			if ( is_wp_error( $session_id ) ) {
				$code = 'scope';
				$target = $this->url( array( 'error' => $code ) );
			} else {
				$target = $this->url( array( 'view' => 'session', 'session' => $session_id, 'created' => 1 ) );
			}
			wp_safe_redirect( $target );
			exit;
		}



		$action = sanitize_key( $_GET['stwc_action'] ?? '' );
		if ( '' === $action ) {
			return;
		}

		// Session-independent actions.
		if ( 'reorder_export' === $action ) {
			check_admin_referer( 'stwc_reorder_export' );
			if ( ! current_user_can( self::CAP ) ) {
				wp_die( esc_html__( 'You are not allowed to export reports.', 'stocktake-for-woocommerce' ) );
			}
			STWC_Report::export_reorder_csv();
			exit;
		}
		if ( 'dismiss_review' === $action ) {
			check_admin_referer( 'stwc_dismiss_review' );
			update_option( 'stwc_review_dismissed', 1, false );
			wp_safe_redirect( $this->url() );
			exit;
		}

		$session_id = absint( $_GET['session'] ?? 0 );
		check_admin_referer( 'stwc_' . $action . '_' . $session_id );

		switch ( $action ) {
			case 'apply':
				$result = STWC_Stock::apply_session( $session_id );
				if ( $result['applied'] > 0 ) {
					// Success moments feed the (dismissible) review nudge.
					update_option( 'stwc_success_applies', (int) get_option( 'stwc_success_applies', 0 ) + 1, false );
				}
				if ( ! empty( $result['errors'] ) ) {
					set_transient( 'stwc_apply_err_' . get_current_user_id(), array_values( $result['errors'] ), 60 );
				}
				wp_safe_redirect( $this->url( array( 'view' => 'session', 'session' => $session_id, 'applied' => $result['applied'], 'skipped' => count( $result['errors'] ) ) ) );
				exit;

			case 'revert':
				$result = STWC_Stock::revert_session( $session_id );
				// Mirrors the apply branch: a restore that could not reach every
				// product must not report plain success. The restore point is
				// deliberately KEPT when errors exist, so the user can retry —
				// but only if we actually tell them something went wrong.
				if ( ! empty( $result['errors'] ) ) {
					set_transient( 'stwc_revert_err_' . get_current_user_id(), array_values( $result['errors'] ), 60 );
				}
				// Nothing restored and nothing failed means there was no restore
				// point left -- press Undo twice, or press Back, and this is where
				// you land. It is not a success and must not be drawn as one.
				$stwc_none = ( 0 === (int) $result['reverted'] && empty( $result['errors'] ) ) ? 1 : 0;
				wp_safe_redirect( $this->url( array( 'view' => 'session', 'session' => $session_id, 'reverted' => $result['reverted'], 'revert_failed' => count( $result['errors'] ), 'revert_none' => $stwc_none ) ) );
				exit;

			case 'complete':
				// Only an active stocktake can be completed — never resurrect a
				// cancelled or already-completed one from a stale tab.
				$stwc_s      = STWC_Sessions::get_session( $session_id );
				$stwc_active = array( 'open' );
				if ( $stwc_s && in_array( $stwc_s['status'], $stwc_active, true ) ) {
					STWC_Sessions::set_status( $session_id, 'completed' );
				}
				wp_safe_redirect( $this->url( array( 'view' => 'session', 'session' => $session_id ) ) );
				exit;


			case 'cancel':
				// Only an active stocktake can be cancelled.
				$stwc_s      = STWC_Sessions::get_session( $session_id );
				$stwc_active = array( 'open' );
				if ( $stwc_s && in_array( $stwc_s['status'], $stwc_active, true ) ) {
					STWC_Sessions::set_status( $session_id, 'cancelled' );
				}
				wp_safe_redirect( $this->url( array( 'view' => 'session', 'session' => $session_id, 'cancelled' => 1 ) ) );
				exit;

			case 'delete':
				STWC_Backups::delete_for_session( $session_id );
				STWC_Sessions::delete_session( $session_id );
				// An apply that died mid-run leaves its concurrency lock behind;
				// the session is gone, so the orphan option must go with it.
				delete_option( 'stwc_apply_lock_' . $session_id );
				wp_safe_redirect( $this->url( array( 'deleted' => 1 ) ) );
				exit;

			case 'export':
				$this->export_csv( $session_id );
				exit;

			case 'quickstart':
				// One-tap quick start: a partial count of everything, straight
				// to scanning. A plain link on purpose — nothing client-side
				// can swallow a navigation the way it can a form submit.
				$new_id = STWC_Sessions::create_session( array( 'scope_type' => 'all', 'count_mode' => 'partial' ) );
				if ( is_wp_error( $new_id ) ) {
					wp_safe_redirect( $this->url( array( 'error' => 'quickstart' ) ) );
				} else {
					wp_safe_redirect( $this->url( array( 'view' => 'count', 'session' => $new_id ) ) );
				}
				exit;

		}
	}


	/* ---------------------------------------------------------------------
	 * Views
	 * ------------------------------------------------------------------- */

	private function render_list() {
		$sessions   = STWC_Sessions::get_sessions();
		$is_manager = current_user_can( self::CAP );
		?>
		<div class="wrap stwc-app">
			<?php
			$quick = '';
			if ( $is_manager ) {
				$quick  = '<a class="stwc-btn stwc-btn--hero" style="text-decoration:none" href="' . esc_url( $this->action_url( 'quickstart', 0 ) ) . '">' . $this->icon( 'scan' ) . ' ' . esc_html__( 'Start counting now', 'stocktake-for-woocommerce' ) . '</a>';
				$quick .= '<a class="stwc-btn stwc-btn--hero" style="text-decoration:none" href="' . esc_url( $this->url( array( 'view' => 'report' ) ) ) . '">' . $this->icon( 'chart' ) . ' ' . esc_html__( 'Reports', 'stocktake-for-woocommerce' ) . '</a>';
			}
			$this->hero( __( 'Stocktake', 'stocktake-for-woocommerce' ), $is_manager ? __( 'Count physical stock, review variances, post adjustments.', 'stocktake-for-woocommerce' ) : __( 'Pick a stocktake below and start counting.', 'stocktake-for-woocommerce' ), $quick );
			?>

			<?php if ( isset( $_GET['deleted'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only flash flag. ?>
				<div class="stwc-flash stwc-flash--ok"><?php esc_html_e( 'Session deleted.', 'stocktake-for-woocommerce' ); ?></div>
			<?php endif; ?>
			<?php if ( isset( $_GET['error'] ) && 'scope' === $_GET['error'] ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput -- display-only flash flag compared to a literal. ?>
				<div class="stwc-flash" style="background:var(--stwc-neg-bg);color:var(--stwc-neg)"><?php esc_html_e( 'Choose at least one category, or select “All products”.', 'stocktake-for-woocommerce' ); ?></div>
			<?php endif; ?>
			<?php if ( isset( $_GET['error'] ) && 'quickstart' === $_GET['error'] ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput -- display-only flash flag compared to a literal. ?>
				<div class="stwc-flash" style="background:var(--stwc-neg-bg);color:var(--stwc-neg)"><?php esc_html_e( 'Quick start could not create a stocktake — please try again.', 'stocktake-for-woocommerce' ); ?></div>
			<?php endif; ?>
			<?php $this->maybe_review_notice(); ?>

			<div class="stwc-cols">
				<div class="stwc-col-main">
					<div class="stwc-card stwc-card--flush">
						<?php if ( empty( $sessions ) ) : ?>
							<div class="stwc-empty">
								<div class="stwc-empty__icon"><?php $this->the_icon( 'clipboard' ); ?></div>
								<h3><?php esc_html_e( 'No stocktakes yet', 'stocktake-for-woocommerce' ); ?></h3>
								<p><?php esc_html_e( 'Create your first session on the right to take a stock snapshot and start counting.', 'stocktake-for-woocommerce' ); ?></p>
							</div>
						<?php else : ?>
							<table class="stwc-table">
								<thead>
									<tr>
										<th><?php esc_html_e( 'Session', 'stocktake-for-woocommerce' ); ?></th>
										<th><?php esc_html_e( 'Mode', 'stocktake-for-woocommerce' ); ?></th>
										<th class="stwc-w-progress"><?php esc_html_e( 'Progress', 'stocktake-for-woocommerce' ); ?></th>
										<?php if ( $is_manager ) : // Money figures are for managers; count staff only pick a session. ?>
											<th class="stwc-right"><?php esc_html_e( 'Variance', 'stocktake-for-woocommerce' ); ?></th>
										<?php endif; ?>
										<th></th>
									</tr>
								</thead>
								<tbody>
								<?php foreach ( $sessions as $s ) :
									$sum = STWC_Sessions::get_summary( (int) $s['id'], $s['count_mode'] );
									$pct = $sum['total_items'] ? round( $sum['counted_items'] / $sum['total_items'] * 100 ) : 0; ?>
									<tr>
										<td>
											<a class="stwc-link" href="<?php echo esc_url( $this->url( array( 'view' => 'session', 'session' => $s['id'] ) ) ); ?>"><strong><?php echo esc_html( $s['name'] ); ?></strong></a>
											<div class="stwc-sub"><?php echo esc_html( mysql2date( get_option( 'date_format' ) . ' H:i', $s['created_at'] ) ); ?> · <span class="stwc-pill stwc-pill--<?php echo esc_attr( $s['status'] ); ?>"><?php echo esc_html( $this->status_label( $s['status'] ) ); ?></span></div>
										</td>
										<td><span class="stwc-mode-tag"><?php echo wp_kses( $this->mode_icon( $s['count_mode'] ), self::svg_allowed() ); ?> <?php echo esc_html( $this->mode_label( $s['count_mode'] ) ); ?></span></td>
										<td>
											<div class="stwc-bar"><span class="stwc-bar__fill" style="width:<?php echo esc_attr( $pct ); ?>%"></span></div>
											<div class="stwc-sub"><?php echo esc_html( $sum['counted_items'] . ' / ' . $sum['total_items'] ); ?></div>
										</td>
										<?php if ( $is_manager ) : ?>
											<td class="stwc-right"><strong><?php echo wp_kses_post( wc_price( $sum['variance_value'] ) ); ?></strong></td>
										<?php endif; ?>
										<td class="stwc-right">
											<?php
										$stwc_view_only = array( 'completed', 'cancelled' );
										?>
										<?php if ( in_array( $s['status'], $stwc_view_only, true ) ) : // Locked statuses cannot be counted — View, not Count/Continue. ?>
												<a class="stwc-btn stwc-btn--sm" href="<?php echo esc_url( $this->url( array( 'view' => 'session', 'session' => $s['id'] ) ) ); ?>"><?php esc_html_e( 'View', 'stocktake-for-woocommerce' ); ?></a>
											<?php else : ?>
												<a class="stwc-btn stwc-btn--sm stwc-btn--primary" href="<?php echo esc_url( $this->url( array( 'view' => 'count', 'session' => $s['id'] ) ) ); ?>"><?php $this->the_icon( 'scan' ); ?> <?php echo $sum['counted_items'] > 0 ? esc_html__( 'Continue', 'stocktake-for-woocommerce' ) : esc_html__( 'Count', 'stocktake-for-woocommerce' ); ?></a>
											<?php endif; ?>
										</td>
									</tr>
								<?php endforeach; ?>
								</tbody>
							</table>
						<?php endif; ?>
					</div>
				</div>

				<?php if ( $is_manager ) : // Manager-only settings (reminder email, presets, roles) must never reach count-only staff markup, not even hidden. ?>
				<div class="stwc-col-side">
					<div class="stwc-card">
						<h2 class="stwc-card__title"><?php $this->the_icon( 'plus' ); ?> <?php esc_html_e( 'New stocktake', 'stocktake-for-woocommerce' ); ?></h2>
						<form method="post" action="<?php echo esc_url( $this->url() ); ?>" class="stwc-form">
							<?php wp_nonce_field( 'stwc_create' ); ?>
							<input type="hidden" name="stwc_action" value="create" />

							<label class="stwc-label"><?php esc_html_e( 'Name', 'stocktake-for-woocommerce' ); ?></label>
							<input type="text" name="name" class="stwc-input" placeholder="<?php esc_attr_e( 'e.g. Warehouse — July', 'stocktake-for-woocommerce' ); ?>" />

							<label class="stwc-label"><?php esc_html_e( 'Counting mode', 'stocktake-for-woocommerce' ); ?></label>
							<div class="stwc-modes">
								<?php
								$this->mode_card( 'partial', __( 'Partial (spot count)', 'stocktake-for-woocommerce' ), __( 'Only items you scan are updated. The rest is left untouched.', 'stocktake-for-woocommerce' ), true );
								$this->mode_card( 'full', __( 'Full / zero-based', 'stocktake-for-woocommerce' ), __( 'Everything not counted is set to 0. A true physical inventory.', 'stocktake-for-woocommerce' ), false );
								?>
							</div>

							<label class="stwc-label"><?php esc_html_e( 'Scope', 'stocktake-for-woocommerce' ); ?></label>
							<select name="scope_type" class="stwc-input" id="stwc-scope-type">
								<option value="all"><?php esc_html_e( 'All stock-managed products', 'stocktake-for-woocommerce' ); ?></option>
								<option value="category"><?php esc_html_e( 'Selected categories', 'stocktake-for-woocommerce' ); ?></option>
							</select>

							<div id="stwc-cat-wrap" style="display:none;">
								<label class="stwc-label"><?php esc_html_e( 'Categories', 'stocktake-for-woocommerce' ); ?></label>
								<select name="categories[]" multiple size="6" class="stwc-input">
									<?php
									foreach ( (array) get_terms( array( 'taxonomy' => 'product_cat', 'hide_empty' => false ) ) as $term ) {
										printf( '<option value="%d">%s</option>', esc_attr( $term->term_id ), esc_html( $term->name ) );
									}
									?>
								</select>
							</div>

							<label class="stwc-check">
								<input type="checkbox" name="blind" value="1" />
								<span><?php esc_html_e( 'Blind count', 'stocktake-for-woocommerce' ); ?> <em><?php esc_html_e( '(hide expected qty while counting)', 'stocktake-for-woocommerce' ); ?></em></span>
							</label>


							<label class="stwc-label"><?php esc_html_e( 'Note', 'stocktake-for-woocommerce' ); ?></label>
							<textarea name="note" class="stwc-input" rows="2"></textarea>

							<button type="submit" class="stwc-btn stwc-btn--primary stwc-btn--block"><?php esc_html_e( 'Create & snapshot stock', 'stocktake-for-woocommerce' ); ?></button>
							<p class="stwc-hint"><?php esc_html_e( 'Current stock is snapshotted now, so ongoing sales do not distort the variance.', 'stocktake-for-woocommerce' ); ?></p>
						</form>
					</div>

				</div>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	private function render_session() {
		$session_id = absint( $_GET['session'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$session    = STWC_Sessions::get_session( $session_id );
		if ( ! $session ) {
			echo '<div class="wrap stwc-app"><div class="stwc-card">' . esc_html__( 'Session not found.', 'stocktake-for-woocommerce' ) . '</div></div>';
			return;
		}

		$mode           = $session['count_mode'];
		$stwc_receiving     = false;
		$stwc_cols      = 7;
		$sum            = STWC_Sessions::get_summary( $session_id, $mode );
		$show_all       = ! empty( $_GET['all'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$remaining_view = ! empty( $_GET['remaining'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$all            = STWC_Sessions::get_items( $session_id );
		$locked         = in_array( $session['status'], array( 'completed', 'cancelled' ), true );
		$backups_n      = STWC_Backups::count_for_session( $session_id );
		$uncounted_n    = STWC_Sessions::remaining_count( $session_id );
		$pct            = $sum['total_items'] ? round( $sum['counted_items'] / $sum['total_items'] * 100 ) : 0;

		// Build the rows to display, mode-aware.
		$rows = array();
		foreach ( $all as $it ) {
			$counted = is_null( $it['counted_qty'] ) ? null : (float) $it['counted_qty'];

			if ( $remaining_view ) {
				if ( ! is_null( $counted ) ) {
					continue;
				}
				$rows[] = array( 'it' => $it, 'eff' => null, 'var' => null );
				continue;
			}

			{
				$effective = STWC_Sessions::effective_counted( $it, $mode );
				$variance  = is_null( $effective ) ? null : $effective - (float) $it['expected_qty'];
			}
			if ( ! $show_all && ( is_null( $variance ) || abs( (float) $variance ) < 0.00001 ) ) {
				continue;
			}
			$rows[] = array( 'it' => $it, 'eff' => $effective, 'var' => $variance );
		}

		// Confirmation text for Apply (zero-count safety for full mode).
		if ( 'full' === $mode && $uncounted_n > 0 ) {
			/* translators: %d: number of uncounted products */
			/* translators: %d: number of products that were not counted. */
			$apply_confirm = sprintf( __( 'WARNING: %d products were not counted and will be set to 0. Apply now?', 'stocktake-for-woocommerce' ), $uncounted_n );
		} else {
			$apply_confirm = __( 'Apply counted quantities to WooCommerce stock?', 'stocktake-for-woocommerce' );
		}
		?>
		<div class="wrap stwc-app">
			<?php
			$status_html  = '<span class="stwc-pill stwc-pill--' . esc_attr( $session['status'] ) . '">' . esc_html( $this->status_label( $session['status'] ) ) . '</span>';
			$status_html .= ' <span class="stwc-mode-tag stwc-mode-tag--hero">' . $this->mode_icon( $mode ) . ' ' . esc_html( $this->mode_label( $mode ) ) . '</span>';
			if ( (int) $session['blind'] === 1 ) {
				$status_html .= ' <span class="stwc-pill stwc-pill--blind">' . esc_html__( 'Blind', 'stocktake-for-woocommerce' ) . '</span>';
			}
			$this->hero( $session['name'], $this->mode_desc( $mode ), $status_html, $this->url() );
			?>

			<?php if ( isset( $_GET['created'] ) ) : ?>
				<div class="stwc-flash stwc-flash--ok"><?php esc_html_e( 'Session created and stock snapshotted. Open the count screen to begin.', 'stocktake-for-woocommerce' ); ?></div>
			<?php endif; ?>
			<?php if ( isset( $_GET['applied'] ) ) : ?>
				<div class="stwc-flash stwc-flash--ok"><?php
					$applied_n = absint( $_GET['applied'] );
					/* translators: %d: number of products updated. */
					printf( esc_html( _n( 'Applied adjustments to %d product.', 'Applied adjustments to %d products.', $applied_n, 'stocktake-for-woocommerce' ) ), (int) $applied_n );
				?></div>
			<?php endif; ?>
			<?php
			// Real apply errors (restore-point failure, status gate, missing
			// product) are stashed in a transient so we can show the actual
			// reason instead of the generic "no longer exists" count.
			$stwc_apply_err = get_transient( 'stwc_apply_err_' . get_current_user_id() );
			if ( $stwc_apply_err ) {
				delete_transient( 'stwc_apply_err_' . get_current_user_id() );
			}
			?>
			<?php if ( $stwc_apply_err ) : ?>
				<div class="stwc-flash" style="background:var(--stwc-amber-bg);color:var(--stwc-amber)"><?php echo esc_html( implode( ' · ', array_map( 'strval', (array) $stwc_apply_err ) ) ); ?></div>
			<?php elseif ( ! empty( $_GET['skipped'] ) ) : ?>
				<div class="stwc-flash" style="background:var(--stwc-amber-bg);color:var(--stwc-amber)"><?php
					$skipped_n = absint( $_GET['skipped'] );
					/* translators: %d: number of products that could not be updated. */
					printf( esc_html( _n( '%d product could not be updated (it no longer exists).', '%d products could not be updated (they no longer exist).', $skipped_n, 'stocktake-for-woocommerce' ) ), (int) $skipped_n );
				?></div>
			<?php endif; ?>
			<?php if ( ! empty( $_GET['revert_none'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
				<div class="stwc-flash" style="background:var(--stwc-amber-bg);color:var(--stwc-amber)"><?php
					// Undo with no restore point left used to print the green
					// "Reverted 0 products to their previous stock." -- a success
					// flash for an operation that did nothing, which is exactly
					// what a second click, the Back button or a second admin sees.
					esc_html_e( 'Restore point not found.', 'stocktake-for-woocommerce' );
				?></div>
			<?php endif; ?>
			<?php if ( isset( $_GET['reverted'] ) && absint( $_GET['reverted'] ) > 0 ) : ?>
				<div class="stwc-flash stwc-flash--ok"><?php
					$reverted_n = absint( $_GET['reverted'] );
					/* translators: %d: number of products restored to previous stock. */
					printf( esc_html( _n( 'Reverted %d product to its previous stock.', 'Reverted %d products to their previous stock.', $reverted_n, 'stocktake-for-woocommerce' ) ), (int) $reverted_n );
				?></div>
			<?php endif; ?>
			<?php
			// Restore failures carry the product NAMES (STWC_Backups::restore()
			// only errors when the product is gone), so the reason is exact here.
			$stwc_revert_err = get_transient( 'stwc_revert_err_' . get_current_user_id() );
			if ( $stwc_revert_err ) {
				delete_transient( 'stwc_revert_err_' . get_current_user_id() );
			}
			?>
			<?php if ( ! empty( $_GET['revert_failed'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
				<div class="stwc-flash" style="background:var(--stwc-amber-bg);color:var(--stwc-amber)"><?php
					$stwc_rf = absint( $_GET['revert_failed'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
					/* translators: %d: number of products that could not be restored. */
					printf( esc_html( _n( '%d product could not be restored because it no longer exists. The restore point was kept, so you can try again.', '%d products could not be restored because they no longer exist. The restore point was kept, so you can try again.', $stwc_rf, 'stocktake-for-woocommerce' ) ), (int) $stwc_rf );
					if ( $stwc_revert_err ) {
						echo ' ' . esc_html( implode( ', ', array_map( 'strval', (array) $stwc_revert_err ) ) );
					}
				?></div>
			<?php endif; ?>
			<?php if ( ! empty( $session['building'] ) ) : ?>
				<div class="stwc-flash" data-stwc-autorefresh="1" style="background:var(--stwc-amber-bg);color:var(--stwc-amber)"><?php
					/* translators: %d: number of products snapshotted so far. */
					printf( esc_html__( 'Building the product list in the background… %d items ready so far. This page refreshes automatically.', 'stocktake-for-woocommerce' ), (int) $sum['total_items'] );
				?></div>
			<?php endif; ?>

			<div class="stwc-grid">
				<div class="stwc-stat">
					<div class="stwc-stat__num"><?php echo esc_html( $pct ); ?>%</div>
					<div class="stwc-stat__lbl"><?php esc_html_e( 'Counted', 'stocktake-for-woocommerce' ); ?></div>
					<div class="stwc-bar stwc-bar--mt"><span class="stwc-bar__fill" style="width:<?php echo esc_attr( $pct ); ?>%"></span></div>
					<div class="stwc-sub"><?php echo esc_html( $sum['counted_items'] . ' / ' . $sum['total_items'] ); ?></div>
				</div>
				<div class="stwc-stat">
					<div class="stwc-stat__num"><?php echo esc_html( $sum['variance_items'] ); ?></div>
					<div class="stwc-stat__lbl"><?php esc_html_e( 'Items with variance', 'stocktake-for-woocommerce' ); ?></div>
				</div>
				<div class="stwc-stat">
					<div class="stwc-stat__num <?php echo esc_attr( $sum['variance_units'] < 0 ? 'stwc-neg' : ( $sum['variance_units'] > 0 ? 'stwc-pos' : '' ) ); ?>"><bdi><?php echo esc_html( ( $sum['variance_units'] > 0 ? '+' : '' ) . (float) $sum['variance_units'] ); ?></bdi></div>
					<div class="stwc-stat__lbl"><?php esc_html_e( 'Net unit difference', 'stocktake-for-woocommerce' ); ?></div>
				</div>
				<div class="stwc-stat">
					<div class="stwc-stat__num <?php echo esc_attr( $sum['variance_value'] < 0 ? 'stwc-neg' : ( $sum['variance_value'] > 0 ? 'stwc-pos' : '' ) ); ?>"><?php echo wp_kses_post( wc_price( $sum['variance_value'] ) ); ?></div>
					<div class="stwc-stat__lbl"><?php esc_html_e( 'Variance value', 'stocktake-for-woocommerce' ); ?></div>
				</div>
			</div>

			<?php if ( ! $locked && ! $remaining_view && ! $stwc_receiving && $uncounted_n > 0 ) : ?>
				<?php $danger = ( 'full' === $mode ); ?>
				<div class="stwc-note<?php echo esc_attr( $danger ? ' stwc-note--warn' : '' ); ?>">
					<span class="stwc-note__icon"><?php $this->the_icon( $danger ? 'alert' : 'target' ); ?></span>
					<div class="stwc-note__body"><strong><?php
						if ( $danger ) {
							/* translators: %d: number of products not yet counted. */
							printf( esc_html( _n( '%d product has not been counted — it will be set to 0 when you apply.', '%d products have not been counted — they will be set to 0 when you apply.', $uncounted_n, 'stocktake-for-woocommerce' ) ), (int) $uncounted_n );
						} else {
							/* translators: 1: number not yet counted, 2: total items in the session. */
							printf( esc_html( _n( '%1$d of %2$d items hasn’t been counted yet. Uncounted items keep their current stock.', '%1$d of %2$d items haven’t been counted yet. Uncounted items keep their current stock.', $uncounted_n, 'stocktake-for-woocommerce' ) ), (int) $uncounted_n, (int) $sum['total_items'] );
						}
					?></strong></div>
					<a class="stwc-btn stwc-btn--sm" href="<?php echo esc_url( $this->url( array( 'view' => 'session', 'session' => $session_id, 'remaining' => 1 ) ) ); ?>"><?php esc_html_e( 'Review not counted', 'stocktake-for-woocommerce' ); ?></a>
				</div>
			<?php endif; ?>

			<div class="stwc-toolbar">
				<?php if ( empty( $stwc_flow_hold ) && ! $locked ) : ?>
					<a class="stwc-btn stwc-btn--primary" href="<?php echo esc_url( $this->url( array( 'view' => 'count', 'session' => $session_id ) ) ); ?>"><?php $this->the_icon( 'scan' ); ?> <?php esc_html_e( 'Open count screen', 'stocktake-for-woocommerce' ); ?></a>
					<?php $stwc_needs_submit = false; // Only a build that carries the approval step routes through a submit. ?>
					<?php $stwc_block = STWC_Stock::apply_blocker( $session ); // Same gate the engine uses, so the button never lies. ?>
					<?php if ( ! $stwc_needs_submit && '' === $stwc_block ) : ?>
						<a class="stwc-btn" href="<?php echo esc_url( $this->action_url( 'apply', $session_id ) ); ?>" onclick="return confirm('<?php echo esc_js( $apply_confirm ); ?>');"><?php $this->the_icon( 'check' ); ?> <?php echo $stwc_receiving ? esc_html__( 'Add to stock', 'stocktake-for-woocommerce' ) : esc_html__( 'Apply adjustments', 'stocktake-for-woocommerce' ); ?></a>
					<?php elseif ( ! $stwc_needs_submit ) : ?>
						<span class="stwc-hint"><?php echo esc_html( $stwc_block ); ?></span>
					<?php endif; ?>
					<a class="stwc-btn" href="<?php echo esc_url( $this->action_url( 'complete', $session_id ) ); ?>"><?php esc_html_e( 'Mark completed', 'stocktake-for-woocommerce' ); ?></a>
					<a class="stwc-btn stwc-btn--ghost" href="<?php echo esc_url( $this->action_url( 'cancel', $session_id ) ); ?>" onclick="return confirm('<?php echo esc_js( __( 'Cancel this stocktake? Its counts are kept but it can no longer be applied.', 'stocktake-for-woocommerce' ) ); ?>');"><?php $this->the_icon( 'cancel' ); ?> <?php esc_html_e( 'Cancel stocktake', 'stocktake-for-woocommerce' ); ?></a>
				<?php endif; ?>
				<?php if ( $backups_n > 0 ) : ?>
					<a class="stwc-btn" href="<?php echo esc_url( $this->action_url( 'revert', $session_id ) ); ?>" onclick="return confirm('<?php echo esc_js( __( 'Undo the last apply and restore the previous stock?', 'stocktake-for-woocommerce' ) ); ?>');"><?php $this->the_icon( 'undo' ); ?> <?php esc_html_e( 'Undo last apply', 'stocktake-for-woocommerce' ); ?></a>
				<?php endif; ?>
				<a class="stwc-btn" href="<?php echo esc_url( $this->action_url( 'export', $session_id ) ); ?>"><?php $this->the_icon( 'download' ); ?> <?php esc_html_e( 'Export CSV', 'stocktake-for-woocommerce' ); ?></a>
				<?php if ( $remaining_view ) : ?>
					<a class="stwc-btn stwc-btn--ghost" href="<?php echo esc_url( $this->url( array( 'view' => 'session', 'session' => $session_id ) ) ); ?>"><?php esc_html_e( 'Back to report', 'stocktake-for-woocommerce' ); ?></a>
				<?php else : ?>
					<a class="stwc-btn stwc-btn--ghost" href="<?php echo esc_url( $this->url( array( 'view' => 'session', 'session' => $session_id, 'all' => $show_all ? 0 : 1 ) ) ); ?>"><?php echo $show_all ? esc_html__( 'Variances only', 'stocktake-for-woocommerce' ) : esc_html__( 'Show all items', 'stocktake-for-woocommerce' ); ?></a>
					<?php if ( $uncounted_n > 0 ) : ?>
						<a class="stwc-btn stwc-btn--ghost" href="<?php echo esc_url( $this->url( array( 'view' => 'session', 'session' => $session_id, 'remaining' => 1 ) ) ); ?>"><?php /* translators: %d: number of products not yet counted. */ printf( esc_html__( 'Not counted (%d)', 'stocktake-for-woocommerce' ), (int) $uncounted_n ); ?></a>
					<?php endif; ?>
				<?php endif; ?>
				<a class="stwc-btn stwc-btn--danger" href="<?php echo esc_url( $this->action_url( 'delete', $session_id ) ); ?>" onclick="return confirm('<?php echo esc_js( __( 'Delete this stocktake permanently?', 'stocktake-for-woocommerce' ) ); ?>');"><?php esc_html_e( 'Delete', 'stocktake-for-woocommerce' ); ?></a>
			</div>

			<div class="stwc-card stwc-card--flush">
				<table class="stwc-table">
					<thead>
						<tr>
							<th><?php esc_html_e( 'Product', 'stocktake-for-woocommerce' ); ?></th>
							<th><?php esc_html_e( 'SKU', 'stocktake-for-woocommerce' ); ?></th>
							<th class="stwc-right"><?php echo $stwc_receiving ? esc_html__( 'Before', 'stocktake-for-woocommerce' ) : esc_html__( 'Expected', 'stocktake-for-woocommerce' ); ?></th>
							<th class="stwc-right"><?php echo $stwc_receiving ? esc_html__( 'Received', 'stocktake-for-woocommerce' ) : esc_html__( 'Counted', 'stocktake-for-woocommerce' ); ?></th>
							<th class="stwc-right"><?php esc_html_e( 'Variance', 'stocktake-for-woocommerce' ); ?></th>
							<th class="stwc-right"><?php esc_html_e( 'Value', 'stocktake-for-woocommerce' ); ?></th>
							<th class="stwc-right"><?php esc_html_e( 'Applied', 'stocktake-for-woocommerce' ); ?></th>
						</tr>
					</thead>
					<tbody>
					<?php if ( empty( $rows ) ) : ?>
						<tr><td colspan="<?php echo (int) $stwc_cols; ?>" class="stwc-muted"><?php
						if ( $remaining_view ) {
							esc_html_e( 'Nothing left — every item has been counted.', 'stocktake-for-woocommerce' );
						} else {
							esc_html_e( 'No variances to show. Either nothing has been counted yet, or everything matched.', 'stocktake-for-woocommerce' );
						}
						?></td></tr>
					<?php else : ?>
						<?php foreach ( $rows as $r ) :
							$it       = $r['it'];
							$eff      = $r['eff'];
							$variance = $r['var'];
							$value    = is_null( $variance ) ? 0 : $variance * (float) $it['unit_cost'];
							$vclass   = is_null( $variance ) ? '' : ( $variance < 0 ? 'stwc-neg' : ( $variance > 0 ? 'stwc-pos' : '' ) );
							$uncounted = is_null( $it['counted_qty'] ); ?>
							<tr>
								<td><?php echo esc_html( $it['name'] ); ?></td>
								<td><code class="stwc-code"><?php echo esc_html( $it['sku'] ?: '—' ); ?></code></td>
								<td class="stwc-right"><?php echo esc_html( self::fmt_qty( $it['expected_qty'] ) ); ?></td>
								<td class="stwc-right">
									<?php if ( $uncounted && 'full' === $mode ) : ?>
										<span class="stwc-muted">0 · <?php esc_html_e( 'uncounted', 'stocktake-for-woocommerce' ); ?></span>
									<?php elseif ( $uncounted ) : ?>
										<span class="stwc-muted">—</span>
									<?php else : ?>
										<?php echo esc_html( self::fmt_qty( $it['counted_qty'] ) ); ?>
									<?php endif; ?>
								</td>
								<td class="stwc-right <?php echo esc_attr( $vclass ); ?>"><?php echo is_null( $variance ) ? '—' : '<bdi>' . esc_html( ( $variance > 0 ? '+' : '' ) . self::fmt_qty( $variance ) ) . '</bdi>'; ?></td>
								<td class="stwc-right <?php echo esc_attr( $vclass ); ?>"><?php echo wp_kses_post( wc_price( $value ) ); ?></td>
								<td class="stwc-right"><?php echo ( (int) $it['applied'] === 1 ) ? '<span class="stwc-pos">✓</span>' : '<span class="stwc-muted">—</span>'; ?></td>
							</tr>
						<?php endforeach; ?>
					<?php endif; ?>
					</tbody>
				</table>
			</div>

		</div>
		<?php
	}

	private function render_count() {
		$session_id = absint( $_GET['session'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$session    = STWC_Sessions::get_session( $session_id );
		if ( ! $session ) {
			echo '<div class="wrap stwc-app"><div class="stwc-card">' . esc_html__( 'Session not found.', 'stocktake-for-woocommerce' ) . '</div></div>';
			return;
		}
		if ( 'open' !== $session['status'] ) {
			// A count that is no longer open is read-only — the count screen
			// must not look actionable (REST refuses anyway).
			$stwc_hint = __( 'It was completed or cancelled. Open the session to review it.', 'stocktake-for-woocommerce' );
			echo '<div class="wrap stwc-app"><div class="stwc-card" style="max-width:560px">'
				. '<h2 class="stwc-card__title">' . esc_html__( 'This stocktake is not open for counting.', 'stocktake-for-woocommerce' ) . '</h2>'
				. '<p class="stwc-hint">' . esc_html( $stwc_hint ) . '</p>'
				. '<a class="stwc-btn stwc-btn--primary" href="' . esc_url( $this->url( array( 'view' => 'session', 'session' => $session_id ) ) ) . '">' . esc_html__( 'Open the session', 'stocktake-for-woocommerce' ) . '</a>'
				. '</div></div>';
			return;
		}
		$mode = $session['count_mode'];
		?>
		<div class="wrap stwc-app stwc-count">
			<?php
			$sub = $this->mode_desc( $mode );
			$this->hero(
				$session['name'],
				$sub,
				'<span class="stwc-mode-tag stwc-mode-tag--hero">' . $this->mode_icon( $mode ) . ' ' . esc_html( $this->mode_label( $mode ) ) . '</span>',
				$this->url( array( 'view' => 'session', 'session' => $session_id ) )
			);
			?>


			<div class="stwc-info">
				<?php $this->the_icon( 'check' ); ?>
				<span><?php esc_html_e( 'Counting does not change your stock yet. When you finish, open the report and press “Apply adjustments”.', 'stocktake-for-woocommerce' ); ?></span>
				<a class="stwc-btn stwc-btn--sm" href="<?php echo esc_url( $this->url( array( 'view' => 'session', 'session' => $session_id ) ) ); ?>"><?php esc_html_e( 'Finish & review', 'stocktake-for-woocommerce' ); ?></a>
			</div>

			<?php if ( ! empty( $session['building'] ) ) : ?>
				<div class="stwc-info" style="background:var(--stwc-amber-bg);border-color:#fde68a;color:var(--stwc-amber)"><?php $this->the_icon( 'stack' ); ?> <span><?php esc_html_e( 'Still preparing the full product list in the background — you can start counting now; more items keep appearing.', 'stocktake-for-woocommerce' ); ?></span></div>
			<?php endif; ?>

			<div class="stwc-count-grid">
				<div class="stwc-card stwc-scan-card">
					<label class="stwc-label" for="stwc-scan"><?php $this->the_icon( 'scan' ); ?> <?php esc_html_e( 'Scan barcode, or type a SKU / product name', 'stocktake-for-woocommerce' ); ?></label>
					<div class="stwc-scan-wrap">
						<input type="text" id="stwc-scan" class="stwc-scan-input" autocomplete="off" autofocus role="combobox" aria-expanded="false" aria-autocomplete="list" aria-controls="stwc-suggest" placeholder="<?php esc_attr_e( 'Scan, or start typing a name…', 'stocktake-for-woocommerce' ); ?>" />
						<button type="button" id="stwc-cam-btn" class="stwc-cam-btn" title="<?php esc_attr_e( 'Scan with phone camera (Beta)', 'stocktake-for-woocommerce' ); ?>"><?php $this->the_icon( 'camera' ); ?><span class="stwc-cam-beta"><?php esc_html_e( 'Beta', 'stocktake-for-woocommerce' ); ?></span></button>
					</div>
					<ul id="stwc-suggest" class="stwc-suggest" role="listbox"></ul>
					<div id="stwc-stage" class="stwc-stage" aria-live="polite"></div>
				</div>

				<div class="stwc-card stwc-side-card">
					<?php
						// No progress meter on the live count screen: a spot count has
						// no fixed "list to finish", so a %/total/remaining against the
						// scope snapshot only confuses. The mode note below still tells
						// the counter what happens to items they do NOT scan.
					$stwc_keep_hint = ( 'full' !== $mode );
					?>
					<?php if ( 'full' === $mode ) : ?>
						<div class="stwc-sub"><?php esc_html_e( 'Uncounted items are set to 0 when the count is applied.', 'stocktake-for-woocommerce' ); ?></div>
					<?php elseif ( $stwc_keep_hint ) : ?>
						<div class="stwc-sub"><?php esc_html_e( 'Uncounted items keep their current stock.', 'stocktake-for-woocommerce' ); ?></div>
					<?php endif; ?>
					<div class="stwc-saved"><?php $this->the_icon( 'check' ); ?> <?php esc_html_e( 'Saved automatically — you can close this and continue later.', 'stocktake-for-woocommerce' ); ?></div>

					<h3 class="stwc-side-title"><?php esc_html_e( 'Recent scans', 'stocktake-for-woocommerce' ); ?></h3>
					<?php
					$recent     = STWC_Sessions::recent_counted( $session_id );
					$stwc_receiving = false;
					$blind      = (int) $session['blind'] === 1;
					?>
					<ul id="stwc-recent" class="stwc-recent">
						<?php if ( empty( $recent ) ) : ?>
							<li class="stwc-recent__empty"><?php esc_html_e( 'Scanned items will appear here.', 'stocktake-for-woocommerce' ); ?></li>
						<?php else : ?>
							<?php foreach ( $recent as $ri ) :
								$rc = (float) $ri['counted_qty'];
								if ( $stwc_receiving ) {
									$rv = $rc;
								} elseif ( $blind ) {
									$rv = null;
								} else {
									$rv = $rc - (float) $ri['expected_qty'];
								}
								if ( is_null( $rv ) ) {
									$chip_cls = 'stwc-chip';
									$chip_txt = esc_html__( 'Counted', 'stocktake-for-woocommerce' ) . ': ' . $rc;
								} else {
									$chip_cls = 'stwc-chip ' . ( $rv < 0 ? 'stwc-chip--neg' : ( $rv > 0 ? 'stwc-chip--pos' : 'stwc-chip--zero' ) );
									$chip_txt = ( $rv > 0 ? '+' : '' ) . $rv;
								} ?>
								<li data-item="<?php echo esc_attr( (int) $ri['id'] ); ?>" data-qty="<?php echo esc_attr( $rc ); ?>">
									<div class="stwc-recent__main">
										<span class="stwc-recent__name"><?php echo esc_html( $ri['name'] ); ?></span>
										<span class="<?php echo esc_attr( $chip_cls ); ?>"><bdi><?php echo esc_html( $chip_txt ); ?></bdi></span>
									</div>
									<div class="stwc-recent__tools">
										<button type="button" class="stwc-rstep" data-d="-1">&minus;</button>
										<input type="number" step="any" inputmode="decimal" class="stwc-recent__qty" value="<?php echo esc_attr( $rc ); ?>" aria-label="<?php esc_attr_e( 'Counted quantity', 'stocktake-for-woocommerce' ); ?>" />
										<button type="button" class="stwc-rstep" data-d="1">+</button>
										<button type="button" class="stwc-recent__del" title="<?php esc_attr_e( 'Remove from count', 'stocktake-for-woocommerce' ); ?>" aria-label="<?php esc_attr_e( 'Remove from count', 'stocktake-for-woocommerce' ); ?>">✕</button>
									</div>
								</li>
							<?php endforeach; ?>
						<?php endif; ?>
					</ul>

					<a class="stwc-btn stwc-btn--primary stwc-btn--block" style="margin-top:16px" href="<?php echo esc_url( $this->url( array( 'view' => 'session', 'session' => $session_id ) ) ); ?>"><?php esc_html_e( 'Finish & review', 'stocktake-for-woocommerce' ); ?></a>
				</div>
			</div>
		</div>
		<?php
	}



	/**
	 * Our own upgrade/pricing screen — fully localized (the SDK's pricing app
	 * is English-only). CTAs deep-link into the SDK's checkout route so the
	 * licence still auto-activates after purchase; tier data mirrors the
	 * Freemius dashboard (plan 55135, annual USD).
	 */
	private function render_upgrade() {
		$tiers = array(
			array(
				'label'      => __( 'Single site', 'stocktake-for-woocommerce' ),
				'price'      => 79,
				'pricing_id' => 72715,
				'popular'    => false,
			),
			array(
				/* translators: %d: number of sites the licence covers. */
				'label'      => sprintf( __( '%d sites', 'stocktake-for-woocommerce' ), 5 ),
				'price'      => 249,
				'pricing_id' => 72716,
				'popular'    => true,
			),
			array(
				/* translators: %d: number of sites the licence covers. */
				'label'      => sprintf( __( '%d sites', 'stocktake-for-woocommerce' ), 20 ),
				'price'      => 499,
				'pricing_id' => 72721,
				'popular'    => false,
			),
		);

		$features = array(
			__( 'Stock Pulse live report', 'stocktake-for-woocommerce' ),
			__( 'Excel (XLSX) export', 'stocktake-for-woocommerce' ),
			__( 'Quick stock check', 'stocktake-for-woocommerce' ),
			__( 'Goods-in / restock mode', 'stocktake-for-woocommerce' ),
			__( 'Full restore-point history', 'stocktake-for-woocommerce' ),
			__( 'Valuation and reorder reports', 'stocktake-for-woocommerce' ),
			__( 'Priority support', 'stocktake-for-woocommerce' ),
		);

		$faq = array(
			array(
				__( 'Can I use my licence on more than one site?', 'stocktake-for-woocommerce' ),
				__( 'Yes — pick the 5-site or 20-site plan, or start with a single site and upgrade any time; you only pay the difference.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'What happens if I cancel or the licence expires?', 'stocktake-for-woocommerce' ),
				__( 'The plugin keeps working and your data stays yours; Pro features lock again and updates and priority support stop.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'How do I get support?', 'stocktake-for-woocommerce' ),
				__( 'Pro customers reach us directly from the Contact page in this menu and get answers with priority.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'Is payment secure?', 'stocktake-for-woocommerce' ),
				__( 'Checkout and billing are handled by Freemius, our merchant of record — cards and PayPal, with proper invoices.', 'stocktake-for-woocommerce' ),
			),
		);

		$checkout_base = '';
		if ( function_exists( 'stwc_fs' ) ) {
			// The checkout overlay localizes itself via the forwarded `language`
			// param (the SDK pricing template merges $_GET into its config).
			// Locales the checkout doesn't ship yet (tr, pt, ar) fall back to
			// 'auto' — geo/browser detection plus the built-in selector.
			$stwc_chk_lang = substr( stwc_effective_locale(), 0, 2 );
			if ( ! in_array( $stwc_chk_lang, array( 'en', 'es', 'de', 'fr', 'it' ), true ) ) {
				$stwc_chk_lang = 'auto';
			}
			$checkout_base = admin_url( 'admin.php?page=' . self::SLUG . '-pricing&checkout=true&plan_id=55135&plan_name=pro&billing_cycle=annual&currency=usd&language=' . $stwc_chk_lang . '&pricing_id=' );
		}

		// LP-style sections, deliberately restrained: existing SVG icon set only,
		// no bundled imagery, no motion. Everything below is plain information
		// about the separately distributed Pro plugin.
		$cards = array(
			array( 'target', __( 'Stock Pulse', 'stocktake-for-woocommerce' ), __( 'A live dashboard of your inventory: total units and value, what’s selling, what’s about to run out and what’s been sitting still for months.', 'stocktake-for-woocommerce' ) ),
			array( 'scan', __( 'Quick stock check', 'stocktake-for-woocommerce' ), __( 'Scan any product at the shelf, see its live stock with the full per-variant breakdown, and fix a wrong quantity on the spot — audit-logged.', 'stocktake-for-woocommerce' ) ),
			array( 'stack', __( 'Tally & goods-in counting', 'stocktake-for-woocommerce' ), __( 'Count box by box across passes with add-on-top tallying, and book deliveries straight into stock with the receiving mode.', 'stocktake-for-woocommerce' ) ),
			array( 'undo', __( 'Full restore-point history', 'stocktake-for-woocommerce' ), __( 'Every apply keeps its automatic backup. Browse the whole history and roll stock back to any earlier point — not just the last one.', 'stocktake-for-woocommerce' ) ),
			array( 'chart', __( 'Valuation & insights', 'stocktake-for-woocommerce' ), __( 'On-hand value at retail and at purchase cost, per category. ABC count priorities and an accuracy & shrinkage trend across stocktakes.', 'stocktake-for-woocommerce' ) ),
			array( 'layers', __( 'Team counting & approvals', 'stocktake-for-woocommerce' ), __( 'Let staff count without admin access, see who counted what, and require a manager’s approval before anything touches live stock.', 'stocktake-for-woocommerce' ) ),
		);

		$compare = array(
			array( __( 'Unlimited stocktake sessions', 'stocktake-for-woocommerce' ), true ),
			array( __( 'Partial and full / zero-based counting', 'stocktake-for-woocommerce' ), true ),
			array( __( 'Count while the store keeps selling (snapshot variance)', 'stocktake-for-woocommerce' ), true ),
			array( __( 'Barcode, SKU and phone-camera scanning', 'stocktake-for-woocommerce' ), true ),
			array( __( 'Blind counting', 'stocktake-for-woocommerce' ), true ),
			array( __( 'Automatic backup and undo of the last apply', 'stocktake-for-woocommerce' ), true ),
			array( __( 'CSV exports and reorder shortlist', 'stocktake-for-woocommerce' ), true ),
			array( __( 'Add-on-top (tally) counting mode', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Goods-in (receiving) mode', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Quick stock check with instant fixes', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Full restore-point history', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Valuation at cost & purchase-price field', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Stock Pulse — live units, value and sales movement', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Running-out risk, top movers and dead-stock lists', 'stocktake-for-woocommerce' ), false ),
			array( __( 'ABC count priorities and accuracy & shrinkage trend', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Per-session variance breakdown by category', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Printable count sheets and branded PDF reports', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Excel (XLSX) exports', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Multiple counters with attribution (who counted what)', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Recount / approval workflow & scheduled reminders', 'stocktake-for-woocommerce' ), false ),
			array( __( 'Priority email support', 'stocktake-for-woocommerce' ), false ),
		);

		echo '<div class="wrap stwc-up">';

		// Hero: gradient panel, copy left, a pure-CSS count-screen mockup right
		// (scan sweep + variance chips — the plugin's actual moment of value).
		echo '<div class="stwc-up-hero2">';
		echo '<div class="stwc-up-hero2__copy">';
		echo '<img class="stwc-up-logo" src="' . esc_url( STWC_URL . 'assets/img/icon-256.png' ) . '" alt="" width="56" height="56" />';
		echo '<h1>' . esc_html__( 'Upgrade to Pro', 'stocktake-for-woocommerce' ) . '</h1>';
		echo '<p class="stwc-up-sub">' . esc_html__( 'Unlock every Pro feature in minutes — priced for stores of any size.', 'stocktake-for-woocommerce' ) . '</p>';
		echo '<div class="stwc-up-points">';
		echo '<span>' . wp_kses( $this->icon( 'check' ), self::svg_allowed() ) . ' ' . esc_html__( 'Activates in minutes', 'stocktake-for-woocommerce' ) . '</span>';
		echo '<span>' . wp_kses( $this->icon( 'check' ), self::svg_allowed() ) . ' ' . esc_html__( 'Your data stays in WordPress', 'stocktake-for-woocommerce' ) . '</span>';
		echo '<span>' . wp_kses( $this->icon( 'check' ), self::svg_allowed() ) . ' ' . esc_html__( 'Keep everything if you cancel', 'stocktake-for-woocommerce' ) . '</span>';
		echo '</div>';
		echo '<a class="stwc-up-hcta" href="#stwc-up-pricing">' . esc_html__( 'See plans & pricing', 'stocktake-for-woocommerce' ) . ' ↓</a>';
		echo '</div>';
		echo '<div class="stwc-up-hero2__mock" aria-hidden="true">';
		echo '<div class="stwc-up-mock">';
		echo '<div class="stwc-up-mock__scan"><span class="stwc-up-mock__beam"></span>' . wp_kses( $this->icon( 'scan' ), self::svg_allowed() ) . ' <em>ST-1024</em></div>';
		echo '<div class="stwc-up-mock__row"><span class="stwc-up-mock__dot"></span><div><b>' . esc_html__( 'Cotton T-Shirt — M', 'stocktake-for-woocommerce' ) . '</b><i>ST-1024 · ' . esc_html__( 'Expected', 'stocktake-for-woocommerce' ) . ' 24</i></div><span class="stwc-up-mock__qty">27</span></div>';
		echo '<div class="stwc-up-mock__chips"><span class="is-pos">+3</span><span class="is-neg">−2</span><span class="is-zero">0</span></div>';
		echo '<div class="stwc-up-mock__toast">' . wp_kses( $this->icon( 'check' ), self::svg_allowed() ) . ' ' . esc_html__( 'Saved — variance +3', 'stocktake-for-woocommerce' ) . '</div>';
		echo '</div>';
		echo '</div>';
		echo '</div>';

		if ( stwc_is_pro() ) {
			echo '<div class="notice notice-success stwc-up-active"><p><strong>' . esc_html__( 'Your Pro licence is active — thank you!', 'stocktake-for-woocommerce' ) . '</strong> ';
			echo esc_html__( 'Manage your licence and billing from the Account page.', 'stocktake-for-woocommerce' ) . '</p></div>';
		}

		echo '<div class="stwc-up-cards">';
		foreach ( $cards as $card ) {
			echo '<div class="stwc-up-fcard">';
			echo '<span class="stwc-up-fic">' . wp_kses( $this->icon( $card[0] ), self::svg_allowed() ) . '</span>';
			echo '<h3>' . esc_html( $card[1] ) . '</h3>';
			echo '<p>' . esc_html( $card[2] ) . '</p>';
			echo '</div>';
		}
		echo '</div>';

		// Deep-dive band: what the Pro dashboard actually feels like (CSS-only
		// mini stat tiles + bar sparkline; sample figures, clearly decorative).
		echo '<div class="stwc-up-dive">';
		echo '<div class="stwc-up-dive__copy">';
		echo '<span class="stwc-up-eyebrow">' . esc_html__( 'Stock Pulse', 'stocktake-for-woocommerce' ) . '</span>';
		echo '<h2>' . esc_html__( 'Your whole inventory, on one live screen', 'stocktake-for-woocommerce' ) . '</h2>';
		echo '<ul>';
		echo '<li>' . wp_kses( $this->icon( 'check' ), self::svg_allowed() ) . ' ' . esc_html__( 'Total units and value the moment you open it', 'stocktake-for-woocommerce' ) . '</li>';
		echo '<li>' . wp_kses( $this->icon( 'check' ), self::svg_allowed() ) . ' ' . esc_html__( 'What runs out next, at the current sales pace', 'stocktake-for-woocommerce' ) . '</li>';
		echo '<li>' . wp_kses( $this->icon( 'check' ), self::svg_allowed() ) . ' ' . esc_html__( 'Dead stock that quietly ties up your money', 'stocktake-for-woocommerce' ) . '</li>';
		echo '</ul>';
		echo '</div>';
		echo '<div class="stwc-up-dive__mock" aria-hidden="true">';
		echo '<div class="stwc-up-stats">';
		echo '<div class="stwc-up-stat"><b>12.480</b><i>' . esc_html__( 'Units on hand', 'stocktake-for-woocommerce' ) . '</i></div>';
		echo '<div class="stwc-up-stat"><b>842K</b><i>' . esc_html__( 'Value at retail price', 'stocktake-for-woocommerce' ) . '</i></div>';
		echo '<div class="stwc-up-stat"><b>96%</b><i>' . esc_html__( 'Count accuracy', 'stocktake-for-woocommerce' ) . '</i></div>';
		echo '</div>';
		echo '<div class="stwc-up-bars"><span style="--h:34%"></span><span style="--h:58%"></span><span style="--h:44%"></span><span style="--h:76%"></span><span style="--h:62%"></span><span style="--h:90%"></span><span style="--h:70%"></span></div>';
		echo '</div>';
		echo '</div>';

		echo '<h2 class="stwc-up-sec">' . esc_html__( 'What’s in Free, what’s in Pro', 'stocktake-for-woocommerce' ) . '</h2>';
		echo '<div class="stwc-up-comp-wrap"><table class="stwc-up-comp">';
		echo '<thead><tr><th></th><th>' . esc_html__( 'Free', 'stocktake-for-woocommerce' ) . '</th><th class="stwc-up-comp-pro">' . esc_html__( 'Pro', 'stocktake-for-woocommerce' ) . '</th></tr></thead><tbody>';
		foreach ( $compare as $row ) {
			echo '<tr><td>' . esc_html( $row[0] ) . '</td>';
			echo '<td class="stwc-up-tick">' . ( $row[1] ? wp_kses( $this->icon( 'check' ), self::svg_allowed() ) : '<span class="stwc-up-dash">—</span>' ) . '</td>';
			echo '<td class="stwc-up-tick stwc-up-comp-pro">' . wp_kses( $this->icon( 'check' ), self::svg_allowed() ) . '</td></tr>';
		}
		echo '</tbody></table></div>';

		echo '<h2 class="stwc-up-sec" id="stwc-up-pricing">' . esc_html__( 'Simple annual pricing', 'stocktake-for-woocommerce' ) . '</h2>';
		echo '<div class="stwc-up-grid">';
		foreach ( $tiers as $tier ) {
			echo '<div class="stwc-up-card' . ( $tier['popular'] ? ' stwc-up-card--pop' : '' ) . '">';
			if ( $tier['popular'] ) {
				echo '<div class="stwc-up-badge">★ ' . esc_html__( 'Most popular', 'stocktake-for-woocommerce' ) . '</div>';
			}
			echo '<h2>' . esc_html( $tier['label'] ) . '</h2>';
			echo '<div class="stwc-up-price"><span class="stwc-up-cur">$</span>' . esc_html( number_format_i18n( $tier['price'] ) ) . '<span class="stwc-up-per">/ ' . esc_html__( 'year', 'stocktake-for-woocommerce' ) . '</span></div>';
			echo '<div class="stwc-up-cycle">' . esc_html__( 'Billed annually', 'stocktake-for-woocommerce' ) . '</div>';
			echo '<ul class="stwc-up-feat">';
			foreach ( $features as $feature ) {
				echo '<li>' . esc_html( $feature ) . '</li>';
			}
			echo '</ul>';
			$cta = $checkout_base ? $checkout_base . $tier['pricing_id'] : stwc_upgrade_url();
			echo '<a class="stwc-up-cta" href="' . esc_url( $cta ) . '">' . esc_html__( 'Upgrade now', 'stocktake-for-woocommerce' ) . '</a>';
			echo '<div class="stwc-up-secure">' . esc_html__( 'Secure checkout — cards & PayPal', 'stocktake-for-woocommerce' ) . '</div>';
			echo '</div>';
		}
		echo '</div>';

		echo '<div class="stwc-up-guarantee"><strong>' . esc_html__( '14-day money-back guarantee', 'stocktake-for-woocommerce' ) . '</strong><p>';
		echo esc_html__( 'If you hit an issue that makes the plugin unusable and we cannot resolve it, we will happily refund your money in full.', 'stocktake-for-woocommerce' );
		echo '</p></div>';

		echo '<h2 class="stwc-up-faq-title">' . esc_html__( 'Frequently asked questions', 'stocktake-for-woocommerce' ) . '</h2>';
		echo '<div class="stwc-up-faq">';
		foreach ( $faq as $item ) {
			echo '<div class="stwc-up-faq-item"><h3>' . esc_html( $item[0] ) . '</h3><p>' . esc_html( $item[1] ) . '</p></div>';
		}
		echo '</div>';

		// Closing CTA band.
		echo '<div class="stwc-up-band">';
		echo '<h2>' . esc_html__( 'Know your stock — not just your numbers', 'stocktake-for-woocommerce' ) . '</h2>';
		echo '<p>' . esc_html__( 'Try Pro risk-free for 14 days. If it doesn’t earn its place, you get your money back.', 'stocktake-for-woocommerce' ) . '</p>';
		echo '<a class="stwc-up-cta stwc-up-band__cta" href="#stwc-up-pricing">' . esc_html__( 'Upgrade now', 'stocktake-for-woocommerce' ) . '</a>';
		echo '</div>';

		echo '</div>';
	}

	/**
	 * Help & FAQ: the questions that actually generate support mail, answered
	 * inside the plugin. Plain <details> accordions — no JS. Free feature, so
	 * it deflects support for both tiers.
	 */
	private function render_help() {
		$faq = array(
			array(
				__( 'I counted everything — why hasn’t my stock changed?', 'stocktake-for-woocommerce' ),
				__( 'Counting only records quantities. Live stock changes when you press “Apply adjustments” on the stocktake’s page. A restore point is saved automatically right before, so you can undo.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'Can customers keep buying while we count?', 'stocktake-for-woocommerce' ),
				__( 'Yes. Each stocktake snapshots the expected quantities when it is created, so sales made during the count don’t distort the variance.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'What is the difference between Partial and Full / zero-based?', 'stocktake-for-woocommerce' ),
				__( 'Partial only updates the items you actually scanned — everything else is left untouched. Full treats uncounted items as 0 (a true physical inventory); a warning lists them before anything is applied.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'A barcode isn’t found when I scan it — why?', 'stocktake-for-woocommerce' ),
				__( 'A scanned code is matched against the SKU first, then WooCommerce’s own GTIN / UPC / EAN / ISBN field (WooCommerce 9.2+). Add the code to the product (or variation) on its edit screen, in either field.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'The phone camera won’t read some barcodes.', 'stocktake-for-woocommerce' ),
				__( 'Camera scanning is Beta: it uses the browser’s native detector with a bundled fallback, and some barcode types or damaged labels won’t decode on every device. More light and a steady hand help; a handheld scanner or typing the SKU always works.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'How do I undo an apply?', 'stocktake-for-woocommerce' ),
				__( 'Open the stocktake and press “Undo last apply” — stock returns to the automatic restore point saved before the apply (including the stock-managed flag). Browsing and restoring the full restore-point history is a Pro feature.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'How are variations handled?', 'stocktake-for-woocommerce' ),
				__( 'Every stock-managed variation is its own line. Scanning a variation’s SKU or GTIN counts that exact variation, and applying updates that variation’s stock — never the parent’s.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'What does Blind count do?', 'stocktake-for-woocommerce' ),
				__( 'It hides the expected quantity on the count screen so counters aren’t biased toward confirming it. Managers still see expected, counted and variance on the stocktake’s page.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'Can staff count without full admin access?', 'stocktake-for-woocommerce' ),
				__( 'Yes — team counting is a Pro feature. You choose which roles may count; those users only see the counting screens, never stock values, reports or settings.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'What’s the difference between the free version and Pro?', 'stocktake-for-woocommerce' ),
				__( 'The free version covers the complete count-and-correct workflow. Pro adds the operation layer: goods-in and tally modes, team counting with approval, full history, valuation and movement reports, Excel export and the quick stock check.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'What do the report tabs show, and where does the data come from?', 'stocktake-for-woocommerce' ),
				__( 'Valuation shows what your on-hand stock is worth right now — units, value at retail and (Pro) at purchase cost, with a per-category breakdown and CSV/Excel export. Reorder lists everything at or below its low-stock level or already out of stock, ready to hand to a supplier. Insights (Pro) ranks products by the share of stock value they hold (ABC) and charts how your count accuracy and shrinkage develop across completed stocktakes. Everything is computed live from WooCommerce’s own lookup tables on every page load — there is no cache and no background job to wait for.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'Why does the valuation say my items have “no cost”?', 'stocktake-for-woocommerce' ),
				array(
					__( 'The plugin never invents costs. For each item it looks for a purchase cost in this order: the plugin’s own Purchase cost field (Pro), WooCommerce’s built-in Cost of Goods (WooCommerce 9.5+), then the Cost of Goods fields of SkyVerge, WPFactory and ATUM. The first value found wins; developers can change the order with the stwc_cost_meta_keys filter.', 'stocktake-for-woocommerce' ),
					__( 'Items with no cost anywhere are counted and shown in the “No cost” column, and they are excluded from cost value and potential profit — the numbers stay honest instead of being padded with guesses. The variance value on stocktakes is the one exception: with no cost it falls back to the product’s regular price, so a missing cost never hides a discrepancy.', 'stocktake-for-woocommerce' ),
				),
			),
			array(
				__( 'Is it compatible with HPOS and current WooCommerce?', 'stocktake-for-woocommerce' ),
				__( 'Yes — the plugin declares HPOS compatibility and requires WooCommerce 7.0 or newer. Stock updates go through WooCommerce’s own functions, so other plugins listening for stock changes are notified normally.', 'stocktake-for-woocommerce' ),
			),
			array(
				__( 'What happens to my data if I uninstall?', 'stocktake-for-woocommerce' ),
				__( 'Deactivating keeps everything. Deleting the plugin from the Plugins screen removes its tables, options and the counting capability — export any reports you want to keep first.', 'stocktake-for-woocommerce' ),
			),
		);
		?>
		<div class="wrap stwc-app">
			<?php $this->hero( __( 'Help & FAQ', 'stocktake-for-woocommerce' ), __( 'Answers to the questions we get most — and where to reach us if you’re still stuck.', 'stocktake-for-woocommerce' ), '', $this->url() ); ?>

			<div class="stwc-cols">
				<div class="stwc-col-main">
					<div class="stwc-card stwc-faq">
						<?php foreach ( $faq as $qa ) : ?>
							<details class="stwc-faq__item">
								<summary><?php echo esc_html( $qa[0] ); ?></summary>
								<?php foreach ( (array) $qa[1] as $stwc_par ) : ?>
									<p><?php echo esc_html( $stwc_par ); ?></p>
								<?php endforeach; ?>
							</details>
						<?php endforeach; ?>
					</div>
				</div>
				<div class="stwc-col-side">
					<div class="stwc-card">
						<h2 class="stwc-card__title"><?php $this->the_icon( 'target' ); ?> <?php esc_html_e( 'Still stuck?', 'stocktake-for-woocommerce' ); ?></h2>
						<p class="stwc-hint" style="margin-top:0"><?php esc_html_e( 'Include your WordPress and WooCommerce versions and the steps you took — it usually halves the back-and-forth.', 'stocktake-for-woocommerce' ); ?></p>
						<?php if ( stwc_is_pro() ) : ?>
							<a class="stwc-btn stwc-btn--primary stwc-btn--block" href="<?php echo esc_url( function_exists( 'stwc_fs' ) ? admin_url( 'admin.php?page=stwc-sessions-contact' ) : stwc_support_url() ); ?>"><?php esc_html_e( 'Contact support (priority)', 'stocktake-for-woocommerce' ); ?></a>
						<?php else : ?>
							<a class="stwc-btn stwc-btn--primary stwc-btn--block" href="<?php echo esc_url( stwc_support_url() ); ?>" target="_blank" rel="noopener"><?php esc_html_e( 'Open the support forum', 'stocktake-for-woocommerce' ); ?></a>
							<p class="stwc-hint"><?php esc_html_e( 'Pro customers get priority email support directly inside wp-admin.', 'stocktake-for-woocommerce' ); ?></p>
						<?php endif; ?>
					</div>
				</div>
			</div>
		</div>
		<?php
	}


	/**
	 * Inventory valuation report. Free sees live grand totals (retail side) as
	 * a teaser; cost/profit, category breakdown and CSV export are Pro.
	 */


	private function render_report() {
		$tabs = array( 'valuation', 'reorder' );
		$tab = sanitize_key( $_GET['tab'] ?? 'valuation' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( ! in_array( $tab, $tabs, true ) ) {
			$tab = 'valuation';
		}
		$heads = array(
			'valuation' => array( __( 'Inventory valuation', 'stocktake-for-woocommerce' ), __( 'What your on-hand stock is worth right now.', 'stocktake-for-woocommerce' ) ),
			'reorder'   => array( __( 'Reorder shortlist', 'stocktake-for-woocommerce' ), __( 'What to buy next: everything at or below its low-stock level, or already out.', 'stocktake-for-woocommerce' ) ),
		);
		?>
		<div class="wrap stwc-app stwc-report">
			<?php
			$this->hero( $heads[ $tab ][0], $heads[ $tab ][1], '', $this->url() );
			$this->report_subnav( $tab );
			if ( 'reorder' === $tab ) {
				$this->report_reorder();
			} else {
				$this->report_valuation();
			}
			?>
		</div>
		<?php
	}

	/**
	 * Sub-navigation for the reports hub. Reorder is Free; Insights is Pro.
	 *
	 * @param string $active
	 */

	private function report_subnav( $active ) {
		$items = array(
			'valuation' => array( 'chart', __( 'Valuation', 'stocktake-for-woocommerce' ), false ),
			'reorder'   => array( 'cart', __( 'Reorder', 'stocktake-for-woocommerce' ), false ),
		);
		echo '<nav class="stwc-subnav">';
		foreach ( $items as $key => $it ) {
			$cls = 'stwc-subnav__item' . ( $key === $active ? ' is-active' : '' );
			echo '<a class="' . esc_attr( $cls ) . '" href="' . esc_url( $this->url( array( 'view' => 'report', 'tab' => $key ) ) ) . '">';
			echo wp_kses( $this->icon( $it[0] ), self::svg_allowed() ) . ' <span>' . esc_html( $it[1] ) . '</span>';
			echo '</a>';
		}
		echo '</nav>';
	}

	/**
	 * Valuation tab. Free shows the live retail totals; the pro build adds
	 * cost/profit tiles, per-category tables and shrinkage history.
	 */
	private function report_valuation() {
		$totals = STWC_Report::totals();
		?>

			<div class="stwc-grid">
				<div class="stwc-stat">
					<div class="stwc-stat__num"><?php echo esc_html( number_format_i18n( $totals['lines'] ) ); ?></div>
					<div class="stwc-stat__lbl"><?php esc_html_e( 'Stock-managed items', 'stocktake-for-woocommerce' ); ?></div>
				</div>
				<div class="stwc-stat">
					<div class="stwc-stat__num"><?php echo esc_html( number_format_i18n( $totals['units'] ) ); ?></div>
					<div class="stwc-stat__lbl"><?php esc_html_e( 'Units on hand', 'stocktake-for-woocommerce' ); ?></div>
				</div>
				<div class="stwc-stat">
					<div class="stwc-stat__num"><?php echo wp_kses_post( wc_price( $totals['retail_value'] ) ); ?></div>
					<div class="stwc-stat__lbl"><?php esc_html_e( 'Value at retail price', 'stocktake-for-woocommerce' ); ?></div>
				</div>
			</div>

		<?php
	}

	/**
	 * Reorder tab (Free): stock-managed products to buy now — at or below their
	 * low-stock level, or already out of stock.
	 */
	private function report_reorder() {
		$cap       = 1000;
		$rows      = STWC_Report::reorder_lines( $cap + 1 );
		$truncated = count( $rows ) > $cap;
		if ( $truncated ) {
			array_pop( $rows );
		}
		$out = 0;
		foreach ( $rows as $r ) {
			if ( 'outofstock' === $r['stock_status'] ) {
				++$out;
			}
		}
		$low = count( $rows ) - $out;
		$neg = STWC_Report::negative_stock_count();
		?>
		<div class="stwc-grid">
			<div class="stwc-stat">
				<div class="stwc-stat__num"><?php echo esc_html( number_format_i18n( count( $rows ) ) ); ?></div>
				<div class="stwc-stat__lbl"><?php esc_html_e( 'Products to reorder', 'stocktake-for-woocommerce' ); ?></div>
			</div>
			<div class="stwc-stat">
				<div class="stwc-stat__num stwc-neg"><?php echo esc_html( number_format_i18n( $out ) ); ?></div>
				<div class="stwc-stat__lbl"><?php esc_html_e( 'Out of stock', 'stocktake-for-woocommerce' ); ?></div>
			</div>
			<div class="stwc-stat">
				<div class="stwc-stat__num"><?php echo esc_html( number_format_i18n( $low ) ); ?></div>
				<div class="stwc-stat__lbl"><?php esc_html_e( 'Low stock', 'stocktake-for-woocommerce' ); ?></div>
			</div>
		</div>

		<?php if ( $neg > 0 ) : ?>
			<div class="stwc-flash" style="background:var(--stwc-neg-bg);color:var(--stwc-neg)"><?php
				/* translators: %d: number of products with a negative stock quantity. */
				printf( esc_html( _n( '%d product shows a negative stock quantity — likely an oversell or edit error worth fixing.', '%d products show negative stock quantities — likely oversells or edit errors worth fixing.', $neg, 'stocktake-for-woocommerce' ) ), (int) $neg );
			?></div>
		<?php endif; ?>

		<?php if ( empty( $rows ) ) : ?>
			<div class="stwc-card">
				<div class="stwc-empty">
					<div class="stwc-empty__icon"><?php $this->the_icon( 'check' ); ?></div>
					<h3><?php esc_html_e( 'Nothing to reorder', 'stocktake-for-woocommerce' ); ?></h3>
					<p><?php esc_html_e( 'Every stock-managed product is above its low-stock level. This list updates automatically as your stock changes.', 'stocktake-for-woocommerce' ); ?></p>
				</div>
			</div>
		<?php else : ?>
			<div class="stwc-toolbar">
				<a class="stwc-btn stwc-btn--primary" href="<?php echo esc_url( wp_nonce_url( $this->url( array( 'stwc_action' => 'reorder_export' ) ), 'stwc_reorder_export' ) ); ?>"><?php $this->the_icon( 'download' ); ?> <?php esc_html_e( 'Export CSV', 'stocktake-for-woocommerce' ); ?></a>
				<button type="button" class="stwc-btn" onclick="window.print()"><?php esc_html_e( 'Print', 'stocktake-for-woocommerce' ); ?></button>
			</div>
			<div class="stwc-card stwc-card--flush">
				<table class="stwc-table">
					<thead>
						<tr>
							<th><?php esc_html_e( 'Product', 'stocktake-for-woocommerce' ); ?></th>
							<th><?php esc_html_e( 'SKU', 'stocktake-for-woocommerce' ); ?></th>
							<th class="stwc-right"><?php esc_html_e( 'On hand', 'stocktake-for-woocommerce' ); ?></th>
							<th class="stwc-right"><?php esc_html_e( 'Low-stock level', 'stocktake-for-woocommerce' ); ?></th>
							<th><?php esc_html_e( 'Status', 'stocktake-for-woocommerce' ); ?></th>
						</tr>
					</thead>
					<tbody>
					<?php
					foreach ( $rows as $r ) :
						$product = wc_get_product( (int) $r['product_id'] );
						$name    = $product ? $product->get_name() : ( '#' . (int) $r['product_id'] );
						$edit    = $product ? get_edit_post_link( $product->get_id() ) : '';
						$oos     = ( 'outofstock' === $r['stock_status'] );
						?>
						<tr>
							<td><?php if ( $edit ) : ?><a class="stwc-link" href="<?php echo esc_url( $edit ); ?>"><?php echo esc_html( $name ); ?></a><?php else : echo esc_html( $name ); endif; ?></td>
							<td><code class="stwc-code"><?php echo esc_html( $r['sku'] ? $r['sku'] : '—' ); ?></code></td>
							<td class="stwc-right <?php echo esc_attr( ( $oos || (float) $r['qty'] <= 0 ) ? 'stwc-neg' : '' ); ?>"><?php echo esc_html( self::fmt_qty( $r['qty'] ) ); ?></td>
							<td class="stwc-right stwc-muted"><?php echo esc_html( self::fmt_qty( $r['threshold'] ) ); ?></td>
							<td><?php if ( $oos ) : ?><span class="stwc-pill stwc-pill--cancelled"><?php esc_html_e( 'Out of stock', 'stocktake-for-woocommerce' ); ?></span><?php else : ?><span class="stwc-badge stwc-badge--low"><?php esc_html_e( 'Low', 'stocktake-for-woocommerce' ); ?></span><?php endif; ?></td>
						</tr>
					<?php endforeach; ?>
					</tbody>
				</table>
			</div>
			<?php if ( $truncated ) : ?>
				<p class="stwc-hint"><?php
					/* translators: %s: number of rows shown. */
					printf( esc_html__( 'Showing the first %s products — export the CSV for the complete list.', 'stocktake-for-woocommerce' ), esc_html( number_format_i18n( $cap ) ) );
				?></p>
			<?php endif; ?>
			<p class="stwc-hint"><?php esc_html_e( 'The threshold is each product’s WooCommerce low-stock amount, or the store default when none is set. Stock-managed products only.', 'stocktake-for-woocommerce' ); ?></p>
		<?php endif; ?>
		<?php
	}


	/**
	 * One-time, dismissible review nudge after a couple of successful applies.
	 * Shown only on our own screens.
	 */
	private function maybe_review_notice() {
		if ( (int) get_option( 'stwc_success_applies', 0 ) < 2 || get_option( 'stwc_review_dismissed' ) ) {
			return;
		}
		$review_url  = 'https://wordpress.org/support/plugin/stocktake-for-woocommerce/reviews/#new-post';
		$dismiss_url = wp_nonce_url( $this->url( array( 'stwc_action' => 'dismiss_review' ) ), 'stwc_dismiss_review' );
		?>
		<div class="stwc-flash" style="background:var(--stwc-primary-soft);color:var(--stwc-primary);display:flex;align-items:center;gap:12px;flex-wrap:wrap">
			<span style="flex:1 1 auto;min-width:200px"><?php esc_html_e( 'Stocktake has updated your stock a few times now 🎉 If it saves you time, a quick review helps a lot.', 'stocktake-for-woocommerce' ); ?></span>
			<a class="stwc-btn stwc-btn--sm stwc-btn--primary" href="<?php echo esc_url( $review_url ); ?>" target="_blank" rel="noopener"><?php esc_html_e( 'Leave a review ★★★★★', 'stocktake-for-woocommerce' ); ?></a>
			<a class="stwc-btn stwc-btn--sm stwc-btn--ghost" href="<?php echo esc_url( $dismiss_url ); ?>"><?php esc_html_e( 'No thanks', 'stocktake-for-woocommerce' ); ?></a>
		</div>
		<?php
	}

	/* ---------------------------------------------------------------------
	 * CSV export
	 * ------------------------------------------------------------------- */

	/**
	 * Neutralise CSV/formula injection: a leading =, +, -, @ (or control char)
	 * in a spreadsheet cell can execute as a formula. Prefix with a quote.
	 *
	 * @param string $value
	 * @return string
	 */
	private static function csv_safe( $value ) {
		$value = (string) $value;
		if ( '' !== $value && strpbrk( $value[0], "=+-@\t\r\n" ) !== false ) {
			return "'" . $value;
		}
		return $value;
	}

	/**
	 * Column titles for the session export.
	 *
	 * @param string $mode
	 * @return string[]
	 */
	public static function session_export_headers( $mode ) {
		$stwc_receiving = false;
		return array(
			__( 'Product', 'stocktake-for-woocommerce' ),
			__( 'SKU', 'stocktake-for-woocommerce' ),
			$stwc_receiving ? __( 'Before', 'stocktake-for-woocommerce' ) : __( 'Expected', 'stocktake-for-woocommerce' ),
			$stwc_receiving ? __( 'Received', 'stocktake-for-woocommerce' ) : __( 'Counted', 'stocktake-for-woocommerce' ),
			__( 'Variance', 'stocktake-for-woocommerce' ),
			__( 'Unit value', 'stocktake-for-woocommerce' ),
			__( 'Variance value', 'stocktake-for-woocommerce' ),
			__( 'Applied', 'stocktake-for-woocommerce' ),
		);
	}

	/**
	 * Session export rows as a generator, fetched in id-keyed pages of 1000 so
	 * an export never materialises the whole item set in memory. Restock
	 * (goods-in) variance is the received amount — the figure the session
	 * screen shows — never received minus the pre-receiving snapshot.
	 *
	 * @param int    $session_id
	 * @param string $mode
	 * @return Generator
	 */
	public static function session_export_rows( $session_id, $mode ) {
		global $wpdb;
		$stwc_receiving = false;
		$last_id    = 0;
		do {
			$batch = $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- literal SQL around the plugin's fixed items table; all variables prepared.
				$wpdb->prepare(
					'SELECT id, name, sku, expected_qty, counted_qty, unit_cost, applied FROM %i WHERE session_id = %d AND id > %d ORDER BY id ASC LIMIT 1000',
				STWC_Sessions::items_table(),
					$session_id,
					$last_id
				),
				ARRAY_A
			);
			foreach ( $batch as $it ) {
				$last_id = (int) $it['id'];
				{
					$eff      = STWC_Sessions::effective_counted( $it, $mode );
					$counted  = is_null( $it['counted_qty'] ) ? ( 'full' === $mode ? 0 : '' ) : (float) $it['counted_qty'];
					$variance = is_null( $eff ) ? '' : $eff - (float) $it['expected_qty'];
				}
				$value = ( '' === $variance ) ? '' : $variance * (float) $it['unit_cost'];
				yield array(
					(string) $it['name'],
					(string) $it['sku'],
					(float) $it['expected_qty'],
					$counted,
					$variance,
					(float) $it['unit_cost'],
					$value,
					( 1 === (int) $it['applied'] ) ? __( 'Yes', 'stocktake-for-woocommerce' ) : __( 'No', 'stocktake-for-woocommerce' ),
				);
			}
		} while ( count( $batch ) === 1000 );
	}

	/**
	 * Column titles + rows for the session export in one array — kept for the
	 * test battery, which inspects it directly. Production exports stream from
	 * session_export_rows() instead.
	 *
	 * @param int $session_id
	 * @return array|WP_Error array( 'headers' => string[], 'rows' => array[] )
	 */
	public static function session_export_data( $session_id ) {
		$session = STWC_Sessions::get_session( $session_id );
		if ( ! $session ) {
			return new WP_Error( 'stwc_no_session', __( 'Session not found.', 'stocktake-for-woocommerce' ) );
		}
		return array(
			'headers' => self::session_export_headers( $session['count_mode'] ),
			'rows'    => iterator_to_array( self::session_export_rows( $session_id, $session['count_mode'] ), false ),
		);
	}

	private function export_csv( $session_id ) {
		$session = STWC_Sessions::get_session( $session_id );
		if ( ! $session ) {
			wp_die( esc_html__( 'Session not found.', 'stocktake-for-woocommerce' ) );
		}

		nocache_headers();
		header( 'Content-Type: text/csv; charset=utf-8' );
		header( 'Content-Disposition: attachment; filename=stocktake-' . $session_id . '.csv' );

		$out = fopen( 'php://output', 'w' );
		fwrite( $out, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- UTF-8 BOM so Excel decodes accented/Turkish text.
		fputcsv( $out, self::session_export_headers( $session['count_mode'] ), ',', '"', '\\' );
		// Rows stream straight from the paged generator — a large session never
		// builds the full dataset in memory.
		foreach ( self::session_export_rows( $session_id, $session['count_mode'] ) as $row ) {
			fputcsv( $out, array_map( array( __CLASS__, 'csv_cell' ), $row ), ',', '"', '\\' );
		}
		fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- php://output stream.
	}

	/** Formula-proof string cells; numbers pass through untouched. */
	private static function csv_cell( $value ) {
		return is_string( $value ) ? self::csv_safe( $value ) : $value;
	}

	/* ---------------------------------------------------------------------
	 * UI helpers
	 * ------------------------------------------------------------------- */

	private function hero( $title, $subtitle = '', $meta_html = '', $back_url = '' ) {
		?>
		<hr class="wp-header-end" />
		<div class="stwc-hero">
			<div class="stwc-hero__main">
				<?php if ( $back_url ) : ?>
					<a class="stwc-hero__back" href="<?php echo esc_url( $back_url ); ?>"><?php $this->the_icon( 'arrow' ); ?></a>
				<?php else : ?>
					<span class="stwc-hero__badge"><?php $this->the_icon( 'clipboard' ); ?></span>
				<?php endif; ?>
				<div>
					<h1 class="stwc-hero__title"><?php echo esc_html( $title ); ?></h1>
					<?php if ( $subtitle ) : ?><p class="stwc-hero__sub"><?php echo esc_html( $subtitle ); ?></p><?php endif; ?>
				</div>
			</div>
			<div class="stwc-hero__meta">
				<?php echo wp_kses( $meta_html, self::svg_kses_html() ); ?>
				<?php $this->lang_switcher(); ?>
			</div>
		</div>
		<?php
	}

	/**
	 * A small, simplified inline SVG flag for a locale.
	 *
	 * @param string $locale
	 * @return string
	 */
	private function flag( $locale ) {
		$svg = array(
			'en_US' => '<rect width="20" height="14" fill="#fff"/><g fill="#b22234"><rect width="20" height="2"/><rect y="4" width="20" height="2"/><rect y="8" width="20" height="2"/><rect y="12" width="20" height="2"/></g><rect width="9" height="8" fill="#3c3b6e"/><g fill="#fff"><circle cx="2" cy="2" r=".6"/><circle cx="5" cy="2" r=".6"/><circle cx="7.5" cy="2" r=".6"/><circle cx="3.5" cy="4" r=".6"/><circle cx="6.2" cy="4" r=".6"/><circle cx="2" cy="6" r=".6"/><circle cx="5" cy="6" r=".6"/><circle cx="7.5" cy="6" r=".6"/></g>',
			'tr_TR' => '<rect width="20" height="14" fill="#e30a17"/><circle cx="8" cy="7" r="3.3" fill="#fff"/><circle cx="9" cy="7" r="2.6" fill="#e30a17"/><path d="M12.1 7l2.3-.75-1.42 1.96V5.79l1.42 1.96z" fill="#fff"/>',
			'es_ES' => '<rect width="20" height="14" fill="#c60b1e"/><rect y="3.5" width="20" height="7" fill="#ffc400"/>',
			'de_DE' => '<rect width="20" height="14" fill="#000"/><rect y="4.67" width="20" height="4.66" fill="#dd0000"/><rect y="9.33" width="20" height="4.67" fill="#ffce00"/>',
			'fr_FR' => '<rect width="20" height="14" fill="#fff"/><rect width="6.67" height="14" fill="#0055a4"/><rect x="13.33" width="6.67" height="14" fill="#ef4135"/>',
			'it_IT' => '<rect width="20" height="14" fill="#fff"/><rect width="6.67" height="14" fill="#008c45"/><rect x="13.33" width="6.67" height="14" fill="#cd212a"/>',
			'pt_BR' => '<rect width="20" height="14" fill="#009c3b"/><path d="M10 1.6 18.4 7 10 12.4 1.6 7z" fill="#ffdf00"/><circle cx="10" cy="7" r="2.6" fill="#002776"/>',
			'ar'    => '<rect width="20" height="14" fill="#006c35"/><rect x="3" y="8.6" width="14" height="1.1" rx=".3" fill="#fff"/><rect x="4.5" y="4.2" width="11" height="2.1" rx=".4" fill="#fff" opacity=".92"/>',
		);
		$body = $svg[ $locale ] ?? '<rect width="20" height="14" fill="#94a3b8"/>';
		return '<span class="stwc-flag"><svg viewBox="0 0 20 14" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' . $body . '</svg></span>';
	}

	/**
	 * Header language switcher: a flag pill that opens a flag+name list. The
	 * collapsed state always shows the *effective* language, never "Automatic".
	 */
	private function lang_switcher() {
		$pref    = stwc_user_locale_pref();
		$eff     = stwc_effective_locale();
		$locales = stwc_locales();
		$eff_lbl = $locales[ $eff ] ?? $eff;
		?>
		<form method="post" action="<?php echo esc_url( $this->url() ); ?>" class="stwc-lang" data-stwc-lang>
			<?php wp_nonce_field( 'stwc_set_locale' ); ?>
			<input type="hidden" name="stwc_action" value="set_locale" />
			<input type="hidden" name="back_view" value="<?php echo esc_attr( sanitize_key( $_GET['view'] ?? 'list' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>" />
			<input type="hidden" name="back_session" value="<?php echo esc_attr( absint( $_GET['session'] ?? 0 ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>" />

			<button type="button" class="stwc-lang__toggle" aria-haspopup="listbox" aria-expanded="false" aria-label="<?php esc_attr_e( 'Language', 'stocktake-for-woocommerce' ); ?>">
				<?php echo wp_kses( $this->flag( $eff ), self::svg_kses_html() ); ?>
				<span class="stwc-lang__label"><?php echo esc_html( $eff_lbl ); ?></span>
				<?php $this->the_icon( 'chev' ); ?>
			</button>

			<ul class="stwc-lang__menu" role="listbox" hidden>
				<li>
					<button type="submit" name="stwc_locale" value="auto" class="stwc-lang__opt<?php echo esc_attr( 'auto' === $pref ? ' is-cur' : '' ); ?>">
						<span class="stwc-flag stwc-flag--auto"><?php $this->the_icon( 'globe' ); ?></span>
						<span class="stwc-lang__optlbl"><?php esc_html_e( 'Automatic', 'stocktake-for-woocommerce' ); ?></span>
						<?php if ( 'auto' === $pref ) { $this->the_icon( 'check' ); } ?>
					</button>
				</li>
				<?php foreach ( $locales as $code => $label ) : ?>
					<li>
						<button type="submit" name="stwc_locale" value="<?php echo esc_attr( $code ); ?>" class="stwc-lang__opt<?php echo esc_attr( $code === $pref ? ' is-cur' : '' ); ?>">
							<?php echo wp_kses( $this->flag( $code ), self::svg_kses_html() ); ?>
							<span class="stwc-lang__optlbl"><?php echo esc_html( $label ); ?></span>
							<?php if ( $code === $pref ) { $this->the_icon( 'check' ); } ?>
						</button>
					</li>
				<?php endforeach; ?>
			</ul>
		</form>
		<?php
	}

	private function mode_card( $value, $title, $desc, $checked, $locked = false ) {
		?>
		<label class="stwc-mode">
			<input type="radio" name="count_mode" value="<?php echo esc_attr( $value ); ?>" <?php checked( $checked ); ?> />
			<span class="stwc-mode__body">
				<span class="stwc-mode__icon"><?php echo wp_kses( $this->mode_icon( $value ), self::svg_allowed() ); ?></span>
				<span class="stwc-mode__text">
					<span class="stwc-mode__title"><?php echo esc_html( $title ); ?></span>
					<span class="stwc-mode__desc"><?php echo esc_html( $desc ); ?></span>
				</span>
			</span>
		</label>
		<?php
	}

	private function mode_label( $mode ) {
		$map = array(
			'full'     => __( 'Full / zero-based', 'stocktake-for-woocommerce' ),
			'partial'  => __( 'Partial', 'stocktake-for-woocommerce' ),
		);
		return $map[ $mode ] ?? $mode;
	}

	private function mode_desc( $mode ) {
		$map = array(
			'full'     => __( 'True physical inventory — anything not counted is set to 0.', 'stocktake-for-woocommerce' ),
			'partial'  => __( 'Spot count — only the items you scan are updated.', 'stocktake-for-woocommerce' ),
		);
		return $map[ $mode ] ?? '';
	}

	private function status_label( $status ) {
		$map = array(
			'open'      => __( 'Open', 'stocktake-for-woocommerce' ),
			'completed' => __( 'Completed', 'stocktake-for-woocommerce' ),
			'cancelled' => __( 'Cancelled', 'stocktake-for-woocommerce' ),
		);
		return $map[ $status ] ?? $status;
	}

	/**
	 * Tiny inline SVG icon set (currentColor).
	 *
	 * @param string $name
	 * @return string
	 */
	private function icon( $name ) {
		$paths = array(
			'scan'      => '<path d="M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2M3 12h18"/>',
			'plus'      => '<path d="M12 5v14M5 12h14"/>',
			'check'     => '<path d="M20 6 9 17l-5-5"/>',
			'download'  => '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/>',
			'arrow'     => '<path d="M19 12H5M12 19l-7-7 7-7"/>',
			'clipboard' => '<rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>',
			'zero'      => '<circle cx="12" cy="12" r="9"/><path d="M12 7v10"/>',
			'target'    => '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4"/>',
			'stack'     => '<path d="M12 2 2 7l10 5 10-5-10-5ZM2 17l10 5 10-5M2 12l10 5 10-5"/>',
			'camera'    => '<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/>',
			'undo'      => '<path d="M3 7v6h6"/><path d="M3 13a9 9 0 1 0 3-7.7L3 8"/>',
			'lock'      => '<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
			'chart'     => '<path d="M3 3v18h18"/><path d="M7 15v3M12 10v8M17 6v12"/>',
			'globe'     => '<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/>',
			'chev'      => '<path d="m6 9 6 6 6-6"/>',
			'cart'      => '<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"/>',
			'alert'     => '<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4M12 17h.01"/>',
			'cancel'    => '<circle cx="12" cy="12" r="9"/><path d="m15 9-6 6M9 9l6 6"/>',
			'layers'    => '<path d="m12 2 10 5-10 5L2 7l10-5z"/><path d="m2 17 10 5 10-5M2 12l10 5 10-5"/>',
		);
		$d = $paths[ $name ] ?? '';
		return '<svg class="stwc-i" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' . $d . '</svg>';
	}

	/**
	 * Allowed tags/attributes for our own inline-SVG icons, so icon markup can
	 * be escaped late through wp_kses() instead of echoed raw. Covers every tag
	 * used by icon() and flag(), plus a few harmless SVG extras for future icons.
	 *
	 * @return array
	 */
	private static function svg_allowed() {
		$svg_attr = array(
			'class'             => true,
			'viewbox'           => true,
			'width'             => true,
			'height'            => true,
			'fill'              => true,
			'stroke'            => true,
			'stroke-width'      => true,
			'stroke-linecap'    => true,
			'stroke-linejoin'   => true,
			'stroke-miterlimit' => true,
			'fill-rule'         => true,
			'clip-rule'         => true,
			'opacity'           => true,
			'transform'         => true,
			'aria-hidden'       => true,
			'focusable'         => true,
			'role'              => true,
			'xmlns'             => true,
		);
		return array(
			'svg'      => $svg_attr,
			'g'        => $svg_attr,
			'path'     => array( 'd' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'fill-rule' => true, 'clip-rule' => true, 'opacity' => true, 'transform' => true ),
			'rect'     => array( 'x' => true, 'y' => true, 'width' => true, 'height' => true, 'rx' => true, 'ry' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'opacity' => true ),
			'circle'   => array( 'cx' => true, 'cy' => true, 'r' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'opacity' => true ),
			'line'     => array( 'x1' => true, 'y1' => true, 'x2' => true, 'y2' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true ),
			'polyline' => array( 'points' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true ),
			'polygon'  => array( 'points' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true ),
		);
	}

	/**
	 * Allowed tags for our internally-built hero/report markup: the SVG icon set
	 * plus the handful of HTML wrappers those strings use (links, spans, mock
	 * tiles). Lets internally built markup (hero meta, category paths) escape late
	 * through wp_kses() with nothing stripped.
	 *
	 * @return array
	 */
	private static function svg_kses_html() {
		return array_merge(
			self::svg_allowed(),
			array(
				'a'      => array( 'class' => true, 'style' => true, 'href' => true, 'target' => true, 'rel' => true, 'title' => true, 'aria-hidden' => true ),
				'span'   => array( 'class' => true, 'style' => true, 'title' => true, 'aria-hidden' => true ),
				'div'    => array( 'class' => true, 'style' => true, 'aria-hidden' => true ),
				'strong' => array( 'class' => true ),
				'b'      => array( 'class' => true ),
				'em'     => array( 'class' => true ),
				'i'      => array( 'class' => true, 'style' => true ),
				'small'  => array( 'class' => true ),
				'br'     => array(),
			)
		);
	}

	/**
	 * Echo one of our inline-SVG icons, escaped late through wp_kses().
	 *
	 * @param string $name Icon key passed to icon().
	 */
	private function the_icon( $name ) {
		echo wp_kses( $this->icon( $name ), self::svg_allowed() );
	}

	private function mode_icon( $mode ) {
		$map = array(
			'full'     => 'zero',
			'partial'  => 'target',
		);
		return $this->icon( $map[ $mode ] ?? 'clipboard' );
	}


	/**
	 * Format a stock quantity for display: kill float noise, trim trailing zeros.
	 *
	 * @param mixed $n
	 * @return string
	 */
	private static function fmt_qty( $n ) {
		$s = rtrim( rtrim( number_format( round( (float) $n, 4 ), 4, '.', '' ), '0' ), '.' );
		return ( '' === $s || '-0' === $s ) ? '0' : $s;
	}

	private function url( $args = array() ) {
		return add_query_arg( wp_parse_args( $args, array( 'page' => self::SLUG ) ), admin_url( 'admin.php' ) );
	}

	private function action_url( $action, $session_id ) {
		return wp_nonce_url(
			$this->url( array( 'stwc_action' => $action, 'session' => $session_id ) ),
			'stwc_' . $action . '_' . $session_id
		);
	}

}
