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

$nonce = wp_create_nonce('auctionforge_admin');
$wp_cron_enabled = \auctionforge\inc\helpers\SettingsHelper::getOption('sync_cheduled', 'off') === 'on';

// Read heartbeat file from plugin directory (always inside open_basedir)
$heartbeat_file = BASE_PATH . '/cron-status.json';
$heartbeat = null;
$external_cron_detected = false;
$heartbeat_age = null;

if (file_exists($heartbeat_file)) {
    $heartbeat = json_decode(file_get_contents($heartbeat_file), true);
    if (is_array($heartbeat) && !empty($heartbeat['last_heartbeat_epoch'])) {
        $external_cron_detected = true;
        $heartbeat_age = time() - intval($heartbeat['last_heartbeat_epoch']);
    }
}

// Task definitions — v3 queue tasks + maintenance tasks
$tasks = array(
    'safety_sync' => array(
        'label' => 'Safety Sync',
        'desc' => 'Runs the hourly safety net: resets stuck jobs, discovers new auctions, delta-checks API state, fixes orphans, and purges old data.',
        'schedule' => 'Hourly (safety cron) + On demand',
        'icon' => 'dashicons-shield',
        'legacy_task' => 'safety_sync',
    ),
    'full_sync' => array(
        'label' => 'Sync Active Auctions',
        'desc' => 'Fetches and updates all active auctions and lots from BidSpirit via the sync queue.',
        'schedule' => 'Real-time (webhooks) + Hourly (safety net)',
        'icon' => 'dashicons-download',
        'v3_action' => 'full_sync',
    ),
    'archive_sync' => array(
        'label' => 'Sync Archived Auctions',
        'desc' => 'Updates sale prices and status for past/ended auctions via the sync queue.',
        'schedule' => 'On demand',
        'icon' => 'dashicons-backup',
        'v3_action' => 'archive_sync',
    ),
    'cleaner' => array(
        'label' => 'Clean Draft/Trash Lots',
        'desc' => 'Permanently deletes lot posts in draft or trash status.',
        'schedule' => 'Weekly (Sunday 4am)',
        'icon' => 'dashicons-trash',
        'legacy_task' => 'cleaner',
        'stale_after' => 691200,
    ),
    'cleanerTrashMeta' => array(
        'label' => 'Clean Orphaned Meta',
        'desc' => 'Removes orphaned meta fields left by deleted lots.',
        'schedule' => 'Weekly (Sunday 4:30am)',
        'icon' => 'dashicons-editor-removeformatting',
        'legacy_task' => 'cleanerTrashMeta',
        'stale_after' => 691200,
    ),
);

// Get task status from heartbeat
function af_get_task_status($heartbeat, $task_key, $stale_after) {
    if (empty($heartbeat) || empty($heartbeat['tasks'][$task_key])) {
        return array('status' => 'unknown', 'label' => 'No data', 'last_run' => null, 'duration' => null, 'age' => null);
    }
    $t = $heartbeat['tasks'][$task_key];
    $age = time() - intval($t['last_run_epoch']);
    $status = $t['status'];

    // Determine display status
    if ($status === 'ok' && $age > $stale_after) {
        $display = 'stale';
        $label = 'Overdue';
    } elseif ($status === 'ok') {
        $display = 'ok';
        $label = 'OK';
    } elseif ($status === 'timeout') {
        $display = 'errors';
        $label = 'Timeout';
    } elseif ($status === 'failed' || $status === 'killed') {
        $display = 'errors';
        $label = 'Failed';
    } else {
        $display = 'unknown';
        $label = ucfirst($status);
    }

    return array(
        'status' => $display,
        'label' => $label,
        'last_run' => $t['last_run'],
        'last_run_epoch' => $t['last_run_epoch'],
        'duration' => $t['duration_seconds'],
        'age' => $age,
        'exit_code' => $t['exit_code'] ?? null,
    );
}

// Human-readable time ago
function af_time_ago($seconds) {
    if ($seconds < 60) return $seconds . 's ago';
    if ($seconds < 3600) return floor($seconds / 60) . 'm ago';
    if ($seconds < 86400) return floor($seconds / 3600) . 'h ' . floor(($seconds % 3600) / 60) . 'm ago';
    return floor($seconds / 86400) . 'd ' . floor(($seconds % 86400) / 3600) . 'h ago';
}

// Get wp-cron next scheduled
function af_get_wp_cron_next($hook) {
    if (empty($hook)) return null;
    $next = wp_next_scheduled($hook, array($_SERVER['SERVER_NAME'] ?? ''));
    if (!$next) $next = wp_next_scheduled($hook);
    return $next;
}
?>
<style>
    .af-sync-wrap { max-width: 960px; margin: 20px auto 40px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
    .af-sync-wrap h1 { font-size: 24px; font-weight: 600; margin-bottom: 5px; display: flex; align-items: center; gap: 10px; }
    .af-sync-wrap h1 .dashicons { font-size: 28px; width: 28px; height: 28px; color: #2271b1; }
    .af-sync-wrap .af-sync-subtitle { color: #666; margin-bottom: 24px; font-size: 14px; }

    .af-sync-card { background: #fff; border: 1px solid #ddd; border-radius: 6px; padding: 24px 28px; margin-bottom: 20px; }
    .af-sync-card-header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #eee; }
    .af-sync-card-header .dashicons { font-size: 20px; width: 20px; height: 20px; color: #2271b1; }
    .af-sync-card-header h2 { font-size: 16px; font-weight: 600; margin: 0; flex: 1; }

    .af-sync-mode { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
    .af-sync-mode.external { background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7; }
    .af-sync-mode.wpcron { background: #e3f2fd; color: #1565c0; border: 1px solid #90caf9; }
    .af-sync-mode.none { background: #fff3e0; color: #e65100; border: 1px solid #ffcc80; }

    .af-sync-badge { display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; border-radius: 3px; font-size: 12px; font-weight: 500; }
    .af-sync-badge.ok { background: #d4edda; color: #155724; }
    .af-sync-badge.errors { background: #f8d7da; color: #721c24; }
    .af-sync-badge.running { background: #d1ecf1; color: #0c5460; }
    .af-sync-badge.unknown { background: #f0f0f1; color: #666; }
    .af-sync-badge.stale { background: #fff3e0; color: #e65100; }
    .af-sync-badge .dashicons { font-size: 14px; width: 14px; height: 14px; }

    .af-sync-tasks { display: grid; gap: 12px; }
    .af-sync-task { background: #f9f9f9; border: 1px solid #e5e5e5; border-radius: 5px; padding: 16px 20px; display: grid; grid-template-columns: 40px 1fr auto; align-items: center; gap: 16px; }
    .af-sync-task .task-icon .dashicons { font-size: 24px; width: 24px; height: 24px; color: #2271b1; }
    .af-sync-task .task-info h3 { margin: 0 0 2px; font-size: 14px; font-weight: 600; }
    .af-sync-task .task-info p { margin: 0; font-size: 12px; color: #666; }
    .af-sync-task .task-info .task-meta { display: flex; gap: 16px; margin-top: 6px; font-size: 11px; color: #888; }
    .af-sync-task .task-info .task-meta code { font-size: 11px; background: #eee; padding: 1px 5px; border-radius: 2px; }
    .af-sync-task .task-actions { display: flex; gap: 6px; align-items: center; }
    .af-sync-task .task-actions .button { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; padding: 2px 10px; height: 28px; }
    .af-sync-task .task-actions .button .dashicons { font-size: 14px; width: 14px; height: 14px; margin-top: 1px; }

    .af-sync-log { background: #1e1e2f; color: #c5c5c5; font-family: Consolas, Monaco, monospace; font-size: 12px; padding: 16px; border-radius: 5px; max-height: 300px; overflow-y: auto; line-height: 1.6; margin-top: 12px; display: none; }
    .af-sync-log .log-line { white-space: pre-wrap; word-break: break-all; }
    .af-sync-log .log-line .ts { color: #6a9fb5; }
    .af-sync-log .log-line .site { color: #a1c659; }
    .af-sync-log .log-line .done { color: #99cc99; }
    .af-sync-log .log-line .fail { color: #f2777a; }
    .af-sync-log .log-line .timeout { color: #ffcc66; }

    .af-sync-result { margin-top: 12px; padding: 10px 14px; border-radius: 4px; font-size: 13px; display: none; }
    .af-sync-result.success { background: #d4edda; color: #155724; border: 1px solid #a3d9a5; display: block; }
    .af-sync-result.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; display: block; }
    .af-sync-result.info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; display: block; }

    .af-sync-info-table { width: 100%; border-collapse: collapse; font-size: 13px; }
    .af-sync-info-table th { text-align: left; padding: 8px 12px; background: #f7f7f7; border: 1px solid #e5e5e5; font-weight: 600; color: #333; width: 200px; }
    .af-sync-info-table td { padding: 8px 12px; border: 1px solid #e5e5e5; color: #444; }
    .af-sync-info-table code { background: #f0f0f1; padding: 2px 8px; border-radius: 3px; font-size: 12px; }

    .af-guide-section { margin-bottom: 20px; border: 1px solid #e5e5e5; border-radius: 5px; overflow: hidden; }
    .af-guide-section:last-child { margin-bottom: 0; }
    .af-guide-heading { margin: 0; padding: 12px 16px; background: #f7f7f7; font-size: 14px; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid #e5e5e5; }
    .af-guide-heading:hover { background: #f0f0f1; }
    .af-guide-heading .dashicons { font-size: 16px; width: 16px; height: 16px; color: #2271b1; }
    .af-guide-body { padding: 16px 20px; font-size: 13px; color: #444; }
    .af-guide-body p { margin: 0 0 10px; }
    .af-guide-body p:last-child { margin-bottom: 0; }
    .af-guide-code { background: #1e1e2f; color: #c5c5c5; font-family: Consolas, Monaco, monospace; font-size: 12px; padding: 14px 16px; border-radius: 5px; overflow-x: auto; line-height: 1.7; white-space: pre; margin: 8px 0; }

    @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
    .spin { animation: spin 1s linear infinite; }

    /* Task execution modal */
    .af-modal-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.55); z-index: 100100; align-items: center; justify-content: center; }
    .af-modal-overlay.active { display: flex; }
    .af-modal { background: #fff; border-radius: 12px; width: 420px; max-width: 92vw; box-shadow: 0 20px 60px rgba(0,0,0,0.3); overflow: hidden; animation: af-modal-in 0.25s ease-out; }
    @keyframes af-modal-in { from { opacity: 0; transform: scale(0.95) translateY(10px); } to { opacity: 1; transform: scale(1) translateY(0); } }

    .af-modal-header { padding: 20px 24px 0; display: flex; align-items: center; gap: 10px; }
    .af-modal-header .dashicons { font-size: 22px; width: 22px; height: 22px; color: #2271b1; }
    .af-modal-header h3 { margin: 0; font-size: 16px; font-weight: 600; color: #1d2327; flex: 1; }
    .af-modal-close { background: none; border: none; cursor: pointer; color: #999; font-size: 20px; padding: 4px; line-height: 1; border-radius: 4px; display: none; }
    .af-modal-close:hover { color: #333; background: #f0f0f1; }
    .af-modal.done .af-modal-close { display: block; }

    .af-modal-body { padding: 24px; text-align: center; }
    .af-modal-desc { font-size: 13px; color: #666; margin: 0 0 24px; }

    /* Progress ring */
    .af-progress-wrap { position: relative; width: 120px; height: 120px; margin: 0 auto 20px; }
    .af-progress-ring { transform: rotate(-90deg); }
    .af-progress-ring circle { fill: none; stroke-width: 6; stroke-linecap: round; }
    .af-progress-ring .ring-bg { stroke: #e5e5e5; }
    .af-progress-ring .ring-fill { stroke: #2271b1; stroke-dasharray: 339.292; stroke-dashoffset: 339.292; transition: stroke-dashoffset 0.5s ease; }
    .af-modal.success .af-progress-ring .ring-fill { stroke: #2e7d32; }
    .af-modal.error .af-progress-ring .ring-fill { stroke: #d32f2f; }
    .af-progress-center { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; }
    .af-progress-timer { font-size: 28px; font-weight: 700; color: #1d2327; font-variant-numeric: tabular-nums; line-height: 1; }
    .af-progress-label { font-size: 10px; color: #888; text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; }

    /* State icon overlay on ring */
    .af-progress-icon { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
    .af-progress-icon .dashicons { font-size: 40px; width: 40px; height: 40px; }
    .af-modal.success .af-progress-icon { display: block; color: #2e7d32; }
    .af-modal.error .af-progress-icon { display: block; color: #d32f2f; }
    .af-modal.success .af-progress-center, .af-modal.error .af-progress-center { display: none; }
    .af-modal.success .af-progress-icon .icon-error,
    .af-modal.error .af-progress-icon .icon-success { display: none; }

    /* Estimate bar */
    .af-modal-estimate { display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: #888; margin-bottom: 12px; padding: 0 4px; }
    .af-modal-progress-bar { height: 4px; background: #e5e5e5; border-radius: 2px; overflow: hidden; margin-bottom: 16px; }
    .af-modal-progress-bar .bar-fill { height: 100%; background: #2271b1; border-radius: 2px; width: 0%; transition: width 0.5s ease; }
    .af-modal.success .af-modal-progress-bar .bar-fill { background: #2e7d32; width: 100% !important; }
    .af-modal.error .af-modal-progress-bar .bar-fill { background: #d32f2f; }

    /* Status message */
    .af-modal-status { font-size: 13px; color: #444; min-height: 20px; }
    .af-modal-status .dashicons { font-size: 16px; width: 16px; height: 16px; vertical-align: -3px; margin-right: 4px; }
    .af-modal.running .af-modal-status { color: #2271b1; }
    .af-modal.success .af-modal-status { color: #2e7d32; font-weight: 600; }
    .af-modal.error .af-modal-status { color: #d32f2f; font-weight: 600; }

    .af-modal-footer { padding: 0 24px 20px; text-align: center; }
    .af-modal-footer .button { min-width: 100px; }

    /* Pulse animation for running state */
    @keyframes af-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
    .af-modal.running .af-progress-label { animation: af-pulse 1.5s ease infinite; }


    /* Kill buttons */
    .af-queue-actions .button-danger { background: #dc3545; border-color: #dc3545; color: #fff; }
    .af-queue-actions .button-danger:hover { background: #c82333; border-color: #bd2130; color: #fff; }
    .af-queue-jobs .btn-kill-job { background: none; border: none; color: #c62828; cursor: pointer; font-size: 11px; padding: 1px 6px; border-radius: 3px; }
    .af-queue-jobs .btn-kill-job:hover { background: #ffebee; text-decoration: underline; }
    /* Sync v3 — Queue Status */
    .af-queue-stats { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-bottom: 16px; }
    .af-queue-stat { text-align: center; padding: 12px 8px; background: #f9f9f9; border: 1px solid #e5e5e5; border-radius: 5px; }
    .af-queue-stat .stat-num { font-size: 28px; font-weight: 700; line-height: 1.2; }
    .af-queue-stat .stat-label { font-size: 11px; color: #888; text-transform: uppercase; letter-spacing: 0.5px; }
    .af-queue-stat.pending .stat-num { color: #e65100; }
    .af-queue-stat.processing .stat-num { color: #1565c0; }
    .af-queue-stat.completed .stat-num { color: #2e7d32; }
    .af-queue-stat.failed .stat-num { color: #c62828; }
    .af-queue-stat.skipped .stat-num { color: #888; }

    .af-queue-jobs { max-height: 300px; overflow-y: auto; }
    .af-queue-jobs table { width: 100%; border-collapse: collapse; font-size: 12px; }
    .af-queue-jobs th { text-align: left; padding: 6px 10px; background: #f7f7f7; border: 1px solid #e5e5e5; font-weight: 600; font-size: 11px; position: sticky; top: 0; }
    .af-queue-jobs td { padding: 5px 10px; border: 1px solid #e5e5e5; font-size: 12px; }
    .af-queue-jobs .status-pending { color: #e65100; font-weight: 600; }
    .af-queue-jobs .status-processing { color: #1565c0; font-weight: 600; }
    .af-queue-jobs .status-completed { color: #2e7d32; }
    .af-queue-jobs .status-failed { color: #c62828; font-weight: 600; }
    .af-queue-jobs .status-skipped { color: #888; }

    .af-queue-actions { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; }
    .af-queue-actions select { max-width: 250px; }

    /* Sync v3 — Live Console */
    .af-console { background: #1e1e2f; color: #c5c5c5; font-family: Consolas, Monaco, 'Courier New', monospace; font-size: 12px; padding: 0; border-radius: 5px; overflow: hidden; }
    .af-console-toolbar { display: flex; gap: 8px; align-items: center; padding: 8px 12px; background: #16162a; border-bottom: 1px solid #2a2a4a; }
    .af-console-toolbar .button { font-size: 11px; padding: 2px 10px; height: 24px; }
    .af-console-toolbar .af-console-status { margin-left: auto; font-size: 11px; }
    .af-console-toolbar .af-console-status.live { color: #99cc99; }
    .af-console-toolbar .af-console-status.paused { color: #ffcc66; }
    .af-console-output { padding: 12px 16px; max-height: 350px; overflow-y: auto; line-height: 1.7; }
    .af-console-output .log-entry { white-space: pre-wrap; word-break: break-all; }
    .af-console-output .log-entry .ts { color: #6a9fb5; }
    .af-console-output .log-entry .ctx { color: #a1c659; }
    .af-console-output .log-entry.level-success { color: #99cc99; }
    .af-console-output .log-entry.level-warning { color: #ffcc66; }
    .af-console-output .log-entry.level-error { color: #f2777a; }
    .af-console-output .log-entry.level-info { color: #c5c5c5; }
    .af-console-empty { color: #555; font-style: italic; padding: 20px; text-align: center; }
</style>

<div class="container wrap bt-admin af-sync-wrap">
    <h1>
        <span class="dashicons dashicons-controls-repeat"></span>
        <?php echo get_admin_page_title(); ?>
    </h1>
    <p class="af-sync-subtitle"><?php _e('Monitor and manage BidSpirit synchronization tasks.', 'auctionforge'); ?></p>

    <div id="af-sync-notices"></div>

    <!-- Sync Mode -->
    <div class="af-sync-card">
        <div class="af-sync-card-header">
            <span class="dashicons dashicons-admin-generic"></span>
            <h2><?php _e('Sync Engine', 'auctionforge'); ?></h2>
            <?php if ($wp_cron_enabled): ?>
                <span class="af-sync-mode wpcron"><span class="dashicons dashicons-wordpress"></span> WP-Cron</span>
            <?php elseif ($external_cron_detected): ?>
                <span class="af-sync-mode external"><span class="dashicons dashicons-clock"></span> External Cron</span>
            <?php else: ?>
                <span class="af-sync-mode none"><span class="dashicons dashicons-warning"></span> Not Configured</span>
            <?php endif; ?>
        </div>

        <table class="af-sync-info-table">
            <tr>
                <th><?php _e('WP-Cron (sync_cheduled)', 'auctionforge'); ?></th>
                <td>
                    <?php if ($wp_cron_enabled): ?>
                        <span class="af-sync-badge ok"><span class="dashicons dashicons-yes"></span> Enabled</span>
                        <span style="color:#888;font-size:12px;margin-left:8px;"><?php _e('Sync runs via WordPress cron hooks on each page request.', 'auctionforge'); ?></span>
                    <?php else: ?>
                        <span class="af-sync-badge unknown"><span class="dashicons dashicons-minus"></span> Disabled</span>
                        <span style="color:#888;font-size:12px;margin-left:8px;"><?php _e('Enable in Settings > "Use wp-cron" to activate.', 'auctionforge'); ?></span>
                    <?php endif; ?>
                </td>
            </tr>
            <tr>
                <th><?php _e('External Cron', 'auctionforge'); ?></th>
                <td>
                    <?php if ($external_cron_detected): ?>
                        <span class="af-sync-badge ok"><span class="dashicons dashicons-yes"></span> Active</span>
                        <?php if ($heartbeat_age !== null): ?>
                            <span style="color:#888;font-size:12px;margin-left:8px;">
                                <?php printf(__('Last heartbeat: %s', 'auctionforge'), af_time_ago($heartbeat_age)); ?>
                                <?php if (!empty($heartbeat['cron_runner'])): ?>
                                    &mdash; <code><?php echo esc_html($heartbeat['cron_runner']); ?></code>
                                <?php endif; ?>
                            </span>
                        <?php endif; ?>
                    <?php else: ?>
                        <span class="af-sync-badge unknown"><span class="dashicons dashicons-minus"></span> No Heartbeat</span>
                        <span style="color:#888;font-size:12px;margin-left:8px;"><?php _e('Run the setup script to install the external cron system.', 'auctionforge'); ?></span>
                    <?php endif; ?>
                </td>
            </tr>
            <tr>
                <th><?php _e('Heartbeat File', 'auctionforge'); ?></th>
                <td>
                    <?php if ($heartbeat): ?>
                        <code><?php echo esc_html($heartbeat_file); ?></code>
                        <span style="color:#888;font-size:12px;margin-left:8px;"><?php _e('Written by the cron runner after each task.', 'auctionforge'); ?></span>
                    <?php else: ?>
                        <span style="color:#888;font-size:12px;"><?php _e('Will be created automatically after the first cron run.', 'auctionforge'); ?></span>
                    <?php endif; ?>
                </td>
            </tr>
        </table>
    </div>

    <!-- Tasks -->
    <div class="af-sync-card">
        <div class="af-sync-card-header">
            <span class="dashicons dashicons-list-view"></span>
            <h2><?php _e('Sync Tasks', 'auctionforge'); ?></h2>
        </div>

        <div class="af-sync-tasks">
            <?php foreach ($tasks as $task_key => $task):
                $is_v3 = !empty($task['v3_action']);
                $ts = !$is_v3 ? af_get_task_status($heartbeat, $task_key, $task['stale_after'] ?? 691200) : null;
            ?>
            <div class="af-sync-task" data-task="<?php echo esc_attr($task_key); ?>">
                <div class="task-icon">
                    <span class="dashicons <?php echo esc_attr($task['icon']); ?>"></span>
                </div>
                <div class="task-info">
                    <h3><?php echo esc_html($task['label']); ?></h3>
                    <p><?php echo esc_html($task['desc']); ?></p>
                    <div class="task-meta">
                        <span><strong>Schedule:</strong> <?php echo esc_html($task['schedule']); ?></span>
                        <?php if ($is_v3): ?>
                            <span class="af-sync-badge ok"><span class="dashicons dashicons-controls-repeat"></span> Via Queue</span>
                        <?php elseif ($ts && $ts['last_run']): ?>
                            <span><strong>Last run:</strong> <?php echo esc_html(af_time_ago($ts['age'])); ?>
                                <?php if ($ts['duration'] !== null): ?>(<?php echo intval($ts['duration']); ?>s)<?php endif; ?>
                            </span>
                            <?php
                                $badge_icon = 'minus';
                                if ($ts['status'] === 'ok') $badge_icon = 'yes';
                                elseif ($ts['status'] === 'errors') $badge_icon = 'warning';
                                elseif ($ts['status'] === 'stale') $badge_icon = 'clock';
                            ?>
                            <span class="af-sync-badge <?php echo esc_attr($ts['status']); ?>">
                                <span class="dashicons dashicons-<?php echo esc_attr($badge_icon); ?>"></span>
                                <?php echo esc_html($ts['label']); ?>
                            </span>
                        <?php else: ?>
                            <span class="af-sync-badge unknown"><span class="dashicons dashicons-minus"></span> No data</span>
                        <?php endif; ?>
                    </div>
                </div>
                <div class="task-actions">
                    <?php if ($is_v3): ?>
                        <button type="button" class="button af-run-v3" data-v3-action="<?php echo esc_attr($task['v3_action']); ?>">
                            <span class="dashicons dashicons-controls-play"></span> Run Now
                        </button>
                    <?php else: ?>
                        <button type="button" class="button af-run-task" data-task="<?php echo esc_attr($task['legacy_task'] ?? $task_key); ?>">
                            <span class="dashicons dashicons-controls-play"></span> Run Now
                        </button>
                    <?php endif; ?>
                </div>
            </div>
            <?php endforeach; ?>

            <!-- Clear Import History -->
            <div class="af-sync-task" data-task="clear_import_history">
                <div class="task-icon">
                    <span class="dashicons dashicons-database-remove"></span>
                </div>
                <div class="task-info">
                    <h3><?php _e('Clear Import History', 'auctionforge'); ?></h3>
                    <p><?php _e('Truncates the import_history staging table. Use when lots get stuck and won\'t re-sync. A full sync after clearing will re-fetch all lots fresh from BidSpirit.', 'auctionforge'); ?></p>
                    <div class="task-meta">
                        <span><strong>Schedule:</strong> On demand only</span>
                        <?php
                        global $wpdb;
                        $ih_total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}import_history");
                        $ih_stuck = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}import_history WHERE syncFinished = 0");
                        ?>
                        <span class="af-sync-badge <?php echo $ih_stuck > 0 ? 'errors' : 'ok'; ?>">
                            <span class="dashicons dashicons-<?php echo $ih_stuck > 0 ? 'warning' : 'yes'; ?>"></span>
                            <?php echo esc_html("{$ih_total} rows ({$ih_stuck} unfinished)"); ?>
                        </span>
                    </div>
                </div>
                <div class="task-actions">
                    <button type="button" class="button button-danger" id="af-btn-clear-import-history" style="background:#dc3545;border-color:#dc3545;color:#fff;">
                        <span class="dashicons dashicons-database-remove"></span> Clear
                    </button>
                </div>
            </div>
        </div>
    </div>

    <!-- Sync v3: Queue Status -->
    <div class="af-sync-card" id="af-queue-card">
        <div class="af-sync-card-header">
            <span class="dashicons dashicons-list-view"></span>
            <h2><?php _e('Sync Queue', 'auctionforge'); ?></h2>
            <span class="af-sync-badge unknown" id="af-queue-worker-badge">
                <span class="dashicons dashicons-minus"></span> <span id="af-queue-worker-label">Loading...</span>
            </span>
        </div>

        <div class="af-queue-actions">
            <button type="button" class="button button-primary" id="af-btn-full-sync">
                <span class="dashicons dashicons-update" style="font-size:14px;width:14px;height:14px;margin-top:3px;margin-right:2px;"></span> Full Sync
            </button>
            <button type="button" class="button" id="af-btn-archive-sync">
                <span class="dashicons dashicons-archive" style="font-size:14px;width:14px;height:14px;margin-top:3px;margin-right:2px;"></span> Archive Sync
            </button>
            <select id="af-auction-select">
                <option value="">— Select Auction —</option>
                <?php
                $activeAuctions = \auctionforge\inc\model\Auction::getActiveAuction();
                if (!empty($activeAuctions)) {
                    echo '<optgroup label="Active Auctions">';
                    foreach ($activeAuctions as $aTerm) {
                        $aId = get_term_meta($aTerm->term_id, 'auction_id', true);
                        if ($aId) {
                            echo '<option value="' . esc_attr($aId) . '">' . esc_html($aTerm->name) . ' (' . esc_html($aId) . ')</option>';
                        }
                    }
                    echo '</optgroup>';
                }
                $archiveAuctions = \auctionforge\inc\model\Auction::getArchiveAuction();
                if (!empty($archiveAuctions)) {
                    echo '<optgroup label="Past Auctions">';
                    foreach ($archiveAuctions as $aTerm) {
                        $aId = get_term_meta($aTerm->term_id, 'auction_id', true);
                        $aState = get_term_meta($aTerm->term_id, 'auction_state', true);
                        if ($aId) {
                            echo '<option value="' . esc_attr($aId) . '">' . esc_html($aTerm->name) . ' (' . esc_html($aId) . ') [' . esc_html($aState) . ']</option>';
                        }
                    }
                    echo '</optgroup>';
                }
                ?>
            </select>
            <button type="button" class="button" id="af-btn-sync-auction">Sync Selected</button>
            <button type="button" class="button" id="af-btn-spawn-worker">Spawn Worker</button>
            <button type="button" class="button" id="af-btn-process-staging" title="Convert lots already staged in import_history into WP posts (runs InsertLotsBS). Use when lots are staged but not appearing on the site.">Process Staging</button>
            <button type="button" class="button" id="af-btn-clear-cron-dupes" title="Remove duplicate / orphaned WP-Cron events (per-hostname bloat). Keeps one clean event per hook.">Clear Cron Dupes</button>
            <button type="button" class="button button-danger" id="af-btn-kill-all" style="margin-left:auto;">Kill All</button>
        </div>

        <div class="af-queue-stats" id="af-queue-stats">
            <div class="af-queue-stat pending"><div class="stat-num" id="af-q-pending">-</div><div class="stat-label">Pending</div></div>
            <div class="af-queue-stat processing"><div class="stat-num" id="af-q-processing">-</div><div class="stat-label">Processing</div></div>
            <div class="af-queue-stat completed"><div class="stat-num" id="af-q-completed">-</div><div class="stat-label">Completed</div></div>
            <div class="af-queue-stat failed"><div class="stat-num" id="af-q-failed">-</div><div class="stat-label">Failed</div></div>
            <div class="af-queue-stat skipped"><div class="stat-num" id="af-q-skipped">-</div><div class="stat-label">Skipped</div></div>
        </div>

        <div class="af-queue-jobs" id="af-queue-jobs">
            <table>
                <thead><tr><th>#</th><th>Type</th><th>Auction</th><th>Status</th><th>Source</th><th>Result</th><th>Created</th><th></th></tr></thead>
                <tbody id="af-queue-tbody"><tr><td colspan="8" style="text-align:center;color:#888;">Loading...</td></tr></tbody>
            </table>
        </div>
    </div>

    <!-- Sync v3: Live Console -->
    <div class="af-sync-card" id="af-console-card">
        <div class="af-sync-card-header">
            <span class="dashicons dashicons-editor-code"></span>
            <h2><?php _e('Live Console', 'auctionforge'); ?></h2>
        </div>

        <div class="af-console">
            <div class="af-console-toolbar">
                <button type="button" class="button" id="af-console-pause">Pause</button>
                <button type="button" class="button" id="af-console-clear">Clear</button>
                <label style="font-size:11px;color:#888;"><input type="checkbox" id="af-console-autoscroll" checked> Auto-scroll</label>
                <span class="af-console-status live" id="af-console-status">● Live</span>
            </div>
            <div class="af-console-output" id="af-console-output">
                <div class="af-console-empty">Waiting for sync activity...</div>
            </div>
        </div>
    </div>

    <!-- How It Works -->
    <div class="af-sync-card">
        <div class="af-sync-card-header">
            <span class="dashicons dashicons-book"></span>
            <h2><?php _e('How It Works', 'auctionforge'); ?></h2>
            <button type="button" class="button button-small af-toggle-guide" data-target="af-guide-content" style="margin-left:auto;">
                <span class="dashicons dashicons-arrow-down-alt2" style="font-size:14px;width:14px;height:14px;margin-top:3px;"></span> <span class="af-guide-btn-label">Show Guide</span>
            </button>
        </div>

        <div id="af-guide-content" style="display:none;">

            <p style="font-size:13px;color:#444;margin-bottom:20px;">
                AuctionForge v3 uses a <strong>webhook-first</strong> architecture. BidSpirit pushes data to your site in real-time via webhooks. A job queue and background worker process the data into WordPress posts. An hourly safety net catches anything missed.
            </p>

            <!-- How Sync Works -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-info-outline"></span> How Sync Works (v3)</h3>
                <div class="af-guide-body">
                    <p><strong>Real-time path (primary):</strong></p>
                    <ol style="list-style:decimal;margin-left:20px;font-size:13px;color:#444;margin-bottom:14px;">
                        <li>BidSpirit fires a webhook to <code>/wp-json/bidspirit/v1/sync</code></li>
                        <li>The plugin verifies the signature and enqueues a job in <code>af_sync_queue</code></li>
                        <li>A background worker is spawned via HTTP loopback</li>
                        <li>The worker claims the job, fetches data from BidSpirit API, stages it, then processes it into WP posts + metadata</li>
                        <li>Cache is cleared automatically</li>
                    </ol>

                    <p><strong>Webhook events from BidSpirit:</strong></p>
                    <table class="af-sync-info-table" style="margin-bottom:14px;">
                        <tr>
                            <th><code>AUCTION_CATALOG_UPDATED</code></th>
                            <td>Fires when a catalog is published or re-published. Triggers a full catalog sync for that auction.</td>
                        </tr>
                        <tr>
                            <th><code>ITEMS_UPDATED</code></th>
                            <td>Fires when individual lot details are edited/saved, or when bids are placed. Triggers a single-item update.</td>
                        </tr>
                        <tr>
                            <th><code>AUCTION_DAY_ENDED</code></th>
                            <td>Fires when an auction day concludes. Triggers a final sync to capture sale prices and auction state.</td>
                        </tr>
                    </table>

                    <p><strong>Safety net (hourly cron):</strong></p>
                    <ul style="list-style:disc;margin-left:20px;font-size:13px;color:#444;">
                        <li>Resets stuck jobs (processing &gt; 10 minutes)</li>
                        <li>Spawns workers for any pending jobs that were not picked up</li>
                        <li>Runs a 24-hour health check &mdash; if no webhook received in 24h, auto-enqueues a full sync</li>
                        <li>Purges old log and queue data</li>
                    </ul>
                </div>
            </div>

            <!-- Job Types -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-list-view"></span> Job Types</h3>
                <div class="af-guide-body">
                    <table class="af-sync-info-table">
                        <tr>
                            <th><code>catalog_sync</code></th>
                            <td>Full catalog sync for a single auction. Fetches auction list + all lots from BidSpirit API, stages to import_history, processes into WP posts + bp_meta_data. Triggered by <code>AUCTION_CATALOG_UPDATED</code> webhook or admin "Sync Selected".</td>
                        </tr>
                        <tr>
                            <th><code>item_update</code></th>
                            <td>Single lot update. Extracts item data from the webhook payload, stages it, processes into the WP post + metadata. Triggered by <code>ITEMS_UPDATED</code> webhook.</td>
                        </tr>
                        <tr>
                            <th><code>full_sync</code></th>
                            <td>Enqueues <code>catalog_sync</code> for every active auction. Triggered by admin "Full Sync" or the 24-hour health check.</td>
                        </tr>
                        <tr>
                            <th><code>day_ended</code></th>
                            <td>Post-auction sync. Updates auction state, re-syncs the catalog for final prices, and clears cache. Triggered by <code>AUCTION_DAY_ENDED</code> webhook.</td>
                        </tr>
                        <tr>
                            <th><code>archive_sync</code></th>
                            <td>Syncs past/ended auctions. Same as catalog_sync but targets ARCHIVED/ENDED auctions. Triggered by admin "Archive Sync".</td>
                        </tr>
                    </table>
                </div>
            </div>

            <!-- Queue System -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-admin-generic"></span> Queue System</h3>
                <div class="af-guide-body">
                    <p>All sync work flows through the <code>af_sync_queue</code> table:</p>
                    <ul style="list-style:disc;margin-left:20px;font-size:13px;color:#444;margin-bottom:14px;">
                        <li><strong>Priority-based FIFO</strong> &mdash; Lower priority number = higher urgency (webhooks = 1-2, admin = 3, archive = 5)</li>
                        <li><strong>Deduplication</strong> &mdash; Only one pending/processing job per auction + type at a time</li>
                        <li><strong>Atomic claiming</strong> &mdash; Workers claim jobs with <code>UPDATE ... WHERE status='pending'</code> to prevent race conditions</li>
                        <li><strong>Automatic retry</strong> &mdash; Failed jobs are retried up to 3 times by the safety cron</li>
                        <li><strong>Worker PID tracking</strong> &mdash; Each job records which worker process is handling it</li>
                    </ul>

                    <p><strong>Job lifecycle:</strong> <code>pending</code> &rarr; <code>processing</code> &rarr; <code>completed</code> / <code>failed</code> / <code>skipped</code></p>
                </div>
            </div>

            <!-- Cron Setup -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-clock"></span> Cron Setup</h3>
                <div class="af-guide-body">
                    <p>The safety net cron is the only scheduled task needed for sync. It runs hourly and catches anything webhooks missed.</p>

                    <p><strong>Option 1: External cron (recommended)</strong></p>
                    <pre class="af-guide-code"># Install via setup script
bash <?php echo esc_html(BASE_PATH); ?>/crontasks/setup.sh install

# Or add manually to crontab:
10 * * * * /www/server/cron/auctionforge-cron.sh syncSafety >> /var/log/auctionforge-cron/syncSafety_cron.log 2>&1

# Maintenance tasks (optional):
0 4 * * 0 /www/server/cron/auctionforge-cron.sh cleaner >> /var/log/auctionforge-cron/cleaner_cron.log 2>&1
30 4 * * 0 /www/server/cron/auctionforge-cron.sh cleanerTrashMeta >> /var/log/auctionforge-cron/cleanerTrashMeta_cron.log 2>&1</pre>

                    <p style="margin-top:14px;"><strong>Option 2: WP-Cron (shared hosting fallback)</strong></p>
                    <p style="font-size:13px;color:#444;">The plugin automatically registers a WP-Cron hook (<code>auctionforge_sync_safety</code>) that runs hourly. This works without any setup but only fires when someone visits the site.</p>
                    <ul style="list-style:disc;margin-left:20px;font-size:13px;color:#888;">
                        <li>No visitors = no safety check (but webhooks still work in real-time)</li>
                        <li>Adequate for most auction sites with regular traffic</li>
                        <li>If <code>DISABLE_WP_CRON</code> is true, you must use external cron</li>
                    </ul>
                </div>
            </div>

            <!-- Setup Script Commands -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-admin-tools"></span> Setup Script Commands</h3>
                <div class="af-guide-body">
                    <table class="af-sync-info-table">
                        <tr>
                            <th><code>install</code></th>
                            <td>Install everything &mdash; cron runner, directories, crontab entries.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh install</code>
                            </td>
                        </tr>
                        <tr>
                            <th><code>migrate</code></th>
                            <td>Interactive v2 &rarr; v3 migration. Removes old importData/importBidSpirit entries, installs new syncSafety entry.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh migrate</code>
                            </td>
                        </tr>
                        <tr>
                            <th><code>status</code></th>
                            <td>Health check &mdash; shows runner, PHP, sites, crontab, locks, last runs.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh status</code>
                            </td>
                        </tr>
                        <tr>
                            <th><code>test [task]</code></th>
                            <td>Run a single task once for this site only.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh test syncSafety</code>
                            </td>
                        </tr>
                        <tr>
                            <th><code>remove</code></th>
                            <td>Remove all crontab entries. Add <code>--purge</code> to also delete scripts and logs.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh remove --purge</code>
                            </td>
                        </tr>
                    </table>
                </div>
            </div>

            <!-- Configuration -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-admin-settings"></span> Configuration Constants</h3>
                <div class="af-guide-body">
                    <p>Add these to <code>wp-config.php</code> to override defaults:</p>
                    <table class="af-sync-info-table">
                        <tr><th><code>AUCTIONFORGE_API_TOKEN</code></th><td><strong>Required.</strong> BidSpirit API token for webhook signature verification.</td></tr>
                        <tr><th><code>AUCTIONFORGE_SYNC_WORKER_TIMEOUT</code></th><td>Worker process timeout in seconds. Default: <code>240</code></td></tr>
                        <tr><th><code>AUCTIONFORGE_SYNC_STALE_JOB</code></th><td>Seconds before a processing job is considered stuck. Default: <code>600</code></td></tr>
                        <tr><th><code>AUCTIONFORGE_SYNC_HEALTH_CHECK</code></th><td>Seconds without webhook before triggering full sync. Default: <code>86400</code></td></tr>
                        <tr><th><code>AUCTIONFORGE_SYNC_LOG_RETENTION</code></th><td>Days to keep log entries. Default: <code>7</code></td></tr>
                        <tr><th><code>AUCTIONFORGE_SYNC_QUEUE_RETENTION</code></th><td>Days to keep completed queue jobs. Default: <code>30</code></td></tr>
                        <tr><th><code>AUCTIONFORGE_PHP_BINARY</code></th><td>PHP CLI path for spawning workers. Default: <code>/www/server/php/83/bin/php</code></td></tr>
                    </table>
                </div>
            </div>

            <!-- Troubleshooting -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-sos"></span> Troubleshooting</h3>
                <div class="af-guide-body">
                    <table class="af-sync-info-table">
                        <tr>
                            <th>Webhooks return 401</th>
                            <td>
                                Check <code>AUCTIONFORGE_API_TOKEN</code> is defined in <code>wp-config.php</code> and matches the token in BidSpirit back office.
                            </td>
                        </tr>
                        <tr>
                            <th>Jobs stuck in "processing"</th>
                            <td>
                                The safety cron auto-resets jobs stuck &gt; 10 min. To reset manually, click "Kill All" above or run:<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh test syncSafety</code>
                            </td>
                        </tr>
                        <tr>
                            <th>Lots not updating on frontend</th>
                            <td>
                                Check the Live Console for errors. Verify that <code>InsertLotsBS</code> is processing staged data (look for "[4/5] Processed X lot(s)" in logs).
                            </td>
                        </tr>
                        <tr>
                            <th>Worker not spawning</th>
                            <td>
                                The worker uses HTTP loopback to <code>http://127.0.0.1</code> with the site's Host header. Check nginx access log for 403/404 on <code>/wp-json/auctionforge/v1/sync-worker</code>.
                            </td>
                        </tr>
                        <tr>
                            <th>Quick health check</th>
                            <td>
                                <code>bash setup.sh status</code> &mdash; shows runner, PHP, sites, crontab, locks, last runs.
                            </td>
                        </tr>
                    </table>
                </div>
            </div>

        </div>
    </div>
</div>
<!-- Task Execution Modal -->
<div class="af-modal-overlay" id="af-task-modal">
    <div class="af-modal running" id="af-modal-inner">
        <div class="af-modal-header">
            <span class="dashicons dashicons-controls-repeat" id="af-modal-icon"></span>
            <h3 id="af-modal-title">Running Task...</h3>
            <button type="button" class="af-modal-close" id="af-modal-close-btn">&times;</button>
        </div>
        <div class="af-modal-body">
            <p class="af-modal-desc" id="af-modal-desc"></p>

            <div class="af-progress-wrap">
                <svg class="af-progress-ring" width="120" height="120">
                    <circle class="ring-bg" cx="60" cy="60" r="54" />
                    <circle class="ring-fill" id="af-ring-fill" cx="60" cy="60" r="54" />
                </svg>
                <div class="af-progress-center">
                    <div class="af-progress-timer" id="af-modal-timer">0s</div>
                    <div class="af-progress-label" id="af-modal-phase">Executing</div>
                </div>
                <div class="af-progress-icon">
                    <span class="dashicons dashicons-yes-alt icon-success"></span>
                    <span class="dashicons dashicons-dismiss icon-error"></span>
                </div>
            </div>

            <div class="af-modal-estimate">
                <span id="af-modal-elapsed-label">Elapsed</span>
                <span id="af-modal-eta">Estimating...</span>
            </div>
            <div class="af-modal-progress-bar">
                <div class="bar-fill" id="af-modal-bar"></div>
            </div>

            <div class="af-modal-status" id="af-modal-status">
                <span class="dashicons dashicons-update spin"></span> Starting task...
            </div>
        </div>
        <div class="af-modal-footer" style="display:none;" id="af-modal-footer">
            <button type="button" class="button button-primary" id="af-modal-done-btn">Done</button>
        </div>
    </div>
</div>

<script>
jQuery(document).ready(function($) {
    var nonce = '<?php echo esc_js($nonce); ?>';
    var ajaxurl = '<?php echo esc_js(admin_url("admin-ajax.php")); ?>';

    // Task metadata from PHP (labels, descriptions, estimated durations from heartbeat)
    var taskMeta = <?php
        $js_tasks = array();
        foreach ($tasks as $key => $t) {
            $hb = (!empty($heartbeat['tasks'][$key])) ? $heartbeat['tasks'][$key] : null;
            $js_tasks[$key] = array(
                'label' => $t['label'],
                'desc' => $t['desc'],
                'icon' => $t['icon'],
                'schedule' => $t['schedule'],
                'estimated' => $hb ? intval($hb['duration_seconds']) : 0,
            );
        }
        echo json_encode($js_tasks);
    ?>;

    // Ring circumference (2 * PI * 54)
    var RING_CIRC = 339.292;
    var timerInterval = null;
    var startTime = 0;

    function formatTime(seconds) {
        if (seconds < 60) return seconds + 's';
        var m = Math.floor(seconds / 60);
        var s = seconds % 60;
        return m + 'm ' + (s < 10 ? '0' : '') + s + 's';
    }

    function setRingProgress(pct) {
        var offset = RING_CIRC * (1 - Math.min(pct, 1));
        $('#af-ring-fill').css('stroke-dashoffset', offset);
    }

    function openModal(taskKey) {
        var meta = taskMeta[taskKey] || {};
        var est = meta.estimated || 0;
        // Add buffer: if we know it takes 5s, estimate ~8s (1.5x + 2s)
        var estDisplay = est > 0 ? Math.ceil(est * 1.5) + 2 : 0;

        var modal = $('#af-modal-inner');
        var overlay = $('#af-task-modal');

        // Reset state
        modal.removeClass('success error done').addClass('running');
        setRingProgress(0);
        $('#af-modal-bar').css('width', '0%');
        $('#af-modal-footer').hide();

        // Populate
        $('#af-modal-title').text(meta.label || taskKey);
        $('#af-modal-desc').text(meta.desc || '');
        $('#af-modal-icon').attr('class', 'dashicons ' + (meta.icon || 'dashicons-controls-repeat'));
        $('#af-modal-timer').text('0s');
        $('#af-modal-phase').text('Executing');
        $('#af-modal-elapsed-label').text('Elapsed');
        $('#af-modal-eta').text(estDisplay > 0 ? 'Est. ~' + formatTime(estDisplay) : 'First run — no estimate');
        $('#af-modal-status').html('<span class="dashicons dashicons-update spin"></span> Connecting to sync engine...');

        overlay.addClass('active');
        startTime = Date.now();

        // Start timer
        clearInterval(timerInterval);
        timerInterval = setInterval(function() {
            var elapsed = Math.floor((Date.now() - startTime) / 1000);
            $('#af-modal-timer').text(formatTime(elapsed));

            // Progress based on estimate
            if (estDisplay > 0) {
                var pct = Math.min(elapsed / estDisplay, 0.95); // Cap at 95% until done
                setRingProgress(pct);
                $('#af-modal-bar').css('width', (pct * 100) + '%');

                if (elapsed < estDisplay) {
                    var remaining = estDisplay - elapsed;
                    $('#af-modal-eta').text('~' + formatTime(remaining) + ' remaining');
                } else {
                    $('#af-modal-eta').text('Taking longer than usual...');
                }
            } else {
                // No estimate — animate indeterminate
                var pulsePct = (Math.sin(elapsed * 0.5) + 1) / 2 * 0.6 + 0.1;
                setRingProgress(pulsePct);
                $('#af-modal-bar').css('width', (pulsePct * 100) + '%');
                $('#af-modal-eta').text('Running...');
            }

            // Update status phases
            if (elapsed >= 2 && elapsed < 5) {
                $('#af-modal-status').html('<span class="dashicons dashicons-update spin"></span> Processing ' + (meta.label || taskKey) + '...');
            } else if (elapsed >= 5 && elapsed < 15) {
                $('#af-modal-phase').text('Working');
                $('#af-modal-status').html('<span class="dashicons dashicons-update spin"></span> Syncing data with BidSpirit...');
            } else if (elapsed >= 15 && elapsed < 30) {
                $('#af-modal-status').html('<span class="dashicons dashicons-update spin"></span> Processing lot data...');
            } else if (elapsed >= 30) {
                $('#af-modal-status').html('<span class="dashicons dashicons-update spin"></span> Still working — large dataset...');
            }
        }, 500);
    }

    function closeModal() {
        clearInterval(timerInterval);
        $('#af-task-modal').removeClass('active');
    }

    function completeModal(success, message) {
        clearInterval(timerInterval);
        var elapsed = Math.floor((Date.now() - startTime) / 1000);
        var modal = $('#af-modal-inner');

        modal.removeClass('running').addClass(success ? 'success' : 'error').addClass('done');
        setRingProgress(1);
        $('#af-modal-bar').css('width', '100%');
        $('#af-modal-timer').text(formatTime(elapsed));
        $('#af-modal-phase').text(success ? 'Complete' : 'Failed');
        $('#af-modal-eta').text('Finished in ' + formatTime(elapsed));

        if (success) {
            $('#af-modal-status').html('<span class="dashicons dashicons-yes"></span> ' + (message || 'Task completed successfully'));
        } else {
            $('#af-modal-status').html('<span class="dashicons dashicons-warning"></span> ' + (message || 'Task failed'));
        }

        $('#af-modal-footer').show();
    }

    // Modal close handlers
    $('#af-modal-close-btn, #af-modal-done-btn').on('click', function() {
        closeModal();
    });
    $('#af-task-modal').on('click', function(e) {
        if (e.target === this && $('#af-modal-inner').hasClass('done')) {
            closeModal();
        }
    });

    // Toggle guide panel
    $(document).on('click', '.af-toggle-guide', function(e) {
        e.preventDefault();
        var targetId = $(this).data('target');
        var target = $('#' + targetId);
        var btn = $(this);
        if (target.length === 0) return;
        target.slideToggle(300, function() {
            if (target.is(':visible')) {
                btn.find('.dashicons').removeClass('dashicons-arrow-down-alt2').addClass('dashicons-arrow-up-alt2');
                btn.find('.af-guide-btn-label').text('Hide Guide');
            } else {
                btn.find('.dashicons').removeClass('dashicons-arrow-up-alt2').addClass('dashicons-arrow-down-alt2');
                btn.find('.af-guide-btn-label').text('Show Guide');
            }
        });
    });

    // Collapsible guide sections
    $('.af-guide-heading').on('click', function() {
        $(this).next('.af-guide-body').slideToggle(200);
    });

    // ─── Sync v3: Queue Status Polling ───
    var queuePollTimer = null;
    var queueIsActive = false;

    function pollQueueStatus() {
        $.post(ajaxurl, { action: 'auctionforge_sync_queue_status', nonce: nonce }, function(r) {
            if (!r.success) return;
            var d = r.data;
            $('#af-q-pending').text(d.counts.pending);
            $('#af-q-processing').text(d.counts.processing);
            $('#af-q-completed').text(d.counts.completed);
            $('#af-q-failed').text(d.counts.failed);
            $('#af-q-skipped').text(d.counts.skipped);

            // Worker badge + adaptive polling
            var wb = $('#af-queue-worker-badge');
            var wl = $('#af-queue-worker-label');
            var wasActive = queueIsActive;
            if (d.has_active_worker || d.counts.processing > 0) {
                wb.removeClass('unknown ok errors stale').addClass('running');
                wl.text('Worker Active');
                queueIsActive = true;
            } else if (d.counts.pending > 0) {
                wb.removeClass('unknown ok running errors').addClass('stale');
                wl.text('Worker Needed');
                queueIsActive = true;
            } else {
                wb.removeClass('running stale errors unknown').addClass('ok');
                wl.text('Idle');
                queueIsActive = false;
            }
            // Switch polling rate if activity changed
            if (wasActive !== queueIsActive) {
                scheduleQueuePoll();
                scheduleConsolePoll();
            }

            // Jobs table
            var tbody = $('#af-queue-tbody');
            if (d.jobs && d.jobs.length > 0) {
                var html = '';
                for (var i = 0; i < d.jobs.length; i++) {
                    var j = d.jobs[i];
                    var statusClass = 'status-' + j.status;
                    var result = j.result_summary || j.error_message || '';
                    if (result.length > 60) result = result.substring(0, 57) + '...';
                    var created = j.created_at ? j.created_at.substring(11, 19) : '';
                    html += '<tr>';
                    html += '<td>' + j.id + '</td>';
                    html += '<td>' + j.job_type + '</td>';
                    html += '<td>' + j.auction_id + '</td>';
                    html += '<td class="' + statusClass + '">' + j.status + '</td>';
                    html += '<td>' + j.source + '</td>';
                    html += '<td title="' + (j.result_summary || j.error_message || '').replace(/"/g, '&quot;') + '">' + result + '</td>';
                    html += '<td>' + created + '</td>';
                    if (j.status === 'pending' || j.status === 'processing') {
                        html += '<td><button class="btn-kill-job" data-job-id="' + j.id + '" title="Cancel this job">✕</button></td>';
                    } else {
                        html += '<td></td>';
                    }
                    html += '</tr>';
                }
                tbody.html(html);
            } else {
                tbody.html('<tr><td colspan="8" style="text-align:center;color:#888;">Queue empty</td></tr>');
            }
        });
    }

    // Adaptive queue polling: 2s when active worker, 6s when idle
    function scheduleQueuePoll() {
        clearInterval(queuePollTimer);
        var interval = queueIsActive ? 2000 : 6000;
        queuePollTimer = setInterval(pollQueueStatus, interval);
    }
    pollQueueStatus();
    scheduleQueuePoll();

    // Queue action buttons
    function doSyncAction(syncAction, auctionId) {
        var data = { action: 'auctionforge_sync_enqueue', nonce: nonce, sync_action: syncAction };
        if (auctionId) data.auction_id = auctionId;

        $.post(ajaxurl, data, function(r) {
            if (r.success) {
                var notice = $('<div class="af-sync-result success">' + r.data.message + '</div>');
                $('#af-sync-notices').prepend(notice);
                setTimeout(function() { notice.fadeOut(400, function() { $(this).remove(); }); }, 4000);
                pollQueueStatus(); // Refresh immediately
            } else {
                var notice = $('<div class="af-sync-result error">' + (r.data || 'Failed') + '</div>');
                $('#af-sync-notices').prepend(notice);
                setTimeout(function() { notice.fadeOut(400, function() { $(this).remove(); }); }, 6000);
            }
        });
    }

    $('#af-btn-full-sync').on('click', function() { doSyncAction('full_sync'); });
    $('#af-btn-archive-sync').on('click', function() { doSyncAction('archive_sync'); });
    $('#af-btn-spawn-worker').on('click', function() { doSyncAction('spawn_worker'); });

    // Process staging — convert already-staged lots into WP posts. Non-blocking
    // on the server; we just switch the console/queue into fast-poll so the
    // operator can watch InsertLotsBS work through the backlog.
    $('#af-btn-process-staging').on('click', function() {
        var btn = $(this);
        btn.prop('disabled', true).text('Starting...');
        $.post(ajaxurl, { action: 'auctionforge_sync_enqueue', nonce: nonce, sync_action: 'process_staging' }, function(r) {
            var msg = r.success ? r.data.message : (r.data || 'Failed');
            var cls = r.success ? 'success' : 'error';
            var notice = $('<div class="af-sync-result ' + cls + '">' + msg + '</div>');
            $('#af-sync-notices').prepend(notice);
            setTimeout(function() { notice.fadeOut(400, function() { $(this).remove(); }); }, 7000);
            queueIsActive = true; scheduleQueuePoll(); scheduleConsolePoll(); pollQueueStatus();
        }).fail(function() {
            $('#af-sync-notices').prepend($('<div class="af-sync-result error">Request failed</div>'));
        }).always(function() {
            btn.prop('disabled', false).text('Process Staging');
        });
    });

    // Clear duplicate / orphan cron events (per-hostname bloat sweep)
    $('#af-btn-clear-cron-dupes').on('click', function() {
        var btn = $(this);
        btn.prop('disabled', true).text('Clearing...');
        $.post(ajaxurl, { action: 'auctionforge_clear_cron_dupes', nonce: nonce }, function(r) {
            var msg = r.success ? r.data.message : (r.data || 'Failed');
            var cls = r.success ? 'success' : 'error';
            var notice = $('<div class="af-sync-result ' + cls + '">' + msg + '</div>');
            $('#af-sync-notices').prepend(notice);
            setTimeout(function() { notice.fadeOut(400, function() { $(this).remove(); }); }, 7000);
        }).fail(function() {
            $('#af-sync-notices').prepend($('<div class="af-sync-result error">Request failed</div>'));
        }).always(function() {
            btn.prop('disabled', false).text('Clear Cron Dupes');
        });
    });
    $('#af-btn-sync-auction').on('click', function() {
        var aid = $('#af-auction-select').val();
        if (!aid) { alert('Select an auction first'); return; }
        doSyncAction('sync_auction', aid);
    });

    // ─── Sync v3: Live Console Polling ───
    // Start from the latest log ID so the console only shows NEW entries
    var consoleSinceId = <?php echo (int) \auctionforge\inc\sync\SyncLogger::getLatestId(); ?>;
    var consolePaused = false;
    var consolePollTimer = null;

    function pollConsole() {
        if (consolePaused) return;
        $.post(ajaxurl, { action: 'auctionforge_sync_log_poll', nonce: nonce, since_id: consoleSinceId, limit: 50 }, function(r) {
            if (!r.success || !r.data.entries || r.data.entries.length === 0) return;

            var output = $('#af-console-output');
            // Remove empty placeholder
            output.find('.af-console-empty').remove();

            var entries = r.data.entries;
            for (var i = 0; i < entries.length; i++) {
                var e = entries[i];
                var ts = e.created_at ? e.created_at.substring(11, 19) : '';
                var ctx = e.context || '';
                var msg = e.message || '';
                var level = e.level || 'info';
                var jobTag = e.job_id ? ' [#' + e.job_id + ']' : '';
                var auctionTag = e.auction_id ? ' [' + e.auction_id + ']' : '';

                var line = '<div class="log-entry level-' + level + '">';
                line += '<span class="ts">' + ts + '</span> ';
                line += '<span class="ctx">[' + ctx + ']</span>';
                line += auctionTag + jobTag + ' ' + msg;
                line += '</div>';
                output.append(line);
            }

            consoleSinceId = r.data.latest_id;

            // Auto-scroll
            if ($('#af-console-autoscroll').is(':checked')) {
                output.scrollTop(output[0].scrollHeight);
            }

            // Trim old entries (keep last 200)
            var lines = output.find('.log-entry');
            if (lines.length > 200) {
                lines.slice(0, lines.length - 200).remove();
            }
        });
    }

    // Adaptive console polling: 800ms when active, 3s when idle
    function scheduleConsolePoll() {
        clearInterval(consolePollTimer);
        var interval = queueIsActive ? 800 : 3000;
        consolePollTimer = setInterval(pollConsole, interval);
    }
    pollConsole();
    scheduleConsolePoll();

    $('#af-console-pause').on('click', function() {
        consolePaused = !consolePaused;
        $(this).text(consolePaused ? 'Resume' : 'Pause');
        $('#af-console-status').text(consolePaused ? '● Paused' : '● Live')
            .removeClass('live paused').addClass(consolePaused ? 'paused' : 'live');
    });

    $('#af-console-clear').on('click', function() {
        $('#af-console-output').html('<div class="af-console-empty">Console cleared</div>');
    });

    // Kill single job
    $(document).on('click', '.btn-kill-job', function() {
        var jobId = $(this).data('job-id');
        if (!confirm('Cancel job #' + jobId + '?')) return;
        $.post(ajaxurl, { action: 'auctionforge_sync_cancel', nonce: nonce, cancel_action: 'cancel_job', job_id: jobId }, function(r) {
            var msg = r.success ? r.data.message : (r.data || 'Failed');
            var cls = r.success ? 'success' : 'error';
            var notice = $('<div class="af-sync-result ' + cls + '">' + msg + '</div>');
            $('#af-sync-notices').prepend(notice);
            setTimeout(function() { notice.fadeOut(400, function() { $(this).remove(); }); }, 4000);
            pollQueueStatus();
        });
    });

    // Kill all
    $('#af-btn-kill-all').on('click', function() {
        if (!confirm('Cancel ALL pending and processing jobs?')) return;
        $.post(ajaxurl, { action: 'auctionforge_sync_cancel', nonce: nonce, cancel_action: 'cancel_all' }, function(r) {
            var msg = r.success ? r.data.message : (r.data || 'Failed');
            var cls = r.success ? 'success' : 'error';
            var notice = $('<div class="af-sync-result ' + cls + '">' + msg + '</div>');
            $('#af-sync-notices').prepend(notice);
            setTimeout(function() { notice.fadeOut(400, function() { $(this).remove(); }); }, 4000);
            pollQueueStatus();
        });
    });

    // Clear import history
    $('#af-btn-clear-import-history').on('click', function() {
        if (!confirm('This will delete ALL rows from the import_history table. Lots will need to be re-synced from BidSpirit.\n\nContinue?')) return;
        var btn = $(this);
        btn.prop('disabled', true).text('Clearing...');
        $.post(ajaxurl, { action: 'auctionforge_clear_import_history', nonce: nonce }, function(r) {
            var msg = r.success ? r.data.message : (r.data || 'Failed');
            var cls = r.success ? 'success' : 'error';
            var notice = $('<div class="af-sync-result ' + cls + '">' + msg + '</div>');
            $('#af-sync-notices').prepend(notice);
            setTimeout(function() { notice.fadeOut(400, function() { $(this).remove(); }); }, 5000);
            btn.prop('disabled', false).html('<span class="dashicons dashicons-database-remove"></span> Clear');
        }).fail(function() {
            var notice = $('<div class="af-sync-result error">Request failed</div>');
            $('#af-sync-notices').prepend(notice);
            setTimeout(function() { notice.fadeOut(400, function() { $(this).remove(); }); }, 5000);
            btn.prop('disabled', false).html('<span class="dashicons dashicons-database-remove"></span> Clear');
        });
    });

    // Run v3 queue task (Sync Active, Sync Archived)
    $('.af-run-v3').on('click', function() {
        var btn = $(this);
        var v3Action = btn.data('v3-action');
        var taskKey = btn.closest('.af-sync-task').data('task');
        var meta = taskMeta[taskKey] || {};

        openModal(taskKey);
        $('#af-modal-status').html('<span class="dashicons dashicons-update spin"></span> Enqueuing jobs...');

        $.post(ajaxurl, { action: 'auctionforge_sync_enqueue', nonce: nonce, sync_action: v3Action }, function(r) {
            if (r.success) {
                completeModal(true, r.data.message);
                pollQueueStatus();
            } else {
                completeModal(false, r.data || 'Failed to enqueue');
            }
        }).fail(function(xhr) {
            var msg = 'Request failed';
            if (xhr.status === 0) msg = 'Connection lost';
            else if (xhr.status === 504) msg = 'Gateway timeout';
            else if (xhr.status === 500) msg = 'Server error';
            completeModal(false, msg + ' (HTTP ' + xhr.status + ')');
        });
    });

    // Run legacy task with modal (cleaner, cleanerTrashMeta)
    $('.af-run-task').on('click', function() {
        var btn = $(this);
        var task = btn.data('task');

        openModal(task);

        $.post(ajaxurl, { action: 'auctionforge_run_cron_task', nonce: nonce, task: task }, function(r) {
            if (r.success) {
                completeModal(true, r.data.message);
            } else {
                completeModal(false, r.data || 'Task failed');
            }
        }).fail(function(xhr) {
            var msg = 'Request failed';
            if (xhr.status === 0) msg = 'Connection lost';
            else if (xhr.status === 504) msg = 'Gateway timeout';
            else if (xhr.status === 500) msg = 'Server error';
            completeModal(false, msg + ' (HTTP ' + xhr.status + ')');
        });
    });
});
</script>

<?php
// Embedded diagnostic panel for the new wpdeploy sync streams.
// Registers no menu of its own — it lives at the bottom of this page.
if (class_exists('AF_Sync_Status_Page')) {
    AF_Sync_Status_Page::render_panel();
}
?>
