<?php
/**
 * The core plugin class.
 *
 * @link       https://github.com/sitestaffr/sitestaffr-plugin
 * @since      1.0.0
 *
 * @package    SiteStaffr
 * @subpackage SiteStaffr/includes
 */

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

/**
 * The core plugin class.
 *
 * This is used to define internationalization, admin-specific hooks, and
 * public-facing site hooks.
 *
 * Also maintains the unique identifier of this plugin as well as the current
 * version of the plugin.
 *
 * @since      1.0.0
 * @package    SiteStaffr
 * @subpackage SiteStaffr/includes
 * @author     SiteStaffr
 */
class SiteStaffr {

    /**
     * The loader that's responsible for maintaining and registering all hooks that power
     * the plugin.
     *
     * @since    1.0.0
     * @access   protected
     * @var      SiteStaffr_Loader    $loader    Maintains and registers all hooks for the plugin.
     */
    protected $loader;

    /**
     * The unique identifier of this plugin.
     *
     * @since    1.0.0
     * @access   protected
     * @var      string    $plugin_name    The string used to uniquely identify this plugin.
     */
    protected $plugin_name;

    /**
     * The current version of the plugin.
     *
     * @since    1.0.0
     * @access   protected
     * @var      string    $version    The current version of the plugin.
     */
    protected $version;

    /**
     * Define the core functionality of the plugin.
     *
     * Set the plugin name and the plugin version that can be used throughout the plugin.
     * Load the dependencies, define the locale, and set the hooks for the admin area and
     * the public-facing side of the site.
     *
     * @since    1.0.0
     */
    public function __construct() {
        $this->version = SITESTAFFR_VERSION;
        $this->plugin_name = 'sitestaffr';

        $this->load_dependencies();
        $this->define_admin_hooks();
        $this->define_webhook_hooks();
        $this->define_rest_api_hooks();
        $this->define_ai_summary_hooks();
        $this->define_email_hooks();
        $this->define_widget_hooks();
        $this->define_transcript_viewer_hooks();
    }

    /**
     * Load the required dependencies for this plugin.
     *
     * @since    1.0.0
     * @access   private
     */
    private function load_dependencies() {
        /**
         * The class responsible for orchestrating the actions and filters of the
         * core plugin.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-loader.php';

        /**
         * The class responsible for database operations.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-database.php';

        /**
         * Centralized logger for safe/redacted plugin logging.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-logger.php';

        /**
         * The class responsible for defining all actions that occur in the admin area.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'admin/class-sitestaffr-admin.php';

        /**
         * The class responsible for setup wizard REST API endpoints.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-setup-api.php';
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-business-hours.php';

        /**
         * Shared business info enrichment utilities (fallback injection).
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-business-enrichment.php';

        /**
         * The class responsible for REST API endpoints (middleware integration).
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-rest-api.php';

        /**
         * The unified ban manager class for phone, visitor, and IP bans.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-ban-manager.php';

        /**
         * The JWT authentication token generator for WebRTC widgets.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-auth-token.php';

        /**
         * Shared customer contact resolution for onboarding and billing.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-account-contact.php';

        /**
         * The site registration manager for middleware authentication.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-site-registration.php';

        /**
         * The class responsible for call management business logic.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-call-manager.php';

        /**
         * Chunk source attribution helpers (sanitize + render "Sources" disclosure).
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/sitestaffr-source-chunks.php';

        /**
         * AI Client for middleware integration (v1.0: Summary generation only)
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-ai-client.php';

        /**
         * Email Notifications Handler
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-email-notifications.php';

        /**
         * AI Summary Generator - Auto-generates summaries when calls complete
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-ai-summary-generator.php';

        /**
         * Usage Manager - Minute-based quota management
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-usage-manager.php';

        /**
         * Billing State Cache Manager (Phase 5 cutover support)
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-billing-state.php';

        /**
         * Conversation Compressor - Compress transcripts to structured JSON (60-70% token reduction)
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-conversation-compressor.php';

        /**
         * Magic Link URL Generator and Verifier for public transcript access.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-magic-link.php';

        /**
         * Public Transcript Viewer route handler.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-transcript-viewer.php';

        /**
         * Qualified Leads — flagging, token, cycle query, CSV, and leads page.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-leads.php';

        /**
         * Which conversations produce an email notification (all vs qualified only).
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-notification-scope.php';

        /**
         * Agent-health alerts — emails the owner when the agent pauses and recovers.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-agent-health-alerts.php';

        /**
         * Native Salesforce integration — Connect/Disconnect + real-time lead push.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-salesforce.php';

        /**
         * Dashboard triage — pure partition/grouping for the admin dashboard.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-dashboard-triage.php';

        /**
         * Test Mode - token plumbing for "Test your widget" sessions.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-test-mode.php';

        /**
         * Shareable Usage & Analytics report (token-authenticated public page).
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-usage-report.php';

        /**
         * Blog Review & Publish — no-login review/edit/publish route.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-blog-review.php';

        /**
         * Content Chunker - Extracts and chunks post/page content for RAG embedding.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-content-chunker.php';

        /**
         * Knowledge Manager - DB operations for RAG chunk storage and vector search.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-knowledge-manager.php';

        /**
         * AI-Visibility Tracker — state store + prompt seeding from the business profile.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-visibility.php';

        /**
         * Background Business Identity job — learns the site on cron so the work
         * survives the owner closing the wizard tab.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-identity-job.php';
        add_action( 'sitestaffr_identity_job_tick', array( 'SiteStaffr_Identity_Job', 'tick' ) );
        // A brand-new site may have no visitors to drive WP-Cron, and some hosts drop
        // cron events. Every admin page load re-arms an unfinished job.
        add_action( 'admin_init', array( 'SiteStaffr_Identity_Job', 'ensure_scheduled' ) );

        /**
         * Blog Manager - Blog post job CRUD and WordPress draft creation.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-blog-manager.php';

        // Cron handler for retrying featured-image attachment after a failed inline sideload.
        add_action( 'sitestaffr_attach_blog_image', array( 'SiteStaffr_Blog_Manager', 'process_pending_blog_image' ) );

        // Plugin-owned autopilot: WP-Cron fires at the next scheduled slot; the
        // tick generates the post and reschedules itself. All schedule/queue/budget
        // state lives in this site's own database.
        add_action( 'sitestaffr_blog_autopilot_tick', array( 'SiteStaffr_Blog_Manager', 'run_autopilot_tick' ) );

        // AI-Visibility Tracker: same self-rescheduling single-event pattern as
        // the blog autopilot tick above, but monthly and with no plugin-side
        // enable toggle — see SiteStaffr_Visibility::reschedule() for why.
        add_action( 'sitestaffr_visibility_tick', array( 'SiteStaffr_Visibility', 'run_tick' ) );

        // Agent-health alerts ride the billing-cache refresh rather than their own
        // cron: a paused agent is discovered inside the existing TTL, which is
        // ample for something a human takes hours to act on.
        add_action( 'sitestaffr_billing_cache_refreshed', array( 'SiteStaffr_Agent_Health_Alerts', 'maybe_alert' ) );

        // Pull-delivery poller: recovers posts whose middleware push was blocked
        // by host bot-protection (parked middleware-side, 24h TTL). Every 5 min.
        add_filter( 'cron_schedules', function ( $schedules ) { // phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval -- 5 min is the pull-delivery SLA.
            if ( ! isset( $schedules['sitestaffr_five_minutes'] ) ) {
                $schedules['sitestaffr_five_minutes'] = array(
                    'interval' => 5 * MINUTE_IN_SECONDS,
                    'display'  => __( 'Every 5 minutes (SiteStaffr)', 'sitestaffr' ),
                );
            }
            return $schedules;
        } );
        add_action( 'sitestaffr_blog_pickup_poll', array( 'SiteStaffr_Blog_Manager', 'pickup_poll' ) );
        // Same 5-min cadence: fail rows stuck 'generating' >30 min (a middleware
        // deploy mid-generation kills its background pipeline silently; the row
        // must not hang forever). A payload that survives after all still
        // self-heals a swept row via the import path.
        add_action( 'sitestaffr_blog_pickup_poll', array( 'SiteStaffr_Blog_Manager', 'sweep_stuck_jobs' ) );
        // Fast path for the same failure: the middleware now marks in-flight
        // jobs failed + retryable on SIGTERM, so an interrupted autopilot post
        // is recovered on the next 5-min tick instead of after the 30-min
        // sweep — and its topic goes back on the queue to be retried once.
        add_action( 'sitestaffr_blog_pickup_poll', array( 'SiteStaffr_Blog_Manager', 'recover_interrupted_jobs' ) );

        // Reservoir refill: single background event scheduled whenever the
        // topic pool runs low (batch planning takes ~30-60s middleware-side —
        // never on a page load or skip).
        add_action( 'sitestaffr_blog_reservoir_refill', array( 'SiteStaffr_Blog_Manager', 'fetch_reservoir_batch' ) );
        add_action( 'admin_init', function () {
            if ( ! wp_next_scheduled( 'sitestaffr_blog_pickup_poll' ) ) {
                wp_schedule_event( time() + MINUTE_IN_SECONDS, 'sitestaffr_five_minutes', 'sitestaffr_blog_pickup_poll' );
            }
        } );

        // Self-heal: deactivation clears the cron event; re-arm it on the next
        // admin visit if autopilot is enabled but no tick is scheduled.
        add_action( 'admin_init', function () {
            $blog_settings = SiteStaffr_Blog_Manager::get_settings();
            if ( ! empty( $blog_settings['autopilot_enabled'] ) && ! wp_next_scheduled( 'sitestaffr_blog_autopilot_tick' ) ) {
                SiteStaffr_Blog_Manager::reschedule_autopilot();
            }
        } );

        // Same self-heal for the visibility tracker's monthly tick (always
        // scheduled — see SiteStaffr_Visibility::reschedule()).
        add_action( 'admin_init', function () {
            if ( ! wp_next_scheduled( 'sitestaffr_visibility_tick' ) ) {
                SiteStaffr_Visibility::reschedule();
            }
        } );

        // Keep the Blog Agent content index (de-dup + internal-link source of
        // truth, all post types incl. CPTs) fresh: invalidate on any publish /
        // edit / status change, with a daily cron as the backstop rebuild.
        add_action( 'save_post', array( 'SiteStaffr_Blog_Manager', 'clear_content_index_cache' ) );
        add_action( 'transition_post_status', array( 'SiteStaffr_Blog_Manager', 'clear_content_index_cache' ) );
        add_action( SiteStaffr_Blog_Manager::CONTENT_INDEX_CRON, array( 'SiteStaffr_Blog_Manager', 'rebuild_content_index' ) );
        add_action( 'admin_init', function () {
            if ( ! wp_next_scheduled( SiteStaffr_Blog_Manager::CONTENT_INDEX_CRON ) ) {
                wp_schedule_event( time() + DAY_IN_SECONDS, 'daily', SiteStaffr_Blog_Manager::CONTENT_INDEX_CRON );
            }
        } );

        // Front-end spacing for generated FAQ sections (only on posts that contain one).
        add_action( 'wp_enqueue_scripts', array( 'SiteStaffr_Blog_Manager', 'enqueue_faq_styles' ) );

        // Clear site pages cache when any post is saved (for Page Expert Mode navigation).
        add_action( 'save_post', array( 'SiteStaffr_Knowledge_Manager', 'clear_site_pages_cache' ) );

        /**
         * Shared public AJAX route registry for widget handlers.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'public/class-sitestaffr-public-ajax-router.php';

        /**
         * Shared request guard helpers for public AJAX handlers.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'public/class-sitestaffr-public-request-guards.php';

        /**
         * Shared middleware request client for public AJAX handlers.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'public/class-sitestaffr-public-middleware-client.php';

        /**
         * Widget public module loader.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'public/modules/class-sitestaffr-widget-module-loader.php';

        /**
         * Shared conversation usage persistence handlers for public AJAX handlers.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'public/class-sitestaffr-public-conversation-usage.php';

        /**
         * The fixed position widget shortcode handler.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'public/class-sitestaffr-widget.php';

        /**
         * The button widget shortcode handler.
         */
        require_once SITESTAFFR_PLUGIN_DIR . 'public/class-sitestaffr-button-widget.php';

        $this->loader = new SiteStaffr_Loader();
    }

    /**
     * Register all of the hooks related to the admin area functionality
     * of the plugin.
     *
     * @since    1.0.0
     * @access   private
     */
    private function define_admin_hooks() {
        $plugin_admin = new SiteStaffr_Admin( $this->get_plugin_name(), $this->get_version() );

        $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
        $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' );
        $this->loader->add_action( 'admin_menu', $plugin_admin, 'add_admin_menu' );
        $this->loader->add_action( 'admin_init', $plugin_admin, 'register_settings' );

        // Setup wizard redirect hook - MANDATORY BLOCKING
        $this->loader->add_action( 'admin_init', $plugin_admin, 'maybe_redirect_to_wizard' );

        // One-time redirect to setup wizard on first activation.
        $this->loader->add_action( 'admin_init', $plugin_admin, 'maybe_redirect_after_activation' );

        // Descriptive browser tab title for conversation detail pages.
        $this->loader->add_filter( 'admin_title', $plugin_admin, 'filter_admin_title', 10, 2 );

        // Staging-only: reset description generation count via URL parameter
        $this->loader->add_action( 'admin_init', $plugin_admin, 'maybe_reset_staging_gen_count' );

        // Show setup incomplete notice on all admin pages
        $this->loader->add_action( 'admin_notices', $plugin_admin, 'show_setup_incomplete_notice' );
        $this->loader->add_action( 'admin_notices', $plugin_admin, 'show_minutes_exhausted_notice' );

        // Add SiteStaffr top admin-bar menu with quick links.
        $this->loader->add_action( 'admin_bar_menu', $plugin_admin, 'add_admin_bar_menu', 80 );


        // CRITICAL FIX: Register AJAX handlers directly via loader, NOT via admin_init.
        // WordPress AJAX handlers (wp_ajax_*) must be registered BEFORE admin_init completes
        // because admin-ajax.php checks for registered handlers after admin_init fires.
        // Using admin_init to register them can cause timing issues where the handler
        // isn't found because it's registered too late in the request lifecycle.
        $this->loader->add_action( 'wp_ajax_sitestaffr_save_wizard', $plugin_admin, 'ajax_save_wizard' );

        // Background Business Identity job: the wizard starts it and polls it; the
        // work itself runs on cron (SiteStaffr_Identity_Job).
        $this->loader->add_action( 'wp_ajax_sitestaffr_start_identity_job', $plugin_admin, 'ajax_start_identity_job' );
        $this->loader->add_action( 'wp_ajax_sitestaffr_identity_job_status', $plugin_admin, 'ajax_identity_job_status' );

        // Late-priority hook to dequeue block editor scripts that other plugins may have enqueued
        // This prevents wp.data undefined errors on the setup wizard page
        $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'dequeue_block_editor_scripts_late', 999 );
    }

    /**
     * Register all of the hooks related to webhook functionality
     * of the plugin.
     *
     * @since    1.0.0
     * @access   private
     */
    private function define_webhook_hooks() {
        $plugin_setup_api = new SiteStaffr_Setup_API();

        $this->loader->add_action( 'rest_api_init', $plugin_setup_api, 'register_routes' );
    }

    /**
     * Register all of the hooks related to REST API functionality
     * for middleware integration.
     *
     * @since    0.11.88
     * @access   private
     */
    private function define_rest_api_hooks() {
        // Defensive load guard: prevent full-site fatal if this class file
        // was missed in deployment or failed to load for any reason.
        if ( ! class_exists( 'SiteStaffr_REST_API' ) ) {
            $rest_api_path = SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-rest-api.php';
            if ( file_exists( $rest_api_path ) ) {
                require_once $rest_api_path;
            }
        }

        if ( ! class_exists( 'SiteStaffr_REST_API' ) ) {
            SiteStaffr_Logger::legacy( 'SiteStaffr: REST API class unavailable; skipping REST route registration to avoid fatal.' );
            return;
        }

        $plugin_rest_api = new SiteStaffr_REST_API();

        $this->loader->add_action( 'rest_api_init', $plugin_rest_api, 'register_routes' );
    }

    /**
     * Register all of the hooks related to email notifications.
     *
     * Hooks the email notification system into call completion events
     * to automatically send email alerts when calls finish.
     *
     * The sitestaffr_call_completed action fires when a call ends with ANY
     * terminal status (completed, no-answer, failed, busy, canceled).
     * This includes when callers hang up mid-conversation.
     *
     * @since    1.0.0
     * @access   private
     */
    private function define_email_hooks() {
        $email_notifications = new SiteStaffr_Email_Notifications();

        // Send email notification when a call ends (any terminal status)
        // Priority 10, accepts 2 arguments: $call_id, $call_status
        $this->loader->add_action( 'sitestaffr_call_completed', $email_notifications, 'send_call_notification', 10, 2 );

        // One-time welcome email when the setup wizard finishes (send-once guarded)
        $this->loader->add_action( 'sitestaffr_setup_wizard_completed', $email_notifications, 'send_welcome_email' );

    }

    /**
     * Register AI Summary Generator hooks.
     *
     * Auto-generates and stores AI summaries when calls complete.
     *
     * @since    1.0.0
     * @access   private
     */
    private function define_ai_summary_hooks() {
        $ai_summary_generator = new SiteStaffr_AI_Summary_Generator();

        // Register hook via loader to avoid duplicate registration
        // The hook is registered HERE, not in the constructor, to prevent duplicates
        // Accepts 4 parameters: $call_id, $call_status, $contact_fields, $submission_source
        $this->loader->add_action( 'sitestaffr_call_completed', $ai_summary_generator, 'generate_and_store_summary', 5, 4 );

        // Flag qualified leads (name + phone/email) at conversation end.
        // Accepts 3 of the 4 args: $call_id, $call_status, $contact_fields.
        $this->loader->add_action( 'sitestaffr_call_completed', 'SiteStaffr_Leads', 'maybe_flag_qualified_lead', 5, 3 );

        // Native Salesforce push — runs AFTER the flag (priority 20) and only
        // schedules a non-blocking WP-Cron push when connected. The cron handler
        // does the actual send. Excludes bulk backfill (which doesn't fire this hook).
        $this->loader->add_action( 'sitestaffr_call_completed', 'SiteStaffr_Salesforce', 'maybe_push_on_completed', 20, 3 );
        $this->loader->add_action( SiteStaffr_Salesforce::PUSH_HOOK, 'SiteStaffr_Salesforce', 'push_lead', 10, 1 );

    }

    /**
     * Register all of the hooks related to WebRTC widget functionality.
     *
     * Initializes widget classes which register shortcodes for
     * fixed position and button widgets.
     *
     * @since    0.11.75
     * @access   private
     */
    private function define_widget_hooks() {
        // Initialize fixed position widget [sitestaffr_widget]
        new SiteStaffr_Widget();

        // Initialize button widget [sitestaffr_button]
        new SiteStaffr_Button_Widget();

    }

    /**
     * Register the public transcript viewer route handler.
     *
     * Hooks into template_redirect to intercept magic link URLs
     * and render public transcript pages without login.
     *
     * @since  1.2.0
     * @access private
     */
    private function define_transcript_viewer_hooks() {
        $transcript_viewer = new SiteStaffr_Transcript_Viewer();
        $this->loader->add_action( 'template_redirect', $transcript_viewer, 'maybe_render_transcript' );
        $this->loader->add_action( 'template_redirect', 'SiteStaffr_Leads', 'maybe_render_leads_page' );
        $this->loader->add_action( 'template_redirect', 'SiteStaffr_Usage_Report', 'maybe_render_report_page' );
        $this->loader->add_action( 'template_redirect', 'SiteStaffr_Blog_Review', 'maybe_render_review' );
    }

    /**
     * Run the loader to execute all of the hooks with WordPress.
     *
     * @since    1.0.0
     */
    public function run() {
        // Check if database needs upgrade based on version
        $this->maybe_upgrade_database();
        $this->maybe_refresh_site_registration();

        if ( class_exists( 'SiteStaffr_Leads' ) ) {
            SiteStaffr_Leads::backfill_existing();
        }

        $this->loader->run();
    }

    /**
     * Refresh site registration periodically for resilient middleware authentication.
     *
     * Prevents stale/missing in-memory registration issues in middleware by
     * re-registering this site on a low-frequency cadence.
     *
     * @since 0.13.78
     * @access private
     */
    private function maybe_refresh_site_registration() {
        // Never do network registration refresh on public page loads.
        // Keep this as an admin-only maintenance task to avoid front-end instability.
        if ( ! is_admin() ) {
            return;
        }

        // Compliance: do not "phone home" until admin setup/onboarding is complete.
        $is_setup_complete = false;
        if ( class_exists( 'SiteStaffr_Admin' ) && method_exists( 'SiteStaffr_Admin', 'is_setup_complete' ) ) {
            $is_setup_complete = (bool) SiteStaffr_Admin::is_setup_complete();
        } else {
            $is_setup_complete = (bool) get_option( 'sitestaffr_setup_completed', false );
        }

        if ( ! $is_setup_complete ) {
            return;
        }

        // Refresh at most once every 6 hours to minimize overhead.
        if ( get_transient( 'sitestaffr_site_registration_recent' ) ) {
            return;
        }

        if ( ! class_exists( 'SiteStaffr_Site_Registration' ) ) {
            require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-site-registration.php';
        }

        if ( class_exists( 'SiteStaffr_Site_Registration' ) ) {
            try {
                $result = SiteStaffr_Site_Registration::register_site();
            } catch ( Exception $e ) {
                $result = false;
                SiteStaffr_Logger::legacy( 'SiteStaffr: Periodic site registration refresh exception - ' . $e->getMessage() );
            }

            if ( ! $result ) {
                SiteStaffr_Logger::legacy( 'SiteStaffr: Periodic site registration refresh failed' );
            }
        }

        set_transient( 'sitestaffr_site_registration_recent', 1, 6 * HOUR_IN_SECONDS );
    }

    /**
     * Check if database needs upgrade and run migration if needed.
     *
     * Compares stored plugin version with current version.
     * If version changed, runs database migration.
     * Safe to run on every plugin load (checks version first).
     *
     * NOTE: This also runs on EVERY plugin load to ensure database columns exist.
     * The upgrade_database() method is smart enough to skip if columns already exist.
     *
     * @since    1.1.0
     * @access   private
     */
    private function maybe_upgrade_database() {
        // Rebrand migration: copy phoneease_* options to sitestaffr_* (one-time).
        if ( ! get_option( 'sitestaffr_rebrand_migrated' ) && false !== get_option( 'phoneease_settings' ) ) {
            $this->migrate_options_from_phoneease();
        }

        // Fix bad middleware URL written by earlier rebrand code (safe to run every time).
        $current_mw_url = get_option( 'sitestaffr_middleware_url' );
        if ( $current_mw_url && false !== strpos( $current_mw_url, 'sitestaffr-middleware-' ) ) {
            update_option( 'sitestaffr_middleware_url', str_replace( 'sitestaffr-middleware-', 'phoneease-middleware-', $current_mw_url ) );
        }

        $stored_version = get_option( 'sitestaffr_version', '0.0.0' );
        $current_version = SITESTAFFR_VERSION;

        // Legacy cleanup: ensure stale transient from older upgrade logic cannot block checks.
        if ( get_transient( 'sitestaffr_db_upgrade_completed' ) ) {
            delete_transient( 'sitestaffr_db_upgrade_completed' );
        }

        // ALWAYS run database upgrade check - it's safe because upgrade_database() checks column existence
        SiteStaffr_Database::upgrade_database();

        // Run minute-based billing migration if needed
        SiteStaffr_Database::run_minute_based_migration();

        // ALWAYS update stored version to match current version
        // This fixes version mismatch issues where stored version > current version
        if ( $stored_version !== $current_version ) {
            update_option( 'sitestaffr_version', $current_version );

            // Re-register with middleware on version upgrade to refresh per-site API secret.
            // Safe to call on every upgrade — register_site() is idempotent.
            if ( class_exists( 'SiteStaffr_Site_Registration' ) && SiteStaffr_Site_Registration::is_registered() ) {
                SiteStaffr_Site_Registration::register_site();
            }
        }

        // Legacy cleanup: auto-placement migration flags are obsolete after v0.14.166.
        delete_option( 'sitestaffr_widget_toggle_migrated' );
        delete_option( 'sitestaffr_widget_toggle_migrated_v2' );
    }

    /**
     * Migrate wp_options from phoneease_* to sitestaffr_* keys.
     *
     * One-time migration for the PhoneEase → SiteStaffr rebrand.
     * Copies values from old keys to new keys (does not overwrite if new key already exists).
     * Old keys are preserved for rollback safety.
     *
     * @since 0.14.270
     */
    private function migrate_options_from_phoneease() {
        $option_keys = array(
            'account_id',
            'api_secret',
            'billing_cache',
            'business_description',
            'business_hours',
            'business_phone',
            'db_version',
            'debug_logging',
            'enable_fragments',
            'installation_id',
            'middleware_url',
            'minute_balance',
            'registration_date',
            'settings',
            'setup_completed',
            'site_api_secret',
            'site_registered',
            'site_secret',
            'site_token',
            'subscription',
            'use_realtime',
            'version',
            'widget_settings',
        );

        foreach ( $option_keys as $key ) {
            $old_value = get_option( 'phoneease_' . $key );
            if ( false !== $old_value && false === get_option( 'sitestaffr_' . $key ) ) {
                update_option( 'sitestaffr_' . $key, $old_value );
            }
        }

        update_option( 'sitestaffr_rebrand_migrated', true );
        SiteStaffr_Logger::legacy( 'SiteStaffr: Completed phoneease → sitestaffr options migration' );
    }

    /**
     * The name of the plugin used to uniquely identify it within the context of
     * WordPress and to define internationalization functionality.
     *
     * @since     1.0.0
     * @return    string    The name of the plugin.
     */
    public function get_plugin_name() {
        return $this->plugin_name;
    }

    /**
     * Retrieve the version number of the plugin.
     *
     * @since     1.0.0
     * @return    string    The version number of the plugin.
     */
    public function get_version() {
        return $this->version;
    }
}
