<?php

/**
 * Spellmatic_Dashboard
 * Registers admin menu pages and enqueues admin assets.
 */
defined( 'ABSPATH' ) || exit;
class Spellmatic_Dashboard {
    /** @var Spellmatic_Dashboard|null */
    private static ?Spellmatic_Dashboard $instance = null;

    public static function get_instance() : Spellmatic_Dashboard {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        add_action( 'admin_menu', [$this, 'register_menus'] );
        add_action( 'admin_enqueue_scripts', [$this, 'enqueue_assets'] );
        add_action( 'admin_post_spellmatic_save_settings', [$this, 'handle_save_settings'] );
        add_action( 'wp_dashboard_setup', [$this, 'register_dashboard_widget'] );
        add_action( 'enqueue_block_editor_assets', [$this, 'enqueue_editor_assets'] );
        // Feature 1: Quick "Spell Check" link in post list row actions
        add_filter(
            'post_row_actions',
            [$this, 'filter_post_row_actions'],
            10,
            2
        );
        add_filter(
            'page_row_actions',
            [$this, 'filter_post_row_actions'],
            10,
            2
        );
        // Feature 2: Health score indicator in admin bar
        add_action( 'admin_bar_menu', [$this, 'add_admin_bar_menu'], 100 );
    }

    // ── Feature 1: Quick "Spell Check" link in post list row actions ─────────
    /**
     * Add "Spell Check" link to post/page row actions.
     * Filters post_row_actions and page_row_actions.
     *
     * @param array $actions The row actions.
     * @param \WP_Post $post The post object.
     * @return array Modified actions with "Spell Check" link added.
     */
    public function filter_post_row_actions( array $actions, \WP_Post $post ) : array {
        // Only show to users with manage_options capability
        if ( !current_user_can( 'manage_options' ) ) {
            return $actions;
        }
        // Build the link URL with auto-scan parameter
        $spell_check_url = add_query_arg( [
            'page'      => 'spellmatic',
            'scan_post' => $post->ID,
        ], admin_url( 'admin.php' ) );
        // Add the "Spell Check" action link
        $actions['spell_check'] = sprintf( '<a href="%s">%s</a>', esc_url( $spell_check_url ), esc_html__( 'Spell Check', 'spellmatic' ) );
        return $actions;
    }

    // ── Feature 2: Health score indicator in admin bar ────────────────────────
    /**
     * Add spelling health score to the WordPress admin bar.
     * Shows colored dot + "Spell: XX%" indicator.
     *
     * @param \WP_Admin_Bar $wp_admin_bar The admin bar object.
     * @return void
     */
    public function add_admin_bar_menu( \WP_Admin_Bar $wp_admin_bar ) : void {
        // Only show in admin area and to users with manage_options capability
        if ( !is_admin() || !current_user_can( 'manage_options' ) ) {
            return;
        }
        // Calculate health score from database
        $health_score = $this->get_spelling_health_score();
        // Determine color based on score
        $color = $this->get_health_score_color( $health_score );
        // Build the admin bar node
        $wp_admin_bar->add_node( [
            'id'    => 'spellmatic-health',
            'title' => $this->render_health_score_title( $health_score, $color ),
            'href'  => admin_url( 'admin.php?page=spellmatic' ),
            'meta'  => [
                'class' => 'spellmatic-admin-bar-node',
            ],
        ] );
    }

    /**
     * Calculate the spelling health score from database results.
     * Returns a percentage (0-100) of resolved vs total issues.
     *
     * @return int Health score percentage (0-100).
     */
    private function get_spelling_health_score() : int {
        global $wpdb;
        $results_table = $wpdb->prefix . 'spellmatic_results';
        // Query the latest scan results.
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table counts; table name built from $wpdb->prefix + hardcoded string.
        $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$results_table}" );
        // If no results, health is perfect (100%)
        if ( 0 === $total ) {
            return 100;
        }
        // Count resolved items (fixed or dismissed)
        $resolved = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$results_table}\n             WHERE status IN ('fixed', 'dismissed')" );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        // Calculate percentage and round
        $score = (int) round( $resolved / $total * 100 );
        return min( 100, max( 0, $score ) );
    }

    /**
     * Get the color for the health score indicator.
     * Green (>=90%), Amber (>=70%), Orange (>=40%), Red (<40%).
     *
     * @param int $score The health score percentage.
     * @return string CSS color value.
     */
    private function get_health_score_color( int $score ) : string {
        if ( $score >= 90 ) {
            return '#10B981';
            // Green
        } elseif ( $score >= 70 ) {
            return '#F59E0B';
            // Amber
        } elseif ( $score >= 40 ) {
            return '#F97316';
            // Orange
        } else {
            return '#EF4444';
            // Red
        }
    }

    /**
     * Render the health score title for the admin bar node.
     * Returns HTML with colored dot + "Spell: XX%".
     *
     * @param int $score The health score percentage.
     * @param string $color The color for the dot.
     * @return string HTML markup.
     */
    private function render_health_score_title( int $score, string $color ) : string {
        $dot = sprintf( '<span style="display:inline-block;width:8px;height:8px;border-radius:50%%;background-color:%s;margin-right:6px;vertical-align:middle;"></span>', esc_attr( $color ) );
        /* translators: %1$d = health score percentage */
        return $dot . sprintf( 'Spell: %1$d%%', $score );
    }

    // ── Register admin menu + submenus ────────────────────────────────────────
    public function register_menus() : void {
        add_menu_page(
            __( 'Spellmatic', 'spellmatic' ),
            __( 'Spellmatic', 'spellmatic' ),
            'manage_options',
            'spellmatic',
            [$this, 'render_dashboard'],
            'dashicons-search',
            80
        );
        add_submenu_page(
            'spellmatic',
            __( 'Dashboard', 'spellmatic' ),
            __( 'Dashboard', 'spellmatic' ),
            'manage_options',
            'spellmatic',
            [$this, 'render_dashboard']
        );
        add_submenu_page(
            'spellmatic',
            __( 'Scan Results', 'spellmatic' ),
            __( 'Results', 'spellmatic' ),
            'manage_options',
            'spellmatic-results',
            [$this, 'render_results']
        );
        add_submenu_page(
            'spellmatic',
            __( 'Settings', 'spellmatic' ),
            __( 'Settings', 'spellmatic' ),
            'manage_options',
            'spellmatic-settings',
            [$this, 'render_settings']
        );
        add_submenu_page(
            'spellmatic',
            __( 'Open Source & Services', 'spellmatic' ),
            __( 'Open Source', 'spellmatic' ),
            'manage_options',
            'spellmatic-open-source',
            [$this, 'render_open_source']
        );
    }

    // ── Enqueue scripts + styles on Spellmatic pages ───────────────────────
    public function enqueue_assets( string $hook ) : void {
        $spellmatic_pages = [
            'toplevel_page_spellmatic',
            'spellmatic_page_spellmatic-results',
            'spellmatic_page_spellmatic-settings',
            'spellmatic_page_spellmatic-open-source'
        ];
        if ( !in_array( $hook, $spellmatic_pages, true ) ) {
            return;
        }
        // Harper WASM
        Spellmatic_Harper::enqueue();
        // Dashboard JS
        wp_enqueue_script(
            'spellmatic-dashboard',
            SPELLMATIC_URL . 'admin/js/dashboard.js',
            ['wp-api-fetch'],
            SPELLMATIC_VERSION,
            true
        );
        // Phase 4: pass custom dictionary + digest settings to JS
        $settings = get_option( 'spellmatic_settings', Spellmatic_Core::default_settings() );
        $next_ts = wp_next_scheduled( Spellmatic_Scheduler::CRON_HOOK );
        // Auto-scan single post when arriving from row action "Spell Check" link.
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display parameter, no state change.
        $auto_scan_post = ( isset( $_GET['scan_post'] ) ? absint( $_GET['scan_post'] ) : 0 );
        wp_localize_script( 'spellmatic-dashboard', 'spellmatic', [
            'nonce'             => wp_create_nonce( 'wp_rest' ),
            'restUrl'           => esc_url_raw( rest_url( 'spellmatic/v1' ) ),
            'adminUrl'          => esc_url_raw( admin_url() ),
            'autoScanPost'      => $auto_scan_post,
            'autoScanPostTitle' => ( $auto_scan_post ? get_the_title( $auto_scan_post ) : '' ),
            'backupDismissed'   => (bool) get_option( 'spellmatic_backup_dismissed', false ),
            'harperJsUrl'       => Spellmatic_Harper::get_harper_url(),
            'postTypes'         => Spellmatic_Scanner::get_public_post_types(),
            'dictionary'        => Spellmatic_Dictionary::get_words(),
            'byok'              => [
                'providers'       => Spellmatic_BYOK::get_status(),
                'activeProvider'  => Spellmatic_BYOK::get_active_provider(),
                'openrouterModel' => Spellmatic_BYOK::get_openrouter_model(),
                'aiEnabled'       => Spellmatic_BYOK::get_ai_settings()['ai_enabled'],
                'aiMaxItems'      => Spellmatic_BYOK::get_ai_settings()['ai_max_items'],
                'creditEstimates' => Spellmatic_BYOK::CREDIT_ESTIMATES,
            ],
            'harper'            => Spellmatic_Harper::get_version_info(),
            'digest'            => [
                'enabled'       => !empty( $settings['digest_enabled'] ),
                'frequency'     => $settings['digest_frequency'] ?? 'weekly',
                'email'         => $settings['digest_email'] ?? '',
                'nextScheduled' => ( $next_ts ? wp_date( 'Y-m-d H:i', $next_ts ) : null ),
            ],
            'strings'           => [
                'scanning'       => __( 'Scanning your site…', 'spellmatic' ),
                'startingScan'   => __( 'Starting scan…', 'spellmatic' ),
                'scanComplete'   => __( 'Scan complete!', 'spellmatic' ),
                'noErrors'       => __( 'Great news — no errors found.', 'spellmatic' ),
                'errorFound'     => __( 'error found', 'spellmatic' ),
                'errorsFound'    => __( 'errors found', 'spellmatic' ),
                'fixApplied'     => __( 'Fix applied.', 'spellmatic' ),
                'adding'         => __( 'Adding…', 'spellmatic' ),
                'reverting'      => __( 'Reverting…', 'spellmatic' ),
                'clearing'       => __( 'Clearing…', 'spellmatic' ),
                'stopping'       => __( 'Stopping…', 'spellmatic' ),
                'stop'           => __( 'Stop', 'spellmatic' ),
                'nothingToFix'   => __( 'Nothing to fix', 'spellmatic' ),
                'applyFix'       => __( 'Apply Fix', 'spellmatic' ),
                'dismiss'        => __( 'Dismiss', 'spellmatic' ),
                'dictionary'     => __( 'Dictionary', 'spellmatic' ),
                'undo'           => __( 'Undo', 'spellmatic' ),
                'fixed'          => __( 'Fixed', 'spellmatic' ),
                'open'           => __( 'Open', 'spellmatic' ),
                'edited'         => __( 'Edited', 'spellmatic' ),
                'addedToDict'    => __( 'Added to dictionary', 'spellmatic' ),
                'noResultsFound' => __( 'No results found', 'spellmatic' ),
                'searchFailed'   => __( 'Search failed', 'spellmatic' ),
                'scanSinglePost' => __( 'Scan Single Post', 'spellmatic' ),
                'clearAll'       => __( 'Clear All', 'spellmatic' ),
            ],
        ] );
        // Dashboard CSS
        wp_enqueue_style(
            'spellmatic-dashboard',
            SPELLMATIC_URL . 'admin/css/dashboard.css',
            [],
            SPELLMATIC_VERSION
        );
    }

    // ── Enqueue editor sidebar assets (post/page block editor only) ────────────
    public function enqueue_editor_assets() : void {
        // enqueue_block_editor_assets also fires on the Site Editor and the
        // block-based widgets screen, where there is no post to check. Limit
        // this to the post editor so no empty Spellmatic panel appears there.
        $screen = ( function_exists( 'get_current_screen' ) ? get_current_screen() : null );
        if ( !$screen || 'post' !== $screen->base ) {
            return;
        }
        // Load Harper WASM engine in the editor too.
        Spellmatic_Harper::enqueue();
        // wp-editor supplies the PluginSidebar / PluginPrePublishPanel slots on
        // WordPress 6.6+. wp-edit-post is kept for the JS fallback on WP < 6.6.
        wp_enqueue_script(
            'spellmatic-editor-sidebar',
            SPELLMATIC_URL . 'admin/js/editor-sidebar.js',
            [
                'wp-plugins',
                'wp-editor',
                'wp-edit-post',
                'wp-element',
                'wp-data',
                'wp-components'
            ],
            SPELLMATIC_VERSION,
            true
        );
        wp_localize_script( 'spellmatic-editor-sidebar', 'spellmaticEditor', [
            'nonce'       => wp_create_nonce( 'wp_rest' ),
            'restUrl'     => esc_url_raw( rest_url( 'spellmatic/v1' ) ),
            'publishGate' => true,
        ] );
    }

    // ── WP Dashboard widget: Site Spelling Health ──────────────────────────────
    public function register_dashboard_widget() : void {
        wp_add_dashboard_widget( 'spellmatic_health', __( 'Spelling Health — Spellmatic', 'spellmatic' ), [$this, 'render_dashboard_widget'] );
    }

    public function render_dashboard_widget() : void {
        global $wpdb;
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Dashboard widget stats from custom tables; must be fresh.
        $total_scanned = (int) $wpdb->get_var( "SELECT SUM(scanned_items) FROM {$wpdb->prefix}spellmatic_scans WHERE status = 'complete'" );
        $open_errors = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_results WHERE status = 'open'" );
        $fixed_errors = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_results WHERE status = 'fixed'" );
        $dismissed = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_results WHERE status = 'dismissed'" );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $total_found = $open_errors + $fixed_errors + $dismissed;
        // Health score: percentage of resolved (fixed + dismissed) out of total found.
        // If no errors found, score is 100%.
        if ( $total_found === 0 ) {
            $score = ( $total_scanned > 0 ? 100 : 0 );
        } else {
            $score = round( ($fixed_errors + $dismissed) / $total_found * 100 );
        }
        // Score colour.
        if ( $score >= 90 ) {
            $color = '#16a34a';
            $label = __( 'Excellent', 'spellmatic' );
        } elseif ( $score >= 70 ) {
            $color = '#f59e0b';
            $label = __( 'Good', 'spellmatic' );
        } elseif ( $score >= 40 ) {
            $color = '#ea580c';
            $label = __( 'Needs Work', 'spellmatic' );
        } else {
            $color = '#dc2626';
            $label = __( 'Poor', 'spellmatic' );
        }
        $scan_url = admin_url( 'admin.php?page=spellmatic' );
        $results_url = admin_url( 'admin.php?page=spellmatic-results' );
        ?>
        <div style="text-align:center;padding:8px 0;">
            <div style="position:relative;display:inline-block;width:90px;height:90px;">
                <svg viewBox="0 0 36 36" style="width:90px;height:90px;transform:rotate(-90deg);">
                    <circle cx="18" cy="18" r="15.9" fill="none" stroke="#e5e7eb" stroke-width="3"></circle>
                    <circle cx="18" cy="18" r="15.9" fill="none" stroke="<?php 
        echo esc_attr( $color );
        ?>"
                            stroke-width="3" stroke-dasharray="<?php 
        echo esc_attr( $score );
        ?>, 100"
                            stroke-linecap="round"></circle>
                </svg>
                <div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;">
                    <span style="font-size:22px;font-weight:700;color:<?php 
        echo esc_attr( $color );
        ?>;"><?php 
        echo absint( $score );
        ?>%</span>
                </div>
            </div>
            <div style="margin-top:6px;font-size:13px;font-weight:600;color:<?php 
        echo esc_attr( $color );
        ?>;"><?php 
        echo esc_html( $label );
        ?></div>
        </div>

        <div style="display:flex;justify-content:space-around;text-align:center;margin:12px 0 8px;font-size:12px;color:#6b7280;">
            <div><strong style="font-size:16px;display:block;color:#1e293b;"><?php 
        echo absint( $open_errors );
        ?></strong><?php 
        esc_html_e( 'Open', 'spellmatic' );
        ?></div>
            <div><strong style="font-size:16px;display:block;color:#16a34a;"><?php 
        echo absint( $fixed_errors );
        ?></strong><?php 
        esc_html_e( 'Fixed', 'spellmatic' );
        ?></div>
            <div><strong style="font-size:16px;display:block;color:#9ca3af;"><?php 
        echo absint( $dismissed );
        ?></strong><?php 
        esc_html_e( 'Dismissed', 'spellmatic' );
        ?></div>
        </div>

        <div style="display:flex;gap:8px;justify-content:center;margin-top:10px;">
            <a href="<?php 
        echo esc_url( $scan_url );
        ?>" class="button button-primary button-small"><?php 
        esc_html_e( 'Run Scan', 'spellmatic' );
        ?></a>
            <?php 
        if ( $open_errors > 0 ) {
            ?>
            <a href="<?php 
            echo esc_url( $results_url );
            ?>" class="button button-small"><?php 
            esc_html_e( 'View Errors', 'spellmatic' );
            ?></a>
            <?php 
        }
        ?>
        </div>
        <?php 
    }

    // ── Inline health score bar (shown on every plugin page) ────────────────
    private function render_health_bar() : void {
        global $wpdb;
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Health bar stats from custom tables; must be fresh.
        $open = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_results WHERE status = 'open'" );
        $fixed = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_results WHERE status = 'fixed'" );
        $dismissed = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_results WHERE status = 'dismissed'" );
        $total = $open + $fixed + $dismissed;
        $scanned = (int) $wpdb->get_var( "SELECT SUM(scanned_items) FROM {$wpdb->prefix}spellmatic_scans WHERE status = 'complete'" );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        if ( $total === 0 ) {
            $score = ( $scanned > 0 ? 100 : 0 );
        } else {
            $score = round( ($fixed + $dismissed) / $total * 100 );
        }
        if ( $score >= 90 ) {
            $color = '#16a34a';
            $label = __( 'Excellent', 'spellmatic' );
        } elseif ( $score >= 70 ) {
            $color = '#f59e0b';
            $label = __( 'Good', 'spellmatic' );
        } elseif ( $score >= 40 ) {
            $color = '#ea580c';
            $label = __( 'Needs Work', 'spellmatic' );
        } else {
            $color = '#dc2626';
            $label = __( 'Poor', 'spellmatic' );
        }
        $results_url = admin_url( 'admin.php?page=spellmatic-results' );
        ?>
        <div class="spellmatic-health-bar">
            <div class="spellmatic-health-bar__ring">
                <svg viewBox="0 0 36 36" width="48" height="48" style="transform:rotate(-90deg);">
                    <circle cx="18" cy="18" r="15.9" fill="none" stroke="#e5e7eb" stroke-width="3.5"></circle>
                    <circle cx="18" cy="18" r="15.9" fill="none" stroke="<?php 
        echo esc_attr( $color );
        ?>"
                            stroke-width="3.5" stroke-dasharray="<?php 
        echo esc_attr( $score );
        ?>, 100"
                            stroke-linecap="round"></circle>
                </svg>
                <span class="spellmatic-health-bar__pct" style="color:<?php 
        echo esc_attr( $color );
        ?>;"><?php 
        echo absint( $score );
        ?>%</span>
            </div>
            <span class="spellmatic-health-bar__label" style="color:<?php 
        echo esc_attr( $color );
        ?>;"><?php 
        echo esc_html( $label );
        ?></span>
            <span class="spellmatic-health-bar__sep"></span>
            <span class="spellmatic-health-bar__stat"><strong><?php 
        echo absint( $open );
        ?></strong> <?php 
        esc_html_e( 'Open', 'spellmatic' );
        ?></span>
            <span class="spellmatic-health-bar__stat spellmatic-health-bar__stat--fixed"><strong><?php 
        echo absint( $fixed );
        ?></strong> <?php 
        esc_html_e( 'Fixed', 'spellmatic' );
        ?></span>
            <span class="spellmatic-health-bar__stat spellmatic-health-bar__stat--dismissed"><strong><?php 
        echo absint( $dismissed );
        ?></strong> <?php 
        esc_html_e( 'Dismissed', 'spellmatic' );
        ?></span>
            <?php 
        if ( $open > 0 ) {
            ?>
                <span class="spellmatic-health-bar__sep"></span>
                <a href="<?php 
            echo esc_url( $results_url );
            ?>" class="button button-primary button-small spellmatic-health-bar__cta">
                    <?php 
            /* translators: %d = number of open errors */
            printf( esc_html__( 'Fix %1$d Errors', 'spellmatic' ), absint( $open ) );
            ?> &rarr;
                </a>
            <?php 
        }
        ?>
        </div>
        <?php 
    }

    // ── Shared page header + sub-nav ─────────────────────────────────────────
    private function render_shared_header( string $active ) : void {
        ?>
        <div class="spellmatic-header">
            <h1><?php 
        esc_html_e( 'Spellmatic', 'spellmatic' );
        ?></h1>
            <?php 
        ?>
            <p class="spellmatic-tagline"><?php 
        esc_html_e( 'Scan and fix spelling, grammar &amp; AI-powered suggestions across your entire site.', 'spellmatic' );
        ?></p>
        </div>

        <?php 
        $this->render_health_bar();
        ?>

        <div class="spellmatic-subnav">
            <a href="<?php 
        echo esc_url( admin_url( 'admin.php?page=spellmatic' ) );
        ?>"
               class="spellmatic-subnav-link<?php 
        echo ( $active === 'scan' ? ' active' : '' );
        ?>">
                <?php 
        esc_html_e( 'Scan', 'spellmatic' );
        ?>
            </a>
            <a href="<?php 
        echo esc_url( admin_url( 'admin.php?page=spellmatic-results' ) );
        ?>"
               class="spellmatic-subnav-link<?php 
        echo ( $active === 'results' ? ' active' : '' );
        ?>">
                <?php 
        esc_html_e( 'Results', 'spellmatic' );
        ?>
            </a>
            <a href="<?php 
        echo esc_url( admin_url( 'admin.php?page=spellmatic-settings' ) );
        ?>"
               class="spellmatic-subnav-link<?php 
        echo ( $active === 'settings' ? ' active' : '' );
        ?>">
                <?php 
        esc_html_e( 'Settings', 'spellmatic' );
        ?>
            </a>
            <a href="<?php 
        echo esc_url( admin_url( 'admin.php?page=spellmatic-open-source' ) );
        ?>"
               class="spellmatic-subnav-link<?php 
        echo ( $active === 'open-source' ? ' active' : '' );
        ?>">
                <?php 
        esc_html_e( 'Open Source', 'spellmatic' );
        ?>
            </a>
            <?php 
        ?>
        </div>

        <?php 
    }

    // ── Dev tracker removed for 1.0 release ────────────────────────────────
    // All 19 features completed. Tracker widget stripped for production.
    // ─────────────────────────────────────────────────────────────────────────
    // ── Render: Onboarding Wizard ─────────────────────────────────────────────
    public function render_onboarding() : void {
        if ( !current_user_can( 'manage_options' ) ) {
            wp_die( 'Access denied.' );
        }
        // Get all public post types
        $post_types = get_post_types( [
            'public'  => true,
            'show_ui' => true,
        ], 'objects' );
        $post_types = array_filter( $post_types, fn( $pt ) => $pt->name !== 'attachment' );
        $post_types_array = array_map( function ( $pt ) {
            return [
                'name'  => $pt->name,
                'label' => $pt->label,
            ];
        }, $post_types );
        ?>
        <!DOCTYPE html>
        <html <?php 
        language_attributes();
        ?>>
        <head>
            <meta charset="<?php 
        bloginfo( 'charset' );
        ?>">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title><?php 
        esc_html_e( 'Spellmatic Setup', 'spellmatic' );
        ?></title>
            <?php 
        wp_register_style(
            'spellmatic-onboarding',
            SPELLMATIC_URL . 'admin/css/onboarding.css',
            [],
            SPELLMATIC_VERSION
        );
        wp_print_styles( ['spellmatic-onboarding'] );
        ?>
        </head>
        <body>
            <div class="onboarding-container">
                <!-- Step 1: Welcome -->
                <div class="onboarding-step active" data-step="1">
                    <h1><?php 
        esc_html_e( 'Welcome to Spellmatic!', 'spellmatic' );
        ?></h1>
                    <p><?php 
        esc_html_e( "Let's set up your site's spell checker in under a minute.", 'spellmatic' );
        ?></p>
                    <div class="onboarding-actions">
                        <button class="button button-primary" onclick="goToStep(2)">
                            <?php 
        esc_html_e( 'Get Started →', 'spellmatic' );
        ?>
                        </button>
                    </div>
                </div>

                <!-- Step 2: Select Content -->
                <div class="onboarding-step" data-step="2">
                    <div class="step-2-content">
                        <h1><?php 
        esc_html_e( 'What would you like to scan?', 'spellmatic' );
        ?></h1>
                        <p><?php 
        esc_html_e( 'Select the content types you want to check for spelling errors.', 'spellmatic' );
        ?></p>
                        <div class="post-types-list">
                            <?php 
        foreach ( $post_types_array as $pt ) {
            ?>
                            <div class="post-type-checkbox">
                                <input type="checkbox" id="pt-<?php 
            echo esc_attr( $pt['name'] );
            ?>"
                                       class="post-type-checkbox-input" value="<?php 
            echo esc_attr( $pt['name'] );
            ?>" checked>
                                <label for="pt-<?php 
            echo esc_attr( $pt['name'] );
            ?>">
                                    <?php 
            echo esc_html( $pt['label'] );
            ?>
                                </label>
                            </div>
                            <?php 
        }
        ?>
                        </div>
                    </div>
                    <div class="onboarding-actions">
                        <button class="button button-primary" onclick="goToStep(3)">
                            <?php 
        esc_html_e( 'Next →', 'spellmatic' );
        ?>
                        </button>
                    </div>
                </div>

                <!-- Step 3: Ready to Scan -->
                <div class="onboarding-step" data-step="3">
                    <h1><?php 
        esc_html_e( "You're all set!", 'spellmatic' );
        ?></h1>
                    <p><?php 
        esc_html_e( 'Run your first scan now to find spelling errors across your site.', 'spellmatic' );
        ?></p>
                    <div class="onboarding-actions">
                        <button class="button button-primary" onclick="completeOnboarding(true)">
                            <?php 
        esc_html_e( 'Scan Now →', 'spellmatic' );
        ?>
                        </button>
                        <a href="#" class="button button-secondary" onclick="completeOnboarding(false); return false;">
                            <?php 
        esc_html_e( "I'll scan later", 'spellmatic' );
        ?>
                        </a>
                    </div>
                    <div class="loading" id="onboarding-loading">
                        <span class="spinner"></span>
                        <span><?php 
        esc_html_e( 'Setting up...', 'spellmatic' );
        ?></span>
                    </div>
                </div>
            </div>

            <?php 
        wp_register_script(
            'spellmatic-onboarding',
            SPELLMATIC_URL . 'admin/js/onboarding.js',
            [],
            SPELLMATIC_VERSION,
            true
        );
        wp_localize_script( 'spellmatic-onboarding', 'spellmaticOnboarding', [
            'nonce'   => wp_create_nonce( 'wp_rest' ),
            'restUrl' => esc_url_raw( rest_url( 'spellmatic/v1/onboarding/complete' ) ),
            'dashUrl' => esc_url_raw( admin_url( 'admin.php?page=spellmatic' ) ),
            'scanUrl' => esc_url_raw( admin_url( 'admin.php?page=spellmatic&trigger_scan=1' ) ),
        ] );
        wp_print_scripts( ['spellmatic-onboarding'] );
        ?>
        </body>
        </html>
        <?php 
    }

    // ── Render: Dashboard page ────────────────────────────────────────────────
    public function render_dashboard() : void {
        if ( !current_user_can( 'manage_options' ) ) {
            wp_die( 'Access denied.' );
        }
        // Check if onboarding is complete; if not, show onboarding wizard instead
        if ( !get_option( 'spellmatic_onboarding_complete', false ) ) {
            $this->render_onboarding();
            return;
        }
        ?>
        <div class="wrap spellmatic-wrap">
            <?php 
        $this->render_shared_header( 'scan' );
        ?>

            <?php 
        ?>

            <div class="spellmatic-scan-panel">

                <div class="spellmatic-scope-wrap">
                    <button type="button" id="spellmatic-scope-toggle" class="spellmatic-scope-toggle" aria-expanded="false">
                        <span class="dashicons dashicons-filter"></span>
                        <?php 
        esc_html_e( 'Scan Options', 'spellmatic' );
        ?>
                        <span class="spellmatic-scope-chevron">&#8250;</span>
                    </button>
                    <div id="spellmatic-scope-panel" class="spellmatic-scope-panel" hidden>
                        <p class="spellmatic-scope-label"><?php 
        esc_html_e( 'Select content types to scan:', 'spellmatic' );
        ?></p>
                        <div id="spellmatic-scope-types" class="spellmatic-scope-types">
                            <?php 
        /* Post type checkboxes rendered by JS from spellmatic.postTypes */
        ?>
                        </div>
                        <label class="spellmatic-scope-select-all">
                            <input type="checkbox" id="spellmatic-scope-all" checked>
                            <?php 
        esc_html_e( 'Select all', 'spellmatic' );
        ?>
                        </label>

                        <div class="spellmatic-single-post-search">
                            <hr style="margin:12px 0 10px;">
                            <p class="spellmatic-scope-label">
                                <?php 
        esc_html_e( 'Scan a single post or page:', 'spellmatic' );
        ?>
                                <?php 
        ?>
                            </p>
                            <div class="spellmatic-post-search-wrap">
                                <input
                                    type="text"
                                    id="spellmatic-post-search"
                                    class="widefat"
                                    placeholder="<?php 
        esc_attr_e( 'Search by title…', 'spellmatic' );
        ?>"
                                    autocomplete="off"
                                    <?php 
        ?>
                                >
                                <div id="spellmatic-post-search-results" class="spellmatic-post-search-results" hidden></div>
                            </div>
                            <div id="spellmatic-post-search-selected" class="spellmatic-post-search-selected" hidden></div>
                        </div>
                    </div>
                </div>

                <div class="spellmatic-scan-actions">
                    <button id="spellmatic-scan-btn" class="button button-primary button-hero spellmatic-scan-btn">
                        <span class="dashicons dashicons-search"></span>
                        <?php 
        esc_html_e( 'Scan Site', 'spellmatic' );
        ?>
                    </button>
                    <?php 
        $ai_settings = Spellmatic_BYOK::get_ai_settings();
        $has_ai_provider = !empty( Spellmatic_BYOK::get_active_provider() );
        if ( $has_ai_provider ) {
            ?>
                    <button
                        id="spellmatic-ai-toggle"
                        class="button spellmatic-ai-toggle <?php 
            echo ( $ai_settings['ai_enabled'] ? 'ai-on' : 'ai-off' );
            ?>"
                        title="<?php 
            esc_attr_e( 'Toggle AI scan pass on/off', 'spellmatic' );
            ?>"
                        aria-pressed="<?php 
            echo ( $ai_settings['ai_enabled'] ? 'true' : 'false' );
            ?>"
                    >
                        <span class="ai-toggle-dot"></span>
                        <span class="ai-toggle-label">
                            AI <?php 
            echo ( $ai_settings['ai_enabled'] ? esc_html__( 'ON', 'spellmatic' ) : esc_html__( 'OFF', 'spellmatic' ) );
            ?>
                        </span>
                    </button>
                    <?php 
        }
        ?>
                </div>

                <div id="spellmatic-progress" class="spellmatic-progress hidden">
                    <div class="spellmatic-progress-bar">
                        <div id="spellmatic-progress-fill" class="spellmatic-progress-fill" style="width:0%"></div>
                    </div>
                    <p id="spellmatic-progress-text"></p>
                </div>
            </div>

            <div id="spellmatic-results-container" class="spellmatic-results-container hidden">
                <div class="spellmatic-results-header">
                    <div class="spellmatic-results-title-wrap">
                        <h2 id="spellmatic-results-title"><?php 
        esc_html_e( 'Results', 'spellmatic' );
        ?></h2>
                        <span id="spellmatic-results-summary" class="spellmatic-results-summary"></span>
                    </div>
                    <div class="spellmatic-bulk-actions">
                        <?php 
        ?>
                    </div>
                </div>

                <div id="spellmatic-filter-bar" class="spellmatic-filter-bar hidden">
                    <input type="text" id="spellmatic-filter-search"
                           class="spellmatic-filter-search"
                           placeholder="<?php 
        esc_attr_e( 'Search by title…', 'spellmatic' );
        ?>">
                    <select id="spellmatic-filter-type" class="spellmatic-filter-select">
                        <option value=""><?php 
        esc_html_e( 'All content types', 'spellmatic' );
        ?></option>
                    </select>
                    <select id="spellmatic-filter-sort" class="spellmatic-filter-select">
                        <option value="errors-desc"><?php 
        esc_html_e( 'Most errors first', 'spellmatic' );
        ?></option>
                        <option value="errors-asc"><?php 
        esc_html_e( 'Fewest errors first', 'spellmatic' );
        ?></option>
                        <option value="alpha"><?php 
        esc_html_e( 'Alphabetical', 'spellmatic' );
        ?></option>
                    </select>
                </div>

                <div id="spellmatic-results-list"></div>

                <div id="spellmatic-pagination" class="spellmatic-pagination hidden"></div>
            </div>
        </div>
        <?php 
    }

    // ── Render: Results page ──────────────────────────────────────────────────
    public function render_results() : void {
        if ( !current_user_can( 'manage_options' ) ) {
            wp_die( 'Access denied.' );
        }
        global $wpdb;
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Results page stats from custom tables; must be fresh.
        $total_open = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_results WHERE status = 'open'" );
        $total_fixed = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_results WHERE status = 'fixed'" );
        $total_dismissed = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_results WHERE status = 'dismissed'" );
        $scan_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}spellmatic_scans WHERE status = 'complete'" );
        // Load ALL results (open + fixed + dismissed) for client-side grouping.
        $results = $wpdb->get_results( "SELECT r.*, p.post_title, p.post_type,\n                    h.id AS history_id, h.fixed_at AS history_fixed_at\n             FROM {$wpdb->prefix}spellmatic_results r\n             LEFT JOIN {$wpdb->posts} p ON p.ID = r.post_id\n             LEFT JOIN {$wpdb->prefix}spellmatic_fix_history h\n                 ON h.result_id = r.id AND h.reverted = 0\n             WHERE r.status IN ('open','fixed','dismissed')\n             ORDER BY r.post_id, FIELD(r.status, 'open', 'fixed', 'dismissed'), r.updated_at DESC", ARRAY_A );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        // Build post meta lookup for the results page JS.
        $rp_post_meta = [];
        foreach ( $results as $r ) {
            $pid = (int) $r['post_id'];
            if ( isset( $rp_post_meta[$pid] ) ) {
                continue;
            }
            $rp_post_meta[$pid] = [
                'title'     => $r['post_title'] ?? 'Post #' . $pid,
                'edit_url'  => get_edit_post_link( $pid, 'raw' ),
                'permalink' => get_permalink( $pid ),
            ];
        }
        // Inject the results data as inline JS for client-side rendering.
        wp_add_inline_script( 'spellmatic-dashboard', 'window.spellmaticResults=' . wp_json_encode( [
            'results'        => array_values( $results ),
            'postMeta'       => (object) $rp_post_meta,
            'totalOpen'      => $total_open,
            'totalFixed'     => $total_fixed,
            'totalDismissed' => $total_dismissed,
        ] ) . ';', 'before' );
        ?>
        <div class="wrap spellmatic-wrap">

            <?php 
        $this->render_shared_header( 'results' );
        ?>

            <div class="spellmatic-results-toolbar">
                <div class="spellmatic-results-meta">
                    <?php 
        printf( 
            /* translators: %1$d: number of open errors */
            esc_html( _n(
                '%1$d open error',
                '%1$d open errors',
                $total_open,
                'spellmatic'
            ) ),
            absint( $total_open )
         );
        ?>
                    &nbsp;&middot;&nbsp;
                    <?php 
        printf( 
            /* translators: %1$d: number of scans completed */
            esc_html( _n(
                '%1$d scan',
                '%1$d scans',
                $scan_count,
                'spellmatic'
            ) ),
            absint( $scan_count )
         );
        ?>
                </div>

                <?php 
        // Fix counter removed — no fix limit in any version.
        ?>

                <div class="spellmatic-results-toolbar-actions">
                    <?php 
        if ( $total_open > 0 ) {
            ?>
                    <?php 
            ?>
                    <button id="spellmatic-results-bulk-dismiss-btn" class="button">
                        <span class="dashicons dashicons-dismiss"></span>
                        <?php 
            esc_html_e( 'Dismiss All', 'spellmatic' );
            ?>
                    </button>
                    <?php 
        }
        ?>

                    <a href="<?php 
        echo esc_url( rest_url( 'spellmatic/v1/results/export' ) . '?_wpnonce=' . esc_attr( wp_create_nonce( 'wp_rest' ) ) );
        ?>"
                       class="button" id="spellmatic-export-csv-btn">
                        <span class="dashicons dashicons-download"></span>
                        <?php 
        esc_html_e( 'Export CSV', 'spellmatic' );
        ?>
                    </a>
                    <button id="spellmatic-clear-results-btn"
                            class="button spellmatic-clear-btn"
                            data-confirm="<?php 
        esc_attr_e( 'Clear all scan results and history? This cannot be undone.', 'spellmatic' );
        ?>">
                        <span class="dashicons dashicons-trash"></span>
                        <?php 
        esc_html_e( 'Clear All', 'spellmatic' );
        ?>
                    </button>
                </div>
            </div>

            <!-- Grouped results: filter bar + card list + pagination (rendered by JS) -->
            <div id="spellmatic-results-page-container" class="spellmatic-results-container">
                <div id="spellmatic-rp-filter-bar" class="spellmatic-filter-bar hidden">
                    <input type="text" id="spellmatic-rp-filter-search"
                           class="spellmatic-filter-search"
                           placeholder="<?php 
        esc_attr_e( 'Search by title…', 'spellmatic' );
        ?>">
                    <select id="spellmatic-rp-filter-type" class="spellmatic-filter-select">
                        <option value=""><?php 
        esc_html_e( 'All content types', 'spellmatic' );
        ?></option>
                    </select>
                    <select id="spellmatic-rp-filter-sort" class="spellmatic-filter-select">
                        <option value="errors-desc"><?php 
        esc_html_e( 'Most errors first', 'spellmatic' );
        ?></option>
                        <option value="errors-asc"><?php 
        esc_html_e( 'Fewest errors first', 'spellmatic' );
        ?></option>
                        <option value="alpha"><?php 
        esc_html_e( 'Alphabetical', 'spellmatic' );
        ?></option>
                    </select>
                    <select id="spellmatic-rp-filter-status" class="spellmatic-filter-select">
                        <option value=""><?php 
        esc_html_e( 'All statuses', 'spellmatic' );
        ?></option>
                        <option value="open"><?php 
        esc_html_e( 'Open only', 'spellmatic' );
        ?></option>
                        <option value="fixed"><?php 
        esc_html_e( 'Fixed only', 'spellmatic' );
        ?></option>
                        <option value="dismissed"><?php 
        esc_html_e( 'Dismissed', 'spellmatic' );
        ?></option>
                    </select>
                </div>

                <div id="spellmatic-rp-results-list"></div>

                <div id="spellmatic-rp-pagination" class="spellmatic-pagination hidden"></div>
            </div>

        </div>
        <?php 
    }

    // ── Render: Settings page ─────────────────────────────────────────────────
    public function render_settings() : void {
        if ( !current_user_can( 'manage_options' ) ) {
            wp_die( 'Access denied.' );
        }
        $settings = get_option( 'spellmatic_settings', Spellmatic_Core::default_settings() );
        $byok = Spellmatic_BYOK::get_status();
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display flag after redirect.
        $saved = isset( $_GET['saved'] );
        ?>
        <div class="wrap spellmatic-wrap">
            <?php 
        $this->render_shared_header( 'settings' );
        ?>

            <?php 
        if ( $saved ) {
            ?>
            <div class="notice notice-success is-dismissible"><p><?php 
            esc_html_e( 'Settings saved.', 'spellmatic' );
            ?></p></div>
            <?php 
        }
        ?>

            <form method="post" action="<?php 
        echo esc_url( admin_url( 'admin-post.php' ) );
        ?>">
                <?php 
        wp_nonce_field( 'spellmatic_save_settings', 'spellmatic_nonce' );
        ?>
                <input type="hidden" name="action" value="spellmatic_save_settings">

                <h2><?php 
        esc_html_e( 'Scan Targets', 'spellmatic' );
        ?></h2>
                <table class="form-table">
                    <tr>
                        <th><?php 
        esc_html_e( 'Content Types', 'spellmatic' );
        ?></th>
                        <td>
                            <label><input type="checkbox" name="scan_posts" value="1" <?php 
        checked( $settings['scan_posts'] );
        ?>> <?php 
        esc_html_e( 'Posts', 'spellmatic' );
        ?></label><br>
                            <label><input type="checkbox" name="scan_pages" value="1" <?php 
        checked( $settings['scan_pages'] );
        ?>> <?php 
        esc_html_e( 'Pages', 'spellmatic' );
        ?></label><br>
                            <label>
                                <input type="checkbox" name="scan_products" value="1" <?php 
        checked( $settings['scan_products'] );
        ?>>
                                <?php 
        esc_html_e( 'WooCommerce Products', 'spellmatic' );
        ?>
                            </label><br>
                            <label>
                                <input type="checkbox" name="scan_comments" value="1" <?php 
        checked( $settings['scan_comments'] );
        ?>>
                                <?php 
        esc_html_e( 'Comments', 'spellmatic' );
        ?>
                            </label>
                        </td>
                    </tr>
                </table>

                <h2><?php 
        esc_html_e( 'AI Integration', 'spellmatic' );
        ?></h2>
                <p class="description"><?php 
        esc_html_e( 'Bring your own API key to catch contextual errors that spell-checkers miss — wrong words, grammar issues, and more. Keys are stored encrypted and never leave your server.', 'spellmatic' );
        ?></p>
                <table class="form-table" id="spellmatic-byok-table">
                    <?php 
        $byok_providers = [
            'openai' => 'OpenAI (GPT-4o mini)',
        ];
        foreach ( $byok_providers as $provider => $label ) {
            ?>
                    <tr>
                        <th><?php 
            echo esc_html( $label );
            ?></th>
                        <td>
                            <?php 
            if ( $byok[$provider]['connected'] ) {
                ?>
                                <span class="spellmatic-key-connected">✓ <?php 
                esc_html_e( 'Connected', 'spellmatic' );
                ?> — <code><?php 
                echo esc_html( $byok[$provider]['masked'] );
                ?></code></span>
                                <input type="password" name="byok_<?php 
                echo esc_attr( $provider );
                ?>" placeholder="<?php 
                esc_attr_e( 'Enter new key to replace…', 'spellmatic' );
                ?>" class="regular-text">
                            <?php 
            } else {
                ?>
                                <input type="password" name="byok_<?php 
                echo esc_attr( $provider );
                ?>" placeholder="<?php 
                /* translators: %s: AI provider name (e.g. OpenAI, Claude) */
                printf( esc_attr__( 'Paste your %s API key…', 'spellmatic' ), esc_attr( $label ) );
                ?>" class="regular-text">
                            <?php 
            }
            ?>
                            <button type="button"
                                class="button spellmatic-byok-test-btn"
                                data-provider="<?php 
            echo esc_attr( $provider );
            ?>"
                                <?php 
            echo ( $byok[$provider]['connected'] ? '' : 'disabled' );
            ?>>
                                <?php 
            esc_html_e( 'Test Connection', 'spellmatic' );
            ?>
                            </button>
                            <span class="spellmatic-byok-test-status spellmatic-dict-status" data-provider="<?php 
            echo esc_attr( $provider );
            ?>" aria-live="polite"></span>
                            <?php 
            $signup_links = [
                'openai'     => ['https://platform.openai.com/api-keys', __( 'Get an OpenAI key →', 'spellmatic' )],
                'claude'     => ['https://console.anthropic.com/settings/keys', __( 'Get a Claude key →', 'spellmatic' )],
                'gemini'     => ['https://aistudio.google.com/apikey', __( 'Get a Gemini key →', 'spellmatic' )],
                'grok'       => ['https://console.x.ai/', __( 'Get a Grok key →', 'spellmatic' )],
                'openrouter' => ['https://openrouter.ai/keys', __( 'Get an OpenRouter key →', 'spellmatic' )],
            ];
            if ( isset( $signup_links[$provider] ) ) {
                ?>
                            <p class="description" style="margin-top:6px;">
                                <?php 
                if ( 'openrouter' === $provider ) {
                    ?>
                                    <?php 
                    esc_html_e( 'OpenRouter lets you use any AI model with one key.', 'spellmatic' );
                    ?>
                                <?php 
                }
                ?>
                                <a href="<?php 
                echo esc_url( $signup_links[$provider][0] );
                ?>" target="_blank"><?php 
                echo esc_html( $signup_links[$provider][1] );
                ?></a>
                            </p>
                            <?php 
            }
            ?>
                        </td>
                    </tr>
                    <?php 
            if ( $provider === 'openrouter' ) {
                ?>
                    <tr>
                        <th><?php 
                esc_html_e( 'OpenRouter Model', 'spellmatic' );
                ?></th>
                        <td>
                            <?php 
                $or_model = Spellmatic_BYOK::get_openrouter_model();
                ?>
                            <input
                                type="text"
                                name="openrouter_model"
                                value="<?php 
                echo esc_attr( $or_model );
                ?>"
                                placeholder="arcee-ai/trinity-mini:free"
                                class="regular-text"
                                spellcheck="false"
                            >
                            <p class="description">
                                <?php 
                esc_html_e( 'Any model slug from', 'spellmatic' );
                ?>
                                <a href="https://openrouter.ai/models" target="_blank">openrouter.ai/models</a>.
                                <?php 
                esc_html_e( 'Free examples: arcee-ai/trinity-mini:free, stepfun/step-3.5-flash:free', 'spellmatic' );
                ?>.
                                <?php 
                esc_html_e( 'Paid: google/gemini-2.5-flash, openai/gpt-4o-mini', 'spellmatic' );
                ?>.
                            </p>
                        </td>
                    </tr>
                    <?php 
            }
            ?>
                    <?php 
        }
        ?>
                    <tr>
                        <th><?php 
        esc_html_e( 'Active Provider', 'spellmatic' );
        ?></th>
                        <td>
                            <?php 
        $settings = get_option( 'spellmatic_settings', Spellmatic_Core::default_settings() );
        $active_provider = sanitize_key( $settings['byok_provider'] ?? '' );
        ?>
                            <select name="byok_provider" id="spellmatic-byok-provider" class="regular-text">
                                <option value=""><?php 
        esc_html_e( '— Auto-select first connected provider —', 'spellmatic' );
        ?></option>
                                <?php 
        foreach ( $byok_providers as $pv => $pl ) {
            ?>
                                <option value="<?php 
            echo esc_attr( $pv );
            ?>" <?php 
            selected( $active_provider, $pv );
            ?>>
                                    <?php 
            echo esc_html( $pl );
            ?>
                                    <?php 
            echo ( $byok[$pv]['connected'] ? ' ✓' : ' (no key)' );
            ?>
                                </option>
                                <?php 
        }
        ?>
                            </select>
                            <p class="description"><?php 
        esc_html_e( 'Which provider to use during AI scan passes.', 'spellmatic' );
        ?></p>
                        </td>
                    </tr>
                </table>

                <?php 
        // ── AI Scan Settings ──────────────────────────────────────────
        $ai_s = Spellmatic_BYOK::get_ai_settings();
        ?>
                <hr style="margin: 24px 0 20px;">
                <h3 style="margin-bottom:4px;"><?php 
        esc_html_e( 'AI Scan Settings', 'spellmatic' );
        ?></h3>
                <p class="description" style="margin-bottom:16px;">
                    <?php 
        esc_html_e( 'Configure how the AI pass runs after each Harper scan. Each item is one post/field sent to your provider — costs are very low (see estimates below).', 'spellmatic' );
        ?>
                </p>
                <table class="form-table">
                    <tr>
                        <th><?php 
        esc_html_e( 'Enable AI Scan', 'spellmatic' );
        ?></th>
                        <td>
                            <label>
                                <input type="checkbox" name="ai_enabled" value="1" <?php 
        checked( $ai_s['ai_enabled'] );
        ?>>
                                <?php 
        esc_html_e( 'Run AI analysis after each scan', 'spellmatic' );
        ?>
                            </label>
                            <p class="description"><?php 
        esc_html_e( 'When enabled, a secondary AI pass catches contextual errors Harper misses (wrong homophones, misused words, grammar).', 'spellmatic' );
        ?></p>
                        </td>
                    </tr>
                    <tr>
                        <th><?php 
        esc_html_e( 'Items per Scan', 'spellmatic' );
        ?></th>
                        <td>
                            <input
                                type="number"
                                name="ai_max_items"
                                value="<?php 
        echo esc_attr( $ai_s['ai_max_items'] );
        ?>"
                                min="1"
                                max="50"
                                class="small-text"
                            >
                            <span class="description">&nbsp;<?php 
        esc_html_e( 'posts/fields (1–50). Each item = 1 API call. More items = deeper scan, more credits used.', 'spellmatic' );
        ?></span>
                            <p class="description" style="margin-top:6px;">
                                <strong><?php 
        esc_html_e( 'Estimated cost per full scan:', 'spellmatic' );
        ?></strong>
                                <span id="spellmatic-ai-cost-preview"></span>
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <th><?php 
        esc_html_e( 'Provider Cost Reference', 'spellmatic' );
        ?></th>
                        <td>
                            <table class="widefat striped" style="max-width:540px;">
                                <thead><tr>
                                    <th><?php 
        esc_html_e( 'Provider', 'spellmatic' );
        ?></th>
                                    <th><?php 
        esc_html_e( 'Est. cost / item', 'spellmatic' );
        ?></th>
                                    <th><?php 
        esc_html_e( 'Rate basis', 'spellmatic' );
        ?></th>
                                </tr></thead>
                                <tbody>
                                <?php 
        foreach ( Spellmatic_BYOK::CREDIT_ESTIMATES as $pv => $est ) {
            ?>
                                <tr>
                                    <td><?php 
            echo esc_html( $est['label'] );
            ?></td>
                                    <td><strong><?php 
            echo esc_html( $est['per_item'] );
            ?></strong></td>
                                    <td><small><?php 
            echo esc_html( $est['note'] );
            ?></small></td>
                                </tr>
                                <?php 
        }
        ?>
                                </tbody>
                            </table>
                            <p class="description" style="margin-top:6px;"><?php 
        esc_html_e( 'Estimates based on ~300 input + ~100 output tokens per item. Actual costs depend on content length.', 'spellmatic' );
        ?></p>
                        </td>
                    </tr>
                </table>

                <?php 
        /* ── Data Management ─────────────────────────────────── */
        ?>
                <h2 style="margin-top:2em;"><?php 
        esc_html_e( 'Data Management', 'spellmatic' );
        ?></h2>
                <table class="form-table">
                    <tr>
                        <th><?php 
        esc_html_e( 'Uninstall Behaviour', 'spellmatic' );
        ?></th>
                        <td>
                            <label>
                                <input type="checkbox" name="delete_data_on_uninstall" value="1"
                                    <?php 
        checked( !empty( $settings['delete_data_on_uninstall'] ) );
        ?>>
                                <?php 
        esc_html_e( 'Remove all Spellmatic data (scan results, settings, dictionary) when the plugin is deleted.', 'spellmatic' );
        ?>
                            </label>
                            <p class="description">
                                <?php 
        esc_html_e( 'Leave unchecked to preserve your data if you plan to reinstall later.', 'spellmatic' );
        ?>
                            </p>
                        </td>
                    </tr>
                </table>

                <?php 
        submit_button( __( 'Save Settings', 'spellmatic' ) );
        ?>
            </form>

            <?php 
        /* ── Phase 4: Custom Dictionary (AJAX-powered, no page reload) ─── */
        ?>
            <hr>
            <h2><?php 
        esc_html_e( 'Custom Dictionary', 'spellmatic' );
        ?></h2>
            <p class="description">
                <?php 
        esc_html_e( 'Add words Harper should always ignore — brand names, proper nouns, industry terms, etc. Words are matched case-insensitively.', 'spellmatic' );
        ?>
            </p>

            <div class="spellmatic-dict-panel" id="spellmatic-dict-panel">
                <div class="spellmatic-dict-add-row">
                    <input
                        type="text"
                        id="spellmatic-dict-input"
                        class="regular-text"
                        placeholder="<?php 
        esc_attr_e( 'Type a word or comma-separated list…', 'spellmatic' );
        ?>"
                        autocomplete="off"
                        spellcheck="false"
                    >
                    <button id="spellmatic-dict-add-btn" class="button button-primary">
                        <?php 
        esc_html_e( 'Add Word', 'spellmatic' );
        ?>
                    </button>
                    <span id="spellmatic-dict-status" class="spellmatic-dict-status" aria-live="polite"></span>
                </div>

                <div id="spellmatic-dict-list" class="spellmatic-dict-list">
                    <?php 
        /* Populated by dashboard.js on page load */
        ?>
                    <p class="spellmatic-dict-empty hidden"><?php 
        esc_html_e( 'No custom words yet. Add your first word above.', 'spellmatic' );
        ?></p>
                </div>

                <div class="spellmatic-dict-export-row" style="margin-top:12px; display:flex; gap:8px; align-items:center;">
                    <button id="spellmatic-dict-export-btn" class="button button-secondary">
                        <?php 
        esc_html_e( 'Export Dictionary', 'spellmatic' );
        ?>
                    </button>
                    <button id="spellmatic-dict-import-btn" class="button button-secondary">
                        <?php 
        esc_html_e( 'Import Words', 'spellmatic' );
        ?>
                    </button>
                    <span id="spellmatic-dict-export-status" class="spellmatic-dict-status" aria-live="polite"></span>
                </div>
                <div id="spellmatic-dict-import-panel" class="hidden" style="margin-top:10px;">
                    <textarea
                        id="spellmatic-dict-import-input"
                        rows="3"
                        class="large-text"
                        placeholder="<?php 
        esc_attr_e( 'Paste comma-separated words: wordpress, elementor, woocommerce…', 'spellmatic' );
        ?>"
                        spellcheck="false"
                    ></textarea>
                    <div style="margin-top:6px; display:flex; gap:8px; align-items:center;">
                        <button id="spellmatic-dict-import-save-btn" class="button button-primary">
                            <?php 
        esc_html_e( 'Add to Dictionary', 'spellmatic' );
        ?>
                        </button>
                        <button id="spellmatic-dict-import-cancel-btn" class="button button-secondary">
                            <?php 
        esc_html_e( 'Cancel', 'spellmatic' );
        ?>
                        </button>
                    </div>
                </div>
            </div>

            <?php 
        /* ── Phase 4: Scan Digest / Scheduler ─────────────────────────── */
        ?>
            <hr>
            <h2><?php 
        esc_html_e( 'Scan Digest Emails', 'spellmatic' );
        ?></h2>
            <p class="description">
                <?php 
        esc_html_e( 'Receive a regular email summary of open spelling errors on your site. The digest reports on the most recent scan results and prompts you when it\'s time to re-scan.', 'spellmatic' );
        ?>
            </p>

            <div class="spellmatic-digest-panel" id="spellmatic-digest-panel">
                <table class="form-table spellmatic-digest-table">
                    <tr>
                        <th><?php 
        esc_html_e( 'Enable Digest', 'spellmatic' );
        ?></th>
                        <td>
                            <label class="spellmatic-toggle-label">
                                <input type="checkbox" id="spellmatic-digest-enabled" class="spellmatic-toggle-input">
                                <span class="spellmatic-toggle-track"></span>
                                <span class="spellmatic-toggle-text"><?php 
        esc_html_e( 'Send periodic scan digest emails', 'spellmatic' );
        ?></span>
                            </label>
                        </td>
                    </tr>
                    <tr id="spellmatic-digest-freq-row">
                        <th><?php 
        esc_html_e( 'Frequency', 'spellmatic' );
        ?></th>
                        <td>
                            <select id="spellmatic-digest-frequency" class="regular-text">
                                <option value="weekly"><?php 
        esc_html_e( 'Weekly', 'spellmatic' );
        ?></option>
                                <option value="monthly"><?php 
        esc_html_e( 'Monthly', 'spellmatic' );
        ?></option>
                            </select>
                        </td>
                    </tr>
                    <tr id="spellmatic-digest-email-row">
                        <th><?php 
        esc_html_e( 'Recipient Email', 'spellmatic' );
        ?></th>
                        <td>
                            <input
                                type="email"
                                id="spellmatic-digest-email"
                                class="regular-text"
                                placeholder="<?php 
        echo esc_attr( get_option( 'admin_email' ) );
        ?>"
                            >
                            <p class="description">
                                <?php 
        printf( 
            /* translators: %s: admin email address */
            esc_html__( 'Leave blank to use the site admin email (%s).', 'spellmatic' ),
            esc_html( get_option( 'admin_email' ) )
         );
        ?>
                            </p>
                        </td>
                    </tr>
                    <tr id="spellmatic-digest-next-row">
                        <th><?php 
        esc_html_e( 'Next Scheduled', 'spellmatic' );
        ?></th>
                        <td>
                            <span id="spellmatic-digest-next" class="spellmatic-digest-next-text">—</span>
                        </td>
                    </tr>
                </table>

                <div class="spellmatic-digest-actions">
                    <button id="spellmatic-digest-save-btn" class="button button-primary">
                        <?php 
        esc_html_e( 'Save Digest Settings', 'spellmatic' );
        ?>
                    </button>
                    <button id="spellmatic-digest-test-btn" class="button">
                        <?php 
        esc_html_e( 'Send Test Email Now', 'spellmatic' );
        ?>
                    </button>
                    <span id="spellmatic-digest-status" class="spellmatic-dict-status" aria-live="polite"></span>
                </div>
            </div>

        </div><!-- .spellmatic-wrap -->
        <?php 
    }

    // ── Handle settings form save (scan targets + BYOK) ───────────────────────
    public function handle_save_settings() : void {
        check_admin_referer( 'spellmatic_save_settings', 'spellmatic_nonce' );
        if ( !current_user_can( 'manage_options' ) ) {
            wp_die( 'Access denied.' );
        }
        $settings = get_option( 'spellmatic_settings', Spellmatic_Core::default_settings() );
        $settings['scan_posts'] = isset( $_POST['scan_posts'] );
        $settings['scan_pages'] = isset( $_POST['scan_pages'] );
        $settings['scan_products'] = isset( $_POST['scan_products'] );
        $settings['scan_comments'] = isset( $_POST['scan_comments'] );
        // Phase 5: BYOK keys — available to all users (they pay for their own API usage).
        foreach ( [
            'openai',
            'claude',
            'gemini',
            'grok',
            'openrouter'
        ] as $provider ) {
            $key = sanitize_text_field( wp_unslash( $_POST['byok_' . $provider] ?? '' ) );
            if ( !empty( $key ) ) {
                Spellmatic_BYOK::save_key( $provider, $key );
            }
        }
        // Active provider choice.
        $byok_provider = sanitize_key( $_POST['byok_provider'] ?? '' );
        if ( in_array( $byok_provider, [
            '',
            'openai',
            'claude',
            'gemini',
            'grok',
            'openrouter'
        ], true ) ) {
            $settings['byok_provider'] = $byok_provider;
        }
        // OpenRouter model slug (not sensitive — stored plaintext).
        $or_model = sanitize_text_field( wp_unslash( $_POST['openrouter_model'] ?? '' ) );
        if ( !empty( $or_model ) ) {
            Spellmatic_BYOK::set_openrouter_model( $or_model );
        }
        // AI scan settings.
        $settings['ai_enabled'] = isset( $_POST['ai_enabled'] );
        $settings['ai_max_items'] = max( 1, min( 50, absint( wp_unslash( $_POST['ai_max_items'] ?? 5 ) ) ) );
        // Data management.
        $settings['delete_data_on_uninstall'] = isset( $_POST['delete_data_on_uninstall'] );
        update_option( 'spellmatic_settings', $settings );
        wp_safe_redirect( add_query_arg( 'saved', '1', admin_url( 'admin.php?page=spellmatic-settings' ) ) );
        exit;
    }

    // ── Results table (legacy flat HTML — kept for reference, no longer called) ─
    private static function get_results_table( int $page = 1, int $per_page = 25 ) : string {
        global $wpdb;
        $offset = ($page - 1) * $per_page;
        // Unified query: open, fixed, AND dismissed results together.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table paginated query.
        $results = $wpdb->get_results( $wpdb->prepare( "SELECT r.*, p.post_title, p.post_modified,\n                        h.id AS history_id, h.fixed_at AS history_fixed_at\n                 FROM {$wpdb->prefix}spellmatic_results r\n                 LEFT JOIN {$wpdb->posts} p ON p.ID = r.post_id\n                 LEFT JOIN {$wpdb->prefix}spellmatic_fix_history h\n                     ON h.result_id = r.id AND h.reverted = 0\n                 WHERE r.status IN ('open','fixed','dismissed')\n                 ORDER BY FIELD(r.status, 'dismissed', 'fixed', 'open'), r.updated_at DESC\n                 LIMIT %d OFFSET %d", $per_page, $offset ), ARRAY_A );
        if ( empty( $results ) ) {
            return '<p>' . esc_html__( 'No errors found. Run a scan from the Dashboard.', 'spellmatic' ) . '</p>';
        }
        $html = '<table class="widefat spellmatic-results-table">';
        $html .= '<thead><tr>';
        $html .= '<th>' . esc_html__( 'Post', 'spellmatic' ) . '</th>';
        $html .= '<th>' . esc_html__( 'Error', 'spellmatic' ) . '</th>';
        $html .= '<th>' . esc_html__( 'Suggestion', 'spellmatic' ) . '</th>';
        $html .= '<th>' . esc_html__( 'Context', 'spellmatic' ) . '</th>';
        $html .= '<th>' . esc_html__( 'Type', 'spellmatic' ) . '</th>';
        $html .= '<th>' . esc_html__( 'Confidence', 'spellmatic' ) . '</th>';
        $html .= '<th>' . esc_html__( 'Status', 'spellmatic' ) . '</th>';
        $html .= '<th>' . esc_html__( 'Actions', 'spellmatic' ) . '</th>';
        $html .= '</tr></thead><tbody>';
        foreach ( $results as $r ) {
            $is_fixed = $r['status'] === 'fixed';
            $is_dismissed = $r['status'] === 'dismissed';
            $edit_link = get_edit_post_link( $r['post_id'] );
            $row_class = ( $is_fixed ? ' class="spellmatic-fixed-row"' : (( $is_dismissed ? ' class="spellmatic-dismissed-row"' : '' )) );
            $html .= '<tr data-result-id="' . esc_attr( $r['id'] ) . '"' . $row_class . '>';
            $html .= '<td><a href="' . esc_url( $edit_link ) . '">' . esc_html( $r['post_title'] ?? '' ) . '</a></td>';
            // Error column: strikethrough if fixed or dismissed.
            if ( $is_fixed || $is_dismissed ) {
                $html .= '<td><code class="spellmatic-error" style="text-decoration:line-through;opacity:0.5;">' . esc_html( $r['original'] ) . '</code></td>';
            } else {
                $html .= '<td><code class="spellmatic-error">' . esc_html( $r['original'] ) . '</code></td>';
            }
            $html .= '<td><code class="spellmatic-fix">' . esc_html( $r['suggestion'] ) . '</code></td>';
            // Context column with inline diff preview (open items only).
            $ctx_html = esc_html( $r['context'] );
            if ( !$is_fixed ) {
                $orig_esc = esc_html( $r['original'] );
                $sugg_esc = esc_html( $r['suggestion'] );
                if ( !empty( $r['original'] ) && !empty( $r['context'] ) ) {
                    $diff_markup = '<span class="spellmatic-diff-del">' . $orig_esc . '</span>' . '<span class="spellmatic-diff-ins">' . $sugg_esc . '</span>';
                    $pos = mb_strpos( $ctx_html, $orig_esc );
                    if ( $pos !== false ) {
                        $ctx_html = mb_substr( $ctx_html, 0, $pos ) . $diff_markup . mb_substr( $ctx_html, $pos + mb_strlen( $orig_esc ) );
                    }
                }
            }
            $html .= '<td class="spellmatic-context">' . $ctx_html . '</td>';
            // Type badge.
            $html .= '<td><span class="spellmatic-badge spellmatic-badge--' . esc_attr( $r['error_type'] ) . '">' . esc_html( ucfirst( $r['error_type'] ) ) . '</span></td>';
            // Confidence column.
            $conf_obj = (object) $r;
            $high_conf = Spellmatic_Fixer::is_high_confidence( $conf_obj );
            if ( $high_conf ) {
                $html .= '<td><span class="spellmatic-badge spellmatic-badge--confidence-high" title="' . esc_attr__( 'Spelling error with a close suggestion — safe to auto-fix', 'spellmatic' ) . '">High</span></td>';
            } else {
                $html .= '<td><span class="spellmatic-badge spellmatic-badge--confidence-low" title="' . esc_attr__( 'Grammar, style, or ambiguous — review manually', 'spellmatic' ) . '">Low</span></td>';
            }
            // Status column.
            if ( $is_fixed ) {
                $html .= '<td><span class="spellmatic-status-fixed">&#10003; ' . esc_html__( 'Fixed', 'spellmatic' ) . '</span></td>';
            } elseif ( $is_dismissed ) {
                $html .= '<td><span class="spellmatic-status-dismissed">' . esc_html__( 'Dismissed', 'spellmatic' ) . '</span></td>';
            } else {
                $html .= '<td><span class="spellmatic-status-open">' . esc_html__( 'Open', 'spellmatic' ) . '</span></td>';
            }
            // Actions column.
            if ( $is_fixed ) {
                if ( !empty( $r['history_id'] ) ) {
                    $html .= '<td><button class="button button-small spellmatic-undo-btn" data-id="' . esc_attr( $r['id'] ) . '" data-action="revert">' . esc_html__( 'Undo', 'spellmatic' ) . '</button></td>';
                } else {
                    $html .= '<td></td>';
                }
            } elseif ( $is_dismissed ) {
                $html .= '<td><button class="button button-small spellmatic-restore-btn" data-id="' . esc_attr( $r['id'] ) . '" data-action="restore">' . esc_html__( 'Restore', 'spellmatic' ) . '</button></td>';
            } else {
                $html .= '<td>
                    <button class="button button-small spellmatic-fix-btn" data-id="' . esc_attr( $r['id'] ) . '" data-action="fix">' . esc_html__( 'Fix', 'spellmatic' ) . '</button>
                    <button class="button button-small spellmatic-dismiss-btn" data-id="' . esc_attr( $r['id'] ) . '" data-action="dismiss">' . esc_html__( 'Dismiss', 'spellmatic' ) . '</button>
                </td>';
            }
            $html .= '</tr>';
        }
        $html .= '</tbody></table>';
        return $html;
    }

    // ── Render: Open Source & Services page ─────────────────────────────────
    public function render_open_source() : void {
        if ( !current_user_can( 'manage_options' ) ) {
            wp_die( 'Access denied.' );
        }
        $harper = Spellmatic_Harper::get_version_info();
        ?>
        <div class="wrap spellmatic-wrap">
            <?php 
        $this->render_shared_header( 'open-source' );
        ?>

            <?php 
        /* ── Harper Grammar Engine ─────────────────────────────────── */
        ?>
            <div class="spellmatic-os-section">
                <h2><?php 
        esc_html_e( 'Grammar Engine — Harper', 'spellmatic' );
        ?></h2>
                <p>
                    <?php 
        esc_html_e( 'Spellmatic is powered by Harper, an open-source grammar and spell-check engine licensed under the Apache 2.0 License. Harper runs entirely in your browser — your content never leaves your server during a standard scan.', 'spellmatic' );
        ?>
                </p>
                <p>
                    <?php 
        esc_html_e( 'The Harper project is actively developed by the open-source community. You can explore the source code, report issues, contribute improvements, or learn more about how the engine works on the project\'s GitHub page.', 'spellmatic' );
        ?>
                </p>
                <table class="form-table">
                    <tr>
                        <th><?php 
        esc_html_e( 'Project', 'spellmatic' );
        ?></th>
                        <td>
                            <a href="https://github.com/Automattic/harper" target="_blank" rel="noopener noreferrer">github.com/Automattic/harper</a>
                            &nbsp;·&nbsp;
                            <a href="https://writewithharper.com" target="_blank" rel="noopener noreferrer">writewithharper.com</a>
                        </td>
                    </tr>
                    <tr>
                        <th><?php 
        esc_html_e( 'License', 'spellmatic' );
        ?></th>
                        <td>
                            <a href="https://github.com/Automattic/harper/blob/master/LICENSE" target="_blank" rel="noopener noreferrer"><?php 
        esc_html_e( 'Apache License 2.0', 'spellmatic' );
        ?></a>
                            — <?php 
        esc_html_e( 'Full license text included in the plugin as LICENSE-harper.txt.', 'spellmatic' );
        ?>
                        </td>
                    </tr>
                    <tr>
                        <th><?php 
        esc_html_e( 'Installed Version', 'spellmatic' );
        ?></th>
                        <td>
                            <strong id="spellmatic-harper-active-version"><?php 
        echo esc_html( $harper['active'] );
        ?></strong>
                            <?php 
        if ( $harper['hasUpgrade'] ) {
            ?>
                                <span class="description">(<?php 
            /* translators: %s: bundled version number */
            printf( esc_html__( 'upgraded from bundled v%s', 'spellmatic' ), esc_html( $harper['bundled'] ) );
            ?>)</span>
                            <?php 
        } else {
            ?>
                                <span class="description">(<?php 
            esc_html_e( 'bundled with plugin', 'spellmatic' );
            ?>)</span>
                            <?php 
        }
        ?>
                        </td>
                    </tr>
                </table>

                <?php 
        /* ── Manual Engine Update Info ─────────────────────────── */
        ?>
                <div class="spellmatic-harper-update-panel" id="spellmatic-harper-update-panel" style="background:#f8f9fa; border:1px solid #ddd; border-radius:6px; padding:16px 20px; margin-top:12px; max-width:700px;">
                    <h3 style="margin-top:0;"><?php 
        esc_html_e( 'Updating the Grammar Engine', 'spellmatic' );
        ?></h3>
                    <p class="description">
                        <?php 
        esc_html_e( 'A stable version of Harper ships bundled with Spellmatic. If you\'d like to use a newer version with improved dictionaries and grammar rules, you can update manually:', 'spellmatic' );
        ?>
                    </p>
                    <ol class="description" style="margin:8px 0 8px 20px;">
                        <li>
                            <?php 
        printf( 
            /* translators: 1: opening link tag, 2: closing link tag */
            esc_html__( 'Download the latest %1$sharper.js from npm%2$s (look in the dist/ folder of the package).', 'spellmatic' ),
            '<a href="https://www.npmjs.com/package/harper.js" target="_blank" rel="noopener noreferrer">',
            '</a>'
         );
        ?>
                        </li>
                        <li>
                            <?php 
        printf( 
            /* translators: %s: file path */
            esc_html__( 'Upload the file to %s on your server (create the spellmatic folder if it does not exist).', 'spellmatic' ),
            '<code>wp-content/uploads/spellmatic/harper.js</code>'
         );
        ?>
                        </li>
                        <li><?php 
        esc_html_e( 'The plugin will automatically detect and use the uploaded file on the next page load.', 'spellmatic' );
        ?></li>
                        <li><?php 
        esc_html_e( 'To revert, simply delete the file — the plugin will fall back to the bundled version.', 'spellmatic' );
        ?></li>
                    </ol>
                    <?php 
        if ( $harper['hasUpgrade'] ) {
            ?>
                    <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-top:12px;">
                        <button id="spellmatic-harper-revert-btn" class="button">
                            <?php 
            /* translators: %s: bundled version number */
            printf( esc_html__( 'Revert to Bundled v%s', 'spellmatic' ), esc_html( $harper['bundled'] ) );
            ?>
                        </button>
                        <span id="spellmatic-harper-update-status" class="spellmatic-dict-status" aria-live="polite"></span>
                    </div>
                    <?php 
        }
        ?>
                </div>
            </div>

            <hr style="margin:32px 0;">

            <?php 
        /* ── External Services ─────────────────────────────────────── */
        ?>
            <div class="spellmatic-os-section">
                <h2><?php 
        esc_html_e( 'External Services', 'spellmatic' );
        ?></h2>
                <p>
                    <?php 
        esc_html_e( 'Spellmatic connects to the following external services. Each connection is documented below with details about what data is sent, when it is sent, and links to each provider\'s terms and privacy policies.', 'spellmatic' );
        ?>
                </p>

                <?php 
        /* ── AI Providers ───────────────────────────────────────── */
        ?>
                <h3><?php 
        esc_html_e( 'AI-Powered Grammar Analysis (Optional, BYOK)', 'spellmatic' );
        ?></h3>
                <p>
                    <?php 
        esc_html_e( 'When you connect an AI provider via Settings and initiate an AI scan, the text content of your posts and pages is sent to your chosen provider for contextual grammar analysis. This only happens when you explicitly click "Scan" with AI enabled. API keys are encrypted with AES-256-CBC and stored on your server — they are never exposed to the browser or transmitted to Spellmatic.', 'spellmatic' );
        ?>
                </p>

                <table class="widefat striped" style="max-width:800px;">
                    <thead>
                        <tr>
                            <th><?php 
        esc_html_e( 'Provider', 'spellmatic' );
        ?></th>
                            <th><?php 
        esc_html_e( 'When Data Is Sent', 'spellmatic' );
        ?></th>
                            <th><?php 
        esc_html_e( 'Terms of Service', 'spellmatic' );
        ?></th>
                            <th><?php 
        esc_html_e( 'Privacy Policy', 'spellmatic' );
        ?></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td><strong><?php 
        esc_html_e( 'OpenAI', 'spellmatic' );
        ?></strong></td>
                            <td><?php 
        esc_html_e( 'User-initiated AI scan with OpenAI selected', 'spellmatic' );
        ?></td>
                            <td><a href="https://openai.com/policies/terms-of-use" target="_blank" rel="noopener noreferrer">openai.com/policies/terms-of-use</a></td>
                            <td><a href="https://openai.com/policies/privacy-policy" target="_blank" rel="noopener noreferrer">openai.com/policies/privacy-policy</a></td>
                        </tr>
                        <tr>
                            <td><strong><?php 
        esc_html_e( 'Anthropic (Claude)', 'spellmatic' );
        ?></strong></td>
                            <td><?php 
        esc_html_e( 'User-initiated AI scan with Claude selected', 'spellmatic' );
        ?></td>
                            <td><a href="https://www.anthropic.com/legal/consumer-terms" target="_blank" rel="noopener noreferrer">anthropic.com/legal/consumer-terms</a></td>
                            <td><a href="https://www.anthropic.com/legal/privacy" target="_blank" rel="noopener noreferrer">anthropic.com/legal/privacy</a></td>
                        </tr>
                        <tr>
                            <td><strong><?php 
        esc_html_e( 'Google (Gemini)', 'spellmatic' );
        ?></strong></td>
                            <td><?php 
        esc_html_e( 'User-initiated AI scan with Gemini selected', 'spellmatic' );
        ?></td>
                            <td><a href="https://ai.google.dev/gemini-api/terms" target="_blank" rel="noopener noreferrer">ai.google.dev/gemini-api/terms</a></td>
                            <td><a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer">policies.google.com/privacy</a></td>
                        </tr>
                        <tr>
                            <td><strong><?php 
        esc_html_e( 'xAI (Grok)', 'spellmatic' );
        ?></strong></td>
                            <td><?php 
        esc_html_e( 'User-initiated AI scan with Grok selected', 'spellmatic' );
        ?></td>
                            <td><a href="https://x.ai/legal/terms-of-service" target="_blank" rel="noopener noreferrer">x.ai/legal/terms-of-service</a></td>
                            <td><a href="https://x.ai/legal/privacy-policy" target="_blank" rel="noopener noreferrer">x.ai/legal/privacy-policy</a></td>
                        </tr>
                        <tr>
                            <td><strong><?php 
        esc_html_e( 'OpenRouter', 'spellmatic' );
        ?></strong></td>
                            <td><?php 
        esc_html_e( 'User-initiated AI scan with OpenRouter selected', 'spellmatic' );
        ?></td>
                            <td><a href="https://openrouter.ai/terms" target="_blank" rel="noopener noreferrer">openrouter.ai/terms</a></td>
                            <td><a href="https://openrouter.ai/privacy" target="_blank" rel="noopener noreferrer">openrouter.ai/privacy</a></td>
                        </tr>
                    </tbody>
                </table>

                <?php 
        /* ── Freemius SDK ───────────────────────────────────────── */
        ?>
                <h3 style="margin-top:24px;"><?php 
        esc_html_e( 'Licensing & Analytics — Freemius', 'spellmatic' );
        ?></h3>
                <p>
                    <?php 
        esc_html_e( 'Spellmatic uses the Freemius SDK for license validation, plan management, and optional opt-in usage analytics. You are asked for consent during plugin activation.', 'spellmatic' );
        ?>
                </p>
                <table class="widefat striped" style="max-width:800px;">
                    <thead>
                        <tr>
                            <th><?php 
        esc_html_e( 'Service', 'spellmatic' );
        ?></th>
                            <th><?php 
        esc_html_e( 'Purpose', 'spellmatic' );
        ?></th>
                            <th><?php 
        esc_html_e( 'Terms of Service', 'spellmatic' );
        ?></th>
                            <th><?php 
        esc_html_e( 'Privacy Policy', 'spellmatic' );
        ?></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td><strong><?php 
        esc_html_e( 'Freemius', 'spellmatic' );
        ?></strong></td>
                            <td><?php 
        esc_html_e( 'License validation, plan management, opt-in analytics', 'spellmatic' );
        ?></td>
                            <td><a href="https://freemius.com/terms/" target="_blank" rel="noopener noreferrer">freemius.com/terms</a></td>
                            <td><a href="https://freemius.com/privacy/" target="_blank" rel="noopener noreferrer">freemius.com/privacy</a></td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <hr style="margin:32px 0;">

            <?php 
        /* ── Plugin Info ────────────────────────────────────────────── */
        ?>
            <div class="spellmatic-os-section">
                <h2><?php 
        esc_html_e( 'About Spellmatic', 'spellmatic' );
        ?></h2>
                <table class="form-table">
                    <tr>
                        <th><?php 
        esc_html_e( 'Plugin Version', 'spellmatic' );
        ?></th>
                        <td><?php 
        echo esc_html( SPELLMATIC_VERSION );
        ?></td>
                    </tr>
                    <tr>
                        <th><?php 
        esc_html_e( 'Website', 'spellmatic' );
        ?></th>
                        <td><a href="https://spellmatic.com" target="_blank" rel="noopener noreferrer">spellmatic.com</a></td>
                    </tr>
                    <tr>
                        <th><?php 
        esc_html_e( 'Support', 'spellmatic' );
        ?></th>
                        <td><a href="https://spellmatic.com/support" target="_blank" rel="noopener noreferrer"><?php 
        esc_html_e( 'Get Help', 'spellmatic' );
        ?></a></td>
                    </tr>
                    <tr>
                        <th><?php 
        esc_html_e( 'License', 'spellmatic' );
        ?></th>
                        <td><?php 
        esc_html_e( 'GPL v2 or later', 'spellmatic' );
        ?></td>
                    </tr>
                </table>
            </div>

        </div><!-- .spellmatic-wrap -->
        <?php 
    }

    // ── Render: Plans page ──────────────────────────────────────────────────
    public function render_upgrade() : void {
    }

}
