<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Google Search Console integration module.
 *
 * Provides OAuth 2.0 connection, search performance data fetching,
 * admin tab rendering, and a dashboard widget.
 *
 * @since 0.2.4
 */
if (class_exists('SEO_Fury_Google_Search_Console')) {
    return;
}

class SEO_Fury_Google_Search_Console {

    /** Google OAuth endpoints */
    private $auth_url      = 'https://accounts.google.com/o/oauth2/v2/auth';
    private $token_url     = 'https://oauth2.googleapis.com/token';
    private $api_base      = 'https://www.googleapis.com/webmasters/v3';
    private $searchanalytics_url = 'https://searchconsole.googleapis.com/webmasters/v3';

    /** Required OAuth scopes */
    private $scopes = 'https://www.googleapis.com/auth/webmasters.readonly';

    /** Option keys */
    private $tokens_option   = 'seo_fury_gsc_tokens';
    private $property_option = 'seo_fury_gsc_property';
    private $client_id_opt   = 'seo_fury_gsc_client_id';
    private $client_secret_opt = 'seo_fury_gsc_client_secret';

    /** Transient prefix for cached data */
    private $cache_prefix = 'sf_gsc_';
    private $cache_ttl    = 3600; // 1 hour

    /** OAuth proxy URL (hosted on seo-fury.com) */
    private $proxy_url = 'https://seo-fury.com/oauth/';

    public function __construct() {
        // AJAX handlers
        add_action( 'wp_ajax_seo_fury_gsc_authorize', array( $this, 'ajax_authorize' ) );
        add_action( 'wp_ajax_seo_fury_gsc_callback', array( $this, 'ajax_callback' ) );
        add_action( 'wp_ajax_seo_fury_gsc_disconnect', array( $this, 'ajax_disconnect' ) );
        add_action( 'wp_ajax_seo_fury_gsc_fetch_data', array( $this, 'ajax_fetch_data' ) );
        add_action( 'wp_ajax_seo_fury_gsc_select_property', array( $this, 'ajax_select_property' ) );
        add_action( 'wp_ajax_seo_fury_gsc_save_credentials', array( $this, 'ajax_save_credentials' ) );
        add_action( 'wp_ajax_seo_fury_gsc_comparison', array( $this, 'ajax_fetch_comparison' ) );
        add_action( 'wp_ajax_seo_fury_gsc_export_csv', array( $this, 'ajax_export_csv' ) );
        add_action( 'wp_ajax_seo_fury_gsc_post_stats', array( $this, 'ajax_post_url_stats' ) );

        // OAuth proxy handlers (one-click connect)
        add_action( 'wp_ajax_seo_fury_gsc_proxy_connect', array( $this, 'ajax_proxy_connect' ) );
        add_action( 'wp_ajax_seo_fury_gsc_proxy_callback', array( $this, 'ajax_proxy_callback' ) );

        // Enqueue Chart.js on plugin admin page
        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_chartjs' ) );

        // Dashboard widget
        add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widget' ) );
    }

    /**
     * Enqueue Chart.js on the GSC admin tab.
     */
    public function enqueue_chartjs( $hook ) {
        if ( $hook !== 'toplevel_page_seo-fury' ) {
            return;
        }
        $tab = isset( $_GET['tab'] ) ? sanitize_text_field( $_GET['tab'] ) : '';
        $subtab = isset( $_GET['subtab'] ) ? sanitize_text_field( $_GET['subtab'] ) : '';
        if ( $tab === 'webmasters' && $subtab === 'gsc' ) {
            wp_enqueue_script( 'chartjs', 'https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js', array(), '4.4.7', true );
        }
        // Also load on dashboard for the widget
        if ( $hook === 'index.php' ) {
            return;
        }
    }

    // =========================================================================
    // Encryption helpers
    // =========================================================================

    /**
     * Encrypt a value using SEO_Fury_Encryption if available.
     */
    private function encrypt( $value ) {
        if ( class_exists( 'SEO_Fury_Encryption' ) && ! empty( $value ) ) {
            return SEO_Fury_Encryption::encrypt( $value );
        }
        return $value;
    }

    /**
     * Decrypt a value using SEO_Fury_Encryption if available.
     */
    private function decrypt( $value ) {
        if ( ! class_exists( 'SEO_Fury_Encryption' ) || empty( $value ) ) {
            return $value;
        }
        $dec = SEO_Fury_Encryption::decrypt( $value );
        if ( $dec !== '' ) {
            return $dec;
        }
        // Decryption failed. If the value looks like our ciphertext (v2 prefix
        // or a base64 blob), it is undecryptable — e.g. the WordPress salt was
        // rotated. Return '' so the caller cleanly reconnects, instead of
        // emitting the raw ciphertext as a credential. Only pass through values
        // that were clearly never encrypted (legacy plaintext).
        if ( strncmp( $value, 'v2:', 3 ) === 0 ) {
            return '';
        }
        $decoded = base64_decode( $value, true );
        if ( $decoded !== false && strlen( $decoded ) > 16 ) {
            return '';
        }
        return $value;
    }

    // =========================================================================
    // Credential helpers
    // =========================================================================

    private function get_client_id() {
        return $this->decrypt( get_option( $this->client_id_opt, '' ) );
    }

    private function get_client_secret() {
        return $this->decrypt( get_option( $this->client_secret_opt, '' ) );
    }

    private function get_redirect_uri() {
        return admin_url( 'admin-ajax.php?action=seo_fury_gsc_callback' );
    }

    // =========================================================================
    // Token management
    // =========================================================================

    /**
     * Get stored tokens (decrypted).
     *
     * @return array|null Array with access_token, refresh_token, expires_at keys, or null.
     */
    private function get_tokens() {
        $raw = get_option( $this->tokens_option, '' );
        if ( empty( $raw ) ) {
            return null;
        }
        $json = $this->decrypt( $raw );
        $tokens = json_decode( $json, true );
        if ( ! is_array( $tokens ) || empty( $tokens['access_token'] ) ) {
            return null;
        }
        return $tokens;
    }

    /**
     * Save tokens (encrypted).
     */
    private function save_tokens( $tokens ) {
        $json = wp_json_encode( $tokens );
        update_option( $this->tokens_option, $this->encrypt( $json ), false );
    }

    /**
     * Delete tokens and cached data.
     */
    private function delete_tokens() {
        delete_option( $this->tokens_option );
        delete_option( $this->property_option );
        $this->clear_cache();
    }

    /**
     * Check if we have a valid connection.
     */
    public function is_connected() {
        $tokens = $this->get_tokens();
        return ! empty( $tokens ) && ! empty( $tokens['refresh_token'] );
    }

    /**
     * Get a valid access token, refreshing if necessary.
     *
     * @return string|WP_Error
     */
    public function get_access_token() {
        $tokens = $this->get_tokens();
        if ( ! $tokens ) {
            return new WP_Error( 'not_connected', __( 'Google Search Console is not connected.', 'seo-fury' ) );
        }

        // If token is still valid (with 60s buffer)
        if ( ! empty( $tokens['expires_at'] ) && $tokens['expires_at'] > ( time() + 60 ) ) {
            return $tokens['access_token'];
        }

        // Refresh the token
        if ( empty( $tokens['refresh_token'] ) ) {
            $this->delete_tokens();
            return new WP_Error( 'no_refresh', __( 'No refresh token available. Please reconnect.', 'seo-fury' ) );
        }

        $response = wp_remote_post( $this->token_url, array(
            'timeout' => 30,
            'body'    => array(
                'client_id'     => $this->get_client_id(),
                'client_secret' => $this->get_client_secret(),
                'refresh_token' => $tokens['refresh_token'],
                'grant_type'    => 'refresh_token',
            ),
        ) );

        if ( is_wp_error( $response ) ) {
            return $response;
        }

        $code = wp_remote_retrieve_response_code( $response );
        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( $code !== 200 || empty( $body['access_token'] ) ) {
            $msg = isset( $body['error_description'] ) ? $body['error_description'] : __( 'Token refresh failed.', 'seo-fury' );
            // If refresh permanently fails, disconnect
            if ( isset( $body['error'] ) && $body['error'] === 'invalid_grant' ) {
                $this->delete_tokens();
            }
            return new WP_Error( 'refresh_failed', $msg );
        }

        $tokens['access_token'] = $body['access_token'];
        $tokens['expires_at']   = time() + intval( $body['expires_in'] );
        // Google may return a new refresh token
        if ( ! empty( $body['refresh_token'] ) ) {
            $tokens['refresh_token'] = $body['refresh_token'];
        }
        $this->save_tokens( $tokens );

        return $tokens['access_token'];
    }

    // =========================================================================
    // API helpers
    // =========================================================================

    /**
     * Make an authenticated GET request to the GSC API.
     */
    private function api_get( $endpoint ) {
        $token = $this->get_access_token();
        if ( is_wp_error( $token ) ) {
            return $token;
        }

        $response = wp_remote_get( $this->api_base . $endpoint, array(
            'timeout' => 30,
            'headers' => array(
                'Authorization' => 'Bearer ' . $token,
                'Accept'        => 'application/json',
            ),
        ) );

        if ( is_wp_error( $response ) ) {
            return $response;
        }

        $code = wp_remote_retrieve_response_code( $response );
        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( $code !== 200 ) {
            $msg = isset( $body['error']['message'] ) ? $body['error']['message'] : __( 'API request failed.', 'seo-fury' );
            return new WP_Error( 'api_error', $msg );
        }

        return $body;
    }

    /**
     * Make an authenticated POST request to the Search Analytics API.
     */
    private function api_post( $endpoint, $payload ) {
        $token = $this->get_access_token();
        if ( is_wp_error( $token ) ) {
            return $token;
        }

        $url = $this->searchanalytics_url . $endpoint;

        $response = wp_remote_post( $url, array(
            'timeout' => 30,
            'headers' => array(
                'Authorization' => 'Bearer ' . $token,
                'Content-Type'  => 'application/json',
                'Accept'        => 'application/json',
            ),
            'body' => wp_json_encode( $payload ),
        ) );

        if ( is_wp_error( $response ) ) {
            return $response;
        }

        $code = wp_remote_retrieve_response_code( $response );
        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( $code !== 200 ) {
            $msg = isset( $body['error']['message'] ) ? $body['error']['message'] : __( 'API request failed.', 'seo-fury' );
            return new WP_Error( 'api_error', $msg );
        }

        return $body;
    }

    // =========================================================================
    // Data fetching
    // =========================================================================

    /**
     * Get list of verified GSC properties.
     *
     * @return array|WP_Error
     */
    public function get_properties() {
        $result = $this->api_get( '/sites' );
        if ( is_wp_error( $result ) ) {
            return $result;
        }
        $sites = array();
        if ( ! empty( $result['siteEntry'] ) ) {
            foreach ( $result['siteEntry'] as $site ) {
                if ( isset( $site['permissionLevel'] ) && $site['permissionLevel'] !== 'siteUnverifiedUser' ) {
                    $sites[] = array(
                        'url'   => $site['siteUrl'],
                        'level' => $site['permissionLevel'],
                    );
                }
            }
        }
        return $sites;
    }

    /**
     * Get currently selected property URL.
     */
    public function get_selected_property() {
        return get_option( $this->property_option, '' );
    }

    /**
     * Fetch search performance data.
     *
     * @param string $range     Date range: '7d', '28d', '3m', or 'custom'.
     * @param string $dimension 'query', 'page', or 'date'.
     * @param int    $row_limit Max rows to fetch.
     * @param string $start_date Custom start (Y-m-d). Only used when $range = 'custom'.
     * @param string $end_date   Custom end (Y-m-d). Only used when $range = 'custom'.
     * @return array|WP_Error
     */
    public function fetch_performance( $range = '28d', $dimension = 'query', $row_limit = 20, $start_date = '', $end_date = '' ) {
        $property = $this->get_selected_property();
        if ( empty( $property ) ) {
            return new WP_Error( 'no_property', __( 'No GSC property selected.', 'seo-fury' ) );
        }

        // Calculate date range
        $dates = $this->calculate_date_range( $range, $start_date, $end_date );

        // Check cache
        $cache_key = $this->cache_prefix . md5( $property . $dimension . $dates['start'] . $dates['end'] . $row_limit );
        $cached = get_transient( $cache_key );
        if ( false !== $cached ) {
            $cached['cached'] = true;
            return $cached;
        }

        $payload = array(
            'startDate'  => $dates['start'],
            'endDate'    => $dates['end'],
            'dimensions' => array( $dimension ),
            'rowLimit'   => intval( $row_limit ),
        );

        $encoded_property = rawurlencode( $property );
        $result = $this->api_post( '/sites/' . $encoded_property . '/searchAnalytics/query', $payload );

        if ( is_wp_error( $result ) ) {
            return $result;
        }

        $rows = isset( $result['rows'] ) ? $result['rows'] : array();

        $data = array(
            'rows'       => $rows,
            'start_date' => $dates['start'],
            'end_date'   => $dates['end'],
            'dimension'  => $dimension,
            'cached'     => false,
        );

        // Cache result
        set_transient( $cache_key, $data, $this->cache_ttl );

        return $data;
    }

    /**
     * Fetch totals (summary) for the date range.
     *
     * @param string $range Date range.
     * @return array|WP_Error Array with clicks, impressions, ctr, position.
     */
    public function fetch_totals( $range = '28d' ) {
        $dates = $this->calculate_date_range( $range );
        return $this->totals_for_dates( $dates['start'], $dates['end'] );
    }

    /**
     * Totals for an explicit date window (used by fetch_totals and the
     * period-over-period comparison).
     */
    private function totals_for_dates( $start, $end ) {
        $property = $this->get_selected_property();
        if ( empty( $property ) ) {
            return new WP_Error( 'no_property', __( 'No GSC property selected.', 'seo-fury' ) );
        }

        $cache_key = $this->cache_prefix . 'totals_' . md5( $property . $start . $end );
        $cached = get_transient( $cache_key );
        if ( false !== $cached ) {
            return $cached;
        }

        $payload = array( 'startDate' => $start, 'endDate' => $end );
        $result = $this->api_post( '/sites/' . rawurlencode( $property ) . '/searchAnalytics/query', $payload );
        if ( is_wp_error( $result ) ) {
            return $result;
        }

        $rows = isset( $result['rows'] ) ? $result['rows'] : array();
        $totals = array( 'clicks' => 0, 'impressions' => 0, 'ctr' => 0, 'position' => 0 );
        if ( ! empty( $rows[0] ) ) {
            $totals['clicks']      = intval( $rows[0]['clicks'] );
            $totals['impressions'] = intval( $rows[0]['impressions'] );
            $totals['ctr']         = floatval( $rows[0]['ctr'] );
            $totals['position']    = floatval( $rows[0]['position'] );
        }

        set_transient( $cache_key, $totals, $this->cache_ttl );
        return $totals;
    }

    /**
     * Totals for a single page URL over the range (page-filtered query). Used
     * by the per-post snippet in the editor.
     */
    public function get_url_stats( $url, $range = '28d' ) {
        $property = $this->get_selected_property();
        if ( empty( $property ) ) {
            return new WP_Error( 'no_property', __( 'No GSC property selected.', 'seo-fury' ) );
        }

        $dates     = $this->calculate_date_range( $range );
        $cache_key = $this->cache_prefix . 'url_' . md5( $property . $url . $dates['start'] . $dates['end'] );
        $cached    = get_transient( $cache_key );
        if ( false !== $cached ) {
            return $cached;
        }

        $payload = array(
            'startDate'             => $dates['start'],
            'endDate'               => $dates['end'],
            'dimensionFilterGroups' => array(
                array(
                    'filters' => array(
                        array( 'dimension' => 'page', 'operator' => 'equals', 'expression' => $url ),
                    ),
                ),
            ),
        );

        $result = $this->api_post( '/sites/' . rawurlencode( $property ) . '/searchAnalytics/query', $payload );
        if ( is_wp_error( $result ) ) {
            return $result;
        }

        $rows   = isset( $result['rows'] ) ? $result['rows'] : array();
        $totals = array( 'clicks' => 0, 'impressions' => 0, 'ctr' => 0, 'position' => 0, 'has_data' => false );
        if ( ! empty( $rows[0] ) ) {
            $totals['clicks']      = intval( $rows[0]['clicks'] );
            $totals['impressions'] = intval( $rows[0]['impressions'] );
            $totals['ctr']         = floatval( $rows[0]['ctr'] );
            $totals['position']    = floatval( $rows[0]['position'] );
            $totals['has_data']    = true;
        }

        set_transient( $cache_key, $totals, $this->cache_ttl );
        return $totals;
    }

    /**
     * AJAX: per-post Search Console snippet for the editor.
     */
    public function ajax_post_url_stats() {
        check_ajax_referer( 'seo_fury_gsc_post_stats', 'nonce' );
        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'seo-fury' ) ) );
        }
        if ( ! $this->is_connected() ) {
            wp_send_json_success( array( 'connected' => false ) );
        }
        $url = isset( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : '';
        if ( $url === '' ) {
            wp_send_json_error( array( 'message' => __( 'No URL provided.', 'seo-fury' ) ) );
        }
        $stats = $this->get_url_stats( $url );
        if ( is_wp_error( $stats ) ) {
            wp_send_json_error( array( 'message' => $stats->get_error_message() ) );
        }
        $stats['connected'] = true;
        wp_send_json_success( $stats );
    }

    /**
     * Current totals plus the immediately-preceding period of equal length,
     * for period-over-period comparison.
     */
    public function fetch_comparison( $range = '28d' ) {
        $current = $this->fetch_totals( $range );
        if ( is_wp_error( $current ) ) {
            return $current;
        }

        $dates    = $this->calculate_date_range( $range );
        $start_ts = strtotime( $dates['start'] );
        $end_ts   = strtotime( $dates['end'] );
        $len      = max( 1, (int) round( ( $end_ts - $start_ts ) / DAY_IN_SECONDS ) + 1 );
        $prev_end   = gmdate( 'Y-m-d', $start_ts - DAY_IN_SECONDS );
        $prev_start = gmdate( 'Y-m-d', $start_ts - DAY_IN_SECONDS - ( $len - 1 ) * DAY_IN_SECONDS );

        $previous = $this->totals_for_dates( $prev_start, $prev_end );
        if ( is_wp_error( $previous ) ) {
            $previous = array( 'clicks' => 0, 'impressions' => 0, 'ctr' => 0, 'position' => 0 );
        }

        return array(
            'current'         => $current,
            'previous'        => $previous,
            'period'          => array( 'start' => $dates['start'], 'end' => $dates['end'] ),
            'previous_period' => array( 'start' => $prev_start, 'end' => $prev_end ),
        );
    }

    /**
     * Calculate start/end dates from a range string.
     */
    private function calculate_date_range( $range, $custom_start = '', $custom_end = '' ) {
        $end = gmdate( 'Y-m-d', strtotime( '-3 days' ) ); // GSC data has ~3 day delay

        switch ( $range ) {
            case '7d':
                $start = gmdate( 'Y-m-d', strtotime( '-10 days' ) ); // 7 days ending 3 days ago
                break;
            case '3m':
                $start = gmdate( 'Y-m-d', strtotime( '-93 days' ) ); // ~3 months ending 3 days ago
                break;
            case 'custom':
                $start = ! empty( $custom_start ) ? sanitize_text_field( $custom_start ) : gmdate( 'Y-m-d', strtotime( '-31 days' ) );
                $end   = ! empty( $custom_end ) ? sanitize_text_field( $custom_end ) : $end;
                break;
            case '28d':
            default:
                $start = gmdate( 'Y-m-d', strtotime( '-31 days' ) ); // 28 days ending 3 days ago
                break;
        }

        return array( 'start' => $start, 'end' => $end );
    }

    /**
     * Clear all cached GSC data.
     */
    private function clear_cache() {
        global $wpdb;
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
                '_transient_' . $this->cache_prefix . '%'
            )
        );
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
                '_transient_timeout_' . $this->cache_prefix . '%'
            )
        );
    }

    // =========================================================================
    // AJAX Handlers
    // =========================================================================

    /**
     * AJAX: Save client credentials.
     */
    public function ajax_save_credentials() {
        check_ajax_referer( 'seo_fury_gsc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'seo-fury' ) ) );
        }

        $client_id     = isset( $_POST['client_id'] ) ? sanitize_text_field( wp_unslash( $_POST['client_id'] ) ) : '';
        $client_secret = isset( $_POST['client_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['client_secret'] ) ) : '';

        if ( empty( $client_id ) || empty( $client_secret ) ) {
            wp_send_json_error( array( 'message' => __( 'Both Client ID and Client Secret are required.', 'seo-fury' ) ) );
        }

        update_option( $this->client_id_opt, $this->encrypt( $client_id ), false );
        update_option( $this->client_secret_opt, $this->encrypt( $client_secret ), false );

        wp_send_json_success( array( 'message' => __( 'Credentials saved.', 'seo-fury' ) ) );
    }

    /**
     * AJAX: Start OAuth proxy flow (one-click connect).
     * Generates URL to redirect user to the proxy server.
     */
    public function ajax_proxy_connect() {
        check_ajax_referer( 'seo_fury_gsc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'seo-fury' ) ) );
        }

        // Generate nonce for CSRF protection
        $proxy_nonce = wp_generate_password( 32, false );
        set_transient( 'seo_fury_gsc_proxy_nonce', $proxy_nonce, 600 ); // 10 min

        // Return URL: where the proxy will redirect after Google OAuth
        $return_url = admin_url( 'admin-ajax.php?action=seo_fury_gsc_proxy_callback' );

        // Build proxy connect URL
        $proxy_connect_url = add_query_arg( array(
            'return_url' => rawurlencode( $return_url ),
            'nonce'      => $proxy_nonce,
        ), $this->proxy_url . 'connect.php' );

        wp_send_json_success( array( 'redirect_url' => $proxy_connect_url ) );
    }

    /**
     * AJAX: Handle OAuth proxy callback.
     * Called when proxy redirects back with a proxy_code.
     * Exchanges proxy_code for tokens via server-to-server request.
     */
    public function ajax_proxy_callback() {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'Permission denied.', 'seo-fury' ) );
        }

        // Check for error from proxy
        if ( isset( $_GET['gsc_error'] ) ) {
            $error = sanitize_text_field( wp_unslash( $_GET['gsc_error'] ) );
            wp_redirect( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc&gsc_error=' . urlencode( $error ) ) );
            exit;
        }

        $proxy_code = isset( $_GET['proxy_code'] ) ? sanitize_text_field( wp_unslash( $_GET['proxy_code'] ) ) : '';
        $nonce      = isset( $_GET['nonce'] ) ? sanitize_text_field( wp_unslash( $_GET['nonce'] ) ) : '';

        // Verify nonce
        $stored_nonce = get_transient( 'seo_fury_gsc_proxy_nonce' );
        if ( empty( $nonce ) || $nonce !== $stored_nonce ) {
            wp_die( esc_html__( 'Invalid OAuth state. Please try again.', 'seo-fury' ) );
        }
        delete_transient( 'seo_fury_gsc_proxy_nonce' );

        if ( empty( $proxy_code ) ) {
            wp_die( esc_html__( 'No proxy code received.', 'seo-fury' ) );
        }

        // Exchange proxy_code for tokens (server-to-server request)
        $response = wp_remote_post( $this->proxy_url . 'token.php', array(
            'timeout' => 30,
            'body'    => array(
                'proxy_code' => $proxy_code,
                'site_url'   => home_url(),
            ),
        ) );

        if ( is_wp_error( $response ) ) {
            wp_redirect( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc&gsc_error=' . urlencode( $response->get_error_message() ) ) );
            exit;
        }

        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( empty( $body['success'] ) || empty( $body['data']['access_token'] ) ) {
            $error_msg = isset( $body['error'] ) ? $body['error'] : __( 'Failed to retrieve tokens from proxy.', 'seo-fury' );
            wp_redirect( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc&gsc_error=' . urlencode( $error_msg ) ) );
            exit;
        }

        $data = $body['data'];

        // Store client credentials from proxy (needed for token refresh)
        if ( ! empty( $data['client_id'] ) ) {
            update_option( $this->client_id_opt, $this->encrypt( $data['client_id'] ), false );
        }
        if ( ! empty( $data['client_secret'] ) ) {
            update_option( $this->client_secret_opt, $this->encrypt( $data['client_secret'] ), false );
        }

        // Store tokens
        $tokens = array(
            'access_token'  => $data['access_token'],
            'refresh_token' => isset( $data['refresh_token'] ) ? $data['refresh_token'] : '',
            'expires_at'    => time() + intval( isset( $data['expires_in'] ) ? $data['expires_in'] : 3600 ),
        );
        $this->save_tokens( $tokens );

        // Redirect to GSC tab with success
        wp_redirect( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc&gsc_connected=1' ) );
        exit;
    }

    /**
     * AJAX: Generate OAuth authorization URL.
     */
    public function ajax_authorize() {
        check_ajax_referer( 'seo_fury_gsc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'seo-fury' ) ) );
        }

        $client_id = $this->get_client_id();
        if ( empty( $client_id ) ) {
            wp_send_json_error( array( 'message' => __( 'Please save your Client ID first.', 'seo-fury' ) ) );
        }

        // Generate a random per-user state token (CSRF protection). A real
        // random value (not a time-bound nonce) stored per user avoids the
        // "Invalid OAuth state" dead-end when the nonce tick rolls or two
        // attempts overlap.
        $state = wp_generate_password( 32, false );
        set_transient( 'seo_fury_gsc_oauth_state_' . get_current_user_id(), $state, 900 ); // 15 min

        $auth_url = add_query_arg( array(
            'client_id'     => $client_id,
            'redirect_uri'  => $this->get_redirect_uri(),
            'response_type' => 'code',
            'scope'         => $this->scopes,
            'access_type'   => 'offline',
            'prompt'        => 'consent',
            'state'         => $state,
        ), $this->auth_url );

        wp_send_json_success( array( 'auth_url' => $auth_url ) );
    }

    /**
     * AJAX: Handle OAuth callback (exchange code for tokens).
     * This is called when Google redirects back to admin-ajax.php.
     */
    public function ajax_callback() {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'Permission denied.', 'seo-fury' ) );
        }

        // Verify state. On mismatch (usually an expired session), redirect back
        // to the GSC tab with a friendly message instead of a white wp_die.
        $state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : '';
        $state_key = 'seo_fury_gsc_oauth_state_' . get_current_user_id();
        $stored_state = get_transient( $state_key );
        if ( empty( $state ) || empty( $stored_state ) || ! hash_equals( (string) $stored_state, $state ) ) {
            wp_redirect( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc&gsc_error=' . urlencode( __( 'The connection session expired. Please click Connect again.', 'seo-fury' ) ) ) );
            exit;
        }
        delete_transient( $state_key );

        // Check for error from Google
        if ( isset( $_GET['error'] ) ) {
            $error = sanitize_text_field( wp_unslash( $_GET['error'] ) );
            wp_redirect( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc&gsc_error=' . urlencode( $error ) ) );
            exit;
        }

        $code = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
        if ( empty( $code ) ) {
            wp_die( esc_html__( 'No authorization code received.', 'seo-fury' ) );
        }

        // Exchange code for tokens
        $response = wp_remote_post( $this->token_url, array(
            'timeout' => 30,
            'body'    => array(
                'code'          => $code,
                'client_id'     => $this->get_client_id(),
                'client_secret' => $this->get_client_secret(),
                'redirect_uri'  => $this->get_redirect_uri(),
                'grant_type'    => 'authorization_code',
            ),
        ) );

        if ( is_wp_error( $response ) ) {
            wp_redirect( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc&gsc_error=' . urlencode( $response->get_error_message() ) ) );
            exit;
        }

        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( empty( $body['access_token'] ) ) {
            $msg = isset( $body['error_description'] ) ? $body['error_description'] : __( 'Token exchange failed.', 'seo-fury' );
            wp_redirect( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc&gsc_error=' . urlencode( $msg ) ) );
            exit;
        }

        $tokens = array(
            'access_token'  => $body['access_token'],
            'refresh_token' => isset( $body['refresh_token'] ) ? $body['refresh_token'] : '',
            'expires_at'    => time() + intval( $body['expires_in'] ),
        );

        $this->save_tokens( $tokens );

        // Redirect back to the GSC tab with success
        wp_redirect( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc&gsc_connected=1' ) );
        exit;
    }

    /**
     * AJAX: Disconnect from GSC.
     */
    public function ajax_disconnect() {
        check_ajax_referer( 'seo_fury_gsc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'seo-fury' ) ) );
        }

        $this->delete_tokens();
        wp_send_json_success( array( 'message' => __( 'Disconnected from Google Search Console.', 'seo-fury' ) ) );
    }

    /**
     * AJAX: period-over-period comparison totals.
     */
    public function ajax_fetch_comparison() {
        check_ajax_referer( 'seo_fury_gsc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'seo-fury' ) ) );
        }
        $range = isset( $_POST['range'] ) ? sanitize_text_field( wp_unslash( $_POST['range'] ) ) : '28d';
        if ( ! in_array( $range, array( '7d', '28d', '3m' ), true ) ) {
            $range = '28d';
        }
        $data = $this->fetch_comparison( $range );
        if ( is_wp_error( $data ) ) {
            wp_send_json_error( array( 'message' => $data->get_error_message() ) );
        }
        wp_send_json_success( $data );
    }

    /**
     * AJAX (GET): export the Top Queries or Top Pages table as CSV.
     */
    public function ajax_export_csv() {
        check_ajax_referer( 'seo_fury_gsc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'Permission denied.', 'seo-fury' ) );
        }
        $dimension = isset( $_GET['dimension'] ) ? sanitize_text_field( wp_unslash( $_GET['dimension'] ) ) : 'query';
        if ( ! in_array( $dimension, array( 'query', 'page' ), true ) ) {
            $dimension = 'query';
        }
        $range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '28d';
        if ( ! in_array( $range, array( '7d', '28d', '3m' ), true ) ) {
            $range = '28d';
        }

        $result = $this->fetch_performance( $range, $dimension, 1000 );
        if ( is_wp_error( $result ) ) {
            wp_die( esc_html( $result->get_error_message() ) );
        }
        $rows = isset( $result['rows'] ) ? $result['rows'] : array();

        header( 'Content-Type: text/csv; charset=utf-8' );
        header( 'Content-Disposition: attachment; filename=seo-fury-gsc-' . $dimension . '.csv' );
        $out = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
        fputcsv( $out, array( ucfirst( $dimension ), 'Clicks', 'Impressions', 'CTR', 'Position' ) );
        foreach ( (array) $rows as $r ) {
            $key = isset( $r['keys'][0] ) ? $r['keys'][0] : '';
            fputcsv( $out, array(
                function_exists( 'seo_fury_csv_safe' ) ? seo_fury_csv_safe( $key ) : $key,
                isset( $r['clicks'] ) ? (int) $r['clicks'] : 0,
                isset( $r['impressions'] ) ? (int) $r['impressions'] : 0,
                isset( $r['ctr'] ) ? round( $r['ctr'] * 100, 2 ) . '%' : '',
                isset( $r['position'] ) ? round( $r['position'], 1 ) : '',
            ) );
        }
        fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
        exit;
    }

    /**
     * AJAX: Fetch performance data.
     */
    public function ajax_fetch_data() {
        check_ajax_referer( 'seo_fury_gsc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'seo-fury' ) ) );
        }

        $range     = isset( $_POST['range'] ) ? sanitize_text_field( wp_unslash( $_POST['range'] ) ) : '28d';
        $dimension = isset( $_POST['dimension'] ) ? sanitize_text_field( wp_unslash( $_POST['dimension'] ) ) : 'query';
        $row_limit = isset( $_POST['row_limit'] ) ? absint( $_POST['row_limit'] ) : 20;

        if ( ! in_array( $range, array( '7d', '28d', '3m', 'custom' ), true ) ) {
            $range = '28d';
        }
        if ( ! in_array( $dimension, array( 'query', 'page', 'date' ), true ) ) {
            $dimension = 'query';
        }
        if ( $row_limit < 1 || $row_limit > 25000 ) {
            $row_limit = 20;
        }

        $start_date = isset( $_POST['start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['start_date'] ) ) : '';
        $end_date   = isset( $_POST['end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['end_date'] ) ) : '';

        // Fetch totals
        $totals = $this->fetch_totals( $range );
        if ( is_wp_error( $totals ) ) {
            wp_send_json_error( array( 'message' => $totals->get_error_message() ) );
        }

        // Fetch dimension data
        $perf = $this->fetch_performance( $range, $dimension, $row_limit, $start_date, $end_date );
        if ( is_wp_error( $perf ) ) {
            wp_send_json_error( array( 'message' => $perf->get_error_message() ) );
        }

        // Also fetch date-series for chart if dimension isn't already 'date'
        $chart_data = array();
        if ( $dimension !== 'date' ) {
            $date_perf = $this->fetch_performance( $range, 'date', 500, $start_date, $end_date );
            if ( ! is_wp_error( $date_perf ) ) {
                $chart_data = $date_perf['rows'];
            }
        } else {
            $chart_data = $perf['rows'];
        }

        wp_send_json_success( array(
            'totals'     => $totals,
            'rows'       => $perf['rows'],
            'chart'      => $chart_data,
            'start_date' => $perf['start_date'],
            'end_date'   => $perf['end_date'],
            'dimension'  => $dimension,
        ) );
    }

    /**
     * AJAX: Select a GSC property.
     */
    public function ajax_select_property() {
        check_ajax_referer( 'seo_fury_gsc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'seo-fury' ) ) );
        }

        $property = isset( $_POST['property'] ) ? esc_url_raw( wp_unslash( $_POST['property'] ) ) : '';
        if ( empty( $property ) ) {
            // Also allow sc-domain: prefix
            $property = isset( $_POST['property'] ) ? sanitize_text_field( wp_unslash( $_POST['property'] ) ) : '';
        }

        if ( empty( $property ) ) {
            wp_send_json_error( array( 'message' => __( 'No property specified.', 'seo-fury' ) ) );
        }

        update_option( $this->property_option, $property, false );
        $this->clear_cache();

        wp_send_json_success( array(
            'message'  => __( 'Property selected.', 'seo-fury' ),
            'property' => $property,
        ) );
    }

    // =========================================================================
    // Dashboard Widget
    // =========================================================================

    /**
     * Register the WordPress dashboard widget.
     */
    public function register_dashboard_widget() {
        if ( ! current_user_can( 'manage_options' ) || ! $this->is_connected() ) {
            return;
        }

        $brand = function_exists( 'seo_fury_get_brand_name' ) ? seo_fury_get_brand_name() : 'SEO Fury';

        wp_add_dashboard_widget(
            'seo_fury_gsc_widget',
            /* translators: %s: brand name */
            sprintf( __( '%s - Search Console', 'seo-fury' ), $brand ),
            array( $this, 'render_dashboard_widget' )
        );
    }

    /**
     * Render the dashboard widget content.
     */
    public function render_dashboard_widget() {
        $config_link = '<p><a href="' . esc_url( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc' ) ) . '">'
            . esc_html__( 'Configure Search Console', 'seo-fury' ) . '</a></p>';

        // Avoid any blocking API call when not connected.
        if ( ! $this->is_connected() ) {
            echo '<p>' . esc_html__( 'Connect Google Search Console to see your search performance here.', 'seo-fury' ) . '</p>';
            echo $config_link; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built with esc_url/esc_html above.
            return;
        }

        // Back off after a failure so a broken connection (e.g. token refresh
        // failing) doesn't make a synchronous request on every dashboard load.
        if ( get_transient( 'seo_fury_gsc_widget_backoff' ) ) {
            echo '<p>' . esc_html__( 'Search Console data is temporarily unavailable. It will refresh shortly.', 'seo-fury' ) . '</p>';
            echo $config_link; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built with esc_url/esc_html above.
            return;
        }

        $totals = $this->fetch_totals( '28d' );
        if ( is_wp_error( $totals ) ) {
            set_transient( 'seo_fury_gsc_widget_backoff', 1, 15 * MINUTE_IN_SECONDS );
            echo '<p>' . esc_html( $totals->get_error_message() ) . '</p>';
            echo $config_link; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built with esc_url/esc_html above.
            return;
        }
        ?>
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px;">
            <div style="text-align: center; padding: 10px; background: var(--sf-primary-light,#F0F6FF); border-radius: 6px;">
                <div style="font-size: 22px; font-weight: 700; color: #2563EB;">
                    <?php echo esc_html( number_format_i18n( $totals['clicks'] ) ); ?>
                </div>
                <div style="font-size: 12px; color: var(--sf-gray-500, #64748b); margin-top: 2px;">
                    <?php esc_html_e( 'Clicks', 'seo-fury' ); ?>
                </div>
            </div>
            <div style="text-align: center; padding: 10px; background: var(--sf-success-light,#F0FDF4); border-radius: 6px;">
                <div style="font-size: 22px; font-weight: 700; color: #16A34A;">
                    <?php echo esc_html( number_format_i18n( $totals['impressions'] ) ); ?>
                </div>
                <div style="font-size: 12px; color: var(--sf-gray-500, #64748b); margin-top: 2px;">
                    <?php esc_html_e( 'Impressions', 'seo-fury' ); ?>
                </div>
            </div>
            <div style="text-align: center; padding: 10px; background: var(--sf-warning-light,#FFFBEB); border-radius: 6px;">
                <div style="font-size: 22px; font-weight: 700; color: #D97706;">
                    <?php echo esc_html( number_format( $totals['ctr'] * 100, 1 ) . '%' ); ?>
                </div>
                <div style="font-size: 12px; color: var(--sf-gray-500, #64748b); margin-top: 2px;">
                    <?php esc_html_e( 'Avg CTR', 'seo-fury' ); ?>
                </div>
            </div>
            <div style="text-align: center; padding: 10px; background: var(--sf-gray-50,#FDF2F8); border-radius: 6px;">
                <div style="font-size: 22px; font-weight: 700; color: #DB2777;">
                    <?php echo esc_html( number_format( $totals['position'], 1 ) ); ?>
                </div>
                <div style="font-size: 12px; color: var(--sf-gray-500, #64748b); margin-top: 2px;">
                    <?php esc_html_e( 'Avg Position', 'seo-fury' ); ?>
                </div>
            </div>
        </div>
        <p style="text-align: center; margin: 0;">
            <a href="<?php echo esc_url( admin_url( 'admin.php?page=seo-fury&tab=webmasters&subtab=gsc' ) ); ?>" class="button button-primary button-small">
                <?php esc_html_e( 'View Full Report', 'seo-fury' ); ?> &rarr;
            </a>
        </p>
        <?php
    }

    // =========================================================================
    // Admin Tab Render
    // =========================================================================

    /**
     * Render the Search Console admin tab.
     * Called from class-admin.php.
     */
    public function render_admin_tab() {
        $is_connected    = $this->is_connected();
        $client_id       = $this->get_client_id();
        $client_secret   = $this->get_client_secret();
        $selected_prop   = $this->get_selected_property();
        $redirect_uri    = $this->get_redirect_uri();
        $brand           = function_exists( 'seo_fury_get_brand_name' ) ? seo_fury_get_brand_name() : 'SEO Fury';
        $nonce           = wp_create_nonce( 'seo_fury_gsc_nonce' );

        // Check for OAuth callback messages
        $gsc_error     = isset( $_GET['gsc_error'] ) ? sanitize_text_field( wp_unslash( $_GET['gsc_error'] ) ) : '';
        $gsc_connected = isset( $_GET['gsc_connected'] ) ? absint( $_GET['gsc_connected'] ) : 0;

        // PRO feature check
        $is_pro_available = true;
        if ( class_exists( 'SEO_Fury_Core' ) ) {
            $core = SEO_Fury_Core::get_instance();
            if ( $core && method_exists( $core, 'get_pro_features' ) ) {
                $pro = $core->get_pro_features();
                if ( $pro && method_exists( $pro, 'has_feature' ) ) {
                    $is_pro_available = $pro->has_feature( 'google_search_console' );
                }
            }
        }

        // Fetch properties for selection if connected
        $properties = array();
        if ( $is_connected && empty( $selected_prop ) ) {
            $props = $this->get_properties();
            if ( ! is_wp_error( $props ) ) {
                $properties = $props;
            }
        }
        ?>
        <div id="sf-gsc-wrap">

            <?php if ( ! $is_pro_available ) : ?>
                <div class="notice notice-warning" style="margin: 0 0 20px;">
                    <p>
                        <strong><?php esc_html_e( 'PRO Feature', 'seo-fury' ); ?>:</strong>
                        <?php esc_html_e( 'Google Search Console integration requires a PRO license.', 'seo-fury' ); ?>
                        <a href="<?php echo esc_url( admin_url( 'admin.php?page=seo-fury&tab=license' ) ); ?>">
                            <?php esc_html_e( 'Upgrade now', 'seo-fury' ); ?> &rarr;
                        </a>
                    </p>
                </div>
            <?php endif; ?>

            <?php if ( ! empty( $gsc_error ) ) : ?>
                <div class="notice notice-error" style="margin: 0 0 20px;">
                    <p><?php echo esc_html( $gsc_error ); ?></p>
                </div>
            <?php endif; ?>

            <?php if ( $gsc_connected ) : ?>
                <div class="notice notice-success" style="margin: 0 0 20px;">
                    <p><?php esc_html_e( 'Successfully connected to Google Search Console!', 'seo-fury' ); ?></p>
                </div>
            <?php endif; ?>

            <!-- Connection Settings -->
            <div class="seo-card" style="margin-bottom: 20px;">
                <h3 style="margin-top: 0; display: flex; align-items: center; gap: 8px;">
                    <span class="dashicons dashicons-admin-generic" style="color: #4285F4;"></span>
                    <?php esc_html_e( 'Connection Settings', 'seo-fury' ); ?>
                    <?php if ( $is_connected ) : ?>
                        <span style="background: #DEF7EC; color: #03543F; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 600;">
                            <?php esc_html_e( 'Connected', 'seo-fury' ); ?>
                        </span>
                    <?php else : ?>
                        <span style="background: var(--sf-danger-light,#FDE8E8); color: #9B1C1C; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 600;">
                            <?php esc_html_e( 'Not Connected', 'seo-fury' ); ?>
                        </span>
                    <?php endif; ?>
                </h3>

                <?php if ( ! $is_connected ) : ?>
                    <!-- One-click proxy connect (primary method) -->
                    <div style="text-align: center; padding: 30px 20px; background: linear-gradient(135deg, var(--sf-primary-light, #f0f6ff) 0%, var(--sf-primary-light, #ebf5ff) 100%); border: 1px solid #BFDBFE; border-radius: 12px; margin-bottom: 20px;">
                        <div style="margin-bottom: 16px;">
                            <span class="dashicons dashicons-google" style="font-size: 48px; width: 48px; height: 48px; color: #4285F4;"></span>
                        </div>
                        <h4 style="margin: 0 0 8px; font-size: 18px; color: var(--sf-gray-900,#1E293B);">
                            <?php esc_html_e( 'Connect Google Search Console', 'seo-fury' ); ?>
                        </h4>
                        <p style="margin: 0 0 20px; color: var(--sf-gray-700, #475569); font-size: 14px; max-width: 500px; margin-left: auto; margin-right: auto;">
                            <?php esc_html_e( 'Connect your Google account to view search performance data directly in WordPress. One click — no API setup required.', 'seo-fury' ); ?>
                        </p>
                        <button type="button" class="button button-primary button-hero" id="sf-gsc-proxy-connect" style="display: inline-flex; align-items: center; gap: 10px; font-size: 15px; padding: 8px 32px; border-radius: 8px; background: #4285F4; border-color: #3367D6; box-shadow: 0 2px 8px rgba(66,133,244,0.3);">
                            <svg width="20" height="20" viewBox="0 0 48 48" style="flex-shrink: 0;">
                                <path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/>
                                <path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/>
                                <path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/>
                                <path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/>
                            </svg>
                            <?php esc_html_e( 'Connect to Google', 'seo-fury' ); ?>
                        </button>
                        <span id="sf-gsc-proxy-status" style="display: block; margin-top: 10px; font-size: 13px;"></span>
                    </div>

                    <!-- Advanced: Manual credentials (collapsible) -->
                    <details style="margin-bottom: 10px;">
                        <summary style="cursor: pointer; font-size: 13px; color: var(--sf-gray-500, #64748b); font-weight: 500; padding: 8px 0; display: flex; align-items: center; gap: 6px;">
                            <span class="dashicons dashicons-admin-tools" style="font-size: 16px; width: 16px; height: 16px; color: var(--sf-gray-400, #94a3b8);"></span>
                            <?php esc_html_e( 'Advanced: Use your own API credentials', 'seo-fury' ); ?>
                        </summary>
                        <div style="padding: 16px; background: var(--sf-gray-50,#F8FAFC); border: 1px solid var(--sf-gray-200, #e2e8f0); border-radius: 8px; margin-top: 8px;">
                            <p style="margin: 0 0 12px; color: var(--sf-gray-700, #475569); font-size: 13px;">
                                <?php esc_html_e( 'If you prefer to use your own Google Cloud project, enter your OAuth 2.0 credentials below.', 'seo-fury' ); ?>
                            </p>

                            <!-- Step 1: Links -->
                            <div style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 12px;">
                                <a href="https://console.cloud.google.com/apis/credentials/oauthclient" target="_blank" rel="noopener" class="button button-small" style="display: inline-flex; align-items: center; gap: 4px;">
                                    <span class="dashicons dashicons-external" style="font-size: 14px; width: 14px; height: 14px; margin-top: 2px;"></span>
                                    <?php esc_html_e( 'Open Google Cloud Console', 'seo-fury' ); ?>
                                </a>
                                <a href="https://console.cloud.google.com/apis/library/searchconsole.googleapis.com" target="_blank" rel="noopener" class="button button-small" style="display: inline-flex; align-items: center; gap: 4px;">
                                    <span class="dashicons dashicons-external" style="font-size: 14px; width: 14px; height: 14px; margin-top: 2px;"></span>
                                    <?php esc_html_e( 'Enable Search Console API', 'seo-fury' ); ?>
                                </a>
                            </div>

                            <!-- Redirect URI -->
                            <div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 14px;">
                                <span style="font-size: 12px; color: var(--sf-gray-500, #64748b); font-weight: 600;"><?php esc_html_e( 'Redirect URI:', 'seo-fury' ); ?></span>
                                <code id="sf-gsc-redirect-uri" style="background: var(--sf-white, #fff); padding: 5px 10px; border: 1px solid var(--sf-gray-200, #cbd5e1); border-radius: 4px; font-size: 12px; user-select: all; cursor: pointer; word-break: break-all;" title="<?php esc_attr_e( 'Click to copy', 'seo-fury' ); ?>"><?php echo esc_html( $redirect_uri ); ?></code>
                                <button type="button" class="button button-small" id="sf-gsc-copy-uri" style="font-size: 12px; min-height: 26px; line-height: 24px;">
                                    <span class="dashicons dashicons-clipboard" style="font-size: 14px; width: 14px; height: 14px; margin-top: 2px;"></span>
                                    <?php esc_html_e( 'Copy', 'seo-fury' ); ?>
                                </button>
                            </div>

                            <!-- Credentials -->
                            <div style="display: flex; gap: 12px; margin-bottom: 14px; flex-wrap: wrap;">
                                <div style="flex: 1; min-width: 200px;">
                                    <label for="sf-gsc-client-id" style="display: block; font-size: 12px; font-weight: 600; color: var(--sf-gray-700, #475569); margin-bottom: 4px;"><?php esc_html_e( 'Client ID', 'seo-fury' ); ?></label>
                                    <input type="text" id="sf-gsc-client-id" style="width: 100%; padding: 8px 10px; border: 1px solid var(--sf-gray-200, #cbd5e1); border-radius: 6px; font-size: 13px;"
                                           value="<?php echo esc_attr( $client_id ); ?>"
                                           placeholder="<?php esc_attr_e( 'xxxx.apps.googleusercontent.com', 'seo-fury' ); ?>">
                                </div>
                                <div style="flex: 1; min-width: 200px;">
                                    <label for="sf-gsc-client-secret" style="display: block; font-size: 12px; font-weight: 600; color: var(--sf-gray-700, #475569); margin-bottom: 4px;"><?php esc_html_e( 'Client Secret', 'seo-fury' ); ?></label>
                                    <input type="password" id="sf-gsc-client-secret" style="width: 100%; padding: 8px 10px; border: 1px solid var(--sf-gray-200, #cbd5e1); border-radius: 6px; font-size: 13px;"
                                           value="<?php echo esc_attr( $client_secret ); ?>"
                                           placeholder="<?php esc_attr_e( 'GOCSPX-xxxxxxxxx', 'seo-fury' ); ?>">
                                </div>
                            </div>

                            <button type="button" class="button button-primary" id="sf-gsc-connect-one-click" style="display: inline-flex; align-items: center; gap: 8px;">
                                <span class="dashicons dashicons-google" style="font-size: 16px; width: 16px; height: 16px;"></span>
                                <?php esc_html_e( 'Save & Connect to Google', 'seo-fury' ); ?>
                            </button>
                            <span id="sf-gsc-cred-status" style="font-size: 13px; margin-left: 8px;"></span>

                            <!-- Collapsible detailed instructions -->
                            <details style="margin-top: 12px;">
                                <summary style="cursor: pointer; font-size: 12px; color: #4285F4; font-weight: 500;"><?php esc_html_e( 'Show detailed step-by-step guide', 'seo-fury' ); ?></summary>
                                <ol style="margin: 8px 0 0; padding-left: 18px; font-size: 12px; color: var(--sf-gray-700, #475569); line-height: 1.8;">
                                    <li><?php esc_html_e( 'Click "Open Google Cloud Console" above (create a Google Cloud project if you don\'t have one).', 'seo-fury' ); ?></li>
                                    <li><?php esc_html_e( 'Click "Enable Search Console API" above to enable the API for your project.', 'seo-fury' ); ?></li>
                                    <li><?php esc_html_e( 'In Credentials, click "Create Credentials" → "OAuth client ID".', 'seo-fury' ); ?></li>
                                    <li><?php esc_html_e( 'Select "Web application" as application type.', 'seo-fury' ); ?></li>
                                    <li><?php esc_html_e( 'Under "Authorized redirect URIs", click "Add URI" and paste the Redirect URI shown above.', 'seo-fury' ); ?></li>
                                    <li><?php esc_html_e( 'Click "Create" and copy the Client ID and Client Secret.', 'seo-fury' ); ?></li>
                                </ol>
                            </details>
                        </div>
                    </details>
                <?php endif; ?>

                <?php if ( $is_connected ) : ?>
                    <div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
                        <button type="button" class="button button-link-delete" id="sf-gsc-disconnect">
                            <span class="dashicons dashicons-dismiss" style="margin-top: 4px; font-size: 16px;"></span>
                            <?php esc_html_e( 'Disconnect', 'seo-fury' ); ?>
                        </button>
                    </div>
                <?php endif; ?>
            </div>

            <?php if ( $is_connected && empty( $selected_prop ) && ! empty( $properties ) ) : ?>
                <!-- Property Selection -->
                <div class="seo-card" style="margin-bottom: 20px;" id="sf-gsc-property-select-card">
                    <h3 style="margin-top: 0; display: flex; align-items: center; gap: 8px;">
                        <span class="dashicons dashicons-admin-site-alt3" style="color: #4285F4;"></span>
                        <?php esc_html_e( 'Select Property', 'seo-fury' ); ?>
                    </h3>
                    <p class="description"><?php esc_html_e( 'Choose the Search Console property to display data for:', 'seo-fury' ); ?></p>
                    <div style="display: flex; flex-direction: column; gap: 8px; margin-top: 12px; max-width: 600px;">
                        <?php foreach ( $properties as $prop ) : ?>
                            <label style="display: flex; align-items: center; gap: 10px; padding: 10px 14px; border: 1px solid var(--sf-gray-200, #e2e8f0); border-radius: 6px; cursor: pointer; transition: border-color 0.2s;"
                                   class="sf-gsc-property-option">
                                <input type="radio" name="sf_gsc_property" value="<?php echo esc_attr( $prop['url'] ); ?>">
                                <span>
                                    <strong><?php echo esc_html( $prop['url'] ); ?></strong>
                                    <br><small style="color: var(--sf-gray-500, #64748b);"><?php echo esc_html( $prop['level'] ); ?></small>
                                </span>
                            </label>
                        <?php endforeach; ?>
                    </div>
                    <div style="margin-top: 12px;">
                        <button type="button" class="button button-primary" id="sf-gsc-select-prop" disabled>
                            <?php esc_html_e( 'Select Property', 'seo-fury' ); ?>
                        </button>
                    </div>
                </div>
            <?php endif; ?>

            <?php if ( $is_connected && ! empty( $selected_prop ) ) : ?>
                <!-- Property Info Bar -->
                <div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; padding: 10px 16px; background: var(--sf-gray-50,#F8FAFC); border: 1px solid var(--sf-gray-200, #e2e8f0); border-radius: 8px;">
                    <div style="display: flex; align-items: center; gap: 8px;">
                        <span class="dashicons dashicons-admin-site-alt3" style="color: #4285F4;"></span>
                        <strong><?php echo esc_html( $selected_prop ); ?></strong>
                        <button type="button" class="button button-small" id="sf-gsc-change-prop" title="<?php esc_attr_e( 'Change property', 'seo-fury' ); ?>">
                            <?php esc_html_e( 'Change', 'seo-fury' ); ?>
                        </button>
                    </div>
                    <div style="display: flex; align-items: center; gap: 8px;">
                        <label for="sf-gsc-range" style="font-weight: 600; font-size: 13px;"><?php esc_html_e( 'Date Range:', 'seo-fury' ); ?></label>
                        <select id="sf-gsc-range" style="min-width: 120px;">
                            <option value="7d"><?php esc_html_e( 'Last 7 days', 'seo-fury' ); ?></option>
                            <option value="28d" selected><?php esc_html_e( 'Last 28 days', 'seo-fury' ); ?></option>
                            <option value="3m"><?php esc_html_e( 'Last 3 months', 'seo-fury' ); ?></option>
                        </select>
                        <button type="button" class="button button-primary" id="sf-gsc-refresh">
                            <span class="dashicons dashicons-update" style="margin-top: 4px; font-size: 16px;"></span>
                            <?php esc_html_e( 'Refresh', 'seo-fury' ); ?>
                        </button>
                    </div>
                </div>

                <!-- Summary Cards -->
                <div id="sf-gsc-totals" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 20px;">
                    <div class="sf-gsc-stat-card" data-metric="clicks">
                        <div class="sf-gsc-stat-value" id="sf-gsc-clicks">--</div>
                        <div class="sf-gsc-stat-label"><?php esc_html_e( 'Total Clicks', 'seo-fury' ); ?></div>
                    </div>
                    <div class="sf-gsc-stat-card" data-metric="impressions">
                        <div class="sf-gsc-stat-value" id="sf-gsc-impressions">--</div>
                        <div class="sf-gsc-stat-label"><?php esc_html_e( 'Total Impressions', 'seo-fury' ); ?></div>
                    </div>
                    <div class="sf-gsc-stat-card" data-metric="ctr">
                        <div class="sf-gsc-stat-value" id="sf-gsc-ctr">--</div>
                        <div class="sf-gsc-stat-label"><?php esc_html_e( 'Avg CTR', 'seo-fury' ); ?></div>
                    </div>
                    <div class="sf-gsc-stat-card" data-metric="position">
                        <div class="sf-gsc-stat-value" id="sf-gsc-position">--</div>
                        <div class="sf-gsc-stat-label"><?php esc_html_e( 'Avg Position', 'seo-fury' ); ?></div>
                    </div>
                </div>

                <!-- Reports: comparison + CSV export -->
                <div class="seo-card" id="sf-gsc-reports" data-nonce="<?php echo esc_attr( wp_create_nonce( 'seo_fury_gsc_nonce' ) ); ?>" data-ajax="<?php echo esc_attr( admin_url( 'admin-ajax.php' ) ); ?>" style="margin-bottom:20px;">
                    <h3 style="margin-top:0;"><?php esc_html_e( 'Reports', 'seo-fury' ); ?></h3>
                    <p>
                        <button type="button" class="button" id="sf-gsc-compare-btn"><?php esc_html_e( 'Compare with previous period', 'seo-fury' ); ?></button>
                        <button type="button" class="button" id="sf-gsc-export-queries"><?php esc_html_e( 'Export queries CSV', 'seo-fury' ); ?></button>
                        <button type="button" class="button" id="sf-gsc-export-pages"><?php esc_html_e( 'Export pages CSV', 'seo-fury' ); ?></button>
                    </p>
                    <div id="sf-gsc-compare-result" style="margin-top:8px;"></div>
                    <script>
                    (function($){
                        var $r = $('#sf-gsc-reports'); if (!$r.length) { return; }
                        var nonce = $r.data('nonce'), ajax = $r.data('ajax');
                        function currentRange(){ var v = $('#sf-gsc-range').val(); return (v === '7d' || v === '3m') ? v : '28d'; }
                        function fmtDelta(cur, prev, isPos){
                            cur = parseFloat(cur) || 0; prev = parseFloat(prev) || 0;
                            var diff = cur - prev;
                            if (!prev) { return ''; }
                            var pct = Math.round((diff / prev) * 100);
                            // For position, lower is better → invert arrow colour.
                            var up = isPos ? (diff < 0) : (diff > 0);
                            var color = up ? '#16A34A' : (diff === 0 ? '#64748B' : '#d63638');
                            var arrow = diff === 0 ? '→' : (diff > 0 ? '▲' : '▼');
                            return ' <span style="color:' + color + ';font-weight:600;">' + arrow + ' ' + Math.abs(pct) + '%</span>';
                        }
                        var L = {
                            current: <?php echo wp_json_encode( __( 'Current', 'seo-fury' ) ); ?>,
                            previous: <?php echo wp_json_encode( __( 'Previous', 'seo-fury' ) ); ?>,
                            clicks: <?php echo wp_json_encode( __( 'Clicks', 'seo-fury' ) ); ?>,
                            impressions: <?php echo wp_json_encode( __( 'Impressions', 'seo-fury' ) ); ?>,
                            ctr: <?php echo wp_json_encode( __( 'CTR', 'seo-fury' ) ); ?>,
                            position: <?php echo wp_json_encode( __( 'Position', 'seo-fury' ) ); ?>,
                            err: <?php echo wp_json_encode( __( 'Could not load comparison.', 'seo-fury' ) ); ?>
                        };
                        function row(label, cur, prev, delta){
                            return '<tr><td>' + label + '</td><td style="text-align:right;">' + cur + delta + '</td><td style="text-align:right;color:var(--sf-gray-500, #64748b);">' + prev + '</td></tr>';
                        }
                        $('#sf-gsc-compare-btn').on('click', function(){
                            var btn = $(this), orig = btn.text(); btn.prop('disabled', true).text('…');
                            $.post(ajax, { action: 'seo_fury_gsc_comparison', nonce: nonce, range: currentRange() }, function(resp){
                                if (!resp || !resp.success) { $('#sf-gsc-compare-result').html('<span style="color:#d63638;">' + L.err + '</span>'); return; }
                                var c = resp.data.current, p = resp.data.previous;
                                var html = '<table class="widefat striped" style="max-width:560px;"><thead><tr><th></th>' +
                                    '<th style="text-align:right;">' + L.current + '</th><th style="text-align:right;">' + L.previous + '</th></tr></thead><tbody>';
                                html += row(L.clicks, c.clicks, p.clicks, fmtDelta(c.clicks, p.clicks, false));
                                html += row(L.impressions, c.impressions, p.impressions, fmtDelta(c.impressions, p.impressions, false));
                                html += row(L.ctr, (c.ctr*100).toFixed(1)+'%', (p.ctr*100).toFixed(1)+'%', fmtDelta(c.ctr, p.ctr, false));
                                html += row(L.position, c.position.toFixed(1), p.position.toFixed(1), fmtDelta(c.position, p.position, true));
                                html += '</tbody></table>';
                                $('#sf-gsc-compare-result').html(html);
                            }).always(function(){ btn.prop('disabled', false).text(orig); });
                        });
                        function exportCsv(dim){
                            var url = ajax + '?action=seo_fury_gsc_export_csv&dimension=' + dim + '&range=' + encodeURIComponent(currentRange()) + '&nonce=' + encodeURIComponent(nonce);
                            window.location.href = url;
                        }
                        $('#sf-gsc-export-queries').on('click', function(){ exportCsv('query'); });
                        $('#sf-gsc-export-pages').on('click', function(){ exportCsv('page'); });
                    })(jQuery);
                    </script>
                </div>

                <!-- Performance Chart -->
                <div class="seo-card" style="margin-bottom: 20px;">
                    <h3 style="margin-top: 0; display: flex; align-items: center; gap: 8px;">
                        <span class="dashicons dashicons-chart-line" style="color: #4285F4;"></span>
                        <?php esc_html_e( 'Performance Over Time', 'seo-fury' ); ?>
                    </h3>
                    <div id="sf-gsc-chart-wrap" style="position: relative; min-height: 200px;">
                        <div id="sf-gsc-chart-loading" style="text-align: center; padding: 60px 0; color: var(--sf-gray-400, #94a3b8);">
                            <span class="dashicons dashicons-update sf-gsc-spin" style="font-size: 32px; width: 32px; height: 32px;"></span>
                            <p><?php esc_html_e( 'Loading chart data...', 'seo-fury' ); ?></p>
                        </div>
                        <canvas id="sf-gsc-chart" style="display: none; width: 100%; height: 250px;"></canvas>
                        <!-- Fallback CSS bar chart -->
                        <div id="sf-gsc-bar-chart" style="display: none;"></div>
                    </div>
                </div>

                <!-- Data Tables -->
                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
                    <!-- Top Queries -->
                    <div class="seo-card">
                        <h3 style="margin-top: 0; display: flex; align-items: center; gap: 8px;">
                            <span class="dashicons dashicons-search" style="color: #16A34A;"></span>
                            <?php esc_html_e( 'Top Queries', 'seo-fury' ); ?>
                        </h3>
                        <div id="sf-gsc-queries-wrap" style="overflow-x: auto;">
                            <table class="widefat striped sf-gsc-sortable" id="sf-gsc-queries-table">
                                <thead>
                                    <tr>
                                        <th class="sf-gsc-sort" data-col="0" data-type="string"><?php esc_html_e( 'Query', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                        <th class="sf-gsc-sort" data-col="1" data-type="number" style="text-align: right;"><?php esc_html_e( 'Clicks', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                        <th class="sf-gsc-sort" data-col="2" data-type="number" style="text-align: right;"><?php esc_html_e( 'Impr.', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                        <th class="sf-gsc-sort" data-col="3" data-type="number" style="text-align: right;"><?php esc_html_e( 'CTR', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                        <th class="sf-gsc-sort" data-col="4" data-type="number" style="text-align: right;"><?php esc_html_e( 'Pos.', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr><td colspan="5" style="text-align: center; padding: 30px; color: var(--sf-gray-400, #94a3b8);">
                                        <span class="dashicons dashicons-update sf-gsc-spin"></span>
                                        <?php esc_html_e( 'Loading...', 'seo-fury' ); ?>
                                    </td></tr>
                                </tbody>
                            </table>
                        </div>
                    </div>

                    <!-- Top Pages -->
                    <div class="seo-card">
                        <h3 style="margin-top: 0; display: flex; align-items: center; gap: 8px;">
                            <span class="dashicons dashicons-admin-page" style="color: #D97706;"></span>
                            <?php esc_html_e( 'Top Pages', 'seo-fury' ); ?>
                        </h3>
                        <div id="sf-gsc-pages-wrap" style="overflow-x: auto;">
                            <table class="widefat striped sf-gsc-sortable" id="sf-gsc-pages-table">
                                <thead>
                                    <tr>
                                        <th class="sf-gsc-sort" data-col="0" data-type="string"><?php esc_html_e( 'Page', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                        <th class="sf-gsc-sort" data-col="1" data-type="number" style="text-align: right;"><?php esc_html_e( 'Clicks', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                        <th class="sf-gsc-sort" data-col="2" data-type="number" style="text-align: right;"><?php esc_html_e( 'Impr.', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                        <th class="sf-gsc-sort" data-col="3" data-type="number" style="text-align: right;"><?php esc_html_e( 'CTR', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                        <th class="sf-gsc-sort" data-col="4" data-type="number" style="text-align: right;"><?php esc_html_e( 'Pos.', 'seo-fury' ); ?> <span class="sf-gsc-sort-icon"></span></th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr><td colspan="5" style="text-align: center; padding: 30px; color: var(--sf-gray-400, #94a3b8);">
                                        <span class="dashicons dashicons-update sf-gsc-spin"></span>
                                        <?php esc_html_e( 'Loading...', 'seo-fury' ); ?>
                                    </td></tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>

            <?php elseif ( $is_connected && empty( $selected_prop ) && empty( $properties ) ) : ?>
                <!-- Connected but no properties loaded yet -->
                <div class="seo-card" style="margin-bottom: 20px;">
                    <p style="text-align: center; padding: 20px;">
                        <button type="button" class="button button-primary" id="sf-gsc-load-props">
                            <span class="dashicons dashicons-admin-site-alt3" style="margin-top: 4px;"></span>
                            <?php esc_html_e( 'Load Properties', 'seo-fury' ); ?>
                        </button>
                    </p>
                </div>
            <?php endif; ?>

        </div>

        <style>
            /* Stat cards */
            .sf-gsc-stat-card {
                background: var(--sf-white, #fff);
                border: 1px solid var(--sf-gray-200, #e2e8f0);
                border-radius: 10px;
                padding: 20px;
                text-align: center;
                transition: box-shadow 0.2s, transform 0.2s;
            }
            .sf-gsc-stat-card:hover {
                box-shadow: 0 4px 12px rgba(0,0,0,0.08);
                transform: translateY(-1px);
            }
            .sf-gsc-stat-card[data-metric="clicks"] { border-top: 3px solid #2563EB; }
            .sf-gsc-stat-card[data-metric="impressions"] { border-top: 3px solid #16A34A; }
            .sf-gsc-stat-card[data-metric="ctr"] { border-top: 3px solid #D97706; }
            .sf-gsc-stat-card[data-metric="position"] { border-top: 3px solid #DB2777; }

            .sf-gsc-stat-value {
                font-size: 28px;
                font-weight: 700;
                color: var(--sf-gray-900,#1E293B);
                line-height: 1.2;
            }
            .sf-gsc-stat-card[data-metric="clicks"] .sf-gsc-stat-value { color: #2563EB; }
            .sf-gsc-stat-card[data-metric="impressions"] .sf-gsc-stat-value { color: #16A34A; }
            .sf-gsc-stat-card[data-metric="ctr"] .sf-gsc-stat-value { color: #D97706; }
            .sf-gsc-stat-card[data-metric="position"] .sf-gsc-stat-value { color: #DB2777; }

            .sf-gsc-stat-label {
                font-size: 13px;
                color: var(--sf-gray-500, #64748b);
                margin-top: 4px;
                font-weight: 500;
            }

            /* Spin animation */
            @keyframes sf-gsc-spin-animation {
                from { transform: rotate(0deg); }
                to { transform: rotate(360deg); }
            }
            .sf-gsc-spin {
                animation: sf-gsc-spin-animation 1s linear infinite;
                display: inline-block;
            }

            /* Property option hover */
            .sf-gsc-property-option:hover {
                border-color: #4285F4 !important;
                background: var(--sf-primary-light,#F0F6FF);
            }

            /* Bar chart fallback */
            .sf-gsc-bar-row {
                display: flex;
                align-items: center;
                gap: 8px;
                margin-bottom: 4px;
                font-size: 12px;
            }
            .sf-gsc-bar-label {
                width: 70px;
                text-align: right;
                color: var(--sf-gray-500, #64748b);
                flex-shrink: 0;
            }
            .sf-gsc-bar-track {
                flex: 1;
                height: 22px;
                background: var(--sf-gray-100,#F1F5F9);
                border-radius: 4px;
                overflow: hidden;
                position: relative;
            }
            .sf-gsc-bar-fill {
                height: 100%;
                border-radius: 4px;
                transition: width 0.5s ease;
                min-width: 2px;
            }
            .sf-gsc-bar-fill.clicks { background: #3B82F6; }
            .sf-gsc-bar-fill.impressions { background: #22C55E; }
            .sf-gsc-bar-val {
                width: 60px;
                text-align: right;
                font-weight: 600;
                color: var(--sf-gray-700,#334155);
                flex-shrink: 0;
            }

            /* Chart legend */
            .sf-gsc-chart-legend {
                display: flex;
                gap: 20px;
                justify-content: center;
                margin-bottom: 12px;
                font-size: 13px;
            }
            .sf-gsc-chart-legend span {
                display: inline-flex;
                align-items: center;
                gap: 6px;
            }
            .sf-gsc-chart-legend i {
                display: inline-block;
                width: 12px;
                height: 12px;
                border-radius: 3px;
            }

            /* Sortable table headers */
            .sf-gsc-sort {
                cursor: pointer;
                user-select: none;
                white-space: nowrap;
            }
            .sf-gsc-sort:hover {
                color: var(--sf-gray-900,#1E293B) !important;
                background: var(--sf-gray-100,#F1F5F9);
            }
            .sf-gsc-sort-icon::after {
                content: '\2195';
                font-size: 10px;
                color: var(--sf-gray-300,#CBD5E1);
                margin-left: 2px;
            }
            .sf-gsc-sort.asc .sf-gsc-sort-icon::after {
                content: '\2191';
                color: #4285F4;
            }
            .sf-gsc-sort.desc .sf-gsc-sort-icon::after {
                content: '\2193';
                color: #4285F4;
            }

            /* Table improvements */
            #sf-gsc-queries-table th,
            #sf-gsc-pages-table th {
                font-size: 12px;
                text-transform: uppercase;
                letter-spacing: 0.05em;
                color: var(--sf-gray-500, #64748b);
                padding: 8px 10px;
            }
            #sf-gsc-queries-table td,
            #sf-gsc-pages-table td {
                padding: 8px 10px;
                font-size: 13px;
            }
            #sf-gsc-queries-table tbody tr:hover,
            #sf-gsc-pages-table tbody tr:hover {
                background: var(--sf-primary-light,#F0F6FF) !important;
            }

            /* Responsive */
            @media (max-width: 1200px) {
                #sf-gsc-wrap > div[style*="grid-template-columns: 1fr 1fr"] {
                    grid-template-columns: 1fr !important;
                }
            }
            @media (max-width: 782px) {
                #sf-gsc-totals {
                    grid-template-columns: 1fr 1fr !important;
                }
            }
        </style>

        <script>
        (function($) {
            'use strict';

            var GSC = {
                nonce: '<?php echo esc_js( $nonce ); ?>',
                ajaxUrl: '<?php echo esc_js( admin_url( "admin-ajax.php" ) ); ?>',
                isConnected: <?php echo $is_connected ? 'true' : 'false'; ?>,
                hasProperty: <?php echo ! empty( $selected_prop ) ? 'true' : 'false'; ?>,

                init: function() {
                    this.bindEvents();
                    if (this.isConnected && this.hasProperty) {
                        this.loadData();
                    }
                },

                bindEvents: function() {
                    var self = this;

                    // Proxy connect (one-click, primary)
                    $('#sf-gsc-proxy-connect').on('click', function() { self.proxyConnect($(this)); });

                    // Manual Save & Connect (advanced mode)
                    $('#sf-gsc-connect-one-click').on('click', function() { self.saveAndConnect($(this)); });

                    // Copy redirect URI
                    $('#sf-gsc-copy-uri').on('click', function() {
                        var uri = $('#sf-gsc-redirect-uri').text();
                        if (navigator.clipboard) {
                            navigator.clipboard.writeText(uri).then(function() {
                                $('#sf-gsc-copy-uri').html('<span class="dashicons dashicons-yes" style="font-size:14px;width:14px;height:14px;margin-top:2px;color:#16A34A;"></span> <?php echo esc_js( __( 'Copied!', 'seo-fury' ) ); ?>');
                                setTimeout(function() {
                                    $('#sf-gsc-copy-uri').html('<span class="dashicons dashicons-clipboard" style="font-size:14px;width:14px;height:14px;margin-top:2px;"></span> <?php echo esc_js( __( 'Copy', 'seo-fury' ) ); ?>');
                                }, 2000);
                            });
                        }
                    });

                    // Legacy: Save credentials (keep for backward compat)
                    $('#sf-gsc-save-creds').on('click', function() { self.saveCredentials($(this)); });

                    // Legacy: Connect
                    $('#sf-gsc-connect').on('click', function() { self.authorize(); });

                    // Disconnect
                    $('#sf-gsc-disconnect').on('click', function() { self.disconnect(); });

                    // Property selection radio
                    $('input[name="sf_gsc_property"]').on('change', function() {
                        $('#sf-gsc-select-prop').prop('disabled', false);
                    });
                    $('#sf-gsc-select-prop').on('click', function() { self.selectProperty(); });

                    // Load properties button
                    $('#sf-gsc-load-props').on('click', function() { self.loadProperties($(this)); });

                    // Change property
                    $('#sf-gsc-change-prop').on('click', function() { self.changeProperty(); });

                    // Date range / refresh
                    $('#sf-gsc-range').on('change', function() { self.loadData(); });
                    $('#sf-gsc-refresh').on('click', function() { self.loadData(); });

                    // Table sorting
                    $(document).on('click', '.sf-gsc-sort', function() { self.sortTable($(this)); });
                },

                sortTable: function($th) {
                    var table = $th.closest('table');
                    var tbody = table.find('tbody');
                    var rows = tbody.find('tr').toArray();
                    var col = parseInt($th.data('col'));
                    var type = $th.data('type') || 'string';

                    // Toggle direction
                    var dir = 'desc';
                    if ($th.hasClass('desc')) dir = 'asc';
                    table.find('.sf-gsc-sort').removeClass('asc desc');
                    $th.addClass(dir);

                    rows.sort(function(a, b) {
                        var aVal = $(a).find('td').eq(col).text().trim();
                        var bVal = $(b).find('td').eq(col).text().trim();

                        if (type === 'number') {
                            aVal = parseFloat(aVal.replace(/[^0-9.\-]/g, '')) || 0;
                            bVal = parseFloat(bVal.replace(/[^0-9.\-]/g, '')) || 0;
                        } else {
                            aVal = aVal.toLowerCase();
                            bVal = bVal.toLowerCase();
                        }

                        if (aVal < bVal) return dir === 'asc' ? -1 : 1;
                        if (aVal > bVal) return dir === 'asc' ? 1 : -1;
                        return 0;
                    });

                    tbody.empty().append(rows);
                },

                ajax: function(action, data, callback) {
                    data = data || {};
                    data.action = action;
                    data.nonce = this.nonce;
                    $.post(this.ajaxUrl, data, callback).fail(function() {
                        alert('<?php echo esc_js( __( 'Network error. Please try again.', 'seo-fury' ) ); ?>');
                    });
                },

                proxyConnect: function($btn) {
                    $btn.prop('disabled', true).html(
                        '<span class="dashicons dashicons-update sf-gsc-spin" style="font-size:20px;width:20px;height:20px;"></span> ' +
                        '<?php echo esc_js( __( 'Connecting...', 'seo-fury' ) ); ?>'
                    );
                    $('#sf-gsc-proxy-status').html('<span style="color:var(--sf-gray-500, #64748b);"><?php echo esc_js( __( 'Redirecting to Google...', 'seo-fury' ) ); ?></span>');

                    this.ajax('seo_fury_gsc_proxy_connect', {}, function(res) {
                        if (res.success && res.data.redirect_url) {
                            window.location.href = res.data.redirect_url;
                        } else {
                            $btn.prop('disabled', false).html(
                                '<svg width="20" height="20" viewBox="0 0 48 48" style="flex-shrink:0;"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/></svg> ' +
                                '<?php echo esc_js( __( 'Connect to Google', 'seo-fury' ) ); ?>'
                            );
                            var msg = res.data ? res.data.message : '<?php echo esc_js( __( 'Connection failed. Try the manual method below.', 'seo-fury' ) ); ?>';
                            $('#sf-gsc-proxy-status').html('<span style="color:#DC2626;">' + msg + '</span>');
                        }
                    });
                },

                saveCredentials: function($btn) {
                    var self = this;
                    var clientId = $('#sf-gsc-client-id').val().trim();
                    var clientSecret = $('#sf-gsc-client-secret').val().trim();

                    if (!clientId || !clientSecret) {
                        $('#sf-gsc-cred-status').html('<span style="color:#DC2626;"><?php echo esc_js( __( 'Both fields are required.', 'seo-fury' ) ); ?></span>');
                        return;
                    }

                    $btn.prop('disabled', true).text('<?php echo esc_js( __( 'Saving...', 'seo-fury' ) ); ?>');

                    this.ajax('seo_fury_gsc_save_credentials', {
                        client_id: clientId,
                        client_secret: clientSecret
                    }, function(res) {
                        $btn.prop('disabled', false).text('<?php echo esc_js( __( 'Save Credentials', 'seo-fury' ) ); ?>');
                        if (res.success) {
                            $('#sf-gsc-cred-status').html('<span style="color:#16A34A;">' + res.data.message + '</span>');
                            $('#sf-gsc-connect').prop('disabled', false);
                        } else {
                            $('#sf-gsc-cred-status').html('<span style="color:#DC2626;">' + (res.data ? res.data.message : '<?php echo esc_js( __( 'Error', 'seo-fury' ) ); ?>') + '</span>');
                        }
                    });
                },

                saveAndConnect: function($btn) {
                    var self = this;
                    var clientId = $('#sf-gsc-client-id').val().trim();
                    var clientSecret = $('#sf-gsc-client-secret').val().trim();

                    if (!clientId || !clientSecret) {
                        $('#sf-gsc-cred-status').html('<span style="color:#DC2626;"><?php echo esc_js( __( 'Please enter both Client ID and Client Secret.', 'seo-fury' ) ); ?></span>');
                        return;
                    }

                    $btn.prop('disabled', true).html('<span class="dashicons dashicons-update sf-gsc-spin" style="font-size:20px;width:20px;height:20px;"></span> <?php echo esc_js( __( 'Connecting...', 'seo-fury' ) ); ?>');
                    $('#sf-gsc-cred-status').html('<span style="color:var(--sf-gray-500, #64748b);"><?php echo esc_js( __( 'Saving credentials...', 'seo-fury' ) ); ?></span>');

                    // Step 1: Save credentials
                    this.ajax('seo_fury_gsc_save_credentials', {
                        client_id: clientId,
                        client_secret: clientSecret
                    }, function(res) {
                        if (res.success) {
                            $('#sf-gsc-cred-status').html('<span style="color:#16A34A;"><?php echo esc_js( __( 'Credentials saved! Redirecting to Google...', 'seo-fury' ) ); ?></span>');
                            // Step 2: Immediately authorize
                            self.authorize();
                        } else {
                            $btn.prop('disabled', false).html('<span class="dashicons dashicons-google" style="font-size:20px;width:20px;height:20px;"></span> <?php echo esc_js( __( 'Save & Connect to Google', 'seo-fury' ) ); ?>');
                            $('#sf-gsc-cred-status').html('<span style="color:#DC2626;">' + (res.data ? res.data.message : '<?php echo esc_js( __( 'Error saving credentials.', 'seo-fury' ) ); ?>') + '</span>');
                        }
                    });
                },

                authorize: function() {
                    var self = this;
                    this.ajax('seo_fury_gsc_authorize', {}, function(res) {
                        if (res.success && res.data.auth_url) {
                            window.location.href = res.data.auth_url;
                        } else {
                            alert(res.data ? res.data.message : '<?php echo esc_js( __( 'Could not generate authorization URL.', 'seo-fury' ) ); ?>');
                        }
                    });
                },

                disconnect: function() {
                    if (!confirm('<?php echo esc_js( __( 'Are you sure you want to disconnect from Google Search Console?', 'seo-fury' ) ); ?>')) {
                        return;
                    }
                    this.ajax('seo_fury_gsc_disconnect', {}, function(res) {
                        if (res.success) {
                            window.location.reload();
                        } else {
                            alert(res.data ? res.data.message : '<?php echo esc_js( __( 'Error', 'seo-fury' ) ); ?>');
                        }
                    });
                },

                selectProperty: function() {
                    var prop = $('input[name="sf_gsc_property"]:checked').val();
                    if (!prop) return;

                    var $btn = $('#sf-gsc-select-prop');
                    $btn.prop('disabled', true).text('<?php echo esc_js( __( 'Saving...', 'seo-fury' ) ); ?>');

                    this.ajax('seo_fury_gsc_select_property', { property: prop }, function(res) {
                        if (res.success) {
                            window.location.reload();
                        } else {
                            $btn.prop('disabled', false).text('<?php echo esc_js( __( 'Select Property', 'seo-fury' ) ); ?>');
                            alert(res.data ? res.data.message : '<?php echo esc_js( __( 'Error', 'seo-fury' ) ); ?>');
                        }
                    });
                },

                loadProperties: function($btn) {
                    var self = this;
                    $btn.prop('disabled', true).html('<span class="dashicons dashicons-update sf-gsc-spin" style="margin-top:4px;"></span> <?php echo esc_js( __( 'Loading...', 'seo-fury' ) ); ?>');

                    // We need a custom AJAX to fetch properties
                    this.ajax('seo_fury_gsc_fetch_data', { range: '7d', dimension: 'query', row_limit: 1 }, function(res) {
                        // If this works, the token is valid — just reload to show property selection
                        window.location.reload();
                    });
                },

                changeProperty: function() {
                    // Clear property and reload to show selection
                    this.ajax('seo_fury_gsc_select_property', { property: '' }, function() {
                        window.location.reload();
                    });
                },

                loadData: function() {
                    var self = this;
                    var range = $('#sf-gsc-range').val() || '28d';

                    // Show loading state
                    $('#sf-gsc-chart-loading').show();
                    $('#sf-gsc-chart, #sf-gsc-bar-chart').hide();

                    // Fetch queries
                    this.ajax('seo_fury_gsc_fetch_data', {
                        range: range,
                        dimension: 'query',
                        row_limit: 20
                    }, function(res) {
                        if (res.success) {
                            self.renderTotals(res.data.totals);
                            self.renderTable('#sf-gsc-queries-table', res.data.rows, 'query');
                            self.renderChart(res.data.chart);
                        } else {
                            self.showError(res.data ? res.data.message : '<?php echo esc_js( __( 'Failed to load data.', 'seo-fury' ) ); ?>');
                        }
                    });

                    // Fetch pages (separate request)
                    this.ajax('seo_fury_gsc_fetch_data', {
                        range: range,
                        dimension: 'page',
                        row_limit: 20
                    }, function(res) {
                        if (res.success) {
                            self.renderTable('#sf-gsc-pages-table', res.data.rows, 'page');
                        }
                    });
                },

                renderTotals: function(totals) {
                    if (!totals) return;
                    $('#sf-gsc-clicks').text(this.formatNumber(totals.clicks));
                    $('#sf-gsc-impressions').text(this.formatNumber(totals.impressions));
                    $('#sf-gsc-ctr').text((totals.ctr * 100).toFixed(1) + '%');
                    $('#sf-gsc-position').text(totals.position.toFixed(1));
                },

                renderTable: function(selector, rows, dimension) {
                    var $tbody = $(selector).find('tbody');
                    $tbody.empty();

                    if (!rows || rows.length === 0) {
                        $tbody.html('<tr><td colspan="5" style="text-align:center;padding:20px;color:var(--sf-gray-400, #94a3b8);"><?php echo esc_js( __( 'No data available for this period.', 'seo-fury' ) ); ?></td></tr>');
                        return;
                    }

                    var self = this;
                    rows.forEach(function(row) {
                        var key = row.keys ? row.keys[0] : '';
                        var display = key;

                        if (dimension === 'page') {
                            // Shorten URLs for display
                            try {
                                var u = new URL(key);
                                display = u.pathname + u.search;
                                if (display.length > 60) {
                                    display = display.substring(0, 57) + '...';
                                }
                            } catch(e) {
                                display = key;
                            }
                        } else if (dimension === 'query' && key.length > 50) {
                            display = key.substring(0, 47) + '...';
                        }

                        var ctr = (row.ctr * 100).toFixed(1) + '%';
                        var pos = row.position.toFixed(1);

                        var $tr = $('<tr>');
                        if (dimension === 'page') {
                            $tr.append($('<td>').html('<a href="' + self.escHtml(key) + '" target="_blank" rel="noopener" title="' + self.escHtml(key) + '" style="text-decoration:none;">' + self.escHtml(display) + '</a>'));
                        } else {
                            $tr.append($('<td>').attr('title', key).text(display));
                        }
                        $tr.append($('<td style="text-align:right;font-weight:600;">').text(self.formatNumber(row.clicks)));
                        $tr.append($('<td style="text-align:right;">').text(self.formatNumber(row.impressions)));
                        $tr.append($('<td style="text-align:right;">').text(ctr));
                        $tr.append($('<td style="text-align:right;">').text(pos));
                        $tbody.append($tr);
                    });
                },

                renderChart: function(chartData) {
                    $('#sf-gsc-chart-loading').hide();

                    if (!chartData || chartData.length === 0) {
                        $('#sf-gsc-bar-chart').html('<p style="text-align:center;padding:40px;color:var(--sf-gray-400, #94a3b8);"><?php echo esc_js( __( 'No chart data available.', 'seo-fury' ) ); ?></p>').show();
                        return;
                    }

                    // Sort by date
                    chartData.sort(function(a, b) {
                        return (a.keys[0] || '').localeCompare(b.keys[0] || '');
                    });

                    // Try Chart.js first (if loaded on page)
                    if (typeof Chart !== 'undefined') {
                        this.renderChartJs(chartData);
                        return;
                    }

                    // Fallback: CSS bar chart
                    this.renderBarChart(chartData);
                },

                renderChartJs: function(chartData) {
                    var $canvas = $('#sf-gsc-chart');
                    $canvas.show();

                    var labels = [];
                    var clicks = [];
                    var impressions = [];

                    chartData.forEach(function(row) {
                        var d = row.keys[0];
                        labels.push(d.substring(5)); // MM-DD
                        clicks.push(row.clicks);
                        impressions.push(row.impressions);
                    });

                    var ctx = $canvas[0].getContext('2d');

                    // Destroy existing chart if any
                    if (window._sfGscChart) {
                        window._sfGscChart.destroy();
                    }

                    window._sfGscChart = new Chart(ctx, {
                        type: 'line',
                        data: {
                            labels: labels,
                            datasets: [
                                {
                                    label: '<?php echo esc_js( __( 'Clicks', 'seo-fury' ) ); ?>',
                                    data: clicks,
                                    borderColor: '#3B82F6',
                                    backgroundColor: 'rgba(59,130,246,0.1)',
                                    fill: true,
                                    tension: 0.3,
                                    pointRadius: 2,
                                },
                                {
                                    label: '<?php echo esc_js( __( 'Impressions', 'seo-fury' ) ); ?>',
                                    data: impressions,
                                    borderColor: '#22C55E',
                                    backgroundColor: 'rgba(34,197,94,0.1)',
                                    fill: true,
                                    tension: 0.3,
                                    pointRadius: 2,
                                    yAxisID: 'y1',
                                }
                            ]
                        },
                        options: {
                            responsive: true,
                            maintainAspectRatio: false,
                            interaction: { mode: 'index', intersect: false },
                            scales: {
                                y: { beginAtZero: true, position: 'left', title: { display: true, text: '<?php echo esc_js( __( 'Clicks', 'seo-fury' ) ); ?>' } },
                                y1: { beginAtZero: true, position: 'right', grid: { drawOnChartArea: false }, title: { display: true, text: '<?php echo esc_js( __( 'Impressions', 'seo-fury' ) ); ?>' } }
                            },
                            plugins: {
                                legend: { position: 'top' }
                            }
                        }
                    });
                },

                renderBarChart: function(chartData) {
                    var $wrap = $('#sf-gsc-bar-chart');
                    $wrap.empty().show();

                    // Legend
                    $wrap.append(
                        '<div class="sf-gsc-chart-legend">' +
                        '<span><i style="background:#3B82F6;"></i> <?php echo esc_js( __( 'Clicks', 'seo-fury' ) ); ?></span>' +
                        '<span><i style="background:#22C55E;"></i> <?php echo esc_js( __( 'Impressions', 'seo-fury' ) ); ?></span>' +
                        '</div>'
                    );

                    // Find max values
                    var maxClicks = 0, maxImpressions = 0;
                    chartData.forEach(function(row) {
                        if (row.clicks > maxClicks) maxClicks = row.clicks;
                        if (row.impressions > maxImpressions) maxImpressions = row.impressions;
                    });
                    if (maxClicks === 0) maxClicks = 1;
                    if (maxImpressions === 0) maxImpressions = 1;

                    // Limit bars for readability
                    var displayData = chartData;
                    if (chartData.length > 30) {
                        // Show every Nth item
                        var step = Math.ceil(chartData.length / 30);
                        displayData = chartData.filter(function(_, i) { return i % step === 0; });
                    }

                    var self = this;
                    displayData.forEach(function(row) {
                        var date = (row.keys[0] || '').substring(5);
                        var clicksPct = (row.clicks / maxClicks * 100).toFixed(1);
                        var impressionsPct = (row.impressions / maxImpressions * 100).toFixed(1);

                        $wrap.append(
                            '<div class="sf-gsc-bar-row">' +
                            '<div class="sf-gsc-bar-label">' + self.escHtml(date) + '</div>' +
                            '<div class="sf-gsc-bar-track">' +
                            '<div class="sf-gsc-bar-fill clicks" style="width:' + clicksPct + '%;"></div>' +
                            '</div>' +
                            '<div class="sf-gsc-bar-val" style="color:#3B82F6;">' + self.formatNumber(row.clicks) + '</div>' +
                            '</div>' +
                            '<div class="sf-gsc-bar-row" style="margin-bottom:8px;">' +
                            '<div class="sf-gsc-bar-label"></div>' +
                            '<div class="sf-gsc-bar-track">' +
                            '<div class="sf-gsc-bar-fill impressions" style="width:' + impressionsPct + '%;"></div>' +
                            '</div>' +
                            '<div class="sf-gsc-bar-val" style="color:#22C55E;">' + self.formatNumber(row.impressions) + '</div>' +
                            '</div>'
                        );
                    });
                },

                showError: function(msg) {
                    $('#sf-gsc-chart-loading').html('<p style="color:#DC2626;">' + this.escHtml(msg) + '</p>');
                    var $tables = $('#sf-gsc-queries-table tbody, #sf-gsc-pages-table tbody');
                    $tables.html('<tr><td colspan="5" style="text-align:center;padding:20px;color:#DC2626;">' + this.escHtml(msg) + '</td></tr>');
                },

                formatNumber: function(n) {
                    if (typeof n !== 'number') n = parseInt(n) || 0;
                    return n.toLocaleString();
                },

                escHtml: function(str) {
                    if (!str) return '';
                    var div = document.createElement('div');
                    div.textContent = str;
                    return div.innerHTML;
                }
            };

            $(document).ready(function() {
                GSC.init();
            });
        })(jQuery);
        </script>
        <?php
    }
}
