<!DOCTYPE html>
<html lang="en">
<head>
    <title>FastScheduler</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
    <style>
        :root {
            --bg-primary: #09090b;
            --bg-secondary: #111113;
            --bg-tertiary: #18181b;
            --bg-elevated: #1f1f23;
            --border-subtle: #27272a;
            --border-default: #3f3f46;
            --text-primary: #fafafa;
            --text-secondary: #a1a1aa;
            --text-tertiary: #71717a;
            --accent-primary: #8b5cf6;
            --accent-secondary: #a78bfa;
            --status-running: #f59e0b;
            --status-running-bg: rgba(245, 158, 11, 0.12);
            --status-scheduled: #3b82f6;
            --status-scheduled-bg: rgba(59, 130, 246, 0.12);
            --status-completed: #10b981;
            --status-completed-bg: rgba(16, 185, 129, 0.12);
            --status-failed: #ef4444;
            --status-failed-bg: rgba(239, 68, 68, 0.12);
            --status-paused: #8b5cf6;
            --status-paused-bg: rgba(139, 92, 246, 0.12);
            --font-sans: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;
            --font-mono: 'JetBrains Mono', ui-monospace, monospace;
            --radius-sm: 6px;
            --radius-md: 10px;
            --radius-lg: 14px;
            --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
            --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
            --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.6);
            --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
            --transition-normal: 250ms cubic-bezier(0.4, 0, 0.2, 1);
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: var(--font-sans);
            background: var(--bg-primary);
            color: var(--text-primary);
            min-height: 100vh;
            line-height: 1.5;
            -webkit-font-smoothing: antialiased;
        }

        /* Top Navbar */
        .navbar {
            position: sticky;
            top: 0;
            z-index: 100;
            background: var(--bg-secondary);
            border-bottom: 1px solid var(--border-subtle);
            padding: 0 32px;
            height: 64px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            backdrop-filter: blur(12px);
        }

        .navbar-left {
            display: flex;
            align-items: center;
            gap: 24px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .logo-icon {
            width: 32px;
            height: 32px;
            background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
            border-radius: var(--radius-sm);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 16px;
        }

        .logo-text {
            font-weight: 700;
            font-size: 18px;
            letter-spacing: -0.5px;
        }

        .status-badge {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 6px 12px;
            background: var(--status-completed-bg);
            border-radius: 100px;
            font-size: 13px;
            font-weight: 500;
            color: var(--status-completed);
        }

        .status-badge.stopped {
            background: var(--status-failed-bg);
            color: var(--status-failed);
        }

        .status-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: currentColor;
            animation: pulse 2s infinite;
        }

        .status-badge.stopped .status-dot {
            animation: none;
        }

        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }

        .navbar-right {
            display: flex;
            align-items: center;
            gap: 16px;
        }

        .search-box {
            position: relative;
        }

        .search-input {
            background: var(--bg-tertiary);
            border: 1px solid var(--border-subtle);
            border-radius: var(--radius-md);
            padding: 8px 14px 8px 36px;
            width: 240px;
            color: var(--text-primary);
            font-family: var(--font-sans);
            font-size: 14px;
            transition: all var(--transition-fast);
        }

        .search-input::placeholder {
            color: var(--text-tertiary);
        }

        .search-input:focus {
            outline: none;
            border-color: var(--accent-primary);
            box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
            width: 300px;
        }

        .search-icon {
            position: absolute;
            left: 12px;
            top: 50%;
            transform: translateY(-50%);
            color: var(--text-tertiary);
            width: 16px;
            height: 16px;
        }

        .update-time {
            font-size: 12px;
            color: var(--text-tertiary);
        }

        /* Main Content */
        .main {
            max-width: 1600px;
            margin: 0 auto;
            padding: 24px 32px;
        }

        /* Stats Grid */
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 16px;
            margin-bottom: 24px;
        }

        .stat-card {
            background: var(--bg-secondary);
            border: 1px solid var(--border-subtle);
            border-radius: var(--radius-lg);
            padding: 20px;
        }

        .stat-label {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 13px;
            color: var(--text-tertiary);
            margin-bottom: 8px;
        }

        .stat-value {
            font-size: 32px;
            font-weight: 700;
            letter-spacing: -1px;
            font-family: var(--font-mono);
        }

        .stat-subtext {
            font-size: 12px;
            color: var(--text-tertiary);
            margin-top: 4px;
        }

        .stat-subtext.negative {
            color: var(--status-failed);
        }

        /* Content Grid - Jobs + Timeline side by side */
        .content-grid {
            display: grid;
            grid-template-columns: 1fr 380px;
            gap: 24px;
        }

        /* Section Headers */
        .section-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 16px;
        }

        .section-title {
            display: flex;
            align-items: center;
            gap: 10px;
            font-size: 16px;
            font-weight: 600;
        }

        .section-title svg {
            width: 18px;
            height: 18px;
            color: var(--text-tertiary);
        }

        /* Filter Tabs */
        .filter-tabs {
            display: flex;
            gap: 4px;
            background: var(--bg-tertiary);
            padding: 4px;
            border-radius: var(--radius-md);
        }

        .filter-tab {
            padding: 6px 12px;
            border-radius: var(--radius-sm);
            font-size: 13px;
            font-weight: 500;
            color: var(--text-secondary);
            cursor: pointer;
            transition: all var(--transition-fast);
            border: none;
            background: transparent;
        }

        .filter-tab:hover {
            color: var(--text-primary);
        }

        .filter-tab.active {
            background: var(--bg-elevated);
            color: var(--text-primary);
            box-shadow: var(--shadow-sm);
        }

        .filter-count {
            margin-left: 4px;
            opacity: 0.6;
        }

        /* Jobs Grid */
        .jobs-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
            gap: 12px;
        }

        .job-card {
            background: var(--bg-secondary);
            border: 1px solid var(--border-subtle);
            border-radius: var(--radius-lg);
            padding: 16px;
            cursor: pointer;
            transition: all var(--transition-fast);
        }

        .job-card:hover {
            border-color: var(--border-default);
            transform: translateY(-1px);
        }

        .job-card.selected {
            border-color: var(--accent-primary);
            box-shadow: 0 0 0 1px var(--accent-primary);
        }

        .job-card.running {
            border-left: 3px solid var(--status-running);
        }

        .job-card.paused {
            border-left: 3px solid var(--status-paused);
            opacity: 0.8;
        }

        .job-card.failed {
            border-left: 3px solid var(--status-failed);
        }

        .job-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 12px;
        }

        .job-name {
            font-weight: 600;
            font-size: 15px;
            font-family: var(--font-mono);
        }

        .job-badge {
            padding: 3px 8px;
            border-radius: 100px;
            font-size: 11px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.3px;
        }

        .job-badge.scheduled {
            background: var(--status-scheduled-bg);
            color: var(--status-scheduled);
        }

        .job-badge.running {
            background: var(--status-running-bg);
            color: var(--status-running);
        }

        .job-badge.paused {
            background: var(--status-paused-bg);
            color: var(--status-paused);
        }

        .job-badge.failed {
            background: var(--status-failed-bg);
            color: var(--status-failed);
        }

        .job-badge.completed {
            background: var(--status-completed-bg);
            color: var(--status-completed);
        }

        .job-meta {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 8px;
            margin-bottom: 12px;
        }

        .job-meta-item {
            display: flex;
            flex-direction: column;
            gap: 2px;
        }

        .job-meta-label {
            font-size: 11px;
            color: var(--text-tertiary);
            text-transform: uppercase;
            letter-spacing: 0.3px;
        }

        .job-meta-value {
            font-size: 13px;
            color: var(--text-secondary);
            font-family: var(--font-mono);
        }

        .job-countdown {
            color: var(--accent-secondary);
        }

        .job-actions {
            display: flex;
            gap: 8px;
            padding-top: 12px;
            border-top: 1px solid var(--border-subtle);
        }

        .action-btn {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
            padding: 8px 12px;
            background: var(--bg-tertiary);
            border: 1px solid var(--border-subtle);
            border-radius: var(--radius-sm);
            color: var(--text-secondary);
            font-size: 12px;
            font-weight: 500;
            cursor: pointer;
            transition: all var(--transition-fast);
        }

        .action-btn:hover {
            background: var(--bg-elevated);
            color: var(--text-primary);
            border-color: var(--border-default);
        }

        .action-btn.danger:hover {
            background: var(--status-failed-bg);
            color: var(--status-failed);
            border-color: var(--status-failed);
        }

        .action-btn svg {
            width: 14px;
            height: 14px;
        }

        /* Timeline Section */
        .timeline-section {
            background: var(--bg-secondary);
            border: 1px solid var(--border-subtle);
            border-radius: var(--radius-lg);
            padding: 20px;
            height: fit-content;
            position: sticky;
            top: 88px;
        }

        .timeline-list {
            display: flex;
            flex-direction: column;
            gap: 2px;
            max-height: calc(100vh - 280px);
            overflow-y: auto;
        }

        .timeline-item {
            display: flex;
            align-items: center;
            gap: 12px;
            padding: 10px 12px;
            border-radius: var(--radius-sm);
            transition: background var(--transition-fast);
        }

        .timeline-item:hover {
            background: var(--bg-tertiary);
        }

        .timeline-icon {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 12px;
            flex-shrink: 0;
        }

        .timeline-icon.completed {
            background: var(--status-completed-bg);
            color: var(--status-completed);
        }

        .timeline-icon.failed {
            background: var(--status-failed-bg);
            color: var(--status-failed);
        }

        .timeline-icon.missed {
            background: var(--status-running-bg);
            color: var(--status-running);
        }

        .timeline-content {
            flex: 1;
            min-width: 0;
        }

        .timeline-name {
            font-size: 13px;
            font-weight: 500;
            font-family: var(--font-mono);
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .timeline-details {
            display: flex;
            gap: 8px;
            font-size: 11px;
            color: var(--text-tertiary);
        }

        .timeline-time {
            font-size: 12px;
            color: var(--text-tertiary);
            font-family: var(--font-mono);
            flex-shrink: 0;
        }

        /* Job Details Panel */
        .panel-overlay {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.6);
            opacity: 0;
            visibility: hidden;
            transition: all var(--transition-normal);
            z-index: 200;
        }

        .panel-overlay.visible {
            opacity: 1;
            visibility: visible;
        }

        .job-details-panel {
            position: fixed;
            top: 0;
            right: 0;
            width: 440px;
            height: 100vh;
            background: var(--bg-secondary);
            border-left: 1px solid var(--border-subtle);
            transform: translateX(100%);
            transition: transform var(--transition-normal);
            z-index: 201;
            display: flex;
            flex-direction: column;
        }

        .job-details-panel.open {
            transform: translateX(0);
        }

        .panel-header {
            padding: 20px;
            border-bottom: 1px solid var(--border-subtle);
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .panel-title {
            font-size: 18px;
            font-weight: 600;
            font-family: var(--font-mono);
        }

        .panel-close {
            width: 32px;
            height: 32px;
            border-radius: var(--radius-sm);
            border: none;
            background: transparent;
            color: var(--text-secondary);
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all var(--transition-fast);
        }

        .panel-close:hover {
            background: var(--bg-tertiary);
            color: var(--text-primary);
        }

        .panel-content {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
        }

        .detail-section {
            margin-bottom: 24px;
        }

        .detail-section h3 {
            font-size: 12px;
            font-weight: 600;
            color: var(--text-tertiary);
            text-transform: uppercase;
            letter-spacing: 0.5px;
            margin-bottom: 12px;
        }

        .detail-grid {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 12px;
        }

        .detail-item {
            background: var(--bg-tertiary);
            padding: 12px;
            border-radius: var(--radius-sm);
        }

        .detail-label {
            font-size: 11px;
            color: var(--text-tertiary);
            margin-bottom: 4px;
        }

        .detail-value {
            font-size: 14px;
            font-family: var(--font-mono);
        }

        .history-list {
            display: flex;
            flex-direction: column;
            gap: 8px;
            max-height: 300px;
            overflow-y: auto;
        }

        .history-item {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 10px;
            background: var(--bg-tertiary);
            border-radius: var(--radius-sm);
            font-size: 13px;
        }

        .history-status {
            width: 8px;
            height: 8px;
            border-radius: 50%;
        }

        .history-status.completed {
            background: var(--status-completed);
        }

        .history-status.failed {
            background: var(--status-failed);
        }

        /* Toast Notifications */
        .toast-container {
            position: fixed;
            bottom: 24px;
            right: 24px;
            z-index: 300;
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .toast {
            display: flex;
            align-items: center;
            gap: 12px;
            padding: 14px 16px;
            background: var(--bg-elevated);
            border: 1px solid var(--border-subtle);
            border-radius: var(--radius-md);
            box-shadow: var(--shadow-lg);
            animation: slideIn 0.3s ease;
            max-width: 360px;
        }

        @keyframes slideIn {
            from {
                transform: translateX(100%);
                opacity: 0;
            }
            to {
                transform: translateX(0);
                opacity: 1;
            }
        }

        .toast-icon {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 12px;
            flex-shrink: 0;
        }

        .toast.success .toast-icon {
            background: var(--status-completed-bg);
            color: var(--status-completed);
        }

        .toast.error .toast-icon {
            background: var(--status-failed-bg);
            color: var(--status-failed);
        }

        .toast-content {
            flex: 1;
        }

        .toast-title {
            font-weight: 600;
            font-size: 13px;
            margin-bottom: 2px;
        }

        .toast-message {
            font-size: 12px;
            color: var(--text-secondary);
        }

        /* Connection Badge */
        .connection-badge {
            display: flex;
            align-items: center;
            gap: 6px;
            padding: 4px 10px;
            border-radius: 100px;
            font-size: 11px;
            font-weight: 500;
        }

        .connection-badge.connected {
            background: var(--status-completed-bg);
            color: var(--status-completed);
        }

        .connection-badge.disconnected {
            background: var(--status-failed-bg);
            color: var(--status-failed);
        }

        .connection-dot {
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: currentColor;
        }

        /* Empty State */
        .empty-state {
            text-align: center;
            padding: 48px 24px;
            color: var(--text-tertiary);
        }

        .empty-state svg {
            width: 48px;
            height: 48px;
            margin-bottom: 16px;
            opacity: 0.5;
        }

        .empty-state h3 {
            font-size: 16px;
            font-weight: 600;
            color: var(--text-secondary);
            margin-bottom: 8px;
        }

        /* Responsive */
        @media (max-width: 1200px) {
            .content-grid {
                grid-template-columns: 1fr;
            }

            .timeline-section {
                position: static;
            }

            .timeline-list {
                max-height: 400px;
            }
        }

        @media (max-width: 768px) {
            .navbar {
                padding: 0 16px;
            }

            .main {
                padding: 16px;
            }

            .stats-grid {
                grid-template-columns: repeat(2, 1fr);
            }

            .jobs-grid {
                grid-template-columns: 1fr;
            }

            .search-input {
                width: 180px;
            }

            .search-input:focus {
                width: 220px;
            }

            .job-details-panel {
                width: 100%;
            }

            .filter-tabs {
                overflow-x: auto;
            }
        }

        /* Scrollbar */
        ::-webkit-scrollbar {
            width: 6px;
            height: 6px;
        }

        ::-webkit-scrollbar-track {
            background: transparent;
        }

        ::-webkit-scrollbar-thumb {
            background: var(--border-default);
            border-radius: 3px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: var(--text-tertiary);
        }
    </style>
</head>
<body>
    <!-- Top Navbar -->
    <nav class="navbar">
        <div class="navbar-left">
            <div class="logo">
                <div class="logo-icon">⚡</div>
                <span class="logo-text">FastScheduler</span>
            </div>
            <div id="scheduler-status" class="status-badge">
                <span class="status-dot"></span>
                <span>Running</span>
            </div>
        </div>
        <div class="navbar-right">
            <div class="search-box">
                <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <circle cx="11" cy="11" r="8"/>
                    <path d="m21 21-4.3-4.3"/>
                </svg>
                <input type="text" class="search-input" placeholder="Search jobs..." id="search-input">
            </div>
            <span class="update-time" id="last-update">--</span>
            <div id="connection-badge" class="connection-badge disconnected">
                <span class="connection-dot"></span>
                <span id="connection-text">Connecting...</span>
            </div>
        </div>
    </nav>

    <!-- Main Content -->
    <main class="main">
        <!-- Stats Grid -->
        <div class="stats-grid">
            <div class="stat-card">
                <div class="stat-label">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <path d="M12 8v4l3 3"/>
                        <circle cx="12" cy="12" r="9"/>
                    </svg>
                    Active Jobs
                </div>
                <div class="stat-value" id="stat-jobs">0</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
                    </svg>
                    Total Runs
                </div>
                <div class="stat-value" id="stat-runs">0</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <circle cx="12" cy="12" r="10"/>
                        <line x1="12" y1="8" x2="12" y2="12"/>
                        <line x1="12" y1="16" x2="12.01" y2="16"/>
                    </svg>
                    Failures
                </div>
                <div class="stat-value" id="stat-failures">0</div>
                <div class="stat-subtext" id="stat-success-rate">--</div>
            </div>
            <div class="stat-card">
                <div class="stat-label">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <circle cx="12" cy="12" r="10"/>
                        <polyline points="12 6 12 12 16 14"/>
                    </svg>
                    Uptime
                </div>
                <div class="stat-value" id="stat-uptime">--</div>
            </div>
        </div>

        <!-- Content Grid -->
        <div class="content-grid">
            <!-- Jobs Section -->
            <section class="jobs-section">
                <div class="section-header">
                    <div class="section-title">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
                            <line x1="16" y1="2" x2="16" y2="6"/>
                            <line x1="8" y1="2" x2="8" y2="6"/>
                            <line x1="3" y1="10" x2="21" y2="10"/>
                        </svg>
                        Scheduled Jobs
                    </div>
                    <div class="filter-tabs">
                        <button class="filter-tab active" data-filter="all">All <span class="filter-count" id="count-all">0</span></button>
                        <button class="filter-tab" data-filter="running">Running <span class="filter-count" id="count-running">0</span></button>
                        <button class="filter-tab" data-filter="scheduled">Scheduled <span class="filter-count" id="count-scheduled">0</span></button>
                        <button class="filter-tab" data-filter="paused">Paused <span class="filter-count" id="count-paused">0</span></button>
                        <button class="filter-tab" data-filter="failed">Failed <span class="filter-count" id="count-failed">0</span></button>
                    </div>
                </div>
                <div class="jobs-grid" id="jobs-grid">
                    <!-- Job cards rendered here -->
                </div>
            </section>

            <!-- Timeline Section -->
            <section class="timeline-section">
                <div class="section-header">
                    <div class="section-title">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
                        </svg>
                        Recent Activity
                    </div>
                </div>
                <div class="timeline-list" id="timeline-list">
                    <!-- Timeline items rendered here -->
                </div>
            </section>
        </div>
    </main>

    <!-- Job Details Panel -->
    <div class="panel-overlay" id="panel-overlay"></div>
    <aside class="job-details-panel" id="job-details-panel">
        <div class="panel-header">
            <span class="panel-title" id="panel-job-name">Job Name</span>
            <button class="panel-close" onclick="closeJobPanel()">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <line x1="18" y1="6" x2="6" y2="18"/>
                    <line x1="6" y1="6" x2="18" y2="18"/>
                </svg>
            </button>
        </div>
        <div class="panel-content">
            <div class="detail-section">
                <h3>Status</h3>
                <div id="panel-status-badge"></div>
            </div>
            <div class="detail-section">
                <h3>Details</h3>
                <div class="detail-grid" id="panel-details">
                    <!-- Details rendered here -->
                </div>
            </div>
            <div class="detail-section">
                <h3>Recent Executions</h3>
                <div class="history-list" id="panel-history">
                    <!-- History items -->
                </div>
            </div>
            <div class="detail-section">
                <h3>Actions</h3>
                <div style="display: flex; gap: 8px; flex-wrap: wrap;" id="panel-actions">
                    <!-- Action buttons -->
                </div>
            </div>
        </div>
    </aside>

    <!-- Toast Container -->
    <div class="toast-container" id="toast-container"></div>

    <script>
        // State
        let eventSource = null;
        let currentData = { jobs: [], history: [], stats: {}, running: false };
        let selectedJobId = null;
        let currentFilter = 'all';
        let searchQuery = '';
        let previousHistory = [];

        // SSE prefix for API calls
        const API_PREFIX = '{{prefix}}';

        // Initialize
        document.addEventListener('DOMContentLoaded', () => {
            setupEventListeners();
            connectSSE();
        });

        function setupEventListeners() {
            // Filter tabs
            document.querySelectorAll('.filter-tab').forEach(tab => {
                tab.addEventListener('click', () => {
                    document.querySelectorAll('.filter-tab').forEach(t => t.classList.remove('active'));
                    tab.classList.add('active');
                    currentFilter = tab.dataset.filter;
                    renderJobs();
                });
            });

            // Search input
            document.getElementById('search-input').addEventListener('input', (e) => {
                searchQuery = e.target.value.toLowerCase();
                renderJobs();
            });

            // Panel overlay
            document.getElementById('panel-overlay').addEventListener('click', closeJobPanel);

            // Keyboard shortcuts
            document.addEventListener('keydown', (e) => {
                if (e.key === 'Escape') closeJobPanel();
            });
        }

        // SSE Connection
        function connectSSE() {
            if (eventSource) {
                eventSource.close();
            }

            eventSource = new EventSource(`${API_PREFIX}/events`);

            eventSource.onopen = () => {
                updateConnectionStatus(true);
            };

            eventSource.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data);
                    updateDashboard(data);
                } catch (e) {
                    console.error('Failed to parse SSE data:', e);
                }
            };

            eventSource.onerror = () => {
                eventSource.close();
                updateConnectionStatus(false);
                setTimeout(connectSSE, 2000);
            };
        }

        function updateConnectionStatus(connected) {
            const badge = document.getElementById('connection-badge');
            const text = document.getElementById('connection-text');
            
            if (connected) {
                badge.classList.remove('disconnected');
                badge.classList.add('connected');
                text.textContent = 'Live';
            } else {
                badge.classList.remove('connected');
                badge.classList.add('disconnected');
                text.textContent = 'Reconnecting...';
            }
        }

        // Dashboard Update
        function updateDashboard(data) {
            // Check for new completions/failures to show toasts
            if (previousHistory.length > 0) {
                const newEvents = data.history.filter(h => 
                    !previousHistory.find(ph => ph.timestamp === h.timestamp && ph.job_id === h.job_id)
                );
                
                newEvents.forEach(event => {
                    if (event.status === 'completed') {
                        showToast('success', 'Job Completed', `${event.func_name} completed successfully`);
                    } else if (event.status === 'failed') {
                        showToast('error', 'Job Failed', `${event.func_name} failed: ${event.error || 'Unknown error'}`);
                    }
                });
            }
            previousHistory = [...data.history];

            currentData = data;

            // Update scheduler status
            const statusBadge = document.getElementById('scheduler-status');
            if (data.running) {
                statusBadge.classList.remove('stopped');
                statusBadge.innerHTML = '<span class="status-dot"></span><span>Running</span>';
            } else {
                statusBadge.classList.add('stopped');
                statusBadge.innerHTML = '<span class="status-dot"></span><span>Stopped</span>';
            }

            // Update stats
            document.getElementById('stat-jobs').textContent = data.jobs.length;
            document.getElementById('stat-runs').textContent = formatNumber(data.stats.total_runs || 0);
            document.getElementById('stat-failures').textContent = formatNumber(data.stats.total_failures || 0);
            document.getElementById('stat-uptime').textContent = data.stats.uptime_readable || '--';

            // Calculate success rate
            const totalRuns = data.stats.total_runs || 0;
            const failures = data.stats.total_failures || 0;
            if (totalRuns > 0) {
                const rate = ((totalRuns - failures) / totalRuns * 100).toFixed(1);
                document.getElementById('stat-success-rate').textContent = `${rate}% success rate`;
                document.getElementById('stat-success-rate').classList.toggle('negative', parseFloat(rate) < 95);
            }

            // Update filter counts
            updateFilterCounts(data.jobs);

            // Update last update time
            document.getElementById('last-update').textContent = new Date().toLocaleTimeString();

            // Render jobs and timeline
            renderJobs();
            renderTimeline();

            // Update panel if open
            if (selectedJobId) {
                const job = data.jobs.find(j => j.job_id === selectedJobId);
                if (job) {
                    renderJobPanel(job);
                }
            }
        }

        function updateFilterCounts(jobs) {
            document.getElementById('count-all').textContent = jobs.length;
            document.getElementById('count-running').textContent = jobs.filter(j => j.status === 'running').length;
            document.getElementById('count-scheduled').textContent = jobs.filter(j => j.status === 'scheduled').length;
            document.getElementById('count-paused').textContent = jobs.filter(j => j.status === 'paused').length;
            document.getElementById('count-failed').textContent = jobs.filter(j => j.status === 'failed').length;
        }

        // Render Jobs
        function renderJobs() {
            const container = document.getElementById('jobs-grid');
            let jobs = currentData.jobs;

            // Apply filter
            if (currentFilter !== 'all') {
                jobs = jobs.filter(j => j.status === currentFilter);
            }

            // Apply search
            if (searchQuery) {
                jobs = jobs.filter(j => 
                    j.func_name.toLowerCase().includes(searchQuery) ||
                    j.schedule.toLowerCase().includes(searchQuery)
                );
            }

            if (jobs.length === 0) {
                container.innerHTML = `
                    <div class="empty-state">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                            <rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
                            <line x1="16" y1="2" x2="16" y2="6"/>
                            <line x1="8" y1="2" x2="8" y2="6"/>
                            <line x1="3" y1="10" x2="21" y2="10"/>
                        </svg>
                        <h3>No jobs found</h3>
                        <p>${searchQuery ? 'Try a different search term' : 'No scheduled jobs'}</p>
                    </div>
                `;
                return;
            }

            container.innerHTML = jobs.map(job => {
                const statusClass = job.status;
                const isSelected = job.job_id === selectedJobId;
                
                return `
                    <div class="job-card ${statusClass} ${isSelected ? 'selected' : ''}" 
                         onclick="openJobPanel('${job.job_id}')"
                         data-job-id="${job.job_id}">
                        <div class="job-header">
                            <span class="job-name">${escapeHtml(job.func_name)}</span>
                            <span class="job-badge ${statusClass}">${job.status}</span>
                        </div>
                        <div class="job-meta">
                            <div class="job-meta-item">
                                <span class="job-meta-label">Schedule</span>
                                <span class="job-meta-value">${escapeHtml(job.schedule)}</span>
                            </div>
                            <div class="job-meta-item">
                                <span class="job-meta-label">Next Run</span>
                                <span class="job-meta-value job-countdown">${formatCountdown(job.next_run_in)}</span>
                            </div>
                            <div class="job-meta-item">
                                <span class="job-meta-label">Executions</span>
                                <span class="job-meta-value">${job.run_count}</span>
                            </div>
                        </div>
                        <div class="job-actions">
                            ${job.status === 'paused' 
                                ? `<button class="action-btn" onclick="event.stopPropagation(); resumeJob('${job.job_id}')">
                                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                        <polygon points="5 3 19 12 5 21 5 3"/>
                                    </svg>
                                    Resume
                                   </button>`
                                : `<button class="action-btn" onclick="event.stopPropagation(); pauseJob('${job.job_id}')">
                                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                        <rect x="6" y="4" width="4" height="16"/>
                                        <rect x="14" y="4" width="4" height="16"/>
                                    </svg>
                                    Pause
                                   </button>`
                            }
                            <button class="action-btn danger" onclick="event.stopPropagation(); cancelJob('${job.job_id}')">
                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                    <line x1="18" y1="6" x2="6" y2="18"/>
                                    <line x1="6" y1="6" x2="18" y2="18"/>
                                </svg>
                                Cancel
                            </button>
                        </div>
                    </div>
                `;
            }).join('');
        }

        // Render Timeline
        function renderTimeline() {
            const container = document.getElementById('timeline-list');
            // Filter to only show completed, failed, and missed events (not running/scheduled)
            const history = [...currentData.history]
                .reverse()
                .filter(event => ['completed', 'failed', 'missed'].includes(event.status))
                .slice(0, 25);

            if (history.length === 0) {
                container.innerHTML = `
                    <div class="empty-state" style="padding: 24px">
                        <p>No activity yet</p>
                    </div>
                `;
                return;
            }

            const statusIcons = {
                completed: '✓',
                failed: '✗',
                missed: '!'
            };

            container.innerHTML = history.map(event => `
                <div class="timeline-item">
                    <div class="timeline-icon ${event.status}">
                        ${statusIcons[event.status] || '-'}
                    </div>
                    <div class="timeline-content">
                        <div class="timeline-name">${escapeHtml(event.func_name)}</div>
                        <div class="timeline-details">
                            <span>${event.status}</span>
                            ${event.execution_time ? `<span>${event.execution_time.toFixed(2)}s</span>` : ''}
                        </div>
                    </div>
                    <div class="timeline-time">${formatTime(event.timestamp_readable)}</div>
                </div>
            `).join('');
        }

        // Job Panel
        function openJobPanel(jobId) {
            selectedJobId = jobId;
            const job = currentData.jobs.find(j => j.job_id === jobId);
            if (!job) return;

            renderJobPanel(job);

            document.getElementById('job-details-panel').classList.add('open');
            document.getElementById('panel-overlay').classList.add('visible');
            
            // Update selected state in job cards
            document.querySelectorAll('.job-card').forEach(card => {
                card.classList.toggle('selected', card.dataset.jobId === jobId);
            });
        }

        function renderJobPanel(job) {
            document.getElementById('panel-job-name').textContent = job.func_name;
            
            document.getElementById('panel-status-badge').innerHTML = `
                <span class="job-badge ${job.status}" style="font-size: 13px; padding: 6px 12px;">
                    ${job.status.toUpperCase()}
                </span>
            `;

            document.getElementById('panel-details').innerHTML = `
                <div class="detail-item">
                    <div class="detail-label">Job ID</div>
                    <div class="detail-value">${job.job_id}</div>
                </div>
                <div class="detail-item">
                    <div class="detail-label">Schedule</div>
                    <div class="detail-value">${escapeHtml(job.schedule)}</div>
                </div>
                <div class="detail-item">
                    <div class="detail-label">Next Run</div>
                    <div class="detail-value">${job.next_run}</div>
                </div>
                <div class="detail-item">
                    <div class="detail-label">Run Count</div>
                    <div class="detail-value">${job.run_count}</div>
                </div>
                <div class="detail-item">
                    <div class="detail-label">Last Run</div>
                    <div class="detail-value">${job.last_run || 'Never'}</div>
                </div>
                <div class="detail-item">
                    <div class="detail-label">Retries</div>
                    <div class="detail-value">${job.retry_count}</div>
                </div>
            `;

            // Render job-specific history
            const jobHistory = currentData.history
                .filter(h => h.func_name === job.func_name && ['completed', 'failed'].includes(h.status))
                .slice(-10)
                .reverse();

            document.getElementById('panel-history').innerHTML = jobHistory.length > 0 
                ? jobHistory.map(h => `
                    <div class="history-item">
                        <div class="history-status ${h.status}"></div>
                        <span style="flex: 1">${h.status}</span>
                        ${h.execution_time ? `<span style="color: var(--text-tertiary)">${h.execution_time.toFixed(2)}s</span>` : ''}
                        <span style="color: var(--text-tertiary)">${formatTime(h.timestamp_readable)}</span>
                    </div>
                `).join('')
                : '<div style="color: var(--text-tertiary); font-size: 13px;">No history yet</div>';

            document.getElementById('panel-actions').innerHTML = `
                ${job.status === 'paused' 
                    ? `<button class="action-btn" onclick="resumeJob('${job.job_id}')" style="flex: 1;">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polygon points="5 3 19 12 5 21 5 3"/>
                        </svg>
                        Resume Job
                       </button>`
                    : `<button class="action-btn" onclick="pauseJob('${job.job_id}')" style="flex: 1;">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <rect x="6" y="4" width="4" height="16"/>
                            <rect x="14" y="4" width="4" height="16"/>
                        </svg>
                        Pause Job
                       </button>`
                }
                <button class="action-btn danger" onclick="cancelJob('${job.job_id}')" style="flex: 1;">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <line x1="18" y1="6" x2="6" y2="18"/>
                        <line x1="6" y1="6" x2="18" y2="18"/>
                    </svg>
                    Cancel Job
                </button>
            `;
        }

        function closeJobPanel() {
            selectedJobId = null;
            document.getElementById('job-details-panel').classList.remove('open');
            document.getElementById('panel-overlay').classList.remove('visible');
            
            document.querySelectorAll('.job-card').forEach(card => {
                card.classList.remove('selected');
            });
        }

        // API Actions
        async function pauseJob(jobId) {
            try {
                const response = await fetch(`${API_PREFIX}/api/jobs/${jobId}/pause`, { method: 'POST' });
                const data = await response.json();
                if (data.success) {
                    showToast('success', 'Job Paused', `${jobId} has been paused`);
                } else {
                    showToast('error', 'Error', data.error || 'Failed to pause job');
                }
            } catch (e) {
                showToast('error', 'Error', 'Failed to pause job');
            }
        }

        async function resumeJob(jobId) {
            try {
                const response = await fetch(`${API_PREFIX}/api/jobs/${jobId}/resume`, { method: 'POST' });
                const data = await response.json();
                if (data.success) {
                    showToast('success', 'Job Resumed', `${jobId} has been resumed`);
                } else {
                    showToast('error', 'Error', data.error || 'Failed to resume job');
                }
            } catch (e) {
                showToast('error', 'Error', 'Failed to resume job');
            }
        }

        async function cancelJob(jobId) {
            if (!confirm('Are you sure you want to cancel this job?')) return;
            
            try {
                const response = await fetch(`${API_PREFIX}/api/jobs/${jobId}/cancel`, { method: 'POST' });
                const data = await response.json();
                if (data.success) {
                    showToast('success', 'Job Cancelled', `${jobId} has been removed`);
                    closeJobPanel();
                } else {
                    showToast('error', 'Error', data.error || 'Failed to cancel job');
                }
            } catch (e) {
                showToast('error', 'Error', 'Failed to cancel job');
            }
        }

        // Toast Notifications
        function showToast(type, title, message) {
            const container = document.getElementById('toast-container');
            const toast = document.createElement('div');
            toast.className = `toast ${type}`;
            toast.innerHTML = `
                <div class="toast-icon">${type === 'success' ? '✓' : '✗'}</div>
                <div class="toast-content">
                    <div class="toast-title">${title}</div>
                    <div class="toast-message">${message}</div>
                </div>
            `;
            container.appendChild(toast);

            setTimeout(() => {
                toast.style.opacity = '0';
                toast.style.transform = 'translateX(100%)';
                setTimeout(() => toast.remove(), 300);
            }, 4000);
        }

        // Utility Functions
        function formatNumber(num) {
            return num.toLocaleString();
        }

        function formatCountdown(seconds) {
            if (seconds <= 0) return 'Now';
            if (seconds < 60) return `${Math.round(seconds)}s`;
            if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
            if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
            return `${Math.floor(seconds / 86400)}d`;
        }

        function formatTime(timeStr) {
            if (!timeStr) return '';
            const parts = timeStr.split(' ');
            return parts[1] || timeStr;
        }

        function escapeHtml(text) {
            if (!text) return '';
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }

        // Cleanup on page unload
        window.addEventListener('beforeunload', () => {
            if (eventSource) {
                eventSource.close();
            }
        });
    </script>
</body>
</html>
