<?php
/**
 * Class Noesia Cs Doctor Service.
 *
 * @package Noesia_Commerce_Suite
 */

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

/**
 * Class implementation.
 */
class Noesia_CS_Doctor_Service {
	const EXAMPLE_LIMIT = 20;
	const QUICK_SCAN_PRODUCT_SCOPE = 300;
	const QUICK_SCAN_BROKEN_LINK_SCOPE = 15;
	const BROKEN_LINK_TIMEOUT = 2;
	const AUTOLOAD_THRESHOLD_BYTES = 800000;
	const REPORT_OPTION = 'noesia_cs_doctor_report';
	const HISTORY_OPTION = 'noesia_cs_doctor_history';
	/**
	 * Runtime dependency or state value.
	 *
	 * @var mixed
	 */
	private $product_repository;

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $product_repository Product repository.
	 */
	public function __construct( Noesia_CS_Product_Repository $product_repository = null ) {
		$this->product_repository = $product_repository ? $product_repository : new Noesia_CS_Product_Repository();
	}

	/**
	 * Handles the related plugin operation.
	 */
	public function run_scan() {
		$results = array(
			'generated_at'      => time(),
			'scanned_products'  => 0,
			'severity_counts'   => array(
				'critical'          => 0,
				'high'              => 0,
				'medium'            => 0,
				'low'               => 0,
				'success'           => 0,
			),
			'checks'  => array(),
			'items'   => array(),
		);

		if ( 'yes' !== Noesia_CS_Settings::get( 'enable_doctor_scan', 'yes' ) ) {
			$results['items'][] = $this->item( 'low', __( 'Ecommerce Doctor scan is disabled.', 'noesia-commerce-suite' ), __( 'Enable product scanning in Settings before running a manual scan.', 'noesia-commerce-suite' ) );
			return $this->store_report( $results );
		}

		if ( ! $this->product_repository->is_available() ) {
			$results['items'][] = $this->item( 'high', __( 'WooCommerce is not active.', 'noesia-commerce-suite' ), __( 'Commerce checks need WooCommerce active.', 'noesia-commerce-suite' ) );
			return $this->store_report( $results );
		}

		$limit = absint( Noesia_CS_Settings::get( 'doctor_product_scan_limit', 250 ) );
		if ( $limit < 1 ) {
			$limit = 250;
		}
		$limit = min( self::QUICK_SCAN_PRODUCT_SCOPE, $limit );

		$products = $this->product_repository->get_published_products( $limit );

		$results['scanned_products']  = count( $products );
		$results['product_limit']     = $limit;

		$issues = array(
			'without_image'      => array( 'count' => 0, 'examples' => array() ),
			'without_price'      => array( 'count' => 0, 'examples' => array() ),
			'without_sku'        => array( 'count' => 0, 'examples' => array() ),
			'without_category'   => array( 'count' => 0, 'examples' => array() ),
			'short_description'  => array( 'count' => 0, 'examples' => array() ),
			'missing_alt'        => array( 'count' => 0, 'examples' => array() ),
			'large_images'       => array( 'count' => 0, 'examples' => array() ),
			'broken_links'       => array( 'count' => 0, 'examples' => array() ),
			'missing_shipping'   => array( 'count' => 0, 'examples' => array() ),
			'missing_related'    => array( 'count' => 0, 'examples' => array() ),
			'hidden_in_stock'    => array( 'count' => 0, 'examples' => array() ),
			'visible_oos'        => array( 'count' => 0, 'examples' => array() ),
		);

		$large_image_kb = absint( Noesia_CS_Settings::get( 'doctor_large_image_kb', 500 ) );
		if ( $large_image_kb < 100 ) {
			$large_image_kb = 500;
		}

		$broken_link_checks  = 0;
		$broken_link_limit   = absint( Noesia_CS_Settings::get( 'doctor_broken_link_limit', 20 ) );
		if ( $broken_link_limit < 1 ) {
			$broken_link_limit = 20;
		}
		$broken_link_limit = min( self::QUICK_SCAN_BROKEN_LINK_SCOPE, $broken_link_limit );

		foreach ( $products as $product ) {
			if ( ! is_a( $product, 'WC_Product' ) ) {
				continue;
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_without_image', 'yes' ) && ! $product->get_image_id() ) {
				$this->add_product_issue( $issues['without_image'], $product );
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_without_price', 'yes' ) && '' === $product->get_regular_price() && '' === $product->get_price() ) {
				$this->add_product_issue( $issues['without_price'], $product );
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_without_sku', 'yes' ) && '' === $product->get_sku() ) {
				$this->add_product_issue( $issues['without_sku'], $product );
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_without_category', 'yes' ) && empty( $product->get_category_ids() ) ) {
				$this->add_product_issue( $issues['without_category'], $product );
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_short_description', 'yes' ) && strlen( wp_strip_all_tags( $product->get_description() ) ) < 80 ) {
				$this->add_product_issue( $issues['short_description'], $product );
			}

			$image_id = $product->get_image_id();
			if ( $image_id && 'yes' === Noesia_CS_Settings::get( 'doctor_scan_missing_alt', 'yes' ) && '' === trim( (string) get_post_meta( $image_id, '_wp_attachment_image_alt', true ) ) ) {
				$this->add_product_issue( $issues['missing_alt'], $product );
			}

			if ( $image_id && 'yes' === Noesia_CS_Settings::get( 'doctor_scan_large_images', 'no' ) ) {
				$file = get_attached_file( $image_id );
				if ( $file && file_exists( $file ) && filesize( $file ) > ( $large_image_kb * 1024 ) ) {
					$this->add_product_issue( $issues['large_images'], $product );
				}
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_missing_shipping_data', 'yes' ) && $product->needs_shipping() && ( '' === trim( (string) $product->get_weight() ) || ! $product->has_dimensions() ) ) {
				$this->add_product_issue( $issues['missing_shipping'], $product );
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_missing_related_products', 'yes' ) && empty( $product->get_cross_sell_ids() ) && empty( $product->get_upsell_ids() ) ) {
				$this->add_product_issue( $issues['missing_related'], $product );
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_hidden_in_stock', 'yes' ) && 'hidden' === $product->get_catalog_visibility() && 'instock' === $product->get_stock_status() ) {
				$this->add_product_issue( $issues['hidden_in_stock'], $product );
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_visible_out_of_stock', 'yes' ) && 'outofstock' === $product->get_stock_status() && 'hidden' !== $product->get_catalog_visibility() ) {
				$this->add_product_issue( $issues['visible_oos'], $product );
			}

			if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_broken_links', 'no' ) && $broken_link_checks < $broken_link_limit ) {
				++$broken_link_checks;
				if ( ! $this->product_permalink_is_reachable( $product ) ) {
					$this->add_product_issue( $issues['broken_links'], $product );
				}
			}
		}

		$results['checks']['broken_link_checked'] = $broken_link_checks;
		$results['checks']['broken_link_limit']   = $broken_link_limit;
		$results['checks']['example_limit']       = self::EXAMPLE_LIMIT;
		$results['checks']['large_image_kb']       = $large_image_kb;

		$this->append_count_item( $results, $issues['without_image'], 'medium', __( 'Products without image', 'noesia-commerce-suite' ), 'without_image' );
		$this->append_count_item( $results, $issues['without_price'], 'high', __( 'Products without price', 'noesia-commerce-suite' ), 'without_price' );
		$this->append_count_item( $results, $issues['without_sku'], 'medium', __( 'Products without SKU', 'noesia-commerce-suite' ), 'without_sku' );
		$this->append_count_item( $results, $issues['without_category'], 'medium', __( 'Products without category', 'noesia-commerce-suite' ), 'without_category' );
		$this->append_count_item( $results, $issues['short_description'], 'low', __( 'Products with short description', 'noesia-commerce-suite' ), 'short_description' );
		$this->append_count_item( $results, $issues['missing_alt'], 'low', __( 'Product images missing alt text', 'noesia-commerce-suite' ), 'missing_alt' );
		$this->append_count_item( $results, $issues['large_images'], 'medium', sprintf( /* translators: %d: Image size threshold in KB. */ __( 'Product images larger than %d KB', 'noesia-commerce-suite' ), $large_image_kb ), 'large_images' );
		$this->append_count_item( $results, $issues['missing_shipping'], 'high', __( 'Products missing shipping data', 'noesia-commerce-suite' ), 'missing_shipping', __( 'Missing weight or dimensions can break carrier or weight-based shipping calculations.', 'noesia-commerce-suite' ) );
		$this->append_count_item( $results, $issues['missing_related'], 'low', __( 'Products without cross-sells or upsells', 'noesia-commerce-suite' ), 'missing_related', __( 'These products may miss opportunities to increase average order value.', 'noesia-commerce-suite' ) );
		$this->append_count_item( $results, $issues['hidden_in_stock'], 'medium', __( 'Hidden products still in stock', 'noesia-commerce-suite' ), 'hidden_in_stock', __( 'These products are in stock but hidden from the catalog, which may indicate forgotten sellable inventory.', 'noesia-commerce-suite' ) );
		$this->append_count_item( $results, $issues['visible_oos'], 'medium', __( 'Visible products out of stock', 'noesia-commerce-suite' ), 'visible_oos', __( 'Visible out-of-stock products can frustrate customers or make catalog pages look inactive.', 'noesia-commerce-suite' ) );
		$this->append_count_item( $results, $issues['broken_links'], 'high', __( 'Product links returning an error', 'noesia-commerce-suite' ), 'broken_links', __( 'These product URLs returned an error during the manual Quick Scan URL scope. Broken product pages can hurt customer trust and block sales paths.', 'noesia-commerce-suite' ) );

		$this->append_risk_and_retention_checks( $results );

		if ( 'yes' === Noesia_CS_Settings::get( 'doctor_check_wc_pages', 'yes' ) && function_exists( 'wc_get_page_id' ) ) {
			foreach ( array( 'cart', 'checkout', 'myaccount' ) as $page_key ) {
				$page_id = wc_get_page_id( $page_key );
				if ( $page_id <= 0 || 'publish' !== get_post_status( $page_id ) ) {
					$results['items'][] = $this->item( 'high', sprintf( /* translators: %s: WooCommerce page key. */ __( 'Missing WooCommerce %s page', 'noesia-commerce-suite' ), $page_key ), __( 'Set the required WooCommerce page in WooCommerce settings.', 'noesia-commerce-suite' ), 1, array(), 'missing_wc_page' );
				}
			}
		}

		if ( empty( $results['items'] ) ) {
			$results['items'][] = $this->item( 'success', __( 'No configured ecommerce issues detected.', 'noesia-commerce-suite' ), sprintf( /* translators: %d: Number of scanned products. */ __( 'The latest manual scan did not find configured problems in %d published products.', 'noesia-commerce-suite' ), absint( $results['scanned_products'] ) ), 0, array(), 'clean' );
		}

		return $this->store_report( $results );
	}

	/**
	 * Handles the related plugin operation.
	 */
	public function get_last_report() {
		$report = get_option( self::REPORT_OPTION, array() );
		if ( ! is_array( $report ) ) {
			$report = array();
		}
		return wp_parse_args(
			$report,
			array(
				'generated_at'       => 0,
				'scanned_products'   => 0,
				'severity_counts'    => array( 'critical' => 0, 'high' => 0, 'medium' => 0, 'low' => 0, 'success' => 0 ),
				'checks'             => array(),
				'items'              => array(),
			)
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $results Results.
	 */
	private function store_report( $results ) {
		$results['severity_counts'] = $this->count_severities( $results['items'] );
		update_option( self::REPORT_OPTION, $results, false );
		return $results;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $items Items.
	 */
	private function count_severities( $items ) {
		$counts = array(
			'critical'  => 0,
			'high'      => 0,
			'medium'    => 0,
			'low'       => 0,
			'success'   => 0,
		);
		foreach ( (array) $items as $item ) {
			$severity = isset( $item['severity'] ) ? sanitize_key( $item['severity'] ) : 'low';
			if ( ! isset( $counts[ $severity ] ) ) {
				$counts[ $severity ] = 0;
			}
			$counts[ $severity ]++;
		}
		return $counts;
	}


	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $results Results.
	 */
	private function append_risk_and_retention_checks( &$results ) {
		if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_suspicious_order_signals', 'yes' ) ) {
			$this->append_suspicious_order_signals( $results );
		}

		if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_data_retention_review', 'yes' ) ) {
			$this->append_data_retention_review( $results );
		}

		if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_temporary_data', 'yes' ) ) {
			$this->append_temporary_data_review( $results );
		}

		if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_autoloaded_options', 'yes' ) ) {
			$this->append_autoloaded_options_health( $results );
		}

		if ( 'yes' === Noesia_CS_Settings::get( 'doctor_scan_action_scheduler_queue', 'yes' ) ) {
			$this->append_action_scheduler_queue_health( $results );
		}
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $results Results.
	 */
	private function append_suspicious_order_signals( &$results ) {
		if ( ! function_exists( 'wc_get_orders' ) ) {
			return;
		}

		$orders = wc_get_orders(
			array(
				'limit'    => 80,
				'orderby'  => 'date',
				'order'    => 'DESC',
				'status'   => array( 'wc-pending', 'wc-processing', 'wc-on-hold', 'wc-completed' ),
				'return'   => 'objects',
			)
		);

		$issue = array(
			'count'     => 0,
			'examples'  => array(),
		);
		$cod_signals = 0;

		foreach ( (array) $orders as $order ) {
			if ( ! is_a( $order, 'WC_Order' ) ) {
				continue;
			}

			$signals = $this->suspicious_order_signals_for_order( $order );
			if ( empty( $signals ) ) {
				continue;
			}

			if ( 'cod' === $order->get_payment_method() ) {
				++$cod_signals;
			}

			$this->add_order_issue( $issue, $order, $signals );
		}

		$results['checks']['suspicious_order_signals_scanned'] = count( (array) $orders );

		if ( $issue['count'] > 0 ) {
			$severity = $cod_signals > 0 ? 'high' : 'medium';
			$message = $cod_signals > 0
				? __( 'Recent orders include risk signals, including Cash on Delivery orders. These are signals only, not proof of fraud. Review before shipping.', 'noesia-commerce-suite' )
				: __( 'Recent orders include risk signals such as disposable email domains or generic-looking names. These are signals only, not proof of fraud.', 'noesia-commerce-suite' );
			$results['items'][] = $this->item( $severity, __( 'Suspicious order signals', 'noesia-commerce-suite' ), $message, $issue['count'], $issue['examples'], 'suspicious_order_signals' );
		}
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $order Order.
	 */
	private function suspicious_order_signals_for_order( $order ) {
		$signals  = array();
		$email    = strtolower( trim( (string) $order->get_billing_email() ) );
		$domain   = '';

		if ( false !== strpos( $email, '@' ) ) {
			$domain = substr( strrchr( $email, '@' ), 1 );
		}

		$disposable_domains = array(
			'10minutemail.com',
			'mailinator.com',
			'guerrillamail.com',
			'yopmail.com',
			'trashmail.com',
			'temp-mail.org',
			'tempmail.com',
			'getnada.com',
			'sharklasers.com',
			'dispostable.com',
		);

		if ( $domain && in_array( $domain, $disposable_domains, true ) ) {
			$signals[] = __( 'disposable email domain', 'noesia-commerce-suite' );
		}

		$name  = strtolower( trim( (string) $order->get_billing_first_name() . ' ' . (string) $order->get_billing_last_name() ) );
		$name  = preg_replace( '/\s+/', ' ', $name );

		if ( $name && preg_match( '/^(test|asdf|asdfg|qwerty|aaaa|bbbb|xxx|xxxxx)(\s+\1)?$/', $name ) ) {
			$signals[] = __( 'generic-looking customer name', 'noesia-commerce-suite' );
		}

		if ( $name && preg_match( '/(.)\1{5,}/', str_replace( ' ', '', $name ) ) ) {
			$signals[] = __( 'repeated-character customer name', 'noesia-commerce-suite' );
		}

		return array_unique( $signals );
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $results Results.
	 */
	private function append_data_retention_review( &$results ) {
		if ( ! function_exists( 'wc_get_orders' ) ) {
			return;
		}

		$months = absint( Noesia_CS_Settings::get( 'doctor_data_retention_months', 24 ) );
		if ( $months < 1 ) {
			$months = 24;
		}

		$timestamp = strtotime( '-' . $months . ' months', current_time( 'timestamp' ) );
		if ( ! $timestamp ) {
			return;
		}
		$date_before  = gmdate( 'Y-m-d H:i:s', $timestamp );
		$statuses     = array( 'wc-cancelled', 'wc-failed', 'wc-refunded' );

		$total = 0;
		$query = wc_get_orders(
			array(
				'limit'         => 1,
				'status'        => $statuses,
				'date_created'  => '<' . $date_before,
				'return'        => 'ids',
				'paginate'      => true,
			)
		);

		if ( is_object( $query ) && isset( $query->total ) ) {
			$total = absint( $query->total );
		} elseif ( is_array( $query ) ) {
			$total = count( $query );
		}

		$results['checks']['data_retention_months'] = $months;
		$results['checks']['old_failed_cancelled_refunded_orders'] = $total;

		if ( $total < 1 ) {
			return;
		}

		$examples = array();
		$ids = wc_get_orders(
			array(
				'limit'         => 8,
				'status'        => $statuses,
				'date_created'  => '<' . $date_before,
				'orderby'       => 'date',
				'order'         => 'DESC',
				'return'        => 'ids',
			)
		);

		foreach ( (array) $ids as $order_id ) {
			$order = wc_get_order( $order_id );
			if ( $order ) {
				$examples[] = $this->order_example( $order );
			}
		}

		$results['items'][] = $this->item(
			'medium',
			__( 'Data retention review items', 'noesia-commerce-suite' ),
			sprintf(
				/* translators: %d: retention age in months. */
				__( 'Cancelled, failed or refunded orders older than %d months should be reviewed against your store data retention policy. Do not delete records required for tax, accounting, warranty or legal obligations.', 'noesia-commerce-suite' ),
				$months
			),
			$total,
			$examples,
			'data_retention_review'
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $table_name Table name.
	 */
	private function safe_table_name( $table_name ) {
		$table_name = is_string( $table_name ) ? $table_name : '';
		return preg_match( '/^[A-Za-z0-9_]+$/', $table_name ) ? $table_name : '';
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $results Results.
	 */
	private function append_temporary_data_review( &$results ) {
		global $wpdb;

		if ( ! $wpdb || empty( $wpdb->options ) ) {
			return;
		}

		$now = time();
		$expired_transients = absint(
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for expired transient records.
			$wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT(option_id) FROM {$wpdb->options} WHERE option_name LIKE %s AND CAST(option_value AS UNSIGNED) < %d",
					$wpdb->esc_like( '_transient_timeout_' ) . '%',
					$now
				)
			)
		);

		$expired_site_transients = absint(
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for expired site transient records.
			$wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT(option_id) FROM {$wpdb->options} WHERE option_name LIKE %s AND CAST(option_value AS UNSIGNED) < %d",
					$wpdb->esc_like( '_site_transient_timeout_' ) . '%',
					$now
				)
			)
		);

		$expired_wc_sessions = absint(
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for expired WooCommerce session records.
			$wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT(option_id) FROM {$wpdb->options} WHERE option_name LIKE %s AND CAST(option_value AS UNSIGNED) < %d",
					$wpdb->esc_like( '_wc_session_expires_' ) . '%',
					$now
				)
			)
		);

		$table_name   = $this->safe_table_name( $wpdb->prefix . 'woocommerce_sessions' );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only table existence check.
		$table_exists = $table_name ? $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) : '';
		if ( $table_exists === $table_name ) {
			$expired_wc_sessions += absint(
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for expired WooCommerce session records.
				$wpdb->get_var(
					$wpdb->prepare( 'SELECT COUNT(session_id) FROM %i WHERE session_expiry < %d', $table_name, $now )
				)
			);
		}

		$total = $expired_transients + $expired_site_transients + $expired_wc_sessions;
		$results['checks']['expired_transients'] = $expired_transients + $expired_site_transients;
		$results['checks']['expired_wc_sessions'] = $expired_wc_sessions;

		if ( $total < 1 ) {
			return;
		}

		$severity = $total > 1000 ? 'medium' : 'low';
		$results['items'][] = $this->item(
			$severity,
			__( 'WooCommerce temporary data review', 'noesia-commerce-suite' ),
			sprintf(
				/* translators: 1: Expired transient count. 2: Expired WooCommerce session count. */
				__( 'Detected %1$d expired WordPress transients and %2$d expired WooCommerce session records. This is read-only; review WooCommerce Status > Tools before running cleanup operations.', 'noesia-commerce-suite' ),
				$expired_transients + $expired_site_transients,
				$expired_wc_sessions
			),
			$total,
			array(),
			'temporary_data_review'
		);
	}


	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $results Results.
	 */
	private function append_autoloaded_options_health( &$results ) {
		global $wpdb;

		if ( ! $wpdb || empty( $wpdb->options ) ) {
			return;
		}

		$autoload_values = array( 'yes', 'on', 'auto-on', 'auto' );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic query for autoloaded option size.
		$row             = $wpdb->get_row(
			$wpdb->prepare(
				'SELECT COUNT(option_id) AS option_count, COALESCE(SUM(LENGTH(option_value)),0) AS total_size FROM %i WHERE autoload IN (%s,%s,%s,%s)',
				$wpdb->options,
				$autoload_values[0],
				$autoload_values[1],
				$autoload_values[2],
				$autoload_values[3]
			),
			ARRAY_A
		);

		$count  = isset( $row['option_count'] ) ? absint( $row['option_count'] ) : 0;
		$bytes  = isset( $row['total_size'] ) ? absint( $row['total_size'] ) : 0;

		$results['checks']['autoloaded_options_count'] = $count;
		$results['checks']['autoloaded_options_bytes'] = $bytes;
		$results['checks']['autoloaded_options_threshold'] = self::AUTOLOAD_THRESHOLD_BYTES;

		if ( $bytes <= self::AUTOLOAD_THRESHOLD_BYTES ) {
			return;
		}

		$examples       = array();
		$example_limit  = min( 10, self::EXAMPLE_LIMIT );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic query for largest autoloaded options.
		$rows          = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT option_name, LENGTH(option_value) AS option_size FROM %i WHERE autoload IN (%s,%s,%s,%s) ORDER BY option_size DESC LIMIT %d',
				$wpdb->options,
				$autoload_values[0],
				$autoload_values[1],
				$autoload_values[2],
				$autoload_values[3],
				$example_limit
			),
			ARRAY_A
		);

		foreach ( (array) $rows as $option_row ) {
			$name  = isset( $option_row['option_name'] ) ? sanitize_text_field( $option_row['option_name'] ) : '';
			$size  = isset( $option_row['option_size'] ) ? absint( $option_row['option_size'] ) : 0;
			if ( '' === $name ) {
				continue;
			}
			$examples[] = array(
				'id'    => 0,
				'name'  => sprintf(
					/* translators: 1: option name. 2: formatted option size. */
					__( '%1$s · %2$s', 'noesia-commerce-suite' ),
					$name,
					$this->format_bytes( $size )
				),
				'edit_url' => '',
			);
		}

		$severity = $bytes > 5 * MB_IN_BYTES ? 'high' : 'medium';
		$results['items'][] = $this->item(
			$severity,
			__( 'Autoloaded options health', 'noesia-commerce-suite' ),
			sprintf(
				/* translators: 1: autoloaded option size. 2: recommended threshold. */
				__( 'Detected %1$s of autoloaded WordPress option data. Autoloaded options are loaded on every request and can increase memory usage when they grow beyond the recommended review threshold of %2$s.', 'noesia-commerce-suite' ),
				$this->format_bytes( $bytes ),
				$this->format_bytes( self::AUTOLOAD_THRESHOLD_BYTES )
			),
			$count,
			$examples,
			'autoloaded_options_health'
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $results Results.
	 */
	private function append_action_scheduler_queue_health( &$results ) {
		global $wpdb;

		if ( ! $wpdb ) {
			return;
		}

		$table_name   = $this->safe_table_name( $wpdb->prefix . 'actionscheduler_actions' );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only table existence check.
		$table_exists = $table_name ? $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) : '';
		if ( $table_exists !== $table_name ) {
			return;
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for Action Scheduler pending actions.
		$pending = absint( $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(action_id) FROM %i WHERE status = %s', $table_name, 'pending' ) ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for Action Scheduler failed actions.
		$failed  = absint( $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(action_id) FROM %i WHERE status = %s', $table_name, 'failed' ) ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for Action Scheduler running actions.
		$running = absint( $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(action_id) FROM %i WHERE status = %s', $table_name, 'in-progress' ) ) );

		$results['checks']['action_scheduler_pending'] = $pending;
		$results['checks']['action_scheduler_failed'] = $failed;
		$results['checks']['action_scheduler_in_progress'] = $running;

		if ( $pending < 1000 && $failed < 100 && $running < 50 ) {
			return;
		}

		$examples = array();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic query for Action Scheduler status groups.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic query for Action Scheduler status groups.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic query for Action Scheduler status groups.
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT hook, status, COUNT(action_id) AS total FROM %i WHERE status IN (%s,%s,%s) GROUP BY hook, status ORDER BY total DESC LIMIT %d',
				$table_name,
				'failed',
				'pending',
				'in-progress',
				10
			),
			ARRAY_A
		);

		foreach ( (array) $rows as $row ) {
			$hook    = isset( $row['hook'] ) ? sanitize_text_field( $row['hook'] ) : '';
			$status  = isset( $row['status'] ) ? sanitize_text_field( $row['status'] ) : '';
			$total   = isset( $row['total'] ) ? absint( $row['total'] ) : 0;
			if ( '' === $hook ) {
				continue;
			}
			$examples[] = array(
				'id'    => 0,
				'name'  => sprintf(
					/* translators: 1: scheduler hook. 2: status. 3: count. */
					__( '%1$s · %2$s · %3$d actions', 'noesia-commerce-suite' ),
					$hook,
					$status,
					$total
				),
				'edit_url' => admin_url( 'admin.php?page=wc-status&tab=action-scheduler' ),
			);
		}

		$severity = ( $failed >= 1000 || $pending >= 5000 ) ? 'high' : 'medium';
		$results['items'][] = $this->item(
			$severity,
			__( 'Action Scheduler queue health', 'noesia-commerce-suite' ),
			sprintf(
				/* translators: 1: pending actions. 2: failed actions. 3: in-progress actions. */
				__( 'Detected %1$d pending, %2$d failed and %3$d in-progress background actions. Large failed or pending queues can indicate broken integrations, delayed automation, webhooks or email jobs.', 'noesia-commerce-suite' ),
				$pending,
				$failed,
				$running
			),
			$pending + $failed + $running,
			$examples,
			'action_scheduler_queue_health'
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $bytes Bytes.
	 */
	private function format_bytes( $bytes ) {
		$bytes = absint( $bytes );
		if ( $bytes >= MB_IN_BYTES ) {
			return number_format_i18n( $bytes / MB_IN_BYTES, 2 ) . ' MB';
		}
		if ( $bytes >= KB_IN_BYTES ) {
			return number_format_i18n( $bytes / KB_IN_BYTES, 1 ) . ' KB';
		}
		return number_format_i18n( $bytes ) . ' B';
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $issue Issue.
	 * @param mixed $order Order.
	 * @param mixed $signals Signals.
	 */
	private function add_order_issue( &$issue, $order, $signals = array() ) {
		$issue['count']++;
		if ( count( $issue['examples'] ) >= self::EXAMPLE_LIMIT ) {
			return;
		}
		$example = $this->order_example( $order );
		if ( ! empty( $signals ) ) {
			$example['name'] .= ' · ' . implode( ', ', array_map( 'sanitize_text_field', $signals ) );
		}
		$issue['examples'][] = $example;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $order Order.
	 */
	private function order_example( $order ) {
		$order_id  = is_a( $order, 'WC_Order' ) ? absint( $order->get_id() ) : absint( $order );
		$label     = sprintf( /* translators: %d: WooCommerce order ID. */ __( 'Order #%d', 'noesia-commerce-suite' ), $order_id );
		$status    = is_a( $order, 'WC_Order' ) ? wc_get_order_status_name( $order->get_status() ) : '';
		if ( $status ) {
			$label .= ' · ' . sanitize_text_field( $status );
		}

		$edit_url = '';
		if ( function_exists( 'wc_get_order' ) ) {
			$order_object = is_a( $order, 'WC_Order' ) ? $order : wc_get_order( $order_id );
			if ( $order_object && method_exists( $order_object, 'get_edit_order_url' ) ) {
				$edit_url = $order_object->get_edit_order_url();
			}
		}

		return array(
			'id'        => $order_id,
			'name'      => $label,
			'edit_url'  => $edit_url,
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $issue Issue.
	 * @param mixed $product Product.
	 */
	private function add_product_issue( &$issue, $product ) {
		$issue['count']++;
		if ( count( $issue['examples'] ) >= self::EXAMPLE_LIMIT ) {
			return;
		}
		$product_id = absint( $product->get_id() );
		$issue['examples'][] = array(
			'id'        => $product_id,
			'name'      => sanitize_text_field( $product->get_name() ),
			'edit_url'  => get_edit_post_link( $product_id, 'raw' ),
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $results Results.
	 * @param mixed $issue Issue.
	 * @param mixed $severity Severity.
	 * @param mixed $title Title.
	 * @param mixed $code Code.
	 * @param mixed $custom_message Custom message.
	 */
	private function append_count_item( &$results, $issue, $severity, $title, $code, $custom_message = '' ) {
		$count = isset( $issue['count'] ) ? absint( $issue['count'] ) : 0;
		if ( $count > 0 ) {
			$message = $custom_message ? $custom_message : sprintf( /* translators: %d: Number of products. */ _n( '%d product needs attention.', '%d products need attention.', $count, 'noesia-commerce-suite' ), $count );
			$results['items'][] = $this->item( $severity, $title, $message, $count, isset( $issue['examples'] ) ? $issue['examples'] : array(), $code );
		}
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $product Product.
	 */
	private function product_permalink_is_reachable( $product ) {
		$url = $product->get_permalink();
		if ( ! $url || ! wp_http_validate_url( $url ) ) {
			return false;
		}

		$response = wp_safe_remote_head(
			$url,
			array(
				'timeout'      => self::BROKEN_LINK_TIMEOUT,
				'redirection'  => 3,
			)
		);

		if ( is_wp_error( $response ) ) {
			$response = wp_safe_remote_get(
				$url,
				array(
					'timeout'      => self::BROKEN_LINK_TIMEOUT,
					'redirection'  => 3,
				)
			);
		}

		if ( is_wp_error( $response ) ) {
			return false;
		}

		$code = absint( wp_remote_retrieve_response_code( $response ) );
		return $code >= 200 && $code < 400;
	}


	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $code Code.
	 * @param mixed $page Page.
	 * @param mixed $per_page Per page.
	 */
	public function get_issue_details( $code, $page = 1, $per_page = 25 ) {
		$code      = sanitize_key( $code );
		$page      = max( 1, absint( $page ) );
		$per_page  = max( 1, min( 100, absint( $per_page ) ) );
		$all_rows  = $this->get_issue_rows_for_code( $code );
		$total     = count( $all_rows );
		$offset    = ( $page - 1 ) * $per_page;

		return array(
			'code'         => $code,
			'title'        => $this->issue_title_for_code( $code ),
			'total'        => $total,
			'page'         => $page,
			'per_page'     => $per_page,
			'total_pages'  => $per_page > 0 ? max( 1, (int) ceil( $total / $per_page ) ) : 1,
			'rows'         => array_slice( $all_rows, $offset, $per_page ),
			'note'         => $this->issue_detail_note_for_code( $code ),
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $code Code.
	 */
	public function stream_issue_csv( $code ) {
		$code      = sanitize_key( $code );
		$rows      = $this->get_issue_rows_for_code( $code );
		$title     = $this->issue_title_for_code( $code );
		$filename  = sanitize_file_name( 'noesia-doctor-' . ( $code ? $code : 'issue' ) . '-' . gmdate( 'Ymd-His' ) . '.csv' );

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

		$output = fopen( 'php://output', 'w' );
		if ( false === $output ) {
			exit;
		}

		fputcsv( $output, array( 'Noesia Commerce Suite Doctor Export' ) );
		fputcsv( $output, array( 'Issue', wp_strip_all_tags( $title ) ) );
		fputcsv( $output, array( 'Generated', gmdate( 'Y-m-d H:i:s' ) . ' UTC' ) );
		fputcsv( $output, array() );
		fputcsv( $output, array( 'Type', 'ID', 'Name', 'Detail', 'Admin URL' ) );

		foreach ( $rows as $row ) {
			fputcsv(
				$output,
				array(
					isset( $row['type'] ) ? $row['type'] : '',
					isset( $row['id'] ) ? $row['id'] : '',
					isset( $row['name'] ) ? $row['name'] : '',
					isset( $row['detail'] ) ? $row['detail'] : '',
					isset( $row['edit_url'] ) ? $row['edit_url'] : '',
				)
			);
		}

		exit;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $code Code.
	 */
	private function get_issue_rows_for_code( $code ) {
		$code = sanitize_key( $code );

		$product_codes = array(
			'without_image',
			'without_price',
			'without_sku',
			'without_category',
			'short_description',
			'missing_alt',
			'large_images',
			'missing_shipping',
			'missing_related',
			'hidden_in_stock',
			'visible_oos',
			'broken_links',
		);

		if ( in_array( $code, $product_codes, true ) ) {
			return $this->get_product_issue_rows( $code );
		}

		switch ( $code ) {
			case 'suspicious_order_signals':
				return $this->get_suspicious_order_rows();
			case 'data_retention_review':
				return $this->get_data_retention_rows();
			case 'temporary_data_review':
				return $this->get_temporary_data_rows();
			case 'autoloaded_options_health':
				return $this->get_autoloaded_options_rows();
			case 'action_scheduler_queue_health':
				return $this->get_action_scheduler_rows();
			case 'missing_wc_page':
				return $this->get_missing_wc_page_rows();
		}

		return array();
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $code Code.
	 */
	private function get_product_issue_rows( $code ) {
		$limit = absint( Noesia_CS_Settings::get( 'doctor_product_scan_limit', 250 ) );
		if ( $limit < 1 ) {
			$limit = 250;
		}
		$limit = min( self::QUICK_SCAN_PRODUCT_SCOPE, $limit );

		if ( 'broken_links' === $code ) {
			$broken_limit = absint( Noesia_CS_Settings::get( 'doctor_broken_link_limit', 20 ) );
			if ( $broken_limit < 1 ) {
				$broken_limit = 20;
			}
			$limit = min( $limit, min( self::QUICK_SCAN_BROKEN_LINK_SCOPE, $broken_limit ) );
		}

		$products  = $this->product_repository->get_published_products( $limit );
		$rows      = array();

		foreach ( (array) $products as $product ) {
			if ( ! is_a( $product, 'WC_Product' ) || ! $this->product_matches_issue_code( $product, $code ) ) {
				continue;
			}

			$product_id = absint( $product->get_id() );
			$rows[] = array(
				'type'      => 'product',
				'id'        => $product_id,
				'name'      => sanitize_text_field( $product->get_name() ),
				'detail'    => $this->product_issue_detail( $product, $code ),
				'edit_url'  => get_edit_post_link( $product_id, 'raw' ),
			);
		}

		return $rows;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $product Product.
	 * @param mixed $code Code.
	 */
	private function product_matches_issue_code( $product, $code ) {
		$code = sanitize_key( $code );
		switch ( $code ) {
			case 'without_image':
				return ! $product->get_image_id();
			case 'without_price':
				return '' === $product->get_regular_price() && '' === $product->get_price();
			case 'without_sku':
				return '' === $product->get_sku();
			case 'without_category':
				return empty( $product->get_category_ids() );
			case 'short_description':
				return strlen( wp_strip_all_tags( $product->get_description() ) ) < 80;
			case 'missing_alt':
				$image_id = $product->get_image_id();
				return $image_id && '' === trim( (string) get_post_meta( $image_id, '_wp_attachment_image_alt', true ) );
			case 'large_images':
				$image_id        = $product->get_image_id();
				$large_image_kb  = absint( Noesia_CS_Settings::get( 'doctor_large_image_kb', 500 ) );
				$file            = $image_id ? get_attached_file( $image_id ) : '';
				return $file && file_exists( $file ) && filesize( $file ) > ( $large_image_kb * 1024 );
			case 'missing_shipping':
				return $product->needs_shipping() && ( '' === trim( (string) $product->get_weight() ) || ! $product->has_dimensions() );
			case 'missing_related':
				return empty( $product->get_cross_sell_ids() ) && empty( $product->get_upsell_ids() );
			case 'hidden_in_stock':
				return 'hidden' === $product->get_catalog_visibility() && 'instock' === $product->get_stock_status();
			case 'visible_oos':
				return 'outofstock' === $product->get_stock_status() && 'hidden' !== $product->get_catalog_visibility();
			case 'broken_links':
				return ! $this->product_permalink_is_reachable( $product );
		}
		return false;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $product Product.
	 * @param mixed $code Code.
	 */
	private function product_issue_detail( $product, $code ) {
		switch ( sanitize_key( $code ) ) {
			case 'missing_shipping':
				return __( 'Missing weight or dimensions for shipping calculations.', 'noesia-commerce-suite' );
			case 'missing_related':
				return __( 'No cross-sells or upsells configured.', 'noesia-commerce-suite' );
			case 'hidden_in_stock':
				return __( 'In stock but hidden from catalog visibility.', 'noesia-commerce-suite' );
			case 'visible_oos':
				return __( 'Visible in catalog while out of stock.', 'noesia-commerce-suite' );
			case 'broken_links':
				return __( 'Product permalink did not return a successful HTTP response during the manual Quick Scan URL check.', 'noesia-commerce-suite' );
		}
		return __( 'Review this product field in WooCommerce.', 'noesia-commerce-suite' );
	}

	/**
	 * Handles the related plugin operation.
	 */
	private function get_suspicious_order_rows() {
		if ( ! function_exists( 'wc_get_orders' ) ) {
			return array();
		}
		$orders = wc_get_orders(
			array(
				'limit'    => 80,
				'orderby'  => 'date',
				'order'    => 'DESC',
				'status'   => array( 'wc-pending', 'wc-processing', 'wc-on-hold', 'wc-completed' ),
				'return'   => 'objects',
			)
		);
		$rows = array();
		foreach ( (array) $orders as $order ) {
			if ( ! is_a( $order, 'WC_Order' ) ) {
				continue;
			}
			$signals = $this->suspicious_order_signals_for_order( $order );
			if ( empty( $signals ) ) {
				continue;
			}
			$example = $this->order_example( $order );
			$rows[] = array(
				'type'      => 'order',
				'id'        => $example['id'],
				'name'      => $example['name'],
				'detail'    => implode( ', ', array_map( 'sanitize_text_field', $signals ) ),
				'edit_url'  => $example['edit_url'],
			);
		}
		return $rows;
	}

	/**
	 * Handles the related plugin operation.
	 */
	private function get_data_retention_rows() {
		if ( ! function_exists( 'wc_get_orders' ) ) {
			return array();
		}
		$months = absint( Noesia_CS_Settings::get( 'doctor_data_retention_months', 24 ) );
		if ( $months < 1 ) {
			$months = 24;
		}
		$timestamp    = strtotime( '-' . $months . ' months', current_time( 'timestamp' ) );
		$date_before  = $timestamp ? gmdate( 'Y-m-d H:i:s', $timestamp ) : '';
		if ( ! $date_before ) {
			return array();
		}
		$ids = wc_get_orders(
			array(
				'limit'         => 5000,
				'status'        => array( 'wc-cancelled', 'wc-failed', 'wc-refunded' ),
				'date_created'  => '<' . $date_before,
				'orderby'       => 'date',
				'order'         => 'DESC',
				'return'        => 'ids',
			)
		);
		$rows = array();
		foreach ( (array) $ids as $order_id ) {
			$order = wc_get_order( $order_id );
			if ( ! $order ) {
				continue;
			}
			$example = $this->order_example( $order );
			$rows[] = array(
				'type'      => 'order',
				'id'        => $example['id'],
				'name'      => $example['name'],
				'detail'    => sprintf( /* translators: %d: numeric value. */ __( 'Older than %d months. Review against retention policy before deleting.', 'noesia-commerce-suite' ), $months ),
				'edit_url'  => $example['edit_url'],
			);
		}
		return $rows;
	}

	/**
	 * Handles the related plugin operation.
	 */
	private function get_temporary_data_rows() {
		global $wpdb;
		if ( ! $wpdb || empty( $wpdb->options ) ) {
			return array();
		}
		$now = time();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for expired transient records.
		$expired_transients = absint( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(option_id) FROM {$wpdb->options} WHERE option_name LIKE %s AND CAST(option_value AS UNSIGNED) < %d", $wpdb->esc_like( '_transient_timeout_' ) . '%', $now ) ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for expired site transient records.
		$expired_site_transients = absint( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(option_id) FROM {$wpdb->options} WHERE option_name LIKE %s AND CAST(option_value AS UNSIGNED) < %d", $wpdb->esc_like( '_site_transient_timeout_' ) . '%', $now ) ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic count for expired WooCommerce session records.
		$expired_wc_sessions = absint( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(option_id) FROM {$wpdb->options} WHERE option_name LIKE %s AND CAST(option_value AS UNSIGNED) < %d", $wpdb->esc_like( '_wc_session_expires_' ) . '%', $now ) ) );
		return array(
			array( 'type'   => 'database', 'id' => 0, 'name' => __( 'Expired WordPress transients', 'noesia-commerce-suite' ), 'detail' => number_format_i18n( $expired_transients + $expired_site_transients ), 'edit_url' => admin_url( 'admin.php?page=wc-status&tab=tools' ) ),
			array( 'type'   => 'database', 'id' => 0, 'name' => __( 'Expired WooCommerce sessions', 'noesia-commerce-suite' ), 'detail' => number_format_i18n( $expired_wc_sessions ), 'edit_url' => admin_url( 'admin.php?page=wc-status&tab=tools' ) ),
		);
	}

	/**
	 * Handles the related plugin operation.
	 */
	private function get_autoloaded_options_rows() {
		global $wpdb;
		if ( ! $wpdb || empty( $wpdb->options ) ) {
			return array();
		}
		$autoload_values = array( 'yes', 'on', 'auto-on', 'auto' );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic query for largest autoloaded options.
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT option_id, option_name, LENGTH(option_value) AS option_size FROM %i WHERE autoload IN (%s,%s,%s,%s) ORDER BY option_size DESC LIMIT 250',
				$wpdb->options,
				$autoload_values[0],
				$autoload_values[1],
				$autoload_values[2],
				$autoload_values[3]
			),
			ARRAY_A
		);
		$output = array();
		foreach ( (array) $rows as $row ) {
			$output[] = array(
				'type'      => 'option',
				'id'        => isset( $row['option_id'] ) ? absint( $row['option_id'] ) : 0,
				'name'      => isset( $row['option_name'] ) ? sanitize_text_field( $row['option_name'] ) : '',
				'detail'    => isset( $row['option_size'] ) ? $this->format_bytes( $row['option_size'] ) : '',
				'edit_url'  => '',
			);
		}
		return $output;
	}

	/**
	 * Handles the related plugin operation.
	 */
	private function get_action_scheduler_rows() {
		global $wpdb;
		if ( ! $wpdb ) {
			return array();
		}
		$table_name   = $this->safe_table_name( $wpdb->prefix . 'actionscheduler_actions' );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only table existence check.
		$table_exists = $table_name ? $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) : '';
		if ( $table_exists !== $table_name ) {
			return array();
		}
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only diagnostic query for Action Scheduler status groups.
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT hook, status, COUNT(action_id) AS total FROM %i WHERE status IN (%s,%s,%s) GROUP BY hook, status ORDER BY total DESC LIMIT %d',
				$table_name,
				'failed',
				'pending',
				'in-progress',
				250
			),
			ARRAY_A
		);
		$output = array();
		foreach ( (array) $rows as $row ) {
			$output[] = array(
				'type'      => 'scheduled_action',
				'id'        => 0,
				'name'      => isset( $row['hook'] ) ? sanitize_text_field( $row['hook'] ) : '',
				'detail'    => sprintf( /* translators: 1: first dynamic value; 2: second numeric value. */ __( '%1$s · %2$d actions', 'noesia-commerce-suite' ), sanitize_text_field( $row['status'] ?? '' ), absint( $row['total'] ?? 0 ) ),
				'edit_url'  => admin_url( 'admin.php?page=wc-status&tab=action-scheduler' ),
			);
		}
		return $output;
	}

	/**
	 * Handles the related plugin operation.
	 */
	private function get_missing_wc_page_rows() {
		if ( ! function_exists( 'wc_get_page_id' ) ) {
			return array();
		}
		$rows = array();
		foreach ( array( 'cart', 'checkout', 'myaccount' ) as $page_key ) {
			$page_id = wc_get_page_id( $page_key );
			if ( $page_id <= 0 || 'publish' !== get_post_status( $page_id ) ) {
				$rows[] = array(
					'type'      => 'woocommerce_page',
					'id'        => $page_id,
					'name'      => sprintf( /* translators: %s: dynamic value. */ __( '%s page', 'noesia-commerce-suite' ), ucfirst( $page_key ) ),
					'detail'    => __( 'Missing or not published in WooCommerce page settings.', 'noesia-commerce-suite' ),
					'edit_url'  => admin_url( 'admin.php?page=wc-settings&tab=advanced' ),
				);
			}
		}
		return $rows;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $code Code.
	 */
	private function issue_title_for_code( $code ) {
		$titles = array(
			'without_image'                  => __( 'Products without image', 'noesia-commerce-suite' ),
			'without_price'                  => __( 'Products without price', 'noesia-commerce-suite' ),
			'without_sku'                    => __( 'Products without SKU', 'noesia-commerce-suite' ),
			'without_category'               => __( 'Products without category', 'noesia-commerce-suite' ),
			'short_description'              => __( 'Products with short description', 'noesia-commerce-suite' ),
			'missing_alt'                    => __( 'Product images missing alt text', 'noesia-commerce-suite' ),
			'large_images'                   => __( 'Large product images', 'noesia-commerce-suite' ),
			'missing_shipping'               => __( 'Products missing shipping data', 'noesia-commerce-suite' ),
			'missing_related'                => __( 'Products without cross-sells or upsells', 'noesia-commerce-suite' ),
			'hidden_in_stock'                => __( 'Hidden products still in stock', 'noesia-commerce-suite' ),
			'visible_oos'                    => __( 'Visible products out of stock', 'noesia-commerce-suite' ),
			'broken_links'                   => __( 'Product links returning an error', 'noesia-commerce-suite' ),
			'suspicious_order_signals'       => __( 'Suspicious order signals', 'noesia-commerce-suite' ),
			'data_retention_review'          => __( 'Data retention review items', 'noesia-commerce-suite' ),
			'temporary_data_review'          => __( 'WooCommerce temporary data review', 'noesia-commerce-suite' ),
			'autoloaded_options_health'      => __( 'Autoloaded options health', 'noesia-commerce-suite' ),
			'action_scheduler_queue_health'  => __( 'Action Scheduler queue health', 'noesia-commerce-suite' ),
			'missing_wc_page'                => __( 'Missing WooCommerce pages', 'noesia-commerce-suite' ),
		);
		return isset( $titles[ $code ] ) ? $titles[ $code ] : __( 'Doctor issue details', 'noesia-commerce-suite' );
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $code Code.
	 */
	private function issue_detail_note_for_code( $code ) {
		if ( 'broken_links' === $code ) {
			return __( 'Broken link details follow the current Quick Scan URL scope because each check performs a real HTTP request.', 'noesia-commerce-suite' );
		}
		if ( in_array( $code, array( 'autoloaded_options_health', 'action_scheduler_queue_health', 'temporary_data_review' ), true ) ) {
			return __( 'This is a read-only technical report. Review official WooCommerce or WordPress tools before cleaning data.', 'noesia-commerce-suite' );
		}
		return __( 'Detailed rows are generated on demand from the current scan settings and may differ if store data changed after the last scan.', 'noesia-commerce-suite' );
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $severity Severity.
	 * @param mixed $title Title.
	 * @param mixed $message Message.
	 * @param mixed $count Count.
	 * @param mixed $examples Examples.
	 * @param mixed $code Code.
	 */
	private function item( $severity, $title, $message, $count = 0, $examples = array(), $code = '' ) {
		return array(
			'severity'  => sanitize_key( $severity ),
			'title'     => sanitize_text_field( $title ),
			'message'   => sanitize_text_field( $message ),
			'count'     => absint( $count ),
			'examples'  => is_array( $examples ) ? $examples : array(),
			'code'      => sanitize_key( $code ),
		);
	}
}
