<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Queues - Oxana</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uplot@1.6.32/dist/uPlot.min.css">
    <script defer src="https://cdn.jsdelivr.net/npm/uplot@1.6.32/dist/uPlot.iife.min.js"></script>
    <style>
        .uplot {
            color: #d1d5db;
            background: transparent;
        }
        .chart-tooltip {
            position: absolute;
            z-index: 10;
            min-width: 9rem;
            pointer-events: none;
            border: 1px solid #374151;
            border-radius: 0.375rem;
            background: rgba(17, 24, 39, 0.96);
            color: #f9fafb;
            padding: 0.5rem 0.625rem;
            font-size: 0.75rem;
            line-height: 1.25rem;
            white-space: nowrap;
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.35);
            transform: translate(-50%, -100%);
        }
        .chart-tooltip.hidden {
            display: none;
        }
        .chart-tooltip-label {
            color: #9ca3af;
        }
    </style>
</head>
<body class="bg-gray-950 text-gray-100 min-h-screen">
    <div class="max-w-[88rem] mx-auto px-4 sm:px-6 lg:px-8 py-8">
        <div class="mb-6">
            <h1 class="text-2xl font-bold tracking-tight">Oxana Dashboard</h1>
        </div>

        {% include "_tabs.html" %}

        {% include "_stats_cards.html" %}

        <section class="mb-10">
            <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4">
                <h2 class="text-lg font-semibold">Queue Lengths</h2>
                <form method="GET" action="{{ base_path }}/queues" class="flex items-center gap-2 text-sm">
                    <input type="hidden" name="sort" value="{{ sort }}">
                    <input type="hidden" name="dir" value="{{ dir }}">
                    <label for="minutes" class="text-gray-400">Window</label>
                    <select id="minutes" name="minutes" class="bg-gray-900 border border-gray-800 rounded px-3 py-1.5 text-gray-200" onchange="this.form.submit()">
                        <option value="60" {% if queue_lengths.minutes == 60 %}selected{% endif %}>60 minutes</option>
                        <option value="120" {% if queue_lengths.minutes == 120 %}selected{% endif %}>2 hours</option>
                        <option value="240" {% if queue_lengths.minutes == 240 %}selected{% endif %}>4 hours</option>
                        <option value="480" {% if queue_lengths.minutes == 480 %}selected{% endif %}>8 hours</option>
                    </select>
                </form>
            </div>
            <div class="relative bg-gray-900 border border-gray-800 rounded-lg p-4">
                <div id="queue-length-chart" class="w-full h-[300px]"></div>
            </div>
        </section>

        <h2 class="text-lg font-semibold mb-4">Queues ({{ stats.queues.len() }})</h2>

        {% if stats.queues.is_empty() %}
        <div class="bg-gray-900 border border-gray-800 rounded-lg p-6 text-center text-gray-500">
            No queues
        </div>
        {% else %}
        <div class="overflow-x-auto">
            <table class="w-full text-sm">
                <thead>
                    <tr class="border-b border-gray-800 text-left text-xs text-gray-400 uppercase tracking-wide">
                        <th class="pb-3 pr-4"><a href="{{ self.sort_href("key") }}" class="hover:text-gray-200">Queue{{ self.sort_arrow("key") }}</a></th>
                        <th class="pb-3 pr-4 text-right"><a href="{{ self.sort_href("concurrency") }}" class="hover:text-gray-200">Concurrency{{ self.sort_arrow("concurrency") }}</a></th>
                        <th class="pb-3 pr-4 text-right"><a href="{{ self.sort_href("state") }}" class="hover:text-gray-200">State{{ self.sort_arrow("state") }}</a></th>
                        <th class="pb-3 pr-4 text-right"><a href="{{ self.sort_href("enqueued") }}" class="hover:text-gray-200">Enqueued{{ self.sort_arrow("enqueued") }}</a></th>
                        <th class="pb-3 pr-4 text-right"><a href="{{ self.sort_href("rate") }}" class="hover:text-gray-200">Rate{{ self.sort_arrow("rate") }}</a></th>
                        <th class="pb-3 pr-4 text-right"><a href="{{ self.sort_href("eta") }}" class="hover:text-gray-200">ETA{{ self.sort_arrow("eta") }}</a></th>
                        <th class="pb-3 pr-4 text-right"><a href="{{ self.sort_href("processed") }}" class="hover:text-gray-200">Processed{{ self.sort_arrow("processed") }}</a></th>
                        <th class="pb-3 pr-4 text-right"><a href="{{ self.sort_href("succeeded") }}" class="hover:text-gray-200">Succeeded{{ self.sort_arrow("succeeded") }}</a></th>
                        <th class="pb-3 pr-4 text-right"><a href="{{ self.sort_href("failed") }}" class="hover:text-gray-200">Failed{{ self.sort_arrow("failed") }}</a></th>
                        <th class="pb-3 pr-4 text-right"><a href="{{ self.sort_href("panicked") }}" class="hover:text-gray-200">Panicked{{ self.sort_arrow("panicked") }}</a></th>
                        <th class="pb-3 text-right"><a href="{{ self.sort_href("latency") }}" class="hover:text-gray-200">Latency{{ self.sort_arrow("latency") }}</a></th>
                    </tr>
                </thead>
                <tbody>
                    {% for queue in &stats.queues %}
                    <tr class="border-b border-gray-800/50 hover:bg-gray-900/50">
                        <td class="py-3 pr-4 font-mono text-sm">{% if queue.queues.is_empty() %}<a href="{{ base_path }}/queues/{{ queue.key|urlencode }}" class="text-blue-400 hover:text-blue-300 hover:underline">{{ queue.key }}</a>{% else %}{{ queue.key }}{% endif %}</td>
                        <td class="py-3 pr-4 text-right text-gray-300">{{ self.concurrency_for(&queue.key) }}{% if self.has_concurrency_override_for(&queue.key) %} <span class="text-gray-500">(<span class="line-through">{{ self.default_concurrency_for(&queue.key) }}</span>)</span>{% endif %}</td>
                        <td class="py-3 pr-4 text-right {{ self.state_class_for(&queue.key) }}">{{ self.state_for(&queue.key) }}</td>
                        <td class="py-3 pr-4 text-right text-blue-400">{{ queue.enqueued }}</td>
                        <td class="py-3 pr-4 text-right text-cyan-400">{{ queue.rate.processed_per_minute|format_rate_per_minute }}</td>
                        <td class="py-3 pr-4 text-right text-yellow-400">{{ queue.rate.eta_s|format_eta_s }}</td>
                        <td class="py-3 pr-4 text-right text-green-400">{{ queue.processed }}</td>
                        <td class="py-3 pr-4 text-right text-green-300">{{ queue.succeeded }}</td>
                        <td class="py-3 pr-4 text-right text-red-400">{{ queue.failed }}</td>
                        <td class="py-3 pr-4 text-right text-red-300">{{ queue.panicked }}</td>
                        <td class="py-3 text-right text-purple-400">{{ queue.latency_s|format_latency }}</td>
                    </tr>
                    {% for dq in &queue.queues %}
                    {% let dq_key = self.dynamic_queue_key(&queue.key, &dq.suffix) %}
                    <tr class="border-b border-gray-800/50 hover:bg-gray-900/50 text-gray-500">
                        <td class="py-2 pr-4 pl-10 font-mono text-xs">&#8627; <a href="{{ base_path }}/queues/{{ queue.key }}%23{{ dq.suffix|urlencode }}" class="text-blue-400 hover:text-blue-300 hover:underline">{{ dq.suffix }}</a></td>
                        <td class="py-2 pr-4 text-right text-xs text-gray-400">{{ self.concurrency_for(&dq_key) }}{% if self.has_concurrency_override_for(&dq_key) %} <span class="text-gray-600">(<span class="line-through">{{ self.default_concurrency_for(&dq_key) }}</span>)</span>{% endif %}</td>
                        <td class="py-2 pr-4 text-right text-xs {{ self.state_class_for(&dq_key) }}">{{ self.state_for(&dq_key) }}</td>
                        <td class="py-2 pr-4 text-right text-xs">{{ dq.enqueued }}</td>
                        <td class="py-2 pr-4 text-right text-xs text-cyan-400">{{ dq.rate.processed_per_minute|format_rate_per_minute }}</td>
                        <td class="py-2 pr-4 text-right text-xs text-yellow-400">{{ dq.rate.eta_s|format_eta_s }}</td>
                        <td class="py-2 pr-4 text-right text-xs">{{ dq.processed }}</td>
                        <td class="py-2 pr-4 text-right text-xs">{{ dq.succeeded }}</td>
                        <td class="py-2 pr-4 text-right text-xs">{{ dq.failed }}</td>
                        <td class="py-2 pr-4 text-right text-xs">{{ dq.panicked }}</td>
                        <td class="py-2 text-right text-xs">{{ dq.latency_s|format_latency }}</td>
                    </tr>
                    {% endfor %}
                    {% endfor %}
                </tbody>
            </table>
        </div>
        {% endif %}
    </div>
    <script>
        window.addEventListener('DOMContentLoaded', function () {
            if (typeof uPlot === 'undefined') {
                return;
            }

            function formatTime(ts) {
                return new Date(ts * 1000).toLocaleString(undefined, {
                    month: 'numeric',
                    day: 'numeric',
                    hour: 'numeric',
                    minute: '2-digit'
                });
            }

            function formatCount(value) {
                return Math.round(value).toLocaleString();
            }

            var palette = [
                '#60a5fa',
                '#4ade80',
                '#f472b6',
                '#facc15',
                '#a78bfa',
                '#2dd4bf',
                '#fb923c',
                '#94a3b8'
            ];

            function seriesColor(index) {
                return palette[index % palette.length];
            }

            function sortedSeriesRows(payload, idx) {
                return payload.series.map(function (series, seriesIdx) {
                    return {
                        series: series,
                        seriesIdx: seriesIdx,
                        value: series.data[idx] || 0
                    };
                }).sort(function (a, b) {
                    return (b.value - a.value) || (a.seriesIdx - b.seriesIdx);
                });
            }

            function buildTooltip(chartEl, payload, formatter, options) {
                options = options || {};
                var tooltip = document.createElement('div');
                tooltip.className = 'chart-tooltip hidden';
                chartEl.appendChild(tooltip);

                chartEl.addEventListener('mouseleave', function () {
                    tooltip.classList.add('hidden');
                });

                return function (u) {
                    var idx = u.cursor.idx;
                    if (idx == null || idx < 0 || idx >= payload.timestamps.length) {
                        tooltip.classList.add('hidden');
                        return;
                    }

                    tooltip.replaceChildren();

                    var time = document.createElement('div');
                    var timeLabel = document.createElement('span');
                    timeLabel.className = 'chart-tooltip-label';
                    timeLabel.textContent = 'Time: ';
                    time.appendChild(timeLabel);
                    time.append(formatTime(payload.timestamps[idx]));
                    tooltip.appendChild(time);

                    sortedSeriesRows(payload, idx).filter(function (entry) {
                        return options.includeZeroValues !== false || entry.value !== 0;
                    }).forEach(function (entry) {
                        var row = document.createElement('div');
                        var label = document.createElement('span');
                        label.className = 'chart-tooltip-label';
                        label.textContent = entry.series.label + ': ';
                        label.style.color = seriesColor(entry.seriesIdx);
                        row.appendChild(label);
                        row.append(formatter(entry.value));
                        tooltip.appendChild(row);
                    });

                    tooltip.classList.remove('hidden');

                    var x = u.over.offsetLeft + u.cursor.left;
                    var y = u.over.offsetTop + u.cursor.top;
                    var halfWidth = tooltip.offsetWidth / 2;
                    var left = Math.min(Math.max(x, halfWidth + 8), chartEl.clientWidth - halfWidth - 8);
                    var top = Math.max(y - 12, tooltip.offsetHeight + 8);

                    tooltip.style.left = left + 'px';
                    tooltip.style.top = top + 'px';
                };
            }

            function renderChart(chartId, payload, valueFormatter, options) {
                var chartEl = document.getElementById(chartId);
                if (!chartEl) {
                    return;
                }

                var data = [payload.timestamps].concat(payload.series.map(function (series) {
                    return series.data;
                }));

                new uPlot({
                    width: Math.max(chartEl.clientWidth, 320),
                    height: 300,
                    legend: { show: false },
                    tzDate: function (ts) { return uPlot.tzDate(new Date(ts * 1e3), 'Etc/UTC'); },
                    scales: {
                        x: { time: true },
                        y: { range: [0, null] }
                    },
                    axes: [
                        {
                            stroke: '#9ca3af',
                            grid: { stroke: 'rgba(31, 41, 55, 0.7)' },
                            ticks: { stroke: '#374151' }
                        },
                        {
                            size: 72,
                            stroke: '#9ca3af',
                            grid: { stroke: 'rgba(31, 41, 55, 0.7)' },
                            ticks: { stroke: '#374151' },
                            values: function (_u, vals) {
                                return vals.map(valueFormatter);
                            }
                        }
                    ],
                    series: [
                        {}
                    ].concat(payload.series.map(function (series, index) {
                        return {
                            label: series.label,
                            stroke: seriesColor(index),
                            width: 2
                        };
                    })),
                    hooks: {
                        setCursor: [buildTooltip(chartEl, payload, valueFormatter, options)]
                    }
                }, data, chartEl);
            }

            renderChart(
                'queue-length-chart',
                {{ self.queue_length_chart_data_json()|safe }},
                formatCount,
                { includeZeroValues: false }
            );
        });
    </script>
</body>
</html>
