<?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
$tasks = array(
    'importData' => array(
        'label' => 'Process Import Queue',
        'desc' => 'Converts raw BidSpirit import data into WordPress lot posts.',
        'schedule' => 'Every minute',
        'icon' => 'dashicons-database-import',
        'wp_hook' => 'data_import',
        'wp_schedule' => 'bp_minutely',
        'stale_after' => 300,       // 5 min — should run every minute
    ),
    'importBidSpirit' => array(
        'label' => 'Sync Upcoming Auctions',
        'desc' => 'Fetches upcoming auctions and lots from the BidSpirit API.',
        'schedule' => 'Hourly',
        'icon' => 'dashicons-download',
        'wp_hook' => 'bidspirit_import',
        'wp_schedule' => 'bp_hourly',
        'stale_after' => 7200,      // 2 hours
    ),
    'lotBidSpiritOld' => array(
        'label' => 'Sync Archived Auctions',
        'desc' => 'Updates sale prices and status for past/ended auctions.',
        'schedule' => 'Daily (3am)',
        'icon' => 'dashicons-backup',
        'wp_hook' => 'bidspirit_archive_import',
        'wp_schedule' => 'bp_24_hourly',
        'stale_after' => 90000,     // 25 hours
    ),
    'cleaner' => array(
        'label' => 'Clean Draft/Trash Lots',
        'desc' => 'Permanently deletes lot posts in draft or trash status.',
        'schedule' => 'Weekly (Sunday 4am)',
        'icon' => 'dashicons-trash',
        'wp_hook' => '',
        'wp_schedule' => '',
        'stale_after' => 691200,    // 8 days
    ),
    'cleanerTrashMeta' => array(
        'label' => 'Clean Orphaned Meta',
        'desc' => 'Removes orphaned meta fields left by deleted lots.',
        'schedule' => 'Weekly (Sunday 4:30am)',
        'icon' => 'dashicons-editor-removeformatting',
        'wp_hook' => '',
        'wp_schedule' => '',
        'stale_after' => 691200,    // 8 days
    ),
);

// 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; }
</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):
                $ts = af_get_task_status($heartbeat, $task_key, $task['stale_after']);
                $wp_next = !empty($task['wp_hook']) ? af_get_wp_cron_next($task['wp_hook']) : null;

                $badge_icon = 'minus';
                if ($ts['status'] === 'ok') $badge_icon = 'yes';
                elseif ($ts['status'] === 'errors') $badge_icon = 'warning';
                elseif ($ts['status'] === 'stale') $badge_icon = 'clock';
            ?>
            <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 ($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 elseif ($wp_cron_enabled && $wp_next): ?>
                            <span><strong>Next WP-Cron:</strong> <?php echo esc_html(date('Y-m-d H:i:s', $wp_next)); ?></span>
                        <?php endif; ?>
                        <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>
                    </div>
                </div>
                <div class="task-actions">
                    <button type="button" class="button af-run-task" data-task="<?php echo esc_attr($task_key); ?>">
                        <span class="dashicons dashicons-controls-play"></span> Run Now
                    </button>
                </div>
            </div>
            <?php endforeach; ?>
        </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 syncs auction data from BidSpirit using scheduled background tasks.
                A single setup script handles everything — or you can configure individual tasks manually.
            </p>

            <!-- Quick Start -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-controls-play"></span> Quick Start — One Command Setup</h3>
                <div class="af-guide-body">
                    <p>SSH into your server and run the setup script that ships with the plugin. It installs the cron runner, creates directories, discovers all sites, and configures the crontab — all in one go.</p>

                    <pre class="af-guide-code"># Full install — everything set up automatically
bash <?php echo esc_html(BASE_PATH); ?>/crontasks/setup.sh install</pre>

                    <p style="margin-top:10px;">That's it. The script will:</p>
                    <ul style="list-style:disc;margin-left:20px;font-size:13px;color:#444;">
                        <li>Install the cron runner to <code>/www/server/cron/auctionforge-cron.sh</code></li>
                        <li>Create log and lock directories</li>
                        <li>Auto-discover all WordPress sites running AuctionForge</li>
                        <li>Add all 5 crontab entries with the correct schedules</li>
                        <li>Verify PHP and show a status summary</li>
                    </ul>

                    <p style="margin-top:14px;"><strong>After install, you can also run it from:</strong></p>
                    <pre class="af-guide-code">/www/server/cron/auctionforge-cron-setup.sh status
/www/server/cron/auctionforge-cron-setup.sh test importData</pre>
                </div>
            </div>

            <!-- The Sync Pipeline -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-info-outline"></span> The Sync Pipeline — What Each Task Does</h3>
                <div class="af-guide-body">
                    <table class="af-sync-info-table" style="margin-bottom:12px;">
                        <tr>
                            <th>importBidSpirit</th>
                            <td>Connects to the BidSpirit API and fetches the auction list plus lot data for upcoming auctions. Stores raw data in an import queue table. <strong>Runs hourly.</strong></td>
                        </tr>
                        <tr>
                            <th>importData</th>
                            <td>Reads the import queue and converts raw lot data into WordPress posts — creates/updates lots with metadata, images, and taxonomy terms. <strong>Runs every minute.</strong></td>
                        </tr>
                        <tr>
                            <th>lotBidSpiritOld</th>
                            <td>Syncs archived/past auctions — updates final sale prices, sold/unsold status, and lot details for ended auctions. <strong>Runs daily at 3am.</strong></td>
                        </tr>
                        <tr>
                            <th>cleaner</th>
                            <td>Permanently deletes lot posts in draft or trash status (100 at a time). <strong>Runs weekly, Sunday 4am.</strong></td>
                        </tr>
                        <tr>
                            <th>cleanerTrashMeta</th>
                            <td>Removes orphaned meta fields left behind by deleted lots. <strong>Runs weekly, Sunday 4:30am.</strong></td>
                        </tr>
                    </table>
                    <p style="font-size:12px;color:#888;">Tasks work as a pipeline: <em>importBidSpirit</em> fetches raw data → <em>importData</em> processes it into WordPress → the rest are maintenance tasks.</p>
                </div>
            </div>

            <!-- Setup Script Commands -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-admin-tools"></span> Setup Script — All Commands</h3>
                <div class="af-guide-body">
                    <table class="af-sync-info-table">
                        <tr>
                            <th><code>install</code></th>
                            <td>
                                Install everything — cron runner, directories, all 5 crontab entries.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh install</code>
                            </td>
                        </tr>
                        <tr>
                            <th><code>install --only</code></th>
                            <td>
                                Install specific tasks only (comma-separated). Useful for adding tasks individually.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh install --only importData,importBidSpirit</code>
                            </td>
                        </tr>
                        <tr>
                            <th><code>status</code></th>
                            <td>
                                Full health check — shows installed runner, PHP version, discovered sites, crontab entries, active locks, and last run results.<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. Defaults to <code>importData</code>.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh test importBidSpirit</code>
                            </td>
                        </tr>
                        <tr>
                            <th><code>remove</code></th>
                            <td>
                                Remove all crontab entries but keep logs and scripts.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh remove</code>
                            </td>
                        </tr>
                        <tr>
                            <th><code>remove --purge</code></th>
                            <td>
                                Remove everything — crontab entries, runner script, logs, lock files.<br>
                                <code style="display:block;margin-top:6px;">bash setup.sh remove --purge</code>
                            </td>
                        </tr>
                    </table>
                </div>
            </div>

            <!-- Manual Cron Runner Usage -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-clock"></span> Cron Runner — Manual / Advanced Usage</h3>
                <div class="af-guide-body">
                    <p>The cron runner is the script that actually executes tasks. It auto-discovers all sites with AuctionForge and runs the specified task for each, with timeouts, locking, and logging.</p>

                    <pre class="af-guide-code"># Run a task for ALL sites
/www/server/cron/auctionforge-cron.sh importBidSpirit

# Run for a single site only
/www/server/cron/auctionforge-cron.sh importData --site <?php echo esc_html($_SERVER['SERVER_NAME'] ?? 'yoursite.com'); ?>

# Override the default timeout (seconds)
/www/server/cron/auctionforge-cron.sh importData --timeout 30</pre>

                    <p style="margin-top:12px;"><strong>If you prefer to set up crontab manually:</strong></p>
                    <pre class="af-guide-code"># Process import queue (every minute, 55s timeout per site)
*/1 * * * * /www/server/cron/auctionforge-cron.sh importData >> /var/log/auctionforge-cron/importData_cron.log 2>&1

# Sync upcoming auctions from BidSpirit (hourly at :05)
5 * * * * /www/server/cron/auctionforge-cron.sh importBidSpirit >> /var/log/auctionforge-cron/importBidSpirit_cron.log 2>&1

# Sync archived/past auctions (daily at 3am)
0 3 * * * /www/server/cron/auctionforge-cron.sh lotBidSpiritOld >> /var/log/auctionforge-cron/lotBidSpiritOld_cron.log 2>&1

# Clean draft/trash lots (weekly Sunday 4am)
0 4 * * 0 /www/server/cron/auctionforge-cron.sh cleaner >> /var/log/auctionforge-cron/cleaner_cron.log 2>&1

# Clean orphaned meta (weekly Sunday 4:30am)
30 4 * * 0 /www/server/cron/auctionforge-cron.sh cleanerTrashMeta >> /var/log/auctionforge-cron/cleanerTrashMeta_cron.log 2>&1</pre>

                    <p style="margin-top:12px;"><strong>File Locations:</strong></p>
                    <table class="af-sync-info-table">
                        <tr><th>Setup Script</th><td><code><?php echo esc_html(BASE_PATH); ?>/crontasks/setup.sh</code></td></tr>
                        <tr><th>Cron Runner</th><td><code>/www/server/cron/auctionforge-cron.sh</code></td></tr>
                        <tr><th>Log Files</th><td><code>/var/log/auctionforge-cron/{task}_{date}.log</code></td></tr>
                        <tr><th>Lock Files</th><td><code>/tmp/auctionforge-cron-locks/{task}_{site}.lock</code></td></tr>
                        <tr><th>PHP Binary</th><td><code>/www/server/php/83/bin/php</code></td></tr>
                    </table>
                </div>
            </div>

            <!-- WP-Cron Fallback -->
            <div class="af-guide-section">
                <h3 class="af-guide-heading"><span class="dashicons dashicons-wordpress"></span> WP-Cron — Shared Hosting Fallback</h3>
                <div class="af-guide-body">
                    <p>If you don't have SSH/server access, you can use WordPress's built-in cron system instead. This is less reliable but works on shared hosting.</p>

                    <p><strong>How to enable:</strong></p>
                    <ol style="list-style:decimal;margin-left:20px;font-size:13px;color:#444;">
                        <li>Go to <strong>Lots &rarr; Settings</strong></li>
                        <li>Find the <strong>"Use wp-cron"</strong> checkbox</li>
                        <li>Check it and save</li>
                    </ol>

                    <p style="margin-top:10px;"><strong>Limitations:</strong></p>
                    <ul style="list-style:disc;margin-left:20px;font-size:13px;color:#888;">
                        <li>Only fires when someone visits the site — no visitors = no sync</li>
                        <li>No hard timeouts — relies on PHP max_execution_time</li>
                        <li>No cross-site coordination or centralized logging</li>
                        <li>Heavier — every cron fires inside a full WordPress bootstrap</li>
                        <li>Cleanup tasks (cleaner, cleanerTrashMeta) are <em>not</em> available via WP-Cron</li>
                    </ul>

                    <p style="margin-top:10px;font-size:13px;"><strong>WP-Cron schedules 3 hooks:</strong> <code>bidspirit_import</code> (hourly), <code>data_import</code> (every minute), <code>bidspirit_archive_import</code> (daily).</p>
                </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>Quick health check</th>
                            <td>
                                <code>bash setup.sh status</code> — shows everything: runner, PHP, sites, crontab, locks, last runs.
                            </td>
                        </tr>
                        <tr>
                            <th>Task stuck / never completes</th>
                            <td>
                                Check for stuck lock files: <code>ls -la /tmp/auctionforge-cron-locks/</code><br>
                                Clear locks: <code>rm -f /tmp/auctionforge-cron-locks/*_{site}.*</code><br>
                                Test manually: <code>bash setup.sh test importData</code>
                            </td>
                        </tr>
                        <tr>
                            <th>Plugin-level lock stuck</th>
                            <td>
                                PHP tasks have their own internal locks as WordPress options:<br>
                                <code>wp option update _auctionforge_sync_start 0</code><br>
                                <code>wp option update _auctionforge_sync_data_start 0</code><br>
                                <code>wp option update _auctionforge_sync_archive_start 0</code>
                            </td>
                        </tr>
                        <tr>
                            <th>Cron not firing at all</th>
                            <td>
                                <code>crontab -l | grep auctionforge</code> — verify entries exist<br>
                                <code>systemctl status cron</code> — check cron daemon<br>
                                Re-install: <code>bash setup.sh install</code>
                            </td>
                        </tr>
                        <tr>
                            <th>Check today's errors</th>
                            <td>
                                <code>grep -E "TIMEOUT|FAIL|KILLED" /var/log/auctionforge-cron/*_$(date +%Y-%m-%d).log</code>
                            </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);
    });

    // Run task with modal
    $('.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 — check if the server is responding';
            else if (xhr.status === 504) msg = 'Gateway timeout — task may still be running on the server';
            else if (xhr.status === 500) msg = 'Server error — check PHP error logs';
            completeModal(false, msg + ' (HTTP ' + xhr.status + ')');
        });
    });
});
</script>
