<?php

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

class SEO_Fury_Sitemap {

    private $plugin_version = '1.0.0';

    public function __construct() {
        $this->init_hooks();
    }

private function init_hooks()
{
    add_action('init', array($this, 'register_sitemap_rewrite'));
    add_filter('query_vars', array($this, 'add_sitemap_query_var'));
    add_action('template_redirect', array($this, 'maybe_generate_sitemaps'), 1);

    add_action('init', array($this, 'register_xsl_rewrite'));

    // Disable WP 5.5+ native sitemap when ours is enabled. Register the filter
    // now (at construction, on plugins_loaded) so it is in place before core's
    // init-priority-10 wp_sitemaps_get_server() reads it — deferring it to an
    // init callback ran too late, so the native sitemap stayed active.
    if (get_option('seo_fury_sitemap_enabled', 0)) {
        add_filter('wp_sitemaps_enabled', '__return_false');
    }

    // Fallback: intercept sitemap requests early via parse_request
    add_action('parse_request', array($this, 'intercept_sitemap_request'), 1);

    add_shortcode('seo_fury_html_sitemap', array($this, 'shortcode_html_sitemap'));

    // Invalidate sitemap cache on post save
    add_action('save_post', array($this, 'invalidate_sitemap_cache'));

    // Invalidate sitemap cache when sitemap settings are updated
    add_action('update_option_seo_fury_sitemap_enabled', array($this, 'invalidate_sitemap_cache'));
    add_action('update_option_seo_fury_sitemap_post_types', array($this, 'invalidate_sitemap_cache'));
    add_action('update_option_seo_fury_sitemap_taxonomies', array($this, 'invalidate_sitemap_cache'));
    add_action('update_option_seo_fury_sitemap_exclude_urls', array($this, 'invalidate_sitemap_cache'));
    add_action('update_option_seo_fury_sitemap_include_images', array($this, 'invalidate_sitemap_cache'));
    add_action('update_option_seo_fury_sitemap_news_enabled', array($this, 'invalidate_sitemap_cache'));
    add_action('update_option_seo_fury_sitemap_video_enabled', array($this, 'invalidate_sitemap_cache'));
}

/**
 * Disable WordPress 5.5+ native sitemap when our sitemap is enabled.
 */
public function disable_wp_native_sitemap() {
    // Kept for the activation hook (SEO_Fury_Core::activate). Runtime disabling
    // is handled by the wp_sitemaps_enabled filter registered in init_hooks().
    if (get_option('seo_fury_sitemap_enabled', 0)) {
        add_filter('wp_sitemaps_enabled', '__return_false');
    }
}

/**
 * Fallback interception: catch sitemap requests via parse_request
 * in case rewrite rules haven't been flushed yet.
 */
public function intercept_sitemap_request($wp) {
    if (!get_option('seo_fury_sitemap_enabled', 0)) {
        return;
    }

    $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
    $request_path = trim((string) wp_parse_url($request_uri, PHP_URL_PATH), '/');
    $home_path = trim((string) wp_parse_url(home_url(), PHP_URL_PATH), '/');
    if ($home_path && strpos($request_path, $home_path) === 0) {
        $request_path = trim((string) substr($request_path, strlen($home_path)), '/');
    }

    // XSL stylesheet
    if ($request_path === 'sitemap.xsl') {
        header('Content-Type: text/xsl; charset=utf-8');
        header('X-Robots-Tag: noindex, follow');
        echo $this->get_xsl_stylesheet(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- XSL is a self-contained stylesheet built with escaped values in get_xsl_stylesheet().
        exit;
    }

    // Sitemap index
    if ($request_path === 'sitemap.xml') {
        $this->generate_sitemap_index();
        exit;
    }

    // News sitemap
    if ($request_path === 'sitemap-news.xml') {
        $this->generate_news_sitemap();
        exit;
    }

    // Video sitemap
    if ($request_path === 'sitemap-videos.xml') {
        $this->generate_video_sitemap();
        exit;
    }

    // Sub-sitemaps: sitemap-{type}.xml or sitemap-{type}-{page}.xml
    if (preg_match('/^sitemap-([a-z0-9_-]+)\.xml$/', $request_path, $matches)) {
        $this->dispatch_type_sitemap($matches[1]);
        exit;
    }
}

    public function register_settings() {
        $sanitize_checkbox  = array('sanitize_callback' => 'absint');
        $sanitize_text      = array('sanitize_callback' => 'sanitize_text_field');
        $sanitize_textarea  = array('sanitize_callback' => 'sanitize_textarea_field');

        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_enabled', $sanitize_checkbox);
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_post_types', array('sanitize_callback' => array($this, 'sanitize_array_setting')));
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_post_types_settings', array('sanitize_callback' => array($this, 'sanitize_array_setting')));
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_include_images', $sanitize_checkbox);
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_exclude_urls', $sanitize_textarea);
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_additional_urls', $sanitize_textarea);
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_additional_urls_data', array(
            'type' => 'array',
            'sanitize_callback' => array($this, 'sanitize_additional_urls_data'),
        ));
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_additional_default_priority', $sanitize_text);
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_additional_default_changefreq', $sanitize_text);
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_taxonomies', array('sanitize_callback' => array($this, 'sanitize_array_setting')));
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_taxonomies_settings', array('sanitize_callback' => array($this, 'sanitize_array_setting')));
        register_setting('seo_fury_sitemap_group', 'seo_fury_html_sitemap_slug', $sanitize_text);
        register_setting('seo_fury_sitemap_group', 'seo_fury_html_sitemap_post_types', array('sanitize_callback' => array($this, 'sanitize_array_setting')));

        add_settings_section(
            'seo_fury_sitemap_xml_section',
            '',
            null,
            'seo_fury_sitemap_group'
        );

        add_settings_field(
            'seo_fury_sitemap_enabled',
            __('Enable XML Sitemap', 'seo-fury'),
            array($this, 'sitemap_enabled_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_xml_section'
        );

        add_settings_field(
            'seo_fury_sitemap_post_types',
            __('Post Types for XML Sitemap', 'seo-fury'),
            array($this, 'sitemap_post_types_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_xml_section'
        );

        add_settings_field(
            'seo_fury_sitemap_taxonomies',
            __('Taxonomies for XML Sitemap', 'seo-fury'),
            array($this, 'sitemap_taxonomies_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_xml_section'
        );

        add_settings_field(
            'seo_fury_sitemap_include_images',
            __('Include Images', 'seo-fury'),
            array($this, 'sitemap_include_images_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_xml_section'
        );

        add_settings_field(
            'seo_fury_sitemap_exclude_urls',
            __('Exclude URLs', 'seo-fury'),
            array($this, 'sitemap_exclude_urls_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_xml_section'
        );

        add_settings_field(
            'seo_fury_sitemap_additional_urls',
            __('Additional URLs', 'seo-fury'),
            array($this, 'sitemap_additional_urls_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_xml_section'
        );

        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_google_ping', array('sanitize_callback' => 'absint'));

        add_settings_field(
            'seo_fury_sitemap_google_ping',
            __('Google Sitemap Ping', 'seo-fury'),
            array($this, 'sitemap_google_ping_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_xml_section'
        );

        add_settings_section(
            'seo_fury_sitemap_html_section',
            __('HTML Sitemap Settings', 'seo-fury'),
            array($this, 'sitemap_html_section_callback'),
            'seo_fury_sitemap_group'
        );

        add_settings_field(
            'seo_fury_html_sitemap_slug',
            __('HTML Sitemap Slug', 'seo-fury'),
            array($this, 'html_sitemap_slug_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_html_section'
        );

        add_settings_field(
            'seo_fury_html_sitemap_post_types',
            __('Post Types for HTML Sitemap', 'seo-fury'),
            array($this, 'html_sitemap_post_types_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_html_section'
        );

        // News Sitemap settings
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_news_enabled', $sanitize_checkbox);
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_news_post_types', array('sanitize_callback' => array($this, 'sanitize_array_setting')));
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_news_publication_name', $sanitize_text);
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_news_language', $sanitize_text);

        add_settings_section(
            'seo_fury_sitemap_news_section',
            __('News Sitemap', 'seo-fury'),
            array($this, 'sitemap_news_section_callback'),
            'seo_fury_sitemap_group'
        );

        add_settings_field(
            'seo_fury_sitemap_news_enabled',
            __('Enable News Sitemap', 'seo-fury'),
            array($this, 'sitemap_news_enabled_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_news_section'
        );

        add_settings_field(
            'seo_fury_sitemap_news_post_types',
            __('Post Types for News Sitemap', 'seo-fury'),
            array($this, 'sitemap_news_post_types_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_news_section'
        );

        add_settings_field(
            'seo_fury_sitemap_news_publication_name',
            __('Publication Name', 'seo-fury'),
            array($this, 'sitemap_news_publication_name_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_news_section'
        );

        add_settings_field(
            'seo_fury_sitemap_news_language',
            __('Language', 'seo-fury'),
            array($this, 'sitemap_news_language_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_news_section'
        );

        // Video Sitemap settings
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_video_enabled', $sanitize_checkbox);
        register_setting('seo_fury_sitemap_group', 'seo_fury_sitemap_video_post_types', array('sanitize_callback' => array($this, 'sanitize_array_setting')));

        add_settings_section(
            'seo_fury_sitemap_video_section',
            __('Video Sitemap', 'seo-fury'),
            array($this, 'sitemap_video_section_callback'),
            'seo_fury_sitemap_group'
        );

        add_settings_field(
            'seo_fury_sitemap_video_enabled',
            __('Enable Video Sitemap', 'seo-fury'),
            array($this, 'sitemap_video_enabled_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_video_section'
        );

        add_settings_field(
            'seo_fury_sitemap_video_post_types',
            __('Post Types for Video Sitemap', 'seo-fury'),
            array($this, 'sitemap_video_post_types_callback'),
            'seo_fury_sitemap_group',
            'seo_fury_sitemap_video_section'
        );
    }

    public function sitemap_xml_section_callback() {
        echo '<p>' . esc_html__('XML sitemap generation settings for search engines.', 'seo-fury') . '</p>';
    }

    public function sitemap_html_section_callback() {
        echo '<p>' . esc_html__('HTML sitemap settings for site visitors.', 'seo-fury') . '</p>';
    }

    // ── News Sitemap callbacks ──

    public function sitemap_news_section_callback() {
        echo '<p>' . esc_html__('Configure News sitemap for Google News. Only includes posts published within the last 48 hours.', 'seo-fury') . '</p>';
    }

    public function sitemap_news_enabled_callback() {
        $enabled = get_option('seo_fury_sitemap_news_enabled', 0);
        ?>
        <label>
            <input type="checkbox" name="seo_fury_sitemap_news_enabled" value="1" <?php checked($enabled, 1); ?>>
            <?php esc_html_e('Enable News Sitemap', 'seo-fury'); ?>
        </label>
        <p class="description"><?php esc_html_e('Generates /sitemap-news.xml with posts from the last 48 hours.', 'seo-fury'); ?></p>
        <?php
    }

    public function sitemap_news_post_types_callback() {
        $selected = get_option('seo_fury_sitemap_news_post_types', array('post'));
        if (!is_array($selected)) $selected = array('post');

        $post_types = get_post_types(array('public' => true), 'objects');
        foreach ($post_types as $pt) {
            if ($pt->name === 'attachment') continue;
            ?>
            <label style="display: block; margin-bottom: 4px;">
                <input type="checkbox" name="seo_fury_sitemap_news_post_types[]"
                       value="<?php echo esc_attr($pt->name); ?>"
                       <?php checked(in_array($pt->name, $selected, true)); ?>>
                <?php echo esc_html($pt->label); ?>
            </label>
            <?php
        }
    }

    public function sitemap_news_publication_name_callback() {
        $name = get_option('seo_fury_sitemap_news_publication_name', get_bloginfo('name'));
        ?>
        <input type="text" name="seo_fury_sitemap_news_publication_name"
               value="<?php echo esc_attr($name); ?>" class="regular-text" />
        <p class="description"><?php esc_html_e('Publication name as registered with Google News.', 'seo-fury'); ?></p>
        <?php
    }

    public function sitemap_news_language_callback() {
        $lang = get_option('seo_fury_sitemap_news_language', 'en');
        ?>
        <input type="text" name="seo_fury_sitemap_news_language"
               value="<?php echo esc_attr($lang); ?>" class="small-text" />
        <p class="description"><?php esc_html_e('ISO 639 language code (e.g. en, ru, de, fr).', 'seo-fury'); ?></p>
        <?php
    }

    // ── Video Sitemap callbacks ──

    public function sitemap_video_section_callback() {
        echo '<p>' . esc_html__('Configure Video sitemap. Automatically detects YouTube and Vimeo embeds in your content.', 'seo-fury') . '</p>';
    }

    public function sitemap_video_enabled_callback() {
        $enabled = get_option('seo_fury_sitemap_video_enabled', 0);
        ?>
        <label>
            <input type="checkbox" name="seo_fury_sitemap_video_enabled" value="1" <?php checked($enabled, 1); ?>>
            <?php esc_html_e('Enable Video Sitemap', 'seo-fury'); ?>
        </label>
        <p class="description"><?php esc_html_e('Generates /sitemap-videos.xml by scanning content for YouTube and Vimeo embeds.', 'seo-fury'); ?></p>
        <?php
    }

    public function sitemap_video_post_types_callback() {
        $selected = get_option('seo_fury_sitemap_video_post_types', array('post', 'page'));
        if (!is_array($selected)) $selected = array('post', 'page');

        $post_types = get_post_types(array('public' => true), 'objects');
        foreach ($post_types as $pt) {
            if ($pt->name === 'attachment') continue;
            ?>
            <label style="display: block; margin-bottom: 4px;">
                <input type="checkbox" name="seo_fury_sitemap_video_post_types[]"
                       value="<?php echo esc_attr($pt->name); ?>"
                       <?php checked(in_array($pt->name, $selected, true)); ?>>
                <?php echo esc_html($pt->label); ?>
            </label>
            <?php
        }
    }

    public function sitemap_google_ping_callback() {
        $enabled = get_option('seo_fury_sitemap_google_ping', 1);
        ?>
        <label>
            <input type="checkbox" name="seo_fury_sitemap_google_ping" value="1" <?php checked($enabled, 1); ?>>
            <?php esc_html_e('Ping Google when sitemap is updated', 'seo-fury'); ?>
        </label>
        <p class="description"><?php esc_html_e('Sends a notification to Google when content changes, prompting re-crawl of your sitemap.', 'seo-fury'); ?></p>
        <?php
    }

    public function sitemap_enabled_callback() {
        $enabled = get_option('seo_fury_sitemap_enabled', 0);
        ?>
        <label>
            <input type="checkbox" name="seo_fury_sitemap_enabled" value="1" <?php checked($enabled, 1); ?>>
            <?php esc_html_e('Enable XML sitemap generation', 'seo-fury'); ?>
        </label>
        <p class="description"><?php esc_html_e('Generates dynamic XML sitemap files for search engines.', 'seo-fury'); ?></p>
        <?php
    }

    public function sitemap_post_types_callback() {
        $selected = get_option('seo_fury_sitemap_post_types', array('post', 'page'));
        if (!is_array($selected)) {
            $selected = array();
        }

        $post_types_settings = get_option('seo_fury_sitemap_post_types_settings', array());
        if (!is_array($post_types_settings)) {
            $post_types_settings = array();
        }

        $post_types = get_post_types(array('public' => true), 'objects');

        echo '<p>' . esc_html__('Select post types to include in the XML sitemap and configure parameters for each type.', 'seo-fury') . '</p>';

        echo '<div class="seo-alert info" style="margin: 0 0 15px; max-width: 900px;">'
            . '<p style="margin: 0 0 6px;"><strong>' . esc_html__('About Priority and Changefreq', 'seo-fury') . '</strong></p>'
            . '<p style="margin: 0 0 6px;"><strong><code>Priority</code> (0.0 – 1.0)</strong> — '
            . esc_html__('a hint to search engines about the relative importance of URLs of this type within your site. Higher = more important. Typical values: homepage 1.0, main pages 0.8, posts 0.7, archive/tag pages 0.3–0.5.', 'seo-fury')
            . '</p>'
            . '<p style="margin: 0 0 6px;"><strong><code>Changefreq</code></strong> — '
            . esc_html__('how often content of this type typically changes (always / hourly / daily / weekly / monthly / yearly / never). Just a hint — not a directive.', 'seo-fury')
            . '</p>'
            . '<p style="margin: 0; color: var(--sf-gray-500, #646970);"><strong>' . esc_html__('Reality check:', 'seo-fury') . '</strong> '
            . esc_html__('Google has officially stated it ignores Priority and Changefreq — it crawls based on its own signals (PageRank, traffic, links). Bing and Yandex still use them as soft hints. Default values are usually fine — change only if you have a specific reason.', 'seo-fury')
            . '</p>'
            . '</div>';

        echo '<style>
            .sitemap-post-type-row {
                display: flex;
                align-items: center;
                margin-bottom: 10px;
                padding: 8px 0;
            }
            .sitemap-post-type-checkbox {
                flex: 0 0 200px;
            }
            .sitemap-post-type-settings {
                display: flex;
                gap: 20px;
                align-items: center;
            }
            .sitemap-setting-group {
                display: flex;
                align-items: center;
                gap: 5px;
            }
            .sitemap-setting-group label {
                font-weight: 500;
                font-size: 12px;
            }
        </style>';

        foreach ($post_types as $post_type) {
            if ($post_type->name === 'attachment') {
                continue;
            }

            $checked = in_array($post_type->name, $selected, true) ? 'checked' : '';

            $default_priority = '0.7';
            $default_changefreq = 'weekly';

            if ($post_type->name === 'page') {
                $default_priority = '0.8';
            }

            $priority = isset($post_types_settings[$post_type->name]['priority'])
                ? $post_types_settings[$post_type->name]['priority']
                : $default_priority;

            $changefreq = isset($post_types_settings[$post_type->name]['changefreq'])
                ? $post_types_settings[$post_type->name]['changefreq']
                : $default_changefreq;
            ?>
            <div class="sitemap-post-type-row">
                <div class="sitemap-post-type-checkbox">
                    <label>
                        <input type="checkbox"
                               name="seo_fury_sitemap_post_types[]"
                               value="<?php echo esc_attr($post_type->name); ?>"
                               <?php checked(in_array($post_type->name, $selected, true)); ?>>
                        <strong><?php echo esc_html($post_type->label); ?></strong> (<?php echo esc_html($post_type->name); ?>)
                    </label>
                </div>

                <div class="sitemap-post-type-settings">
                    <div class="sitemap-setting-group">
                        <label title="<?php esc_attr_e('Priority (0.0 – 1.0): hint about relative importance within your site. Higher = more important. Google ignores it; Bing/Yandex use it as a soft hint.', 'seo-fury'); ?>" style="cursor: help; border-bottom: 1px dotted var(--sf-gray-400, #646970);"><?php esc_html_e('Priority:', 'seo-fury'); ?></label>
                        <input type="number"
                               name="seo_fury_sitemap_post_types_settings[<?php echo esc_attr($post_type->name); ?>][priority]"
                               value="<?php echo esc_attr($priority); ?>"
                               min="0"
                               max="1"
                               step="0.1"
                               style="width: 80px;">
                    </div>

                    <div class="sitemap-setting-group">
                        <label title="<?php esc_attr_e('Changefreq: how often this type of content typically changes. Just a hint, not a directive — bots crawl based on their own signals.', 'seo-fury'); ?>" style="cursor: help; border-bottom: 1px dotted var(--sf-gray-400, #646970);"><?php esc_html_e('Changefreq:', 'seo-fury'); ?></label>
                        <select name="seo_fury_sitemap_post_types_settings[<?php echo esc_attr($post_type->name); ?>][changefreq]"
                                style="width: 120px;">
                            <option value="always" <?php selected($changefreq, 'always'); ?>><?php esc_html_e('Always', 'seo-fury'); ?></option>
                            <option value="hourly" <?php selected($changefreq, 'hourly'); ?>><?php esc_html_e('Hourly', 'seo-fury'); ?></option>
                            <option value="daily" <?php selected($changefreq, 'daily'); ?>><?php esc_html_e('Daily', 'seo-fury'); ?></option>
                            <option value="weekly" <?php selected($changefreq, 'weekly'); ?>><?php esc_html_e('Weekly', 'seo-fury'); ?></option>
                            <option value="monthly" <?php selected($changefreq, 'monthly'); ?>><?php esc_html_e('Monthly', 'seo-fury'); ?></option>
                            <option value="yearly" <?php selected($changefreq, 'yearly'); ?>><?php esc_html_e('Yearly', 'seo-fury'); ?></option>
                            <option value="never" <?php selected($changefreq, 'never'); ?>><?php esc_html_e('Never', 'seo-fury'); ?></option>
                        </select>
                    </div>
                </div>
            </div>
            <?php
        }
    }

    public function sitemap_taxonomies_callback() {
        $selected = get_option('seo_fury_sitemap_taxonomies', array());
        if (!is_array($selected)) {
            $selected = array();
        }

        $tax_settings = get_option('seo_fury_sitemap_taxonomies_settings', array());
        if (!is_array($tax_settings)) {
            $tax_settings = array();
        }

        $taxonomies = get_taxonomies(array('public' => true, 'show_ui' => true), 'objects');

        echo '<p>' . esc_html__('Select taxonomies to include in the XML sitemap (categories, tags, product categories, etc.).', 'seo-fury') . '</p>';

        foreach ($taxonomies as $taxonomy) {
            if ($taxonomy->name === 'post_format') {
                continue;
            }

            $checked = in_array($taxonomy->name, $selected, true) ? 'checked' : '';
            $priority = isset($tax_settings[$taxonomy->name]['priority'])
                ? $tax_settings[$taxonomy->name]['priority']
                : '0.4';
            $changefreq = isset($tax_settings[$taxonomy->name]['changefreq'])
                ? $tax_settings[$taxonomy->name]['changefreq']
                : 'weekly';
            ?>
            <div class="sitemap-post-type-row">
                <div class="sitemap-post-type-checkbox">
                    <label>
                        <input type="checkbox"
                               name="seo_fury_sitemap_taxonomies[]"
                               value="<?php echo esc_attr($taxonomy->name); ?>"
                               <?php checked(in_array($taxonomy->name, $selected, true)); ?>>
                        <strong><?php echo esc_html($taxonomy->label); ?></strong> (<?php echo esc_html($taxonomy->name); ?>)
                    </label>
                </div>

                <div class="sitemap-post-type-settings">
                    <div class="sitemap-setting-group">
                        <label title="<?php esc_attr_e('Priority (0.0 – 1.0): hint about relative importance within your site. Higher = more important. Google ignores it; Bing/Yandex use it as a soft hint.', 'seo-fury'); ?>" style="cursor: help; border-bottom: 1px dotted var(--sf-gray-400, #646970);"><?php esc_html_e('Priority:', 'seo-fury'); ?></label>
                        <input type="number"
                               name="seo_fury_sitemap_taxonomies_settings[<?php echo esc_attr($taxonomy->name); ?>][priority]"
                               value="<?php echo esc_attr($priority); ?>"
                               min="0"
                               max="1"
                               step="0.1"
                               style="width: 80px;">
                    </div>

                    <div class="sitemap-setting-group">
                        <label title="<?php esc_attr_e('Changefreq: how often this type of content typically changes. Just a hint, not a directive — bots crawl based on their own signals.', 'seo-fury'); ?>" style="cursor: help; border-bottom: 1px dotted var(--sf-gray-400, #646970);"><?php esc_html_e('Changefreq:', 'seo-fury'); ?></label>
                        <select name="seo_fury_sitemap_taxonomies_settings[<?php echo esc_attr($taxonomy->name); ?>][changefreq]"
                                style="width: 120px;">
                            <option value="always" <?php selected($changefreq, 'always'); ?>><?php esc_html_e('Always', 'seo-fury'); ?></option>
                            <option value="hourly" <?php selected($changefreq, 'hourly'); ?>><?php esc_html_e('Hourly', 'seo-fury'); ?></option>
                            <option value="daily" <?php selected($changefreq, 'daily'); ?>><?php esc_html_e('Daily', 'seo-fury'); ?></option>
                            <option value="weekly" <?php selected($changefreq, 'weekly'); ?>><?php esc_html_e('Weekly', 'seo-fury'); ?></option>
                            <option value="monthly" <?php selected($changefreq, 'monthly'); ?>><?php esc_html_e('Monthly', 'seo-fury'); ?></option>
                            <option value="yearly" <?php selected($changefreq, 'yearly'); ?>><?php esc_html_e('Yearly', 'seo-fury'); ?></option>
                            <option value="never" <?php selected($changefreq, 'never'); ?>><?php esc_html_e('Never', 'seo-fury'); ?></option>
                        </select>
                    </div>
                </div>
            </div>
            <?php
        }
    }

    public function sitemap_include_images_callback() {
        $include = get_option('seo_fury_sitemap_include_images', 0);
        ?>
        <label>
            <input type="checkbox" name="seo_fury_sitemap_include_images" value="1" <?php checked($include, 1); ?>>
            <?php esc_html_e('Include images in sitemap', 'seo-fury'); ?>
        </label>
        <?php
    }

    public function sitemap_exclude_urls_callback() {
        $exclude = get_option('seo_fury_sitemap_exclude_urls', '');
        ?>
        <textarea name="seo_fury_sitemap_exclude_urls" rows="5" class="large-text"><?php echo esc_textarea($exclude); ?></textarea>
        <p class="description"><?php esc_html_e('Full or relative URLs to exclude from the XML sitemap (one per line).', 'seo-fury'); ?></p>
        <?php
    }

    public function sitemap_additional_urls_callback() {
        $additional = get_option('seo_fury_sitemap_additional_urls', '');
        $default_priority = get_option('seo_fury_sitemap_additional_default_priority', '0.5');
        $default_changefreq = get_option('seo_fury_sitemap_additional_default_changefreq', 'weekly');
        $urls_data = get_option('seo_fury_sitemap_additional_urls_data', array());
        if (!is_array($urls_data)) $urls_data = array();

        $core = SEO_Fury_Core::get_instance();
        $pro = $core->get_pro_features();
        $is_pro = $pro && $pro->is_pro();
        $hide_branding = function_exists('seo_fury_hide_branding') ? seo_fury_hide_branding() : false;
        ?>

        <!-- Default settings for all additional URLs -->
        <div style="margin-bottom: 20px;">
            <p class="description" style="margin-bottom: 10px;"><?php esc_html_e('Default priority and change frequency for all additional URLs (unless overridden individually below).', 'seo-fury'); ?></p>
            <div style="display: flex; align-items: center; gap: 20px; flex-wrap: wrap;">
                <div class="sitemap-setting-group">
                    <label title="<?php esc_attr_e('Priority (0.0 – 1.0): hint about relative importance within your site. Higher = more important. Google ignores it; Bing/Yandex use it as a soft hint.', 'seo-fury'); ?>" style="cursor: help; border-bottom: 1px dotted var(--sf-gray-400, #646970);"><?php esc_html_e('Default Priority:', 'seo-fury'); ?></label>
                    <input type="number"
                           name="seo_fury_sitemap_additional_default_priority"
                           value="<?php echo esc_attr($default_priority); ?>"
                           min="0" max="1" step="0.1"
                           style="width: 80px;">
                </div>
                <div class="sitemap-setting-group">
                    <label title="<?php esc_attr_e('Changefreq: how often this type of content typically changes. Just a hint, not a directive — bots crawl based on their own signals.', 'seo-fury'); ?>" style="cursor: help; border-bottom: 1px dotted var(--sf-gray-400, #646970);"><?php esc_html_e('Default Changefreq:', 'seo-fury'); ?></label>
                    <select name="seo_fury_sitemap_additional_default_changefreq" style="width: 120px;">
                        <option value="always" <?php selected($default_changefreq, 'always'); ?>><?php esc_html_e('Always', 'seo-fury'); ?></option>
                        <option value="hourly" <?php selected($default_changefreq, 'hourly'); ?>><?php esc_html_e('Hourly', 'seo-fury'); ?></option>
                        <option value="daily" <?php selected($default_changefreq, 'daily'); ?>><?php esc_html_e('Daily', 'seo-fury'); ?></option>
                        <option value="weekly" <?php selected($default_changefreq, 'weekly'); ?>><?php esc_html_e('Weekly', 'seo-fury'); ?></option>
                        <option value="monthly" <?php selected($default_changefreq, 'monthly'); ?>><?php esc_html_e('Monthly', 'seo-fury'); ?></option>
                        <option value="yearly" <?php selected($default_changefreq, 'yearly'); ?>><?php esc_html_e('Yearly', 'seo-fury'); ?></option>
                        <option value="never" <?php selected($default_changefreq, 'never'); ?>><?php esc_html_e('Never', 'seo-fury'); ?></option>
                    </select>
                </div>
            </div>
        </div>

        <!-- Simple textarea for basic URLs (FREE) -->
        <textarea name="seo_fury_sitemap_additional_urls" rows="5" class="large-text"><?php echo esc_textarea($additional); ?></textarea>
        <p class="description"><?php esc_html_e('Additional URLs to add to the XML sitemap (one per line). These will use the default priority and changefreq above.', 'seo-fury'); ?></p>

        <!-- Per-URL settings table (PRO) -->
        <div style="margin-top: 25px; padding-top: 20px; border-top: 1px solid var(--sf-gray-200, #e2e8f0);">
            <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
                <h4 style="margin: 0; font-size: 14px;">
                    <?php esc_html_e('Per-URL Priority & Changefreq', 'seo-fury'); ?>
                    <?php if (!$is_pro): ?>
                        <span style="background: linear-gradient(135deg, #4F46E5, #7C3AED); color: #fff; font-size: 10px; padding: 2px 8px; border-radius: 10px; margin-left: 8px; font-weight: 600;">PRO</span>
                    <?php endif; ?>
                </h4>
                <?php if ($is_pro): ?>
                    <button type="button" id="sf-add-additional-url" class="button button-small" style="margin-left: auto;"><?php esc_html_e('+ Add URL', 'seo-fury'); ?></button>
                <?php endif; ?>
            </div>

            <?php if (!$is_pro): ?>
                <div style="background: var(--sf-gray-50, #f8fafc); border: 1px dashed var(--sf-gray-200, #cbd5e1); border-radius: 8px; padding: 20px; text-align: center; color: var(--sf-gray-500, #64748b);">
                    <p style="margin: 0 0 8px; font-size: 14px;"><strong><?php esc_html_e('Override priority & changefreq per URL', 'seo-fury'); ?></strong></p>
                    <p style="margin: 0 0 12px; font-size: 13px;"><?php esc_html_e('Upgrade to PRO to set individual priority and change frequency for each additional URL in your sitemap.', 'seo-fury'); ?></p>
                    <?php if (!$hide_branding): ?>
                        <a href="https://seo-fury.com/#pricing" target="_blank" class="button button-primary button-small"><?php esc_html_e('Upgrade to PRO', 'seo-fury'); ?></a>
                    <?php endif; ?>
                </div>
            <?php else: ?>
                <table class="wp-list-table widefat fixed striped" id="sf-additional-urls-table" style="margin-top: 5px;">
                    <thead>
                        <tr>
                            <th style="width: 45%; text-align: center;"><?php esc_html_e('URL', 'seo-fury'); ?></th>
                            <th style="width: 15%; text-align: center;"><?php esc_html_e('Priority', 'seo-fury'); ?></th>
                            <th style="width: 20%; text-align: center;"><?php esc_html_e('Changefreq', 'seo-fury'); ?></th>
                            <th style="width: 20%; text-align: center;"><?php esc_html_e('Actions', 'seo-fury'); ?></th>
                        </tr>
                    </thead>
                    <tbody id="sf-additional-urls-body">
                        <?php if (!empty($urls_data)): ?>
                            <?php foreach ($urls_data as $i => $row): ?>
                                <?php // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- All dynamic values below use esc_attr(). ?>
                                <tr data-index="<?php echo esc_attr( $i ); ?>">
                                    <td>
                                        <input type="url" name="seo_fury_sitemap_additional_urls_data[<?php echo esc_attr( $i ); ?>][url]"
                                               value="<?php echo esc_attr($row['url'] ?? ''); ?>"
                                               class="regular-text" style="width:100%;" placeholder="https://" required>
                                    </td>
                                    <td>
                                        <input type="number" name="seo_fury_sitemap_additional_urls_data[<?php echo esc_attr( $i ); ?>][priority]"
                                               value="<?php echo esc_attr($row['priority'] ?? '0.5'); ?>"
                                               min="0" max="1" step="0.1" style="width: 70px;">
                                    </td>
                                    <td>
                                        <select name="seo_fury_sitemap_additional_urls_data[<?php echo esc_attr( $i ); ?>][changefreq]" style="width: 110px;">
                                            <?php
                                            $freqs = array(
                                                'always'  => __('Always', 'seo-fury'),
                                                'hourly'  => __('Hourly', 'seo-fury'),
                                                'daily'   => __('Daily', 'seo-fury'),
                                                'weekly'  => __('Weekly', 'seo-fury'),
                                                'monthly' => __('Monthly', 'seo-fury'),
                                                'yearly'  => __('Yearly', 'seo-fury'),
                                                'never'   => __('Never', 'seo-fury'),
                                            );
                                            $cur_freq = $row['changefreq'] ?? 'weekly';
                                            foreach ($freqs as $f => $f_label):
                                            ?>
                                                <option value="<?php echo esc_attr($f); ?>" <?php selected($cur_freq, $f); ?>><?php echo esc_html($f_label); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </td>
                                    <td>
                                        <button type="button" class="button button-small sf-remove-url-row" style="color:#d63638;"><?php esc_html_e('Remove', 'seo-fury'); ?></button>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                        <?php // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped ?>
                        <?php else: ?>
                            <tr id="sf-no-urls-row">
                                <td colspan="4" style="text-align:center; color:var(--sf-gray-400, #94a3b8); padding:15px;"><?php esc_html_e('No per-URL overrides yet. Click "+ Add URL" to add one.', 'seo-fury'); ?></td>
                            </tr>
                        <?php endif; ?>
                    </tbody>
                </table>

                <script>
                (function(){
                    var idx = <?php echo (int) ( !empty($urls_data) ? max(array_keys($urls_data)) + 1 : 0 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Integer cast is safe. ?>;
                    var freqs = ['always','hourly','daily','weekly','monthly','yearly','never'];
                    var removeLabel = <?php echo wp_json_encode(__('Remove', 'seo-fury')); ?>;
                    var emptyMessage = <?php echo wp_json_encode(__('No per-URL overrides yet. Click "+ Add URL" to add one.', 'seo-fury')); ?>;

                    document.getElementById('sf-add-additional-url').addEventListener('click', function(){
                        var tbody = document.getElementById('sf-additional-urls-body');
                        var noRow = document.getElementById('sf-no-urls-row');
                        if (noRow) noRow.remove();

                        var tr = document.createElement('tr');
                        tr.setAttribute('data-index', idx);

                        var optsHtml = '';
                        for (var fi = 0; fi < freqs.length; fi++) {
                            var sel = (freqs[fi] === 'weekly') ? ' selected' : '';
                            optsHtml += '<option value="' + freqs[fi] + '"' + sel + '>' + freqs[fi].charAt(0).toUpperCase() + freqs[fi].slice(1) + '</option>';
                        }

                        tr.innerHTML =
                            '<td><input type="url" name="seo_fury_sitemap_additional_urls_data[' + idx + '][url]" class="regular-text" style="width:100%;" placeholder="https://" required></td>' +
                            '<td><input type="number" name="seo_fury_sitemap_additional_urls_data[' + idx + '][priority]" value="0.5" min="0" max="1" step="0.1" style="width:70px;"></td>' +
                            '<td><select name="seo_fury_sitemap_additional_urls_data[' + idx + '][changefreq]" style="width:110px;">' + optsHtml + '</select></td>' +
                            '<td><button type="button" class="button button-small sf-remove-url-row" style="color:#d63638;">' + removeLabel + '</button></td>';

                        tbody.appendChild(tr);
                        idx++;
                    });

                    document.addEventListener('click', function(e){
                        if (e.target.classList.contains('sf-remove-url-row')) {
                            var row = e.target.closest('tr');
                            if (row) row.remove();
                            var tbody = document.getElementById('sf-additional-urls-body');
                            if (tbody && tbody.querySelectorAll('tr').length === 0) {
                                var emptyTr = document.createElement('tr');
                                emptyTr.id = 'sf-no-urls-row';
                                emptyTr.innerHTML = '<td colspan="4" style="text-align:center;color:var(--sf-gray-400, #94a3b8);padding:15px;">' + emptyMessage + '</td>';
                                tbody.appendChild(emptyTr);
                            }
                        }
                    });
                })();
                </script>
            <?php endif; ?>
        </div>
        <?php
    }

    public function sanitize_additional_urls_data($input) {
        if (!is_array($input)) return array();
        $clean = array();
        foreach ($input as $row) {
            $url = isset($row['url']) ? esc_url_raw(trim($row['url'])) : '';
            if (empty($url)) continue;
            $priority = isset($row['priority']) ? floatval($row['priority']) : 0.5;
            $priority = max(0, min(1, $priority));
            $valid_freqs = array('always','hourly','daily','weekly','monthly','yearly','never');
            $changefreq = (isset($row['changefreq']) && in_array($row['changefreq'], $valid_freqs)) ? $row['changefreq'] : 'weekly';
            $clean[] = array(
                'url' => $url,
                'priority' => round($priority, 1),
                'changefreq' => $changefreq,
            );
        }
        return $clean;
    }

    /**
     * Sanitize array settings (post types, taxonomies checkboxes, nested settings).
     */
    public function sanitize_array_setting($input) {
        if (!is_array($input)) {
            return array();
        }

        return $this->sanitize_array_recursive($input);
    }

    /**
     * Recursively sanitize an array of settings.
     */
    private function sanitize_array_recursive($input) {
        $sanitized = array();
        foreach ($input as $key => $value) {
            $key = sanitize_text_field($key);
            if (is_array($value)) {
                $sanitized[$key] = $this->sanitize_array_recursive($value);
            } else {
                $sanitized[$key] = sanitize_text_field($value);
            }
        }
        return $sanitized;
    }

    public function html_sitemap_slug_callback() {
        $slug = get_option('seo_fury_html_sitemap_slug', 'html-sitemap');
        ?>
        <input type="text" name="seo_fury_html_sitemap_slug" value="<?php echo esc_attr($slug); ?>" class="regular-text">
        <span class="description" style="margin-left: 10px;"><?php esc_html_e('HTML sitemap slug (e.g., html-sitemap)', 'seo-fury'); ?></span>
        <?php
    }

    public function html_sitemap_post_types_callback() {
        $selected = get_option('seo_fury_html_sitemap_post_types', array('post', 'page'));
        if (!is_array($selected)) {
            $selected = array();
        }

        $post_types = get_post_types(array('public' => true), 'objects');

        foreach ($post_types as $post_type) {
            if ($post_type->name === 'attachment') {
                continue;
            }

            $checked = in_array($post_type->name, $selected, true) ? 'checked' : '';
            ?>
            <label style="display: block; margin-bottom: 5px;">
                <input type="checkbox" name="seo_fury_html_sitemap_post_types[]" value="<?php echo esc_attr($post_type->name); ?>" <?php checked(in_array($post_type->name, $selected, true)); ?>>
                <?php echo esc_html($post_type->label); ?>
            </label>
            <?php
        }

        echo '<p class="description" style="margin-top: 10px;">' . esc_html__('Select which post types to show in the HTML sitemap.', 'seo-fury') . '</p>';
    }

    public function register_sitemap_rewrite() {
        add_rewrite_rule(
            '^sitemap\.xml$',
            'index.php?seo_fury_sitemap=index',
            'top'
        );

        add_rewrite_rule(
            '^sitemap-([a-z0-9_-]+)\.xml$',
            'index.php?seo_fury_sitemap=$matches[1]',
            'top'
        );

        $html_slug = get_option('seo_fury_html_sitemap_slug', 'html-sitemap');
        add_rewrite_rule(
            '^' . $html_slug . '/?$',
            'index.php?seo_fury_sitemap=html',
            'top'
        );
    }

public function register_xsl_rewrite() {
    add_rewrite_rule(
        '^sitemap\.xsl$',
        'index.php?seo_fury_sitemap_xsl=1',
        'top'
    );

    add_filter('query_vars', array($this, 'add_xsl_query_var'));

    add_action('template_redirect', array($this, 'maybe_serve_xsl'));
}

    public function maybe_serve_xsl() {
        if (get_query_var('seo_fury_sitemap_xsl')) {
            header('Content-Type: text/xsl; charset=utf-8');
            header('X-Robots-Tag: noindex, follow');
            echo $this->get_xsl_stylesheet(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- XSL stylesheet built with escaped values.
            exit;
        }
    }

public function add_xsl_query_var($vars) {
    if (wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
        return $vars;
    }

    $vars[] = 'seo_fury_sitemap_xsl';
    return $vars;
}

public function add_sitemap_query_var($vars)
{
    if ($this->is_rest_request()) {
        return $vars;
    }

    $vars[] = 'seo_fury_sitemap';
    return $vars;
}

private function is_rest_request()
{
    if (defined('REST_REQUEST') && REST_REQUEST) {
        return true;
    }

    if (isset($_SERVER['REQUEST_URI']) && strpos(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])), '/wp-json/') !== false) {
        return true;
    }

    return false;
}

    public function maybe_generate_sitemaps() {
        $sitemap_type = get_query_var('seo_fury_sitemap');

        if ($sitemap_type) {
            if ($sitemap_type === 'index') {
                $this->generate_sitemap_index();
                exit;
            } elseif ($sitemap_type === 'html') {
                $this->generate_html_sitemap();
                exit;
            } elseif ($sitemap_type === 'news') {
                $this->generate_news_sitemap();
                exit;
            } elseif ($sitemap_type === 'videos') {
                $this->generate_video_sitemap();
                exit;
            } else {
                // Post-type sitemap (optionally paginated) or taxonomy ("tax-")
                $this->dispatch_type_sitemap($sitemap_type);
                exit;
            }
        }
    }

    /**
     * Max URLs per individual sitemap file before it is paginated. The
     * sitemaps protocol allows 50,000; we keep files small for fast delivery
     * and low memory. Filterable for power users.
     */
    private function get_urls_per_page() {
        $per = (int) apply_filters('seo_fury_sitemap_urls_per_page', 1000);
        return $per > 0 ? $per : 1000;
    }

    /**
     * Shared meta_query that excludes per-page noindexed posts. Used by both
     * the URL generator and the count used to paginate the index.
     */
    private function get_sitemap_post_meta_query() {
        return array(
            'relation' => 'OR',
            array(
                'key'     => '_seo_fury_page_robots_noindex',
                'compare' => 'NOT EXISTS',
            ),
            array(
                'key'     => '_seo_fury_page_robots_noindex',
                'value'   => '1',
                'compare' => '!=',
            ),
        );
    }

    /**
     * Total number of indexable published posts of a type (mirrors the
     * generator's filters) — one lightweight query via found_posts.
     */
    private function count_sitemap_posts($post_type) {
        $q = new WP_Query(array(
            'post_type'              => $post_type,
            'post_status'            => 'publish',
            'posts_per_page'         => 1,
            'fields'                 => 'ids',
            'no_found_rows'          => false,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'meta_query'             => $this->get_sitemap_post_meta_query(),
        ));
        return (int) $q->found_posts;
    }

    /**
     * Parse a "{type}" or "{type}-{page}" or "tax-{taxonomy}" token and
     * dispatch to the right generator. Centralised so the rewrite path and the
     * REQUEST_URI fallback can't drift apart. A trailing -N is treated as a
     * page number only when the remainder is an enabled post type (otherwise a
     * CPT slug ending in a number would be mis-parsed).
     */
    private function dispatch_type_sitemap($token) {
        if (strpos($token, 'tax-') === 0) {
            $tax_token = substr($token, 4);
            $tax  = $tax_token;
            $page = 1;
            if (preg_match('/^(.+)-(\d+)$/', $tax_token, $m)) {
                $allowed_tax = get_option('seo_fury_sitemap_taxonomies', array());
                if (is_array($allowed_tax) && in_array($m[1], $allowed_tax, true)) {
                    $tax  = $m[1];
                    $page = max(1, (int) $m[2]);
                }
            }
            $this->generate_xml_sitemap_by_taxonomy($tax, $page);
            return;
        }

        $type = $token;
        $page = 1;
        if (preg_match('/^(.+)-(\d+)$/', $token, $m)) {
            $allowed = get_option('seo_fury_sitemap_post_types', array('post', 'page'));
            if (is_array($allowed) && in_array($m[1], $allowed, true)) {
                $type = $m[1];
                $page = max(1, (int) $m[2]);
            }
        }
        $this->generate_xml_sitemap_by_type($type, $page);
    }

    public function generate_sitemap_index() {
        $enabled = get_option('seo_fury_sitemap_enabled', 0);
        if (!$enabled) {
            status_header(404);
            return;
        }

        $post_types = get_option('seo_fury_sitemap_post_types', array('post', 'page'));
        if (!is_array($post_types) || empty($post_types)) {
            status_header(404);
            return;
        }

        header('Content-Type: application/xml; charset=utf-8');
        header('X-Robots-Tag: noindex, follow');
        header('Cache-Control: public, max-age=3600');

        // Check transient cache
        $cache_key = 'index';
        $cached = get_transient('seo_fury_sitemap_' . $cache_key);
        if ($cached) {
            echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Cached XML sitemap built with escaped values.
            exit;
        }

        $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
        $xml .= '<?xml-stylesheet type="text/xsl" href="' . esc_url( home_url('/sitemap.xsl') ) . '"?>' . "\n";
        $xml .= '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";

        foreach ($post_types as $post_type) {
            $post_type_obj = get_post_type_object($post_type);
            if (!$post_type_obj) {
                continue;
            }

            // Don't include noindex post types in sitemap index
            if (get_option('seo_fury_pt_robots_noindex_' . $post_type, 0)) {
                continue;
            }

            $latest_post = get_posts(array(
                'post_type' => $post_type,
                'post_status' => 'publish',
                'posts_per_page' => 1,
                'orderby' => 'modified',
                'order' => 'DESC'
            ));

            $lastmod = !empty($latest_post) ? get_post_modified_time('c', false, $latest_post[0]) : '';

            // Paginate: one <sitemap> entry per page of URLs. Page 1 keeps the
            // legacy URL (sitemap-{type}.xml); page 2+ adds a -N suffix.
            $per_page = $this->get_urls_per_page();
            $total    = $this->count_sitemap_posts($post_type);
            $pages    = max(1, (int) ceil($total / $per_page));

            for ($p = 1; $p <= $pages; $p++) {
                $loc = ($p === 1)
                    ? home_url("/sitemap-{$post_type}.xml")
                    : home_url("/sitemap-{$post_type}-{$p}.xml");
                $xml .= '  <sitemap>' . "\n";
                $xml .= '    <loc>' . esc_url($loc) . '</loc>' . "\n";
                if ($lastmod) {
                    $xml .= '    <lastmod>' . esc_html($lastmod) . '</lastmod>' . "\n";
                }
                $xml .= '  </sitemap>' . "\n";
            }
        }

        // Taxonomies
        $taxonomies = get_option('seo_fury_sitemap_taxonomies', array());
        if (is_array($taxonomies) && !empty($taxonomies)) {
            foreach ($taxonomies as $taxonomy) {
                $tax_obj = get_taxonomy($taxonomy);
                if (!$tax_obj) {
                    continue;
                }

                $terms = get_terms(array(
                    'taxonomy' => $taxonomy,
                    'hide_empty' => true,
                    'number' => 1,
                    'orderby' => 'count',
                    'order' => 'DESC',
                ));

                $lastmod = '';
                if (!empty($terms) && !is_wp_error($terms)) {
                    $latest_in_tax = get_posts(array(
                        'post_type' => 'any',
                        'post_status' => 'publish',
                        'posts_per_page' => 1,
                        'orderby' => 'modified',
                        'order' => 'DESC',
                        'tax_query' => array(
                            array(
                                'taxonomy' => $taxonomy,
                                'terms' => $terms[0]->term_id,
                            ),
                        ),
                    ));
                    if (!empty($latest_in_tax)) {
                        $lastmod = get_post_modified_time('c', false, $latest_in_tax[0]);
                    }
                }

                // Paginate taxonomy terms beyond the per-file limit, same as
                // post-type sitemaps. Page 1 keeps the legacy URL.
                $per_page = $this->get_urls_per_page();
                $term_total = get_terms(array(
                    'taxonomy'   => $taxonomy,
                    'hide_empty' => true,
                    'fields'     => 'count',
                ));
                $term_total = is_wp_error($term_total) ? 0 : (int) $term_total;
                $tax_pages  = max(1, (int) ceil($term_total / $per_page));

                for ($tp = 1; $tp <= $tax_pages; $tp++) {
                    $loc = ($tp === 1)
                        ? home_url("/sitemap-tax-{$taxonomy}.xml")
                        : home_url("/sitemap-tax-{$taxonomy}-{$tp}.xml");
                    $xml .= '  <sitemap>' . "\n";
                    $xml .= '    <loc>' . esc_url($loc) . '</loc>' . "\n";
                    if ($lastmod) {
                        $xml .= '    <lastmod>' . esc_html($lastmod) . '</lastmod>' . "\n";
                    }
                    $xml .= '  </sitemap>' . "\n";
                }
            }
        }

        // News sitemap
        if (get_option('seo_fury_sitemap_news_enabled', 0)) {
            $xml .= '  <sitemap>' . "\n";
            $xml .= '    <loc>' . esc_url(home_url('/sitemap-news.xml')) . '</loc>' . "\n";
            $xml .= '    <lastmod>' . esc_html( wp_date('c') ) . '</lastmod>' . "\n";
            $xml .= '  </sitemap>' . "\n";
        }

        // Video sitemap
        if (get_option('seo_fury_sitemap_video_enabled', 0)) {
            $xml .= '  <sitemap>' . "\n";
            $xml .= '    <loc>' . esc_url(home_url('/sitemap-videos.xml')) . '</loc>' . "\n";
            $xml .= '  </sitemap>' . "\n";
        }

        $xml .= '</sitemapindex>';

        set_transient('seo_fury_sitemap_' . $cache_key, $xml, HOUR_IN_SECONDS);

        echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- XML sitemap index built with escaped values.
        exit;
    }

    public function generate_xml_sitemap_by_type($post_type, $page = 1) {
        $enabled = get_option('seo_fury_sitemap_enabled', 0);
        if (!$enabled) {
            status_header(404);
            return;
        }

        $allowed_types = get_option('seo_fury_sitemap_post_types', array('post', 'page'));
        if (!in_array($post_type, $allowed_types, true)) {
            status_header(404);
            return;
        }

        // Skip entire post type if marked as noindex — no sitemap for noindex types
        if (get_option('seo_fury_pt_robots_noindex_' . $post_type, 0)) {
            status_header(404);
            return;
        }

        $page           = max(1, (int) $page);
        $per_page       = $this->get_urls_per_page();
        $exclude_urls   = $this->get_exclude_urls();
        $include_images = get_option('seo_fury_sitemap_include_images', 0);

        $args = array(
            'post_type' => $post_type,
            'post_status' => 'publish',
            'posts_per_page' => $per_page,
            'offset' => ($page - 1) * $per_page,
            'orderby' => 'modified',
            'order' => 'DESC',
            'no_found_rows' => true,
            'update_post_meta_cache' => true,
            'update_post_term_cache' => false,
            'meta_query' => $this->get_sitemap_post_meta_query(),
        );

        header('Content-Type: application/xml; charset=utf-8');
        header('X-Robots-Tag: noindex, follow');
        header('Cache-Control: public, max-age=3600');

        // Check transient cache
        $cache_key = 'type_' . $post_type . ($page > 1 ? '_p' . $page : '');
        $cached = get_transient('seo_fury_sitemap_' . $cache_key);
        if ($cached) {
            echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Cached XML sitemap built with escaped values.
            exit;
        }

        $posts = get_posts($args);

        $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
        $xml .= '<?xml-stylesheet type="text/xsl" href="' . esc_url( home_url('/sitemap.xsl') . '?type=' . urlencode($post_type) ) . '"?>' . "\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";

        $default_priority = $this->get_priority($post_type);
        $changefreq = $this->get_changefreq($post_type);
        $front_page_id = (int) get_option('page_on_front');
        $home_url_slash = trailingslashit(home_url('/'));

        foreach ($posts as $post) {
            $permalink = get_permalink($post);

            if (in_array($permalink, $exclude_urls, true)) {
                continue;
            }

            $lastmod = get_post_modified_time('c', false, $post);

            // Homepage always gets highest priority
            $priority = $default_priority;
            if ($front_page_id && $post->ID === $front_page_id) {
                $priority = '1.0';
            } elseif (!$front_page_id && trailingslashit($permalink) === $home_url_slash) {
                $priority = '1.0';
            }

            $xml .= '  <url>' . "\n";
            $xml .= '    <loc>' . esc_url($permalink) . '</loc>' . "\n";
            $xml .= '    <lastmod>' . esc_html($lastmod) . '</lastmod>' . "\n";
            $xml .= '    <changefreq>' . esc_html($changefreq) . '</changefreq>' . "\n";
            $xml .= '    <priority>' . esc_html($priority) . '</priority>' . "\n";

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

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

        if ($post_type === 'page' && $page === 1) {
            $additional_urls = $this->get_additional_urls();
            foreach ($additional_urls as $url_entry) {
                $xml .= '  <url>' . "\n";
                $xml .= '    <loc>' . esc_url($url_entry['url']) . '</loc>' . "\n";
                $xml .= '    <changefreq>' . esc_html($url_entry['changefreq']) . '</changefreq>' . "\n";
                $xml .= '    <priority>' . esc_html($url_entry['priority']) . '</priority>' . "\n";
                $xml .= '  </url>' . "\n";
            }
        }

        $xml .= '</urlset>';

        set_transient('seo_fury_sitemap_' . $cache_key, $xml, HOUR_IN_SECONDS);

        echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- XML sitemap built with escaped values.
        exit;
    }

    /**
     * Генерация XML карты сайта для таксономии (категории, метки, и т.д.)
     */
    public function generate_xml_sitemap_by_taxonomy($taxonomy, $page = 1) {
        $enabled = get_option('seo_fury_sitemap_enabled', 0);
        if (!$enabled) {
            status_header(404);
            return;
        }

        $allowed_taxonomies = get_option('seo_fury_sitemap_taxonomies', array());
        if (!is_array($allowed_taxonomies) || !in_array($taxonomy, $allowed_taxonomies, true)) {
            status_header(404);
            return;
        }

        $tax_obj = get_taxonomy($taxonomy);
        if (!$tax_obj) {
            status_header(404);
            return;
        }

        $exclude_urls = $this->get_exclude_urls();

        $page     = max(1, (int) $page);
        $per_page = $this->get_urls_per_page();

        $terms = get_terms(array(
            'taxonomy' => $taxonomy,
            'hide_empty' => true,
            'number' => $per_page,
            'offset' => ($page - 1) * $per_page,
        ));

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

        $tax_settings = get_option('seo_fury_sitemap_taxonomies_settings', array());
        $priority = isset($tax_settings[$taxonomy]['priority']) ? $tax_settings[$taxonomy]['priority'] : '0.4';
        $changefreq = isset($tax_settings[$taxonomy]['changefreq']) ? $tax_settings[$taxonomy]['changefreq'] : 'weekly';

        header('Content-Type: application/xml; charset=utf-8');
        header('X-Robots-Tag: noindex, follow');
        header('Cache-Control: public, max-age=3600');

        // Check transient cache
        $cache_key = 'tax_' . $taxonomy . ($page > 1 ? '_p' . $page : '');
        $cached = get_transient('seo_fury_sitemap_' . $cache_key);
        if ($cached) {
            echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Cached taxonomy sitemap built with escaped values.
            exit;
        }

        $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
        $xml .= '<?xml-stylesheet type="text/xsl" href="' . esc_url( home_url('/sitemap.xsl') . '?type=tax-' . urlencode($taxonomy) ) . '"?>' . "\n";
        $xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";

        // Batch query: get latest modified date per term
        $term_dates = array();
        global $wpdb;
        $term_ids = wp_list_pluck($terms, 'term_id');
        if (!empty($term_ids)) {
            $placeholders = implode(',', array_fill(0, count($term_ids), '%d'));
            $query = $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $placeholders is a safe string of '%d' tokens built via array_fill.
                "SELECT tt.term_id, MAX(p.post_modified_gmt) as last_modified
                 FROM {$wpdb->term_relationships} tr
                 JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
                 JOIN {$wpdb->posts} p ON tr.object_id = p.ID
                 WHERE tt.term_id IN ($placeholders) AND p.post_status = 'publish'
                 GROUP BY tt.term_id",
                ...$term_ids
            );
            $term_dates = $wpdb->get_results($query, OBJECT_K); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared above.
        }

        foreach ($terms as $term) {
            $term_link = get_term_link($term);
            if (is_wp_error($term_link)) {
                continue;
            }

            if (in_array($term_link, $exclude_urls, true)) {
                continue;
            }

            $xml .= '  <url>' . "\n";
            $xml .= '    <loc>' . esc_url($term_link) . '</loc>' . "\n";

            $lastmod = isset($term_dates[$term->term_id]) ? $term_dates[$term->term_id]->last_modified : '';
            if ($lastmod) {
                $xml .= '    <lastmod>' . esc_html( mysql2date('c', $lastmod) ) . '</lastmod>' . "\n";
            }

            $xml .= '    <changefreq>' . esc_html($changefreq) . '</changefreq>' . "\n";
            $xml .= '    <priority>' . esc_html($priority) . '</priority>' . "\n";
            $xml .= '  </url>' . "\n";
        }

        $xml .= '</urlset>';

        set_transient('seo_fury_sitemap_' . $cache_key, $xml, HOUR_IN_SECONDS);

        echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Taxonomy sitemap built with escaped values.
        exit;
    }

    /**
     * Invalidate all sitemap transient caches.
     */
    public function invalidate_sitemap_cache() {
        global $wpdb;
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
                $wpdb->esc_like('_transient_seo_fury_sitemap_') . '%',
                $wpdb->esc_like('_transient_timeout_seo_fury_sitemap_') . '%'
            )
        );

        // NOTE: Google and Bing both retired their sitemap "ping" endpoints in
        // 2023 (https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping)
        // — the old https://www.google.com/ping URL now just 404s. Pinging it
        // wasted an outbound request on every content save, so it was removed.
        // Search engines discover the sitemap via robots.txt and recrawl on
        // their own schedule.
    }

    private function get_xsl_stylesheet() {
        $home_url = esc_url(home_url('/sitemap.xml'));
        $brand_name = function_exists('seo_fury_get_brand_name') ? seo_fury_get_brand_name() : 'SEO Fury';
        $hide_branding = function_exists('seo_fury_hide_branding') ? seo_fury_hide_branding() : false;

        $post_type = isset($_GET['type']) ? sanitize_text_field($_GET['type']) : '';
        $post_type_label = '';
        if ($post_type) {
            if (strpos($post_type, 'tax-') === 0) {
                $tax_name = substr($post_type, 4);
                $tax_obj = get_taxonomy($tax_name);
                if ($tax_obj) {
                    $post_type_label = $tax_obj->label;
                } else {
                    $post_type_label = ucfirst($tax_name);
                }
            } else {
                $post_type_obj = get_post_type_object($post_type);
                if ($post_type_obj) {
                    $post_type_label = $post_type_obj->label;
                } else {
                    $post_type_label = ucfirst($post_type);
                }
            }
        }

        $badge_html = '';

        $sub_label_title = $post_type_label ? esc_html($post_type_label) : esc_html__('URLs', 'seo-fury');

        return '<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
    xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">

    <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:template match="/">
        <html xmlns="http://www.w3.org/1999/xhtml">
            <head>
                <title>' . esc_html__('XML Sitemap', 'seo-fury') . '</title>
                <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
                <meta name="robots" content="noindex, follow"/>
                <style type="text/css">
                    * { margin: 0; padding: 0; box-sizing: border-box; }
                    body {
                        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                        font-size: 14px;
                        color: var(--sf-gray-900, #1e293b);
                        background: linear-gradient(135deg, #f0f4ff 0%, #faf5ff 50%, #fef2f2 100%);
                        min-height: 100vh;
                        padding: 32px 16px;
                    }
                    .sf-wrap {
                        max-width: 1100px;
                        margin: 0 auto;
                    }
                    .sf-header {
                        display: flex;
                        align-items: center;
                        justify-content: space-between;
                        margin-bottom: 24px;
                        flex-wrap: wrap;
                        gap: 12px;
                    }
                    .sf-header-left { display: flex; flex-direction: column; gap: 4px; }
                    h1 {
                        font-size: 26px;
                        font-weight: 700;
                        color: var(--sf-gray-900, #0f172a);
                        letter-spacing: -0.3px;
                    }
                    .sf-subtitle {
                        font-size: 14px;
                        color: var(--sf-gray-500, #64748b);
                        line-height: 1.5;
                        margin-top: 2px;
                    }
                    .sf-back {
                        display: inline-flex;
                        align-items: center;
                        gap: 6px;
                        color: #6366f1;
                        text-decoration: none;
                        font-weight: 500;
                        font-size: 13px;
                        padding: 6px 14px;
                        border-radius: 8px;
                        background: rgba(99, 102, 241, 0.08);
                        transition: all 0.15s;
                        margin-bottom: 16px;
                    }
                    .sf-back:hover { background: rgba(99, 102, 241, 0.15); text-decoration: none; }
                    .sf-stats {
                        display: flex;
                        gap: 16px;
                        margin-bottom: 20px;
                        flex-wrap: wrap;
                    }
                    .sf-stat-card {
                        background: var(--sf-white, #fff);
                        border: 1px solid var(--sf-gray-200, #e2e8f0);
                        border-radius: 12px;
                        padding: 16px 24px;
                        display: flex;
                        align-items: center;
                        gap: 12px;
                        box-shadow: 0 1px 3px rgba(0,0,0,0.04);
                    }
                    .sf-stat-icon {
                        width: 40px;
                        height: 40px;
                        border-radius: 10px;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        flex-shrink: 0;
                    }
                    .sf-stat-icon.purple { background: linear-gradient(135deg, #ede9fe, #ddd6fe); color: #7c3aed; }
                    .sf-stat-icon.blue { background: linear-gradient(135deg, #dbeafe, #bfdbfe); color: #2563eb; }
                    .sf-stat-icon.green { background: linear-gradient(135deg, #dcfce7, #bbf7d0); color: #16a34a; }
                    .sf-stat-number { font-size: 22px; font-weight: 700; color: var(--sf-gray-900, #0f172a); line-height: 1; }
                    .sf-stat-label { font-size: 12px; color: var(--sf-gray-400, #94a3b8); font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 2px; }
                    .sf-card {
                        background: var(--sf-white, #fff);
                        border: 1px solid var(--sf-gray-200, #e2e8f0);
                        border-radius: 16px;
                        overflow: hidden;
                        box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.02);
                    }
                    .sf-tabs {
                        display: flex;
                        gap: 0;
                        border-bottom: 1px solid var(--sf-gray-200, #e2e8f0);
                        padding: 0 24px;
                        background: #fafbfc;
                    }
                    .sf-tab {
                        padding: 14px 20px;
                        cursor: pointer;
                        border: none;
                        background: none;
                        font-size: 13px;
                        font-weight: 600;
                        color: var(--sf-gray-400, #94a3b8);
                        border-bottom: 2px solid transparent;
                        margin-bottom: -1px;
                        transition: all 0.15s;
                        display: flex;
                        align-items: center;
                        gap: 6px;
                    }
                    .sf-tab:hover { color: #6366f1; }
                    .sf-tab.active { color: #6366f1; border-bottom-color: #6366f1; }
                    .sf-tab-badge {
                        background: var(--sf-gray-100, #f1f5f9);
                        color: var(--sf-gray-500, #64748b);
                        font-size: 11px;
                        padding: 2px 8px;
                        border-radius: 100px;
                        font-weight: 600;
                    }
                    .sf-tab.active .sf-tab-badge { background: #eef2ff; color: #6366f1; }
                    .sf-tab-content { display: none; }
                    .sf-tab-content.active { display: block; }
                    table {
                        width: 100%;
                        border-collapse: collapse;
                    }
                    thead th {
                        background: var(--sf-gray-50, #f8fafc);
                        padding: 10px 20px;
                        text-align: center;
                        font-weight: 600;
                        font-size: 11px;
                        text-transform: uppercase;
                        letter-spacing: 0.5px;
                        color: var(--sf-gray-400, #94a3b8);
                        border-bottom: 1px solid var(--sf-gray-200, #e2e8f0);
                        white-space: nowrap;
                    }
                    tbody td {
                        padding: 12px 20px;
                        border-bottom: 1px solid var(--sf-gray-200, #f1f5f9);
                        font-size: 13px;
                        color: var(--sf-gray-700, #334155);
                        text-align: center;
                    }
                    tbody tr:last-child td { border-bottom: none; }
                    tbody tr { transition: background 0.1s; }
                    tbody tr:hover { background: var(--sf-gray-50, #f8fafc); }
                    .sf-num {
                        color: var(--sf-gray-400, #cbd5e1);
                        font-size: 12px;
                        font-weight: 500;
                        min-width: 32px;
                    }
                    a { color: #6366f1; text-decoration: none; }
                    a:hover { color: #4f46e5; text-decoration: underline; }
                    .sf-url {
                        max-width: 480px;
                        word-break: break-all;
                        font-weight: 500;
                        text-align: left;
                    }
                    .sf-url a { color: var(--sf-gray-900, #1e293b); }
                    .sf-url a:hover { color: #6366f1; }
                    .sf-tag {
                        display: inline-block;
                        font-size: 11px;
                        font-weight: 600;
                        padding: 3px 10px;
                        border-radius: 6px;
                        text-transform: capitalize;
                    }
                    .sf-tag-freq {
                        background: #f0fdf4;
                        color: #15803d;
                        border: 1px solid #bbf7d0;
                    }
                    .sf-tag-priority {
                        background: #fefce8;
                        color: #a16207;
                        border: 1px solid #fde68a;
                    }
                    .sf-date {
                        color: var(--sf-gray-400, #94a3b8);
                        font-size: 12px;
                        white-space: nowrap;
                    }
                    .sf-img-preview {
                        width: 60px;
                        height: 40px;
                        object-fit: cover;
                        border-radius: 6px;
                        border: 1px solid var(--sf-gray-200, #e2e8f0);
                    }
                    .sf-footer {
                        text-align: center;
                        padding: 24px 16px 8px;
                        font-size: 12px;
                        color: var(--sf-gray-400, #94a3b8);
                        letter-spacing: 0.3px;
                    }
                    @media (max-width: 768px) {
                        body { padding: 16px 8px; }
                        h1 { font-size: 20px; }
                        .sf-stats { flex-direction: column; }
                        thead th, tbody td { padding: 8px 12px; }
                        .sf-url { max-width: 240px; }
                    }
                </style>
            </head>
            <body>
                <div class="sf-wrap">
                    <xsl:choose>
                        <!-- ========== SITEMAP INDEX ========== -->
                        <xsl:when test="sitemap:sitemapindex">
                            <div class="sf-header">
                                <div class="sf-header-left">
                                    <h1>' . esc_html__('XML Sitemap', 'seo-fury') . '</h1>
                                    <div class="sf-subtitle">' . esc_html__('This sitemap index contains links to all sub-sitemaps for this website.', 'seo-fury') . '</div>
                                </div>
                                ' . $badge_html . '
                            </div>

                            <div class="sf-stats">
                                <div class="sf-stat-card">
                                    <div class="sf-stat-icon purple">
                                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
                                    </div>
                                    <div>
                                        <div class="sf-stat-number"><xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/></div>
                                        <div class="sf-stat-label">' . esc_html__('Sub-sitemaps', 'seo-fury') . '</div>
                                    </div>
                                </div>
                            </div>

                            <div class="sf-card">
                                <table>
                                    <thead>
                                        <tr>
                                            <th style="width: 48px;">#</th>
                                            <th>' . esc_html__('Sitemap URL', 'seo-fury') . '</th>
                                            <th style="width: 200px;">' . esc_html__('Last Modified', 'seo-fury') . '</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
                                            <tr>
                                                <td class="sf-num"><xsl:value-of select="position()"/></td>
                                                <td class="sf-url">
                                                    <a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc"/></a>
                                                </td>
                                                <td class="sf-date"><xsl:value-of select="substring(sitemap:lastmod, 1, 10)"/></td>
                                            </tr>
                                        </xsl:for-each>
                                    </tbody>
                                </table>
                            </div>
                        </xsl:when>

                        <!-- ========== URL SITEMAP (sub-sitemap) ========== -->
                        <xsl:otherwise>
                            <a href="' . $home_url . '" class="sf-back">
                                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
                                ' . esc_html__('All sitemaps', 'seo-fury') . '
                            </a>

                            <div class="sf-header">
                                <div class="sf-header-left">
                                    <h1>' . esc_html($sub_label_title) . '</h1>
                                    <div class="sf-subtitle">' . esc_html__('XML sitemap for search engines (Google, Bing, Yandex).', 'seo-fury') . '</div>
                                </div>
                                ' . $badge_html . '
                            </div>

                            <div class="sf-stats">
                                <div class="sf-stat-card">
                                    <div class="sf-stat-icon blue">
                                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
                                    </div>
                                    <div>
                                        <div class="sf-stat-number"><xsl:value-of select="count(sitemap:urlset/sitemap:url)"/></div>
                                        <div class="sf-stat-label">' . esc_html__('URLs', 'seo-fury') . '</div>
                                    </div>
                                </div>
                                <xsl:if test="count(sitemap:urlset/sitemap:url/image:image) &gt; 0">
                                    <div class="sf-stat-card">
                                        <div class="sf-stat-icon green">
                                            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
                                        </div>
                                        <div>
                                            <div class="sf-stat-number"><xsl:value-of select="count(sitemap:urlset/sitemap:url/image:image)"/></div>
                                            <div class="sf-stat-label">' . esc_html__('Images', 'seo-fury') . '</div>
                                        </div>
                                    </div>
                                </xsl:if>
                            </div>

                            <div class="sf-card">
                                <div class="sf-tabs">
                                    <button class="sf-tab active" onclick="showTab(\'urls\', this)">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
                                        ' . esc_html__('URLs', 'seo-fury') . '
                                        <span class="sf-tab-badge"><xsl:value-of select="count(sitemap:urlset/sitemap:url)"/></span>
                                    </button>
                                    <xsl:if test="count(sitemap:urlset/sitemap:url/image:image) &gt; 0">
                                        <button class="sf-tab" onclick="showTab(\'images\', this)">
                                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
                                            ' . esc_html__('Images', 'seo-fury') . '
                                            <span class="sf-tab-badge"><xsl:value-of select="count(sitemap:urlset/sitemap:url/image:image)"/></span>
                                        </button>
                                    </xsl:if>
                                </div>

                                <!-- URLs tab -->
                                <div id="urls-tab" class="sf-tab-content active">
                                    <table>
                                        <thead>
                                            <tr>
                                                <th style="width: 48px;">#</th>
                                                <th>' . esc_html__('URL', 'seo-fury') . '</th>
                                                <th style="width: 110px;">' . esc_html__('Priority', 'seo-fury') . '</th>
                                                <th style="width: 110px;">' . esc_html__('Changefreq', 'seo-fury') . '</th>
                                                <th style="width: 120px;">' . esc_html__('Last Modified', 'seo-fury') . '</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <xsl:for-each select="sitemap:urlset/sitemap:url">
                                                <tr>
                                                    <td class="sf-num"><xsl:value-of select="position()"/></td>
                                                    <td class="sf-url">
                                                        <a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc"/></a>
                                                    </td>
                                                    <td>
                                                        <xsl:if test="sitemap:priority">
                                                            <span class="sf-tag sf-tag-priority"><xsl:value-of select="sitemap:priority"/></span>
                                                        </xsl:if>
                                                    </td>
                                                    <td>
                                                        <xsl:if test="sitemap:changefreq">
                                                            <span class="sf-tag sf-tag-freq"><xsl:value-of select="sitemap:changefreq"/></span>
                                                        </xsl:if>
                                                    </td>
                                                    <td class="sf-date"><xsl:value-of select="substring(sitemap:lastmod, 1, 10)"/></td>
                                                </tr>
                                            </xsl:for-each>
                                        </tbody>
                                    </table>
                                </div>

                                <!-- Images tab -->
                                <xsl:if test="count(sitemap:urlset/sitemap:url/image:image) &gt; 0">
                                    <div id="images-tab" class="sf-tab-content">
                                        <table>
                                            <thead>
                                                <tr>
                                                    <th style="width: 48px;">#</th>
                                                    <th>' . esc_html__('Page', 'seo-fury') . '</th>
                                                    <th>' . esc_html__('Image URL', 'seo-fury') . '</th>
                                                    <th style="width: 80px;">' . esc_html__('Preview', 'seo-fury') . '</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <xsl:for-each select="sitemap:urlset/sitemap:url">
                                                    <xsl:variable name="pageUrl" select="sitemap:loc"/>
                                                    <xsl:for-each select="image:image">
                                                        <tr>
                                                            <td class="sf-num"><xsl:value-of select="position()"/></td>
                                                            <td class="sf-url">
                                                                <a href="{$pageUrl}"><xsl:value-of select="$pageUrl"/></a>
                                                            </td>
                                                            <td class="sf-url" style="max-width:320px;">
                                                                <a href="{image:loc}" target="_blank"><xsl:value-of select="image:loc"/></a>
                                                            </td>
                                                            <td>
                                                                <img src="{image:loc}" alt="" class="sf-img-preview"/>
                                                            </td>
                                                        </tr>
                                                    </xsl:for-each>
                                                </xsl:for-each>
                                            </tbody>
                                        </table>
                                    </div>
                                </xsl:if>
                            </div>
                        </xsl:otherwise>
                    </xsl:choose>

                    ' . ($hide_branding ? '' : '<div class="sf-footer">' .
                    /* translators: %s: brand name */
                    sprintf(esc_html__('Generated by %s', 'seo-fury'), esc_html($brand_name)) . '</div>') . '

                </div>

                <script type="text/javascript">
                    function showTab(name, btn) {
                        var panels = document.querySelectorAll(\'.sf-tab-content\');
                        for (var i = 0; i &lt; panels.length; i++) panels[i].classList.remove(\'active\');
                        var tabs = document.querySelectorAll(\'.sf-tab\');
                        for (var j = 0; j &lt; tabs.length; j++) tabs[j].classList.remove(\'active\');
                        document.getElementById(name + \'-tab\').classList.add(\'active\');
                        btn.classList.add(\'active\');
                    }
                </script>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>';
    }

    private function get_exclude_urls() {
        $exclude = get_option('seo_fury_sitemap_exclude_urls', '');
        $urls = array_filter(array_map('trim', explode("\n", $exclude)));

        $normalized = array();
        foreach ($urls as $url) {
            if (strpos($url, 'http') === 0) {
                $normalized[] = $url;
            } else {
                $normalized[] = home_url($url);
            }
        }

        return $normalized;
    }

    private function get_additional_urls() {
        $default_priority = get_option('seo_fury_sitemap_additional_default_priority', '0.5');
        $default_changefreq = get_option('seo_fury_sitemap_additional_default_changefreq', 'weekly');

        $urls_data = get_option('seo_fury_sitemap_additional_urls_data', array());
        $overrides = array();
        if (is_array($urls_data)) {
            foreach ($urls_data as $row) {
                if (!empty($row['url'])) {
                    $overrides[$row['url']] = array(
                        'priority'   => isset($row['priority']) ? round(floatval($row['priority']), 1) : floatval($default_priority),
                        'changefreq' => isset($row['changefreq']) ? $row['changefreq'] : $default_changefreq,
                    );
                }
            }
        }

        $additional = get_option('seo_fury_sitemap_additional_urls', '');
        $raw_urls = array_filter(array_map('trim', explode("\n", $additional)));

        $result = array();
        $seen = array();

        foreach ($raw_urls as $url) {
            $normalized = (strpos($url, 'http') === 0) ? $url : home_url($url);
            if (isset($seen[$normalized])) continue;
            $seen[$normalized] = true;

            if (isset($overrides[$normalized])) {
                $result[] = array(
                    'url'        => $normalized,
                    'priority'   => $overrides[$normalized]['priority'],
                    'changefreq' => $overrides[$normalized]['changefreq'],
                );
                unset($overrides[$normalized]);
            } else {
                $result[] = array(
                    'url'        => $normalized,
                    'priority'   => floatval($default_priority),
                    'changefreq' => $default_changefreq,
                );
            }
        }

        foreach ($overrides as $url => $settings) {
            if (isset($seen[$url])) continue;
            $result[] = array(
                'url'        => $url,
                'priority'   => $settings['priority'],
                'changefreq' => $settings['changefreq'],
            );
        }

        return $result;
    }

    private function get_priority($post_type) {
        $post_types_settings = get_option('seo_fury_sitemap_post_types_settings', array());

        if (isset($post_types_settings[$post_type]['priority'])) {
            return $post_types_settings[$post_type]['priority'];
        }

        $priorities = array(
            'page' => '0.8',
            'post' => '0.7',
        );

        return isset($priorities[$post_type]) ? $priorities[$post_type] : '0.5';
    }

    private function get_changefreq($post_type) {
        $post_types_settings = get_option('seo_fury_sitemap_post_types_settings', array());

        if (isset($post_types_settings[$post_type]['changefreq'])) {
            return $post_types_settings[$post_type]['changefreq'];
        }

        return 'weekly';
    }

    private function get_post_images($post) {
        $images = array();

        if (has_post_thumbnail($post->ID)) {
            $thumbnail_id = get_post_thumbnail_id($post->ID);
            $image_url = wp_get_attachment_image_url($thumbnail_id, 'full');
            if ($image_url) {
                $images[] = $image_url;
            }
        }

        preg_match_all('/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $post->post_content, $matches);
        if (!empty($matches[1])) {
            foreach ($matches[1] as $img_url) {
                if (!in_array($img_url, $images, true)) {
                    $images[] = $img_url;
                }
            }
        }

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

    /**
     * Generate News sitemap (Google News protocol).
     * Only includes posts from the last 48 hours, max 1000.
     */
    public function generate_news_sitemap() {
        if (!get_option('seo_fury_sitemap_enabled', 0) || !get_option('seo_fury_sitemap_news_enabled', 0)) {
            status_header(404);
            return;
        }

        $cache_key = 'news';
        $cached = get_transient('seo_fury_sitemap_' . $cache_key);
        if ($cached) {
            header('Content-Type: application/xml; charset=utf-8');
            header('X-Robots-Tag: noindex, follow');
            echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Cached news sitemap built with escaped values.
            exit;
        }

        $post_types = get_option('seo_fury_sitemap_news_post_types', array('post'));
        if (!is_array($post_types)) $post_types = array('post');

        $pub_name = get_option('seo_fury_sitemap_news_publication_name', get_bloginfo('name'));
        $language = get_option('seo_fury_sitemap_news_language', 'en');

        // Google News: only posts from last 48 hours, max 1000
        $posts = get_posts(array(
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => 1000,
            'orderby'        => 'date',
            'order'          => 'DESC',
            'date_query'     => array(
                array(
                    'after' => '48 hours ago',
                ),
            ),
            'no_found_rows'          => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        ));

        // Check exclude URLs
        $exclude_urls = $this->get_exclude_urls();

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

        foreach ($posts as $post) {
            $url = get_permalink($post);

            // Skip excluded URLs
            if (in_array($url, $exclude_urls, true)) {
                continue;
            }

            // Skip noindex posts
            $noindex = get_post_meta($post->ID, '_seo_fury_robots_noindex', true);
            if ($noindex) {
                continue;
            }

            $xml .= '  <url>' . "\n";
            $xml .= '    <loc>' . esc_url($url) . '</loc>' . "\n";
            $xml .= '    <news:news>' . "\n";
            $xml .= '      <news:publication>' . "\n";
            $xml .= '        <news:name>' . esc_html($pub_name) . '</news:name>' . "\n";
            $xml .= '        <news:language>' . esc_html($language) . '</news:language>' . "\n";
            $xml .= '      </news:publication>' . "\n";
            $xml .= '      <news:publication_date>' . esc_html( get_the_date('c', $post) ) . '</news:publication_date>' . "\n";
            $xml .= '      <news:title>' . esc_html(get_the_title($post)) . '</news:title>' . "\n";
            $xml .= '    </news:news>' . "\n";
            $xml .= '  </url>' . "\n";
        }

        if (empty($posts)) {
            $xml .= '  <!-- No news articles published in the last 48 hours. Google News sitemap only includes recent posts. -->' . "\n";
        }

        $xml .= '</urlset>';

        // Cache for 15 minutes (news changes frequently)
        set_transient('seo_fury_sitemap_' . $cache_key, $xml, 15 * MINUTE_IN_SECONDS);

        header('Content-Type: application/xml; charset=utf-8');
        header('X-Robots-Tag: noindex, follow');
        echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- News sitemap built with escaped values.
        exit;
    }

    /**
     * Generate Video sitemap (Google Video sitemap protocol).
     * Scans post content for YouTube and Vimeo embeds.
     */
    public function generate_video_sitemap() {
        if (!get_option('seo_fury_sitemap_enabled', 0) || !get_option('seo_fury_sitemap_video_enabled', 0)) {
            status_header(404);
            return;
        }

        $cache_key = 'videos';
        $cached = get_transient('seo_fury_sitemap_' . $cache_key);
        if ($cached) {
            header('Content-Type: application/xml; charset=utf-8');
            header('X-Robots-Tag: noindex, follow');
            echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Cached video sitemap built with escaped values.
            exit;
        }

        $post_types = get_option('seo_fury_sitemap_video_post_types', array('post', 'page'));
        if (!is_array($post_types)) $post_types = array('post', 'page');

        $exclude_urls = $this->get_exclude_urls();

        // Query posts with potential video embeds
        $posts = get_posts(array(
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => 1000,
            'orderby'        => 'modified',
            'order'          => 'DESC',
            'no_found_rows'          => true,
            'update_post_term_cache' => false,
        ));

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

        foreach ($posts as $post) {
            $videos = $this->extract_videos_from_content($post->post_content);
            if (empty($videos)) {
                continue;
            }

            $url = get_permalink($post);
            if (in_array($url, $exclude_urls, true)) {
                continue;
            }

            $noindex = get_post_meta($post->ID, '_seo_fury_robots_noindex', true);
            if ($noindex) {
                continue;
            }

            foreach ($videos as $video) {
                // Use CDATA for text fields to avoid XML entity issues (e.g. &hellip;).
                // A literal "]]>" inside the value would close the CDATA early and
                // break the XML, so it is split with the standard CDATA escape.
                $v_title = $video['title'] ? $video['title'] : get_the_title($post);
                $v_desc  = $video['description'] ? $video['description'] : wp_trim_words(wp_strip_all_tags($post->post_content), 50, '...');
                $v_title = str_replace(']]>', ']]]]><![CDATA[>', $v_title);
                $v_desc  = str_replace(']]>', ']]]]><![CDATA[>', $v_desc);

                $xml .= '  <url>' . "\n";
                $xml .= '    <loc>' . esc_url($url) . '</loc>' . "\n";
                $xml .= '    <video:video>' . "\n";
                $xml .= '      <video:thumbnail_loc>' . esc_url($video['thumbnail']) . '</video:thumbnail_loc>' . "\n";
                $xml .= '      <video:title><![CDATA[' . $v_title . ']]></video:title>' . "\n";
                $xml .= '      <video:description><![CDATA[' . $v_desc . ']]></video:description>' . "\n";
                if (!empty($video['player_loc'])) {
                    $xml .= '      <video:player_loc>' . esc_url($video['player_loc']) . '</video:player_loc>' . "\n";
                }
                if (!empty($video['content_loc'])) {
                    $xml .= '      <video:content_loc>' . esc_url($video['content_loc']) . '</video:content_loc>' . "\n";
                }
                $xml .= '      <video:publication_date>' . esc_html( get_the_date('c', $post) ) . '</video:publication_date>' . "\n";
                $xml .= '    </video:video>' . "\n";
                $xml .= '  </url>' . "\n";
            }
        }

        $xml .= '</urlset>';

        set_transient('seo_fury_sitemap_' . $cache_key, $xml, HOUR_IN_SECONDS);

        header('Content-Type: application/xml; charset=utf-8');
        header('X-Robots-Tag: noindex, follow');
        echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Video sitemap built with escaped values.
        exit;
    }

    /**
     * Extract YouTube and Vimeo videos from post content.
     *
     * @param string $content Post content HTML.
     * @return array Array of video data arrays.
     */
    private function extract_videos_from_content($content) {
        $videos = array();

        // YouTube: various embed formats
        // iframe src="https://www.youtube.com/embed/VIDEO_ID"
        // https://www.youtube.com/watch?v=VIDEO_ID
        // https://youtu.be/VIDEO_ID
        if (preg_match_all('/(?:youtube\.com\/(?:embed\/|watch\?v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/', $content, $matches)) {
            foreach (array_unique($matches[1]) as $video_id) {
                $videos[] = array(
                    'thumbnail'   => 'https://img.youtube.com/vi/' . $video_id . '/hqdefault.jpg',
                    'player_loc'  => 'https://www.youtube.com/embed/' . $video_id,
                    'content_loc' => '',
                    'title'       => '',
                    'description' => '',
                );
            }
        }

        // Vimeo: iframe src="https://player.vimeo.com/video/VIDEO_ID"
        // or https://vimeo.com/VIDEO_ID
        if (preg_match_all('/(?:player\.vimeo\.com\/video\/|vimeo\.com\/)(\d+)/', $content, $matches)) {
            foreach (array_unique($matches[1]) as $video_id) {
                $videos[] = array(
                    'thumbnail'   => 'https://vumbnail.com/' . $video_id . '.jpg',
                    'player_loc'  => 'https://player.vimeo.com/video/' . $video_id,
                    'content_loc' => '',
                    'title'       => '',
                    'description' => '',
                );
            }
        }

        return $videos;
    }

    public function generate_html_sitemap() {
        $post_types = get_option('seo_fury_html_sitemap_post_types', array('post', 'page'));

        if (!is_array($post_types) || empty($post_types)) {
            status_header(404);
            return;
        }

        get_header();
        ?>
        <div class="html-sitemap-container" style="padding: 30px 15px; max-width: 1200px; margin: 0 auto; font-size: 0.75em;">
            <h1 style="margin-bottom: 22px; font-size: 2em;"><?php esc_html_e('Sitemap', 'seo-fury'); ?></h1>

            <?php foreach ($post_types as $post_type): ?>
                <?php
                $post_type_obj = get_post_type_object($post_type);
                if (!$post_type_obj) {
                    continue;
                }

                $posts = get_posts(array(
                    'post_type' => $post_type,
                    'post_status' => 'publish',
                    'posts_per_page' => 1000,
                    'orderby' => 'title',
                    'order' => 'ASC',
                ));

                if (empty($posts)) {
                    continue;
                }
                ?>

                <div class="sitemap-section" style="margin-bottom: 30px;">
                    <h2 style="border-bottom: 2px solid #0073aa; padding-bottom: 8px; margin-bottom: 15px; font-size: 1.6em;">
                        <?php echo esc_html($post_type_obj->label); ?>
                    </h2>

                    <ul style="list-style: none; padding: 0; columns: 2; column-gap: 30px;">
                        <?php foreach ($posts as $post): ?>
                            <li style="margin-bottom: 8px; break-inside: avoid;">
                                <a href="<?php echo esc_url(get_permalink($post)); ?>" style="color: #0073aa; text-decoration: none;">
                                    <?php echo esc_html($post->post_title); ?>
                                </a>
                            </li>
                        <?php endforeach; ?>
                    </ul>
                </div>
            <?php endforeach; ?>

            <?php
            // Таксономии в HTML карте сайта
            $taxonomies = get_option('seo_fury_sitemap_taxonomies', array());
            if (is_array($taxonomies) && !empty($taxonomies)):
                foreach ($taxonomies as $taxonomy):
                    $tax_obj = get_taxonomy($taxonomy);
                    if (!$tax_obj) continue;

                    $terms = get_terms(array(
                        'taxonomy' => $taxonomy,
                        'hide_empty' => true,
                        'orderby' => 'name',
                        'order' => 'ASC',
                    ));

                    if (empty($terms) || is_wp_error($terms)) continue;
            ?>
                <div class="sitemap-section" style="margin-bottom: 30px;">
                    <h2 style="border-bottom: 2px solid #0073aa; padding-bottom: 8px; margin-bottom: 15px; font-size: 1.6em;">
                        <?php echo esc_html($tax_obj->label); ?>
                    </h2>

                    <ul style="list-style: none; padding: 0; columns: 2; column-gap: 30px;">
                        <?php foreach ($terms as $term):
                            $term_link = get_term_link($term);
                            if (is_wp_error($term_link)) continue;
                        ?>
                            <li style="margin-bottom: 8px; break-inside: avoid;">
                                <a href="<?php echo esc_url($term_link); ?>" style="color: #0073aa; text-decoration: none;">
                                    <?php echo esc_html($term->name); ?>
                                </a>
                                <span style="color: var(--sf-gray-400, #999); font-size: 0.9em;">(<?php echo intval($term->count); ?>)</span>
                            </li>
                        <?php endforeach; ?>
                    </ul>
                </div>
            <?php
                endforeach;
            endif;
            ?>

            <?php if (empty($post_types)): ?>
                <p><?php esc_html_e('No entries available to display in the sitemap.', 'seo-fury'); ?></p>
            <?php endif; ?>
        </div>
        <?php
        get_footer();
        exit;
    }

    public function shortcode_html_sitemap($atts) {
        $atts = shortcode_atts(array(
            'post_types' => '',
        ), $atts);

        if (!empty($atts['post_types'])) {
            $post_types = array_map('trim', explode(',', $atts['post_types']));
        } else {
            $post_types = get_option('seo_fury_html_sitemap_post_types', array('post', 'page'));
        }

        if (!is_array($post_types) || empty($post_types)) {
            return '<p>' . esc_html__('No entries available to display in the sitemap.', 'seo-fury') . '</p>';
        }

        ob_start();
        ?>
        <div class="html-sitemap-shortcode" style="font-size: 0.75em;">
            <?php foreach ($post_types as $post_type): ?>
                <?php
                $post_type_obj = get_post_type_object($post_type);
                if (!$post_type_obj) {
                    continue;
                }

                $posts = get_posts(array(
                    'post_type' => $post_type,
                    'post_status' => 'publish',
                    'posts_per_page' => 1000,
                    'orderby' => 'title',
                    'order' => 'ASC',
                ));

                if (empty($posts)) {
                    continue;
                }
                ?>

                <div class="sitemap-section" style="margin-bottom: 22px;">
                    <h3 style="font-size: 1.4em;"><?php echo esc_html($post_type_obj->label); ?></h3>
                    <ul>
                        <?php foreach ($posts as $post): ?>
                            <li style="margin-bottom: 6px;">
                                <a href="<?php echo esc_url(get_permalink($post)); ?>">
                                    <?php echo esc_html($post->post_title); ?>
                                </a>
                            </li>
                        <?php endforeach; ?>
                    </ul>
                </div>
            <?php endforeach; ?>

            <?php
            // Таксономии в HTML карте сайта (шорткод)
            $taxonomies = get_option('seo_fury_sitemap_taxonomies', array());
            if (is_array($taxonomies) && !empty($taxonomies)):
                foreach ($taxonomies as $taxonomy):
                    $tax_obj = get_taxonomy($taxonomy);
                    if (!$tax_obj) continue;

                    $terms = get_terms(array(
                        'taxonomy' => $taxonomy,
                        'hide_empty' => true,
                        'orderby' => 'name',
                        'order' => 'ASC',
                    ));

                    if (empty($terms) || is_wp_error($terms)) continue;
            ?>
                <div class="sitemap-section" style="margin-bottom: 22px;">
                    <h3 style="font-size: 1.4em;"><?php echo esc_html($tax_obj->label); ?></h3>
                    <ul>
                        <?php foreach ($terms as $term):
                            $term_link = get_term_link($term);
                            if (is_wp_error($term_link)) continue;
                        ?>
                            <li style="margin-bottom: 6px;">
                                <a href="<?php echo esc_url($term_link); ?>">
                                    <?php echo esc_html($term->name); ?>
                                </a>
                                <span style="color: var(--sf-gray-400, #999);">(<?php echo intval($term->count); ?>)</span>
                            </li>
                        <?php endforeach; ?>
                    </ul>
                </div>
            <?php
                endforeach;
            endif;
            ?>
        </div>
        <?php
        return ob_get_clean();
    }
}
