WME Quick PP Importer

Quickly add place points based on open address data sources.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name        WME Quick PP Importer
// @namespace   https://github.com/gk1220
// @version     2026.04.14.00
// @description Quickly add place points based on open address data sources.
// @author      Gerhard (g1220k)
// @homepageURL https://github.com/gk1220/wme-quick-pp-importer
// @supportURL  https://github.com/gk1220/wme-quick-pp-importer/issues
// @match       https://www.waze.com/editor*
// @match       https://beta.waze.com/editor*
// @match       https://www.waze.com/*/editor*
// @match       https://beta.waze.com/*/editor*
// @exclude     https://www.waze.com/user/editor*
// @exclude     https://beta.waze.com/user/editor*
// @connect     wms.kbox.at
// @license     GPL-3.0
// @grant       GM_xmlhttpRequest
// @grant       unsafeWindow
// ==/UserScript==
(function () {
    'use strict';

    class AppState {
        wmeSDK = null;
        importState = {
            isActive: false,
            isPaused: false,
            selectedSegmentIds: [],
            loadedAddresses: [],
            selectedAddresses: new Map(),
        };
        config = {
            apiBaseUrl: "https://wms.kbox.at",
            searchRadius: 0.5,
            autoFillDistance: 50,
        };
        listeners = new Map();
        setWmeSDK(sdk) {
            this.wmeSDK = sdk;
        }
        getWmeSDK() {
            if (!this.wmeSDK) {
                throw new Error("WME SDK not initialized");
            }
            return this.wmeSDK;
        }
        activateImport() {
            this.importState.isActive = true;
            this.emit("importActivated");
        }
        deactivateImport() {
            this.importState.isActive = false;
            this.importState.isPaused = false;
            this.emit("importDeactivated");
            this.clearAddresses();
        }
        togglePause() {
            this.importState.isPaused = !this.importState.isPaused;
            this.emit(this.importState.isPaused ? "importPaused" : "importResumed");
        }
        _debugMode = false;
        get debugMode() { return this._debugMode; }
        setDebugMode(val) {
            this._debugMode = val;
            console.log(`🔧 Debug Mode: ${val ? 'AN' : 'AUS'}`);
        }
        getImportState() {
            return { ...this.importState };
        }
        setSelectedSegments(segmentIds) {
            this.importState.selectedSegmentIds = segmentIds;
            this.emit("segmentsSelected", segmentIds);
        }
        getSelectedSegments() {
            return [...this.importState.selectedSegmentIds];
        }
        setAddresses(addresses) {
            this.importState.loadedAddresses = addresses;
            this.emit("addressesLoaded", addresses);
        }
        getAddresses() {
            return [...this.importState.loadedAddresses];
        }
        getAddressById(id) {
            return this.importState.loadedAddresses.find(a => a.id === id);
        }
        markAddressProcessed(id) {
            const address = this.importState.loadedAddresses.find(a => a.id === id);
            if (address) {
                address.status = 'lightGreen';
                this.emit("addressUpdated", address);
            }
        }
        clearAddresses() {
            this.importState.loadedAddresses = [];
            this.importState.selectedAddresses.clear();
            this.emit("addressesCleared");
        }
        selectAddress(address) {
            this.importState.selectedAddresses.set(address.id, address);
            this.emit("addressSelected", address);
        }
        removeSelectedAddress(addressId) {
            this.importState.selectedAddresses.delete(addressId);
            this.emit("addressDeselected", addressId);
        }
        getSelectedAddresses() {
            return Array.from(this.importState.selectedAddresses.values());
        }
        getConfig() {
            return { ...this.config };
        }
        updateConfig(partial) {
            this.config = { ...this.config, ...partial };
            this.emit("configUpdated", this.config);
        }
        on(event, listener) {
            if (!this.listeners.has(event)) {
                this.listeners.set(event, new Set());
            }
            this.listeners.get(event).add(listener);
            return () => {
                this.listeners.get(event)?.delete(listener);
            };
        }
        emit(event, ...args) {
            const eventListeners = this.listeners.get(event);
            if (eventListeners) {
                eventListeners.forEach(listener => listener(...args));
            }
        }
        logState() {
            console.group("🔍 Quick PP Importer State");
            console.log("Import Active:", this.importState.isActive);
            console.log("Paused:", this.importState.isPaused);
            console.log("Selected Segments:", this.importState.selectedSegmentIds.length);
            console.log("Loaded Addresses:", this.importState.loadedAddresses.length);
            console.log("Selected Addresses:", this.importState.selectedAddresses.size);
            console.log("Config:", this.config);
            console.groupEnd();
        }
    }
    const appState = new AppState();
    function debug(...args) {
        if (appState.debugMode)
            console.log(...args);
    }

    const TILE = {
        SIZE_M: 750,
        TTL_DAYS: 7,
        MAX: 300,
        NS: 'WME_PP_TILE_',
        META: 'WME_PP_META'
    };
    const hasGM = typeof GM_getValue === 'function' && typeof GM_setValue === 'function';
    const memTiles = new Map();
    class AddressDataClient {
        baseUrl = "https://wms.kbox.at";
        apiPath = "/adr";
        requestQueue = Promise.resolve();
        tileKeyForXY(x, y) {
            return `${Math.floor(x / TILE.SIZE_M)}_${Math.floor(y / TILE.SIZE_M)}`;
        }
        tilesForBounds(bounds) {
            const x1 = Math.floor(bounds.left / TILE.SIZE_M);
            const y1 = Math.floor(bounds.bottom / TILE.SIZE_M);
            const x2 = Math.floor(bounds.right / TILE.SIZE_M);
            const y2 = Math.floor(bounds.top / TILE.SIZE_M);
            const keys = [];
            for (let ty = y1; ty <= y2; ty += 1) {
                for (let tx = x1; tx <= x2; tx += 1) {
                    keys.push(`${tx}_${ty}`);
                }
            }
            return keys;
        }
        bboxFromTiles(keys) {
            let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
            for (const k of keys) {
                const [txS, tyS] = k.split('_');
                const tx = +txS;
                const ty = +tyS;
                const left = tx * TILE.SIZE_M;
                const bottom = ty * TILE.SIZE_M;
                const right = left + TILE.SIZE_M;
                const top = bottom + TILE.SIZE_M;
                minX = Math.min(minX, left);
                minY = Math.min(minY, bottom);
                maxX = Math.max(maxX, right);
                maxY = Math.max(maxY, top);
            }
            return { x1: Math.floor(minX), y1: Math.floor(minY), x2: Math.ceil(maxX), y2: Math.ceil(maxY) };
        }
        nowDays() {
            return Math.floor(Date.now() / 86400000);
        }
        getTileFromStore(key) {
            const m = memTiles.get(key);
            if (m)
                return m;
            if (!hasGM)
                return null;
            try {
                const raw = GM_getValue(TILE.NS + key, null);
                if (!raw)
                    return null;
                const obj = JSON.parse(raw);
                memTiles.set(key, obj);
                return obj;
            }
            catch {
                return null;
            }
        }
        putTileToStore(key, obj) {
            memTiles.set(key, obj);
            if (!hasGM)
                return;
            try {
                GM_setValue(TILE.NS + key, JSON.stringify(obj));
                const meta = this.loadMeta();
                this.touchLRU(meta, key);
                this.enforceLRU(meta);
                this.saveMeta(meta);
            }
            catch { }
        }
        loadMeta() {
            if (!hasGM)
                return { order: [] };
            try {
                const m = GM_getValue(TILE.META, null);
                return m ? JSON.parse(m) : { order: [] };
            }
            catch {
                return { order: [] };
            }
        }
        saveMeta(meta) {
            if (!hasGM)
                return;
            try {
                GM_setValue(TILE.META, JSON.stringify(meta));
            }
            catch { }
        }
        touchLRU(meta, key) {
            meta.order = (meta.order || []).filter((k) => k !== key);
            meta.order.push(key);
        }
        enforceLRU(meta) {
            while ((meta.order || []).length > TILE.MAX) {
                const victim = meta.order.shift();
                try {
                    GM_deleteValue(TILE.NS + victim);
                }
                catch { }
                memTiles.delete(victim);
            }
        }
        isFresh(tileObj) {
            return !!(tileObj && typeof tileObj.ts === 'number' && this.nowDays() - tileObj.ts <= TILE.TTL_DAYS);
        }
        clearCache() {
            try {
                if (hasGM) {
                    GM_listValues().forEach(k => {
                        if (String(k).startsWith(TILE.NS) || k === TILE.META) {
                            GM_deleteValue(k);
                        }
                    });
                }
                memTiles.clear();
                debug('🗑️ PP Cache cleared');
            }
            catch (e) {
                console.error('❌ clearCache error', e);
            }
        }
        async fetchAddressesByBoundingBox(left, bottom, right, top) {
            try {
                debug(`📍 Fetching addresses: bbox=[${left},${bottom},${right},${top}]`);
                const webMercatorBounds = {
                    left: this.lonToWebMercator(left),
                    bottom: this.latToWebMercator(bottom),
                    right: this.lonToWebMercator(right),
                    top: this.latToWebMercator(top)
                };
                debug(`📍 Converted bbox to Web Mercator: [${webMercatorBounds.left},${webMercatorBounds.bottom},${webMercatorBounds.right},${webMercatorBounds.top}]`);
                const neededKeys = this.tilesForBounds(webMercatorBounds);
                let allFresh = true;
                let assembled = [];
                for (const key of neededKeys) {
                    const tile = this.getTileFromStore(key);
                    debug(`💾 Checking cache for tile ${key}:`, tile ? 'found' : 'not found');
                    if (tile) {
                        debug(`💾 Tile ${key} fresh:`, this.isFresh(tile));
                    }
                    if (!this.isFresh(tile)) {
                        allFresh = false;
                        break;
                    }
                    if (tile?.items?.length) {
                        assembled = assembled.concat(tile.items);
                    }
                }
                if (allFresh) {
                    debug(`💾 Cache hit (${neededKeys.length} tile(s)) - skipping network`);
                    return this.processRawAddresses(assembled);
                }
                debug(`🌐 Cache miss - fetching ${neededKeys.length} tile(s) from network`);
                const addresses = await this.fetchTilesFromNetwork(neededKeys);
                return addresses;
            }
            catch (error) {
                console.error("❌ Error fetching addresses:", error);
                return [];
            }
        }
        async fetchTilesFromNetwork(neededKeys) {
            return new Promise((resolve, reject) => {
                const body = this.bboxFromTiles(neededKeys);
                debug(`🔗 Requesting tiles:`, body);
                GM_xmlhttpRequest({
                    method: "POST",
                    url: this.baseUrl + this.apiPath,
                    data: JSON.stringify(body),
                    headers: { "Content-Type": "application/json" },
                    timeout: 10000,
                    onload: (response) => {
                        try {
                            if (response.status >= 200 && response.status < 300) {
                                let result;
                                try {
                                    result = JSON.parse(response.responseText || '[]');
                                }
                                catch (e) {
                                    console.error('❌ JSON parse fail', e, response.responseText);
                                    reject(new Error(`API JSON parse error: ${e}`));
                                    return;
                                }
                                debug(`📦 Parsed response:`, result);
                                debug(`📦 Is array:`, Array.isArray(result));
                                if (!Array.isArray(result)) {
                                    console.error(`❌ API Response is not an array:`, result);
                                    reject(new Error(`API returned unexpected format: expected array of addresses`));
                                    return;
                                }
                                const buckets = new Map();
                                for (const r of result) {
                                    const x = r.lon;
                                    const y = r.lat;
                                    const key = this.tileKeyForXY(x, y);
                                    if (!buckets.has(key))
                                        buckets.set(key, []);
                                    buckets.get(key).push({
                                        lon: x,
                                        lat: y,
                                        strassenname: r.strassenname || r.sn || "",
                                        hausnummerzahl1: r.hausnummerzahl1 || r.hn || "",
                                        gemeinde: r.gemeinde || r.gn || ""
                                    });
                                }
                                const today = this.nowDays();
                                let assembled = [];
                                for (const k of neededKeys) {
                                    const items = buckets.get(k) || [];
                                    this.putTileToStore(k, { ts: today, items });
                                    assembled = assembled.concat(items);
                                }
                                debug(`✅ Loaded ${assembled.length} addresses from ${neededKeys.length} tiles`);
                                resolve(this.processRawAddresses(assembled));
                            }
                            else {
                                console.error(`❌ API Error Status ${response.status}: ${response.responseText}`);
                                reject(new Error(`API Error: ${response.status} - ${response.statusText}`));
                            }
                        }
                        catch (error) {
                            console.error(`❌ Parse error:`, error);
                            reject(error);
                        }
                    },
                    onerror: (error) => {
                        console.error(`❌ GM_xmlhttpRequest error:`, error);
                        const errorMsg = error && error.message ? error.message : String(error);
                        reject(new Error(`Network error: ${errorMsg}`));
                    },
                    ontimeout: () => {
                        console.error(`❌ Request timeout`);
                        reject(new Error("Request timeout"));
                    }
                });
            });
        }
        processRawAddresses(rawAddresses) {
            return rawAddresses.map((raw, index) => {
                const [longitude, latitude] = this.webMercatorToLonLat(raw.lon, raw.lat);
                return {
                    id: `addr-${Date.now()}-${index}`,
                    latitude,
                    longitude,
                    streetName: raw.strassenname || raw.sn || "",
                    houseNumber: raw.hausnummerzahl1 || raw.hn || "",
                    city: raw.gemeinde || raw.gn || "",
                    status: "gray",
                    markerId: undefined,
                };
            });
        }
        lonToWebMercator(lon) {
            return lon * 6378137 * (Math.PI / 180);
        }
        latToWebMercator(lat) {
            const rad = lat * (Math.PI / 180);
            return 6378137 * Math.log(Math.tan(Math.PI / 4 + rad / 2));
        }
        webMercatorToLonLat(x, y) {
            const lon = (x / 6378137) * (180 / Math.PI);
            const lat = (2 * Math.atan(Math.exp(y / 6378137)) - Math.PI / 2) * (180 / Math.PI);
            return [lon, lat];
        }
        async fetchAddressesBySegment(segmentIds) {
            debug("🔄 Fetching by segment IDs:", segmentIds);
            return [];
        }
    }
    const addressDataClient = new AddressDataClient();
    const clearAddressCache = () => addressDataClient.clearCache();

    class MapRenderer {
        wmeSDK = null;
        layerName = "Quick PP Importer";
        isLayerCreated = false;
        setWmeSDK(sdk) {
            this.wmeSDK = sdk;
            this.createLayer();
        }
        createLayer() {
            if (!this.wmeSDK || this.isLayerCreated)
                return;
            try {
                this.wmeSDK.Map.addLayer({
                    layerName: this.layerName,
                    styleContext: {
                        fillColor: (context) => context.feature ? this.getColorForStatus(context.feature.properties.status) : '#BDBDBD',
                        radius: (context) => context.feature ? Math.max(2 + (String(context.feature.properties.houseNumber || '').length || 1) * 5, 12) : 12,
                        opacity: () => 1,
                        cursor: () => 'pointer',
                        title: (context) => context.feature ?
                            (context.feature.properties.streetName && context.feature.properties.houseNumber
                                ? `${context.feature.properties.streetName} - ${context.feature.properties.houseNumber}${context.feature.properties.city ? ', ' + context.feature.properties.city : ''}`
                                : '') : '',
                        number: (context) => context.feature ? (context.feature.properties.houseNumber || '?') : '?'
                    },
                    styleRules: [
                        {
                            style: {
                                fillColor: '${fillColor}',
                                fillOpacity: 1,
                                fontColor: '#111111',
                                fontOpacity: 1,
                                fontWeight: 'bold',
                                strokeColor: '#ffffff',
                                strokeOpacity: 1,
                                strokeWidth: 2,
                                pointRadius: '${radius}',
                                graphicName: 'circle',
                                label: '${number}',
                                cursor: '${cursor}',
                                title: '${title}'
                            }
                        }
                    ]
                });
                this.wmeSDK.Map.setLayerVisibility({ layerName: this.layerName, visibility: false });
                this.isLayerCreated = true;
                console.log(`✅ Map layer created: ${this.layerName}`);
                this.setupEventListeners();
            }
            catch (error) {
                console.error("❌ Error creating map layer:", error);
            }
        }
        setupEventListeners() {
            if (!this.wmeSDK)
                return;
            this.wmeSDK.Events.on({
                eventName: "wme-map-mouse-click",
                eventHandler: (clickEvent) => {
                    if (!appState.getImportState().isActive)
                        return;
                    if (appState.getImportState().isPaused)
                        return;
                    const clickLon = clickEvent.lon;
                    const clickLat = clickEvent.lat;
                    const cosLat = Math.cos(clickLat * Math.PI / 180);
                    const THRESHOLD_M = 40;
                    const addresses = appState.getAddresses();
                    let nearest = null;
                    let nearestDist = Infinity;
                    for (const addr of addresses) {
                        if (addr.status === 'lightGreen')
                            continue;
                        const dLat = (addr.latitude - clickLat) * 111000;
                        const dLon = (addr.longitude - clickLon) * 111000 * cosLat;
                        const dist = Math.hypot(dLat, dLon);
                        if (dist < nearestDist) {
                            nearestDist = dist;
                            nearest = addr;
                        }
                    }
                    const nearby = addresses
                        .filter(a => a.status !== 'lightGreen')
                        .map(a => ({
                        name: `${a.streetName} ${a.houseNumber}`,
                        dist: Math.hypot((a.latitude - clickLat) * 111000, (a.longitude - clickLon) * 111000 * cosLat)
                    }))
                        .filter(a => a.dist < 100)
                        .sort((a, b) => a.dist - b.dist);
                    if (nearby.length > 0) {
                        debug(`🔍 Addresses within 100m of click:`, nearby.map(a => `${a.name} (${a.dist.toFixed(1)}m)`));
                    }
                    if (!nearest || nearestDist > THRESHOLD_M)
                        return;
                    const address = nearest;
                    debug(`📌 Nearest address ${nearestDist.toFixed(0)}m away: ${address.streetName} ${address.houseNumber} — RPP at click pos`);
                    appState.selectAddress(address);
                    this.createPlacePoint(address, { lon: clickLon, lat: clickLat }).then(() => {
                        appState.markAddressProcessed(address.id);
                        this.refreshMarker(address.id);
                    });
                }
            });
            this.wmeSDK.Events.on({
                eventName: "wme-map-move-end",
                eventHandler: () => {
                    this.wmeSDK?.Map.redrawLayer({ layerName: this.layerName });
                }
            });
            console.log("✅ Event listeners registered");
        }
        async renderAddresses(addresses) {
            if (!this.wmeSDK || !this.isLayerCreated) {
                console.error("❌ WME SDK or layer not initialized");
                return;
            }
            debug(`🎨 Rendering ${addresses.length} address markers`);
            try {
                this.wmeSDK.Map.removeAllFeaturesFromLayer({
                    layerName: this.layerName
                });
                if (addresses.length === 0) {
                    debug("📍 No addresses to render");
                    this.setLayerVisibility(false);
                    return;
                }
                const features = addresses.map(address => ({
                    type: "Feature",
                    id: address.id,
                    geometry: {
                        type: "Point",
                        coordinates: [address.longitude, address.latitude]
                    },
                    properties: {
                        streetName: address.streetName,
                        houseNumber: address.houseNumber,
                        city: address.city,
                        status: address.status
                    }
                }));
                this.wmeSDK.Map.addFeaturesToLayer({
                    layerName: this.layerName,
                    features: features
                });
                this.setLayerVisibility(true);
                debug(`📍 ${addresses.length} addresses rendered`);
            }
            catch (error) {
                console.error("❌ Error rendering addresses:", error);
            }
        }
        getColorForStatus(status) {
            switch (status) {
                case "green":
                    return "#4CAF50";
                case "lightGreen":
                    return "#ABFA99";
                case "gray":
                    return "#BDBDBD";
                default:
                    return "#BDBDBD";
            }
        }
        setLayerVisibility(visible) {
            if (!this.wmeSDK || !this.isLayerCreated)
                return;
            this.wmeSDK.Map.setLayerVisibility({
                layerName: this.layerName,
                visibility: visible
            });
        }
        refreshMarker(addressId) {
            if (!this.wmeSDK || !this.isLayerCreated)
                return;
            const address = appState.getAddressById(addressId);
            if (!address)
                return;
            try {
                this.wmeSDK.Map.removeFeaturesFromLayer({
                    layerName: this.layerName,
                    featureIds: [addressId]
                });
                this.wmeSDK.Map.addFeaturesToLayer({
                    layerName: this.layerName,
                    features: [{
                            type: "Feature",
                            id: address.id,
                            geometry: { type: "Point", coordinates: [address.longitude, address.latitude] },
                            properties: {
                                streetName: address.streetName,
                                houseNumber: address.houseNumber,
                                city: address.city,
                                status: address.status
                            }
                        }]
                });
            }
            catch (e) {
                this.renderAddresses(appState.getAddresses());
            }
        }
        async createPlacePoint(address, position) {
            try {
                const placeLon = position?.lon ?? address.longitude;
                const placeLat = position?.lat ?? address.latitude;
                debug(`🏠 Creating RPP: ${address.streetName} ${address.houseNumber}`, position ? `at (${placeLon.toFixed(6)}, ${placeLat.toFixed(6)})` : '');
                if (!this.wmeSDK) {
                    console.error('❌ WME SDK not initialized');
                    return;
                }
                const geometry = { type: 'Point', coordinates: [placeLon, placeLat] };
                const newPlaceId = this.wmeSDK.DataModel.Venues.addVenue({ category: 'RESIDENTIAL', geometry }).toString();
                debug(`✅ Created place via SDK: id=${newPlaceId}`);
                const lockInfo = this.getDesiredResidentialLockInfo();
                if (lockInfo) {
                    try {
                        this.wmeSDK.DataModel.Venues.updateVenue({
                            venueId: newPlaceId,
                            lockRank: lockInfo.lockRank
                        });
                        debug(`🔒 Set RPP lock level to L${lockInfo.lockRank + 1} (user rank L${lockInfo.userRank + 1})`);
                    }
                    catch (lockErr) {
                        console.warn(`⚠️ Failed to set lock level for ${newPlaceId}:`, lockErr);
                    }
                }
                try {
                    this.wmeSDK.DataModel.Venues.replaceNavigationPoints({
                        venueId: newPlaceId,
                        navigationPoints: [{ isEntry: true, isPrimary: true, point: geometry }]
                    });
                    debug(`🔁 Navigation point set for venue ${newPlaceId}`);
                }
                catch (navErr) {
                    console.warn(`⚠️ Failed to set navigation points for ${newPlaceId}:`, navErr);
                }
                let streetFound = false;
                try {
                    let streetId = undefined;
                    const closestStreetId = this.findClosestSegmentStreetId(placeLon, placeLat, address.streetName);
                    if (closestStreetId != null) {
                        streetId = closestStreetId;
                        const allStreets = this.wmeSDK.DataModel.Streets.getAll();
                        const matchedStreet = allStreets.find(s => s.id === closestStreetId);
                        const matchedName = matchedStreet?.name?.trim().toLowerCase();
                        const targetName = address.streetName?.trim().toLowerCase();
                        streetFound = !!(matchedName && targetName && matchedName === targetName);
                        debug(`🛣️ Street: streetId=${streetId}, name="${matchedStreet?.name}", matched=${streetFound}`);
                    }
                    if (address.houseNumber || streetId) {
                        this.wmeSDK.DataModel.Venues.updateAddress({
                            venueId: newPlaceId,
                            houseNumber: address.houseNumber || '',
                            ...(streetId ? { streetId } : {})
                        });
                        debug(`📝 Address set for venue ${newPlaceId} (streetFound=${streetFound})`);
                    }
                }
                catch (addrErr) {
                    console.warn(`⚠️ Failed to update address for ${newPlaceId}:`, addrErr);
                }
                try {
                    this.wmeSDK.Editing.setSelection({ selection: { objectType: 'venue', ids: [newPlaceId] } });
                    debug(`🔎 Selected new venue ${newPlaceId}`);
                }
                catch (selErr) {
                    console.warn(`⚠️ Failed to select new venue ${newPlaceId}:`, selErr);
                }
                if (!streetFound) {
                    debug(`📋 Street not resolved for "${address.streetName}" — opening address editor`);
                    setTimeout(() => this.openAddressEditor(), 50);
                }
                else {
                    debug(`✅ Street resolved for "${address.streetName}" — address complete`);
                }
            }
            catch (error) {
                console.error(`❌ Error creating RPP:`, error);
            }
        }
        findClosestSegmentStreetId(lon, lat, streetName) {
            try {
                const segments = this.wmeSDK.DataModel.Segments?.getAll?.();
                if (!segments || segments.length === 0)
                    return null;
                let matchingStreetIds = null;
                if (streetName) {
                    const normalizedTarget = streetName.trim().toLowerCase();
                    const allStreets = this.wmeSDK.DataModel.Streets.getAll();
                    const matched = allStreets.filter(s => s.name?.trim().toLowerCase() === normalizedTarget);
                    if (matched.length > 0) {
                        matchingStreetIds = new Set(matched.map(s => s.id));
                    }
                }
                const getMatchAndDist = (seg) => {
                    const coords = seg.geometry?.coordinates;
                    if (!coords || coords.length < 2)
                        return null;
                    let minDist = Infinity;
                    for (let i = 0; i < coords.length - 1; i++) {
                        const d = this.distPointToSegment(lon, lat, coords[i][0], coords[i][1], coords[i + 1][0], coords[i + 1][1]);
                        if (d < minDist)
                            minDist = d;
                    }
                    if (!matchingStreetIds) {
                        if (seg.primaryStreetId == null)
                            return null;
                        return { streetId: seg.primaryStreetId, dist: minDist };
                    }
                    if (seg.primaryStreetId != null && matchingStreetIds.has(seg.primaryStreetId)) {
                        return { streetId: seg.primaryStreetId, dist: minDist };
                    }
                    for (const altId of (seg.alternateStreetIds ?? [])) {
                        if (matchingStreetIds.has(altId)) {
                            return { streetId: altId, dist: minDist };
                        }
                    }
                    return null;
                };
                let bestStreetId = null;
                let bestDist = Infinity;
                if (matchingStreetIds) {
                    for (const seg of segments) {
                        const result = getMatchAndDist(seg);
                        if (result && result.dist < bestDist) {
                            bestDist = result.dist;
                            bestStreetId = result.streetId;
                        }
                    }
                }
                if (bestStreetId === null) {
                    console.warn(`⚠️ No segment found with street name "${streetName}" — falling back to closest segment`);
                    for (const seg of segments) {
                        if (seg.primaryStreetId == null)
                            continue;
                        const coords = seg.geometry?.coordinates;
                        if (!coords || coords.length < 2)
                            continue;
                        for (let i = 0; i < coords.length - 1; i++) {
                            const d = this.distPointToSegment(lon, lat, coords[i][0], coords[i][1], coords[i + 1][0], coords[i + 1][1]);
                            if (d < bestDist) {
                                bestDist = d;
                                bestStreetId = seg.primaryStreetId;
                            }
                        }
                    }
                }
                return bestStreetId;
            }
            catch (e) {
                console.warn('⚠️ Segment street lookup failed:', e);
                return null;
            }
        }
        distPointToSegment(px, py, ax, ay, bx, by) {
            const dx = bx - ax;
            const dy = by - ay;
            const lenSq = dx * dx + dy * dy;
            if (lenSq === 0)
                return Math.hypot(px - ax, py - ay);
            const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq));
            return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
        }
        async openAddressEditor(tries = 1) {
            const addressEditView = document.querySelector('.address-edit-view');
            if (addressEditView) {
                const fullAddress = addressEditView.querySelector('.full-address');
                fullAddress?.click();
                await this.sleep(150);
                const hnHost = document.querySelector('.house-number');
                const input = hnHost?.shadowRoot?.querySelector('#id');
                input?.focus();
                debug(`📋 Address editor opened (focus on house-number field)`);
            }
            else if (tries < 1000) {
                setTimeout(() => this.openAddressEditor(tries + 1), 200);
            }
        }
        sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }
        getDesiredResidentialLockInfo() {
            if (!this.wmeSDK)
                return null;
            const userInfo = this.wmeSDK.State.getUserInfo();
            const userRank = userInfo?.rank;
            if (typeof userRank !== 'number' || Number.isNaN(userRank)) {
                return null;
            }
            const normalizedUserRank = Math.max(Math.trunc(userRank), 0);
            return {
                userRank: normalizedUserRank,
                lockRank: Math.min(normalizedUserRank, 3)
            };
        }
        clearMarkers() {
            if (!this.wmeSDK || !this.isLayerCreated)
                return;
            this.wmeSDK.Map.removeAllFeaturesFromLayer({
                layerName: this.layerName
            });
            this.setLayerVisibility(false);
            console.log("🗑️  All markers cleared");
        }
        redrawLayer() {
            if (!this.wmeSDK)
                return;
            this.wmeSDK.Map.redrawLayer({ layerName: this.layerName });
        }
    }
    const mapRenderer = new MapRenderer();

    class SegmentSelector {
        wmeSDK = null;
        selectedSegments = [];
        selectedStreetNames = [];
        selectedHouseNumbers = [];
        setWmeSDK(sdk) {
            this.wmeSDK = sdk;
            this.setupSelectionListener();
        }
        setupSelectionListener() {
            if (!this.wmeSDK)
                return;
            this.wmeSDK.Events.on({
                eventName: "wme-selection-changed",
                eventHandler: async () => {
                    await this.updateSelectedSegments();
                }
            });
            console.log("✅ Segment selection listener registered");
        }
        async updateSelectedSegments() {
            if (!this.wmeSDK)
                return;
            const selection = this.wmeSDK.Editing.getSelection();
            if (!selection || selection.objectType !== 'segment' || selection.ids.length === 0) {
                this.selectedSegments = [];
                this.selectedStreetNames = [];
                this.selectedHouseNumbers = [];
                appState.setSelectedSegments([]);
                return;
            }
            this.selectedSegments = selection.ids.map((segmentId) => this.wmeSDK.DataModel.Segments.getById({ segmentId })).filter(x => x);
            this.selectedStreetNames = this.extractStreetNames(this.selectedSegments);
            debug(`📌 Selected segments: ${this.selectedSegments.length}, streets:`, this.selectedStreetNames);
            appState.setSelectedSegments(this.selectedSegments);
            await this.fetchSelectedHouseNumbers();
        }
        extractStreetNames(segments) {
            if (!this.wmeSDK)
                return [];
            const streetNames = new Set();
            segments.forEach(segment => {
                if (segment.primaryStreetId) {
                    const street = this.wmeSDK.DataModel.Streets.getById({ streetId: segment.primaryStreetId });
                    if (street?.name) {
                        streetNames.add(street.name.toLowerCase());
                    }
                }
                segment.alternateStreetIds?.forEach((streetId) => {
                    const street = this.wmeSDK.DataModel.Streets.getById({ streetId });
                    if (street?.name) {
                        streetNames.add(street.name.toLowerCase());
                    }
                });
            });
            return Array.from(streetNames);
        }
        async fetchSelectedHouseNumbers() {
            if (!this.wmeSDK)
                return;
            if (this.selectedSegments.length === 0) {
                this.selectedHouseNumbers = [];
                return;
            }
            const segmentIds = this.selectedSegments
                .map(segment => segment.id ?? segment.segmentId)
                .filter((segmentId) => typeof segmentId === 'number');
            if (segmentIds.length === 0) {
                this.selectedHouseNumbers = [];
                return;
            }
            try {
                this.selectedHouseNumbers = await this.wmeSDK.DataModel.HouseNumbers.fetchHouseNumbers({
                    segmentIds
                });
                debug(`📍 Loaded ${this.selectedHouseNumbers.length} existing house numbers for selected segments`);
            }
            catch (error) {
                console.error("❌ Error fetching selected house numbers:", error);
                this.selectedHouseNumbers = [];
            }
        }
        async loadAddressesForSegments() {
            if (this.selectedSegments.length === 0)
                return;
            console.log("🔍 Loading addresses for selected segments...");
            try {
                await this.fetchSelectedHouseNumbers();
                const mapExtent = this.wmeSDK.Map.getMapExtent();
                console.log(`📍 Using map extent: [${mapExtent}]`);
                const [left, bottom, right, top] = mapExtent;
                const addresses = await addressDataClient.fetchAddressesByBoundingBox(left, bottom, right, top);
                const filteredAddresses = this.filterAndColorAddresses(addresses);
                appState.setAddresses(filteredAddresses);
                await mapRenderer.renderAddresses(filteredAddresses);
            }
            catch (error) {
                console.error("❌ Error loading addresses for segments:", error);
            }
        }
        calculateSegmentsBounds() {
            if (this.selectedSegments.length === 0)
                return null;
            let north = -90, south = 90, east = -180, west = 180;
            this.selectedSegments.forEach(segment => {
                if (segment.geometry && segment.geometry.coordinates) {
                    debug("🔍 Segment geometry:", segment.geometry);
                    segment.geometry.coordinates.forEach((coord) => {
                        debug("🔍 Raw coordinate:", coord);
                        const [x, y] = coord;
                        if (x >= -180 && x <= 180 && y >= -90 && y <= 90) {
                            north = Math.max(north, y);
                            south = Math.min(south, y);
                            east = Math.max(east, x);
                            west = Math.min(west, x);
                        }
                        else {
                            const lat = (y / 6378137) * (180 / Math.PI);
                            const lon = (x / 6378137) * (180 / Math.PI);
                            debug("🔍 Converted lat/lon:", lat, lon);
                            north = Math.max(north, lat);
                            south = Math.min(south, lat);
                            east = Math.max(east, lon);
                            west = Math.min(west, lon);
                        }
                    });
                }
            });
            debug("🔍 Calculated bounds:", { north, south, east, west });
            const padding = 0.005;
            return {
                north: north + padding,
                south: south - padding,
                east: east + padding,
                west: west - padding
            };
        }
        filterAndColorAddresses(addresses) {
            const existingRpps = new Set();
            try {
                const venues = this.wmeSDK.DataModel.Venues.getAll();
                for (const venue of venues) {
                    try {
                        const va = this.wmeSDK.DataModel.Venues.getAddress({ venueId: venue.id });
                        if (va?.street?.name && va?.houseNumber) {
                            existingRpps.add(`${va.street.name.trim().toLowerCase()}|${va.houseNumber.trim().toLowerCase()}`);
                        }
                    }
                    catch { }
                }
            }
            catch (e) {
                console.warn('⚠️ Could not read existing venues for duplicate check:', e);
            }
            return addresses.map(address => {
                const streetMatch = this.selectedStreetNames.some(selectedStreet => this.normalizeStreetName(address.streetName).includes(this.normalizeStreetName(selectedStreet)) ||
                    this.normalizeStreetName(selectedStreet).includes(this.normalizeStreetName(address.streetName)));
                let status = 'gray';
                if (streetMatch) {
                    const key = `${address.streetName.trim().toLowerCase()}|${address.houseNumber.trim().toLowerCase()}`;
                    status = existingRpps.has(key) ? 'lightGreen' : 'green';
                }
                else {
                    const similarMatch = this.selectedStreetNames.some(selectedStreet => this.calculateStreetSimilarity(address.streetName, selectedStreet) > 0.8);
                    if (similarMatch) {
                        status = 'lightGreen';
                    }
                }
                return { ...address, status };
            });
        }
        addressHasExistingHouseNumber(address) {
            if (!address.houseNumber)
                return false;
            const normalizedAddrNumber = this.normalizeHouseNumber(address.houseNumber);
            return this.selectedHouseNumbers.some(hn => this.normalizeHouseNumber(hn.number) === normalizedAddrNumber);
        }
        normalizeHouseNumber(houseNumber) {
            return houseNumber.toLowerCase().trim().replace(/\s+/g, '');
        }
        normalizeStreetName(name) {
            return name.toLowerCase()
                .replace(/straße|strasse|str\.?/g, 'str')
                .replace(/gasse|g\.?/g, 'g')
                .replace(/platz|pl\.?/g, 'pl')
                .replace(/weg|w\.?/g, 'w')
                .replace(/\s+/g, '')
                .replace(/[^a-z0-9]/g, '');
        }
        calculateStreetSimilarity(name1, name2) {
            const norm1 = this.normalizeStreetName(name1);
            const norm2 = this.normalizeStreetName(name2);
            if (norm1 === norm2)
                return 1.0;
            const longer = norm1.length > norm2.length ? norm1 : norm2;
            const shorter = norm1.length > norm2.length ? norm2 : norm1;
            if (longer.length === 0)
                return 1.0;
            const distance = this.levenshteinDistance(longer, shorter);
            return (longer.length - distance) / longer.length;
        }
        levenshteinDistance(str1, str2) {
            const matrix = [];
            for (let i = 0; i <= str2.length; i++) {
                matrix[i] = [i];
            }
            for (let j = 0; j <= str1.length; j++) {
                matrix[0][j] = j;
            }
            for (let i = 1; i <= str2.length; i++) {
                for (let j = 1; j <= str1.length; j++) {
                    if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
                        matrix[i][j] = matrix[i - 1][j - 1];
                    }
                    else {
                        matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
                    }
                }
            }
            return matrix[str2.length][str1.length];
        }
        async loadAddressesForSelectedSegments() {
            await this.loadAddressesForSegments();
        }
        async loadAddressesManually() {
            await this.loadAddressesForSegments();
        }
        getSelectedSegments() {
            return this.selectedSegments;
        }
        hasSelectedSegments() {
            return this.selectedSegments.length > 0;
        }
        getSelectedStreetNames() {
            return this.selectedStreetNames;
        }
    }
    const segmentSelector = new SegmentSelector();

    const LS_KEY = 'WME_PP_LAST_SEEN_VERSION';
    const shortcutState = {
        toggleKey: 'P',
        resumeKey: 'O',
    };
    const UPDATE_NOTES = {
        '2026.04.14.00': [
            'Shortcut-Anzeige wird ohne Browser-Reload live aktualisiert',
            'Wenn Tasten belegt sind, werden Shortcuts auf "nicht gesetzt" registriert',
            'Nicht gesetzte Shortcuts bitte im WME-Shortcut-Menü zuweisen',
        ],
        '2026.04.13.00': [
            'Runde Marker statt quadratische für Adresspunkte',
            'Lock Level des neu angelegten RPPs wird auf den User-Level gesetzt (max. L4)',
        ],
        '2026.04.12.00': [
            'Update-Benachrichtigung im Sidebar-Tab (diese Meldung)',
            'Erstveröffentlichung auf Greasy Fork',
            'Automatische Updates über Greasy Fork',
            'Tile-basiertes Adress-Caching (750m, 7 Tage TTL)',
            'Duplikat-Erkennung für bereits vorhandene RPPs',
            'Fuzzy-Matching für Straßennamen (Levenshtein)',
        ],
    };
    async function initializeScript(wmeSDK) {
        console.log(`✅ WME Quick PP Importer: SDK v.${wmeSDK.getSDKVersion()} initialized`);
        appState.setWmeSDK(wmeSDK);
        try {
            await setupSidebarTab(wmeSDK);
            showUpdateNotification();
            setupMapLayer(wmeSDK);
            segmentSelector.setWmeSDK(wmeSDK);
            setupEventListeners(wmeSDK);
            console.log("🚀 Quick PP Importer fully initialized");
        }
        catch (error) {
            console.error("❌ Initialization failed:", error);
            throw error;
        }
    }
    async function setupSidebarTab(wmeSDK) {
        const { tabLabel, tabPane } = await wmeSDK.Sidebar.registerScriptTab();
        tabLabel.innerText = "🏠 Quick PP";
        tabPane.innerHTML = `
        <div id="qpi-sidebar-root" style="padding: 12px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">
            <h3 style="margin: 0 0 10px 0; font-size: 16px;">Quick PP Importer</h3>
            
            <div style="margin-bottom: 8px;">
                <input type="checkbox" id="qpi-enable" style="cursor: pointer;">
                <label for="qpi-enable" style="cursor: pointer; margin-left: 5px;">Import Mode aktivieren</label>
            </div>
            
            <div style="background: #f0f0f0; padding: 8px; border-radius: 4px; font-size: 12px; line-height: 1.4;">
                <p style="margin: 0; font-weight: bold;">Anleitung:</p>
                <p style="margin: 5px 0 0 0;">1. Straße(n) selektieren</p>
                <p style="margin: 5px 0 0 0;" id="qpi-toggle-hint">2. <strong>${formatShortcutKey(shortcutState.toggleKey)}</strong> → Adressen laden + Import starten</p>
                <p style="margin: 5px 0 0 0;">3. Auf Marker klicken → RPP erstellen</p>
                <p style="margin: 5px 0 0 0;" id="qpi-shortcut-summary"><strong>Esc</strong> = Pausieren &nbsp;|&nbsp; <strong>${formatShortcutKey(shortcutState.resumeKey)}</strong> = Fortsetzen &nbsp;|&nbsp; <strong>${formatShortcutKey(shortcutState.toggleKey)}</strong> = Stoppen</p>
                <p style="margin: 5px 0 0 0; color: #666;" id="qpi-shortcut-config-hint"></p>
            </div>
            
            <div style="margin-top: 10px; font-size: 11px; color: #666;">
                <p style="margin: 0;" id="qpi-status">Status: Bereit</p>
                <p style="margin: 5px 0 0 0;" id="qpi-address-count">Adressen geladen: 0</p>
            </div>

            <div style="margin-top: 10px; border-top: 1px solid #ddd; padding-top: 8px;">
                <input type="checkbox" id="qpi-debug" style="cursor: pointer;">
                <label for="qpi-debug" style="cursor: pointer; margin-left: 5px; font-size: 11px; color: #888;">Debug-Ausgaben in Console</label>
            </div>
        </div>
    `;
        const enableCheckbox = tabPane.querySelector("#qpi-enable");
        if (enableCheckbox) {
            enableCheckbox.addEventListener("change", (e) => {
                const checked = e.target.checked;
                if (checked) {
                    appState.activateImport();
                }
                else {
                    appState.deactivateImport();
                }
            });
        }
        const debugCheckbox = tabPane.querySelector("#qpi-debug");
        if (debugCheckbox) {
            debugCheckbox.addEventListener("change", (e) => {
                appState.setDebugMode(e.target.checked);
            });
        }
        appState.on("addressesLoaded", (addresses) => {
            const countEl = tabPane.querySelector("#qpi-address-count");
            if (countEl) {
                countEl.textContent = `Adressen geladen: ${addresses.length}`;
            }
        });
        appState.on("importActivated", () => {
            const statusEl = tabPane.querySelector("#qpi-status");
            if (statusEl)
                statusEl.textContent = "Status: 🟢 Aktiv";
            const cb = tabPane.querySelector("#qpi-enable");
            if (cb)
                cb.checked = true;
        });
        appState.on("importDeactivated", () => {
            const statusEl = tabPane.querySelector("#qpi-status");
            if (statusEl)
                statusEl.textContent = "Status: Bereit";
            const cb = tabPane.querySelector("#qpi-enable");
            if (cb)
                cb.checked = false;
        });
        appState.on("importPaused", () => {
            const statusEl = tabPane.querySelector("#qpi-status");
            if (statusEl)
                statusEl.textContent = `Status: ⏸️ Pausiert (${formatShortcutKey(shortcutState.resumeKey)} = Fortsetzen)`;
        });
        appState.on("importResumed", () => {
            const statusEl = tabPane.querySelector("#qpi-status");
            if (statusEl)
                statusEl.textContent = "Status: 🟢 Aktiv";
        });
        updateShortcutUi();
    }
    function showUpdateNotification() {
        const currentVersion = GM_info.script.version;
        const lastSeen = localStorage.getItem(LS_KEY);
        if (lastSeen === currentVersion)
            return;
        const notes = UPDATE_NOTES[currentVersion];
        if (!notes || notes.length === 0)
            return;
        const tabPane = document.querySelector('#qpi-sidebar-root');
        if (!tabPane)
            return;
        const banner = document.createElement('div');
        banner.id = 'qpi-update-banner';
        banner.style.cssText = `
        background: #e8f4fd; border: 1px solid #90cdf4; border-radius: 6px;
        padding: 10px 12px; margin-bottom: 10px; font-size: 12px; position: relative;
    `;
        banner.innerHTML = `
        <button id="qpi-update-dismiss" style="
            position: absolute; top: 6px; right: 8px; background: none; border: none;
            font-size: 14px; cursor: pointer; color: #555; line-height: 1;
        " title="Schließen">✕</button>
        <div style="font-weight: bold; margin-bottom: 6px; color: #1a6fa3;">
            🎉 Neu in v${currentVersion}
        </div>
        <ul style="margin: 0; padding-left: 16px; color: #333;">
            ${notes.map(n => `<li style="margin-bottom: 3px;">${n}</li>`).join('')}
        </ul>
    `;
        tabPane.prepend(banner);
        document.getElementById('qpi-update-dismiss')?.addEventListener('click', () => {
            banner.remove();
            localStorage.setItem(LS_KEY, currentVersion);
        });
    }
    function setupMapLayer(wmeSDK) {
        console.log("✅ Map layer setup delegated to mapRenderer");
    }
    function setMapCursor(wmeSDK, cursor) {
        try {
            const viewport = wmeSDK.Map.getMapViewportElement();
            viewport.style.cursor = cursor;
        }
        catch (e) {
            const viewport = document.querySelector('.olMapViewport');
            if (viewport)
                viewport.style.cursor = cursor;
        }
    }
    function keyCodeToLabel(code) {
        if (Number.isNaN(code))
            return '';
        if (code >= 65 && code <= 90)
            return String.fromCharCode(code);
        if (code >= 48 && code <= 57)
            return String.fromCharCode(code);
        const special = {
            13: 'Enter',
            27: 'Esc',
            32: 'Space'
        };
        return special[code] ?? String(code);
    }
    function decodeCompactShortcut(shortcutKey) {
        const match = shortcutKey.match(/^(\d+)[\.,](\d+)$/);
        if (!match)
            return null;
        const modifierMask = Number(match[1]);
        const keyCode = Number(match[2]);
        if (!Number.isFinite(modifierMask) || !Number.isFinite(keyCode))
            return null;
        const modifiers = [];
        if (modifierMask & 2)
            modifiers.push('Ctrl');
        if (modifierMask & 1)
            modifiers.push('Alt');
        if (modifierMask & 4)
            modifiers.push('Shift');
        return { modifiers, keyCode };
    }
    function formatShortcutKey(shortcutKey) {
        if (shortcutKey === null)
            return 'nicht gesetzt';
        const compact = decodeCompactShortcut(shortcutKey);
        if (compact) {
            const keyLabel = keyCodeToLabel(compact.keyCode);
            return compact.modifiers.length ? `${compact.modifiers.join('+')}+${keyLabel}` : keyLabel;
        }
        const parts = shortcutKey.split('+').filter(Boolean);
        if (!parts.length)
            return shortcutKey;
        const keyPart = parts.pop();
        const modifierPart = parts.join('');
        const modifierLabels = [];
        if (modifierPart.includes('C'))
            modifierLabels.push('Ctrl');
        if (modifierPart.includes('A'))
            modifierLabels.push('Alt');
        if (modifierPart.includes('S'))
            modifierLabels.push('Shift');
        const parsedCode = Number(keyPart);
        const keyLabel = Number.isFinite(parsedCode)
            ? keyCodeToLabel(parsedCode)
            : keyPart.toUpperCase();
        return modifierLabels.length ? `${modifierLabels.join('+')}+${keyLabel}` : keyLabel;
    }
    function updateShortcutUi() {
        const toggleHint = document.querySelector('#qpi-toggle-hint');
        if (toggleHint) {
            toggleHint.innerHTML = `2. <strong>${formatShortcutKey(shortcutState.toggleKey)}</strong> → Adressen laden + Import starten`;
        }
        const shortcutSummary = document.querySelector('#qpi-shortcut-summary');
        if (shortcutSummary) {
            shortcutSummary.innerHTML = `<strong>Esc</strong> = Pausieren &nbsp;|&nbsp; <strong>${formatShortcutKey(shortcutState.resumeKey)}</strong> = Fortsetzen &nbsp;|&nbsp; <strong>${formatShortcutKey(shortcutState.toggleKey)}</strong> = Stoppen`;
        }
        const shortcutConfigHint = document.querySelector('#qpi-shortcut-config-hint');
        if (!shortcutConfigHint)
            return;
        const missingKeys = [shortcutState.toggleKey, shortcutState.resumeKey].some(key => key === null);
        shortcutConfigHint.textContent = missingKeys
            ? 'Belegte Shortcuts wurden ohne Taste registriert und können im WME-Shortcut-Menü frei zugewiesen werden.'
            : 'Shortcuts können im WME-Shortcut-Menü angepasst werden.';
        const statusEl = document.querySelector('#qpi-status');
        if (statusEl && appState.getImportState().isPaused) {
            statusEl.textContent = `Status: ⏸️ Pausiert (${formatShortcutKey(shortcutState.resumeKey)} = Fortsetzen)`;
        }
    }
    function isTypingTarget(target) {
        const element = target;
        if (!element)
            return false;
        const tagName = element.tagName?.toUpperCase();
        if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT')
            return true;
        if (element.isContentEditable)
            return true;
        const role = (element.getAttribute?.('role') || '').toLowerCase();
        return role === 'textbox' || role === 'searchbox' || role === 'combobox';
    }
    async function toggleImportMode() {
        const state = appState.getImportState();
        if (state.isActive) {
            debug("⏹️  Import Mode: OFF");
            appState.deactivateImport();
            return;
        }
        if (!segmentSelector.hasSelectedSegments()) {
            console.warn("⚠️  P gedrückt aber kein Segment ausgewählt — bitte zuerst Straße(n) selektieren");
            return;
        }
        debug("▶️  Import Mode: ON — Lade Adressen...");
        appState.activateImport();
        await segmentSelector.loadAddressesForSelectedSegments();
    }
    function resumeImportMode(wmeSDK) {
        const state = appState.getImportState();
        if (state.isActive && state.isPaused) {
            debug("▶️  Fortgesetzt");
            appState.togglePause();
            setMapCursor(wmeSDK, 'crosshair');
        }
    }
    function registerShortcut(wmeSDK, shortcutId, shortcutKeys, description, callback) {
        const assignedShortcutKey = wmeSDK.Shortcuts.areShortcutKeysInUse({ shortcutKeys }) ? null : shortcutKeys;
        try {
            wmeSDK.Shortcuts.createShortcut({
                shortcutId,
                description,
                shortcutKeys: assignedShortcutKey,
                callback
            });
            if (assignedShortcutKey === null) {
                console.warn(`⚠️  Shortcut ${shortcutKeys} bereits belegt — Eintrag ohne Taste registriert`);
            }
            return assignedShortcutKey;
        }
        catch (error) {
            if (assignedShortcutKey !== null) {
                console.warn(`⚠️  SDK-Shortcut ${shortcutKeys} konnte nicht registriert werden — versuche Eintrag ohne Taste`, error);
                wmeSDK.Shortcuts.createShortcut({
                    shortcutId,
                    description,
                    shortcutKeys: null,
                    callback
                });
                return null;
            }
            throw error;
        }
    }
    function syncShortcutStateFromSdk(wmeSDK) {
        const registeredShortcuts = wmeSDK.Shortcuts.getAllShortcuts();
        const toggleShortcut = registeredShortcuts.find(shortcut => shortcut.shortcutId === 'qpi-toggle');
        const resumeShortcut = registeredShortcuts.find(shortcut => shortcut.shortcutId === 'qpi-resume');
        const nextToggleKey = toggleShortcut?.shortcutKeys ?? null;
        const nextResumeKey = resumeShortcut?.shortcutKeys ?? null;
        if (shortcutState.toggleKey === nextToggleKey && shortcutState.resumeKey === nextResumeKey) {
            return;
        }
        shortcutState.toggleKey = nextToggleKey;
        shortcutState.resumeKey = nextResumeKey;
        updateShortcutUi();
    }
    function startShortcutStateSync(wmeSDK) {
        const sync = () => syncShortcutStateFromSdk(wmeSDK);
        sync();
        window.addEventListener('focus', sync);
        document.addEventListener('visibilitychange', () => {
            if (!document.hidden)
                sync();
        });
        window.setInterval(sync, 1000);
    }
    function setupEventListeners(wmeSDK) {
        shortcutState.toggleKey = registerShortcut(wmeSDK, 'qpi-toggle', 'P', 'Import starten (Segment auswählen, dann P drücken) / Stoppen', () => toggleImportMode());
        shortcutState.resumeKey = registerShortcut(wmeSDK, 'qpi-resume', 'O', 'Import fortsetzen (nach Pause)', () => resumeImportMode(wmeSDK));
        updateShortcutUi();
        startShortcutStateSync(wmeSDK);
        appState.on('importActivated', () => setMapCursor(wmeSDK, 'crosshair'));
        appState.on('importDeactivated', () => setMapCursor(wmeSDK, ''));
        appState.on('importPaused', () => setMapCursor(wmeSDK, ''));
        appState.on('importResumed', () => setMapCursor(wmeSDK, 'crosshair'));
        document.addEventListener('keydown', (e) => {
            if (isTypingTarget(e.target))
                return;
            if (e.key !== 'Escape')
                return;
            const state = appState.getImportState();
            if (!state.isActive)
                return;
            if (state.isPaused)
                return;
            debug("⏸️  Import Mode: PAUSIERT (Esc) — O zum Fortsetzen");
            appState.togglePause();
        });
        console.log("✅ SDK Shortcuts registered");
    }

    const initSDK = async () => {
        console.log("⏳ Waiting for WME SDK to initialize...");
        try {
            const unsafeWindow = window.unsafeWindow || window;
            if (unsafeWindow.SDK_INITIALIZED) {
                console.log("⏳ SDK_INITIALIZED promise found, waiting...");
                await unsafeWindow.SDK_INITIALIZED;
                console.log("✅ SDK_INITIALIZED promise resolved");
            }
            else {
                console.log("⚠️  SDK_INITIALIZED not found in unsafeWindow");
                throw new Error("SDK_INITIALIZED not available");
            }
            if (!unsafeWindow.getWmeSdk) {
                console.log("⚠️  getWmeSdk not available in unsafeWindow");
                throw new Error("getWmeSdk not available");
            }
            console.log("🚀 Creating WME SDK instance...");
            const wmeSDK = unsafeWindow.getWmeSdk({
                scriptId: "wme-quick-pp-importer",
                scriptName: "WME Quick PP Importer"
            });
            console.log("✅ WME SDK instance created");
            return wmeSDK;
        }
        catch (error) {
            console.error("❌ SDK initialization failed:", error);
            throw error;
        }
    };
    initSDK().then(async (wmeSDK) => {
        try {
            mapRenderer.setWmeSDK(wmeSDK);
            await initializeScript(wmeSDK);
            appState.on("addressesLoaded", (addresses) => {
                console.log(`📍 ${addresses.length} addresses loaded:`, addresses);
            });
            appState.on("importActivated", () => {
                console.log("▶️  Import Mode: ACTIVE");
                appState.logState();
            });
            appState.on("importDeactivated", () => {
                console.log("⏹️  Import Mode: DEACTIVATED");
                mapRenderer.clearMarkers();
            });
            appState.on("segmentsSelected", (segments) => {
                console.log("📌 Segments selected:", segments.length);
            });
            const pageWindow = window.unsafeWindow || window;
            pageWindow.testQuickPP = {
                testAPI: async () => {
                    console.log("🧪 Testing API (real kbox.at call)...");
                    try {
                        const mapExtent = wmeSDK.Map.getMapExtent();
                        const [left, bottom, right, top] = mapExtent;
                        console.log(`📍 Using map extent: [${mapExtent}]`);
                        const addresses = await addressDataClient.fetchAddressesByBoundingBox(left, bottom, right, top);
                        console.log("✅ API Test Result:", addresses);
                        return addresses;
                    }
                    catch (error) {
                        console.error("❌ API Test failed:", error);
                        throw error;
                    }
                },
                showState: () => appState.logState(),
                clearCache: () => {
                    clearAddressCache();
                    console.log("🗑️ Address cache cleared");
                },
                updatePositions: () => {
                    mapRenderer.redrawLayer();
                    console.log("🔄 Layer redrawn");
                },
                countMarkers: () => {
                    const count = appState.getAddresses().length;
                    console.log(`📊 Current markers: ${count}`);
                    return count;
                },
                loadAddresses: async () => {
                    console.log("🔍 Loading addresses for selected segments...");
                    await segmentSelector.loadAddressesManually();
                    console.log("✅ Address loading triggered");
                },
            };
            console.log("💡 Debug Commands: testQuickPP.testAPI() | .showState() | .clearCache() | .updatePositions() | .countMarkers() | .loadAddresses()");
        }
        catch (error) {
            console.error("❌ Script initialization failed:", error);
        }
    }).catch((error) => {
        console.error("❌ Script initialization promise rejected:", error);
        console.error("Stack:", error instanceof Error ? error.stack : "no stack");
    });

})();