<?php
/**
 * SEO Manager Module
 *
 * Complete SEO toolkit: meta tags, indexing control, social sharing, and XML sitemaps.
 *
 * @package BlaminhorEssentials
 */

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * SEO Manager module class
 */
class Blaminhor_Essentials_Module_SEO_Manager extends Blaminhor_Essentials_Module {

	/**
	 * Module ID
	 *
	 * @var string
	 */
	protected $id = 'seo-manager';

	/**
	 * Module name
	 *
	 * @var string
	 */
	protected $name = 'SEO/GEO';

	/**
	 * Initialize module
	 */
	protected function init() {
		$this->name        = 'SEO/GEO';
		$this->description = __( 'Complete SEO toolkit: meta tags, indexing control, social sharing, and XML sitemaps.', 'blaminhor-essentials' );
	}

	/**
	 * Get default settings
	 *
	 * @return array
	 */
	protected function get_default_settings() {
		return array(
			// Indexing settings.
			'noindex_entire_site'     => false,
			'indexable_post_types'    => array( 'post', 'page' ),
			'indexable_taxonomies'    => array( 'category', 'post_tag' ),
			'noindex_archives'        => false,
			'noindex_author_archives' => true,
			'noindex_date_archives'   => true,
			'noindex_search'          => true,

			// Social defaults.
			'og_default_image'        => '',
			'twitter_card_type'       => 'summary_large_image',
			'twitter_site'            => '',

			// Title templates.
			'title_separator'         => '-',
			'title_home'              => '{site_name} {sep} {tagline}',
			'title_search'            => '{search_query} {sep} {site_name}',
			'title_404'               => '{text_404} {sep} {site_name}',
			'title_author_archive'    => '{author} {sep} {site_name}',
			'title_date_archive'      => '{date} {sep} {site_name}',
			'title_templates'         => array(),

			// AI settings.
			'ai_noai'                 => false,
			'ai_block_chatgpt'        => false,
			'ai_block_google_ai'      => false,
			'ai_block_claude'         => false,
			'ai_block_perplexity'     => false,
			'ai_block_common_crawl'   => false,

			// Schema settings.
			'schema_enabled'              => true,
			'schema_site_type'            => 'Organization',
			'schema_org_name'             => '',
			'schema_org_logo'             => '',
			'schema_org_url'              => '',
			'schema_same_as'              => '',
			'schema_post_types'           => array( 'post', 'page' ),
			'schema_default_article_type' => 'Article',
			'schema_default_page_type'    => 'WebPage',
			'schema_add_website'          => true,
			'schema_add_breadcrumbs'      => true,
			'schema_auto_faq'             => true,
			'schema_auto_howto'           => true,
			'schema_auto_video'           => true,

			// Author E-E-A-T settings.
			'schema_author_job_title'     => '',
			'schema_author_description'   => '',
			'schema_author_knows_about'   => '',
			'schema_author_alumni_of'     => '',
			'schema_author_same_as'       => '',

			// Table of Contents settings.
			'toc_enabled'                 => false,
			'toc_depth'                   => 'h2-h3',
			'toc_style'                   => 'numbered',
			'toc_title'                   => 'Table of Contents',

			// Key Takeaways settings.
			'takeaways_enabled'           => true,

			// Sitemap settings.
			'sitemap_enabled'         => true,
			'sitemap_post_types'      => array( 'post', 'page' ),
			'sitemap_taxonomies'      => array( 'category', 'post_tag' ),
			'sitemap_include_lastmod' => true,
			'sitemap_include_images'  => false,
			'sitemap_posts_per_file'  => 1000,
			'sitemap_ping_engines'    => false,
		);
	}

	/**
	 * Register hooks
	 */
	protected function register_hooks() {
		// Admin hooks.
		add_action( 'admin_init', array( $this, 'handle_settings_submission' ) );
		add_action( 'admin_notices', array( $this, 'show_settings_saved_notice' ) );
		add_action( 'admin_notices', array( $this, 'show_noindex_entire_site_notice' ) );
		add_action( 'admin_init', array( $this, 'maybe_flush_rewrite_rules' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );

		// Meta box for individual post/page SEO.
		add_action( 'add_meta_boxes', array( $this, 'add_seo_meta_box' ) );
		add_action( 'save_post', array( $this, 'save_seo_meta_box' ), 10, 2 );

		// Frontend meta tags.
		add_action( 'wp_head', array( $this, 'output_meta_tags' ), 1 );

		// JSON-LD structured data.
		if ( $this->get_setting( 'schema_enabled', true ) ) {
			add_action( 'wp_head', array( $this, 'output_json_ld' ), 5 );
		}

		// AI bot blocking via robots.txt.
		add_filter( 'robots_txt', array( $this, 'filter_robots_txt' ), 10, 2 );

		// Remove WordPress SEO title if we're handling it.
		add_filter( 'pre_get_document_title', array( $this, 'filter_document_title' ), 999 );
		remove_action( 'wp_head', 'rel_canonical' );
		add_action( 'wp_head', array( $this, 'output_canonical' ), 2 );

		// Sitemap functionality.
		if ( $this->get_setting( 'sitemap_enabled', true ) ) {
			// Run at init priority 99 to ensure all CPTs are registered before adding rewrite rules.
			add_action( 'init', array( $this, 'add_sitemap_rewrite_rules' ), 99 );
			add_action( 'wp_loaded', array( $this, 'maybe_flush_rewrite_rules' ) );
			add_action( 'template_redirect', array( $this, 'render_sitemap' ), 1 ); // Priority 1 = run early.
			add_filter( 'query_vars', array( $this, 'add_sitemap_query_vars' ) );

			// Invalidate cache on content changes.
			add_action( 'save_post', array( $this, 'invalidate_sitemap_cache' ) );
			add_action( 'delete_post', array( $this, 'invalidate_sitemap_cache' ) );
			add_action( 'create_term', array( $this, 'invalidate_sitemap_cache' ) );
			add_action( 'edit_term', array( $this, 'invalidate_sitemap_cache' ) );
			add_action( 'delete_term', array( $this, 'invalidate_sitemap_cache' ) );

			// Ping search engines.
			if ( $this->get_setting( 'sitemap_ping_engines', false ) ) {
				add_action( 'publish_post', array( $this, 'ping_search_engines' ) );
				add_action( 'publish_page', array( $this, 'ping_search_engines' ) );
			}
		}

		// Table of Contents shortcode.
		if ( $this->get_setting( 'toc_enabled', false ) ) {
			add_shortcode( 'blaminhor_toc', array( $this, 'render_toc_shortcode' ) );
			add_filter( 'the_content', array( $this, 'filter_content_add_heading_ids' ), 8 );
		}

		// Key Takeaways frontend rendering.
		if ( $this->get_setting( 'takeaways_enabled', true ) ) {
			add_filter( 'the_content', array( $this, 'render_key_takeaways' ), 9 );
		}

		// Frontend CSS for ToC and Key Takeaways.
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) );

		// AJAX handlers.
		add_action( 'wp_ajax_ap_seo_get_content_list', array( $this, 'ajax_get_content_list' ) );
		add_action( 'wp_ajax_ap_seo_save_meta', array( $this, 'ajax_save_meta' ) );
		add_action( 'wp_ajax_ap_seo_get_meta', array( $this, 'ajax_get_meta' ) );
		add_action( 'wp_ajax_ap_seo_import', array( $this, 'ajax_import_seo' ) );
		add_action( 'wp_ajax_ap_seo_geo_score', array( $this, 'ajax_geo_score' ) );
		add_action( 'wp_ajax_ap_seo_seo_score', array( $this, 'ajax_seo_score' ) );
	}

	/**
	 * Enqueue module assets
	 *
	 * @param string $hook Admin page hook.
	 */
	public function enqueue_assets( $hook ) {
		// Enqueue for post editor (metabox).
		if ( in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
			// Enqueue CSS and JS for the meta box.
			wp_enqueue_style(
				'blaminhor-essentials-modules',
				BLAMINHOR_ESSENTIALS_PLUGIN_URL . 'assets/css/modules.css',
				array(),
				BLAMINHOR_ESSENTIALS_VERSION
			);

			wp_enqueue_script(
				'blaminhor-essentials-modules',
				BLAMINHOR_ESSENTIALS_PLUGIN_URL . 'assets/js/modules.js',
				array( 'jquery' ),
				BLAMINHOR_ESSENTIALS_VERSION,
				true
			);

			global $post;
			$default_desc  = '';
			$default_title = '';
			if ( $post ) {
				$default_desc  = $this->get_default_description( $post );
				$default_title = $this->get_default_title( $post );
			}

			$separator  = $this->get_setting( 'title_separator', '-' );
			$post_title = $post ? wp_specialchars_decode( get_the_title( $post ), ENT_QUOTES ) : '';

			$config = array(
				'defaultDesc'  => $default_desc,
				'defaultTitle' => $default_title,
				'nonce'        => wp_create_nonce( 'blaminhor_essentials_admin' ),
				'ajaxurl'      => admin_url( 'admin-ajax.php' ),
				'postId'       => $post ? $post->ID : 0,
				'strings'      => array(
					'viewDetails'  => __( 'View details', 'blaminhor-essentials' ),
					'refreshScore' => __( 'Refresh', 'blaminhor-essentials' ),
					'scoreNote'    => __( 'Not all criteria apply to every page. Aim for the highest relevant score, not necessarily 100.', 'blaminhor-essentials' ),
				),
				'variables'    => array(
					'{site_name}' => get_bloginfo( 'name' ),
					'{sep}'       => $separator,
					'{title}'     => $post_title,
					'{tagline}'   => get_bloginfo( 'description' ),
					'{year}'      => gmdate( 'Y' ),
					'{month}'     => gmdate( 'm' ),
					'{day}'       => gmdate( 'd' ),
				),
			);

			wp_add_inline_script(
				'blaminhor-essentials-modules',
				'jQuery(document).ready(function($) { if (typeof BlaminhorEssentialsSeoMetabox !== "undefined") { BlaminhorEssentialsSeoMetabox.init(' . wp_json_encode( $config ) . '); } });'
			);
			return;
		}

		// Enqueue for admin settings page.
		if ( strpos( $hook, 'blaminhor-essentials-seo-manager' ) === false ) {
			return;
		}

		$config = array(
			'nonce'    => wp_create_nonce( 'blaminhor_essentials_admin' ),
			'ajaxurl'  => admin_url( 'admin-ajax.php' ),
			'strings'  => array(
				'loading'        => __( 'Loading...', 'blaminhor-essentials' ),
				'noContent'      => __( 'No content found.', 'blaminhor-essentials' ),
				'notSet'         => __( 'Not set', 'blaminhor-essentials' ),
				'yes'            => __( 'Yes', 'blaminhor-essentials' ),
				'no'             => __( 'No', 'blaminhor-essentials' ),
				'edit'           => __( 'Edit', 'blaminhor-essentials' ),
				'itemsImported'  => __( 'items imported', 'blaminhor-essentials' ),
				'itemsSkipped'   => __( 'skipped (existing data)', 'blaminhor-essentials' ),
				'unsavedChanges' => __( 'You have unsaved changes. Are you sure you want to close?', 'blaminhor-essentials' ),
			),
			'titlePreview' => array(
				'site_name'     => get_bloginfo( 'name' ),
				'tagline'       => get_bloginfo( 'description' ),
				'sep'           => $this->get_setting( 'title_separator', '-' ),
				'sample_title'  => __( 'Example Post Title', 'blaminhor-essentials' ),
				'sample_term'   => __( 'Example Category', 'blaminhor-essentials' ),
				'sample_author' => 'John Doe',
				'sample_search' => 'WordPress',
				'text_404'      => __( 'Page not found', 'blaminhor-essentials' ),
				'year'          => gmdate( 'Y' ),
				'month'         => gmdate( 'm' ),
				'day'           => gmdate( 'd' ),
				'date'          => gmdate( 'F Y' ),
				'page_number'   => '2',
			),
		);

		wp_add_inline_script(
			'blaminhor-essentials-modules',
			'jQuery(document).ready(function($) { BlaminhorEssentialsSeoAdmin.init(' . wp_json_encode( $config ) . '); });'
		);
	}

	/**
	 * Add SEO meta box to post types
	 */
	public function add_seo_meta_box() {
		$post_types = blaminhor_essentials_get_post_types();

		foreach ( $post_types as $post_type ) {
			add_meta_box(
				'blaminhor_essentials_seo',
				__( 'SEO Settings', 'blaminhor-essentials' ) . ' - Blaminhor Essentials',
				array( $this, 'render_seo_meta_box' ),
				$post_type->name,
				'normal',
				'high'
			);
		}
	}

	/**
	 * Render SEO meta box
	 *
	 * @param WP_Post $post Post object.
	 */
	public function render_seo_meta_box( $post ) {
		wp_nonce_field( 'blaminhor_essentials_seo_meta_box', 'blaminhor_essentials_seo_nonce' );

		$meta      = $this->get_post_seo_meta( $post->ID );
		$permalink = get_permalink( $post );
		$site_name = get_bloginfo( 'name' );
		// Generate default description from content
		$default_desc = $this->get_default_description( $post );

		$default_title = $this->get_default_title( $post );
		$post_extra    = array(
			'{title}'  => wp_specialchars_decode( get_the_title( $post ), ENT_QUOTES ),
			'{author}' => get_the_author_meta( 'display_name', $post->post_author ),
		);
		$raw_title     = $meta['seo_title'] ? $this->replace_title_variables( $meta['seo_title'], $post_extra ) : $default_title;
		$raw_desc      = $meta['seo_description'] ? $this->replace_title_variables( $meta['seo_description'], $post_extra ) : $default_desc;
		?>
		<div class="ap-seo-meta-box">
			<div class="ap-seo-tabs">
				<button type="button" class="ap-seo-tab active" data-tab="seo"><?php esc_html_e( 'SEO', 'blaminhor-essentials' ); ?></button>
				<button type="button" class="ap-seo-tab" data-tab="geo"><?php esc_html_e( 'GEO', 'blaminhor-essentials' ); ?></button>
				<button type="button" class="ap-seo-tab" data-tab="social"><?php esc_html_e( 'Social', 'blaminhor-essentials' ); ?></button>
			</div>

			<!-- SEO Tab -->
			<div class="ap-seo-tab-content active" data-tab-content="seo">
				<!-- SEO Score -->
				<div class="ap-seo-field">
					<label><strong><?php esc_html_e( 'SEO Score', 'blaminhor-essentials' ); ?></strong></label>
					<div id="ap-seo-score-container">
						<div class="ap-score-loading"><?php esc_html_e( 'Analyzing content...', 'blaminhor-essentials' ); ?></div>
					</div>
				</div>

				<div class="ap-seo-field">
					<label for="ap_seo_title">
						<?php esc_html_e( 'SEO Title', 'blaminhor-essentials' ); ?>
						<span class="ap-seo-char-count" id="ap-title-count">0/60</span>
					</label>
					<input type="text" id="ap_seo_title" name="ap_seo_title" value="<?php echo esc_attr( $meta['seo_title'] ); ?>" placeholder="<?php echo esc_attr( $post->post_title ); ?>">
					<div class="ap-seo-progress"><div class="ap-seo-progress-bar" id="ap-title-bar"></div></div>
					<div class="seo-title-variables" data-target="#ap_seo_title">
						<button type="button" class="seo-var-btn" data-var="{title}"><?php esc_html_e( 'Title', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{sep}"><?php esc_html_e( 'Separator', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{site_name}"><?php esc_html_e( 'Site Name', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{author}"><?php esc_html_e( 'Author', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{category}"><?php esc_html_e( 'Category', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{post_type}"><?php esc_html_e( 'Post Type', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{year}"><?php esc_html_e( 'Year', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{page_number}"><?php esc_html_e( 'Page Number', 'blaminhor-essentials' ); ?></button>
					</div>
				</div>

				<div class="ap-seo-field">
					<label for="ap_seo_description">
						<?php esc_html_e( 'Meta Description', 'blaminhor-essentials' ); ?>
						<span class="ap-seo-char-count" id="ap-desc-count">0/160</span>
					</label>
					<textarea id="ap_seo_description" name="ap_seo_description" placeholder="<?php esc_attr_e( 'Enter a description for search engines...', 'blaminhor-essentials' ); ?>"><?php echo esc_textarea( $meta['seo_description'] ); ?></textarea>
					<div class="ap-seo-progress"><div class="ap-seo-progress-bar" id="ap-desc-bar"></div></div>
					<div class="seo-title-variables" data-target="#ap_seo_description">
						<button type="button" class="seo-var-btn" data-var="{title}"><?php esc_html_e( 'Title', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{sep}"><?php esc_html_e( 'Separator', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{site_name}"><?php esc_html_e( 'Site Name', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{author}"><?php esc_html_e( 'Author', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{category}"><?php esc_html_e( 'Category', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{post_type}"><?php esc_html_e( 'Post Type', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{year}"><?php esc_html_e( 'Year', 'blaminhor-essentials' ); ?></button>
						<button type="button" class="seo-var-btn" data-var="{page_number}"><?php esc_html_e( 'Page Number', 'blaminhor-essentials' ); ?></button>
					</div>
				</div>

				<div class="ap-seo-field ap-seo-keywords-field">
					<label for="ap_seo_keywords_input"><?php esc_html_e( 'Focus Keywords', 'blaminhor-essentials' ); ?></label>
					<div class="ap-seo-keywords-container">
						<div class="ap-seo-keywords-tags" id="ap_seo_keywords_tags">
							<?php
							if ( ! empty( $meta['seo_keywords'] ) ) {
								$keywords = array_map( 'trim', explode( ',', $meta['seo_keywords'] ) );
								foreach ( $keywords as $keyword ) {
									if ( ! empty( $keyword ) ) {
										echo '<span class="ap-seo-keyword-tag">' . esc_html( $keyword ) . '<button type="button" class="ap-seo-keyword-remove">&times;</button></span>';
									}
								}
							}
							?>
						</div>
						<input type="text" id="ap_seo_keywords_input" class="ap-seo-keywords-input" placeholder="<?php esc_attr_e( 'Type and press Enter to add...', 'blaminhor-essentials' ); ?>">
					</div>
					<input type="hidden" id="ap_seo_keywords" name="ap_seo_keywords" value="<?php echo esc_attr( $meta['seo_keywords'] ); ?>">
					<p class="description"><?php esc_html_e( 'Press Enter or comma to add a keyword. Click × to remove.', 'blaminhor-essentials' ); ?></p>
				</div>

				<!-- SERP Preview -->
				<div class="ap-seo-preview">
					<div class="ap-seo-preview-header">
						<strong><?php esc_html_e( 'SERP Preview', 'blaminhor-essentials' ); ?></strong>
					</div>
					<div class="ap-serp-previews-container">
						<!-- Mobile Preview -->
						<div class="ap-serp-preview-col">
							<div class="ap-serp-device-label">
								<span class="dashicons dashicons-smartphone ap-icon-16"></span>
								<?php esc_html_e( 'Mobile', 'blaminhor-essentials' ); ?>
							</div>
							<?php
							$site_icon_url  = get_site_icon_url( 32 );
							$thumbnail_url  = get_the_post_thumbnail_url( $post, 'thumbnail' );
							$post_date_text = get_the_date( '', $post );
							?>
							<div class="ap-serp-preview mobile">
								<div class="ap-serp-url">
									<div class="ap-serp-url-icon">
										<?php if ( $site_icon_url ) : ?>
											<img src="<?php echo esc_url( $site_icon_url ); ?>" alt="" width="18" height="18">
										<?php else : ?>
											<span class="dashicons dashicons-admin-site ap-icon-16" class="ap-text-gray"></span>
										<?php endif; ?>
									</div>
									<div class="ap-serp-url-info">
										<div class="ap-serp-site-name"><?php echo esc_html( $site_name ); ?></div>
										<div class="ap-serp-url-text"><?php echo esc_html( wp_parse_url( $permalink, PHP_URL_HOST ) ); ?></div>
									</div>
								</div>
								<div class="ap-serp-title ap-serp-title-sync-mobile"><?php echo esc_html( $raw_title ); ?></div>
								<div class="ap-serp-body">
									<div class="ap-serp-desc ap-serp-desc-sync-mobile"><span class="ap-serp-date"><?php echo esc_html( $post_date_text . ' — ' ); ?></span><span class="ap-serp-desc-text"><?php echo esc_html( $raw_desc ); ?></span></div>
									<?php if ( $thumbnail_url ) : ?>
										<img class="ap-serp-thumbnail" src="<?php echo esc_url( $thumbnail_url ); ?>" alt="">
									<?php endif; ?>
								</div>
							</div>
						</div>
						<!-- Desktop Preview -->
						<div class="ap-serp-preview-col">
							<div class="ap-serp-device-label">
								<span class="dashicons dashicons-desktop ap-icon-16"></span>
								<?php esc_html_e( 'Desktop', 'blaminhor-essentials' ); ?>
							</div>
							<div class="ap-serp-preview desktop">
								<div class="ap-serp-url">
									<div class="ap-serp-url-icon">
										<?php if ( $site_icon_url ) : ?>
											<img src="<?php echo esc_url( $site_icon_url ); ?>" alt="" width="18" height="18">
										<?php else : ?>
											<span class="dashicons dashicons-admin-site ap-icon-16" class="ap-text-gray"></span>
										<?php endif; ?>
									</div>
									<div class="ap-serp-url-info">
										<div class="ap-serp-site-name"><?php echo esc_html( $site_name ); ?></div>
										<div class="ap-serp-url-text"><?php echo esc_html( $this->format_serp_breadcrumb( $permalink ) ); ?></div>
									</div>
								</div>
								<div class="ap-serp-title ap-serp-title-sync-desktop"><?php echo esc_html( $raw_title ); ?></div>
								<div class="ap-serp-desc ap-serp-desc-sync-desktop"><span class="ap-serp-date"><?php echo esc_html( $post_date_text . ' — ' ); ?></span><span class="ap-serp-desc-text"><?php echo esc_html( $raw_desc ); ?></span></div>
							</div>
						</div>
					</div>
				</div>
				<hr>

				<h4><?php esc_html_e( 'Robots Meta', 'blaminhor-essentials' ); ?></h4>

				<div class="ap-seo-checkbox">
					<input type="checkbox" id="ap_noindex" name="ap_noindex" value="1" <?php checked( $meta['noindex'] ); ?>>
					<label for="ap_noindex"><?php esc_html_e( 'Do not index this page (noindex)', 'blaminhor-essentials' ); ?></label>
				</div>

				<div class="ap-seo-checkbox">
					<input type="checkbox" id="ap_nofollow" name="ap_nofollow" value="1" <?php checked( $meta['nofollow'] ); ?>>
					<label for="ap_nofollow"><?php esc_html_e( 'Do not follow links on this page (nofollow)', 'blaminhor-essentials' ); ?></label>
				</div>

				<div class="ap-seo-checkbox">
					<input type="checkbox" id="ap_noai" name="ap_noai" value="1" <?php checked( $meta['noai'] ); ?>>
					<label for="ap_noai"><?php esc_html_e( 'Block AI training on this content (noai, noimageai)', 'blaminhor-essentials' ); ?></label>
				</div>

				<hr>

				<div class="ap-seo-field">
					<label for="ap_canonical_url"><strong><?php esc_html_e( 'Canonical URL', 'blaminhor-essentials' ); ?></strong></label>
					<input type="url" id="ap_canonical_url" name="ap_canonical_url" value="<?php echo esc_url( $meta['canonical_url'] ); ?>" placeholder="<?php echo esc_url( $permalink ); ?>">
					<p class="description"><?php esc_html_e( 'Leave empty to use the default permalink.', 'blaminhor-essentials' ); ?></p>
				</div>

				</div>

			<!-- GEO Tab -->
			<div class="ap-seo-tab-content" data-tab-content="geo">
				<!-- GEO Score -->
				<div class="ap-seo-field">
					<label><strong><?php esc_html_e( 'GEO Score', 'blaminhor-essentials' ); ?></strong></label>
					<div id="ap-geo-score-container">
						<div class="ap-score-loading"><?php esc_html_e( 'Analyzing content...', 'blaminhor-essentials' ); ?></div>
					</div>
				</div>

				<div class="ap-seo-field">
					<label for="ap_schema_description"><strong><?php esc_html_e( 'Schema Description', 'blaminhor-essentials' ); ?></strong></label>
					<p class="description ap-mb-sm"><?php esc_html_e( 'Custom description used in structured data (JSON-LD). If empty, falls back to your SEO meta description, then to an automatic excerpt.', 'blaminhor-essentials' ); ?></p>
					<textarea id="ap_schema_description" name="ap_schema_description" rows="3" style="width:100%;" placeholder="<?php esc_attr_e( 'Describe this page for search engines and AI...', 'blaminhor-essentials' ); ?>"><?php echo esc_textarea( $meta['schema_description'] ); ?></textarea>
				</div>

				<hr>

				<?php
				// Key Takeaways.
				$key_takeaways = get_post_meta( $post->ID, '_blaminhor_essentials_key_takeaways', true );
				if ( ! is_array( $key_takeaways ) ) {
					$key_takeaways = array();
				}
				?>
				<div class="ap-seo-field">
					<label><strong><?php esc_html_e( 'Key Takeaways', 'blaminhor-essentials' ); ?></strong></label>
					<p class="description ap-mb-sm"><?php esc_html_e( 'Summarize your content in key points. These will be displayed at the top of the page and added to the Article schema.', 'blaminhor-essentials' ); ?></p>
					<div id="ap-key-takeaways-list">
						<?php if ( ! empty( $key_takeaways ) ) : ?>
							<?php foreach ( $key_takeaways as $i => $takeaway ) : ?>
								<div class="ap-key-takeaway-item">
									<input type="text" name="ap_key_takeaways[]" value="<?php echo esc_attr( $takeaway ); ?>" placeholder="<?php esc_attr_e( 'Add a key takeaway...', 'blaminhor-essentials' ); ?>">
									<button type="button" class="button ap-key-takeaway-remove" title="<?php esc_attr_e( 'Remove', 'blaminhor-essentials' ); ?>">&times;</button>
								</div>
							<?php endforeach; ?>
						<?php else : ?>
							<div class="ap-key-takeaway-item">
								<input type="text" name="ap_key_takeaways[]" value="" placeholder="<?php esc_attr_e( 'Add a key takeaway...', 'blaminhor-essentials' ); ?>">
								<button type="button" class="button ap-key-takeaway-remove" title="<?php esc_attr_e( 'Remove', 'blaminhor-essentials' ); ?>">&times;</button>
							</div>
						<?php endif; ?>
					</div>
					<button type="button" class="button" id="ap-key-takeaway-add">+ <?php esc_html_e( 'Add Takeaway', 'blaminhor-essentials' ); ?></button>
				</div>

				<hr>

				<!-- Schema section -->
				<?php
				$schema_preview = $this->get_post_schema_preview( $post );
				$faq_count      = $schema_preview['faq_count'];
				$howto_count    = isset( $schema_preview['howto_count'] ) ? $schema_preview['howto_count'] : 0;
				$video_count    = isset( $schema_preview['video_count'] ) ? $schema_preview['video_count'] : 0;
				?>

				<h4><?php esc_html_e( 'Detected Schemas', 'blaminhor-essentials' ); ?></h4>

				<div class="ap-geo-detected-schemas">
					<?php if ( $faq_count > 0 ) : ?>
					<div class="ap-seo-faq-detected">
						<span class="dashicons dashicons-yes-alt"></span>
						<?php
						printf(
							/* translators: %d: number of FAQ questions detected */
							esc_html__( 'FAQ detected: %d questions found', 'blaminhor-essentials' ),
							intval( $faq_count )
						);
						?>
					</div>
					<?php endif; ?>
					<?php if ( $howto_count > 0 ) : ?>
					<div class="ap-seo-faq-detected">
						<span class="dashicons dashicons-yes-alt"></span>
						<?php
						printf(
							/* translators: %d: number of HowTo steps detected */
							esc_html__( 'HowTo detected: %d steps found', 'blaminhor-essentials' ),
							intval( $howto_count )
						);
						?>
					</div>
					<?php endif; ?>
					<?php if ( $video_count > 0 ) : ?>
					<div class="ap-seo-faq-detected">
						<span class="dashicons dashicons-yes-alt"></span>
						<?php
						printf(
							/* translators: %d: number of videos detected */
							esc_html__( 'Video detected: %d found', 'blaminhor-essentials' ),
							intval( $video_count )
						);
						?>
					</div>
					<?php endif; ?>
					<?php if ( 0 === $faq_count && 0 === $howto_count && 0 === $video_count ) : ?>
					<p class="description"><?php esc_html_e( 'No FAQ, HowTo, or Video content detected in this page.', 'blaminhor-essentials' ); ?></p>
					<?php endif; ?>
				</div>

				<hr>

				<div class="ap-seo-field">
					<label for="ap_schema_type"><?php esc_html_e( 'Schema Type', 'blaminhor-essentials' ); ?></label>
					<?php
					$schema_type    = $meta['schema_type'];
					$schema_options = array(
						''             => __( 'Default (use global setting)', 'blaminhor-essentials' ),
						'Article'      => 'Article',
						'BlogPosting'  => 'BlogPosting',
						'NewsArticle'  => 'NewsArticle',
						'TechArticle'  => 'TechArticle',
						'WebPage'      => 'WebPage',
						'AboutPage'    => 'AboutPage',
						'ContactPage'  => 'ContactPage',
						'FAQPage'      => 'FAQPage',
						'None'         => __( 'None (disable structured data)', 'blaminhor-essentials' ),
					);
					?>
					<select id="ap_schema_type" name="ap_schema_type">
						<?php foreach ( $schema_options as $value => $label ) : ?>
							<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $schema_type, $value ); ?>><?php echo esc_html( $label ); ?></option>
						<?php endforeach; ?>
					</select>
				</div>

				<div class="ap-seo-field">
					<label for="ap_schema_custom_json"><?php esc_html_e( 'Custom JSON-LD', 'blaminhor-essentials' ); ?></label>
					<textarea id="ap_schema_custom_json" name="ap_schema_custom_json" class="ap-schema-json-textarea" placeholder='{"@context":"https://schema.org","@type":"Product","name":"..."}'><?php echo esc_textarea( $meta['schema_custom_json'] ); ?></textarea>
					<p class="description"><?php esc_html_e( 'Advanced: add custom JSON-LD markup. Must be valid JSON.', 'blaminhor-essentials' ); ?></p>
					<div id="ap-schema-json-status" class="ap-schema-json-status"></div>
				</div>

				<?php if ( ! empty( $schema_preview['schemas'] ) ) : ?>
				<hr>

				<div class="ap-seo-field">
					<label><?php esc_html_e( 'Generated JSON-LD Preview', 'blaminhor-essentials' ); ?></label>
					<p class="description ap-mb-sm">
						<?php esc_html_e( 'This is the structured data that will be automatically injected in the page head. It is read-only.', 'blaminhor-essentials' ); ?>
						<?php
						$schema_link = '<a href="' . esc_url( admin_url( 'admin.php?page=blaminhor-essentials-seo-manager&tab=schema' ) ) . '">' . esc_html__( 'GEO settings', 'blaminhor-essentials' ) . '</a>';
						echo wp_kses(
							sprintf(
								/* translators: %s: link to GEO settings tab */
								__( 'To modify the publisher name, logo, or social profiles, go to %s.', 'blaminhor-essentials' ),
								$schema_link
							),
							array( 'a' => array( 'href' => array() ) )
						);
						?>
					</p>
					<?php foreach ( $schema_preview['schemas'] as $i => $schema_block ) : ?>
					<div class="ap-schema-preview-block">
						<div class="ap-schema-preview-label"><?php echo esc_html( $schema_block['type'] ); ?></div>
						<pre class="ap-schema-preview-json"><?php echo esc_html( $schema_block['json'] ); ?></pre>
					</div>
					<?php endforeach; ?>
				</div>
				<?php endif; ?>
			</div>

			<!-- Social Tab -->
			<div class="ap-seo-tab-content" data-tab-content="social">
				<details class="ap-tab-info">
					<summary><span class="dashicons dashicons-arrow-right-alt2"></span><?php esc_html_e( 'Learn more', 'blaminhor-essentials' ); ?></summary>
					<div class="ap-tab-info-content">
						<p><?php esc_html_e( 'Customize how this page looks when shared on social media. Leave fields empty to use the SEO title, description, and featured image automatically.', 'blaminhor-essentials' ); ?></p>
					</div>
				</details>
				<h4><?php esc_html_e( 'Open Graph (Facebook, LinkedIn, etc.)', 'blaminhor-essentials' ); ?></h4>

				<div class="ap-seo-field">
					<label for="ap_og_title"><?php esc_html_e( 'OG Title', 'blaminhor-essentials' ); ?></label>
					<input type="text" id="ap_og_title" name="ap_og_title" value="<?php echo esc_attr( $meta['og_title'] ); ?>" placeholder="<?php esc_attr_e( 'Leave empty to use SEO title', 'blaminhor-essentials' ); ?>">
				</div>

				<div class="ap-seo-field">
					<label for="ap_og_description"><?php esc_html_e( 'OG Description', 'blaminhor-essentials' ); ?></label>
					<textarea id="ap_og_description" name="ap_og_description" placeholder="<?php esc_attr_e( 'Leave empty to use meta description', 'blaminhor-essentials' ); ?>"><?php echo esc_textarea( $meta['og_description'] ); ?></textarea>
				</div>

				<div class="ap-seo-field">
					<label for="ap_og_image"><?php esc_html_e( 'OG Image URL', 'blaminhor-essentials' ); ?></label>
					<input type="text" id="ap_og_image" name="ap_og_image" value="<?php echo esc_url( $meta['og_image'] ); ?>" placeholder="<?php esc_attr_e( 'Leave empty to use featured image', 'blaminhor-essentials' ); ?>">
					<p class="description"><?php esc_html_e( 'Recommended: 1200x630 pixels.', 'blaminhor-essentials' ); ?></p>
				</div>

				<hr>

				<h4><?php esc_html_e( 'Twitter Card', 'blaminhor-essentials' ); ?></h4>

				<div class="ap-seo-field">
					<label for="ap_twitter_title"><?php esc_html_e( 'Twitter Title', 'blaminhor-essentials' ); ?></label>
					<input type="text" id="ap_twitter_title" name="ap_twitter_title" value="<?php echo esc_attr( $meta['twitter_title'] ); ?>" placeholder="<?php esc_attr_e( 'Leave empty to use OG title', 'blaminhor-essentials' ); ?>">
				</div>

				<div class="ap-seo-field">
					<label for="ap_twitter_description"><?php esc_html_e( 'Twitter Description', 'blaminhor-essentials' ); ?></label>
					<textarea id="ap_twitter_description" name="ap_twitter_description" placeholder="<?php esc_attr_e( 'Leave empty to use OG description', 'blaminhor-essentials' ); ?>"><?php echo esc_textarea( $meta['twitter_description'] ); ?></textarea>
				</div>
			</div>

		</div>
		<?php
	}

	/**
	 * Save SEO meta box data
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post Post object.
	 */
	public function save_seo_meta_box( $post_id, $post ) {
		// Verify nonce.
		if ( ! isset( $_POST['blaminhor_essentials_seo_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['blaminhor_essentials_seo_nonce'] ) ), 'blaminhor_essentials_seo_meta_box' ) ) {
			return;
		}

		// Check autosave.
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		// Check permissions.
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		// Save meta fields.
		$fields = array(
			'ap_seo_title'         => 'seo_title',
			'ap_seo_description'   => 'seo_description',
			'ap_seo_keywords'      => 'seo_keywords',
			'ap_og_title'          => 'og_title',
			'ap_og_description'    => 'og_description',
			'ap_og_image'          => 'og_image',
			'ap_twitter_title'     => 'twitter_title',
			'ap_twitter_description' => 'twitter_description',
			'ap_canonical_url'     => 'canonical_url',
		);

		foreach ( $fields as $post_key => $meta_key ) {
			$value = isset( $_POST[ $post_key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $post_key ] ) ) : '';
			update_post_meta( $post_id, '_blaminhor_essentials_' . $meta_key, $value );
		}

		// Checkboxes.
		update_post_meta( $post_id, '_blaminhor_essentials_noindex', isset( $_POST['ap_noindex'] ) ? '1' : '' );
		update_post_meta( $post_id, '_blaminhor_essentials_nofollow', isset( $_POST['ap_nofollow'] ) ? '1' : '' );
		update_post_meta( $post_id, '_blaminhor_essentials_noai', isset( $_POST['ap_noai'] ) ? '1' : '' );

		// Schema description.
		$schema_desc = isset( $_POST['ap_schema_description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['ap_schema_description'] ) ) : '';
		update_post_meta( $post_id, '_blaminhor_essentials_schema_description', $schema_desc );

		// Schema type.
		$schema_type = isset( $_POST['ap_schema_type'] ) ? sanitize_text_field( wp_unslash( $_POST['ap_schema_type'] ) ) : '';
		$valid_schema_types = array( '', 'Article', 'BlogPosting', 'NewsArticle', 'TechArticle', 'WebPage', 'AboutPage', 'ContactPage', 'FAQPage', 'None' );
		if ( in_array( $schema_type, $valid_schema_types, true ) ) {
			update_post_meta( $post_id, '_blaminhor_essentials_schema_type', $schema_type );
		}

		// Key Takeaways.
		$key_takeaways = array();
		if ( isset( $_POST['ap_key_takeaways'] ) && is_array( $_POST['ap_key_takeaways'] ) ) {
			$raw_takeaways = wp_unslash( $_POST['ap_key_takeaways'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			foreach ( $raw_takeaways as $takeaway ) {
				$clean = sanitize_text_field( $takeaway );
				if ( ! empty( $clean ) ) {
					$key_takeaways[] = $clean;
				}
			}
		}
		update_post_meta( $post_id, '_blaminhor_essentials_key_takeaways', $key_takeaways );

		// Schema custom JSON.
		$schema_custom_json = isset( $_POST['ap_schema_custom_json'] ) ? wp_unslash( $_POST['ap_schema_custom_json'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		if ( ! empty( $schema_custom_json ) ) {
			// Validate JSON.
			$decoded = json_decode( $schema_custom_json );
			if ( null !== $decoded ) {
				update_post_meta( $post_id, '_blaminhor_essentials_schema_custom_json', sanitize_textarea_field( wp_slash( $schema_custom_json ) ) );
			}
		} else {
			update_post_meta( $post_id, '_blaminhor_essentials_schema_custom_json', '' );
		}
	}

	/**
	 * Format a URL as a Google-style breadcrumb (site.com › category › slug).
	 *
	 * @param string $url Full URL.
	 * @return string
	 */
	private function format_serp_breadcrumb( $url ) {
		$parsed = wp_parse_url( $url );
		$host   = isset( $parsed['host'] ) ? $parsed['host'] : '';
		$path   = isset( $parsed['path'] ) ? trim( $parsed['path'], '/' ) : '';
		if ( empty( $path ) ) {
			return $host;
		}
		$segments = explode( '/', $path );
		return $host . ' › ' . implode( ' › ', $segments );
	}

	/**
	 * Get default formatted title for a post (using title templates).
	 *
	 * @param WP_Post $post Post object.
	 * @return string Formatted title or post title if no template.
	 */
	private function get_default_title( $post ) {
		$templates = $this->get_setting( 'title_templates', array() );
		$pt        = get_post_type( $post );
		$tpl_raw   = isset( $templates[ $pt ] ) ? $templates[ $pt ] : '';
		$tpl       = is_array( $tpl_raw ) ? ( isset( $tpl_raw['template'] ) ? $tpl_raw['template'] : '' ) : $tpl_raw;

		if ( ! empty( $tpl ) ) {
			$categories = get_the_category( $post->ID );
			$pt_object  = get_post_type_object( $pt );
			$extra      = array(
				'{title}'     => wp_specialchars_decode( get_the_title( $post ), ENT_QUOTES ),
				'{author}'    => get_the_author_meta( 'display_name', $post->post_author ),
				'{post_type}' => $pt_object ? $pt_object->labels->singular_name : '',
				'{category}'  => ! empty( $categories ) ? $categories[0]->name : '',
			);
			return $this->replace_title_variables( $tpl, $extra );
		}

		return wp_specialchars_decode( get_the_title( $post ), ENT_QUOTES );
	}

	/**
	 * Get default description from post content
	 * Tries multiple methods to extract text from various content types including page builders
	 *
	 * @param WP_Post $post Post object.
	 * @return string
	 */
	private function get_default_description( $post ) {
		// 1. First try the post excerpt if available
		if ( ! empty( $post->post_excerpt ) ) {
			return wp_specialchars_decode( wp_trim_words( wp_strip_all_tags( $post->post_excerpt ), 25, '...' ) );
		}

		// 2. Try stripping shortcodes from content
		$clean_content = strip_shortcodes( $post->post_content );
		$text = wp_trim_words( wp_strip_all_tags( $clean_content ), 25, '...' );
		if ( ! empty( $text ) ) {
			return wp_specialchars_decode( $text );
		}

		// 3. Try Elementor data if available
		$elementor_data = get_post_meta( $post->ID, '_elementor_data', true );
		if ( ! empty( $elementor_data ) ) {
			$data = json_decode( $elementor_data, true );
			if ( is_array( $data ) ) {
				$text = $this->extract_text_from_elementor( $data );
				if ( ! empty( $text ) ) {
					return wp_specialchars_decode( wp_trim_words( $text, 25, '...' ) );
				}
			}
		}

		return '';
	}

	/**
	 * Extract text content from Elementor data recursively
	 *
	 * @param array $elements Elementor elements.
	 * @return string
	 */
	private function extract_text_from_elementor( $elements ) {
		$text = '';
		foreach ( $elements as $element ) {
			// Get text from settings
			if ( isset( $element['settings'] ) ) {
				foreach ( array( 'title', 'editor', 'description', 'text', 'content' ) as $key ) {
					if ( ! empty( $element['settings'][ $key ] ) ) {
						$text .= ' ' . wp_strip_all_tags( $element['settings'][ $key ] );
					}
				}
			}
			// Recurse into child elements
			if ( ! empty( $element['elements'] ) ) {
				$text .= ' ' . $this->extract_text_from_elementor( $element['elements'] );
			}
		}
		return trim( $text );
	}

	/**
	 * Get post SEO meta
	 *
	 * @param int $post_id Post ID.
	 * @return array
	 */
	private function get_post_seo_meta( $post_id ) {
		return array(
			'seo_title'           => get_post_meta( $post_id, '_blaminhor_essentials_seo_title', true ),
			'seo_description'     => get_post_meta( $post_id, '_blaminhor_essentials_seo_description', true ),
			'seo_keywords'        => get_post_meta( $post_id, '_blaminhor_essentials_seo_keywords', true ),
			'og_title'            => get_post_meta( $post_id, '_blaminhor_essentials_og_title', true ),
			'og_description'      => get_post_meta( $post_id, '_blaminhor_essentials_og_description', true ),
			'og_image'            => get_post_meta( $post_id, '_blaminhor_essentials_og_image', true ),
			'twitter_title'       => get_post_meta( $post_id, '_blaminhor_essentials_twitter_title', true ),
			'twitter_description' => get_post_meta( $post_id, '_blaminhor_essentials_twitter_description', true ),
			'canonical_url'       => get_post_meta( $post_id, '_blaminhor_essentials_canonical_url', true ),
			'noindex'             => get_post_meta( $post_id, '_blaminhor_essentials_noindex', true ),
			'nofollow'            => get_post_meta( $post_id, '_blaminhor_essentials_nofollow', true ),
			'noai'                => get_post_meta( $post_id, '_blaminhor_essentials_noai', true ),
			'schema_type'         => get_post_meta( $post_id, '_blaminhor_essentials_schema_type', true ),
			'schema_description'  => get_post_meta( $post_id, '_blaminhor_essentials_schema_description', true ),
			'schema_custom_json'  => get_post_meta( $post_id, '_blaminhor_essentials_schema_custom_json', true ),
		);
	}

	/**
	 * Output meta tags in head
	 */
	public function output_meta_tags() {
		// Global noindex for entire site.
		if ( $this->get_setting( 'noindex_entire_site', false ) ) {
			echo '<meta name="robots" content="noindex, nofollow">' . "\n";
			return; // Skip other meta tags if entire site is noindexed.
		}

		if ( is_singular() ) {
			$this->output_singular_meta_tags();
		} elseif ( is_front_page() ) {
			$this->output_homepage_meta_tags();
		} elseif ( is_archive() || is_search() ) {
			$this->output_archive_meta_tags();
		}

		// Global AI meta tags.
		if ( $this->get_setting( 'ai_noai', false ) ) {
			echo '<meta name="robots" content="noai, noimageai">' . "\n";
		}
	}

	/**
	 * Output singular meta tags
	 */
	private function output_singular_meta_tags() {
		global $post;

		if ( ! $post ) {
			return;
		}

		$meta = $this->get_post_seo_meta( $post->ID );

		// Build extra variables for this post context.
		$pt         = get_post_type( $post );
		$categories = get_the_category( $post->ID );
		$extra      = array(
			'{title}'     => get_the_title( $post ),
			'{author}'    => get_the_author_meta( 'display_name', $post->post_author ),
			'{post_type}' => get_post_type_object( $pt ) ? get_post_type_object( $pt )->labels->singular_name : '',
			'{category}'  => ! empty( $categories ) ? $categories[0]->name : '',
		);

		// Meta description.
		$description = $meta['seo_description'];
		if ( ! empty( $description ) ) {
			$description = $this->replace_title_variables( $description, $extra );
		} else {
			$description = $this->get_default_description( $post );
		}
		if ( ! empty( $description ) ) {
			echo '<meta name="description" content="' . esc_attr( $description ) . '">' . "\n";
		}

		// Robots meta.
		$robots = array();
		$indexable_types = $this->get_setting( 'indexable_post_types', array( 'post', 'page' ) );

		if ( $meta['noindex'] || ! in_array( $post->post_type, $indexable_types, true ) ) {
			$robots[] = 'noindex';
		}
		if ( $meta['nofollow'] ) {
			$robots[] = 'nofollow';
		}
		if ( $meta['noai'] ) {
			$robots[] = 'noai';
			$robots[] = 'noimageai';
		}
		if ( ! empty( $robots ) ) {
			echo '<meta name="robots" content="' . esc_attr( implode( ', ', $robots ) ) . '">' . "\n";
		}

		// Open Graph.
		$og_title = $meta['og_title'] ?: $meta['seo_title'] ?: '';
		if ( ! empty( $og_title ) ) {
			$og_title = $this->replace_title_variables( $og_title, $extra );
		} else {
			$og_title = $post->post_title;
		}
		$og_desc = $meta['og_description'] ? $this->replace_title_variables( $meta['og_description'], $extra ) : $description;
		$og_image = $meta['og_image'];

		if ( empty( $og_image ) && has_post_thumbnail( $post ) ) {
			$og_image = get_the_post_thumbnail_url( $post, 'large' );
		}
		if ( empty( $og_image ) ) {
			$og_image = $this->get_setting( 'og_default_image', '' );
		}

		echo '<meta property="og:type" content="article">' . "\n";
		echo '<meta property="og:title" content="' . esc_attr( $og_title ) . '">' . "\n";
		echo '<meta property="og:url" content="' . esc_url( get_permalink( $post ) ) . '">' . "\n";
		if ( ! empty( $og_desc ) ) {
			echo '<meta property="og:description" content="' . esc_attr( $og_desc ) . '">' . "\n";
		}
		if ( ! empty( $og_image ) ) {
			echo '<meta property="og:image" content="' . esc_url( $og_image ) . '">' . "\n";
		}
		echo '<meta property="og:site_name" content="' . esc_attr( get_bloginfo( 'name' ) ) . '">' . "\n";

		// Twitter Card.
		$twitter_card = $this->get_setting( 'twitter_card_type', 'summary_large_image' );
		$twitter_title = $meta['twitter_title'] ? $this->replace_title_variables( $meta['twitter_title'], $extra ) : $og_title;
		$twitter_desc  = $meta['twitter_description'] ? $this->replace_title_variables( $meta['twitter_description'], $extra ) : $og_desc;
		$twitter_site  = $this->get_setting( 'twitter_site', '' );

		echo '<meta name="twitter:card" content="' . esc_attr( $twitter_card ) . '">' . "\n";
		echo '<meta name="twitter:title" content="' . esc_attr( $twitter_title ) . '">' . "\n";
		if ( ! empty( $twitter_desc ) ) {
			echo '<meta name="twitter:description" content="' . esc_attr( $twitter_desc ) . '">' . "\n";
		}
		if ( ! empty( $og_image ) ) {
			echo '<meta name="twitter:image" content="' . esc_url( $og_image ) . '">' . "\n";
		}
		if ( ! empty( $twitter_site ) ) {
			echo '<meta name="twitter:site" content="' . esc_attr( $twitter_site ) . '">' . "\n";
		}
	}

	/**
	 * Output homepage meta tags
	 */
	private function output_homepage_meta_tags() {
		$description = get_bloginfo( 'description' );
		$site_name   = get_bloginfo( 'name' );
		$og_image    = $this->get_setting( 'og_default_image', '' );

		if ( ! empty( $description ) ) {
			echo '<meta name="description" content="' . esc_attr( $description ) . '">' . "\n";
		}

		echo '<meta property="og:type" content="website">' . "\n";
		echo '<meta property="og:title" content="' . esc_attr( $site_name ) . '">' . "\n";
		echo '<meta property="og:url" content="' . esc_url( home_url( '/' ) ) . '">' . "\n";
		if ( ! empty( $description ) ) {
			echo '<meta property="og:description" content="' . esc_attr( $description ) . '">' . "\n";
		}
		if ( ! empty( $og_image ) ) {
			echo '<meta property="og:image" content="' . esc_url( $og_image ) . '">' . "\n";
		}
		echo '<meta property="og:site_name" content="' . esc_attr( $site_name ) . '">' . "\n";
	}

	/**
	 * Output archive meta tags
	 */
	private function output_archive_meta_tags() {
		$robots = array();

		if ( is_search() && $this->get_setting( 'noindex_search', true ) ) {
			$robots[] = 'noindex';
		} elseif ( is_author() && $this->get_setting( 'noindex_author_archives', true ) ) {
			$robots[] = 'noindex';
		} elseif ( is_date() && $this->get_setting( 'noindex_date_archives', true ) ) {
			$robots[] = 'noindex';
		} elseif ( $this->get_setting( 'noindex_archives', false ) ) {
			$robots[] = 'noindex';
		}

		if ( ! empty( $robots ) ) {
			echo '<meta name="robots" content="' . esc_attr( implode( ', ', $robots ) ) . '">' . "\n";
		}
	}

	/**
	 * Replace title template variables with actual values
	 *
	 * @param string $template Template string with variables.
	 * @param array  $extra    Extra variables to replace.
	 * @return string
	 */
	private function replace_title_variables( $template, $extra = array() ) {
		$separator = $this->get_setting( 'title_separator', '-' );

		$variables = array(
			'{sep}'       => $separator,
			'{site_name}' => get_bloginfo( 'name' ),
			'{tagline}'   => get_bloginfo( 'description' ),
			'{year}'      => gmdate( 'Y' ),
			'{month}'     => gmdate( 'm' ),
			'{day}'       => gmdate( 'd' ),
			/* translators: This is the default 404 page title prefix used in title templates. */
			'{text_404}'  => __( 'Page not found', 'blaminhor-essentials' ),
		);

		// Merge extra variables (e.g., {title}, {author}, {term_name}).
		$variables = array_merge( $variables, $extra );

		$result = str_replace( array_keys( $variables ), array_values( $variables ), $template );

		// Remove any unresolved placeholders (e.g. {page_number} when not on a paginated page).
		$result = preg_replace( '/\s*\{[a-z_]+\}/', '', $result );

		return trim( $result );
	}

	/**
	 * Filter document title
	 *
	 * @param string $title Current title.
	 * @return string
	 */
	public function filter_document_title( $title ) {
		$templates = $this->get_setting( 'title_templates', array() );

		// Singular pages.
		if ( is_singular() ) {
			global $post;
			if ( ! $post ) {
				return $title;
			}

			$seo_title = get_post_meta( $post->ID, '_blaminhor_essentials_seo_title', true );
			$pt        = get_post_type( $post );
			$tpl_raw   = isset( $templates[ $pt ] ) ? $templates[ $pt ] : '';
			$tpl       = is_array( $tpl_raw ) ? ( isset( $tpl_raw['template'] ) ? $tpl_raw['template'] : '' ) : $tpl_raw;

			// Build extra variables for this post context.
			$categories = get_the_category( $post->ID );
			$extra      = array(
				'{title}'     => get_the_title( $post ),
				'{author}'    => get_the_author_meta( 'display_name', $post->post_author ),
				'{post_type}' => get_post_type_object( $pt )->labels->singular_name,
				'{category}'  => ! empty( $categories ) ? $categories[0]->name : '',
			);

			// Manual SEO title takes priority.
			if ( ! empty( $seo_title ) ) {
				return $this->replace_title_variables( $seo_title, $extra );
			}

			// Template for this post type.
			if ( ! empty( $tpl ) ) {
				return $this->replace_title_variables( $tpl, $extra );
			}

			return $title;
		}

		// Homepage / front page.
		if ( is_front_page() || is_home() ) {
			$tpl = $this->get_setting( 'title_home', '' );
			if ( ! empty( $tpl ) ) {
				return $this->replace_title_variables( $tpl );
			}
			return $title;
		}

		// Taxonomy archives.
		if ( is_category() || is_tag() || is_tax() ) {
			$term = get_queried_object();
			if ( $term && isset( $term->taxonomy ) ) {
				$tpl_raw = isset( $templates[ $term->taxonomy ] ) ? $templates[ $term->taxonomy ] : '';
				$tpl     = is_array( $tpl_raw ) ? ( isset( $tpl_raw['template'] ) ? $tpl_raw['template'] : '' ) : $tpl_raw;
				$extra   = array(
					'{term_name}' => $term->name,
					'{title}'     => $term->name,
				);

				if ( ! empty( $tpl ) ) {
					return $this->replace_title_variables( $tpl, $extra );
				}
			}
			return $title;
		}

		// Post type archives.
		if ( is_post_type_archive() ) {
			$pt_obj = get_queried_object();
			if ( $pt_obj && isset( $pt_obj->name ) ) {
				$tpl_raw = isset( $templates[ $pt_obj->name ] ) ? $templates[ $pt_obj->name ] : '';
				$tpl     = is_array( $tpl_raw ) ? ( isset( $tpl_raw['template'] ) ? $tpl_raw['template'] : '' ) : $tpl_raw;
				$extra   = array(
					'{title}'     => post_type_archive_title( '', false ),
					'{post_type}' => $pt_obj->labels->name,
				);

				if ( ! empty( $tpl ) ) {
					return $this->replace_title_variables( $tpl, $extra );
				}
			}
			return $title;
		}

		// Search results.
		if ( is_search() ) {
			$tpl = $this->get_setting( 'title_search', '' );
			if ( ! empty( $tpl ) ) {
				$extra = array(
					'{search_query}' => get_search_query(),
				);
				return $this->replace_title_variables( $tpl, $extra );
			}
			return $title;
		}

		// 404 page.
		if ( is_404() ) {
			$tpl = $this->get_setting( 'title_404', '' );
			if ( ! empty( $tpl ) ) {
				return $this->replace_title_variables( $tpl );
			}
			return $title;
		}

		// Author archives.
		if ( is_author() ) {
			$tpl = $this->get_setting( 'title_author_archive', '' );
			if ( ! empty( $tpl ) ) {
				$extra = array(
					'{author}' => get_the_author(),
				);
				return $this->replace_title_variables( $tpl, $extra );
			}
			return $title;
		}

		// Date archives.
		if ( is_date() ) {
			$tpl = $this->get_setting( 'title_date_archive', '' );
			if ( ! empty( $tpl ) ) {
				$date = '';
				if ( is_year() ) {
					$date = get_the_date( 'Y' );
				} elseif ( is_month() ) {
					$date = get_the_date( 'F Y' );
				} elseif ( is_day() ) {
					$date = get_the_date();
				}
				$extra = array(
					'{date}' => $date,
				);
				return $this->replace_title_variables( $tpl, $extra );
			}
			return $title;
		}

		// Paginated pages.
		$paged = get_query_var( 'paged' );
		if ( $paged > 1 && ! empty( $title ) ) {
			$extra = array(
				'{page_number}' => $paged,
			);
			$title = $this->replace_title_variables( $title, $extra );
		}

		return $title;
	}

	/**
	 * Output canonical URL
	 */
	public function output_canonical() {
		if ( ! is_singular() ) {
			rel_canonical();
			return;
		}

		global $post;
		if ( ! $post ) {
			rel_canonical();
			return;
		}

		$canonical = get_post_meta( $post->ID, '_blaminhor_essentials_canonical_url', true );
		if ( empty( $canonical ) ) {
			$canonical = get_permalink( $post );
		}

		echo '<link rel="canonical" href="' . esc_url( $canonical ) . '">' . "\n";
	}

	/**
	 * AJAX: Get content list for SEO overview
	 */
	public function ajax_get_content_list() {
		check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'Unauthorized' );
		}

		$post_type = isset( $_POST['post_type'] ) ? sanitize_key( $_POST['post_type'] ) : 'post';
		$page      = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
		$per_page  = 20;

		$query = new WP_Query( array(
			'post_type'        => $post_type,
			'post_status'      => 'publish',
			'posts_per_page'   => $per_page,
			'paged'            => $page,
			'orderby'          => 'modified',
			'order'            => 'DESC',
			'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Required to prevent third-party theme filters (e.g. Avada) from altering admin content list results.
		) );

		$items = array();
		foreach ( $query->posts as $post ) {
			$meta = $this->get_post_seo_meta( $post->ID );
			// Generate default description from content
			$default_desc = $this->get_default_description( $post );
			// Parse keywords into array
			$keywords = array();
			if ( ! empty( $meta['seo_keywords'] ) ) {
				$keywords = array_map( 'trim', explode( ',', $meta['seo_keywords'] ) );
				$keywords = array_filter( $keywords );
			}
			$items[] = array(
				'id'           => $post->ID,
				'title'        => $post->post_title,
				'url'          => get_permalink( $post ),
				'edit_url'     => get_edit_post_link( $post->ID, 'raw' ),
				'seo_title'    => $meta['seo_title'],
				'seo_desc'     => $meta['seo_description'],
				'seo_keywords' => $keywords,
				'default_desc' => $default_desc,
				'noindex'      => $meta['noindex'],
				'has_seo'      => ! empty( $meta['seo_title'] ) || ! empty( $meta['seo_description'] ),
			);
		}

		wp_send_json_success( array(
			'items'      => $items,
			'total'      => $query->found_posts,
			'pages'      => $query->max_num_pages,
			'current'    => $page,
		) );
	}

	/**
	 * AJAX: Save meta for a post
	 */
	public function ajax_save_meta() {
		check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'Unauthorized' );
		}

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
		if ( ! $post_id ) {
			wp_send_json_error( 'Invalid post ID' );
		}

		$field = isset( $_POST['field'] ) ? sanitize_key( $_POST['field'] ) : '';
		$value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';

		$allowed_fields = array( 'seo_title', 'seo_description', 'seo_keywords', 'noindex', 'nofollow', 'og_title', 'og_description', 'og_image', 'twitter_title', 'twitter_description', 'noai', 'canonical_url', 'schema_type', 'schema_description', 'schema_custom_json' );
		if ( ! in_array( $field, $allowed_fields, true ) ) {
			wp_send_json_error( 'Invalid field' );
		}

		update_post_meta( $post_id, '_blaminhor_essentials_' . $field, $value );

		wp_send_json_success();
	}

	/**
	 * AJAX: Get meta for a post
	 */
	public function ajax_get_meta() {
		check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'Unauthorized' );
		}

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
		if ( ! $post_id ) {
			wp_send_json_error( 'Invalid post ID' );
		}

		$meta = $this->get_post_seo_meta( $post_id );
		$post = get_post( $post_id );

		// Generate default description from content
		$default_desc = $this->get_default_description( $post );

		$separator = $this->get_setting( 'title_separator', '-' );

		wp_send_json_success( array(
			'meta'          => $meta,
			'title'         => $post->post_title,
			'default_title' => $this->get_default_title( $post ),
			'url'           => get_permalink( $post ),
			'default_desc'  => $default_desc,
			'post_date'     => get_the_date( '', $post ),
			'thumbnail_url' => has_post_thumbnail( $post ) ? get_the_post_thumbnail_url( $post, 'thumbnail' ) : '',
			'variables'     => array(
				'{site_name}' => get_bloginfo( 'name' ),
				'{sep}'       => $separator,
				'{title}'     => wp_specialchars_decode( get_the_title( $post ), ENT_QUOTES ),
				'{tagline}'   => get_bloginfo( 'description' ),
				'{year}'      => gmdate( 'Y' ),
				'{month}'     => gmdate( 'm' ),
				'{day}'       => gmdate( 'd' ),
			),
		) );
	}

	// =========================================================================
	// SITEMAP FUNCTIONALITY (migrated from sitemap module)
	// =========================================================================

	/**
	 * Return every URL this module would include in its sitemap, across all
	 * indexed post types. Same logic as generate_post_sitemap() (publish only,
	 * excludes posts flagged _blaminhor_essentials_noindex), but as a flat
	 * array suitable for the cache preloader — no XML, no HTTP roundtrip.
	 *
	 * @return string[] Permalinks of all indexed posts, plus home_url('/').
	 */
	public function get_sitemap_urls() {
		// Some sites have a third-party `home_url` filter that returns false
		// on certain admin contexts (Avada / Fusion has been observed doing
		// this). Read the raw option as a fallback so this method always
		// produces working URLs, regardless of broken upstream filters.
		$home = home_url( '/' );
		if ( ! is_string( $home ) || '' === $home ) {
			$raw_home = (string) get_option( 'home' );
			$home     = '' === $raw_home ? '' : trailingslashit( $raw_home );
		}

		$urls = array();
		if ( '' !== $home ) {
			$urls[] = $home;
		}

		$enabled_types = (array) $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) );
		$per_page      = (int) $this->get_setting( 'sitemap_posts_per_file', 1000 );

		foreach ( $enabled_types as $post_type ) {
			if ( ! post_type_exists( $post_type ) ) {
				continue;
			}
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Same noindex filter as generate_post_sitemap()
			$ids = get_posts( array(
				'post_type'      => $post_type,
				'post_status'    => 'publish',
				'posts_per_page' => $per_page,
				'orderby'        => 'modified',
				'order'          => 'DESC',
				'fields'         => 'ids',
				'meta_query'     => array(
					'relation' => 'OR',
					array( 'key' => '_blaminhor_essentials_noindex', 'compare' => 'NOT EXISTS' ),
					array( 'key' => '_blaminhor_essentials_noindex', 'value' => '1', 'compare' => '!=' ),
				),
			) );
			foreach ( (array) $ids as $id ) {
				// Try the standard WP function first.
				$link = get_permalink( (int) $id );
				if ( is_string( $link ) && '' !== $link ) {
					$urls[] = $link;
					continue;
				}
				// Fallback: build the URL from the post slug + the raw home
				// option, so a broken `home_url` / `post_link` filter chain
				// doesn't strip the entire site from the queue.
				if ( '' === $home ) {
					continue;
				}
				$post = get_post( (int) $id );
				if ( ! $post || empty( $post->post_name ) ) {
					continue;
				}
				if ( 'page' === $post->post_type ) {
					$urls[] = $home . $post->post_name . '/';
				} else {
					$urls[] = $home . '?p=' . (int) $id;
				}
			}
		}

		return array_values( array_unique( $urls ) );
	}

	/**
	 * Add rewrite rules for sitemap URLs
	 */
	public function add_sitemap_rewrite_rules() {
		// Main sitemap index.
		add_rewrite_rule( '^sitemap\.xml$', 'index.php?ap_sitemap=index', 'top' );

		// Post type sitemaps: post-sitemap.xml, page-sitemap.xml, etc.
		$post_types = $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) );
		foreach ( $post_types as $post_type ) {
			if ( post_type_exists( $post_type ) ) {
				add_rewrite_rule( '^' . $post_type . '-sitemap\.xml$', 'index.php?ap_sitemap=' . $post_type, 'top' );
			}
		}

		// Taxonomy sitemaps: category-sitemap.xml, post_tag-sitemap.xml, etc.
		$taxonomies = $this->get_setting( 'sitemap_taxonomies', array( 'category', 'post_tag' ) );
		foreach ( $taxonomies as $taxonomy ) {
			if ( taxonomy_exists( $taxonomy ) ) {
				add_rewrite_rule( '^' . $taxonomy . '-sitemap\.xml$', 'index.php?ap_sitemap=' . $taxonomy, 'top' );
			}
		}
	}

	/**
	 * Flush rewrite rules if needed
	 */
	public function maybe_flush_rewrite_rules() {
		$option_key = 'blaminhor_essentials_sitemap_rules_flushed';
		$rules_hash = md5( serialize( array(
			'version'    => defined( 'BLAMINHOR_ESSENTIALS_VERSION' ) ? BLAMINHOR_ESSENTIALS_VERSION : '1.0.0',
			'enabled'    => $this->get_setting( 'sitemap_enabled', true ),
			'post_types' => $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) ),
			'taxonomies' => $this->get_setting( 'sitemap_taxonomies', array( 'category', 'post_tag' ) ),
			'flush_v4'   => true, // Force flush on update (v4: CPT rewrite rules timing fix).
		) ) );

		if ( get_option( $option_key ) !== $rules_hash ) {
			flush_rewrite_rules( false );
			update_option( $option_key, $rules_hash );
		}
	}

	/**
	 * Add custom query vars
	 *
	 * @param array $vars Query vars.
	 * @return array
	 */
	public function add_sitemap_query_vars( $vars ) {
		$vars[] = 'ap_sitemap';
		return $vars;
	}

	/**
	 * Render sitemap based on query var
	 */
	public function render_sitemap() {
		$sitemap_type = get_query_var( 'ap_sitemap' );

		// Fallback: detect sitemap from URL if rewrite rules didn't work.
		if ( empty( $sitemap_type ) ) {
			$sitemap_type = $this->detect_sitemap_from_url();
		}

		if ( empty( $sitemap_type ) ) {
			return;
		}

		// Check cache first.
		$cache_key = 'blaminhor_essentials_sitemap_' . $sitemap_type;
		$cached    = get_transient( $cache_key );

		if ( false !== $cached ) {
			$this->output_sitemap_xml( $cached );
			exit;
		}

		// Generate sitemap.
		if ( 'index' === $sitemap_type ) {
			$xml = $this->generate_sitemap_index();
		} elseif ( post_type_exists( $sitemap_type ) ) {
			// Post type sitemap: post-sitemap.xml, page-sitemap.xml, etc.
			$xml = $this->generate_post_sitemap( $sitemap_type );
		} elseif ( taxonomy_exists( $sitemap_type ) ) {
			// Taxonomy sitemap: category-sitemap.xml, post_tag-sitemap.xml, etc.
			$xml = $this->generate_taxonomy_sitemap( $sitemap_type );
		} else {
			return;
		}

		// Cache for 1 hour.
		set_transient( $cache_key, $xml, HOUR_IN_SECONDS );

		$this->output_sitemap_xml( $xml );
		exit;
	}

	/**
	 * Detect sitemap type from URL (fallback when rewrite rules don't work)
	 *
	 * @return string|false Sitemap type or false if not a sitemap URL.
	 */
	private function detect_sitemap_from_url() {
		$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
		$path        = wp_parse_url( $request_uri, PHP_URL_PATH );

		if ( empty( $path ) ) {
			return false;
		}

		// Handle subdirectory installations: remove home path prefix.
		$home_path = wp_parse_url( home_url(), PHP_URL_PATH );
		if ( ! empty( $home_path ) ) {
			$home_path = trailingslashit( $home_path );
			if ( strpos( $path, $home_path ) === 0 ) {
				$path = substr( $path, strlen( $home_path ) );
			}
		}

		// Remove leading slash and get filename.
		$path = ltrim( $path, '/' );

		// Main sitemap index.
		if ( 'sitemap.xml' === $path ) {
			return 'index';
		}

		// Check for type-sitemap.xml pattern.
		if ( preg_match( '/^([a-z0-9_-]+)-sitemap\.xml$/i', $path, $matches ) ) {
			$type = $matches[1];

			// Check if it's a valid post type.
			$post_types = $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) );
			if ( in_array( $type, $post_types, true ) && post_type_exists( $type ) ) {
				return $type;
			}

			// Check if it's a valid taxonomy.
			$taxonomies = $this->get_setting( 'sitemap_taxonomies', array( 'category', 'post_tag' ) );
			if ( in_array( $type, $taxonomies, true ) && taxonomy_exists( $type ) ) {
				return $type;
			}
		}

		return false;
	}

	/**
	 * Output XML with proper headers
	 *
	 * @param string $xml XML content.
	 */
	private function output_sitemap_xml( $xml ) {
		header( 'Content-Type: application/xml; charset=utf-8' );
		header( 'X-Robots-Tag: noindex, follow' );
		echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * Generate sitemap index
	 *
	 * @return string
	 */
	private function generate_sitemap_index() {
		$xml  = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
		$xml .= '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";

		// Post type sitemaps.
		$post_types = $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) );
		foreach ( $post_types as $post_type ) {
			if ( ! post_type_exists( $post_type ) ) {
				continue;
			}

			$count = wp_count_posts( $post_type );
			if ( empty( $count->publish ) ) {
				continue;
			}

			$xml .= '<sitemap>' . "\n";
			$xml .= '<loc>' . esc_url( home_url( '/' . $post_type . '-sitemap.xml' ) ) . '</loc>' . "\n";
			$xml .= '<lastmod>' . $this->get_post_type_lastmod( $post_type ) . '</lastmod>' . "\n";
			$xml .= '</sitemap>' . "\n";
		}

		// Taxonomy sitemaps.
		$taxonomies = $this->get_setting( 'sitemap_taxonomies', array( 'category', 'post_tag' ) );
		foreach ( $taxonomies as $taxonomy ) {
			if ( ! taxonomy_exists( $taxonomy ) ) {
				continue;
			}

			$count = wp_count_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => true ) );
			if ( empty( $count ) || is_wp_error( $count ) ) {
				continue;
			}

			$xml .= '<sitemap>' . "\n";
			$xml .= '<loc>' . esc_url( home_url( '/' . $taxonomy . '-sitemap.xml' ) ) . '</loc>' . "\n";
			$xml .= '</sitemap>' . "\n";
		}

		$xml .= '</sitemapindex>';

		return $xml;
	}

	/**
	 * Generate post type sitemap
	 *
	 * @param string $post_type Post type.
	 * @return string
	 */
	private function generate_post_sitemap( $post_type ) {
		$enabled_types = $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) );

		if ( ! in_array( $post_type, $enabled_types, true ) || ! post_type_exists( $post_type ) ) {
			return $this->generate_404_xml();
		}

		$include_lastmod = $this->get_setting( 'sitemap_include_lastmod', true );
		$include_images  = $this->get_setting( 'sitemap_include_images', false );
		$per_page        = $this->get_setting( 'sitemap_posts_per_file', 1000 );

		// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Required to exclude noindex posts from sitemap
		// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Required for filtering indexed posts in sitemap.
		$posts = get_posts( array(
			'post_type'      => $post_type,
			'post_status'    => 'publish',
			'posts_per_page' => $per_page,
			'orderby'        => 'modified',
			'order'          => 'DESC',
			'meta_query'     => array(
				'relation' => 'OR',
				array(
					'key'     => '_blaminhor_essentials_noindex',
					'compare' => 'NOT EXISTS',
				),
				array(
					'key'     => '_blaminhor_essentials_noindex',
					'value'   => '1',
					'compare' => '!=',
				),
			),
		) );

		$xml  = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
		$xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
		if ( $include_images ) {
			$xml .= ' xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"';
		}
		$xml .= '>' . "\n";

		foreach ( $posts as $post ) {
			$xml .= '<url>' . "\n";
			$xml .= '<loc>' . esc_url( get_permalink( $post ) ) . '</loc>' . "\n";

			if ( $include_lastmod ) {
				$lastmod_date = $this->format_lastmod_date( $post->post_modified_gmt, $post->post_modified );
				if ( $lastmod_date ) {
					$xml .= '<lastmod>' . $lastmod_date . '</lastmod>' . "\n";
				}
			}

			// Priority based on post type.
			if ( 'page' === $post_type ) {
				if ( (int) get_option( 'page_on_front' ) === $post->ID ) {
					$xml .= '<priority>1.0</priority>' . "\n";
				} else {
					$xml .= '<priority>0.8</priority>' . "\n";
				}
			} else {
				$xml .= '<priority>0.6</priority>' . "\n";
			}

			// Images.
			if ( $include_images ) {
				$images = $this->get_post_images( $post );
				foreach ( $images as $image ) {
					$xml .= '<image:image>' . "\n";
					$xml .= '<image:loc>' . esc_url( $image ) . '</image:loc>' . "\n";
					$xml .= '</image:image>' . "\n";
				}
			}

			$xml .= '</url>' . "\n";
		}

		$xml .= '</urlset>';

		return $xml;
	}

	/**
	 * Generate taxonomy sitemap
	 *
	 * @param string $taxonomy Taxonomy name.
	 * @return string
	 */
	private function generate_taxonomy_sitemap( $taxonomy ) {
		$enabled_taxonomies = $this->get_setting( 'sitemap_taxonomies', array( 'category', 'post_tag' ) );

		if ( ! in_array( $taxonomy, $enabled_taxonomies, true ) || ! taxonomy_exists( $taxonomy ) ) {
			return $this->generate_404_xml();
		}

		$terms = get_terms( array(
			'taxonomy'   => $taxonomy,
			'hide_empty' => true,
			'number'     => 1000,
		) );

		if ( is_wp_error( $terms ) ) {
			$terms = array();
		}

		$xml  = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
		$xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";

		foreach ( $terms as $term ) {
			$xml .= '<url>' . "\n";
			$xml .= '<loc>' . esc_url( get_term_link( $term ) ) . '</loc>' . "\n";
			$xml .= '<priority>0.4</priority>' . "\n";
			$xml .= '</url>' . "\n";
		}

		$xml .= '</urlset>';

		return $xml;
	}

	/**
	 * Generate 404 XML response
	 *
	 * @return string
	 */
	private function generate_404_xml() {
		return '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"></urlset>';
	}

	/**
	 * Get lastmod date for a post type
	 *
	 * @param string $post_type Post type.
	 * @return string
	 */
	private function get_post_type_lastmod( $post_type ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT post_modified_gmt, post_modified FROM {$wpdb->posts} WHERE post_type = %s AND post_status = 'publish' ORDER BY post_modified_gmt DESC LIMIT 1",
				$post_type
			)
		);

		if ( $row ) {
			$date = $this->format_lastmod_date( $row->post_modified_gmt, $row->post_modified );
			if ( $date ) {
				return $date;
			}
		}

		return gmdate( 'Y-m-d\TH:i:sP' );
	}

	/**
	 * Format a lastmod date for sitemap, with fallback for invalid GMT dates.
	 *
	 * @param string $gmt_date   GMT date (post_modified_gmt).
	 * @param string $local_date Local date fallback (post_modified).
	 * @return string|false W3C formatted date or false if both dates are invalid.
	 */
	private function format_lastmod_date( $gmt_date, $local_date = '' ) {
		if ( $gmt_date && '0000-00-00 00:00:00' !== $gmt_date ) {
			return mysql2date( 'Y-m-d\TH:i:sP', $gmt_date );
		}

		if ( $local_date && '0000-00-00 00:00:00' !== $local_date ) {
			return mysql2date( 'Y-m-d\TH:i:sP', $local_date );
		}

		return false;
	}

	/**
	 * Get images from post content
	 *
	 * @param WP_Post $post Post object.
	 * @return array
	 */
	private function get_post_images( $post ) {
		$images = array();

		// Featured image.
		if ( has_post_thumbnail( $post ) ) {
			$images[] = get_the_post_thumbnail_url( $post, 'full' );
		}

		// Images in content.
		preg_match_all( '/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $post->post_content, $matches );

		if ( ! empty( $matches[1] ) ) {
			foreach ( $matches[1] as $src ) {
				// Only include images from this site.
				if ( strpos( $src, home_url() ) === 0 || strpos( $src, '/' ) === 0 ) {
					$images[] = strpos( $src, '/' ) === 0 ? home_url( $src ) : $src;
				}
			}
		}

		return array_unique( array_slice( $images, 0, 10 ) );
	}

	/**
	 * Invalidate sitemap cache
	 */
	public function invalidate_sitemap_cache() {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			"DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_blaminhor_essentials_sitemap_%' OR option_name LIKE '_transient_timeout_blaminhor_essentials_sitemap_%'"
		);
	}

	/**
	 * Ping search engines
	 */
	public function ping_search_engines() {
		$sitemap_url = home_url( '/sitemap.xml' );

		// Google.
		wp_remote_get( 'https://www.google.com/ping?sitemap=' . rawurlencode( $sitemap_url ), array(
			'blocking' => false,
			'timeout'  => 3,
		) );
	}

	/**
	 * Process settings
	 *
	 * @param array $data Form data.
	 */
	protected function process_settings( $data ) {
		// WordPress slashes $_POST automatically. Unslash once recursively so
		// sanitize_* calls below receive clean input and apostrophes do not
		// accumulate backslashes across successive saves.
		$data = wp_unslash( $data );

		$settings = array();

		// INDEXING SETTINGS
		$settings['noindex_entire_site'] = isset( $data['noindex_entire_site'] );

		$settings['indexable_post_types'] = array();
		if ( isset( $data['indexable_post_types'] ) && is_array( $data['indexable_post_types'] ) ) {
			$settings['indexable_post_types'] = array_map( 'sanitize_key', $data['indexable_post_types'] );
		}

		$settings['indexable_taxonomies'] = array();
		if ( isset( $data['indexable_taxonomies'] ) && is_array( $data['indexable_taxonomies'] ) ) {
			$settings['indexable_taxonomies'] = array_map( 'sanitize_key', $data['indexable_taxonomies'] );
		}

		$settings['noindex_archives']        = ! isset( $data['index_archives'] );
		$settings['noindex_author_archives'] = ! isset( $data['index_author_archives'] );
		$settings['noindex_date_archives']   = ! isset( $data['index_date_archives'] );
		$settings['noindex_search']          = ! isset( $data['index_search'] );
		$settings['ai_noai']                 = isset( $data['ai_noai'] );
		$settings['ai_block_chatgpt']        = isset( $data['ai_block_chatgpt'] );
		$settings['ai_block_google_ai']      = isset( $data['ai_block_google_ai'] );
		$settings['ai_block_claude']         = isset( $data['ai_block_claude'] );
		$settings['ai_block_perplexity']     = isset( $data['ai_block_perplexity'] );
		$settings['ai_block_common_crawl']   = isset( $data['ai_block_common_crawl'] );

		// TITLE TEMPLATE SETTINGS
		$valid_separators = array( '-', '–', '—', '|', '>', '·', '~' );
		$settings['title_separator'] = isset( $data['title_separator'] ) && in_array( $data['title_separator'], $valid_separators, true )
			? $data['title_separator']
			: '-';

		$settings['title_home']           = isset( $data['title_home'] ) ? sanitize_text_field( $data['title_home'] ) : '';
		$settings['title_search']         = isset( $data['title_search'] ) ? sanitize_text_field( $data['title_search'] ) : '';
		$settings['title_404']            = isset( $data['title_404'] ) ? sanitize_text_field( $data['title_404'] ) : '';
		$settings['title_author_archive'] = isset( $data['title_author_archive'] ) ? sanitize_text_field( $data['title_author_archive'] ) : '';
		$settings['title_date_archive']   = isset( $data['title_date_archive'] ) ? sanitize_text_field( $data['title_date_archive'] ) : '';

		$settings['title_templates'] = array();
		if ( isset( $data['title_templates'] ) && is_array( $data['title_templates'] ) ) {
			foreach ( $data['title_templates'] as $key => $tpl ) {
				$clean_key = sanitize_key( $key );
				if ( ! empty( $clean_key ) ) {
					// Accept both string (new format) and array (old format) for backwards compatibility.
					if ( is_array( $tpl ) ) {
						$tpl = isset( $tpl['template'] ) ? $tpl['template'] : '';
					}
					$settings['title_templates'][ $clean_key ] = sanitize_text_field( $tpl );
				}
			}
		}

		// SOCIAL SETTINGS
		$settings['og_default_image']    = isset( $data['og_default_image'] ) ? esc_url_raw( $data['og_default_image'] ) : '';
		$settings['twitter_card_type']   = isset( $data['twitter_card_type'] ) ? sanitize_key( $data['twitter_card_type'] ) : 'summary_large_image';
		$settings['twitter_site']        = isset( $data['twitter_site'] ) ? sanitize_text_field( $data['twitter_site'] ) : '';

		// SCHEMA SETTINGS
		$settings['schema_enabled'] = isset( $data['schema_enabled'] );

		$valid_site_types = array( 'Organization', 'Person' );
		$settings['schema_site_type'] = isset( $data['schema_site_type'] ) && in_array( $data['schema_site_type'], $valid_site_types, true )
			? $data['schema_site_type']
			: 'Organization';

		$settings['schema_org_name'] = isset( $data['schema_org_name'] ) ? sanitize_text_field( $data['schema_org_name'] ) : '';
		$settings['schema_org_logo'] = isset( $data['schema_org_logo'] ) ? esc_url_raw( $data['schema_org_logo'] ) : '';
		$settings['schema_org_url']  = isset( $data['schema_org_url'] ) ? esc_url_raw( $data['schema_org_url'] ) : '';
		$settings['schema_same_as']  = isset( $data['schema_same_as'] ) ? sanitize_textarea_field( $data['schema_same_as'] ) : '';

		$settings['schema_post_types'] = array();
		if ( isset( $data['schema_post_types'] ) && is_array( $data['schema_post_types'] ) ) {
			$settings['schema_post_types'] = array_map( 'sanitize_key', $data['schema_post_types'] );
		}

		$valid_article_types = array( 'Article', 'BlogPosting', 'NewsArticle', 'TechArticle' );
		$settings['schema_default_article_type'] = isset( $data['schema_default_article_type'] ) && in_array( $data['schema_default_article_type'], $valid_article_types, true )
			? $data['schema_default_article_type']
			: 'Article';

		$valid_page_types = array( 'WebPage', 'AboutPage', 'ContactPage', 'FAQPage' );
		$settings['schema_default_page_type'] = isset( $data['schema_default_page_type'] ) && in_array( $data['schema_default_page_type'], $valid_page_types, true )
			? $data['schema_default_page_type']
			: 'WebPage';

		$settings['schema_add_website']    = isset( $data['schema_add_website'] );
		$settings['schema_add_breadcrumbs'] = isset( $data['schema_add_breadcrumbs'] );
		$settings['schema_auto_faq']       = isset( $data['schema_auto_faq'] );
		$settings['schema_auto_howto']     = isset( $data['schema_auto_howto'] );
		$settings['schema_auto_video']     = isset( $data['schema_auto_video'] );

		// AUTHOR E-E-A-T SETTINGS.
		$settings['schema_author_job_title']   = isset( $data['schema_author_job_title'] ) ? sanitize_text_field( $data['schema_author_job_title'] ) : '';
		$settings['schema_author_description'] = isset( $data['schema_author_description'] ) ? sanitize_textarea_field( $data['schema_author_description'] ) : '';
		$settings['schema_author_knows_about'] = isset( $data['schema_author_knows_about'] ) ? sanitize_textarea_field( $data['schema_author_knows_about'] ) : '';
		$settings['schema_author_alumni_of']   = isset( $data['schema_author_alumni_of'] ) ? sanitize_text_field( $data['schema_author_alumni_of'] ) : '';
		$settings['schema_author_same_as']     = isset( $data['schema_author_same_as'] ) ? sanitize_textarea_field( $data['schema_author_same_as'] ) : '';

		// TABLE OF CONTENTS SETTINGS.
		$settings['toc_enabled'] = isset( $data['toc_enabled'] );

		$valid_toc_depths = array( 'h2', 'h2-h3', 'h2-h4' );
		$settings['toc_depth'] = isset( $data['toc_depth'] ) && in_array( $data['toc_depth'], $valid_toc_depths, true )
			? $data['toc_depth']
			: 'h2-h3';

		$valid_toc_styles = array( 'numbered', 'bullets', 'plain' );
		$settings['toc_style'] = isset( $data['toc_style'] ) && in_array( $data['toc_style'], $valid_toc_styles, true )
			? $data['toc_style']
			: 'numbered';

		$settings['toc_title'] = isset( $data['toc_title'] ) ? sanitize_text_field( $data['toc_title'] ) : 'Table of Contents';

		// KEY TAKEAWAYS SETTINGS.
		$settings['takeaways_enabled'] = isset( $data['takeaways_enabled'] );

		// SITEMAP SETTINGS
		$settings['sitemap_enabled'] = isset( $data['sitemap_enabled'] );

		$settings['sitemap_post_types'] = array();
		if ( isset( $data['sitemap_post_types'] ) && is_array( $data['sitemap_post_types'] ) ) {
			$settings['sitemap_post_types'] = array_map( 'sanitize_key', $data['sitemap_post_types'] );
		}

		$settings['sitemap_taxonomies'] = array();
		if ( isset( $data['sitemap_taxonomies'] ) && is_array( $data['sitemap_taxonomies'] ) ) {
			$settings['sitemap_taxonomies'] = array_map( 'sanitize_key', $data['sitemap_taxonomies'] );
		}

		$settings['sitemap_include_lastmod'] = isset( $data['sitemap_include_lastmod'] );
		$settings['sitemap_include_images']  = isset( $data['sitemap_include_images'] );
		$settings['sitemap_posts_per_file']  = isset( $data['sitemap_posts_per_file'] ) ? absint( $data['sitemap_posts_per_file'] ) : 1000;
		$settings['sitemap_ping_engines']    = isset( $data['sitemap_ping_engines'] );

		// Check if sitemap settings changed to determine if we need to flush rules.
		$old_sitemap_enabled = $this->get_setting( 'sitemap_enabled', true );
		$old_sitemap_pts     = $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) );
		$old_sitemap_tax     = $this->get_setting( 'sitemap_taxonomies', array( 'category', 'post_tag' ) );

		$sitemap_changed = ( $old_sitemap_enabled !== $settings['sitemap_enabled'] ||
		                     $old_sitemap_pts !== $settings['sitemap_post_types'] ||
		                     $old_sitemap_tax !== $settings['sitemap_taxonomies'] );

		$this->save_settings( $settings );

		// Flush rewrite rules if sitemap settings changed.
		if ( $sitemap_changed ) {
			delete_option( 'blaminhor_essentials_sitemap_rules_flushed' );
			flush_rewrite_rules( false );
		}

		// Invalidate sitemap cache.
		$this->invalidate_sitemap_cache();

		// Set transient for success message instead of using add_action.
		set_transient( 'blaminhor_essentials_seo_settings_saved', true, 30 );

		// Redirect to avoid form resubmission and ensure proper page render.
		$redirect_url = add_query_arg(
			array(
				'page' => 'blaminhor-essentials-seo-manager',
				'settings-saved' => '1',
			),
			admin_url( 'admin.php' )
		);

		// Preserve the active tab from hidden form field.
		if ( isset( $data['active_tab'] ) ) {
			$tab = sanitize_key( $data['active_tab'] );
			$valid_tabs = array( 'overview', 'indexing', 'titles', 'social', 'schema', 'sitemap', 'import' );
			if ( in_array( $tab, $valid_tabs, true ) ) {
				$redirect_url .= '#' . $tab;
			}
		}

		// Skip the admin POST-Redirect-Get pattern when called from WP-CLI or
		// the REST API: the redirect is meaningless there and `exit;` would
		// terminate the CLI process before the caller could surface a result.
		if ( ( defined( 'WP_CLI' ) && WP_CLI ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
			return;
		}

		wp_safe_redirect( $redirect_url );
		exit;
	}

	/**
	 * Show settings saved notice after redirect
	 */
	public function show_settings_saved_notice() {
		if ( get_transient( 'blaminhor_essentials_seo_settings_saved' ) ) {
			delete_transient( 'blaminhor_essentials_seo_settings_saved' );
			blaminhor_essentials_admin_notice( __( 'Settings saved.', 'blaminhor-essentials' ), 'success' );
		}
	}

	/**
	 * Show admin notice when entire site is set to noindex
	 */
	public function show_noindex_entire_site_notice() {
		$noindex_plugin = $this->get_setting( 'noindex_entire_site', false );
		$noindex_wp     = '0' === (string) get_option( 'blog_public' );

		if ( ! $noindex_plugin && ! $noindex_wp ) {
			return;
		}

		if ( $noindex_plugin ) {
			$settings_url = admin_url( 'admin.php?page=blaminhor-essentials-seo-manager&tab=indexing' );
		} else {
			$settings_url = admin_url( 'options-reading.php' );
		}

		?>
		<div class="notice notice-warning">
			<span class="dashicons dashicons-warning"></span>
			<span>
				<?php
				echo wp_kses(
					sprintf(
						/* translators: %s: URL to SEO settings */
						__( 'Search engine indexing is currently disabled for this site. <a href="%s">Change this setting</a>', 'blaminhor-essentials' ),
						esc_url( $settings_url )
					),
					array( 'a' => array( 'href' => array() ) )
				);
				?>
			</span>
		</div>
		<?php
	}

	/**
	 * Render settings page
	 */
	public function render_settings_page() {
		$post_types  = blaminhor_essentials_get_post_types();
		$taxonomies  = blaminhor_essentials_get_taxonomies();

		// Get active tab from URL parameter.
		$valid_tabs = array( 'overview', 'indexing', 'titles', 'social', 'schema', 'sitemap', 'import' );
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab parameter is not sensitive, used for UI navigation only.
		$active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'overview';
		if ( ! in_array( $active_tab, $valid_tabs, true ) ) {
			$active_tab = 'overview';
		}
		?>
		<div class="wrap blaminhor-essentials-wrap">
			<div class="blaminhor-essentials-header">
				<h1>SEO/GEO</h1>
				<p class="blaminhor-essentials-tagline"><?php esc_html_e( 'Complete SEO toolkit: meta tags, indexing control, social sharing, and XML sitemaps.', 'blaminhor-essentials' ); ?></p>
			</div>

			<div class="blaminhor-essentials-tabs-wrapper">
				<div class="blaminhor-essentials-tabs">
					<button class="blaminhor-essentials-tab<?php echo 'overview' === $active_tab ? ' active' : ''; ?>" data-tab="overview"><?php esc_html_e( 'Content Overview', 'blaminhor-essentials' ); ?></button>
					<button class="blaminhor-essentials-tab<?php echo 'indexing' === $active_tab ? ' active' : ''; ?>" data-tab="indexing"><?php esc_html_e( 'Indexing', 'blaminhor-essentials' ); ?></button>
					<button class="blaminhor-essentials-tab<?php echo 'titles' === $active_tab ? ' active' : ''; ?>" data-tab="titles"><?php esc_html_e( 'Titles', 'blaminhor-essentials' ); ?></button>
					<button class="blaminhor-essentials-tab<?php echo 'social' === $active_tab ? ' active' : ''; ?>" data-tab="social"><?php esc_html_e( 'Social', 'blaminhor-essentials' ); ?></button>
					<button class="blaminhor-essentials-tab<?php echo 'schema' === $active_tab ? ' active' : ''; ?>" data-tab="schema"><?php esc_html_e( 'Schema', 'blaminhor-essentials' ); ?></button>
					<button class="blaminhor-essentials-tab<?php echo 'sitemap' === $active_tab ? ' active' : ''; ?>" data-tab="sitemap"><?php esc_html_e( 'Sitemap', 'blaminhor-essentials' ); ?></button>
					<button class="blaminhor-essentials-tab<?php echo 'import' === $active_tab ? ' active' : ''; ?>" data-tab="import"><?php esc_html_e( 'Import', 'blaminhor-essentials' ); ?></button>
				</div>

				<!-- Content Overview Tab -->
				<?php $indexable_post_types = $this->get_setting( 'indexable_post_types', array( 'post', 'page' ) ); ?>
				<div class="blaminhor-essentials-tab-content<?php echo 'overview' === $active_tab ? ' active' : ''; ?>" data-tab-content="overview">
					<h3><?php esc_html_e( 'SEO Content Overview', 'blaminhor-essentials' ); ?></h3>
					<details class="ap-tab-info">
						<summary><span class="dashicons dashicons-arrow-right-alt2"></span><?php esc_html_e( 'Learn more', 'blaminhor-essentials' ); ?></summary>
						<div class="ap-tab-info-content">
							<p><?php esc_html_e( 'Manage SEO meta tags for all your content from one place.', 'blaminhor-essentials' ); ?></p>
						</div>
					</details>

					<div style="margin: 20px 0;">
						<label for="ap-seo-post-type-filter" style="margin-right: 10px;"><?php esc_html_e( 'Content Type:', 'blaminhor-essentials' ); ?></label>
						<select id="ap-seo-post-type-filter">
							<?php foreach ( $post_types as $pt ) : ?>
								<option value="<?php echo esc_attr( $pt->name ); ?>" data-indexable="<?php echo in_array( $pt->name, $indexable_post_types, true ) ? '1' : '0'; ?>"<?php selected( $pt->name, 'page' ); ?>><?php echo esc_html( $pt->labels->name ); ?></option>
							<?php endforeach; ?>
						</select>
						<button type="button" class="button" id="ap-seo-refresh"><?php esc_html_e( 'Refresh', 'blaminhor-essentials' ); ?></button>
					</div>

					<div id="ap-seo-noindex-warning" class="blaminhor-essentials-notice warning ap-hidden ap-mb-lg">
						<span class="dashicons dashicons-warning" style="color: #dba617; margin-right: 8px;"></span>
						<?php esc_html_e( 'This content type is not set to be indexed. SEO settings will have no effect. You can change this in the Indexing tab.', 'blaminhor-essentials' ); ?>
					</div>

					<table class="widefat striped" id="ap-seo-content-table">
						<thead>
							<tr>
								<th style="width: 22%;"><?php esc_html_e( 'Title', 'blaminhor-essentials' ); ?></th>
								<th style="width: 18%;"><?php esc_html_e( 'SEO Title', 'blaminhor-essentials' ); ?></th>
								<th style="width: 25%;"><?php esc_html_e( 'Meta Description', 'blaminhor-essentials' ); ?></th>
								<th style="width: 18%;"><?php esc_html_e( 'Focus Keywords', 'blaminhor-essentials' ); ?></th>
								<th style="width: 8%;"><?php esc_html_e( 'Index', 'blaminhor-essentials' ); ?></th>
								<th style="width: 9%;"><?php esc_html_e( 'Actions', 'blaminhor-essentials' ); ?></th>
							</tr>
						</thead>
						<tbody id="ap-seo-content-tbody">
							<tr><td colspan="6" class="ap-table-empty"><?php esc_html_e( 'Loading...', 'blaminhor-essentials' ); ?></td></tr>
						</tbody>
					</table>

					<div id="ap-seo-pagination" class="ap-pagination"></div>

					<!-- Edit Modal -->
					<div id="ap-seo-edit-modal" class="ap-db-modal ap-hidden">
						<div class="ap-db-modal-overlay"></div>
						<div class="ap-db-modal-content">
							<h3><?php esc_html_e( 'Edit SEO Meta', 'blaminhor-essentials' ); ?></h3>
							<input type="hidden" id="ap-seo-edit-post-id">

							<div class="ap-seo-field">
								<label class="ap-label-between">
									<?php esc_html_e( 'SEO Title', 'blaminhor-essentials' ); ?>
									<span id="ap-seo-edit-title-count" class="ap-seo-char-count">0/60</span>
								</label>
								<input type="text" id="ap-seo-edit-title" style="width: 100%;" placeholder="<?php esc_attr_e( 'Leave empty to use the post title', 'blaminhor-essentials' ); ?>">
								<div class="ap-char-progress">
									<div id="ap-seo-edit-title-bar" class="ap-char-progress-fill"></div>
								</div>
								<div class="seo-title-variables" data-target="#ap-seo-edit-title">
									<button type="button" class="seo-var-btn" data-var="{title}"><?php esc_html_e( 'Title', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{sep}"><?php esc_html_e( 'Separator', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{site_name}"><?php esc_html_e( 'Site Name', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{author}"><?php esc_html_e( 'Author', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{category}"><?php esc_html_e( 'Category', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{post_type}"><?php esc_html_e( 'Post Type', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{year}"><?php esc_html_e( 'Year', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{page_number}"><?php esc_html_e( 'Page Number', 'blaminhor-essentials' ); ?></button>
								</div>
							</div>

							<div class="ap-seo-field">
								<label class="ap-label-between">
									<?php esc_html_e( 'Meta Description', 'blaminhor-essentials' ); ?>
									<span id="ap-seo-edit-desc-count" class="ap-seo-char-count">0/160</span>
								</label>
								<textarea id="ap-seo-edit-desc" style="width: 100%; min-height: 80px;" placeholder="<?php esc_attr_e( 'Enter a description for search engines...', 'blaminhor-essentials' ); ?>"></textarea>
								<div class="ap-char-progress">
									<div id="ap-seo-edit-desc-bar" class="ap-char-progress-fill"></div>
								</div>
								<div class="seo-title-variables" data-target="#ap-seo-edit-desc">
									<button type="button" class="seo-var-btn" data-var="{title}"><?php esc_html_e( 'Title', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{sep}"><?php esc_html_e( 'Separator', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{site_name}"><?php esc_html_e( 'Site Name', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{author}"><?php esc_html_e( 'Author', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{category}"><?php esc_html_e( 'Category', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{post_type}"><?php esc_html_e( 'Post Type', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{year}"><?php esc_html_e( 'Year', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{page_number}"><?php esc_html_e( 'Page Number', 'blaminhor-essentials' ); ?></button>
								</div>
							</div>

							<!-- SERP Preview -->
							<div class="ap-seo-preview" style="margin-top: 0; margin-bottom: 25px;">
								<div class="ap-seo-preview-header">
									<strong><?php esc_html_e( 'SERP Preview', 'blaminhor-essentials' ); ?></strong>
								</div>
								<div class="ap-serp-previews-container" style="flex-direction: column;">
									<!-- Mobile Preview -->
									<div class="ap-serp-preview-col">
										<div class="ap-serp-device-label">
											<span class="dashicons dashicons-smartphone ap-icon-16"></span>
											<?php esc_html_e( 'Mobile', 'blaminhor-essentials' ); ?>
										</div>
										<?php $modal_site_icon_url = get_site_icon_url( 32 ); ?>
										<div class="ap-serp-preview mobile">
											<div class="ap-serp-url">
												<div class="ap-serp-url-icon">
													<?php if ( $modal_site_icon_url ) : ?>
														<img src="<?php echo esc_url( $modal_site_icon_url ); ?>" alt="" width="18" height="18">
													<?php else : ?>
														<span class="dashicons dashicons-admin-site ap-icon-16" class="ap-text-gray"></span>
													<?php endif; ?>
												</div>
												<div class="ap-serp-url-info">
													<div class="ap-serp-site-name"><?php echo esc_html( get_bloginfo( 'name' ) ); ?></div>
													<div class="ap-serp-url-text ap-seo-edit-url-sync-domain"></div>
												</div>
											</div>
											<div class="ap-serp-title ap-seo-edit-title-sync-mobile"></div>
											<div class="ap-serp-body">
												<div class="ap-serp-desc ap-seo-edit-desc-sync-mobile"><span class="ap-serp-date ap-seo-edit-date"></span><span class="ap-serp-desc-text"></span></div>
												<img class="ap-serp-thumbnail ap-seo-edit-thumbnail ap-hidden" src="" alt="">
											</div>
										</div>
									</div>
									<!-- Desktop Preview -->
									<div class="ap-serp-preview-col">
										<div class="ap-serp-device-label">
											<span class="dashicons dashicons-desktop ap-icon-16"></span>
											<?php esc_html_e( 'Desktop', 'blaminhor-essentials' ); ?>
										</div>
										<div class="ap-serp-preview desktop">
											<div class="ap-serp-url">
												<div class="ap-serp-url-icon">
													<?php if ( $modal_site_icon_url ) : ?>
														<img src="<?php echo esc_url( $modal_site_icon_url ); ?>" alt="" width="18" height="18">
													<?php else : ?>
														<span class="dashicons dashicons-admin-site ap-icon-16" class="ap-text-gray"></span>
													<?php endif; ?>
												</div>
												<div class="ap-serp-url-info">
													<div class="ap-serp-site-name"><?php echo esc_html( get_bloginfo( 'name' ) ); ?></div>
													<div class="ap-serp-url-text ap-seo-edit-url-sync"></div>
												</div>
											</div>
											<div class="ap-serp-title ap-seo-edit-title-sync-desktop"></div>
											<div class="ap-serp-desc ap-seo-edit-desc-sync-desktop"><span class="ap-serp-date ap-seo-edit-date"></span><span class="ap-serp-desc-text"></span></div>
										</div>
									</div>
								</div>
							</div>

							<div class="ap-seo-field">
								<label class="ap-label-block" style="font-weight: 500;">
									<?php esc_html_e( 'Focus Keywords', 'blaminhor-essentials' ); ?>
								</label>
								<div class="ap-seo-keywords-container" id="ap-seo-edit-keywords-container">
									<div class="ap-seo-keywords-tags" id="ap-seo-edit-keywords-tags"></div>
									<input type="text" id="ap-seo-edit-keywords-input" class="ap-seo-keywords-input" placeholder="<?php esc_attr_e( 'Type and press Enter to add...', 'blaminhor-essentials' ); ?>">
								</div>
								<input type="hidden" id="ap-seo-edit-keywords">
								<p class="description"><?php esc_html_e( 'Press Enter or comma to add a keyword. Click × to remove.', 'blaminhor-essentials' ); ?></p>
							</div>

							<!-- Schema Section -->
							<div class="ap-seo-meta-section">
								<h4 class="ap-seo-meta-section-title"><span class="dashicons dashicons-editor-code"></span> <?php esc_html_e( 'Schema', 'blaminhor-essentials' ); ?></h4>
								<p class="description ap-mb-md"><?php esc_html_e( 'Structured data (JSON-LD) helps search engines and AI understand your content. Override the default schema type or add custom markup for rich results.', 'blaminhor-essentials' ); ?></p>

								<div class="ap-seo-field">
									<label for="ap-seo-edit-schema-type"><?php esc_html_e( 'Schema Type', 'blaminhor-essentials' ); ?></label>
									<select id="ap-seo-edit-schema-type" style="width: 100%;">
										<option value=""><?php esc_html_e( 'Default (use global setting)', 'blaminhor-essentials' ); ?></option>
										<option value="Article">Article</option>
										<option value="BlogPosting">BlogPosting</option>
										<option value="NewsArticle">NewsArticle</option>
										<option value="TechArticle">TechArticle</option>
										<option value="WebPage">WebPage</option>
										<option value="AboutPage">AboutPage</option>
										<option value="ContactPage">ContactPage</option>
										<option value="FAQPage">FAQPage</option>
										<option value="None"><?php esc_html_e( 'None (disable structured data)', 'blaminhor-essentials' ); ?></option>
									</select>
								</div>

								<div class="ap-seo-field">
									<label for="ap-seo-edit-schema-desc"><?php esc_html_e( 'Schema Description', 'blaminhor-essentials' ); ?></label>
									<textarea id="ap-seo-edit-schema-desc" rows="3" style="width: 100%;" placeholder="<?php esc_attr_e( 'Describe this page for search engines and AI...', 'blaminhor-essentials' ); ?>"></textarea>
									<p class="description"><?php esc_html_e( 'Custom description used in structured data (JSON-LD). If empty, falls back to your SEO meta description, then to an automatic excerpt.', 'blaminhor-essentials' ); ?></p>
								</div>

								<div class="ap-seo-field">
									<label for="ap-seo-edit-schema-json"><?php esc_html_e( 'Custom JSON-LD', 'blaminhor-essentials' ); ?></label>
									<textarea id="ap-seo-edit-schema-json" class="ap-schema-json-textarea" style="width: 100%;" placeholder='{"@context":"https://schema.org","@type":"Product","name":"..."}'></textarea>
									<p class="description"><?php esc_html_e( 'Advanced: add custom JSON-LD markup. Must be valid JSON. Use this for schema types not covered above (Product, Event, Recipe, etc.).', 'blaminhor-essentials' ); ?></p>
								</div>
							</div>

							<!-- Social Section -->
							<div class="ap-seo-meta-section">
								<h4 class="ap-seo-meta-section-title"><span class="dashicons dashicons-share"></span> <?php esc_html_e( 'Social', 'blaminhor-essentials' ); ?></h4>
								<p class="description ap-mb-md"><?php esc_html_e( 'Customize how this page looks when shared on social media. Leave fields empty to use the SEO title, description, and featured image automatically.', 'blaminhor-essentials' ); ?></p>

								<div class="ap-seo-field">
									<label for="ap-seo-edit-og-title"><?php esc_html_e( 'OG Title', 'blaminhor-essentials' ); ?></label>
									<input type="text" id="ap-seo-edit-og-title" style="width: 100%;" placeholder="<?php esc_attr_e( 'Leave empty to use SEO title', 'blaminhor-essentials' ); ?>">
								</div>

								<div class="ap-seo-field">
									<label for="ap-seo-edit-og-desc"><?php esc_html_e( 'OG Description', 'blaminhor-essentials' ); ?></label>
									<textarea id="ap-seo-edit-og-desc" style="width: 100%; min-height: 60px;" placeholder="<?php esc_attr_e( 'Leave empty to use meta description', 'blaminhor-essentials' ); ?>"></textarea>
								</div>

								<div class="ap-seo-field">
									<label for="ap-seo-edit-og-image"><?php esc_html_e( 'OG Image URL', 'blaminhor-essentials' ); ?></label>
									<input type="text" id="ap-seo-edit-og-image" style="width: 100%;" placeholder="<?php esc_attr_e( 'Leave empty to use featured image', 'blaminhor-essentials' ); ?>">
									<p class="description"><?php esc_html_e( 'Recommended: 1200x630 pixels.', 'blaminhor-essentials' ); ?></p>
								</div>

								<div class="ap-seo-field">
									<label for="ap-seo-edit-twitter-title"><?php esc_html_e( 'Twitter Title', 'blaminhor-essentials' ); ?></label>
									<input type="text" id="ap-seo-edit-twitter-title" style="width: 100%;" placeholder="<?php esc_attr_e( 'Leave empty to use OG title', 'blaminhor-essentials' ); ?>">
								</div>

								<div class="ap-seo-field">
									<label for="ap-seo-edit-twitter-desc"><?php esc_html_e( 'Twitter Description', 'blaminhor-essentials' ); ?></label>
									<textarea id="ap-seo-edit-twitter-desc" style="width: 100%; min-height: 60px;" placeholder="<?php esc_attr_e( 'Leave empty to use OG description', 'blaminhor-essentials' ); ?>"></textarea>
								</div>
							</div>

							<!-- Advanced Section -->
							<div class="ap-seo-meta-section">
								<h4 class="ap-seo-meta-section-title"><span class="dashicons dashicons-admin-generic"></span> <?php esc_html_e( 'Advanced', 'blaminhor-essentials' ); ?></h4>
								<p class="description ap-mb-md"><?php esc_html_e( 'Fine-tune how search engines and AI crawlers handle this specific page. These settings override the global defaults.', 'blaminhor-essentials' ); ?></p>

								<div class="ap-seo-checkbox">
									<label class="ap-label-flex">
										<input type="checkbox" id="ap-seo-edit-noindex">
										<?php esc_html_e( 'Do not index this page (noindex)', 'blaminhor-essentials' ); ?>
									</label>
								</div>

								<div class="ap-seo-checkbox">
									<label class="ap-label-flex">
										<input type="checkbox" id="ap-seo-edit-nofollow">
										<?php esc_html_e( 'Do not follow links on this page (nofollow)', 'blaminhor-essentials' ); ?>
									</label>
								</div>

								<div class="ap-seo-checkbox">
									<label class="ap-label-flex">
										<input type="checkbox" id="ap-seo-edit-noai">
										<?php esc_html_e( 'Block AI training on this content (noai, noimageai)', 'blaminhor-essentials' ); ?>
									</label>
								</div>

								<div class="ap-seo-field ap-mt-md">
									<label for="ap-seo-edit-canonical"><strong><?php esc_html_e( 'Canonical URL', 'blaminhor-essentials' ); ?></strong></label>
									<input type="url" id="ap-seo-edit-canonical" style="width: 100%;" placeholder="<?php esc_attr_e( 'Leave empty to use the default permalink.', 'blaminhor-essentials' ); ?>">
								</div>
							</div>

							<div class="ap-db-modal-footer">
								<button type="button" class="button" id="ap-seo-edit-cancel"><?php esc_html_e( 'Cancel', 'blaminhor-essentials' ); ?></button>
								<button type="button" class="button button-primary" id="ap-seo-edit-save"><?php esc_html_e( 'Save', 'blaminhor-essentials' ); ?></button>
							</div>
						</div>
					</div>
				</div>

				<!-- Settings Form (single form for all settings tabs) -->
				<form method="post" id="ap-seo-settings-form">
					<?php $this->render_nonce_field(); ?>
					<input type="hidden" name="active_tab" id="ap-seo-active-tab" value="">

				<!-- Indexing Tab -->
				<div class="blaminhor-essentials-tab-content<?php echo 'indexing' === $active_tab ? ' active' : ''; ?>" data-tab-content="indexing">
						<h3><?php esc_html_e( 'Site Indexing', 'blaminhor-essentials' ); ?></h3>
						<details class="ap-tab-info">
							<summary><span class="dashicons dashicons-arrow-right-alt2"></span><?php esc_html_e( 'Learn more', 'blaminhor-essentials' ); ?></summary>
							<div class="ap-tab-info-content">
								<p><?php esc_html_e( 'Control which parts of your site are visible to search engines. Choose which content types and archive pages should be indexed, and manage how AI crawlers interact with your site.', 'blaminhor-essentials' ); ?></p>
							</div>
						</details>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Disable Indexing', 'blaminhor-essentials' ); ?></th>
								<td>
									<label>
										<input type="checkbox" name="noindex_entire_site" <?php checked( $this->get_setting( 'noindex_entire_site', false ) ); ?>>
										<?php esc_html_e( 'Do not index this site on search engines for now', 'blaminhor-essentials' ); ?>
									</label>
									<p class="description"><?php esc_html_e( 'When enabled, a noindex meta tag will be added to all pages. A notice will appear in the admin area as a reminder.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Indexable Content Types', 'blaminhor-essentials' ); ?></h3>
						<p class="description"><?php esc_html_e( 'Select which content types should be indexed by search engines.', 'blaminhor-essentials' ); ?></p>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Post Types', 'blaminhor-essentials' ); ?></th>
								<td>
									<fieldset>
										<?php
										$indexable_pts = $this->get_setting( 'indexable_post_types', array( 'post', 'page' ) );
										foreach ( $post_types as $pt ) :
										?>
											<label class="ap-label-block">
												<input type="checkbox" name="indexable_post_types[]" value="<?php echo esc_attr( $pt->name ); ?>"
													<?php checked( in_array( $pt->name, $indexable_pts, true ) ); ?>>
												<?php echo esc_html( $pt->labels->singular_name ); ?>
											</label>
										<?php endforeach; ?>
									</fieldset>
								</td>
							</tr>
							<tr>
								<th scope="row"><?php esc_html_e( 'Taxonomies', 'blaminhor-essentials' ); ?></th>
								<td>
									<fieldset>
										<?php
										$indexable_tax = $this->get_setting( 'indexable_taxonomies', array( 'category', 'post_tag' ) );
										foreach ( $taxonomies as $tax ) :
										?>
											<label class="ap-label-block">
												<input type="checkbox" name="indexable_taxonomies[]" value="<?php echo esc_attr( $tax->name ); ?>"
													<?php checked( in_array( $tax->name, $indexable_tax, true ) ); ?>>
												<?php echo esc_html( $tax->labels->singular_name ); ?>
											</label>
										<?php endforeach; ?>
									</fieldset>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Archive Pages', 'blaminhor-essentials' ); ?></h3>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Index Archives', 'blaminhor-essentials' ); ?></th>
								<td>
									<label class="ap-label-block">
										<input type="checkbox" name="index_archives" <?php checked( ! $this->get_setting( 'noindex_archives', false ) ); ?>>
										<?php esc_html_e( 'Category and tag archives', 'blaminhor-essentials' ); ?>
									</label>
									<label class="ap-label-block">
										<input type="checkbox" name="index_author_archives" <?php checked( ! $this->get_setting( 'noindex_author_archives', true ) ); ?>>
										<?php esc_html_e( 'Author archives', 'blaminhor-essentials' ); ?>
									</label>
									<label class="ap-label-block">
										<input type="checkbox" name="index_date_archives" <?php checked( ! $this->get_setting( 'noindex_date_archives', true ) ); ?>>
										<?php esc_html_e( 'Date archives', 'blaminhor-essentials' ); ?>
									</label>
									<label class="ap-label-block">
										<input type="checkbox" name="index_search" <?php checked( ! $this->get_setting( 'noindex_search', true ) ); ?>>
										<?php esc_html_e( 'Search results pages', 'blaminhor-essentials' ); ?>
									</label>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'AI Crawlers', 'blaminhor-essentials' ); ?></h3>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Meta Tags', 'blaminhor-essentials' ); ?></th>
								<td>
									<label>
										<input type="checkbox" name="ai_noai" <?php checked( $this->get_setting( 'ai_noai', false ) ); ?>>
										<?php esc_html_e( 'Add noai and noimageai meta tags. Requests AI engines not to use your content for training.', 'blaminhor-essentials' ); ?>
									</label>
								</td>
							</tr>
							<tr>
								<th scope="row"><?php esc_html_e( 'Block via robots.txt', 'blaminhor-essentials' ); ?></th>
								<td>
									<fieldset>
										<label class="ap-label-block">
											<input type="checkbox" name="ai_block_chatgpt" <?php checked( $this->get_setting( 'ai_block_chatgpt', false ) ); ?>>
											<?php esc_html_e( 'Block GPTBot (OpenAI/ChatGPT) from crawling your site via robots.txt.', 'blaminhor-essentials' ); ?>
										</label>
										<label class="ap-label-block">
											<input type="checkbox" name="ai_block_google_ai" <?php checked( $this->get_setting( 'ai_block_google_ai', false ) ); ?>>
											<?php esc_html_e( 'Block Google-Extended (Gemini AI) from crawling your site.', 'blaminhor-essentials' ); ?>
										</label>
										<label class="ap-label-block">
											<input type="checkbox" name="ai_block_claude" <?php checked( $this->get_setting( 'ai_block_claude', false ) ); ?>>
											<?php esc_html_e( 'Block ClaudeBot (Anthropic) from crawling your site.', 'blaminhor-essentials' ); ?>
										</label>
										<label class="ap-label-block">
											<input type="checkbox" name="ai_block_perplexity" <?php checked( $this->get_setting( 'ai_block_perplexity', false ) ); ?>>
											<?php esc_html_e( 'Block PerplexityBot from crawling your site.', 'blaminhor-essentials' ); ?>
										</label>
										<label class="ap-label-block">
											<input type="checkbox" name="ai_block_common_crawl" <?php checked( $this->get_setting( 'ai_block_common_crawl', false ) ); ?>>
											<?php esc_html_e( 'Block CCBot (Common Crawl). Common Crawl data is used by many AI training datasets.', 'blaminhor-essentials' ); ?>
										</label>
									</fieldset>
									<p class="description ap-mt-sm"><?php esc_html_e( 'Blocking AI crawlers prevents them from accessing your content. This is different from the noai meta tag above, which only requests they don\'t use it for training. Both can be used together.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
						</table>

				</div>

				<!-- Titles Tab -->
				<div class="blaminhor-essentials-tab-content<?php echo 'titles' === $active_tab ? ' active' : ''; ?>" data-tab-content="titles">
						<h3><?php esc_html_e( 'Title Templates', 'blaminhor-essentials' ); ?></h3>
						<details class="ap-tab-info">
							<summary><span class="dashicons dashicons-arrow-right-alt2"></span><?php esc_html_e( 'Learn more', 'blaminhor-essentials' ); ?></summary>
							<div class="ap-tab-info-content">
								<p><?php esc_html_e( 'These templates define default title tags for your content. They are automatically applied unless a custom SEO title is set on a specific post or page.', 'blaminhor-essentials' ); ?></p>
							</div>
						</details>

						<hr />

						<h3><?php esc_html_e( 'Title Separator', 'blaminhor-essentials' ); ?></h3>

						<table class="form-table">
							<tr>
								<th scope="row">
									<label for="title_separator"><?php esc_html_e( 'Separator', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<?php $current_sep = $this->get_setting( 'title_separator', '-' ); ?>
									<select name="title_separator" id="title_separator">
										<option value="-" <?php selected( $current_sep, '-' ); ?>>-</option>
										<option value="–" <?php selected( $current_sep, '–' ); ?>>–</option>
										<option value="—" <?php selected( $current_sep, '—' ); ?>>—</option>
										<option value="|" <?php selected( $current_sep, '|' ); ?>>|</option>
										<option value=">" <?php selected( $current_sep, '>' ); ?>>></option>
										<option value="·" <?php selected( $current_sep, '·' ); ?>>·</option>
										<option value="~" <?php selected( $current_sep, '~' ); ?>>~</option>
									</select>
									<p class="description"><?php esc_html_e( 'Character used as {sep} in title templates.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Post Types', 'blaminhor-essentials' ); ?></h3>
						<p class="description"><?php esc_html_e( 'Configure title templates for each post type. The template is used when no manual SEO title is set.', 'blaminhor-essentials' ); ?></p>

						<?php
						$title_templates = $this->get_setting( 'title_templates', array() );
						foreach ( $post_types as $pt ) :
							$pt_raw = isset( $title_templates[ $pt->name ] ) ? $title_templates[ $pt->name ] : '';
							$pt_template = is_array( $pt_raw ) ? ( isset( $pt_raw['template'] ) ? $pt_raw['template'] : '' ) : $pt_raw;
							$pt_placeholder = '{title} {sep} {site_name}';
							$pt_preview = ! empty( $pt_template ) ? $pt_template : $pt_placeholder;
						?>
						<div class="seo-title-accordion" data-preview-context="post_type">
							<div class="seo-title-accordion-header" data-accordion-toggle>
								<span class="dashicons dashicons-arrow-right-alt2"></span>
								<strong><?php echo esc_html( $pt->labels->name ); ?></strong>
								<code><?php echo esc_html( $pt->name ); ?></code>
								<span class="seo-title-accordion-preview"><?php echo esc_html( $pt_preview ); ?></span>
							</div>
							<div class="seo-title-accordion-body">
								<input type="text" name="title_templates[<?php echo esc_attr( $pt->name ); ?>]" class="large-text seo-title-input"
									value="<?php echo esc_attr( $pt_template ); ?>"
									placeholder="<?php echo esc_attr( $pt_placeholder ); ?>">
								<div class="seo-title-variables">
									<button type="button" class="seo-var-btn" data-var="{title}"><?php esc_html_e( 'Title', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{sep}"><?php esc_html_e( 'Separator', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{site_name}"><?php esc_html_e( 'Site Name', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{author}"><?php esc_html_e( 'Author', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{category}"><?php esc_html_e( 'Category', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{post_type}"><?php esc_html_e( 'Post Type', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{year}"><?php esc_html_e( 'Year', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{page_number}"><?php esc_html_e( 'Page Number', 'blaminhor-essentials' ); ?></button>
								</div>
								<div class="seo-title-preview"><?php esc_html_e( 'Preview:', 'blaminhor-essentials' ); ?> <span></span></div>
							</div>
						</div>
						<?php endforeach; ?>

						<hr>

						<h3><?php esc_html_e( 'Taxonomies', 'blaminhor-essentials' ); ?></h3>
						<p class="description"><?php esc_html_e( 'Configure title templates for taxonomy archive pages.', 'blaminhor-essentials' ); ?></p>

						<?php
						foreach ( $taxonomies as $tax ) :
							$tax_raw = isset( $title_templates[ $tax->name ] ) ? $title_templates[ $tax->name ] : '';
							$tax_template = is_array( $tax_raw ) ? ( isset( $tax_raw['template'] ) ? $tax_raw['template'] : '' ) : $tax_raw;
							$tax_placeholder = '{term_name} {sep} {site_name}';
							$tax_preview = ! empty( $tax_template ) ? $tax_template : $tax_placeholder;
						?>
						<div class="seo-title-accordion" data-preview-context="taxonomy">
							<div class="seo-title-accordion-header" data-accordion-toggle>
								<span class="dashicons dashicons-arrow-right-alt2"></span>
								<strong><?php echo esc_html( $tax->labels->name ); ?></strong>
								<code><?php echo esc_html( $tax->name ); ?></code>
								<span class="seo-title-accordion-preview"><?php echo esc_html( $tax_preview ); ?></span>
							</div>
							<div class="seo-title-accordion-body">
								<input type="text" name="title_templates[<?php echo esc_attr( $tax->name ); ?>]" class="large-text seo-title-input"
									value="<?php echo esc_attr( $tax_template ); ?>"
									placeholder="<?php echo esc_attr( $tax_placeholder ); ?>">
								<div class="seo-title-variables">
									<button type="button" class="seo-var-btn" data-var="{term_name}"><?php esc_html_e( 'Term Name', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{sep}"><?php esc_html_e( 'Separator', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{site_name}"><?php esc_html_e( 'Site Name', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{year}"><?php esc_html_e( 'Year', 'blaminhor-essentials' ); ?></button>
									<button type="button" class="seo-var-btn" data-var="{page_number}"><?php esc_html_e( 'Page Number', 'blaminhor-essentials' ); ?></button>
								</div>
								<div class="seo-title-preview"><?php esc_html_e( 'Preview:', 'blaminhor-essentials' ); ?> <span></span></div>
							</div>
						</div>
						<?php endforeach; ?>

						<hr>

						<h3><?php esc_html_e( 'Special Pages', 'blaminhor-essentials' ); ?></h3>

						<?php
						$special_pages = array(
							array(
								'key'         => 'title_search',
								'label'       => __( 'Search Results', 'blaminhor-essentials' ),
								'default'     => '{search_query} {sep} {site_name}',
								'vars'        => array(
									'{search_query}' => __( 'Search Query', 'blaminhor-essentials' ),
									'{sep}'          => __( 'Separator', 'blaminhor-essentials' ),
									'{site_name}'    => __( 'Site Name', 'blaminhor-essentials' ),
								),
								'preview_ctx' => 'search',
							),
							array(
								'key'         => 'title_404',
								'label'       => __( '404 Page', 'blaminhor-essentials' ),
								'default'     => '{text_404} {sep} {site_name}',
								'vars'        => array(
									'{text_404}'  => __( '404 Text', 'blaminhor-essentials' ),
									'{sep}'       => __( 'Separator', 'blaminhor-essentials' ),
									'{site_name}' => __( 'Site Name', 'blaminhor-essentials' ),
								),
								'preview_ctx' => '404',
							),
							array(
								'key'         => 'title_author_archive',
								'label'       => __( 'Author Archives', 'blaminhor-essentials' ),
								'default'     => '{author} {sep} {site_name}',
								'vars'        => array(
									'{author}'    => __( 'Author', 'blaminhor-essentials' ),
									'{sep}'       => __( 'Separator', 'blaminhor-essentials' ),
									'{site_name}' => __( 'Site Name', 'blaminhor-essentials' ),
								),
								'preview_ctx' => 'author',
							),
							array(
								'key'         => 'title_date_archive',
								'label'       => __( 'Date Archives', 'blaminhor-essentials' ),
								'default'     => '{date} {sep} {site_name}',
								'vars'        => array(
									'{date}'      => __( 'Date', 'blaminhor-essentials' ),
									'{year}'      => __( 'Year', 'blaminhor-essentials' ),
									'{month}'     => __( 'Month', 'blaminhor-essentials' ),
									'{day}'       => __( 'Day', 'blaminhor-essentials' ),
									'{sep}'       => __( 'Separator', 'blaminhor-essentials' ),
									'{site_name}' => __( 'Site Name', 'blaminhor-essentials' ),
								),
								'preview_ctx' => 'date',
							),
						);

						foreach ( $special_pages as $sp ) :
							$sp_value = $this->get_setting( $sp['key'], $sp['default'] );
						?>
						<div class="seo-title-field" data-preview-context="<?php echo esc_attr( $sp['preview_ctx'] ); ?>">
							<label for="<?php echo esc_attr( $sp['key'] ); ?>"><strong><?php echo esc_html( $sp['label'] ); ?></strong></label>
							<input type="text" name="<?php echo esc_attr( $sp['key'] ); ?>" id="<?php echo esc_attr( $sp['key'] ); ?>" class="large-text seo-title-input"
								value="<?php echo esc_attr( $sp_value ); ?>"
								placeholder="<?php echo esc_attr( $sp['default'] ); ?>">
							<div class="seo-title-variables">
								<?php foreach ( $sp['vars'] as $var => $label ) : ?>
									<button type="button" class="seo-var-btn" data-var="<?php echo esc_attr( $var ); ?>"><?php echo esc_html( $label ); ?></button>
								<?php endforeach; ?>
							</div>
							<div class="seo-title-preview"><?php esc_html_e( 'Preview:', 'blaminhor-essentials' ); ?> <span></span></div>
						</div>
						<?php endforeach; ?>

				</div>

				<!-- Social Tab -->
				<div class="blaminhor-essentials-tab-content<?php echo 'social' === $active_tab ? ' active' : ''; ?>" data-tab-content="social">
						<h3><?php esc_html_e( 'Open Graph Defaults', 'blaminhor-essentials' ); ?></h3>
						<details class="ap-tab-info">
							<summary><span class="dashicons dashicons-arrow-right-alt2"></span><?php esc_html_e( 'Learn more', 'blaminhor-essentials' ); ?></summary>
							<div class="ap-tab-info-content">
								<p><?php esc_html_e( 'Configure how your content appears when shared on social media (Facebook, Twitter/X, LinkedIn). These global defaults apply to all pages unless overridden individually.', 'blaminhor-essentials' ); ?></p>
							</div>
						</details>

						<table class="form-table">
							<tr>
								<th scope="row">
									<label for="og_default_image"><?php esc_html_e( 'Default OG Image', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<input type="url" name="og_default_image" id="og_default_image" class="large-text"
										value="<?php echo esc_url( $this->get_setting( 'og_default_image', '' ) ); ?>"
										placeholder="https://example.com/image.jpg">
									<p class="description"><?php esc_html_e( 'Default image used when a post has no featured image. Recommended: 1200x630 pixels.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Twitter Card Settings', 'blaminhor-essentials' ); ?></h3>

						<table class="form-table">
							<tr>
								<th scope="row">
									<label for="twitter_card_type"><?php esc_html_e( 'Card Type', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<select name="twitter_card_type" id="twitter_card_type">
										<option value="summary" <?php selected( $this->get_setting( 'twitter_card_type', 'summary_large_image' ), 'summary' ); ?>><?php esc_html_e( 'Summary', 'blaminhor-essentials' ); ?></option>
										<option value="summary_large_image" <?php selected( $this->get_setting( 'twitter_card_type', 'summary_large_image' ), 'summary_large_image' ); ?>><?php esc_html_e( 'Summary with Large Image', 'blaminhor-essentials' ); ?></option>
									</select>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="twitter_site"><?php esc_html_e( 'Twitter Username', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<input type="text" name="twitter_site" id="twitter_site" class="regular-text"
										value="<?php echo esc_attr( $this->get_setting( 'twitter_site', '' ) ); ?>"
										placeholder="@username">
								</td>
							</tr>
						</table>
				</div>

				<!-- Schema Tab -->
				<div class="blaminhor-essentials-tab-content<?php echo 'schema' === $active_tab ? ' active' : ''; ?>" data-tab-content="schema">
						<h3><?php esc_html_e( 'Site Identity', 'blaminhor-essentials' ); ?></h3>
						<details class="ap-tab-info">
							<summary><span class="dashicons dashicons-arrow-right-alt2"></span><?php esc_html_e( 'Learn more', 'blaminhor-essentials' ); ?></summary>
							<div class="ap-tab-info-content">
								<p><?php esc_html_e( 'This information is used by search engines and AI to identify your site and build knowledge panels.', 'blaminhor-essentials' ); ?></p>
							</div>
						</details>

						<table class="form-table">
							<tr>
								<th scope="row">
									<label for="schema_site_type"><?php esc_html_e( 'Site Represents', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<select name="schema_site_type" id="schema_site_type">
										<option value="Organization" <?php selected( $this->get_setting( 'schema_site_type', 'Organization' ), 'Organization' ); ?>><?php esc_html_e( 'Organization', 'blaminhor-essentials' ); ?></option>
										<option value="Person" <?php selected( $this->get_setting( 'schema_site_type', 'Organization' ), 'Person' ); ?>><?php esc_html_e( 'Person', 'blaminhor-essentials' ); ?></option>
									</select>
									<p class="description"><?php esc_html_e( 'Choose whether your site represents an organization or a person. This information is used by search engines and AI to identify your site.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_org_name"><?php esc_html_e( 'Name', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<input type="text" name="schema_org_name" id="schema_org_name" class="regular-text"
										value="<?php echo esc_attr( $this->get_setting( 'schema_org_name', '' ) ); ?>"
										placeholder="<?php echo esc_attr( get_bloginfo( 'name' ) ); ?>">
									<p class="description"><?php esc_html_e( 'The official name of your organization or your full name.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_org_logo"><?php esc_html_e( 'Logo URL', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<input type="url" name="schema_org_logo" id="schema_org_logo" class="large-text"
										value="<?php echo esc_url( $this->get_setting( 'schema_org_logo', '' ) ); ?>"
										placeholder="https://example.com/logo.png">
									<p class="description"><?php esc_html_e( 'Your organization logo. Recommended: at least 112x112px.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_org_url"><?php esc_html_e( 'Website URL', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<input type="url" name="schema_org_url" id="schema_org_url" class="large-text"
										value="<?php echo esc_url( $this->get_setting( 'schema_org_url', '' ) ); ?>"
										placeholder="<?php echo esc_url( home_url() ); ?>">
									<p class="description"><?php esc_html_e( 'Your official website URL.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_same_as"><?php esc_html_e( 'Social Profiles', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<textarea name="schema_same_as" id="schema_same_as" class="large-text" rows="4"
										placeholder="https://facebook.com/yourpage&#10;https://twitter.com/yourhandle&#10;https://linkedin.com/company/yourcompany"><?php echo esc_textarea( $this->get_setting( 'schema_same_as', '' ) ); ?></textarea>
									<p class="description"><?php esc_html_e( 'Links to your social profiles (Facebook, Twitter, LinkedIn, YouTube...). One URL per line. These help AI engines verify your identity.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Structured Data Defaults', 'blaminhor-essentials' ); ?></h3>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Enable JSON-LD', 'blaminhor-essentials' ); ?></th>
								<td>
									<label>
										<input type="checkbox" name="schema_enabled" <?php checked( $this->get_setting( 'schema_enabled', true ) ); ?>>
										<?php esc_html_e( 'Enable structured data (JSON-LD) output. Disable if another plugin already generates structured data.', 'blaminhor-essentials' ); ?>
									</label>
								</td>
							</tr>
							<tr>
								<th scope="row"><?php esc_html_e( 'Content Types', 'blaminhor-essentials' ); ?></th>
								<td>
									<fieldset>
										<?php
										$schema_pts = $this->get_setting( 'schema_post_types', array( 'post', 'page' ) );
										foreach ( $post_types as $pt ) :
										?>
											<label class="ap-label-block">
												<input type="checkbox" name="schema_post_types[]" value="<?php echo esc_attr( $pt->name ); ?>"
													<?php checked( in_array( $pt->name, $schema_pts, true ) ); ?>>
												<?php echo esc_html( $pt->labels->singular_name ); ?>
											</label>
										<?php endforeach; ?>
									</fieldset>
									<p class="description"><?php esc_html_e( 'Select which content types should include structured data.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_default_article_type"><?php esc_html_e( 'Default Article Type', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<select name="schema_default_article_type" id="schema_default_article_type">
										<option value="Article" <?php selected( $this->get_setting( 'schema_default_article_type', 'Article' ), 'Article' ); ?>>Article</option>
										<option value="BlogPosting" <?php selected( $this->get_setting( 'schema_default_article_type', 'Article' ), 'BlogPosting' ); ?>>BlogPosting</option>
										<option value="NewsArticle" <?php selected( $this->get_setting( 'schema_default_article_type', 'Article' ), 'NewsArticle' ); ?>>NewsArticle</option>
										<option value="TechArticle" <?php selected( $this->get_setting( 'schema_default_article_type', 'Article' ), 'TechArticle' ); ?>>TechArticle</option>
									</select>
									<p class="description"><?php esc_html_e( 'Default schema type for posts. Article works for most sites. Use BlogPosting for personal blogs, NewsArticle for news sites.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_default_page_type"><?php esc_html_e( 'Default Page Type', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<select name="schema_default_page_type" id="schema_default_page_type">
										<option value="WebPage" <?php selected( $this->get_setting( 'schema_default_page_type', 'WebPage' ), 'WebPage' ); ?>>WebPage</option>
										<option value="AboutPage" <?php selected( $this->get_setting( 'schema_default_page_type', 'WebPage' ), 'AboutPage' ); ?>>AboutPage</option>
										<option value="ContactPage" <?php selected( $this->get_setting( 'schema_default_page_type', 'WebPage' ), 'ContactPage' ); ?>>ContactPage</option>
										<option value="FAQPage" <?php selected( $this->get_setting( 'schema_default_page_type', 'WebPage' ), 'FAQPage' ); ?>>FAQPage</option>
									</select>
									<p class="description"><?php esc_html_e( 'Default schema type for pages. Use AboutPage, ContactPage, etc. for specific pages — you can override this per page.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
							<tr>
								<th scope="row"><?php esc_html_e( 'Additional Schemas', 'blaminhor-essentials' ); ?></th>
								<td>
									<label class="ap-label-block">
										<input type="checkbox" name="schema_add_website" <?php checked( $this->get_setting( 'schema_add_website', true ) ); ?>>
										<?php esc_html_e( 'Add WebSite schema with search action on homepage. Helps AI engines understand your site structure.', 'blaminhor-essentials' ); ?>
									</label>
									<label class="ap-label-block">
										<input type="checkbox" name="schema_add_breadcrumbs" <?php checked( $this->get_setting( 'schema_add_breadcrumbs', true ) ); ?>>
										<?php esc_html_e( 'Add BreadcrumbList schema on content pages. Helps search engines and AI display your page hierarchy.', 'blaminhor-essentials' ); ?>
									</label>
									<label class="ap-label-block">
										<input type="checkbox" name="schema_auto_faq" <?php checked( $this->get_setting( 'schema_auto_faq', true ) ); ?>>
										<?php esc_html_e( 'Automatically detect FAQ content and generate FAQPage structured data.', 'blaminhor-essentials' ); ?>
									</label>
									<label class="ap-label-block">
										<input type="checkbox" name="schema_auto_howto" <?php checked( $this->get_setting( 'schema_auto_howto', true ) ); ?>>
										<?php esc_html_e( 'Automatically detect HowTo content (ordered lists, step-by-step) and generate HowTo structured data.', 'blaminhor-essentials' ); ?>
									</label>
									<label class="ap-label-block">
										<input type="checkbox" name="schema_auto_video" <?php checked( $this->get_setting( 'schema_auto_video', true ) ); ?>>
										<?php esc_html_e( 'Automatically detect embedded videos (YouTube, Vimeo, HTML5) and generate VideoObject structured data.', 'blaminhor-essentials' ); ?>
									</label>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Author E-E-A-T', 'blaminhor-essentials' ); ?></h3>
						<p class="description"><?php esc_html_e( 'Enrich the author schema with credentials and expertise signals. This information helps AI engines evaluate your authority on topics you write about.', 'blaminhor-essentials' ); ?></p>

						<table class="form-table">
							<tr>
								<th scope="row">
									<label for="schema_author_job_title"><?php esc_html_e( 'Job Title', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<input type="text" name="schema_author_job_title" id="schema_author_job_title" class="regular-text"
										value="<?php echo esc_attr( $this->get_setting( 'schema_author_job_title', '' ) ); ?>"
										placeholder="<?php esc_attr_e( 'e.g. Senior Developer', 'blaminhor-essentials' ); ?>">
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_author_description"><?php esc_html_e( 'Bio / Description', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<textarea name="schema_author_description" id="schema_author_description" class="large-text" rows="2"
										placeholder="<?php esc_attr_e( 'e.g. 10 years experience in web development', 'blaminhor-essentials' ); ?>"><?php echo esc_textarea( $this->get_setting( 'schema_author_description', '' ) ); ?></textarea>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_author_knows_about"><?php esc_html_e( 'Areas of Expertise', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<textarea name="schema_author_knows_about" id="schema_author_knows_about" class="large-text" rows="3"
										placeholder="<?php esc_attr_e( 'One topic per line, e.g. WordPress, PHP, JavaScript', 'blaminhor-essentials' ); ?>"><?php echo esc_textarea( $this->get_setting( 'schema_author_knows_about', '' ) ); ?></textarea>
									<p class="description"><?php esc_html_e( 'One topic per line. Used in the knowsAbout schema property.', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_author_alumni_of"><?php esc_html_e( 'Education', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<input type="text" name="schema_author_alumni_of" id="schema_author_alumni_of" class="regular-text"
										value="<?php echo esc_attr( $this->get_setting( 'schema_author_alumni_of', '' ) ); ?>"
										placeholder="<?php esc_attr_e( 'e.g. MIT, Stanford University', 'blaminhor-essentials' ); ?>">
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="schema_author_same_as"><?php esc_html_e( 'Author Profiles', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<textarea name="schema_author_same_as" id="schema_author_same_as" class="large-text" rows="3"
										placeholder="https://linkedin.com/in/yourprofile&#10;https://twitter.com/yourhandle&#10;https://yoursite.com"><?php echo esc_textarea( $this->get_setting( 'schema_author_same_as', '' ) ); ?></textarea>
									<p class="description"><?php esc_html_e( 'One URL per line. Links to the author\'s personal profiles (LinkedIn, Twitter, website).', 'blaminhor-essentials' ); ?></p>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Table of Contents', 'blaminhor-essentials' ); ?></h3>
						<p class="description"><?php esc_html_e( 'Add a table of contents to your posts using the [blaminhor_toc] shortcode. Improves content structure for both readers and AI engines.', 'blaminhor-essentials' ); ?></p>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Enable', 'blaminhor-essentials' ); ?></th>
								<td>
									<label>
										<input type="checkbox" name="toc_enabled" <?php checked( $this->get_setting( 'toc_enabled', false ) ); ?>>
										<?php esc_html_e( 'Enable the [blaminhor_toc] shortcode', 'blaminhor-essentials' ); ?>
									</label>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="toc_depth"><?php esc_html_e( 'Heading Depth', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<select name="toc_depth" id="toc_depth">
										<option value="h2" <?php selected( $this->get_setting( 'toc_depth', 'h2-h3' ), 'h2' ); ?>>H2</option>
										<option value="h2-h3" <?php selected( $this->get_setting( 'toc_depth', 'h2-h3' ), 'h2-h3' ); ?>>H2 – H3</option>
										<option value="h2-h4" <?php selected( $this->get_setting( 'toc_depth', 'h2-h3' ), 'h2-h4' ); ?>>H2 – H4</option>
									</select>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="toc_style"><?php esc_html_e( 'List Style', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<select name="toc_style" id="toc_style">
										<option value="numbered" <?php selected( $this->get_setting( 'toc_style', 'numbered' ), 'numbered' ); ?>><?php esc_html_e( 'Numbered', 'blaminhor-essentials' ); ?></option>
										<option value="bullets" <?php selected( $this->get_setting( 'toc_style', 'numbered' ), 'bullets' ); ?>><?php esc_html_e( 'Bullets', 'blaminhor-essentials' ); ?></option>
										<option value="plain" <?php selected( $this->get_setting( 'toc_style', 'numbered' ), 'plain' ); ?>><?php esc_html_e( 'Plain', 'blaminhor-essentials' ); ?></option>
									</select>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="toc_title"><?php esc_html_e( 'Title', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<input type="text" name="toc_title" id="toc_title" class="regular-text"
										value="<?php echo esc_attr( $this->get_setting( 'toc_title', 'Table of Contents' ) ); ?>"
										placeholder="Table of Contents">
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Key Takeaways', 'blaminhor-essentials' ); ?></h3>
						<p class="description"><?php esc_html_e( 'Key takeaways are summary points displayed at the top of your content. They are ideal for AI citation and improve content scannability.', 'blaminhor-essentials' ); ?></p>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Frontend Display', 'blaminhor-essentials' ); ?></th>
								<td>
									<label>
										<input type="checkbox" name="takeaways_enabled" <?php checked( $this->get_setting( 'takeaways_enabled', true ) ); ?>>
										<?php esc_html_e( 'Display key takeaways at the top of content pages. The input fields in the editor are always available regardless of this setting.', 'blaminhor-essentials' ); ?>
									</label>
								</td>
							</tr>
						</table>
				</div>

				<!-- Sitemap Tab -->
				<div class="blaminhor-essentials-tab-content<?php echo 'sitemap' === $active_tab ? ' active' : ''; ?>" data-tab-content="sitemap">
						<details class="ap-tab-info">
							<summary><span class="dashicons dashicons-arrow-right-alt2"></span><?php esc_html_e( 'Learn more', 'blaminhor-essentials' ); ?></summary>
							<div class="ap-tab-info-content">
								<p><?php esc_html_e( 'XML sitemaps help search engines discover and crawl all your content efficiently. Configure which content types to include and how your sitemaps are generated.', 'blaminhor-essentials' ); ?></p>
							</div>
						</details>
						<?php if ( $this->get_setting( 'sitemap_enabled', true ) ) : ?>
							<div class="blaminhor-essentials-notice success ap-mb-lg">
								<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">
									<span class="dashicons dashicons-yes-alt" style="color: #00a32a; font-size: 20px;"></span>
									<strong><?php esc_html_e( 'Your XML sitemaps are active', 'blaminhor-essentials' ); ?></strong>
								</div>

								<div class="ap-sitemaps-list">
									<!-- Sitemap Index -->
									<div class="ap-sitemap-item">
										<div class="ap-sitemap-item-info">
											<span class="dashicons dashicons-list-view" class="ap-text-primary"></span>
											<span><strong><?php esc_html_e( 'Sitemap Index', 'blaminhor-essentials' ); ?></strong></span>
										</div>
										<a href="<?php echo esc_url( home_url( '/sitemap.xml' ) ); ?>" target="_blank" class="button button-small">
											<span class="dashicons dashicons-external ap-icon-14"></span>
											<?php esc_html_e( 'View', 'blaminhor-essentials' ); ?>
										</a>
									</div>

									<?php
									// Get enabled post types for sitemap
									$sitemap_pts = $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) );
									foreach ( $sitemap_pts as $pt_name ) :
										$pt_obj = get_post_type_object( $pt_name );
										if ( ! $pt_obj ) continue;
										$sitemap_url = home_url( '/' . $pt_name . '-sitemap.xml' );
									?>
									<div class="ap-sitemap-item">
										<div class="ap-sitemap-item-info">
											<span class="dashicons dashicons-media-text" class="ap-text-gray"></span>
											<span><?php echo esc_html( $pt_obj->labels->name ); ?></span>
											<code class="ap-code"><?php echo esc_html( $pt_name ); ?>-sitemap.xml</code>
										</div>
										<a href="<?php echo esc_url( $sitemap_url ); ?>" target="_blank" class="button button-small">
											<span class="dashicons dashicons-external ap-icon-14"></span>
											<?php esc_html_e( 'View', 'blaminhor-essentials' ); ?>
										</a>
									</div>
									<?php endforeach; ?>

									<?php
									// Get enabled taxonomies for sitemap
									$sitemap_tax = $this->get_setting( 'sitemap_taxonomies', array( 'category', 'post_tag' ) );
									foreach ( $sitemap_tax as $tax_name ) :
										$tax_obj = get_taxonomy( $tax_name );
										if ( ! $tax_obj ) continue;
										$sitemap_url = home_url( '/' . $tax_name . '-sitemap.xml' );
									?>
									<div class="ap-sitemap-item">
										<div class="ap-sitemap-item-info">
											<span class="dashicons dashicons-tag" class="ap-text-gray"></span>
											<span><?php echo esc_html( $tax_obj->labels->name ); ?></span>
											<code class="ap-code"><?php echo esc_html( $tax_name ); ?>-sitemap.xml</code>
										</div>
										<a href="<?php echo esc_url( $sitemap_url ); ?>" target="_blank" class="button button-small">
											<span class="dashicons dashicons-external ap-icon-14"></span>
											<?php esc_html_e( 'View', 'blaminhor-essentials' ); ?>
										</a>
									</div>
									<?php endforeach; ?>
								</div>
							</div>
						<?php endif; ?>

						<h3><?php esc_html_e( 'General', 'blaminhor-essentials' ); ?></h3>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Enable Sitemap', 'blaminhor-essentials' ); ?></th>
								<td>
									<label>
										<input type="checkbox" name="sitemap_enabled" <?php checked( $this->get_setting( 'sitemap_enabled', true ) ); ?>>
										<?php esc_html_e( 'Generate XML sitemap', 'blaminhor-essentials' ); ?>
									</label>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Content', 'blaminhor-essentials' ); ?></h3>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Post Types', 'blaminhor-essentials' ); ?></th>
								<td>
									<fieldset>
										<?php
										$sitemap_pts = $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) );
										foreach ( $post_types as $pt ) :
										?>
											<label class="ap-label-block">
												<input type="checkbox" name="sitemap_post_types[]" value="<?php echo esc_attr( $pt->name ); ?>"
													<?php checked( in_array( $pt->name, $sitemap_pts, true ) ); ?>>
												<?php echo esc_html( $pt->labels->singular_name ); ?>
											</label>
										<?php endforeach; ?>
									</fieldset>
								</td>
							</tr>
							<tr>
								<th scope="row"><?php esc_html_e( 'Taxonomies', 'blaminhor-essentials' ); ?></th>
								<td>
									<fieldset>
										<?php
										$sitemap_tax = $this->get_setting( 'sitemap_taxonomies', array( 'category', 'post_tag' ) );
										foreach ( $taxonomies as $tax ) :
										?>
											<label class="ap-label-block">
												<input type="checkbox" name="sitemap_taxonomies[]" value="<?php echo esc_attr( $tax->name ); ?>"
													<?php checked( in_array( $tax->name, $sitemap_tax, true ) ); ?>>
												<?php echo esc_html( $tax->labels->singular_name ); ?>
											</label>
										<?php endforeach; ?>
									</fieldset>
								</td>
							</tr>
						</table>

						<hr>

						<h3><?php esc_html_e( 'Options', 'blaminhor-essentials' ); ?></h3>

						<table class="form-table">
							<tr>
								<th scope="row"><?php esc_html_e( 'Include in Sitemap', 'blaminhor-essentials' ); ?></th>
								<td>
									<label class="ap-label-block">
										<input type="checkbox" name="sitemap_include_lastmod" <?php checked( $this->get_setting( 'sitemap_include_lastmod', true ) ); ?>>
										<?php esc_html_e( 'Last modification date', 'blaminhor-essentials' ); ?>
									</label>
									<label>
										<input type="checkbox" name="sitemap_include_images" <?php checked( $this->get_setting( 'sitemap_include_images', false ) ); ?>>
										<?php esc_html_e( 'Images (Google Image sitemap)', 'blaminhor-essentials' ); ?>
									</label>
								</td>
							</tr>
							<tr>
								<th scope="row">
									<label for="sitemap_posts_per_file"><?php esc_html_e( 'URLs per Sitemap', 'blaminhor-essentials' ); ?></label>
								</th>
								<td>
									<input type="number" id="sitemap_posts_per_file" name="sitemap_posts_per_file"
										value="<?php echo esc_attr( $this->get_setting( 'sitemap_posts_per_file', 1000 ) ); ?>"
										min="100" max="50000" class="small-text">
								</td>
							</tr>
							<tr>
								<th scope="row"><?php esc_html_e( 'Search Engines', 'blaminhor-essentials' ); ?></th>
								<td>
									<label>
										<input type="checkbox" name="sitemap_ping_engines" <?php checked( $this->get_setting( 'sitemap_ping_engines', false ) ); ?>>
										<?php esc_html_e( 'Ping Google when new content is published', 'blaminhor-essentials' ); ?>
									</label>
								</td>
							</tr>
						</table>
				</div>

				<!-- Import Tab -->
				<div class="blaminhor-essentials-tab-content<?php echo 'import' === $active_tab ? ' active' : ''; ?>" data-tab-content="import">
					<h3><?php esc_html_e( 'Import SEO Data', 'blaminhor-essentials' ); ?></h3>
					<details class="ap-tab-info">
						<summary><span class="dashicons dashicons-arrow-right-alt2"></span><?php esc_html_e( 'Learn more', 'blaminhor-essentials' ); ?></summary>
						<div class="ap-tab-info-content">
							<p><?php esc_html_e( 'Import SEO metadata from other popular SEO plugins. This will copy titles, descriptions, and social media settings to Blaminhor Essentials format.', 'blaminhor-essentials' ); ?></p>
						</div>
					</details>

					<?php
					$import_sources = $this->get_available_import_sources();

					if ( empty( $import_sources ) ) :
					?>
						<div class="blaminhor-essentials-notice info">
							<?php esc_html_e( 'No SEO plugins with importable data found. Supported plugins: Yoast SEO, Rank Math, All in One SEO.', 'blaminhor-essentials' ); ?>
						</div>
					<?php else : ?>
						<div class="ap-import-sources">
							<?php foreach ( $import_sources as $source_id => $source ) : ?>
								<div class="ap-import-source-card">
									<div class="ap-import-source-layout">
										<div class="ap-import-source-info">
											<h4 class="ap-import-source-title">
												<span class="dashicons dashicons-download ap-icon-mr" class="ap-text-primary"></span>
												<?php echo esc_html( $source['name'] ); ?>
											</h4>
											<p style="margin: 0; color: #646970;">
												<?php
												printf(
													/* translators: %d: number of posts */
													esc_html__( '%d posts with SEO data found', 'blaminhor-essentials' ),
													intval( $source['count'] )
												);
												?>
											</p>
										</div>
										<div class="ap-import-source-actions">
											<button type="button" class="button button-primary ap-import-seo-btn"
												data-source="<?php echo esc_attr( $source_id ); ?>"
												data-count="<?php echo esc_attr( $source['count'] ); ?>">
												<?php esc_html_e( 'Import', 'blaminhor-essentials' ); ?>
											</button>
										</div>
									</div>
									<div class="ap-import-options">
										<label class="ap-label-flex">
											<input type="checkbox" class="ap-import-overwrite" data-source="<?php echo esc_attr( $source_id ); ?>">
											<?php esc_html_e( 'Overwrite existing data', 'blaminhor-essentials' ); ?>
										</label>
									</div>
									<div class="ap-import-progress ap-hidden ap-mt-md" data-source="<?php echo esc_attr( $source_id ); ?>">
										<div class="ap-mini-progress-bar">
											<div class="ap-import-progress-bar ap-mini-progress-fill"></div>
										</div>
										<p class="ap-import-status"></p>
									</div>
									<div class="ap-import-result ap-hidden ap-mt-md" data-source="<?php echo esc_attr( $source_id ); ?>"></div>
								</div>
							<?php endforeach; ?>
						</div>

						<div class="blaminhor-essentials-notice info ap-mt-lg">
							<p><strong><?php esc_html_e( 'Note:', 'blaminhor-essentials' ); ?></strong> <?php esc_html_e( 'The original SEO data will not be deleted. You can safely test the import and keep both plugins active during the transition.', 'blaminhor-essentials' ); ?></p>
						</div>
					<?php endif; ?>
				</div>

				<!-- Submit button for all settings tabs -->
				<p class="submit ap-form-submit">
					<button type="submit" class="button button-primary"><?php esc_html_e( 'Save Settings', 'blaminhor-essentials' ); ?></button>
				</p>
				</form>
			</div>
		</div>
		<?php
	}

	/**
	 * Get available import sources
	 *
	 * Detects installed SEO plugins with importable data.
	 *
	 * @return array Array of available import sources with counts.
	 */
	private function get_available_import_sources() {
		global $wpdb;

		$sources = array();

		// Yoast SEO detection.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time import detection query.
		$yoast_count = $wpdb->get_var(
			"SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta}
			WHERE meta_key IN ('_yoast_wpseo_title', '_yoast_wpseo_metadesc')
			AND meta_value != ''"
		);
		if ( $yoast_count > 0 ) {
			$sources['yoast'] = array(
				'name'  => 'Yoast SEO',
				'count' => intval( $yoast_count ),
			);
		}

		// Rank Math detection.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time import detection query.
		$rankmath_count = $wpdb->get_var(
			"SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta}
			WHERE meta_key IN ('rank_math_title', 'rank_math_description')
			AND meta_value != ''"
		);
		if ( $rankmath_count > 0 ) {
			$sources['rankmath'] = array(
				'name'  => 'Rank Math',
				'count' => intval( $rankmath_count ),
			);
		}

		// All in One SEO detection.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time import detection query.
		$aioseo_count = $wpdb->get_var(
			"SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta}
			WHERE meta_key IN ('_aioseo_title', '_aioseo_description')
			AND meta_value != ''"
		);
		if ( $aioseo_count > 0 ) {
			$sources['aioseo'] = array(
				'name'  => 'All in One SEO',
				'count' => intval( $aioseo_count ),
			);
		}

		return $sources;
	}

	/**
	 * Get meta key mapping for import source
	 *
	 * @param string $source Import source ID.
	 * @return array Meta key mappings.
	 */
	private function get_import_mapping( $source ) {
		$mappings = array(
			'yoast'    => array(
				'_yoast_wpseo_title'                => '_blaminhor_essentials_seo_title',
				'_yoast_wpseo_metadesc'             => '_blaminhor_essentials_seo_description',
				'_yoast_wpseo_focuskw'              => '_blaminhor_essentials_seo_keywords',
				'_yoast_wpseo_opengraph-title'      => '_blaminhor_essentials_og_title',
				'_yoast_wpseo_opengraph-description' => '_blaminhor_essentials_og_description',
				'_yoast_wpseo_opengraph-image'      => '_blaminhor_essentials_og_image',
				'_yoast_wpseo_twitter-title'        => '_blaminhor_essentials_twitter_title',
				'_yoast_wpseo_twitter-description'  => '_blaminhor_essentials_twitter_description',
			),
			'rankmath' => array(
				'rank_math_title'               => '_blaminhor_essentials_seo_title',
				'rank_math_description'         => '_blaminhor_essentials_seo_description',
				'rank_math_focus_keyword'       => '_blaminhor_essentials_seo_keywords',
				'rank_math_facebook_title'      => '_blaminhor_essentials_og_title',
				'rank_math_facebook_description' => '_blaminhor_essentials_og_description',
				'rank_math_facebook_image'      => '_blaminhor_essentials_og_image',
				'rank_math_twitter_title'       => '_blaminhor_essentials_twitter_title',
				'rank_math_twitter_description' => '_blaminhor_essentials_twitter_description',
			),
			'aioseo'   => array(
				'_aioseo_title'              => '_blaminhor_essentials_seo_title',
				'_aioseo_description'        => '_blaminhor_essentials_seo_description',
				'_aioseo_keywords'           => '_blaminhor_essentials_seo_keywords',
				'_aioseo_og_title'           => '_blaminhor_essentials_og_title',
				'_aioseo_og_description'     => '_blaminhor_essentials_og_description',
				'_aioseo_og_image'           => '_blaminhor_essentials_og_image',
				'_aioseo_twitter_title'      => '_blaminhor_essentials_twitter_title',
				'_aioseo_twitter_description' => '_blaminhor_essentials_twitter_description',
			),
		);

		return isset( $mappings[ $source ] ) ? $mappings[ $source ] : array();
	}

	/**
	 * Get placeholder mapping from foreign SEO plugin to Blaminhor format
	 *
	 * @param string $source Source plugin (yoast, rankmath, aioseo).
	 * @return array Associative array of foreign_placeholder => blaminhor_placeholder.
	 */
	private function get_placeholder_mapping( $source ) {
		$mappings = array(
			'yoast'    => array(
				'%%title%%'            => '{title}',
				'%%sep%%'              => '{sep}',
				'%%sitename%%'         => '{site_name}',
				'%%tagline%%'          => '{tagline}',
				'%%sitedesc%%'         => '{tagline}',
				'%%page%%'             => '{page_number}',
				'%%pagenumber%%'       => '{page_number}',
				'%%date%%'             => '{date}',
				'%%currentyear%%'      => '{year}',
				'%%currentmonth%%'     => '{month}',
				'%%currentday%%'       => '{day}',
				'%%author%%'           => '{author}',
				'%%name%%'             => '{author}',
				'%%category%%'         => '{category}',
				'%%primary_category%%' => '{category}',
				'%%category_title%%'   => '{category}',
				'%%term_title%%'       => '{term_name}',
				'%%term%%'             => '{term_name}',
				'%%pt_single%%'        => '{post_type}',
				'%%pt_plural%%'        => '{post_type}',
				'%%searchphrase%%'     => '{search_query}',
			),
			'rankmath' => array(
				'%title%'           => '{title}',
				'%sep%'             => '{sep}',
				'%sitename%'        => '{site_name}',
				'%sitedesc%'        => '{tagline}',
				'%page%'            => '{page_number}',
				'%date%'            => '{date}',
				'%currentyear%'     => '{year}',
				'%currentmonth%'    => '{month}',
				'%currentday%'      => '{day}',
				'%author%'          => '{author}',
				'%name%'            => '{author}',
				'%category%'        => '{category}',
				'%term%'            => '{term_name}',
				'%pt_single%'       => '{post_type}',
				'%pt_plural%'       => '{post_type}',
				'%search_query%'    => '{search_query}',
			),
			'aioseo'   => array(
				'#post_title'       => '{title}',
				'#separator'        => '{sep}',
				'#site_title'       => '{site_name}',
				'#tagline'          => '{tagline}',
				'#post_date'        => '{date}',
				'#current_year'     => '{year}',
				'#current_month'    => '{month}',
				'#current_day'      => '{day}',
				'#author_name'      => '{author}',
				'#category'         => '{category}',
				'#taxonomy_title'   => '{term_name}',
				'#post_type'        => '{post_type}',
				'#search_term'      => '{search_query}',
			),
		);

		return isset( $mappings[ $source ] ) ? $mappings[ $source ] : array();
	}

	/**
	 * Convert foreign SEO placeholders to Blaminhor format
	 *
	 * @param string $value  The meta value containing foreign placeholders.
	 * @param string $source Source plugin (yoast, rankmath, aioseo).
	 * @return string Converted value.
	 */
	private function convert_placeholders( $value, $source ) {
		if ( empty( $value ) ) {
			return $value;
		}

		$mapping = $this->get_placeholder_mapping( $source );
		if ( empty( $mapping ) ) {
			return $value;
		}

		// Apply the mapping.
		$value = str_replace( array_keys( $mapping ), array_values( $mapping ), $value );

		// Clean up remaining unconverted placeholders.
		switch ( $source ) {
			case 'yoast':
				$value = preg_replace( '/%%[a-z_]+%%/', '', $value );
				break;
			case 'rankmath':
				$value = preg_replace( '/%[a-z_]+%/', '', $value );
				break;
			case 'aioseo':
				$value = preg_replace( '/#[a-z_]+/', '', $value );
				break;
		}

		// Clean up multiple spaces.
		$value = preg_replace( '/\s{2,}/', ' ', trim( $value ) );

		return $value;
	}

	/**
	 * AJAX handler for SEO data import
	 */
	public function ajax_import_seo() {
		check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
		}

		$source    = isset( $_POST['source'] ) ? sanitize_text_field( wp_unslash( $_POST['source'] ) ) : '';
		$overwrite = isset( $_POST['overwrite'] ) && 'true' === sanitize_text_field( wp_unslash( $_POST['overwrite'] ) );
		$offset    = isset( $_POST['offset'] ) ? intval( $_POST['offset'] ) : 0;
		$batch     = 50;

		$mapping = $this->get_import_mapping( $source );
		if ( empty( $mapping ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid import source.', 'blaminhor-essentials' ) ) );
		}

		global $wpdb;

		// Get source meta keys.
		$source_keys = array_keys( $mapping );
		$placeholders = implode( ',', array_fill( 0, count( $source_keys ), '%s' ) );

		// Get posts with source data.
		$query_args = array_merge( $source_keys, array( $offset, $batch ) );
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
		$posts = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT DISTINCT post_id FROM {$wpdb->postmeta}
				WHERE meta_key IN ($placeholders)
				AND meta_value != ''
				ORDER BY post_id ASC
				LIMIT %d, %d",
				$query_args
			)
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber

		$imported = 0;
		$skipped  = 0;

		foreach ( $posts as $post_id ) {
			foreach ( $mapping as $source_key => $target_key ) {
				$source_value = get_post_meta( $post_id, $source_key, true );

				if ( empty( $source_value ) ) {
					continue;
				}

				// Check if target already has data.
				$existing_value = get_post_meta( $post_id, $target_key, true );

				if ( ! empty( $existing_value ) && ! $overwrite ) {
					++$skipped;
					continue;
				}

				// Convert foreign placeholders for title/description fields.
				$convertible_keys = array(
					'_blaminhor_essentials_seo_title',
					'_blaminhor_essentials_seo_description',
					'_blaminhor_essentials_og_title',
					'_blaminhor_essentials_og_description',
					'_blaminhor_essentials_twitter_title',
					'_blaminhor_essentials_twitter_description',
				);
				if ( in_array( $target_key, $convertible_keys, true ) ) {
					$source_value = $this->convert_placeholders( $source_value, $source );
				}

				update_post_meta( $post_id, $target_key, sanitize_text_field( $source_value ) );
				++$imported;
			}
		}

		// Get total count for progress.
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
		$total = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta}
				WHERE meta_key IN ($placeholders)
				AND meta_value != ''",
				$source_keys
			)
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare

		$processed   = $offset + count( $posts );
		$has_more    = $processed < $total;
		$next_offset = $has_more ? $processed : 0;

		wp_send_json_success(
			array(
				'imported'    => $imported,
				'skipped'     => $skipped,
				'processed'   => $processed,
				'total'       => intval( $total ),
				'has_more'    => $has_more,
				'next_offset' => $next_offset,
			)
		);
	}

	// =========================================================================
	// JSON-LD STRUCTURED DATA
	// =========================================================================

	/**
	 * Output JSON-LD structured data in wp_head
	 */
	/**
	 * Get schema preview data for a post (used in metabox).
	 *
	 * @param WP_Post $post Post object.
	 * @return array Array with 'schemas' (array of type/json blocks) and 'faq_count'.
	 */
	private function get_post_schema_preview( $post ) {
		$result = array(
			'schemas'     => array(),
			'faq_count'   => 0,
			'howto_count' => 0,
			'video_count' => 0,
		);

		if ( ! $this->get_setting( 'schema_enabled', true ) ) {
			return $result;
		}

		$meta = $this->get_post_seo_meta( $post->ID );

		// Check if schema is disabled for this post.
		if ( 'None' === $meta['schema_type'] ) {
			return $result;
		}

		// Check if this post type has schema enabled.
		$schema_pts = $this->get_setting( 'schema_post_types', array( 'post', 'page' ) );
		if ( ! in_array( get_post_type( $post ), $schema_pts, true ) ) {
			return $result;
		}

		$json_flags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT;

		// Main schema (Article/WebPage).
		$schema_type = $this->resolve_schema_type( $post, $meta );
		$is_article  = in_array( $schema_type, array( 'Article', 'BlogPosting', 'NewsArticle', 'TechArticle' ), true );

		$main_schema = array(
			'@context' => 'https://schema.org',
			'@type'    => $schema_type,
			'headline' => wp_strip_all_tags( get_the_title( $post ) ),
		);

		$description = $meta['seo_description'];
		if ( empty( $description ) ) {
			$description = $this->get_default_description( $post );
		}
		if ( ! empty( $description ) ) {
			$main_schema['description'] = $description;
		}

		$main_schema['url']           = get_permalink( $post );
		$main_schema['datePublished'] = get_the_date( 'c', $post );
		$main_schema['dateModified']  = get_the_modified_date( 'c', $post );

		if ( has_post_thumbnail( $post ) ) {
			$thumb_data = wp_get_attachment_image_src( get_post_thumbnail_id( $post ), 'full' );
			if ( $thumb_data ) {
				$main_schema['image'] = array(
					'@type'  => 'ImageObject',
					'url'    => $thumb_data[0],
					'width'  => $thumb_data[1],
					'height' => $thumb_data[2],
				);
			}
		}

		if ( $is_article ) {
			$main_schema['author'] = array(
				'@type' => 'Person',
				'name'  => get_the_author_meta( 'display_name', $post->post_author ),
			);
		}

		$main_schema['publisher'] = array(
			'@type' => 'Organization',
			'name'  => $this->get_schema_org_name(),
		);

		$result['schemas'][] = array(
			'type' => $schema_type,
			'json' => wp_json_encode( $main_schema, $json_flags ),
		);

		// BreadcrumbList.
		if ( $this->get_setting( 'schema_add_breadcrumbs', true ) ) {
			$breadcrumb = $this->build_breadcrumb_schema( $post );
			if ( $breadcrumb ) {
				$result['schemas'][] = array(
					'type' => 'BreadcrumbList',
					'json' => wp_json_encode( $breadcrumb, $json_flags ),
				);
			}
		}

		// FAQ detection.
		if ( $this->get_setting( 'schema_auto_faq', true ) && 'FAQPage' !== $schema_type ) {
			$faq_schema = $this->detect_faq_schema( $post );
			if ( $faq_schema ) {
				$result['faq_count'] = count( $faq_schema['mainEntity'] );
				$result['schemas'][] = array(
					'type' => 'FAQPage (' . $result['faq_count'] . ' Q&A)',
					'json' => wp_json_encode( $faq_schema, $json_flags ),
				);
			}
		}

		// HowTo detection.
		if ( $this->get_setting( 'schema_auto_howto', true ) ) {
			$howto_schema = $this->detect_howto_schema( $post );
			if ( $howto_schema ) {
				$result['howto_count'] = count( $howto_schema['step'] );
				$result['schemas'][] = array(
					'type' => 'HowTo (' . $result['howto_count'] . ' steps)',
					'json' => wp_json_encode( $howto_schema, $json_flags ),
				);
			}
		}

		// VideoObject detection.
		if ( $this->get_setting( 'schema_auto_video', true ) ) {
			$video_schemas = $this->detect_video_schema( $post );
			$result['video_count'] = count( $video_schemas );
			foreach ( $video_schemas as $video_schema ) {
				$result['schemas'][] = array(
					'type' => 'VideoObject',
					'json' => wp_json_encode( $video_schema, $json_flags ),
				);
			}
		}

		return $result;
	}

	public function output_json_ld() {
		$schemas = array();

		if ( is_front_page() || is_home() ) {
			$schemas = $this->get_homepage_schemas();
		} elseif ( is_singular() ) {
			$schemas = $this->get_singular_schemas();
		}

		foreach ( $schemas as $schema ) {
			echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>' . "\n";
		}
	}

	/**
	 * Get JSON-LD schemas for homepage
	 *
	 * @return array
	 */
	private function get_homepage_schemas() {
		$schemas = array();

		// Organization or Person schema.
		$schemas[] = $this->build_entity_schema();

		// WebSite schema with SearchAction.
		if ( $this->get_setting( 'schema_add_website', true ) ) {
			$website = array(
				'@context' => 'https://schema.org',
				'@type'    => 'WebSite',
				'name'     => $this->get_schema_org_name(),
				'url'      => $this->get_schema_org_url(),
			);

			$website['potentialAction'] = array(
				'@type'       => 'SearchAction',
				'target'      => array(
					'@type'       => 'EntryPoint',
					'urlTemplate' => home_url( '/?s={search_term_string}' ),
				),
				'query-input' => 'required name=search_term_string',
			);

			$schemas[] = $website;
		}

		return $schemas;
	}

	/**
	 * Get JSON-LD schemas for singular content
	 *
	 * @return array
	 */
	private function get_singular_schemas() {
		global $post;

		if ( ! $post ) {
			return array();
		}

		$meta = $this->get_post_seo_meta( $post->ID );

		// Check if schema is disabled for this post.
		if ( 'None' === $meta['schema_type'] ) {
			// Still output custom JSON if provided.
			return $this->get_custom_json_schemas( $meta );
		}

		// Check if this post type has schema enabled.
		$schema_pts = $this->get_setting( 'schema_post_types', array( 'post', 'page' ) );
		if ( ! in_array( get_post_type( $post ), $schema_pts, true ) ) {
			return $this->get_custom_json_schemas( $meta );
		}

		$schemas = array();

		// Determine schema type.
		$schema_type = $this->resolve_schema_type( $post, $meta );

		// Build main schema.
		$is_article = in_array( $schema_type, array( 'Article', 'BlogPosting', 'NewsArticle', 'TechArticle' ), true );

		$main_schema = array(
			'@context' => 'https://schema.org',
			'@type'    => $schema_type,
		);

		// Common properties.
		$main_schema['headline']        = wp_strip_all_tags( get_the_title( $post ) );
		$main_schema['mainEntityOfPage'] = array(
			'@type' => 'WebPage',
			'@id'   => get_permalink( $post ),
		);

		// Description: schema_description → seo_description → auto fallback.
		$description = $meta['schema_description'];
		if ( empty( $description ) ) {
			$description = $meta['seo_description'];
		}
		if ( empty( $description ) ) {
			$description = $this->get_default_description( $post );
		}
		if ( ! empty( $description ) ) {
			$main_schema['description'] = $description;
		}

		// URL.
		$main_schema['url'] = get_permalink( $post );

		// Dates.
		$main_schema['datePublished'] = get_the_date( 'c', $post );
		$main_schema['dateModified']  = get_the_modified_date( 'c', $post );

		// Featured image.
		if ( has_post_thumbnail( $post ) ) {
			$thumb_id   = get_post_thumbnail_id( $post );
			$thumb_data = wp_get_attachment_image_src( $thumb_id, 'full' );
			if ( $thumb_data ) {
				$main_schema['image'] = array(
					'@type'  => 'ImageObject',
					'url'    => $thumb_data[0],
					'width'  => $thumb_data[1],
					'height' => $thumb_data[2],
				);
			}
		}

		// Author (for articles) — enriched with E-E-A-T.
		if ( $is_article ) {
			$main_schema['author'] = $this->build_author_schema( $post );
		}

		// Key Takeaways — add to abstract.
		$takeaways = get_post_meta( $post->ID, '_blaminhor_essentials_key_takeaways', true );
		if ( is_array( $takeaways ) && ! empty( $takeaways ) ) {
			$main_schema['abstract'] = implode( '. ', $takeaways ) . '.';
		}

		// Publisher.
		$publisher = array(
			'@type' => 'Organization',
			'name'  => $this->get_schema_org_name(),
		);
		$logo = $this->get_schema_org_logo();
		if ( ! empty( $logo ) ) {
			$publisher['logo'] = array(
				'@type' => 'ImageObject',
				'url'   => $logo,
			);
		}
		$main_schema['publisher'] = $publisher;

		$schemas[] = $main_schema;

		// BreadcrumbList.
		if ( $this->get_setting( 'schema_add_breadcrumbs', true ) ) {
			$breadcrumb = $this->build_breadcrumb_schema( $post );
			if ( $breadcrumb ) {
				$schemas[] = $breadcrumb;
			}
		}

		// FAQ auto-detection.
		if ( $this->get_setting( 'schema_auto_faq', true ) && 'FAQPage' !== $schema_type ) {
			$faq_schema = $this->detect_faq_schema( $post );
			if ( $faq_schema ) {
				$schemas[] = $faq_schema;
			}
		}

		// HowTo auto-detection.
		if ( $this->get_setting( 'schema_auto_howto', true ) ) {
			$howto_schema = $this->detect_howto_schema( $post );
			if ( $howto_schema ) {
				$schemas[] = $howto_schema;
			}
		}

		// VideoObject auto-detection.
		if ( $this->get_setting( 'schema_auto_video', true ) ) {
			$video_schemas = $this->detect_video_schema( $post );
			foreach ( $video_schemas as $video_schema ) {
				$schemas[] = $video_schema;
			}
		}

		// Custom JSON-LD.
		$custom = $this->get_custom_json_schemas( $meta );
		$schemas = array_merge( $schemas, $custom );

		return $schemas;
	}

	/**
	 * Resolve the schema type for a post
	 *
	 * @param WP_Post $post Post object.
	 * @param array   $meta Post SEO meta.
	 * @return string
	 */
	private function resolve_schema_type( $post, $meta ) {
		// Per-post override.
		if ( ! empty( $meta['schema_type'] ) && 'None' !== $meta['schema_type'] ) {
			return $meta['schema_type'];
		}

		// Global default by post type.
		if ( 'page' === get_post_type( $post ) ) {
			return $this->get_setting( 'schema_default_page_type', 'WebPage' );
		}

		return $this->get_setting( 'schema_default_article_type', 'Article' );
	}

	/**
	 * Build Organization or Person schema
	 *
	 * @return array
	 */
	private function build_entity_schema() {
		$type = $this->get_setting( 'schema_site_type', 'Organization' );

		$entity = array(
			'@context' => 'https://schema.org',
			'@type'    => $type,
			'name'     => $this->get_schema_org_name(),
			'url'      => $this->get_schema_org_url(),
		);

		$logo = $this->get_schema_org_logo();
		if ( ! empty( $logo ) ) {
			if ( 'Organization' === $type ) {
				$entity['logo'] = array(
					'@type' => 'ImageObject',
					'url'   => $logo,
				);
			} else {
				$entity['image'] = $logo;
			}
		}

		// sameAs social profiles.
		$same_as_raw = $this->get_setting( 'schema_same_as', '' );
		if ( ! empty( $same_as_raw ) ) {
			$urls = array_filter( array_map( 'trim', explode( "\n", $same_as_raw ) ) );
			$urls = array_values( array_filter( $urls, 'wp_http_validate_url' ) );
			if ( ! empty( $urls ) ) {
				$entity['sameAs'] = $urls;
			}
		}

		return $entity;
	}

	/**
	 * Build BreadcrumbList schema for a post
	 *
	 * @param WP_Post $post Post object.
	 * @return array|null
	 */
	private function build_breadcrumb_schema( $post ) {
		$items    = array();
		$position = 1;

		// Home.
		$items[] = array(
			'@type'    => 'ListItem',
			'position' => $position,
			'name'     => __( 'Home', 'blaminhor-essentials' ),
			'item'     => home_url( '/' ),
		);
		++$position;

		// Category (for posts).
		if ( 'post' === get_post_type( $post ) ) {
			$categories = get_the_category( $post->ID );
			if ( ! empty( $categories ) ) {
				$cat = $categories[0];
				$items[] = array(
					'@type'    => 'ListItem',
					'position' => $position,
					'name'     => $cat->name,
					'item'     => get_category_link( $cat->term_id ),
				);
				++$position;
			}
		}

		// Current page.
		$items[] = array(
			'@type'    => 'ListItem',
			'position' => $position,
			'name'     => wp_strip_all_tags( get_the_title( $post ) ),
		);

		return array(
			'@context'        => 'https://schema.org',
			'@type'           => 'BreadcrumbList',
			'itemListElement' => $items,
		);
	}

	/**
	 * Get custom JSON-LD schemas from post meta
	 *
	 * @param array $meta Post SEO meta.
	 * @return array
	 */
	private function get_custom_json_schemas( $meta ) {
		if ( empty( $meta['schema_custom_json'] ) ) {
			return array();
		}

		$decoded = json_decode( $meta['schema_custom_json'], true );
		if ( ! is_array( $decoded ) ) {
			return array();
		}

		// If it's a single schema (has @type), wrap in array.
		if ( isset( $decoded['@type'] ) ) {
			// Ensure @context is present.
			if ( ! isset( $decoded['@context'] ) ) {
				$decoded['@context'] = 'https://schema.org';
			}
			return array( $decoded );
		}

		return array();
	}

	/**
	 * Get schema organization/person name with fallback
	 *
	 * @return string
	 */
	private function get_schema_org_name() {
		$name = $this->get_setting( 'schema_org_name', '' );
		return ! empty( $name ) ? $name : get_bloginfo( 'name' );
	}

	/**
	 * Get schema organization/person URL with fallback
	 *
	 * @return string
	 */
	private function get_schema_org_url() {
		$url = $this->get_setting( 'schema_org_url', '' );
		return ! empty( $url ) ? $url : home_url( '/' );
	}

	/**
	 * Get schema organization logo with fallback
	 *
	 * @return string
	 */
	private function get_schema_org_logo() {
		$logo = $this->get_setting( 'schema_org_logo', '' );
		if ( ! empty( $logo ) ) {
			return $logo;
		}
		$site_icon = get_site_icon_url( 512 );
		return $site_icon ? $site_icon : '';
	}

	// =========================================================================
	// FAQ AUTO-DETECTION
	// =========================================================================

	/**
	 * Detect FAQ content in post HTML and return FAQPage schema
	 *
	 * @param WP_Post $post Post object.
	 * @return array|null FAQPage schema or null.
	 */
	private function detect_faq_schema( $post ) {
		$content = apply_filters( 'the_content', $post->post_content );
		if ( empty( $content ) ) {
			return null;
		}

		$qa_pairs = array();

		// Suppress HTML parsing errors.
		$use_errors = libxml_use_internal_errors( true );
		$dom        = new DOMDocument();
		$dom->loadHTML( '<?xml encoding="UTF-8"><div>' . $content . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
		libxml_clear_errors();
		libxml_use_internal_errors( $use_errors );

		$xpath = new DOMXPath( $dom );

		// Pattern 1: Gutenberg FAQ blocks (Yoast, Rank Math, etc.).
		$faq_blocks = $xpath->query( '//*[contains(@class,"wp-block-yoast-faq-block") or contains(@class,"rank-math-faq-block") or contains(@class,"schema-faq")]' );
		if ( $faq_blocks->length > 0 ) {
			foreach ( $faq_blocks as $block ) {
				$qa_pairs = array_merge( $qa_pairs, $this->extract_qa_from_headings( $block, $xpath ) );
			}
		}

		// Pattern 2: Schema.org microdata (itemtype="https://schema.org/Question").
		$schema_questions = $xpath->query( '//*[contains(@itemtype,"schema.org/Question")]' );
		if ( $schema_questions->length > 0 ) {
			foreach ( $schema_questions as $question_el ) {
				$q_name = $xpath->query( './/*[@itemprop="name"]', $question_el );
				$a_text = $xpath->query( './/*[@itemprop="text" or @itemprop="acceptedAnswer"]', $question_el );
				if ( $q_name->length > 0 && $a_text->length > 0 ) {
					$q = trim( $q_name->item( 0 )->textContent );
					$a = trim( $a_text->item( $a_text->length - 1 )->textContent );
					if ( ! empty( $q ) && ! empty( $a ) ) {
						$qa_pairs[] = array( 'q' => $q, 'a' => $a );
					}
				}
			}
		}

		// Pattern 3: <details><summary> pattern (native HTML5 accordions).
		$details = $xpath->query( '//details' );
		if ( $details->length > 0 ) {
			foreach ( $details as $detail ) {
				$summary = $xpath->query( './summary', $detail );
				if ( $summary->length > 0 ) {
					$q = trim( $summary->item( 0 )->textContent );
					// Get text content after summary.
					$a_parts = array();
					foreach ( $detail->childNodes as $child ) {
						if ( 'summary' !== $child->nodeName ) {
							$text = trim( $child->textContent );
							if ( ! empty( $text ) ) {
								$a_parts[] = $text;
							}
						}
					}
					$a = implode( ' ', $a_parts );
					if ( ! empty( $q ) && ! empty( $a ) ) {
						$qa_pairs[] = array( 'q' => $q, 'a' => $a );
					}
				}
			}
		}

		// Pattern 4: Containers with FAQ-related classes containing heading + content pairs.
		$faq_containers = $xpath->query( '//*[contains(@class,"faq") or contains(@class,"accordion")]' );
		if ( $faq_containers->length > 0 && empty( $qa_pairs ) ) {
			foreach ( $faq_containers as $container ) {
				$qa_pairs = array_merge( $qa_pairs, $this->extract_qa_from_headings( $container, $xpath ) );
			}
		}

		// Pattern 5: .faq-item, .question/.answer pattern.
		$faq_items = $xpath->query( '//*[contains(@class,"faq-item") or contains(@class,"faq_item")]' );
		if ( $faq_items->length > 0 && empty( $qa_pairs ) ) {
			foreach ( $faq_items as $item ) {
				$q_el = $xpath->query( './/*[contains(@class,"question")]', $item );
				$a_el = $xpath->query( './/*[contains(@class,"answer")]', $item );
				if ( $q_el->length > 0 && $a_el->length > 0 ) {
					$q = trim( $q_el->item( 0 )->textContent );
					$a = trim( $a_el->item( 0 )->textContent );
					if ( ! empty( $q ) && ! empty( $a ) ) {
						$qa_pairs[] = array( 'q' => $q, 'a' => $a );
					}
				}
			}
		}

		if ( empty( $qa_pairs ) ) {
			return null;
		}

		// Deduplicate by question.
		$seen    = array();
		$unique  = array();
		foreach ( $qa_pairs as $pair ) {
			$key = md5( $pair['q'] );
			if ( ! isset( $seen[ $key ] ) ) {
				$seen[ $key ] = true;
				$unique[]     = $pair;
			}
		}

		// Build FAQPage schema.
		$faq_entities = array();
		foreach ( $unique as $pair ) {
			$faq_entities[] = array(
				'@type'          => 'Question',
				'name'           => $pair['q'],
				'acceptedAnswer' => array(
					'@type' => 'Answer',
					'text'  => $pair['a'],
				),
			);
		}

		return array(
			'@context'   => 'https://schema.org',
			'@type'      => 'FAQPage',
			'mainEntity' => $faq_entities,
		);
	}

	/**
	 * Extract Q&A pairs from heading + sibling content inside a container
	 *
	 * @param DOMElement $container Container element.
	 * @param DOMXPath   $xpath     XPath instance.
	 * @return array Array of q/a pairs.
	 */
	private function extract_qa_from_headings( $container, $xpath ) {
		$pairs    = array();
		$headings = $xpath->query( './/h2|.//h3|.//h4', $container );

		foreach ( $headings as $heading ) {
			$q       = trim( $heading->textContent );
			$a_parts = array();
			$sibling = $heading->nextSibling;

			while ( $sibling ) {
				if ( $sibling instanceof DOMElement && preg_match( '/^h[2-4]$/i', $sibling->nodeName ) ) {
					break;
				}
				$text = trim( $sibling->textContent );
				if ( ! empty( $text ) ) {
					$a_parts[] = $text;
				}
				$sibling = $sibling->nextSibling;
			}

			$a = implode( ' ', $a_parts );
			if ( ! empty( $q ) && ! empty( $a ) ) {
				$pairs[] = array( 'q' => $q, 'a' => $a );
			}
		}

		return $pairs;
	}

	// =========================================================================
	// ROBOTS.TXT AI BOT BLOCKING
	// =========================================================================

	/**
	 * Filter robots.txt to add AI bot blocking directives
	 *
	 * @param string $output Robots.txt content.
	 * @param bool   $public Site visibility.
	 * @return string
	 */
	public function filter_robots_txt( $output, $public ) {
		$bots = array(
			'ai_block_chatgpt'      => 'GPTBot',
			'ai_block_google_ai'    => 'Google-Extended',
			'ai_block_claude'       => 'ClaudeBot',
			'ai_block_perplexity'   => 'PerplexityBot',
			'ai_block_common_crawl' => 'CCBot',
		);

		$blocked = array();
		foreach ( $bots as $setting_key => $user_agent ) {
			if ( $this->get_setting( $setting_key, false ) ) {
				$blocked[] = $user_agent;
			}
		}

		if ( empty( $blocked ) ) {
			return $output;
		}

		$output .= "\n# Blaminhor Essentials - AI Crawler Blocking\n";
		foreach ( $blocked as $user_agent ) {
			$output .= "User-agent: " . $user_agent . "\n";
			$output .= "Disallow: /\n\n";
		}

		return $output;
	}

	// =========================================================================
	// HOWTO AUTO-DETECTION
	// =========================================================================

	/**
	 * Detect HowTo content in post HTML and return HowTo schema
	 *
	 * @param WP_Post $post Post object.
	 * @return array|null HowTo schema or null.
	 */
	private function detect_howto_schema( $post ) {
		$content = apply_filters( 'the_content', $post->post_content );
		if ( empty( $content ) ) {
			return null;
		}

		$steps = array();

		$use_errors = libxml_use_internal_errors( true );
		$dom        = new DOMDocument();
		$dom->loadHTML( '<?xml encoding="UTF-8"><div>' . $content . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
		libxml_clear_errors();
		libxml_use_internal_errors( $use_errors );

		$xpath = new DOMXPath( $dom );

		// Pattern 1: Gutenberg HowTo blocks (Yoast, Rank Math).
		$howto_blocks = $xpath->query( '//*[contains(@class,"wp-block-yoast-how-to-block") or contains(@class,"rank-math-howto-block") or contains(@class,"schema-howto")]' );
		if ( $howto_blocks->length > 0 ) {
			foreach ( $howto_blocks as $block ) {
				$steps = array_merge( $steps, $this->extract_steps_from_container( $block, $xpath ) );
			}
		}

		// Pattern 2: Elements with howto/steps/tutorial classes.
		if ( empty( $steps ) ) {
			$howto_containers = $xpath->query( '//*[contains(@class,"howto") or contains(@class,"how-to") or contains(@class,"steps") or contains(@class,"tutorial") or contains(@class,"step-by-step")]' );
			if ( $howto_containers->length > 0 ) {
				foreach ( $howto_containers as $container ) {
					$steps = array_merge( $steps, $this->extract_steps_from_container( $container, $xpath ) );
				}
			}
		}

		// Pattern 3: Ordered lists with descriptive items (> 20 chars).
		if ( empty( $steps ) ) {
			$ordered_lists = $xpath->query( '//ol' );
			foreach ( $ordered_lists as $ol ) {
				$items = $xpath->query( './li', $ol );
				if ( $items->length >= 2 ) {
					$list_steps   = array();
					$has_long_items = true;
					foreach ( $items as $li ) {
						$text = trim( $li->textContent );
						if ( mb_strlen( $text ) < 20 ) {
							$has_long_items = false;
							break;
						}
						$list_steps[] = $text;
					}
					if ( $has_long_items && ! empty( $list_steps ) ) {
						foreach ( $list_steps as $step_text ) {
							$steps[] = array(
								'name' => wp_trim_words( $step_text, 8, '' ),
								'text' => $step_text,
							);
						}
						break; // Use first qualifying list.
					}
				}
			}
		}

		if ( empty( $steps ) ) {
			return null;
		}

		// Build HowTo schema.
		$howto_steps = array();
		foreach ( $steps as $i => $step ) {
			$howto_steps[] = array(
				'@type'    => 'HowToStep',
				'position' => $i + 1,
				'name'     => $step['name'],
				'text'     => $step['text'],
			);
		}

		$description = get_post_meta( $post->ID, '_blaminhor_essentials_seo_description', true );
		if ( empty( $description ) ) {
			$description = $this->get_default_description( $post );
		}

		$schema = array(
			'@context'    => 'https://schema.org',
			'@type'       => 'HowTo',
			'name'        => wp_strip_all_tags( get_the_title( $post ) ),
			'step'        => $howto_steps,
		);

		if ( ! empty( $description ) ) {
			$schema['description'] = $description;
		}

		return $schema;
	}

	/**
	 * Extract steps from a container element (headings + following content)
	 *
	 * @param DOMElement $container Container element.
	 * @param DOMXPath   $xpath     XPath instance.
	 * @return array Array of name/text step pairs.
	 */
	private function extract_steps_from_container( $container, $xpath ) {
		$steps    = array();
		$headings = $xpath->query( './/h2|.//h3|.//h4', $container );

		foreach ( $headings as $heading ) {
			$name    = trim( $heading->textContent );
			$parts   = array();
			$sibling = $heading->nextSibling;

			while ( $sibling ) {
				if ( $sibling instanceof DOMElement && preg_match( '/^h[2-4]$/i', $sibling->nodeName ) ) {
					break;
				}
				$text = trim( $sibling->textContent );
				if ( ! empty( $text ) ) {
					$parts[] = $text;
				}
				$sibling = $sibling->nextSibling;
			}

			$step_text = implode( ' ', $parts );
			if ( ! empty( $name ) && ! empty( $step_text ) ) {
				$steps[] = array( 'name' => $name, 'text' => $step_text );
			}
		}

		return $steps;
	}

	// =========================================================================
	// VIDEO AUTO-DETECTION
	// =========================================================================

	/**
	 * Detect video embeds in post HTML and return VideoObject schemas
	 *
	 * @param WP_Post $post Post object.
	 * @return array Array of VideoObject schemas (one per video).
	 */
	private function detect_video_schema( $post ) {
		$content = apply_filters( 'the_content', $post->post_content );
		if ( empty( $content ) ) {
			return array();
		}

		$videos = array();

		$use_errors = libxml_use_internal_errors( true );
		$dom        = new DOMDocument();
		$dom->loadHTML( '<?xml encoding="UTF-8"><div>' . $content . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
		libxml_clear_errors();
		libxml_use_internal_errors( $use_errors );

		$xpath = new DOMXPath( $dom );

		// Pattern 1: YouTube iframes.
		$iframes = $xpath->query( '//iframe[contains(@src,"youtube.com/embed") or contains(@src,"youtu.be")]' );
		foreach ( $iframes as $iframe ) {
			$src = $iframe->getAttribute( 'src' );
			if ( preg_match( '/(?:youtube\.com\/embed\/|youtu\.be\/)([a-zA-Z0-9_-]+)/', $src, $matches ) ) {
				$video_id = $matches[1];
				$videos[] = array(
					'name'         => wp_strip_all_tags( get_the_title( $post ) ),
					'thumbnailUrl' => 'https://img.youtube.com/vi/' . $video_id . '/maxresdefault.jpg',
					'embedUrl'     => 'https://www.youtube.com/embed/' . $video_id,
				);
			}
		}

		// Pattern 2: Vimeo iframes.
		$vimeo_iframes = $xpath->query( '//iframe[contains(@src,"player.vimeo.com")]' );
		foreach ( $vimeo_iframes as $iframe ) {
			$src = $iframe->getAttribute( 'src' );
			if ( preg_match( '/player\.vimeo\.com\/video\/(\d+)/', $src, $matches ) ) {
				$video_id = $matches[1];
				$videos[] = array(
					'name'     => wp_strip_all_tags( get_the_title( $post ) ),
					'embedUrl' => 'https://player.vimeo.com/video/' . $video_id,
				);
			}
		}

		// Pattern 3: Gutenberg embed blocks (YouTube/Vimeo).
		$embed_blocks = $xpath->query( '//*[contains(@class,"wp-block-embed-youtube") or contains(@class,"wp-block-embed-vimeo")]//iframe' );
		foreach ( $embed_blocks as $iframe ) {
			$src = $iframe->getAttribute( 'src' );
			// Skip if already detected.
			$already = false;
			foreach ( $videos as $v ) {
				if ( isset( $v['embedUrl'] ) && strpos( $src, $v['embedUrl'] ) !== false ) {
					$already = true;
					break;
				}
			}
			if ( $already ) {
				continue;
			}
			if ( preg_match( '/youtube\.com\/embed\/([a-zA-Z0-9_-]+)/', $src, $matches ) ) {
				$videos[] = array(
					'name'         => wp_strip_all_tags( get_the_title( $post ) ),
					'thumbnailUrl' => 'https://img.youtube.com/vi/' . $matches[1] . '/maxresdefault.jpg',
					'embedUrl'     => 'https://www.youtube.com/embed/' . $matches[1],
				);
			}
		}

		// Pattern 4: HTML5 <video> tags.
		$video_tags = $xpath->query( '//video' );
		foreach ( $video_tags as $video ) {
			$src = $video->getAttribute( 'src' );
			if ( empty( $src ) ) {
				$source = $xpath->query( './source', $video );
				if ( $source->length > 0 ) {
					$src = $source->item( 0 )->getAttribute( 'src' );
				}
			}
			if ( ! empty( $src ) ) {
				$videos[] = array(
					'name'       => wp_strip_all_tags( get_the_title( $post ) ),
					'contentUrl' => $src,
				);
			}
		}

		if ( empty( $videos ) ) {
			return array();
		}

		// Build VideoObject schemas.
		$description = get_post_meta( $post->ID, '_blaminhor_essentials_seo_description', true );
		if ( empty( $description ) ) {
			$description = $this->get_default_description( $post );
		}

		$schemas = array();
		foreach ( $videos as $video ) {
			$schema = array(
				'@context'    => 'https://schema.org',
				'@type'       => 'VideoObject',
				'name'        => $video['name'],
				'uploadDate'  => get_the_date( 'c', $post ),
			);
			if ( ! empty( $description ) ) {
				$schema['description'] = $description;
			}
			if ( ! empty( $video['thumbnailUrl'] ) ) {
				$schema['thumbnailUrl'] = $video['thumbnailUrl'];
			}
			if ( ! empty( $video['embedUrl'] ) ) {
				$schema['embedUrl'] = $video['embedUrl'];
			}
			if ( ! empty( $video['contentUrl'] ) ) {
				$schema['contentUrl'] = $video['contentUrl'];
			}
			$schemas[] = $schema;
		}

		return $schemas;
	}

	// =========================================================================
	// AUTHOR E-E-A-T SCHEMA
	// =========================================================================

	/**
	 * Build enriched author Person schema with E-E-A-T properties
	 *
	 * @param WP_Post $post Post object.
	 * @return array Person schema array.
	 */
	private function build_author_schema( $post ) {
		$author = array(
			'@type' => 'Person',
			'name'  => get_the_author_meta( 'display_name', $post->post_author ),
			'url'   => get_author_posts_url( $post->post_author ),
		);

		$job_title = $this->get_setting( 'schema_author_job_title', '' );
		if ( ! empty( $job_title ) ) {
			$author['jobTitle'] = $job_title;
		}

		$description = $this->get_setting( 'schema_author_description', '' );
		if ( ! empty( $description ) ) {
			$author['description'] = $description;
		}

		$knows_about = $this->get_setting( 'schema_author_knows_about', '' );
		if ( ! empty( $knows_about ) ) {
			$topics = array_filter( array_map( 'trim', explode( "\n", $knows_about ) ) );
			if ( ! empty( $topics ) ) {
				$author['knowsAbout'] = array_values( $topics );
			}
		}

		$alumni_of = $this->get_setting( 'schema_author_alumni_of', '' );
		if ( ! empty( $alumni_of ) ) {
			$author['alumniOf'] = $alumni_of;
		}

		$same_as = $this->get_setting( 'schema_author_same_as', '' );
		if ( ! empty( $same_as ) ) {
			$urls = array_filter( array_map( 'trim', explode( "\n", $same_as ) ) );
			$urls = array_values( array_filter( $urls, 'wp_http_validate_url' ) );
			if ( ! empty( $urls ) ) {
				$author['sameAs'] = $urls;
			}
		}

		return $author;
	}

	// =========================================================================
	// TABLE OF CONTENTS (SHORTCODE)
	// =========================================================================

	/**
	 * Add heading IDs to content for ToC anchoring
	 *
	 * @param string $content Post content.
	 * @return string Modified content with heading IDs.
	 */
	public function filter_content_add_heading_ids( $content ) {
		if ( ! is_singular() || empty( $content ) ) {
			return $content;
		}

		$depth = $this->get_setting( 'toc_depth', 'h2-h3' );
		$pattern = $this->get_heading_regex( $depth );

		$used_ids = array();
		$content  = preg_replace_callback( $pattern, function ( $matches ) use ( &$used_ids ) {
			$tag     = $matches[1];
			$attrs   = $matches[2];
			$text    = $matches[3];
			$close   = $matches[4];

			// Skip if already has an id.
			if ( preg_match( '/\bid=["\']/', $attrs ) ) {
				return $matches[0];
			}

			$id = sanitize_title( wp_strip_all_tags( $text ) );
			if ( empty( $id ) ) {
				$id = 'heading';
			}

			// Ensure unique.
			$base_id = $id;
			$counter = 1;
			while ( in_array( $id, $used_ids, true ) ) {
				$id = $base_id . '-' . $counter;
				++$counter;
			}
			$used_ids[] = $id;

			return '<' . $tag . $attrs . ' id="' . esc_attr( $id ) . '">' . $text . '</' . $close . '>';
		}, $content );

		return $content;
	}

	/**
	 * Render the [blaminhor_toc] shortcode
	 *
	 * @param array  $atts    Shortcode attributes.
	 * @param string $content Shortcode content.
	 * @return string Table of contents HTML.
	 */
	public function render_toc_shortcode( $atts = array(), $content = null ) {
		if ( ! is_singular() ) {
			return '';
		}

		global $post;
		if ( ! $post ) {
			return '';
		}

		$depth   = $this->get_setting( 'toc_depth', 'h2-h3' );
		$style   = $this->get_setting( 'toc_style', 'numbered' );
		$title   = $this->get_setting( 'toc_title', 'Table of Contents' );
		$pattern = $this->get_heading_regex( $depth );

		$post_content = $post->post_content;
		preg_match_all( $pattern, $post_content, $matches, PREG_SET_ORDER );

		if ( count( $matches ) < 2 ) {
			return '';
		}

		$list_tag = 'numbered' === $style ? 'ol' : 'ul';
		$class    = 'plain' === $style ? ' class="blaminhor-toc-plain"' : '';

		$output  = '<nav class="blaminhor-toc" aria-label="' . esc_attr( $title ) . '">';
		if ( ! empty( $title ) ) {
			$output .= '<p class="blaminhor-toc-title">' . esc_html( $title ) . '</p>';
		}
		$output .= '<' . $list_tag . ' class="blaminhor-toc-list"' . $class . '>';

		$current_level = 2;
		foreach ( $matches as $match ) {
			$level = intval( substr( $match[1], 1 ) ); // h2 -> 2, h3 -> 3.
			$text  = wp_strip_all_tags( $match[3] );
			$id    = sanitize_title( $text );

			if ( empty( $id ) ) {
				continue;
			}

			// Open/close nested lists as needed.
			if ( $level > $current_level ) {
				$output .= '<' . $list_tag . '>';
			} elseif ( $level < $current_level ) {
				$diff = $current_level - $level;
				for ( $j = 0; $j < $diff; $j++ ) {
					$output .= '</li></' . $list_tag . '>';
				}
			} else {
				if ( $level !== 2 || $current_level !== 2 || $output !== '<nav class="blaminhor-toc" aria-label="' . esc_attr( $title ) . '"><p class="blaminhor-toc-title">' . esc_html( $title ) . '</p><' . $list_tag . ' class="blaminhor-toc-list"' . $class . '>' ) {
					$output .= '</li>';
				}
			}

			$output .= '<li><a href="#' . esc_attr( $id ) . '">' . esc_html( $text ) . '</a>';
			$current_level = $level;
		}

		// Close remaining open tags.
		while ( $current_level > 2 ) {
			$output .= '</li></' . $list_tag . '>';
			--$current_level;
		}
		$output .= '</li></' . $list_tag . '></nav>';

		return $output;
	}

	/**
	 * Get heading regex pattern based on depth setting
	 *
	 * @param string $depth Depth setting (h2, h2-h3, h2-h4).
	 * @return string Regex pattern.
	 */
	private function get_heading_regex( $depth ) {
		switch ( $depth ) {
			case 'h2':
				return '/<(h2)([^>]*)>(.*?)<\/(h2)>/si';
			case 'h2-h4':
				return '/<(h[2-4])([^>]*)>(.*?)<\/(h[2-4])>/si';
			case 'h2-h3':
			default:
				return '/<(h[23])([^>]*)>(.*?)<\/(h[23])>/si';
		}
	}

	// =========================================================================
	// KEY TAKEAWAYS
	// =========================================================================

	/**
	 * Render key takeaways at the top of content
	 *
	 * @param string $content Post content.
	 * @return string Modified content.
	 */
	public function render_key_takeaways( $content ) {
		if ( ! is_singular() || ! is_main_query() ) {
			return $content;
		}

		global $post;
		if ( ! $post ) {
			return $content;
		}

		$takeaways = get_post_meta( $post->ID, '_blaminhor_essentials_key_takeaways', true );
		if ( ! is_array( $takeaways ) || empty( $takeaways ) ) {
			return $content;
		}

		$title  = __( 'Key Takeaways', 'blaminhor-essentials' );
		$output = '<div class="blaminhor-key-takeaways">';
		$output .= '<p class="blaminhor-key-takeaways-title">' . esc_html( $title ) . '</p>';
		$output .= '<ul>';
		foreach ( $takeaways as $takeaway ) {
			$output .= '<li>' . esc_html( $takeaway ) . '</li>';
		}
		$output .= '</ul></div>';

		return $output . $content;
	}

	// =========================================================================
	// FRONTEND ASSETS
	// =========================================================================

	/**
	 * Enqueue frontend CSS for ToC and Key Takeaways
	 */
	public function enqueue_frontend_assets() {
		if ( ! is_singular() ) {
			return;
		}

		global $post;
		if ( ! $post ) {
			return;
		}

		$needs_css = false;

		// Check if Key Takeaways are present.
		if ( $this->get_setting( 'takeaways_enabled', true ) ) {
			$takeaways = get_post_meta( $post->ID, '_blaminhor_essentials_key_takeaways', true );
			if ( is_array( $takeaways ) && ! empty( $takeaways ) ) {
				$needs_css = true;
			}
		}

		// Check if ToC shortcode is used.
		if ( $this->get_setting( 'toc_enabled', false ) && has_shortcode( $post->post_content, 'blaminhor_toc' ) ) {
			$needs_css = true;
		}

		if ( $needs_css ) {
			wp_enqueue_style(
				'blaminhor-essentials-geo-frontend',
				BLAMINHOR_ESSENTIALS_PLUGIN_URL . 'assets/css/modules.css',
				array(),
				BLAMINHOR_ESSENTIALS_VERSION
			);
		}
	}

	// =========================================================================
	// LIVE META HELPERS
	// =========================================================================

	/**
	 * Sanitize live meta data sent from the metabox via AJAX.
	 *
	 * @return array|null Sanitized live data or null if not provided.
	 */
	private function sanitize_live_meta() {
		if ( empty( $_POST['live_meta'] ) || ! is_array( $_POST['live_meta'] ) ) {
			return null;
		}

		$raw  = wp_unslash( $_POST['live_meta'] );
		$live = array();

		$live['seo_title']          = isset( $raw['seo_title'] ) ? sanitize_text_field( $raw['seo_title'] ) : '';
		$live['seo_description']    = isset( $raw['seo_description'] ) ? sanitize_textarea_field( $raw['seo_description'] ) : '';
		$live['seo_keywords']       = isset( $raw['seo_keywords'] ) ? sanitize_text_field( $raw['seo_keywords'] ) : '';
		$live['noindex']            = ! empty( $raw['noindex'] ) ? '1' : '';
		$live['schema_type']        = isset( $raw['schema_type'] ) ? sanitize_text_field( $raw['schema_type'] ) : '';
		$live['schema_description'] = isset( $raw['schema_description'] ) ? sanitize_textarea_field( $raw['schema_description'] ) : '';
		$live['content']            = isset( $raw['content'] ) ? wp_kses_post( $raw['content'] ) : null;

		$live['key_takeaways'] = array();
		if ( ! empty( $raw['key_takeaways'] ) && is_array( $raw['key_takeaways'] ) ) {
			foreach ( $raw['key_takeaways'] as $takeaway ) {
				$val = sanitize_text_field( $takeaway );
				if ( '' !== $val ) {
					$live['key_takeaways'][] = $val;
				}
			}
		}

		return $live;
	}

	// =========================================================================
	// GEO SCORE
	// =========================================================================

	/**
	 * AJAX handler: Calculate GEO Score for a post
	 */
	public function ajax_geo_score() {
		check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'Unauthorized' );
		}

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
		if ( ! $post_id ) {
			wp_send_json_error( 'Invalid post ID' );
		}

		$post = get_post( $post_id );
		if ( ! $post ) {
			wp_send_json_error( 'Post not found' );
		}

		$live = $this->sanitize_live_meta();
		$result = $this->calculate_geo_score( $post, $live );
		wp_send_json_success( $result );
	}

	/**
	 * Calculate GEO Score for a post (20 criteria, 0-100)
	 *
	 * @param WP_Post $post      Post object.
	 * @param array   $live_meta Optional live meta data from metabox.
	 * @return array Score data with total, sections, and items.
	 */
	private function calculate_geo_score( $post, $live_meta = null ) {
		$raw_content = ( $live_meta && null !== $live_meta['content'] ) ? $live_meta['content'] : $post->post_content;
		$content     = apply_filters( 'the_content', $raw_content );
		$text        = wp_strip_all_tags( $content );
		$meta        = $this->get_post_seo_meta( $post->ID );
		if ( $live_meta ) {
			$meta = array_merge( $meta, array_intersect_key( $live_meta, $meta ) );
		}

		$sections = array();

		// --- STRUCTURE & READABILITY (25 points) ---
		$structure = array(
			'label' => __( 'Structure & Readability', 'blaminhor-essentials' ),
			'max'   => 25,
			'score' => 0,
			'items' => array(),
		);

		// 1. Heading hierarchy (5 pts).
		$has_h2 = (bool) preg_match( '/<h2/i', $content );
		$structure['items'][] = array(
			'pass'  => $has_h2,
			'score' => $has_h2 ? 5 : 0,
			'label' => __( 'Heading hierarchy (H2 present)', 'blaminhor-essentials' ),
			'url'   => 'https://www.semrush.com/blog/heading-tags/',
		);

		// 2. Short paragraphs (5 pts).
		$paragraphs = preg_split( '/<\/p>/i', $content );
		$para_count = 0;
		$long_paras = 0;
		foreach ( $paragraphs as $para ) {
			$para_text = trim( wp_strip_all_tags( $para ) );
			if ( empty( $para_text ) ) {
				continue;
			}
			++$para_count;
			$sentences = preg_split( '/[.!?]+/', $para_text );
			if ( count( $sentences ) > 5 ) {
				++$long_paras;
			}
		}
		$short_paras = $para_count > 0 && $long_paras < ( $para_count / 2 );
		$structure['items'][] = array(
			'pass'  => $short_paras,
			'score' => $short_paras ? 5 : 0,
			'label' => __( 'Short paragraphs (< 5 sentences each)', 'blaminhor-essentials' ),
			'url'   => 'https://yoast.com/paragraph-length-check/',
		);

		// 3. Lists present (5 pts).
		$has_lists = (bool) preg_match( '/<[ou]l/i', $content );
		$structure['items'][] = array(
			'pass'  => $has_lists,
			'score' => $has_lists ? 5 : 0,
			'label' => __( 'Lists present (ol/ul)', 'blaminhor-essentials' ),
			'url'   => 'https://backlinko.com/hub/seo/featured-snippets',
		);

		// 4. Content length > 300 words (5 pts).
		$word_count = str_word_count( $text );
		$long_enough = $word_count > 300;
		$structure['items'][] = array(
			'pass'  => $long_enough,
			'score' => $long_enough ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: word count */
				__( 'Content length (%d words, min: 300)', 'blaminhor-essentials' ),
				$word_count
			),
			'url'   => 'https://yoast.com/blog-post-word-count-seo/',
		);

		// 5. Direct answer in first 200 words (5 pts).
		$first_words = wp_trim_words( $text, 200, '' );
		$first_para  = preg_match( '/<p[^>]*>(.*?)<\/p>/si', $content, $fp_match ) ? wp_strip_all_tags( $fp_match[1] ) : '';
		$first_para_words = str_word_count( $first_para );
		$direct_answer    = $first_para_words >= 40;
		$structure['items'][] = array(
			'pass'  => $direct_answer,
			'score' => $direct_answer ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: word count of opening paragraph */
				__( 'Direct answer in opening paragraph (%d words, min: 40)', 'blaminhor-essentials' ),
				$first_para_words
			),
			'url'   => 'https://www.semrush.com/blog/featured-snippets/',
		);

		$structure['score'] = array_sum( array_column( $structure['items'], 'score' ) );
		$sections[] = $structure;

		// --- MACHINE INTERPRETABILITY (25 points) ---
		$machine = array(
			'label' => __( 'Machine Interpretability', 'blaminhor-essentials' ),
			'max'   => 25,
			'score' => 0,
			'items' => array(),
		);

		// 6. Schema markup active (5 pts).
		$schema_active = ! empty( $meta['schema_type'] ) && 'None' !== $meta['schema_type'];
		if ( empty( $meta['schema_type'] ) ) {
			$schema_active = true; // Default = active.
		}
		$machine['items'][] = array(
			'pass'  => $schema_active,
			'score' => $schema_active ? 5 : 0,
			'label' => __( 'Schema markup active', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data',
		);

		// 7. FAQ detected (5 pts).
		$faq_schema = $this->detect_faq_schema( $post );
		$has_faq    = ! empty( $faq_schema );
		$machine['items'][] = array(
			'pass'  => $has_faq,
			'score' => $has_faq ? 5 : 0,
			'label' => __( 'FAQ content detected', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/structured-data/faqpage',
		);

		// 8. Images with alt (5 pts).
		preg_match_all( '/<img[^>]*>/i', $content, $img_matches );
		$total_imgs   = count( $img_matches[0] );
		$imgs_with_alt = 0;
		foreach ( $img_matches[0] as $img ) {
			if ( preg_match( '/alt=["\'][^"\']+["\']/', $img ) ) {
				++$imgs_with_alt;
			}
		}
		$all_alts = $total_imgs === 0 || $imgs_with_alt === $total_imgs;
		$machine['items'][] = array(
			'pass'  => $all_alts,
			'score' => $all_alts ? 5 : 0,
			'label' => $total_imgs > 0
				? sprintf(
					/* translators: %1$d: images with alt, %2$d: total images */
					__( 'Images with alt text (%1$d/%2$d)', 'blaminhor-essentials' ),
					$imgs_with_alt,
					$total_imgs
				)
				: __( 'Images with alt text', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/google-images',
		);

		// 9. Rich structured data (5 pts) — at least 2 schema types.
		$schema_count = 1; // Main schema.
		if ( $has_faq ) {
			++$schema_count;
		}
		$howto_schema = $this->detect_howto_schema( $post );
		if ( ! empty( $howto_schema ) ) {
			++$schema_count;
		}
		$video_schemas = $this->detect_video_schema( $post );
		if ( ! empty( $video_schemas ) ) {
			++$schema_count;
		}
		$rich_data = $schema_count >= 2;
		$machine['items'][] = array(
			'pass'  => $rich_data,
			'score' => $rich_data ? 5 : 0,
			'label' => __( 'Rich structured data (2+ schema types)', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data',
		);

		// 10. Table of Contents present (5 pts).
		$has_toc = has_shortcode( $post->post_content, 'blaminhor_toc' );
		$machine['items'][] = array(
			'pass'  => $has_toc,
			'score' => $has_toc ? 5 : 0,
			'label' => __( 'Table of Contents present', 'blaminhor-essentials' ),
			'url'   => 'https://yoast.com/seo-friendly-blog-post/',
		);

		$machine['score'] = array_sum( array_column( $machine['items'], 'score' ) );
		$sections[] = $machine;

		// --- CITABILITY (25 points) ---
		$citability = array(
			'label' => __( 'Citability', 'blaminhor-essentials' ),
			'max'   => 25,
			'score' => 0,
			'items' => array(),
		);

		// 11. External links (5 pts).
		$home = home_url();
		preg_match_all( '/<a[^>]+href=["\']([^"\']+)["\'][^>]*>/i', $content, $link_matches );
		$external_count = 0;
		foreach ( $link_matches[1] as $href ) {
			if ( strpos( $href, 'http' ) === 0 && strpos( $href, $home ) !== 0 ) {
				++$external_count;
			}
		}
		$has_external = $external_count > 0;
		$citability['items'][] = array(
			'pass'  => $has_external,
			'score' => $has_external ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: number of external links */
				__( 'External links (%d sources)', 'blaminhor-essentials' ),
				$external_count
			),
			'url'   => 'https://www.semrush.com/blog/outbound-links/',
		);

		// 12. Data/numbers present (5 pts).
		$has_numbers = (bool) preg_match( '/\d+[%,.]?\d*/', $text );
		$citability['items'][] = array(
			'pass'  => $has_numbers,
			'score' => $has_numbers ? 5 : 0,
			'label' => __( 'Precise data/numbers present', 'blaminhor-essentials' ),
			'url'   => 'https://arxiv.org/html/2311.09735v3',
		);

		// 13. Key Takeaways (5 pts) — at least 2.
		$takeaways = ( $live_meta && ! empty( $live_meta['key_takeaways'] ) ) ? $live_meta['key_takeaways'] : get_post_meta( $post->ID, '_blaminhor_essentials_key_takeaways', true );
		$takeaway_count = is_array( $takeaways ) ? count( $takeaways ) : 0;
		$has_takeaways  = $takeaway_count >= 2;
		$citability['items'][] = array(
			'pass'  => $has_takeaways,
			'score' => $has_takeaways ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: number of key takeaways */
				__( 'Key Takeaways (%d points, min: 2)', 'blaminhor-essentials' ),
				$takeaway_count
			),
			'url'   => 'https://arxiv.org/html/2311.09735v3',
		);

		// 14. Content freshness (5 pts) — modified < 6 months ago.
		$modified    = strtotime( $post->post_modified );
		$six_months  = strtotime( '-6 months' );
		$is_fresh    = $modified > $six_months;
		$citability['items'][] = array(
			'pass'  => $is_fresh,
			'score' => $is_fresh ? 5 : 0,
			'label' => __( 'Content updated recently (< 6 months)', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/ranking-systems-guide',
		);

		// 15. HowTo or Video detected (5 pts).
		$has_howto_or_video = ! empty( $howto_schema ) || ! empty( $video_schemas );
		$citability['items'][] = array(
			'pass'  => $has_howto_or_video,
			'score' => $has_howto_or_video ? 5 : 0,
			'label' => __( 'HowTo or Video detected', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/structured-data/howto',
		);

		$citability['score'] = array_sum( array_column( $citability['items'], 'score' ) );
		$sections[] = $citability;

		// --- E-E-A-T & AUTHORITY (25 points) ---
		$eeat = array(
			'label' => __( 'E-E-A-T & Authority', 'blaminhor-essentials' ),
			'max'   => 25,
			'score' => 0,
			'items' => array(),
		);

		// 16. Author info configured (5 pts).
		$author_name  = $this->get_schema_org_name();
		$author_job   = $this->get_setting( 'schema_author_job_title', '' );
		$author_desc  = $this->get_setting( 'schema_author_description', '' );
		$has_author   = ! empty( $author_name ) && ( ! empty( $author_job ) || ! empty( $author_desc ) );
		$eeat['items'][] = array(
			'pass'  => $has_author,
			'score' => $has_author ? 5 : 0,
			'label' => __( 'Author info configured', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/establish-business-details',
		);

		// 17. Social profiles (5 pts).
		$author_social = $this->get_setting( 'schema_author_same_as', '' );
		$has_social    = ! empty( $author_social );
		$eeat['items'][] = array(
			'pass'  => $has_social,
			'score' => $has_social ? 5 : 0,
			'label' => __( 'Author social profiles', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/establish-business-details',
		);

		// 18. Schema description set (5 pts).
		$has_schema_desc = ! empty( $meta['schema_description'] );
		$eeat['items'][] = array(
			'pass'  => $has_schema_desc,
			'score' => $has_schema_desc ? 5 : 0,
			'label' => __( 'Schema description set', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data',
		);

		// 19. Well-structured content (5 pts) — at least 3 H2 sections.
		preg_match_all( '/<h2/i', $content, $h2_matches );
		$h2_count      = count( $h2_matches[0] );
		$well_sectioned = $h2_count >= 3;
		$eeat['items'][] = array(
			'pass'  => $well_sectioned,
			'score' => $well_sectioned ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: number of H2 headings */
				__( 'Content divided into sections (%d H2, min: 3)', 'blaminhor-essentials' ),
				$h2_count
			),
			'url'   => 'https://arxiv.org/html/2311.09735v3',
		);

		// 20. Clear opening summary (5 pts) — first 100 words cover > 50 words.
		$first_100 = wp_trim_words( $text, 100, '' );
		$first_100_count = str_word_count( $first_100 );
		$has_summary = $first_100_count >= 50;
		$eeat['items'][] = array(
			'pass'  => $has_summary,
			'score' => $has_summary ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: word count in first 100 words */
				__( 'Clear opening summary (%d words, min: 50)', 'blaminhor-essentials' ),
				$first_100_count
			),
			'url'   => 'https://arxiv.org/html/2311.09735v3',
		);

		$eeat['score'] = array_sum( array_column( $eeat['items'], 'score' ) );
		$sections[] = $eeat;

		// Total.
		$total = 0;
		foreach ( $sections as $section ) {
			$total += $section['score'];
		}

		return array(
			'total'    => $total,
			'max'      => 100,
			'sections' => $sections,
		);
	}

	/**
	 * AJAX: Calculate SEO Score
	 */
	public function ajax_seo_score() {
		check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'Unauthorized' );
		}

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
		if ( ! $post_id ) {
			wp_send_json_error( 'Invalid post ID' );
		}

		$post = get_post( $post_id );
		if ( ! $post ) {
			wp_send_json_error( 'Post not found' );
		}

		$live = $this->sanitize_live_meta();
		$result = $this->calculate_seo_score( $post, $live );
		wp_send_json_success( $result );
	}

	/**
	 * Calculate SEO Score for a post (20 criteria, 0-100)
	 *
	 * @param WP_Post $post      Post object.
	 * @param array   $live_meta Optional live meta data from metabox.
	 * @return array Score data with total, sections, and items.
	 */
	private function calculate_seo_score( $post, $live_meta = null ) {
		$raw_content = ( $live_meta && null !== $live_meta['content'] ) ? $live_meta['content'] : $post->post_content;
		$content     = apply_filters( 'the_content', $raw_content );
		$text        = wp_strip_all_tags( $content );
		$meta        = $this->get_post_seo_meta( $post->ID );
		if ( $live_meta ) {
			$meta = array_merge( $meta, array_intersect_key( $live_meta, $meta ) );
		}

		$sections = array();

		// --- ON-PAGE ESSENTIALS (25 points) ---
		$onpage = array(
			'label' => __( 'On-Page Essentials', 'blaminhor-essentials' ),
			'max'   => 25,
			'score' => 0,
			'items' => array(),
		);

		// 1. Custom SEO title (5 pts).
		$has_title = ! empty( $meta['seo_title'] );
		$onpage['items'][] = array(
			'pass'  => $has_title,
			'score' => $has_title ? 5 : 0,
			'label' => __( 'Custom SEO title set', 'blaminhor-essentials' ),
		);

		// 2. SEO title length 30-60 chars (5 pts).
		$title_text = $meta['seo_title'] ? $meta['seo_title'] : wp_strip_all_tags( get_the_title( $post ) );
		$title_text = $this->replace_title_variables( $title_text, array(
			'{title}'  => wp_specialchars_decode( get_the_title( $post ), ENT_QUOTES ),
			'{author}' => get_the_author_meta( 'display_name', $post->post_author ),
		) );
		$title_len    = mb_strlen( $title_text );
		$title_ok_len = $title_len >= 30 && $title_len <= 60;
		$onpage['items'][] = array(
			'pass'  => $title_ok_len,
			'score' => $title_ok_len ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: character count */
				__( 'Title length optimal (%d chars, ideal: 30-60)', 'blaminhor-essentials' ),
				$title_len
			),
			'url'   => 'https://www.semrush.com/blog/title-tag/',
		);

		// 3. Custom meta description (5 pts).
		$has_desc = ! empty( $meta['seo_description'] );
		$onpage['items'][] = array(
			'pass'  => $has_desc,
			'score' => $has_desc ? 5 : 0,
			'label' => __( 'Custom meta description set', 'blaminhor-essentials' ),
		);

		// 4. Meta description length 120-160 chars (5 pts).
		$desc_text = $meta['seo_description'] ? $meta['seo_description'] : $this->get_default_description( $post );
		$desc_len    = mb_strlen( $desc_text );
		$desc_ok_len = $desc_len >= 120 && $desc_len <= 160;
		$onpage['items'][] = array(
			'pass'  => $desc_ok_len,
			'score' => $desc_ok_len ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: character count */
				__( 'Description length optimal (%d chars, ideal: 120-160)', 'blaminhor-essentials' ),
				$desc_len
			),
			'url'   => 'https://yoast.com/meta-description-length/',
		);

		// 5. Focus keywords defined (5 pts).
		$has_keywords = ! empty( $meta['seo_keywords'] );
		$onpage['items'][] = array(
			'pass'  => $has_keywords,
			'score' => $has_keywords ? 5 : 0,
			'label' => __( 'Focus keywords defined', 'blaminhor-essentials' ),
		);

		$onpage['score'] = array_sum( array_column( $onpage['items'], 'score' ) );
		$sections[] = $onpage;

		// --- CONTENT QUALITY (25 points) ---
		$quality = array(
			'label' => __( 'Content Quality', 'blaminhor-essentials' ),
			'max'   => 25,
			'score' => 0,
			'items' => array(),
		);

		// 6. Content > 300 words (5 pts).
		$word_count  = str_word_count( $text );
		$long_enough = $word_count > 300;
		$quality['items'][] = array(
			'pass'  => $long_enough,
			'score' => $long_enough ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: word count */
				__( 'Content length (%d words, min: 300)', 'blaminhor-essentials' ),
				$word_count
			),
			'url'   => 'https://yoast.com/blog-post-word-count-seo/',
		);

		// 7. Heading hierarchy — H2 present (5 pts).
		$has_h2 = (bool) preg_match( '/<h2/i', $content );
		$quality['items'][] = array(
			'pass'  => $has_h2,
			'score' => $has_h2 ? 5 : 0,
			'label' => __( 'Heading structure (H2 present)', 'blaminhor-essentials' ),
			'url'   => 'https://www.semrush.com/blog/heading-tags/',
		);

		// 8. Images in content (5 pts).
		preg_match_all( '/<img[^>]*>/i', $content, $img_matches );
		$total_imgs = count( $img_matches[0] );
		$has_images = $total_imgs > 0;
		$quality['items'][] = array(
			'pass'  => $has_images,
			'score' => $has_images ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: number of images */
				__( 'Images in content (%d)', 'blaminhor-essentials' ),
				$total_imgs
			),
		);

		// 9. All images have alt text (5 pts).
		$imgs_with_alt = 0;
		foreach ( $img_matches[0] as $img ) {
			if ( preg_match( '/alt=["\'][^"\']+["\']/', $img ) ) {
				++$imgs_with_alt;
			}
		}
		$all_alts = $total_imgs === 0 || $imgs_with_alt === $total_imgs;
		$quality['items'][] = array(
			'pass'  => $all_alts,
			'score' => $all_alts ? 5 : 0,
			'label' => $total_imgs > 0
				? sprintf(
					/* translators: %1$d: images with alt, %2$d: total images */
					__( 'Images with alt text (%1$d/%2$d)', 'blaminhor-essentials' ),
					$imgs_with_alt,
					$total_imgs
				)
				: __( 'Images with alt text', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/google-images',
		);

		// 10. Featured image set (5 pts).
		$has_thumb = has_post_thumbnail( $post );
		$quality['items'][] = array(
			'pass'  => $has_thumb,
			'score' => $has_thumb ? 5 : 0,
			'label' => __( 'Featured image set', 'blaminhor-essentials' ),
		);

		$quality['score'] = array_sum( array_column( $quality['items'], 'score' ) );
		$sections[] = $quality;

		// --- TECHNICAL SEO (25 points) ---
		$technical = array(
			'label' => __( 'Technical SEO', 'blaminhor-essentials' ),
			'max'   => 25,
			'score' => 0,
			'items' => array(),
		);

		// 11. Not set to noindex (5 pts).
		$is_indexable = empty( $meta['noindex'] );
		$technical['items'][] = array(
			'pass'  => $is_indexable,
			'score' => $is_indexable ? 5 : 0,
			'label' => $is_indexable
				? __( 'Page is indexable', 'blaminhor-essentials' )
				: __( 'Page is set to noindex', 'blaminhor-essentials' ),
		);

		// 12. URL slug clean (< 75 chars) (5 pts).
		$slug       = $post->post_name;
		$slug_len   = mb_strlen( $slug );
		$slug_clean = $slug_len > 0 && $slug_len <= 75;
		$technical['items'][] = array(
			'pass'  => $slug_clean,
			'score' => $slug_clean ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: slug character count */
				__( 'URL slug length (%d chars, max: 75)', 'blaminhor-essentials' ),
				$slug_len
			),
			'url'   => 'https://backlinko.com/hub/seo/url-slug',
		);

		// 13. Internal links present (5 pts).
		$home = home_url();
		preg_match_all( '/<a[^>]+href=["\']([^"\']+)["\'][^>]*>/i', $content, $link_matches );
		$internal_count = 0;
		$external_count = 0;
		foreach ( $link_matches[1] as $href ) {
			if ( strpos( $href, $home ) === 0 ) {
				++$internal_count;
			} elseif ( strpos( $href, 'http' ) === 0 ) {
				++$external_count;
			}
		}
		$has_internal = $internal_count > 0;
		$technical['items'][] = array(
			'pass'  => $has_internal,
			'score' => $has_internal ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: number of internal links */
				__( 'Internal links (%d)', 'blaminhor-essentials' ),
				$internal_count
			),
			'url'   => 'https://www.semrush.com/blog/internal-links/',
		);

		// 14. External links present (5 pts).
		$has_external = $external_count > 0;
		$technical['items'][] = array(
			'pass'  => $has_external,
			'score' => $has_external ? 5 : 0,
			'label' => sprintf(
				/* translators: %d: number of external links */
				__( 'External links (%d)', 'blaminhor-essentials' ),
				$external_count
			),
			'url'   => 'https://www.semrush.com/blog/outbound-links/',
		);

		// 15. No empty/broken href links (5 pts).
		$broken_count = 0;
		foreach ( $link_matches[1] as $href ) {
			$href = trim( $href );
			if ( empty( $href ) || '#' === $href || 'javascript:void(0)' === $href ) {
				++$broken_count;
			}
		}
		$no_broken = 0 === $broken_count;
		$technical['items'][] = array(
			'pass'  => $no_broken,
			'score' => $no_broken ? 5 : 0,
			'label' => $no_broken
				? __( 'No empty or placeholder links', 'blaminhor-essentials' )
				: sprintf(
					/* translators: %d: number of broken links */
					__( 'Empty or placeholder links found (%d)', 'blaminhor-essentials' ),
					$broken_count
				),
		);

		$technical['score'] = array_sum( array_column( $technical['items'], 'score' ) );
		$sections[] = $technical;

		// --- READABILITY & STRUCTURE (25 points) ---
		$readability = array(
			'label' => __( 'Readability & Structure', 'blaminhor-essentials' ),
			'max'   => 25,
			'score' => 0,
			'items' => array(),
		);

		// 16. Short paragraphs (5 pts).
		$paragraphs = preg_split( '/<\/p>/i', $content );
		$para_count = 0;
		$long_paras = 0;
		foreach ( $paragraphs as $para ) {
			$para_text = trim( wp_strip_all_tags( $para ) );
			if ( empty( $para_text ) ) {
				continue;
			}
			++$para_count;
			$sentences = preg_split( '/[.!?]+/', $para_text );
			if ( count( $sentences ) > 5 ) {
				++$long_paras;
			}
		}
		$short_paras = $para_count > 0 && $long_paras < ( $para_count / 2 );
		$readability['items'][] = array(
			'pass'  => $short_paras,
			'score' => $short_paras ? 5 : 0,
			'label' => __( 'Short paragraphs (< 5 sentences avg)', 'blaminhor-essentials' ),
			'url'   => 'https://yoast.com/paragraph-length-check/',
		);

		// 17. Lists present (5 pts).
		$has_lists = (bool) preg_match( '/<[ou]l/i', $content );
		$readability['items'][] = array(
			'pass'  => $has_lists,
			'score' => $has_lists ? 5 : 0,
			'label' => __( 'Lists present (ol/ul)', 'blaminhor-essentials' ),
			'url'   => 'https://backlinko.com/hub/seo/featured-snippets',
		);

		// 18. Multiple heading levels H2+H3 (5 pts).
		$has_h3       = (bool) preg_match( '/<h3/i', $content );
		$multi_levels = $has_h2 && $has_h3;
		$readability['items'][] = array(
			'pass'  => $multi_levels,
			'score' => $multi_levels ? 5 : 0,
			'label' => __( 'Multiple heading levels (H2 + H3)', 'blaminhor-essentials' ),
			'url'   => 'https://www.semrush.com/blog/heading-tags/',
		);

		// 19. Content freshness < 12 months (5 pts).
		$modified     = strtotime( $post->post_modified );
		$twelve_months = strtotime( '-12 months' );
		$is_fresh     = $modified > $twelve_months;
		$readability['items'][] = array(
			'pass'  => $is_fresh,
			'score' => $is_fresh ? 5 : 0,
			'label' => __( 'Content updated within 12 months', 'blaminhor-essentials' ),
			'url'   => 'https://developers.google.com/search/docs/appearance/ranking-systems-guide',
		);

		// 20. Opening paragraph substantial (5 pts) — first paragraph > 30 words.
		$first_para = preg_match( '/<p[^>]*>(.*?)<\/p>/si', $content, $fp_match ) ? wp_strip_all_tags( $fp_match[1] ) : '';
		$opening_ok = str_word_count( $first_para ) >= 30;
		$readability['items'][] = array(
			'pass'  => $opening_ok,
			'score' => $opening_ok ? 5 : 0,
			'label' => __( 'Substantial opening paragraph (30+ words)', 'blaminhor-essentials' ),
			'url'   => 'https://www.semrush.com/blog/featured-snippets/',
		);

		$readability['score'] = array_sum( array_column( $readability['items'], 'score' ) );
		$sections[] = $readability;

		// Total.
		$total = 0;
		foreach ( $sections as $section ) {
			$total += $section['score'];
		}

		return array(
			'total'    => $total,
			'max'      => 100,
			'sections' => $sections,
		);
	}
}
