WME Circles

Kreise im WME erstellen - Updated for new Waze API

// ==UserScript==
// @name         WME Circles
// @namespace    http://tampermonkey.net/
// @version      2025.07.19
// @description  Kreise im WME erstellen - Updated for new Waze API
// @namespace    https://greasyfork.org/de/users/863740-horst-wittlich
// @author       Hiwi234
// @match        https://*.waze.com/editor*
// @match        https://*.waze.com/*/editor*
// @match        https://beta.waze.com/editor*
// @match        https://beta.waze.com/*/editor*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=waze.com
// @grant        none
// @license      MIT
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    let uOpenLayers;
    let uWaze;
    let radiusLayer, infoLayer;
    let polygonControl, freehandControl;

    function initializeScript() {
        uWaze = window.W;
        uOpenLayers = window.OpenLayers;
        
        if (!uWaze || !uOpenLayers || !uWaze.map) {
            console.log('[WME Circles] WME objects not ready, retrying...');
            setTimeout(initializeScript, 500);
            return;
        }

        console.log('[WME Circles] Initializing...');
        radiusInit();
    }

    function addSidePanel() {
        try {
            // Use new W.userscripts API
            const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("wme-circles");
            
            tabLabel.innerText = 'Circles';
            tabLabel.title = 'WME Circles';

            setupSidebarContent(tabPane);
        } catch (error) {
            console.log('[WME Circles] New API failed, using fallback:', error);
            addSidePanelFallback();
        }
    }

    function addSidePanelFallback() {
        // Fallback to manual DOM manipulation
        let userTabs = document.getElementById('user-info');
        if (!userTabs) {
            setTimeout(addSidePanelFallback, 1000);
            return;
        }

        let navTabs = userTabs.getElementsByClassName('nav-tabs')[0];
        let tabContent = userTabs.getElementsByClassName('tab-content')[0];

        if (!navTabs || !tabContent) {
            setTimeout(addSidePanelFallback, 1000);
            return;
        }

        let tab = document.createElement('li');
        tab.innerHTML = '<a href="#sidepanel-wme-circles" data-toggle="tab">Circles</a>';
        navTabs.appendChild(tab);

        let tabPane = document.createElement('section');
        tabPane.id = "sidepanel-wme-circles";
        tabPane.className = "tab-pane";
        tabContent.appendChild(tabPane);

        setupSidebarContent(tabPane);
    }

    function setupSidebarContent(tabPane) {
        tabPane.innerHTML = `
            <div style="padding: 15px;">
                <h3>WME Circles v2025</h3>
                <p id="circles-current-radius"></p>
                
                <div style="margin: 15px 0;">
                    <label style="display: flex; align-items: center; cursor: pointer; margin-bottom: 8px;">
                        <input type="checkbox" id="wme-circles-edit-mode" style="margin-right: 8px;">
                        Enable Circle Drawing
                    </label>
                    <label style="display: flex; align-items: center; cursor: pointer;">
                        <input type="checkbox" id="wme-freehand-edit-mode" style="margin-right: 8px;">
                        Enable Free Hand Drawing
                    </label>
                </div>
                
                <div style="display: flex; flex-direction: column; gap: 10px; margin-top: 20px;">
                    <button id="wme-circles-select" style="width: 100%; padding: 8px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">
                        Select Streets within
                    </button>
                    <button id="wme-circles-clear" style="width: 100%; padding: 8px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;">
                        Clear Circles
                    </button>
                </div>
                
                <div style="margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
                    <strong>Display Options:</strong>
                    <div style="margin-top: 12px;">
                        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
                            <span>Show Radius</span>
                            <label class="toggle-switch">
                                <input type="checkbox" id="show-radius" checked>
                                <span class="slider"></span>
                            </label>
                        </div>
                        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
                            <span>Show Diameter</span>
                            <label class="toggle-switch">
                                <input type="checkbox" id="show-diameter">
                                <span class="slider"></span>
                            </label>
                        </div>
                        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
                            <span>Show Area</span>
                            <label class="toggle-switch">
                                <input type="checkbox" id="show-area">
                                <span class="slider"></span>
                            </label>
                        </div>
                        <div style="display: flex; justify-content: space-between; align-items: center;">
                            <span>Imperial Units (ft/mi)</span>
                            <label class="toggle-switch">
                                <input type="checkbox" id="use-imperial">
                                <span class="slider"></span>
                            </label>
                        </div>
                    </div>
                </div>
                
                <div style="margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
                    <strong>Filter Options:</strong>
                    <div style="margin-top: 12px;">
                        <div style="display: flex; justify-content: space-between; align-items: center;">
                            <span>Only drivable segments</span>
                            <label class="toggle-switch">
                                <input type="checkbox" id="filter-drivable-only" checked>
                                <span class="slider"></span>
                            </label>
                        </div>
                    </div>
                </div>
                
                <div style="margin-top: 20px; padding: 10px; background: #e8f5e8; border-radius: 4px;">
                    <strong>Perimeter Blocking:</strong>
                    <div style="margin-top: 12px;">
                        <div style="display: flex; justify-content: space-between; align-items: center;">
                            <div>
                                <div>Block perimeter inbound only</div>
                                <div style="font-size: 11px; color: #666;">(hollow circle - exit allowed)</div>
                            </div>
                            <label class="toggle-switch">
                                <input type="checkbox" id="block-inbound-only">
                                <span class="slider"></span>
                            </label>
                        </div>
                    </div>
                </div>
                
                <style>
                    .toggle-switch {
                        position: relative;
                        display: inline-block;
                        width: 44px;
                        height: 24px;
                    }
                    
                    .toggle-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: 24px;
                    }
                    
                    .slider:before {
                        position: absolute;
                        content: "";
                        height: 18px;
                        width: 18px;
                        left: 3px;
                        bottom: 3px;
                        background-color: white;
                        transition: .3s;
                        border-radius: 50%;
                        box-shadow: 0 2px 4px rgba(0,0,0,0.2);
                    }
                    
                    input:checked + .slider {
                        background-color: #4CAF50;
                    }
                    
                    input:checked + .slider:before {
                        transform: translateX(20px);
                    }
                    
                    .slider:hover {
                        box-shadow: 0 0 8px rgba(76, 175, 80, 0.3);
                    }
                </style>
            </div>
        `;

        // Get UI elements
        let checkbox = tabPane.querySelector('#wme-circles-edit-mode');
        let freehandCheckbox = tabPane.querySelector('#wme-freehand-edit-mode');
        let clearButton = tabPane.querySelector('#wme-circles-clear');
        let selectButton = tabPane.querySelector('#wme-circles-select');
        let showRadiusCheckbox = tabPane.querySelector('#show-radius');
        let showDiameterCheckbox = tabPane.querySelector('#show-diameter');
        let showAreaCheckbox = tabPane.querySelector('#show-area');
        let useImperialCheckbox = tabPane.querySelector('#use-imperial');
        let filterDrivableCheckbox = tabPane.querySelector('#filter-drivable-only');
        let blockInboundCheckbox = tabPane.querySelector('#block-inbound-only');

        // Formatting functions
        function formatLength(meters, useImperial = false) {
            if (useImperial) {
                let feet = meters * 3.28084;
                if (feet >= 5280) {
                    let miles = feet / 5280;
                    return `${Math.round(miles * 100) / 100} mi`;
                } else {
                    return `${Math.round(feet)} ft`;
                }
            } else {
                if (meters > 1000) {
                    return `${Math.round((meters / 1000) * 100) / 100} km`;
                } else {
                    return `${Math.round(meters)} m`;
                }
            }
        }

        function formatArea(squareMeters, useImperial = false) {
            if (useImperial) {
                let squareFeet = squareMeters * 10.7639;
                if (squareFeet >= 27878400) { // 1 square mile
                    let squareMiles = squareFeet / 27878400;
                    return `${Math.round(squareMiles * 100) / 100} mi²`;
                } else if (squareFeet >= 43560) { // 1 acre
                    let acres = squareFeet / 43560;
                    return `${Math.round(acres * 100) / 100} ac`;
                } else {
                    return `${Math.round(squareFeet)} ft²`;
                }
            } else {
                if (squareMeters > 1000000) {
                    return `${Math.round((squareMeters / 1000000) * 100) / 100} km²`;
                } else {
                    return `${Math.round(squareMeters)} m²`;
                }
            }
        }

        function updateAllAnnotations() {
            infoLayer.destroyFeatures();
            
            for (let feature of radiusLayer.features) {
                if (feature.geometry.CLASS_NAME === "OpenLayers.Geometry.Polygon") {
                    let components = feature.geometry.components[0].components;
                    if (components.length > 90) {
                        addCircleAnnotations(feature);
                    } else {
                        addFreehandAnnotations(feature);
                    }
                }
            }
        }

        function addCircleAnnotations(f) {
            let minX = f.geometry.bounds.left;
            let minY = f.geometry.bounds.bottom;
            let maxX = f.geometry.bounds.right;
            let maxY = f.geometry.bounds.top;

            let startX = (minX + maxX) / 2;
            let startY = (minY + maxY) / 2;

            let startPoint = new uOpenLayers.Geometry.Point(startX, startY);
            let endPoint = new uOpenLayers.Geometry.Point(maxX, startY);
            let radius = new uOpenLayers.Geometry.LineString([startPoint, endPoint]);
            let len = radius.getGeodesicLength(new uOpenLayers.Projection("EPSG:900913"));

            let area = Math.PI * len * len;

            let showRadius = showRadiusCheckbox?.checked;
            let showDiameter = showDiameterCheckbox?.checked;
            let showArea = showAreaCheckbox?.checked;
            let useImperial = useImperialCheckbox?.checked;

            let centerStyle = {
                strokeColor: "#c40606",
                strokeWidth: 2,
                pointRadius: 5,
                fillOpacity: 0.2
            };

            let labelText = [];

            if (showRadius) {
                labelText.push('R: ' + formatLength(len, useImperial));
            }

            if (showDiameter) {
                labelText.push('Ø: ' + formatLength(len * 2, useImperial));
            }

            if (showArea) {
                labelText.push('A: ' + formatArea(area, useImperial));
            }

            let lineStyle = {
                strokeColor: "#c40606",
                strokeWidth: 3,
                label: labelText.join(' | '),
                labelAlign: "left",
                labelXOffset: "20",
                labelYOffset: "10",
                labelOutlineColor: "white",
                labelOutlineWidth: 3
            };

            let center = new uOpenLayers.Feature.Vector(startPoint, {}, centerStyle);
            if (labelText.length > 0) {
                let radiusLine = new uOpenLayers.Feature.Vector(radius, { 'length': len }, lineStyle);
                infoLayer.addFeatures([center, radiusLine]);
            } else {
                infoLayer.addFeatures([center]);
            }
        }

        function addFreehandAnnotations(f) {
            let bounds = f.geometry.getBounds();
            let minX = bounds.left;
            let minY = bounds.bottom;
            let maxX = bounds.right;
            let maxY = bounds.top;

            let centerX = (minX + maxX) / 2;
            let centerY = (minY + maxY) / 2;
            let width = maxX - minX;
            let height = maxY - minY;
            let diameter = Math.max(width, height);

            let centerPoint = new uOpenLayers.Geometry.Point(centerX, centerY);
            let endPoint = new uOpenLayers.Geometry.Point(centerX + diameter/2, centerY);
            let diameterLine = new uOpenLayers.Geometry.LineString([centerPoint, endPoint]);
            let len = diameterLine.getGeodesicLength(new uOpenLayers.Projection("EPSG:900913"));

            let area = f.geometry.getArea();

            let showRadius = showRadiusCheckbox?.checked;
            let showDiameter = showDiameterCheckbox?.checked;
            let showArea = showAreaCheckbox?.checked;
            let useImperial = useImperialCheckbox?.checked;

            let centerStyle = {
                strokeColor: "#c40606",
                strokeWidth: 2,
                pointRadius: 5,
                fillOpacity: 0.2
            };

            let labelText = [];

            if (showRadius) {
                labelText.push('R: ' + formatLength(len / 2, useImperial));
            }

            if (showDiameter) {
                labelText.push('Ø: ' + formatLength(len, useImperial));
            }

            if (showArea) {
                labelText.push('A: ' + formatArea(area, useImperial));
            }

            let lineStyle = {
                strokeColor: "#c40606",
                strokeWidth: 3,
                label: labelText.join(' | '),
                labelAlign: "left",
                labelXOffset: "20",
                labelYOffset: "10",
                labelOutlineColor: "white",
                labelOutlineWidth: 3
            };

            let center = new uOpenLayers.Feature.Vector(centerPoint, {}, centerStyle);
            if (labelText.length > 0) {
                let diameterLineFeature = new uOpenLayers.Feature.Vector(diameterLine, { 'diameter': len }, lineStyle);
                infoLayer.addFeatures([center, diameterLineFeature]);
            } else {
                infoLayer.addFeatures([center]);
            }
        }

        // Event handlers
        showRadiusCheckbox.addEventListener('change', updateAllAnnotations);
        showDiameterCheckbox.addEventListener('change', updateAllAnnotations);
        showAreaCheckbox.addEventListener('change', updateAllAnnotations);
        useImperialCheckbox.addEventListener('change', updateAllAnnotations);

        checkbox.addEventListener('click', (e) => {
            if (e.target.checked) {
                freehandCheckbox.checked = false;
                freehandControl.deactivate();
                polygonControl.activate();
            } else {
                polygonControl.deactivate();
            }
        });

        freehandCheckbox.addEventListener('click', (e) => {
            if (e.target.checked) {
                checkbox.checked = false;
                polygonControl.deactivate();
                freehandControl.activate();
            } else {
                freehandControl.deactivate();
            }
        });

        clearButton.addEventListener('click', (e) => {
            infoLayer.destroyFeatures();
            radiusLayer.destroyFeatures();
            checkbox.checked = false;
            freehandCheckbox.checked = false;
            polygonControl.deactivate();
            freehandControl.deactivate();
        });

        selectButton.addEventListener('click', (e) => {
            const toSelect = [];
            const segments = uWaze.model.segments.getObjectArray();
            const blockInboundOnly = blockInboundCheckbox?.checked;
            
            if (blockInboundOnly) {
                // Find perimeter segments only - create hollow circle
                console.log('[WME Circles] Finding perimeter segments for hollow circle blocking...');
                
                for (const drawnFeature of radiusLayer.features) {
                    const circleGeometry = drawnFeature.geometry;
                    const bounds = circleGeometry.getBounds();
                    const circleCenter = {
                        x: (bounds.left + bounds.right) / 2,
                        y: (bounds.bottom + bounds.top) / 2
                    };
                    
                    // Calculate circle radius for perimeter detection
                    const radius = Math.abs(bounds.right - bounds.left) / 2;
                    const perimeterTolerance = radius * 0.15; // 15% tolerance for perimeter detection
                    
                    for (const segment of segments) {
                        if (!segment.attributes?.roadType) continue;

                        if (filterDrivableCheckbox.checked) {
                            const type = segment.attributes.roadType;
                            if (type < 1 || type > 6) continue;
                        }

                        const segGeom = segment.getOLGeometry ? segment.getOLGeometry() : segment.geometry;
                        if (!segGeom) continue;

                        // Check if segment intersects with circle
                        if (circleGeometry.intersects(segGeom)) {
                            // Calculate segment's distance from circle center
                            const coords = segGeom.components || segGeom.getVertices();
                            if (!coords || coords.length < 2) continue;
                            
                            // Check multiple points along segment
                            let isPerimeterSegment = false;
                            const checkPoints = Math.min(coords.length, 5);
                            
                            for (let i = 0; i < checkPoints; i++) {
                                const point = coords[Math.floor(i * (coords.length - 1) / (checkPoints - 1))];
                                const distFromCenter = Math.sqrt(
                                    Math.pow(point.x - circleCenter.x, 2) + 
                                    Math.pow(point.y - circleCenter.y, 2)
                                );
                                
                                // Check if point is near the perimeter (within tolerance of radius)
                                if (Math.abs(distFromCenter - radius) <= perimeterTolerance) {
                                    isPerimeterSegment = true;
                                    break;
                                }
                            }
                            
                            if (isPerimeterSegment) {
                                if (shouldBlockSegmentInbound(segment, segGeom, circleCenter)) {
                                    toSelect.push(segment);
                                }
                            }
                        }
                    }
                }
            } else {
                // Original logic - all segments within circle
                for (const segment of segments) {
                    if (!segment.attributes?.roadType) continue;

                    if (filterDrivableCheckbox.checked) {
                        const type = segment.attributes.roadType;
                        if (type < 1 || type > 6) continue;
                    }

                    const segGeom = segment.getOLGeometry ? segment.getOLGeometry() : segment.geometry;
                    if (!segGeom) continue;

                    let circleCenter = null;
                    let isInCircle = false;

                    for (const drawnFeature of radiusLayer.features) {
                        if (drawnFeature.geometry.intersects(segGeom)) {
                            isInCircle = true;
                            const bounds = drawnFeature.geometry.getBounds();
                            circleCenter = {
                                x: (bounds.left + bounds.right) / 2,
                                y: (bounds.bottom + bounds.top) / 2
                            };
                            break;
                        }
                    }

                    if (isInCircle) {
                        toSelect.push(segment);
                    }
                }
            }

            console.log(`[WME Circles] Selected ${toSelect.length} segments${blockInboundOnly ? ' (perimeter only)' : ''}`);

            if (toSelect.length > 0) {
                if (blockInboundOnly) {
                    // Apply direction restrictions to perimeter segments
                    applyDirectionRestrictions(toSelect);
                } else {
                    // Regular selection
                    uWaze.selectionManager.setSelectedModels(toSelect);
                }
            }

            checkbox.checked = false;
            freehandCheckbox.checked = false;
            polygonControl.deactivate();
            freehandControl.deactivate();
        });

        function shouldBlockSegmentInbound(segment, segGeom, circleCenter) {
            const coords = segGeom.components || segGeom.getVertices();
            if (!coords || coords.length < 2) {
                console.log(`[WME Circles] No valid coordinates for segment ${segment.getID()}`);
                return false;
            }
            
            const startPoint = coords[0];
            const endPoint = coords[coords.length - 1];
            
            // Calculate which end is closer to circle center
            const startDist = Math.sqrt(
                Math.pow(startPoint.x - circleCenter.x, 2) + 
                Math.pow(startPoint.y - circleCenter.y, 2)
            );
            const endDist = Math.sqrt(
                Math.pow(endPoint.x - circleCenter.x, 2) + 
                Math.pow(endPoint.y - circleCenter.y, 2)
            );
            
            segment._circleDirectionInfo = {
                startPoint: startPoint,
                endPoint: endPoint,
                startDist: startDist,
                endDist: endDist,
                circleCenter: circleCenter
            };
            
            console.log(`[WME Circles] Set direction info for segment ${segment.getID()}: startDist=${Math.round(startDist)}, endDist=${Math.round(endDist)}`);
            
            return true;
        }

        function applyDirectionRestrictions(segments) {
            console.log(`[WME Circles] Applying direction restrictions to ${segments.length} segments`);
            
            // First select the segments
            uWaze.selectionManager.setSelectedModels(segments);
            
            // Add delay to ensure selection is processed
            setTimeout(() => {
                let successCount = 0;
                let modifiedCount = 0;
                
                // Apply restrictions to each segment
                for (const segment of segments) {
                    try {
                        const dirInfo = segment._circleDirectionInfo;
                        if (!dirInfo) {
                            console.log(`[WME Circles] No direction info for segment ${segment.getID()}, skipping`);
                            continue;
                        }
                        
                        const currentFwd = segment.attributes.fwdDirection;
                        const currentRev = segment.attributes.revDirection;
                        
                        // Only modify bidirectional segments
                        if (!currentFwd || !currentRev) {
                            console.log(`[WME Circles] Segment ${segment.getID()}: Already one-way, skipping`);
                            continue;
                        }
                        
                        // FIXED: Simplified direction logic
                        // If start is closer to center: Block REVERSE (B->A), Keep FORWARD (A->B) 
                        // If end is closer to center: Block FORWARD (A->B), Keep REVERSE (B->A)
                        
                        let newFwd = currentFwd;
                        let newRev = currentRev;
                        
                        if (dirInfo.startDist < dirInfo.endDist) {
                            // A is closer to center than B
                            // Traffic A->B goes TO center (BLOCK)  
                            // Traffic B->A goes AWAY from center (KEEP)
                            newFwd = false;  // Block A->B (inbound)
                            newRev = true;   // Keep B->A (outbound)
                            console.log(`[WME Circles] Segment ${segment.getID()}: A closer to center - blocking A->B (inbound), keeping B->A (outbound)`);
                        } else {
                            // B is closer to center than A  
                            // Traffic A->B goes AWAY from center (KEEP)
                            // Traffic B->A goes TO center (BLOCK)
                            newFwd = true;   // Keep A->B (outbound)
                            newRev = false;  // Block B->A (inbound)
                            console.log(`[WME Circles] Segment ${segment.getID()}: B closer to center - keeping A->B (outbound), blocking B->A (inbound)`);
                        }
                        
                        // Only apply if there's a change needed
                        if (newFwd !== currentFwd || newRev !== currentRev) {
                            try {
                                // Check if user has edit permissions for this segment
                                if (!segment.arePropertiesEditable || !segment.arePropertiesEditable()) {
                                    console.warn(`[WME Circles] No edit permissions for segment ${segment.getID()}`);
                                    continue;
                                }
                                
                                // Direct model update using WME's internal methods
                                const modelSegment = W.model.segments.getObjectById(segment.getID());
                                if (modelSegment) {
                                    // Set attributes directly and mark as modified
                                    modelSegment.attributes.fwdDirection = newFwd;
                                    modelSegment.attributes.revDirection = newRev;
                                    
                                    // Mark segment as modified for save system
                                    modelSegment._hasUnsavedAttributes = true;
                                    if (modelSegment.changed) {
                                        modelSegment.changed.fwdDirection = true;
                                        modelSegment.changed.revDirection = true;
                                    } else {
                                        modelSegment.changed = {
                                            fwdDirection: true,
                                            revDirection: true
                                        };
                                    }
                                    
                                    // Trigger UI update
                                    modelSegment.trigger('change:fwdDirection');
                                    modelSegment.trigger('change:revDirection');
                                    modelSegment.trigger('change');
                                    
                                    // Let WME handle map updates naturally
                                    
                                    modifiedCount++;
                                    console.log(`[WME Circles] ✓ Modified segment ${segment.getID()}: fwd=${newFwd}, rev=${newRev}`);
                                } else {
                                    console.warn(`[WME Circles] Model segment not found: ${segment.getID()}`);
                                }
                                
                            } catch (error) {
                                console.warn(`[WME Circles] Failed to modify segment ${segment.getID()}:`, error);
                            }
                        }
                        
                        successCount++;
                        
                        // Clean up temporary data
                        delete segment._circleDirectionInfo;
                        
                    } catch (error) {
                        console.warn('[WME Circles] Error processing segment:', error);
                    }
                }
                
                console.log(`[WME Circles] Processed ${successCount}/${segments.length} segments, modified ${modifiedCount} segments`);
                
                // Show user feedback
                const statusElement = document.getElementById('circles-current-radius');
                if (statusElement) {
                    if (modifiedCount > 0) {
                        statusElement.innerHTML = `✓ Applied direction restrictions to ${modifiedCount} segments (outbound traffic preserved)`;
                        statusElement.style.color = '#4CAF50';
                    } else {
                        statusElement.innerHTML = `⚠ No segments needed modification (${successCount} processed)`;
                        statusElement.style.color = '#ff9800';
                    }
                    
                    // Clear message after 4 seconds
                    setTimeout(() => {
                        statusElement.innerHTML = '';
                        statusElement.style.color = '';
                    }, 4000);
                }
                
                // Force map refresh without causing errors
                try {
                    // Only trigger minimal updates
                    if (W.model && W.model.segments) {
                        W.model.segments.trigger('change');
                    }
                } catch (e) {
                    // Ignore render errors
                }
                
            }, 500);
        }
    }

    function radiusInit() {
        radiusLayer = new uOpenLayers.Layer.Vector("WME Circle Control Layer", {
            displayInLayerSwitcher: true,
            uniqueName: "__CircleControlLayer",
            visibility: true,
            style: {
                "fillColor": "#c40606",
                "fillOpacity": 0.2,
                "strokeColor": "#c40606",
                "strokeOpacity": 1,
                "strokeWidth": 1,
                "strokeLinecap": "round",
                "strokeDashstyle": "solid",
                "pointRadius": 6,
                "pointerEvents": "visiblePainted",
                "labelAlign": "cm",
                "labelOutlineColor": "white",
                "labelOutlineWidth": 3
            }
        });

        infoLayer = new uOpenLayers.Layer.Vector("WME Circle Visual Layer", {
            displayInLayerSwitcher: true,
            uniqueName: "__DrawCircleDisplayLayer",
            visibility: true
        });

        let polygonHandler = uOpenLayers.Handler.RegularPolygon;
        polygonControl = new uOpenLayers.Control.DrawFeature(radiusLayer, polygonHandler, {
            handlerOptions: {
                sides: 100
            }
        });

        let polygonHandlerFreehand = uOpenLayers.Handler.Polygon;
        freehandControl = new uOpenLayers.Control.DrawFeature(radiusLayer, polygonHandlerFreehand, {
            handlerOptions: {
                freehand: true,
                freehandToggle: null
            }
        });

        uWaze.map.addLayer(radiusLayer);
        uWaze.map.addLayer(infoLayer);
        uWaze.map.addControl(polygonControl);
        uWaze.map.addControl(freehandControl);

        addSidePanel();

        polygonControl.handler.callbacks.move = function (e) {
            let linearRing = new uOpenLayers.Geometry.LinearRing(e.components[0].components);
            let geometry = new uOpenLayers.Geometry.Polygon([linearRing]);
            let polygonFeature = new uOpenLayers.Feature.Vector(geometry, null);
            let polybounds = polygonFeature.geometry.getBounds();
            let minX = polybounds.left;
            let minY = polybounds.bottom;
            let maxX = polybounds.right;
            let maxY = polybounds.top;
            let startX = (minX + maxX) / 2;
            let startY = (minY + maxY) / 2;
            let startPoint = new uOpenLayers.Geometry.Point(startX, startY);
            let endPoint = new uOpenLayers.Geometry.Point(maxX, startY);
            let radius = new uOpenLayers.Geometry.LineString([startPoint, endPoint]);
            let len = radius.getGeodesicLength(new uOpenLayers.Projection("EPSG:900913"));

            let unit = 'm';
            if (len > 1000) {
                len = Math.round((len / 1000) * 100) / 100;
                unit = "km";
            } else {
                len = Math.round(len);
            }

            let rad = document.getElementById('circles-current-radius');
            if (rad) {
                rad.innerHTML = 'Current Radius: ' + len + ' ' + unit;
            }

            infoLayer.destroyFeatures(); 
            
            let centerStyle = {
                strokeColor: "#ff9800",
                strokeWidth: 2,
                pointRadius: 3,
                fillOpacity: 0.3
            };

            let lineStyle = {
                strokeColor: "#ff9800",
                strokeWidth: 2,
                label: len + ' ' + unit,
                labelAlign: "left",
                labelXOffset: "15",
                labelYOffset: "5",
                labelOutlineColor: "white",
                labelOutlineWidth: 2,
                strokeDashstyle: "dash"
            };

            let center = new uOpenLayers.Feature.Vector(startPoint, {}, centerStyle);
            let radiusLine = new uOpenLayers.Feature.Vector(radius, {}, lineStyle);

            infoLayer.addFeatures([center, radiusLine]);
        }

        polygonControl.events.on({
            'featureadded': function (e) {
                let rad = document.getElementById('circles-current-radius');
                if (rad) {
                    rad.innerHTML = '';
                }
                setTimeout(() => {
                    // Trigger update from within tabPane scope
                    const event = new CustomEvent('updateAnnotations');
                    document.dispatchEvent(event);
                }, 100);
            }
        });

        freehandControl.events.on({
            'featureadded': function (e) {
                let rad = document.getElementById('circles-current-radius');
                if (rad) {
                    rad.innerHTML = '';
                }
                setTimeout(() => {
                    // Trigger update from within tabPane scope
                    const event = new CustomEvent('updateAnnotations');
                    document.dispatchEvent(event);
                }, 100);
            }
        });

        // Listen for custom update events
        document.addEventListener('updateAnnotations', () => {
            // This will call the updateAllAnnotations function from the sidebar scope
            const updateEvent = new CustomEvent('forceUpdate');
            document.dispatchEvent(updateEvent);
        });
    }

    // Use the new W.userscripts.state API
    if (W?.userscripts?.state?.isReady) {
        console.log('[WME Circles] WME already ready, initializing...');
        initializeScript();
    } else {
        console.log('[WME Circles] Waiting for WME ready event...');
        document.addEventListener("wme-ready", initializeScript, { once: true });
    }

})();