<?php
/**
 * Tam Rank™ Dashboard Page - Focus Flow Design
 */

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

class TamRank_Dashboard
{

    public function __construct()
    {
        // Dashboard will be called from settings-page.php
    }

    /**
     * Render dashboard page
     */
    public static function render_dashboard()
    {
        $dashboard_data = self::get_dashboard_data();
        $last_update = get_option('tam_rank_last_scan', false);
        $focus_actions = self::get_focus_actions($dashboard_data);

        // Compute grouped tasks once for header chip + tasks section
        $task_data = self::get_grouped_tasks($dashboard_data);

        // Priority Engine (impact-ranked top-3 cards) moved to tam-rank-pro
        // (cluster-10 strip). PRO answers this filter with the ranked card data
        // when the priority_actions feature is licensed; FREE-only returns the
        // empty default and the render below degrades to the plain task list.
        $priority_data = apply_filters('tamrank_priority_data', array(), $task_data);
        ?>
        <div class="tam-focus-dashboard">
            <?php self::render_styles(); ?>

            <!-- Nieuwe Dashboard Header Widget -->
            <div class="tam-header-widget">
                <div class="tam-header-top">
                    <div class="tam-header-score-section">
                        <div class="tam-header-score-circle-wrapper">
                            <svg class="tam-header-score-circle" viewBox="0 0 120 120">
                                <circle class="tam-score-bg" cx="60" cy="60" r="54" fill="none"
                                    stroke="var(--tr-color-slate-200)" stroke-width="12" />
                                <circle class="tam-score-fill" cx="60" cy="60" r="54" fill="none"
                                    stroke="<?php echo esc_attr(self::get_score_color($dashboard_data['avg_score'])); ?>"
                                    stroke-width="12" stroke-linecap="round" stroke-dasharray="339.292"
                                    stroke-dashoffset="<?php echo esc_attr(339.292 - (339.292 * $dashboard_data['avg_score'] / 100)); ?>"
                                    transform="rotate(-90 60 60)" />
                            </svg>
                            <div class="tam-header-score-value">
                                <span class="tam-header-score-number"><?php echo esc_html($dashboard_data['avg_score']); ?></span>
                            </div>
                        </div>
                        <span class="tam-header-score-label"><?php tamrank_e('seo_score', 'SEO-score'); ?></span>
                    </div>

                    <div class="tam-header-stats-section">
                        <?php
                        // Aantal *daadwerkelijk geanalyseerde* pagina's uit dezelfde
                        // cached stats die de SEO Audit pagina gebruikt, zodat beide
                        // UI's nooit uit elkaar lopen. Fallback op 0 als er nog geen
                        // bulk audit heeft gedraaid.
                        $audit_cache = get_option('tam_rank_audit_overall_stats', array());
                        $total_pages_analyzed = isset($audit_cache['stats']['audited_pages'])
                            ? (int) $audit_cache['stats']['audited_pages']
                            : 0;

                        // Tel openstaande taken via grouped task data
                        $open_tasks_count = 0;
                        foreach ($task_data['grouped'] as $_grp) {
                            $open_tasks_count += $_grp['total_count'] - $_grp['completed_count'];
                        }
                        foreach ($task_data['singletons'] as $_sng) {
                            if (!$_sng['completed']) $open_tasks_count++;
                        }
                        ?>
                        <div class="tam-header-chips-row">
                            <span class="tam-header-chip"><?php echo esc_html(sprintf(tamrank__('pages_analyzed', '%d pages analyzed'), $total_pages_analyzed)); ?></span>
                            <span class="tam-header-chip" data-chip="open-tasks"><?php echo esc_html(sprintf(tamrank__('open_tasks_count', '%d open tasks'), $open_tasks_count)); ?></span>
                        </div>
                        <div class="tam-header-chips-row">
                            <?php
                            // tam_rank_format_date() geeft sinds de guard '' terug bij een datum
                            // die niet te lezen is, in plaats van stilletjes de huidige tijd. Die
                            // lege uitkomst valt hier op dezelfde zin terug als een ontbrekende
                            // scan: we weten het niet, en dat is hetzelfde verhaal voor de lezer.
                            $last_scan_label = $last_update ? tam_rank_format_date($last_update) : '';
                            if ('' === $last_scan_label) {
                                $last_scan_label = tamrank__('no_scan_yet', 'No scan performed yet');
                            }
                            ?>
                            <span class="tam-header-chip"><?php echo esc_html(tamrank__('last_scan', 'Last scan')); ?>: <?php echo esc_html($last_scan_label); ?></span>
                        </div>
                    </div>

                    <div class="tam-header-action-section">
                        <a class="tam-btn-scan-refresh" href="<?php echo esc_url(admin_url('admin.php?page=tam-rank-audit')); ?>">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M21 2v6h-6" />
                                <path d="M3 12a9 9 0 0 1 15-6.7L21 8" />
                                <path d="M3 22v-6h6" />
                                <path d="M21 12a9 9 0 0 1-15 6.7L3 16" />
                            </svg>
                            <?php tamrank_e('rescan', 'Run scan again'); ?>
                        </a>
                    </div>
                </div>

                <div class="tam-header-bottom">
                    <?php if (!tam_rank_can_use_feature('gsc_dashboard_basic')): ?>
                        <div class="tam-header-gsc-metrics">
                            <span style="color: var(--tr-color-slate-500); font-size: 13px;"><?php tamrank_e('gsc_pro_upsell', 'Search Console insights are available with PRO.'); ?></span>
                        </div>
                        <a href="<?php echo esc_url(tam_rank_get_upgrade_url('dashboard', 'gsc_widget')); ?>" class="tam-header-gsc-link" target="_blank" rel="noopener"><?php tamrank_e('discover_pro', 'Discover PRO'); ?> →</a>
                    <?php else:
                        $gsc_connected = class_exists('TamRank_GSC_Connector') && TamRank_GSC_Connector::is_connected() && !empty(TamRank_GSC_Connector::get_property());
                        if ($gsc_connected):
                            $gsc = TamRank_GSC_Connector::get_instance();
                            $summary = $gsc->get_cached_summary();
                            $has_error = $summary['error'] ?? false;
                            if (!$has_error):
                                ?>
                                <div class="tam-header-gsc-metrics">
                                    <div class="tam-header-gsc-metric">
                                        <strong><?php echo esc_html(number_format($summary['clicks'])); ?></strong> clicks
                                        <?php if (isset($summary['clicks_change'])): ?>
                                            <span
                                                class="tam-gsc-trend <?php echo $summary['clicks_change'] >= 0 ? 'tam-trend-up' : 'tam-trend-down'; ?>">
                                                <?php echo $summary['clicks_change'] >= 0 ? '↑' : '↓'; ?>
                                                <?php echo esc_html(abs($summary['clicks_change'])); ?>%
                                            </span>
                                        <?php endif; ?>
                                    </div>
                                    <div class="tam-header-gsc-separator"></div>

                                    <div class="tam-header-gsc-metric">
                                        <strong><?php echo esc_html(number_format($summary['impressions'] / 1000, 1)); ?>K</strong> <?php tamrank_e('impressions', 'impressions'); ?>
                                        <?php if (isset($summary['impressions_change'])): ?>
                                            <span
                                                class="tam-gsc-trend <?php echo $summary['impressions_change'] >= 0 ? 'tam-trend-up' : 'tam-trend-down'; ?>">
                                                <?php echo $summary['impressions_change'] >= 0 ? '↑' : '↓'; ?>
                                                <?php echo esc_html(abs($summary['impressions_change'])); ?>%
                                            </span>
                                        <?php endif; ?>
                                    </div>
                                    <div class="tam-header-gsc-separator"></div>

                                    <div class="tam-header-gsc-metric">
                                        <span class="tam-gsc-label-text"><?php tamrank_e('position', 'Position'); ?></span>
                                        <strong><?php echo esc_html(number_format($summary['position'], 1)); ?></strong>
                                        <?php if (isset($summary['position_change'])): ?>
                                            <span
                                                class="tam-gsc-trend <?php echo $summary['position_change'] <= 0 ? 'tam-trend-up' : 'tam-trend-down'; ?>">
                                                <?php echo $summary['position_change'] <= 0 ? '↑' : '↓'; ?>
                                                <?php echo esc_html(abs($summary['position_change'])); ?>
                                            </span>
                                        <?php endif; ?>
                                    </div>
                                </div>
                                <a href="#gsc-performance" class="tam-header-gsc-link">Search Console →</a>
                            <?php else: ?>
                                <div class="tam-header-gsc-metrics">
                                    <span style="color: var(--tr-color-slate-500); font-size: 13px;"><?php tamrank_e('gsc_data_error', 'GSC data could not be loaded.'); ?></span>
                                </div>
                            <?php endif; else: ?>
                            <div class="tam-header-gsc-metrics">
                                <span style="color: var(--tr-color-slate-500); font-size: 13px;"><?php tamrank_e('gsc_connect_prompt', 'Connect Google Search Console for live results.'); ?></span>
                            </div>
                            <a href="<?php echo esc_url(admin_url('admin.php?page=tam-rank-settings')); ?>" class="tam-header-gsc-link"><?php tamrank_e('gsc_connect_link', 'Connect'); ?> →</a>
                        <?php endif;
                    endif; ?>
                </div>

            </div>



            <?php self::render_seo_tasks_section($task_data, $priority_data); ?>

            <?php self::render_gsc_section(); ?>

            <?php self::render_add_task_modal(); ?>

            <?php self::render_footer(); ?>

            <?php self::render_scripts(); ?>
        </div>
        <?php
    }

    /**
     * Get score color based on value
     */
    private static function get_score_color($score)
    {
        // Hergebruik exact dezelfde drempels als get_score_color_for_category(),
        // zodat de gauge nooit een andere kleur toont dan de rest van de UI.
        // (Was een afwijkende 3-tier schaal die de 'good'-tier oversloeg en
        // 60-79 oranje kleurde i.p.v. teal — terwijl de audit-pagina 60-79 als
        // groen/'Good' toont. De status-CSS-variabelen heten precies gelijk aan
        // de teruggegeven klassenamen, dus dit mapt 1-op-1.)
        return 'var(--tr-color-status-' . self::get_score_color_for_category($score) . ')';
    }

    /**
     * Get focus actions based on dashboard data
     */
    private static function get_focus_actions($data)
    {
        $actions = array();

        // Missing meta descriptions - High priority (homepage first)
        // Missing meta titles - Highest priority
        if ($data['missing_titles'] > 0) {
            $actions[] = array(
                'title' => sprintf(tamrank__('optimize_pages_no_title', 'Optimize %d pages without meta title'), $data['missing_titles']),
                'description' => tamrank__('optimize_pages_no_title_desc', 'Meta titles are crucial for SEO. Pages without a title rank up to 40% worse in search results.'),
                'count' => $data['missing_titles'],
                'url' => admin_url('admin.php?page=tam-rank-audit&tab=missing-titles'),
                'priority' => 'high',
                'points' => 20 * (int) $data['missing_titles'],
                'time' => 5,
            );
        }

        // Missing meta descriptions - High priority (but lower than title)
        // Check specifically for homepage first
        if (!empty($data['homepage_missing_description'])) {
            $actions[] = array(
                'title' => tamrank__('add_meta_desc_homepage', 'Add meta description to Homepage'),
                'description' => tamrank__('add_meta_desc_homepage_desc', 'Your homepage is missing a meta description. This can increase your click-through rate by an average of 5.8%.'),
                'count' => 1,
                'url' => admin_url('post.php?post=' . get_option('page_on_front') . '&action=edit'),
                'priority' => 'high',
                'points' => 10,
                'time' => 2,
            );
        } elseif ($data['missing_descriptions'] > 0) {
            // Generic missing descriptions
            $actions[] = array(
                'title' => sprintf(tamrank__('add_meta_desc_pages', 'Add meta description to %d pages'), $data['missing_descriptions']),
                'description' => tamrank__('add_meta_desc_pages_desc', 'Meta descriptions are crucial for CTR. Add them to improve your organic traffic.'),
                'count' => $data['missing_descriptions'],
                'url' => admin_url('admin.php?page=tam-rank-audit&tab=missing-descriptions'),
                'priority' => 'high',
                'points' => 10 * (int) $data['missing_descriptions'],
                'time' => 5 * min($data['missing_descriptions'], 5), // Est. time matches action count capped at 5
            );
        }

        // Orphan pages surface via the "Internal links" health card + the
        // audit orphans tab only. Deliberately NO SEO-task here: the grouped
        // task "Add internal links to %d page(s)" (grp_no_internal_links,
        // outgoing-links check) already covers internal linking in the task
        // list, and a second near-identical task reads as duplication.

        // Duplicate H1s - Medium priority
        if ($data['duplicate_h1s'] > 0) {
            $actions[] = array(
                'title' => sprintf(tamrank__('improve_h1_structure', 'Improve H1 structure on %d pages'), $data['duplicate_h1s']),
                'description' => tamrank__('improve_h1_structure_desc', 'Each page should have exactly one H1 tag. Duplicate H1s confuse search engines.'),
                'count' => $data['duplicate_h1s'],
                'url' => admin_url('admin.php?page=tam-rank-audit'),
                'priority' => 'medium',
                'points' => 5 * (int) $data['duplicate_h1s'],
                'time' => 10,
            );
        }

        // Slow pages - Low priority
        if ($data['slow_pages'] > 0) {
            $actions[] = array(
                'title' => sprintf(tamrank__('optimize_slow_pages', 'Optimize %d slow pages'), $data['slow_pages']),
                'description' => tamrank__('optimize_slow_pages_desc', 'These pages load slower than 3 seconds. This hurts your SEO and conversion.'),
                'count' => $data['slow_pages'],
                'url' => admin_url('admin.php?page=tam-rank-pagespeed'),
                'priority' => 'low',
                'points' => 20,
                'time' => 20,
            );
        }

        // Add fallback actions if we have less than 3
        $fallback_actions = array(
            array(
                'title' => tamrank__('check_internal_links', 'Check your internal links'),
                'description' => tamrank__('check_internal_links_desc', 'Strong internal linking improves your site structure and helps search engines better understand your content.'),
                'count' => 0,
                'url' => admin_url('admin.php?page=tam-rank-audit'),
                'priority' => 'low',
                'points' => 5,
                'time' => 15,
            ),
            array(
                'title' => tamrank__('add_alt_texts', 'Add alt texts to images'),
                'description' => tamrank__('add_alt_texts_desc', 'Alt texts improve accessibility and help with SEO by providing context to your images.'),
                'count' => 0,
                'url' => admin_url('upload.php'),
                'priority' => 'low',
                'points' => 5,
                'time' => 10,
            ),
            array(
                'title' => tamrank__('optimize_sitemap', 'Optimize your sitemap'),
                'description' => tamrank__('optimize_sitemap_desc', 'A well-configured XML sitemap helps search engines find and index your pages faster.'),
                'count' => 0,
                'url' => admin_url('admin.php?page=tam-rank-settings'),
                'priority' => 'low',
                'points' => 5,
                'time' => 5,
            )
        );

        // Add fallback actions until we have at least 3
        $fallback_index = 0;
        while (count($actions) < 3 && $fallback_index < count($fallback_actions)) {
            $actions[] = $fallback_actions[$fallback_index];
            $fallback_index++;
        }

        return $actions;
    }

    /**
     * Get CSS score class for category score
     */
    private static function get_score_color_for_category($score)
    {
        if ($score >= 80)
            return 'excellent';
        if ($score >= 60)
            return 'good';
        if ($score >= 40)
            return 'attention';
        return 'improve';
    }

    /**
     * Get quick wins checklist items based on dashboard data
     * Returns dynamic items that can be checked off
     */
    private static function get_quick_wins_checklist($data)
    {
        global $wpdb;
        $wins = array();

        // 1. Meta title optimaliseren voor homepage
        $homepage_id = get_option('page_on_front');
        if ($homepage_id) {
            $homepage_title = get_post_meta($homepage_id, '_tam_rank_meta_title', true);
            if (empty($homepage_title)) {
                $wins[] = array(
                    'id' => 'meta_title_homepage',
                    'title' => tamrank__('add_meta_title_homepage', 'Add meta title to Homepage'),
                    'category' => 'META',
                    'time' => 2,
                    'points' => 20,
                    'url' => get_edit_post_link($homepage_id)
                );
            }
        }

        // 2. Focus zoekwoord toevoegen aan Homepage
        if ($homepage_id) {
            $homepage_keyword = get_post_meta($homepage_id, '_tam_rank_focus_keyword', true);
            if (empty($homepage_keyword)) {
                $wins[] = array(
                    'id' => 'focus_keyword_homepage',
                    'title' => tamrank__('add_focus_keyword_homepage', 'Add focus keyword to Homepage'),
                    'category' => 'META',
                    'time' => 1,
                    'points' => 5,
                    'url' => get_edit_post_link($homepage_id)
                );
            }
        }

        // 3. Alt-tekst toevoegen aan afbeeldingen zonder alt
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- direct query on core posts/postmeta tables; admin-context read, caching not applicable; only table identifiers are interpolated, no user values in this static query
        $images_without_alt = $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->posts} p
             LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_wp_attachment_image_alt'
             WHERE p.post_type = 'attachment'
             AND p.post_mime_type LIKE 'image/%'
             AND (pm.meta_value IS NULL OR pm.meta_value = '')"
        );
        if ($images_without_alt > 0) {
            $wins[] = array(
                'id' => 'alt_text_images',
                'title' => sprintf(tamrank__('add_alt_text_images', 'Add alt text to %d image(s)'), min($images_without_alt, 10)),
                'category' => tamrank__('category_image', 'IMAGE'),
                'time' => 1,
                'points' => 5,
                'url' => admin_url('upload.php?mode=list')
            );
        }

        // 4. Interne link toevoegen naar belangrijke pagina's
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- direct query on the core posts table; admin-context read, caching not applicable; only the table identifier is interpolated, no user values in this static query
        $pages_without_internal_links = $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->posts}
             WHERE post_type = 'page'
             AND post_status = 'publish'
             AND post_content NOT LIKE '%<a %href=%'"
        );
        if ($pages_without_internal_links > 0) {
            $wins[] = array(
                'id' => 'internal_links',
                'title' => tamrank__('add_internal_link_services', 'Add internal link to /services'),
                'category' => tamrank__('category_content', 'CONTENT'),
                'time' => 2,
                'points' => 5,
                'url' => admin_url('admin.php?page=tam-rank-audit')
            );
        }

        // 6. Meta description toevoegen aan pagina's zonder
        if ($data['missing_descriptions'] > 0) {
            // Build config-driven post type IN clause
            $qw_slugs = TamRank_Content_Type_Config::get_enabled_audit_slugs();
            if (empty($qw_slugs)) {
                $qw_slugs = array('post', 'page');
            }
            $qw_placeholders = implode(', ', array_fill(0, count($qw_slugs), '%s'));
            // Same exclusions as the audit/dashboard counters: don't suggest fixing a
            // noindex or audit-excluded page that won't show up in the audit anyway.
            $qw_excl = tam_rank_audit_exclusion_clauses('p');
            // Haal eerste pagina zonder description op
            // Direct query on core posts/postmeta tables; admin-context read, caching not applicable; only table identifiers, a runtime-built %s placeholder list and the $qw_excl clause (intval'd IDs) are interpolated; user values use prepare() placeholders.
            // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table/placeholder identifiers and the $qw_excl clause (built from intval'd IDs) are interpolated; user values bound via prepare()
            $page_without_desc = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT p.ID FROM {$wpdb->posts} p
                     LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tam_rank_meta_description'
                     WHERE p.post_type IN ({$qw_placeholders}) AND p.post_status = 'publish'
                     {$qw_excl}
                     AND (pm.meta_value IS NULL OR pm.meta_value = '')
                     ORDER BY p.post_date DESC LIMIT 1",
                    ...$qw_slugs
                )
            );
            // phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter
            if ($page_without_desc) {
                $page_title = get_the_title($page_without_desc);
                $wins[] = array(
                    'id' => 'meta_desc_' . $page_without_desc,
                    'title' => sprintf(tamrank__('add_meta_desc_to', 'Add meta description to "%s"'), wp_trim_words($page_title, 4, '...')),
                    'category' => 'META',
                    'time' => 2,
                    'points' => 10,
                    'url' => get_edit_post_link($page_without_desc)
                );
            }
        }

        // 7. 404 fouten oplossen
        if ($data['404_errors'] > 0) {
            $wins[] = array(
                'id' => 'fix_404_errors',
                'title' => sprintf(tamrank__('view_fix_broken_links', 'View and fix %d broken link(s)'), min($data['404_errors'], 5)),
                'category' => tamrank__('category_technical', 'TECHNICAL'),
                'time' => 3,
                'points' => 10,
                'url' => admin_url('admin.php?page=tam-rank-404')
            );
        }

        // 8. Schema markup controleren
        $schema_enabled = get_option('tam_rank_enable_schema', true);
        if (!$schema_enabled) {
            $wins[] = array(
                'id' => 'enable_schema',
                'title' => tamrank__('enable_schema_markup', 'Enable Schema markup'),
                'category' => tamrank__('category_technical', 'TECHNICAL'),
                'time' => 1,
                'points' => 10,
                'url' => admin_url('admin.php?page=tam-rank-settings&tab=schema')
            );
        }

        // 9. Sitemap controleren/genereren
        $sitemap_exists = file_exists(ABSPATH . 'sitemap.xml') || file_exists(ABSPATH . 'sitemap_index.xml');
        if (!$sitemap_exists) {
            $wins[] = array(
                'id' => 'check_sitemap',
                'title' => tamrank__('check_xml_sitemap', 'Check XML Sitemap'),
                'category' => tamrank__('category_technical', 'TECHNICAL'),
                'time' => 2,
                'points' => 10,
                'url' => admin_url('admin.php?page=tam-rank-settings&tab=sitemap')
            );
        }

        // 10. PageSpeed test uitvoeren (PRO only)
        $last_pagespeed_test = get_option('tam_rank_last_pagespeed_test', 0);
        $one_week_ago = time() - (7 * 24 * 60 * 60);
        if (tam_rank_can_use_feature('pagespeed_analysis') && $last_pagespeed_test < $one_week_ago) {
            $wins[] = array(
                'id' => 'run_pagespeed',
                'title' => tamrank__('run_pagespeed_test', 'Run PageSpeed test'),
                'category' => tamrank__('category_speed', 'SPEED'),
                'time' => 2,
                'points' => 10,
                'url' => admin_url('admin.php?page=tam-rank-pagespeed')
            );
        }

        // Limiteer tot max 12 items
        return array_slice($wins, 0, 12);
    }

    /**
     * Get dashboard data
     */
    private static function get_dashboard_data()
    {
        // Light cache to avoid heavy queries on each admin load
        $cache_key = 'tam_rank_dashboard_data';
        $cached = get_transient($cache_key);
        if ($cached && is_array($cached)) {
            return $cached;
        }

        global $wpdb;

        // Get basic stats
        $stats = tam_rank_get_statistics();

        // Average SEO score
        $avg_score = tam_rank_get_average_seo_score();

        // Indexed pages (not noindex)
        $indexed_pages = tam_rank_get_indexed_pages_count();

        // Build config-driven post type IN clause for this method
        $dash_slugs = TamRank_Content_Type_Config::get_enabled_audit_slugs();
        if (empty($dash_slugs)) {
            $dash_slugs = array('post', 'page');
        }
        $dash_placeholders = implode(', ', array_fill(0, count($dash_slugs), '%s'));

        // Exclusions mirror the SEO Audit page so the dashboard counts the same set:
        // skip noindex pages and audit-excluded posts. Queries below alias posts as `p`.
        $dash_excl = tam_rank_audit_exclusion_clauses('p');

        // Missing meta titles
        // Direct query on core posts/postmeta tables; admin-context read, caching not applicable; only table identifiers and a runtime-built %s placeholder list are interpolated; user values use prepare() placeholders.
        // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table/placeholder identifiers and the $dash_excl clause (built from intval'd IDs) are interpolated; user values bound via prepare()
        $missing_titles = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->posts} p
                 LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tam_rank_meta_title'
                 WHERE p.post_type IN ({$dash_placeholders}) AND p.post_status = 'publish'
                 {$dash_excl}
                 AND (pm.meta_value IS NULL OR pm.meta_value = '')",
                ...$dash_slugs
            )
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter

        // Missing meta descriptions
        // Direct query on core posts/postmeta tables; admin-context read, caching not applicable; only table identifiers and a runtime-built %s placeholder list are interpolated; user values use prepare() placeholders.
        // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table/placeholder identifiers and the $dash_excl clause (built from intval'd IDs) are interpolated; user values bound via prepare()
        $missing_descriptions = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->posts} p
                 LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tam_rank_meta_description'
                 WHERE p.post_type IN ({$dash_placeholders}) AND p.post_status = 'publish'
                 {$dash_excl}
                 AND (pm.meta_value IS NULL OR pm.meta_value = '')",
                ...$dash_slugs
            )
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter

        // Check specifically for homepage
        $homepage_missing_description = false;
        $front_page_id = get_option('page_on_front');
        if ($front_page_id) {
            $home_desc = get_post_meta($front_page_id, '_tam_rank_meta_description', true);
            if (empty($home_desc)) {
                $homepage_missing_description = true;
            }
        }

        // 404 errors
        $error_404s = tam_rank_get_404_count();

        // Calculate metrics
        $critical_errors = $missing_titles + $missing_descriptions;
        $warnings = $error_404s;
        $duplicate_h1s = 0; // Placeholder - would need content analysis
        $slow_pages = 0; // Placeholder - would need PageSpeed data
        $improvements = $duplicate_h1s + $slow_pages;

        // Afbeeldingen statistieken - haal uit SEO audit data indien beschikbaar
        $missing_alt = 0;
        $total_images = 0;
        // TODO: 'tam_rank_audit_data' wordt nergens geschreven (onafgemaakte
        // image-audit -> dashboard koppeling); deze read geeft nu altijd de lege
        // default. Bewust niet verwijderd: markeert een onaffe feature.
        $audit_data = get_option('tam_rank_audit_data', array());
        if (!empty($audit_data) && isset($audit_data['images'])) {
            $missing_alt = isset($audit_data['images']['missing_alt']) ? (int) $audit_data['images']['missing_alt'] : 0;
            $total_images = isset($audit_data['images']['total']) ? (int) $audit_data['images']['total'] : 0;
        }

        $data = array(
            'avg_score' => $avg_score,
            'indexed_pages' => $indexed_pages,
            'critical_errors' => $critical_errors,
            'warnings' => $warnings,
            'improvements' => $improvements,
            'missing_titles' => (int) $missing_titles,
            'missing_descriptions' => (int) $missing_descriptions,
            'duplicate_h1s' => $duplicate_h1s,
            'slow_pages' => $slow_pages,
            '404_errors' => $error_404s,
            'missing_alt' => $missing_alt,
            'total_images' => $total_images,
            'homepage_missing_description' => $homepage_missing_description,
        );

        // Cache for 10 minutes to keep admin responsive
        set_transient($cache_key, $data, MINUTE_IN_SECONDS * 10);
        return $data;
    }

    /**
     * Render styles
     */

    /**
     * Headless warm-up for the priority-actions cache.
     *
     * Runs the exact same chain as render_dashboard() (get_dashboard_data ->
     * get_grouped_tasks -> tamrank_priority_data filter), but without rendering
     * any markup. The PRO priority engine hangs on that filter and writes the
     * tamrank_priority_actions transient as a side-effect, so calling this from
     * a headless context (e.g. the MCP get_priority_actions endpoint on a cold
     * cache) warms the same cache a browser dashboard-view would. Returns the
     * filtered priority data so callers can use it directly if wanted.
     *
     * @return array Priority data (empty array on FREE-only / no PRO engine).
     */
    public static function compute_priority_actions()
    {
        $dashboard_data = self::get_dashboard_data();
        $task_data      = self::get_grouped_tasks($dashboard_data);
        return apply_filters('tamrank_priority_data', array(), $task_data);
    }

    /**
     * Build grouped + singleton task data for SEO tasks section.
     */
    private static function get_grouped_tasks($dashboard_data)
    {
        global $wpdb;
        $completed_ids = get_option('tam_rank_completed_quick_wins', array());

        // Build config-driven post type IN clause (reused across queries in this method)
        $gt_slugs = TamRank_Content_Type_Config::get_enabled_audit_slugs();
        if (empty($gt_slugs)) {
            $gt_slugs = array('post', 'page');
        }
        $gt_placeholders = implode(', ', array_fill(0, count($gt_slugs), '%s'));
        // Same exclusions as the audit/dashboard counters: skip noindex + audit-excluded
        // posts so these task lists match the headline counts that gate them.
        $gt_excl = tam_rank_audit_exclusion_clauses('p');

        $grouped = array();
        $singletons = array();

        // --- GROUPED: Missing meta titles ---
        if ($dashboard_data['missing_titles'] > 0) {
            // Direct query on core posts/postmeta tables; admin-context read, caching not applicable; only table identifiers, a runtime-built %s placeholder list and the $gt_excl clause (intval'd IDs) are interpolated; user values use prepare() placeholders.
            // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table/placeholder identifiers and the $gt_excl clause (built from intval'd IDs) are interpolated; user values bound via prepare()
            $posts = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT p.ID, p.post_title FROM {$wpdb->posts} p
                     LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tam_rank_meta_title'
                     WHERE p.post_type IN ({$gt_placeholders}) AND p.post_status = 'publish'
                     {$gt_excl}
                     AND (pm.meta_value IS NULL OR pm.meta_value = '')
                     ORDER BY p.post_date DESC LIMIT 50",
                    ...$gt_slugs
                )
            );
            // phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter
            if (!empty($posts)) {
                // Prime post + meta caches in two batched queries so the
                // get_permalink()/get_edit_post_link() calls below hit cache
                // instead of firing a get_post() per row (N+1 at 1000 posts).
                _prime_post_caches(wp_list_pluck($posts, 'ID'), false, true);
                $pages = array();
                $completed_count = 0;
                foreach ($posts as $p) {
                    $task_id = 'grp_missing_titles_' . $p->ID;
                    $is_done = in_array($task_id, $completed_ids);
                    if ($is_done) $completed_count++;
                    $pages[] = array(
                        'post_id'   => $p->ID,
                        'title'     => $p->post_title,
                        'path'      => str_replace(home_url(), '', get_permalink($p->ID)),
                        'edit_url'  => get_edit_post_link($p->ID, 'raw'),
                        'task_id'   => $task_id,
                        'completed' => $is_done,
                    );
                }
                $grouped[] = array(
                    'group_id'        => 'grp_missing_titles',
                    'title'           => tamrank__('task_missing_title', 'Missing meta title'),
                    'description'     => tamrank__('task_missing_title_desc', 'Pages without a meta title rank up to 40% lower in search results. Add a unique meta title to each page.'),
                    'category'        => 'META SEO',
                    'cat_slug'        => 'meta-seo',
                    'priority'        => tamrank__('priority_high', 'high'),
                    'time'            => 2,
                    'url'             => admin_url('admin.php?page=tam-rank-audit&tab=missing-titles'),
                    'source'          => 'tamrank',
                    'pages'           => $pages,
                    'completed_count' => $completed_count,
                    'total_count'     => count($pages),
                    'all_completed'   => $completed_count >= count($pages),
                );
            }
        }

        // --- GROUPED: Missing meta descriptions ---
        if ($dashboard_data['missing_descriptions'] > 0) {
            // Direct query on core posts/postmeta tables; admin-context read, caching not applicable; only table identifiers, a runtime-built %s placeholder list and the $gt_excl clause (intval'd IDs) are interpolated; user values use prepare() placeholders.
            // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table/placeholder identifiers and the $gt_excl clause (built from intval'd IDs) are interpolated; user values bound via prepare()
            $posts = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT p.ID, p.post_title FROM {$wpdb->posts} p
                     LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tam_rank_meta_description'
                     WHERE p.post_type IN ({$gt_placeholders}) AND p.post_status = 'publish'
                     {$gt_excl}
                     AND (pm.meta_value IS NULL OR pm.meta_value = '')
                     ORDER BY p.post_date DESC LIMIT 50",
                    ...$gt_slugs
                )
            );
            // phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter
            if (!empty($posts)) {
                _prime_post_caches(wp_list_pluck($posts, 'ID'), false, true);
                $pages = array();
                $completed_count = 0;
                foreach ($posts as $p) {
                    $task_id = 'grp_missing_descs_' . $p->ID;
                    $is_done = in_array($task_id, $completed_ids);
                    if ($is_done) $completed_count++;
                    $pages[] = array(
                        'post_id'   => $p->ID,
                        'title'     => $p->post_title,
                        'path'      => str_replace(home_url(), '', get_permalink($p->ID)),
                        'edit_url'  => get_edit_post_link($p->ID, 'raw'),
                        'task_id'   => $task_id,
                        'completed' => $is_done,
                    );
                }
                $grouped[] = array(
                    'group_id'        => 'grp_missing_descs',
                    'title'           => tamrank__('task_missing_desc', 'Missing meta description'),
                    'description'     => tamrank__('task_missing_desc_desc', 'A good meta description increases your click-through rate (CTR) by an average of 5.8%. Add a description to each page.'),
                    'category'        => 'META SEO',
                    'cat_slug'        => 'meta-seo',
                    'priority'        => tamrank__('priority_high', 'high'),
                    'time'            => 2,
                    'url'             => admin_url('admin.php?page=tam-rank-audit&tab=missing-descriptions'),
                    'source'          => 'tamrank',
                    'pages'           => $pages,
                    'completed_count' => $completed_count,
                    'total_count'     => count($pages),
                    'all_completed'   => $completed_count >= count($pages),
                );
            }
        }

        // --- GROUPED: Outdated content ---
        $flagged_ids = TamRank_Outdated_Scanner::get_flagged_post_ids();
        if (!empty($flagged_ids)) {
            // Cap to match the other groups (50) and prime caches so the
            // get_the_title()/get_permalink()/get_detections() loop below
            // avoids a per-row N+1 (the flagged-id query has no LIMIT of its own).
            $flagged_ids = array_slice($flagged_ids, 0, 50);
            _prime_post_caches($flagged_ids, false, true);
            $field_labels = array(
                'title' => tamrank__('field_title', 'Title'), 'meta_title' => tamrank__('field_meta_title', 'Meta title'),
                'description' => tamrank__('field_meta_description', 'Meta description'), 'slug' => 'URL', 'content' => tamrank__('field_content', 'Content'),
            );
            $pages = array();
            $completed_count = 0;
            foreach ($flagged_ids as $post_id) {
                $task_id = 'outdated_' . $post_id;
                $is_done = in_array($task_id, $completed_ids);
                if ($is_done) $completed_count++;

                $detections = TamRank_Outdated_Scanner::get_detections($post_id);
                $by_field = array();
                foreach ($detections as $det) {
                    $by_field[$det['field']][] = (int) $det['year'];
                }
                $parts = array();
                foreach ($by_field as $field => $years) {
                    $label = isset($field_labels[$field]) ? $field_labels[$field] : $field;
                    $parts[] = $label . ': ' . implode(', ', array_unique($years));
                }

                $pages[] = array(
                    'post_id'   => $post_id,
                    'title'     => get_the_title($post_id),
                    'path'      => str_replace(home_url(), '', get_permalink($post_id)),
                    'edit_url'  => admin_url('post.php?post=' . $post_id . '&action=edit'),
                    'task_id'   => $task_id,
                    'completed' => $is_done,
                    'detail'    => implode(' · ', $parts),
                );
            }
            $grouped[] = array(
                'group_id'        => 'grp_outdated',
                'title'           => tamrank__('task_outdated_content', 'Outdated content found'),
                'description'     => tamrank__('task_outdated_content_desc', 'These pages contain outdated years. Update them to keep your content current.'),
                'category'        => tamrank__('category_outdated', 'OUTDATED'),
                'cat_slug'        => 'verouderd',
                'priority'        => tamrank__('priority_high', 'high'),
                'time'            => 3,
                'url'             => admin_url('admin.php?page=tam-rank-audit'),
                'source'          => 'tamrank',
                'pages'           => $pages,
                'completed_count' => $completed_count,
                'total_count'     => count($pages),
                'all_completed'   => $completed_count >= count($pages),
            );
        }

        // --- GROUPED: Broken links (404) ---
        // A 404 the user already fixed with an active redirect is not a task any
        // more, so it is filtered out of the list *and* out of the count in the
        // title. Filtered, never deleted: the log stays complete as history, and
        // switching the rule off brings the task straight back.
        $table_404 = $wpdb->prefix . 'tam_rank_404_log';
        $unresolved_404 = class_exists('TamRank_404_Resolution')
            ? ' WHERE ' . TamRank_404_Resolution::unresolved_sql('url')
            : '';
        // Direct query on a custom plugin table (wp_tam_rank_404_log); admin-context read, caching not applicable; only the prefix-built table identifier and the plugin's own resolution predicate are interpolated and there is no user input to bind via prepare().
        // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- prefix-built table identifier only; no user input to bind
        $broken_links = $wpdb->get_results(
            "SELECT url, COUNT(*) as hits, MAX(timestamp) as last_hit
             FROM {$table_404}{$unresolved_404}
             GROUP BY url
             ORDER BY hits DESC
             LIMIT 50"
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

        // A pattern rule cannot be resolved in SQL, so the regex step runs here
        // over the rows we are about to show — at most 50, and only on a site
        // that actually has active regex rules.
        if (!empty($broken_links) && class_exists('TamRank_404_Resolution') && TamRank_404_Resolution::has_regex_rules()) {
            $still_broken = array_flip(TamRank_404_Resolution::drop_regex_resolved(
                wp_list_pluck($broken_links, 'url')
            ));
            $broken_links = array_values(array_filter($broken_links, function ($link) use ($still_broken) {
                return isset($still_broken[$link->url]);
            }));
        }

        if (!empty($broken_links)) {
            $pages = array();
            $completed_count = 0;
            foreach ($broken_links as $link) {
                $task_id = 'grp_404_' . md5($link->url);
                $is_done = in_array($task_id, $completed_ids);
                if ($is_done) $completed_count++;
                $pages[] = array(
                    'post_id'   => 0,
                    'title'     => $link->url,
                    'path'      => $link->hits . 'x ' . tamrank__('seen', 'seen'),
                    'edit_url'  => admin_url('admin.php?page=tam-rank-404'),
                    'task_id'   => $task_id,
                    'completed' => $is_done,
                );
            }
            $grouped[] = array(
                'group_id'        => 'grp_404',
                'title'           => sprintf(tamrank__('task_broken_links', 'Review and fix %d broken link(s)'), count($broken_links)),
                'description'     => tamrank__('task_broken_links_desc', 'Broken links create a bad user experience and hurt your SEO. Set up redirects or remove the links.'),
                'category'        => tamrank__('category_technical', 'TECHNICAL'),
                'cat_slug'        => 'technisch',
                'priority'        => tamrank__('priority_high', 'high'),
                'time'            => 3,
                'url'             => admin_url('admin.php?page=tam-rank-404'),
                'source'          => 'tamrank',
                'pages'           => $pages,
                'completed_count' => $completed_count,
                'total_count'     => count($pages),
                'all_completed'   => $completed_count >= count($pages),
            );
        }

        // --- GROUPED: Images missing alt text ---
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- direct query on core posts/postmeta tables; admin-context read, caching not applicable; only table identifiers are interpolated, no user values in this static query
        $images_no_alt = $wpdb->get_results(
            "SELECT p.ID, p.post_title, p.guid FROM {$wpdb->posts} p
             LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_wp_attachment_image_alt'
             WHERE p.post_type = 'attachment'
             AND p.post_mime_type LIKE 'image/%'
             AND (pm.meta_value IS NULL OR pm.meta_value = '')
             ORDER BY p.post_date DESC LIMIT 50"
        );
        if (!empty($images_no_alt)) {
            // Prime caches so get_attached_file() (reads _wp_attached_file meta)
            // hits cache instead of one meta query per image row.
            _prime_post_caches(wp_list_pluck($images_no_alt, 'ID'), false, true);
            $pages = array();
            $completed_count = 0;
            foreach ($images_no_alt as $img) {
                $task_id = 'grp_alt_' . $img->ID;
                $is_done = in_array($task_id, $completed_ids);
                if ($is_done) $completed_count++;
                $filename = basename(get_attached_file($img->ID) ?: $img->guid);
                $pages[] = array(
                    'post_id'   => $img->ID,
                    'title'     => $img->post_title ?: $filename,
                    'path'      => $filename,
                    'edit_url'  => admin_url('upload.php?item=' . $img->ID),
                    'task_id'   => $task_id,
                    'completed' => $is_done,
                );
            }
            $grouped[] = array(
                'group_id'        => 'grp_alt_images',
                'title'           => sprintf(tamrank__('task_missing_alt', 'Alt text missing on %d image(s)'), count($images_no_alt)),
                'description'     => tamrank__('task_missing_alt_desc', 'Images without alt text are bad for accessibility and SEO. Add a descriptive alt text to each image.'),
                'category'        => tamrank__('category_image', 'IMAGE'),
                'cat_slug'        => 'afbeelding',
                'priority'        => tamrank__('priority_medium', 'medium'),
                'time'            => 1,
                'url'             => admin_url('upload.php?mode=list'),
                'source'          => 'tamrank',
                'pages'           => $pages,
                'completed_count' => $completed_count,
                'total_count'     => count($pages),
                'all_completed'   => $completed_count >= count($pages),
            );
        }

        // --- GROUPED: Missing focus keyword ---
        // Direct query on core posts/postmeta tables; admin-context read, caching not applicable; only table identifiers, a runtime-built %s placeholder list and the $gt_excl clause (intval'd IDs) are interpolated; user values use prepare() placeholders.
        // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table/placeholder identifiers and the $gt_excl clause (built from intval'd IDs) are interpolated; user values bound via prepare()
        $posts_no_keyword = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT p.ID, p.post_title FROM {$wpdb->posts} p
                 LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tam_rank_focus_keyword'
                 WHERE p.post_type IN ({$gt_placeholders}) AND p.post_status = 'publish'
                 {$gt_excl}
                 AND (pm.meta_value IS NULL OR pm.meta_value = '')
                 ORDER BY p.post_date DESC LIMIT 50",
                ...$gt_slugs
            )
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter
        if (!empty($posts_no_keyword)) {
            _prime_post_caches(wp_list_pluck($posts_no_keyword, 'ID'), false, true);
            $pages = array();
            $completed_count = 0;
            foreach ($posts_no_keyword as $p) {
                $task_id = 'grp_missing_keyword_' . $p->ID;
                $is_done = in_array($task_id, $completed_ids);
                if ($is_done) $completed_count++;
                $pages[] = array(
                    'post_id'   => $p->ID,
                    'title'     => $p->post_title,
                    'path'      => str_replace(home_url(), '', get_permalink($p->ID)),
                    'edit_url'  => get_edit_post_link($p->ID, 'raw'),
                    'task_id'   => $task_id,
                    'completed' => $is_done,
                );
            }
            $grouped[] = array(
                'group_id'        => 'grp_missing_keyword',
                'title'           => sprintf(tamrank__('task_missing_keyword', 'Missing focus keyword on %d page(s)'), count($posts_no_keyword)),
                'description'     => tamrank__('task_missing_keyword_desc', 'Without a focus keyword, TamRank cannot check if your content is properly optimized. Set a keyword for each page.'),
                'category'        => 'META SEO',
                'cat_slug'        => 'meta-seo',
                'priority'        => tamrank__('priority_medium', 'medium'),
                'time'            => 1,
                'url'             => admin_url('admin.php?page=tam-rank-audit'),
                'source'          => 'tamrank',
                'pages'           => $pages,
                'completed_count' => $completed_count,
                'total_count'     => count($posts_no_keyword),
                'all_completed'   => $completed_count >= count($posts_no_keyword),
            );
        }

        // --- GROUPED: Missing internal links ---
        $site_url = home_url();
        // Direct query on the core posts table; admin-context read, caching not applicable; only the table identifier and a runtime-built %s placeholder list are interpolated; user values use prepare() placeholders.
        // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table/placeholder identifiers only; user values bound via prepare()
        $posts_no_links = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT p.ID, p.post_title FROM {$wpdb->posts} p
                 WHERE p.post_type IN ({$gt_placeholders}) AND p.post_status = 'publish'
                 AND p.post_content NOT LIKE %s
                 AND CHAR_LENGTH(p.post_content) > 300
                 ORDER BY p.post_date DESC LIMIT 50",
                ...array_merge($gt_slugs, array('%<a %href=%' . $wpdb->esc_like(wp_parse_url($site_url, PHP_URL_HOST)) . '%'))
            )
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter
        if (!empty($posts_no_links)) {
            _prime_post_caches(wp_list_pluck($posts_no_links, 'ID'), false, true);
            $pages = array();
            $completed_count = 0;
            foreach ($posts_no_links as $p) {
                $task_id = 'grp_no_internal_links_' . $p->ID;
                $is_done = in_array($task_id, $completed_ids);
                if ($is_done) $completed_count++;
                $pages[] = array(
                    'post_id'   => $p->ID,
                    'title'     => $p->post_title,
                    'path'      => str_replace(home_url(), '', get_permalink($p->ID)),
                    'edit_url'  => get_edit_post_link($p->ID, 'raw'),
                    'task_id'   => $task_id,
                    'completed' => $is_done,
                );
            }
            $grouped[] = array(
                'group_id'        => 'grp_no_internal_links',
                'title'           => sprintf(tamrank__('task_no_internal_links', 'Add internal links to %d page(s)'), count($posts_no_links)),
                'description'     => tamrank__('task_no_internal_links_desc', 'Internal links help Google discover your content and distribute authority across your site. Pages without any internal links are isolated.'),
                'category'        => tamrank__('category_content', 'CONTENT'),
                'cat_slug'        => 'content',
                'priority'        => tamrank__('priority_high', 'high'),
                'time'            => 2,
                'url'             => admin_url('admin.php?page=tam-rank-audit'),
                'source'          => 'tamrank',
                'pages'           => $pages,
                'completed_count' => $completed_count,
                'total_count'     => count($posts_no_links),
                'all_completed'   => $completed_count >= count($posts_no_links),
            );
        }

        // --- SINGLETONS ---
        $descriptions = array(
            'meta_title_homepage'    => tamrank__('task_desc_meta_title_homepage', 'Go to your homepage in the editor and fill in a meta title in the TamRank SEO panel.'),
            'focus_keyword_homepage' => tamrank__('task_desc_focus_keyword_homepage', 'Choose a focus keyword for your homepage in the TamRank SEO panel.'),
            'lazy_loading'           => tamrank__('task_desc_lazy_loading', 'Enable lazy loading in TamRank > Settings for faster load times.'),
            'enable_schema'          => tamrank__('task_desc_enable_schema', 'Enable Schema markup via TamRank > Settings > Schema.'),
            'check_sitemap'          => tamrank__('task_desc_check_sitemap', 'Check if your XML sitemap is correctly configured.'),
            'run_pagespeed'          => tamrank__('task_desc_run_pagespeed', 'Run a PageSpeed test to measure load times.'),
            'internal_links'         => tamrank__('task_desc_internal_links', 'Add internal links to important pages.'),
        );

        $quick_wins = self::get_quick_wins_checklist($dashboard_data);
        foreach ($quick_wins as $win) {
            // Skip items that are now grouped tasks
            if ($win['id'] === 'alt_text_images' || $win['id'] === 'fix_404_errors') continue;
            if (strpos($win['id'], 'meta_desc_') === 0) continue;

            $cat_raw = $win['category'];
            $cat_slug = sanitize_title(strtolower($cat_raw));
            if ($cat_slug === 'meta') $cat_slug = 'meta-seo';
            if ($cat_slug === 'image') $cat_slug = 'afbeelding';
            if ($cat_slug === 'speed') $cat_slug = 'snelheid';
            if ($cat_slug === 'technical') $cat_slug = 'technisch';

            $singletons[] = array(
                'id'          => $win['id'],
                'title'       => $win['title'],
                'description' => isset($descriptions[$win['id']]) ? $descriptions[$win['id']] : '',
                'category'    => $cat_raw,
                'cat_slug'    => $cat_slug,
                'priority'    => 'middel',
                'time'        => $win['time'],
                'url'         => $win['url'] ?? '',
                'source'      => 'tamrank',
                'completed'   => in_array($win['id'], $completed_ids),
            );
        }

        // Manual tasks
        $manual_tasks = get_option('tam_rank_manual_tasks', array());
        foreach ($manual_tasks as $manual) {
            $prio = in_array($manual['priority'] ?? '', array('hoog', 'middel', 'laag')) ? $manual['priority'] : 'middel';
            $singletons[] = array(
                'id'          => $manual['id'],
                'title'       => $manual['title'],
                'description' => $manual['description'] ?? '',
                'category'    => __('TASK', 'tam-rank'),
                'cat_slug'    => 'taak',
                'priority'    => $prio,
                'time'        => 0,
                'url'         => '',
                'source'      => 'manual',
                'completed'   => in_array($manual['id'], $completed_ids),
            );
        }

        // Fallback focus actions (count=0) as singletons
        $focus_actions = self::get_focus_actions($dashboard_data);
        foreach ($focus_actions as $action) {
            if ($action['count'] > 0) continue;
            $task_id = 'action_' . sanitize_title(substr($action['title'], 0, 40));
            $singletons[] = array(
                'id'          => $task_id,
                'title'       => $action['title'],
                'description' => $action['description'],
                'category'    => 'SEO',
                'cat_slug'    => 'seo',
                'priority'    => 'laag',
                'time'        => $action['time'],
                'url'         => $action['url'],
                'source'      => 'tamrank',
                'completed'   => in_array($task_id, $completed_ids),
            );
        }

        return array('grouped' => $grouped, 'singletons' => $singletons);
    }

    /**
     * Render unified SEO Tasks section — priority cards + other tasks
     */
    private static function render_seo_tasks_section($task_data, $priority_data = array())
    {
        $grouped    = $task_data['grouped'];
        $singletons = $task_data['singletons'];

        $priority_cards      = $priority_data['cards'] ?? array();
        $excluded_group_ids  = $priority_data['excluded_group_ids'] ?? array();
        $remaining_impacts   = $priority_data['remaining_impacts'] ?? array();
        $has_gsc             = $priority_data['has_gsc'] ?? false;

        // Sort singletons: uncompleted first, then by priority
        $priority_order = array('hoog' => 0, 'middel' => 1, 'laag' => 2);

        $active_singletons = array_filter($singletons, function ($s) { return !$s['completed']; });
        $done_singletons   = array_filter($singletons, function ($s) { return $s['completed']; });

        usort($active_singletons, function ($a, $b) use ($priority_order) {
            return ($priority_order[$a['priority']] ?? 1) - ($priority_order[$b['priority']] ?? 1);
        });

        // Filter out groups that are already shown as priority cards
        $other_grouped = array_filter($grouped, function ($g) use ($excluded_group_ids) {
            return !in_array($g['group_id'], $excluded_group_ids, true);
        });

        $active_grouped = array_filter($other_grouped, function ($g) { return !$g['all_completed']; });
        $done_grouped   = array_filter($other_grouped, function ($g) { return $g['all_completed']; });

        // Sort remaining active grouped by impact score (desc)
        if (!empty($remaining_impacts)) {
            usort($active_grouped, function ($a, $b) use ($remaining_impacts) {
                $a_impact = $remaining_impacts[$a['group_id']] ?? 0;
                $b_impact = $remaining_impacts[$b['group_id']] ?? 0;
                return $b_impact <=> $a_impact;
            });
        }

        $other_count = count($active_grouped) + count($active_singletons);
        $total_cards = count($grouped) + count($singletons);

        $icon_check   = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6.5 9 17.5 4 12.5"/></svg>';
        $icon_chevron = '<svg class="tam-task-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>';
        $icon_tr      = '<svg width="12" height="12" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="4" y="4" width="32" height="32" rx="8" fill="currentColor" opacity="0.2"/><rect x="10" y="10" width="20" height="5" rx="2" fill="currentColor"/><rect x="15" y="10" width="5" height="20" rx="2" fill="currentColor"/></svg>';
        $icon_pencil  = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>';
        ?>
        <?php
        // The change-tracker count badge + timeline are a PRO feature. FREE ships the
        // tab and the upsell; the PRO change tracker fills the count (filter) and the
        // panel body (action) when it is active.
        $is_pro = function_exists('tam_rank_license_active') && tam_rank_license_active();
        $change_count = (int) apply_filters('tamrank_dashboard_changes_count', 0);
        ?>
        <div class="tam-section tam-seo-tasks-section">
            <div class="tam-section-header">
                <div class="tam-section-title-group">
                    <div class="tam-tasks-tabs">
                        <button class="tam-tasks-tab is-active" data-tab="tasks">
                            <?php tamrank_e('seo_tasks', 'Your SEO Tasks'); ?>
                            <span class="tam-tasks-tab-count"><?php echo esc_html($total_cards); ?></span>
                        </button>
                        <button class="tam-tasks-tab" data-tab="changes">
                            <?php tamrank_e('change_recent', 'Recent Changes'); ?>
                            <?php if ($change_count > 0): ?>
                            <span class="tam-tasks-tab-count"><?php echo esc_html($change_count); ?></span>
                            <?php endif; ?>
                        </button>
                    </div>
                </div>
                <div class="tam-seo-tasks-header-actions">
                    <button class="tam-btn-add-task" id="tam-open-add-task">
                        + <?php tamrank_e('add_task', 'Add task'); ?>
                    </button>
                    <?php if ($total_cards > 4): ?>
                    <a href="#" class="tam-seo-tasks-view-all">
                        <?php echo esc_html(sprintf(tamrank__('view_all_tasks', 'View all tasks (%d) →'), $total_cards)); ?>
                    </a>
                    <?php endif; ?>
                </div>
            </div>

            <!-- Tab: Tasks -->
            <div class="tam-tasks-panel is-active" data-panel="tasks">
            <div class="tam-seo-tasks-list" id="tam-seo-tasks-list">
                <?php if (empty($grouped) && empty($singletons) && empty($priority_cards)): ?>
                    <div class="tam-no-tasks">
                        <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--tr-color-primary)" stroke-width="1.5">
                            <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
                            <polyline points="22 4 12 14.01 9 11.01" />
                        </svg>
                        <h4><?php tamrank_e('looking_good', 'Looking good!'); ?></h4>
                        <p><?php tamrank_e('looking_good_desc', 'No critical issues found. Keep publishing quality content and monitoring your performance.'); ?></p>
                        <a href="<?php echo esc_url(admin_url('admin.php?page=tam-rank-audit')); ?>" class="tr-btn tr-btn--primary tr-btn--sm tam-no-tasks-action">
                            <?php tamrank_e('looking_good_action', 'Run a new SEO audit'); ?> &rarr;
                        </a>
                    </div>
                <?php else: ?>

                    <?php
                    // ── 1. Priority cards (emerald border-left) ──
                    foreach ($priority_cards as $card):
                        $prio_label = $card['priority'] === 'high'
                            ? tamrank__('priority_high', 'High')
                            : tamrank__('priority_medium', 'Medium');
                        $prio_class = $card['priority'] === 'high' ? 'high' : 'medium';
                    ?>
                    <div class="tam-task-card tam-task-card--priority" data-group-id="<?php echo esc_attr($card['group_id']); ?>" data-type="priority">
                        <div class="tam-task-card-header">
                            <div class="tam-task-checkbox-wrap">
                                <label class="tam-task-checkbox">
                                    <input type="checkbox">
                                    <span class="tam-task-checkbox-icon"><?php echo wp_kses($icon_check, tam_rank_kses_svg()); ?></span>
                                </label>
                            </div>
                            <div class="tam-task-card-body">
                                <span class="tam-task-category-badge tam-cat-<?php echo esc_attr($card['cat_slug']); ?>"><?php echo esc_html($card['category']); ?></span>
                                <h4 class="tam-task-title"><?php echo esc_html($card['title']); ?></h4>
                            </div>
                            <div class="tam-task-card-meta">
                                <span class="tam-task-priority tam-priority-<?php echo esc_attr($prio_class); ?>"><?php echo esc_html(ucfirst($prio_label)); ?></span>
                                <?php echo wp_kses($icon_chevron, tam_rank_kses_svg()); ?>
                            </div>
                        </div>
                        <div class="tam-task-card-panel">
                            <p class="tam-task-card-desc"><?php echo esc_html($card['description']); ?></p>
                            <?php if ($card['has_gsc'] && !empty($card['top_item']['impressions'])): ?>
                            <div class="tam-task-card-meta-info">
                                <span><?php echo esc_html(sprintf(tamrank__('impressions_per_month', '%s impressions/month'), number_format($card['top_item']['impressions']))); ?></span>
                            </div>
                            <?php endif; ?>
                            <?php if ($card['count'] > 1 && !empty($card['page_list'])): ?>
                            <div class="tam-task-page-list">
                                <?php foreach ($card['page_list'] as $page): ?>
                                <div class="tam-task-page-item">
                                    <div class="tam-task-page-info">
                                        <span class="tam-task-page-name"><?php echo esc_html($page['title']); ?></span>
                                        <?php if ($card['has_gsc'] && $page['impressions'] > 0): ?>
                                        <span class="tam-task-page-path"><?php echo esc_html(number_format($page['impressions'])); ?> impr</span>
                                        <?php endif; ?>
                                    </div>
                                    <?php if (!empty($page['edit_url'])): ?>
                                    <a href="<?php echo esc_url($page['edit_url']); ?>" class="tam-task-page-fix">Fix →</a>
                                    <?php endif; ?>
                                </div>
                                <?php endforeach; ?>
                            </div>
                            <?php if ($card['count'] > 4): ?>
                            <a href="<?php echo esc_url($card['fix_url']); ?>" class="tam-fix-link">
                                <?php echo esc_html(sprintf(tamrank__('view_all_pages', 'View all %d pages'), $card['count'])); ?> →
                            </a>
                            <?php endif; ?>
                            <?php else: ?>
                            <div class="tam-task-card-footer">
                                <span class="tam-task-card-source"><?php echo wp_kses($icon_tr, tam_rank_kses_svg()); ?> <?php tamrank_e('found_by_tamrank', 'Found by TamRank'); ?></span>
                                <a href="<?php echo esc_url($card['fix_url']); ?>" class="tam-task-card-fix-all"><?php tamrank_e('fix_this', 'Fix this'); ?> →</a>
                            </div>
                            <?php endif; ?>
                        </div>
                    </div>
                    <?php endforeach; ?>

                    <?php
                    // ── GSC upsell hint (only when GSC not connected at all) ──
                    $gsc_is_connected = class_exists('TamRank_GSC_Connector') && TamRank_GSC_Connector::is_connected();
                    if (!$gsc_is_connected && !empty($priority_cards)):
                    ?>
                    <div class="tam-gsc-hint">
                        <span><?php tamrank_e('connect_gsc_priority_hint', 'Connect Search Console to prioritize tasks by actual search traffic.'); ?></span>
                        <a href="<?php echo esc_url(admin_url('admin.php?page=tam-rank-settings')); ?>"><?php tamrank_e('gsc_connect_link', 'Connect'); ?> →</a>
                    </div>
                    <?php endif; ?>

                    <?php
                    // ── Divider between priority and other tasks ──
                    if (!empty($priority_cards) && ($other_count > 0 || !empty($done_grouped) || !empty($done_singletons))):
                    ?>
                    <div class="tam-tasks-divider">
                        <span class="tam-tasks-divider-line"></span>
                        <span class="tam-tasks-divider-label"><?php echo esc_html(sprintf(tamrank__('other_tasks', 'Other tasks (%d)'), $other_count)); ?></span>
                        <span class="tam-tasks-divider-line"></span>
                    </div>
                    <?php endif; ?>

                    <?php
                    // ── 2. Remaining active grouped cards ──
                    foreach ($active_grouped as $group): ?>
                    <div class="tam-task-card" data-group-id="<?php echo esc_attr($group['group_id']); ?>" data-type="grouped">
                        <div class="tam-task-card-header">
                            <span class="tam-task-progress"><?php echo esc_html($group['completed_count']); ?> / <?php echo esc_html($group['total_count']); ?></span>
                            <div class="tam-task-card-body">
                                <span class="tam-task-category-badge tam-cat-<?php echo esc_attr($group['cat_slug']); ?>"><?php echo esc_html($group['category']); ?></span>
                                <h4 class="tam-task-title"><?php echo esc_html($group['title']); ?></h4>
                            </div>
                            <div class="tam-task-card-meta">
                                <span class="tam-task-priority tam-priority-<?php echo esc_attr($group['priority']); ?>"><?php echo esc_html(ucfirst($group['priority'])); ?></span>
                                <?php if ($group['time']): ?>
                                <span class="tam-task-time"><?php echo esc_html($group['time']); ?> min</span>
                                <?php endif; ?>
                                <?php echo wp_kses($icon_chevron, tam_rank_kses_svg()); ?>
                            </div>
                        </div>
                        <div class="tam-task-card-panel">
                            <p class="tam-task-card-desc"><?php echo esc_html($group['description']); ?></p>
                            <div class="tam-task-page-list">
                                <?php foreach ($group['pages'] as $page): ?>
                                <div class="tam-task-page-item<?php echo $page['completed'] ? ' is-completed' : ''; ?>" data-task-id="<?php echo esc_attr($page['task_id']); ?>">
                                    <label class="tam-task-checkbox">
                                        <input type="checkbox" <?php checked($page['completed']); ?>>
                                        <span class="tam-task-checkbox-icon"><?php echo wp_kses($icon_check, tam_rank_kses_svg()); ?></span>
                                    </label>
                                    <div class="tam-task-page-info">
                                        <span class="tam-task-page-name"><?php echo esc_html($page['title']); ?></span>
                                        <span class="tam-task-page-path"><?php echo esc_html($page['path']); ?></span>
                                    </div>
                                    <?php if (!empty($page['detail'])): ?>
                                    <span class="tam-task-page-detail"><?php echo esc_html($page['detail']); ?></span>
                                    <?php endif; ?>
                                    <a href="<?php echo esc_url($page['edit_url']); ?>" class="tam-task-page-fix">Fix →</a>
                                </div>
                                <?php endforeach; ?>
                            </div>
                            <div class="tam-task-card-footer">
                                <span class="tam-task-card-source"><?php echo wp_kses($icon_tr, tam_rank_kses_svg()); ?> <?php tamrank_e('found_by_tamrank', 'Found by TamRank'); ?></span>
                                <a href="<?php echo esc_url($group['url']); ?>" class="tam-task-card-fix-all"><?php tamrank_e('fix_all_pages', 'Fix all pages'); ?> →</a>
                            </div>
                        </div>
                    </div>
                    <?php endforeach; ?>

                    <?php
                    // ── 3. Active singletons ──
                    foreach ($active_singletons as $task): ?>
                    <div class="tam-task-card" data-task-id="<?php echo esc_attr($task['id']); ?>" data-type="singleton">
                        <div class="tam-task-card-header">
                            <div class="tam-task-checkbox-wrap">
                                <label class="tam-task-checkbox">
                                    <input type="checkbox">
                                    <span class="tam-task-checkbox-icon"><?php echo wp_kses($icon_check, tam_rank_kses_svg()); ?></span>
                                </label>
                            </div>
                            <div class="tam-task-card-body">
                                <span class="tam-task-category-badge tam-cat-<?php echo esc_attr($task['cat_slug']); ?>"><?php echo esc_html($task['category']); ?></span>
                                <h4 class="tam-task-title"><?php echo esc_html($task['title']); ?></h4>
                            </div>
                            <div class="tam-task-card-meta">
                                <span class="tam-task-priority tam-priority-<?php echo esc_attr($task['priority']); ?>"><?php echo esc_html(tamrank_priority_label($task['priority'])); ?></span>
                                <?php if ($task['time']): ?>
                                <span class="tam-task-time"><?php echo esc_html($task['time']); ?> min</span>
                                <?php endif; ?>
                                <?php echo wp_kses($icon_chevron, tam_rank_kses_svg()); ?>
                            </div>
                        </div>
                        <div class="tam-task-card-panel">
                            <?php if (!empty($task['description'])): ?>
                            <p class="tam-task-card-desc"><?php echo esc_html($task['description']); ?></p>
                            <?php endif; ?>
                            <div class="tam-task-card-footer">
                                <span class="tam-task-card-source">
                                    <?php if ($task['source'] === 'tamrank'): ?>
                                        <?php echo wp_kses($icon_tr, tam_rank_kses_svg()); ?> <?php tamrank_e('found_by_tamrank', 'Found by TamRank'); ?>
                                    <?php else: ?>
                                        <?php echo wp_kses($icon_pencil, tam_rank_kses_svg()); ?> <?php tamrank_e('added_manually', 'Added manually'); ?>
                                    <?php endif; ?>
                                </span>
                                <?php if (!empty($task['url'])): ?>
                                <a href="<?php echo esc_url($task['url']); ?>" class="tam-task-card-fix-all">Fix it →</a>
                                <?php endif; ?>
                            </div>
                        </div>
                    </div>
                    <?php endforeach; ?>

                    <?php
                    // ── 4. Completed grouped cards ──
                    foreach ($done_grouped as $group): ?>
                    <div class="tam-task-card is-completed" data-group-id="<?php echo esc_attr($group['group_id']); ?>" data-type="grouped">
                        <div class="tam-task-card-header">
                            <span class="tam-task-progress"><?php echo esc_html($group['completed_count']); ?> / <?php echo esc_html($group['total_count']); ?></span>
                            <div class="tam-task-card-body">
                                <span class="tam-task-category-badge tam-cat-<?php echo esc_attr($group['cat_slug']); ?>"><?php echo esc_html($group['category']); ?></span>
                                <h4 class="tam-task-title"><?php echo esc_html($group['title']); ?></h4>
                            </div>
                            <div class="tam-task-card-meta">
                                <span class="tam-task-priority tam-priority-<?php echo esc_attr($group['priority']); ?>"><?php echo esc_html(ucfirst($group['priority'])); ?></span>
                                <?php echo wp_kses($icon_chevron, tam_rank_kses_svg()); ?>
                            </div>
                        </div>
                        <div class="tam-task-card-panel">
                            <p class="tam-task-card-desc"><?php echo esc_html($group['description']); ?></p>
                            <div class="tam-task-page-list">
                                <?php foreach ($group['pages'] as $page): ?>
                                <div class="tam-task-page-item is-completed" data-task-id="<?php echo esc_attr($page['task_id']); ?>">
                                    <label class="tam-task-checkbox">
                                        <input type="checkbox" checked>
                                        <span class="tam-task-checkbox-icon"><?php echo wp_kses($icon_check, tam_rank_kses_svg()); ?></span>
                                    </label>
                                    <div class="tam-task-page-info">
                                        <span class="tam-task-page-name"><?php echo esc_html($page['title']); ?></span>
                                        <span class="tam-task-page-path"><?php echo esc_html($page['path']); ?></span>
                                    </div>
                                    <a href="<?php echo esc_url($page['edit_url']); ?>" class="tam-task-page-fix">Fix →</a>
                                </div>
                                <?php endforeach; ?>
                            </div>
                        </div>
                    </div>
                    <?php endforeach; ?>

                    <?php
                    // ── 5. Completed singletons ──
                    foreach ($done_singletons as $task): ?>
                    <div class="tam-task-card is-completed" data-task-id="<?php echo esc_attr($task['id']); ?>" data-type="singleton">
                        <div class="tam-task-card-header">
                            <div class="tam-task-checkbox-wrap">
                                <label class="tam-task-checkbox">
                                    <input type="checkbox" checked>
                                    <span class="tam-task-checkbox-icon"><?php echo wp_kses($icon_check, tam_rank_kses_svg()); ?></span>
                                </label>
                            </div>
                            <div class="tam-task-card-body">
                                <span class="tam-task-category-badge tam-cat-<?php echo esc_attr($task['cat_slug']); ?>"><?php echo esc_html($task['category']); ?></span>
                                <h4 class="tam-task-title"><?php echo esc_html($task['title']); ?></h4>
                            </div>
                            <div class="tam-task-card-meta">
                                <span class="tam-task-priority tam-priority-<?php echo esc_attr($task['priority']); ?>"><?php echo esc_html(tamrank_priority_label($task['priority'])); ?></span>
                                <?php echo wp_kses($icon_chevron, tam_rank_kses_svg()); ?>
                            </div>
                        </div>
                        <div class="tam-task-card-panel">
                            <?php if (!empty($task['description'])): ?>
                            <p class="tam-task-card-desc"><?php echo esc_html($task['description']); ?></p>
                            <?php endif; ?>
                            <div class="tam-task-card-footer">
                                <span class="tam-task-card-source">
                                    <?php if ($task['source'] === 'tamrank'): ?>
                                        <?php echo wp_kses($icon_tr, tam_rank_kses_svg()); ?> <?php tamrank_e('found_by_tamrank', 'Found by TamRank'); ?>
                                    <?php else: ?>
                                        <?php echo wp_kses($icon_pencil, tam_rank_kses_svg()); ?> <?php tamrank_e('added_manually', 'Added manually'); ?>
                                    <?php endif; ?>
                                </span>
                            </div>
                        </div>
                    </div>
                    <?php endforeach; ?>
                <?php endif; ?>
            </div>
            </div><!-- /.tam-tasks-panel[tasks] -->

            <!-- Tab: Recent Changes -->
            <div class="tam-tasks-panel" data-panel="changes">
                <?php if ( ! $is_pro ): ?>
                    <div class="tam-change-pro-gate">
                        <div class="tam-change-pro-icon">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
                        </div>
                        <h4><?php tamrank_e('change_pro_title', 'Track your SEO changes'); ?></h4>
                        <p><?php tamrank_e('change_pro_desc', 'See what you changed and how much your score improved. Automatically.'); ?></p>
                        <a href="<?php echo esc_url(tam_rank_get_upgrade_url('dashboard', 'change_tracker')); ?>" class="tam-change-pro-btn" target="_blank" rel="noopener"><?php tamrank_e('activate_pro', 'Upgrade to PRO'); ?> &rarr;</a>
                    </div>
                <?php else: ?>
                    <?php
                    /**
                     * Renders the change-tracker timeline on the dashboard "Recent
                     * Changes" tab. The PRO change tracker hooks this; FREE ships only
                     * the upsell shown above.
                     */
                    do_action('tamrank_dashboard_changes_panel');
                    ?>
                <?php endif; ?>
            </div><!-- /.tam-tasks-panel[changes] -->

        </div>
        <?php
    }

    /**
     * Render "Add Task" modal
     */
    private static function render_add_task_modal()
    {
        ?>
        <div class="tam-modal-overlay" id="tam-add-task-modal" hidden>
            <div class="tam-modal">

                <!-- Header -->
                <div class="tam-modal-header">
                    <h4><?php tamrank_e('add_task_modal_title', 'Add new task'); ?></h4>
                    <button class="tam-modal-close" id="tam-close-add-task" aria-label="<?php echo esc_attr__('Close', 'tam-rank'); ?>">×</button>
                </div>

                <!-- Form -->
                <form id="tam-add-task-form">
                    <?php wp_nonce_field('tam_rank_manual_task_nonce', 'tam_task_nonce'); ?>

                    <div class="tam-modal-body">

                        <!-- Titel -->
                        <div class="tam-form-group">
                            <label class="tam-form-label" for="tam-task-title">
                                <?php tamrank_e('task_title', 'Title'); ?>
                                <span class="tam-required">*</span>
                            </label>
                            <input type="text" id="tam-task-title" name="title" class="tam-form-input"
                                placeholder="<?php echo esc_attr(tamrank__('task_title_placeholder', 'Describe the task...')); ?>" required>
                        </div>

                        <!-- Prioriteit -->
                        <div class="tam-form-group">
                            <label class="tam-form-label"><?php tamrank_e('task_priority', 'Priority'); ?></label>
                            <div class="tam-priority-toggle">
                                <button type="button" class="tam-prio-btn" data-value="laag">
                                    <?php tamrank_e('priority_low', 'Low'); ?>
                                </button>
                                <button type="button" class="tam-prio-btn is-active" data-value="middel">
                                    <?php tamrank_e('priority_medium', 'Medium'); ?>
                                </button>
                                <button type="button" class="tam-prio-btn" data-value="hoog">
                                    <?php tamrank_e('priority_high', 'High'); ?>
                                </button>
                                <input type="hidden" name="priority" value="middel">
                            </div>
                        </div>

                        <!-- Meer opties toggle -->
                        <div class="tam-more-options-toggle" id="tam-more-options-toggle">
                            <span><?php tamrank_e('more_options', 'More options'); ?></span>
                            <svg class="tam-more-options-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>
                        </div>

                        <!-- Meer opties inhoud -->
                        <div class="tam-more-options-body" id="tam-more-options-body" hidden>
                            <div class="tam-form-group">
                                <label class="tam-form-label" for="tam-task-note">
                                    <?php tamrank_e('task_note', 'Note'); ?>
                                </label>
                                <textarea id="tam-task-note" name="note" class="tam-form-textarea"
                                    placeholder="<?php echo esc_attr(tamrank__('add_note_placeholder', 'Add a note...')); ?>"
                                    rows="4"></textarea>
                            </div>
                            <div class="tam-form-group">
                                <label class="tam-form-label" for="tam-task-page">
                                    <?php tamrank_e('task_page', 'Related page'); ?>
                                </label>
                                <input type="text" id="tam-task-page" name="page" class="tam-form-input"
                                    placeholder="<?php echo esc_attr(tamrank__('search_page_placeholder', 'Search a page...')); ?>">
                            </div>
                            <div class="tam-form-group">
                                <label class="tam-form-label" for="tam-task-deadline">
                                    <?php tamrank_e('task_deadline', 'Deadline'); ?>
                                </label>
                                <div class="tam-date-input-wrap">
                                    <svg class="tam-date-icon" width="16" height="16" viewBox="0 0 24 24" fill="none"
                                        stroke="currentColor" stroke-width="2">
                                        <rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
                                        <line x1="16" y1="2" x2="16" y2="6" />
                                        <line x1="8" y1="2" x2="8" y2="6" />
                                        <line x1="3" y1="10" x2="21" y2="10" />
                                    </svg>
                                    <input type="date" id="tam-task-deadline" name="deadline"
                                        class="tam-form-input tam-date-input">
                                </div>
                            </div>
                        </div>

                    </div><!-- /.tam-modal-body -->

                    <!-- Footer -->
                    <div class="tam-modal-footer">
                        <button type="button" id="tam-cancel-add-task" class="tam-btn-modal-cancel">
                            <?php tamrank_e('cancel', 'Cancel'); ?>
                        </button>
                        <button type="submit" class="tam-btn-modal-submit">
                            <?php tamrank_e('add_task_submit', 'Add task'); ?>
                        </button>
                    </div>
                </form>

            </div>
        </div>
        <?php
    }

    private static function render_styles()
    {
        // Styles are now enqueued via assets/css/tamrank-dashboard.css
    }

    /**
     * Render scripts
     */
    private static function render_scripts()
    {
        $quick_win_nonce = wp_create_nonce('tam_rank_quick_win_nonce');
        $manual_task_nonce = wp_create_nonce('tam_rank_manual_task_nonce');
        $outdated_scan_nonce = wp_create_nonce( TamRank_Outdated_Scanner::NONCE_KEY );
        $needs_outdated_scan = ! get_transient( 'tamrank_outdated_scan_done' );
        ob_start();
        ?>
            jQuery(document).ready(function ($) {
                // Animate score circle on load
                setTimeout(function () {
                    $('.tam-score-fill').css('transition', 'stroke-dashoffset 1.5s ease-out');
                }, 100);

                // -------------------------------------------------------
                // Outdated content — auto-scan on dashboard load
                // -------------------------------------------------------
                <?php if ( $needs_outdated_scan ) : ?>
                (function runOutdatedScan(offset) {
                    $.post(ajaxurl, {
                        action: 'tam_rank_scan_outdated',
                        nonce: '<?php echo esc_js($outdated_scan_nonce); ?>',
                        offset: offset
                    }, function (res) {
                        if (res.success && res.data.has_more) {
                            runOutdatedScan(res.data.next_offset);
                        } else if (res.success) {
                            location.reload();
                        }
                    });
                })(0);
                <?php endif; ?>

                // -------------------------------------------------------
                // SEO Taken — Checkbox toggle
                // -------------------------------------------------------
                function saveTaskStatus(taskId, isCompleted) {
                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'tam_rank_toggle_quick_win',
                            win_id: taskId,
                            completed: isCompleted ? 1 : 0,
                            nonce: '<?php echo esc_js($quick_win_nonce); ?>'
                        },
                        error: function (xhr, status, error) {
                            console.error('TamRank: AJAX error', error);
                        }
                    });
                }

                // -------------------------------------------------------
                // SEO Taken — Expand/collapse card
                // -------------------------------------------------------
                $(document).on('click', '.tam-task-card-header', function (e) {
                    if ($(e.target).is('input[type="checkbox"]') || $(e.target).closest('.tam-task-checkbox').length) {
                        return;
                    }
                    $(this).closest('.tam-task-card').toggleClass('is-open');
                });

                // -------------------------------------------------------
                // SEO Taken — Toast bij afronding van een hele kaart
                // (piek-moment, Branding §Motion & Feedback)
                // -------------------------------------------------------
                var taskDoneTitle = <?php echo wp_json_encode(tamrank__('task_completed', 'Task completed'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>;
                function celebrateCardDone($card) {
                    if (!window.TamRankToast) return;
                    var cardTitle = $card.find('.tam-task-title').first().text().trim();
                    TamRankToast.show(taskDoneTitle, cardTitle, 'success');
                }

                // -------------------------------------------------------
                // SEO Taken — Per-page checkbox (grouped cards)
                // -------------------------------------------------------
                $(document).on('change', '.tam-task-page-item .tam-task-checkbox input[type="checkbox"]', function () {
                    var $page = $(this).closest('.tam-task-page-item');
                    var $card = $(this).closest('.tam-task-card');
                    var taskId = $page.data('task-id');
                    var isCompleted = $(this).prop('checked');
                    var wasCardCompleted = $card.hasClass('is-completed');

                    $page.toggleClass('is-completed', isCompleted);
                    saveTaskStatus(taskId, isCompleted);

                    // Update progress counter
                    var total = $card.find('.tam-task-page-item').length;
                    var done = $card.find('.tam-task-page-item.is-completed').length;
                    $card.find('.tam-task-progress').text(done + ' / ' + total);

                    if (done >= total) {
                        $card.addClass('is-completed');
                        if (!wasCardCompleted) {
                            celebrateCardDone($card);
                        }
                    } else {
                        $card.removeClass('is-completed');
                    }
                    updateOpenTasksChip();
                });

                // -------------------------------------------------------
                // SEO Taken — Singleton checkbox
                // -------------------------------------------------------
                $(document).on('change', '.tam-task-card[data-type="singleton"] .tam-task-card-header .tam-task-checkbox input[type="checkbox"]', function () {
                    var $card = $(this).closest('.tam-task-card');
                    var taskId = $card.data('task-id');
                    var isCompleted = $(this).prop('checked');
                    var wasCompleted = $card.hasClass('is-completed');
                    $card.toggleClass('is-completed', isCompleted);
                    saveTaskStatus(taskId, isCompleted);
                    if (isCompleted && !wasCompleted) {
                        celebrateCardDone($card);
                    }
                    updateOpenTasksChip();
                });

                // -------------------------------------------------------
                // SEO Taken — Update header chip
                // -------------------------------------------------------
                function updateOpenTasksChip() {
                    var count = 0;
                    $('.tam-task-card[data-type="grouped"]').each(function () {
                        count += $(this).find('.tam-task-page-item:not(.is-completed)').length;
                    });
                    count += $('.tam-task-card[data-type="singleton"]:not(.is-completed)').length;
                    var tmpl = <?php echo wp_json_encode(tamrank__('open_tasks_count', '%d open tasks'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>;
                    $('.tam-header-chip[data-chip="open-tasks"]').text(tmpl.replace('%d', count));
                }

                // -------------------------------------------------------
                // SEO Taken — Modal open/close
                // -------------------------------------------------------
                function openTaskModal() {
                    $('#tam-add-task-modal').removeAttr('hidden');
                    $('#tam-task-title').focus();
                }
                function closeTaskModal() {
                    $('#tam-add-task-modal').attr('hidden', true);
                    $('#tam-add-task-form')[0].reset();
                    $('.tam-prio-btn').removeClass('is-active');
                    $('.tam-prio-btn[data-value="middel"]').addClass('is-active');
                    $('input[name="priority"]').val('middel');
                    $('#tam-more-options-body').attr('hidden', true);
                    $('#tam-more-options-toggle').removeClass('is-open');
                }

                // Tab switching: Tasks / Recent Changes
                $(document).on('click', '.tam-tasks-tab', function () {
                    var tab = $(this).data('tab');
                    $('.tam-tasks-tab').removeClass('is-active');
                    $(this).addClass('is-active');
                    $('.tam-tasks-panel').removeClass('is-active');
                    $('.tam-tasks-panel[data-panel="' + tab + '"]').addClass('is-active');
                    // Hide/show add task button (only for tasks tab)
                    if (tab === 'tasks') {
                        $('.tam-seo-tasks-header-actions').show();
                    } else {
                        $('.tam-seo-tasks-header-actions').hide();
                    }
                });

                $('#tam-open-add-task').on('click', openTaskModal);
                $('#tam-close-add-task, #tam-cancel-add-task').on('click', closeTaskModal);
                $('#tam-add-task-modal').on('click', function (e) {
                    if ($(e.target).is('#tam-add-task-modal')) closeTaskModal();
                });
                $(document).on('keydown', function (e) {
                    if (e.key === 'Escape') closeTaskModal();
                });

                // -------------------------------------------------------
                // SEO Taken — "Bekijk alle taken" toggle
                // -------------------------------------------------------
                $(document).on('click', '.tam-seo-tasks-view-all', function (e) {
                    e.preventDefault();
                    var $link = $(this);
                    var $list = $('#tam-seo-tasks-list');
                    var isOpen = $list.hasClass('is-expanded');

                    if (isOpen) {
                        $list.removeClass('is-expanded');
                        var total = $list.find('.tam-task-card').length;
                        $link.text('<?php echo esc_js(tamrank__('view_all_tasks', 'View all tasks')); ?> (' + total + ') →');
                    } else {
                        $list.addClass('is-expanded');
                        $link.text('<?php echo esc_js(tamrank__('collapse_tasks', 'Collapse')); ?> ↑');
                    }
                });

                // -------------------------------------------------------
                // SEO Taken — "Meer opties" toggle
                // -------------------------------------------------------
                $('#tam-more-options-toggle').on('click', function () {
                    var $toggle = $(this);
                    var $body = $('#tam-more-options-body');
                    var isOpen = !$body.attr('hidden');
                    if (isOpen) {
                        $body.attr('hidden', true);
                        $toggle.removeClass('is-open');
                    } else {
                        $body.removeAttr('hidden');
                        $toggle.addClass('is-open');
                    }
                });

                // -------------------------------------------------------
                // SEO Taken — Priority toggle
                // -------------------------------------------------------
                $(document).on('click', '.tam-prio-btn', function () {
                    var $btn = $(this);
                    $btn.closest('.tam-priority-toggle').find('.tam-prio-btn').removeClass('is-active');
                    $btn.addClass('is-active');
                    $btn.closest('.tam-priority-toggle').find('input[type="hidden"]').val($btn.data('value'));
                });

                // -------------------------------------------------------
                // SEO Taken — Form submit (add manual task)
                // -------------------------------------------------------
                $('#tam-add-task-form').on('submit', function (e) {
                    e.preventDefault();
                    var $form = $(this);
                    var $btn = $form.find('.tam-btn-modal-submit');
                    $btn.prop('disabled', true);

                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'tam_rank_add_manual_task',
                            title: $form.find('input[name="title"]').val(),
                            priority: $form.find('input[name="priority"]').val(),
                            note: $form.find('textarea[name="note"]').val(),
                            page: $form.find('input[name="page"]').val(),
                            deadline: $form.find('input[name="deadline"]').val(),
                            nonce: '<?php echo esc_js($manual_task_nonce); ?>'
                        },
                        success: function (response) {
                            if (response.success && response.data.task_html) {
                                var $list = $('#tam-seo-tasks-list');
                                $list.find('.tam-no-tasks').remove();
                                $list.prepend(response.data.task_html);

                                if (!$list.hasClass('is-expanded')) {
                                    var newCount = $list.find('.tam-task-card').length;
                                    $('.tam-seo-tasks-view-all').text('<?php echo esc_js(tamrank__('view_all_tasks', 'View all tasks')); ?> (' + newCount + ') →');
                                }

                                updateOpenTasksChip();
                                closeTaskModal();
                            } else {
                                console.error('TamRank: Failed to add task', response);
                            }
                            $btn.prop('disabled', false);
                        },
                        error: function (xhr, status, error) {
                            console.error('TamRank: AJAX error', error);
                            $btn.prop('disabled', false);
                        }
                    });
                });
            });
        <?php
        wp_add_inline_script('tam-rank-admin', ob_get_clean());
    }

    /**
     * Render footer
     */
    private static function render_footer()
    {
        tam_rank_render_footer();
    }

    /**
     * Render full GSC section with chart
     */
    private static function render_gsc_section()
    {
        // GSC dashboard is a FREE feature; if it is somehow unavailable, render nothing.
        if (!tam_rank_can_use_feature('gsc_dashboard_basic')) {
            return;
        }

        // Not connected, or no property selected yet → show the connect empty-state
        // instead of leaving a blank gap where the chart belongs.
        $gsc_connected = class_exists('TamRank_GSC_Connector')
            && TamRank_GSC_Connector::is_connected()
            && !empty(TamRank_GSC_Connector::get_property());

        if (!$gsc_connected) {
            // Respect a per-user dismissal of the connect prompt.
            if (get_user_meta(get_current_user_id(), 'tam_rank_gsc_connect_dismissed', true)) {
                return;
            }
            self::render_gsc_connect_card();
            return;
        }

        $property = TamRank_GSC_Connector::get_property();

        // Strip sc-domain: prefix for display
        $property_display = (strpos($property, 'sc-domain:') === 0) ? substr($property, 10) : $property;
        ?>
        <div class="tam-section tam-gsc-section" id="gsc-performance">
            <div class="tam-section-header">
                <div class="tam-section-title-group">
                    <div>
                        <div class="tam-gsc-title-row">
                            <h3>Search Console</h3>
                            <span class="tam-gsc-connected-badge"><?php tamrank_e('gsc_connected', 'Connected'); ?></span>
                        </div>
                        <span class="tam-gsc-property"><?php echo esc_html($property_display); ?></span>
                    </div>
                </div>
                <?php
                // Date windows available to this install. The PRO add-on extends
                // this list via the tamrank_gsc_available_periods filter; the free
                // plugin ships the base windows (no locked or disabled controls).
                $gsc_periods = apply_filters('tamrank_gsc_available_periods', array(7, 28));
                $gsc_period_labels = array(
                    7   => tamrank__('gsc_range_7d', 'Last 7 days'),
                    28  => tamrank__('gsc_range_28d', 'Last 28 days'),
                    90  => tamrank__('gsc_range_90d', 'Last 3 months'),
                    180 => tamrank__('gsc_range_180d', 'Last 6 months'),
                    365 => tamrank__('gsc_range_365d', 'Last 12 months'),
                    480 => tamrank__('gsc_range_480d', 'Last 16 months'),
                );
                ?>
                <select id="gsc-date-range" class="tam-date-select">
                    <?php foreach ($gsc_periods as $gsc_days):
                        $gsc_days = (int) $gsc_days;
                        if (!isset($gsc_period_labels[$gsc_days])) {
                            continue;
                        }
                        ?>
                        <option value="<?php echo esc_attr($gsc_days); ?>"><?php echo esc_html($gsc_period_labels[$gsc_days]); ?></option>
                    <?php endforeach; ?>
                </select>
            </div>

            <!-- Metric Cards -->
            <div class="tam-gsc-metrics-grid">
                <div class="tam-gsc-metric-card">
                    <span class="tam-gsc-metric-value" id="gsc-clicks">--</span>
                    <span class="tam-gsc-metric-label"><?php tamrank_e('gsc_metric_clicks', 'Clicks'); ?></span>
                    <span class="tam-gsc-metric-change" id="gsc-clicks-change">--</span>
                </div>

                <div class="tam-gsc-metric-card">
                    <span class="tam-gsc-metric-value" id="gsc-impressions">--</span>
                    <span class="tam-gsc-metric-label"><?php tamrank_e('gsc_metric_impressions', 'Impressions'); ?></span>
                    <span class="tam-gsc-metric-change" id="gsc-impressions-change">--</span>
                </div>

                <div class="tam-gsc-metric-card">
                    <span class="tam-gsc-metric-value" id="gsc-ctr">--</span>
                    <span class="tam-gsc-metric-label">CTR</span>
                    <span class="tam-gsc-metric-change" id="gsc-ctr-change">--</span>
                </div>

                <div class="tam-gsc-metric-card">
                    <span class="tam-gsc-metric-value" id="gsc-position">--</span>
                    <span class="tam-gsc-metric-label"><?php tamrank_e('gsc_avg_position', 'Avg. Position'); ?></span>
                    <span class="tam-gsc-metric-change" id="gsc-position-change">--</span>
                </div>
            </div>

            <!-- Chart Container -->
            <div id="gsc-chart" class="tam-gsc-chart">
                <div class="tam-gsc-chart-loading">
                    <div class="tam-spinner"></div>
                    <span><?php tamrank_e('gsc_loading', 'Loading data...'); ?></span>
                </div>
            </div>

            <!-- Footer CTA -->
            <?php if (tam_rank_can_use_feature('gsc_page_insights')): ?>
                <div class="tam-gsc-section-footer">
                    <a href="<?php echo esc_url(admin_url('admin.php?page=tam-rank-page-insights')); ?>" class="tam-gsc-full-report-link">
                        <?php tamrank_e('page_insights', 'Page Insights'); ?> &rarr;
                    </a>
                </div>
            <?php else: ?>
                <div class="tr-upsell">
                    <p class="tr-upsell-text"><?php tamrank_e('page_insights_pro_upsell', 'See which keywords rank per page, with CTR, position stability and uplift potential.'); ?></p>
                    <a href="<?php echo esc_url(tam_rank_get_upgrade_url('dashboard', 'page_insights')); ?>" class="tr-upsell-link" target="_blank" rel="noopener"><?php tamrank_e('view_pro', 'View PRO'); ?> &rarr;</a>
                </div>
            <?php endif; ?>
        </div>
        <?php
    }

    /**
     * Render the Search Console "connect" empty-state.
     *
     * Shown when the GSC dashboard (a FREE feature) is available but no property
     * is connected yet — so the chart area guides the user to connect instead of
     * leaving a blank gap. Replaces the old PRO-upsell guide card, which became
     * dead code when gsc_dashboard_basic moved to the FREE tier.
     */
    private static function render_gsc_connect_card()
    {
        $connect_url = admin_url('admin.php?page=tam-rank-settings');
        ?>
        <div class="tam-section tam-gsc-section tam-gsc-section--connect" id="gsc-performance">
            <!-- Widget header -->
            <div class="tam-gsc-connect-header">
                <h3>Search Console</h3>
                <span class="tam-gsc-status-pill"><span class="tam-gsc-status-dot"></span><?php tamrank_e('gsc_status_not_connected', 'Not connected'); ?></span>
                <button type="button" class="tam-gsc-connect-dismiss" aria-label="<?php echo esc_attr(tamrank__('gsc_connect_dismiss', 'Hide this until later')); ?>" data-tr-tooltip="<?php echo esc_attr(tamrank__('gsc_connect_dismiss', 'Hide this until later')); ?>">
                    <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>
                </button>
            </div>

            <!-- Two-column body: connect card + blurred preview teaser -->
            <div class="tam-gsc-connect-body">
                <!-- Left: connect card -->
                <div class="tam-gsc-connect-info">
                    <div class="tr-guide-card tr-guide-card--connect">
                        <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"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
                            </div>
                            <div class="tr-guide-card__info">
                                <h4 class="tr-guide-card__title"><?php tamrank_e('gsc_connect_card_title', 'Connect Google Search Console'); ?></h4>
                                <p class="tr-guide-card__desc"><?php tamrank_e('gsc_connect_card_desc', 'See your real clicks, impressions and positions from Google, right in your dashboard.'); ?></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('gsc_benefit_clicks', 'Clicks & impressions'); ?></div>
                            <div class="tr-guide-card__benefit"><span class="tr-guide-card__check">&check;</span> <?php tamrank_e('gsc_benefit_positions', 'Keyword positions'); ?></div>
                            <div class="tr-guide-card__benefit"><span class="tr-guide-card__check">&check;</span> <?php tamrank_e('gsc_benefit_ctr', 'CTR per page'); ?></div>
                            <div class="tr-guide-card__benefit"><span class="tr-guide-card__check">&check;</span> <?php tamrank_e('gsc_benefit_trends', 'Trend analysis'); ?></div>
                        </div>
                        <p class="tr-guide-card__pro-note"><?php tamrank_e('gsc_connect_pro_note', 'Keywords per page and longer periods (3-16 months) are part of PRO.'); ?></p>
                        <div class="tr-guide-card__action">
                            <span class="tr-guide-card__action-label"><?php tamrank_e('gsc_connect_microcopy', 'Free · about 1 minute'); ?></span>
                            <a href="<?php echo esc_url($connect_url); ?>" class="tr-btn tr-btn--primary tr-btn--md">
                                <?php tamrank_e('gsc_connect_cta', 'Connect Search Console'); ?>
                                <svg width="15" height="15" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
                            </a>
                        </div>
                    </div>
                </div>

                <!-- Right: blurred preview teaser -->
                <div class="tam-gsc-connect-preview">
                    <div class="tam-gsc-connect-preview-inner">
                        <div class="tam-gsc-preview-metrics">
                            <div class="tam-gsc-preview-metric">
                                <span class="tam-gsc-preview-value">2.847</span>
                                <span class="tam-gsc-preview-label"><?php tamrank_e('gsc_metric_clicks', 'Clicks'); ?></span>
                            </div>
                            <div class="tam-gsc-preview-metric">
                                <span class="tam-gsc-preview-value">48.2K</span>
                                <span class="tam-gsc-preview-label"><?php tamrank_e('gsc_metric_impressions', 'Impressions'); ?></span>
                            </div>
                            <div class="tam-gsc-preview-metric">
                                <span class="tam-gsc-preview-value">5.9%</span>
                                <span class="tam-gsc-preview-label">CTR</span>
                            </div>
                            <div class="tam-gsc-preview-metric">
                                <span class="tam-gsc-preview-value">8.4</span>
                                <span class="tam-gsc-preview-label"><?php tamrank_e('position', 'Position'); ?></span>
                            </div>
                        </div>
                        <!-- SVG line chart mockup -->
                        <div class="tam-gsc-preview-chart">
                            <svg viewBox="0 0 300 100" preserveAspectRatio="none" fill="none">
                                <defs>
                                    <linearGradient id="gsc-connect-gradient" x1="0" y1="0" x2="0" y2="1">
                                        <stop offset="0%" stop-color="var(--tr-color-primary)" stop-opacity="0.15"/>
                                        <stop offset="100%" stop-color="var(--tr-color-primary)" stop-opacity="0"/>
                                    </linearGradient>
                                </defs>
                                <path d="M0 70 Q30 65 60 55 T120 45 T180 35 T240 25 T300 15" stroke="var(--tr-color-primary)" stroke-width="2" stroke-linecap="round"/>
                                <path d="M0 70 Q30 65 60 55 T120 45 T180 35 T240 25 T300 15 L300 100 L0 100 Z" fill="url(#gsc-connect-gradient)"/>
                            </svg>
                        </div>
                    </div>
                    <span class="tam-gsc-preview-tag">
                        <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
                        <?php tamrank_e('gsc_preview_teaser', 'Preview - this is what you see after connecting'); ?>
                    </span>
                </div>
            </div>
        </div>
        <?php
    }
}

/**
 * AJAX Handler: dismiss the Search Console connect prompt (per user).
 */
add_action('wp_ajax_tamrank_gsc_dismiss_connect', 'tam_rank_gsc_dismiss_connect_ajax');
function tam_rank_gsc_dismiss_connect_ajax()
{
    if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'tamrank_gsc_nonce')) {
        wp_send_json_error(array('message' => tamrank__('error_nonce_invalid', 'Invalid security token. Please refresh the page.'), 'code' => 'nonce_invalid'));
        return;
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error(array('message' => tamrank__('error_permission_denied', 'You do not have permission for this action.'), 'code' => 'permission_denied'));
        return;
    }

    update_user_meta(get_current_user_id(), 'tam_rank_gsc_connect_dismissed', 1);
    wp_send_json_success();
}

/**
 * AJAX Handler voor Quick Wins toggle
 */
add_action('wp_ajax_tam_rank_toggle_quick_win', 'tam_rank_toggle_quick_win_ajax');
function tam_rank_toggle_quick_win_ajax()
{
    // Check nonce
    if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'tam_rank_quick_win_nonce')) {
        wp_send_json_error(array('message' => tamrank__('error_nonce_invalid', 'Invalid security token. Please refresh the page.'), 'code' => 'nonce_invalid'));
        return;
    }

    // Check permissions
    if (!current_user_can('manage_options')) {
        wp_send_json_error(array('message' => tamrank__('error_permission_denied', 'You do not have permission for this action.'), 'code' => 'permission_denied'));
        return;
    }

    $win_id = isset($_POST['win_id']) ? sanitize_text_field(wp_unslash($_POST['win_id'])) : '';
    $completed = isset($_POST['completed']) ? (bool) intval($_POST['completed']) : false;

    if (empty($win_id)) {
        wp_send_json_error(array('message' => tamrank__('error_missing_parameter', 'A required parameter is missing.'), 'code' => 'missing_parameter'));
        return;
    }

    // Get current completed wins
    $completed_wins = get_option('tam_rank_completed_quick_wins', array());

    if ($completed) {
        // Add to completed list
        if (!in_array($win_id, $completed_wins)) {
            $completed_wins[] = $win_id;
        }
    } else {
        // Remove from completed list
        $completed_wins = array_diff($completed_wins, array($win_id));
        $completed_wins = array_values($completed_wins); // Re-index array
    }

    // Save updated list
    update_option('tam_rank_completed_quick_wins', $completed_wins);

    wp_send_json_success(array(
        'message' => 'Quick win status updated',
        'win_id' => $win_id,
        'completed' => $completed,
        'completed_count' => count($completed_wins)
    ));
}

/**
 * AJAX Handler voor handmatige taak toevoegen
 */
add_action('wp_ajax_tam_rank_add_manual_task', 'tam_rank_add_manual_task_ajax');
function tam_rank_add_manual_task_ajax()
{
    if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'tam_rank_manual_task_nonce')) {
        wp_send_json_error(array('message' => tamrank__('error_nonce_invalid', 'Invalid security token. Please refresh the page.'), 'code' => 'nonce_invalid'));
        return;
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error(array('message' => tamrank__('error_permission_denied', 'You do not have permission for this action.'), 'code' => 'permission_denied'));
        return;
    }

    $title = sanitize_text_field(wp_unslash($_POST['title'] ?? ''));
    if (empty($title)) {
        wp_send_json_error(array('message' => tamrank__('error_missing_parameter', 'A required parameter is missing.'), 'code' => 'missing_parameter'));
        return;
    }

    $priority_raw = sanitize_text_field(wp_unslash($_POST['priority'] ?? 'middel'));
    $priority = in_array($priority_raw, array('laag', 'middel', 'hoog')) ? $priority_raw : 'middel';

    $task = array(
        'id' => 'manual_' . uniqid(),
        'title' => $title,
        'description' => sanitize_textarea_field(wp_unslash($_POST['note'] ?? '')),
        'priority' => $priority,
        'page' => sanitize_text_field(wp_unslash($_POST['page'] ?? '')),
        'deadline' => sanitize_text_field(wp_unslash($_POST['deadline'] ?? '')),
        'source' => 'manual',
        'created_at' => current_time('mysql'),
    );

    $tasks = get_option('tam_rank_manual_tasks', array());
    $tasks[] = $task;
    update_option('tam_rank_manual_tasks', $tasks);

    // Build HTML for the new task card
    $icon_check   = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6.5 9 17.5 4 12.5"/></svg>';
    $icon_pencil  = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>';
    $icon_chevron = '<svg class="tam-task-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>';

    ob_start();
    ?>
    <div class="tam-task-card" data-task-id="<?php echo esc_attr($task['id']); ?>" data-type="singleton">
        <div class="tam-task-card-header">
            <div class="tam-task-checkbox-wrap">
                <label class="tam-task-checkbox">
                    <input type="checkbox">
                    <span class="tam-task-checkbox-icon"><?php echo wp_kses($icon_check, tam_rank_kses_svg()); ?></span>
                </label>
            </div>
            <div class="tam-task-card-body">
                <span class="tam-task-category-badge tam-cat-taak"><?php echo esc_html(__('TASK', 'tam-rank')); ?></span>
                <h4 class="tam-task-title"><?php echo esc_html($task['title']); ?></h4>
            </div>
            <div class="tam-task-card-meta">
                <span class="tam-task-priority tam-priority-<?php echo esc_attr($task['priority']); ?>">
                    <?php echo esc_html(tamrank_priority_label($task['priority'])); ?>
                </span>
                <?php echo wp_kses($icon_chevron, tam_rank_kses_svg()); ?>
            </div>
        </div>
        <div class="tam-task-card-panel">
            <?php if (!empty($task['description'])): ?>
            <p class="tam-task-card-desc"><?php echo esc_html($task['description']); ?></p>
            <?php endif; ?>
            <div class="tam-task-card-footer">
                <span class="tam-task-card-source"><?php echo wp_kses($icon_pencil, tam_rank_kses_svg()); ?> <?php echo esc_html(tamrank__('added_manually', 'Added manually')); ?></span>
            </div>
        </div>
    </div>
    <?php
    $task_html = ob_get_clean();

    wp_send_json_success(array(
        'task' => $task,
        'task_html' => $task_html,
    ));
}
