<?php
/**
 * SEO Analyzer — test callback implementations
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\SeoAnalyzer;

use SEOvaultSEO\Core\PostMeta;
use SEOvaultSEO\Core\Options;
use SEOvaultSEO\Utils\Db;
use SEOvaultSEO\Utils\SeoAssessment;

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

class Tests {

	/**
	 * Check if the site is visible to search engines (blog_public option).
	 *
	 * @return array
	 */
	public static function test_blog_public() {
		$public = (bool) get_option( 'blog_public' );

		if ( ! $public ) {
			return array(
				'status'  => 'fail',
				'message' => '<strong>' . __( 'Search engines cannot see your website.', 'seovault-ai' ) . '</strong> ' .
					sprintf(
						'<a href="%s">%s</a> %s',
						esc_url( admin_url( 'options-reading.php' ) ),
						__( 'Go to Settings > Reading', 'seovault-ai' ),
						__( 'and uncheck "Discourage search engines from indexing this site".', 'seovault-ai' )
					),
			);
		}

		return array(
			'status'  => 'ok',
			'message' => __( 'Your site is accessible to search engines.', 'seovault-ai' ),
		);
	}

	/**
	 * Check if permalink structure includes %postname%.
	 *
	 * @return array
	 */
	public static function test_permalink_structure() {
		$structure = get_option( 'permalink_structure' );

		if ( '' === $structure ) {
			return array(
				'status'  => 'fail',
				'message' => __( 'Permalinks are set to the default (plain) structure. Pretty permalinks are disabled.', 'seovault-ai' ),
			);
		}

		if ( false === strpos( $structure, '%postname%' ) ) {
			return array(
				'status'  => 'fail',
				'message' => __( 'Permalinks are custom but do not include the post name. Use a structure with %postname% for best SEO.', 'seovault-ai' ),
			);
		}

		return array(
			'status'  => 'ok',
			'message' => sprintf(
				/* translators: %s: permalink structure */
				__( 'Permalink structure is set to %s.', 'seovault-ai' ),
				'<code>' . esc_html( $structure ) . '</code>'
			),
		);
	}

	/**
	 * Check if site tagline is set and not the default.
	 *
	 * @return array
	 */
	public static function test_site_tagline() {
		$tagline = get_bloginfo( 'description' );

		if ( '' === $tagline ) {
			return array(
				'status'  => 'warning',
				'message' => sprintf(
					'<a href="%s" target="_blank">%s</a>.',
					esc_url( admin_url( 'options-general.php' ) ),
					__( 'No tagline set. Set one in Settings > General', 'seovault-ai' )
				),
			);
		}

		$translated = __( 'Just another WordPress site', 'seovault-ai' );
		if ( $tagline === $translated || 'Just another WordPress site' === $tagline ) {
			return array(
				'status'  => 'fail',
				'message' => __( 'Your site tagline is set to the default "Just another WordPress site". Change it to a unique, descriptive phrase.', 'seovault-ai' ),
			);
		}

		return array(
			'status'  => 'ok',
			'message' => __( 'Your site tagline is set to a custom value.', 'seovault-ai' ),
		);
	}

	/**
	 * Check for published posts without a focus keyword.
	 *
	 * @return array
	 */
	public static function test_focus_keywords() {
		global $wpdb;

		$meta_key = PostMeta::PREFIX . 'focus_keyword';

		$post_types = array_values( array_map( 'sanitize_key', self::get_allowed_post_types() ) );
		$type_sql   = '';
		if ( ! empty( $post_types ) ) {
			$type_sql = ' AND ' . $wpdb->posts . '.post_type IN (' . Db::prepare_in_list( $post_types, '%s' ) . ')';
		}

		$noindex_key = PostMeta::PREFIX . 'robots_noindex';
		$query_args   = array_merge( array( $meta_key, $noindex_key ), $post_types );

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Dynamic IN fragment contains placeholders only; values are passed to prepare().
		$sql = "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_type
			FROM {$wpdb->posts}
			LEFT JOIN {$wpdb->postmeta} AS fk ON {$wpdb->posts}.ID = fk.post_id AND fk.meta_key = %s
			LEFT JOIN {$wpdb->postmeta} AS ni ON {$wpdb->posts}.ID = ni.post_id AND ni.meta_key = %s
			WHERE 1=1
				{$type_sql}
				AND {$wpdb->posts}.post_status = 'publish'
				AND fk.meta_value IS NULL
				AND (ni.meta_value IS NULL OR ni.meta_value != '1')
			GROUP BY {$wpdb->posts}.ID";

		$results = $wpdb->get_results( $wpdb->prepare( $sql, $query_args ), ARRAY_A );
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared

		if ( empty( $results ) ) {
			return array(
				'status'  => 'ok',
				'message' => __( 'All published posts have focus keywords set.', 'seovault-ai' ),
			);
		}

		$grouped = self::group_by_post_type( $results );
		$links   = self::build_post_type_links( $grouped, '&seovaultseo_no_fk=1' );

		return array(
			'status'  => 'fail',
			'message' => sprintf(
				/* translators: %s: post type links */
				__( 'There are %s with no focus keyword set.', 'seovault-ai' ),
				implode( ', ', $links )
			),
		);
	}

	/**
	 * Check for posts where the focus keyword does not appear in the title.
	 *
	 * @return array
	 */
	public static function test_post_titles() {
		global $wpdb;

		$meta_key = PostMeta::PREFIX . 'focus_keyword';

		$post_types = array_values( array_map( 'sanitize_key', self::get_allowed_post_types() ) );
		$type_sql   = '';
		if ( ! empty( $post_types ) ) {
			$type_sql = ' AND ' . $wpdb->posts . '.post_type IN (' . Db::prepare_in_list( $post_types, '%s' ) . ')';
		}
		$query_args = array_merge( array( $meta_key ), $post_types );

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Dynamic IN fragment contains placeholders only; values are passed to prepare().
		$sql = "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_type, {$wpdb->posts}.post_title, pm.meta_value AS focus_keyword
			FROM {$wpdb->posts}
			INNER JOIN {$wpdb->postmeta} AS pm ON {$wpdb->posts}.ID = pm.post_id AND pm.meta_key = %s
			WHERE 1=1
				{$type_sql}
				AND {$wpdb->posts}.post_status = 'publish'
				AND REPLACE({$wpdb->posts}.post_title, '&amp;', '&') NOT LIKE CONCAT('%', SUBSTRING_INDEX(pm.meta_value, ',', 1), '%')
			GROUP BY {$wpdb->posts}.ID";

		$results = $wpdb->get_results( $wpdb->prepare( $sql, $query_args ), ARRAY_A );
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared

		if ( empty( $results ) ) {
			return array(
				'status'  => 'ok',
				'message' => __( 'Focus keywords appear in the titles of all published posts where they are set.', 'seovault-ai' ),
			);
		}

		$grouped = self::group_by_post_type( $results );
		$links   = self::build_post_type_links( $grouped, '&seovaultseo_fk_title=1' );

		$info = array();
		$ids  = array_slice( wp_list_pluck( $results, 'ID' ), 0, 20 );
		foreach ( $ids as $post_id ) {
			$info[] = '<a href="' . esc_url( get_permalink( $post_id ) ) . '" target="_blank">' . esc_html( get_the_title( $post_id ) ) . '</a>';
		}
		$remaining = count( $results ) - count( $ids );
		if ( $remaining > 0 ) {
			$info[] = sprintf( /* translators: %d: remaining count */ __( '+%d More...', 'seovault-ai' ), $remaining );
		}

		return array(
			'status'  => 'fail',
			'message' => sprintf(
				/* translators: %s: post type links */
				__( 'There are %s published posts where the focus keyword does not appear in the post title.', 'seovault-ai' ),
				implode( ', ', $links )
			),
			'info'    => $info,
		);
	}

	/**
	 * Check if title and description templates are configured.
	 *
	 * @return array
	 */
	public static function test_title_templates() {
		$title_template = Options::get_title_template();
		$desc_template  = Options::get_description_template();
		$issues         = array();

		if ( empty( $title_template ) || '%title% %sep% %sitename%' === $title_template ) {
			$issues[] = __( 'Title template is using the default value.', 'seovault-ai' );
		}

		if ( empty( $desc_template ) || '%excerpt%' === $desc_template ) {
			$issues[] = __( 'Description template is using the default value.', 'seovault-ai' );
		}

		if ( empty( $issues ) ) {
			return array(
				'status'  => 'ok',
				'message' => __( 'Title and meta description templates are configured with custom values.', 'seovault-ai' ),
			);
		}

		return array(
			'status'  => 'warning',
			'message' => implode( ' ', $issues ) . ' ' . sprintf(
				'<a href="%s">%s</a>.',
				esc_url( admin_url( 'admin.php?page=seovault-ai-general' ) ),
				__( 'Configure templates', 'seovault-ai' )
			),
		);
	}

	/**
	 * Assess page-specific SEO data when WordPress uses a static front page.
	 *
	 * Latest-posts homepages have no single post to assess, so they are
	 * intentionally exempt from this page-level check.
	 *
	 * @return array
	 */
	public static function test_homepage_seo() {
		if ( 'page' !== get_option( 'show_on_front', 'posts' ) ) {
			return array(
				'status'  => 'ok',
				'message' => __( 'Homepage displays the latest posts; no static page SEO assessment is required.', 'seovault-ai' ),
			);
		}

		$front_page_id = max( 0, (int) get_option( 'page_on_front' ) );
		$front_page    = $front_page_id ? get_post( $front_page_id ) : null;

		if ( ! $front_page || 'page' !== $front_page->post_type || 'publish' !== $front_page->post_status ) {
			return array(
				'status'  => 'fail',
				'message' => sprintf(
					'<strong>%s</strong> <a href="%s">%s</a>.',
					__( 'The selected static homepage is missing, invalid, or not published.', 'seovault-ai' ),
					esc_url( admin_url( 'options-reading.php' ) ),
					__( 'Review Settings > Reading', 'seovault-ai' )
				),
			);
		}

		$assessment = SeoAssessment::assess_post( $front_page_id );
		$seo_title  = trim( (string) PostMeta::get_seo_title( $front_page_id ) );
		$seo_desc   = trim( (string) PostMeta::get_seo_description( $front_page_id ) );
		$missing    = array();

		if ( '' === $seo_title ) {
			$missing[] = __( 'custom SEO title', 'seovault-ai' );
		}
		if ( '' === $seo_desc ) {
			$missing[] = __( 'meta description', 'seovault-ai' );
		}

		$status_map = array(
			SeoAssessment::STATUS_GOOD    => 'ok',
			SeoAssessment::STATUS_OKAY    => 'warning',
			SeoAssessment::STATUS_BAD     => 'fail',
			SeoAssessment::STATUS_UNKNOWN => 'fail',
		);
		$status     = isset( $status_map[ $assessment['overall'] ] ) ? $status_map[ $assessment['overall'] ] : 'fail';
		$edit_link  = get_edit_post_link( $front_page_id, 'raw' );
		$edit_label = __( 'Edit the homepage SEO settings', 'seovault-ai' );
		if ( ! $edit_link ) {
			$edit_link = admin_url( 'post.php?post=' . $front_page_id . '&action=edit' );
		}

		if ( ! empty( $missing ) ) {
			if ( 'ok' === $status ) {
				$status = 'warning';
			}

			return array(
				'status'  => $status,
				'message' => sprintf(
					/* translators: 1: comma-separated missing SEO fields, 2: edit URL, 3: edit link label */
					__( 'The static homepage is missing: %1$s. <a href="%2$s">%3$s</a>.', 'seovault-ai' ),
					esc_html( implode( ', ', $missing ) ),
					esc_url( $edit_link ),
					esc_html( $edit_label )
				),
			);
		}

		if ( 'ok' === $status ) {
			return array(
				'status'  => 'ok',
				'message' => __( 'The static homepage has page-specific SEO metadata and passes its SEO assessment.', 'seovault-ai' ),
			);
		}

		return array(
			'status'  => $status,
			'message' => sprintf(
				'<strong>%s</strong> <a href="%s">%s</a>.',
				'warning' === $status
					? __( 'The static homepage SEO assessment found improvements to make.', 'seovault-ai' )
					: __( 'The static homepage SEO assessment found issues that need attention.', 'seovault-ai' ),
				esc_url( $edit_link ),
				esc_html( $edit_label )
			),
		);
	}

	/**
	 * Check if sitemap.xml is accessible.
	 *
	 * @return array
	 */
	public static function test_sitemap() {
		$sitemap_url = home_url( '/sitemap.xml' );
		$response    = wp_remote_get( $sitemap_url, self::remote_get_args( 10 ) );

		if ( is_wp_error( $response ) ) {
			return array(
				'status'  => 'fail',
				'message' => __( 'Could not reach the sitemap. Error: ', 'seovault-ai' ) . esc_html( $response->get_error_message() ),
			);
		}

		$code = wp_remote_retrieve_response_code( $response );

		if ( 200 === (int) $code ) {
			return array(
				'status'  => 'ok',
				'message' => sprintf(
					/* translators: %s: sitemap URL */
					__( 'Sitemap is accessible at %s', 'seovault-ai' ),
					'<a href="' . esc_url( $sitemap_url ) . '" target="_blank">' . esc_html( $sitemap_url ) . '</a>'
				),
			);
		}

		return array(
			'status'  => 'fail',
			'message' => sprintf(
				/* translators: %d: HTTP response code */
				__( 'Sitemap returned HTTP %d. Ensure the sitemap is enabled in SEOvault AI settings.', 'seovault-ai' ),
				$code
			),
		);
	}

	/**
	 * Check if robots.txt exists.
	 *
	 * @return array
	 */
	public static function test_robots_txt() {
		$robots_url = home_url( '/robots.txt' );
		$response   = wp_remote_get( $robots_url, self::remote_get_args( 10 ) );

		if ( is_wp_error( $response ) ) {
			return array(
				'status'  => 'warning',
				'message' => __( 'Could not check robots.txt. It may not exist or the server is blocking requests.', 'seovault-ai' ),
			);
		}

		$code = wp_remote_retrieve_response_code( $response );

		if ( 200 === (int) $code ) {
			$body = wp_remote_retrieve_body( $response );
			if ( false !== stripos( $body, 'user-agent' ) ) {
				return array(
					'status'  => 'ok',
					'message' => sprintf(
						/* translators: %s: robots.txt URL */
						__( 'robots.txt is accessible at %s', 'seovault-ai' ),
						'<a href="' . esc_url( $robots_url ) . '" target="_blank">' . esc_html( $robots_url ) . '</a>'
					),
				);
			}
		}

		return array(
			'status'  => 'warning',
			'message' => __( 'robots.txt was not found or does not contain valid directives. WordPress will generate a virtual robots.txt if no physical file exists.', 'seovault-ai' ),
		);
	}

	/**
	 * Check if the site uses HTTPS.
	 *
	 * @return array
	 */
	public static function test_ssl() {
		if ( is_ssl() ) {
			$home_url = home_url();
			if ( 0 === strpos( $home_url, 'https://' ) ) {
				return array(
					'status'  => 'ok',
					'message' => __( 'Your site is served over HTTPS.', 'seovault-ai' ),
				);
			}
		}

		return array(
			'status'  => 'fail',
			'message' => __( 'Your site is not using HTTPS. Install an SSL certificate and update your site URLs to https://', 'seovault-ai' ),
		);
	}

	/**
	 * Check if schema markup is enabled.
	 *
	 * @return array
	 */
	public static function test_schema() {
		$schemas = get_option( 'seovaultseo_schemas', array() );

		if ( empty( $schemas ) ) {
			$schemas = \SEOvaultSEO\Schema\SchemaUtils::get_default_schemas();
		}

		$enabled_count = 0;
		foreach ( $schemas as $schema ) {
			if ( \SEOvaultSEO\Schema\SchemaUtils::resolve_schema_enabled( $schema ) ) {
				++$enabled_count;
			}
		}

		if ( 0 === $enabled_count ) {
			return array(
				'status'  => 'warning',
				'message' => sprintf(
					'<a href="%s">%s</a>.',
					esc_url( admin_url( 'admin.php?page=seovault-ai-general&tab=vtab-schema' ) ),
					__( 'Schema types exist but none are enabled. Enable at least one in SEOvault AI > General > Schema', 'seovault-ai' )
				),
			);
		}

		return array(
			'status'  => 'ok',
			'message' => sprintf(
				/* translators: %d: number of enabled schema types */
				__( '%d schema type(s) are enabled and active.', 'seovault-ai' ),
				$enabled_count
			),
		);
	}

	/**
	 * Check if Open Graph tags are being output.
	 *
	 * @return array
	 */
	public static function test_open_graph() {
		$output_mode = Options::get_output_mode();

		if ( 'disabled' === $output_mode ) {
			return array(
				'status'  => 'fail',
				'message' => __( 'Output mode is set to Disabled. No Open Graph tags are being output.', 'seovault-ai' ),
			);
		}

		$default_image = Options::get_default_social_image();

		if ( empty( $default_image ) ) {
			return array(
				'status'  => 'warning',
				'message' => sprintf(
					'<a href="%s">%s</a>.',
					esc_url( admin_url( 'admin.php?page=seovault-ai-general' ) ),
					__( 'No default social image is set. Add one in SEOvault AI > General > Social', 'seovault-ai' )
				),
			);
		}

		return array(
			'status'  => 'ok',
			'message' => __( 'Open Graph tags are being output and a default social image is configured.', 'seovault-ai' ),
		);
	}

	/**
	 * Check if canonical URLs are being output.
	 *
	 * @return array
	 */
	public static function test_canonical() {
		$output_mode = Options::get_output_mode();

		if ( 'disabled' === $output_mode ) {
			return array(
				'status'  => 'fail',
				'message' => __( 'Output mode is Disabled. Canonical URLs are not being generated.', 'seovault-ai' ),
			);
		}

		if ( 'manual' === $output_mode ) {
			return array(
				'status'  => 'warning',
				'message' => __( 'Output mode is Manual. Canonical URLs are stored but not output to the frontend. Switch to Auto mode for automatic output.', 'seovault-ai' ),
			);
		}

		return array(
			'status'  => 'ok',
			'message' => __( 'Canonical URLs are being automatically output in Auto mode.', 'seovault-ai' ),
		);
	}

	/**
	 * Check if RSS feeds are available.
	 *
	 * @return array
	 */
	public static function test_rss_feeds() {
		$feed_url = get_feed_link();
		$response = wp_remote_get( $feed_url, self::remote_get_args( 10 ) );

		if ( is_wp_error( $response ) ) {
			return array(
				'status'  => 'warning',
				'message' => __( 'Could not verify RSS feed accessibility.', 'seovault-ai' ),
			);
		}

		$code = wp_remote_retrieve_response_code( $response );

		if ( 200 === (int) $code ) {
			return array(
				'status'  => 'ok',
				'message' => sprintf(
					/* translators: %s: feed URL */
					__( 'RSS feed is available at %s', 'seovault-ai' ),
					'<a href="' . esc_url( $feed_url ) . '" target="_blank">' . esc_html( $feed_url ) . '</a>'
				),
			);
		}

		return array(
			'status'  => 'warning',
			'message' => sprintf(
				/* translators: %d: HTTP response code */
				__( 'RSS feed returned HTTP %d. Check Settings > Reading for feed settings.', 'seovault-ai' ),
				$code
			),
		);
	}

	/**
	 * Check server response time (TTFB).
	 *
	 * @return array
	 */
	public static function test_response_time() {
		$start    = microtime( true );
		$response = wp_remote_get( home_url( '/' ), self::remote_get_args( 15 ) );
		$elapsed  = microtime( true ) - $start;

		$blocked = self::homepage_http_gate(
			$response,
			__( 'Could not measure server response time.', 'seovault-ai' )
		);
		if ( null !== $blocked ) {
			return $blocked;
		}

		$ms = round( $elapsed * 1000 );

		if ( $ms < 600 ) {
			return array(
				'status'  => 'ok',
				'message' => sprintf(
					/* translators: %d: server response time in milliseconds */
					__( 'Server response time is excellent (%dms).', 'seovault-ai' ),
					$ms
				),
			);
		}

		if ( $ms < 1500 ) {
			return array(
				'status'  => 'warning',
				'message' => sprintf(
					/* translators: %d: server response time in milliseconds */
					__( 'Server response time is acceptable but could be improved (%dms). Consider using a caching plugin.', 'seovault-ai' ),
					$ms
				),
			);
		}

		return array(
			'status'  => 'fail',
			'message' => sprintf(
				/* translators: %d: server response time in milliseconds */
				__( 'Server response time is slow (%dms). Install a caching plugin and optimize your server.', 'seovault-ai' ),
				$ms
			),
		);
	}

	/**
	 * Check if the homepage has exactly one H1 heading.
	 *
	 * @return array
	 */
	public static function test_h1_heading() {
		$response = wp_remote_get( home_url( '/' ), self::remote_get_args( 15 ) );

		$blocked = self::homepage_http_gate(
			$response,
			__( 'Could not fetch the homepage to check H1 headings.', 'seovault-ai' )
		);
		if ( null !== $blocked ) {
			return $blocked;
		}

		$body  = wp_remote_retrieve_body( $response );
		$count = preg_match_all( '/<h1[^>]*>/i', $body );

		if ( 1 === $count ) {
			return array(
				'status'  => 'ok',
				'message' => __( 'Homepage has exactly one H1 heading.', 'seovault-ai' ),
			);
		}

		if ( 0 === $count ) {
			return array(
				'status'  => 'fail',
				'message' => __( 'Homepage has no H1 heading. Add an H1 that describes your page content.', 'seovault-ai' ),
			);
		}

		return array(
			'status'  => 'warning',
			'message' => sprintf(
				/* translators: %d: number of H1 headings on the homepage */
				__( 'Homepage has %d H1 headings. Use only one H1 per page.', 'seovault-ai' ),
				$count
			),
		);
	}

	/**
	 * Check if a caching plugin is active.
	 *
	 * @return array
	 */
	public static function test_cache_plugin() {
		$active = \SEOvaultSEO\Utils\CachePlugins::get_active();

		if ( ! empty( $active ) ) {
			return array(
				'status'  => 'ok',
				'message' => sprintf(
					/* translators: %s: comma-separated cache plugin names */
					__( 'A caching plugin is active on your site (%s).', 'seovault-ai' ),
					implode( ', ', $active )
				),
			);
		}

		if ( function_exists( 'wp_cache_flush' ) && defined( 'WP_CACHE' ) && WP_CACHE ) {
			return array(
				'status'  => 'ok',
				'message' => __( 'WordPress object caching is enabled.', 'seovault-ai' ),
			);
		}

		return array(
			'status'  => 'warning',
			'message' => __( 'No caching plugin detected. Install one to improve page load speed.', 'seovault-ai' ),
		);
	}

	/**
	 * Check if www and non-www versions redirect properly.
	 *
	 * @return array
	 */
	public static function test_non_www() {
		$home_url = home_url();
		$parse    = wp_parse_url( $home_url );
		$host     = isset( $parse['host'] ) ? $parse['host'] : '';
		$scheme   = isset( $parse['scheme'] ) ? $parse['scheme'] : 'http';

		$is_www   = 0 === strpos( $host, 'www.' );
		$alt_host = $is_www ? substr( $host, 4 ) : 'www.' . $host;
		$alt_url  = $scheme . '://' . $alt_host . '/';

		$response = wp_remote_get(
			$alt_url,
			self::remote_get_args(
				10,
				array(
					'redirection' => 0,
				)
			)
		);

		if ( is_wp_error( $response ) ) {
			return array(
				'status'  => 'warning',
				'message' => __( 'Could not verify www/non-www redirect. The alternate version may not resolve to your server.', 'seovault-ai' ),
			);
		}

		$code = wp_remote_retrieve_response_code( $response );

		if ( $code >= 301 && $code <= 302 ) {
			return array(
				'status'  => 'ok',
				'message' => __( 'The non-preferred domain version redirects to the preferred one.', 'seovault-ai' ),
			);
		}

		if ( 200 === $code ) {
			return array(
				'status'  => 'warning',
				'message' => __( 'Both www and non-www versions are accessible. Set up a redirect from the non-preferred version to avoid duplicate content issues.', 'seovault-ai' ),
			);
		}

		return array(
			'status'  => 'ok',
			'message' => __( 'The non-preferred domain version does not return a 200, which is acceptable.', 'seovault-ai' ),
		);
	}

	// ─── Helper methods ──────────────────────────────────────────

	/**
	 * Get post types that are allowed for SEO analysis.
	 *
	 * @return array
	 */
	private static function get_allowed_post_types() {
		$types = get_post_types( array( 'public' => true ), 'names' );
		unset( $types['attachment'] );
		return array_values( $types );
	}

	/**
	 * Group results by post type.
	 *
	 * @param array $data Result rows.
	 * @return array
	 */
	private static function group_by_post_type( $data ) {
		$grouped = array();
		foreach ( $data as $row ) {
			$type = isset( $row['post_type'] ) ? $row['post_type'] : '';
			if ( ! isset( $grouped[ $type ] ) ) {
				$grouped[ $type ] = array();
			}
			$grouped[ $type ][] = $row;
		}
		return $grouped;
	}

	/**
	 * Shared HTTP args for analyzer self-requests.
	 *
	 * SSL verification is on by default. Local self-signed setups can disable via
	 * `add_filter( 'seovaultseo_analyzer_sslverify', '__return_false' )`.
	 *
	 * @param int   $timeout Timeout in seconds.
	 * @param array $extra   Extra args merged on top.
	 * @return array
	 */
	private static function remote_get_args( $timeout = 10, $extra = array() ) {
		$args = array(
			'timeout'   => (int) $timeout,
			'sslverify' => (bool) apply_filters( 'seovaultseo_analyzer_sslverify', true ),
		);
		return array_merge( $args, $extra );
	}

	/**
	 * Gate homepage analyzer checks on HTTP status before TTFB/H1 parsing.
	 *
	 * WP_Error / empty code → warning. 4xx/5xx → fail. Unfollowed 3xx → warning.
	 * Returns null when the response is 2xx and analysis may continue.
	 *
	 * @param array|\WP_Error $response            HTTP response from wp_remote_get.
	 * @param string          $unavailable_message Warning when the request cannot be evaluated.
	 * @return array|null
	 */
	private static function homepage_http_gate( $response, $unavailable_message ) {
		if ( is_wp_error( $response ) ) {
			return array(
				'status'  => 'warning',
				'message' => $unavailable_message,
			);
		}

		$code = (int) wp_remote_retrieve_response_code( $response );

		if ( $code <= 0 ) {
			return array(
				'status'  => 'warning',
				'message' => $unavailable_message,
			);
		}

		if ( $code >= 400 ) {
			return array(
				'status'  => 'fail',
				'message' => sprintf(
					/* translators: %d: HTTP response code */
					__( 'Homepage returned HTTP %d. Fix the server error before relying on SEO checks.', 'seovault-ai' ),
					$code
				),
			);
		}

		if ( $code >= 300 ) {
			return array(
				'status'  => 'warning',
				'message' => sprintf(
					/* translators: %d: HTTP response code */
					__( 'Homepage returned HTTP %d (redirect). Ensure redirects resolve to a healthy page.', 'seovault-ai' ),
					$code
				),
			);
		}

		if ( $code < 200 ) {
			return array(
				'status'  => 'warning',
				'message' => sprintf(
					/* translators: %d: HTTP response code */
					__( 'Homepage returned unexpected HTTP %d.', 'seovault-ai' ),
					$code
				),
			);
		}

		return null;
	}

	/**
	 * Build human-readable post type links.
	 *
	 * @param array  $grouped       Grouped results.
	 * @param string $extra_params  Extra URL params.
	 * @return array
	 */
	private static function build_post_type_links( $grouped, $extra_params = '' ) {
		$links = array();
		foreach ( $grouped as $post_type => $rows ) {
			$pto = get_post_type_object( $post_type );
			if ( ! $pto ) {
				continue;
			}
			$count   = count( $rows );
			$links[] = sprintf(
				'<a href="%s%s" target="_blank">%d %s</a>',
				esc_url( admin_url( 'edit.php?post_type=' . $post_type ) ),
				esc_attr( $extra_params ),
				$count,
				$count > 1 ? $pto->label : $pto->labels->singular_name
			);
		}
		return $links;
	}
}
