WME UR Minus (UR-)

Adds minimum UR filtering.

// ==UserScript==
// @name                WME UR Minus (UR-)
// @namespace           https://greasyfork.org/users/gad_m/wme_ur_minus
// @description         Adds minimum UR filtering.
// @include 	        /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor.*$/
// @author              gad_m
// @license             MIT
// @grant               GM_addStyle
// @version             1.33
// @grant               GM_xmlhttpRequest
// @connect             raw.githubusercontent.com
//noinspection SpellCheckingInspection
// @icon                data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD8AAAA/CAYAAABXXxDfAAAKpGlDQ1BJQ0MgUHJvZmlsZQAASImVlgdUU9kWhs+96SGhBZBO6E2QTgApoYfem42QBAglhkAQsCODIzCiqIiAIuhQFRwLIDYEFCsKil0HRATUcbBgw/JuYBFm5q333np7rXPPd/fdZ5+9z7pnrR8AMp7J56fA0gCk8jIEIV6u1KjoGCpuDGCAGiAAO6DCZKXz6UFBfgCxufnv9uEOgETzLRNRrn///l9Nhs1JZwEABSEcx05npSJ8HBnPWHxBBgCoMsSvvSqDL+LTCMsJkAIRvinihFl+JuK4Wf40ExMW4gYAmgQAnsRkChIAICkifmomKwHJQ6IhbMZjc3kIJyLslJq6ko1wPcIGSAwfYVF+Wtxf8iT8LWecOCeTmSDm2V5mDO/OTeenMLP/z+P435aaIpzbQw+IGhB4h4hm5MzuJa/0FTMvLiBwjrnsmfgZThR6h88xK90tZo7ZTHdf8dqUAL85jud6MsR5Mhhhc8xJ9widY8HKEPFe8QI3+hwzBfP7CpPDxf5EDkOcPycxLHKOM7kRAXOcnhzqOx/jJvYLhCHi+jk8L9f5fT3Fvaem/6VfLkO8NiMxzFvcO3O+fg6PPp8zPUpcG5vj7jEfEy6O52e4ivfipwSJ4zkpXmJ/emaoeG0G8kPOrw0Sn2ES0ydojoEHiAAWwBa5c9YZnKwMUQNuK/nZAm5CYgaVjtwsDpXBY5kupFqYWVgCILqns7/Bu3sz9w9SwM/7eFIAWF1EYM+8L04SgFNo5MpdnPfptQMgbQNA53WWUJA560OLHhhABFJADigBdaANDIAJUpsNcAAuSJ0+IBCEgWiwHLBAIkgFArAKrAEbQT4oBNvALlAOqsABUA8Og6OgDZwG58FFcBXcBIPgIRgCo+AlmAQfwDQEQTiIDFEgJUgD0oWMIQuIBjlBHpAfFAJFQ7FQAsSDhNAaaBNUCJVA5VA11AD9Bp2EzkOXoX7oPjQMTUBvoS8wCibBcrAarAcvgmkwHfaFw+BlcAKcBufAefBWuAyugQ/BrfB5+Co8CA/BL+EpFEBJoBRQmigTFA3lhgpExaDiUQLUOlQBqhRVg2pGdaB6UbdQQ6hXqM9oLJqCpqJN0A5ob3Q4moVOQ69DF6HL0fXoVnQP+hZ6GD2J/o4hY1Qxxhh7DAMThUnArMLkY0oxtZgTmAuYQcwo5gMWi1XA6mNtsd7YaGwSdjW2CLsX24LtxPZjR7BTOBxOCWeMc8QF4pi4DFw+bg/uEO4cbgA3ivuEl8Br4C3wnvgYPA+fiy/FN+LP4gfwY/hpgjRBl2BPCCSwCdmEYsJBQgfhBmGUME2UIeoTHYlhxCTiRmIZsZl4gfiI+E5CQkJLwk4iWIIrsUGiTOKIxCWJYYnPJFmSEcmNtJQkJG0l1ZE6SfdJ78hksh7ZhRxDziBvJTeQu8lPyJ8kKZKmkgxJtuR6yQrJVskByddSBCldKbrUcqkcqVKpY1I3pF5JE6T1pN2kmdLrpCukT0rflZ6SociYywTKpMoUyTTKXJYZl8XJ6sl6yLJl82QPyHbLjlBQFG2KG4VF2UQ5SLlAGZXDyunLMeSS5ArlDsv1yU3Ky8pbyUfIZ8lXyJ+RH1JAKegpMBRSFIoVjircUfiyQG0BfQFnwZYFzQsGFnxUVFF0UeQoFii2KA4qflGiKnkoJSttV2pTeqyMVjZSDlZepbxP+YLyKxU5FQcVlkqBylGVB6qwqpFqiOpq1QOq11Sn1NTVvNT4anvUutVeqSuou6gnqe9UP6s+oUHRcNLgauzUOKfxgipPpVNTqGXUHuqkpqqmt6ZQs1qzT3NaS18rXCtXq0XrsTZRm6Ydr71Tu0t7UkdDx19njU6TzgNdgi5NN1F3t26v7kc9fb1Ivc16bXrj+or6DP0c/Sb9RwZkA2eDNIMag9uGWEOaYbLhXsObRrCRtVGiUYXRDWPY2MaYa7zXuH8hZqHdQt7CmoV3TUgmdJNMkyaTYVMFUz/TXNM209eLdBbFLNq+qHfRdzNrsxSzg2YPzWXNfcxzzTvM31oYWbAsKixuW5ItPS3XW7ZbvrEytuJY7bO6Z02x9rfebN1l/c3G1kZg02wzYatjG2tbaXuXJkcLohXRLtlh7Fzt1tudtvtsb2OfYX/U/k8HE4dkh0aH8cX6izmLDy4ecdRyZDpWOw45UZ1infY7DTlrOjOda5yfumi7sF1qXcbohvQk+iH6a1czV4HrCdePbvZua9063VHuXu4F7n0esh7hHuUeTzy1PBM8mzwnvay9Vnt1emO8fb23e99lqDFYjAbGpI+tz1qfHl+Sb6hvue9TPyM/gV+HP+zv47/D/1GAbgAvoC0QBDICdwQ+DtIPSgs6FYwNDgquCH4eYh6yJqQ3lBK6IrQx9EOYa1hx2MNwg3BheFeEVMTSiIaIj5HukSWRQ1GLotZGXY1WjuZGt8fgYiJiamOmlngs2bVkdKn10vyld5bpL8tadnm58vKU5WdWSK1grjgWi4mNjG2M/coMZNYwp+IYcZVxkyw31m7WS7YLeyd7guPIKeGMxTvGl8SPJzgm7EiYSHROLE18xXXjlnPfJHknVSV9TA5Mrkv+kRKZ0pKKT41NPcmT5SXzelaqr8xa2c835ufzh9Ls03alTQp8BbXpUPqy9PYMOUQQXRMaCH8SDmc6ZVZkfloVsepYlkwWL+tatlH2luyxHM+cX1ejV7NWd63RXLNxzfBa+trqddC6uHVd67XX560f3eC1oX4jcWPyxuu5Zrklue83RW7qyFPL25A38pPXT035kvmC/LubHTZX/Yz+mftz3xbLLXu2fC9gF1wpNCssLfxaxCq68ov5L2W//Ngav7Wv2KZ43zbsNt62O9udt9eXyJTklIzs8N/RupO6s2Dn+10rdl0utSqt2k3cLdw9VOZX1r5HZ8+2PV/LE8sHK1wrWipVK7dUftzL3juwz2Vfc5VaVWHVl/3c/feqvapba/RqSg9gD2QeeH4w4mDvr7RfG2qVawtrv9Xx6obqQ+p7GmwbGhpVG4ub4CZh08ShpYduHnY/3N5s0lzdotBSeAQcER558Vvsb3eO+h7tOkY71nxc93jlCcqJglaoNbt1si2xbag9ur3/pM/Jrg6HjhOnTE/VndY8XXFG/kzxWeLZvLM/zuWcm+rkd746n3B+pGtF18PuqO7bPcE9fRd8L1y66Hmxu5fee+6S46XTl+0vn7xCu9J21eZq6zXrayeuW18/0WfT13rD9kb7TbubHf2L+88OOA+cv+V+6+Jtxu2rgwGD/XfC79y7u/Tu0D32vfH7KfffPMh8MP1wwyPMo4LH0o9Ln6g+qfnd8PeWIZuhM8Puw9eehj59OMIaefks/dnX0bzn5OelYxpjDeMW46cnPCduvljyYvQl/+X0q/w/ZP6ofG3w+vifLn9em4yaHH0jePPjbdE7pXd1763ed00FTT35kPph+mPBJ6VP9Z9pn3u/RH4Zm171Ffe17Jvht47vvt8f/Uj98YPPFDBnpAAKGXB8PABv6wAgRwNAQXQxccmsjp4xaFb7zxD4TzyrtWcM0RpNGwAI6QRAJMv2izQI8i7lAkAQMsJcAGxpKR5zmndGn4tMvhsAc284eHnhow5aFPiHzWr3v9T9zxmIslqBf87/AkNzAoL4UwR7AAAAVmVYSWZNTQAqAAAACAABh2kABAAAAAEAAAAaAAAAAAADkoYABwAAABIAAABEoAIABAAAAAEAAAA/oAMABAAAAAEAAAA/AAAAAEFTQ0lJAAAAU2NyZWVuc2hvdBUUw9kAAAHUaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjYzPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjYzPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6VXNlckNvbW1lbnQ+U2NyZWVuc2hvdDwvZXhpZjpVc2VyQ29tbWVudD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CjQ7XFgAAAd9SURBVGgF7VpLb1tVEL73+sZ27DzkkKT0EbWkafpwUAvirbYpQsCSJQuExB8AsWDJhhVICJZlwQYhkIA1EkgVaiktC6BVpTZVQqO0SloREpqX47d9L+7MfMf1+BE3LQuam4W/M3PnzDme78ycc65jnzx50re26J+zRb83fe0t/eXd/4J5x/bI7aOJZcJdjywQ9nWtEkY68oSxSIkwk+Np5EsRkpfXewnnbg8Szi8nCD3/wXL1YL3RFP8/Hw+Eedvmmjk8eIu++djQNGEszAw3DUeZn8Q6eAXEOnKkSHTyChkemCU5U+AVcWVuhOSZhZ2Evm8TbvYjYH6zkQOzxw9eJBeJGDNmKUJ82UyNGg1sspBlIrDHvGJhXhHPDF8h1cjgHOEvU08SYmXAvl0MmG83UrDr60pRc3z0D8KI5KrPRd6yhUkwqGX4MXrVDwsBC8MyDe6ZiK9Q49WxXwl/nnqKcCndzQZtfgbMtxkoC7k3vu836hJxpZqbnGbOfFAujiHaUgx8UGmqNTuAnZ6PIr7ihceJuFkyPTbK8zk1cZTkdmtAwLyOtJaxjx8duUCPIiGuvhaYE8p82e9BrE5+w7gM0NQeDkC5KQ6yQvBc9J0yn6P7uAaduvoCjbDROSBgXjOt5cf6bpKqr5PP6qaqGwakB5iC6LHC7d5LGjfOJzQn3E9yaX1G8CqhX+RzAnIfhFtQkJVlRFuN3xfl+WG+M7eHpEdjCJhvHBfLciSHk9unyMQXJhF6X6jRqe508K0sPvwu9Qsnnm02BPstcw3Jzn1Ocm7+B9arlWScmBrDVV+PPybzvbG0i7p4qE3GATcC5lVAjLit6x9qx0IZQh1hS1aCIcjh21d38hOyD0V3GF+tGnYoSo9je94mtN0ewuzsNzXdUAPMeHp8sY65PN9Bmf98aqDGD4SAeURC446ev1gloTY5D0NQIXJs95vUapdxuNEY3fkGqQqL5wjLGd5tNhpf16KdMv+AeR3hiuw20BlVQvZ15HZlhzXPqKH233D/y6QurfG+nZ37kuTu5Ec1/bz8Isnrkx/w87GPCe1QJ6PN0wr3Hyc5e+Nrwo3Gt/AiQQ4iZv7SW0OQ8zoikGMhvjX5Hl+4bclx3Np4l63EO8onNqeDq7TlbycX4YGX4KoGUc3Dg7xSLIerfY1RRXDj+0m10fjV9Vht3emI+ZOTBh8B8w2CQqqIg/s6R9RUW1BuOtbG0Anze/YImDV23LBDfB6Ibn9NPakVQz2HWCGEYgUgtWutTcabyhSx5PapDUWunXUTo4dV3bLaF4ocm0iI36ubIIAJUXhp/kXGL8lJ0I0Z0/tpOG4XdQ/3P0dYWOB3dqAWGY4aZN4fyKDFcqjl8AHzzcKTK3JuIvfVtm5Sz7b4p5fsze/IVWzPW81cbkofHXqd+uXnz9f0R+kB6nNArth4F4GTgHlEQuNSro9UPS6/YdH7PHIMOZe59hXZu128P4f7n9cuNyXbbpz6mVsllqA6d2jnt3O862g95IB5RELjQnobqfbErxP6eHknhmDc7LA+537qwvtkERniE1z8wHsk207LzUW8ViE7+y0J2RlGs8+LSd34sg3gvT7mX/VY2wqYr41HVbqZ5t/Bj5Q7SBnGiU/d4y29IlD9Z3+kfk6YV5DbO0ry+sSnPIjH5we/XCS5a+wdQpwX0lc/Yzv9ucH4eS9MPTB/3R1ywDwiobEs/wNzbXUfPUr2XibUv7xoIuAH+2/6zy9Ipe1U0bZSlz5k/zi6iSPTT/QbjY/5Yv7ipg4C5utCohRTqwdJszt2gzDurhGiqlarLqkrJz/mHAwZGVQLg3XnBu5e19/8JijPcZLTftMlfp+A+RrzJo2A+SaBMeqSx0yeWzhGuvFtPxFG7azY8HPkpl4JZgVIEaguAJPE4odl0YruDs/yrxu4TRg/rM95/O7v/CLPD/PFfIwj1QiYVwFpKq4W+H595q8TZHNi8DRh1OEVgN/uwDxyGu/TFX+VgyFTWGdvLuyyBkAhzhMi53xm/Mz8izSPVLmbsN2PgPl2IwW7lQJH+PTf46QaHzhLaH7TE0PcBVCVJVUrvDKjuKVBjxViniPHUSTEb7rEjJ9d5PHXSjwf5x6pvEdzGf0hgXu7ZqkvvVLgffX7W6/Qk2QPnwBHu6+T7Np8y7OEYhAIpsEw3CK1TcpLo+Tzu7jp9b1kenltjLDs850DjNfXDnhujAHzjePSvrbo8QK6uHSYOk2s8Ikw2cP/0XGolxEeOeMhVRHFHKl+eeUAPZxM8W2w4PM7RUeoNitFXJjdpeqyZStgvmV4GjzUEYYMRsDQxZXHqXfe49x8ArdCSX70w0pALfh9mf+beirFOQ47+IesscFUW6oC5luG566HiPRdKmqCEVRbIOwm00m2sznWh7sv8SP8d5cYXkg9Ta3pzAhhiIt85SCINcGGGE/rxU3bEDDfdqjEUEdcy578nq/9TmZ4BVg2U7o/yueCicwRMp3O8hsj7NvoD/9A6IFar2XYaQyY1xHRcrNINtMjJ7UfyNN5PqEBoXfv67wJL+3jlmY++PLtL5SHy/Jfzo5l8wX1+F0AAAAASUVORK5CYII=
// ==/UserScript==

/* global W */
/* global I18n */
/* global jQuery */
/* global OpenLayers */
/* global GM_addStyle */
/* global GM_xmlhttpRequest */

(function() {

    'use strict';
    console.info('wme-ur-minus: loading...');
    let wMethods = {};
    let LonLat;
    let Projection;
    let Point;
    let LinearRing;
    let Polygon;
    const SCRIPT_ID = "ur_minus";
    const UR_MINUS_PREFIX = 'wme-ur-minus_';
    const USER_PREF_ACTIVE = UR_MINUS_PREFIX + 'user_preferences_active';

    const USER_PREF_ALL_URS = UR_MINUS_PREFIX + 'user_preferences_all_urs';
    const USER_PREF_NO_COMMENTS = UR_MINUS_PREFIX + 'user_preferences_no_comments';
    const USER_PREF_HAS_COMMENTS = UR_MINUS_PREFIX + 'user_preferences_has_comments';
    const USER_PREF_LAST_COMMENT_BY_ME = UR_MINUS_PREFIX + 'user_preferences_last_comment_by_me';
    const USER_PREF_LAST_COMMENT_BEFORE = UR_MINUS_PREFIX + 'user_preferences_last_comment_before';
    const USER_PREF_LAST_COMMENT_BEFORE_NUM_OF_DAYS = UR_MINUS_PREFIX + 'user_preferences_last_comment_before_num_of_days';
    const USER_PREF_WITH_COMMENTS_NOT_MINE = UR_MINUS_PREFIX + 'user_preferences_with_comments_not_mine';
    const USER_PREF_OUTSIDE_MY_AREAS_UR = UR_MINUS_PREFIX + 'user_preferences_outside_my_areas_ur';
    const USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_UR = UR_MINUS_PREFIX + 'user_preferences_inside_other_managers_ur';

    const USER_PREF_ALL_PURS = UR_MINUS_PREFIX + 'user_preferences_all_purs';
    const USER_PREF_ABOVE_MY_RANK = UR_MINUS_PREFIX + 'user_preferences_above_my_rank';
    const USER_PREF_OUTSIDE_MY_AREAS_PUR = UR_MINUS_PREFIX + 'user_preferences_outside_my_areas_pur';
    const USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_PUR = UR_MINUS_PREFIX + 'user_preferences_inside_other_managers_pur';
    const USER_PREF_CHARGING_STATIONS = UR_MINUS_PREFIX + 'user_preferences_charging_stations_pur';
    const WME_UR_MINUS_DIV_TOOLTIP_ID = UR_MINUS_PREFIX + 'DivTooltip';
    const ALL_USER_PERF_CHECKBOXES_IDS = [USER_PREF_ALL_URS, USER_PREF_NO_COMMENTS, USER_PREF_HAS_COMMENTS, USER_PREF_LAST_COMMENT_BY_ME, USER_PREF_LAST_COMMENT_BEFORE, USER_PREF_WITH_COMMENTS_NOT_MINE, USER_PREF_OUTSIDE_MY_AREAS_UR, USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_UR, USER_PREF_ALL_PURS, USER_PREF_ABOVE_MY_RANK, USER_PREF_OUTSIDE_MY_AREAS_PUR, USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_PUR, USER_PREF_CHARGING_STATIONS];
    const STORAGE_KEY_MY_MANAGED_AREAS = UR_MINUS_PREFIX + "my-managed-areas";
    let urMinus_csrfToken;

    if (typeof OpenLayers != 'undefined' && W && W['userscripts'] && W['userscripts']['state'] && W['userscripts']['state']['isReady']) {
        console.debug('wme-ur-minus: WME is ready.');
        init();
    } else {
        console.debug('wme-ur-minus: WME is not ready. adding event listener.');
        document.addEventListener("wme-ready", init, {
            once: true,
        });
    }

    function getCsrfToken(cb) {
        console.debug('wme-ur-minus: getCsrfToken()');
        let url = "https://" + document.location.host + W['Config'].paths['configurationInfo'];
        GM_xmlhttpRequest({
            url: url,
            headers: {
                "Content-Type": "application/json",
                "Accept": "application/json, text/javascript, */*; q=0.01"            },
            onload: function(response) {
                if (response.status === 200) {
                    let cookies = response['responseHeaders'].match(/_csrf_token=(.*); Path/i);
                    if (cookies && cookies.length >= 2) {
                        let csrfToken = cookies[1];
                        console.debug('wme-ur-minus: getCsrfToken() returning: ' + csrfToken);
                        cb(csrfToken);
                    } else {
                        console.error('wme-ur-minus: getCsrfToken() responseHeaders: ' + response['responseHeaders']);
                    }
                } else {
                    console.error('wme-ur-minus: getCsrfToken() response status: ' + response.status);
                }
            }
        });
    }


    function init() {
        console.info('wme-ur-minus: init()');
        document.addEventListener("wme-map-data-loaded", dataLoadedEvent);
        //noinspection JSUnresolvedVariable
        LonLat = OpenLayers.LonLat.bind(OpenLayers);
        //noinspection JSUnresolvedVariable
        Projection = OpenLayers.Projection.bind(OpenLayers);
        //noinspection JSUnresolvedVariable
        Point = OpenLayers.Geometry.Point.bind(OpenLayers);
        //noinspection JSUnresolvedVariable
        LinearRing = OpenLayers.Geometry.LinearRing.bind(OpenLayers);
        //noinspection JSUnresolvedVariable
        Polygon = OpenLayers.Geometry.Polygon.bind(OpenLayers);
        //noinspection JSUnresolvedVariable
        wMethods["getAppRegionCode"] = W['app'].getAppRegionCode;
        //noinspection JSUnresolvedVariable
        wMethods["waitForElementConnected"] = W['userscripts'].waitForElementConnected.bind(W['userscripts']);
        //noinspection JSUnresolvedVariable
        wMethods["registerSidebarTab"] = W['userscripts'].registerSidebarTab.bind(W['userscripts']);
        //noinspection JSUnresolvedVariable
        //wMethods["getLayerByName"] = W['map'].getLayerByName.bind(W['map']);
        getCsrfToken(function (val) {
            urMinus_csrfToken = val;
            afterGettingCsrfToken();
        })
    }

    function afterGettingCsrfToken() {
        setTimeout(function () {
            handleUI();
            processFilters();
        }, 1000)
    }

    function processFilters() {
        console.info('wme-ur-minus: processFilters()');
        let userPrefActive = localStorage.getItem(USER_PREF_ACTIVE) === "true";
        let managedAreas = getManagedAreas();
        processMurFilters(userPrefActive, managedAreas);
        processVenueFilters(userPrefActive, managedAreas);
        addTooltipEvents();
    }

    function processVenueFilters(userPrefActive, managedAreas) {
        console.debug('wme-ur-minus: processVenueFilters()');
        if (W.model['venues'] && W.model['venues'].objects) {
            let allPlaceUpdateRequestIDs = Object.values(W.model['venues'].objects).filter(venue => venue.attributes['venueUpdateRequests'].length > 0).map(venue => venue.attributes.id);
            /*
            let allPlaceUpdateRequestIDs = jQuery("div").map(function () {
                if (parseInt($(this).attr("data-update-count")) > 0) {
                    return $(this).attr("data-id");
                }
            }).get();
            */
            let userID = W['loginManager']['user']['id'] || W['loginManager']['user']['attributes']['id'];
            let userRank = W['loginManager']['user']['rank'] || W['loginManager']['user']['attributes']['rank'];
            let userPrefAllPurs = localStorage.getItem(USER_PREF_ALL_PURS) === "true";
            let userPrefAboveMyRank = localStorage.getItem(USER_PREF_ABOVE_MY_RANK) === "true";
            let userPrefOutsideMyAreasPur = localStorage.getItem(USER_PREF_OUTSIDE_MY_AREAS_PUR) === "true";
            let userPrefInsideOtherManagersAreasPur = localStorage.getItem(USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_PUR) === "true";
            let userPrefChargingStations = localStorage.getItem(USER_PREF_CHARGING_STATIONS) === "true";
            let allVenues = W.model['venues'].objects;
            console.info('wme-ur-minus: processVenueFilters() processing ' + allPlaceUpdateRequestIDs.length + ' venues update requests...');
            allPlaceUpdateRequestIDs.forEach(id => {
                let venue = allVenues[id];
                let hideBecauseAllPurs = shouldHBecauseAllPurs(userPrefActive, venue, userPrefAllPurs);
                let hideBecauseAboveMyRank = shouldHBecauseAboveMyRank(userPrefActive, venue, userPrefAboveMyRank, userRank);
                let hideBecauseOutsideMyAreaPur = shouldHideBecauseOutsideMyArea(userPrefActive, venue, userID, userPrefOutsideMyAreasPur, managedAreas["my"]);
                let hideBecauseInsideOtherManagersAreasPur = shouldHideBecauseInsideOtherManagersAreas(userPrefActive, venue, userID, userPrefInsideOtherManagersAreasPur, managedAreas);
                let hideBecauseChargingStationsPur = shouldHideBecauseChargingStationsPur(userPrefActive, venue, userPrefChargingStations);
                let hide = hideBecauseAllPurs || hideBecauseAboveMyRank || hideBecauseOutsideMyAreaPur || hideBecauseInsideOtherManagersAreasPur || hideBecauseChargingStationsPur;
                if (hide) {
                    hideMUR({id:venue['attributes']['id']});
                } else {
                    showMUR({id:venue['attributes']['id']});
                }
            });
        }
    }

    function processMurFilters(userPrefActive, managedAreas) {
        console.debug('wme-ur-minus: processMurFilters()');
        if (W.model.mapUpdateRequests && W.model.mapUpdateRequests.objects) {
            getUrComments(userPrefActive, function (urWithCommentsResponse) {
                let userID = W['loginManager']['user']['id'] || W['loginManager']['user']['attributes']['id'];
                let userPrefAllUrs = localStorage.getItem(USER_PREF_ALL_URS) === "true";
                let userPrefNoComments = localStorage.getItem(USER_PREF_NO_COMMENTS) === "true";
                let userPrefHasComments = localStorage.getItem(USER_PREF_HAS_COMMENTS) === "true";
                let userPrefLastCommentByMe = localStorage.getItem(USER_PREF_LAST_COMMENT_BY_ME) === "true";
                let now = (new Date()).getTime();
                let userPrefLastCommentBefore = localStorage.getItem(USER_PREF_LAST_COMMENT_BEFORE) === "true";
                let userPrefLastCommentBeforeNumOfDays = parseInt(localStorage.getItem(USER_PREF_LAST_COMMENT_BEFORE_NUM_OF_DAYS));
                let userPrefWithCommentsNotMine = localStorage.getItem(USER_PREF_WITH_COMMENTS_NOT_MINE) === "true";
                let userPrefOutsideMyAreasUr = localStorage.getItem(USER_PREF_OUTSIDE_MY_AREAS_UR) === "true";
                let userPrefInsideOtherManagersAreasUr = localStorage.getItem(USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_UR) === "true";
                let users = urWithCommentsResponse['users']['objects'];
                console.info('wme-ur-minus: processMurFilters() processing ' + Object.keys(W.model.mapUpdateRequests.objects).length + ' map update requests...');
                Object.values(W.model.mapUpdateRequests.objects).forEach(mur => {
                    let murID = mur['attributes']['id'];
                    console.debug('wme-ur-minus: processMurFilters() checking map update request ' + murID);
                    let murComments = urWithCommentsResponse['updateRequestSessions']['objects'].find(obj => obj.id === murID);
                    let hideBecauseAllUrs = shouldHideBecauseAllUrs(userPrefActive, mur, userID, userPrefAllUrs);
                    let hideBecauseNoComments = shouldHideBecauseNoComments(userPrefActive, mur, murComments, userPrefNoComments);
                    let hideBecauseHasComments = shouldHideBecauseHasComments(userPrefActive, mur, murComments, userPrefHasComments);
                    let hideBecauseLastCommentByMe = shouldHideBecauseLastCommentByMe(userPrefActive, mur, userID, userPrefLastCommentByMe, murComments);
                    let hideBecauseLastCommentTooNew = shouldHideBecauseLastCommentTooNew(userPrefActive, mur, userPrefLastCommentBefore, userPrefLastCommentBeforeNumOfDays, now);
                    let hideBecauseWithCommentsNotMine = shouldHideBecauseWithCommentsNotMine(userPrefActive, mur, userPrefWithCommentsNotMine, murComments, users, userID);
                    let hideBecauseOutsideMyAreaUr = shouldHideBecauseOutsideMyArea(userPrefActive, mur, userID, userPrefOutsideMyAreasUr, managedAreas["my"]);
                    let hideBecauseInsideOtherManagersAreasUr = shouldHideBecauseInsideOtherManagersAreas(userPrefActive, mur, userID, userPrefInsideOtherManagersAreasUr, managedAreas);
                    let hide = hideBecauseAllUrs || hideBecauseNoComments || hideBecauseHasComments || hideBecauseLastCommentByMe || hideBecauseLastCommentTooNew || hideBecauseWithCommentsNotMine || hideBecauseOutsideMyAreaUr || hideBecauseInsideOtherManagersAreasUr;
                    if (hide) {
                        hideMUR({id:murID});
                    } else {
                        showMUR({id:murID, murComments:murComments});
                    }
                });
            });
        }
    }

    const DUMMY_RESPONSE = {"updateRequestSessions":{"objects":[]},"users":{"objects":[]}};
    function getUrComments(userPrefActive, cb) {
        console.debug('wme-ur-minus: getUrComments()');
        if (userPrefActive) {
            let hasCommentsIDs = Object.values(W.model.mapUpdateRequests.objects).filter(mur => mur.attributes['hasComments']).map(mur => mur.attributes.id)
            if (hasCommentsIDs.length > 0) {
                getUpdateRequestSessions(hasCommentsIDs, function (response) {
                    if (response) {
                        console.debug('wme-ur-minus: getUrComments() got ' + response['updateRequestSessions']['objects'].length + ' URs with comments and ' + response['users']['objects'].length + " users");
                        cb(response);
                    } else {
                        console.error('wme-ur-minus: getUrComments() no response');
                    }
                });
            } else {
                cb(DUMMY_RESPONSE);
            }
        } else {
            cb(DUMMY_RESPONSE);
        }
    }

    function shouldHBecauseAllPurs(userPrefActive, venue, userPrefAllPurs) {
        let result = userPrefActive && userPrefAllPurs;
        console.debug('wme-ur-minus: shouldHBecauseAllPurs() venue: ' + venue['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHBecauseAboveMyRank(userPrefActive, venue, userPrefAboveMyRank, userRank) {
        let result = false;
        if (userPrefActive && userPrefAboveMyRank) {
            result = (userRank < venue.attributes['lockRank'] || venue.attributes['adLocked']);
        }
        console.debug('wme-ur-minus: shouldHBecauseAboveMyRank() venue: ' + venue['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHideBecauseLastCommentTooNew(userPrefActive, mur, userPrefLastCommentBefore, userPrefLastCommentBeforeNumOfDays, now) {
        let result = false;
        if (userPrefActive && userPrefLastCommentBefore) {
            result = (now - mur.attributes['updatedOn']) < 1000 * 60 * 60 * 24 * userPrefLastCommentBeforeNumOfDays;
        }
        console.debug('wme-ur-minus: shouldHideBecauseLastCommentTooNew() mur: ' + mur['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHideBecauseWithCommentsNotMine(userPrefActive, mur, userPrefWithCommentsNotMine, murComments, users, userID) {
        let result = false;
        if (userPrefActive && userPrefWithCommentsNotMine) {
            if (murComments && murComments.comments.length) {
                // has comments
                result = true;
                murComments.comments.forEach(comment => {
                    if (comment['userID'] === userID) {
                        console.debug('wme-ur-minus: shouldHideBecauseWithCommentsNotMine() mur: ' + mur['attributes']['id'] + " found 'mine' comment: " + comment.text);
                        result = false;
                    }
                })
            }
        }
        console.debug('wme-ur-minus: shouldHideBecauseWithCommentsNotMine() mur: ' + mur['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHideBecauseAllUrs(userPrefActive, mur, userID, userPrefAllUrs) {
        let result = userPrefActive && userPrefAllUrs;
        console.debug('wme-ur-minus: shouldHideBecauseAllUrs() mur: ' + mur['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHideBecauseLastCommentByMe(userPrefActive, mur, userID, userPrefLastCommentByMe, murComments) {
        let result = false;
        if (userPrefActive && userPrefLastCommentByMe) {
            if (murComments && murComments.comments && murComments.comments.length > 0) {
                result = (userID === murComments.comments[murComments.comments.length-1]['userID']);
            }
        }
        console.debug('wme-ur-minus: shouldHideBecauseLastCommentByMe() mur: ' + mur['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHideBecauseNoComments(userPrefActive, mur, murComments, userPrefNoComments) {
        let result = false;
        if (userPrefActive && userPrefNoComments) {
            result = !murComments || !murComments.comments || murComments.comments.length === 0;
        }
        console.debug('wme-ur-minus: shouldHideBecauseNoComments() mur: ' + mur['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHideBecauseHasComments(userPrefActive, mur, murComments, userPrefHasComments) {
        let result = false;
        if (userPrefActive && userPrefHasComments) {
            result = murComments && murComments.comments && murComments.comments.length > 0;
        }
        console.debug('wme-ur-minus: shouldHideBecauseHasComments() mur: ' + mur['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHideBecauseOutsideMyArea(userPrefActive, obj, userID, userPrefOutsideMyAreasUr, myManagedAreas) {
        let result = false;
        if (userPrefActive && userPrefOutsideMyAreasUr) {
            result = true;
            Object.values(myManagedAreas).forEach(managedArea => {
                let geometry = obj['attributes']['geometry'];
                if (geometry instanceof OpenLayers['Geometry']['Polygon']) {
                    // noinspection JSUnresolvedFunction
                    geometry = geometry.getCentroid();
                }
                // noinspection JSUnresolvedFunction
                if (managedArea['geometry'].containsPoint(geometry)) {
                    result = false;
                }
            });
        }
        console.debug('wme-ur-minus: shouldHideBecauseOutsideMyArea() obj: ' + obj['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHideBecauseInsideOtherManagersAreas(userPrefActive, obj, userID, userPrefInsideOtherManagersAreasUr, managedAreas) {
        let result = false;
        if (userPrefActive && userPrefInsideOtherManagersAreasUr) {
            // check if inside others managed area:
            Object.values(managedAreas["others"]).forEach(managedArea => {
                let geometry = obj['attributes']['geometry'];
                if (geometry instanceof OpenLayers['Geometry']['Polygon']) {
                    // noinspection JSUnresolvedFunction
                    geometry = geometry.getCentroid();
                }
                // noinspection JSUnresolvedFunction
                if (managedArea['geometry'].containsPoint(geometry)) {
                    result = true;
                    // inside others managers area. check if not in mine
                    Object.values(managedAreas["my"]).forEach(managedArea => {
                        let geometry = obj['attributes']['geometry'];
                        if (geometry instanceof OpenLayers['Geometry']['Polygon']) {
                            // noinspection JSUnresolvedFunction
                            geometry = geometry.getCentroid();
                        }
                        // noinspection JSUnresolvedFunction
                        if (managedArea['geometry'].containsPoint(geometry)) {
                            console.debug('wme-ur-minus: shouldHideBecauseInsideOtherManagersAreas() obj: ' + obj['attributes']['id'] + " is in both other and mine areas. Will not hide");
                            result = false;
                        }
                    });
                }
            });
        }
        console.debug('wme-ur-minus: shouldHideBecauseInsideOtherManagersAreas() obj: ' + obj['attributes']['id'] + ": " + result);
        return result;
    }

    function shouldHideBecauseChargingStationsPur(userPrefActive, venue, userPrefChargingStations) {
        let result = false;
        if (userPrefActive && userPrefChargingStations) {
            if (venue.attributes.categories.includes("CHARGING_STATION")) {
                result = true;
            }
        }
        console.debug('wme-ur-minus: shouldHideBecauseChargingStationsPur() obj: ' + venue['attributes']['id'] + ": " + result);
        return result;
    }

    function hideMUR(params) {
        console.debug('wme-ur-minus: hideMUR() id: ' + params.id);
        let olID = getOlID(params.id);
        if (olID) {
            jQuery('image[id="' + olID +'"]').hide();
        } else {
            console.warn('wme-ur-minus: hideMUR() id: ' + params.id + ' olID not found.');
        }
    }

    function showMUR(params) {
        console.debug('wme-ur-minus: showMUR() id: ' + params.id);
        let olID = getOlID(params.id);
        if (olID) {
            let images = jQuery('image[id="' + olID +'"]');
            images.show();
            if (params.murComments) {
                if (images.length === 1) {
                    addCommentsCounter(images[0], params.murComments);
                } else {
                    console.debug('wme-ur-minus: showMUR() id: ' + params.id + ' not found with olID ' + olID);
                }
            }
        } else {
            console.warn('wme-ur-minus: showMUR() id: ' + params.id + ' not found.');
        }
    }

    function getOlID(murID) {
        let olID = W.map.mapUpdateRequestsLayer.featureMap.get(murID)?.geometry.id;
        if (olID) {
            return olID;
        } else {
            console.warn('wme-ur-minus: getOlID() id: ' + murID + ' not found.');
            return null;
        }
    }

    function addCommentsCounter(imageElement, murComments) {
        console.debug('wme-ur-minus: addCommentsCounter() id: ' + murComments.id);
        let commentsCounter = murComments?.comments?.length || 0;
        const uniqueGroupId = `speech-bubble-group-${imageElement.id}`;
        const existingGroup = imageElement.parentNode.querySelector(`#${uniqueGroupId}`);
        if (existingGroup) {
            existingGroup.remove();
        }
        const groupElement = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        groupElement.setAttribute('id', uniqueGroupId);
        const imageX = parseFloat(imageElement.getAttribute('x'));
        const imageY = parseFloat(imageElement.getAttribute('y'));
        const imageWidth = parseFloat(imageElement.getAttribute('width'));
        //const imageHeight = parseFloat(imageElement.getAttribute('height'));
        const centerX = imageX + imageWidth - 7; // Adjusted to align with the bubble
        const centerY = imageY + 7; // Adjusted to center vertically
        const circleElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        circleElement.setAttribute('cx', centerX);
        circleElement.setAttribute('cy', centerY);
        circleElement.setAttribute('r', '7'); // Radius to fully cover the three dots
        circleElement.setAttribute('fill', '#ffffff'); // Match the speech bubble's white background
        circleElement.setAttribute('stroke', '#000000'); // Optional border for visibility
        circleElement.setAttribute('stroke-width', '1');
        const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
        textElement.setAttribute('x', centerX);
        textElement.setAttribute('y', centerY);
        textElement.setAttribute('dy', '3'); // Fine-tune vertical centering
        textElement.setAttribute('font-size', '10'); // Slightly larger for readability
        textElement.setAttribute('fill', '#000000'); // Black text
        textElement.setAttribute('font-family', 'Arial, sans-serif');
        textElement.setAttribute('text-anchor', 'middle'); // Center horizontally
        textElement.textContent = commentsCounter.toString();
        groupElement.appendChild(circleElement);
        groupElement.appendChild(textElement);
        imageElement.parentNode.appendChild(groupElement);
    }

    function handleUI() {
        console.info('wme-ur-minus: handleUI()');
        let registerSidebarTabResult = wMethods["registerSidebarTab"](SCRIPT_ID);
        wMethods["waitForElementConnected"](registerSidebarTabResult['tabLabel']).then(() => {
            registerSidebarTabResult['tabLabel'].innerText = "UR-";
        });
        wMethods["waitForElementConnected"](registerSidebarTabResult['tabPane']).then(() => {
            drawUI(registerSidebarTabResult['tabPane']);
        });
    }

    function drawUI(tabPane) {
        console.debug('wme-ur-minus: drawUI()');
        addUserScriptTab(tabPane);
        addTooltips();
        addStyle();
        // register for new events. to invoke only once on startup, make a short delay
        setTimeout(function () {
            document.addEventListener("wme-map-data-loaded", processFilters)
        }, 1000);
        // refresh filters on panel close
        document.body.addEventListener('click', function(event) {
            if (event.target.classList.contains('close-panel')) {
                console.debug('wme-ur-minus: click() on close-panel');
                processFilters();
            }
        });
        console.info('wme-ur-minus: drawUI() done');
    }

    function addStyle() {
        GM_addStyle(`.ur-minus-comments-tooltip {
            background: white;
            border-radius: 50%;
            padding: 8px;
            border: 1px solid black;
            width: 4px;
            height: 4px;
            position: absolute;
            top: -8px;
            left: 20px;
            justify-content: center;
            align-items: center;
            display: flex;
            font-size: 10px;
        }`);
        GM_addStyle(`.ur-minus-remove-after-background-image::after {
            background-image: none !important;
        }`);
    }

    function addUserScriptTab(tabPane) {
        console.debug('wme-ur-minus: addUserScriptTab()');
        let aSection = document.createElement('section');
        aSection.id = 'close-mur';
        aSection.className = 'tab-pane';
        let section = document.createElement('p');
        aSection.appendChild(section);
        let userPrefActive = localStorage.getItem(USER_PREF_ACTIVE) === "true" ? ' checked' : '';
        let userPrefActiveStr = userPrefActive ? '':' disabled';
        let userPrefAllUrsStr = localStorage.getItem(USER_PREF_ALL_URS) === "true" ? ' checked' : '';
        let userPrefNoCommentsStr = localStorage.getItem(USER_PREF_NO_COMMENTS) === "true" ? ' checked' : '';
        let userPrefHasCommentsStr = localStorage.getItem(USER_PREF_HAS_COMMENTS) === "true" ? ' checked' : '';
        let userPrefLastCommentByMeStr = localStorage.getItem(USER_PREF_LAST_COMMENT_BY_ME) === "true" ? ' checked' : '';
        let userPrefLastCommentBefore = localStorage.getItem(USER_PREF_LAST_COMMENT_BEFORE) === "true" ? ' checked' : '';
        let userPrefWithCommentsNotMine = localStorage.getItem(USER_PREF_WITH_COMMENTS_NOT_MINE) === "true" ? ' checked' : '';
        let userPrefOutsideMyAreasUr = localStorage.getItem(USER_PREF_OUTSIDE_MY_AREAS_UR) === "true" ? ' checked' : '';
        let userPrefInsideOtherManagersAreasUr = localStorage.getItem(USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_UR) === "true" ? ' checked' : '';
        let userPrefAllPurs = localStorage.getItem(USER_PREF_ALL_PURS) === "true" ? ' checked' : '';
        let userPrefAboveMyRank = localStorage.getItem(USER_PREF_ABOVE_MY_RANK) === "true" ? ' checked' : '';
        let userPrefOutsideMyAreasPur = localStorage.getItem(USER_PREF_OUTSIDE_MY_AREAS_PUR) === "true" ? ' checked' : '';
        let userPrefInsideOtherManagersAreasPur = localStorage.getItem(USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_PUR) === "true" ? ' checked' : '';
        let userPrefChargingStationsPur = localStorage.getItem(USER_PREF_CHARGING_STATIONS) === "true" ? ' checked' : '';
        section.innerHTML = '<b>When active, filter user reports</b><br/>' +
            '<br/>Active:&nbsp;<input type="checkbox" id="WME_ur_minus_active"' + userPrefActive + '/><br/>' +
            '<br/><b>Hide Map Update Requests:</b><br/>' +
            '<input type="checkbox" id="' + USER_PREF_ALL_URS +'"' + userPrefAllUrsStr + userPrefActiveStr + '/>  All<br/>' +
            '<input type="checkbox" id="' + USER_PREF_HAS_COMMENTS +'"' + userPrefHasCommentsStr + userPrefActiveStr + '/>  Have comments<br/>' +
            '<input type="checkbox" id="' + USER_PREF_NO_COMMENTS +'"' + userPrefNoCommentsStr + userPrefActiveStr + '/>  Have no comments<br/>' +
            '<input type="checkbox" id="' + USER_PREF_LAST_COMMENT_BY_ME +'"' + userPrefLastCommentByMeStr + userPrefActiveStr + '/>  Last comment by me<br/>' +
            '<input type="checkbox" id="' + USER_PREF_LAST_COMMENT_BEFORE + '"' + userPrefLastCommentBefore + userPrefActiveStr + '/>  Last comment at least <select id="' + USER_PREF_LAST_COMMENT_BEFORE_NUM_OF_DAYS + '"><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="12">12</option><option value="14">14</option></select>&nbsp;days old.<br/>' +
            '<input type="checkbox" id="' + USER_PREF_WITH_COMMENTS_NOT_MINE +'"' + userPrefWithCommentsNotMine + userPrefActiveStr + '/>  With comments - not mine<br/>' +
            '<input type="checkbox" id="' + USER_PREF_OUTSIDE_MY_AREAS_UR +'"' + userPrefOutsideMyAreasUr + userPrefActiveStr + '/>  Outside my managed area(s)<br/>' +
            '<input type="checkbox" id="' + USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_UR +'"' + userPrefInsideOtherManagersAreasUr + userPrefActiveStr + '/>  Inside other managers areas<br/>' +
            '<br/><b>Hide Place Update Requests:</b><br/>' +
            '<input type="checkbox" id="' + USER_PREF_ALL_PURS + '"' + userPrefAllPurs + userPrefActiveStr + '/>  All<br/>' +
            '<input type="checkbox" id="' + USER_PREF_ABOVE_MY_RANK + '"' + userPrefAboveMyRank + userPrefActiveStr + '/>  Above my rank<br/>' +
            '<input type="checkbox" id="' + USER_PREF_OUTSIDE_MY_AREAS_PUR +'"' + userPrefOutsideMyAreasPur + userPrefActiveStr + '/>  Outside my managed area(s)<br/>' +
            '<input type="checkbox" id="' + USER_PREF_INSIDE_OTHERS_MANAGERS_AREAS_PUR +'"' + userPrefInsideOtherManagersAreasPur + userPrefActiveStr + '/>  Inside other managers areas<br/>' +
            '<input type="checkbox" id="' + USER_PREF_CHARGING_STATIONS +'"' + userPrefChargingStationsPur + userPrefActiveStr + '/>  ' + I18n.lookup("venues.categories.CHARGING_STATION") + '<br/>';
        tabPane.append(aSection);
        // set values from storage and register events
        jQuery("#WME_ur_minus_active").on("change", urMinusActiveChange);
        ALL_USER_PERF_CHECKBOXES_IDS.forEach(id => {
            jQuery("#" + id).on("change", urMinusUserPerfCheckboxChange);
        })
        let numOfDaysItem = jQuery("#" + USER_PREF_LAST_COMMENT_BEFORE_NUM_OF_DAYS);
        numOfDaysItem.val(localStorage.getItem(USER_PREF_LAST_COMMENT_BEFORE_NUM_OF_DAYS));
        numOfDaysItem.on("change", urMinusNumOfDaysChange);
        console.info('wme-ur-minus: addUserScriptTab() done');
    }

    function addTooltipEvents() {
        console.debug('wme-ur-minus: addTooltipEvents()');
        // mur
        //let urLayerId = wMethods["getLayerByName"]("update_requests").id;
        let allUpdateRequestsSelector = "div.map-marker.user-generated.map-problem";
        jQuery(allUpdateRequestsSelector).on( "mouseenter", {type:"mur"}, urMouseEnter ).on( "mouseleave", urMouseOut );
        //console.debug('wme-ur-minus: addTooltipEvents() number of mur: ' + jQuery(allUpdateRequestsSelector).length + " murs: " + Object.keys(W.model.mapUpdateRequests.objects).length);
        // pur
        let allPlaceUpdateRequests = jQuery("div.map-marker.place-update").filter(function () {
            return parseInt(jQuery(this).attr("data-update-count")) > 0;
        });
        jQuery(allPlaceUpdateRequests).on( "mouseenter", {type:"venue"} , urMouseEnter).on( "mouseleave", urMouseOut );
        // mp
        //let mpLayerId = wMethods["getLayerByName"]("mapProblems").id;
        let allMapProblemsSelector = "div.map-marker:not(.user-generated).map-problem";
        jQuery(allMapProblemsSelector).on( "mouseenter", {type:"mp"} , urMouseEnter).on( "mouseleave", urMouseOut );
        // segments suggestions
        //let ssLayerId = wMethods["getLayerByName"]("segment_suggestions_markers").id;
        let allSSSelector = "div.map-marker.segment-suggestion-marker";
        jQuery(allSSSelector).on( "mouseenter", {type:"ss"} , urMouseEnter).on( "mouseleave", urMouseOut );
    }

    function urMouseEnter(event) {
        console.debug('wme-ur-minus: urMouseEnter()');
        let toolTipDiv = jQuery('#' + WME_UR_MINUS_DIV_TOOLTIP_ID);
        if (toolTipDiv.css("visibility") === "hidden") {
            toolTipDiv.css('visibility', 'visible');
            toolTipDiv.css('top', jQuery(event.currentTarget).offset().top);
            toolTipDiv.css('left', jQuery(event.currentTarget).offset().left + 25);
            toolTipDiv.css('display', 'block');
            if (event.data.type === "mur") {
                let murID = this.getAttribute('data-id');
                let mur = W.model.mapUpdateRequests.objects[murID]
                if (mur) {
                    toolTipDiv.html(getUrTooltipText(mur));
                } else {
                    console.error('wme-ur-minus: urMouseEnter() mur not found: ' + murID);
                }
            } else if (event.data.type === "venue") {
                let venueID = this.getAttribute('data-id');
                let venue = W.model['venues'].objects[venueID]
                if (venue) {
                    toolTipDiv.html(getPurTooltipText(venue));
                } else {
                    console.error('wme-ur-minus: urMouseEnter() mur venue found: ' + venueID);
                }
            } else if (event.data.type === "mp") {
                let mpID = this.getAttribute('data-id');
                let mp = W.model['mapProblems'].objects[mpID]
                if (mp) {
                    toolTipDiv.html(getMpTooltipText(mp));
                } else {
                    console.error('wme-ur-minus: urMouseEnter() mp not found: ' + mpID);
                }
            } else if (event.data.type === "ss") {
                let mousePosition = jQuery('.wz-map-ol-control-span-mouse-position').text().split(" ");
                let lat = mousePosition[0];
                let lon = mousePosition[1];
                let ssID;
                let candidateDistance = 1000000;
                Object.keys(W.model['segmentSuggestions'].objects).forEach(id => {
                    let segmentSuggestion = W.model['segmentSuggestions'].objects[id];
                    //let centroid2 = segmentSuggestion.attributes.geometry.getCentroid();
                    let center = segmentSuggestion.getCenter();
                    let segmentSuggestionCentroid = convertTo4326(center.x,center.y);
                    let latDistance = Math.round(Math.abs(lat * 1000000 - segmentSuggestionCentroid.lat * 1000000));
                    let lonDistance = Math.round(Math.abs(lon * 1000000 - segmentSuggestionCentroid.lon * 1000000));
                    const expectedGap = Math.pow(2, 24 - W.map.getZoom()) * 1.5; //  14=1000; 15=500; 16=250; 17=125; 18= 64; 19=32; 2^(23-W.map.getZoom())
                    let maxDistance = Math.max(latDistance, lonDistance)
                    if (maxDistance < expectedGap && maxDistance < candidateDistance) {
                        console.debug('wme-ur-minus: urMouseEnter() for ss ID: ' + id + ' max distance is: ' + maxDistance + " less than expected: " + expectedGap + " and less than candidate: " + candidateDistance);
                        ssID = id;
                        candidateDistance = maxDistance;
                    } else {
                        console.debug('wme-ur-minus: urMouseEnter() for ss ID: ' + id + ' max distance is: ' + maxDistance + " more than expected: " + expectedGap);
                    }
                });
                if (ssID) {
                    let ss = W.model['segmentSuggestions'].objects[ssID]
                    if (ss) {
                        toolTipDiv.html(getSsTooltipText(ss));
                    } else {
                        console.error('wme-ur-minus: urMouseEnter() ss not found for ID ' + ssID + '. No tooltip will be generated');
                    }
                } else {
                    console.error('wme-ur-minus: urMouseEnter() No ID was found as candidate. No tooltip will be generated');
                }
            }
        }
    }

    function getPurTooltipText(venue) {
        let attributes = venue.attributes;
        console.debug('wme-ur-minus: getPurTooltipText() venue ID: ' + attributes.id);
        let venueName = getVenueName(attributes);
        let result = '<p style="text-align: center"><b>' + venueName + '</b></p>';
        // open
        result += "<b><u>Open in:</u></b> ";
        result += "<a target='_blank' href='" + getPurURL(attributes) + "'>new tab</a>";
        result += "<br/>";
        // update type
        let updateTypeValue;
        let updateType = venue.attributes['venueUpdateRequests'][0].attributes['updateType'];
        if (updateType === 'flag') {
            let subject = venue.attributes['venueUpdateRequests'][0].attributes['subject'];
            updateTypeValue = I18n.lookup("venues.update_requests.panel.flag_title." + subject);
        } else {
            updateTypeValue = I18n.lookup("venues.update_requests.panel.title." + updateType);
        }
        result += "<b><u>Reason:</u></b> ";
        result += escapeTooltipText(updateTypeValue);
        result += "<br/>";
        // description
        result += "<b><u>Description:</u></b> ";
        result += attributes.description?escapeTooltipText(attributes.description):"(none)";
        result += "<br/>";
        // categories
        result += "<b><u>Categories:</u></b> ";
        let translatedCategories = attributes.categories.map(category => I18n.lookup("venues.categories." + category));
        result += escapeTooltipText(translatedCategories.join(","));
        result += "<br/>";
        return result;
    }

    function getVenueName(attributes) {
        if (attributes['residential']) {
            let houseNumber = attributes['houseNumber'];
            let streetID = attributes['streetID'];
            if (streetID) {
                let streenName = W.model.streets.objects[streetID].name || W.model.streets.objects[streetID].attributes.name;
                let cityID = W.model.streets.objects[streetID].cityID || W.model.streets.objects[streetID].attributes.cityID;
                let cityName = W.model['cities'].objects[cityID].attributes.name;
                return streenName + " " + houseNumber + ", " + cityName;
            } else {
                // TODO: I18n.lookup("");
                return I18n.lookup("edit.venue.no_address");
            }
        } else {
            return attributes['name'];
        }
    }

    function getUrTooltipText(mur) {
        let attributes = mur.attributes;
        console.debug('wme-ur-minus: getUrTooltipText() murID: ' + attributes.id);
        let result = '<p style="text-align: center"><b>' + attributes['typeText'] + '</b></p>';
        // open
        result += "<b><u>Open in:</u></b> ";
        result += "<a target='_blank' href='" + getMurURL(attributes) + "'>new tab</a>";
        result += "<br/>";
        // ID
        result += "<b><u>ID:</u></b> ";
        result += attributes.id;
        result += "<br/>";
        // description
        result += "<b><u>Description:</u></b> ";
        result += attributes.description?escapeTooltipText(attributes.description):"(none)";
        result += "<br/>";
        // driveDate
        result += "<b><u>Drive Date:</u></b> ";
        result += new Date(attributes.driveDate).toLocaleString(I18n.locale, {year: '2-digit', month: '2-digit',day: '2-digit', hour:'numeric', minute:'2-digit'});
        result += "<br/>";
        // Source
        if (attributes.source) {
            result += "<b><u>Source:</u></b> ";
            result += I18n.lookup(`issue_tracker.filters.UPDATE_REQUESTS.sources.${attributes.source}.title`);
            result += "<br/>";
        }
        return result;
    }

    function getMpTooltipText(mp) {
        let attributes = mp.attributes;
        console.debug('wme-ur-minus: getMpTooltipText() murID: ' + attributes.id);
        let subType = attributes['subType'];
        let texts = I18n.lookup(`problems.types.${subType}`);
        let result = '<p style="text-align: center"><b>' + texts['title'] + '</b></p>';
        // open
        result += "<b><u>Open in:</u></b> ";
        result += "<a target='_blank' href='" + getMpURL(attributes) + "'>new tab</a>";
        result += "<br/>";
        // ID
        result += "<b><u>ID:</u></b> ";
        result += attributes.id;
        result += "<br/>";
        // description
        result += "<b><u>Description:</u></b> ";
        result += texts['description'];
        result += "<br/>";
        // solution
        result += "<b><u>Solution:</u></b> ";
        result += texts['solution'];
        result += "<br/>";
        return result;
    }

    function getSsTooltipText(ss) {
        let attributes = ss.attributes;
        console.debug('wme-ur-minus: getSsTooltipText() murID: ' + attributes.id);
        let texts = I18n.lookup(`edit.segment_suggestion`);
        let result = '<p style="text-align: center"><b>' + texts['title'] + '</b></p>';
        // open
        result += "<b><u>Open in:</u></b> ";
        result += "<a target='_blank' href='" + getSsURL(ss) + "'>new tab</a>";
        result += "<br/>";
        // ID
        result += "<b><u>ID:</u></b> ";
        result += attributes.id;
        result += "<br/>";
        return result;
    }

    function getPurURL(purAttributes) {
        let id = purAttributes.id;
        let boundsCenterLonLat = purAttributes.geometry.getBounds().getCenterLonLat();
        let x = purAttributes.geometry.x?purAttributes.geometry.x:boundsCenterLonLat.lon;
        let y = purAttributes.geometry.y?purAttributes.geometry.y:boundsCenterLonLat.lat;
        let convertedTo4326 = convertTo4326(x, y);
        return document.location.origin + "/" + I18n.locale + "/editor?env=" + wMethods["getAppRegionCode"]() + "&lon=" + convertedTo4326.lon.toFixed(6) + "&lat=" + convertedTo4326.lat.toFixed(6) + "&zoomLevel=19&venueUpdateRequest=" + id;
    }

    function getMurURL(murAttributes) {
        let id = murAttributes.id;
        let x = murAttributes.geometry.x;
        let y = murAttributes.geometry.y;
        let convertedTo4326 = convertTo4326(x, y);
        return document.location.origin + "/" + I18n.locale + "/editor?env=" + wMethods["getAppRegionCode"]() + "&lon=" + convertedTo4326.lon.toFixed(6) + "&lat=" + convertedTo4326.lat.toFixed(6) + "&zoomLevel=19&mapUpdateRequest=" + id;
    }

    function getMpURL(mpAttributes) {
        let id = mpAttributes.id;
        let x = mpAttributes.geometry.x;
        let y = mpAttributes.geometry.y;
        let convertedTo4326 = convertTo4326(x, y);
        return document.location.origin + "/" + I18n.locale + "/editor?env=" + wMethods["getAppRegionCode"]() + "&lon=" + convertedTo4326.lon.toFixed(6) + "&lat=" + convertedTo4326.lat.toFixed(6) + "&zoomLevel=19&mapProblem=" + id;
    }

    function getSsURL(ss) {
        let ssAttributes = ss.attributes;
        let id = ssAttributes.id;
        let center = ss.getCenter();
        let convertedTo4326 = convertTo4326(center.x,center.y);
        return document.location.origin + "/" + I18n.locale + "/editor?env=" + wMethods["getAppRegionCode"]() + "&lon=" + convertedTo4326.lon.toFixed(6) + "&lat=" + convertedTo4326.lat.toFixed(6) + "&zoomLevel=19&segmentSuggestions=" + id;
    }

    function convertTo4326(x, y) {
        let projI = new Projection("EPSG:900913");
        let projE = new Projection("EPSG:4326");
        return (new LonLat(x, y)).transform(projI, projE);
    }

    function escapeTooltipText(str) {
        return (str||'').replace('%20', '"').replace('%27', "'").replace('%28', "(").replace('%29', ")");
    }

    function urMouseOut() {
        console.debug('wme-ur-minus: urMouseOut()');
        let tooltipDiv = jQuery('#' + WME_UR_MINUS_DIV_TOOLTIP_ID + ':hover');
        if (tooltipDiv.length > 0) {
            tooltipDiv.on("mouseleave", function () {
                jQuery('#' + WME_UR_MINUS_DIV_TOOLTIP_ID).css('visibility', 'hidden');
            });
        } else {
            jQuery('#' + WME_UR_MINUS_DIV_TOOLTIP_ID).css('visibility', 'hidden');
        }
    }

    function urMinusActiveChange() {
        console.info('wme-ur-minus: urMinusActiveChange() new value: ' + this.checked);
        localStorage.setItem(USER_PREF_ACTIVE, this.checked);
        if (this.checked) {
            setEnabled();
        } else {
            setDisabled();
        }
        processFilters();
    }

    function setEnabled() {
        console.info('wme-ur-minus: setEnabled()');
        ALL_USER_PERF_CHECKBOXES_IDS.forEach(id => {
            jQuery("#" + id).removeAttr("disabled");
        });
    }

    function setDisabled() {
        console.info('wme-ur-minus: setDisabled()');
        ALL_USER_PERF_CHECKBOXES_IDS.forEach(id => {
            jQuery("#" + id).attr("disabled", "true");
        });
        jQuery("div.ur-minus-comments-tooltip").remove();
        jQuery("div.has-comments").removeClass('ur-minus-remove-after-background-image');
    }

    function urMinusUserPerfCheckboxChange() {
        console.info('wme-ur-minus: urMinusUserPerfCheckboxChange() for ID: "' + this.id+ '" new value: ' + this.checked);
        localStorage.setItem(this.id, this.checked);
        processFilters();
    }

    function urMinusNumOfDaysChange() {
        console.info('wme-ur-minus: urMinusNumOfDaysChange() new value: ' + this.value);
        localStorage.setItem(USER_PREF_LAST_COMMENT_BEFORE_NUM_OF_DAYS, this.value);
        processFilters();
    }

    function addTooltips() {
        console.debug('wme-ur-minus: addTooltips()');
        let urTooltipDiv = document.createElement('div');
        urTooltipDiv.id = WME_UR_MINUS_DIV_TOOLTIP_ID;
        urTooltipDiv.setAttribute("style", "position: absolute; visibility: hidden; top: 107px; left: 1040px; z-index: 10000; background-color: aliceblue; border-width: 3px; border-style: solid; border-radius: 10px; box-shadow: silver 5px 5px 10px; padding: 4px; height: auto; width: auto; overflow: auto; max-width: 300px; word-wrap: break-word");
        document.body.appendChild(urTooltipDiv);
        urTooltipDiv.addEventListener("mouseover", function () {
            this.style.visibility = 'visible';
        }, false);
        urTooltipDiv.addEventListener("mouseout", function () {
            this.style.visibility = 'hidden';
        }, false);
        urTooltipDiv.addEventListener("dblclick", function () {
            this.style.visibility = 'hidden';
        }, false);
        console.debug('wme-ur-minus: addTooltips() done');
    }

    function getUpdateRequestSessions(ids, cb) {
        console.info('wme-ur-minus: getUpdateRequestSessions() for ' + ids.length + ' IDs.');
        let url = "https://" + document.location.host + W['Config'].paths['updateRequestSessions'] + "?ids=" + ids.join(",");
        fetch(url, {
            headers: {
                "X-CSRF-Token": urMinus_csrfToken,
                "Accept": "application/json"
            }
        }).then(response => {
            if (response.ok && response.status === 200) {
                console.info('wme-ur-minus: getUpdateRequestSessions() response OK');
                response.json().then(json => cb(json));
            } else {
                console.error('wme-ur-minus: getUpdateRequestSessions() failed with status: ' + response.status);
            }
        }).catch(error => {
            console.error('wme-ur-minus: getUpdateRequestSessions() failed with error: ' + error);
        });
    }

    function getManagedAreasWme() {
        let userID = W['loginManager']['user']['id'] || W['loginManager']['user']['attributes']['id'];
        let userName = W['loginManager']['user']['userName'] || W['loginManager']['user']['attributes']['userName'];
        console.debug('wme-ur-minus: getManagedAreasWme() for ' + userName);
        let my = {};
        let others = {};
        if (W.model['managedAreas'] && W.model['managedAreas'].objects) {
            Object.values(W.model['managedAreas'].objects).forEach(managedArea => {
                if ((managedArea['userID'] || managedArea.attributes['userID']) === userID) {
                    my[managedArea['id']] = managedArea;
                } else {
                    others[managedArea['id'] || managedArea.attributes['id']] = managedArea;
                }
            });
        }
        console.info('wme-ur-minus: getManagedAreasWme() for ' + userName + ' returning my ' + Object.keys(my).length + " areas");
        console.info('wme-ur-minus: getManagedAreasWme() for ' + userName + ' returning others ' + Object.keys(others).length + " areas");
        return {my:my, others:others};
    }

    function storeAreaGeomenty(managedArea) {
        let id = managedArea['id'];
        console.debug('wme-ur-minus: storeAreaGeomenty() id: ' + id);
        // noinspection JSUnresolvedFunction
        let vertices = managedArea['geometry'].getVertices();
        let pointsArray = vertices.map(vertice => [vertice['x'],vertice['y']]);
        let fromStorage = JSON.parse(localStorage.getItem(STORAGE_KEY_MY_MANAGED_AREAS) || "{}");
        fromStorage[id] = pointsArray;
        localStorage.setItem(STORAGE_KEY_MY_MANAGED_AREAS, JSON.stringify(fromStorage));
        console.info('wme-ur-minus: storeAreaGeomenty() id: ' + id + " done.");
    }

    function getManagedAreas() {
        console.debug('wme-ur-minus: getManagedAreas()');
        let my = {};
        // get from WME:
        let fromWME = getManagedAreasWme();
        // get from storage:
        let myFromStorage = JSON.parse(localStorage.getItem(STORAGE_KEY_MY_MANAGED_AREAS) || "{}");
        // convert ones from storage from string into objects:
        Object.entries(myFromStorage).forEach(([id, xyArray]) => {
            let pointsArray = xyArray.map(xy => new Point(xy[0], xy[1]));
            let linearRing = new LinearRing(pointsArray)
            let polygon = new Polygon([linearRing]);
            my[id] = { geometry:polygon };
        });
        // find which of my areas was found in WME but not yet in storage, and store them.
        let needToStore = Object.keys(fromWME['my']).filter(key => !myFromStorage[key]);
        needToStore.forEach(key => {
            console.debug('wme-ur-minus: getManagedAreas() area ' + key + ' found in WME but not in storage.');
            storeAreaGeomenty(fromWME['my'][key]);
            my[key] = fromWME[key];
        });
        return {my:my, "others":fromWME["others"]};
    }

    function dataLoadedEvent() {
        console.debug('wme-ur-minus: dataLoadedEvent()');
        addTooltipEvents();
    }

})();