<?php
/*
 * Plugin Name: AuctionForge
 * Description: Auction Integration for BidSpirit by SmoothByte IT
 * Version: 3.7.1.1
 * Requires at least: 5.2
 * Requires PHP: 7.2
 * Author: SmoothByte IT
 * Author URI: https://smoothbyteit.dev
 * Text Domain: auctionforge
 **/

use auctionforge\inc\abs\Importer;
use auctionforge\inc\controllers\BidspiritLots;
use auctionforge\inc\controllers\PolylangSupport;
use auctionforge\inc\CurlRequest;
use auctionforge\inc\Debug;
use auctionforge\inc\helpers\Conditions;
use auctionforge\inc\helpers\SettingsHelper;
use auctionforge\inc\import\AuctionList;
use auctionforge\inc\import\InsertLotsBS;
use auctionforge\inc\import\LotList;
use auctionforge\inc\InstanceDB;
use auctionforge\inc\model\FirebaseLog;
use auctionforge\inc\model\FirebaseUser;
use auctionforge\inc\model\LotMetaData;
use auctionforge\inc\model\SubmissionFormData;
// use YahnisElsts\PluginUpdateChecker\v5\PucFactory; // Disabled - update checker removed

if (!defined('ABSPATH')) {
    die('You shouldnt be here');
}

function auctionforge_plugin_info() {
    return get_file_data(__FILE__, ['ver' => 'Version']);
}

function auctionforge_plugin_file_info($relPath) {
    if (file_exists($relPath)) {
        return get_file_data($relPath, ['ver' => 'Version']);
    } else {
        return null;
    }
}

$data = auctionforge_plugin_info();

define('AUCTIONFORGE_VERSION', $data['ver']);
define('AUCTIONFORGE_DB_VERSION', 25);
define('AUCTIONFORGE_DB_OPTION_NAME', 'auctionforge_db_version');
define('AUCTIONFORGE_DB_UPDATE_VERSION', 6);
define('AUCTIONFORGE_DB_UPDATE_OPTION_NAME', 'auctionforge_db_update_version');
define('AUCTIONFORGE_CONFIG_OPTION_NAME', 'auctionforge_config_version');
define('AUCTIONFORGE_CONFIG_VERSION', 4);

define('AUCTIONFORGE_SYNC_START', '_auctionforge_sync_start');
define('AUCTIONFORGE_SYNC_ARCHIVE_START', '_auctionforge_sync_archive_start');
define('AUCTIONFORGE_SYNC_DATA_START', '_auctionforge_sync_data_start');
define('BASE_PATH', __DIR__);
define('AUCTIONFORGE_PLUGIN_SLUG', basename(__DIR__));

/** Sentry error reporting - DISABLED */

/**
 * Limit for the InsertLots task
 */
if (!defined('RAW_LOT_LIMIT')) {
    define('RAW_LOT_LIMIT', 250);
}
if (!defined('TRY_LOT_LIMIT')) {
    define('TRY_LOT_LIMIT', 3);
}
if (!defined('RECORDS_EXPIRE')) {
    define('RECORDS_EXPIRE', 3);
}
if (!defined('BP_HOURLY')) {
    define('BP_HOURLY', 3600);
}
if (!defined('BP_MINUTELY')) {
    define('BP_MINUTELY', 60);
}
if (!defined('BP_24_HOURLY')) {
    define('BP_24_HOURLY', 86400 * 5);
}

// Sync v3 — webhook-first architecture constants
if (!defined('AUCTIONFORGE_SYNC_WORKER_TIMEOUT')) {
    define('AUCTIONFORGE_SYNC_WORKER_TIMEOUT', 240);
}
if (!defined('AUCTIONFORGE_SYNC_STALE_JOB')) {
    define('AUCTIONFORGE_SYNC_STALE_JOB', 600);
}
if (!defined('AUCTIONFORGE_SYNC_HEALTH_CHECK')) {
    define('AUCTIONFORGE_SYNC_HEALTH_CHECK', 86400);
}
if (!defined('AUCTIONFORGE_SYNC_LOG_RETENTION')) {
    define('AUCTIONFORGE_SYNC_LOG_RETENTION', 7);
}
if (!defined('AUCTIONFORGE_SYNC_QUEUE_RETENTION')) {
    define('AUCTIONFORGE_SYNC_QUEUE_RETENTION', 30);
}
if (!defined('AUCTIONFORGE_CURL_TIMEOUT')) {
    define('AUCTIONFORGE_CURL_TIMEOUT', 30);
}
if (!defined('AUCTIONFORGE_PHP_BINARY')) {
    define('AUCTIONFORGE_PHP_BINARY', '/www/server/php/83/bin/php');

}

function getDirAuctionForgePlugins() {
    return plugin_dir_path( __FILE__ );
}

require_once plugin_dir_path( __FILE__ ) . 'inc/coreIncludes.php';
require_once plugin_dir_path( __FILE__ ) . 'inc/ip-logger.php';

// AuctionForge Legal Agreements
require_once plugin_dir_path( __FILE__ ) . 'inc/legal/class-legal-agreements.php';

// AuctionForge Site Statistics
require_once plugin_dir_path( __FILE__ ) . 'inc/stats/class-stats-init.php';
AF_Stats_Init::init();

register_activation_hook(__FILE__, 'activatePlugin');
register_deactivation_hook(__FILE__, 'deactivatePlugin');
register_uninstall_hook(__FILE__, 'uninstallPlugin');

function activatePlugin() {
    createTableImportHistoryTable();
    createBpFirebaseTables();
    createBpLotDataTable();
    createBpSubmissonDataTable();
    createBpMetaTableIndexes();
    createBpViewHistoryTable();
    createLotAnalyticsTables();
    AF_Stats_Init::activate();
    auctionforge_create_ip_log_table();
    AF_Legal_Agreements::create_tables();
    AF_Legal_Agreements::seed_defaults();
    createSyncQueueTable();
    createSyncLogTable();
    \auctionforge\inc\model\SMSSubscriber::createTable();

    delete_option('rewrite_rules');
    update_option('rewrite_rules', '');
}

function auctionforge_upgrade_action($upgraderObject, $hookExtra) {
    // Only run for AuctionForge plugin upgrades
    if (!isset($hookExtra['plugins']) || !is_array($hookExtra['plugins'])) return;
    if (!in_array(AUCTIONFORGE_PLUGIN_BASENAME, $hookExtra['plugins'])) return;

    // Run all DB table creation/updates on plugin upgrade
    createTableImportHistoryTable();
    createBpFirebaseTables();
    createBpLotDataTable();
    createBpSubmissonDataTable();
    createBpMetaTableIndexes();
    createBpViewHistoryTable();
    createLotAnalyticsTables();
    AF_Stats_Init::activate();
    auctionforge_create_ip_log_table();
    AF_Legal_Agreements::create_tables();
    AF_Legal_Agreements::seed_defaults();
    createSyncQueueTable();
    createSyncLogTable();
    \auctionforge\inc\model\SMSSubscriber::createTable();

    delete_option('rewrite_rules');
    update_option('rewrite_rules', '');
}
add_action('upgrader_process_complete', 'auctionforge_upgrade_action', 10, 2);

/**
 * AuctionForge Auto Updater - managed by WP Plugin Deployer
 */
require_once plugin_dir_path(__FILE__) . 'inc/class-auto-updater.php';

define('AUCTIONFORGE_PLUGIN_BASENAME', plugin_basename(__FILE__));

$auctionforge_updater = new AuctionForge_Auto_Updater(AUCTIONFORGE_PLUGIN_BASENAME, AUCTIONFORGE_VERSION);

// AJAX handlers and admin menu for update settings and sync status (admin only)
if (is_admin()) {
    add_action('admin_menu', 'auctionforge_register_dashboard_page', 5);
    add_action('admin_menu', 'auctionforge_register_updates_page');
    add_action('admin_menu', 'auctionforge_register_sync_status_page');
    add_action('admin_menu', 'auctionforge_register_ip_log_page');
    add_action('admin_menu', 'auctionforge_register_legal_agreements_page');
    add_action('wp_ajax_auctionforge_get_version_content', 'auctionforge_ajax_get_version_content');
    add_action('wp_ajax_auctionforge_search_ip_logs', 'auctionforge_ajax_search_ip_logs');
    add_action('wp_ajax_auctionforge_export_ip_logs', 'auctionforge_ajax_export_ip_logs');
    add_action('wp_ajax_auctionforge_lookup_linked', 'auctionforge_ajax_lookup_linked');
    add_action('wp_ajax_auctionforge_test_update_connection', 'auctionforge_ajax_test_update_connection');
    add_action('wp_ajax_auctionforge_check_updates_now', 'auctionforge_ajax_check_updates_now');
    add_action('wp_ajax_auctionforge_report_health_now', 'auctionforge_ajax_report_health_now');
    add_action('wp_ajax_auctionforge_save_update_settings', 'auctionforge_ajax_save_update_settings');
    add_action('wp_ajax_auctionforge_register_site', 'auctionforge_ajax_register_site');
    add_action('wp_ajax_auctionforge_request_access', 'auctionforge_ajax_request_access');
    add_action('wp_ajax_auctionforge_clear_registration', 'auctionforge_ajax_clear_registration');
    add_action('wp_ajax_auctionforge_diagnose_connection', 'auctionforge_ajax_diagnose_connection');
    add_action('wp_ajax_auctionforge_fix_connection', 'auctionforge_ajax_fix_connection');
    add_action('wp_ajax_auctionforge_run_cron_task', 'auctionforge_ajax_run_cron_task');
    add_action('wp_ajax_auctionforge_run_wpcron_task', 'auctionforge_ajax_run_wpcron_task');
    add_action('wp_ajax_auctionforge_sync_tracking_now', 'auctionforge_ajax_sync_tracking_now');
    add_action('wp_ajax_auctionforge_sync_enforcement_now', 'auctionforge_ajax_sync_enforcement_now');

    // Sync v3 — webhook-first AJAX handlers
    add_action('wp_ajax_auctionforge_sync_log_poll', 'auctionforge_ajax_sync_log_poll');
    add_action('wp_ajax_auctionforge_sync_queue_status', 'auctionforge_ajax_sync_queue_status');
    add_action('wp_ajax_auctionforge_sync_enqueue', 'auctionforge_ajax_sync_enqueue');
    add_action('wp_ajax_auctionforge_sync_cancel', 'auctionforge_ajax_sync_cancel');
    add_action('wp_ajax_auctionforge_clear_import_history', 'auctionforge_ajax_clear_import_history');
    add_action('wp_ajax_auctionforge_clear_cron_dupes', 'auctionforge_ajax_clear_cron_dupes');
}

// Sync v3 — worker REST endpoint (must be outside is_admin() for REST context)
add_action('rest_api_init', 'auctionforge_register_sync_worker_endpoint');

// IP logging AJAX - available to both logged-in and non-logged-in users
add_action('wp_ajax_auctionforge_log_ip', 'auctionforge_ajax_log_ip');
add_action('wp_ajax_nopriv_auctionforge_log_ip', 'auctionforge_ajax_log_ip');

// Legal agreements AJAX - fetch agreements and log consent (both logged-in and non-logged-in)
add_action('wp_ajax_auctionforge_get_legal_agreements', 'auctionforge_ajax_get_legal_agreements');
add_action('wp_ajax_nopriv_auctionforge_get_legal_agreements', 'auctionforge_ajax_get_legal_agreements');
add_action('wp_ajax_auctionforge_log_agreement', 'auctionforge_ajax_log_agreement');
add_action('wp_ajax_nopriv_auctionforge_log_agreement', 'auctionforge_ajax_log_agreement');

// Bid count AJAX - available to both logged-in and non-logged-in users
add_action('wp_ajax_auctionforge_record_bid', 'auctionforge_ajax_record_bid');
add_action('wp_ajax_nopriv_auctionforge_record_bid', 'auctionforge_ajax_record_bid');

// Registration proxy - routes through WP for server-side validation
add_action('wp_ajax_auctionforge_proxy_register', 'auctionforge_ajax_proxy_register');
add_action('wp_ajax_nopriv_auctionforge_proxy_register', 'auctionforge_ajax_proxy_register');

// SMS Notifications - save/load preferences (requires BidSpirit auth, both contexts)
add_action('wp_ajax_auctionforge_sms_save', 'auctionforge_ajax_sms_save');
add_action('wp_ajax_nopriv_auctionforge_sms_save', 'auctionforge_ajax_sms_save');
add_action('wp_ajax_auctionforge_sms_load', 'auctionforge_ajax_sms_load');
add_action('wp_ajax_nopriv_auctionforge_sms_load', 'auctionforge_ajax_sms_load');
add_action('wp_ajax_auctionforge_sms_test', 'auctionforge_ajax_sms_test');

// Invoice PDF generator
if (class_exists('\auctionforge\inc\api\InvoicePdfGenerator')) {
    \auctionforge\inc\api\InvoicePdfGenerator::init();
}

function auctionforge_ajax_record_bid() {
    check_ajax_referer('auctionforge_bid_count', 'nonce');

    $post_id = intval($_POST['post_id'] ?? 0);
    if (empty($post_id)) {
        wp_send_json_error('No post_id provided');
        return;
    }

    $term_id = intval($_POST['term_id'] ?? 0);

    $result = \auctionforge\inc\model\LotAnalytics::recordBid($post_id, $term_id);
    $newCount = \auctionforge\inc\model\LotAnalytics::getEffectiveBidCount($post_id);

    wp_send_json_success(array('recorded' => (bool)$result, 'bid_count' => $newCount));
}

// Registration proxy: server-side validation before forwarding to BidSpirit
function auctionforge_ajax_proxy_register() {
    check_ajax_referer('auctionforge_reg_proxy', 'nonce');

    $ip = auctionforge_get_client_ip();

    // --- Rate limiting: max 5 registration attempts per IP per hour ---
    $rate_key = 'af_reg_rate_' . md5($ip);
    $attempts = intval(get_transient($rate_key));
    if ($attempts >= 5) {
        auctionforge_record_proxy_rejection(
            sanitize_text_field($_POST['firstName'] ?? ''),
            sanitize_text_field($_POST['lastName'] ?? ''),
            sanitize_email($_POST['email'] ?? ''),
            'rate_limit',
            'IP ' . $ip . ' exceeded 5 registrations/hour (attempt ' . ($attempts + 1) . ')'
        );
        // Fake BidSpirit success response so bot doesn't know it was blocked
        wp_send_json(array(
            'response' => array(
                'email' => sanitize_email($_POST['email'] ?? ''),
                'firstName' => sanitize_text_field($_POST['firstName'] ?? ''),
                'lastName' => sanitize_text_field($_POST['lastName'] ?? ''),
            )
        ));
        return;
    }
    set_transient($rate_key, $attempts + 1, HOUR_IN_SECONDS);

    $firstName = sanitize_text_field($_POST['firstName'] ?? '');
    $lastName  = sanitize_text_field($_POST['lastName'] ?? '');
    $email     = sanitize_email($_POST['email'] ?? '');

    // --- Honeypot check ---
    $honeypot = sanitize_text_field($_POST['company_url'] ?? '');
    if (!empty($honeypot)) {
        auctionforge_record_proxy_rejection($firstName, $lastName, $email, 'honeypot', 'company_url field filled: ' . $honeypot);
        wp_send_json(array(
            'response' => array(
                'email' => $email,
                'firstName' => $firstName,
                'lastName' => $lastName,
            )
        ));
        return;
    }

    // --- Server-side name validation ---
    $nameError = auctionforge_validate_name_server($firstName, $lastName);
    if ($nameError !== false) {
        auctionforge_record_proxy_rejection($firstName, $lastName, $email, 'name_validation', $nameError);
        wp_send_json(array(
            'response' => array(
                'email' => $email,
                'firstName' => $firstName,
                'lastName' => $lastName,
            )
        ));
        return;
    }

    // --- All checks passed: DO NOT forward via curl ---
    // Instead of acting as a proxy, we simply tell the frontend validation passed
    wp_send_json(array(
        'response' => array(
            'validation_passed' => true,
        )
    ));
}

function auctionforge_validate_name_server($firstName, $lastName) {
    $firstName = trim($firstName);
    $lastName  = trim($lastName);

    if (strlen($firstName) < 1 || strlen($lastName) < 1) {
        return 'Name fields cannot be empty';
    }

    $names = array('First name' => $firstName, 'Last name' => $lastName);
    foreach ($names as $label => $name) {
        if (strlen($name) > 25) return $label . ' is too long';

        if (strlen($name) >= 8) {
            // Case transition ratio
            $transitions = 0;
            for ($i = 1; $i < strlen($name); $i++) {
                $pU = ctype_upper($name[$i-1]);
                $cU = ctype_upper($name[$i]);
                $pL = ctype_lower($name[$i-1]);
                $cL = ctype_lower($name[$i]);
                if (($pU && $cL && $i > 1) || ($pL && $cU)) $transitions++;
            }
            if ($transitions / strlen($name) > 0.35 && strlen($name) >= 10)
                return $label . ' appears to contain random characters';

            // Internal uppercase cluster: 3+ consecutive uppercase NOT at start
            if (preg_match_all('/[A-Z]{3,}/', $name, $matches, PREG_OFFSET_CAPTURE)) {
                foreach ($matches[0] as $match) {
                    if ($match[1] > 0) {
                        return $label . ' appears to contain random characters';
                    }
                }
            }

            // Vowel check
            $vowels = preg_match_all('/[aeiouAEIOU]/', $name);
            if ($vowels === 0 && strlen($name) >= 6)
                return $label . ' must contain at least one vowel';
            if ($vowels / strlen($name) < 0.12 && strlen($name) >= 10)
                return $label . ' appears invalid';

            // Repeated characters
            if (preg_match('/(.)\1{4,}/', $name))
                return $label . ' contains too many repeated characters';
        }

        if (preg_match('/^\d+$/', $name)) return $label . ' cannot be only numbers';
    }
    return false;
}

function auctionforge_record_proxy_rejection($firstName, $lastName, $email, $reason, $details) {
    if (!function_exists('auctionforge_get_client_ip')) return;
    global $wpdb;
    $table = $wpdb->prefix . 'auctionforge_rejected_registrations';
    if ($wpdb->get_var("SHOW TABLES LIKE '$table'") !== $table) return;
    $wpdb->insert($table, array(
        'first_name'        => $firstName,
        'last_name'         => $lastName,
        'email'             => $email,
        'ip_address'        => auctionforge_get_client_ip(),
        'device_id'         => '',
        'device_fingerprint'=> '',
        'rejection_reason'  => $reason,
        'rejection_details' => $details,
        'user_agent'        => sanitize_text_field($_SERVER['HTTP_USER_AGENT'] ?? ''),
        'created_at'        => current_time('mysql'),
    ));
}

// Fraud check AJAX - available to both logged-in and non-logged-in users
add_action('wp_ajax_auctionforge_check_fraud', 'auctionforge_ajax_check_fraud');
add_action('wp_ajax_nopriv_auctionforge_check_fraud', 'auctionforge_ajax_check_fraud');

function auctionforge_register_dashboard_page() {
    add_submenu_page(
        'edit.php?post_type=lot',
        __('AuctionForge Dashboard', 'auctionforge'),
        __('Dashboard', 'auctionforge'),
        'manage_options',
        'auctionforge-dashboard',
        'auctionforge_render_dashboard_page',
        0
    );
}

function auctionforge_render_dashboard_page() {
    require_once plugin_dir_path(__FILE__) . 'admin/view/dashboard.php';
}

function auctionforge_register_updates_page() {
    add_submenu_page(
        'edit.php?post_type=lot',
        __('AuctionForge Updates', 'auctionforge'),
        __('Updates', 'auctionforge'),
        'manage_options',
        'auctionforge-updates',
        'auctionforge_render_updates_page'
    );
}

function auctionforge_render_updates_page() {
    require_once plugin_dir_path(__FILE__) . 'admin/view/updates.php';
}

function auctionforge_register_sync_status_page() {
    add_submenu_page(
        'edit.php?post_type=lot',
        __('Sync Status', 'auctionforge'),
        __('Sync Status', 'auctionforge'),
        'manage_options',
        'auctionforge-sync-status',
        'auctionforge_render_sync_status_page'
    );
}

function auctionforge_render_sync_status_page() {
    require_once plugin_dir_path(__FILE__) . 'admin/view/sync-status.php';
}

function auctionforge_register_legal_agreements_page() {
    add_submenu_page(
        'edit.php?post_type=lot',
        __('Legal Agreements', 'auctionforge'),
        __('Legal Agreements', 'auctionforge'),
        'manage_options',
        'auctionforge-legal-agreements',
        'auctionforge_render_legal_agreements_page'
    );
}

function auctionforge_render_legal_agreements_page() {
    require_once plugin_dir_path(__FILE__) . 'admin/view/legal-agreements.php';
}

/**
 * AJAX: Get enabled legal agreements for the frontend approval modal.
 */
function auctionforge_ajax_get_legal_agreements() {
    check_ajax_referer('bp-ajax-string', 'security');
    $agreements = AF_Legal_Agreements::get_enabled_agreements();
    wp_send_json_success(array('agreements' => $agreements));
}

/**
 * AJAX: Log a user's agreement acceptance during auction approval.
 */
function auctionforge_ajax_log_agreement() {
    check_ajax_referer('bp-ajax-string', 'security');

    $agreement_type = sanitize_text_field(isset($_POST['agreement_type']) ? $_POST['agreement_type'] : '');
    $version_agreed = intval(isset($_POST['version_agreed']) ? $_POST['version_agreed'] : 0);
    $user_email     = sanitize_email(isset($_POST['user_email']) ? $_POST['user_email'] : '');
    $auction_id     = sanitize_text_field(isset($_POST['auction_id']) ? $_POST['auction_id'] : '');
    $visitor_hash   = sanitize_text_field(isset($_POST['visitor_hash']) ? $_POST['visitor_hash'] : '');

    $valid_types = array(
        AF_Legal_Agreements::TYPE_TERMS,
        AF_Legal_Agreements::TYPE_REFUND,
        AF_Legal_Agreements::TYPE_CHARGEBACK,
    );

    if (!in_array($agreement_type, $valid_types) || $version_agreed < 1 || empty($user_email)) {
        wp_send_json_error('Invalid parameters');
        return;
    }

    $ip = function_exists('auctionforge_get_client_ip') ? auctionforge_get_client_ip() : '0.0.0.0';
    $user_agent = sanitize_text_field(isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');

    $result = AF_Legal_Agreements::log_agreement(array(
        'agreement_type' => $agreement_type,
        'version_agreed' => $version_agreed,
        'user_email'     => $user_email,
        'visitor_hash'   => $visitor_hash,
        'ip_address'     => $ip,
        'user_agent'     => $user_agent,
        'auction_id'     => $auction_id,
    ));

    if ($result) {
        wp_send_json_success(array('logged' => true, 'id' => $result));
    } else {
        wp_send_json_error('Failed to log agreement');
    }
}

/**
 * AJAX: Get the content of a specific agreement version (admin only).
 */
function auctionforge_ajax_get_version_content() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) {
        wp_send_json_error('Unauthorized');
        return;
    }

    $type    = sanitize_text_field(isset($_POST['agreement_type']) ? $_POST['agreement_type'] : '');
    $version = intval(isset($_POST['version']) ? $_POST['version'] : 0);
    $user_email = sanitize_email(isset($_POST['user_email']) ? $_POST['user_email'] : '');

    if (empty($type) || $version < 1) {
        wp_send_json_error('Invalid parameters');
        return;
    }

    $doc = AF_Legal_Agreements::get_version_content($type, $version);
    if ($doc) {
        // Try to resolve user name from the user tracking system
        $user_name = '';
        if (!empty($user_email)) {
            global $wpdb;
            $tracking_table = $wpdb->prefix . 'auctionforge_user_tracking';
            $tracked_user = $wpdb->get_row($wpdb->prepare(
                "SELECT first_name, last_name FROM $tracking_table WHERE email = %s LIMIT 1",
                $user_email
            ));
            if ($tracked_user) {
                $user_name = trim($tracked_user->first_name . ' ' . $tracked_user->last_name);
            }
        }

        wp_send_json_success(array(
            'content'   => $doc->content,
            'version'   => intval($doc->version),
            'type'      => $type,
            'label'     => AF_Legal_Agreements::get_type_label($type),
            'date'      => isset($doc->updated_at) ? $doc->updated_at : (isset($doc->created_at) ? $doc->created_at : ''),
            'user_name' => $user_name,
        ));
    } else {
        wp_send_json_error('Version not found');
    }
}

function auctionforge_ajax_run_cron_task() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    $allowed_tasks = array('importBidSpirit', 'importData', 'lotBidSpiritOld', 'cleaner', 'cleanerTrashMeta', 'safety_sync');
    $task = isset($_POST['task']) ? sanitize_text_field($_POST['task']) : '';

    if (!in_array($task, $allowed_tasks)) {
        wp_send_json_error('Invalid task: ' . $task);
        return;
    }

    // Run task directly via PHP classes (exec/shell_exec disabled in PHP-FPM)
    $result = array('success' => true, 'details' => '');
    $error  = null;

    // Buffer any stray output (PHP notices/deprecations emitted by the task or
    // its BidSpirit API calls) so it cannot corrupt the JSON response. Without
    // this, a single notice turned a successful run into a misleading
    // "Request failed (HTTP 200)" in the admin (HTTP 200 body, unparseable JSON).
    ob_start();

    try {
        switch ($task) {
            case 'importBidSpirit':
                $auctionList = new \auctionforge\inc\import\AuctionList();
                $auctionList->init();
                $lotList = new \auctionforge\inc\import\LotList();
                $lotList->setPriority(4);
                $lotList->init();
                $result['details'] = 'Auction list + lot list synced (upcoming)';
                break;

            case 'importData':
                $insertLots = new \auctionforge\inc\import\InsertLotsBS();
                $insertLots->init();
                $result['details'] = 'Import queue processed';
                break;

            case 'lotBidSpiritOld':
                $auctionList = new \auctionforge\inc\import\AuctionList();
                $auctionList->init();
                $lotList = new \auctionforge\inc\import\LotList();
                $lotList->setPriority(5);
                $lotList->init(false);
                $result['details'] = 'Auction list + lot list synced (archived)';
                break;

            case 'cleaner':
                $deleted = 0;
                $lots = get_posts(array(
                    'post_type' => 'lot',
                    'numberposts' => 100,
                    'post_status' => array('draft', 'trash'),
                    'lang' => '',
                ));
                foreach ($lots as $lot) {
                    wp_delete_post($lot->ID, true);
                    $deleted++;
                }
                $result['details'] = sprintf('%d draft/trash lots deleted', $deleted);
                break;

            case 'cleanerTrashMeta':
                \auctionforge\inc\helpers\CleanerTrashMetaFields::run();
                $result['details'] = 'Orphaned meta fields cleaned';
                break;

            case 'safety_sync':
                $summary = \auctionforge\inc\sync\SafetySync::run();
                $parts = [];
                if ($summary['stuck_reset'] > 0) $parts[] = $summary['stuck_reset'] . ' stuck job(s) reset';
                if ($summary['missed_ended'] > 0) $parts[] = $summary['missed_ended'] . ' missed-end webhook(s) detected';
                if ($summary['delta_synced'] > 0) $parts[] = $summary['delta_synced'] . ' delta sync(s) enqueued';
                if ($summary['orphans_fixed'] > 0) $parts[] = $summary['orphans_fixed'] . ' orphan(s) fixed';
                if ($summary['queue_purged'] > 0 || $summary['log_purged'] > 0) $parts[] = $summary['queue_purged'] . '/' . $summary['log_purged'] . ' old rows purged';
                if (empty($parts)) $parts[] = 'All systems healthy — nothing to fix';
                $result['details'] = implode('; ', $parts);
                break;
        }
    } catch (\Exception $e) {
        $error = 'Task failed: ' . $e->getMessage();
    } catch (\Error $e) {
        $error = 'Task error: ' . $e->getMessage();
    }

    // Discard (but log) any stray output so the JSON response stays clean.
    $stray = trim((string) ob_get_clean());
    if ($stray !== '') {
        \auctionforge\inc\sync\SyncLogger::log('admin', "run_cron_task({$task}) suppressed stray output: " . mb_substr($stray, 0, 300), 'warning');
    }

    if ($error !== null) {
        wp_send_json_error($error);
        return;
    }

    wp_send_json_success(array(
        'message' => sprintf('Task "%s" completed. %s', $task, $result['details']),
    ));
}

function auctionforge_ajax_clear_cron_dupes() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    if (!class_exists('AF_Cron_Cleanup')) {
        wp_send_json_error('Cron cleanup unavailable');
        return;
    }

    $cleared = AF_Cron_Cleanup::force_sweep();
    $total = array_sum($cleared);

    if ($total > 0) {
        $parts = array();
        foreach ($cleared as $hook => $n) { $parts[] = "{$hook}: {$n}"; }
        wp_send_json_success(['message' => "Cleared {$total} duplicate/orphan cron event(s) — " . implode(', ', $parts)]);
    } else {
        wp_send_json_success(['message' => 'No duplicate or orphan cron events found — schedule is clean.']);
    }
}

function auctionforge_ajax_run_wpcron_task() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    $hook = isset($_POST['hook']) ? sanitize_text_field($_POST['hook']) : '';
    $allowed_hooks = array('bidspirit_import', 'data_import', 'bidspirit_archive_import');

    if (!in_array($hook, $allowed_hooks)) {
        wp_send_json_error('Invalid hook: ' . $hook);
        return;
    }

    $server = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';

    // Fire the action directly
    do_action($hook, $server);

    wp_send_json_success(array(
        'message' => sprintf('WP-Cron hook "%s" executed successfully', $hook)
    ));
}

function auctionforge_ajax_test_update_connection() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }
    
    $server_url = isset($_POST['server_url']) ? esc_url_raw($_POST['server_url']) : '';
    $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
    
    if (empty($server_url) || empty($api_key)) {
        wp_send_json_error('Server URL and API Key are required');
        return;
    }
    
    $url = rtrim($server_url, '/') . '/api.php/info';
    $response = wp_remote_get($url, array(
        'headers' => array('X-API-Key' => $api_key),
        'timeout' => 15
    ));
    
    if (is_wp_error($response)) {
        wp_send_json_error('Connection failed: ' . $response->get_error_message());
        return;
    }
    
    $body = json_decode(wp_remote_retrieve_body($response), true);
    
    if (isset($body['app_version'])) {
        wp_send_json_success(array('message' => 'Connection successful!', 'version' => $body['app_version']));
    } else {
        wp_send_json_error('Invalid response from server');
    }
}

function auctionforge_ajax_check_updates_now() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }
    
    delete_site_transient('update_plugins');
    wp_update_plugins();
    
    $updates = get_site_transient('update_plugins');
    $has_update = isset($updates->response[AUCTIONFORGE_PLUGIN_BASENAME]);
    
    if ($has_update) {
        $update_data = $updates->response[AUCTIONFORGE_PLUGIN_BASENAME];
        wp_send_json_success(array(
            'has_update' => true,
            'new_version' => $update_data->new_version,
            'message' => 'Update available: version ' . $update_data->new_version
        ));
    } else {
        wp_send_json_success(array('has_update' => false, 'message' => 'Plugin is up to date'));
    }
}

function auctionforge_ajax_report_health_now() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }
    
    $updater = new AuctionForge_Auto_Updater(AUCTIONFORGE_PLUGIN_BASENAME, AUCTIONFORGE_VERSION);
    $updater->report_health();
    
    wp_send_json_success(array(
        'message' => 'Health report sent successfully',
        'wp_version' => get_bloginfo('version'),
        'php_version' => PHP_VERSION,
        'plugin_version' => AUCTIONFORGE_VERSION
    ));
}

function auctionforge_ajax_sync_enforcement_now() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }
    
    $updater = new AuctionForge_Auto_Updater(AUCTIONFORGE_PLUGIN_BASENAME, AUCTIONFORGE_VERSION);
    $updater->sync_fraud_enforcement();
    
    $enforcement = get_option('auctionforge_fraud_enforcement', false);
    $synced_at = get_option('auctionforge_fraud_enforcement_synced_at', '');
    
    wp_send_json_success(array(
        'message' => 'Enforcement synced: ' . ($enforcement ? 'ENFORCING' : 'DRY RUN'),
        'enforcement' => $enforcement,
        'synced_at' => $synced_at
    ));
}

function auctionforge_ajax_save_update_settings() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }
    
    $server_url = isset($_POST['server_url']) ? esc_url_raw($_POST['server_url']) : '';
    $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
    $auto_update = isset($_POST['auto_update']) ? (bool) $_POST['auto_update'] : false;
    $fraud_enforcement = isset($_POST['fraud_enforcement']) ? (bool) $_POST['fraud_enforcement'] : null;
    
    update_option('auctionforge_update_server_url', $server_url);
    update_option('auctionforge_update_api_key', $api_key);
    update_option('auctionforge_auto_update', $auto_update);
    
    if ($fraud_enforcement !== null) {
        update_option('auctionforge_fraud_enforcement', $fraud_enforcement);
    }
    
    wp_send_json_success(array('message' => 'Settings saved successfully!'));
}

function auctionforge_ajax_register_site() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }
    
    $server_url = isset($_POST['server_url']) ? esc_url_raw($_POST['server_url']) : '';
    $master_key = isset($_POST['master_key']) ? sanitize_text_field($_POST['master_key']) : '';
    
    if (empty($server_url) || empty($master_key)) {
        wp_send_json_error('Server URL and Master Key are required');
        return;
    }
    
    update_option('auctionforge_update_server_url', $server_url);
    
    $updater = new AuctionForge_Auto_Updater(AUCTIONFORGE_PLUGIN_BASENAME, AUCTIONFORGE_VERSION);
    $result = $updater->register_site($master_key);
    
    if ($result['success']) {
        wp_send_json_success($result);
    } else {
        wp_send_json_error($result['message']);
    }
}

function auctionforge_ajax_request_access() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }
    
    $server_url = isset($_POST['server_url']) ? esc_url_raw($_POST['server_url']) : '';
    
    if (empty($server_url)) {
        wp_send_json_error('Deployment Server URL is required');
        return;
    }
    
    update_option('auctionforge_update_server_url', $server_url);
    
    $updater = new AuctionForge_Auto_Updater(AUCTIONFORGE_PLUGIN_BASENAME, AUCTIONFORGE_VERSION);
    $result = $updater->request_access();
    
    if ($result['success']) {
        wp_send_json_success($result);
    } else {
        wp_send_json_error($result['message']);
    }
}

function auctionforge_ajax_clear_registration() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    delete_option('auctionforge_update_api_key');
    wp_send_json_success(array('message' => 'Registration cleared successfully'));
}

/**
 * Connection diagnostics: ask wpdeploy /whoami who it thinks we are, then
 * compare to home_url(). Surfaces identity mismatches caused by
 * wp_options carried over from a clone/template (where the stored api_key
 * resolves to a DIFFERENT wpdeploy.sites row than this site's home_url(),
 * so all sync/health/fraud data ends up filed under the wrong site).
 */
function auctionforge_ajax_diagnose_connection() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    $updater = new AuctionForge_Auto_Updater(AUCTIONFORGE_PLUGIN_BASENAME, AUCTIONFORGE_VERSION);
    $result  = $updater->whoami();

    if (!is_array($result) || empty($result['success'])) {
        wp_send_json_error(array(
            'message' => 'Could not reach the deployment server /whoami endpoint. Server may need updating.',
            'raw'     => $result,
        ));
        return;
    }

    $home   = home_url();
    $caller = isset($result['caller_site_url']) ? (string) $result['caller_site_url'] : '';
    $matches = $caller !== '' && (auctionforge_normalize_host($home) === auctionforge_normalize_host($caller));

    wp_send_json_success(array(
        'matches'                       => $matches,
        'home_url'                      => $home,
        'server_says_caller_url'        => $caller,
        'server_says_caller_id'         => $result['caller_site_id'] ?? null,
        'server_says_caller_name'       => $result['caller_site_name'] ?? null,
        'auth_type'                     => $result['authenticated_as_type'] ?? null,
        'via_master'                    => !empty($result['via_master']),
        'last_check'                    => $result['caller_last_check'] ?? null,
        'plugin_version_seen_by_server' => $result['caller_plugin_version'] ?? null,
        'remote_ip_seen'                => $result['your_remote_ip'] ?? null,
    ));
}

/**
 * Heal an identity mismatch: re-register, wipe Kuma state, refresh.
 * See AuctionForge_Auto_Updater::reset_connection() for the contract.
 */
function auctionforge_ajax_fix_connection() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    $updater = new AuctionForge_Auto_Updater(AUCTIONFORGE_PLUGIN_BASENAME, AUCTIONFORGE_VERSION);
    $result  = $updater->reset_connection();
    if (empty($result['success'])) {
        wp_send_json_error($result);
        return;
    }
    wp_send_json_success($result);
}

/**
 * Lowercase, scheme-stripped, www-stripped host for cross-comparing
 * site_url-style strings. Used by Connection Diagnostics to compare
 * home_url() against the URL wpdeploy reports for the auth'd site.
 */
function auctionforge_normalize_host($url) {
    $url = strtolower((string) $url);
    $url = preg_replace('#^https?://#', '', $url);
    $url = preg_replace('#^www\.#', '', $url);
    return rtrim($url, '/');
}

function auctionforge_ajax_check_fraud() {
    check_ajax_referer('auctionforge_fraud_check', 'nonce');
    
    $email = sanitize_email(isset($_POST['email']) ? $_POST['email'] : '');
    if (empty($email)) {
        wp_send_json_error('No email provided');
        return;
    }
    
    $deviceId = sanitize_text_field(isset($_POST['device_id']) ? $_POST['device_id'] : '');
    $fingerprint = sanitize_text_field(isset($_POST['device_fingerprint']) ? $_POST['device_fingerprint'] : '');
    $ipAddress = auctionforge_get_client_ip();
    
    $enforcement = get_option('auctionforge_fraud_enforcement', false);
    
    // Cache key includes device info so different devices get separate checks
    $cacheKey = 'af_fraud_' . md5($email . $deviceId . $fingerprint . $ipAddress);
    $cached = get_transient($cacheKey);
    
    if ($cached !== false) {
        $cached['enforcement'] = (bool) $enforcement;
        wp_send_json_success($cached);
        return;
    }
    
    // Query central server
    $serverUrl = get_option('auctionforge_update_server_url', '');
    $apiKey = get_option('auctionforge_update_api_key', '');
    
    if (empty($serverUrl) || empty($apiKey)) {
        wp_send_json_success(array(
            'is_flagged' => false,
            'risk_level' => 'none',
            'enforcement' => (bool) $enforcement,
            'source' => 'not_configured',
        ));
        return;
    }
    
    $queryParams = array('email' => $email);
    if ($deviceId) $queryParams['device_id'] = $deviceId;
    if ($fingerprint) $queryParams['device_fingerprint'] = $fingerprint;
    if ($ipAddress) $queryParams['ip_address'] = $ipAddress;
    
    $url = rtrim($serverUrl, '/') . '/api.php/check-fraud?' . http_build_query($queryParams);
    $response = wp_remote_get($url, array(
        'timeout' => 10,
        'headers' => array('X-API-Key' => $apiKey),
    ));
    
    if (is_wp_error($response)) {
        wp_send_json_success(array(
            'is_flagged' => false,
            'risk_level' => 'none',
            'enforcement' => (bool) $enforcement,
            'source' => 'api_error',
        ));
        return;
    }
    
    $body = json_decode(wp_remote_retrieve_body($response), true);
    
    $result = array(
        'is_flagged' => !empty($body['is_flagged']),
        'risk_level' => $body['risk_level'] ?? 'none',
        'reason' => isset($body['fraud_record']['reason']) ? $body['fraud_record']['reason'] : '',
        'match_type' => $body['match_type'] ?? 'email',
        'linked_to' => $body['linked_to'] ?? null,
        'enforcement' => (bool) $enforcement,
        'source' => 'central_server',
    );
    
    // Cache for 10 minutes
    set_transient($cacheKey, $result, 600);
    
    wp_send_json_success($result);
}

/**
 * AJAX: Save SMS notification preferences for a user.
 */
function auctionforge_ajax_sms_save() {
    check_ajax_referer('bp-ajax-string', 'security');

    $email       = sanitize_email(isset($_POST['email']) ? $_POST['email'] : '');
    $phone       = sanitize_text_field(isset($_POST['phone']) ? $_POST['phone'] : '');
    $countryCode = sanitize_text_field(isset($_POST['country_code']) ? $_POST['country_code'] : '1');

    if (empty($email)) {
        wp_send_json_error('Email is required');
        return;
    }

    // If phone is empty and opt-in is off, delete the subscriber record
    $smsOptIn = isset($_POST['sms_opt_in']) ? intval($_POST['sms_opt_in']) : 0;
    if (empty($phone) && !$smsOptIn) {
        \auctionforge\inc\model\SMSSubscriber::deleteByEmail($email);
        wp_send_json_success(array('message' => 'SMS notifications disabled'));
        return;
    }

    if (empty($phone)) {
        wp_send_json_error('Phone number is required when SMS is enabled');
        return;
    }

    $optIns = array(
        'sms_opt_in'           => $smsOptIn,
        'auction_published'    => isset($_POST['opt_auction_published']) ? intval($_POST['opt_auction_published']) : 1,
        'auction_starts_soon'  => isset($_POST['opt_auction_starts_soon']) ? intval($_POST['opt_auction_starts_soon']) : 1,
        'item_live_soon'       => isset($_POST['opt_item_live_soon']) ? intval($_POST['opt_item_live_soon']) : 1,
        'outbid'               => isset($_POST['opt_outbid']) ? intval($_POST['opt_outbid']) : 1,
    );

    $result = \auctionforge\inc\model\SMSSubscriber::upsert($email, $phone, $countryCode, $optIns);

    if ($result) {
        wp_send_json_success(array('message' => 'SMS preferences saved'));
    } else {
        wp_send_json_error('Failed to save SMS preferences');
    }
}

/**
 * AJAX: Load SMS notification preferences for a user.
 */
function auctionforge_ajax_sms_load() {
    check_ajax_referer('bp-ajax-string', 'security');

    $email = sanitize_email(isset($_POST['email']) ? $_POST['email'] : '');
    if (empty($email)) {
        wp_send_json_error('Email is required');
        return;
    }

    $subscriber = \auctionforge\inc\model\SMSSubscriber::getByEmail($email);
    $smsEnabled = \auctionforge\inc\services\BrevoSMS::isEnabled();

    if ($subscriber) {
        wp_send_json_success(array(
            'sms_available'          => $smsEnabled,
            'has_subscription'       => true,
            'phone_number'           => $subscriber->phone_number,
            'country_code'           => $subscriber->country_code,
            'sms_opt_in'             => intval($subscriber->sms_opt_in),
            'opt_auction_published'  => intval($subscriber->opt_in_auction_published),
            'opt_auction_starts_soon'=> intval($subscriber->opt_in_auction_starts_soon),
            'opt_item_live_soon'     => intval($subscriber->opt_in_item_live_soon),
            'opt_outbid'             => intval($subscriber->opt_in_outbid),
        ));
    } else {
        wp_send_json_success(array(
            'sms_available'    => $smsEnabled,
            'has_subscription' => false,
        ));
    }
}

/**
 * AJAX: Send a test SMS (admin only).
 */
function auctionforge_ajax_sms_test() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) {
        wp_send_json_error('Unauthorized');
        return;
    }

    $phone       = sanitize_text_field(isset($_POST['phone']) ? $_POST['phone'] : '');
    $countryCode = sanitize_text_field(isset($_POST['country_code']) ? $_POST['country_code'] : '1');

    if (empty($phone)) {
        wp_send_json_error('Phone number is required');
        return;
    }

    $recipient = preg_replace('/[^0-9]/', '', $countryCode . $phone);
    $blogName  = html_entity_decode(get_option('blogname', 'AuctionForge'), ENT_QUOTES, 'UTF-8');
    $content   = sprintf('Test from %s: SMS notifications are working! This is a test message from your auction platform.', $blogName);

    $result = \auctionforge\inc\services\BrevoSMS::sendSMS($recipient, $content, 'test');

    if ($result['success']) {
        wp_send_json_success(array(
            'message'   => 'Test SMS sent successfully!',
            'messageId' => $result['messageId'],
        ));
    } else {
        wp_send_json_error('SMS failed: ' . $result['error']);
    }
}

function auctionforge_ajax_sync_tracking_now() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }
    
    $updater = new AuctionForge_Auto_Updater(AUCTIONFORGE_PLUGIN_BASENAME, AUCTIONFORGE_VERSION);
    $result = $updater->sync_user_tracking();
    
    if ($result) {
        $count = get_option('auctionforge_last_tracking_sync_count', 0);
        $alerts = get_option('auctionforge_last_tracking_sync_alerts', 0);
        wp_send_json_success(array(
            'message' => "Tracking sync completed. {$count} records synced, {$alerts} alerts generated.",
            'synced' => $count,
            'alerts' => $alerts,
        ));
    } else {
        $status = get_option('auctionforge_last_tracking_sync_status', 'unknown');
        wp_send_json_error("Sync failed: {$status}");
    }
}

function auctionforge_db_update() {
    add_option(AUCTIONFORGE_DB_OPTION_NAME, 1);
    $dbVersion = get_option(AUCTIONFORGE_DB_OPTION_NAME);

    if ((int)$dbVersion < AUCTIONFORGE_DB_VERSION && dropImportHistoryTable()) {
        createTableImportHistoryTable();
        createBpFirebaseTables();
        createBpLotDataTable();
        createBpSubmissonDataTable();
        createBpMetaTableIndexes();
        createBpViewHistoryTable();
        createLotAnalyticsTables();
        AF_Stats_Init::activate();
        auctionforge_create_ip_log_table();
        AF_Legal_Agreements::create_tables();
        AF_Legal_Agreements::seed_defaults();
        createSyncQueueTable();
        createSyncLogTable();

        update_option(AUCTIONFORGE_DB_OPTION_NAME, AUCTIONFORGE_DB_VERSION, 'no');

        // Flush rewrite rules after DB update to prevent 404s
        add_action('init', function() {
            flush_rewrite_rules(false);
        }, 99);
    }
}
add_action('plugins_loaded', 'auctionforge_db_update');

function auctionforge_db_fixes_update() {
    $dbVersion = get_option(AUCTIONFORGE_DB_UPDATE_OPTION_NAME, 0);

    if ((int)$dbVersion < AUCTIONFORGE_DB_UPDATE_VERSION) {
        updateImagesListStrField();
        addCriminalContentFieldInMeta();
        addCriminalContentFieldInHistory();
        addImportHistoryIndexes();

        update_option(AUCTIONFORGE_DB_UPDATE_OPTION_NAME, AUCTIONFORGE_DB_UPDATE_VERSION, 'no');
    }
}
add_action('init', 'auctionforge_db_fixes_update', 20);

function updateImagesListStrField() {
    $wpdb = InstanceDB::getInstanceDB();
    $table_name = LotMetaData::getTableName();
    $column_name = 'imagesListStr';

    $column_info = $wpdb->get_row(
        $wpdb->prepare(
            "SHOW COLUMNS FROM $table_name LIKE %s",
            $column_name
        )
    );

    if ($column_info && $column_info->Type !== 'varchar(2048)') {
        $result = $wpdb->query(
            "ALTER TABLE $table_name MODIFY $column_name VARCHAR(2048) NOT NULL"
        );

        if ($result === false) {
            Debug::errorLog("An error of adding a field $column_name: " . $wpdb->last_error);
        } else {
            Debug::errorLog("Field $column_name successfully added");
        }
    }
}

function addCriminalContentFieldInMeta() {
    global $wpdb;
    $table_name = LotMetaData::getTableName();
    $column_name = 'criminalContent';

    $column_exists = $wpdb->get_row(
        $wpdb->prepare(
            "SHOW COLUMNS FROM $table_name LIKE %s",
            $column_name
        )
    );

    if (!$column_exists) {
        $sql = "ALTER TABLE $table_name 
                ADD COLUMN $column_name TINYINT(1) NOT NULL DEFAULT 0 
                AFTER eroticContent";

        $result = $wpdb->query($sql);

        if ($result === false) {
            Debug::errorLog("An error of adding a field $column_name: " . $wpdb->last_error);
        } else {
            Debug::errorLog("Field $column_name successfully added");
        }
    }
}

function addCriminalContentFieldInHistory() {
    global $wpdb;

    $db = InstanceDB::getInstanceDB();
    $table_name = $db->prefix . Importer::TABLE_NAME;
    $column_name = 'criminalContent';

    $column_exists = $wpdb->get_row(
        $wpdb->prepare(
            "SHOW COLUMNS FROM $table_name LIKE %s",
            $column_name
        )
    );

    if (!$column_exists) {
        $sql = "ALTER TABLE $table_name 
                ADD COLUMN $column_name TINYINT(1) NOT NULL DEFAULT 0 
                AFTER eroticContent";

        $result = $wpdb->query($sql);

        if ($result === false) {
            Debug::errorLog("An error of adding a field $column_name: " . $wpdb->last_error);
        } else {
            Debug::errorLog("Field $column_name successfully added");
        }
    }
}

function addImportHistoryIndexes() {
    $db = InstanceDB::getInstanceDB();
    $table_name = $db->prefix . Importer::TABLE_NAME;

    $lotSyncIndex = $db->get_var(
        $db->prepare("SHOW INDEX FROM {$table_name} WHERE Key_name = %s", 'idx_lot_sync')
    );
    if (!$lotSyncIndex) {
        $db->query("ALTER TABLE {$table_name} ADD INDEX idx_lot_sync (lotId, syncFinished)");
    }

    $syncStatusIndex = $db->get_var(
        $db->prepare("SHOW INDEX FROM {$table_name} WHERE Key_name = %s", 'idx_sync_status')
    );
    if (!$syncStatusIndex) {
        $db->query("ALTER TABLE {$table_name} ADD INDEX idx_sync_status (syncFinished, createDatetime)");
    }

    $primaryKey = $db->get_var("SHOW KEYS FROM {$table_name} WHERE Key_name = 'PRIMARY'");
    if (!$primaryKey) {
        $db->query("ALTER TABLE {$table_name} ADD PRIMARY KEY (id)");
    }

    $uniqueIdIndex = $db->get_var(
        $db->prepare("SHOW INDEX FROM {$table_name} WHERE Key_name = %s", 'id')
    );
    if ($uniqueIdIndex) {
        $db->query("ALTER TABLE {$table_name} DROP INDEX id");
    }
}

function auctionforge_config_update() {
    add_option(AUCTIONFORGE_CONFIG_OPTION_NAME, 1);
    $configVersion = get_option(AUCTIONFORGE_CONFIG_OPTION_NAME);

    if (intval($configVersion) < AUCTIONFORGE_CONFIG_VERSION) {
        flush_rewrite_rules();
        update_option(AUCTIONFORGE_CONFIG_OPTION_NAME, AUCTIONFORGE_CONFIG_VERSION, 'no');
    }
}
add_action('plugins_loaded', 'auctionforge_config_update');

function deactivatePlugin() {
    dropImportHistoryTable();
    flush_rewrite_rules();
}

function dropImportHistoryTable() {
    delete_option(AUCTIONFORGE_DB_OPTION_NAME);
    $db = InstanceDB::getInstanceDB();
    $sql = 'DROP TABLE IF EXISTS `' . $db->prefix . Importer::TABLE_NAME . "`;";
    $db->query($sql);

    return $db->query($sql);
}

function dropBidspiritTables() {
    $db = InstanceDB::getInstanceDB();
    $sql = 'DROP TABLE IF EXISTS `' . $db->prefix . FirebaseUser::TABLE_APP_USERS . "`;";
    $db->query($sql);
    $sql = 'DROP TABLE IF EXISTS `' . $db->prefix . FirebaseLog::TABLE_APP_LOGS . "`;";
    $db->query($sql);
    $sql = 'DROP TABLE IF EXISTS `' . $db->prefix . LotMetaData::TABLE_LOT_META . "`;";
    return $db->query($sql);
}

function createTableImportHistoryTable() {
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

    $db = InstanceDB::getInstanceDB();
    $table_name = $db->prefix . Importer::TABLE_NAME;
    if ($db->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
        $sql = "CREATE TABLE `".$table_name."` (
        id int(11) unsigned NOT NULL AUTO_INCREMENT,
        lotId int(11) unsigned NOT NULL,
        postId int(11) unsigned NULL,
        paddedIndex varchar(15) NULL,
        auctionSlug varchar(55) NOT NULL,
        lang varchar(55) NOT NULL,
        itemIndex varchar(55) NOT NULL,
        name varchar(255),
        nameL2 varchar(255),
        description text,
        descriptionL2 text,
        author varchar(255),
        authorL2 varchar(255),
        mlFields text,
        details text,
        startPrice integer(11),
        leadingBid integer(11),
        soldBid integer(11),
        auctionId integer(11),
        imagesListStr varchar(2048),
        categoryId integer(11),
        estimatedPrice varchar(55),
        tags varchar(255),
        videoUrl varchar(255),
        extraInfo text,
        params text,
        postSaleDisabled tinyint(1),
        imagesBase varchar(255),
        imagesCloned tinyint(1),
        buyoutPrice integer(11),
        lastUpdate bigint(15),
        fullPriceVat tinyint(1),
        largeDisplay tinyint(1),
        viewOnly tinyint(1),
        hidden tinyint(1),
        hiddenInPortal tinyint(1),
        removedFromAuction tinyint(1),
        eroticContent tinyint(1),
        criminalContent tinyint(1),
        isGem tinyint(1),
        priority integer(1),
        createDatetime varchar(55),
        syncStart tinyint(1) default 0,
        syncFinished tinyint(1) default 0,
        syncDatetime varchar(55),
        syncErrors text,
        syncTimes integer(1),";

        $sql = apply_filters('auctionforge_query_history_table', $sql);

        $sql .= "PRIMARY KEY (id),
        KEY idx_lot_sync (lotId, syncFinished),
        KEY idx_sync_status (syncFinished, createDatetime)\r\n)
        CHARACTER SET utf8mb4
        COLLATE utf8mb4_unicode_ci;\r\n";

        dbDelta($sql);
    }

    addImportHistoryIndexes();
}

function createBpFirebaseTables() {
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

    $db = InstanceDB::getInstanceDB();
    $table_name = $db->prefix . FirebaseUser::TABLE_APP_USERS;
    $table_name_logs = $db->prefix . FirebaseLog::TABLE_APP_LOGS;

    if ($db->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
        $sql = "CREATE TABLE IF NOT EXISTS `".$table_name."` (
        id int(11) unsigned NOT NULL AUTO_INCREMENT,
        firebase_id varchar(255) NOT NULL,
        created DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated DATETIME ON UPDATE CURRENT_TIMESTAMP,
        bidspirit_user_email varchar(55) UNIQUE,
        UNIQUE KEY (id)\r\n)
        CHARACTER SET utf8mb4
        COLLATE utf8mb4_unicode_ci;\r\n";
        dbDelta($sql);

        $sql = "CREATE UNIQUE INDEX bp_firebase_users_bidspirit_user_email_uindex  ON `" . $db->prefix . $table_name . "` (`bidspirit_user_email`);";
        dbDelta($sql);
    }

    if ($db->get_var("SHOW TABLES LIKE '$table_name_logs'") != $table_name_logs) {
        $sql = "CREATE TABLE IF NOT EXISTS `".$table_name_logs."` (
        id int(11) unsigned NOT NULL AUTO_INCREMENT,
        firebase_id varchar(255) NOT NULL,
        created DATETIME DEFAULT CURRENT_TIMESTAMP,
        bidspirit_user_email varchar(55),
        bidspirit_lot_id varchar(55),
        request_from_bidspirit text,
        request_to_firebase text,
        response_from_firebase text,
        response_to_bidspirit text,
        status enum('none', 'sent', 'error') default 'none',
        UNIQUE KEY (id)\r\n)
        CHARACTER SET utf8mb4
        COLLATE utf8mb4_unicode_ci;\r\n";
        dbDelta($sql);
    }
}

function createBpLotDataTable() {
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

    $db = InstanceDB::getInstanceDB();
    $table_name = LotMetaData::getTableName();

    if ($db->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
        $sql = "CREATE TABLE IF NOT EXISTS `".$table_name."` (
        postId bigint(20) unsigned NOT NULL,
        termId bigint(20) unsigned NOT NULL,
        id int(11) unsigned NOT NULL,
        featured tinyint(1) DEFAULT 0,
        lang varchar(2) DEFAULT 'en',
        paddedIndex varchar(15) NULL,
        itemIndex varchar(55) NOT NULL,
        name varchar(255),        
        author varchar(255),
        details text,
        startPrice integer(11),
        leadingBid integer(11),
        soldBid integer(11),
        auctionId integer(11),
        imagesListStr varchar(2048),
        categoryId integer(11),
        estimatedPrice varchar(55),
        estimatedPriceInt varchar(55),
        tags varchar(255),
        videoUrl varchar(255),
        extraInfo text,
        params text,
        postSaleDisabled tinyint(1),
        imagesBase varchar(255),
        imagesCloned tinyint(1),
        buyoutPrice integer(11),
        lastUpdate bigint(15),
        fullPriceVat tinyint(1),
        largeDisplay tinyint(1),
        viewOnly tinyint(1),
        hidden tinyint(1),
        hiddenInPortal tinyint(1),
        removedFromAuction tinyint(1),
        eroticContent tinyint(1),
        isGem tinyint(1),
        createDatetime varchar(55),
        updateDatetime TIMESTAMP DEFAULT now() ON UPDATE now(),";

        $sql = apply_filters('auctionforge_query_meta_table', $sql);

        $sql .= "PRIMARY KEY postId (postId)\r\n)
        CHARACTER SET utf8mb4
        COLLATE utf8mb4_unicode_ci;\r\n";
        dbDelta($sql);
    }
}

function createBpSubmissonDataTable() {
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

    $db = InstanceDB::getInstanceDB();
    $table_name = SubmissionFormData::getTableName();

    if ($db->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
        $sql = "CREATE TABLE `$table_name`
                (
                    `id`          bigint(20) unsigned NOT NULL AUTO_INCREMENT,
                    `email`       varchar(125),
                    `data`        text               null,
                    `is_exported` tinyint  default 0 not null,
                    `ip`          varchar(55),
                    `created`     DATETIME DEFAULT now() ON UPDATE now(),
                ";
        $sql = apply_filters('auctionforge_query_bp_submission_data', $sql);

        $sql .= "\r\nPRIMARY KEY `id` (id)
                ) CHARACTER SET utf8mb4
                  COLLATE utf8mb4_unicode_ci COMMENT 'SubmissionFormData';\r\n";

        dbDelta($sql);
    }
}

function createBpMetaTableIndexes() {
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

    $table_name = LotMetaData::getTableName();
    $sql = "CREATE INDEX bp_meta_data_indexes ON " . $table_name . " (id, termId, paddedIndex, itemIndex, startPrice, leadingBid, soldBid, categoryId, estimatedPriceInt, buyoutPrice);";
    dbDelta($sql);
}

function createBpViewHistoryTable() {
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

    $db = InstanceDB::getInstanceDB();
    $table_name = \auctionforge\inc\model\ViewHistory::getTableName();

    if ($db->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
        $sql = "CREATE TABLE IF NOT EXISTS `" . $table_name . "` (
        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        user_email varchar(125) NOT NULL,
        post_id bigint(20) unsigned NOT NULL,
        term_id bigint(20) unsigned NOT NULL DEFAULT 0,
        viewed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY user_lot (user_email, post_id),
        KEY user_viewed (user_email, viewed_at)
        ) CHARACTER SET utf8mb4
        COLLATE utf8mb4_unicode_ci;\r\n";
        dbDelta($sql);
    }
}

function createLotAnalyticsTables() {
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

    $db = InstanceDB::getInstanceDB();
    $analyticsTable = \auctionforge\inc\model\LotAnalytics::getAnalyticsTableName();
    $favoritesTable = \auctionforge\inc\model\LotAnalytics::getFavoritesTableName();

    if ($db->get_var("SHOW TABLES LIKE '$analyticsTable'") != $analyticsTable) {
        $sql = "CREATE TABLE IF NOT EXISTS `" . $analyticsTable . "` (
        post_id bigint(20) unsigned NOT NULL,
        term_id bigint(20) unsigned NOT NULL DEFAULT 0,
        view_count int unsigned NOT NULL DEFAULT 0,
        unique_visitors int unsigned NOT NULL DEFAULT 0,
        catalogue_clicks int unsigned NOT NULL DEFAULT 0,
        external_clicks int unsigned NOT NULL DEFAULT 0,
        favorite_count int unsigned NOT NULL DEFAULT 0,
        share_count int unsigned NOT NULL DEFAULT 0,
        avg_time_on_page int unsigned NOT NULL DEFAULT 0,
        first_viewed_at datetime DEFAULT CURRENT_TIMESTAMP,
        last_viewed_at datetime DEFAULT CURRENT_TIMESTAMP,
        unique_hashes mediumtext,
        PRIMARY KEY (post_id),
        KEY idx_term_id (term_id),
        KEY idx_view_count (view_count),
        KEY idx_favorite_count (favorite_count),
        KEY idx_last_viewed (last_viewed_at)
        ) CHARACTER SET utf8mb4
        COLLATE utf8mb4_unicode_ci;\r\n";
        dbDelta($sql);
    }

    if ($db->get_var("SHOW TABLES LIKE '$favoritesTable'") != $favoritesTable) {
        $sql = "CREATE TABLE IF NOT EXISTS `" . $favoritesTable . "` (
        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        user_email varchar(125) NOT NULL,
        post_id bigint(20) unsigned NOT NULL,
        term_id bigint(20) unsigned NOT NULL DEFAULT 0,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY user_lot (user_email, post_id),
        KEY idx_post_id (post_id),
        KEY idx_user_email (user_email)
        ) CHARACTER SET utf8mb4
        COLLATE utf8mb4_unicode_ci;\r\n";
        dbDelta($sql);
    }

    // Create shares table
    $sharesTable = \auctionforge\inc\model\LotAnalytics::getSharesTableName();
    if ($db->get_var("SHOW TABLES LIKE '$sharesTable'") != $sharesTable) {
        $sql = "CREATE TABLE IF NOT EXISTS `" . $sharesTable . "` (
        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        post_id bigint(20) unsigned NOT NULL,
        term_id bigint(20) unsigned NOT NULL DEFAULT 0,
        channel varchar(30) NOT NULL DEFAULT 'unknown',
        visitor_hash varchar(64) NOT NULL DEFAULT '',
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY idx_post_id (post_id),
        KEY idx_channel (channel),
        KEY idx_created (created_at)
        ) CHARACTER SET utf8mb4
        COLLATE utf8mb4_unicode_ci;\r\n";
        dbDelta($sql);
    }

    // Add new columns to existing analytics table if missing
    $cols = $db->get_col("SHOW COLUMNS FROM `$analyticsTable`");
    if (!in_array('catalogue_clicks', $cols)) {
        $db->query("ALTER TABLE `$analyticsTable` ADD COLUMN catalogue_clicks int unsigned NOT NULL DEFAULT 0 AFTER unique_visitors");
    }
    if (!in_array('external_clicks', $cols)) {
        $db->query("ALTER TABLE `$analyticsTable` ADD COLUMN external_clicks int unsigned NOT NULL DEFAULT 0 AFTER catalogue_clicks");
    }
    if (!in_array('share_count', $cols)) {
        $db->query("ALTER TABLE `$analyticsTable` ADD COLUMN share_count int unsigned NOT NULL DEFAULT 0 AFTER favorite_count");
    }
    if (!in_array('bid_count', $cols)) {
        $db->query("ALTER TABLE `$analyticsTable` ADD COLUMN bid_count int unsigned NOT NULL DEFAULT 0 AFTER share_count");
    }
}


function createSyncQueueTable() {
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    $db = \auctionforge\inc\InstanceDB::getInstanceDB();
    $table = $db->prefix . \auctionforge\inc\sync\SyncQueue::TABLE_NAME;
    $charset = $db->get_charset_collate();

    if ($db->get_var("SHOW TABLES LIKE '{$table}'") != $table) {
        $sql = "CREATE TABLE `{$table}` (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            job_type VARCHAR(50) NOT NULL,
            auction_id VARCHAR(50) NOT NULL,
            payload LONGTEXT,
            status VARCHAR(20) NOT NULL DEFAULT 'pending',
            priority TINYINT UNSIGNED NOT NULL DEFAULT 3,
            source VARCHAR(20) NOT NULL DEFAULT 'webhook',
            attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
            worker_pid VARCHAR(20) DEFAULT NULL,
            result_summary TEXT,
            error_message TEXT,
            created_at DATETIME NOT NULL,
            updated_at DATETIME NOT NULL,
            started_at DATETIME DEFAULT NULL,
            completed_at DATETIME DEFAULT NULL,
            PRIMARY KEY (id),
            KEY idx_status_priority (status, priority, created_at),
            KEY idx_auction_type (auction_id, job_type),
            KEY idx_created (created_at)
        ) {$charset};";
        dbDelta($sql);
    }
}

function createSyncLogTable() {
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    $db = \auctionforge\inc\InstanceDB::getInstanceDB();
    $table = $db->prefix . \auctionforge\inc\sync\SyncLogger::TABLE_NAME;
    $charset = $db->get_charset_collate();

    if ($db->get_var("SHOW TABLES LIKE '{$table}'") != $table) {
        $sql = "CREATE TABLE `{$table}` (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            context VARCHAR(30) NOT NULL,
            message VARCHAR(1000) NOT NULL,
            level VARCHAR(10) NOT NULL DEFAULT 'info',
            auction_id VARCHAR(50) DEFAULT NULL,
            job_id BIGINT UNSIGNED DEFAULT NULL,
            created_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            KEY idx_job (job_id),
            KEY idx_created_level (created_at, level),
            KEY idx_auction (auction_id)
        ) {$charset};";
        dbDelta($sql);
    }
}

function auctionforge_ajax_sync_log_poll() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    $since_id = isset($_POST['since_id']) ? intval($_POST['since_id']) : 0;
    $limit = isset($_POST['limit']) ? min(intval($_POST['limit']), 100) : 50;

    $entries = \auctionforge\inc\sync\SyncLogger::getRecent($since_id, $limit);
    $latest_id = !empty($entries) ? end($entries)->id : $since_id;

    wp_send_json_success([
        'entries' => $entries,
        'latest_id' => (int) $latest_id,
        'count' => count($entries),
    ]);
}

function auctionforge_ajax_sync_queue_status() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    $counts = \auctionforge\inc\sync\SyncQueue::getStatusCounts();
    $recent = \auctionforge\inc\sync\SyncQueue::getRecentJobs(15);
    $has_worker = \auctionforge\inc\sync\SyncQueue::hasActiveWorker();

    wp_send_json_success([
        'counts' => $counts,
        'jobs' => $recent,
        'has_active_worker' => $has_worker,
    ]);
}


function auctionforge_register_sync_worker_endpoint() {
    register_rest_route('auctionforge/v1', '/sync-worker', array(
        'methods'  => 'POST',
        'callback' => 'auctionforge_rest_sync_worker',
        'permission_callback' => '__return_true',
    ));
}

function auctionforge_rest_sync_worker(\WP_REST_Request $request) {
    // Verify worker key header
    $key = $request->get_header('X-AF-Worker-Key');
    $expected = \auctionforge\inc\sync\SyncQueue::getWorkerKey();

    if (empty($key) || $key !== $expected) {
        return new \WP_REST_Response(['error' => 'Unauthorized'], 403);
    }

    // Increase limits for long-running sync
    @set_time_limit(300);
    @ini_set('memory_limit', '512M');

    // Tell WordPress and plugins this is a bulk import — suppresses pingbacks,
    // SEO sitemap pings, Smush image processing, SmartCrawl updates, etc.
    if (!defined('WP_IMPORTING')) {
        define('WP_IMPORTING', true);
    }

    // Route: process-staging runs InsertLotsBS in a fresh request (blocking, no fastcgi detach)
    $action = $request->get_header('X-AF-Action');
    if ($action === 'process-staging') {
        return auctionforge_rest_process_staging();
    }

    // Close the HTTP connection immediately so the caller doesn't wait.
    // The worker continues processing in the background.
    if (function_exists('fastcgi_finish_request')) {
        // Send response headers + body, then detach
        status_header(200);
        header('Content-Type: application/json');
        echo json_encode(['status' => 'started']);
        fastcgi_finish_request();
    }

    // Now run the worker inline — this continues after the HTTP response is sent
    $timeout = defined('AUCTIONFORGE_SYNC_WORKER_TIMEOUT') ? AUCTIONFORGE_SYNC_WORKER_TIMEOUT : 240;
    $startTime = time();
    $pid = getmypid();
    $jobsProcessed = 0;
    $jobsFailed = 0;

    $initialCounts = \auctionforge\inc\sync\SyncQueue::getStatusCounts();
    $pendingCount = $initialCounts['pending'];
    \auctionforge\inc\sync\SyncLogger::log('system', "═══ Worker started (PID {$pid}, HTTP loopback) ═══", 'info');
    \auctionforge\inc\sync\SyncLogger::log('system', "  Queue: {$pendingCount} pending, timeout: {$timeout}s", 'info');

    $jobNumber = 0;
    while ((time() - $startTime) < $timeout) {
        $job = \auctionforge\inc\sync\SyncQueue::claimNext($pid);

        if (!$job) {
            \auctionforge\inc\sync\SyncLogger::log('system', "Queue empty — all jobs processed", 'info');
            break;
        }

        $jobNumber++;
        $jobStart = microtime(true);

        \auctionforge\inc\sync\SyncLogger::log('system', "━━━ Job {$jobNumber}/{$pendingCount}: #{$job->id} {$job->job_type} [auction {$job->auction_id}] (source: {$job->source}) ━━━", 'info', $job->auction_id, $job->id);

        try {
            $result = [];

            switch ($job->job_type) {
                case \auctionforge\inc\sync\SyncQueue::TYPE_CATALOG_SYNC:
                case \auctionforge\inc\sync\SyncQueue::TYPE_FULL_SYNC:
                    $result = \auctionforge\inc\sync\CatalogSyncWorker::process($job);
                    break;
                case \auctionforge\inc\sync\SyncQueue::TYPE_ITEM_UPDATE:
                    $result = \auctionforge\inc\sync\ItemUpdateWorker::process($job);
                    break;
                case \auctionforge\inc\sync\SyncQueue::TYPE_DAY_ENDED:
                    $result = \auctionforge\inc\sync\CatalogSyncWorker::processDayEnded($job);
                    break;
                case \auctionforge\inc\sync\SyncQueue::TYPE_ARCHIVE_SYNC:
                    $result = \auctionforge\inc\sync\CatalogSyncWorker::process($job);
                    break;
                case \auctionforge\inc\sync\SyncQueue::TYPE_FULL_SYNC_UPCOMING:
                case \auctionforge\inc\sync\SyncQueue::TYPE_FULL_SYNC_PAST:
                    $result = \auctionforge\inc\sync\BulkSyncWorker::process($job);
                    break;
                default:
                    \auctionforge\inc\sync\SyncLogger::log('worker', "Unknown job type: {$job->job_type}", 'warning', $job->auction_id, $job->id);
                    \auctionforge\inc\sync\SyncQueue::fail($job->id, "Unknown job type: {$job->job_type}");
                    $jobsFailed++;
                    continue 2;
            }

            $duration = round(microtime(true) - $jobStart, 2);

            if (!empty($result['errors'])) {
                $errorMsg = implode('; ', array_slice($result['errors'], 0, 3));
                if (!empty($result['lots_processed']) || !empty($result['updated'])) {
                    \auctionforge\inc\sync\SyncQueue::complete($job->id, "Partial: {$errorMsg} ({$duration}s)");
                    \auctionforge\inc\sync\SyncLogger::log('system', "Job #{$job->id} completed with warnings ({$duration}s)", 'warning', $job->auction_id, $job->id);
                    $jobsProcessed++;
                } else {
                    \auctionforge\inc\sync\SyncQueue::fail($job->id, $errorMsg);
                    \auctionforge\inc\sync\SyncLogger::log('system', "Job #{$job->id} failed ({$duration}s): {$errorMsg}", 'error', $job->auction_id, $job->id);
                    $jobsFailed++;
                }
            } else {
                $summary = '';
                if (isset($result['lots_processed'])) {
                    $summary = "{$result['lots_processed']} lots ({$duration}s)";
                } elseif (isset($result['updated'])) {
                    $summary = "Item updated ({$duration}s)";
                } else {
                    $summary = "OK ({$duration}s)";
                }
                \auctionforge\inc\sync\SyncQueue::complete($job->id, $summary);
                \auctionforge\inc\sync\SyncLogger::log('system', "Job #{$job->id} completed: {$summary}", 'success', $job->auction_id, $job->id);
                $jobsProcessed++;
            }

            // Progress
            $elapsed = time() - $startTime;
            $remainingJobs = $pendingCount - $jobNumber;
            if ($remainingJobs > 0 && $jobNumber > 0) {
                $avgTime = $elapsed / $jobNumber;
                $eta = round($remainingJobs * $avgTime);
                \auctionforge\inc\sync\SyncLogger::log('system', "  Progress: {$jobNumber}/{$pendingCount} done, ~{$eta}s remaining", 'info');
            }

        } catch (\Exception $e) {
            \auctionforge\inc\sync\SyncQueue::fail($job->id, $e->getMessage());
            \auctionforge\inc\sync\SyncLogger::log('worker', "Unhandled exception: " . $e->getMessage(), 'error', $job->auction_id, $job->id);
            $jobsFailed++;
        } catch (\Throwable $t) {
            \auctionforge\inc\sync\SyncQueue::fail($job->id, $t->getMessage());
            \auctionforge\inc\sync\SyncLogger::log('worker', "Fatal error: " . $t->getMessage(), 'error', $job->auction_id, $job->id);
            $jobsFailed++;
        }

        // Time check
        $elapsed = time() - $startTime;
        $timeLeft = $timeout - $elapsed;
        if ($timeLeft < 30) {
            $remainingJobs = $pendingCount - $jobNumber;
            \auctionforge\inc\sync\SyncLogger::log('system', "⚠ Nearing timeout ({$timeLeft}s left) — stopping with {$remainingJobs} jobs remaining", 'warning');
            break;
        }
    }

    $totalDuration = time() - $startTime;
    \auctionforge\inc\sync\SyncLogger::log('system', "═══ Worker finished (PID {$pid}): {$jobsProcessed} completed, {$jobsFailed} failed, {$totalDuration}s total ═══", 'info');

    // Don't return WP_REST_Response — we already sent the response via fastcgi_finish_request
    die();
}

function auctionforge_rest_process_staging() {
    $batchSize = 50;
    $maxPasses = 100;
    $totalInserted = 0;
    $pass = 0;

    while ($pass < $maxPasses) {
        $pass++;
        $insertLots = new \auctionforge\inc\import\InsertLotsBS();
        $insertLots->init(['limit' => $batchSize]);
        $insertInfo = $insertLots->getLoggedInfo();

        // loggedInfo is flat: {"unupdated lots": N, "updated lots": M}
        $batchCount = isset($insertInfo['updated lots']) ? (int) $insertInfo['updated lots'] : 0;
        $totalInserted += $batchCount;

        if ($batchCount === 0) break;

        if ($pass % 5 === 0) {
            \auctionforge\inc\sync\SyncLogger::log('worker', "  [5/6] ... {$totalInserted} posts processed so far (pass {$pass})", 'info');
        }
    }

    return new \WP_REST_Response([
        'total_inserted' => $totalInserted,
        'passes' => $pass,
    ], 200);
}

function auctionforge_ajax_sync_cancel() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    $action = isset($_POST['cancel_action']) ? sanitize_text_field($_POST['cancel_action']) : '';

    switch ($action) {
        case 'cancel_job':
            $jobId = isset($_POST['job_id']) ? intval($_POST['job_id']) : 0;
            if ($jobId <= 0) { wp_send_json_error('Invalid job_id'); return; }
            $ok = \auctionforge\inc\sync\SyncQueue::cancelJob($jobId);
            if ($ok) {
                wp_send_json_success(['message' => "Job #{$jobId} cancelled"]);
            } else {
                wp_send_json_error("Job #{$jobId} cannot be cancelled (already completed/failed)");
            }
            break;

        case 'cancel_all':
            $result = \auctionforge\inc\sync\SyncQueue::cancelAll();
            wp_send_json_success([
                'message' => "Cancelled {$result['cancelled']} pending, killed {$result['killed']} processing",
                'cancelled' => $result['cancelled'],
                'killed' => $result['killed'],
            ]);
            break;

        default:
            wp_send_json_error('Unknown cancel_action');
    }
}

function auctionforge_ajax_clear_import_history() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    global $wpdb;
    $table = $wpdb->prefix . 'import_history';
    $count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
    $wpdb->query("TRUNCATE TABLE {$table}");

    wp_send_json_success([
        'message' => "Cleared {$count} rows from import history",
        'cleared' => $count,
    ]);
}

function auctionforge_ajax_sync_enqueue() {
    check_ajax_referer('auctionforge_admin', 'nonce');
    if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; }

    $action = isset($_POST['sync_action']) ? sanitize_text_field($_POST['sync_action']) : '';
    $auction_id = isset($_POST['auction_id']) ? sanitize_text_field($_POST['auction_id']) : '';

    switch ($action) {
        case 'full_sync':
            // Discover auctions from BidSpirit API first (creates taxonomy terms)
            $auctionList = new \auctionforge\inc\import\AuctionList();
            $auctionList->init();

            $auctions = \auctionforge\inc\model\Auction::getActiveAuction();
            $auctions = \auctionforge\inc\model\Auction::getTermFields($auctions, 'auction_id');
            $enqueued = 0;
            foreach ($auctions as $aid) {
                $jobId = \auctionforge\inc\sync\SyncQueue::enqueue(
                    \auctionforge\inc\sync\SyncQueue::TYPE_FULL_SYNC,
                    $aid, null, 3, 'admin'
                );
                if ($jobId) $enqueued++;
            }
            \auctionforge\inc\sync\SyncQueue::spawnWorker();
            wp_send_json_success(['message' => "Enqueued full sync for {$enqueued} auction(s)", 'enqueued' => $enqueued]);
            break;

        case 'sync_auction':
            if (empty($auction_id)) { wp_send_json_error('No auction_id'); return; }
            $jobId = \auctionforge\inc\sync\SyncQueue::enqueue(
                \auctionforge\inc\sync\SyncQueue::TYPE_CATALOG_SYNC,
                $auction_id, null, 1, 'admin'
            );
            if ($jobId) {
                \auctionforge\inc\sync\SyncQueue::spawnWorker();
                wp_send_json_success(['message' => "Enqueued catalog sync for auction {$auction_id}", 'job_id' => $jobId]);
            } else {
                wp_send_json_success(['message' => "Job already pending for auction {$auction_id}", 'deduplicated' => true]);
            }
            break;

        case 'archive_sync':
            $auctions = \auctionforge\inc\model\Auction::getArchiveAuction();
            $auctions = \auctionforge\inc\model\Auction::getTermFields($auctions, 'auction_id');
            $enqueued = 0;
            foreach ($auctions as $aid) {
                $jobId = \auctionforge\inc\sync\SyncQueue::enqueue(
                    \auctionforge\inc\sync\SyncQueue::TYPE_ARCHIVE_SYNC,
                    $aid, null, 5, 'admin'
                );
                if ($jobId) $enqueued++;
            }
            if ($enqueued > 0) {
                \auctionforge\inc\sync\SyncQueue::spawnWorker();
            }
            wp_send_json_success(['message' => "Enqueued archive sync for {$enqueued} past auction(s)", 'enqueued' => $enqueued]);
            break;

        case 'spawn_worker':
            \auctionforge\inc\sync\SyncQueue::spawnWorker();
            wp_send_json_success(['message' => 'Worker spawned']);
            break;

        case 'process_staging':
            // Convert lots already staged in import_history into WP posts.
            // Fired non-blocking so the request returns immediately; the
            // processor runs in its own request and streams progress to the
            // Live Console. The reported count is the backlog at kick-off.
            $backlog = \auctionforge\inc\sync\SyncQueue::stagingBacklogCount();
            if ($backlog === 0) {
                wp_send_json_success(['message' => 'Nothing to process — staging is already clear.', 'backlog' => 0]);
                break;
            }
            \auctionforge\inc\sync\SyncQueue::triggerStagingProcessor(false);
            wp_send_json_success([
                'message' => "Staging processor started for {$backlog} lot(s). Watch the Live Console below — posts are created in batches of 50.",
                'backlog' => $backlog,
            ]);
            break;

        default:
            wp_send_json_error('Unknown sync_action');
    }
}

function uninstallPlugin() {
    dropBidspiritTables();
    return true;
}

add_action('wp_enqueue_scripts', function () {
    global $post;
    $sourceVersion = AUCTIONFORGE_VERSION;

    $cssFile = ".css";
    $jsFile = ".js";
    if (true || WP_DEBUG) { // force cache bust for v2.7.3.0 fraud prevention
        $sourceVersion = time();
    }
    $appLibsJsFile = "libs.min.js";

    wp_enqueue_script('bitspirit_front_libs_app', plugin_dir_url(__FILE__).'assets/js/'.$appLibsJsFile, array('jquery'),
        $sourceVersion, true);

    // Enqueue AuctionForgeModal - custom modal library with nested modals support
    wp_enqueue_script('auctionforge_modal', plugin_dir_url(__FILE__).'assets/js/auctionforge-modal.js', array('jquery'), $sourceVersion, true);
    
    // Enable AuctionForgeModal as MicroModal replacement (for nested modals support)
    wp_add_inline_script('auctionforge_modal', 'window.USE_AUCTIONFORGE_MODAL = true;', 'before');

    wp_enqueue_script('bitspirit_front_app', plugin_dir_url(__FILE__).'assets/js/app' . $jsFile, array('jquery', 'bitspirit_front_libs_app', 'auctionforge_modal'),
        $sourceVersion, true);

    // Enqueue password validation script
    wp_enqueue_script('bitspirit_password_validation', plugin_dir_url(__FILE__).'assets/js/password-validation' . $jsFile, array('jquery', 'bitspirit_front_app'),
        $sourceVersion, true);

    wp_enqueue_style('bitspirit_frontend_vendors_style', plugin_dir_url(__FILE__).'assets/css/vendor' . $cssFile, [], $sourceVersion, 'all');
    wp_enqueue_style('bitspirit_frontend_libs_style', plugin_dir_url(__FILE__).'assets/css/libs' . $cssFile, [], $sourceVersion, 'all');

    // Enqueue password validation styles
    wp_enqueue_style('bitspirit_password_validation_style', plugin_dir_url(__FILE__).'assets/css/password-validation' . $cssFile, [], $sourceVersion, 'all');

    // Enqueue minimal layout styles if layout style is set to minimal
    $layoutStyle = \auctionforge\inc\helpers\SettingsHelper::getOption(\auctionforge\inc\controllers\Settings::LAYOUT_STYLE, 'classic');
    if ($layoutStyle === 'minimal') {
        wp_enqueue_style('auctionforge_minimal_layout', plugin_dir_url(__FILE__).'assets/css/minimal-layout.css', [], $sourceVersion, 'all');

        // Add inline CSS for minimal template custom colors
        $minHdrBg = \auctionforge\inc\helpers\SettingsHelper::getOption('minimal_header_bg', '#f5f5f5');
        $minTitleColor = \auctionforge\inc\helpers\SettingsHelper::getOption('minimal_title_color', '#333333');
        $minDateColor = \auctionforge\inc\helpers\SettingsHelper::getOption('minimal_datetime_color', '#666666');
        $minBtnBg = \auctionforge\inc\helpers\SettingsHelper::getOption('minimal_btn_bg', '#333333');
        $minBtnText = \auctionforge\inc\helpers\SettingsHelper::getOption('minimal_btn_text', '#ffffff');
        $minimalInlineCss = "";
        $minimalInlineCss .= ".bp-header__content-minimal, .bp-header__content-profile-page { background-color: {$minHdrBg}; }";
        $minimalInlineCss .= ".bp-header__content-minimal h1.title, .bp-header__content-minimal h1.title-sm, .item-header-title { color: {$minTitleColor}; }";
        $minimalInlineCss .= ".bp-item-header__time { color: {$minDateColor}; }";
        $minimalInlineCss .= ".bp-back-to-catalog-button { background-color: {$minBtnBg} !important; color: {$minBtnText} !important; }";
        $minimalInlineCss .= ".bp-back-to-catalog-button:hover { background-color: {$minBtnBg} !important; color: {$minBtnText} !important; opacity: 0.85; }";
        $minimalInlineCss .= "@media (max-width: 768px) { .navigation-bar .nav-item { background-color: {$minBtnBg} !important; color: {$minBtnText} !important; } }";

        wp_add_inline_style('auctionforge_minimal_layout', $minimalInlineCss);
    }

    // Hide profile fields (applies to both Classic and Minimal layouts)
    $hideHonorific = \auctionforge\inc\helpers\SettingsHelper::getOption('hide_profile_honorific', 'off');
    $hideId = \auctionforge\inc\helpers\SettingsHelper::getOption('hide_profile_id', 'off');
    $profileHideCss = '';
    if ($hideHonorific === 'on') {
        $profileHideCss .= "#addressFieldhonorific { display: none !important; }";
    }
    if ($hideId === 'on') {
        $profileHideCss .= "#addressFieldid { display: none !important; }";
    }
    if ($profileHideCss !== '') {
        wp_add_inline_style('bitspirit_frontend_libs_style', $profileHideCss);
    }

    // Lot analytics view counter CSS
    $viewCounterCss = '.af-lot-view-counter, .af-lot-bid-counter { display: inline-block; font-size: 0.85em; color: #888; margin-top: 4px; margin-right: 12px; }';
    wp_add_inline_style('bitspirit_frontend_libs_style', $viewCounterCss);

    // My Bids Future/Ended toggle buttons CSS
    $bidsToggleCss = '
    .bp-bids-toggle-wrapper { text-align: center; padding: 15px 0 5px; }
    .bp-bids-toggle-btn { display: inline-block; padding: 10px 24px; font-size: 14px; font-weight: 600; border: 2px solid #222; cursor: pointer; transition: all 0.2s ease; margin: 0 4px; background: #fff; color: #222; }
    .bp-bids-toggle-btn:first-child { border-radius: 4px 0 0 4px; margin-right: 0; }
    .bp-bids-toggle-btn:last-child { border-radius: 0 4px 4px 0; margin-left: 0; border-left: none; }
    .bp-bids-toggle-btn.bp-bids-toggle-active { background: #222; color: #fff; }
    .bp-bids-toggle-btn:hover:not(.bp-bids-toggle-active) { background: #f0f0f0; }
    @media (max-width: 480px) { .bp-bids-toggle-btn { padding: 8px 16px; font-size: 13px; } }
    ';
    wp_add_inline_style('bitspirit_frontend_libs_style', $bidsToggleCss);
    
    // Enqueue profile modal styles if experimental features and option are enabled
    if (method_exists(\auctionforge\inc\helpers\SettingsHelper::class, 'isProfileModalEnabled')
        && \auctionforge\inc\helpers\SettingsHelper::isProfileModalEnabled()) {
        wp_enqueue_style('bitspirit_profile_modal_style', plugin_dir_url(__FILE__).'assets/css/profile-modal' . $cssFile, [], $sourceVersion, 'all');
        wp_enqueue_style('bitspirit_profile_modal_nav_style', plugin_dir_url(__FILE__).'assets/css/profile-modal-nav'. $cssFile, [], $sourceVersion, 'all');
    }
}, 1);

add_action('wp_enqueue_scripts', function () {
    wp_localize_script('bitspirit_front_app', 'ScoreSettings', [
        'nonce' => wp_create_nonce('wp_rest')
    ]);

    wp_localize_script('bitspirit_front_app', 'AuctionForgePlugin', [
        'pluginUrl' => plugin_dir_url(__FILE__),
        'imageBlank' => plugin_dir_url(__FILE__) . 'img/not_found.jpg',
    ]);

    wp_localize_script('bitspirit_front_app', 'AuctionForgeIPLog', [
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('auctionforge_ip_log'),
    ]);

    wp_localize_script('bitspirit_front_app', 'AuctionForgeFraud', [
        'ajaxUrl'     => admin_url('admin-ajax.php'),
        'nonce'       => wp_create_nonce('auctionforge_fraud_check'),
        'enforcement' => (bool) get_option('auctionforge_fraud_enforcement', false),
    ]);

    wp_localize_script('bitspirit_front_app', 'AuctionForgeBidCount', [
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('auctionforge_bid_count'),
    ]);

    wp_localize_script('bitspirit_front_app', 'AuctionForgeRegProxy', [
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('auctionforge_reg_proxy'),
    ]);

    $auctionSettings = apply_filters('bidspirit_api_settings_override', []);
    $auctionInfo = apply_filters('bidspirit_api_info_override', []);
    $auctionHouse = apply_filters('bidspirit_api_house_override', []);
    $auctionItemInfo = apply_filters('bidspirit_api_item_info_override', []);
    
    wp_localize_script('bitspirit_front_app', 'API_Settings', [
        'auctionSettings' => $auctionSettings,
        'auctionInfo' => $auctionInfo,
        'auctionHouse' => $auctionHouse,
        'auctionItemInfo' => $auctionItemInfo,
        'settings' => apply_filters('bitspirit_api_settings_configure', [])
    ]);

    /*wp_localize_script('bitspirit_front_app', 'auctionforgeData', array(
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('auctionforge-nonce'),
        'stripePublicKey' => get_option('auctionforge_stripe_public_key', ''),
        'currency' => get_option('auctionforge_currency', 'USD'),
        'locale' => get_locale(),
        'timezone' => wp_timezone_string()
    ));*/
});
//require_once plugin_dir_path( __FILE__ ) . '/AuctionForge.php';

// Commission label override — catches Bidspirit JS client injections on single lot pages
// Placed in wp_footer so it works regardless of theme template overrides
add_action('wp_footer', function () {
    if (!is_singular('lot')) {
        return;
    }

    $commissionSetting = \auctionforge\inc\helpers\SettingsHelper::getOption(\auctionforge\inc\controllers\Settings::COMMISSION_LABEL, 'buyers_premium');
    // Only override when admin wants "Buyer's Premium" — Bidspirit defaults to "Auction house commission"
    if ($commissionSetting !== 'buyers_premium') {
        return;
    }

    $label = esc_js(__("Buyer's Premium", 'auctionforge'));
    ?>
    <script>
    (function(){
        var label = '<?= $label ?>';
        var running = false;
        function overrideCommissionLabel() {
            if (running) return;
            running = true;
            // Replace text nodes in .bp-control__commission elements
            var els = document.querySelectorAll('.bp-control__commission');
            for (var i = 0; i < els.length; i++) {
                for (var j = 0; j < els[i].childNodes.length; j++) {
                    var node = els[i].childNodes[j];
                    if (node.nodeType === 3 && /Auction [Hh]ouse [Cc]ommission/.test(node.textContent)) {
                        node.textContent = node.textContent.replace(/Auction [Hh]ouse [Cc]ommission/g, label);
                    }
                }
            }
            // Also catch the bid confirmation modal
            var modalEls = document.querySelectorAll('#bid-house-commission span');
            for (var k = 0; k < modalEls.length; k++) {
                for (var m = 0; m < modalEls[k].childNodes.length; m++) {
                    var node2 = modalEls[k].childNodes[m];
                    if (node2.nodeType === 3 && /Auction [Hh]ouse [Cc]ommission/.test(node2.textContent)) {
                        node2.textContent = node2.textContent.replace(/Auction [Hh]ouse [Cc]ommission/g, label);
                    }
                }
            }
            running = false;
        }
        overrideCommissionLabel();
        var target = document.getElementById('single-lot') || document.body;
        var observer = new MutationObserver(function() { overrideCommissionLabel(); });
        observer.observe(target, { childList: true, subtree: true, characterData: true });
        setTimeout(overrideCommissionLabel, 1000);
        setTimeout(overrideCommissionLabel, 3000);
        setTimeout(overrideCommissionLabel, 5000);
        setTimeout(function(){ observer.disconnect(); }, 10000);
    })();
    </script>
    <?php
}, 99);

// Add defer attribute to scripts
function defer_scripts($tag, $handle, $src) {
    unset($src);
    $handleList = apply_filters('auctionforge_defer_scripts_list', array(
        'bitspirit_front_libs_app',
        'bitspirit_front_app',
        'stripe_js'
    ));

    if (in_array($handle, $handleList)) {
        return str_replace(' src', ' defer src', $tag);
    }

    return $tag;
}
add_filter('script_loader_tag', 'defer_scripts', 10, 3);

// Add the preload attribute to the style files.
function preload_style($tag, $handle, $href, $media) {
    $handleList = apply_filters('auctionforge_defer_styles_list', array(
        'bitspirit_frontend_fonts',
        'bitspirit_frontend_libs_style',
    ));
    $preloadTag = "";

    if (in_array($handle, $handleList)) {
        $preloadTag = "<link rel=\"preload\" href=\"" . $href . "\" as=\"style\" media=\"" . $media . "\" />";
    }

    return $preloadTag . $tag;
}
add_filter('style_loader_tag', 'preload_style', 10, 4);

function auctionforge_admin_scripts() {
    $data = auctionforge_plugin_info();
    wp_enqueue_script('bitspirit_admin_sweet_alert_2', plugin_dir_url( __FILE__ ) . 'admin/assets/js/sweetalert2.js', array(), '',
        true);
    wp_enqueue_script('bitspirit_admin_flatpickr_js', plugin_dir_url( __FILE__ ) . 'admin/assets/js/flatpickr.min.js', array(), '4.6.13', true);
    wp_enqueue_script('bitspirit_admin_swiper', plugin_dir_url( __FILE__ ) . 'admin/assets/js/swiper.min.js', array(), '', true);
    wp_enqueue_script('bitspirit_app_admin', plugin_dir_url( __FILE__ ) . 'admin/assets/js/bitspirit_admin-app.js',
        array('jquery'), $data['ver'], true);
    wp_enqueue_style('bitspirit_admin_flatpickr_css', '//cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css');
    wp_enqueue_style('bitspirit_app_admin_css', plugin_dir_url( __FILE__ ) . 'admin/assets/css/bitspirit_admin-app.css');
    wp_enqueue_style('bitspirit_app_admin_swiper', plugin_dir_url( __FILE__ ) . 'admin/assets/css/swiper.min.css');
}
add_action('admin_enqueue_scripts', 'auctionforge_admin_scripts');

function bp_templates() {
    $basePath = basename( __DIR__ ) . '/templates/';
    $templatePath = get_stylesheet_directory() . '/auctionforge/';
    $files = array(
        'single' => 'single-lot.php',
        'auction' => 'page_auction.php',
        'auction_list' => 'page_auction_list.php',
        'archive_list' => 'page_auction_list.php',
        'profile' => 'page_profile.php',
        'welcome_app_page' => 'page_welcome.php'
    );
    $files = apply_filters('bp_template_filter', $files);

    $templates = array();
    foreach ($files as $key => $file) {
        if (file_exists($templatePath . $file)) {
            $templates[$key] = $templatePath . $file;
        } else {
            $templates[$key] = WP_PLUGIN_DIR . "/" . $basePath . $file;
        }
    }
    return $templates;
}

function bp_template_include( $template ) {
    global $post;
    $templates = bp_templates();
    $auctionListKey = 'auction_list';
    $manualListKey = 'manual_auction_list';
    $archiveListKey = 'archive_list';
    $profileKey = 'profile';
    $welcomeKey = 'welcome_app_page';
    $auctionPolylangId = 0;
    $archivePolylangId = 0;
    $manualPolylangId = 0;
    $profilePolylangId = 0;
    $welcomePolylangId = 0;
    $pageAuctionListKey = SettingsHelper::getOption($auctionListKey, 0);
    $pageManualListKey = SettingsHelper::getOption($manualListKey, 0);
    $pageArchiveListKey = SettingsHelper::getOption($archiveListKey, 0);
    $pageProfileKey = SettingsHelper::getOption($profileKey, 0);
    $pageWelcomeKey = SettingsHelper::getOption($welcomeKey, 0);

    if (PolylangSupport::isPolylangActive()) {
        $lang = pll_current_language();
        $auctionPolylangId = pll_get_post($pageAuctionListKey, $lang);
        $archivePolylangId = pll_get_post($pageArchiveListKey, $lang);
        $manualPolylangId = pll_get_post($pageManualListKey, $lang);
        $profilePolylangId = pll_get_post($pageProfileKey, $lang);
        $welcomePolylangId = pll_get_post($pageWelcomeKey, $lang);
    }

    if (Conditions::isTaxTemplate()) {
        $path_file = wp_normalize_path($templates['auction']);
    }

    if (Conditions::isSingleLotTemplate($post)) {
        $path_file = wp_normalize_path($templates['single']);
    }

    if (Conditions::isAuctionListTemplate($post, $pageAuctionListKey, $auctionPolylangId)) {
        $path_file = wp_normalize_path($templates[$auctionListKey]);
    }

    if (Conditions::isManualAuctionListTemplate($post, $pageManualListKey, $manualPolylangId)) {
        $path_file = wp_normalize_path($templates[$auctionListKey]);
    }

    if (Conditions::isArchiveListTemplate($post, $pageArchiveListKey, $archivePolylangId)) {
        $path_file = wp_normalize_path($templates[$auctionListKey]);
    }

    if (Conditions::isProfileTemplate($post, $pageProfileKey, $profilePolylangId)) {
        $path_file = wp_normalize_path($templates[$profileKey]);
    }

    if (Conditions::isPageTemplate($post, $pageWelcomeKey, $welcomePolylangId)) {
        $path_file = wp_normalize_path($templates[$welcomeKey]);
    }

    $template = apply_filters('bp_template_action', $template);
    if (key_exists($template, $templates)) {
        $path_file = wp_normalize_path($templates[$template]);
    }

    if (!empty($path_file)) {
        $path_file = apply_filters('bp_template_include_action', $path_file, $templates);
    }

    if ( !empty($path_file) && file_exists( $path_file ) ) {
        return $path_file;
    }

    return $template;
}
add_filter( 'template_include', 'bp_template_include', 99 );

function auctionforge_pre_get_lot_sort($query) {
    if( is_admin() || ! $query->is_main_query() ) {
        return;
    }

    if(Conditions::isAuctionPage($query)) {
        /** @var WP_Query $query */

        $query->set(LotMetaData::BP_QUERY, array("order" => 1));
        $fieldQuery = LotMetaData::BP_QUERY;
        $meta_query = [];
        $part = get_query_var('part');
        if (!empty($part) && intval($part) > 0) {
            $meta_query = (array)$query->get('meta_query');
            $auction = get_queried_object();

            $auctionDays = SettingsHelper::getTermOption($auction->term_id, 'auction_days');
            if (!empty($auctionDays[intval($part) - 1])) {
                $auctionDay = $auctionDays[intval($part) - 1];
            } else {
                $auctionDay = null;
                $query->set_404();
                status_header(404);
            }

            if (isset($auctionDay->firstLotIndex) && isset($auctionDay->lastLotIndex)) {
                $meta_query = array(
                    "bp_index" => array(
                        "between" => array(
                            array(
                                "compare" => ">=",
                                "value" => intval($auctionDay->firstLotIndex),
                            ),
                            array(
                                "compare" => "<=",
                                "value" => intval($auctionDay->lastLotIndex),
                            ),
                        ),
                    ),
                );
            }

            $meta_query = apply_filters('auctionforge_before_main_meta_query', $meta_query, $auction, $auctionDays, $auctionDay, $part);
            $query->set($fieldQuery, $meta_query);
        } elseif (!empty($part)) {
            $meta_query = (array)$query->get('meta_query');
            $auction = get_queried_object();

            $meta_query = apply_filters('auctionforge_before_main_part_meta_query', $meta_query, $auction, $part);
            $query->set($fieldQuery, $meta_query);
        }

        $orderByFields = BidspiritLots::sort_order();

        if (isset($orderByFields['orderBy']) && $orderByFields['orderBy'] == 'soldBid') {
            $compare = ">";
            $data["bp_field"][] = [
                "field" => "soldBid",
                "value" => 0,
                "compare" => $compare
            ];
            $query->set(LotMetaData::BP_QUERY, array_merge($data, $meta_query));
        }

        $query->set('bp_orderby', $orderByFields['orderBy']);
        $query->set('order', $orderByFields['order']);
    }
}
add_action('pre_get_posts' ,'auctionforge_pre_get_lot_sort', 1, 1);

/** Phone-home ping - DISABLED */

function sortAuctionASC($a, $b)
{
    $auctionDaysA = SettingsHelper::getTermOption($a->term_id, 'auction_days');
    $auctionDaysB = SettingsHelper::getTermOption($b->term_id, 'auction_days');

    if (empty($auctionDaysA) || empty($auctionDaysB)) {
        return 1;
    }

    if (!empty($auctionDaysA[0]->auctionStarted) && !empty($auctionDaysB[0]->auctionStarted)) {
        $dateTimeA = $auctionDaysA[0]->auctionStarted;
        $dateTimeB = $auctionDaysB[0]->auctionStarted;
    } else {
        $dateTimeA = $auctionDaysA[0]->date . " " . $auctionDaysA[0]->time;
        $dateTimeB = $auctionDaysB[0]->date . " " . $auctionDaysB[0]->time;
    }

    if ($dateTimeA == $dateTimeB) {
        return 0;
    }
    return ($dateTimeA < $dateTimeB) ? -1 : 1;
}

function sortAuctionDESC($a, $b)
{
    $auctionDaysA = SettingsHelper::getTermOption($a->term_id, 'auction_days');
    $auctionDaysB = SettingsHelper::getTermOption($b->term_id, 'auction_days');

    if (empty($auctionDaysA) || empty($auctionDaysB)) {
        return -1;
    }

    if (!empty($auctionDaysA[0]->auctionStarted) && !empty($auctionDaysB[0]->auctionStarted)) {
        $dateTimeA = $auctionDaysA[0]->auctionStarted;
        $dateTimeB = $auctionDaysB[0]->auctionStarted;
    } else {
        $dateTimeA = $auctionDaysA[0]->date . " " . $auctionDaysA[0]->time;
        $dateTimeB = $auctionDaysB[0]->date . " " . $auctionDaysB[0]->time;
    }

    if ($dateTimeA == $dateTimeB) {
        return 0;
    }
    return ($dateTimeA > $dateTimeB) ? -1 : 1;
}

function cron_add_minutely( $schedules ) {
    $schedules['bp_minutely'] = array(
        'interval' => BP_MINUTELY,
        'display' => 'Minutely'
    );
    return $schedules;
}
add_filter('cron_schedules', 'cron_add_minutely');

function cron_add_3_minutely( $schedules ) {
    $schedules['bp_3_minutely'] = array(
        'interval' => BP_MINUTELY * 3,
        'display' => 'Every 3 minutes'
    );
    return $schedules;
}
add_filter('cron_schedules', 'cron_add_3_minutely');

function cron_add_ten_minutely( $schedules ) {
    $schedules['bp_ten_minutely'] = array(
        'interval' => BP_MINUTELY * 10,
        'display' => 'Every 10 minutes'
    );
    return $schedules;
}
add_filter('cron_schedules', 'cron_add_ten_minutely');

function cron_add_hourly( $schedules ) {
    $schedules['bp_hourly'] = array(
        'interval' => BP_HOURLY,
        'display' => 'Hourly'
    );
    return $schedules;
}
add_filter('cron_schedules', 'cron_add_hourly');

function cron_add_24_hourly( $schedules ) {
    $schedules['bp_24_hourly'] = array(
        'interval' => BP_24_HOURLY,
        'display' => 'Every 24 hours'
    );
    return $schedules;
}
add_filter('cron_schedules', 'cron_add_24_hourly');

// NOTE (3.7.0.28): cron events no longer carry $_SERVER['SERVER_NAME'] as args.
// Pre-3.7.0.28 versions did, which meant every distinct hostname this site was
// reached under (canonical, www., IP-direct, internal cron caller, old domain
// from before a rename) produced its own duplicate entry in wp_options.cron —
// the wp_next_scheduled() guard only matches the same args. Handlers `unset
// ($host)` anyway; the arg was always vestigial. Migration block in
// inc/maintenance/class-cron-cleanup.php sweeps stale per-hostname events on
// upgrade. Guards on $_SERVER['SERVER_NAME'] removed too so wp-cli / OS cron
// bootstraps schedule events on first run.
if (SettingsHelper::getOption('sync_cheduled', 'off') === 'on') {
    if (!wp_next_scheduled('bidspirit_import')) {
        wp_schedule_event(time(), 'bp_hourly', 'bidspirit_import');
    }
    function bidspirit_import_action() {
        $auctionList = new AuctionList();
        $auctionList->init();

        $lotList = new LotList();
        $lotList->init();
    }
    add_action('bidspirit_import', 'bidspirit_import_action');

    if (!wp_next_scheduled('data_import')) {
        wp_schedule_event(time(), 'bp_hourly', 'data_import');
    }
    function data_import_action() {
        $insertLots = new InsertLotsBS();
        $insertLots->init();
    }
    add_action('data_import', 'data_import_action');

    if (!wp_next_scheduled('bidspirit_archive_import')) {
        wp_schedule_event(time(), 'bp_24_hourly', 'bidspirit_archive_import');
    }
    function bidspirit_archive_import_action() {
        $auctionList = new AuctionList();
        $auctionList->init();

        $lotList = new LotList();
        $lotList->init(false);
    }
    add_action('bidspirit_archive_import', 'bidspirit_archive_import_action');
}

// Sync v3 — WP-Cron hourly safety net (always active, not gated by sync_cheduled)
if (!wp_next_scheduled('auctionforge_sync_safety')) {
    wp_schedule_event(time(), 'hourly', 'auctionforge_sync_safety');
}
function auctionforge_sync_safety_action() {
    // Reset stuck jobs
    \auctionforge\inc\sync\SyncQueue::resetStuckJobs();

    // Spawn worker if pending jobs exist
    $counts = \auctionforge\inc\sync\SyncQueue::getStatusCounts();
    if ($counts['pending'] > 0 && !\auctionforge\inc\sync\SyncQueue::hasActiveWorker()) {
        \auctionforge\inc\sync\SyncLogger::log('safety', "WP-Cron safety: {$counts['pending']} pending, spawning worker", 'info');
        \auctionforge\inc\sync\SyncQueue::spawnWorker();
    }

    // Drain orphaned staging: lots staged into import_history but never turned
    // into WP posts (a prior worker died before processing, the staging trigger
    // was missed, or the worker spawn was failing). The worker loop only
    // converts rows attached to a queue job, so sweep the staging table here.
    // Fire non-blocking — the processor runs in its own request (300s budget)
    // and logs progress to the Live Console; blocking here would exceed the
    // cron request's max_execution_time on most hosts.
    $stagingBacklog = \auctionforge\inc\sync\SyncQueue::stagingBacklogCount();
    if ($stagingBacklog > 0) {
        \auctionforge\inc\sync\SyncLogger::log('safety', "WP-Cron safety: {$stagingBacklog} lot(s) staged but not posted — kicking staging processor", 'info');
        \auctionforge\inc\sync\SyncQueue::triggerStagingProcessor(false);
    }

    // Purge old data
    \auctionforge\inc\sync\SyncQueue::purge(AUCTIONFORGE_SYNC_QUEUE_RETENTION);
    \auctionforge\inc\sync\SyncLogger::purge(AUCTIONFORGE_SYNC_LOG_RETENTION);
}
add_action('auctionforge_sync_safety', 'auctionforge_sync_safety_action');

function auctionforge_locale() {
    unload_textdomain('auctionforge');
    unload_textdomain('auctionforge');
    load_plugin_textdomain('auctionforge', false, dirname(plugin_basename(__FILE__ )) . '/language/');
    load_plugin_textdomain('auctionforge', false, dirname(plugin_basename(__FILE__)) . '/language/');
}
add_action('init', 'auctionforge_locale', 0);

add_image_size(BidspiritLots::$imageSizes[0], 185, 0, false);
add_image_size(BidspiritLots::$imageSizes[1], 280, 0, false);
add_image_size(BidspiritLots::$imageSizes[2], 770, 0, false);

// init plugin update checker - DISABLED to prevent site tracking
// require_once __DIR__ . '/plugin-update-checker/plugin-update-checker.php';

/**
 * Disable Debug Bar integration to prevent conflicts with Query Monitor
 * @todo Need test
 * @see #947
 */
// add_filter('puc_v5_debug_bar_enabled', '__return_false');



/** Legacy update checker - DISABLED (replaced by AuctionForge auto-updater) */
