Greasy Fork

Greasy Fork is available in English.

WME EZRoad Mod

Easily update roads

当前为 2025-05-20 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         WME EZRoad Mod
// @namespace    http://greasyfork.icu/users/1087400
// @version      2.4.7
// @description  Easily update roads
// @author       https://github.com/michaelrosstarr, http://greasyfork.icu/en/users/1087400-kid4rm90s
// @include 	 /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor.*$/
// @exclude      https://www.waze.com/user/*editor/*
// @exclude      https://www.waze.com/*/user/*editor/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        GM_info
// @grant 		 unsafeWindow
// @icon         https://www.google.com/s2/favicons?sz=64&domain=waze.com
// @license      GNU GPL(v3)
// @connect      greasyfork.org
// @require      http://greasyfork.icu/scripts/24851-wazewrap/code/WazeWrap.js
// @require      https://update.greasyfork.icu/scripts/509664/WME%20Utils%20-%20Bootstrap.js
// ==/UserScript==

/*Script modified from WME EZRoad (http://greasyfork.icu/en/scripts/518381-wme-ezsegments) original author: Michaelrosstarr and thanks to him*/

(function main() {
  "use strict";
   const updateMessage = 'Added \'Support for copying alternative segment name from connected segment\'<br>Thanks to<br>- MapOmatic, without him this would not be possible.<br>';
   const scriptName = GM_info.script.name;
   const scriptVersion = GM_info.script.version;
  const downloadUrl = 'http://greasyfork.icu/scripts/528552-wme-ezroad-mod/code/wme-ezroad-mod.user.js';
   let wmeSDK;

const roadTypes = [
    { id: 1, name: 'Motorway', value: 3 },
	{ id: 2, name: 'Ramp', value: 4 },
	{ id: 3, name: 'Major Highway', value: 6 },
	{ id: 4, name: 'Minor Highway', value: 7 },
	{ id: 5, name: 'Primary Street', value: 2 },
    { id: 6, name: 'Street', value: 1 },
    { id: 7, name: 'Narrow Street', value: 22 },
	{ id: 8, name: 'Offroad', value: 8 },
	{ id: 9, name: 'Parking Road', value: 20 },
	{ id: 10, name: 'Private Road', value: 17 },
    { id: 11, name: 'Ferry', value: 15 },
	{ id: 12, name: 'Railroad', value: 18 },
	{ id: 13, name: 'Runway/Taxiway', value: 19 },
	{ id: 14, name: 'Foothpath', value: 5 },
	{ id: 15, name: 'Pedestrianised Area', value: 10 },
	{ id: 16, name: 'Stairway', value: 16 },
];
const defaultOptions = {
    roadType: 1,
    unpaved: false,
    setStreet: false,
    autosave: false,
    setSpeed: 40,
    setLock: false,
    updateSpeed: false,
    copySegmentName: false,
    locks: roadTypes.map(roadType => ({ id: roadType.id, lock: 1 })),
    speeds: roadTypes.map(roadType => ({ id: roadType.id, speed: 40 }))
};

const locks = [
    { id: 1, value: 1 },
    { id: 2, value: 2 },
    { id: 3, value: 3 },
    { id: 4, value: 4 },
    { id: 5, value: 5 },
    { id: 6, value: 6 },
	{ id: 'HRCS', value: 'HRCS' },
]

const log = (message) => {
    if (typeof message === 'string') {
        console.log('WME_EZRoads_Mod: ' + message);
    } else {
        console.log('WME_EZRoads_Mod: ', message);
    }
}

unsafeWindow.SDK_INITIALIZED.then(initScript);

function initScript() {
    wmeSDK = getWmeSdk({ scriptId: "wme-ez-roads-mod", scriptName: "EZ Roads Mod" });
    WME_EZRoads_Mod_bootstrap();
}

const getCurrentCountry = () => {
    return wmeSDK.DataModel.Countries.getTopCountry();
}

const getTopCity = () => {
    return wmeSDK.DataModel.Cities.getTopCity();
}

const getAllCities = () => {
    return wmeSDK.DataModel.Cities.getAll();
}

const saveOptions = (options) => {
    window.localStorage.setItem('WME_EZRoads_Mod_Options', JSON.stringify(options));
}

const getOptions = () => {
    const savedOptions = JSON.parse(window.localStorage.getItem('WME_EZRoads_Mod_Options')) || {};
	// Merge saved options with defaults to ensure all expected options exist
    return { ...defaultOptions, ...savedOptions };
	}

const WME_EZRoads_Mod_bootstrap = () => {
    if (
        !document.getElementById('edit-panel')
        || !wmeSDK.DataModel.Countries.getTopCountry()
    ) {
        setTimeout(WME_EZRoads_Mod_bootstrap, 250);
        return;
    }

    if (wmeSDK.State.isReady) {
        WME_EZRoads_Mod_init();
    } else {
        wmeSDK.Events.once({ eventName: 'wme-ready' }).then(WME_EZRoads_Mod_init());
    }
}

let openPanel;

const WME_EZRoads_Mod_init = () => {
    log("Initing");

    const roadObserver = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            for (let i = 0; i < mutation.addedNodes.length; i++) {
                const addedNode = mutation.addedNodes[i];

                if (addedNode.nodeType === Node.ELEMENT_NODE) {
                    let editSegment = addedNode.querySelector('#segment-edit-general');
                    if (editSegment) {
                        openPanel = editSegment;

                        // Check if THIS SPECIFIC panel already has the button
                        const parentElement = editSegment.parentNode;
                        if (!parentElement.querySelector('[data-ez-roadmod-button="true"]')) {
                            log("Creating Quick Set Road button for this panel");
                            const quickButton = document.createElement('wz-button');
                            quickButton.setAttribute('type', 'button');
                            quickButton.setAttribute('style', 'margin-bottom: 5px; width: 100%');
                            quickButton.setAttribute('disabled', 'false');
                            quickButton.setAttribute('data-ez-roadmod-button', 'true');
                            quickButton.setAttribute('id', 'ez-roadmod-quick-button-' + Date.now()); // Unique ID using timestamp
                            quickButton.classList.add('send-button', 'ez-comment-button');
                            quickButton.textContent = 'Quick Update Segment';
                            parentElement.insertBefore(quickButton, editSegment);
                            quickButton.addEventListener('mousedown', () => handleUpdate());
                            log("Button created for current panel");
                        } else {
                            log("This panel already has the button, skipping creation");
                        }
					}
				}
			}
        });
    });

    roadObserver.observe(document.getElementById('edit-panel'), { childList: true, subtree: true });

    constructSettings();

    document.addEventListener("keydown", (event) => {
        // Check if the active element is an input or textarea
        const isInputActive = document.activeElement && (
            document.activeElement.tagName === 'INPUT' ||
            document.activeElement.tagName === 'TEXTAREA' ||
            document.activeElement.contentEditable === 'true' ||
            document.activeElement.tagName === 'WZ-AUTOCOMPLETE' ||
            document.activeElement.tagName === 'WZ-TEXTAREA'
        );

        log(document.activeElement.tagName);
        log(isInputActive);

        // Only trigger the update if the active element is not an input or textarea
        if (!isInputActive && event.key.toLowerCase() === "g") {
            handleUpdate();
        }
    });

    log("Completed Init")
}

const getEmptyStreet = () => {

}

const getEmptyCity = () => {
    return wmeSDK.DataModel.Cities.getCity({
        cityName: '',
        countryId: getCurrentCountry().id
    }) || wmeSDK.DataModel.Cities.addCity({
        cityName: '',
        countryId: getCurrentCountry().id
    });
}

// Helper function to wrap updates in a promise with delay
const delayedUpdate = (updateFn, delay) => {
    return new Promise(resolve => {
        setTimeout(() => {
            updateFn();
            resolve();
        }, delay);
    });
};

function getHighestSegLock(segID) {
    const segObj = W.model.segments.getObjectById(segID);
    if (!segObj) {
        console.warn(`Segment object with ID ${segID} not found in W.model.segments.`);
        return 1; // Default lock level if segment not found
    }
    const segType = segObj.attributes.roadType;
    const checkedSegs = [];
    let forwardLock = null; // Initialize to L1
    let reverseLock = null; // Initialize to L1

    function processForNode(forwardID) {
        checkedSegs.push(forwardID);
        const forNode = W.model.segments.getObjectById(forwardID).getToNode();
        if (!forNode) return forwardLock; // defensive check

        const forNodeSegs = [...forNode.attributes.segIDs];

        for (let j = 0; j < forNodeSegs.length; j++) {
            if (forNodeSegs[j] === forwardID) { forNodeSegs.splice(j, 1); }
        }

        for (let i = 0; i < forNodeSegs.length; i++) {
            const conSegObj = W.model.segments.getObjectById(forNodeSegs[i]);
            if (!conSegObj) continue; // Defensive check - skip if segment object is not found
            const conSeg = conSegObj.attributes;

            if (conSeg.roadType !== segType) {
                forwardLock = Math.max(conSeg.lockRank + 0, forwardLock); // +1 to convert lockRank to lock level
            } else {
                for (let k = 0; k < forNodeSegs.length; k++) {
                    if (!checkedSegs.some(segNum => segNum === conSeg.id)) {
                        const tempRank = processForNode(conSeg.id);
                        forwardLock = Math.max(tempRank, forwardLock);
                    }
                }
            }
        }
        return forwardLock;
    }

    function processRevNode(reverseID) {
        checkedSegs.push(reverseID);
        const revNode = W.model.segments.getObjectById(reverseID).getFromNode();
        if (!revNode) return reverseLock; // defensive check, return current lock if node is not valid

        const revNodeSegs = [...revNode.attributes.segIDs];

        for (let j = 0; j < revNodeSegs.length; j++) {
            if (revNodeSegs[j] === reverseID) { revNodeSegs.splice(j, 1); }
        }

        for (let i = 0; i < revNodeSegs.length; i++) {
            const conSegObj = W.model.segments.getObjectById(revNodeSegs[i]);
             if (!conSegObj) continue; // Defensive check - skip if segment object is not found
            const conSeg = conSegObj.attributes;

            if (conSeg.roadType !== segType) {
                reverseLock = Math.max(conSeg.lockRank + 0, reverseLock); // +1 to convert lockRank to lock level
            } else {
                for (let k = 0; k < revNodeSegs.length; k++) {
                    if (!checkedSegs.some(segNum => segNum === conSeg.id)) {
                        const tempRank = processRevNode(conSeg.id);
                        reverseLock = Math.max(tempRank, reverseLock);
                    }
                }
            }
        }
        return reverseLock;
    }

    let calculatedLock = Math.max(processForNode(segID), processRevNode(segID));
    return Math.min(calculatedLock, 6); // Limit to L6
}

const handleUpdate = () => {
    const selection = wmeSDK.Editing.getSelection();

    if (!selection || selection.objectType !== 'segment') return;

    log('Updating RoadType');
    const options = getOptions();
    let alertMessageParts = [];
    let updatedRoadType = false;
    let updatedLockLevel = false;
    let updatedSpeedLimit = false;
    let updatedStreet = false;
    let updatedPaved = false;
    let updatedCityName = false;
    let updatedSegmentName = false;
    const updatePromises = [];

    selection.ids.forEach(id => {

        // Road Type
        updatePromises.push(delayedUpdate(() => {
            if (options.roadType) {
                const seg = wmeSDK.DataModel.Segments.getById({segmentId: id});
				const selectedRoad = roadTypes.find(rt => rt.value === options.roadType);
                //alertMessageParts.push(`Road Type: <b>${selectedRoad.name}</b>`);
                //updatedRoadType = true;
				log(`Segment ID: ${id}, Current Road Type: ${seg.roadType}, Target Road Type: ${options.roadType}, Target Road Name : ${selectedRoad.name}`); // Log current and target road type
                if (seg.roadType === options.roadType) {
                    log(`Segment ID: ${id} already has the target road type: ${options.roadType}. Skipping update.`);
                    alertMessageParts.push(`Road Type: <b>${selectedRoad.name} exists. Skipping update.</b>`);
                    updatedRoadType = true;
                } else {
                    try {
                        wmeSDK.DataModel.Segments.updateSegment({ segmentId: id, roadType: options.roadType });
                        log('Road type updated successfully.');
                        alertMessageParts.push(`Road Type: <b>${selectedRoad.name}</b>`);
                        updatedRoadType = true;
                    } catch (error) {
                        console.error('Error updating road type:', error);
                    }
                }
            }
        }, 200)); // 200ms delay before road type update

        // Set lock if enabled
        updatePromises.push(delayedUpdate(() => {
            if (options.setLock) {
            const rank = wmeSDK.State.getUserInfo().rank;
            const selectedRoad = roadTypes.find(rt => rt.value === options.roadType);
            if (selectedRoad) {
                let lockSetting = options.locks.find(l => l.id === selectedRoad.id);
                if (lockSetting) {
                    let toLock = lockSetting.lock;
                    if (toLock === 'HRCS') {
                        toLock = getHighestSegLock(id);
                    } else {
                        toLock = parseInt(toLock, 10);
                        toLock = Math.max(toLock - 1, 0); // Adjust to 0-based rank, ensuring it does not go below 0
                    }

                if (rank < toLock) toLock = rank;

                log(toLock);

                try {
                    const seg = wmeSDK.DataModel.Segments.getById({ segmentId: id });
                    let displayLockLevel = (toLock === 'HRCS' || isNaN(toLock)) ? 'HRCS' : `L${toLock + 1}`;
                    let currentDisplayLockLevel;
                    if (seg.lockRank === 'HRCS') { // Should not happen, but for safety
                        currentDisplayLockLevel = 'HRCS';
                    } //else if (isNaN(seg.lockRank)) {
                        // currentDisplayLockLevel = 'Unknown'; // Or handle as appropriate
                    // }
                    else {
                        currentDisplayLockLevel = `L${seg.lockRank + 1}`;
                    }
                    if (seg.lockRank === toLock || (lockSetting.lock === 'HRCS' && currentDisplayLockLevel === displayLockLevel)) { // Compare lock levels
                    log(`Segment ID: ${id} already has the target lock level: ${displayLockLevel}. Skipping update.`);
                    alertMessageParts.push(`Lock Level: <b>${displayLockLevel} exists. Skipping update.</b>`);
                    updatedLockLevel = true;
                    } else {
                    wmeSDK.DataModel.Segments.updateSegment({
                        segmentId: id,
                        lockRank: toLock
                    });
                    alertMessageParts.push(`Lock Level: <b>${displayLockLevel}</b>`);
                    updatedLockLevel = true;
                    }
                } catch (error) {
                    console.error('Error updating segment lock rank:', error);
                }
                }
            }
            }
        }, 300)); // 250ms delay before lock rank update

        // Speed Limit - use road-specific speed if updateSpeed is enabled
        updatePromises.push(delayedUpdate(() => {		
        if (options.updateSpeed) {
            const selectedRoad = roadTypes.find(rt => rt.value === options.roadType);
            if (selectedRoad) {
                const speedSetting = options.speeds.find(s => s.id === selectedRoad.id);
                log('Selected road for speed: ' + selectedRoad.name);
                log('Speed setting found: ' + (speedSetting ? 'yes' : 'no'));

                if (speedSetting) {
                    const speedValue = parseInt(speedSetting.speed, 10);
                    log('Speed value to set: ' + speedValue);

                    // Apply speed if it's a valid number (including 0)
                    if (!isNaN(speedValue) && speedValue >= 0) {
                        log('Applying speed: ' + speedValue);
                        const seg = wmeSDK.DataModel.Segments.getById({ segmentId: id });
                        if (seg.fwdSpeedLimit !== speedValue || seg.revSpeedLimit !== speedValue) {
                            wmeSDK.DataModel.Segments.updateSegment({
                                segmentId: id,
                                fwdSpeedLimit: speedValue,
                                revSpeedLimit: speedValue
                            });
                            alertMessageParts.push(`Speed Limit: <b>${speedValue}</b>`);
                            updatedSpeedLimit = true;
                        } else {
                            log(`Segment ID: ${id} already has the target speed limit: ${speedValue}. Skipping update.`);
                            alertMessageParts.push(`Speed Limit: <b>${speedValue} exists. Skipping update.</b>`);
                            updatedSpeedLimit = true;
                        }
                        //alertMessageParts.push(`Speed Limit: <b>${speedValue}</b>`);
                        //updatedSpeedLimit = true;
                    } else {
                        log('Not applying speed - invalid value: ' + speedSetting.speed);
                        alertMessageParts.push(`Speed Limit: <b>Invalid value ${speedValue}</b>`);
                        updatedSpeedLimit = true;
                    }
                }
            }
        } else {
            log('Speed updates disabled');
        }
        }, 400)); // 300ms delay before lock rank update		

        // Handling the street
        if (options.setStreet) {
            let city;
            let street;
            city = getTopCity() || getEmptyCity();
            street = wmeSDK.DataModel.Streets.getStreet({
                cityId: city.id,
                streetName: '',
            });
                //alertMessageParts.push(`City Name: <b>${city?.name || 'None'}</b>`);
                //updatedCityName = true;
            log(`City Name: ${city?.name}, City ID: ${city?.id}, Street ID: ${street?.id}`);
            if(!street) {
                street = wmeSDK.DataModel.Streets.addStreet({
                    streetName: '',
                    cityId: city.id
                });
                 log(`Created new empty street. Street ID: ${street?.id}`);
            }
            try {
                wmeSDK.DataModel.Segments.updateAddress({
                    segmentId: id,
                    primaryStreetId: street.id
                });
                alertMessageParts.push(`City Name: <b>${city?.name || 'None'}</b>`);
                updatedCityName = true;
            } catch (error) {
                console.error('Error updating segment address:', error);
            }
        }

        log(options);

        // Updated unpaved handler with fallback
        if (options.unpaved) {
            // First try the new method - look for the unpaved chip using the icon class
            const unpavedIcon = openPanel.querySelector('.w-icon-unpaved-fill');
            let unpavedToggled = false;

            if (unpavedIcon) {
                // Click the parent wz-checkable-chip element
                const unpavedChip = unpavedIcon.closest('wz-checkable-chip');
                if (unpavedChip) {
                    unpavedChip.click();
                    log('Clicked unpaved chip');
                    unpavedToggled = true;
                }
            }

            // If new method failed, try the old method as fallback
            if (!unpavedToggled) {
                try {
                    const wzCheckbox = openPanel.querySelector('wz-checkbox[name="unpaved"]');
                    if (wzCheckbox) {
                        const hiddenInput = wzCheckbox.querySelector('input[type="checkbox"][name="unpaved"]');
                        if (hiddenInput) {
                            hiddenInput.click();
                            log('Clicked unpaved checkbox (non-compact mode)');
                            unpavedToggled = true;
                        }
                    }
                } catch (e) {
                    log('Fallback to non-compact mode unpaved toggle method failed: ' + e);
                }
            }

                if (unpavedToggled) {
                    alertMessageParts.push(`Paved Status: <b>Unpaved</b>`);
                    updatedPaved = true;
            }
        } else {
                alertMessageParts.push(`Paved Status: <b>Paved</b>`); // Assuming default is paved if not explicitly unpaved
                updatedPaved = true;
            }

        // 3a. Copy segment name from connected segment if enabled
        updatePromises.push(delayedUpdate(() => {
            if (options.copySegmentName) {
                try {
                    const seg = wmeSDK.DataModel.Segments.getById({ segmentId: id });
                    const fromNode = seg.fromNodeId;
                    const toNode = seg.toNodeId;
                    let connectedSegId = null;
                    const allSegs = wmeSDK.DataModel.Segments.getAll();
                    for (let s of allSegs) {
                        if (s.id !== id && (s.fromNodeId === fromNode || s.toNodeId === fromNode || s.fromNodeId === toNode || s.toNodeId === toNode)) {
                            connectedSegId = s.id;
                            break;
                        }
                    }
                    if (connectedSegId) {
                        const connectedSeg = wmeSDK.DataModel.Segments.getById({ segmentId: connectedSegId });
                        const streetId = connectedSeg.primaryStreetId;
                        const altStreetIds = connectedSeg.alternateStreetIds || [];
                        const street = wmeSDK.DataModel.Streets.getById({ streetId });
                        // Get alternate street names
                        let altNames = [];
                        altStreetIds.forEach(streetId => {
                            const altStreet = wmeSDK.DataModel.Streets.getById({ streetId });
                            if (altStreet && altStreet.name) altNames.push(altStreet.name);
                        });
                        if (street && (street.name || street.englishName || street.signText)) {
                            // Copy both main and alternate street IDs
                            wmeSDK.DataModel.Segments.updateAddress({
                                segmentId: id,
                                primaryStreetId: streetId,
                                alternateStreetIds: altStreetIds
                            });
                            let aliasMsg = altNames.length ? ` (Alternatives: ${altNames.join(', ')})` : '';
                            alertMessageParts.push(`Copied Name: <b>${street.name || ''}${aliasMsg}</b>`);
                            updatedSegmentName = true;
                        } else {
                            alertMessageParts.push(`Copied Name: <b>None (connected segment has no name)</b>`);
                        }
                    } else {
                        alertMessageParts.push(`Copied Name: <b>None (no connected segment found)</b>`);
                    }
                } catch (error) {
                    console.error('Error copying segment name:', error);
                }
            }
        }, 150)); // Run early in the update chain
    })

    Promise.all(updatePromises).then(() => {
        const showAlert = () => {
            const updatedFeatures = [];
            if (updatedSegmentName) updatedFeatures.push(alertMessageParts.find(part => part.startsWith("Copied Name")));
            if (updatedCityName) updatedFeatures.push(alertMessageParts.find(part => part.startsWith("City")));
            if(updatedRoadType) updatedFeatures.push(alertMessageParts.find(part => part.startsWith("Road Type")));
            if(updatedLockLevel) updatedFeatures.push(alertMessageParts.find(part => part.startsWith("Lock Level")));
            if(updatedSpeedLimit) updatedFeatures.push(alertMessageParts.find(part => part.startsWith("Speed Limit")));
            if(updatedPaved) updatedFeatures.push(alertMessageParts.find(part => part.startsWith("Paved")));
            const message = updatedFeatures.filter(Boolean).join(', ');
            if (message) {
                if (WazeWrap?.Alerts) {
                    WazeWrap.Alerts.info('EZRoads Mod', `Segment updated with: ${message}`, false, false, 7000);
                } else {
                    alert('EZRoads Mod: Segment updated (WazeWrap Alerts not available)');
                }
            }
        }

        // Autosave - DELAYED AUTOSAVE
        if (options.autosave) {
            setTimeout(() => {
                log('Delayed Autosave starting...');
                wmeSDK.Editing.save().then(() => {
                    log('Delayed Autosave completed.');
                    showAlert();
                });
            }, 500); // 1000ms (1 second) delay before autosave
        } else {
            showAlert();
        }
    });
}

const constructSettings = () => {
    const localOptions = getOptions();
    let currentRoadType = localOptions.roadType;
    const update = (key, value) => {
        const options = getOptions();
        options[key] = value;
        localOptions[key] = value;
        saveOptions(options);
    };

    // Update lock level for a specific road type
    const updateLockLevel = (roadTypeId, lockLevel) => {
        const options = getOptions();
        const lockIndex = options.locks.findIndex(l => l.id === roadTypeId);
        if (lockIndex !== -1) {
            options.locks[lockIndex].lock = lockLevel; // Keep as string to handle 'HRCS'
            localOptions.locks = options.locks;
            saveOptions(options);
        }
    };

    // Update speed for a specific road type
    const updateSpeed = (roadTypeId, speed) => {
        const options = getOptions();
        const speedIndex = options.speeds.findIndex(s => s.id === roadTypeId);

        // Make sure we have a valid integer
        let speedValue = parseInt(speed, 10);
        if (isNaN(speedValue)) {
            speedValue = -1; // Default to -1 for invalid values
        }

        log(`Updating speed for road type ${roadTypeId} to ${speedValue}`);

        if (speedIndex !== -1) {
            options.speeds[speedIndex].speed = speedValue;
            localOptions.speeds = options.speeds;
            saveOptions(options);
        }
    };

    // Reset all options to defaults
    const resetOptions = () => {
        saveOptions(defaultOptions);
        // Refresh the page to reload settings
        window.location.reload();
    };

    // Checkbox option definitions
    const checkboxOptions = [
        { id: 'setStreet', text: 'Set Street To None', key: 'setStreet' },
        { id: 'autosave', text: 'Autosave on Action', key: 'autosave' },
        { id: 'unpaved', text: 'Set Road as Unpaved', key: 'unpaved' },
        { id: 'setLock', text: 'Set the lock level', key: 'setLock' },
        { id: 'updateSpeed', text: 'Update speed limits', key: 'updateSpeed' },
        { id: 'copySegmentName', text: 'Copy Segment Name from Connected', key: 'copySegmentName' }
    ];

    // Helper function to create radio buttons
    const createRadioButton = (roadType) => {
        const id = `road-${roadType.id}`;
        const isChecked = localOptions.roadType === roadType.value;
        const lockSetting = localOptions.locks.find(l => l.id === roadType.id) || { id: roadType.id, lock: 1 };
        const speedSetting = localOptions.speeds.find(s => s.id === roadType.id) || { id: roadType.id, speed: 40 };

        const div = $(`<div class="ezroadsmod-option">
            <div class="ezroadsmod-radio-container">
                <input type="radio" id="${id}" name="defaultRoad" ${isChecked ? 'checked' : ''}>
                <label for="${id}">${roadType.name}</label>
                <select id="lock-level-${roadType.id}" class="road-lock-level" data-road-id="${roadType.id}" ${!localOptions.setLock ? 'disabled' : ''}>
                    ${locks.map(lock => `<option value="${lock.value}" ${lockSetting.lock === lock.value ? 'selected' : ''}>${lock.value === 'HRCS' ? 'HRCS' : 'L' + lock.value}</option>`).join('')}
                </select>
                <input type="number" id="speed-${roadType.id}" class="road-speed" data-road-id="${roadType.id}"
                       value="${speedSetting.speed}" min="-1" ${!localOptions.updateSpeed ? 'disabled' : ''}>
            </div>
        </div>`);

        div.find('input[type="radio"]').on('click', () => {
            update('roadType', roadType.value);
            currentRoadType = roadType.value;
        });

        div.find('select').on('change', function () {
            updateLockLevel(roadType.id, $(this).val());
        });

        div.find('input.road-speed').on('change', function () {
            // Get the value as a number
            const speedValue = parseInt($(this).val(), 10);
            // If it's not a number, reset to 0
            if (isNaN(speedValue)) {
                $(this).val(0);
                updateSpeed(roadType.id, 0);
            } else {
                updateSpeed(roadType.id, speedValue);
            }
        });

        return div;
    };

    // Helper function to create checkboxes
    const createCheckbox = (option) => {
    const isChecked = localOptions[option.key];
    const div = $(`<div class="ezroadsmod-option">
        <input type="checkbox" id="${option.id}" name="${option.id}" ${isChecked ? 'checked' : ''}>
        <label for="${option.id}">${option.text}</label>
    </div>`);
    div.on('click', () => {
        // Mutually exclusive logic for setStreet and copySegmentName
        if (option.key === 'setStreet' && $(`#${option.id}`).prop('checked')) {
            $('#copySegmentName').prop('checked', false);
            update('copySegmentName', false);
        }
        if (option.key === 'copySegmentName' && $(`#${option.id}`).prop('checked')) {
            $('#setStreet').prop('checked', false);
            update('setStreet', false);
        }
        update(option.key, $(`#${option.id}`).prop('checked'));
    });
    return div;
};

    // -- Set up the tab for the script
    wmeSDK.Sidebar.registerScriptTab().then(({ tabLabel, tabPane }) => {
        tabLabel.innerText = 'EZRoads Mod';
        tabLabel.title = 'Easily Update Roads';

        // Setup base styles
        const styles = $(`<style>
            #ezroadsmod-settings h2, #ezroadsmod-settings h5 {
                margin-top: 0;
                margin-bottom: 10px;
            }
            .ezroadsmod-section {
                margin-bottom: 15px;
            }
            .ezroadsmod-option {
                margin-bottom: 8px;
            }
            .ezroadsmod-radio-container {
                display: flex;
                align-items: center;
            }
            .ezroadsmod-radio-container input[type="radio"] {
                margin-right: 5px;
            }
            .ezroadsmod-radio-container label {
                flex: 1;
                margin-right: 10px;
                text-align: left;
            }
            .ezroadsmod-radio-container select {
                width: 80px;
                margin-left: auto;
                margin-right: 5px;
            }
            .ezroadsmod-radio-container input.road-speed {
                width: 60px;
            }
            .ezroadsmod-reset-button {
                margin-top: 20px;
                padding: 8px 12px;
                background-color: #f44336;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-weight: bold;
            }
            .ezroadsmod-reset-button:hover {
                background-color: #d32f2f;
            }
        </style>`);

        tabPane.innerHTML = '<div id="ezroadsmod-settings"></div>';
        const scriptContentPane = $('#ezroadsmod-settings');
		scriptContentPane.append(styles);

		// Header section
        const header = $(`<div class="ezroadsmod-section">

        <h2>EZRoads Mod</h2>
            <div>Current Version: <b>${scriptVersion}</b></div>
            <div>Update Keybind: <kbd>g</kbd></div>
        </div>`);
        scriptContentPane.append(header);


        // Road type and options header
        const roadTypeHeader = $(`<div class="ezroads-section">
            <div style="display: flex; align-items: center;">
                <div style="flex-grow: 1; text-align: center;">Road Type</div>
                <div style="width: 80px; text-align: center;">Lock</div>
                <div style="width: 60px; text-align: center;">Speed</div>
            </div>
        </div>`);
        scriptContentPane.append(roadTypeHeader);

        // Road type section with header
        const roadTypeSection = $(`<div class="ezroads-section">
            <div id="road-type-options"></div>
        </div>`);
        scriptContentPane.append(roadTypeSection);

        const roadTypeOptions = roadTypeSection.find('#road-type-options');
        roadTypes.forEach(roadType => {
            roadTypeOptions.append(createRadioButton(roadType));
        });
        // Additional options section
        const additionalSection = $(`<div class="ezroadsmod-section">
            <h5>Additional Options</h5>
            <div id="additional-options"></div>
        </div>`);
        scriptContentPane.append(additionalSection);

        const additionalOptions = additionalSection.find('#additional-options');
        checkboxOptions.forEach(option => {
            additionalOptions.append(createCheckbox(option));
        });

        // Update all lock dropdowns when setLock checkbox changes
        $(document).on('click', '#setLock', function () {
            const isChecked = $(this).prop('checked');
            $('.road-lock-level').prop('disabled', !isChecked);
        });

        // Update all speed inputs when updateSpeed checkbox changes
        $(document).on('click', '#updateSpeed', function () {
            const isChecked = $(this).prop('checked');
            $('.road-speed').prop('disabled', !isChecked);
            log('Speed update option changed to: ' + isChecked);
        });

        // Remove the separate lock levels section

        // Reset button section
        const resetButton = $(`<button class="ezroadsmod-reset-button">Reset All Options</button>`);
        resetButton.on('click', function () {
            if (confirm('Are you sure you want to reset all options to default values? It will reload the webpage!')) {
                resetOptions();
            }
        });
        scriptContentPane.append(resetButton);
    });
};
 function scriptupdatemonitor() {
        if (WazeWrap?.Ready) {
			bootstrap({ scriptUpdateMonitor: { downloadUrl } });
            WazeWrap.Interface.ShowScriptUpdate(scriptName, scriptVersion, updateMessage);
        } else {
            setTimeout(scriptupdatemonitor, 250);
        }
    }
    // Start the "scriptupdatemonitor"
    scriptupdatemonitor();
    console.log(`${scriptName} initialized.`);

    /*
Change Log

Version
2.4.5 - 2025-05-11
       Added Support for HRCS
	   Fixed -
        - Various other bugs
2.4.6 - 2025-05-20
       Added support for copying segment name from connected segment
	   Limitation -
        - Currently Alt names wont be copied.
2.4.7 - 2025-05-21
       Added support for copying alternative segment name from connected segment
	   Thanks to -
        - MapOmatic, without him this would not be possible.      
*/

})();