Auto Refresh Interface for Hotsauce

Auto refresh for HotSOS

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Auto Refresh Interface for Hotsauce
// @namespace    http://tampermonkey.net/
// @version      1.0.5
// @description  Auto refresh for HotSOS
// @author       PC
// @match        https://na4.m-tech.com/*
// @run-at       document-idle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Simplified CSS
    GM_addStyle(`
        #auto-refresh-container {
            position: fixed;
            top: 20px;
            left: 20px;
            background-color: #f5f5f5;
            border: 1px solid #ddd;
            border-radius: 6px;
            padding: 8px 10px;
            z-index: 9999;
            font-family: Arial, sans-serif;
            min-width: 150px;
            max-width: 200px;
            user-select: none;
            font-size: 12px;
        }
        #auto-refresh-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
            cursor: move;
            padding-bottom: 6px;
            border-bottom: 1px solid #eee;
        }
        #auto-refresh-title {
            font-weight: bold;
            color: #444;
        }
        #auto-refresh-controls {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        #auto-refresh-collapse {
            cursor: pointer;
            font-size: 14px;
            width: 16px;
            height: 16px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
        }
        #auto-refresh-body {
            overflow: hidden;
            transition: max-height 0.3s ease;
        }
        #auto-refresh-presets {
            padding-bottom: 8px;
            margin-bottom: 8px;
            border-bottom: 1px solid #eee;
        }
        .auto-refresh-preset {
            display: inline-block;
            background-color: #e9e9e9;
            border: none;
            border-radius: 4px;
            padding: 4px 8px;
            margin: 3px;
            cursor: pointer;
            font-size: 11px;
        }
        .auto-refresh-preset:hover {
            background-color: #d9d9d9;
        }
        .auto-refresh-preset.active {
            background-color: #4a89dc;
            color: white;
        }
        .auto-refresh-disabled .auto-refresh-preset {
            opacity: 0.5;
            cursor: default;
        }
        #auto-refresh-status {
            margin-top: 6px;
            font-size: 11px;
            color: #666;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            padding-top: 2px;
        }
        #auto-refresh-status.warning { color: #f44336; }
        #auto-refresh-status.info { color: #9e9e9e; }
        #auto-refresh-status.success { color: #4caf50; }
        #auto-refresh-edit-overlay {
            position: fixed;
            top: 0; left: 0; right: 0; bottom: 0;
            background-color: rgba(0,0,0,0.5);
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        #auto-refresh-edit-panel {
            background-color: white;
            border-radius: 6px;
            padding: 16px;
            width: 250px;
        }
        .auto-refresh-form-label {
            display: block;
            margin-bottom: 4px;
            font-weight: bold;
            font-size: 12px;
        }
        .auto-refresh-form-input {
            width: 100%;
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 12px;
        }
        .auto-refresh-form-buttons {
            display: flex;
            justify-content: flex-end;
            margin-top: 10px;
        }
        .auto-refresh-form-button {
            padding: 6px 12px;
            border: none;
            border-radius: 4px;
            margin-left: 8px;
            cursor: pointer;
            font-size: 12px;
        }
        .auto-refresh-form-button.cancel { background-color: #f5f5f5; }
        .auto-refresh-form-button.save {
            background-color: #4a89dc;
            color: white;
        }
        .switch {
            position: relative;
            display: inline-block;
            width: 32px;
            height: 16px;
        }
        .switch input { opacity: 0; width: 0; height: 0; }
        .slider {
            position: absolute;
            cursor: pointer;
            top: 0; left: 0; right: 0; bottom: 0;
            background-color: #ccc;
            transition: .3s;
            border-radius: 16px;
        }
        .slider:before {
            position: absolute;
            content: "";
            height: 12px;
            width: 12px;
            left: 2px;
            bottom: 2px;
            background-color: white;
            transition: .3s;
            border-radius: 50%;
        }
        input:checked + .slider {
            background-color: #4a89dc;
        }
        input:checked + .slider:before {
            transform: translateX(16px);
        }
        /* Feature toggle style */
        .feature-toggle {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
            padding-bottom: 8px;
            border-bottom: 1px solid #eee;
        }
        .feature-label {
            font-size: 11px;
            color: #444;
        }
        /* User Tip styles */
        #auto-refresh-tip {
            font-size: 10px;
            color: #666;
            margin-top: 4px;
            margin-bottom: 8px;
            line-height: 1.2;
            border-radius: 3px;
            overflow: hidden;
        }
        #auto-refresh-tip-header {
            display: flex;
            background-color: #e0e0e0;
            padding: 4px 6px;
            font-weight: bold;
            border-left: 2px solid #4a89dc;
            cursor: pointer;
        }
        #auto-refresh-tip-content {
            padding: 0;
            background-color: #f0f0f0;
            font-style: italic;
            border-left: 2px solid #4a89dc;
            max-height: 0;
            transition: all 0.3s ease;
            overflow: hidden;
            opacity: 0;
        }
        #auto-refresh-tip-content.expanded {
            padding: 6px;
            max-height: 50px;
            opacity: 1;
        }
    `);

    // Default presets
    const DEFAULT_PRESETS = [
        { name: '15s', seconds: 15 },
        { name: '30s', seconds: 30 },
        { name: '1min', seconds: 60 },
        { name: '5min', seconds: 300 }
    ];

    // Time-based schedule with direct second values
    const TIME_SCHEDULE = [
        { start: [8, 0], end: [11, 0], seconds: 15, name: '15s' },
        { start: [11, 0], end: [13, 0], seconds: 30, name: '30s' },
        { start: [13, 0], end: [17, 0], seconds: 60, name: '1min' },
        { start: [17, 0], end: [20, 0], seconds: 30, name: '30s' },
        { start: [20, 0], end: [1, 0], seconds: 60, name: '1min' },
        { start: [1, 0], end: [8, 0], seconds: 300, name: '5min' }
    ];

    // Predefined time options for dropdown
    const TIME_OPTIONS = [15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 120, 180, 240, 300, 360, 420, 480, 540];

    // Simplified state management
    let state = {
        enabled: true,
        collapsed: false,
        activePreset: null,
        userSelectedPreset: null, // Track user's manually selected preset
        presets: GM_getValue('autoRefreshPresets', DEFAULT_PRESETS),
        position: GM_getValue('autoRefreshPosition', { x: 20, y: 20 }),
        refreshTimer: null,
        lastRefreshTime: null,
        editingPreset: null,
        onServiceOrdersPage: false,
        initialized: false,
        observerDebounce: false,
        refreshButtonObserved: false,
        isDragging: false,
        tipExpanded: false,
        pendingRefresh: false, // Flag to prevent duplicate refreshes

        // Feature flags
        useTimeBasedPresets: GM_getValue('autoRefreshUseTimeBasedPresets', true),
        jitterEnabled: true, // Always enabled but hidden from user

        // Time-based preset tracking
        currentTimePreset: null,
        timeCheckInterval: null,
        lastPresetChangeTime: null // Track when preset was last changed
    };

    // Check if we're on the service orders page
    function checkIfOnServiceOrdersPage() {
        try {
            // Check if URL matches the service orders page pattern
            const isServiceOrdersURL = window.location.href.includes('/service-optimization/operations/service-orders');
            const refreshButton = findRefreshButton();

            // Only consider it a service orders page if both conditions are met
            state.onServiceOrdersPage = isServiceOrdersURL && !!refreshButton;

            // Update UI visibility based on page type
            const container = document.getElementById('auto-refresh-container');
            if (container) {
                container.style.display = state.onServiceOrdersPage ? 'block' : 'none';
            }

            if (state.onServiceOrdersPage && !state.refreshButtonObserved && refreshButton) {
                observeRefreshButton(refreshButton);
            }

            return state.onServiceOrdersPage;
        } catch (error) {
            console.error('Error checking page type:', error);
            return false;
        }
    }

    // Observe the refresh button for manual clicks
    function observeRefreshButton(button) {
        if (!button || state.refreshButtonObserved) return;

        button.addEventListener('click', function() {
            if (state.enabled) {
                // Update last refresh time
                state.lastRefreshTime = new Date();

                // Clear any pending refresh
                clearAutoRefresh();

                // Don't immediately refresh again, just schedule next refresh
                scheduleNextRefresh(state.activePreset);

                // Update status with improved format
                updateStatusWithRefreshInfo();
            }
        });

        state.refreshButtonObserved = true;
    }

    // Find refresh button - optimized selector search
    function findRefreshButton() {
        try {
            // Try specific selectors first for better performance
            const selectors = [
                'button[soe-data-cy="refresh"]',
                'button[mat-icon-button] soe-icon[icon="refresh-dot"]',
                'button[mat-icon-button] soe-icon[icon="refresh"]',
                'button[aria-label="refresh"]'
            ];

            for (const selector of selectors) {
                const button = document.querySelector(selector);
                if (button) return button;
            }

            // Fallback to broader search
            const buttons = document.querySelectorAll('button');
            for (const button of buttons) {
                if (button.innerHTML.toLowerCase().includes('refresh') ||
                    button.innerHTML.toLowerCase().includes('refresh-dot')) {
                    return button;
                }
            }
            return null;
        } catch (error) {
            console.error('Error finding refresh button:', error);
            return null;
        }
    }

    // Format time as HH:MM:SS
    function formatTime(date) {
        if (!date) return 'Never';
        return date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', second:'2-digit'});
    }

    // Update status message with improved format
    function updateStatusWithRefreshInfo() {
        if (!state.enabled) {
            updateStatus('info', 'Auto refresh is off');
            return;
        }

        if (!state.activePreset) {
            updateStatus('info', 'Select a refresh interval');
            return;
        }

        const lastTime = state.lastRefreshTime ? `${formatTime(state.lastRefreshTime)}` : '';
        updateStatus('success', `Last checked : ${lastTime}`);
    }

    // Update status message
    function updateStatus(type, message) {
        const statusEl = document.getElementById('auto-refresh-status');
        if (statusEl) {
            statusEl.className = type || '';
            statusEl.textContent = message || '';
        }
    }

    // Get jittered interval based on base seconds
    function getJitteredInterval(baseSeconds) {
        // Calculate jitter based on the base time
        if (baseSeconds <= 15) {
            // For minimum time (15s), only add positive jitter (up to +25%)
            return baseSeconds + (baseSeconds * 0.25 * Math.random());
        } else if (baseSeconds >= 540) { // 9 minutes in seconds
            // For maximum time (9min), only subtract jitter (up to -25%)
            return baseSeconds - (baseSeconds * 0.25 * Math.random());
        } else {
            // For all other values, add random jitter between -25% and +25%
            return baseSeconds * (1 + (Math.random() * 0.5 - 0.25));
        }
    }

    // Get the appropriate time-based interval in seconds
    function getTimeBasedInterval() {
        const now = new Date();
        const currentHour = now.getHours();
        const currentMinute = now.getMinutes();

        // Convert current time to decimal hours for easier comparison
        const currentTimeDecimal = currentHour + (currentMinute / 60);

        // Find the matching time range
        for (const timeSlot of TIME_SCHEDULE) {
            const [startHour, startMinute] = timeSlot.start;
            const [endHour, endMinute] = timeSlot.end;

            // Convert to decimal hours
            let startTimeDecimal = startHour + (startMinute / 60);
            let endTimeDecimal = endHour + (endMinute / 60);

            // Handle overnight ranges (e.g., 20:00 - 1:00)
            if (endTimeDecimal < startTimeDecimal) {
                if (currentTimeDecimal >= startTimeDecimal || currentTimeDecimal < endTimeDecimal) {
                    return timeSlot;
                }
            } else {
                if (currentTimeDecimal >= startTimeDecimal && currentTimeDecimal < endTimeDecimal) {
                    return timeSlot;
                }
            }
        }

        // Default to 30s if no match found (shouldn't happen with a complete schedule)
        return { seconds: 30, name: '30s' };
    }

    // Get time-based preset
    function getTimeBasedPreset() {
        const timeSlot = getTimeBasedInterval();

        // Look for a matching preset first
        const matchingPreset = state.presets.find(p => p.seconds === timeSlot.seconds);

        if (matchingPreset) {
            return matchingPreset;
        } else {
            // Return a virtual preset if no matching preset exists
            return {
                name: timeSlot.name,
                seconds: timeSlot.seconds,
                isVirtual: true  // Mark as virtual preset
            };
        }
    }

    // Start time check interval
    function startTimeCheck() {
        if (state.timeCheckInterval) {
            clearInterval(state.timeCheckInterval);
        }

        // Immediately update current time preset
        updateCurrentTimePreset();

        // Check periodically for time-based interval changes
        state.timeCheckInterval = setInterval(() => {
            // Only check and apply if time-based presets are enabled
            if (state.useTimeBasedPresets) {
                const previousTimePreset = state.currentTimePreset;
                updateCurrentTimePreset();

                // Apply the new preset if we've changed time slots
                if (previousTimePreset && state.currentTimePreset &&
                    previousTimePreset.seconds !== state.currentTimePreset.seconds) {
                    applyCurrentTimePreset();
                }
            }
        }, 300000); // Check every 5 minutes
    }

    // Update the current time-based preset
    function updateCurrentTimePreset() {
        state.currentTimePreset = getTimeBasedPreset();
    }

    // Apply the current time-based preset
    function applyCurrentTimePreset() {
        if (!state.currentTimePreset || !state.onServiceOrdersPage) {
            return;
        }

        // Record when preset was changed
        state.lastPresetChangeTime = new Date();

        // Completely clear any existing refresh
        clearAutoRefresh();

        // Set the active preset to the current time preset
        state.activePreset = state.currentTimePreset;

        // Start auto refresh with this preset if enabled
        if (state.enabled) {
            // Don't trigger immediate refresh if we recently refreshed
            const shouldTriggerNow = !state.lastRefreshTime ||
                (new Date() - state.lastRefreshTime > 10000); // 10 seconds threshold

            startAutoRefresh(state.activePreset, !shouldTriggerNow);
        } else {
            // Just update the UI to highlight the correct preset
            updateActivePreset();
        }
    }

    // Trigger refresh click
    function triggerRefresh() {
        // Prevent double refresh by checking the pendingRefresh flag
        if (state.pendingRefresh) {
            return false;
        }

        state.pendingRefresh = true;

        const refreshButton = findRefreshButton();
        if (refreshButton) {
            refreshButton.click();
            state.lastRefreshTime = new Date();

            // Update status with improved format
            updateStatusWithRefreshInfo();

            // Reset pending flag after a short delay
            setTimeout(() => {
                state.pendingRefresh = false;
            }, 1000);

            return true;
        } else {
            updateStatus('warning', 'Refresh button not found');
            state.pendingRefresh = false;
            return false;
        }
    }

    // Schedule next refresh - separated from startAutoRefresh for cleaner code
    function scheduleNextRefresh(preset) {
        if (!preset || !state.enabled || !state.onServiceOrdersPage) {
            return;
        }

        // Clear any existing timer first
        clearAutoRefresh();

        // Calculate jittered interval
        const jitteredSeconds = getJitteredInterval(preset.seconds);
        const intervalMs = Math.round(jitteredSeconds * 1000);

        // Set timer for next refresh
        state.refreshTimer = setTimeout(() => {
            if (state.enabled && state.onServiceOrdersPage) {
                triggerRefresh();
                // Schedule the next refresh
                scheduleNextRefresh(preset);
            }
        }, intervalMs);
    }

    // Start auto refresh with given preset
    function startAutoRefresh(preset, skipInitialRefresh = false) {
        // Don't start if disabled
        if (!state.enabled) return;

        // Clear existing timer first to prevent multiple refreshes
        clearAutoRefresh();

        // Set active preset
        state.activePreset = preset;

        // Update UI to reflect the current active preset
        updateActivePreset();

        // Only start timer if enabled and on service orders page
        if (state.enabled && state.onServiceOrdersPage) {
            // Trigger a refresh immediately when setting a new preset, unless skipInitialRefresh is true
            if (!skipInitialRefresh) {
                triggerRefresh();
            }

            // Schedule the next refresh
            scheduleNextRefresh(preset);
        }

        // Save state
        saveState();
    }

    // Clear auto refresh timer
    function clearAutoRefresh() {
        if (state.refreshTimer) {
            clearTimeout(state.refreshTimer);
            state.refreshTimer = null;
        }
    }

    // Update active preset highlighting
    function updateActivePreset() {
        // Remove active class from all presets
        document.querySelectorAll('.auto-refresh-preset').forEach(btn => {
            btn.classList.remove('active');
        });

        // Add active class to current preset if it's one of the displayed presets
        if (state.activePreset) {
            const activeBtn = document.querySelector(`.auto-refresh-preset[data-seconds="${state.activePreset.seconds}"]`);
            if (activeBtn) {
                activeBtn.classList.add('active');
            }
        }
    }

    // Toggle auto refresh enabled state
    function toggleEnabled() {
        state.enabled = !state.enabled;

        // Update UI
        const container = document.getElementById('auto-refresh-container');
        if (container) {
            if (state.enabled) {
                container.classList.remove('auto-refresh-disabled');
                if (state.activePreset && state.onServiceOrdersPage) {
                    startAutoRefresh(state.activePreset);
                }
            } else {
                container.classList.add('auto-refresh-disabled');
                clearAutoRefresh();
                // When disabled, immediately update status to "off" message
                updateStatus('info', 'Auto refresh is off');
            }
        }

        // Update page status
        updatePageStatus();

        // Save state
        saveState();
    }

    // Toggle time-based presets feature
    function toggleTimeBasedPresets() {
        state.useTimeBasedPresets = !state.useTimeBasedPresets;

        // Update UI
        updateTimeBasedToggle();

        // Always trigger refresh immediately when toggle is changed
        // This will update the "Last checked" time
        if (state.onServiceOrdersPage && state.enabled) {
            triggerRefresh();
        }

        if (state.useTimeBasedPresets) {
            // When turning ON time-based presets, switch to time-based preset immediately
            updateCurrentTimePreset();

            // Remember current user preset before switching
            if (state.activePreset) {
                state.userSelectedPreset = {...state.activePreset};
            }

            // Apply the time-based preset (without triggering another refresh)
            clearAutoRefresh();
            state.activePreset = state.currentTimePreset;

            // Start auto refresh with this preset if enabled
            if (state.enabled && state.onServiceOrdersPage) {
                scheduleNextRefresh(state.activePreset);
            }

            // Make sure the time-based preset button is visually active
            document.querySelectorAll('.auto-refresh-preset').forEach(btn => {
                btn.classList.remove('active');

                // Add active class to the current time preset
                if (state.activePreset && btn.dataset.seconds == state.activePreset.seconds) {
                    btn.classList.add('active');
                }
            });
        } else {
            // When turning OFF time-based presets, switch back to user's manually selected preset
            if (state.userSelectedPreset) {
                clearAutoRefresh();
                state.activePreset = state.userSelectedPreset;

                // If enabled, schedule next refresh with the user's preset
                if (state.enabled && state.onServiceOrdersPage) {
                    scheduleNextRefresh(state.activePreset);
                } else {
                    // Just update UI if not enabled
                    updateActivePreset();
                }
            }
        }

        // Update status after changes
        updateStatusWithRefreshInfo();

        // Save state
        saveState();
    }

    // Update time-based toggle state in UI
    function updateTimeBasedToggle() {
        const timeToggle = document.getElementById('time-based-toggle');
        if (timeToggle) {
            timeToggle.checked = state.useTimeBasedPresets;
        }
    }

    // Toggle tip expanded/collapsed state
    function toggleTip() {
        state.tipExpanded = !state.tipExpanded;

        const tipContent = document.getElementById('auto-refresh-tip-content');
        if (!tipContent) return;

        if (state.tipExpanded) {
            tipContent.classList.add('expanded');
        } else {
            tipContent.classList.remove('expanded');
        }

        // Save state
        saveState();
    }

    // Toggle collapsed state
    function toggleCollapsed() {
        state.collapsed = !state.collapsed;

        // Update UI
        const body = document.getElementById('auto-refresh-body');
        const collapseBtn = document.getElementById('auto-refresh-collapse');

        if (body && collapseBtn) {
            if (state.collapsed) {
                body.style.maxHeight = '0';
                collapseBtn.textContent = '+';
            } else {
                body.style.maxHeight = '500px';
                collapseBtn.textContent = '-';
            }
        }

        // Save state
        saveState();
    }

    // Save state to GM storage
    function saveState() {
        try {
            GM_setValue('autoRefreshPresets', state.presets);
            GM_setValue('autoRefreshPosition', state.position);
            GM_setValue('autoRefreshEnabled', state.enabled);
            GM_setValue('autoRefreshCollapsed', state.collapsed);
            GM_setValue('autoRefreshActivePreset', state.activePreset);
            GM_setValue('autoRefreshUseTimeBasedPresets', state.useTimeBasedPresets);
            GM_setValue('autoRefreshTipExpanded', state.tipExpanded);
        } catch (error) {
            console.error('Error saving state:', error);
        }
    }

    // Load state from GM storage
    function loadState() {
        try {
            state.presets = GM_getValue('autoRefreshPresets', DEFAULT_PRESETS);
            state.position = GM_getValue('autoRefreshPosition', { x: 20, y: 20 });
            state.enabled = GM_getValue('autoRefreshEnabled', true);
            state.collapsed = GM_getValue('autoRefreshCollapsed', false);
            state.tipExpanded = GM_getValue('autoRefreshTipExpanded', false);

            // Always default to time-based presets on page reload/relogin
            state.useTimeBasedPresets = true;

            // Store this value to preserve user's manual toggle for time-based presets
            // during the current session, but not across reloads
            const savedTimeBasedSetting = GM_getValue('autoRefreshUseTimeBasedPresets', true);
            GM_setValue('autoRefreshUseTimeBasedPresets', true);

            // Get the current time-based preset
            updateCurrentTimePreset();

            // Always use time-based preset on reload, regardless of previous setting
            state.activePreset = state.currentTimePreset;

            // If we've loaded an active preset and we're enabled, set the lastRefreshTime
            // to avoid the "Select a refresh interval" message flash on load
            if (state.activePreset && state.enabled) {
                state.lastRefreshTime = new Date();
            }
        } catch (error) {
            console.error('Error loading state:', error);
            // Fallback to defaults
            state.presets = DEFAULT_PRESETS;
            state.position = { x: 20, y: 20 };
            state.enabled = true;
            state.collapsed = false;
            state.useTimeBasedPresets = true;
            state.tipExpanded = false;
            updateCurrentTimePreset();
            state.activePreset = state.currentTimePreset;

            // Prevent "Select a refresh interval" message
            state.lastRefreshTime = new Date();
        }
    }

    // Format preset name based on seconds
    function formatPresetName(seconds) {
        return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}min`;
    }

    // Show preset edit panel
    function showEditPanel(preset) {
        state.editingPreset = preset;

        // Create overlay and panel
        const overlay = document.createElement('div');
        overlay.id = 'auto-refresh-edit-overlay';

        const panel = document.createElement('div');
        panel.id = 'auto-refresh-edit-panel';

        // Create options HTML - with predefined array
        const optionsHTML = TIME_OPTIONS.map(value => {
            const selected = value === preset.seconds ? 'selected' : '';
            const label = value < 60 ? `${value}s` : `${Math.floor(value/60)}min`;
            return `<option value="${value}" ${selected}>${label}</option>`;
        }).join('');

        panel.innerHTML = `
            <h3 style="font-size: 14px; margin-top: 0;">Edit Preset</h3>
            <div class="auto-refresh-form-group">
                <label class="auto-refresh-form-label">Select Interval:</label>
                <select id="edit-preset-seconds" class="auto-refresh-form-input" style="appearance: auto; background-color: white;">
                    ${optionsHTML}
                </select>
                <div id="edit-preset-error" style="color: #f44336; font-size: 11px; margin: 8px 0; display: none;"></div>
            </div>
            <div class="auto-refresh-form-buttons">
                <button class="auto-refresh-form-button cancel">Cancel</button>
                <button class="auto-refresh-form-button save">Save</button>
            </div>
        `;

        overlay.appendChild(panel);
        document.body.appendChild(overlay);

        // Add event listeners
        overlay.querySelector('.cancel').addEventListener('click', hideEditPanel);
        overlay.querySelector('.save').addEventListener('click', saveEditedPreset);

        // Handle pressing Enter key
        const selectInput = document.getElementById('edit-preset-seconds');
        selectInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                saveEditedPreset();
            }
        });

        // Focus the select field
        selectInput.focus();
    }

    // Save edited preset with duplicate check
    function saveEditedPreset() {
        const secondsInput = document.getElementById('edit-preset-seconds');
        const errorElement = document.getElementById('edit-preset-error');

        if (!secondsInput) return;

        const seconds = parseInt(secondsInput.value, 10);
        if (isNaN(seconds)) return;

        // Check for duplicate
        const duplicatePreset = state.presets.find(p =>
            p.seconds === seconds &&
            !(p.name === state.editingPreset.name && p.seconds === state.editingPreset.seconds)
        );

        if (duplicatePreset) {
            if (errorElement) {
                errorElement.textContent = `Preset "${duplicatePreset.name}" already uses this interval.`;
                errorElement.style.display = 'block';
            }
            return;
        }

        // Format name and update preset
        const name = formatPresetName(seconds);
        const presetIndex = state.presets.findIndex(p =>
            p.name === state.editingPreset.name && p.seconds === state.editingPreset.seconds
        );

        if (presetIndex >= 0) {
            state.presets[presetIndex] = { name, seconds };

            // If editing active preset, update it
            if (state.activePreset &&
                state.activePreset.name === state.editingPreset.name &&
                state.activePreset.seconds === state.editingPreset.seconds) {
                state.activePreset = { name, seconds };
                if (state.enabled && state.onServiceOrdersPage) {
                    startAutoRefresh(state.activePreset);
                }
            }

            // Update UI and save
            createOrUpdateUI();
            saveState();
        }

        hideEditPanel();
    }

    // Hide preset edit panel
    function hideEditPanel() {
        const overlay = document.getElementById('auto-refresh-edit-overlay');
        if (overlay) overlay.remove();
        state.editingPreset = null;
    }

    // Make element draggable - simplified for both mouse and touch
    function makeDraggable(element, handleElement) {
        let startX, startY, initialX, initialY;
        let isDragging = false;

        const onStart = (e) => {
            // Don't initiate drag if it's a button or control
            if (e.target.id === 'auto-refresh-collapse' ||
                e.target.id === 'auto-refresh-toggle' ||
                e.target.id === 'time-based-toggle' ||
                e.target.closest('button') ||
                e.target.closest('.switch')) {
                return;
            }

            isDragging = true;
            state.isDragging = true;

            // Get starting positions
            if (e.type === 'mousedown') {
                startX = e.clientX;
                startY = e.clientY;
            } else if (e.type === 'touchstart') {
                startX = e.touches[0].clientX;
                startY = e.touches[0].clientY;
            }

            initialX = element.offsetLeft;
            initialY = element.offsetTop;

            // Add event listeners
            if (e.type === 'mousedown') {
                document.addEventListener('mousemove', onMove);
                document.addEventListener('mouseup', onEnd);
            } else if (e.type === 'touchstart') {
                document.addEventListener('touchmove', onMove, { passive: false });
                document.addEventListener('touchend', onEnd);
            }

            // Prevent default for handle
            if (e.target === handleElement || handleElement.contains(e.target)) {
                if (e.preventDefault) e.preventDefault();
            }
        };

        const onMove = (e) => {
            if (!isDragging) return;

            // Calculate new position
            let clientX, clientY;
            if (e.type === 'mousemove') {
                clientX = e.clientX;
                clientY = e.clientY;
            } else if (e.type === 'touchmove') {
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
                e.preventDefault(); // Prevent scrolling when dragging
            }

            const deltaX = clientX - startX;
            const deltaY = clientY - startY;
            const newLeft = initialX + deltaX;
            const newTop = initialY + deltaY;

            // Keep within viewport
            const maxTop = window.innerHeight - element.offsetHeight;
            const maxLeft = window.innerWidth - element.offsetWidth;
            element.style.top = `${Math.min(Math.max(0, newTop), maxTop)}px`;
            element.style.left = `${Math.min(Math.max(0, newLeft), maxLeft)}px`;
        };

        const onEnd = () => {
            isDragging = false;

            // Small delay to prevent accidental clicks
            setTimeout(() => { state.isDragging = false; }, 50);

            // Remove event listeners
            document.removeEventListener('mousemove', onMove);
            document.removeEventListener('mouseup', onEnd);
            document.removeEventListener('touchmove', onMove);
            document.removeEventListener('touchend', onEnd);

            // Save position
            state.position = {
                x: parseInt(element.style.left, 10) || 20,
                y: parseInt(element.style.top, 10) || 20
            };
            saveState();
        };

        handleElement.addEventListener('mousedown', onStart);
        handleElement.addEventListener('touchstart', onStart, { passive: true });
    }

    // Update page status based on current state
    function updatePageStatus() {
        if (!state.onServiceOrdersPage) {
            updateStatus('warning', 'Please navigate to Service Orders');
        } else if (!state.enabled) {
            // When disabled, always show "off" message regardless of last refresh time
            updateStatus('info', 'Auto refresh is off');
        } else if (state.activePreset) {
            // Show improved status message with interval and last time
            updateStatusWithRefreshInfo();
        } else {
            updateStatus('info', 'Select a refresh interval');
        }
    }

    // Create or update UI
    function createOrUpdateUI() {
        // Check if UI already exists
        let container = document.getElementById('auto-refresh-container');

        if (!container) {
            // Create new container
            container = document.createElement('div');
            container.id = 'auto-refresh-container';
            document.body.appendChild(container);

            // Set position
            container.style.left = `${state.position.x}px`;
            container.style.top = `${state.position.y}px`;

            // Create UI structure
            container.innerHTML = `
                <div id="auto-refresh-header">
                    <span id="auto-refresh-title">Auto Refresh</span>
                    <div id="auto-refresh-controls">
                        <label class="switch">
                            <input type="checkbox" id="auto-refresh-toggle" ${state.enabled ? 'checked' : ''}>
                            <span class="slider"></span>
                        </label>
                        <span id="auto-refresh-collapse">${state.collapsed ? '+' : '-'}</span>
                    </div>
                </div>
                <div id="auto-refresh-body" style="max-height: ${state.collapsed ? '0' : '500px'};">
                    <div class="feature-toggle">
                        <span class="feature-label">Use time-based presets</span>
                        <label class="switch">
                            <input type="checkbox" id="time-based-toggle" ${state.useTimeBasedPresets ? 'checked' : ''}>
                            <span class="slider"></span>
                        </label>
                    </div>
                    <div id="auto-refresh-tip">
                        <div id="auto-refresh-tip-header">
                            <span>User Tip</span>
                        </div>
                        <div id="auto-refresh-tip-content" class="${state.tipExpanded ? 'expanded' : ''}">
                            Busy? Go fast. Slow? Take it easy
                        </div>
                    </div>
                    <div id="auto-refresh-presets"></div>
                    <div id="auto-refresh-status" class="info">Initializing...</div>
                </div>
            `;

            // Add toggle event listeners
            document.getElementById('auto-refresh-toggle').addEventListener('change', toggleEnabled);
            document.getElementById('time-based-toggle').addEventListener('change', toggleTimeBasedPresets);

            // Add collapse event listener
            const domCollapseBtn = document.getElementById('auto-refresh-collapse');
            if (domCollapseBtn) {
                domCollapseBtn.onclick = function(e) {
                    if (e) {
                        e.stopPropagation();
                        e.preventDefault();
                    }
                    toggleCollapsed();
                    return false;
                };
            }

            // Add tip toggle functionality
            const tipHeader = document.getElementById('auto-refresh-tip-header');
            if (tipHeader) {
                tipHeader.addEventListener('click', function(e) {
                    toggleTip();
                    e.stopPropagation();
                });
            }

            // Make draggable
            makeDraggable(container, document.getElementById('auto-refresh-header'));

            // Set disabled class if needed
            if (!state.enabled) {
                container.classList.add('auto-refresh-disabled');
            }
        }

        // Update presets
        const presetsContainer = document.getElementById('auto-refresh-presets');
        if (presetsContainer) {
            presetsContainer.innerHTML = '';

            state.presets.forEach(preset => {
                const presetBtn = document.createElement('button');
                presetBtn.className = 'auto-refresh-preset';
                presetBtn.textContent = preset.name;
                presetBtn.dataset.seconds = preset.seconds;

                // Set active class if needed
                if (state.activePreset &&
                    state.activePreset.seconds === preset.seconds) {
                    presetBtn.classList.add('active');
                }

    // Normal click event
                presetBtn.addEventListener('click', () => {
                    if (!state.isDragging && state.enabled) {
                        // When user selects a preset manually, disable time-based presets
                        state.useTimeBasedPresets = false;
                        updateTimeBasedToggle();

                        // Save this as the user's manual preset
                        state.userSelectedPreset = {...preset};

                        // Set this preset as active
                        startAutoRefresh(preset);

                        // Ensure this preset gets highlighted (not just in startAutoRefresh)
                        document.querySelectorAll('.auto-refresh-preset').forEach(btn => {
                            btn.classList.remove('active');
                        });
                        presetBtn.classList.add('active');
                    }
                });

                // Right-click for edit
                presetBtn.addEventListener('contextmenu', (e) => {
                    e.preventDefault();
                    showEditPanel(preset);
                });

                // Long press for mobile
                let longPressTimer;
                let longPressStarted = false;
                let longPressFired = false; // New flag to track when long press action has fired

                presetBtn.addEventListener('touchstart', () => {
                    longPressStarted = true;
                    longPressFired = false;
                    longPressTimer = setTimeout(() => {
                        if (longPressStarted) {
                            longPressFired = true; // Set flag when edit panel is shown
                            showEditPanel(preset);
                        }
                    }, 800);
                });

                presetBtn.addEventListener('touchmove', () => {
                    longPressStarted = false;
                    clearTimeout(longPressTimer);
                });

                presetBtn.addEventListener('touchend', () => {
                    // Only activate preset if it wasn't a long press
                    if (!state.isDragging && longPressStarted && !longPressFired && state.enabled) {
                        // Normal tap behavior - activate the preset
                        state.useTimeBasedPresets = false;
                        updateTimeBasedToggle();

                        // Save this as the user's manual preset
                        state.userSelectedPreset = {...preset};

                        startAutoRefresh(preset);

                        // Ensure this preset gets highlighted
                        document.querySelectorAll('.auto-refresh-preset').forEach(btn => {
                            btn.classList.remove('active');
                        });
                        presetBtn.classList.add('active');
                    }
                    longPressStarted = false;
                    clearTimeout(longPressTimer);
                });

                presetsContainer.appendChild(presetBtn);
            });
        }

        // Update status
        updatePageStatus();
    }

    // Check page and update UI accordingly
    function checkPageAndUpdateUI() {
        const wasOnServiceOrdersPage = state.onServiceOrdersPage;
        state.onServiceOrdersPage = checkIfOnServiceOrdersPage();

        // Reset button observed state if needed
        if (!state.onServiceOrdersPage) {
            state.refreshButtonObserved = false;
        }

        // Update status
        updatePageStatus();

        // Handle page transitions
        if (!wasOnServiceOrdersPage && state.onServiceOrdersPage) {
            // Just arrived at service orders page
            if (state.enabled) {
                triggerRefresh();
                if (state.activePreset) {
                    startAutoRefresh(state.activePreset);
                }
            }
        } else if (wasOnServiceOrdersPage && !state.onServiceOrdersPage) {
            // Just left service orders page
            clearAutoRefresh();
        }
    }

    // Setup observer for page changes
    function setupObserver() {
        const observer = new MutationObserver(() => {
            // Debounce to prevent excessive checks
            if (!state.observerDebounce) {
                state.observerDebounce = true;
                setTimeout(() => {
                    checkPageAndUpdateUI();
                    state.observerDebounce = false;
                }, 1000);
            }
        });

        // Observe body changes
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // Initialize the script
    function init() {
        // Load saved state
        loadState();

        // Create UI
        createOrUpdateUI();

        // Make sure the correct toggle state is shown
        updateTimeBasedToggle();

        // Setup observer
        setupObserver();

        // Start time check for time-based presets
        startTimeCheck();

        // Check current page
        checkPageAndUpdateUI();

        // Start auto refresh if applicable
        if (state.activePreset && state.onServiceOrdersPage && state.enabled) {
            startAutoRefresh(state.activePreset);
        }

        // Ensure the correct preset is highlighted
        updateActivePreset();

        state.initialized = true;
    }

    // Handle global errors
    window.addEventListener('error', function(event) {
        if (event.filename && event.filename.includes('Auto Refresh Tool')) {
            //console.log('Auto Refresh Tool error:', error);
            event.preventDefault();
            return true;
        }
        return false;
    }, true);

    // Initialize with retry mechanism
    let initAttempts = 0;
    const maxInitAttempts = 3;

    function attemptInit() {
        if (initAttempts >= maxInitAttempts) {
            console.error('Failed to initialize Auto Refresh Tool after multiple attempts');
            return;
        }

        if (!state.initialized) {
            initAttempts++;
            init();

            // Schedule another attempt if needed
            if (!state.initialized) {
                setTimeout(attemptInit, 2000);
            }
        }
    }

    // Start initialization with delay
    setTimeout(attemptInit, 1000);

    // Backup initialization
    setTimeout(() => {
        if (!document.getElementById('auto-refresh-container')) {
            createOrUpdateUI();
            checkPageAndUpdateUI();
        }
    }, 5000);
})();