<?php
/*
Plugin Name: RANK AI by Morningscore
Description: Automatically fix health problems found in Morningscore via RANK AI
Version: 1.0.6
Author: Morningscore
*/


/* ==== documentation ====

class-rank-admin.php: all the admin functionality
class-rank-ajax.php: all the ajax functionality for processing and instant updates and logging
class-rank-processor.php: Uses Action Scheduler to process the batch data in chunks and save new data to db
api-caller.php: all the api functionality for calling the RANK AI API and get health fixes. Called by Action Scheduler.
class-rank-frontend-filters.php: Fetching data from db and using hooks/filters to modify frontend

*/

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

// Define plugin constants
define( 'RANK_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'RANK_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'RANK_DB_VERSION', '1.9' ); // Database version - incremented for image_optimize page_urls column
define( 'RANK_TABLE_NAME', 'rank_seo_overrides' ); // Table name without prefix
define( 'RANK_EXACT_ALT_MATCHING', false ); // Set to true for exact alt tag matching (slower but more precise)

// Determine context once for conditional loading
$rank_is_admin_context = ( is_admin() || wp_doing_ajax() || wp_doing_cron() );
$rank_is_agent_rest    = (
    isset( $_SERVER['REQUEST_URI'] )
    && strpos( $_SERVER['REQUEST_URI'], '/wp-json/rank-ai/v1/agent' ) !== false
);
$rank_needs_background = ( $rank_is_admin_context || $rank_is_agent_rest );

// Plugin Update Checker — only needed in admin context
if ( $rank_is_admin_context ) {
    require_once RANK_PLUGIN_DIR . 'plugin-update-checker/plugin-update-checker.php';
    $rankUpdateChecker = YahnisElsts\PluginUpdateChecker\v5p5\PucFactory::buildUpdateChecker(
        'https://morningscore.io/dl/rank/wp-rank-ai-update-plugin.json',
        __FILE__,
        'rank-ai-by-morningscore'
    );
}

// Action Scheduler — needed for admin/cron/ajax and ms-chat agent REST
if ( $rank_needs_background ) {
    require_once RANK_PLUGIN_DIR . 'lib/action-scheduler/action-scheduler.php';
}

// Include files needed on ALL requests (frontend + admin)
// config.php: defines rank_normalize_url(), rank_debug_log(), and other shared helpers
// class-rank-frontend-filters.php: the only class that actively runs on frontend
require_once RANK_PLUGIN_DIR . 'includes/config.php';
require_once RANK_PLUGIN_DIR . 'includes/class-rank-frontend-filters.php';

// Include admin / agent-background files — not needed for frontend visitors
if ( $rank_needs_background ) {
    require_once RANK_PLUGIN_DIR . 'includes/api-caller.php';
    require_once RANK_PLUGIN_DIR . 'includes/class-rank-processor.php';
    require_once RANK_PLUGIN_DIR . 'includes/class-rank-admin.php';
    require_once RANK_PLUGIN_DIR . 'includes/class-rank-ajax.php';
    require_once RANK_PLUGIN_DIR . 'includes/class-rank-already-fixed-checker.php';
    require_once RANK_PLUGIN_DIR . 'includes/class-rank-delete-handler.php';
    require_once RANK_PLUGIN_DIR . 'includes/class-rank-url-list.php';
    require_once RANK_PLUGIN_DIR . 'includes/rank-writer/class-rank-writer.php';
    require_once RANK_PLUGIN_DIR . 'includes/ms-chat/class-rank-ms-chat-services.php';
}

require_once RANK_PLUGIN_DIR . 'includes/ms-chat/class-rank-ms-chat.php';
require_once RANK_PLUGIN_DIR . 'includes/ms-chat/class-rank-ms-chat-agent.php';

// Rank Booster handles its own frontend/admin split internally
require_once RANK_PLUGIN_DIR . 'includes/rank-booster/class-rank-booster.php';

// Register activation/deactivation hooks - these are fine here
register_activation_hook( __FILE__, 'rank_activate_plugin' );
register_deactivation_hook( __FILE__, 'rank_deactivate_plugin' );

/**
 * Plugin activation.
 */
function rank_activate_plugin() {
    global $wpdb;
    $table_name = $wpdb->prefix . RANK_TABLE_NAME;
    $charset_collate = $wpdb->get_charset_collate();

    // Check if the table already exists to decide if we need to update the schema
    // $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) ) === $table_name;

    // SQL to create/update the table
    // IMPORTANT: dbDelta is sensitive to formatting.
    // Each field should be on its own line.
    // There should be two spaces between PRIMARY KEY and the definition of the key.
    // There should be one space between KEY/UNIQUE KEY and the key name.
    $sql = "CREATE TABLE $table_name (
        override_id mediumint(9) NOT NULL AUTO_INCREMENT,
        post_id bigint(20) UNSIGNED NOT NULL,
        object_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
        override_type varchar(50) NOT NULL,
        issue_type varchar(50) DEFAULT NULL,
        override_value longtext NOT NULL,
        original_page_url text DEFAULT NULL,
        image_url text DEFAULT NULL,
        page_urls text DEFAULT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY  (override_id),
        UNIQUE KEY unique_override (post_id, object_id, override_type, original_page_url(255)),
        KEY idx_post_type (post_id, override_type),
        KEY idx_object_type (object_id, override_type),
        KEY idx_image_type (image_url(191), override_type)
    ) $charset_collate;";

    // Include upgrade script for dbDelta()
    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql );

    // Store the version
    update_option('rank_db_version', RANK_DB_VERSION, false);

    // Fix autoload settings for existing options
    rank_fix_autoload_settings();

    // Clear any remaining permission check cache
    delete_transient('rank_db_permissions_check');

    // Invalidate frontend filter caches (table structure may have changed)
    delete_transient('rank_has_overrides');
    delete_transient('rank_has_url_replacements');

    // Create Rank Writer uploads table
    require_once RANK_PLUGIN_DIR . 'includes/rank-writer/class-rank-writer-db.php';
    Rank_Writer_DB::create_table();

    // Create Rank Booster jobs table
    require_once RANK_PLUGIN_DIR . 'includes/rank-booster/class-rank-booster-db.php';
    Rank_Booster_DB::create_table();

    Rank_Ms_Chat::schedule_cron();
    Rank_Ms_Chat::register( true );
}

/**
 * Plugin deactivation.
 */
function rank_deactivate_plugin() {
    // Clear scheduled actions
    if ( function_exists( 'as_unschedule_all_actions' ) ) {
        as_unschedule_all_actions( 'rank_process_queue_item' );
        // Stop the Rank Booster background stuck-job watchdog.
        as_unschedule_all_actions( 'rank_booster_watchdog' );
    }

    Rank_Ms_Chat::clear_cron();
    Rank_Ms_Chat::unregister();
}

/**
 * Enqueue admin scripts with proper dependencies
 */
function rank_enqueue_admin_scripts() {
    // Only load on admin pages
    if (!is_admin()) {
        return;
    }

    // Get the current screen to check if we're on our plugin page
    $screen = get_current_screen();
    if ( ! $screen || 'toplevel_page_rank-admin' !== $screen->id ) {
        return;
    }

    // Determine current tab for conditional loading
    $current_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'bulk_fix';

    // Helper function to get file modification time for cache busting
    function get_file_version($file_path) {
        $full_path = RANK_PLUGIN_DIR . $file_path;
        return file_exists($full_path) ? filemtime($full_path) : '1.0';
    }

    // ── Shared scripts (all tabs) ──
    wp_enqueue_script('jquery');

    wp_enqueue_script(
        'rank-helpers',
        RANK_PLUGIN_URL . 'admin/js/modules/helpers.js',
        array('jquery'),
        get_file_version('admin/js/modules/helpers.js'),
        true
    );

    // Localize shared data on the helpers handle (available to all tabs)
    wp_localize_script('rank-helpers', 'rank_ajax_obj', array(
        'ajax_url' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('rank_ajax_nonce'),
        'plugin_url' => RANK_PLUGIN_URL,
        'status_preparing' => __('Preparing health issues...', 'rank-seo'),
        'status_reviewing' => __('Ready for review. Decide for each item.', 'rank-seo'),
        'status_scheduling' => __('Scheduling tasks for processing...', 'rank-seo'),
        'status_prepared' => __('Health issues prepared. Ready to start fixing.', 'rank-seo'),
        'status_complete' => __('Processing Complete!', 'rank-seo'),
        'prepare_urls_text' => __('Prepare URLs', 'rank-seo'),
        'start_fixing_text' => __('Start Fixing', 'rank-seo'),
        'text_decide_label' => __('Review', 'rank-seo'),
        'error_scheduling' => __('Error scheduling tasks.', 'rank-seo'),
        'error_no_batch_id' => __('No batch ID found. Please try preparing again.', 'rank-seo'),
        'delete_success_message' => __('All RANK AI data deleted successfully.', 'rank-seo'),
        'delete_error_message' => __('Error deleting data.', 'rank-seo'),
        'confirm_delete_text' => __('DELETE', 'rank-seo'),
        'confirm_cancel_text' => __('Are you sure you want to cancel? This will reset all processing. All processed fixes are saved ✅.', 'rank-seo'),
        'force_reload_on_cancel' => apply_filters('rank_force_reload_on_cancel', '0'),
        'text_page' => __('Page', 'rank-seo'),
        'text_of' => __('of', 'rank-seo'),
        'text_items' => __('items', 'rank-seo'),
        'text_prev' => __('Previous', 'rank-seo'),
        'text_next' => __('Next', 'rank-seo'),
        'issue_type_labels' => array(
            'missing_alt_tags' => __('Image missing alt tag', 'rank-seo'),
            'missing_meta_description' => __('Missing meta description', 'rank-seo'),
            'duplicate_meta_description' => __('Duplicate meta description', 'rank-seo'),
            'duplicate_title' => __('Duplicate title', 'rank-seo'),
        ),
        // Categories that edit real content and store the original, so their
        // reversal is a "Restore original" (not a "Delete"). Derived from config
        // so new content-editing fixes are picked up automatically.
        'restore_categories' => array_values( array_keys( array_filter(
            function_exists('rank_get_issue_type_config') ? rank_get_issue_type_config() : array(),
            function ( $cfg ) { return isset( $cfg['handler_type'] ) && $cfg['handler_type'] === 'content_fix'; }
        ) ) ),
        'text_restore' => __('Restore', 'rank-seo'),
        'text_restore_original' => __('Restore original', 'rank-seo'),
        'text_confirm_restore' => __('Restore the original? This undoes the fix and puts the original content back.', 'rank-seo'),
        'text_restore_success' => __('Original restored.', 'rank-seo'),
    ));

    // ── Bulk Fix tab (health issues, approval, processing) ──
    if ( $current_tab === 'bulk_fix' ) {
        wp_enqueue_script(
            'rank-health-issues',
            RANK_PLUGIN_URL . 'admin/js/modules/sequential-health-issues.js',
            array('jquery', 'rank-helpers'),
            get_file_version('admin/js/modules/sequential-health-issues.js'),
            true
        );

        wp_enqueue_script(
            'rank-approval',
            RANK_PLUGIN_URL . 'admin/js/modules/approval.js',
            array('jquery', 'rank-helpers'),
            get_file_version('admin/js/modules/approval.js'),
            true
        );

        wp_enqueue_script(
            'rank-processing',
            RANK_PLUGIN_URL . 'admin/js/modules/processing.js',
            array('jquery', 'rank-helpers', 'rank-approval'),
            get_file_version('admin/js/modules/processing.js'),
            true
        );

        // Main admin script — orchestrates bulk fix modules
        wp_enqueue_script(
            'rank-admin',
            RANK_PLUGIN_URL . 'admin/js/admin.js',
            array('jquery', 'rank-helpers', 'rank-health-issues', 'rank-approval', 'rank-processing'),
            get_file_version('admin/js/admin.js'),
            true
        );

        // Localize health issues data
        $health_check_categories = function_exists('rank_get_health_check_categories') ? rank_get_health_check_categories() : array();
        wp_localize_script('rank-health-issues', 'rankLocalizedData', array(
            'ajax_url' => admin_url('admin-ajax.php'),
            'nonce'    => wp_create_nonce('rank_ajax_nonce'),
            'healthCheckCategories' => $health_check_categories,
            'text_credits_exhausted' => __('Your AI credits have run out.', 'rank-seo'),
            'text_no_issues_found' => __('No health issues found that require fixing. Good job!', 'rank-seo'),
            'text_no_issues_credits_exhausted' => __('No health issues found that require fixing, and AI credits have run out.', 'rank-seo'),
            'text_error_loading_issues' => __('Error loading health issues:', 'rank-seo'),
            'text_ajax_error_loading_issues' => __('AJAX Error loading health issues:', 'rank-seo'),
            'text_config_missing' => __('Error: Health check categories configuration is missing.', 'rank-seo'),
            'text_all_good' => __('All good ✅', 'rank-seo'),
            'text_fix_issues' => __('Fix Issues', 'rank-seo'),
            'status_preparing' => __('Preparing health issues...', 'rank-seo'),
            'tooltip_already_fixed' => __('Try to do a full Health scan. Expect ≈10% fixes to not be detected. WordPress is very complex. Nothing breaks if it does not work. Good luck, captain! 🧑‍🚀👍️', 'rank-seo')
        ));
        // Fixes JS (subtab inside bulk_fix)
        wp_enqueue_script(
            'rank-admin-fixes',
            RANK_PLUGIN_URL . 'admin/js/admin-fixes.js',
            array('jquery', 'rank-helpers'),
            get_file_version('admin/js/admin-fixes.js'),
            true
        );

        // Booster CSS needed for subtab styling
        wp_enqueue_style(
            'rank-booster-css',
            RANK_PLUGIN_URL . 'admin/css/rank-booster.css',
            array(),
            get_file_version('admin/css/rank-booster.css')
        );
    }

    // ── Data Management tab (delete data + debug logs from admin.js) ──
    if ( $current_tab === 'data_management' ) {
        wp_enqueue_script(
            'rank-admin',
            RANK_PLUGIN_URL . 'admin/js/admin.js',
            array('jquery', 'rank-helpers'),
            get_file_version('admin/js/admin.js'),
            true
        );
    }

    // ── API key tab (ms-chat connect) ──
    if ( $current_tab === 'api_config' ) {
        wp_enqueue_script(
            'rank-ms-chat-connect',
            RANK_PLUGIN_URL . 'admin/js/ms-chat-connect.js',
            array( 'jquery' ),
            get_file_version( 'admin/js/ms-chat-connect.js' ),
            true
        );

        wp_localize_script(
            'rank-ms-chat-connect',
            'rank_ms_chat_obj',
            array(
                'ajax_url' => admin_url( 'admin-ajax.php' ),
                'nonce'    => wp_create_nonce( 'rank_ajax_nonce' ),
            )
        );
    }

    // ── Rank Writer tab ──
    if ( $current_tab === 'rank_writer' ) {
        wp_enqueue_script(
            'rank-writer',
            RANK_PLUGIN_URL . 'admin/js/modules/rank-writer.js',
            array('jquery', 'rank-helpers'),
            get_file_version('admin/js/modules/rank-writer.js'),
            true
        );

        wp_enqueue_style(
            'rank-writer-css',
            RANK_PLUGIN_URL . 'admin/css/rank-writer.css',
            array(),
            get_file_version('admin/css/rank-writer.css')
        );
    }

    // ── Rank Booster tab ──
    if ( $current_tab === 'page_booster' ) {
        wp_enqueue_script(
            'rank-booster',
            RANK_PLUGIN_URL . 'admin/js/modules/rank-booster.js',
            array('jquery', 'rank-helpers'),
            get_file_version('admin/js/modules/rank-booster.js'),
            true
        );

        wp_enqueue_script(
            'rank-booster-processing',
            RANK_PLUGIN_URL . 'admin/js/modules/rank-booster-processing.js',
            array('jquery', 'rank-helpers', 'rank-booster'),
            get_file_version('admin/js/modules/rank-booster-processing.js'),
            true
        );

        wp_enqueue_style(
            'rank-booster-css',
            RANK_PLUGIN_URL . 'admin/css/rank-booster.css',
            array(),
            get_file_version('admin/css/rank-booster.css')
        );
    }
}
add_action('admin_enqueue_scripts', 'rank_enqueue_admin_scripts');

/**
 * Initialize the plugin's main functionalities.
 * This function will be hooked to 'plugins_loaded'.
 */
function rank_initialize_plugin_components() {
    $is_admin_context = ( is_admin() || wp_doing_ajax() || wp_doing_cron() );
    $is_agent_rest    = (
        isset( $_SERVER['REQUEST_URI'] )
        && strpos( $_SERVER['REQUEST_URI'], '/wp-json/rank-ai/v1/agent' ) !== false
    );
    $needs_background = ( $is_admin_context || $is_agent_rest );

    // DB version check — only needed in admin/cron context (avoids DB query on every frontend load)
    if ( $is_admin_context ) {
        $installed_db_version = get_option('rank_db_version');
        if ($installed_db_version !== RANK_DB_VERSION) {
            rank_update_db_check();
        }
    }

    // --- Admin-only UI ---
    if ( $is_admin_context ) {
        Rank_Admin::init();
        Rank_URL_List::init();
    }

    // --- Admin / agent REST background processing ---
    if ( $needs_background ) {
        Rank_Ajax::init();
        Rank_Processor::init();
        Rank_Writer::init();
        add_filter( 'action_scheduler_queue_runner_batch_size', 'rank_filter_action_scheduler_batch_size_callback' );
    }

    // --- Frontend Filters — always initialize (this is the core frontend functionality) ---
    Rank_Frontend_Filters::init();

    // --- Rank Booster handles its own frontend/admin split internally ---
    Rank_Booster::init();

    // --- ms-chat registration, connect UI, and agent REST ---
    Rank_Ms_Chat::init();
    Rank_Ms_Chat_Agent::init();
}
// Use a priority that allows other plugins (like WooCommerce, which also uses AS) to load first if necessary.
// A priority of 20: Safe bet.
add_action( 'plugins_loaded', 'rank_initialize_plugin_components', 20 );

/**
 * Callback function for filtering Action Scheduler batch size.
 * and to follow a more common callback naming pattern.
 */
function rank_filter_action_scheduler_batch_size_callback( $batch_size ) {
    // Set batch size to 1 due to long-running API calls.
    return 1;
}

/**
 * Check if database needs to be updated and run the update if needed
 */
function rank_update_db_check() {
    if (get_option('rank_db_version') != RANK_DB_VERSION) {
        rank_activate_plugin();
    }
}

/**
 * Fix autoload settings for specific RANK plugin options
 * This runs during plugin activation and updates
 */
function rank_fix_autoload_settings() {
    global $wpdb;

    // Specific options that should never be autoloaded
    $options_to_fix = array(
        'rank_internal_url_replacements',
        'rank_external_url_replacements',
        'rank_db_version',
        'rank_debug_enabled',
        'rank_ms_chat_agent_token',
        'rank_ms_chat_resource_id',
        'rank_ms_chat_linked_at',
        'rank_ms_chat_agent_service_secret',
        'rank_ms_chat_last_register',
    );

    foreach ($options_to_fix as $option_name) {
        // Check if option exists and has wrong autoload setting
        $current_autoload = $wpdb->get_var($wpdb->prepare(
            "SELECT autoload FROM {$wpdb->options} WHERE option_name = %s",
            $option_name
        ));

        // Fix if it's set to 'yes' or 'auto' (both mean autoloaded)
        if ($current_autoload === 'yes' || $current_autoload === 'auto') {
            $wpdb->update(
                $wpdb->options,
                array('autoload' => 'no'),
                array('option_name' => $option_name),
                array('%s'),
                array('%s')
            );

            error_log("RANK Plugin: Fixed autoload setting for {$option_name} (was: {$current_autoload}, now: no)");
        }
    }

    // Also fix any batch options that might exist
    $wpdb->query("UPDATE {$wpdb->options} SET autoload = 'no' WHERE option_name LIKE 'rank_batch_%' AND autoload IN ('yes', 'auto')");
}


/**
 * Check for system issues and display admin notices.
 */
function rank_check_system_status() {
    // Only run on admin pages
    if (!is_admin()) {
        return;
    }

    // Check if main table exists - that's what matters most
    $table_exists = Rank_Processor::check_table_exists();

    if (!$table_exists) {
        add_action('admin_notices', function() {
            echo '<div class="error"><p>';
            echo '<strong>Rank AI Plugin:</strong> Database table missing. ';
            echo 'Try deactivating and reactivating the plugin to create the table.';
            echo '</p></div>';
        });
        return; // If table doesn't exist, no point checking other things
    }

    // Check Action Scheduler
    $action_scheduler = Rank_Processor::check_action_scheduler();

    if (!$action_scheduler['exists'] || !$action_scheduler['hooks_registered']) {
        add_action('admin_notices', function() {
            echo '<div class="notice notice-warning"><p>';
            echo '<strong>Rank AI Plugin:</strong> Action Scheduler is not properly configured. ';
            echo 'This will prevent scheduled SEO fixes from running.';
            echo '</p></div>';
        });
    }
}
add_action('admin_init', 'rank_check_system_status');

// REST API endpoint for external tools to verify plugin is activated
add_action('rest_api_init', function () {
    register_rest_route('rank-ai/v1', '/status', array(
        'methods'             => 'GET',
        'callback'            => function () {
            return new WP_REST_Response(array('status' => 'Activated'), 200);
        },
        'permission_callback' => '__return_true',
    ));
});

?>
