Auto Refresh Interface for Hotsauce

Auto refresh for HotSOS

// ==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);
})();