<?php
/**
 * Tam Rank™ Settings Page
 */

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

class TamRank_Settings_Page
{

    public function __construct()
    {
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('admin_init', array($this, 'admin_init'));
        add_action('wp_ajax_tam_rank_toggle_sitemap_type', array($this, 'ajax_toggle_sitemap_type'));
    }

    public function add_admin_menu()
    {
        add_menu_page(
            'Tam Rank™',
            'Tam Rank™',
            'manage_options',
            'tam-rank-dashboard',
            array($this, 'dashboard_page'),
            'none',
            30
        );

        // Add submenus
        $this->add_admin_submenus();

        // Add custom CSS to inject the colored SVG icon (enqueued globally so the
        // top-level menu icon is styled on every admin page).
        add_action('admin_enqueue_scripts', array($this, 'enqueue_menu_icon_css'));
    }

    public function enqueue_menu_icon_css()
    {
        wp_enqueue_style(
            'tamrank-admin-menu-icon',
            TAM_RANK_PLUGIN_URL . 'assets/css/admin-menu-icon.css',
            array(),
            TAM_RANK_VERSION
        );
    }

    public function add_admin_submenus()
    {
        // Freemium model: All menus visible, PRO features marked with badge
        $is_pro = tam_rank_license_active();
        $pro_badge = !$is_pro ? ' <span class="tamrank-menu-pro-badge">PRO</span>' : '';

        // Dashboard als eerste submenu item (always visible - FREE)
        add_submenu_page(
            'tam-rank-dashboard',
            tamrank__('dashboard', 'Dashboard'),
            tamrank__('dashboard', 'Dashboard'),
            'manage_options',
            'tam-rank-dashboard',
            array($this, 'dashboard_page')
        );

        // Instellingen (FREE - basic settings accessible)
        add_submenu_page(
            'tam-rank-dashboard',
            tamrank__('general_settings', 'General Settings'),
            tamrank__('settings', 'Settings'),
            'manage_options',
            'tam-rank-settings',
            array($this, 'settings_page')
        );

        // PageSpeed submenu moved to the tam-rank-pro add-on (cluster-1 Free/PRO
        // strip). PRO re-registers it here via its own admin_menu callback.

        // Topical Authority submenu moved to the tam-rank-pro add-on (cluster-2
        // Free/PRO strip). PRO re-registers it via its own admin_menu callback.

        // SEO Audit (Strict Modularity: Hidden if disabled)
        if (TamRank_Features::is_enabled('seo_audit')) {
            add_submenu_page(
                'tam-rank-dashboard',
                tamrank__('seo_audit', 'SEO Audit'),
                tamrank__('seo_audit', 'SEO Audit'),
                'manage_options',
                'tam-rank-audit',
                array($this, 'audit_page')
            );
        }

        // 404 Monitor (Strict Modularity: Hidden if disabled)
        if (TamRank_Features::is_enabled('404_monitoring')) {
            add_submenu_page(
                'tam-rank-dashboard',
                tamrank__('404_monitor', '404 Monitor'),
                tamrank__('404_monitor', '404 Monitor'),
                'manage_options',
                'tam-rank-404',
                array($this, 'monitor_404_page')
            );
        }

        // Redirects (Strict Modularity: Hidden if disabled)
        if (TamRank_Features::is_enabled('redirects')) {
            add_submenu_page(
                'tam-rank-dashboard',
                tamrank__('redirects', 'Redirects'),
                tamrank__('redirects', 'Redirects'),
                'manage_options',
                'tam-rank-redirects',
                array($this, 'redirects_page')
            );
        }

        // Schema Builder (FREE)
        add_submenu_page(
            'tam-rank-dashboard',
            tamrank__('schema', 'Schema') . ' Builder',
            tamrank__('schema', 'Schema'),
            'manage_options',
            'tam-rank-schema',
            array($this, 'schema_page')
        );
        // GSC Page Insights submenu moved to the tam-rank-pro add-on (cluster-3
        // strip); PRO registers it on admin_menu with its own page controller.

        // License page is added by AdminPage class and always visible
    }

    public function admin_init()
    {
        register_setting('tam_rank_settings', 'tam_rank_robots_txt', array(
            'type'              => 'string',
            'sanitize_callback' => array($this, 'sanitize_robots_txt'),
            'default'           => '',
        ));
        register_setting('tam_rank_settings', 'tam_rank_sitemap_settings', array(
            'type'              => 'array',
            'sanitize_callback' => array($this, 'sanitize_sitemap_settings'),
            'default'           => array(),
        ));
        register_setting('tam_rank_settings', 'tam_rank_pagespeed_api_key', array(
            'type'              => 'string',
            'sanitize_callback' => array($this, 'sanitize_pagespeed_api_key'),
        ));
        register_setting('tam_rank_settings', 'tam_rank_general_settings', array(
            'type' => 'array',
            'sanitize_callback' => array($this, 'sanitize_general_settings'),
            'default' => array(),
        ));
        register_setting('tam_rank_settings', 'tam_rank_rss_settings', array(
            'type'              => 'array',
            'sanitize_callback' => array($this, 'sanitize_rss_settings'),
            'default'           => array(),
        ));
        // The sanitizer lives on TamRank_Alt_Templates, not here: the "also fill
        // existing media" switch saves itself over AJAX, and both paths have to
        // write a byte-identical array. One definition, two callers.
        register_setting('tam_rank_settings', 'tam_rank_alt_template_settings', array(
            'type'              => 'array',
            'sanitize_callback' => array('TamRank_Alt_Templates', 'sanitize_settings'),
            'default'           => array(),
        ));

        // AI Assistent instellingen
        register_setting('tam_rank_settings', 'tam_rank_ai_enabled', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_ai_enabled'),
            'default' => true,
        ));
        register_setting('tam_rank_settings', 'tam_rank_ai_default_tone', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_ai_default_tone'),
            'default' => 'professioneel',
        ));

        // tam_rank_ai_api_endpoint is deliberately NOT registered here. This
        // page has no field for it in any state, and nothing else in the
        // plugin writes it, so its only role in the group was to be handed a
        // null on every save: esc_url_raw(null) raised the ltrim() deprecation
        // that broke the redirect back to the page ("Settings saved" never
        // appeared) and emptied the stored value along the way. An option a
        // form cannot edit does not belong in that form's group.

        // AI Brandprofiel
        register_setting('tam_rank_settings', 'tamrank_ai_brand_profile', array(
            'type' => 'array',
            'sanitize_callback' => array($this, 'sanitize_brand_profile'),
            'default' => array(),
        ));
    }

    /**
     * Did this save carry no field at all for the option being sanitized?
     *
     * The Settings API walks every option registered in the group and hands
     * update_option() whatever it finds in the POST — null when the page has no
     * field for it. Null therefore means "this form did not ask about it", and
     * never "the user emptied it": a text field that was rendered and cleared
     * arrives as '', an unchecked checkbox is answered by its hidden companion,
     * and a select always posts its selection.
     *
     * Panels behind the licence check render nothing at all, so before this
     * guard existed a FREE install wiped its stored PageSpeed key, AI switch and
     * writing style the moment anyone pressed Save on an unrelated tab, and the
     * brand profile fell back to its default whenever the PRO card was not
     * there to render its seven fields. Every sanitizer for an option that can
     * be absent asks this first and hands back what is stored.
     *
     * @param mixed $input Value the Settings API handed over.
     * @return bool
     */
    private function field_absent_from_form($input)
    {
        return $input === null;
    }

    /**
     * Sanitize the AI switch.
     *
     * The checkbox has a hidden companion posting 0, so an unchecked switch
     * still arrives as a value and still saves as off. Absent means the whole
     * AI panel was not rendered — a FREE install, or a licence that lapsed —
     * and then the stored preference stands.
     */
    public function sanitize_ai_enabled($input)
    {
        if ($this->field_absent_from_form($input)) {
            return tam_rank_cached_option('tam_rank_ai_enabled', true);
        }

        return rest_sanitize_boolean($input);
    }

    /**
     * Sanitize the default writing style. Same absence rule as the switch.
     */
    public function sanitize_ai_default_tone($input)
    {
        if ($this->field_absent_from_form($input)) {
            return tam_rank_cached_option('tam_rank_ai_default_tone', 'professioneel');
        }

        return sanitize_text_field($input);
    }

    /**
     * Sanitize general settings (features + content_types)
     * Ensures unchecked checkboxes are saved as false
     */
    public function sanitize_general_settings($input)
    {
        $input    = (array) $input;
        $defaults = TamRank_Features::get_defaults();
        $existing = tam_rank_cached_option('tam_rank_general_settings', array());
        $existing = is_array($existing) ? $existing : array();

        // Only the Settings form submits a full feature-toggle payload (via hidden
        // `value="0"` companions). Partial callers like onboarding language save or
        // audit-exclusion save must not wipe toggles to false.
        $is_full_toggle_submission = isset($input['_tamrank_full_toggles']) && $input['_tamrank_full_toggles'] === '1';
        unset($input['_tamrank_full_toggles']);

        // Boolean fields: the feature-toggle defaults plus a few standalone
        // checkboxes that live outside TamRank_Features. Same partial-aware rule
        // for all of them — derive from the form on a full submission, otherwise
        // preserve the existing value so partial callers don't wipe them.
        //
        // The three noindex_* keys are archive switches (PARITY-27). They are
        // deliberately NOT in TamRank_Features::get_defaults(): their defaults
        // are not all "on" and live in tam_rank_archive_noindex_defaults(). An
        // absent key therefore means "never saved", which that helper reads as
        // the default - not as off.
        $bool_keys = array_merge(
            array_keys($defaults),
            array(
                'enable_webp', 'convert_png', 'enable_auto_indexing', 'enable_indexnow', 'show_content_types',
                'noindex_date_archives', 'noindex_author_archives',
            )
        );
        foreach ($bool_keys as $key) {
            if (isset($input[$key])) {
                $input[$key] = (bool) $input[$key];
            } elseif ($is_full_toggle_submission) {
                // Form posted without this key despite marker → genuinely unchecked
                $input[$key] = false;
            } elseif (array_key_exists($key, $existing)) {
                // Partial update from another caller → keep what was there
                $input[$key] = (bool) $existing[$key];
            } elseif (array_key_exists($key, $defaults)) {
                // Never set yet → fall back to default
                $input[$key] = (bool) $defaults[$key];
            }
        }

        // Content_types: only re-sanitize when the form actually submitted it,
        // otherwise preserve existing (prevents partial callers from wiping it).
        if (isset($input['content_types']) || $is_full_toggle_submission) {
            $input['content_types'] = $this->sanitize_content_types_general(
                isset($input['content_types']) ? $input['content_types'] : array()
            );
        } elseif (isset($existing['content_types'])) {
            $input['content_types'] = $existing['content_types'];
        }

        // WebP quality: clamp to the slider range; preserve existing if not posted.
        if (isset($input['webp_quality'])) {
            $input['webp_quality'] = max(40, min(100, (int) $input['webp_quality']));
        } elseif (isset($existing['webp_quality'])) {
            $input['webp_quality'] = (int) $existing['webp_quality'];
        }

        // Interface language: strict enum. Prefer the submitted value, fall back
        // to the existing one, drop anything invalid.
        $allowed_langs = array('en', 'nl', 'de', 'fr');
        if (isset($input['language']) && in_array($input['language'], $allowed_langs, true)) {
            // valid submitted value — keep as-is
        } elseif (isset($existing['language']) && in_array($existing['language'], $allowed_langs, true)) {
            $input['language'] = $existing['language'];
        } else {
            unset($input['language']);
        }

        // Out-of-band keys written directly by the audit-exclusion AJAX handler.
        // Prefer the submitted value, fall back to existing so a full settings-form
        // save (which never posts these) doesn't drop them.
        if (isset($input['audit_exclude_noindex'])) {
            $input['audit_exclude_noindex'] = (bool) $input['audit_exclude_noindex'];
        } elseif (array_key_exists('audit_exclude_noindex', $existing)) {
            $input['audit_exclude_noindex'] = (bool) $existing['audit_exclude_noindex'];
        }
        if (isset($input['audit_excluded_taxonomies'])) {
            $input['audit_excluded_taxonomies'] = array_map('sanitize_key', (array) $input['audit_excluded_taxonomies']);
        } elseif (isset($existing['audit_excluded_taxonomies'])) {
            $input['audit_excluded_taxonomies'] = array_map('sanitize_key', (array) $existing['audit_excluded_taxonomies']);
        }

        // Strict whitelist: return ONLY known keys so no unexpected POST data is
        // ever persisted. Never return the raw $input.
        $allowed_keys = array_merge(
            array_keys($defaults),
            array(
                'enable_webp', 'convert_png', 'enable_auto_indexing', 'enable_indexnow', 'show_content_types',
                'noindex_date_archives', 'noindex_author_archives',
                'webp_quality', 'language', 'content_types',
                'audit_exclude_noindex', 'audit_excluded_taxonomies',
            )
        );
        $out = array();
        foreach ($allowed_keys as $key) {
            if (array_key_exists($key, $input)) {
                $out[$key] = $input[$key];
            }
        }
        return $out;
    }

    /**
     * Sanitize the content_types sub-array for general settings.
     * Enumerates allowed types, casts values to bool, enforces CFG-03.
     *
     * @param array $raw Raw input content_types array from form submission.
     * @return array Sanitized content_types array.
     */
    private function sanitize_content_types_general(array $raw): array
    {
        $ct_sanitized = [
            'post_types'  => [],
            'taxonomies'  => [],
        ];

        // Allowed post types (page has publicly_queryable=false in WP but IS public)
        $all_post_types = get_post_types(['public' => true], 'objects');
        foreach ($all_post_types as $slug => $pt) {
            if ($slug === 'attachment') continue;
            if ($pt->publicly_queryable === false && $slug !== 'page') continue;
            $pt_raw = isset($raw['post_types'][$slug]) && is_array($raw['post_types'][$slug])
                ? $raw['post_types'][$slug] : [];
            $ct_sanitized['post_types'][$slug] = [
                'audit' => isset($pt_raw['audit']) ? (bool) $pt_raw['audit'] : false,
            ];
        }

        // Allowed taxonomies (skip internal ones)
        $internal_taxonomies = ['post_format', 'nav_menu', 'link_category', 'wp_theme', 'wp_template_part_area'];
        $all_taxonomies = get_taxonomies(['public' => true], 'objects');
        foreach ($all_taxonomies as $slug => $tax) {
            if (in_array($slug, $internal_taxonomies, true)) continue;
            if ($tax->publicly_queryable === false) continue;
            $tax_raw = isset($raw['taxonomies'][$slug]) && is_array($raw['taxonomies'][$slug])
                ? $raw['taxonomies'][$slug] : [];
            $seo_fields = isset($tax_raw['seo_fields']) ? (bool) $tax_raw['seo_fields'] : false;

            // Single toggle: seo_fields controls both SEO meta fields and audit
            $ct_sanitized['taxonomies'][$slug] = [
                'seo_fields' => $seo_fields,
                'audit'      => $seo_fields,
            ];
        }

        return $ct_sanitized;
    }

    /**
     * Sanitize the robots.txt body.
     *
     * robots.txt is plain text, not HTML. wp_kses_post() (the previous callback) runs
     * wp_kses_normalize_entities(), which rewrites every bare '&' to '&amp;' and breaks
     * a rule like "Disallow: /zoek/?q=1&sort=asc". sanitize_textarea_field() is no good
     * either: _sanitize_text_fields() strips percent-encoded octets in a loop, so
     * "Disallow: /pad%20met%20spatie/" comes back as "/padmetspatie/". Both were
     * measured against WordPress core, not assumed. Strip tags and normalise line
     * endings, nothing else - the same primitive Rank Math uses for this field.
     *
     * @param string $input Raw textarea input.
     * @return string Sanitized robots.txt body.
     */
    public function sanitize_robots_txt($input)
    {
        $input = str_replace(array("\r\n", "\r"), "\n", (string) $input);

        return trim(wp_strip_all_tags($input, false));
    }

    /**
     * Sanitize sitemap settings.
     *
     * Note this callback runs on EVERY update_option('tam_rank_sitemap_settings'),
     * not just on a settings-form submit: register_setting() hangs it on the
     * sanitize_option_{$option} filter and update_option() always passes through
     * that filter, in AJAX requests too. So the single-type toggle AJAX and any
     * other writer land here as well, and none of them post the form's hidden
     * inputs. Keys that can be absent for that reason therefore use keep-semantics
     * (absent = keep what is stored), never checkbox-semantics (absent = off).
     *
     * @param array $input Raw input from form submission.
     * @return array Sanitized sitemap settings.
     */
    public function sanitize_sitemap_settings($input)
    {
        $input   = (array) $input;
        $current = get_option('tam_rank_sitemap_settings', array());
        $current = is_array($current) ? $current : array();

        // Master switch. Keep-semantics: a bare ! empty() would switch the sitemap
        // off on the first write that is not a settings-form submit. The form always
        // posts an explicit value through its hidden input, so unchecking still works.
        if (array_key_exists('sitemap_enabled', $input)) {
            $enabled = (bool) $input['sitemap_enabled'];
        } elseif (array_key_exists('sitemap_enabled', $current)) {
            $enabled = (bool) $current['sitemap_enabled'];
        } else {
            $enabled = true; // Absent = on, so running installs are untouched.
        }

        // Build and return a strict whitelist; never return the raw $input. A
        // register_setting sanitizer must drop unexpected top-level keys so no
        // arbitrary POST data is persisted.
        //
        // 'auto_generate' is deliberately NOT in this whitelist and must never be
        // read as a setting. It had a whitelist entry but no input field anywhere, so
        // the expression 'auto_generate' => ! empty($input['auto_generate']) always
        // evaluated to false and every install that ever saved this page has
        // auto_generate === false stored. Using it as the sitemap master switch would
        // have switched the sitemap off on all of those sites during a plain update.
        // The master switch lives in 'sitemap_enabled' instead; the stale
        // auto_generate value is dropped on the next save and read nowhere.
        return array(
            'sitemap_enabled' => $enabled,
            'include_images'  => ! empty($input['include_images']),
            'content_types'   => $this->sanitize_content_types_sitemap(
                isset($input['content_types']) ? (array) $input['content_types'] : [],
                isset($current['content_types']) ? (array) $current['content_types'] : []
            ),
        );
    }

    /**
     * Sanitize the content_types sub-array for sitemap settings.
     *
     * Two rules, both keep-semantics (see sanitize_sitemap_settings()):
     *  - an absent slug keeps its stored value, defaulting to true. Without this the
     *    single-type toggle AJAX - which posts one slug - wrote every other type as an
     *    explicit false, emptying the sitemap after one click on a site that had never
     *    saved this tab. The form is unaffected because it renders a hidden input with
     *    value 0 for every type it shows.
     *  - slugs that are stored but not registered right now (WooCommerce temporarily
     *    deactivated, a theme CPT gone) are carried over instead of dropped. Otherwise
     *    an explicit product => false would silently disappear and fall back to
     *    "missing = on" when the plugin comes back. Those values were sanitized when
     *    they were written, so this does not weaken the strict whitelist for POST data.
     *
     * @param array $raw     Raw input content_types array.
     * @param array $current Currently stored content_types array.
     * @return array Sanitized sitemap content_types array.
     */
    private function sanitize_content_types_sitemap(array $raw, array $current = []): array
    {
        $ct_sanitized = [
            'post_types'  => [],
            'taxonomies'  => [],
        ];

        foreach (['post_types', 'taxonomies'] as $group) {
            if (empty($current[$group]) || !is_array($current[$group])) {
                continue;
            }
            foreach ($current[$group] as $stored_slug => $stored_value) {
                $stored_slug = sanitize_key($stored_slug);
                if ($stored_slug !== '') {
                    $ct_sanitized[$group][$stored_slug] = (bool) $stored_value;
                }
            }
        }

        // Allowed post types (page has publicly_queryable=false in WP but IS public)
        $all_post_types = get_post_types(['public' => true], 'objects');
        foreach ($all_post_types as $slug => $pt) {
            if ($slug === 'attachment') continue;
            if ($pt->publicly_queryable === false && $slug !== 'page') continue;
            if (isset($raw['post_types'][$slug])) {
                $ct_sanitized['post_types'][$slug] = (bool) $raw['post_types'][$slug];
            } elseif (!array_key_exists($slug, $ct_sanitized['post_types'])) {
                $ct_sanitized['post_types'][$slug] = true;
            }
        }

        // Allowed taxonomies (skip internal ones)
        $internal_taxonomies = ['post_format', 'nav_menu', 'link_category', 'wp_theme', 'wp_template_part_area'];
        $all_taxonomies = get_taxonomies(['public' => true], 'objects');
        foreach ($all_taxonomies as $slug => $tax) {
            if (in_array($slug, $internal_taxonomies, true)) continue;
            if ($tax->publicly_queryable === false) continue;
            if (isset($raw['taxonomies'][$slug])) {
                $ct_sanitized['taxonomies'][$slug] = (bool) $raw['taxonomies'][$slug];
            } elseif (!array_key_exists($slug, $ct_sanitized['taxonomies'])) {
                $ct_sanitized['taxonomies'][$slug] = true;
            }
        }

        return $ct_sanitized;
    }

    /**
     * AJAX handler: toggle a single sitemap content type on/off.
     * Saves immediately without full form submit.
     */
    public function ajax_toggle_sitemap_type()
    {
        check_ajax_referer('tam_rank_sitemap_toggle', 'nonce');

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

        $group   = sanitize_key(wp_unslash($_POST['group'] ?? ''));
        $slug    = sanitize_key(wp_unslash($_POST['slug'] ?? ''));
        $enabled = !empty($_POST['enabled']);

        if (!in_array($group, ['post_types', 'taxonomies'], true) || empty($slug)) {
            wp_send_json_error('Invalid parameters');
        }

        $settings = tam_rank_cached_option('tam_rank_sitemap_settings', []);
        if (!isset($settings['content_types'])) {
            $settings['content_types'] = [];
        }
        if (!isset($settings['content_types'][$group])) {
            $settings['content_types'][$group] = [];
        }
        $settings['content_types'][$group][$slug] = $enabled;
        update_option('tam_rank_sitemap_settings', $settings);

        // Clear all sitemap transients (shared list, also used by the migration write
        // path and the rollback so all three invalidate exactly the same set).
        tam_rank_flush_sitemap_transients();

        // Flush opcache for sitemap so new config is picked up
        if (function_exists('opcache_invalidate')) {
            opcache_invalidate(TAM_RANK_PLUGIN_PATH . 'includes/sitemap.php', true);
        }

        wp_send_json_success(['slug' => $slug, 'group' => $group, 'enabled' => $enabled]);
    }

    /**
     * Sanitize en valideer het AI brand profiel
     *
     * @param array $input Ruwe invoer van het formulier
     * @return array Opgeschoonde data
     */
    /**
     * Sanitize the RSS feed settings.
     *
     * Strict whitelist: never returns raw input. The before/after text is allowed
     * to contain HTML (a link back to the original is the whole point), so it goes
     * through wp_kses_post() rather than sanitize_textarea_field().
     *
     * @param mixed $input Raw input from the settings form.
     * @return array
     */
    public function sanitize_rss_settings($input)
    {
        $input = (array) $input;

        return array(
            'enabled' => ! empty( $input['enabled'] ),
            'before'  => isset( $input['before'] ) ? wp_kses_post( trim( (string) $input['before'] ) ) : '',
            'after'   => isset( $input['after'] ) ? wp_kses_post( trim( (string) $input['after'] ) ) : '',
        );
    }

    // The alt/title template sanitizer used to sit here. It moved to
    // TamRank_Alt_Templates::sanitize_settings() when the "also fill existing
    // media" switch got its own AJAX save: two writing paths, one sanitizer.

    public function sanitize_brand_profile($input)
    {
        // The seven brand-voice fields are rendered by the PRO add-on into this
        // form. Without that card there is nothing to save, and overwriting the
        // profile with an empty array would quietly reset the brand voice.
        if ($this->field_absent_from_form($input)) {
            return tam_rank_cached_option('tamrank_ai_brand_profile', array());
        }

        $sanitized = array();

        // Bedrijfsnaam
        if (isset($input['name'])) {
            $sanitized['name'] = sanitize_text_field($input['name']);
        }

        // Korte merk omschrijving
        if (isset($input['description'])) {
            $sanitized['description'] = sanitize_textarea_field($input['description']);
        }

        // Tone of voice
        $allowed_tones = array('', 'professioneel', 'informeel', 'creatief', 'zakelijk', 'direct');
        if (isset($input['tone']) && in_array($input['tone'], $allowed_tones)) {
            $sanitized['tone'] = $input['tone'];
        } else {
            $sanitized['tone'] = '';
        }

        // Extra tone instructies
        if (isset($input['tone_extra'])) {
            $sanitized['tone_extra'] = sanitize_text_field($input['tone_extra']);
        }

        // Doelgroep
        if (isset($input['audience'])) {
            $sanitized['audience'] = sanitize_text_field($input['audience']);
        }

        // USPs - splits op nieuwe regels, trim, filter lege waarden
        if (isset($input['usps'])) {
            $usps_raw = sanitize_textarea_field($input['usps']);
            $usps_array = explode("\n", $usps_raw);
            $usps_array = array_map('trim', $usps_array);
            $usps_array = array_filter($usps_array, function ($item) {
                return !empty($item);
            });
            $sanitized['usps'] = array_values($usps_array);
        }

        // Verboden woorden - splits op komma, trim, filter lege waarden
        if (isset($input['forbidden_words'])) {
            $forbidden_raw = sanitize_text_field($input['forbidden_words']);
            $forbidden_array = explode(',', $forbidden_raw);
            $forbidden_array = array_map('trim', $forbidden_array);
            $forbidden_array = array_filter($forbidden_array, function ($item) {
                return !empty($item);
            });
            $sanitized['forbidden_words'] = array_values($forbidden_array);
        }

        return $sanitized;
    }

    /**
     * Validate PageSpeed API key before saving.
     *
     * Empty string = user is removing the key (allowed).
     * Non-empty = test against Google's PageSpeed API. Invalid keys get a fast
     * 400/403 response; valid keys start an analysis (we don't wait for it).
     */
    public function sanitize_pagespeed_api_key($input)
    {
        // No field on this page: the PageSpeed card only renders its input when
        // the feature is available, so on FREE (or after a licence lapses) this
        // option is absent from every save. Keeping the stored key is the whole
        // point — it is a secret the user pasted once.
        if ($this->field_absent_from_form($input)) {
            return tam_rank_cached_option('tam_rank_pagespeed_api_key', '');
        }

        $key = sanitize_text_field($input);

        // Empty = removing key, that's fine. The field is only submitted when
        // the user opened the change form or pressed Remove, so an empty value
        // is a deliberate removal and never a side effect of saving another tab.
        if (empty($key)) {
            return '';
        }

        // Format check: Google API keys start with "AIza" and are ~39 chars
        if (strlen($key) < 30 || strpos($key, 'AIza') !== 0) {
            add_settings_error(
                'tam_rank_pagespeed_api_key',
                'invalid_format',
                tamrank__('ps_key_invalid_format', 'This doesn\'t look like a valid Google API key. Keys start with "AIza" and are about 39 characters long.'),
                'error'
            );
            return tam_rank_cached_option('tam_rank_pagespeed_api_key', '');
        }

        // Live validation against Google runs ONLY when the PageSpeed feature is
        // active (the separate PRO add-on, which actually performs the tests). The
        // free plugin must never contact Google here: the PageSpeed key field is
        // not rendered without the PRO feature, and the readme's External Services
        // section states the free plugin makes no PageSpeed call. Without the
        // feature active, accept the format-valid key without any external request.
        if (!function_exists('tam_rank_can_use_feature') || !tam_rank_can_use_feature('pagespeed_analysis')) {
            return $key;
        }

        // Live validation: test the key against Google's PageSpeed API
        $test_url = add_query_arg(array(
            'url'      => 'https://google.com',
            'key'      => $key,
            'category' => 'performance',
            'strategy' => 'mobile',
        ), 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed');

        $response = wp_remote_get($test_url, array('timeout' => 8));

        if (is_wp_error($response)) {
            // Network error — save anyway, don't block on connectivity issues
            return $key;
        }

        $status = wp_remote_retrieve_response_code($response);

        if ($status === 400 || $status === 403) {
            $body = json_decode(wp_remote_retrieve_body($response), true);
            $error_msg = isset($body['error']['message']) ? $body['error']['message'] : '';

            // Check for specific "API key not valid" error
            if (stripos($error_msg, 'API key not valid') !== false || stripos($error_msg, 'forbidden') !== false || stripos($error_msg, 'invalid') !== false) {
                add_settings_error(
                    'tam_rank_pagespeed_api_key',
                    'invalid_key',
                    tamrank__('ps_key_invalid', 'This API key is not valid. Please check that the key is correct and that the PageSpeed Insights API is enabled in your Google Cloud project.'),
                    'error'
                );
                return tam_rank_cached_option('tam_rank_pagespeed_api_key', '');
            }

            // API enabled but other error (e.g. quota) — key itself is valid
            if (stripos($error_msg, 'quota') !== false || stripos($error_msg, 'limit') !== false) {
                return $key;
            }

            // Unknown 400/403 — save with warning
            add_settings_error(
                'tam_rank_pagespeed_api_key',
                'key_warning',
                tamrank__('ps_key_warning', 'API key saved, but Google returned an unexpected response. The key may not work correctly.'),
                'warning'
            );
            return $key;
        }

        // 200 or other success = key is valid
        return $key;
    }

    public function dashboard_page()
    {
        // Freemium model: Dashboard is always accessible
        // Show license activation option but allow access to free features
        require_once TAM_RANK_PLUGIN_PATH . 'admin/dashboard.php';
        echo '<div class="tam-dashboard-wrap">';
        TamRank_Dashboard::render_dashboard();
        echo '</div>';
    }

    public function settings_page()
    {
        // Freemium model: Settings page is accessible for basic settings
        // PRO-only tabs will show upgrade messages

        $robots_txt = tam_rank_cached_option('tam_rank_robots_txt', '');
        $sitemap_settings = tam_rank_cached_option('tam_rank_sitemap_settings', array());
        $general_settings = tam_rank_cached_option('tam_rank_general_settings', array());

        $license_manager = new \TamRank\LicenseManager();
        $is_license_active = $license_manager->is_license_active();

        // Get credits info (only if license is active)
        //
        // Dit staat BEWUST vóór get_license_info(). get_cached_credits() is de
        // enige plek die /v1/sites/status ophaalt, en daarmee de enige schrijver
        // van `tier`/`sites` in tam_rank_credits_cache; get_license_info() leest
        // ze daar passief uit. Andersom om stond de licentiekaart na elke
        // cachewissel precies één laadbeurt zonder tiernaam.
        $credits_info = array('remaining' => 0, 'total' => 0, 'used' => 0);
        if ($is_license_active) {
            $credits_info = $license_manager->get_cached_credits();
        }

        // Get license info
        $license_info = $license_manager->get_license_info();

        // AI settings
        $ai_enabled = get_option('tam_rank_ai_enabled', true);
        $ai_default_tone = get_option('tam_rank_ai_default_tone', 'professioneel');
        $ai_api_endpoint = get_option('tam_rank_ai_api_endpoint', '');

        // GSC Connector
        $gsc_connector = class_exists('TamRank_GSC_Connector') ? TamRank_GSC_Connector::get_instance() : null;
        $gsc_connected = $gsc_connector && TamRank_GSC_Connector::is_connected();
        $gsc_property = $gsc_connector ? TamRank_GSC_Connector::get_property() : null;
        $gsc_property_display = $gsc_property;
        if ($gsc_property_display && strpos($gsc_property_display, 'sc-domain:') === 0) {
            $gsc_property_display = substr($gsc_property_display, 10);
        }
        $gsc_last_sync = $gsc_connector ? TamRank_GSC_Connector::get_last_sync() : null;
        $gsc_properties = $gsc_connector ? $gsc_connector->get_cached_properties() : array();
        $gsc_needs_selection = $gsc_connector && $gsc_connector->needs_property_selection();

        if ($gsc_needs_selection && $gsc_connector) {
            $need_fresh_fetch = empty($gsc_properties);
            if (!$need_fresh_fetch && is_array($gsc_properties)) {
                $first = reset($gsc_properties);
                if (!is_array($first) || (!isset($first['siteUrl']) && !isset($first['site_url']))) {
                    $need_fresh_fetch = true;
                }
            }
            if ($need_fresh_fetch) {
                $fetched = $gsc_connector->fetch_properties();
                if (!is_wp_error($fetched) && !empty($fetched)) {
                    $gsc_properties = $fetched;
                    update_option('tam_rank_gsc_properties', $fetched);
                }
            }
        }

        if (get_transient('tam_rank_gsc_connected')) {
            delete_transient('tam_rank_gsc_connected');
            add_settings_error('tam_rank_gsc', 'gsc_connected', tamrank__('gsc_connected_success', 'Google Search Console successfully connected!'), 'success');
        }
        if (get_transient('tam_rank_gsc_select_property')) {
            delete_transient('tam_rank_gsc_select_property');
            add_settings_error('tam_rank_gsc', 'gsc_select_property', tamrank__('gsc_select_property_prompt', 'Connected to Google! Now select a property.'), 'success');
        }

        ?>
        <div class="tr-settings-wrap">
            <div class="tr-settings-inner">

                <h1 class="tr-page-title"><?php tamrank_e('settings', 'Settings'); ?></h1>

                <!-- Tab Navigation -->
                <nav class="tr-pill-nav tam-rank-nav">
                    <a href="#general" class="tr-pill is-active"><?php tamrank_e('tab_general', 'General'); ?></a>
                    <a href="#integrations" class="tr-pill"><?php tamrank_e('tab_integrations', 'Integrations'); ?></a>
                    <a href="#ai-assistent" class="tr-pill">
                        <?php tamrank_e('tab_ai_assistant', 'AI Assistant'); ?>
                        <?php if (!tam_rank_can_use_feature('ai_meta_generator')): ?>
                            <span class="tr-badge tr-badge--pro">PRO</span>
                        <?php endif; ?>
                    </a>
                    <a href="#technical" class="tr-pill"><?php tamrank_e('tab_technical', 'Technical SEO'); ?></a>
                    <a href="#license" class="tr-pill"><?php tamrank_e('tab_license', 'License'); ?></a>
                    <a href="#import-export" class="tr-pill"><?php tamrank_e('import_export', 'Import / Export'); ?></a>
                </nav>

                <form method="post" action="options.php" class="tam-rank-form">
                    <?php settings_fields('tam_rank_settings'); ?>
                    <input type="hidden" name="tam_rank_general_settings[_tamrank_full_toggles]" value="1">

                    <!-- ═══════════════════════════════════════════════════════
                         TAB 1 — Algemeen (#general)
                         ═══════════════════════════════════════════════════════ -->
                    <div id="general" class="tr-tab-panel is-active">

                        <!-- Functionaliteit Card -->
                        <div class="tr-card">
                            <div class="tr-card-header">
                                <div>
                                    <h2 class="tr-card-title"><?php tamrank_e('features', 'Features'); ?></h2>
                                    <p class="tr-card-subtitle">
                                        <?php tamrank_e('features_description', 'Enable or disable TamRank features'); ?></p>
                                </div>
                            </div>
                            <div class="tr-card-body">
                                <!-- SEO Meta Box -->
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('seo_meta_box', 'SEO Meta Box'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('seo_meta_box_description', 'Shows the TamRank SEO meta box when editing posts and pages. Here you can set meta titles, descriptions and focus keywords.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[enable_meta_boxes]" value="0">
                                        <input type="checkbox" id="enable_meta_boxes"
                                            name="tam_rank_general_settings[enable_meta_boxes]" value="1" <?php checked(isset($general_settings['enable_meta_boxes']) ? $general_settings['enable_meta_boxes'] : 1); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <!-- 404 Monitoring -->
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('404_monitoring', '404 Monitoring'); ?>
                                        </p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('404_monitoring_description', 'Automatically tracks which pages give a 404 error. This way you can detect broken links and set up redirects.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[enable_404_monitoring]" value="0">
                                        <input type="checkbox" id="enable_404_monitoring"
                                            name="tam_rank_general_settings[enable_404_monitoring]" value="1" <?php checked(isset($general_settings['enable_404_monitoring']) ? $general_settings['enable_404_monitoring'] : 1); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <!-- SEO Audit -->
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('seo_audit', 'SEO Audit'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('seo_audit_description', 'Analyze your pages for SEO improvements. Get a score and concrete tips to improve your visibility.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[enable_seo_audit]" value="0">
                                        <input type="checkbox" id="enable_seo_audit"
                                            name="tam_rank_general_settings[enable_seo_audit]" value="1" <?php checked(isset($general_settings['enable_seo_audit']) ? $general_settings['enable_seo_audit'] : 1); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <!-- Redirects -->
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('redirects', 'Redirects'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('redirects_description', 'Manage 301/302/307/410/451 redirects and automatically redirect deleted pages.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[enable_redirects]" value="0">
                                        <input type="checkbox" id="enable_redirects"
                                            name="tam_rank_general_settings[enable_redirects]" value="1" <?php checked(isset($general_settings['enable_redirects']) ? $general_settings['enable_redirects'] : 1); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <!-- Auto-redirect on delete -->
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('auto_redirect_setting_label', 'Prompt for redirect when deleting a page'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('auto_redirect_setting_refresh_note', 'Page refresh required for toggle change to take effect.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[enable_auto_redirect_on_delete]" value="0">
                                        <input type="checkbox" id="enable_auto_redirect_on_delete"
                                            name="tam_rank_general_settings[enable_auto_redirect_on_delete]" value="1" <?php checked(isset($general_settings['enable_auto_redirect_on_delete']) ? $general_settings['enable_auto_redirect_on_delete'] : 1); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <!-- Auto Indexering -->
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('auto_indexing', 'Auto-indexing'); ?>
                                        </p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('auto_indexing_description', 'Automatically request indexing from Google after publishing or updating a page.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[enable_auto_indexing]" value="0">
                                        <input type="checkbox" id="enable_auto_indexing"
                                            name="tam_rank_general_settings[enable_auto_indexing]" value="1" <?php checked(isset($general_settings['enable_auto_indexing']) ? $general_settings['enable_auto_indexing'] : 0); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <!-- IndexNow -->
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('indexnow_label', 'IndexNow'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('indexnow_description', 'Notify Bing, Yandex, Naver, Seznam, Amazon and Yep the moment you publish or update a page. One submission reaches all of them. Switching this on registers your site with TamRank and publishes a small key file in your site root.'); ?>
                                        </p>
                                        <?php
                                        $indexnow_key_url = class_exists('TamRank_IndexNow') ? TamRank_IndexNow::key_url() : '';
                                        $indexnow_result  = class_exists('TamRank_IndexNow') ? TamRank_IndexNow::get_last_result() : null;
                                        if (!empty($general_settings['enable_indexnow']) && $indexnow_key_url !== ''):
                                            ?>
                                            <p class="tr-toggle-row__desc">
                                                <?php tamrank_e('indexnow_key_file', 'Key file:'); ?>
                                                <a href="<?php echo esc_url($indexnow_key_url); ?>" target="_blank" rel="noopener"><?php echo esc_html($indexnow_key_url); ?></a>
                                            </p>
                                        <?php endif; ?>
                                        <?php
                                        if (!empty($general_settings['enable_indexnow']) && is_array($indexnow_result) && !empty($indexnow_result['message'])):
                                            // The alert variant follows indexnow_status — the verdict of the
                                            // search engines themselves — not the HTTP status of our own API
                                            // call, which answers 200 for a relayed 403 as well.
                                            $indexnow_variants = array(
                                                'ok'            => 'tr-alert--success',
                                                'pending'       => 'tr-alert--info',
                                                'rate_limited'  => 'tr-alert--warning',
                                                'unavailable'   => 'tr-alert--warning',
                                                'no_api_key'    => 'tr-alert--warning',
                                                'key_invalid'   => 'tr-alert--error',
                                                'host_mismatch' => 'tr-alert--error',
                                                'error'         => 'tr-alert--error',
                                            );
                                            $indexnow_state   = isset($indexnow_result['state']) ? (string) $indexnow_result['state'] : 'error';
                                            $indexnow_variant = isset($indexnow_variants[$indexnow_state]) ? $indexnow_variants[$indexnow_state] : 'tr-alert--error';
                                            ?>
                                            <div class="tr-alert <?php echo esc_attr($indexnow_variant); ?>" style="margin-top:var(--tr-space-3);">
                                                <span class="tr-alert-icon" aria-hidden="true">&#8226;</span>
                                                <span data-indexnow-state="<?php echo esc_attr($indexnow_state); ?>"><?php echo esc_html($indexnow_result['message']); ?></span>
                                            </div>
                                        <?php endif; ?>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[enable_indexnow]" value="0">
                                        <input type="checkbox" id="enable_indexnow"
                                            name="tam_rank_general_settings[enable_indexnow]" value="1" <?php checked(isset($general_settings['enable_indexnow']) ? $general_settings['enable_indexnow'] : 0); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <!-- Change Tracking toggle — rendered by PRO (cluster 14 strip) -->
                                <?php
                                /**
                                 * Fires inside the General settings toggle group. PRO renders the
                                 * SEO Change Tracker toggle here (gated on change_tracking); the field
                                 * posts into FREE's registered tam_rank_general_settings option, so a
                                 * previously-saved value keeps working. FREE-only installs render nothing.
                                 *
                                 * @since 1.2.0
                                 * @param array $general_settings Current general settings values.
                                 */
                                do_action('tamrank_settings_change_tracking_toggle', $general_settings);
                                ?>

                            </div>
                        </div>

                        <?php
                        // --- Content Types: detect custom types ---
                        $ct_config = TamRank_Content_Type_Config::get_config();
                        $ct_general = $ct_config['general'];
                        $builtin_taxonomies = ['category', 'post_tag'];
                        $builtin_post_types = ['post', 'page'];
                        $ct_internal_tax = ['post_format', 'nav_menu', 'link_category', 'wp_theme', 'wp_template_part_area'];

                        $all_taxonomies = get_taxonomies(['public' => true], 'objects');
                        $custom_taxonomies = [];
                        $builtin_tax_list = [];
                        foreach ($all_taxonomies as $tax_slug => $tax) {
                            if (in_array($tax_slug, $ct_internal_tax, true)) continue;
                            if ($tax->publicly_queryable === false) continue;
                            if (!in_array($tax_slug, $builtin_taxonomies, true)) {
                                $custom_taxonomies[$tax_slug] = $tax;
                            } else {
                                $builtin_tax_list[$tax_slug] = $tax;
                            }
                        }

                        $all_post_types = get_post_types(['public' => true], 'objects');
                        $custom_post_types = [];
                        $builtin_pt_list = [];
                        foreach ($all_post_types as $pt_slug => $pt) {
                            if ($pt_slug === 'attachment') continue;
                            if ($pt->publicly_queryable === false && $pt_slug !== 'page') continue;
                            if (!in_array($pt_slug, $builtin_post_types, true)) {
                                $custom_post_types[$pt_slug] = $pt;
                            } else {
                                $builtin_pt_list[$pt_slug] = $pt;
                            }
                        }

                        $has_custom_types = !empty($custom_taxonomies) || !empty($custom_post_types);
                        $ct_expanded = !empty($general_settings['show_content_types']);
                        ?>

                        <?php if ($has_custom_types) : ?>
                        <!-- Content Types Card -->
                        <div class="tr-card">
                            <div class="tr-card-header" style="display:flex;align-items:center;justify-content:space-between;">
                                <div>
                                    <h2 class="tr-card-title"><?php tamrank_e('content_types', 'Content Types'); ?></h2>
                                    <p class="tr-card-subtitle">
                                        <?php tamrank_e('content_types_desc_toggle', 'Configure which content types are included in SEO audit and sitemap. By default only posts and pages are active.'); ?>
                                    </p>
                                </div>
                                <label class="tr-toggle-switch">
                                    <input type="hidden" name="tam_rank_general_settings[show_content_types]" value="0">
                                    <input type="checkbox" id="tr-ct-toggle"
                                        name="tam_rank_general_settings[show_content_types]" value="1"
                                        <?php checked($ct_expanded); ?>>
                                    <span class="tr-toggle-track"></span>
                                </label>
                            </div>
                            <div class="tr-card-body" id="tr-ct-body" <?php echo $ct_expanded ? '' : 'hidden'; ?>>

                                <?php foreach ($builtin_tax_list as $tax_slug => $tax) :
                                    $tax_general = isset($ct_general['taxonomies'][$tax_slug]) ? $ct_general['taxonomies'][$tax_slug] : ['seo_fields' => true, 'audit' => true];
                                    $tax_enabled = !empty($tax_general['seo_fields']);
                                ?>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php echo esc_html($tax->label); ?></p>
                                        <p class="tr-toggle-row__desc"><?php echo esc_html(sprintf(tamrank__('tax_seo_desc', 'SEO fields and audit for %s'), strtolower($tax->label))); ?></p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[content_types][taxonomies][<?php echo esc_attr($tax_slug); ?>][seo_fields]" value="0">
                                        <input type="checkbox"
                                            name="tam_rank_general_settings[content_types][taxonomies][<?php echo esc_attr($tax_slug); ?>][seo_fields]"
                                            value="1"
                                            <?php checked($tax_enabled); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>
                                <?php endforeach; ?>

                                <!-- Post Types (built-in) -->
                                <p class="tr-content-group__label" style="margin-top: var(--tr-space-4)"><?php tamrank_e('post_types', 'Post Types'); ?></p>
                                <?php foreach ($builtin_pt_list as $pt_slug => $pt) :
                                    $pt_general = isset($ct_general['post_types'][$pt_slug]) ? $ct_general['post_types'][$pt_slug] : ['audit' => true];
                                    $pt_audit_on = !empty($pt_general['audit']);
                                ?>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php echo esc_html($pt->label); ?></p>
                                        <p class="tr-toggle-row__desc"><?php tamrank_e('post_type_audit_desc', 'SEO audit for this post type'); ?></p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[content_types][post_types][<?php echo esc_attr($pt_slug); ?>][audit]" value="0">
                                        <input type="checkbox"
                                            name="tam_rank_general_settings[content_types][post_types][<?php echo esc_attr($pt_slug); ?>][audit]"
                                            value="1"
                                            <?php checked($pt_audit_on); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>
                                <?php endforeach; ?>

                                <!-- Custom types -->
                                <?php if (!empty($custom_taxonomies)) : ?>
                                <p class="tr-content-group__label" style="margin-top: var(--tr-space-4)"><?php tamrank_e('custom_taxonomies', 'Custom Taxonomies'); ?></p>
                                <?php foreach ($custom_taxonomies as $tax_slug => $tax) :
                                    $tax_general = isset($ct_general['taxonomies'][$tax_slug]) ? $ct_general['taxonomies'][$tax_slug] : ['seo_fields' => true, 'audit' => true];
                                    $tax_seo_on = !empty($tax_general['seo_fields']);
                                    $tax_audit_on = !empty($tax_general['audit']);
                                ?>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php echo esc_html($tax->label); ?></p>
                                        <p class="tr-toggle-row__desc"><?php echo esc_html(sprintf(tamrank__('tax_seo_desc', 'SEO fields and audit for %s'), strtolower($tax->label))); ?></p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[content_types][taxonomies][<?php echo esc_attr($tax_slug); ?>][seo_fields]" value="0">
                                        <input type="checkbox"
                                            name="tam_rank_general_settings[content_types][taxonomies][<?php echo esc_attr($tax_slug); ?>][seo_fields]"
                                            value="1"
                                            <?php checked($tax_enabled); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>
                                <?php endforeach; ?>
                                <?php endif; ?>

                                <?php if (!empty($custom_post_types)) : ?>
                                <p class="tr-content-group__label" style="margin-top: var(--tr-space-4)"><?php tamrank_e('custom_post_types', 'Custom Post Types'); ?></p>
                                <?php foreach ($custom_post_types as $pt_slug => $pt) :
                                    $pt_general = isset($ct_general['post_types'][$pt_slug]) ? $ct_general['post_types'][$pt_slug] : ['audit' => true];
                                    $pt_audit_on = !empty($pt_general['audit']);
                                ?>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php echo esc_html($pt->label); ?></p>
                                        <p class="tr-toggle-row__desc"><?php tamrank_e('post_type_audit_desc', 'SEO audit for this post type'); ?></p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_general_settings[content_types][post_types][<?php echo esc_attr($pt_slug); ?>][audit]" value="0">
                                        <input type="checkbox"
                                            name="tam_rank_general_settings[content_types][post_types][<?php echo esc_attr($pt_slug); ?>][audit]"
                                            value="1"
                                            <?php checked($pt_audit_on); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>
                                <?php endforeach; ?>
                                <?php endif; ?>

                            </div>
                        </div>

                        <?php ob_start(); ?>
                        document.getElementById('tr-ct-toggle').addEventListener('change', function() {
                            document.getElementById('tr-ct-body').hidden = !this.checked;
                        });
                        <?php wp_add_inline_script('tam-rank-admin', ob_get_clean()); ?>
                        <?php endif; ?>

                        <!-- Taal & Regio Card -->
                        <div class="tr-card">
                            <div class="tr-card-header">
                                <div>
                                    <h2 class="tr-card-title"><?php tamrank_e('language_region', 'Language & Region'); ?></h2>
                                    <p class="tr-card-subtitle">
                                        <?php tamrank_e('language_region_desc', 'Default language for analyses, suggestions and reports'); ?>
                                    </p>
                                </div>
                            </div>
                            <div class="tr-card-body">
                                <div class="tr-toggle-row" style="align-items: flex-start;">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('language', 'Language'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('language_description', 'Sets the language of the TamRank interface'); ?>
                                        </p>
                                    </div>
                                    <select id="tam_rank_language" name="tam_rank_general_settings[language]" class="tr-select"
                                        style="width: 220px; flex-shrink: 0;">
                                        <?php
                                        $current_language = isset($general_settings['language']) ? $general_settings['language'] : 'en';
                                        $available_languages = function_exists('tamrank_get_available_languages') ? tamrank_get_available_languages() : array('en' => 'English');
                                        foreach ($available_languages as $code => $name) {
                                            printf(
                                                '<option value="%s" %s>%s</option>',
                                                esc_attr($code),
                                                selected($current_language, $code, false),
                                                esc_html($name)
                                            );
                                        }
                                        ?>
                                    </select>
                                </div>
                            </div>
                        </div>

                    </div><!-- /general -->

                    <!-- ═══════════════════════════════════════════════════════
                         TAB 2 — Integraties (#integrations)
                         ═══════════════════════════════════════════════════════ -->
                    <div id="integrations" class="tr-tab-panel">

                        <!-- Google Search Console Connection Card (Stepped Card v2) -->
                        <?php
                        // Property mismatch detection (reused across states)
                        $property_mismatch = false;
                        $current_host_normalized = '';
                        if ($gsc_connected && $gsc_property) {
                            $current_site_url = site_url();
                            $current_host = wp_parse_url($current_site_url, PHP_URL_HOST);
                            $property_to_check = $gsc_property;
                            if (strpos($property_to_check, 'sc-domain:') === 0) {
                                $property_to_check = substr($property_to_check, 10);
                            }
                            $property_host = (strpos($property_to_check, '://') !== false)
                                ? wp_parse_url($property_to_check, PHP_URL_HOST)
                                : $property_to_check;
                            $current_host_normalized = preg_replace('/^www\./i', '', $current_host);
                            $property_host_normalized = preg_replace('/^www\./i', '', $property_host);
                            $property_mismatch = ($current_host_normalized !== $property_host_normalized);
                        }

                        // Determine badge — gsc_connect is FREE, so PRO badge branch is a dead path
                        $gsc_badge_class = 'tr-badge--inactive';
                        $gsc_badge_text  = tamrank__('not_connected', 'Not connected');
                        if (!tam_rank_can_use_feature('gsc_connect')) {
                            $gsc_badge_class = 'tr-badge--pro';
                            $gsc_badge_text  = 'PRO';
                        } elseif ($gsc_connected && $gsc_property && $property_mismatch) {
                            $gsc_badge_class = 'tr-badge--warning';
                            $gsc_badge_text  = tamrank__('gsc_mismatch_badge', 'Mismatch');
                        } elseif ($gsc_connected && $gsc_property) {
                            $gsc_badge_class = 'tr-badge--connected';
                            $gsc_badge_text  = tamrank__('connected', 'Connected');
                        } elseif ($gsc_needs_selection) {
                            $gsc_badge_class = 'tr-badge--warning';
                            $gsc_badge_text  = tamrank__('needs_setup', 'Select property');
                        }

                        // Disconnect URL (reused)
                        $gsc_disconnect_url = wp_nonce_url(add_query_arg(array('action' => 'tam_rank_gsc_disconnect', 'page' => 'tam-rank-settings'), admin_url('admin.php')), 'tam_rank_gsc_disconnect');
                        ?>
                        <div class="tr-connection-card">
                            <!-- Header — constant across all states -->
                            <div class="tr-connection-header">
                                <div class="tr-connection-icon">G</div>
                                <div>
                                    <p class="tr-connection-name">Google Search Console</p>
                                    <p class="tr-connection-desc"><?php tamrank_e('gsc_description', 'View clicks, impressions and positions directly in your dashboard.'); ?></p>
                                </div>
                                <div class="tr-card-header-right">
                                    <span class="tr-badge <?php echo esc_attr($gsc_badge_class); ?>"><span class="tr-badge-dot"></span> <?php echo esc_html($gsc_badge_text); ?></span>
                                </div>
                            </div>

                            <?php if (!tam_rank_can_use_feature('gsc_connect')): ?>
                                <!-- State 1: Free plan — upsell bar (dead branch: gsc_connect is FREE) -->
                                <div class="tr-upsell">
                                    <p class="tr-upsell-text"><?php tamrank_e('gsc_pro_upsell_settings', 'Link Google Search Console and view keywords, positions and clicks per page.'); ?></p>
                                    <a href="<?php echo esc_url(tam_rank_get_upgrade_url('settings', 'gsc_integration')); ?>" class="tr-upsell-link" target="_blank" rel="noopener"><?php tamrank_e('view_pro', 'View PRO'); ?> &rarr;</a>
                                </div>

                            <?php elseif ($gsc_connected && $gsc_property && !$property_mismatch): ?>
                                <!-- State 4: Connected, correct property -->
                                <div class="tr-card-body">
                                    <div class="tr-connected-row" id="tam_gsc_connected_row">
                                        <div class="tr-connected-property">
                                            <span class="dot"></span>
                                            <span><?php echo esc_html($gsc_property_display); ?></span>
                                        </div>
                                        <div class="tr-connected-actions">
                                            <button type="button" id="tam_gsc_change_property" class="tr-btn tr-btn--ghost"><?php tamrank_e('gsc_change_property', 'Change'); ?></button>
                                            <span class="tr-action-sep">|</span>
                                            <a href="<?php echo esc_url($gsc_disconnect_url); ?>" class="tr-btn tr-btn--ghost tr-btn--danger" data-tr-confirm="<?php echo esc_attr(tamrank__('gsc_disconnect_confirm', 'Are you sure you want to disconnect?')); ?>" data-tr-confirm-title="<?php echo esc_attr(tamrank__('gsc_disconnect_title', 'Disconnect Search Console?')); ?>" data-tr-confirm-label="<?php echo esc_attr(tamrank__('gsc_disconnect_link', 'Disconnect')); ?>" data-tr-confirm-variant="danger"><?php tamrank_e('gsc_disconnect_link', 'Disconnect'); ?></a>
                                        </div>
                                    </div>

                                    <!-- State 4b: Change property form (hidden, toggled via JS) -->
                                    <div id="tam_gsc_property_form" class="tr-advanced-content">
                                        <?php
                                        $change_properties = $gsc_connector ? $gsc_connector->fetch_properties() : array();
                                        if (!is_wp_error($change_properties) && !empty($change_properties)):
                                        ?>
                                        <div class="tr-property-select">
                                            <select class="tr-select tam-gsc-property-select">
                                                <?php foreach ($change_properties as $prop):
                                                    $prop_url = $prop['siteUrl'] ?? $prop['site_url'] ?? '';
                                                    if (empty($prop_url)) continue;
                                                    $selected_attr = ($prop_url === $gsc_property) ? 'selected' : '';
                                                    $prop_display = (strpos($prop_url, 'sc-domain:') === 0) ? substr($prop_url, 10) : $prop_url;
                                                ?>
                                                    <option value="<?php echo esc_attr($prop_url); ?>" <?php echo esc_attr($selected_attr); ?>><?php echo esc_html($prop_display); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                            <div class="tam-gsc-message tr-alert" hidden></div>
                                            <div class="tr-select-actions">
                                                <a href="<?php echo esc_url($gsc_disconnect_url); ?>" class="tr-btn tr-btn--ghost tr-btn--danger" data-tr-confirm="<?php echo esc_attr(tamrank__('gsc_disconnect_confirm', 'Are you sure you want to disconnect?')); ?>" data-tr-confirm-title="<?php echo esc_attr(tamrank__('gsc_disconnect_title', 'Disconnect Search Console?')); ?>" data-tr-confirm-label="<?php echo esc_attr(tamrank__('gsc_disconnect_link', 'Disconnect')); ?>" data-tr-confirm-variant="danger"><?php tamrank_e('gsc_disconnect_link', 'Disconnect'); ?></a>
                                                <button type="button" id="tam_gsc_cancel_change" class="tr-btn tr-btn--ghost"><?php tamrank_e('cancel', 'Cancel'); ?></button>
                                                <button type="button" class="tr-btn tr-btn--primary tam-gsc-save-property" data-label="<?php echo esc_attr(tamrank__('gsc_save_property', 'Save')); ?>"><?php tamrank_e('gsc_save_property', 'Save'); ?></button>
                                            </div>
                                        </div>
                                        <?php else: ?>
                                            <p class="tr-select-hint"><?php tamrank_e('gsc_no_other_properties', 'No other properties available.'); ?></p>
                                        <?php endif; ?>
                                    </div>
                                </div>
                                <div class="tr-connected-footer">
                                    <span class="tr-connected-footer__sync"><?php tamrank_e('gsc_data_syncs', 'Data syncs daily'); ?></span>
                                    <a href="<?php echo esc_url(admin_url('admin.php?page=tam-rank-dashboard')); ?>"><?php tamrank_e('gsc_go_to_dashboard', 'Go to Dashboard'); ?> &rarr;</a>
                                </div>

                            <?php elseif ($gsc_connected && $gsc_property && $property_mismatch): ?>
                                <!-- State 5: Connected, wrong property -->
                                <div class="tr-card-body" style="display:flex;flex-direction:column;gap:var(--tr-space-4);">
                                    <div class="tr-mismatch-row">
                                        <span class="dot"></span>
                                        <span class="tr-mismatch-property"><?php echo esc_html($gsc_property_display); ?></span>
                                        <span class="tr-mismatch-hint"><?php printf(esc_html(tamrank__('gsc_mismatch_expected', 'Expected: %s')), esc_html($current_host_normalized)); ?></span>
                                    </div>
                                    <div class="tr-alert tr-alert--warning">
                                        <span class="tr-alert-icon" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495z"/><line x1="10" y1="8" x2="10" y2="12"/><line x1="10" y1="15" x2="10" y2="15"/></svg></span>
                                        <div class="tr-alert-content">
                                            <span><?php tamrank_e('gsc_mismatch_desc', "This property doesn't match your site domain. Select the correct property below to receive accurate data."); ?></span>
                                        </div>
                                    </div>
                                    <?php
                                    $mismatch_properties = $gsc_connector ? $gsc_connector->fetch_properties() : array();
                                    // Disconnect is a local-only action and must stay reachable even when
                                    // the property list fails to load — otherwise a failed fetch locks the
                                    // user out of the one action that recovers a broken/mismatched connection.
                                    $mismatch_has_props = !is_wp_error($mismatch_properties) && !empty($mismatch_properties);
                                    ?>
                                    <div class="tr-property-select">
                                        <?php if ($mismatch_has_props): ?>
                                        <select class="tr-select tam-gsc-property-select">
                                            <option value=""><?php tamrank_e('gsc_select_placeholder', 'Select a property...'); ?></option>
                                            <?php foreach ($mismatch_properties as $prop):
                                                $prop_url = $prop['siteUrl'] ?? $prop['site_url'] ?? '';
                                                if (empty($prop_url)) continue;
                                                $selected_attr = ($prop_url === $gsc_property) ? 'selected' : '';
                                                $prop_display = (strpos($prop_url, 'sc-domain:') === 0) ? substr($prop_url, 10) : $prop_url;
                                            ?>
                                                <option value="<?php echo esc_attr($prop_url); ?>" <?php echo esc_attr($selected_attr); ?>><?php echo esc_html($prop_display); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                        <?php else: ?>
                                        <p class="tr-select-hint"><?php tamrank_e('gsc_properties_load_error', 'Could not load your Search Console properties. Disconnect and reconnect to try again.'); ?></p>
                                        <?php endif; ?>
                                        <div class="tam-gsc-message tr-alert" hidden></div>
                                        <div class="tr-select-actions">
                                            <a href="<?php echo esc_url($gsc_disconnect_url); ?>" class="tr-btn tr-btn--ghost tr-btn--danger" data-tr-confirm="<?php echo esc_attr(tamrank__('gsc_disconnect_confirm', 'Are you sure you want to disconnect?')); ?>" data-tr-confirm-title="<?php echo esc_attr(tamrank__('gsc_disconnect_title', 'Disconnect Search Console?')); ?>" data-tr-confirm-label="<?php echo esc_attr(tamrank__('gsc_disconnect_link', 'Disconnect')); ?>" data-tr-confirm-variant="danger"><?php tamrank_e('gsc_disconnect_link', 'Disconnect'); ?></a>
                                            <?php if ($mismatch_has_props): ?>
                                            <button type="button" class="tr-btn tr-btn--primary tam-gsc-save-property" data-label="<?php echo esc_attr(tamrank__('gsc_save_property', 'Save')); ?>"><?php tamrank_e('gsc_save_property', 'Save'); ?></button>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                </div>

                            <?php elseif ($gsc_needs_selection): ?>
                                <!-- State 3: Property selection (after OAuth) -->
                                <div class="tr-card-body">
                                    <div class="tr-alert tr-alert--success" style="margin-bottom:var(--tr-space-5);">
                                        <span class="tr-alert-icon">&#10003;</span>
                                        <span><?php tamrank_e('gsc_select_property', 'Google account connected. Select the property for this site.'); ?></span>
                                    </div>
                                    <div class="tr-property-select">
                                        <select class="tr-select tam-gsc-property-select">
                                            <option value=""><?php tamrank_e('gsc_select_placeholder', 'Select a property...'); ?></option>
                                            <?php foreach ($gsc_properties as $property):
                                                if (!is_array($property)) continue;
                                                $site_url = $property['siteUrl'] ?? $property['site_url'] ?? null;
                                                if (empty($site_url)) continue;
                                                $site_url_display = (strpos($site_url, 'sc-domain:') === 0) ? substr($site_url, 10) : $site_url;
                                            ?>
                                                <option value="<?php echo esc_attr($site_url); ?>"><?php echo esc_html($site_url_display); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                        <p class="tr-select-hint"><?php tamrank_e('gsc_select_hint', "Choose the property that matches this website's domain."); ?></p>
                                        <div class="tam-gsc-message tr-alert" hidden></div>
                                        <div class="tr-select-actions">
                                            <button type="button" class="tr-btn tr-btn--primary tam-gsc-save-property" data-label="<?php echo esc_attr(tamrank__('gsc_confirm_selection', 'Confirm')); ?>"><?php tamrank_e('gsc_confirm_selection', 'Confirm'); ?></button>
                                        </div>
                                    </div>
                                </div>

                            <?php elseif ($gsc_connector): ?>
                                <!-- State 2: PRO, not connected -->
                                <?php
                                // No network call on render. Registration + the VPS auth-URL
                                // fetch run only after the user clicks Connect, in the
                                // nonce-verified handle_connect() handler (wp.org guideline 7).
                                $gsc_connect_url = wp_nonce_url(
                                    admin_url('admin.php?page=tam-rank-settings&action=tam_rank_gsc_connect'),
                                    'tam_rank_gsc_connect_init'
                                );
                                $gsc_connect_error = get_transient('tam_rank_gsc_connect_error');
                                if ($gsc_connect_error) {
                                    delete_transient('tam_rank_gsc_connect_error');
                                }
                                ?>
                                <?php
                                // Ownership-proof verification failure gets its own
                                // calm, actionable notice (reuses the warning-alert
                                // component). Any other connect error keeps the
                                // generic error + promo + connect button.
                                $gsc_verify_failed = $gsc_connect_error && (($gsc_connect_error['code'] ?? '') === 'verify_failed');
                                ?>
                                <div class="tr-card-body tr-connect-body">
                                    <?php if ($gsc_verify_failed):
                                        $vf_data   = (isset($gsc_connect_error['data']) && is_array($gsc_connect_error['data'])) ? $gsc_connect_error['data'] : array();
                                        $vf_url     = !empty($vf_data['checked_url']) ? $vf_data['checked_url'] : home_url('/.well-known/tamrank-challenge.txt');
                                        $vf_code    = !empty($vf_data['reason']) ? $vf_data['reason'] : 'unreachable';
                                        $vf_reasons = array(
                                            'timeout'        => tamrank__('gsc_verify_reason_timeout', 'The page could not be reached from outside (timeout).'),
                                            'unreachable'    => tamrank__('gsc_verify_reason_unreachable', 'The page could not be reached from outside.'),
                                            'non_200'        => tamrank__('gsc_verify_reason_non200', 'The page did not load correctly from outside.'),
                                            'redirect'       => tamrank__('gsc_verify_reason_redirect', 'The page redirected, which we cannot follow for security.'),
                                            'nonce_mismatch' => tamrank__('gsc_verify_reason_mismatch', 'The verification value did not match. Please try again.'),
                                        );
                                        $vf_reason = isset($vf_reasons[$vf_code]) ? $vf_reasons[$vf_code] : $vf_reasons['unreachable'];
                                    ?>
                                        <div class="tr-alert tr-alert--warning tr-alert--structured">
                                            <span class="tr-alert__icon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
                                            <div class="tr-alert__content">
                                                <p class="tr-alert__title"><?php tamrank_e('gsc_verify_failed', "We couldn't verify your site"); ?></p>
                                                <p class="tr-alert__desc"><?php tamrank_e('gsc_verify_failed_desc', "Before connecting, TamRank places a short, one-time verification file on your site and checks it from our servers. This time we couldn't reach it — usually a firewall, security plugin or staging setup blocking outside requests."); ?></p>
                                                <div class="tr-verify-detail">
                                                    <div class="tr-verify-detail__row"><span class="tr-verify-detail__k"><?php tamrank_e('gsc_verify_checked', 'We checked'); ?></span><span class="tr-verify-detail__v"><code><?php echo esc_html($vf_url); ?></code></span></div>
                                                    <div class="tr-verify-detail__row"><span class="tr-verify-detail__k"><?php tamrank_e('gsc_verify_reason', 'Reason'); ?></span><span class="tr-verify-detail__v"><?php echo esc_html($vf_reason); ?></span></div>
                                                </div>
                                                <div class="tr-verify-actions">
                                                    <a href="<?php echo esc_url($gsc_connect_url); ?>" class="tr-btn tr-btn--primary">
                                                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
                                                        <?php tamrank_e('gsc_verify_try_again', 'Try again'); ?>
                                                    </a>
                                                    <span class="tr-verify-hint"><?php
                                                        printf(
                                                            wp_kses(tamrank__('gsc_verify_support', "Still not working? %s and we'll connect your site manually."), array('a' => array('href' => array(), 'target' => array(), 'rel' => array()))),
                                                            '<a href="https://tamrank.com/contact/" target="_blank" rel="noopener">' . esc_html(tamrank__('gsc_verify_contact', 'Contact support')) . '</a>'
                                                        );
                                                    ?></span>
                                                </div>
                                            </div>
                                        </div>
                                    <?php else: ?>
                                        <p><?php tamrank_e('gsc_connect_promo', 'Connect your Search Console to see live performance data — clicks, impressions, and keyword positions per page.'); ?></p>
                                        <?php if ($gsc_connect_error): ?>
                                            <div class="tr-alert tr-alert--error">
                                                <span class="tr-alert-icon">!</span>
                                                <span>
                                                    <strong><?php tamrank_e('gsc_auth_error', 'Could not initialize Google connection.'); ?></strong>
                                                    <?php if (!empty($gsc_connect_error['msg'])): ?>
                                                        <br><small><?php echo esc_html($gsc_connect_error['msg']); ?> <code><?php echo esc_html($gsc_connect_error['code']); ?></code></small>
                                                    <?php endif; ?>
                                                </span>
                                            </div>
                                        <?php endif; ?>
                                        <div>
                                            <a href="<?php echo esc_url($gsc_connect_url); ?>" class="tr-btn tr-btn--primary">
                                                <svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z" fill="white"/></svg>
                                                <?php tamrank_e('gsc_connect_btn', 'Connect with Google'); ?>
                                            </a>
                                        </div>
                                    <?php endif; ?>
                                </div>
                            <?php endif; ?>
                        </div>

                        <?php
                        /**
                         * PRO seam — GA4 conversion connection card.
                         *
                         * Renders directly under the Google Search Console card (above
                         * PageSpeed) so the PRO GA4 connector sits with the other Google
                         * connections. Passes the current GSC connected-state so the PRO
                         * card can align its copy/state with the GSC connection. Mirrors
                         * the existing tamrank_settings_* seam pattern; all GA4 logic
                         * stays in PRO.
                         */
                        do_action('tamrank_settings_ga4_connect', $gsc_connected);
                        ?>

                        <!-- Unified GSC property selection JS (deduplicated) -->
                        <?php ob_start(); ?>
                        jQuery(document).ready(function($) {
                            // State 4 → 4b: toggle change form
                            $(document).on('click', '#tam_gsc_change_property', function() {
                                $('#tam_gsc_property_form').addClass('is-open');
                                $('#tam_gsc_connected_row .tr-connected-actions').hide();
                            });
                            $(document).on('click', '#tam_gsc_cancel_change', function() {
                                $('#tam_gsc_property_form').removeClass('is-open');
                                $('#tam_gsc_connected_row .tr-connected-actions').show();
                                $('.tam-gsc-message').hide();
                            });

                            // Unified property save (works for State 3, 4b, and 5)
                            $(document).on('click', '.tam-gsc-save-property', function() {
                                var $btn = $(this);
                                var $container = $btn.closest('.tr-property-select');
                                var $select = $container.find('select');
                                var $msg = $container.find('.tam-gsc-message');
                                var property = $select.val();

                                if (!property) {
                                    $msg.removeClass('tr-alert--success').addClass('tr-alert--error')
                                        .html('<span class="tr-alert-icon">!</span><span><?php echo esc_js(tamrank__('gsc_select_property_error', 'Please select a property.')); ?></span>')
                                        .removeAttr('hidden').show();
                                    return;
                                }

                                $btn.prop('disabled', true).text('<?php echo esc_js(tamrank__('saving', 'Saving...')); ?>');
                                $msg.hide();

                                $.ajax({
                                    url: ajaxurl,
                                    type: 'POST',
                                    data: {
                                        action: 'tamrank_gsc_select_property',
                                        property: property,
                                        nonce: '<?php echo esc_js(wp_create_nonce('tamrank_gsc_select_property_ajax')); ?>'
                                    },
                                    success: function(response) {
                                        if (response.success) {
                                            $msg.removeClass('tr-alert--error').addClass('tr-alert--success')
                                                .html('<span class="tr-alert-icon">&#10003;</span><span>' + (response.data.message || '<?php echo esc_js(tamrank__('gsc_property_saved', 'Property saved!')); ?>') + '</span>')
                                                .removeAttr('hidden').show();
                                            setTimeout(function() { location.reload(); }, 1000);
                                        } else {
                                            $msg.removeClass('tr-alert--success').addClass('tr-alert--error')
                                                .html('<span class="tr-alert-icon">!</span><span>' + (response.data.message || '<?php echo esc_js(tamrank__('gsc_property_error', 'An error occurred.')); ?>') + '</span>')
                                                .removeAttr('hidden').show();
                                            $btn.prop('disabled', false).text($btn.data('label'));
                                        }
                                    },
                                    error: function() {
                                        $msg.removeClass('tr-alert--success').addClass('tr-alert--error')
                                            .html('<span class="tr-alert-icon">!</span><span><?php echo esc_js(tamrank__('connection_error', 'Connection error. Please try again.')); ?></span>')
                                            .removeAttr('hidden').show();
                                        $btn.prop('disabled', false).text($btn.data('label'));
                                    }
                                });
                            });
                        });
                        <?php wp_add_inline_script('tam-rank-admin', ob_get_clean()); ?>

                        <!-- Google PageSpeed Card (Stepped Card v2) -->
                        <?php
                        $ps_api_key = tam_rank_cached_option('tam_rank_pagespeed_api_key', '');
                        $ps_has_key = !empty($ps_api_key);
                        $ps_masked  = $ps_has_key ? tam_rank_mask_api_key($ps_api_key) : '';

                        $ps_badge_class = 'tr-badge--inactive';
                        $ps_badge_text  = tamrank__('not_configured', 'Not configured');
                        if (!tam_rank_can_use_feature('pagespeed_analysis')) {
                            $ps_badge_class = 'tr-badge--pro';
                            $ps_badge_text  = 'PRO';
                        } elseif ($ps_has_key) {
                            $ps_badge_class = 'tr-badge--connected';
                            $ps_badge_text  = tamrank__('configured', 'Configured');
                        }
                        ?>
                        <div class="tr-connection-card">
                            <div class="tr-connection-header">
                                <div class="tr-connection-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg></div>
                                <div>
                                    <p class="tr-connection-name"><?php tamrank_e('pagespeed_insights', 'PageSpeed Insights'); ?></p>
                                    <p class="tr-connection-desc"><?php tamrank_e('pagespeed_card_desc', 'Test page speed and get optimization tips.'); ?></p>
                                </div>
                                <div class="tr-card-header-right">
                                    <span class="tr-badge <?php echo esc_attr($ps_badge_class); ?>"><span class="tr-badge-dot"></span> <?php echo esc_html($ps_badge_text); ?></span>
                                </div>
                            </div>

                            <?php if (!tam_rank_can_use_feature('pagespeed_analysis')): ?>
                                <!-- State 1: Free plan — upsell -->
                                <div class="tr-upsell">
                                    <p class="tr-upsell-text"><?php tamrank_e('pagespeed_pro_upsell_settings', 'Test your page speed directly from WordPress with Google PageSpeed Insights.'); ?></p>
                                    <a href="<?php echo esc_url(tam_rank_get_upgrade_url('settings', 'pagespeed')); ?>" class="tr-upsell-link" target="_blank" rel="noopener"><?php tamrank_e('view_pro', 'View PRO'); ?> &rarr;</a>
                                </div>

                            <?php elseif ($ps_has_key): ?>
                                <!-- State 3: Key configured — compact connected row -->
                                <div class="tr-card-body">
                                    <div class="tr-connected-row" id="tr_ps_connected_row">
                                        <div class="tr-connected-property">
                                            <span class="dot"></span>
                                            <span class="tr-key-display"><?php echo esc_html($ps_masked); ?></span>
                                        </div>
                                        <div class="tr-connected-actions">
                                            <button type="button" id="tr_ps_change_key" class="tr-btn tr-btn--ghost"><?php tamrank_e('gsc_change_property', 'Change'); ?></button>
                                            <span class="tr-action-sep">|</span>
                                            <button type="button" id="tr_ps_remove_key" class="tr-btn tr-btn--ghost tr-btn--danger"><?php tamrank_e('ps_remove', 'Remove'); ?></button>
                                        </div>
                                    </div>

                                    <!-- State 3b: Change key form (hidden, toggle via JS) -->
                                    <div id="tr_ps_change_form" class="tr-advanced-content">
                                        <div class="tr-input-row">
                                            <?php /* Disabled until the user opens this form. It sits inside the
                                                     settings <form> and is only hidden with CSS, so without this it
                                                     posted its empty value along with every save of every other tab
                                                     and overwrote the stored API key with "". Empty still means
                                                     "remove" — the Change and Remove handlers enable the field when
                                                     that is what the user asked for. */ ?>
                                            <input type="password" autocomplete="off" id="google_pagespeed_api_key" name="tam_rank_pagespeed_api_key" class="tr-input" value="" disabled placeholder="<?php echo esc_attr(tamrank__('ps_new_key_placeholder', 'Paste new API key...')); ?>">
                                            <button type="submit" class="tr-btn tr-btn--primary"><?php tamrank_e('gsc_save_property', 'Save'); ?></button>
                                            <button type="button" id="tr_ps_cancel_change" class="tr-btn tr-btn--ghost"><?php tamrank_e('cancel', 'Cancel'); ?></button>
                                        </div>
                                    </div>
                                </div>
                                <div class="tr-connected-footer">
                                    <span class="tr-connected-footer__sync"><?php tamrank_e('ps_ready', 'Ready to analyze'); ?></span>
                                    <a href="<?php echo esc_url(admin_url('admin.php?page=tam-rank-pagespeed')); ?>"><?php tamrank_e('ps_go_to', 'Go to PageSpeed'); ?> &rarr;</a>
                                </div>

                            <?php else: ?>
                                <!-- State 2: PRO, no key — inline input row -->
                                <div class="tr-card-body">
                                    <div class="tr-input-row">
                                        <input type="password" autocomplete="off" id="google_pagespeed_api_key" name="tam_rank_pagespeed_api_key" class="tr-input" value="" placeholder="<?php echo esc_attr(tamrank__('pagespeed_api_key_placeholder_v2', 'Paste your Google API key here...')); ?>">
                                        <a href="https://developers.google.com/speed/docs/insights/v5/get-started" target="_blank" class="tr-help-link"><?php tamrank_e('how_to_get_api_key_v2', 'How do I get a key?'); ?> &rarr;</a>
                                        <button type="submit" class="tr-btn tr-btn--primary"><?php tamrank_e('save_btn', 'Save'); ?></button>
                                    </div>
                                </div>
                            <?php endif; ?>
                        </div>

                        <!-- PageSpeed card JS -->
                        <?php ob_start(); ?>
                        jQuery(document).ready(function($) {
                            $('#tr_ps_change_key').on('click', function() {
                                $('#tr_ps_change_form').addClass('is-open');
                                // Only now does the key field join the submit.
                                $('#google_pagespeed_api_key').prop('disabled', false).trigger('focus');
                                $('#tr_ps_connected_row .tr-connected-actions').hide();
                            });
                            $('#tr_ps_cancel_change').on('click', function() {
                                $('#tr_ps_change_form').removeClass('is-open');
                                // Out of the submit again, and empty, so a later save cannot post it.
                                $('#google_pagespeed_api_key').val('').prop('disabled', true);
                                $('#tr_ps_connected_row .tr-connected-actions').show();
                            });
                            $('#tr_ps_remove_key').on('click', function() {
                                var $form = $(this).closest('form');
                                window.TamRank.confirm({
                                    title: '<?php echo esc_js(tamrank__('ps_remove_title', 'Remove API key?')); ?>',
                                    message: '<?php echo esc_js(tamrank__('ps_remove_confirm', 'Are you sure you want to remove this API key?')); ?>',
                                    confirmLabel: '<?php echo esc_js(tamrank__('remove', 'Remove')); ?>',
                                    variant: 'danger'
                                }).then(function (ok) {
                                    if (!ok) return;
                                    // Removing is the one case where an empty value is meant, so the
                                    // field is enabled on purpose here.
                                    $('#google_pagespeed_api_key').val('').prop('disabled', false);
                                    $form.submit();
                                });
                            });
                        });
                        <?php wp_add_inline_script('tam-rank-admin', ob_get_clean()); ?>

                        <?php
                        /**
                         * PRO seam — AI Agents (MCP) token management + Connect-Claude setup.
                         *
                         * The PRO add-on (TamRank_Pro_Agents_Settings) fills this with the
                         * PAT manager, gated on an active licence (unlicensed PRO installs
                         * get its own upsell card). Mirrors the existing tamrank_settings_*
                         * seam pattern; all MCP logic stays in PRO.
                         */
                        do_action('tamrank_settings_agent_tokens');

                        // FREE-only install (no PRO listener on the seam): render the
                        // upsell card ourselves so the integration stays discoverable.
                        // Same card PRO shows on unlicensed installs — the has_action()
                        // guard means exactly one of the two ever renders.
                        if (!has_action('tamrank_settings_agent_tokens')):
                        ?>
                        <div class="tr-connection-card">
                            <div class="tr-connection-header">
                                <div class="tr-connection-icon" aria-hidden="true">
                                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                        <rect x="4" y="8" width="16" height="12" rx="2"></rect>
                                        <path d="M12 8V4"></path>
                                        <circle cx="12" cy="3" r="1"></circle>
                                        <path d="M9 17h6"></path>
                                    </svg>
                                </div>
                                <div>
                                    <p class="tr-connection-name"><?php tamrank_e('agent_card_title', 'AI Agents (MCP)'); ?></p>
                                    <p class="tr-connection-desc"><?php tamrank_e('agent_card_desc', 'Let an AI agent (Claude Code, Claude Desktop or Cursor) read your SEO state and propose fixes against this site.'); ?></p>
                                </div>
                                <div class="tr-card-header-right">
                                    <span class="tr-badge tr-badge--pro"><span class="tr-badge-dot"></span> PRO</span>
                                </div>
                            </div>
                            <div class="tr-upsell">
                                <p class="tr-upsell-text"><?php tamrank_e('agent_upsell_text', 'Connect Claude and other AI agents to fix meta, redirects and 404s. Every change is preview-first, with full rollback.'); ?></p>
                                <a href="<?php echo esc_url(tam_rank_get_upgrade_url('settings', 'agent_tokens')); ?>" class="tr-upsell-link" target="_blank" rel="noopener"><?php tamrank_e('view_pro', 'View PRO'); ?> &rarr;</a>
                            </div>
                        </div>
                        <?php endif; ?>

                    </div><!-- /integrations -->

                    <!-- ═══════════════════════════════════════════════════════
                         TAB 3 — AI Assistent (#ai-assistent)
                         ═══════════════════════════════════════════════════════ -->
                    <div id="ai-assistent" class="tr-tab-panel">
                        <?php if (!tam_rank_can_use_feature( 'ai_meta_generator' )): ?>
                            <!-- PRO Guide Card -->
                            <div class="tr-guide-card">
                                <div class="tr-guide-card__top">
                                    <div class="tr-guide-card__icon">
                                        <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"
                                            stroke-linecap="round" stroke-linejoin="round">
                                            <path
                                                d="M12 2a4 4 0 0 0-4 4v2H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2h-2V6a4 4 0 0 0-4-4z" />
                                            <circle cx="12" cy="15" r="2" />
                                        </svg>
                                    </div>
                                    <div class="tr-guide-card__info">
                                        <h4 class="tr-guide-card__title"><?php tamrank_e('ai_assistant', 'AI Assistant'); ?></h4>
                                        <p class="tr-guide-card__desc">
                                            <?php tamrank_e('ai_pro_description', 'Let AI write SEO titles and meta descriptions that actually sound like your brand. Set your brand profile once, generate in one click.'); ?>
                                        </p>
                                    </div>
                                </div>
                                <div class="tr-guide-card__benefits">
                                    <div class="tr-guide-card__benefit"><span class="tr-guide-card__check">&check;</span>
                                        <?php tamrank_e('ai_benefit_meta', 'AI-written titles & meta descriptions'); ?></div>
                                    <div class="tr-guide-card__benefit"><span class="tr-guide-card__check">&check;</span>
                                        <?php tamrank_e('ai_benefit_brand', 'One-time brand profile setup'); ?></div>
                                    <div class="tr-guide-card__benefit"><span class="tr-guide-card__check">&check;</span>
                                        <?php tamrank_e('ai_benefit_styles', '4 writing styles to match your tone'); ?></div>
                                    <div class="tr-guide-card__benefit"><span class="tr-guide-card__check">&check;</span>
                                        <?php tamrank_e('ai_benefit_bulk', 'Bulk-generate meta for selected posts in one click'); ?></div>
                                </div>
                                <div class="tr-guide-card__action">
                                    <span
                                        class="tr-guide-card__action-label"><?php tamrank_e('included_in_pro', 'Included in TamRank PRO'); ?></span>
                                    <a href="<?php echo esc_url(tam_rank_get_upgrade_url()); ?>" class="tr-btn tr-btn--secondary"
                                        target="_blank" rel="noopener"><?php tamrank_e('view_pro', 'View PRO'); ?> &rarr;</a>
                                </div>
                            </div>
                            <?php if (tamrank_pro_addon_installed()): ?>
                                <div class="tr-guide-card__license">
                                    <span
                                        class="tr-guide-card__license-text"><?php tamrank_e('have_license_already', 'I already have a license'); ?>?
                                        <a href="<?php echo esc_url(admin_url('admin.php?page=tam-rank-settings#license')); ?>"
                                            class="tr-guide-card__license-link"><?php tamrank_e('activate_here', 'Activate here'); ?></a></span>
                                </div>
                            <?php endif; ?>
                        <?php else: ?>
                            <!-- AI Instellingen Card -->
                            <div class="tr-card">
                                <div class="tr-card-header">
                                    <div>
                                        <h2 class="tr-card-title"><?php tamrank_e('ai_settings', 'AI Settings'); ?></h2>
                                        <p class="tr-card-subtitle">
                                            <?php tamrank_e('ai_assistant_description', 'Generate SEO titles and meta descriptions with AI.'); ?>
                                        </p>
                                    </div>
                                </div>
                                <div class="tr-card-body">
                                    <!-- AI Toggle -->
                                    <div class="tr-toggle-row">
                                        <div class="tr-toggle-row__info">
                                            <p class="tr-toggle-row__label">
                                                <?php tamrank_e('enable_ai_assistant', 'Enable AI Assistant'); ?></p>
                                            <p class="tr-toggle-row__desc">
                                                <?php tamrank_e('enable_ai_description', 'Show AI generation buttons in the SEO metabox'); ?>
                                            </p>
                                        </div>
                                        <?php /* Hidden companion: an unchecked checkbox posts nothing, and
                                                 "nothing" now means "this page had no field for it". Without this
                                                 the switch could no longer be turned off. */ ?>
                                        <input type="hidden" name="tam_rank_ai_enabled" value="0">
                                        <label class="tr-toggle-switch">
                                            <input type="checkbox" id="tam_rank_ai_enabled" name="tam_rank_ai_enabled" value="1"
                                                <?php checked($ai_enabled); ?>>
                                            <span class="tr-toggle-track"></span>
                                        </label>
                                    </div>

                                    <!-- AI Options (hidden when AI disabled) -->
                                    <div id="tam-ai-options-wrapper" <?php echo !$ai_enabled ? 'hidden' : ''; ?>>
                                        <!-- Writing Style -->
                                        <div class="tr-field-row">
                                            <label class="tr-label"
                                                for="tam_rank_ai_default_tone"><?php tamrank_e('default_writing_style', 'Default Writing Style'); ?></label>
                                            <select id="tam_rank_ai_default_tone" name="tam_rank_ai_default_tone" class="tr-select">
                                                <option value="professioneel" <?php selected($ai_default_tone, 'professioneel'); ?>>
                                                    <?php tamrank_e('professional', 'Professional'); ?></option>
                                                <option value="informeel" <?php selected($ai_default_tone, 'informeel'); ?>>
                                                    <?php tamrank_e('informal', 'Informal'); ?></option>
                                                <option value="creatief" <?php selected($ai_default_tone, 'creatief'); ?>>
                                                    <?php tamrank_e('creative', 'Creative'); ?></option>
                                                <option value="zakelijk" <?php selected($ai_default_tone, 'zakelijk'); ?>>
                                                    <?php tamrank_e('business', 'Business'); ?></option>
                                            </select>
                                            <p class="tr-field-hint">
                                                <?php tamrank_e('writing_style_description', 'The default writing style for AI generated content. This can be adjusted per page.'); ?>
                                            </p>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <?php
                            /**
                             * Brand Profile card (PRO, cluster-7c strip). The 7-field brand-voice
                             * form moved to tam-rank-pro; PRO renders it here inside this settings
                             * <form> so it still saves through FREE's registered
                             * tamrank_ai_brand_profile setting + sanitize_brand_profile(). FREE keeps
                             * the option, sanitizer, read helpers and the API-payload injection.
                             */
                            do_action('tamrank_settings_ai_brand_profile', $ai_enabled);
                            ?>

                            <?php ob_start(); ?>
                                jQuery(document).ready(function ($) {
                                    $('#tam_rank_ai_enabled').on('change', function () {
                                        var wrapper = document.getElementById('tam-ai-options-wrapper');
                                        var brandCard = document.getElementById('tam-brand-profile-card');
                                        if ($(this).is(':checked')) {
                                            if (wrapper) wrapper.removeAttribute('hidden');
                                            if (brandCard) brandCard.removeAttribute('hidden');
                                        } else {
                                            if (wrapper) wrapper.setAttribute('hidden', '');
                                            if (brandCard) brandCard.setAttribute('hidden', '');
                                        }
                                    });
                                });
                            <?php wp_add_inline_script('tam-rank-admin', ob_get_clean()); ?>
                        <?php endif; ?>
                    </div><!-- /ai-assistent -->

                    <!-- ═══════════════════════════════════════════════════════
                         TAB 4 — Technisch SEO (#technical)
                         ═══════════════════════════════════════════════════════ -->
                    <div id="technical" class="tr-tab-panel">

                        <!-- Robots.txt Card -->
                        <div class="tr-card">
                            <div class="tr-card-header">
                                <div>
                                    <h2 class="tr-card-title"><?php tamrank_e('robots_txt', 'Robots.txt'); ?><?php echo tamrank_render_info_tooltip(tamrank__('robots_txt_info', 'The robots.txt tells search engines which parts of your website they can visit.') . ' ' . tamrank__('robots_how_optout', 'It works by exception: as long as you block nothing, search engines may read everything. Most sites never have to change a thing here.') . ' ' . tamrank__('robots_how_layers', 'This screen only knows what WordPress hands out. Your hosting, a CDN or a firewall sits in front of it and can keep crawlers out regardless - you cannot see that from here.'), array('size' => 'lg')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- returns escaped, plugin-generated tooltip markup (helper escapes text + attributes internally) ?></h2>
                                    <p class="tr-card-subtitle">
                                        <?php tamrank_e('robots_txt_desc', 'Manage which pages search engines can index'); ?>
                                    </p>
                                </div>
                            </div>
                            <div class="tr-card-body">
                                <?php
                                // Status line (2026-08). One glance has to answer "do I
                                // need to do anything here?" - robots.txt is opt-out, so
                                // the healthy answer is "no", and saying it takes one quiet
                                // line with a green dot, not a panel. It ABSORBS the three
                                // notices that used to stand here (physical file, suppressed
                                // Sitemap: line, stale Sitemap: line): those texts are the
                                // detail lines of the fact rows, which only render when
                                // there is something to act on. Presentation only - the
                                // checks behind it, the serve path and the save path are
                                // untouched. The explanation that used to sit above the
                                // switch is now the info tooltip on the card title, the
                                // same affordance every other explanation in the plugin uses.
                                $tr_robots_status = tam_rank_robots_status();
                                tamrank_render_robots_status_line($tr_robots_status);
                                ?>

                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label">
                                            <?php tamrank_e('enable_robots_txt', 'Activate robots.txt management'); ?></p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="checkbox" id="tamrank_robots_enabled" value="1" <?php checked(!empty($robots_txt)); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>
                                <div id="tr-robots-content" <?php echo empty($robots_txt) ? 'hidden' : ''; ?>>
                                    <div class="tr-field-row">
                                        <pre
                                            class="tr-code-block"><code><?php echo esc_html($robots_txt ?: "User-agent: *\nDisallow: /wp-admin/\nAllow: /wp-admin/admin-ajax.php\n\nSitemap: " . home_url('/sitemap.xml')); ?></code></pre>
                                        <textarea id="robots_txt" name="tam_rank_robots_txt" class="tr-textarea" rows="8"
                                            style="display: none;"
                                            placeholder="User-agent: *&#10;Disallow: /wp-admin/&#10;Allow: /wp-admin/admin-ajax.php&#10;&#10;Sitemap: <?php echo esc_url(home_url('/sitemap.xml')); ?>"><?php echo esc_textarea($robots_txt); ?></textarea>
                                    </div>
                                    <div class="tr-pills" style="justify-content: flex-end;">
                                        <a href="<?php echo esc_url(home_url('/robots.txt')); ?>" target="_blank"
                                            class="tr-btn tr-btn--secondary"><?php tamrank_e('view_robots', 'View current robots.txt'); ?>
                                            &#8599;</a>
                                        <button type="button" id="tr-robots-edit-btn"
                                            class="tr-btn tr-btn--primary"><?php tamrank_e('edit_robots', 'Edit robots.txt'); ?></button>
                                    </div>
                                    <?php ob_start(); ?>
                                        jQuery(document).ready(function ($) {
                                            $('#tr-robots-edit-btn').on('click', function () {
                                                var $pre = $(this).closest('.tr-card-body').find('.tr-code-block');
                                                var $textarea = $(this).closest('.tr-card-body').find('.tr-textarea');
                                                // "Klaar met bewerken" → sync pre + auto-save form
                                                if ($textarea.is(':visible')) {
                                                    $pre.find('code').text($textarea.val());
                                                    $pre.toggle();
                                                    $textarea.toggle();
                                                    $(this).text('<?php tamrank_e('edit_robots', 'Edit robots.txt'); ?>');
                                                    // Submit the settings form to save
                                                    $(this).closest('form').submit();
                                                    return;
                                                }
                                                $pre.toggle();
                                                $textarea.toggle();
                                                $(this).text('<?php tamrank_e('done_editing', 'Done editing'); ?>');
                                            });

                                            // One-click fix for a Sitemap: line left behind by a
                                            // domain move. The corrected body is computed in PHP
                                            // (attribute), so the button only fills the field and
                                            // submits - the same save path as "Done editing".
                                            $('#tr-robots-sitemap-fix').on('click', function () {
                                                var fixed = $(this).attr('data-robots-fixed');
                                                var $body = $(this).closest('.tr-card-body');
                                                $body.find('.tr-textarea').val(fixed);
                                                $body.find('.tr-code-block code').text(fixed);
                                                $(this).closest('form').trigger('submit');
                                            });
                                        });
                                    <?php wp_add_inline_script('tam-rank-admin', ob_get_clean()); ?>
                                </div>
                            </div>
                        </div>

                        <!-- Archive indexing card (PARITY-27). Three archive
                             types that have no screen of their own to carry a
                             noindex, so they get a site-wide switch here. The
                             defaults live in tam_rank_archive_noindex_defaults(). -->
                        <div class="tr-card">
                            <div class="tr-card-header">
                                <div>
                                    <h2 class="tr-card-title"><?php tamrank_e('archive_indexing', 'Archive indexing'); ?></h2>
                                    <p class="tr-card-subtitle">
                                        <?php tamrank_e('archive_indexing_desc', 'Keep listing pages that repeat content from elsewhere out of the search results. The posts themselves stay indexed, visitors can still reach every archive, and links on it are still followed.'); ?>
                                    </p>
                                </div>
                            </div>
                            <div class="tr-card-body">
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label">
                                            <?php tamrank_e('noindex_date_archives', 'Noindex date archives'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('noindex_date_archives_desc', 'Year, month and day archives such as /2026/ and /2026/08/. On most themes these repeat entire posts on a URL nobody searches for.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="checkbox" id="tr_noindex_date_archives"
                                            name="tam_rank_general_settings[noindex_date_archives]" value="1"
                                            <?php checked(tam_rank_archive_noindex_enabled('noindex_date_archives')); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label">
                                            <?php tamrank_e('noindex_author_archives', 'Noindex author archives'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('noindex_author_archives_desc', 'Pages such as /author/name/. Off by default: on a site with one writer this archive is often a real page you want found.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="checkbox" id="tr_noindex_author_archives"
                                            name="tam_rank_general_settings[noindex_author_archives]" value="1"
                                            <?php checked(tam_rank_archive_noindex_enabled('noindex_author_archives')); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>
                                <?php
                                // No switch for search results. They were already
                                // noindexed before 1.3.8 and WordPress core does it
                                // too, so an off position would not make them
                                // indexable. Say what happens instead of offering a
                                // control that cannot deliver.
                                ?>
                                <div class="tr-notice tr-notice--info">
                                    <?php tamrank_e('noindex_search_results_note', 'Your own search result pages are always set to noindex. WordPress does this itself as well, so there is nothing to switch here.'); ?>
                                </div>
                            </div>
                        </div>

                        <!-- XML Sitemap Card -->
                        <div class="tr-card">
                            <div class="tr-card-header">
                                <div>
                                    <h2 class="tr-card-title"><?php tamrank_e('xml_sitemap', 'XML Sitemap'); ?></h2>
                                    <p class="tr-card-subtitle">
                                        <?php tamrank_e('xml_sitemap_desc', 'Configure which content appears in the sitemap'); ?>
                                    </p>
                                </div>
                            </div>
                            <div class="tr-card-body">
                                <?php
                                // Master switch. Missing key means on, so nothing changes for
                                // installations that never touched this setting.
                                $sitemap_enabled = tam_rank_sitemap_is_enabled();
                                ?>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label">
                                            <?php tamrank_e('enable_xml_sitemap', 'Activate XML sitemap'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('enable_xml_sitemap_desc', 'When switched off TamRank stops serving /sitemap.xml and WordPress\' own /wp-sitemap.xml becomes available again. The HTML sitemap shortcode and IndexNow keep working.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_sitemap_settings[sitemap_enabled]" value="0">
                                        <input type="checkbox" id="tam_rank_sitemap_enabled"
                                            name="tam_rank_sitemap_settings[sitemap_enabled]" value="1"
                                            <?php checked($sitemap_enabled); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <div id="tr-sitemap-dependents" <?php echo $sitemap_enabled ? '' : 'hidden'; ?>>
                                <?php
                                // Dynamic content type toggles for sitemap (auto-save via AJAX)
                                $sitemap_config = TamRank_Content_Type_Config::get_config()['sitemap'];
                                $sitemap_builtin_pt  = ['post', 'page'];
                                $sitemap_builtin_tax = ['category', 'post_tag'];
                                $sitemap_custom_pts  = [];
                                $sitemap_custom_taxs = [];
                                $sitemap_toggle_nonce = wp_create_nonce('tam_rank_sitemap_toggle');

                                // --- Flat list: post types, taxonomies, images ---
                                ?>
                                <?php
                                $sitemap_post_types = get_post_types(['public' => true], 'objects');
                                foreach ($sitemap_post_types as $pt_slug => $pt) :
                                    if ($pt_slug === 'attachment') continue;
                                    if ($pt->publicly_queryable === false && $pt_slug !== 'page') continue;
                                    if (!in_array($pt_slug, $sitemap_builtin_pt, true)) { $sitemap_custom_pts[$pt_slug] = $pt; continue; }
                                    $pt_sitemap_on = isset($sitemap_config['post_types'][$pt_slug]) ? (bool) $sitemap_config['post_types'][$pt_slug] : true;
                                ?>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php echo esc_html($pt->label); ?></p>
                                        <p class="tr-toggle-row__desc"><?php tamrank_e('post_type_sitemap_desc', 'Add published items of this post type to the sitemap'); ?></p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_sitemap_settings[content_types][post_types][<?php echo esc_attr($pt_slug); ?>]" value="0">
                                        <input type="checkbox"
                                            class="js-sitemap-toggle"
                                            data-group="post_types"
                                            data-slug="<?php echo esc_attr($pt_slug); ?>"
                                            name="tam_rank_sitemap_settings[content_types][post_types][<?php echo esc_attr($pt_slug); ?>]"
                                            value="1"
                                            <?php checked($pt_sitemap_on); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>
                                <?php endforeach; ?>

                                <?php
                                $sitemap_internal_tax = ['post_format', 'nav_menu', 'link_category', 'wp_theme', 'wp_template_part_area'];
                                $sitemap_taxonomies = get_taxonomies(['public' => true], 'objects');
                                foreach ($sitemap_taxonomies as $tax_slug => $tax) :
                                    if (in_array($tax_slug, $sitemap_internal_tax, true)) continue;
                                    if ($tax->publicly_queryable === false) continue;
                                    if (!in_array($tax_slug, $sitemap_builtin_tax, true)) { $sitemap_custom_taxs[$tax_slug] = $tax; continue; }
                                    $tax_sitemap_on = isset($sitemap_config['taxonomies'][$tax_slug]) ? (bool) $sitemap_config['taxonomies'][$tax_slug] : true;
                                ?>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php echo esc_html($tax->label); ?></p>
                                        <p class="tr-toggle-row__desc"><?php tamrank_e('taxonomy_sitemap_desc', 'Add archive pages of this taxonomy to the sitemap'); ?></p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_sitemap_settings[content_types][taxonomies][<?php echo esc_attr($tax_slug); ?>]" value="0">
                                        <input type="checkbox"
                                            class="js-sitemap-toggle"
                                            data-group="taxonomies"
                                            data-slug="<?php echo esc_attr($tax_slug); ?>"
                                            name="tam_rank_sitemap_settings[content_types][taxonomies][<?php echo esc_attr($tax_slug); ?>]"
                                            value="1"
                                            <?php checked($tax_sitemap_on); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>
                                <?php endforeach; ?>

                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('images_label', 'Images'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('images_sitemap_desc', 'Add image locations to the sitemap'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="checkbox" id="include_images"
                                            name="tam_rank_sitemap_settings[include_images]" value="1" <?php checked(isset($sitemap_settings['include_images']) ? $sitemap_settings['include_images'] : 1); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <?php if (!empty($sitemap_custom_pts) || !empty($sitemap_custom_taxs)) : ?>
                                <div class="tr-more-types-wrap">
                                    <button type="button" class="tr-more-types-toggle"><?php tamrank_e('advanced_settings', 'Advanced settings'); ?> &#9662;</button>
                                    <div class="tr-more-types-content" hidden>
                                        <?php if (!empty($sitemap_custom_pts)) : ?>
                                        <p class="tr-content-group__label"><?php tamrank_e('custom_post_types', 'Custom Post Types'); ?></p>
                                        <?php foreach ($sitemap_custom_pts as $pt_slug => $pt) :
                                            $pt_sitemap_on = isset($sitemap_config['post_types'][$pt_slug]) ? (bool) $sitemap_config['post_types'][$pt_slug] : true;
                                        ?>
                                        <div class="tr-toggle-row">
                                            <div class="tr-toggle-row__info">
                                                <p class="tr-toggle-row__label"><?php echo esc_html($pt->label); ?></p>
                                                <p class="tr-toggle-row__desc"><?php tamrank_e('post_type_sitemap_desc', 'Add published items of this post type to the sitemap'); ?></p>
                                            </div>
                                            <label class="tr-toggle-switch">
                                                <input type="hidden" name="tam_rank_sitemap_settings[content_types][post_types][<?php echo esc_attr($pt_slug); ?>]" value="0">
                                                <input type="checkbox"
                                                    class="js-sitemap-toggle"
                                                    data-group="post_types"
                                                    data-slug="<?php echo esc_attr($pt_slug); ?>"
                                                    name="tam_rank_sitemap_settings[content_types][post_types][<?php echo esc_attr($pt_slug); ?>]"
                                                    value="1"
                                                    <?php checked($pt_sitemap_on); ?>>
                                                <span class="tr-toggle-track"></span>
                                            </label>
                                        </div>
                                        <?php endforeach; ?>
                                        <?php endif; ?>

                                        <?php if (!empty($sitemap_custom_taxs)) : ?>
                                        <p class="tr-content-group__label"><?php tamrank_e('custom_taxonomies', 'Custom Taxonomies'); ?></p>
                                        <?php foreach ($sitemap_custom_taxs as $tax_slug => $tax) :
                                            $tax_sitemap_on = isset($sitemap_config['taxonomies'][$tax_slug]) ? (bool) $sitemap_config['taxonomies'][$tax_slug] : true;
                                        ?>
                                        <div class="tr-toggle-row">
                                            <div class="tr-toggle-row__info">
                                                <p class="tr-toggle-row__label"><?php echo esc_html($tax->label); ?></p>
                                                <p class="tr-toggle-row__desc"><?php tamrank_e('taxonomy_sitemap_desc', 'Add archive pages of this taxonomy to the sitemap'); ?></p>
                                            </div>
                                            <label class="tr-toggle-switch">
                                                <input type="hidden" name="tam_rank_sitemap_settings[content_types][taxonomies][<?php echo esc_attr($tax_slug); ?>]" value="0">
                                                <input type="checkbox"
                                                    class="js-sitemap-toggle"
                                                    data-group="taxonomies"
                                                    data-slug="<?php echo esc_attr($tax_slug); ?>"
                                                    name="tam_rank_sitemap_settings[content_types][taxonomies][<?php echo esc_attr($tax_slug); ?>]"
                                                    value="1"
                                                    <?php checked($tax_sitemap_on); ?>>
                                                <span class="tr-toggle-track"></span>
                                            </label>
                                        </div>
                                        <?php endforeach; ?>
                                        <?php endif; ?>
                                    </div>
                                </div>
                                <?php endif; ?>

                                <div class="tr-pills" style="justify-content: flex-end;">
                                    <a href="<?php echo esc_url(home_url('/sitemap.xml')); ?>" target="_blank"
                                        class="tr-btn tr-btn--secondary"><?php tamrank_e('view_sitemap', 'View current sitemap'); ?>
                                        &#8599;</a>
                                    <button type="button" id="regenerate-sitemap"
                                        class="tr-btn tr-btn--primary"><?php tamrank_e('regenerate_sitemap', 'Regenerate sitemap'); ?></button>
                                </div>

                                </div><!-- /#tr-sitemap-dependents -->

                                <?php ob_start(); ?>
                                jQuery(function($){
                                    $('#tam_rank_sitemap_enabled').on('change', function(){
                                        var dependents = document.getElementById('tr-sitemap-dependents');
                                        if (!dependents) return;
                                        if ($(this).is(':checked')) {
                                            dependents.removeAttribute('hidden');
                                        } else {
                                            dependents.setAttribute('hidden', '');
                                        }
                                    });
                                    // Listen on the <label> wrapper — Safari fires click on
                                    // the label, not the checkbox. Use setTimeout(0) so the
                                    // browser has already toggled the checkbox state.
                                    $('.tr-toggle-row').has('.js-sitemap-toggle').find('.tr-toggle-switch').on('click', function(e){
                                        var $label = $(this);
                                        var $cb = $label.find('.js-sitemap-toggle');
                                        if (!$cb.length) return;
                                        var $row = $label.closest('.tr-toggle-row');
                                        setTimeout(function(){
                                            var isChecked = $cb.prop('checked');
                                            $row.css('opacity','0.6');
                                            $.ajax({
                                                url: ajaxurl,
                                                type: 'POST',
                                                data: {
                                                    action: 'tam_rank_toggle_sitemap_type',
                                                    nonce:  '<?php echo esc_js($sitemap_toggle_nonce); ?>',
                                                    group:  $cb.data('group'),
                                                    slug:   $cb.data('slug'),
                                                    enabled: isChecked ? 1 : 0
                                                },
                                                success: function(r){
                                                    $row.css('opacity','1');
                                                    if (!r.success) {
                                                        $cb.prop('checked', !isChecked);
                                                    }
                                                },
                                                error: function(xhr){
                                                    $row.css('opacity','1');
                                                    $cb.prop('checked', !isChecked);
                                                }
                                            });
                                        }, 50);
                                    });
                                });
                                <?php wp_add_inline_script('tam-rank-admin', ob_get_clean()); ?>
                            </div>
                        </div>

                        <!-- Afbeeldingsoptimalisatie Card -->
                        <div class="tr-card">
                            <div class="tr-card-header">
                                <div>
                                    <h2 class="tr-card-title">
                                        <?php tamrank_e('image_optimization', 'Image optimization'); ?></h2>
                                    <p class="tr-card-subtitle">
                                        <?php tamrank_e('image_optimization_desc', 'Optimize images for better performance.'); ?>
                                    </p>
                                </div>
                            </div>
                            <div class="tr-card-body">
                                <!-- WebP conversie -->
                                <div class="tr-toggle-row" id="tr-webp-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('enable_webp', 'WebP conversion'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('enable_webp_desc', 'Automatically convert images to WebP format.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="checkbox" id="enable_webp" name="tam_rank_general_settings[enable_webp]"
                                            value="1" <?php checked(isset($general_settings['enable_webp']) ? $general_settings['enable_webp'] : 0); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <!-- Kwaliteit slider -->
                                <?php $webp_quality = isset($general_settings['webp_quality']) ? (int) $general_settings['webp_quality'] : 80; ?>
                                <div class="tr-field-row tr-slider-row">
                                    <label class="tr-label"><?php tamrank_e('webp_quality', 'Quality'); ?></label>
                                    <div class="tr-slider-wrap">
                                        <input type="range" class="tr-slider" id="webp_quality"
                                            name="tam_rank_general_settings[webp_quality]" min="40" max="100"
                                            value="<?php echo esc_attr($webp_quality); ?>">
                                        <span class="tr-slider-value"><?php echo esc_html($webp_quality); ?></span>
                                    </div>
                                </div>

                                <!-- Convert existing images (new uploads convert automatically when WebP is on) -->
                                <div class="tr-field-row tr-webp-bulk-row">
                                    <button type="button" class="tr-btn tr-btn--secondary" id="tamrank-webp-bulk">
                                        <?php tamrank_e('webp_bulk_button', 'Convert existing images'); ?>
                                    </button>
                                    <span id="tamrank-webp-bulk-status" class="tr-webp-bulk-status"></span>
                                </div>

                                <!-- Geavanceerde instellingen -->
                                <details class="tr-advanced-details">
                                    <summary class="tr-advanced-toggle">
                                        <?php tamrank_e('advanced_settings', 'Advanced settings'); ?>
                                        <i class="tr-advanced-toggle__icon">&#9662;</i>
                                    </summary>
                                    <!-- PNG conversie -->
                                    <div class="tr-toggle-row">
                                        <div class="tr-toggle-row__info">
                                            <p class="tr-toggle-row__label"><?php tamrank_e('convert_png', 'PNG to WebP'); ?>
                                            </p>
                                            <p class="tr-toggle-row__desc">
                                                <?php tamrank_e('convert_png_desc', 'Convert PNG images to WebP.'); ?>
                                            </p>
                                        </div>
                                        <label class="tr-toggle-switch">
                                            <input type="hidden" name="tam_rank_general_settings[convert_png]" value="0">
                                            <input type="checkbox" id="convert_png"
                                                name="tam_rank_general_settings[convert_png]" value="1" <?php checked(isset($general_settings['convert_png']) ? $general_settings['convert_png'] : 1); ?>>
                                            <span class="tr-toggle-track"></span>
                                        </label>
                                    </div>
                                </details>

                                <hr class="tr-separator">

                                <!-- Automatische alt/titel bij upload (FREE). Standaard uit. -->
                                <?php
                                // TamRank_Alt_Templates is loaded unconditionally by the
                                // plugin bootstrap; the guard is only here so a
                                // partially-loaded install degrades to a disabled block
                                // instead of a fatal.
                                $alt_tpl_available = class_exists('TamRank_Alt_Templates');
                                $alt_tpl_settings  = $alt_tpl_available
                                    ? TamRank_Alt_Templates::get_settings()
                                    : array('enabled' => false, 'alt_template' => '', 'title_template' => '', 'bulk_enabled' => false);
                                $alt_tpl_enabled      = ! empty($alt_tpl_settings['enabled']);
                                $alt_tpl_placeholders = $alt_tpl_available ? TamRank_Alt_Templates::get_placeholders() : array();

                                // Second, independent switch: filling media that is
                                // already in the library. Read from its own key, and
                                // never inferred from $alt_tpl_enabled.
                                //
                                // The numbers are read whether the switch is on or off,
                                // because the panel is now rendered either way (hidden
                                // while it is off) and the switch can turn it visible
                                // without a page load. Two indexed queries on one admin
                                // screen; the alternative is a panel that appears with
                                // "Undo (0)" under a library full of markers.
                                $alt_bulk_available = class_exists('TamRank_Alt_Bulk');
                                $alt_bulk_enabled   = $alt_bulk_available && TamRank_Alt_Bulk::is_enabled();
                                $alt_bulk_state     = $alt_bulk_available
                                    ? TamRank_Alt_Bulk::get_panel_state()
                                    : array('filled' => 0, 'fillable' => 0, 'resume' => null);
                                $alt_bulk_filled    = (int) $alt_bulk_state['filled'];
                                $alt_bulk_resume    = $alt_bulk_state['resume'];

                                // How many images still have an empty alt field.
                                // Zero means Preview would be guaranteed to come
                                // back with nothing, so both it and Fill are
                                // rendered off. This is a live count, not a
                                // memory of the last run: upload one image
                                // without an alt text and they are back.
                                $alt_bulk_fillable  = (int) $alt_bulk_state['fillable'];

                                // The pattern fields serve both switches, so they show
                                // up as soon as EITHER one is on.
                                $alt_fields_visible = $alt_tpl_enabled || $alt_bulk_enabled;
                                ?>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('auto_alt_templates', 'Automatic alt text on upload'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('auto_alt_templates_desc', 'Fill the alt text of new images from a pattern. Existing alt text is never overwritten.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_alt_template_settings[enabled]" value="0">
                                        <input type="checkbox" id="tamrank_alt_templates_enabled"
                                            name="tam_rank_alt_template_settings[enabled]" value="1" <?php checked($alt_tpl_enabled); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <?php
                                // Label boven het veld, veld over de volle
                                // kaartbreedte: zelfde rijvorm als de
                                // kwaliteits-slider hierboven in deze kaart.
                                // De inline-variant (label links, veld rechts,
                                // max 300px) liet een gat van ~370px vallen en
                                // kapte de langere placeholder af.
                                ?>
                                <div id="tr-alt-templates-content" <?php echo $alt_fields_visible ? '' : 'hidden'; ?>>
                                    <div class="tr-field-row">
                                        <label class="tr-label"
                                            for="tamrank_alt_template"><?php tamrank_e('alt_template_label', 'Alt text pattern'); ?></label>
                                        <input type="text" id="tamrank_alt_template"
                                            name="tam_rank_alt_template_settings[alt_template]" class="tr-input"
                                            value="<?php echo esc_attr($alt_tpl_settings['alt_template']); ?>"
                                            placeholder="<?php echo esc_attr('{filename}'); ?>">
                                    </div>

                                    <div class="tr-field-row">
                                        <label class="tr-label"
                                            for="tamrank_title_template"><?php tamrank_e('media_title_template_label', 'Media title pattern'); ?></label>
                                        <input type="text" id="tamrank_title_template"
                                            name="tam_rank_alt_template_settings[title_template]" class="tr-input"
                                            value="<?php echo esc_attr($alt_tpl_settings['title_template']); ?>"
                                            placeholder="<?php echo esc_attr(tamrank__('media_title_template_placeholder', 'Leave empty to keep the WordPress title')); ?>">
                                    </div>

                                    <p class="tr-field-hint">
                                        <?php tamrank_e('alt_template_placeholders_hint', 'Available placeholders:'); ?>
                                        <?php foreach ($alt_tpl_placeholders as $alt_ph) : ?>
                                            <code><?php echo esc_html($alt_ph); ?></code>
                                        <?php endforeach; ?>
                                        <br>
                                        <?php tamrank_e('alt_template_never_overwrite', 'TamRank only fills what is still empty. An alt text you wrote yourself is never replaced, and a media title you changed stays as it is.'); ?>
                                        <br>
                                        <?php tamrank_e('alt_template_unsaved_post_hint', 'Uploading straight into a post you have not saved yet works too: TamRank fills in {post_title} as soon as you save that post, unless you changed the value yourself in the meantime. {category} is often still the default one at that first save.'); ?>
                                    </p>
                                </div>

                                <?php
                                // ── Existing media (FREE, 1.3.7). Its own switch, off by
                                // default, independent of the upload switch above. While
                                // it is off there is no panel and no button here at all,
                                // and the AJAX routes refuse as well.
                                ?>
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label"><?php tamrank_e('alt_bulk_toggle', 'Also fill existing media'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('alt_bulk_toggle_desc', 'Adds a button that fills the alt text of images already in your library, in one action. You see what it would write before anything is written, and you can undo it.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_alt_template_settings[bulk_enabled]" value="0">
                                        <input type="checkbox" id="tamrank_alt_bulk_enabled"
                                            name="tam_rank_alt_template_settings[bulk_enabled]" value="1" <?php checked($alt_bulk_enabled); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <?php
                                // The panel is rendered whether the switch is on or off,
                                // and simply carries `hidden` while the SAVED setting is
                                // off. Building it in JS instead would move this escaping
                                // and these translated strings into the browser, where
                                // they would drift out of step with the PHP the moment
                                // either side is edited. What the switch does is toggle
                                // one attribute on markup that was already escaped here.
                                ?>
                                <div id="tr-alt-bulk-panel" class="tr-alt-bulk-panel"
                                    data-fillable="<?php echo esc_attr($alt_bulk_fillable); ?>"
                                    <?php echo $alt_bulk_enabled ? '' : 'hidden'; ?>>
                                    <p class="tr-field-hint">
                                        <?php tamrank_e('alt_bulk_explainer', 'Uses the alt text pattern above. Media titles are left alone, images that already have an alt text are left alone, and nothing is written until you have seen the preview and confirmed it.'); ?>
                                    </p>

                                    <p class="tr-field-hint tr-alt-bulk-resume" id="tr-alt-bulk-resume" <?php echo is_array($alt_bulk_resume) ? '' : 'hidden'; ?>>
                                        <?php
                                        printf(
                                            /* translators: 1: number of images already filled in the interrupted run. */
                                            esc_html(tamrank__('alt_bulk_resume_notice', 'A previous run stopped early after %d images. Starting it again picks up where it left off.')),
                                            (int) (is_array($alt_bulk_resume) ? $alt_bulk_resume['filled'] : 0)
                                        );
                                        ?>
                                    </p>

                                    <?php
                                    // What is standing in the library right now, as opposed
                                    // to what just happened. #tr-alt-bulk-status below is the
                                    // transient aria-live line that a walk overwrites and
                                    // empties again; this one survives a reload, because
                                    // `filled` comes straight from get_panel_state() — the
                                    // same count that fills in the Undo button next to it.
                                    ?>
                                    <p id="tr-alt-bulk-filled" class="tr-alt-bulk-filled" <?php echo $alt_bulk_filled > 0 ? '' : 'hidden'; ?>>
                                        <span class="tr-alt-bulk-filled__icon" aria-hidden="true">&#10003;</span>
                                        <span class="tr-alt-bulk-filled__text">
                                            <?php
                                            printf(
                                                /* translators: 1: number of images TamRank filled in. */
                                                esc_html(tamrank__('alt_bulk_filled_notice', '%d images filled by TamRank — you can undo this.')),
                                                (int) $alt_bulk_filled
                                            );
                                            ?>
                                        </span>
                                    </p>

                                    <div class="tr-alt-bulk-actions">
                                        <button type="button" class="tr-btn tr-btn--secondary" id="tr-alt-bulk-preview" <?php disabled($alt_bulk_fillable < 1); ?>>
                                            <?php tamrank_e('alt_bulk_preview_btn', 'Show what would happen'); ?>
                                        </button>
                                        <button type="button" class="tr-btn tr-btn--primary" id="tr-alt-bulk-run" disabled
                                            data-resume="<?php echo esc_attr(is_array($alt_bulk_resume) ? (int) $alt_bulk_resume['after_id'] : 0); ?>">
                                            <?php tamrank_e('alt_bulk_run_btn', 'Fill in alt text'); ?>
                                        </button>
                                        <button type="button" class="tr-btn tr-btn--secondary" id="tr-alt-bulk-undo" <?php disabled($alt_bulk_filled < 1); ?>>
                                            <?php
                                            printf(
                                                /* translators: 1: number of images TamRank filled in. */
                                                esc_html(tamrank__('alt_bulk_undo_btn', 'Undo (%d)')),
                                                (int) $alt_bulk_filled
                                            );
                                            ?>
                                        </button>
                                    </div>

                                    <?php
                                    // Why those two buttons are off. It sits under
                                    // them rather than on them: a disabled button
                                    // gets `pointer-events: none` from buttons.css,
                                    // so a tooltip there would never open. The text
                                    // is static, so the script only toggles this
                                    // attribute — the string itself lives here and
                                    // nowhere else.
                                    ?>
                                    <p id="tr-alt-bulk-nothing" class="tr-field-hint tr-alt-bulk-nothing" <?php echo $alt_bulk_fillable > 0 ? 'hidden' : ''; ?>>
                                        <?php tamrank_e('alt_bulk_nothing_left', 'All images already have an alt text — nothing to fill.'); ?>
                                    </p>

                                    <p id="tr-alt-bulk-status" class="tr-alt-bulk-status" role="status" aria-live="polite"></p>
                                    <div id="tr-alt-bulk-result" class="tr-alt-bulk-result"></div>
                                </div>
                            </div>
                        </div>

                        <!-- RSS Feed Card (FREE). Standaard uit, zelfde principe als WebP. -->
                        <?php
                        // TamRank_RSS is loaded unconditionally by the plugin bootstrap;
                        // the guard is only here so a partially-loaded install degrades
                        // to a disabled card instead of a fatal.
                        $rss_available = class_exists('TamRank_RSS');
                        $rss_settings  = $rss_available
                            ? TamRank_RSS::get_settings()
                            : array('enabled' => false, 'before' => '', 'after' => '');
                        $rss_enabled      = ! empty($rss_settings['enabled']);
                        $rss_placeholders = $rss_available ? TamRank_RSS::get_placeholders() : array();

                        // Other SEO plugins add their own paragraph through the same two
                        // filters. get_feed_text_warnings() already returns nothing while
                        // our own feed text is off — read-only, it never touches another
                        // plugin's settings.
                        $rss_conflicts = $rss_available ? TamRank_RSS::get_feed_text_warnings() : array();
                        ?>
                        <div class="tr-card">
                            <div class="tr-card-header">
                                <div>
                                    <h2 class="tr-card-title"><?php tamrank_e('rss_feed', 'RSS feed'); ?></h2>
                                    <p class="tr-card-subtitle">
                                        <?php tamrank_e('rss_feed_desc', 'Add a line before or after every item in your feed, with a link back to the original.'); ?>
                                    </p>
                                </div>
                            </div>
                            <div class="tr-card-body">
                                <div class="tr-toggle-row">
                                    <div class="tr-toggle-row__info">
                                        <p class="tr-toggle-row__label">
                                            <?php tamrank_e('enable_rss_feed', 'Activate feed text'); ?></p>
                                        <p class="tr-toggle-row__desc">
                                            <?php tamrank_e('rss_feed_info', 'Sites that republish your feed then carry a link back to you.'); ?>
                                        </p>
                                    </div>
                                    <label class="tr-toggle-switch">
                                        <input type="hidden" name="tam_rank_rss_settings[enabled]" value="0">
                                        <input type="checkbox" id="tamrank_rss_enabled"
                                            name="tam_rank_rss_settings[enabled]" value="1" <?php checked($rss_enabled); ?>>
                                        <span class="tr-toggle-track"></span>
                                    </label>
                                </div>

                                <div id="tr-rss-content" <?php echo $rss_enabled ? '' : 'hidden'; ?>>
                                    <?php foreach ($rss_conflicts as $rss_conflict) : ?>
                                        <div class="tr-alert tr-alert--warning tr-alert--structured">
                                            <span class="tr-alert__icon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
                                            <div class="tr-alert__content">
                                                <p class="tr-alert__title"><?php
                                                    printf(
                                                        esc_html(tamrank__('rss_conflict_title', '%s also puts text in your feed')),
                                                        esc_html($rss_conflict['name'])
                                                    );
                                                ?></p>
                                                <p class="tr-alert__desc"><?php
                                                    printf(
                                                        esc_html(tamrank__('rss_conflict_desc', 'With both switched on, every item in your feed carries two lines. TamRank leaves that setting alone, so it is up to you whether to clear it in %s.')),
                                                        esc_html($rss_conflict['name'])
                                                    );
                                                ?></p>
                                                <p class="tr-rss-conflict-action">
                                                    <a href="<?php echo esc_url($rss_conflict['settings_url']); ?>"><?php
                                                        printf(
                                                            esc_html(tamrank__('rss_conflict_link', 'Open the feed setting in %s')),
                                                            esc_html($rss_conflict['name'])
                                                        );
                                                    ?></a>
                                                </p>
                                            </div>
                                        </div>
                                    <?php endforeach; ?>

                                    <div class="tr-field-row">
                                        <label class="tr-label"
                                            for="tamrank_rss_before"><?php tamrank_e('rss_before_label', 'Text before each item'); ?></label>
                                        <textarea id="tamrank_rss_before" name="tam_rank_rss_settings[before]"
                                            class="tr-textarea" rows="2"
                                            placeholder="<?php echo esc_attr(tamrank__('rss_before_placeholder', 'This post first appeared on %SITELINK%.')); ?>"><?php echo esc_textarea($rss_settings['before']); ?></textarea>
                                    </div>

                                    <hr class="tr-separator">

                                    <div class="tr-field-row">
                                        <label class="tr-label"
                                            for="tamrank_rss_after"><?php tamrank_e('rss_after_label', 'Text after each item'); ?></label>
                                        <textarea id="tamrank_rss_after" name="tam_rank_rss_settings[after]"
                                            class="tr-textarea" rows="2"
                                            placeholder="<?php echo esc_attr(tamrank__('rss_after_placeholder', 'Read the original: %POSTLINK%.')); ?>"><?php echo esc_textarea($rss_settings['after']); ?></textarea>
                                    </div>

                                    <p class="tr-field-hint">
                                        <?php tamrank_e('rss_placeholders_hint', 'Available placeholders:'); ?>
                                        <?php foreach ($rss_placeholders as $rss_ph) : ?>
                                            <code><?php echo esc_html($rss_ph); ?></code>
                                        <?php endforeach; ?>
                                    </p>
                                </div>
                            </div>
                        </div>

                        <!-- .htaccess Editor Card (FREE). Deliberately the LAST
                             card in this tab: advanced, can break the site on a
                             wrong rule, so it sits below the everyday settings
                             (Sam, 12 jul 2026). -->
                        <?php TamRank_Htaccess_Editor::render_card(); ?>

                    </div><!-- /technical -->

                    <!-- ═══════════════════════════════════════════════════════
                         TAB 5 — Licentie (#license)
                         ═══════════════════════════════════════════════════════ -->
                    <div id="license" class="tr-tab-panel">
                        <?php
                        // The license tab has three states, all rendered by the
                        // tam-rank-pro add-on via seams (cluster-15 strip):
                        //   1. Licensed/active  -> tamrank_settings_license_active
                        //      (credits, masked key, deactivate).
                        //   2. PRO installed but not yet activated
                        //      -> tamrank_settings_license_form (the key-entry form).
                        //   3. FREE-only (no add-on) -> render_license_tab_free upsell.
                        // The presence guard (tamrank_pro_addon_installed) ensures a
                        // FREE-only install always lands on the upsell, never an
                        // activation form it cannot handle.
                        ?>
                        <?php if ($is_license_active && has_action('tamrank_settings_license_active')): ?>
                            <?php do_action('tamrank_settings_license_active', $license_info, $credits_info); ?>
                        <?php elseif (tamrank_pro_addon_installed() && has_action('tamrank_settings_license_form')): ?>
                            <?php do_action('tamrank_settings_license_form', $license_info); ?>
                        <?php else: ?>
                            <?php $this->render_license_tab_free(); ?>
                        <?php endif; ?>
                    </div><!-- /license -->

                    <!-- ═══════════════════════════════════════════════════════
                         TAB 6 — Import / Export (#import-export)
                         ═══════════════════════════════════════════════════════ -->
                    <div id="import-export" class="tr-tab-panel">
                        <?php $this->render_import_export_tab(); ?>
                    </div><!-- /import-export -->

                    <!-- Save Bar -->
                    <div class="tr-save-bar">
                        <button type="submit"
                            class="tr-btn tr-btn--primary"><?php tamrank_e('save_settings', 'Save Settings'); ?></button>
                        <span class="tr-save-feedback"><?php tamrank_e('settings_saved', 'Settings saved!'); ?></span>
                    </div>

                </form>

                <?php $this->render_footer(); ?>
            </div>
        </div>

        <?php ob_start(); ?>
            jQuery(document).ready(function ($) {
                // Two tabs carry nothing this form saves, so the save bar leaves
                // the screen there instead of standing around greyed out (Sam,
                // 16 aug: a faded button above a tab with nothing to save is
                // noise, and the tooltip explaining it does not earn its place).
                //
                // One list for both tabs, built in PHP, so a tab drops out of it
                // the moment it does have something to save: with PRO the AI
                // Assistant tab holds real settings again and keeps its save bar.
                var tabsWithoutSave = <?php
                    $tr_no_save_tabs = array();

                    // AI Assistant only advertises the PRO feature while it is
                    // unlicensed; with PRO it holds real settings again.
                    if (!tam_rank_can_use_feature('ai_meta_generator')) {
                        $tr_no_save_tabs[] = '#ai-assistent';
                    }

                    // License never stores anything through this form, in any of
                    // its three states — the key field posts to the activation
                    // AJAX, not to options.php.
                    $tr_no_save_tabs[] = '#license';

                    echo wp_json_encode($tr_no_save_tabs);
                ?>;
                function toggleSaveButton() {
                    var hash = window.location.hash || '#general';
                    var $saveBar = $('.tr-save-bar');
                    if (tabsWithoutSave.indexOf(hash) !== -1) {
                        $saveBar.hide();
                    } else {
                        $saveBar.show();
                    }
                }
                toggleSaveButton();
                $(window).on('hashchange', toggleSaveButton);
                $('.tr-pill-nav .tr-pill').on('click', function () {
                    setTimeout(toggleSaveButton, 50);
                });

                // Preserve active tab on form submit
                $('.tam-rank-form').on('submit', function () {
                    if (window.location.hash) {
                        sessionStorage.setItem('tam_rank_active_tab', window.location.hash);
                    }
                });
                if (sessionStorage.getItem('tam_rank_active_tab')) {
                    var savedTab = sessionStorage.getItem('tam_rank_active_tab');
                    window.location.hash = savedTab;
                    sessionStorage.removeItem('tam_rank_active_tab');
                    // admin.js already ran its tab init before this inline block,
                    // so setting the hash alone leaves the General panel showing.
                    // The hashchange listener there normally catches it; call it
                    // outright as well for the case where the hash was already
                    // savedTab and no event fires.
                    if (window.TamRank && typeof window.TamRank.activateTabFromHash === 'function') {
                        window.TamRank.activateTabFromHash();
                    }
                    toggleSaveButton();
                }

                // Compression toggle show/hide quality slider
                $('#enable_compression').on('change', function () {
                    var qualityRow = document.getElementById('tr-compression-quality');
                    if (qualityRow) {
                        if (this.checked) { qualityRow.removeAttribute('hidden'); } else { qualityRow.setAttribute('hidden', ''); }
                    }
                });

                // RSS feed toggle
                var rssToggle = document.getElementById('tamrank_rss_enabled');
                var rssContent = document.getElementById('tr-rss-content');
                if (rssToggle && rssContent) {
                    rssToggle.addEventListener('change', function () {
                        if (rssToggle.checked) { rssContent.removeAttribute('hidden'); } else { rssContent.setAttribute('hidden', ''); }
                    });
                }

                // Automatic alt/title templates toggle. The pattern fields serve
                // both switches, so they stay visible while EITHER is on — the
                // two stored settings are still written from two separate
                // checkboxes and never touch each other.
                var altTplToggle = document.getElementById('tamrank_alt_templates_enabled');
                var altBulkToggle = document.getElementById('tamrank_alt_bulk_enabled');
                var altTplContent = document.getElementById('tr-alt-templates-content');
                function tamrankAltFieldsSync() {
                    if (!altTplContent) { return; }
                    var wanted = (altTplToggle && altTplToggle.checked) || (altBulkToggle && altBulkToggle.checked);
                    if (wanted) { altTplContent.removeAttribute('hidden'); } else { altTplContent.setAttribute('hidden', ''); }
                }
                if (altTplToggle) { altTplToggle.addEventListener('change', tamrankAltFieldsSync); }
                // The bulk switch saves itself; showing and hiding its panel is
                // tamrank-alt-bulk.js's job, because that is where the nonce and
                // the fresh counts live. All that is left here is the pattern
                // fields, which both switches share.
                if (altBulkToggle) { altBulkToggle.addEventListener('change', tamrankAltFieldsSync); }

                // Robots.txt toggle
                var robotsToggle = document.getElementById('tamrank_robots_enabled');
                var robotsContent = document.getElementById('tr-robots-content');
                if (robotsToggle && robotsContent) {
                    robotsToggle.addEventListener('change', function () {
                        if (robotsToggle.checked) { robotsContent.removeAttribute('hidden'); } else { robotsContent.setAttribute('hidden', ''); }
                    });
                }

                // Regenerate sitemap
                $('#regenerate-sitemap').on('click', function () {
                    var button = $(this);
                    button.prop('disabled', true).text('<?php echo esc_js(tamrank__('generating', 'Generating...')); ?>');
                    $.ajax({
                        url: ajaxurl, type: 'POST',
                        data: { action: 'tam_rank_regenerate_sitemap', nonce: '<?php echo esc_js(wp_create_nonce('tam_rank_nonce')); ?>' },
                        success: function (response) {
                            if (response.success) {
                                alert('<?php echo esc_js(tamrank__('sitemap_regenerated', 'Sitemap regenerated successfully!')); ?>');
                            } else {
                                alert('<?php echo esc_js(tamrank__('sitemap_error', 'An error occurred: ')); ?>' + response.data);
                            }
                        },
                        error: function () {
                            alert('<?php echo esc_js(tamrank__('sitemap_generate_error', 'An error occurred while generating the sitemap.')); ?>');
                        },
                        complete: function () {
                            button.prop('disabled', false).text('<?php echo esc_js(tamrank__('regenerate_sitemap', 'Regenerate sitemap')); ?>');
                        }
                    });
                });

                // Deactivate-license JS moved to the tam-rank-pro add-on; it is
                // emitted with the active-state license tab (cluster-15 strip).
            });
        <?php wp_add_inline_script('tam-rank-admin', ob_get_clean()); ?>
        <?php
    }

    // pagespeed_page() render moved to the tam-rank-pro add-on
    // (tam-rank-pro/admin/class-pro-pagespeed-page.php), cluster-1 Free/PRO strip.

    // content_strategy_page() (Topical Authority render) moved to the tam-rank-pro
    // add-on (tam-rank-pro/admin/class-pro-topical-authority-page.php), cluster-2 strip.

    public function audit_page()
    {
        // Freemium: Basis SEO audit is gratis voor iedereen
        // Volledige audit features worden in de audit-page.php zelf gecontroleerd
        TamRank_Audit_Page::render_audit_page();
    }

    public function schema_page()
    {
        // Schema Builder is a FREE feature (auto schema detection included)
        // Call the standalone render function
        tamrank_schema_render_admin_page();
    }

    public function monitor_404_page()
    {
        // The 404 Monitor (logging + list + redirect creation) is FREE. The
        // optional PRO add-on can extend the list through the do_action/filter
        // seams below; with no add-on present every seam is a no-op.
        global $wpdb, $tam_rank_redirect_suggestions;

        $table_name = $wpdb->prefix . 'tam_rank_404_log';
        $per_page = 50;
        $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only request inspection (admin page / OAuth callback / menu detection); no state-changing action to protect.
        $offset = ($page - 1) * $per_page;

        // Optional list filter value, passed through to the add-on seam below.
        $filter = isset($_GET['filter']) ? sanitize_text_field(wp_unslash($_GET['filter'])) : 'all'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only request inspection (admin page / OAuth callback / menu detection); no state-changing action to protect.

        // A 404 that already has an active redirect is fixed, so it drops out of
        // the list and out of both counters. Filtered, never deleted: the log
        // keeps its history, and switching the rule off puts the URL right back
        // on this page. The predicate rides inside the existing queries, so it
        // costs no extra query — see TamRank_404_Resolution.
        $unresolved_and = class_exists('TamRank_404_Resolution')
            ? ' AND ' . TamRank_404_Resolution::unresolved_sql('url')
            : '';
        $base_where = ($unresolved_and === '') ? '' : 'WHERE 1=1' . $unresolved_and;

        /**
         * Count the distinct still-broken URLs behind a WHERE clause.
         *
         * The exact rules are already inside $where. Only a site with active
         * regex rules pays for the second step, and then it is one extra query
         * per counter — never one per row.
         */
        $tr_count_open = function ($where) use ($wpdb, $table_name) {
            if (!class_exists('TamRank_404_Resolution') || !TamRank_404_Resolution::has_regex_rules()) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- direct query on a custom plugin table (tam_rank_404_log); only the table identifier and an already-prepared WHERE fragment are interpolated; admin-context read, caching not applicable
                return (int) $wpdb->get_var("SELECT COUNT(DISTINCT url) FROM $table_name $where");
            }
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- direct query on a custom plugin table (tam_rank_404_log); only the table identifier and an already-prepared WHERE fragment are interpolated; admin-context read, caching not applicable
            $urls = (array) $wpdb->get_col("SELECT DISTINCT url FROM $table_name $where");
            return count(TamRank_404_Resolution::drop_regex_resolved($urls));
        };

        // Optional extra WHERE clause supplied by the PRO add-on (returns an
        // already-prepared fragment). It ANDs onto the free clause above.
        $where_clause = (string) apply_filters('tamrank_404_list_where', $base_where, $filter, $table_name);

        // Get 404 logs gegroepeerd per URL
        // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL, WordPress.DB.PreparedSQLPlaceholders, PluginCheck.Security.DirectDB.UnescapedDBParameter -- direct query on a custom plugin table (tam_rank_404_log); only the $table_name table identifier and the already-prepared $where_clause fragment are interpolated, user values use prepare() placeholders; admin-context read, caching not applicable
        // Optional extra SELECT columns supplied by the PRO add-on (a fragment
        // beginning with a comma). The free query selects no extra columns.
        $extra_columns = (string) apply_filters('tamrank_404_list_columns', '');
        $logs = $wpdb->get_results($wpdb->prepare(
            "SELECT url, COUNT(*) as hits, MAX(timestamp) as timestamp,
                    MIN(id) as id{$extra_columns}
             FROM $table_name $where_clause
             GROUP BY url
             ORDER BY hits DESC
             LIMIT %d OFFSET %d",
            $per_page,
            $offset
        ));
        // phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL, WordPress.DB.PreparedSQLPlaceholders, PluginCheck.Security.DirectDB.UnescapedDBParameter

        // Same regex step as the counters, over the rows about to be rendered.
        if (!empty($logs) && class_exists('TamRank_404_Resolution') && TamRank_404_Resolution::has_regex_rules()) {
            $tr_open_urls = array_flip(TamRank_404_Resolution::drop_regex_resolved(wp_list_pluck($logs, 'url')));
            $logs = array_values(array_filter($logs, function ($log) use ($tr_open_urls) {
                return isset($tr_open_urls[$log->url]);
            }));
        }

        // Totaal aantal unieke URLs (met filter)
        $total_logs = $tr_count_open($where_clause);
        $total_pages = ceil($total_logs / $per_page);

        // Statistieken
        $count_all = $tr_count_open($base_where);
        $count_this_week = $tr_count_open('WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)' . $unresolved_and);

        // Get redirect suggestions count (for banner)
        $suggestion_counts = $tam_rank_redirect_suggestions ? $tam_rank_redirect_suggestions->count_suggestions() : array('pending' => 0, 'accepted' => 0, 'dismissed' => 0);
        ?>
        <div class="wrap tr-page-wrap tr-page-wrap--narrow">

            <!-- Page Header -->
            <div class="tr-404-page-header">
                <div>
                    <h1 class="tr-404-page-title"><?php tamrank_e('404_monitor', '404 Error Monitor'); ?></h1>
                    <p class="tr-404-page-subtitle">
                        <?php echo esc_html(tamrank__('404_monitor_desc', 'Overview of all 404 errors on your website.')); ?>
                        <?php
                        /**
                         * Optional subtitle suffix for the PRO add-on. FREE-only
                         * installs render nothing.
                         *
                         * @since 1.2.0
                         */
                        do_action('tamrank_404_subtitle_suffix');

                        // Retention note: keep users from being surprised that old
                        // entries disappear. Pulls the live filter value so the copy
                        // always matches the actual retention window.
                        $tr_404_retention = (int) apply_filters('tamrank_404_retention_days', 90);
                        if ($tr_404_retention > 0) {
                            echo ' ' . esc_html(sprintf(
                                /* translators: %d = number of days 404 entries are kept */
                                tamrank__('404_retention_note', 'Old 404s that are no longer visited are cleaned up automatically after %d days.'),
                                $tr_404_retention
                            ));
                        }
                        ?>
                    </p>
                </div>
                <div class="tr-404-header-actions">
                    <a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=tam-rank-404&export_404_csv=1'), 'tam_rank_export_404_csv')); ?>" id="tr-404-export" class="tr-btn tr-btn--ghost tr-btn--sm">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M21 15V19C21 20.1 20.1 21 19 21H5C3.9 21 3 20.1 3 19V15M7 10L12 15M12 15L17 10M12 15V3"/>
                        </svg>
                        <?php tamrank_e('export_csv', 'Export CSV'); ?>
                    </a>
                    <?php ob_start(); ?>
                    (function(){
                        var btn = document.getElementById('tr-404-export');
                        if (!btn) return;
                        btn.addEventListener('click', function() {
                            setTimeout(function() {
                                if (typeof TamRankToast !== 'undefined') {
                                    TamRankToast.success(
                                        '<?php echo esc_js(tamrank__('export_complete', 'Export complete')); ?>',
                                        '<?php echo esc_js(tamrank__('csv_downloaded', 'CSV file downloaded')); ?>'
                                    );
                                }
                            }, 1000);
                        });
                    })();
                    <?php wp_add_inline_script('tam-rank-admin', ob_get_clean()); ?>
                    <button type="button" id="tr-404-clear-log" class="tr-btn-danger-ghost">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polyline points="3 6 5 6 21 6" />
                            <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2" />
                        </svg>
                        <?php tamrank_e('clear_log', 'Clear Log'); ?>
                    </button>
                </div>
            </div>

            <!-- Redirect Suggestion Banner -->
            <?php if ($suggestion_counts['pending'] > 0): ?>
                <div class="tr-404-suggestion-banner">
                    <div>
                        <p><strong><?php echo (int) $suggestion_counts['pending']; ?></strong>
                            <?php tamrank_e('redirect_suggestions_available', 'redirect suggestions available'); ?></p>
                        <p><?php tamrank_e('auto_redirect_desc', 'TamRank has automatically suggested redirects for your 404 URLs.'); ?>
                        </p>
                    </div>
                    <a href="<?php echo esc_url(admin_url('admin.php?page=tam-rank-redirects')); ?>" class="tr-btn tr-btn--primary">
                        <?php tamrank_e('view_suggestions', 'View suggestions'); ?>
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M5 12h14M12 5l7 7-7 7" />
                        </svg>
                    </a>
                </div>
            <?php endif; ?>

            <!-- Stats Grid -->
            <div class="tr-404-stats">
                <div class="tr-404-stat-card tr-404-stat-card--error">
                    <div class="tr-404-stat-value"><?php echo number_format($count_all); ?></div>
                    <div class="tr-404-stat-label"><?php tamrank_e('total_404s', 'Total 404s'); ?></div>
                </div>
                <div class="tr-404-stat-card tr-404-stat-card--warning">
                    <div class="tr-404-stat-value"><?php echo number_format($count_this_week); ?></div>
                    <div class="tr-404-stat-label"><?php tamrank_e('new_this_week', 'New this week'); ?></div>
                </div>
            </div>

            <!-- Toolbar -->
            <div class="tr-404-toolbar">
                <!-- Filter Pills + Search -->
                <div class="tr-404-toolbar-row">
                    <div class="tr-404-filter-pills">
                        <a href="<?php echo esc_url(add_query_arg(array('filter' => 'all', 'paged' => 1))); ?>"
                            class="tr-404-pill <?php echo $filter === 'all' ? 'active' : ''; ?>">
                            <?php tamrank_e('filter_all', 'All'); ?>
                            <span class="tr-404-pill-count"><?php echo number_format($count_all); ?></span>
                        </a>
                        <?php
                        /**
                         * Optional extra filter pills for the PRO add-on. FREE shows
                         * only the All pill.
                         *
                         * @since 1.2.0
                         * @param string $filter Active filter value.
                         */
                        do_action('tamrank_404_toolbar_extra', $filter);
                        ?>
                    </div>
                    <div class="tr-404-search">
                        <svg class="tr-404-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none"
                            stroke="currentColor" stroke-width="2">
                            <circle cx="11" cy="11" r="8" />
                            <line x1="21" y1="21" x2="16.65" y2="16.65" />
                        </svg>
                        <input type="text" id="tr-404-search" class="tr-input"
                            placeholder="<?php tamrank_e('search_urls', 'Search URLs...'); ?>">
                    </div>
                </div>
            </div>

            <?php // ===== LOGS VIEW ===== ?>

                <?php
                /**
                 * Optional active-filter chip bar for the PRO add-on. The add-on
                 * reads the request itself; FREE renders nothing.
                 *
                 * @since 1.2.0
                 * @param string $table_name The 404 log table name.
                 */
                do_action('tamrank_404_list_top_bar', $table_name);
                ?>

                <?php if (!empty($logs)): ?>
                    <div class="tr-404-table-wrap">
                        <table class="tr-404-table">
                            <thead>
                                <tr>
                                    <th><input type="checkbox" class="tr-check tr-404-checkbox" id="tr-404-select-all"></th>
                                    <th><?php tamrank_e('404_url', '404 URL'); ?></th>
                                    <?php
                                    /**
                                     * Optional extra column header for the PRO add-on;
                                     * FREE-only renders nothing, keeping the header and
                                     * body column counts aligned.
                                     *
                                     * @since 1.2.0
                                     */
                                    do_action('tamrank_404_extra_column_header');
                                    ?>
                                    <th class="tr-th-center"><?php tamrank_e('hits', 'Hits'); ?></th>
                                    <th><?php tamrank_e('date', 'Date'); ?></th>
                                    <th><?php tamrank_e('actions', 'Actions'); ?></th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php foreach ($logs as $log): ?>
                                    <tr data-url="<?php echo esc_attr($log->url); ?>">
                                        <td><input type="checkbox" class="tr-check tr-404-checkbox tr-404-row-check"
                                                value="<?php echo esc_attr($log->url); ?>"></td>
                                        <td><span class="tr-404-url"><?php echo esc_html($log->url); ?></span></td>
                                        <?php
                                        /**
                                         * Optional extra column cell for the PRO add-on,
                                         * rendered per row; FREE-only renders nothing.
                                         *
                                         * @since 1.2.0
                                         * @param object $log The grouped 404 log row.
                                         */
                                        do_action('tamrank_404_extra_column_cell', $log);
                                        ?>
                                        <td class="tr-404-hits <?php echo $log->hits >= 10 ? 'tr-404-hits--high' : ''; ?>">
                                            <?php echo number_format((int) $log->hits); ?></td>
                                        <td class="tr-404-date"><?php
                                            // De kolom timestamp is nullable (tam-rank.php: "datetime
                                            // DEFAULT CURRENT_TIMESTAMP"). De logger schrijft er altijd
                                            // current_time('mysql') in, dus in de praktijk staat hier een
                                            // datum, maar een hand-geplaatste of geimporteerde rij kan
                                            // NULL dragen. Zonder guard gaf strtotime(null) false en
                                            // gmdate() daarop 01-01-1970: een datum die er nooit is
                                            // geweest. Zelfde guard en zelfde streepje als de kolom
                                            // Bijgewerkt in de schematabel.
                                            $log_ts = (isset($log->timestamp) && is_scalar($log->timestamp) && '' !== $log->timestamp)
                                                ? strtotime((string) $log->timestamp)
                                                : false;
                                            if (false !== $log_ts) {
                                                echo esc_html(gmdate('d-m-Y H:i', $log_ts));
                                            } else {
                                                printf(
                                                    '<span aria-hidden="true">&mdash;</span><span class="screen-reader-text">%s</span>',
                                                    esc_html(tamrank__('unknown', 'Unknown'))
                                                );
                                            }
                                        ?></td>
                                        <td>
                                            <button type="button" class="tr-404-redirect-btn" data-url="<?php echo esc_attr($log->url); ?>">
                                                <?php tamrank_e('redirect', 'Redirect'); ?> &rarr;
                                            </button>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>

                    <!-- Bulk Action Bar -->
                    <div class="tr-404-bulk-bar" id="tr-404-bulk-bar">
                        <div class="tr-404-bulk-bar-left">
                            <span class="tr-404-bulk-count" id="tr-404-bulk-count">0</span>
                            <span><?php tamrank_e('selected', 'selected'); ?></span>
                        </div>
                        <div class="tr-404-bulk-bar-actions">
                            <button type="button" class="tr-404-bulk-redirect"
                                id="tr-404-bulk-redirect"><?php tamrank_e('create_redirect', 'Create Redirect'); ?></button>
                            <button type="button" class="tr-404-bulk-ignore"
                                id="tr-404-bulk-ignore"><?php tamrank_e('ignore', 'Ignore'); ?></button>
                        </div>
                        <button type="button" class="tr-404-bulk-close" id="tr-404-bulk-close" aria-label="<?php echo esc_attr(tamrank__('close', 'Close')); ?>">&times;</button>
                    </div>

                    <!-- Pagination -->
                    <?php if ($total_pages > 1): ?>
                        <div class="tr-404-pagination">
                            <span><?php echo wp_kses_post(sprintf(tamrank__('page_of', 'Page %1$s of %2$s'), '<strong>' . (int) $page . '</strong>', '<strong>' . (int) $total_pages . '</strong>')); ?></span>
                            <div>
                                <?php
                                $pagination_base = add_query_arg(array('filter' => $filter, 'view' => 'logs'));
                                echo wp_kses_post(paginate_links(array(
                                    'base' => add_query_arg('paged', '%#%', $pagination_base),
                                    'format' => '',
                                    'prev_text' => '&laquo; ' . tamrank__('prev', 'Previous'),
                                    'next_text' => tamrank__('next', 'Next') . ' &raquo;',
                                    'total' => $total_pages,
                                    'current' => $page,
                                    'mid_size' => 2,
                                    'end_size' => 1,
                                )));
                                ?>
                            </div>
                        </div>
                    <?php endif; ?>

                <?php else: ?>
                    <!-- Empty State -->
                    <div class="tr-404-empty">
                        <div class="tr-404-empty-icon">
                            <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                                <circle cx="12" cy="12" r="10" />
                                <path d="M8 14s1.5 2 4 2 4-2 4-2" />
                                <circle cx="9" cy="9" r="1" fill="currentColor" />
                                <circle cx="15" cy="9" r="1" fill="currentColor" />
                            </svg>
                        </div>
                        <p class="tr-404-empty-text">
                            <?php if ($filter !== 'all'): ?>
                                <?php tamrank_e('no_404_errors_filter', 'No 404 errors found with this filter.'); ?>
                            <?php else: ?>
                                <?php tamrank_e('no_404_errors', 'No 404 errors found.'); ?>
                            <?php endif; ?>
                        </p>
                    </div>
                <?php endif; ?>

            <!-- Slide-in Redirect Panel -->
            <div class="tr-404-panel-overlay" id="tr-404-panel-overlay"></div>
            <div class="tr-404-panel" id="tr-404-panel">
                <div class="tr-404-panel-header">
                    <h3 class="tr-404-panel-title"><?php tamrank_e('create_redirect', 'Create redirect'); ?></h3>
                    <button type="button" class="tr-404-panel-close" id="tr-404-panel-close" aria-label="<?php echo esc_attr(tamrank__('close', 'Close')); ?>"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true"><line x1="6" y1="6" x2="18" y2="18"></line><line x1="6" y1="18" x2="18" y2="6"></line></svg></button>
                </div>
                <div class="tr-404-panel-body">
                    <!-- Single source -->
                    <div class="tr-404-panel-field" id="tr-404-panel-source-field">
                        <label class="tr-404-panel-label">404 URL</label>
                        <div class="tr-404-panel-source-box">
                            <span class="tr-404-panel-source-url" id="tr-404-panel-source"></span>
                        </div>
                        <div class="tr-404-panel-source-meta" id="tr-404-panel-source-meta"></div>
                    </div>
                    <!-- Bulk sources -->
                    <div class="tr-404-panel-field tr-404-panel-bulk-info" id="tr-404-panel-bulk-info" style="display:none;">
                        <label class="tr-404-panel-label">404 URLs</label>
                        <div class="tr-404-panel-bulk-list" id="tr-404-panel-bulk-list"></div>
                    </div>
                    <!-- Target URL -->
                    <div class="tr-404-panel-field">
                        <label class="tr-404-panel-label"><?php tamrank_e('redirect_to', 'Redirect to'); ?></label>
                        <input type="text" class="tr-input tr-404-panel-target-input" id="tr-404-panel-target"
                            placeholder="<?php echo esc_attr(home_url('/')); ?>">
                        <?php
                        /*
                         * Shown for any status outside 3xx, exactly like the note under
                         * the To field on the Redirects screen. Same string, so the two
                         * places cannot start explaining the same rule differently.
                         */
                        ?>
                        <span class="tr-404-panel-note" id="tr-404-panel-notarget-note" hidden><?php
                            echo esc_html(tamrank__('to_url_not_needed_no_target', 'Not needed for this status: the page is not being forwarded, so there is nothing to point at.'));
                        ?></span>
                    </div>
                    <!-- Redirect type -->
                    <div class="tr-404-panel-field">
                        <label class="tr-404-panel-label"><?php tamrank_e('redirect_type', 'Redirect type'); ?></label>
                        <label class="tr-404-panel-radio is-recommended">
                            <input type="radio" name="tr-404-redirect-type" value="301" checked>
                            <span class="tr-404-panel-radio-dot"></span>
                            <span class="tr-404-panel-radio-content">
                                <strong>301 — Permanent (<?php tamrank_e('recommended', 'recommended'); ?>)</strong>
                                <span
                                    class="tr-404-panel-radio-desc"><?php tamrank_e('301_desc', 'Use this when the page has moved permanently.'); ?></span>
                            </span>
                        </label>
                        <div class="tr-404-panel-divider"></div>
                        <details class="tr-404-panel-advanced">
                            <summary><?php tamrank_e('advanced', 'Advanced'); ?></summary>
                            <div class="tr-404-panel-advanced-options">
                                <label class="tr-404-panel-radio">
                                    <input type="radio" name="tr-404-redirect-type" value="302">
                                    <span class="tr-404-panel-radio-dot"></span>
                                    <span class="tr-404-panel-radio-content">
                                        <strong>302 — Temporary</strong>
                                        <span
                                            class="tr-404-panel-radio-desc"><?php tamrank_e('302_desc', 'Temporary redirect.'); ?></span>
                                    </span>
                                </label>
                                <label class="tr-404-panel-radio">
                                    <input type="radio" name="tr-404-redirect-type" value="307">
                                    <span class="tr-404-panel-radio-dot"></span>
                                    <span class="tr-404-panel-radio-content">
                                        <strong>307 — Temporary (strict)</strong>
                                        <span
                                            class="tr-404-panel-radio-desc"><?php tamrank_e('307_desc', 'Preserves the HTTP method.'); ?></span>
                                    </span>
                                </label>
                                <label class="tr-404-panel-radio">
                                    <input type="radio" name="tr-404-redirect-type" value="410">
                                    <span class="tr-404-panel-radio-dot"></span>
                                    <span class="tr-404-panel-radio-content">
                                        <strong>410 — Gone</strong>
                                        <span
                                            class="tr-404-panel-radio-desc"><?php tamrank_e('410_desc', 'Page has been permanently removed.'); ?></span>
                                    </span>
                                </label>
                                <label class="tr-404-panel-radio">
                                    <input type="radio" name="tr-404-redirect-type" value="451">
                                    <span class="tr-404-panel-radio-dot"></span>
                                    <span class="tr-404-panel-radio-content">
                                        <strong><?php echo esc_html(tamrank__('redirect_type_451', '451 - Unavailable for legal reasons')); ?></strong>
                                        <span
                                            class="tr-404-panel-radio-desc"><?php tamrank_e('451_desc', 'Page is withheld on legal grounds.'); ?></span>
                                    </span>
                                </label>
                            </div>
                        </details>
                    </div>
                </div>
                <div class="tr-404-panel-footer">
                    <button type="button" class="tr-btn tr-btn--primary tr-404-panel-save-btn"
                        id="tr-404-panel-save"><?php tamrank_e('save_redirect', 'Save redirect'); ?></button>
                    <button type="button" class="tr-404-panel-cancel-link"
                        id="tr-404-panel-cancel"><?php tamrank_e('cancel', 'Cancel'); ?></button>
                </div>
            </div>

            <?php $this->render_footer(); ?>
        </div>
        <?php
    }

    public function redirects_page()
    {
        // Redirects is a FREE feature (incl. the local self-learning engine)
        // Only advanced features like regex redirects, redirect chains, and smart matching v2 (AI) are PRO
        global $wpdb, $tam_rank_redirect_suggestions;

        $table_name = $wpdb->prefix . 'tam_rank_redirects';

        // Get redirect suggestions data
        $all_suggestions = $tam_rank_redirect_suggestions ? $tam_rank_redirect_suggestions->get_suggestions(40, 'pending') : array();
        $suggestion_counts = $tam_rank_redirect_suggestions ? $tam_rank_redirect_suggestions->count_suggestions() : array('pending' => 0, 'accepted' => 0, 'dismissed' => 0);

        // Get learning stats
        $learning_stats = $tam_rank_redirect_suggestions ? $tam_rank_redirect_suggestions->get_learning_stats() : array('total_decisions' => 0, 'acceptance_rate' => 0, 'last_learning' => 0);

        // Handle form submissions — store notice for rendering inside form card
        $redir_notice = '';
        $redir_notice_type = '';
        if (isset($_POST['action']) && $_POST['action'] === 'add_redirect' && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'] ?? '')), 'tam_rank_nonce')) {
            $source = sanitize_text_field(wp_unslash($_POST['source_url'] ?? ''));
            $match_type = sanitize_key(wp_unslash($_POST['match_type'] ?? 'exact'));
            $match_type = ($match_type === 'regex') ? 'regex' : 'exact';
            // A regex target may carry $1..$9 backreferences, which sanitize_url()
            // keeps; the pattern itself is vetted by TamRank_Redirect_Regex.
            $target = sanitize_url(wp_unslash($_POST['target_url'] ?? ''));
            $type = sanitize_text_field(wp_unslash($_POST['redirect_type'] ?? ''));

            global $tam_rank_redirect_manager;
            if ($tam_rank_redirect_manager) {
                $result = $tam_rank_redirect_manager->create_redirect($source, $target, $type, false, $match_type);
                if (is_wp_error($result)) {
                    $redir_notice_type = 'error';
                    $redir_notice = esc_html($result->get_error_message());
                } else {
                    $redir_notice_type = 'success';
                    // translators: %s is the source URL/path the redirect forwards from.
                    $redir_notice = sprintf( __('Redirect created. Traffic from <strong>%s</strong> is now being forwarded.', 'tam-rank'), esc_html($source) );
                }
            } elseif ($match_type === 'regex') {
                // The manager is what owns the regex guard. Without it there is
                // no safe path to store a pattern, so refuse rather than write
                // an unvetted regex straight into the table.
                $redir_notice_type = 'error';
                $redir_notice = esc_html(tamrank__('regex_error_unavailable', 'Regex redirects are unavailable right now. Reload the page and try again.'));
            } elseif (class_exists('TamRank_Redirect_Regex') && TamRank_Redirect_Regex::looks_like_pattern($source)) {
                // Same refusal as the manager: without it this fallback would
                // be the one path that still stores a pattern as a dead path.
                $redir_notice_type = 'error';
                $redir_notice = esc_html(tamrank__('regex_looks_like_pattern', 'This looks like a regex pattern, but Match is set to Exact. Set Match to Regex to forward every matching path, or enter a plain path such as /old-page.'));
            } else {
                $wpdb->suppress_errors();
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- direct write on a custom plugin table (tam_rank_redirects); values bound via $wpdb->insert() format array; admin-context write, caching not applicable
                $result = $wpdb->insert(
                    $table_name,
                    array(
                        'source_url' => $source,
                        'target_url' => $target,
                        'redirect_type' => $type,
                        'match_type' => 'exact',
                        'active' => 1
                    ),
                    array('%s', '%s', '%s', '%s', '%d')
                );
                $wpdb->suppress_errors(false);

                if ($result) {
                    $redir_notice_type = 'success';
                    // translators: %s is the source URL/path the redirect forwards from.
                    $redir_notice = sprintf( __('Redirect created. Traffic from <strong>%s</strong> is now being forwarded.', 'tam-rank'), esc_html($source) );
                } elseif (strpos($wpdb->last_error, 'Duplicate entry') !== false) {
                    $redir_notice_type = 'warning';
                    // translators: %s is the source URL/path that already has a redirect.
                    $redir_notice = sprintf( __('A redirect for <strong>%s</strong> already exists. Remove the existing redirect first if you want to change it.', 'tam-rank'), esc_html($source) );
                } else {
                    $redir_notice_type = 'error';
                    $redir_notice = __('Could not create the redirect. Check the entered URLs and try again.', 'tam-rank');
                }
            }
        }

        // Get redirects
        $redirects = $wpdb->get_results("SELECT * FROM $table_name ORDER BY created_at DESC"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- direct query on a custom plugin table (tam_rank_redirects); only the table identifier is interpolated, no user input in query; admin-context read, caching not applicable
        $redirect_count = count($redirects);

        // Filter logic
        $filter = isset($_GET['filter']) ? sanitize_text_field(wp_unslash($_GET['filter'])) : 'all'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only request inspection (admin page / OAuth callback / menu detection); no state-changing action to protect.
        $auto_count = 0;
        $manual_count = 0;
        $import_count = 0;
        // Three buckets, checked in this order. A suggestion carries
        // auto_generated = 1 AND created_by = 'suggestion', so it stays in Auto
        // exactly as before; only a wizard import (auto_generated = 0,
        // created_by = 'import') is peeled out of Manual. isset() guards the
        // install where the created_by column has not been added yet: there the
        // condition is simply never true and the old two buckets remain.
        foreach ($redirects as $r) {
            if (!empty($r->auto_generated)) {
                $auto_count++;
            } elseif (isset($r->created_by) && $r->created_by === 'import') {
                $import_count++;
            } else {
                $manual_count++;
            }
        }
        $base_url = admin_url('admin.php?page=tam-rank-redirects');

        // Filter displayed redirects
        $filtered_redirects = $redirects;
        if ($filter === 'auto') {
            $filtered_redirects = array_filter($redirects, function ($r) {
                return !empty($r->auto_generated);
            });
        } elseif ($filter === 'import') {
            $filtered_redirects = array_filter($redirects, function ($r) {
                return empty($r->auto_generated) && isset($r->created_by) && $r->created_by === 'import';
            });
        } elseif ($filter === 'manual') {
            // Mirrors the counting above: Manual is what is left after Auto and
            // Import, so the three pills add up to All.
            $filtered_redirects = array_filter($redirects, function ($r) {
                return empty($r->auto_generated) && !(isset($r->created_by) && $r->created_by === 'import');
            });
        }
        ?>
        <div class="wrap tr-page-wrap">


            <!-- Page Header -->
            <div class="tr-redir-page-header">
                <div>
                    <h1 class="tr-redir-page-title"><?php tamrank_e('redirect_manager', 'Redirect Manager'); ?></h1>
                    <p class="tr-redir-page-subtitle">
                        <?php tamrank_e('redirect_manager_desc', 'Manage all redirects on your website'); ?>
                    </p>
                </div>
                <div class="tr-redir-ie-wrap">
                    <button type="button" id="toggle-import-export" class="tr-btn tr-btn--md tr-btn--secondary">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
                            <polyline points="14 2 14 8 20 8" />
                        </svg>
                        <?php tamrank_e('import_export', 'Import / Export'); ?>
                        <svg id="import-export-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none"
                            stroke="currentColor" stroke-width="2">
                            <polyline points="6 9 12 15 18 9" />
                        </svg>
                    </button>

                    <!-- Import/Export Dropdown -->
                    <div id="import-export-panel" class="tr-redir-ie-dropdown">
                        <div class="tr-redir-csv-label"><?php tamrank_e('csv_format', 'CSV Format'); ?></div>
                        <code class="tr-redir-csv-code">/old-url,https://example.com/new-url,301</code>

                        <div class="tr-redir-ie-actions">
                            <button type="button" id="download-csv-template" class="tr-redir-ie-btn tr-redir-ie-btn--secondary">
                                <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                    stroke-width="2">
                                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                                    <polyline points="7 10 12 15 17 10" />
                                    <line x1="12" y1="15" x2="12" y2="3" />
                                </svg>
                                <?php tamrank_e('template', 'Template'); ?>
                            </button>
                            <button type="button" id="export-redirects" class="tr-redir-ie-btn tr-redir-ie-btn--primary">
                                <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                    stroke-width="2">
                                    <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12" />
                                </svg>
                                <?php tamrank_e('export_csv', 'Export CSV'); ?>
                            </button>
                            <input type="file" id="import-file" accept=".csv" style="display: none;">
                            <button type="button" id="select-import-file" class="tr-redir-ie-btn tr-redir-ie-btn--secondary">
                                <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                    stroke-width="2">
                                    <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
                                </svg>
                                <?php tamrank_e('select_csv', 'Select CSV'); ?>
                            </button>
                            <button type="button" id="import-redirects" class="tr-redir-ie-btn tr-redir-ie-btn--primary"
                                style="display: none;">
                                <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                    stroke-width="2">
                                    <polyline points="20 6 9 17 4 12" />
                                </svg>
                                <?php tamrank_e('import_csv', 'Import CSV'); ?>
                            </button>
                        </div>

                        <div id="import-file-name" class="tr-redir-import-filename">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
                                <polyline points="14 2 14 8 20 8" />
                            </svg>
                            <span id="selected-file-name"></span>
                        </div>
                        <div id="import-result" class="tr-redir-import-result"></div>
                    </div>
                </div>
            </div>

            <!-- Add New Redirect Form -->
            <div class="tr-redir-add-card">
                <div class="tr-redir-add-title"><?php tamrank_e('add_redirect', 'Add Redirect'); ?></div>
                <form method="post" class="tr-redir-add-form">
                    <input type="hidden" name="action" value="add_redirect">
                    <input type="hidden" name="nonce" value="<?php echo esc_attr(wp_create_nonce('tam_rank_nonce')); ?>">
                    <div class="tr-redir-add-row">
                    <div class="tr-redir-add-field tr-redir-add-field--match">
                        <label class="tr-redir-add-label">
                            <?php tamrank_e('match_type', 'Match'); ?>
                            <span class="tr-tooltip tr-tooltip--bottom tr-tooltip--lg">
                                <button type="button" class="tr-tooltip-trigger" aria-describedby="tt-match-type">
                                    <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                        stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                                        <circle cx="12" cy="12" r="10" />
                                        <line x1="12" y1="16" x2="12" y2="12" />
                                        <line x1="12" y1="8" x2="12.01" y2="8" />
                                    </svg>
                                </button>
                                <span class="tr-tooltip__bubble" role="tooltip" id="tt-match-type">
                                    <strong><?php tamrank_e('match_type_exact', 'Exact'); ?></strong>
                                    <?php tamrank_e('match_type_exact_desc', 'Forwards one specific path.'); ?><br><br>
                                    <strong><?php tamrank_e('match_type_regex', 'Regex'); ?></strong>
                                    <?php tamrank_e('match_type_regex_desc', 'Forwards every path matching a pattern, for example ^/blog/(.*)$. Type the pattern without slashes around it; $1 to $9 in the target URL fill in what the pattern captured.'); ?>
                                </span>
                            </span>
                        </label>
                        <select name="match_type" id="tr-redir-match-type" class="tr-redir-type-select">
                            <option value="exact"><?php tamrank_e('match_type_exact', 'Exact'); ?></option>
                            <option value="regex"><?php tamrank_e('match_type_regex', 'Regex'); ?></option>
                        </select>
                    </div>
                    <div class="tr-redir-add-field tr-redir-add-field--from">
                        <label class="tr-redir-add-label" for="tr-redir-source-url">
                            <span id="tr-redir-source-label"
                                data-label-exact="<?php echo esc_attr(tamrank__('from_url', 'From URL (relative path)')); ?>"
                                data-label-regex="<?php echo esc_attr(tamrank__('from_url_regex', 'From URL (regex pattern)')); ?>"><?php tamrank_e('from_url', 'From URL (relative path)'); ?></span>
                        </label>
                        <input type="text" name="source_url"
                            id="tr-redir-source-url"
                            placeholder="<?php echo esc_attr(tamrank__('from_url_placeholder', '/old-page')); ?>"
                            data-placeholder-exact="<?php echo esc_attr(tamrank__('from_url_placeholder', '/old-page')); ?>"
                            data-placeholder-regex="<?php echo esc_attr(tamrank__('from_url_placeholder_regex', '^/blog/(.*)$')); ?>"
                            required class="tr-redir-add-input">
                    </div>
                    <div class="tr-redir-add-field tr-redir-add-field--to">
                        <label class="tr-redir-add-label"><?php tamrank_e('to_url', 'To URL (full path)'); ?></label>
                        <?php
                        /*
                         * `required` stays in the markup and is lifted by the script
                         * below for any status outside 3xx — a 410 Gone and a 451
                         * Unavailable For Legal Reasons have nowhere to send anyone,
                         * and the server ignores the target anyway. Keeping it here
                         * means a browser without the script still asks for a target
                         * on every status, which is the harmless way round.
                         */
                        ?>
                        <input type="url" name="target_url"
                            id="tr-redir-target-url"
                            placeholder="<?php echo esc_attr(tamrank__('to_url_placeholder', 'https://yourwebsite.com/new-page')); ?>"
                            data-placeholder-exact="<?php echo esc_attr(tamrank__('to_url_placeholder', 'https://yourwebsite.com/new-page')); ?>"
                            data-placeholder-regex="<?php echo esc_attr(tamrank__('to_url_placeholder_regex', 'https://yourwebsite.com/news/$1')); ?>"
                            required
                            class="tr-redir-add-input">
                        <span class="tr-redir-add-note" id="tr-redir-notarget-note" hidden><?php
                            echo esc_html(tamrank__('to_url_not_needed_no_target', 'Not needed for this status: the page is not being forwarded, so there is nothing to point at.'));
                        ?></span>
                    </div>
                    <div class="tr-redir-add-field tr-redir-add-field--type">
                        <label class="tr-redir-add-label">
                            <?php tamrank_e('redirect_type', 'Type'); ?>
                            <?php
                            // All standard redirect status codes are available to every user.
                            $label_307 = tamrank__('redirect_type_307', '307 - Temporary');
                            $label_410 = tamrank__('redirect_type_410', '410 - Gone');
                            $label_451 = tamrank__('redirect_type_451', '451 - Unavailable for legal reasons');
                            ?>
                            <span class="tr-tooltip tr-tooltip--bottom tr-tooltip--lg">
                                <button type="button" class="tr-tooltip-trigger" aria-describedby="tt-redirect-type">
                                    <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                        stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                                        <circle cx="12" cy="12" r="10" />
                                        <line x1="12" y1="16" x2="12" y2="12" />
                                        <line x1="12" y1="8" x2="12.01" y2="8" />
                                    </svg>
                                </button>
                                <span class="tr-tooltip__bubble" role="tooltip" id="tt-redirect-type">
                                    <strong><?php tamrank_e('redirect_type_301', '301 - Permanent'); ?></strong>
                                    <?php tamrank_e('redirect_type_301_desc', 'Page has moved permanently. Search engines update their index.'); ?><br><br>
                                    <strong><?php tamrank_e('redirect_type_302', '302 - Temporary'); ?></strong>
                                    <?php tamrank_e('redirect_type_302_desc', 'Page is temporarily moved. Original URL stays indexed.'); ?><br><br>
                                    <strong><?php echo esc_html($label_307); ?></strong>
                                    <?php tamrank_e('redirect_type_307_desc', 'Like 302, but preserves HTTP method (POST stays POST).'); ?><br><br>
                                    <strong><?php echo esc_html($label_410); ?></strong>
                                    <?php tamrank_e('redirect_type_410_desc', 'Page is permanently deleted. Search engines remove from index.'); ?><br><br>
                                    <strong><?php echo esc_html($label_451); ?></strong>
                                    <?php tamrank_e('redirect_type_451_desc', 'Page is withheld on legal grounds, for example after a takedown request. Search engines remove it from the index, and the status says why.'); ?>
                                </span>
                            </span>
                        </label>
                        <select name="redirect_type" id="tr-redir-status-type" class="tr-redir-type-select">
                            <option value="301">301</option>
                            <option value="302">302</option>
                            <option value="307">307</option>
                            <option value="410">410</option>
                            <option value="451">451</option>
                        </select>
                    </div>
                    <button type="submit" id="btn-add-redirect" class="tr-btn tr-btn--md tr-btn--primary">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <line x1="12" y1="5" x2="12" y2="19" />
                            <line x1="5" y1="12" x2="19" y2="12" />
                        </svg>
                        <?php tamrank_e('add_button', 'Add'); ?>
                    </button>
                    </div>
                    <p class="tr-redir-add-hint" id="tr-redir-add-hint"
                        data-hint-exact="<?php echo esc_attr(tamrank__('redirect_hint_exact', 'From URL: the path visitors request, for example /old-page. Only that one path is forwarded.')); ?>"
                        data-hint-regex="<?php echo esc_attr(tamrank__('redirect_hint_regex', 'From URL: a pattern without slashes around it, for example ^/blog/(.*)$. Put $1 in the To URL for what (.*) captured — https://yourwebsite.com/news/$1 — otherwise every match lands on the same page. A pattern runs against the full URI including the query string: ^/blog/(.*)$ takes ?utm_source=... along, ^/blog/([^?]*) stops at the question mark.')); ?>">
                        <?php echo esc_html(tamrank__('redirect_hint_exact', 'From URL: the path visitors request, for example /old-page. Only that one path is forwarded.')); ?>
                    </p>
                </form>
                <?php if ($redir_notice): ?>
                    <div class="tr-alert tr-alert--<?php echo esc_attr($redir_notice_type); ?>" style="margin-top: var(--tr-space-3);">
                        <div class="tr-alert__content"><p class="tr-alert__desc"><?php echo wp_kses_post($redir_notice); ?></p></div>
                        <button type="button" class="tr-alert__close" aria-label="<?php echo esc_attr(tamrank__('dismiss', 'Dismiss')); ?>">&times;</button>
                    </div>
                <?php endif; ?>
            </div>

            <!-- Redirects Table -->
            <div class="tr-redir-table-wrap">
                <!-- Section Header with Filter Pills -->
                <div class="tr-redir-section-header">
                    <span class="tr-redir-section-title"><?php tamrank_e('existing_redirects', 'Existing Redirects'); ?></span>
                    <span class="tr-redir-count-pill"><?php echo esc_html($redirect_count); ?></span>

                    <div class="tr-pill-nav">
                        <a href="<?php echo esc_url($base_url); ?>"
                            class="tr-pill <?php echo $filter === 'all' ? 'is-active' : ''; ?>">
                            <?php tamrank_e('filter_all', 'All'); ?> <span>(<?php echo esc_html($redirect_count); ?>)</span>
                        </a>
                        <a href="<?php echo esc_url($base_url . '&filter=auto'); ?>"
                            class="tr-pill <?php echo $filter === 'auto' ? 'is-active' : ''; ?>">
                            <?php tamrank_e('filter_auto', 'Auto'); ?> <span>(<?php echo esc_html($auto_count); ?>)</span>
                        </a>
                        <a href="<?php echo esc_url($base_url . '&filter=import'); ?>"
                            class="tr-pill <?php echo $filter === 'import' ? 'is-active' : ''; ?>">
                            <?php tamrank_e('filter_import', 'Import'); ?> <span>(<?php echo esc_html($import_count); ?>)</span>
                        </a>
                        <a href="<?php echo esc_url($base_url . '&filter=manual'); ?>"
                            class="tr-pill <?php echo $filter === 'manual' ? 'is-active' : ''; ?>">
                            <?php tamrank_e('filter_manual', 'Manual'); ?> <span>(<?php echo esc_html($manual_count); ?>)</span>
                        </a>
                    </div>

                    <div class="tr-redir-section-actions">
                        <?php
                        /**
                         * Fires in the redirects table toolbar. Add-ons (e.g. the PRO
                         * redirect-chain detector) hook this to inject their own controls.
                         */
                        do_action('tamrank_redirects_toolbar');
                        ?>
                        <div class="tr-redir-search-wrap">
                            <svg class="tr-redir-search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none"
                                stroke="currentColor" stroke-width="2">
                                <circle cx="11" cy="11" r="8" />
                                <line x1="21" y1="21" x2="16.65" y2="16.65" />
                            </svg>
                            <input type="text" id="redirect-search" placeholder="<?php echo esc_attr(tamrank__('search_placeholder', 'Search...')); ?>" class="tr-redir-search-input">
                        </div>
                    </div>
                </div>

                <!-- Table -->
                <?php if (!empty($filtered_redirects)): ?>
                    <table class="tr-redir-table">
                        <thead>
                            <tr>
                                <th><?php tamrank_e('from', 'From'); ?></th>
                                <th><?php tamrank_e('to', 'To'); ?></th>
                                <th class="tr-th-center" style="width: 60px;"><?php tamrank_e('redirect_type', 'Type'); ?></th>
                                <th class="tr-th-center" style="width: 65px;"><?php tamrank_e('source', 'Source'); ?></th>
                                <th class="tr-th-center" style="width: 50px;"><?php tamrank_e('hits', 'Hits'); ?></th>
                                <th class="tr-th-center" style="width: 60px;"><?php tamrank_e('status', 'Status'); ?></th>
                                <?php
                                /** Fires in the redirects table header row, for an add-on extra column header. */
                                do_action('tamrank_redirects_chain_column_header');
                                ?>
                                <th class="tr-th-center" style="width: <?php echo esc_attr(apply_filters('tamrank_redirects_actions_col_width', 100)); ?>px;">
                                    <?php tamrank_e('actions', 'Actions'); ?></th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ($filtered_redirects as $redirect): ?>
                                <tr class="redirect-row <?php echo !$redirect->active ? 'is-inactive' : ''; ?>"
                                    data-id="<?php echo (int) $redirect->id; ?>"
                                    data-source="<?php echo esc_attr($redirect->source_url); ?>"
                                    data-target="<?php echo esc_attr($redirect->target_url); ?>"
                                    data-type="<?php echo esc_attr($redirect->redirect_type); ?>"
                                    data-match-type="<?php echo esc_attr(isset($redirect->match_type) && $redirect->match_type === 'regex' ? 'regex' : 'exact'); ?>">
                                    <?php $is_regex_rule = isset($redirect->match_type) && $redirect->match_type === 'regex'; ?>
                                    <?php
                                    /*
                                     * A pattern stored on Exact can never fire: exact matching is a
                                     * string comparison, so it would take a visitor literally
                                     * requesting /^/old/(.*)$ . Rules like this were saved silently
                                     * before the 1.3.7 guard and still sit in the table looking
                                     * Active, hit counter and all. We only FLAG them — we never
                                     * rewrite match_type, because a dead rule that starts moving
                                     * real traffic after an update is worse than a dead rule.
                                     * Same guard shape as the manager: no regex class, no claim.
                                     */
                                    $pattern_on_exact = !$is_regex_rule
                                        && class_exists('TamRank_Redirect_Regex')
                                        && TamRank_Redirect_Regex::looks_like_pattern($redirect->source_url);
                                    ?>
                                    <td data-search="<?php echo esc_attr($redirect->source_url); ?>">
                                        <?php // The marker trails the pattern: every From cell then starts on the same x. ?>
                                        <span class="tr-redir-from-cell">
                                            <code class="tr-redir-url-code"><?php echo esc_html($redirect->source_url); ?></code>
                                            <?php if ($is_regex_rule): ?>
                                                <span class="tr-redir-match-note"
                                                    data-tr-tooltip="<?php echo esc_attr(tamrank__('match_type_regex_badge_tip', 'Pattern rule: forwards every path that matches this pattern.')); ?>"><?php tamrank_e('match_type_regex_label', 'regex'); ?></span>
                                            <?php elseif ($pattern_on_exact): ?>
                                                <span class="tr-redir-match-note tr-redir-match-note--warn"
                                                    data-tr-tooltip="<?php echo esc_attr(tamrank__('regex_looks_like_pattern', 'This looks like a regex pattern, but Match is set to Exact. Set Match to Regex to forward every matching path, or enter a plain path such as /old-page.')); ?>"><?php tamrank_e('match_type_pattern_on_exact_label', 'never matches'); ?></span>
                                            <?php endif; ?>
                                        </span>
                                    </td>
                                    <td data-search="<?php echo esc_attr($redirect->target_url); ?>">
                                        <?php if (trim((string) $redirect->target_url) === ''): ?>
                                            <?php // A 410 has no destination. An empty <a href> would link to this admin page. ?>
                                            <span class="tr-redir-no-target" aria-hidden="true">&mdash;</span>
                                        <?php elseif ($is_regex_rule): ?>
                                            <?php // A regex target can carry $1..$9, so it is not a clickable URL. ?>
                                            <code class="tr-redir-url-code"><?php echo esc_html($redirect->target_url); ?></code>
                                        <?php else: ?>
                                            <a href="<?php echo esc_url($redirect->target_url); ?>" target="_blank"
                                                rel="noopener noreferrer"
                                                class="tr-redir-to-link"><?php echo esc_html($redirect->target_url); ?></a>
                                        <?php endif; ?>
                                    </td>
                                    <td class="tr-th-center">
                                        <span
                                            class="tr-redir-type-badge tr-redir-type-badge--<?php echo esc_attr($redirect->redirect_type); ?>"><?php echo esc_html($redirect->redirect_type); ?></span>
                                    </td>
                                    <td class="tr-th-center">
                                        <?php if (!empty($redirect->auto_generated) && $redirect->auto_generated): ?>
                                            <span
                                                class="tr-redir-source-badge tr-redir-source-badge--auto"><?php tamrank_e('auto', 'Auto'); ?></span>
                                        <?php elseif (isset($redirect->created_by) && $redirect->created_by === 'import'): ?>
                                            <span
                                                class="tr-redir-source-badge tr-redir-source-badge--import"><?php tamrank_e('import', 'Import'); ?></span>
                                        <?php else: ?>
                                            <span
                                                class="tr-redir-source-badge tr-redir-source-badge--manual"><?php tamrank_e('manual', 'Manual'); ?></span>
                                        <?php endif; ?>
                                    </td>
                                    <td class="tr-th-center">
                                        <?php echo number_format((int) $redirect->hits); ?>
                                    </td>
                                    <td class="tr-th-center">
                                        <span class="tr-redir-status">
                                            <span
                                                class="tr-redir-status-dot tr-redir-status-dot--<?php echo $redirect->active ? 'active' : 'inactive'; ?>"></span>
                                            <span
                                                class="tr-redir-status-label"><?php echo esc_html($redirect->active ? tamrank__('active', 'Active') : tamrank__('inactive', 'Inactive')); ?></span>
                                        </span>
                                    </td>
                                    <?php
                                    /** Fires in each redirect row for an add-on extra column cell. @param object $redirect */
                                    do_action('tamrank_redirects_chain_column_cell', $redirect);
                                    ?>
                                    <td class="tr-th-center">
                                        <div class="tr-redir-actions">
                                            <button type="button"
                                                class="toggle-redirect tr-redir-action-btn tr-redir-action-btn--<?php echo $redirect->active ? 'toggle-active' : 'toggle-inactive'; ?>"
                                                data-id="<?php echo esc_attr($redirect->id); ?>" data-active="<?php echo esc_attr($redirect->active); ?>"
                                                data-tr-tooltip="<?php echo $redirect->active ? esc_attr(tamrank__('deactivate', 'Deactivate')) : esc_attr(tamrank__('activate', 'Activate')); ?>">
                                                <?php if ($redirect->active): ?>
                                                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="white"
                                                        stroke-width="2">
                                                        <rect x="6" y="4" width="4" height="16" />
                                                        <rect x="14" y="4" width="4" height="16" />
                                                    </svg>
                                                <?php else: ?>
                                                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="white"
                                                        stroke-width="2">
                                                        <polygon points="5 3 19 12 5 21 5 3" />
                                                    </svg>
                                                <?php endif; ?>
                                            </button>
                                            <button type="button"
                                                class="edit-redirect tr-redir-action-btn tr-redir-action-btn--edit"
                                                data-id="<?php echo esc_attr($redirect->id); ?>"
                                                data-tr-tooltip="<?php echo esc_attr(tamrank__('edit', 'Edit')); ?>">
                                                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="white"
                                                    stroke-width="2">
                                                    <path d="M12 20h9" />
                                                    <path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z" />
                                                </svg>
                                            </button>
                                            <button type="button"
                                                class="delete-redirect tr-redir-action-btn tr-redir-action-btn--delete"
                                                data-id="<?php echo esc_attr($redirect->id); ?>"
                                                data-tr-tooltip="<?php echo esc_attr(tamrank__('delete', 'Delete')); ?>">
                                                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="white"
                                                    stroke-width="2">
                                                    <polyline points="3 6 5 6 21 6" />
                                                    <path
                                                        d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2" />
                                                </svg>
                                            </button>
                                            <?php
                                            /** Fires in the redirect row action buttons. @param object $redirect */
                                            do_action('tamrank_redirects_row_actions', $redirect);
                                            ?>
                                        </div>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                <?php else: ?>
                    <div class="tr-redir-empty-table">
                        <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                            <polyline points="15 14 20 9 15 4" />
                            <path d="M4 20v-7a4 4 0 0 1 4-4h12" />
                        </svg>
                        <p><?php tamrank_e('no_redirects', 'No redirects created yet.'); ?></p>
                        <p class="tr-redir-empty-table__hint"><?php tamrank_e('no_redirects_hint', 'Create your first redirect with the form above.'); ?></p>
                    </div>
                <?php endif; ?>
            </div>

            <!-- AI Suggestions Card -->
            <?php
            // Redirect-suggesties (genereren/accepteren/wegklikken) en de zelflerende
            // engine draaien volledig lokaal en zijn FREE.
            $has_redirect_access = tam_rank_can_use_feature( 'redirect_suggestions_basic' );
            ?>
            <div class="tr-redir-suggestions-card">
                <div class="tr-redir-suggestions-header">
                    <div class="tr-redir-suggestions-icon">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
                            stroke-linecap="round" stroke-linejoin="round">
                            <path
                                d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" />
                        </svg>
                    </div>
                    <div class="tr-redir-suggestions-info">
                        <h3 class="tr-redir-suggestions-title">
                            <?php tamrank_e('auto_redirect_suggestions', 'Automatic Redirect Suggestions'); ?></h3>
                        <p class="tr-redir-suggestions-desc">
                            <?php tamrank_e('auto_redirect_suggestions_desc', 'TamRank analyzes your 404 URLs and suggests matching redirects automatically.'); ?>
                        </p>
                    </div>
                    <?php if ($has_redirect_access): ?>
                        <div class="tr-redir-suggestions-counts">
                            <span class="tr-redir-count-badge tr-redir-count-badge--pending"
                                id="suggestion-count-pending-redirects"><?php echo (int) $suggestion_counts['pending']; ?>
                                <?php tamrank_e('pending', 'pending'); ?></span>
                            <span
                                class="tr-redir-count-badge tr-redir-count-badge--accepted"><?php echo (int) $suggestion_counts['accepted']; ?>
                                <?php tamrank_e('accepted', 'accepted'); ?></span>
                        </div>
                        <button type="button" id="generate-suggestions-redirects" class="tr-btn tr-btn--sm tr-btn--primary">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.3" />
                            </svg>
                            <?php tamrank_e('generate', 'Generate'); ?>
                        </button>
                    <?php endif; ?>
                </div>

                <?php if ($has_redirect_access): ?>
                    <?php if (!empty($all_suggestions)): ?>
                        <div class="tr-redir-suggestions-list">
                            <?php foreach ($all_suggestions as $suggestion):
                                $confidence = isset($suggestion->confidence_score) ? round($suggestion->confidence_score) : round($suggestion->score);
                                $confidence_class = $confidence >= 75 ? 'high' : ($confidence >= 50 ? 'medium' : 'low');
                                ?>
                                <div class="tr-redir-suggestion-row suggestion-row-redirects" data-id="<?php echo esc_attr($suggestion->id); ?>">
                                    <div class="tr-redir-suggestion-content">
                                        <div class="tr-redir-suggestion-from">
                                            <code class="tr-redir-url-code"><?php echo esc_html($suggestion->source_url); ?></code>
                                        </div>
                                        <p class="tr-redir-suggestion-to">
                                            &rarr; <span><?php echo esc_html($suggestion->target_url); ?></span>
                                        </p>
                                    </div>
                                    <span
                                        class="tr-redir-suggestion-score tr-redir-type-badge--<?php echo esc_attr($confidence >= 75 ? '301' : ($confidence >= 50 ? '307' : '302')); ?>"><?php echo esc_html($confidence); ?>%</span>
                                    <div class="tr-redir-suggestion-actions">
                                        <button type="button" class="accept-suggestion-redirects tr-redir-btn-accept"
                                            data-id="<?php echo esc_attr($suggestion->id); ?>"
                                            data-tr-tooltip="<?php echo esc_attr(tamrank__('accept', 'Accept')); ?>">
                                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                                stroke-width="2.5">
                                                <polyline points="20 6 9 17 4 12" />
                                            </svg>
                                            <?php echo esc_html(tamrank__('accept', 'Accept')); ?>
                                        </button>
                                        <button type="button" class="dismiss-suggestion-redirects tr-redir-btn-dismiss"
                                            data-id="<?php echo esc_attr($suggestion->id); ?>" data-tr-tooltip="<?php echo esc_attr(tamrank__('dismiss', 'Dismiss')); ?>">
                                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                                stroke-width="2.5">
                                                <line x1="18" y1="6" x2="6" y2="18" />
                                                <line x1="6" y1="6" x2="18" y2="18" />
                                            </svg>
                                            <?php echo esc_html(tamrank__('dismiss', 'Dismiss')); ?>
                                        </button>
                                    </div>
                                </div>
                            <?php endforeach; ?>
                        </div>
                    <?php else: ?>
                        <div class="tr-redir-suggestions-empty">
                            <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                                <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
                            </svg>
                            <p><?php tamrank_e('no_redirect_suggestions', 'No open suggestions. Click "Generate" to create new ones.'); ?>
                            </p>
                        </div>
                    <?php endif; ?>

                    <!-- TamRank Learning Footer (lokale zelflerende engine, FREE) -->
                    <div class="tr-redir-learning-footer">
                        <div class="tr-redir-learning-icon">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" />
                                <path d="M8 12l3 3 5-6" stroke-linecap="round" stroke-linejoin="round" />
                            </svg>
                        </div>
                        <div class="tr-redir-learning-text">
                            <span
                                class="tr-redir-learning-highlight"><?php tamrank_e('tamrank_learning', 'TamRank Learning'); ?></span>
                            &mdash;
                            <?php tamrank_e('learning_stats_desc', 'TamRank learns from your decisions. After every 5 decisions, the algorithm is optimized.'); ?>
                        </div>
                        <div class="tr-redir-learning-stats">
                            <div class="tr-redir-learning-stat">
                                <div class="tr-redir-learning-stat-value">
                                    <?php
                                    if ($learning_stats['last_learning'] > 0) {
                                        echo esc_html(date_i18n('j M', $learning_stats['last_learning']));
                                    } else {
                                        tamrank_e('not_yet', 'Not yet');
                                    }
                                    ?>
                                </div>
                                <div class="tr-redir-learning-stat-label"><?php tamrank_e('last_updated', 'Last updated'); ?></div>
                            </div>
                            <div class="tr-redir-learning-stat">
                                <div class="tr-redir-learning-stat-value">
                                    <?php echo number_format($learning_stats['total_decisions']); ?></div>
                                <div class="tr-redir-learning-stat-label"><?php tamrank_e('decisions', 'Decisions'); ?></div>
                            </div>
                            <div class="tr-redir-learning-stat">
                                <div class="tr-redir-learning-stat-value"><?php echo esc_html($learning_stats['acceptance_rate']); ?>%</div>
                                <div class="tr-redir-learning-stat-label"><?php tamrank_e('acceptance', 'Acceptance'); ?></div>
                            </div>
                        </div>
                    </div>
                <?php endif; ?>
            </div>

            <?php $this->render_footer(); ?>
        </div>

        <?php ob_start(); ?>
            jQuery(document).ready(function ($) {
                // Match-type switch: a regex is not a URL, so relax the browser's
                // url validation on the target and swap in pattern placeholders.
                // Label and hint move along, because "From URL (relative path)"
                // above a field that wants ^/blog/(.*)$ is what makes people
                // type a pattern with Match still on Exact.
                $('#tr-redir-match-type').on('change', function () {
                    var isRegex = $(this).val() === 'regex';
                    var source = document.getElementById('tr-redir-source-url');
                    var target = document.getElementById('tr-redir-target-url');
                    var label = document.getElementById('tr-redir-source-label');
                    var hint = document.getElementById('tr-redir-add-hint');
                    if (source) {
                        source.placeholder = isRegex ? source.dataset.placeholderRegex : source.dataset.placeholderExact;
                    }
                    if (target) {
                        target.type = isRegex ? 'text' : 'url';
                        target.placeholder = isRegex ? target.dataset.placeholderRegex : target.dataset.placeholderExact;
                    }
                    if (label) {
                        label.textContent = isRegex ? label.dataset.labelRegex : label.dataset.labelExact;
                    }
                    if (hint) {
                        hint.textContent = isRegex ? hint.dataset.hintRegex : hint.dataset.hintExact;
                    }
                }).trigger('change');

                // A status outside 3xx has no destination: handle_redirects()
                // sends the status and never reads the target. So the To field
                // stops being mandatory the moment 410 or 451 is picked —
                // otherwise the form demands a URL the server throws away, which
                // is how ruis ends up in the table. This mirrors the server's
                // TamRank_Redirect_Manager::status_needs_target() by asking the
                // code instead of matching a list, so a status added through the
                // tamrank_redirect_types filter behaves the same here.
                $('#tr-redir-status-type').on('change', function () {
                    var code = parseInt($(this).val(), 10);
                    var needsTarget = (code >= 300 && code <= 399);
                    var target = document.getElementById('tr-redir-target-url');
                    var note = document.getElementById('tr-redir-notarget-note');
                    if (target) {
                        target.required = needsTarget;
                    }
                    if (note) {
                        note.hidden = needsTarget;
                    }
                }).trigger('change');

                // Toggle Import/Export Dropdown
                $('#toggle-import-export').on('click', function (e) {
                    e.stopPropagation();
                    $('#import-export-panel').toggleClass('is-open');
                });

                // Close dropdown when clicking outside
                $(document).on('click', function (e) {
                    if (!$(e.target).closest('.tr-redir-ie-wrap').length) {
                        $('#import-export-panel').removeClass('is-open');
                    }
                });

                // Search redirects
                $('#redirect-search').on('keyup', function () {
                    var query = $(this).val().toLowerCase();
                    $('.redirect-row').each(function () {
                        var row = $(this);
                        var sourceUrl = (row.find('td:first').data('search') || '').toString().toLowerCase();
                        var targetUrl = (row.find('td:eq(1)').data('search') || '').toString().toLowerCase();
                        row.toggle(sourceUrl.includes(query) || targetUrl.includes(query));
                    });
                });

                // Toggle redirect
                $('.toggle-redirect').on('click', function () {
                    var button = $(this);
                    var id = button.data('id');
                    var active = button.data('active');

                    button.prop('disabled', true);

                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'tam_rank_toggle_redirect',
                            nonce: '<?php echo esc_js(wp_create_nonce('tam_rank_nonce')); ?>',
                            id: id,
                            active: active ? 0 : 1
                        },
                        success: function (response) {
                            if (response.success) {
                                location.reload();
                            } else {
                                TamRankToast.show(tamRank.i18n.title_redirect_toggle, response.data || tamRank.i18n.error_redirect_toggle, 'error');
                            }
                        },
                        error: function () {
                            TamRankToast.show(tamRank.i18n.title_redirect_toggle, tamRank.i18n.error_redirect_toggle, 'error');
                        },
                        complete: function () {
                            button.prop('disabled', false);
                        }
                    });
                });

                // Delete redirect
                $('.delete-redirect').on('click', function () {
                    var button = $(this);
                    var id = button.data('id');
                    window.TamRank.confirm({
                        title: '<?php echo esc_js(tamrank__('redirect_delete_confirm_title', 'Delete redirect?')); ?>',
                        message: '<?php echo esc_js(tamrank__('redirect_delete_confirm_message', 'This redirect will be permanently deleted.')); ?>',
                        confirmLabel: '<?php echo esc_js(tamrank__('redirect_delete_confirm_label', 'Delete')); ?>',
                        variant: 'danger'
                    }).then(function (ok) {
                        if (!ok) return;
                        doDelete(button, id);
                    });
                });
                function doDelete(button, id) {
                    button.prop('disabled', true);

                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'tam_rank_delete_redirect',
                            nonce: '<?php echo esc_js(wp_create_nonce('tam_rank_nonce')); ?>',
                            id: id
                        },
                        success: function (response) {
                            if (response.success) {
                                button.closest('tr').fadeOut(300, function () { $(this).remove(); });
                            } else {
                                TamRankToast.show(tamRank.i18n.title_redirect_delete, response.data || tamRank.i18n.error_redirect_delete, 'error');
                            }
                        },
                        error: function () {
                            TamRankToast.show(tamRank.i18n.title_redirect_delete, tamRank.i18n.error_redirect_delete, 'error');
                        },
                        complete: function () {
                            button.prop('disabled', false);
                        }
                    });
                }

                // Edit redirect — opens a TamRank.dialog with prefilled form
                $(document).on('click', '.edit-redirect', function () {
                    var row = $(this).closest('tr.redirect-row');
                    var id = parseInt(row.data('id'), 10);
                    var source = row.data('source') || '';
                    var target = row.data('target') || '';
                    var type = (row.data('type') || '301').toString();

                    var i18n = (window.tamRank && window.tamRank.i18n) || {};
                    var lblFrom = (i18n.from_url || 'From URL (relative path)');
                    var lblTo = (i18n.to_url || 'To URL (full path)');
                    var lblType = (i18n.redirect_type || 'Type');
                    var phFrom = (i18n.from_url_placeholder || '/old-page');
                    var phTo = (i18n.to_url_placeholder || 'https://example.com/new-page');
                    function optEl(val, label) {
                        var sel = (val === type) ? ' selected' : '';
                        return '<option value="' + val + '"' + sel + '>' + label + '</option>';
                    }
                    // All five redirect types are free (matches the Add-Redirect form and backend validation).
                    var typeOptions = optEl('301', '301') + optEl('302', '302') + optEl('307', '307') + optEl('410', '410') + optEl('451', '451');

                    var formHtml = ''
                        + '<div class="tr-dialog-error" data-role="error"></div>'
                        + '<form data-role="edit-redirect-form" novalidate>'
                        +   '<div class="tr-dialog-field" data-field="source">'
                        +     '<label for="tr-edit-source">' + escHtml(lblFrom) + '</label>'
                        +     '<input type="text" id="tr-edit-source" name="source" required '
                        +       'placeholder="' + escHtml(phFrom) + '" '
                        +       'value="' + escHtml(source) + '">'
                        +   '</div>'
                        +   '<div class="tr-dialog-field" data-field="target">'
                        +     '<label for="tr-edit-target">' + escHtml(lblTo) + '</label>'
                        +     '<input type="text" id="tr-edit-target" name="target" required '
                        +       'placeholder="' + escHtml(phTo) + '" '
                        +       'value="' + escHtml(target) + '">'
                        +   '</div>'
                        +   '<div class="tr-dialog-field" data-field="type">'
                        +     '<label for="tr-edit-type">' + escHtml(lblType) + '</label>'
                        +     '<select id="tr-edit-type" name="type">' + typeOptions + '</select>'
                        +   '</div>'
                        + '</form>';

                    window.TamRank.dialog({
                        title: i18n.edit_redirect || 'Edit Redirect',
                        body: formHtml,
                        confirmLabel: i18n.update_redirect || 'Update',
                        cancelLabel: i18n.cancel || 'Cancel',
                        variant: 'primary',
                        size: 'md',
                        onConfirm: function (dialogEl) {
                            var $dlg = $(dialogEl);
                            var $err = $dlg.find('[data-role="error"]');
                            var $src = $dlg.find('#tr-edit-source');
                            var $tgt = $dlg.find('#tr-edit-target');
                            var $typ = $dlg.find('#tr-edit-type');

                            $dlg.find('.tr-dialog-field').removeClass('has-error');
                            $err.removeClass('is-visible').text('');

                            var sVal = $.trim($src.val() || '');
                            var tVal = $.trim($tgt.val() || '');
                            var typeVal = $typ.val() || '301';

                            if (!sVal) { $dlg.find('[data-field="source"]').addClass('has-error'); $src.focus(); return false; }
                            // Same rule as the add form and the server: only a real
                            // redirection needs somewhere to go. A 410 or a 451 may
                            // be saved with the target cleared.
                            var typeCode = parseInt(typeVal, 10);
                            if (!tVal && typeCode >= 300 && typeCode <= 399) { $dlg.find('[data-field="target"]').addClass('has-error'); $tgt.focus(); return false; }

                            return $.ajax({
                                url: ajaxurl,
                                type: 'POST',
                                data: {
                                    action: 'tam_rank_update_redirect',
                                    nonce: '<?php echo esc_js(wp_create_nonce('tam_rank_nonce')); ?>',
                                    id: id,
                                    source: sVal,
                                    target: tVal,
                                    type: typeVal
                                }
                            }).then(function (response) {
                                if (response && response.success) {
                                    var msg = (response.data && response.data.message) || (i18n.redirect_updated || 'Redirect updated');
                                    if (window.TamRankToast) {
                                        TamRankToast.show('TamRank', msg, 'success');
                                    }
                                    setTimeout(function () { location.reload(); }, 400);
                                    return true;
                                }
                                var errMsg = (response && response.data && response.data.message) || 'Update failed';
                                $err.text(errMsg).addClass('is-visible');
                                return false;
                            }, function () {
                                $err.text('Network error').addClass('is-visible');
                                return false;
                            });
                        }
                    });
                });

                function escHtml(s) {
                    return String(s == null ? '' : s)
                        .replace(/&/g, '&amp;')
                        .replace(/</g, '&lt;')
                        .replace(/>/g, '&gt;')
                        .replace(/"/g, '&quot;')
                        .replace(/'/g, '&#39;');
                }


                // Export redirects
                $('#export-redirects').on('click', function () {
                    var button = $(this);
                    var originalHtml = button.html();

                    button.prop('disabled', true).html('<span class="tam-loading"></span> <?php echo esc_js(tamrank__('redirect_exporting', 'Exporting...')); ?>');

                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'tam_rank_export_redirects',
                            nonce: '<?php echo esc_js(wp_create_nonce('tam_rank_nonce')); ?>'
                        },
                        success: function (response) {
                            if (response.success) {
                                var blob = new Blob([response.data.csv], { type: 'text/csv;charset=utf-8;' });
                                var link = document.createElement('a');
                                link.href = URL.createObjectURL(blob);
                                link.download = response.data.filename;
                                link.style.display = 'none';
                                document.body.appendChild(link);
                                link.click();
                                document.body.removeChild(link);
                                TamRankToast.show(tamRank.i18n.title_export_success, response.data.total + ' <?php echo esc_js(tamrank__('redirect_export_count_suffix', 'redirects exported.')); ?>', 'success');
                            } else {
                                TamRankToast.show(tamRank.i18n.title_export, response.data || tamRank.i18n.error_redirect_export, 'error');
                            }
                        },
                        error: function () {
                            TamRankToast.show(tamRank.i18n.title_export, tamRank.i18n.error_redirect_export, 'error');
                        },
                        complete: function () {
                            button.prop('disabled', false).html(originalHtml);
                        }
                    });
                });

                // Select import file
                $('#select-import-file').on('click', function () {
                    $('#import-file').trigger('click');
                });

                var selectedFile = null;
                $('#import-file').on('change', function () {
                    selectedFile = this.files[0];
                    if (selectedFile) {
                        $('#selected-file-name').text(selectedFile.name);
                        $('#import-file-name').addClass('is-visible');
                        $('#import-redirects').css('display', 'inline-flex');
                    } else {
                        $('#import-file-name').removeClass('is-visible');
                        $('#import-redirects').hide();
                    }
                });

                // Import redirects
                $('#import-redirects').on('click', function () {
                    if (!selectedFile) {
                        TamRankToast.show(tamRank.i18n.title_csv_select, tamRank.i18n.error_csv_select, 'warning');
                        return;
                    }

                    var button = $(this);
                    var originalHtml = button.html();

                    button.prop('disabled', true).html('<span class="tam-loading"></span> <?php echo esc_js(tamrank__('redirect_importing', 'Importing...')); ?>');

                    var reader = new FileReader();
                    reader.onload = function (e) {
                        $.ajax({
                            url: ajaxurl,
                            type: 'POST',
                            data: {
                                action: 'tam_rank_import_redirects',
                                nonce: '<?php echo esc_js(wp_create_nonce('tam_rank_nonce')); ?>',
                                csv_data: e.target.result
                            },
                            success: function (response) {
                                if (response.success) {
                                    showImportResult('success', response.data.message);
                                    if (response.data.imported > 0) {
                                        setTimeout(function () { location.reload(); }, 2000);
                                    }
                                } else {
                                    showImportResult('error', response.data.message || '<?php echo esc_js(tamrank__('redirect_import_failed', 'Import failed')); ?>');
                                }
                            },
                            error: function () {
                                showImportResult('error', '<?php echo esc_js(tamrank__('redirect_import_error', 'An error occurred during import.')); ?>');
                            },
                            complete: function () {
                                button.prop('disabled', false).html(originalHtml);
                                $('#import-file').val('');
                                selectedFile = null;
                                $('#import-file-name').removeClass('is-visible');
                                $('#import-redirects').hide();
                            }
                        });
                    };
                    reader.readAsText(selectedFile);
                });

                // Helper: show import result
                function showImportResult(type, message) {
                    var $result = $('#import-result');
                    $result.removeClass('tr-redir-import-result--success tr-redir-import-result--error');
                    $result.addClass('is-visible tr-redir-import-result--' + type);
                    $result.html(message);
                    setTimeout(function () { $result.removeClass('is-visible'); }, 10000);
                }

                // Download CSV template
                $('#download-csv-template').on('click', function () {
                    var siteUrl = '<?php echo esc_url(home_url()); ?>';
                    var templateCsv =
                        'Source URL,Target URL,Redirect Type\n' +
                        '/oude-pagina,' + siteUrl + '/nieuwe-pagina,301\n' +
                        '/oud-product,' + siteUrl + '/producten/nieuw-product,301\n' +
                        '/tijdelijke-aanbieding,' + siteUrl + '/aanbiedingen,302\n' +
                        '/verouderd-artikel,' + siteUrl + '/blog/bijgewerkt-artikel,301\n' +
                        '/oude-categorie/subcategorie,' + siteUrl + '/nieuwe-categorie,301';

                    var blob = new Blob([templateCsv], { type: 'text/csv;charset=utf-8;' });
                    var link = document.createElement('a');
                    link.href = URL.createObjectURL(blob);
                    link.download = 'tam-rank-redirect-template.csv';
                    link.style.display = 'none';
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);

                    var button = $(this);
                    var originalHtml = button.html();
                    button.html('<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> <?php echo esc_js(tamrank__('redirect_downloaded', 'Downloaded!')); ?>');
                    setTimeout(function () { button.html(originalHtml); }, 2000);
                });

                // Generate suggestions
                $('#generate-suggestions-redirects').on('click', function () {
                    var button = $(this);
                    var originalHtml = button.html();

                    button.prop('disabled', true).html('<span class="tam-loading"></span> Genereren...');

                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'tam_rank_generate_suggestions',
                            nonce: '<?php echo esc_js(wp_create_nonce('tam_rank_nonce')); ?>'
                        },
                        success: function (response) {
                            if (response.success) {
                                location.reload();
                            } else {
                                TamRankToast.show(tamRank.i18n.title_suggestions, response.data || tamRank.i18n.error_suggestions_load, 'error');
                                button.prop('disabled', false).html(originalHtml);
                            }
                        },
                        error: function () {
                            TamRankToast.show(tamRank.i18n.title_suggestions, tamRank.i18n.error_suggestions_load, 'error');
                            button.prop('disabled', false).html(originalHtml);
                        }
                    });
                });

                // Accept suggestion
                $('.accept-suggestion-redirects').on('click', function () {
                    var button = $(this);
                    var row = button.closest('.suggestion-row-redirects');
                    var id = button.data('id');

                    button.prop('disabled', true);

                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'tam_rank_accept_suggestion',
                            nonce: '<?php echo esc_js(wp_create_nonce('tam_rank_nonce')); ?>',
                            id: id
                        },
                        success: function (response) {
                            if (response.success) {
                                row.addClass('is-removing');
                                setTimeout(function () {
                                    row.remove();
                                    var countEl = $('#suggestion-count-pending-redirects');
                                    var currentCount = parseInt(countEl.text()) || 0;
                                    countEl.text((currentCount - 1) + ' wachtend');
                                }, 400);
                                setTimeout(function () { location.reload(); }, 600);
                            } else {
                                TamRankToast.show(tamRank.i18n.title_suggestion_action, response.data || tamRank.i18n.error_suggestion_accept, 'error');
                                button.prop('disabled', false);
                            }
                        },
                        error: function () {
                            TamRankToast.show(tamRank.i18n.title_suggestion_action, tamRank.i18n.error_suggestion_accept, 'error');
                            button.prop('disabled', false);
                        }
                    });
                });

                // Dismiss suggestion
                $('.dismiss-suggestion-redirects').on('click', function () {
                    var button = $(this);
                    var row = button.closest('.suggestion-row-redirects');
                    var id = button.data('id');

                    button.prop('disabled', true);

                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'tam_rank_dismiss_suggestion',
                            nonce: '<?php echo esc_js(wp_create_nonce('tam_rank_nonce')); ?>',
                            id: id
                        },
                        success: function (response) {
                            if (response.success) {
                                row.addClass('is-removing');
                                setTimeout(function () {
                                    row.remove();
                                    var countEl = $('#suggestion-count-pending-redirects');
                                    var currentCount = parseInt(countEl.text()) || 0;
                                    countEl.text((currentCount - 1) + ' wachtend');
                                }, 400);
                            } else {
                                TamRankToast.show(tamRank.i18n.title_suggestion_action, response.data || tamRank.i18n.error_suggestion_reject, 'error');
                                button.prop('disabled', false);
                            }
                        },
                        error: function () {
                            TamRankToast.show(tamRank.i18n.title_suggestion_action, tamRank.i18n.error_suggestion_reject, 'error');
                            button.prop('disabled', false);
                        }
                    });
                });
            });
        <?php
        wp_add_inline_script('tam-rank-admin', ob_get_clean());
    }

    /**
     * Render Import / Export tab
     */
    private function render_import_export_tab()
    {
        $onboarding_status = get_option('tamrank_onboarding_complete');
        $has_backup = false;

        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- only the {$wpdb->postmeta} table identifier is interpolated, no user input in query; admin-context request-scoped read, caching not applicable
        $backup_count = (int) $wpdb->get_var(
            "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key = '_tamrank_migration_backup'"
        );

        // A migration that only touched robots.txt and/or the sitemap configuration
        // leaves zero post-meta backups. Gating the rollback card on $backup_count
        // alone would hide it while there is very much something to roll back.
        if (!class_exists('TamRank_Option_Backup')) {
            require_once TAM_RANK_PLUGIN_PATH . 'includes/onboarding/class-tamrank-option-backup.php';
        }
        // Only the options the migration really captured count here: a migration
        // without the robots.txt / sitemap categories leaves no option backup at all,
        // and one with a single category leaves exactly one.
        $backed_up_options   = TamRank_Option_Backup::captured_option_names();
        $option_backup_count = count($backed_up_options);
        $has_option_backup   = $option_backup_count > 0;
        $has_robots_backup   = in_array('tam_rank_robots_txt', $backed_up_options, true);
        $has_sitemap_backup  = in_array('tam_rank_sitemap_settings', $backed_up_options, true);
        $has_backup          = $backup_count > 0 || $has_option_backup;

        // Has a migration already run? tamrank_migration_state is the only place
        // that records it: TamRank_Migration_Manager writes status/source/updated_at
        // on every batch, and the rollback deletes the option when it finishes - so
        // a rolled-back site correctly falls back to the "never migrated" card.
        // A migration that is still 'in_progress' is not done, and keeps the normal
        // presentation. The date guard is for hand-edited option data only:
        // save_state() always writes updated_at, and without a readable date there
        // is nothing honest to put on the line.
        $migration_state = get_option('tamrank_migration_state', array());
        $migration_done  = is_array($migration_state)
            && isset($migration_state['status'])
            && 'completed' === $migration_state['status']
            && !empty($migration_state['updated_at'])
            && false !== strtotime($migration_state['updated_at']);

        if ($migration_done) {
            // mysql2date() reads the string as site-local time (that is what
            // current_time('mysql') wrote), so no timezone shift. The format is
            // numeric on purpose: WP's month names follow the WP locale, while
            // this screen speaks TamRank's own language setting - and d-m-Y is
            // what the SEO Audit's "last scan" line already shows.
            $migration_date = mysql2date('d-m-Y', $migration_state['updated_at']);
            // Brand names, not translatable strings. An older migration can have
            // stored no source at all (the manager's legacy auto-detect path), and
            // the plugin may since have been deleted - so this map has to stand on
            // its own instead of asking the scanner.
            $source_names   = array(
                'yoast'    => 'Yoast SEO',
                'rankmath' => 'Rank Math',
                'aioseo'   => 'All in One SEO',
            );
            $migration_source = isset($migration_state['source'], $source_names[$migration_state['source']])
                ? $source_names[$migration_state['source']]
                : '';
        }

        // Detection
        if (!class_exists('TamRank_SEO_Scanner')) {
            require_once TAM_RANK_PLUGIN_PATH . 'includes/onboarding/class-tamrank-seo-scanner.php';
        }
        $scanner = new TamRank_SEO_Scanner();
        $detection = $scanner->detect_seo_plugin();
        ?>

        <!-- SEO Migratie -->
        <div class="tr-card" style="margin-bottom: var(--tr-space-6);">
            <div class="tr-card-header" style="padding: var(--tr-space-6); border-bottom: 1px solid var(--tr-color-surface);">
                <div>
                    <h2
                        style="font-size: var(--tr-text-lg); font-weight: var(--tr-font-semibold); color: var(--tr-color-slate-900); margin: 0 0 var(--tr-space-1) 0;">
                        <?php tamrank_e('seo_data_migration', 'SEO Data Migration'); ?>
                    </h2>
                    <p style="font-size: var(--tr-text-sm); color: var(--tr-color-slate-500); margin: 0;">
                        <?php tamrank_e('seo_data_migration_desc', 'Import SEO data from Yoast SEO, Rank Math or All in One SEO, or roll back a previous migration.'); ?>
                    </p>
                </div>
            </div>
            <div style="padding: var(--tr-space-6);">

                <?php if ($migration_done): ?>
                    <!-- Already migrated: one quiet line, and re-running is a small
                         secondary action instead of the card's headline. -->
                    <p class="tr-migration-status">
                        <span class="tr-migration-status__dot" aria-hidden="true"></span>
                        <span class="tr-migration-status__text">
                            <?php
                            if ($migration_source && $backup_count > 0) {
                                echo esc_html(sprintf(
                                    tamrank__('migration_done_source_count', 'Migrated from %1$s on %2$s - %3$d items transferred.'),
                                    $migration_source,
                                    $migration_date,
                                    (int) $backup_count
                                ));
                            } elseif ($migration_source) {
                                echo esc_html(sprintf(
                                    tamrank__('migration_done_source', 'Migrated from %1$s on %2$s.'),
                                    $migration_source,
                                    $migration_date
                                ));
                            } elseif ($backup_count > 0) {
                                echo esc_html(sprintf(
                                    tamrank__('migration_done_count', 'Migration completed on %1$s - %2$d items transferred.'),
                                    $migration_date,
                                    (int) $backup_count
                                ));
                            } else {
                                echo esc_html(sprintf(
                                    tamrank__('migration_done', 'Migration completed on %s.'),
                                    $migration_date
                                ));
                            }
                            ?>
                        </span>
                    </p>
                    <div style="margin-top: var(--tr-space-4);">
                        <a href="<?php echo esc_url(admin_url('admin.php?page=tamrank-onboarding')); ?>"
                            class="tr-btn tr-btn--secondary tr-btn--sm">
                            <?php tamrank_e('migrate_again', 'Migrate again'); ?>
                        </a>
                    </div>

                <?php elseif ($detection['plugin']): ?>
                    <!-- Plugin detected -->
                    <div
                        style="display: flex; align-items: center; gap: var(--tr-space-3); padding: var(--tr-space-4); background: var(--tr-color-primary-light); border-radius: var(--tr-radius-md); margin-bottom: var(--tr-space-5);">
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--tr-color-primary)"
                            stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
                            <polyline points="22 4 12 14.01 9 11.01" />
                        </svg>
                        <span style="font-size: var(--tr-text-sm); color: var(--tr-color-slate-700);">
                            <?php echo wp_kses_post(sprintf(
                                tamrank__('migration_plugin_found', '%s found with %d items containing SEO data.'),
                                '<strong>' . esc_html($detection['plugin_name']) . '</strong>',
                                (int) $detection['data_count']
                            )); ?>
                        </span>
                    </div>

                    <?php if ($detection['data_count'] > 0): ?>
                        <p style="font-size: var(--tr-text-sm); color: var(--tr-color-slate-600); margin: 0 0 var(--tr-space-4) 0;">
                            <?php tamrank_e('migration_wizard_desc', 'Start the migration wizard to safely transfer your SEO data to TamRank. Existing TamRank data will not be overwritten.'); ?>
                        </p>
                        <div style="display: flex; align-items: center; gap: var(--tr-space-3); flex-wrap: wrap;">
                            <a href="<?php echo esc_url(admin_url('admin.php?page=tamrank-onboarding')); ?>"
                                class="tr-btn tr-btn--primary">
                                <?php tamrank_e('start_migration_wizard', 'Start Migration Wizard'); ?>
                            </a>
                            <a href="<?php echo esc_url(admin_url('admin.php?page=tamrank-onboarding&restart=1')); ?>"
                                style="font-size: var(--tr-text-sm); color: var(--tr-color-slate-500); text-decoration: none;">
                                <?php tamrank_e('restart_wizard', 'Restart wizard'); ?>
                            </a>
                        </div>
                    <?php else: ?>
                        <p style="font-size: var(--tr-text-sm); color: var(--tr-color-slate-600); margin: 0 0 var(--tr-space-4) 0;">
                            <?php tamrank_e('no_migration_data', 'No SEO data found to migrate. You can add a meta title and description per page in the TamRank SEO box in the editor. The SEO Audit shows which pages still need one.'); ?>
                        </p>
                        <a href="<?php echo esc_url(admin_url('admin.php?page=tamrank-onboarding&restart=1')); ?>"
                            class="tr-btn tr-btn--secondary">
                            <?php tamrank_e('restart_wizard', 'Restart wizard'); ?>
                        </a>
                    <?php endif; ?>

                <?php else: ?>
                    <!-- No plugin detected -->
                    <div
                        style="display: flex; align-items: center; gap: var(--tr-space-3); padding: var(--tr-space-4); background: var(--tr-color-info-bg); border-radius: var(--tr-radius-md); margin-bottom: var(--tr-space-5);">
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--tr-color-info)" stroke-width="2"
                            stroke-linecap="round" stroke-linejoin="round">
                            <circle cx="12" cy="12" r="10" />
                            <line x1="12" y1="16" x2="12" y2="12" />
                            <line x1="12" y1="8" x2="12.01" y2="8" />
                        </svg>
                        <span style="font-size: var(--tr-text-sm); color: var(--tr-color-slate-700);">
                            <?php tamrank_e('no_migration_plugin', 'No Yoast SEO, Rank Math or All in One SEO found. Install or activate one of these plugins to migrate data, or use the wizard for a clean start.'); ?>
                        </span>
                    </div>
                    <div style="display: flex; align-items: center; gap: var(--tr-space-3); flex-wrap: wrap;">
                        <a href="<?php echo esc_url(admin_url('admin.php?page=tamrank-onboarding')); ?>"
                            class="tr-btn tr-btn--primary">
                            <?php tamrank_e('open_setup_wizard', 'Open Setup Wizard'); ?>
                        </a>
                        <?php if ($onboarding_status === 'completed'): ?>
                            <a href="<?php echo esc_url(admin_url('admin.php?page=tamrank-onboarding&restart=1')); ?>"
                                style="font-size: var(--tr-text-sm); color: var(--tr-color-slate-500); text-decoration: none;">
                                <?php tamrank_e('restart_wizard', 'Restart wizard'); ?>
                            </a>
                        <?php endif; ?>
                    </div>
                <?php endif; ?>

            </div>
        </div>

        <?php if ($has_backup): ?>
            <!-- Rollback -->
            <div class="tr-card" style="margin-bottom: var(--tr-space-6);">
                <div class="tr-card-header" style="padding: var(--tr-space-6); border-bottom: 1px solid var(--tr-color-surface);">
                    <div>
                        <h2
                            style="font-size: var(--tr-text-lg); font-weight: var(--tr-font-semibold); color: var(--tr-color-slate-900); margin: 0 0 var(--tr-space-1) 0;">
                            <?php tamrank_e('rollback_migration', 'Rollback Migration'); ?>
                        </h2>
                        <p style="font-size: var(--tr-text-sm); color: var(--tr-color-slate-500); margin: 0;">
                            <?php
                            // Three variants, because a migration can have touched posts
                            // only, settings only, or both - and the settings half names
                            // only what is really in the backup, never both when the
                            // migration captured one of the two.
                            if ($backup_count > 0 && $has_option_backup) {
                                if ($has_robots_backup && $has_sitemap_backup) {
                                    $rollback_copy = tamrank__('rollback_backup_count_both', 'There are %d items and your robots.txt / sitemap settings with backup data. You can fully reverse the migration.');
                                } elseif ($has_robots_backup) {
                                    $rollback_copy = tamrank__('rollback_backup_count_both_robots', 'There are %d items and your robots.txt with backup data. You can fully reverse the migration.');
                                } else {
                                    $rollback_copy = tamrank__('rollback_backup_count_both_sitemap', 'There are %d items and your sitemap settings with backup data. You can fully reverse the migration.');
                                }
                                echo esc_html(sprintf($rollback_copy, (int) $backup_count));
                            } elseif ($backup_count > 0) {
                                echo esc_html(sprintf(
                                    tamrank__('rollback_backup_count', 'There are %d items with backup data. You can fully reverse the migration.'),
                                    (int) $backup_count
                                ));
                            } elseif ($has_robots_backup && $has_sitemap_backup) {
                                echo esc_html(tamrank__('rollback_backup_count_settings', 'Your robots.txt and sitemap settings from before the migration have been saved. You can fully reverse the migration.'));
                            } elseif ($has_robots_backup) {
                                echo esc_html(tamrank__('rollback_backup_count_settings_robots', 'Your robots.txt from before the migration has been saved. You can fully reverse the migration.'));
                            } else {
                                echo esc_html(tamrank__('rollback_backup_count_settings_sitemap', 'Your sitemap settings from before the migration have been saved. You can fully reverse the migration.'));
                            }
                            ?>
                        </p>
                    </div>
                </div>
                <div style="padding: var(--tr-space-6);">
                    <div id="tamrank-settings-rollback-progress" style="display:none; margin-bottom: var(--tr-space-4);">
                        <div
                            style="width: 100%; height: 8px; background: var(--tr-color-surface); border-radius: var(--tr-radius-full); overflow: hidden;">
                            <div id="tamrank-settings-rollback-fill"
                                style="height: 100%; background: var(--tr-color-primary); border-radius: var(--tr-radius-full); transition: width 300ms ease; width: 0%">
                            </div>
                        </div>
                        <p id="tamrank-settings-rollback-status"
                            style="font-size: var(--tr-text-xs); color: var(--tr-color-slate-500); margin: var(--tr-space-2) 0 0;">
                        </p>
                    </div>
                    <div id="tamrank-settings-rollback-done"
                        style="display:none; padding: var(--tr-space-4); background: var(--tr-color-success-bg); border-radius: var(--tr-radius-md); margin-bottom: var(--tr-space-4);">
                        <span
                            style="font-size: var(--tr-text-sm); color: #047857;"><?php tamrank_e('rollback_complete', 'Rollback complete! All migrated data has been restored.'); ?></span>
                    </div>
                    <button type="button" class="tr-btn tr-btn--destructive" id="tamrank-settings-rollback-btn">
                        <?php tamrank_e('rollback_migration', 'Rollback Migration'); ?>
                    </button>
                </div>
            </div>

            <?php ob_start(); ?>
                (function () {
                    var btn = document.getElementById('tamrank-settings-rollback-btn');
                    var progressWrap = document.getElementById('tamrank-settings-rollback-progress');
                    var fill = document.getElementById('tamrank-settings-rollback-fill');
                    var status = document.getElementById('tamrank-settings-rollback-status');
                    var doneBox = document.getElementById('tamrank-settings-rollback-done');
                    // With zero post-meta backups the progress bar would hang on 0/0.
                    // The option restore is then the single unit of work.
                    var total = <?php echo (int) max(1, $backup_count + $option_backup_count); ?>;
                    var settingsUnits = <?php echo (int) $option_backup_count; ?>;

                    if (!btn) return;

                    btn.addEventListener('click', function () {
                        window.TamRank.confirm({
                            title: <?php echo wp_json_encode(tamrank__('rollback_title', 'Roll back migration?'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>,
                            message: <?php echo wp_json_encode(tamrank__('rollback_confirm', 'All migrated data will be restored to the original state.'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>,
                            confirmLabel: <?php echo wp_json_encode(tamrank__('rollback', 'Roll back'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>,
                            variant: 'danger'
                        }).then(function (ok) {
                            if (!ok) return;
                            btn.disabled = true;
                            btn.textContent = <?php echo wp_json_encode(tamrank__('processing', 'Processing...'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>;
                            progressWrap.style.display = 'block';
                            // The option restore runs inside the first batch; count it
                            // up front so the bar never sits at 0 / 0.
                            runBatch(0, settingsUnits);
                        });
                    });

                    function runBatch(offset, done) {
                        var fd = new FormData();
                        fd.append('action', 'tamrank_onboarding_rollback');
                        fd.append('nonce', <?php echo wp_json_encode(wp_create_nonce('tamrank_onboarding'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>);
                        fd.append('offset', offset);

                        var xhr = new XMLHttpRequest();
                        xhr.open('POST', <?php echo wp_json_encode(admin_url('admin-ajax.php'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>);
                        xhr.onload = function () {
                            try {
                                var r = JSON.parse(xhr.responseText);
                                if (r.success && r.data) {
                                    done += r.data.processed;
                                    var pct = Math.min(Math.round((done / total) * 100), 100);
                                    fill.style.width = pct + '%';
                                    status.textContent = done + ' / ' + total + ' items';

                                    if (r.data.done) {
                                        progressWrap.style.display = 'none';
                                        doneBox.style.display = 'block';
                                        btn.style.display = 'none';
                                    } else {
                                        runBatch(r.data.next_offset, done);
                                    }
                                }
                            } catch (e) {
                                status.textContent = '<?php echo esc_js(tamrank__('rescore_error_occurred', 'An error occurred')); ?>';
                                btn.disabled = false;
                            }
                        };
                        xhr.onerror = function () {
                            status.textContent = '<?php echo esc_js(tamrank__('rescore_network_error', 'Network error')); ?>';
                            btn.disabled = false;
                        };
                        xhr.send(fd);
                    }
                })();
            <?php wp_add_inline_script('tam-rank-admin', ob_get_clean()); ?>
        <?php endif; ?>

        <!-- Change History card — rendered by PRO (cluster 14 strip) -->
        <?php
        /**
         * "Clear change history" card + its AJAX driver. PRO renders it (gated on
         * change_tracking); the tamrank_clear_change_log AJAX handler lives in the
         * PRO change tracker. FREE-only installs render nothing.
         *
         * @since 1.2.0
         */
        do_action('tamrank_settings_change_history_card');
        ?>

        <?php
        // Privacy & Data — data lifecycle controls (uninstall behavior)
        $general_settings_ie = tam_rank_cached_option('tam_rank_general_settings', array());
        ?>
        <div class="tr-card" id="tr-privacy-data">
            <div class="tr-card-header">
                <div>
                    <h2 class="tr-card-title"><?php tamrank_e('privacy_data', 'Privacy & Data'); ?></h2>
                    <p class="tr-card-subtitle">
                        <?php tamrank_e('privacy_data_desc', 'Control what happens to your data when TamRank is deleted.'); ?>
                    </p>
                </div>
            </div>
            <div class="tr-card-body">
                <div class="tr-toggle-row">
                    <div class="tr-toggle-row__info">
                        <p class="tr-toggle-row__label"><?php tamrank_e('delete_all_data_on_uninstall_label', 'Delete all data on uninstall'); ?></p>
                        <p class="tr-toggle-row__desc">
                            <?php tamrank_e('delete_all_data_on_uninstall_desc', 'When you delete TamRank, also drop all stored data: settings, audit scores, focus keywords, schema configs, redirect rules, change logs, topical maps and slug synonyms. Default: off — when this stays off, deleting the plugin keeps everything so a reinstall loses no data.'); ?>
                        </p>
                    </div>
                    <label class="tr-toggle-switch">
                        <input type="hidden" name="tam_rank_general_settings[delete_all_data_on_uninstall]" value="0">
                        <input type="checkbox" id="delete_all_data_on_uninstall"
                            name="tam_rank_general_settings[delete_all_data_on_uninstall]" value="1" <?php checked( ! empty( $general_settings_ie['delete_all_data_on_uninstall'] ) ); ?>>
                        <span class="tr-toggle-track"></span>
                    </label>
                </div>
            </div>
        </div>
        <?php
    }

    private static function render_footer()
    {
        tam_rank_render_footer();
    }

    /**
     * Render license tab voor FREE gebruikers
     * Tone: positief, rustig, uitnodigend — geen push/lock-iconen
     */
    private function render_license_tab_free()
    {
        $check_svg = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--tr-color-primary)" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>';
        ?>
        <div class="tr-license-free">

            <!-- Status -->
            <div class="tr-card">
                <div class="tr-card-body">
                    <div class="tr-license-free__header">
                        <img src="<?php echo esc_url(TAM_RANK_PLUGIN_URL . 'assets/img/tamrank-icon-dark.svg'); ?>" alt=""
                            width="40" height="40">
                        <div>
                            <h2 class="tr-card-title"><?php tamrank_e('using_tamrank_free', 'You are using TamRank Free'); ?></h2>
                            <p class="tr-card-subtitle">
                                <?php tamrank_e('free_plan_desc', 'SEO analysis, schema markup and more — free.'); ?></p>
                        </div>
                        <span class="tr-badge tr-badge--free">Free</span>
                    </div>
                </div>
            </div>

            <!-- Twee kolommen: Free + PRO -->
            <div class="tr-license-free__columns">

                <!-- Wat je al hebt -->
                <div class="tr-card">
                    <div class="tr-card-header">
                        <h2 class="tr-card-title"><?php tamrank_e('included_in_free', 'Included in Free'); ?></h2>
                    </div>
                    <div class="tr-card-body">
                        <ul class="tr-license-free__features">
                            <li><?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?><span><?php tamrank_e('free_feature_meta_score', 'Meta score analysis'); ?></span>
                            </li>
                            <li><?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?><span><?php tamrank_e('free_feature_schema', 'Schema markup generator'); ?></span>
                            </li>
                            <li><?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?><span><?php tamrank_e('free_feature_seo_audit', 'SEO Audit'); ?></span>
                            </li>
                            <li><?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?><span><?php tamrank_e('free_feature_404', '404 Monitor'); ?></span>
                            </li>
                            <li><?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?><span><?php tamrank_e('free_feature_redirects', 'Redirects'); ?></span>
                            </li>
                            <li><?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?><span><?php tamrank_e('free_feature_robots', 'Robots.txt editor'); ?></span>
                            </li>
                        </ul>
                    </div>
                </div>

                <!-- PRO voordelen -->
                <div class="tr-card">
                    <div class="tr-card-header">
                        <h2 class="tr-card-title"><?php tamrank_e('get_more_from_tamrank', 'Get more from TamRank'); ?></h2>
                    </div>
                    <div class="tr-card-body">
                        <ul class="tr-license-free__features tr-license-free__features--pro">
                            <li>
                                <?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?>
                                <span><strong><?php tamrank_e('pro_ai_meta_title', 'AI Meta Generator'); ?></strong> —
                                    <?php tamrank_e('pro_ai_meta_desc', 'Automatically generate SEO titles and descriptions'); ?></span>
                            </li>
                            <li>
                                <?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?>
                                <span><strong><?php tamrank_e('pro_gsc_title', 'Search Console Insights'); ?></strong> —
                                    <?php tamrank_e('pro_gsc_desc', 'Keywords, positions and clicks per page'); ?></span>
                            </li>
                            <li>
                                <?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?>
                                <span><strong><?php tamrank_e('pro_pagespeed_title', 'PageSpeed Monitor'); ?></strong> —
                                    <?php tamrank_e('pro_pagespeed_desc', 'Track your load times and Core Web Vitals'); ?></span>
                            </li>
                            <li>
                                <?php echo wp_kses($check_svg, tam_rank_kses_svg()); ?>
                                <span><strong><?php tamrank_e('pro_mcp_title', 'AI Agent (MCP)'); ?></strong> —
                                    <?php tamrank_e('pro_mcp_desc', 'Let Claude, Cursor and ChatGPT run SEO actions for you'); ?></span>
                            </li>
                        </ul>

                        <p class="tr-license-free__more">
                            <?php tamrank_e('pro_more_features', '+ 8 more features including Topical Authority and priority support'); ?>
                        </p>

                        <div class="tr-license-free__cta">
                            <a href="<?php echo esc_url(tam_rank_get_upgrade_url('settings', 'license_tab')); ?>"
                                class="tr-btn tr-btn--primary tr-btn--lg" target="_blank" rel="noopener">
                                <?php tamrank_e('upgrade_to_pro', 'Upgrade to PRO'); ?>
                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                    stroke-width="2">
                                    <path d="M5 12h14M12 5l7 7-7 7" />
                                </svg>
                            </a>
                            <p class="tr-license-free__benefits">
                                <?php tamrank_e('benefit_instant', 'Immediately active'); ?> ·
                                <?php tamrank_e('auto_connect_short', 'No reconfiguration needed'); ?>
                            </p>
                        </div>

                        <div class="tr-separator"></div>

                        <?php
                        // Activation lives in the PRO add-on. On a FREE-only install
                        // there is nothing to activate yet, so instead of a dead
                        // "Activate here" link (which used to point at the license
                        // form that has since moved to PRO) we explain the three
                        // steps: buy, install the add-on, then activate. render_license_tab_free()
                        // only renders for non-active states, so this guidance always applies here.
                        ?>
                        <div class="tr-license-free__howto">
                            <p class="tr-license-free__howto-title">
                                <?php tamrank_e('pro_activate_howto_title', 'How does PRO activation work?'); ?>
                            </p>
                            <ol class="tr-license-free__howto-steps">
                                <li><?php tamrank_e('pro_activate_step1', 'Choose a PRO plan and complete checkout.'); ?></li>
                                <li><?php tamrank_e('pro_activate_step2', 'Install the PRO add-on you receive by email.'); ?></li>
                                <li><?php tamrank_e('pro_activate_step3', 'Enter your license key to unlock all PRO features instantly.'); ?></li>
                            </ol>
                        </div>
                    </div>
                </div>

            </div>

        </div>
        <?php
    }

}
