WME Place Harmonizer

Harmonizes, formats, and locks a selected place

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name        WME Place Harmonizer
// @namespace   WazeUSA
// @version     2026.05.19.00
// @description Harmonizes, formats, and locks a selected place
// @author      WMEPH Development Group
// @include      https://www.waze.com/editor*
// @include      https://www.waze.com/*/editor*
// @include      https://beta.waze.com/editor*
// @include      https://beta.waze.com/*/editor*
// @exclude      https://www.waze.com/user/editor*
// @exclude      https://www.waze.com/dashboard/editor
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @require     https://update.greasyfork.org/scripts/509664/WME%20Utils%20-%20Bootstrap.js
// @require     https://greasyfork.org/scripts/37486-wme-utils-hoursparser/code/WME%20Utils%20-%20HoursParser.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js
// @require     https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js
// @license     GNU GPL v3
// @connect     greasyfork.org
// @grant       GM_addStyle
// @grant       GM_xmlhttpRequest
// @grant       GM_setClipboard
// ==/UserScript==

/* global _ */
/* global WazeWrap */
/* global LZString */
/* global HoursParser */
/* global I18n */
/* global google */
/* global turf */

/* eslint-disable max-classes-per-file */

(function main() {
  'use strict';

  // **************************************************************************************************************
  // IMPORTANT: Update this when releasing a new version of script
  // **************************************************************************************************************
  const SHOW_UPDATE_MESSAGE = true;
  const SCRIPT_UPDATE_MESSAGE = [
    'v 2026.05.19.00 : FULL SDK Migration version',
  ];

  // **************************************************************************************************************
  // TODO: SDK Limitations & Workarounds
  // **************************************************************************************************************
  // 1. Venue.description: Not exposed in WME SDK Venue interface for READING
  //    Current Status: updateVenue() DOES support writing, but Venue object doesn't expose reading
  //    Current Solution: Read from DOM textarea via UPDATED_FIELDS.description selector during WMEPH mode
  //
  //    LOCATIONS AFFECTED (runs in both highlight and WMEPH modes):
  //      - Line 4076: SuspectDesc - checks for google/yelp (guard: `args.description &&`)
  //      - Line 4567, 4572: DisplayNoteIfNeeded - pharmacy/drivethru checks (guard: `!args.description ||`)
  //      - Line 4471: MissingUSPSDescription - USPS validation (guard: `!args.highlightOnly`)
  //
  //    WHEN SDK ADDS description TO Venue INTERFACE:
  //      1. Update HarmonizationArgs constructor (line 6387):
  //         FROM: this.description = !highlightOnly ? this.getDescriptionFromDOM() : null;
  //         TO:   this.description = venue.description;
  //      2. DELETE getDescriptionFromDOM() method (lines 6396-6404)
  //      3. DELETE highlightOnly check in MissingUSPSDescription (line 4470)
  //      4. SIMPLIFY guard in SuspectDesc (line 4076):
  //         FROM: args.description && /(google|yelp)/i.test(args.description)
  //         TO:   /(google|yelp)/i.test(args.description)
  //      5. SIMPLIFY guards in DisplayNoteIfNeeded (lines 4567, 4572):
  //         FROM: !args.description || /pattern/
  //         TO:   /pattern/
  //      6. Update this TODO section or remove entirely
  //

  // **************************************************************************************************************
  // GLOBAL VARIABLES AND CONSTANTS
  // **************************************************************************************************************
  let sdk; // Declared as let because script checks for existing sdk before initialization
  let wmephSettings = {}; // Script-wide settings cache from WMEPH-Settings localStorage

  const SCRIPT_VERSION = GM_info.script.version.toString(); // pull version from header
  const SCRIPT_NAME = GM_info.script.name;
  const IS_BETA_VERSION = /Beta/i.test(SCRIPT_NAME); //  enables dev messages and unique DOM options if the script is called "... Beta"
  const BETA_VERSION_STR = IS_BETA_VERSION ? 'Beta' : ''; // strings to differentiate DOM elements between regular and beta script

  const MAX_CACHE_SIZE = 25000;
  const PROD_DOWNLOAD_URL = 'https://greasyfork.org/scripts/28690-wme-place-harmonizer/code/WME%20Place%20Harmonizer.user.js';
  const BETA_DOWNLOAD_URL =
    'YUhSMGNITTZMeTluY21WaGMzbG1iM0pyTG05eVp5OXpZM0pwY0hSekx6STROamc1TFhkdFpTMXdiR0ZqWlMxb1lYSnRiMjVwZW1WeUxXSmxkR0V2WTI5a1pTOVhUVVVsTWpCUWJHRmpaU1V5TUVoaGNtMXZibWw2WlhJbE1qQkNaWFJoTG5WelpYSXVhbk09';
  const GF_URL = 'https://greasyfork.org/scripts/28690-wme-place-harmonizer';

  const dec = (s) => atob(atob(s));

  let _layer;
  let _resultsCache = {};
  let _resultsCacheOrder = []; // Track insertion order for LRU eviction
  let _initAlreadyRun = false; // This is used to skip a couple things if already run once.  This could probably be handled better...
  let _textEntryValues = null; // Store the values entered in text boxes so they can be re-added when the banner is reassembled.

  // lock levels are offset by one
  const LOCK_LEVEL_2 = 1;
  const LOCK_LEVEL_4 = 3;

  // An enum to help clarify flag severity levels
  const SEVERITY = {
    GREEN: 0,
    BLUE: 1,
    YELLOW: 2,
    RED: 3,
    // 4 isn't used anymore
    PINK: 5,
    ORANGE: 6,
    // Historical note: 'lock', 'lock1', and 'adLock' severity levels existed in older WME but are no longer in use
  };

  // Pre-compiled regex patterns for performance (avoid recompilation in loops)
  const REGEX_PATTERNS = {
    BUTTON_ON: /^buttOn_(.*)/i,
    BUTTON_OFF: /^buttOff_(.+)/i,
    PS_ON: /^psOn_(.+)/i,
    PS_OFF: /^psOff_(.+)/i,
    FORCE_BRAND: /forceBrand<>([^,<]+)/i,
    LOCAL_URL: /^localURL_(.+)/i,
    CHECK_LOCALIZATION: /^checkLocalization<>(.+)/i,
    PHONE: /phone<>(.*?)<>/,
    KEEP_NAME: /keepName/g,
    OPTION_ALT_NAME: /^optionAltName<>(.+)/i,
    CLOSED: /^closed$/i,
    BRAND_PARENT: /^brandParent(\d+)/,
    STR_MATCH_ANY: /^strMatchAny$/i,
    PHARM_HOURS: /^pharmhours$/i,
    NOT_A_BANK: /^notABank$/i,
    OPTION_CAT2: /^optionCat2$/i,
    OPTION_NAME2: /^optionName2$/i,
    ALT_NAME2_DESC: /^altName2Desc$/i,
    SUB_FUEL: /^subFuel$/i,
    REGEX_NAME_MATCH: /^regexNameMatch<>(.+)<>/i,
    LOCK_AT: /^lockAt(\d)$/i,
    NO_UPDATE_ALIAS: /^noUpdateAlias$/i,
  };

  // Severity level colors (used for both map layer and banner background)
  const SEVERITY_COLORS = {
    [SEVERITY.GREEN]: '#08d608', // complete
    [SEVERITY.BLUE]: '#0000FF', // minor issues
    [SEVERITY.YELLOW]: '#FFFF00', // moderate issues
    [SEVERITY.RED]: '#FF0000', // major issues
    [SEVERITY.PINK]: '#FF1493', // extreme issues
    [SEVERITY.ORANGE]: '#FFA500', // other issues
    lock: '#08d608', // Used when SEVERITY = GREEN, but venue is not locked to regional standereds (old #8B008B)
    lock1: '#0000FF', //Used when SEVERITY = BLUE, but venue is not locked to regional standereds (old #FF69B4)
    adLock: '#FFD700', // ad-locked
  };

  // UI colors used throughout the script for consistency
  const UI_COLORS = {
    primary: '#0075e3', // Primary action button, links
    primaryAlt: '#0099ff', // Alternative primary
    accent: '#33ccff', // Accent elements
    success: '#118742', // Success states, buttons
    textDefault: '#3a3a3a', // Default text color
    textLink: '#3232e6', // Link text color
    textDisabled: '#999', // Disabled text
    fallback: '#CCCCCC', // Fallback/unset color
  };

  // Parking lot color mapping
  const PARKING_TYPE_COLORS = {
    PUBLIC: '#0000FF', // blue
    RESTRICTED: '#FFFF00', // yellow
    PRIVATE: '#FF0000', // red
  };

  // SHORTCUT STUFF
  let _shortcutParse;
  // *** Cache Management Helpers ***
  // Adds entry to cache with automatic LRU eviction when MAX_CACHE_SIZE is exceeded
  function addToResultsCache(id, value) {
    // Remove from order list if it already exists (for updates)
    const existingIndex = _resultsCacheOrder.indexOf(id);
    if (existingIndex > -1) {
      _resultsCacheOrder.splice(existingIndex, 1);
    }
    _resultsCacheOrder.push(id); // Add to end (most recent)
    _resultsCache[id] = value;

    // Evict oldest entry if cache exceeds MAX_CACHE_SIZE
    if (Object.keys(_resultsCache).length > MAX_CACHE_SIZE) {
      const oldestId = _resultsCacheOrder.shift();
      delete _resultsCache[oldestId];
    }
  }

  let _modifKey = 'Alt+';
  /**
   * Maps keycodes to their corresponding display names.
   * Used for shortcut normalization with C (Control), A (Alt), S (Shift) modifiers.
   * @const {Object<number, string>}
   */
  // prettier-ignore
  const _KEYCODE_TO_CHAR = {
        65:'A',66:'B',67:'C',68:'D',69:'E',70:'F',71:'G',72:'H',73:'I',74:'J',75:'K',76:'L',
        77:'M',78:'N',79:'O',80:'P',81:'Q',82:'R',83:'S',84:'T',85:'U',86:'V',87:'W',88:'X',
        89:'Y',90:'Z', 48:'0',49:'1',50:'2',51:'3',52:'4',53:'5',54:'6',55:'7',56:'8',57:'9',
        112:'F1',113:'F2',114:'F3',115:'F4',116:'F5',117:'F6',118:'F7',119:'F8',120:'F9',121:'F10',122:'F11',123:'F12',
        32:'Space',13:'Enter',9:'Tab',27:'Esc',8:'Backspace',46:'Delete',36:'Home',35:'End',33:'PageUp',34:'PageDown',45:'Insert',
        37:'←',38:'↑',39:'→',40:'↓', 188:',',190:'.',191:'/',186:';',222:"'",219:'[',221:']',220:'\\',189:'-',187:'=',192:'`',
    };

  /** Reverse mapping: display name to keycode. @const {Object<string, number>} */
  const _CHAR_TO_KEYCODE = Object.fromEntries(Object.entries(_KEYCODE_TO_CHAR).map(([k, v]) => [v.toUpperCase(), Number(k)]));

  /** Bitwise values for modifier keys: C=Control(1), S=Shift(2), A=Alt(4). @const {Object<string, number>} */
  const _MOD_CHAR_TO_VAL = { C: 1, S: 2, A: 4 };

  // Whitelisting vars
  let _venueWhitelist;
  const WL_BUTTON_TEXT = 'WL';
  const WL_LOCAL_STORE_NAME = 'WMEPH-venueWhitelistNew';
  const WL_LOCAL_STORE_NAME_COMPRESSED = 'WMEPH-venueWhitelistCompressed';

  // Pending feed request tracker (prevents duplicate banners from async race conditions)
  let _pendingFeedRequest;

  // Dupe check variables
  let _dupeLayer;
  let _dupeIDList = [];
  let _dupeHNRangeList;
  let _dupeHNRangeDistList;

  // Web search window specifications
  let _searchResultsWindowSpecs = `"resizable=yes, top=${Math.round(window.screen.height * 0.1)}, left=${Math.round(
    window.screen.width * 0.3,
  )}, width=${Math.round(window.screen.width * 0.7)}, height=${Math.round(window.screen.height * 0.8)}"`;
  const SEARCH_RESULTS_WINDOW_NAME = '"WMEPH Search Results"';
  let _wmephMousePosition;
  let _cloneMaster = null;

  // Banner UI elements
  let _buttonBanner2;
  let _servicesBanner;
  let _dupeBanner;

  // State flags
  let _disableHighlightTest = false; // Set to true to temporarily disable highlight checks immediately when venues change.
  let _isHarmonizing = false; // Prevent recursive harmonization when venue data changes during harmonization
  let _previousVenueServices = null; // Tracks services state to detect services-only changes

  // User information object
  const USER = {
    ref: null,
    rank: null,
    name: null,
    isBetaUser: false,
    isDevUser: false,
  };

  // Userlists
  let _wmephDevList;
  let _wmephBetaList;
  let _userLanguage;

  // Setting identifiers
  const SETTING_IDS = {
    sfUrlWarning: 'SFURLWarning', // Warning message for first time using localized storefinder URL.
    gLinkWarning: 'GLinkWarning', // Warning message for first time using Google search to not to use the Google info itself.
  };

  // Reference URLs
  const URLS = {
    forum: 'https://www.waze.com/discuss/t/script-wme-place-harmonizer/178574',
    usaPnh: 'https://docs.google.com/spreadsheets/d/1-f-JTWY5UnBx-rFTa4qhyGMYdHBZWNirUTOgn222zMY/edit#gid=0',
    placesWiki: 'https://www.waze.com/discuss/t/places/377947',
    restAreaWiki: 'https://www.waze.com/discuss/t/rest-areas/378691',
    uspsWiki: 'https://www.waze.com/discuss/t/post-office-places/378648',
  };

  // Master Service Key Mapping Reference
  // Maps PNH service keys (from Google Sheet ps_* columns) to code banner keys and WME Service IDs
  // Format: Sheet (ps_*) → Code (add*) → WME Service ID
  // Currently used (12): ps_valet, ps_drivethru, ps_wifi, ps_restrooms, ps_cc, ps_reservations, ps_outside, ps_ac, ps_parking, ps_deliveries, ps_takeaway, ps_wheelchair
  // Available for expansion (14): ps_curbside, ps_disability_parking, ps_airport_shuttle, ps_carwash, ps_carpool_parking, ps_covered, ps_ev_charging_station, ps_on_site_attendant, ps_park_and_ride, ps_security, ps_reservations_pl, ps_valet_pl, ps_vallet_service_pl, ps_247
  // Sheet owners can add new services by adding them to the Categories section (ps_* columns) in the PNH Google Sheet.
  const PNH_TO_BANNER_SERVICE_KEY_MAP = {
    // General Services (GENERAL_SERVICE_TYPE)
    ps_valet: 'addValet', // VALLET_SERVICE
    ps_drivethru: 'addDriveThru', // DRIVETHROUGH
    ps_wifi: 'addWiFi', // WI_FI
    ps_restrooms: 'addRestrooms', // RESTROOMS
    ps_cc: 'addCreditCards', // CREDIT_CARDS
    ps_reservations: 'addReservations', // RESERVATIONS
    ps_outside: 'addOutside', // OUTSIDE_SEATING
    ps_ac: 'addAC', // AIR_CONDITIONING
    ps_parking: 'addParking', // PARKING_FOR_CUSTOMERS
    ps_deliveries: 'addDeliveries', // DELIVERIES
    ps_takeaway: 'addTakeAway', // TAKE_AWAY
    ps_wheelchair: 'addWheelchair', // WHEELCHAIR_ACCESSIBLE
    ps_curbside: 'addCurbside', // CURBSIDE_PICKUP
    // Parking Lot Services (PARKING_LOT_SERVICE_TYPE)
    ps_disability_parking: 'addDisabilityParking', // DISABILITY_PARKING
    ps_airport_shuttle: 'addAirportShuttle', // AIRPORT_SHUTTLE
    ps_carwash: 'addCarWash', // CAR_WASH
    ps_carpool_parking: 'addCarpoolParking', // CARPOOL_PARKING
    ps_covered: 'addCovered', // COVERED
    ps_ev_charging_station: 'addEVChargingStation', // EV_CHARGING_STATION
    ps_on_site_attendant: 'addOnSiteAttendant', // ON_SITE_ATTENDANT
    ps_park_and_ride: 'addParkAndRide', // PARK_AND_RIDE
    ps_security: 'addSecurity', // SECURITY
    ps_reservations_pl: 'addReservationsPL', // RESERVATIONS
    ps_valet_pl: 'addValetPL', // VALET
    ps_vallet_service_pl: 'addValletServicePL', // VALLET_SERVICE
    // Special
    ps_247: 'add247', // (hours only, not a service ID)
  };

  // CAT and SUB CAT Stuff
  let CAT = {};
  let SUBCATEGORIES_BY_ID = {};

  let _catTransWaze2Lang; // pulls the category translations
  const EV_PAYMENT_METHOD = {
    APP: 'APP',
    CREDIT: 'CREDIT',
    DEBIT: 'DEBIT',
    MEMBERSHIP_CARD: 'MEMBERSHIP_CARD',
    ONLENE_PAYMENT: 'ONLINE_PAYMENT',
    PLUG_IN_AUTO_CHARGER: 'PLUG_IN_AUTO_CHARGE',
    OTHER: 'OTHER',
  };
  // Common payment types found at: https://wazeopedia.waze.com/wiki/USA/Places/EV_charging_station
  const /* The above code is a comment in JavaScript. It appears to be defining a constant or variable
  named COMMON_EV_PAYMENT_METHODS and using a delimiter " */

    COMMON_EV_PAYMENT_METHODS = {
      'Blink Charging': [EV_PAYMENT_METHOD.APP, EV_PAYMENT_METHOD.MEMBERSHIP_CARD, EV_PAYMENT_METHOD.PLUG_IN_AUTO_CHARGER, EV_PAYMENT_METHOD.OTHER],
      ChargePoint: [EV_PAYMENT_METHOD.APP, EV_PAYMENT_METHOD.CREDIT, EV_PAYMENT_METHOD.DEBIT, EV_PAYMENT_METHOD.MEMBERSHIP_CARD],
      'Electrify America': [EV_PAYMENT_METHOD.APP, EV_PAYMENT_METHOD.CREDIT, EV_PAYMENT_METHOD.DEBIT, EV_PAYMENT_METHOD.MEMBERSHIP_CARD, EV_PAYMENT_METHOD.PLUG_IN_AUTO_CHARGER],
      EVgo: [EV_PAYMENT_METHOD.APP, EV_PAYMENT_METHOD.CREDIT, EV_PAYMENT_METHOD.DEBIT, EV_PAYMENT_METHOD.MEMBERSHIP_CARD, EV_PAYMENT_METHOD.PLUG_IN_AUTO_CHARGER],
      SemaConnect: [EV_PAYMENT_METHOD.APP, EV_PAYMENT_METHOD.MEMBERSHIP_CARD, EV_PAYMENT_METHOD.OTHER],
      Tesla: [EV_PAYMENT_METHOD.PLUG_IN_AUTO_CHARGER],
    };
  const GENERAL_SERVICES = [
    'VALLET_SERVICE',
    'DRIVETHROUGH',
    'WI_FI',
    'RESTROOMS',
    'CREDIT_CARDS',
    'RESERVATIONS',
    'OUTSIDE_SEATING',
    'AIR_CONDITIONING',
    'PARKING_FOR_CUSTOMERS',
    'DELIVERIES',
    'TAKE_AWAY',
    'CURBSIDE_PICKUP',
    'WHEELCHAIR_ACCESSIBLE',
  ];
  const PARKING_LOT_SERVICES = [
    'AIRPORT_SHUTTLE',
    'CAR_WASH',
    'CARPOOL_PARKING',
    'COVERED',
    'DISABILITY_PARKING',
    'EV_CHARGING_STATION',
    'ON_SITE_ATTENDANT',
    'PARK_AND_RIDE',
    'RESERVATIONS',
    'SECURITY',
    'VALET',
    'VALLET_SERVICE',
  ];
  const WME_SERVICES_ARRAY = [...GENERAL_SERVICES, ...PARKING_LOT_SERVICES];

  const COLLEGE_ABBREVIATIONS = ['USF', 'USFSP', 'UF', 'UCF', 'UA', 'UGA', 'FSU', 'UM', 'SCP', 'FAU', 'FIU'];
  // Change place.name to title case
  const TITLECASE_SETTINGS = {
    ignoreWords: 'an|and|as|at|by|for|from|hhgregg|in|into|of|on|or|the|to|with'.split('|'),
    // eslint-disable-next-line max-len
    capWords:
      '3M|AAA|AMC|AOL|AT&T|ATM|BBC|BLT|BMV|BMW|BP|CBS|CCS|CGI|CISCO|CJ|CNG|CNN|CVS|DHL|DKNY|DMV|DSW|EMS|ER|ESPN|FCU|FCUK|FDNY|GNC|H&M|HP|HSBC|IBM|IHOP|IKEA|IRS|JBL|JCPenney|KFC|LLC|MBNA|MCA|MCI|NBC|NYPD|PDQ|PNC|TCBY|TNT|TV|UPS|USA|USPS|VW|XYZ|ZZZ'.split(
        '|',
      ),
    specWords: "d'Bronx|iFix|ExtraMile|ChargePoint|EVgo|SemaConnect".split('|'),
  };
  const NO_NUM_SKIP = [
    'BANK',
    'ATM',
    'HOTEL',
    'MOTEL',
    'STORE',
    'MARKET',
    'SUPERMARKET',
    'GYM',
    'GAS',
    'GASOLINE',
    'GASSTATION',
    'CAFE',
    'OFFICE',
    'OFFICES',
    'CARRENTAL',
    'RENTALCAR',
    'RENTAL',
    'SALON',
    'BAR',
    'BUILDING',
    'LOT',
    ...COLLEGE_ABBREVIATIONS,
  ];
  // These arrays are populated lazily after CAT is initialized
  const getPrimaryCatsToIgnoreMissingPhoneUrl = () => ['ISLAND', 'SEA_LAKE_POOL', 'RIVER_STREAM', 'CANAL', 'JUNCTION_INTERCHANGE', 'SCENIC_LOOKOUT_VIEWPOINT', 'SWAMP_MARSH'];
  const getPrimaryCatsToFlagGreenMissingPhoneUrl = () => ['BRIDGE', 'FOREST_GROVE', 'DAM', 'TUNNEL', 'CEMETERY'];
  const getAnyCatsToFlagGreenMissingPhoneUrl = () => ['REST_AREAS'];
  const REGIONS_THAT_WANT_PLA_PHONE_URL = ['SER'];
  const getChainApprovalPrimaryCatsToIgnore = () => [
    'POST_OFFICE',
    'BRIDGE',
    'FOREST_GROVE',
    'DAM',
    'TUNNEL',
    'CEMETERY',
    'ISLAND',
    'SEA_LAKE_POOL',
    'SWAMP_MARSH',
    'RIVER_STREAM',
    'CANAL',
    'JUNCTION_INTERCHANGE',
    'SCENIC_LOOKOUT_VIEWPOINT',
  ];
  const BAD_URL = 'badURL';
  const BAD_PHONE = 'badPhone';
  // Feeds that are not in use and it's safe to delete the place. Use regex.
  const FEEDS_TO_SKIP = [/^google$/i, /^yext\d?/i, /^wazeads$/i, /^parkme$/i, /^navads(na)?$/i];

  const UPDATED_FIELDS = {
    name: {
      updated: false,
      selector: '#venue-edit-general wz-text-input[name="name"]',
      shadowSelector: 'input',
      tab: 'general',
    },
    aliases: {
      updated: false,
      selector: '#venue-edit-general > div.aliases.form-group > wz-list',
      tab: 'general',
    },
    address: {
      updated: false,
      selector: '#venue-edit-general div.address-edit-view div.full-address-container',
      tab: 'general',
    },
    categories: {
      updated: false,
      selector: '#venue-edit-general > div.categories-control.form-group > wz-card',
      shadowSelector: 'div',
      tab: 'general',
    },
    description: {
      updated: false,
      selector: '#venue-edit-general wz-textarea[name="description"]',
      shadowSelector: 'textarea',
      tab: 'general',
    },
    lockRank: {
      updated: false,
      selector: '#venue-edit-general > div.lock-edit',
      tab: 'general',
    },
    externalProvider: {
      updated: false,
      selector: '#venue-edit-general > div.external-providers-control.form-group > wz-list',
      tab: 'general',
    },
    brand: { updated: false, selector: '.venue .brand .select2-container', tab: 'general' },
    url: {
      updated: false,
      selector: '#venue-url',
      shadowSelector: 'input',
      tab: 'more-info',
    },
    phone: {
      updated: false,
      selector: '#venue-phone',
      shadowSelector: 'input',
      tab: 'more-info',
    },
    openingHours: {
      updated: false,
      selector: '#venue-edit-more-info div.opening-hours.form-group > wz-list',
      tab: 'more-info',
    },
    cost: {
      updated: false,
      selector: '#venue-edit-more-info wz-select[name="costType"]',
      shadowSelector: 'div.select-box',
      tab: 'more-info',
    },
    canExit: { updated: false, selector: '.venue label[for="can-exit-checkbox"]', tab: 'more-info' },
    hasTBR: { updated: false, selector: '.venue label[for="has-tbr"]', tab: 'more-info' },
    lotType: { updated: false, selector: '#venue-edit-more-info > form > div:nth-child(1) > wz-radio-group', tab: 'more-info' },
    parkingSpots: {
      updated: false,
      selector: '#venue-edit-more-info wz-select[name="estimatedNumberOfSpots"]',
      shadowSelector: '#select-wrapper > div',
      tab: 'more-info',
    },
    lotElevation: { updated: false, selector: '.venue .lot-checkbox', tab: 'more-info' },
    evNetwork: { updated: false, selector: '', tab: 'general' },
    evPaymentMethods: {
      updated: false,
      selector: '#venue-edit-general > div.charging-station-controls div.wz-multiselect > wz-card',
      shadowSelector: 'div',
      tab: 'general',
    },
    evCostType: {
      updated: false,
      selector: '#venue-edit-general > div.charging-station-controls > wz-select',
      shadowSelector: '#select-wrapper > div > div',
      tab: 'general',
    },

    getFieldProperties() {
      return Object.keys(this)
        .filter((key) => this[key].hasOwnProperty('updated'))
        .map((key) => this[key]);
    },
    getUpdatedTabNames() {
      return uniq(
        this.getFieldProperties()
          .filter((prop) => prop.updated)
          .map((prop) => prop.tab),
      );
    },
    reset() {
      this.clearEditPanelHighlights();
      this.getFieldProperties().forEach((prop) => {
        prop.updated = false;
      });
    },
    init() {
      uniq(WME_SERVICES_ARRAY).forEach((service) => {
        const propName = `services_${service}`;
        this[propName] = { updated: false, selector: `.venue label[for="service-checkbox-${service}"]`, tab: 'more-info' };
      });

      sdk.Events.on({ eventName: 'wme-selection-changed', eventHandler: () => errorHandler(() => this.reset()) });
    },
    getTabElement(tabName) {
      let tabText;
      if (tabName === 'more-info') {
        tabText = 'More info';
      } else if (tabName === 'general') {
        tabText = 'General';
      } else {
        return null;
      }
      const tabElements = document.querySelector('#edit-panel div.venue-edit-section > wz-tabs')?.shadowRoot?.querySelectorAll('.wz-tab-label');
      if (tabElements) {
        return [...tabElements].filter((elem) => elem.textContent === tabText)[0];
      }
      return null;
    },
    clearEditPanelHighlights() {
      this.getFieldProperties()
        .filter((prop) => prop.updated)
        .forEach((prop) => {
          if (prop.shadowSelector) {
            $(document.querySelector(prop.selector)?.shadowRoot?.querySelector(prop.shadowSelector)).css('background-color', '');
          } else {
            $(prop.selector).css({ 'background-color': '' });
          }
          $(this.getTabElement(prop.tab)).css({ 'background-color': '' });
        });
    },
    // Highlight fields in the editor panel that have been updated by WMEPH.
    updateEditPanelHighlights() {
      // This setTimeout is necessary to get some highlights to work.
      setTimeout(() => {
        this.getFieldProperties()
          .filter((prop) => prop.updated)
          .forEach((prop) => {
            if (prop.shadowSelector) {
              $(document.querySelector(prop.selector)?.shadowRoot?.querySelector(prop.shadowSelector)).css('background-color', '#dfd');
            } else {
              $(prop.selector).css({ 'background-color': '#dfd' });
            }
            $(this.getTabElement(prop.tab)).css({ 'background-color': '#dfd' });
          });
      }, 100);
    },
    checkNewAttributes(newAttributes, venue) {
      const checkAttribute = (name) => {
        if (newAttributes.hasOwnProperty(name)) {
          // SDK venues have flattened properties, not nested in .attributes
          const oldValue = venue[name];
          const newValue = newAttributes[name];
          if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
            UPDATED_FIELDS[name].updated = true;
          }
        }
      };
      checkAttribute('categories');
      checkAttribute('name');
      checkAttribute('openingHours');
      checkAttribute('description');
      checkAttribute('aliases');
      checkAttribute('url');
      checkAttribute('phone');
      checkAttribute('lockRank');
    },
  };
  // PNH DATA
  const PNH_DATA = {
    /** @type {Country} */
    USA: null,
    /** @type {Country} */
    CAN: null,
  };
  // Split out state-based data
  let _psStateIx;
  let _psState2LetterIx;
  let _psRegionIx;
  let _psGoogleFormStateIx;
  let _psDefaultLockLevelIx;
  let _psAreaCodeIx;
  let _stateDataTemp;
  let _areaCodeList = '800,822,833,844,855,866,877,888'; //  include toll free non-geographic area codes

  let OpeningHour;
  const DEFAULT_HOURS_TEXT = 'Paste hours here';

  // GOOGLE LINK STUFF
  let _googlePlacePtFeature;
  let _googlePlaceLineFeature;
  let _destroyGooglePlacePointTimeoutId;

  // CSS STUFF
  const _CSS = `
    #edit-panel .venue-feature-editor {
        overflow: initial;
    }
    #sidebar .wmeph-pane {
        width: auto;
        padding: 8px !important;
    }
    #WMEPH_banner .wmeph-btn {
        background-color: #fbfbfb;
        box-shadow: 0 2px 0 #aaa;
        border: solid 1px #bbb;
        font-weight:normal;
        margin-bottom: 2px;
        margin-right:4px
    }
    .wmeph-btn, .wmephwl-btn {
        height: 19px;
        font-family: "Boing", sans-serif;
    }
    .btn.wmeph-btn {
        padding: 0px 3px;
    }
    .btn.wmephwl-btn {
        padding: 0px 1px 0px 2px;
        height: 18px;
        box-shadow: 0 2px 0 #b3b3b3;
    }

    #WMEPH_banner .banner-row {
        padding:2px 4px;
        cursor: default;
    }
    #WMEPH_banner .banner-row.red {
        color:#b51212;
        background-color: #f0dcdc;
    }
    #WMEPH_banner .banner-row.blue {
        color:#3232e6;
        background-color: #dcdcf0;
    }
    #WMEPH_banner .banner-row.yellow {
        color:#584a04;
        background-color: #f0f0c2;
    }
    #WMEPH_banner .banner-row.gray {
        color:#3a3a3a;
        background-color: #eeeeee;
    }
    #WMEPH_banner .banner-row.orange {
        color:#754900;
        background-color: #ffd389
    }
    #WMEPH_banner .banner-row.lightgray {
        color:#3a3a3a;
        background-color: #f5f5f5;
    }
    #WMEPH_banner .banner-row .dupe {
        padding-left:8px;
    }
    #WMEPH_banner {
        background-color:#fff;
        color:black; font-size:14px;
        padding-top:8px;
        padding-bottom:8px;
        margin-left:4px;
        margin-right:4px;
        line-height:18px;
        margin-top:2px;
        border: solid 1px #8d8c8c;
        border-radius: 6px;
        margin-bottom: 4px;
    }
    #WMEPH_banner input[type=text] {
        font-size: 13px !important;
        height:22px !important;
        font-family: "Open Sans", Alef, helvetica, sans-serif !important;
    }
    #WMEPH_banner div:last-child {
        padding-bottom: 3px !important;
    }
    #wmeph-run-panel {
        display: flex;
        flex-wrap: wrap;
        gap: 4px;
        align-items: center;
        padding: 4px;
        color: black;
        font-size: 14px;
    }
    #wmeph-run-panel .wmeph-run-btn {
        flex: 1 0 83px;
        min-width: 83px;
        height: 28px;
        padding: 4px 8px !important;
        font-size: 12px !important;
        border-radius: 14px;
        border: 1px solid;
        background-color: transparent !important;
        box-shadow: none !important;
    }
    #wmeph-run-panel .wmeph-btn {
        flex: 0 1 auto;
        height: 28px;
    }
    #wmeph-run-panel .wmeph-clone-row {
        display: flex;
        // flex-wrap: wrap;
        gap: 4px;
        align-items: center;
        // justify-content: space-around;
        // margin-top: 4px;
    }
    #wmeph-run-panel .wmeph-clone-btn {
        height: 18px !important;
        padding: 0px 10px !important;
        font-size: 9px !important;
        border-radius: 9px;
        border: 1px solid;
        background-color: transparent !important;
        box-shadow: none !important;
        transition: all 0.2s ease;
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
    }
    #wmeph-run-panel .wmeph-clone-btn.btn-warning {
        border-color: #e37400 !important;
        color: #e37400 !important;
    }
    #wmeph-run-panel .wmeph-clone-btn.btn-warning:hover {
        background-color: rgba(227, 116, 0, 0.1) !important;
        border-color: #ffc400 !important;
    }
    #wmeph-run-panel .wmeph-clone-btn.btn-info {
        border-color: #0099ff !important;
        color: #0099ff !important;
    }
    #wmeph-run-panel .wmeph-clone-btn.btn-info:hover {
        background-color: rgba(0, 153, 255, 0.1) !important;
        border-color: #33ccff !important;
    }
    #wmeph-run-panel .wmeph-clone-row .wmeph-icon-toggle {
        font-size: 18px !important;
        color: #0075e3;
    }
    #wmeph-run-panel .wmeph-clone-row .wmeph-icon-toggle i {
        font-size: 18px !important;
    }
    #wmeph-run-panel .wmeph-clone-row .wmeph-icon-toggle:hover {
        transform: scale(1.1);
    }
    [wz-theme="dark"] #wmeph-run-panel .wmeph-clone-row .wmeph-icon-toggle {
        color: #33ccff;
    }
    /* Waze color palette - Chip/outline style */
    #runWMEPH {
        border-color: #0075e3 !important;
        color: #0075e3 !important;
        transition: all 0.2s ease;
    }
    #runWMEPH:hover {
        background-color: rgba(0, 153, 255, 0.1) !important;
        border-color: #0099ff !important;
    }
    #WMEPHurl {
        border-color: #118742 !important;
        color: #118742 !important;
        transition: all 0.2s ease;
    }
    #WMEPHurl:hover {
        background-color: rgba(27, 171, 80, 0.1) !important;
        border-color: #1bab50 !important;
    }
    #wmephSearch {
        border-color: #0099ff !important;
        color: #0099ff !important;
        transition: all 0.2s ease;
    }
    #wmephSearch:hover {
        background-color: rgba(51, 204, 255, 0.1) !important;
        border-color: #33ccff !important;
    }
    #wmephPlugShareSearch {
        border-color: #118742 !important;
        color: #118742 !important;
        transition: all 0.2s ease;
    }
    #wmephPlugShareSearch:hover {
        background-color: rgba(30, 171, 146, 0.1) !important;
        border-color: #1ee592 !important;
    }
    #WMEPH_tools {
        display: flex;
        flex-wrap: wrap;
        gap: 4px;
        align-items: center;
        padding: 4px 6px !important;
    }
    #WMEPH_tools > div {
        display: flex;
        flex-wrap: wrap;
        gap: 4px;
        align-items: center;
    }
    #WMEPH_tools .wmeph-btn {
        padding: 2px 6px !important;
        margin-bottom: 0 !important;
        margin-right: 0 !important;
        font-size: 12px;
        height: 18px;
        flex-shrink: 0;
    }
    #WMEPH_tools .wmeph-clone-btn {
        height: 18px !important;
        padding: 0px 10px !important;
        font-size: 9px !important;
        border-radius: 9px;
        border: 1px solid;
        background-color: transparent !important;
        box-shadow: none !important;
        transition: all 0.2s ease;
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
    }
    #WMEPH_tools .wmeph-clone-btn.btn-info {
        border-color: #0099ff !important;
        color: #0099ff !important;
    }
    #WMEPH_tools .wmeph-clone-btn.btn-info:hover {
        background-color: rgba(0, 153, 255, 0.1) !important;
        border-color: #33ccff !important;
    }
    .wmeph-fat-btn {
        padding-left:8px;
        padding-right:8px;
        padding-top:4px;
        margin-right:3px;
        display:inline-block;
        font-weight:normal;
        height:24px;
        font-family: "Boing", sans-serif;
    }
    .ui-autocomplete {
        max-height: 300px;
        overflow-y: auto;
        overflow-x: hidden;
    }
    .wmeph-hr {
        border-color: #ccc;
    }
    .wmeph-hr {
        border-color: #ccc;
    }

    @keyframes highlight {
        0% {
            background: #ffff99;
        }
        100% {
            background: none;
        }
    }

    .highlight {
        animation: highlight 1.5s;
    }

    .google-logo {
        /*font-size: 16px*/
    }
    .google-logo.red{
        color: #ea4335
    }
    .google-logo.blue {
        color: #4285f4
    }
    .google-logo.orange {
        color: #fbbc05
    }
    .google-logo.green {
        color: #34a853
    }

    /* WMEPH Section Wrapper - Phase 1 Incremental */
    .wmeph-section {
        background-color: #fff;
        border: solid 1px #8d8c8c;
        border-radius: 6px;
        margin: 2px 4px 4px 4px;
    }
    [wz-theme="dark"] .wmeph-section {
        background-color: #2c2c2c;
        border-color: #5f6368;
    }

    .wmeph-section-header {
        display: flex;
        align-items: center;
        background: linear-gradient(to right, #f5f5f5 0%, #ffffff 100%);
        border-bottom: 1px solid #dadce0;
        padding: 2px 2px;
        font-weight: 600;
        font-size: 14px;
        color: #202124;
        cursor: default;
        user-select: none;
    }
    [wz-theme="dark"] .wmeph-section-header {
        background: linear-gradient(to right, #3a3a3a 0%, #2c2c2c 100%);
        border-bottom-color: #5f6368;
        color: #e8eaed;
    }

    .wmeph-section-body {
        padding: 4px 0;
    }
    `;

  // **************************************************************************************************************
  // UTILITY/HELPER FUNCTIONS
  // **************************************************************************************************************

  /**
   * Checks if a value is null, undefined, or contains only whitespace.
   * @param {string} str - String value to check
   * @returns {boolean} True if null, undefined, or whitespace-only; false otherwise
   */
  function isNullOrWhitespace(str) {
    return !str?.trim().length;
  }

  /**
   * Calculates total distance along a path of points (polyline length).
   * Sums the distances between consecutive points in the array.
   * @param {number[][]|object[]} pointArray - Array of points as [lon, lat] arrays or {longitude, latitude} objects
   * @returns {number} Total distance in meters along the polyline
   */
  function calculateDistance(pointArray) {
    if (pointArray.length < 2) return 0;

    const line = turf.lineString(pointArray);
    const length = turf.length(line, { units: 'meters' });
    return length; //multiply by 3.28084 to convert to feet
  }

  /**
   * Returns array with duplicate values removed (unique elements only).
   * Uses Set to efficiently deduplicate while preserving first-occurrence order.
   * @param {array} arrayIn - Input array containing potential duplicates
   * @returns {array} New array containing unique elements from input array
   */
  function uniq(arrayIn) {
    return [...new Set(arrayIn)];
  }

  function sortWithIndex(toSort) {
    for (let i = 0; i < toSort.length; i++) {
      toSort[i] = [toSort[i], i];
    }
    toSort.sort((left, right) => (left[0] < right[0] ? -1 : 1));
    toSort.sortIndices = [];
    for (let j = 0; j < toSort.length; j++) {
      toSort.sortIndices.push(toSort[j][1]);
      // eslint-disable-next-line prefer-destructuring
      toSort[j] = toSort[j][0];
    }
    return toSort;
  }

  function log(...args) {
    console.log(`WMEPH${IS_BETA_VERSION ? '-β' : ''}:`, ...args);
  }

  function logDev(...args) {
    if (USER.isDevUser) {
      console.debug(`WMEPH${IS_BETA_VERSION ? '-β' : ''} (dev):`, ...args);
    }
  }

  function errorHandler(callback, ...args) {
    try {
      callback(...args);
    } catch (ex) {
      logDev(ex);
    }
  }

  // **************************************************************************************************************
  // SETTINGS/STORAGE FUNCTIONS
  // **************************************************************************************************************

  function getWMEPHSetting(key, defaultValue = null) {
    return wmephSettings[key] !== undefined ? wmephSettings[key] : defaultValue;
  }

  function setWMEPHSetting(key, value) {
    wmephSettings[key] = value;
    // Don't include internal metadata (_migrationVersion) when saving to localStorage
    const settingsToSave = Object.fromEntries(Object.entries(wmephSettings).filter(([k]) => !k.startsWith('_')));
    localStorage.setItem('WMEPH-Settings', JSON.stringify(settingsToSave));
  }

  function saveWhitelistToLS(compress) {
    let wlString = JSON.stringify(_venueWhitelist);
    if (compress) {
      if (wlString.length < 4800000) {
        // Also save to regular storage as a back up
        localStorage.setItem(WL_LOCAL_STORE_NAME, wlString);
      }
      wlString = LZString.compressToUTF16(wlString);
      localStorage.setItem(WL_LOCAL_STORE_NAME_COMPRESSED, wlString);
    } else {
      localStorage.setItem(WL_LOCAL_STORE_NAME, wlString);
    }
  }

  function loadWhitelistFromLS(decompress) {
    let wlString;
    if (decompress) {
      wlString = localStorage.getItem(WL_LOCAL_STORE_NAME_COMPRESSED);
      if (!wlString) {
        logDev('Compressed whitelist not found, trying uncompressed fallback');
        wlString = localStorage.getItem(WL_LOCAL_STORE_NAME);
      } else {
        wlString = LZString.decompressFromUTF16(wlString);
      }
    } else {
      wlString = localStorage.getItem(WL_LOCAL_STORE_NAME);
    }

    if (!wlString) {
      logDev('No whitelist found in localStorage');
      _venueWhitelist = {};
      return;
    }

    try {
      _venueWhitelist = JSON.parse(wlString);
    } catch (e) {
      logDev('Error parsing whitelist:', e);
      _venueWhitelist = {};
    }
  }

  function backupWhitelistToLS(compress) {
    let wlString = JSON.stringify(_venueWhitelist);
    if (compress) {
      wlString = LZString.compressToUTF16(wlString);
      localStorage.setItem(WL_LOCAL_STORE_NAME_COMPRESSED + Math.floor(Date.now() / 1000), wlString);
    } else {
      localStorage.setItem(WL_LOCAL_STORE_NAME + Math.floor(Date.now() / 1000), wlString);
    }
  }

  // **************************************************************************************************************
  // SHORTCUT/KEYBOARD FUNCTIONS
  // **************************************************************************************************************

  function _comboToRaw(str) {
    if (!str || str === '' || str === '-1' || str === 'None') return null;
    if (/^\d+,-?\d+$/.test(str)) {
      const kc = parseInt(str.split(',')[1], 10);
      return kc < 0 ? null : str;
    }
    const s = String(str).toUpperCase();
    if (/^[A-Z0-9]$/.test(s)) return `0,${s.charCodeAt(0)}`;
    if (_CHAR_TO_KEYCODE[s] !== undefined) return `0,${_CHAR_TO_KEYCODE[s]}`;
    const mLetter = s.match(/^([ACS]+)\+([A-Z0-9])$/);
    if (mLetter) {
      const mod = mLetter[1].split('').reduce((a, c) => a | (_MOD_CHAR_TO_VAL[c] || 0), 0);
      return `${mod},${mLetter[2].charCodeAt(0)}`;
    }
    const mNumeric = s.match(/^([ACS]+)\+(\d+)$/);
    if (mNumeric) {
      const mod = mNumeric[1].split('').reduce((a, c) => a | (_MOD_CHAR_TO_VAL[c] || 0), 0);
      return `${mod},${mNumeric[2]}`;
    }
    const mSpecial = s.match(/^([ACS]+)\+(.+)$/);
    if (mSpecial && _CHAR_TO_KEYCODE[mSpecial[2]] !== undefined) {
      const mod = mSpecial[1].split('').reduce((a, c) => a | (_MOD_CHAR_TO_VAL[c] || 0), 0);
      return `${mod},${_CHAR_TO_KEYCODE[mSpecial[2]]}`;
    }
    return null;
  }

  /**
   * Converts raw "modifier,keycode" format to human-readable combo format (e.g. "A+R").
   * @param {string|null} str Raw format string or any shortcut value
   * @returns {string|null} Combo format string or null if no key
   */
  function _rawToCombo(str) {
    const raw = _comboToRaw(str);
    if (!raw) return null;
    const [modStr, keyStr] = raw.split(',');
    const mod = parseInt(modStr, 10);
    const keyCode = parseInt(keyStr, 10);
    const keyChar = _KEYCODE_TO_CHAR[keyCode] || String(keyCode);
    let mods = '';
    if (mod & 1) mods += 'C';
    if (mod & 2) mods += 'S';
    if (mod & 4) mods += 'A';
    return mods ? `${mods}+${keyChar}` : keyChar;
  }

  /**
   * Normalizes any shortcut value to a {raw, combo} pair for consistent storage.
   * @param {string|Object|null} val Shortcut value: string (any format), {raw,combo} object, or null
   * @returns {Object} Object with raw (keycode format) and combo (display format) properties
   */
  function _normalizeShortcut(val) {
    const src = val && typeof val === 'object' ? (val.raw ?? val.combo) : val;
    const raw = _comboToRaw(src);
    const combo = _rawToCombo(raw);
    return { raw, combo };
  }

  /**
   * Loads shortcut from settings in both raw and combo formats.
   * @param {string} settingsKey The settings key identifier
   * @returns {Object} Object with raw and combo properties
   */
  function loadShortcut(settingsKey) {
    const raw = getWMEPHSetting(`WMEPH_shortcut_${settingsKey}_raw`);
    const combo = getWMEPHSetting(`WMEPH_shortcut_${settingsKey}_combo`);
    return { raw: raw || null, combo: combo || null };
  }

  /**
   * Saves shortcut to settings in both raw and combo formats for consistency.
   * RAW format (keycodes) handles SDK inconsistencies, COMBO format aids debugging.
   * @param {string} settingsKey The settings key identifier
   * @param {string|Object|null} shortcutValue Shortcut value in any format
   */
  function saveShortcut(settingsKey, shortcutValue) {
    const normalized = _normalizeShortcut(shortcutValue);
    setWMEPHSetting(`WMEPH_shortcut_${settingsKey}_raw`, normalized.raw || '');
    setWMEPHSetting(`WMEPH_shortcut_${settingsKey}_combo`, normalized.combo || '');
  }

  /**
   * Loads the harmonize shortcut from UI-managed localStorage keys.
   * @returns {string|null} Shortcut in SDK combo format (e.g. "C+A+H")
   */
  function loadHarmonizeShortcut() {
    const keyLetter = getWMEPHSetting('WMEPH-KeyboardShortcut') || 'A';
    const useCtrl = getWMEPHSetting('WMEPH-KBSModifierKey') === '1';

    // Build SDK format with single-letter modifiers: C=Ctrl, S=Shift, A=Alt
    let sdkKey = '';
    if (useCtrl) sdkKey += 'C';
    if (/^[A-Z]{1}$/.test(keyLetter)) sdkKey += 'S'; // Add S if uppercase (means Shift)
    sdkKey += 'A'; // Add Alt modifier

    // Add the key letter (lowercase)
    const keyChar = keyLetter.toLowerCase();
    sdkKey += '+' + keyChar;

    const normalized = _normalizeShortcut(sdkKey);
    logDev(`loadHarmonizeShortcut: letter=${keyLetter}, useCtrl=${useCtrl}, sdkKey=${sdkKey}, raw=${normalized.raw}, combo=${normalized.combo}`);
    if (!normalized.combo) {
      logDev(`Failed to normalize harmonize shortcut key: ${sdkKey}`);
    }
    return normalized.combo || null; // Return null if normalization failed
  }

  /**
   * Registers an SDK shortcut with normalized combo format.
   * Stores both raw and combo formats for consistency, handles key conflicts.
   * @param {string} shortcutId Unique identifier for the shortcut
   * @param {string} description Human-readable description for the shortcut
   * @param {string|null} defaultKey Default shortcut key if none stored (combo or raw format)
   * @param {Function} callback Function to execute when shortcut is triggered
   */
  function registerShortcut(shortcutId, description, defaultKey, callback) {
    const stored = loadShortcut(shortcutId);
    const keyToUse = stored.combo || (defaultKey ? _normalizeShortcut(defaultKey).combo : null);

    try {
      // SDK only accepts combo format:
      //  "A" - press 'a' to trigger the shortcut
      //  "C+3" - press Ctrl-3 to trigger the shortcut
      //  "AS+32" - press Alt-Shift-Space to trigger the shortcut
      sdk.Shortcuts.createShortcut({
        shortcutId,
        description,
        callback,
        shortcutKeys: keyToUse,
      });

      // Re-fetch from SDK to capture what it stored, then normalize both formats
      const allShortcuts = sdk.Shortcuts.getAllShortcuts();
      const registered = allShortcuts.find((s) => s.shortcutId === shortcutId);
      if (registered?.shortcutKeys) {
        saveShortcut(shortcutId, registered.shortcutKeys);
        const normalized = _normalizeShortcut(registered.shortcutKeys);
        logDev(`Registered shortcut: ${shortcutId} = ${normalized.combo || 'none'}`);
      } else if (keyToUse) {
        saveShortcut(shortcutId, keyToUse);
        logDev(`Registered shortcut: ${shortcutId} = ${keyToUse}`);
      }
    } catch (ex) {
      if (String(ex).includes('already in use')) {
        // Shortcut keys conflict with another shortcut; register without keys
        logDev(`Shortcut "${shortcutId}" keys already in use, registering without keys`);
        try {
          sdk.Shortcuts.createShortcut({
            shortcutId,
            description,
            callback,
            shortcutKeys: null,
          });
          saveShortcut(shortcutId, null);
        } catch (ex2) {
          logDev(`Failed to register shortcut ${shortcutId} without keys: ${ex2}`);
        }
      } else {
        logDev(`Failed to register shortcut ${shortcutId}: ${ex}`);
      }
    }
  }

  // KB Shortcut object
  const SHORTCUT = {
    allShortcuts: {}, // All the shortcuts are stored in this array
    add(shortcutCombo, callback, opt) {
      // Provide a set of default options
      const defaultOptions = {
        type: 'keydown',
        propagate: false,
        disableInInput: false,
        target: document,
        keycode: false,
      };
      if (!opt) {
        opt = defaultOptions;
      } else {
        Object.keys(defaultOptions).forEach((dfo) => {
          if (typeof opt[dfo] === 'undefined') {
            opt[dfo] = defaultOptions[dfo];
          }
        });
      }
      let ele = opt.target;
      if (typeof opt.target === 'string') {
        ele = document.getElementById(opt.target);
      }
      shortcutCombo = shortcutCombo.toLowerCase();
      // The function to be called at keypress
      // eslint-disable-next-line func-names
      const func = function keyPressFunc(e) {
        e = e || window.event;
        if (opt.disableInInput) {
          // Don't enable shortcut keys in Input, Textarea fields
          let element;
          if (e.target) {
            element = e.target;
          } else if (e.srcElement) {
            element = e.srcElement;
          }
          if (element.nodeType === 3) {
            element = element.parentNode;
          }
          if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
            return;
          }
        }
        // Find Which key is pressed
        let code;
        if (e.keyCode) {
          code = e.keyCode;
        } else if (e.which) {
          code = e.which;
        }
        let character = String.fromCharCode(code).toLowerCase();
        if (code === 188) {
          character = ',';
        } // If the user presses , when the type is onkeydown
        if (code === 190) {
          character = '.';
        } // If the user presses , when the type is onkeydown
        const keys = shortcutCombo.split('+');
        // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
        let kp = 0;
        // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
        const shiftNums = {
          '`': '~',
          1: '!',
          2: '@',
          3: '#',
          4: '$',
          5: '%',
          6: '^',
          7: '&',
          8: '*',
          9: '(',
          0: ')',
          '-': '_',
          '=': '+',
          ';': ':',
          "'": '"',
          ',': '<',
          '.': '>',
          '/': '?',
          '\\': '|',
        };
        // Special Keys - and their codes
        const specialKeys = {
          esc: 27,
          escape: 27,
          tab: 9,
          space: 32,
          return: 13,
          enter: 13,
          backspace: 8,
          scrolllock: 145,
          // eslint-disable-next-line camelcase
          scroll_lock: 145,
          scroll: 145,
          capslock: 20,
          // eslint-disable-next-line camelcase
          caps_lock: 20,
          caps: 20,
          numlock: 144,
          // eslint-disable-next-line camelcase
          num_lock: 144,
          num: 144,
          pause: 19,
          break: 19,
          insert: 45,
          home: 36,
          delete: 46,
          end: 35,
          pageup: 33,
          // eslint-disable-next-line camelcase
          page_up: 33,
          pu: 33,
          pagedown: 34,
          // eslint-disable-next-line camelcase
          page_down: 34,
          pd: 34,
          left: 37,
          up: 38,
          right: 39,
          down: 40,
          f1: 112,
          f2: 113,
          f3: 114,
          f4: 115,
          f5: 116,
          f6: 117,
          f7: 118,
          f8: 119,
          f9: 120,
          f10: 121,
          f11: 122,
          f12: 123,
        };
        const modifiers = {
          shift: { wanted: false, pressed: false },
          ctrl: { wanted: false, pressed: false },
          alt: { wanted: false, pressed: false },
          meta: { wanted: false, pressed: false }, // Meta is Mac specific
        };
        if (e.ctrlKey) {
          modifiers.ctrl.pressed = true;
        }
        if (e.shiftKey) {
          modifiers.shift.pressed = true;
        }
        if (e.altKey) {
          modifiers.alt.pressed = true;
        }
        if (e.metaKey) {
          modifiers.meta.pressed = true;
        }
        for (let i = 0; i < keys.length; i++) {
          const k = keys[i];
          // Modifiers
          if (k === 'ctrl' || k === 'control') {
            kp++;
            modifiers.ctrl.wanted = true;
          } else if (k === 'shift') {
            kp++;
            modifiers.shift.wanted = true;
          } else if (k === 'alt') {
            kp++;
            modifiers.alt.wanted = true;
          } else if (k === 'meta') {
            kp++;
            modifiers.meta.wanted = true;
          } else if (k.length > 1) {
            // If it is a special key
            if (specialKeys[k] === code) {
              kp++;
            }
          } else if (opt.keycode) {
            if (opt.keycode === code) {
              kp++;
            }
          } else if (character === k) {
            // The special keys did not match
            kp++;
          } else if (shiftNums[character] && e.shiftKey) {
            // Stupid Shift key bug created by using lowercase
            character = shiftNums[character];
            if (character === k) {
              kp++;
            }
          }
        }

        if (
          kp === keys.length &&
          modifiers.ctrl.pressed === modifiers.ctrl.wanted &&
          modifiers.shift.pressed === modifiers.shift.wanted &&
          modifiers.alt.pressed === modifiers.alt.wanted &&
          modifiers.meta.pressed === modifiers.meta.wanted
        ) {
          callback(e);
          if (!opt.propagate) {
            // Stop the event
            // e.cancelBubble is supported by IE - this will kill the bubbling process.
            e.cancelBubble = true;
            e.returnValue = false;
            // e.stopPropagation works in Firefox.
            if (e.stopPropagation) {
              e.stopPropagation();
              e.preventDefault();
            }

            // 5/19/2019 (MapOMatic) Not sure if this return value is necessary.
            // eslint-disable-next-line consistent-return
            return false;
          }
        }
      };
      this.allShortcuts[shortcutCombo] = { callback: func, target: ele, event: opt.type };
      // Attach the function with the event
      if (ele.addEventListener) {
        ele.addEventListener(opt.type, func, false);
      } else if (ele.attachEvent) {
        ele.attachEvent(`on${opt.type}`, func);
      } else {
        ele[`on${opt.type}`] = func;
      }
    },
    // Remove a shortcut by specifying its key combination
    remove(shortcutCombo) {
      shortcutCombo = shortcutCombo.toLowerCase();
      const binding = this.allShortcuts[shortcutCombo];
      delete this.allShortcuts[shortcutCombo];
      if (!binding) {
        return;
      }
      const type = binding.event;
      const ele = binding.target;
      const { callback } = binding;
      if (ele.detachEvent) {
        ele.detachEvent(`on${type}`, callback);
      } else if (ele.removeEventListener) {
        ele.removeEventListener(type, callback, false);
      } else {
        ele[`on${type}`] = false;
      }
    },
  }; // END Shortcut function

  // **************************************************************************************************************
  // MAP/LAYER/GEOMETRY FUNCTIONS
  // **************************************************************************************************************

  /**
   * Redraws a layer to reflect style changes.
   * @param {string} layerName Name of the layer to redraw
   */
  function redrawLayer(layerName) {
    if (!layerName) return;
    try {
      sdk.Map.redrawLayer({ layerName });
    } catch (e) {
      logDev(`Failed to redraw layer ${layerName}:`, e);
    }
  }

  /**
   * Calculates the centroid (center point) of a venue's geometry using Turf.js.
   * For points, returns the point itself; for areas, calculates the geometric center.
   * @param {object} venue - Venue object with geometry property (GeoJSON format)
   * @returns {number[]|null} Coordinates as [longitude, latitude], or null if geometry invalid or missing
   */
  function getVenueCentroid(venue) {
    if (!venue?.geometry) return null;
    try {
      const point = turf.centroid(venue.geometry);
      return point.geometry.coordinates; // [lon, lat]
    } catch (e) {
      logDev('getVenueCentroid error:', e, venue);
      return null;
    }
  }

  /**
   * Calculates the distance between two geographic points using Turf.js.
   * Uses the haversine formula for great-circle distance (accounts for Earth's curvature).
   * @param {number[]|object} pt1 - First point as [longitude, latitude] array or {longitude, latitude} object
   * @param {number[]|object} pt2 - Second point as [longitude, latitude] array or {longitude, latitude} object
   * @returns {number} Distance in meters, or Infinity if either point is invalid
   */
  function calculatePointDistance(pt1, pt2) {
    if (!pt1 || !pt2) return Infinity;
    try {
      const coords1 = Array.isArray(pt1) ? pt1 : [pt1.longitude, pt1.latitude];
      const coords2 = Array.isArray(pt2) ? pt2 : [pt2.longitude, pt2.latitude];
      if (!coords1[0] || !coords2[0]) return Infinity; // Invalid coords

      return turf.distance(turf.point(coords1), turf.point(coords2), { units: 'meters' });
    } catch (e) {
      logDev('calculatePointDistance error:', e, pt1, pt2);
      return Infinity;
    }
  }

  /**
   * Gets the current map extent as a bounding box in WGS84 coordinates.
   * @returns {number[]|null} Bounding box [minLon, minLat, maxLon, maxLat], or null on error
   */
  function getMapBoundingBox() {
    try {
      const bbox = sdk.Map.getMapExtent();
      return bbox;
    } catch (e) {
      logDev('getMapBoundingBox error:', e);
      return null;
    }
  }

  // **************************************************************************************************************
  // VENUE/SEGMENT FUNCTIONS
  // **************************************************************************************************************

  /**
   * Retrieves address details for a venue using the WME SDK.
   * Returns house number, street, city, state, and postal code information.
   * @param {object} venue - Venue object with id property
   * @returns {object|null} Address object with {houseNumber, street, city, state, postalCode}, or null if invalid venue
   */
  function getVenueAddress(venue) {
    if (!venue || !venue.id) return null;
    return sdk.DataModel.Venues.getAddress({ venueId: venue.id });
  }

  /**
   * Retrieves address details for a segment using the WME SDK.
   * @param {object} segment Segment object with id property
   * @returns {object|null} Address object, or null if invalid segment
   */
  function getSegmentAddress(segment) {
    if (!segment || !segment.id) return null;
    return sdk.DataModel.Segments.getAddress({ segmentId: segment.id });
  }

  function getSelectedVenue() {
    const selection = sdk.Editing.getSelection();
    if (selection?.objectType === 'venue' && selection?.ids?.length === 1) {
      return sdk.DataModel.Venues.getById({ venueId: selection.ids[0] });
    }
    return null;
  }

  function getVenueLonLat(venue) {
    const centroid = turf.centroid(venue.geometry);
    return { longitude: centroid.geometry.coordinates[0], latitude: centroid.geometry.coordinates[1] };
  }

  function isAlwaysOpen(venue) {
    return is247Hours(venue.openingHours);
  }

  function is247Hours(openingHours) {
    if (!openingHours || openingHours.length !== 1) return false;
    const hours = openingHours[0];
    return hours.days?.length === 7 && (hours.allDay === true || (hours.fromHour === '00:00' && hours.toHour === '00:00'));
  }

  function isEmergencyRoom(venue) {
    return /(?:emergency\s+(?:room|department|dept))|\b(?:er|ed)\b/i.test(venue.name);
  }

  function isRestArea(venue) {
    return venue.categories.includes('REST_AREAS') && /rest\s*area/i.test(venue.name);
  }

  /**
   * Determines flag severity based on PVA (Place Verification Attribute) value.
   * Maps PVA codes to highlight colors: RED (missing/invalid), BLUE (confirmed), YELLOW (secondary), GREEN (ok).
   * Special handling for emergency rooms (coded as 'hosp' category).
   * @param {string} pvaValue - PVA code ("0", "2", "3", "hosp", "", etc.)
   * @param {object} venue - Venue object (used to check if hospital is emergency room)
   * @returns {number} Severity constant (SEVERITY.RED/BLUE/YELLOW/GREEN) for use in highlighting
   */
  function getPvaSeverity(pvaValue, venue) {
    const isER = pvaValue === 'hosp' && isEmergencyRoom(venue);
    let severity;
    if (pvaValue === '' || pvaValue === '0' || (pvaValue === 'hosp' && !isER)) {
      severity = SEVERITY.RED;
    } else if (pvaValue === '2') {
      severity = SEVERITY.BLUE;
    } else if (pvaValue === '3') {
      severity = SEVERITY.YELLOW;
    } else {
      severity = SEVERITY.GREEN;
    }
    return severity;
  }

  /**
   * Checks if a venue is residential.
   * @param {object|null} venue Venue object with residential property or categories array
   * @returns {boolean} True if venue is residential
   */
  function isVenueResidential(venue) {
    if (!venue) return false;
    // SDK has venue.residential property and RESIDENTIAL category ID
    return venue.residential === true || venue.categories?.includes('RESIDENTIAL');
  }

  /**
   * Checks if a venue is a charging station.
   * @param {object|null} venue Venue object with categories array
   * @returns {boolean} True if venue's primary category is CHARGING_STATION
   */
  function isVenueChargingStation(venue) {
    if (!venue) return false;
    const primaryCategory = venue.categories?.[0];
    return primaryCategory === 'CHARGING_STATION';
  }

  /**
   * Checks if a venue is a parking lot.
   * @param {object|null} venue Venue object with categories array
   * @returns {boolean} True if venue has PARKING_LOT category
   */
  function isVenueParkingLot(venue) {
    if (!venue) return false;
    return venue.categories?.includes('PARKING_LOT') ?? false;
  }

  /**
   * Checks if a venue is a point geometry (not polygon or line).
   * @param {object|null} venue Venue object with geometry property
   * @returns {boolean} True if venue geometry type is Point
   */
  function isVenuePoint(venue) {
    if (!venue || !venue.geometry) return false;
    return venue.geometry.type === 'Point';
  }

  function isCategoryConfiguredAsArea(category, pnhCategoryInfos, regionCode) {
    if (!pnhCategoryInfos) return false;
    const categoryInfo = pnhCategoryInfos.getById(category);
    if (!categoryInfo) return false;
    // Regional override takes precedence
    if (regionCode && categoryInfo.regArea?.includes(regionCode)) {
      return true;
    }
    return categoryInfo.area === '1';
  }

  /**
   * Gets applicable services based on venue type.
   * @param {object} venue Venue object to check
   * @returns {object} Service configuration (PARKING_LOT_SERVICES or GENERAL_SERVICES)
   */
  function getApplicableServices(venue) {
    if (isVenueParkingLot(venue)) {
      return PARKING_LOT_SERVICES;
    }
    return GENERAL_SERVICES;
  }

  // **************************************************************************************************************
  // CLASSES
  // **************************************************************************************************************

  class Country {
    /** @type {string} */
    countryCode;
    /** @type {string} */
    countryName;
    /** @type {PnhCategoryInfos} */
    categoryInfos;
    /** @type {PnhEntry[]} */
    pnh;
    /** @type {Object<string, Region>} */
    regions;
    /** @type {PnhEntry[]} */
    closedChains;

    /**
     * Creates an instance of Country.
     * @param {string} code Country code, e.g. USA, CAN
     * @param {string} name Country name, for display purposes
     * @param {string[][]} allSpreadsheetData Raw data from Google Sheets API (2D array of rows)
     * @param {number} categoryColumnIndex Column index for category information
     * @param {number} pnhColumnIndex Column index for PNH data
     * @param {Object<string, Region>} regions Object mapping region codes to Region instances
     */
    constructor(code, name, allSpreadsheetData, categoryColumnIndex, pnhColumnIndex, regions) {
      this.countryCode = code;
      this.countryName = name;
      this.categoryInfos = new PnhCategoryInfos();
      Pnh.processCategories(Pnh.processImportedDataColumn(allSpreadsheetData, categoryColumnIndex), this.categoryInfos);
      this.pnh = Pnh.processPnhSSRows(allSpreadsheetData, pnhColumnIndex, this);
      this.closedChains = this.pnh.filter((entry) => entry.chainIsClosed);
      this.regions = regions;
    }
  }
  class Region {
    static #defaultNewChainRequestEntryIds = ['entry.925969794', 'entry.1970139752', 'entry.1749047694'];
    static #defaultApproveChainRequestEntryIds = ['entry.925969794', 'entry.50214576', 'entry.1749047694'];
    regionCode;
    #formId;
    #newChainRequestEntryIds;
    #approveChainRequestEntryIds;

    constructor(regionCode, formId, newChainRequestEntryIds, approveChainRequestEntryIds) {
      this.regionCode = regionCode;
      this.#formId = formId;
      this.#newChainRequestEntryIds = newChainRequestEntryIds ?? Region.#defaultNewChainRequestEntryIds;
      this.#approveChainRequestEntryIds = approveChainRequestEntryIds ?? Region.#defaultApproveChainRequestEntryIds;
    }

    #getFormUrl(entryIds, entryValues) {
      const entryValuesUrl = entryValues.map((value, idx) => `${entryIds[idx]}=${value}`).join('&');
      return `https://docs.google.com/forms/d/${this.#formId}/viewform?${entryValuesUrl}`;
    }

    getNewChainFormUrl(entryValues) {
      return this.#getFormUrl(this.#newChainRequestEntryIds, entryValues);
    }

    getApproveChainFormUrl(entryValues) {
      return this.#getFormUrl(this.#approveChainRequestEntryIds, entryValues);
    }
  }
  class PnhCategoryInfos {
    #categoriesById = {};
    #categoriesByName = {};

    add(categoryInfo) {
      this.#categoriesById[categoryInfo.id] = categoryInfo;
      this.#categoriesByName[categoryInfo.name.toUpperCase()] = categoryInfo;
    }

    getById(id) {
      return this.#categoriesById[id];
    }

    getByName(name) {
      return this.#categoriesByName[name.toUpperCase()];
    }

    toArray() {
      return Object.values(this.#categoriesById);
    }
  }
  class PnhEntry {
    /** @type {string} */
    order;

    /** @type {string} */
    name;

    /** @type {string[]} */
    aliases;

    /** @type {string} */
    primaryCategory;

    /** @type {string[]} */
    altCategories;

    /** @type {string} */
    description;

    /** @type {string} */
    url;

    /** @type {string} */
    notes;

    /** @type {string[]} */
    regions;

    /**
     * If this is true, the PNH entry should be ignored.
     * @type {boolean}
     * */
    disabled;

    /** @type {Symbol} */
    forceCategoryMatching;

    flagsToAdd = {};

    flagsToRemove = {};

    /** @type {string[]} */
    servicesToAdd = [];

    /** @type {string[]} */
    servicesToRemove = [];

    /** @type {string} */
    forceBrand;

    /** @type {RegExp} */
    localUrlCheckRegEx;

    /** @type {RegExp} */
    localizationRegEx;

    /** @type {string} */
    recommendedPhone;

    /**
     * Prevent name change
     * @type {boolean}
     */
    keepName = false;

    /** @type {string} */
    optionalAlias;

    /** @type {boolean} */
    chainIsClosed;

    /**
     * Value is -1 if no value has been set in PNH.
     * @type {number}
     */
    brandParentLevel = -1;

    /** @type {boolean} */
    strMatchAny;

    /** @type {string[]} */
    spaceMatchList;

    /** @type {boolean} */
    pharmhours;

    /** @type {boolean} */
    notABank;

    /** @type {boolean} */
    optionCat2;

    /** @type {boolean} */
    optionName2;

    /** @type {boolean} */
    altName2Desc;

    /** @type {boolean} */
    subFuel;

    /** @type {RegExp} */
    regexNameMatch;

    /** @type {number} */
    lockAt;

    /** @type {boolean} */
    noUpdateAlias;

    /** @type {boolean} */
    betaEnable;

    /** @type {string[]} */
    searchnameword;

    /** @type {string[]} */
    searchNameList;

    /** @type {boolean} */
    hasSpecialCases = false;

    /**
     * true if the PNH entry is invalid and should be skipped
     * @type {boolean}
     */
    invalid = false;

    /**
     *
     * @param {string[]} columnHeaders
     * @param {string} rowString A pipe-separated string with all of the PNH entry's data
     * @param {Country} country
     */
    constructor(columnHeaders, rowString, country) {
      const parseResult = this.#parseSpreadsheetRow(columnHeaders, rowString, country);
      if (!this.invalid && (!this.disabled || this.betaEnable)) {
        this.#buildSearchNameList(parseResult);
      }
    }

    /**
     * Makes a string uppercase, then removes AND (anywhere), THE (only at the beginning),
     * and any non-alphanumeric characters.
     * @param {string} str
     */
    static #tighten(str) {
      return str
        .toUpperCase()
        .replace(/ AND /g, '')
        .replace(/^THE /g, '')
        .replace(/[^A-Z0-9]/g, '');
    }

    /**
     * Makes a string uppercase and removes any non-alphanumeric characters except for commas.
     * @param {string} str
     */
    static #stripNonAlphaKeepCommas(str) {
      return str.toUpperCase().replace(/[^A-Z0-9,]/g, '');
    }

    /**
     *
     * @param {string[]} columnHeaders
     * @param {string} rowString
     * @param {Country} country
     * @returns
     */
    #parseSpreadsheetRow(columnHeaders, rowString, country) {
      /**  Contains values needed for immediate processing, but not to be stored in the PnhEntry */
      const result = {
        searchnamebase: null,
        searchnamemid: null,
        searchnameend: null,
        skipAltNameMatch: null,
        warningMessages: [],
      };

      try {
        const columnValues = rowString.split('|');

        // Do any preprocessing here:
        const disabled = columnValues[columnHeaders.indexOf(Pnh.SSHeader.disable)].trim();
        if (disabled === '1') {
          // If the row is disabled, no need to process the rest of it.
          this.disabled = true;
          return result;
        }

        // Step through columns and process the row values.
        columnHeaders.forEach((header, i) => {
          try {
            if (Pnh.COLUMNS_TO_IGNORE.includes(header)) return;

            // If an invalid value is found, don't bother parsing the rest of the row data.
            if (!this.invalid) {
              let value = columnValues[i].trim();
              if (!value.length) {
                value = undefined;
              } else if (header === Pnh.SSHeader.aliases) {
                // Validate aliases: ignore if starts with paren (invalid format), otherwise normalize commas
                if (value.startsWith('(')) {
                  value = undefined; // Ignore aliases if the cell starts with paren
                } else {
                  value = value.replace(/,[^A-za-z0-9]*/g, ','); // Tighten up commas if more than one alias
                }
              }

              switch (header) {
                case Pnh.SSHeader.order:
                case Pnh.SSHeader.description:
                case Pnh.SSHeader.notes:
                case Pnh.SSHeader.displaynote:
                case Pnh.SSHeader.sfurl:
                case Pnh.SSHeader.sfurllocal:
                  header = header.substring(3);
                  this[header] = value;
                  break;
                case Pnh.SSHeader.url:
                  if (value) this.url = normalizeURL(value);
                  break;
                case Pnh.SSHeader.searchnamebase:
                  result.searchnamebase = value;
                  break;
                case Pnh.SSHeader.searchnamemid:
                  result.searchnamemid = value;
                  break;
                case Pnh.SSHeader.searchnameend:
                  result.searchnameend = value;
                  break;
                case Pnh.SSHeader.searchnameword:
                  this.searchnameword = value?.toUpperCase().replace(/, /g, ',').split(',');
                  break;
                case Pnh.SSHeader.name:
                  if (value?.toUpperCase() !== 'PLEASE REUSE') {
                    this.name = value;
                  } else {
                    // No need to post warning here. Just skip it.
                    this.invalid = true;
                  }
                  break;
                case Pnh.SSHeader.aliases:
                  this.aliases = value?.split(',').map((v) => v.trim()) || [];
                  break;
                case Pnh.SSHeader.category1:
                  if (value) {
                    this.primaryCategory = country.categoryInfos.getByName(value)?.id;
                    if (typeof this.primaryCategory === 'undefined') {
                      result.warningMessages.push(`Unrecognized primary category value: ${value}`);
                    }
                  } else {
                    result.warningMessages.push('No primary category assigned. PNH entry will be ignored!');
                    this.invalid = true;
                  }
                  break;
                case Pnh.SSHeader.category2:
                  this.altCategories = value
                    ? value.split(',').reduce((acc, catName) => {
                        const trimmedName = catName.trim();
                        const cat = country.categoryInfos.getByName(trimmedName)?.id;
                        if (!cat) {
                          result.warningMessages.push(`Unrecognized alternate category: ${trimmedName}`);
                        } else if (typeof cat === 'string') {
                          acc.push(cat);
                        }
                        return acc;
                      }, [])
                    : [];
                  break;
                case Pnh.SSHeader.region:
                  if (value) {
                    this.regions = value
                      .toUpperCase()
                      .split(',')
                      .map((v) => v.trim());
                    // TODO: Check for valid regions.
                  } else {
                    // If no regions, ignore it.
                    this.invalid = true;
                    result.warningMessages.push('No regions specified. PNH entry will be ignored!');
                  }
                  break;
                case Pnh.SSHeader.disable:
                  // Handled the '1' case earlier in preprocessing
                  if (value === 'altName') {
                    result.skipAltNameMatch = true;
                  } else if (value) {
                    result.warningMessages.push(`Unrecognized value in ${Pnh.SSHeader.disable} column: ${value}`);
                  }
                  return;
                case Pnh.SSHeader.forcecat:
                  if (!value || value === '0') {
                    this.forceCategoryMatching = Pnh.ForceCategoryMatchingType.NONE;
                  } else if (value === '1') {
                    this.forceCategoryMatching = Pnh.ForceCategoryMatchingType.PRIMARY;
                  } else if (value === '2') {
                    this.forceCategoryMatching = Pnh.ForceCategoryMatchingType.ANY;
                  } else {
                    result.warningMessages.push(`Unrecognized value in ${Pnh.SSHeader.forcecat} column: ${value}`);
                  }
                  break;
                case Pnh.SSHeader.speccase:
                  if (value) {
                    this.hasSpecialCases = true;
                    value = value.split(',').map((v) => v.trim());
                    /* eslint-disable no-cond-assign */
                    value.forEach((specialCase) => {
                      let match;
                      if ((match = specialCase.match(REGEX_PATTERNS.BUTTON_ON))) {
                        const [, scFlag] = match;
                        switch (scFlag) {
                          case 'addCat2':
                            // flag = new Flag.AddCat2();
                            break;
                          case 'addPharm':
                          case 'addSuper':
                          case 'appendAMPM':
                          case 'addATM':
                          case 'addConvStore':
                            this.flagsToAdd[scFlag] = true;
                            break;
                          default:
                            result.warningMessages.push(`Unrecognized ph_specCase value: ${specialCase}`);
                        }
                      } else if ((match = specialCase.match(REGEX_PATTERNS.BUTTON_OFF))) {
                        const [, scFlag] = match;
                        switch (scFlag) {
                          case 'addConvStore':
                            this.flagsToRemove[scFlag] = true;
                            break;
                          default:
                            result.warningMessages.push(`Unrecognized ph_specCase value: ${specialCase}`);
                        }
                        // } else if (match = specCase.match(REGEX_PATTERNS.MESS_ON)) {
                        //    [, scFlag] = match;
                        //    _buttonBanner[scFlag].active = true;
                        // } else if (match = specCase.match(REGEX_PATTERNS.MESS_OFF)) {
                        //    [, scFlag] = match;
                        //    _buttonBanner[scFlag].active = false;
                      } else if ((match = specialCase.match(REGEX_PATTERNS.PS_ON))) {
                        const [, scFlag] = match;
                        // Map ps_* keys to banner keys (e.g., ps_valet → addValet)
                        const mappedKey = PNH_TO_BANNER_SERVICE_KEY_MAP[scFlag] || scFlag;
                        // Only add if the service exists in the banner
                        if (mappedKey && mappedKey.length > 0) {
                          this.servicesToAdd.push(mappedKey);
                        }
                      } else if ((match = specialCase.match(REGEX_PATTERNS.PS_OFF))) {
                        const [, scFlag] = match;
                        // Map ps_* keys to banner keys (e.g., ps_valet → addValet)
                        const mappedKey = PNH_TO_BANNER_SERVICE_KEY_MAP[scFlag] || scFlag;
                        // Only add if the service exists in the banner
                        if (mappedKey && mappedKey.length > 0) {
                          this.servicesToRemove.push(mappedKey);
                        }
                      } else if ((match = specialCase.match(REGEX_PATTERNS.FORCE_BRAND))) {
                        // If brand is going to be forced, use that.  Otherwise, use existing brand.
                        [, this.forceBrand] = match;
                      } else if ((match = specialCase.match(REGEX_PATTERNS.LOCAL_URL))) {
                        // parseout localURL data if exists (meaning place can have a URL distinct from the chain URL
                        [, this.localURLcheck] = new RegExp(match, 'i');
                      } else if ((match = specialCase.match(REGEX_PATTERNS.CHECK_LOCALIZATION))) {
                        const [, localizationString] = match;
                        this.localizationRegEx = new RegExp(localizationString, 'g');
                      } else if ((match = specialCase.match(REGEX_PATTERNS.PHONE))) {
                        [, this.recommendedPhone] = match;
                      } else if (REGEX_PATTERNS.KEEP_NAME.test(specialCase)) {
                        this.keepName = true;
                      } else if ((match = specialCase.match(REGEX_PATTERNS.OPTION_ALT_NAME))) {
                        [, this.optionalAlias] = match;
                      } else if (REGEX_PATTERNS.CLOSED.test(specialCase)) {
                        this.chainIsClosed = true;
                      } else if ((match = specialCase.match(REGEX_PATTERNS.BRAND_PARENT))) {
                        try {
                          this.brandParentLevel = parseInt(match[1], 10);
                        } catch {
                          result.warningMessages.push(`Invalid forceBrand value: ${specialCase}`);
                        }
                      } else if (REGEX_PATTERNS.STR_MATCH_ANY.test(specialCase)) {
                        this.strMatchAny = true;
                      } else if (REGEX_PATTERNS.PHARM_HOURS.test(specialCase)) {
                        this.pharmhours = true;
                      } else if (REGEX_PATTERNS.NOT_A_BANK.test(specialCase)) {
                        this.notABank = true;
                      } else if (REGEX_PATTERNS.OPTION_CAT2.test(specialCase)) {
                        this.optionCat2 = true;
                      } else if (REGEX_PATTERNS.OPTION_NAME2.test(specialCase)) {
                        this.optionName2 = true;
                      } else if (REGEX_PATTERNS.ALT_NAME2_DESC.test(specialCase)) {
                        this.altName2Desc = true;
                      } else if (REGEX_PATTERNS.SUB_FUEL.test(specialCase)) {
                        this.subFuel = true;
                      } else if ((match = specialCase.match(REGEX_PATTERNS.REGEX_NAME_MATCH))) {
                        this.regexNameMatch = new RegExp(match[1].replace(/\\/, '\\').replace(/<or>/g, '|'), 'i');
                      } else if ((match = specialCase.match(REGEX_PATTERNS.LOCK_AT))) {
                        try {
                          this.lockAt = parseInt(match[1], 10);
                          if (this.lockAt < 1 || this.lockAt > 6) {
                            throw new Error();
                          }
                        } catch {
                          result.warningMessages.push(`Invalid ph_speccase lockAt value (must be between 1 and 6): ${specialCase}`);
                        }
                      } else if (REGEX_PATTERNS.NO_UPDATE_ALIAS.test(specialCase)) {
                        this.noUpdateAlias = true;
                      } else if (/^betaEnable$/i.test(specialCase)) {
                        this.betaEnable = true;
                      } else {
                        result.warningMessages.push(`Unrecognized ph_speccase value: ${specialCase}`);
                      }
                    });
                    /* eslint-enable no-cond-assign */
                  }
                  break;
                case '': // Ignore this
                  break;
                default:
                // Ignore unrecognized headers here.
              }
            }
          } catch (ex) {
            result.warningMessages.push(`An unexpected error occurred while processing column: ${header}. PNH entry will be ignored.`);
          }
        }); // END ROW PROCESSING

        // Do any post-processing of row values here:
        if (this.strMatchAny || this.primaryCategory === 'HOTEL') {
          // Space match uses custom string transformations: uppercase, strip AND/THE, remove non-alphanumerics
          this.spaceMatchList = [
            this.name
              .toUpperCase()
              .replace(/ AND /g, ' ')
              .replace(/^THE /g, '')
              .replace(/[^A-Z0-9 ]/g, ' ')
              .replace(/ {2,}/g, ' '),
          ];
          if (this.searchnameword) {
            this.spaceMatchList.push(...this.searchnameword);
          }
        }
      } catch (ex) {
        result.warningMessages.push(`An unexpected error occurred while parsing. PNH entry will be ignored! :\n${ex.toString()}`);
        this.disabled = true;
      }

      if (result.warningMessages.length) {
        logDev(`${country.countryName}: PNH Order # ${this.order} parsing issues:\n- ${result.warningMessages.join('\n- ')}`);
      }
      return result;
    }

    #buildSearchNameList(parseResult) {
      let newNameList = [PnhEntry.#tighten(this.name)];

      if (!parseResult.skipAltNameMatch) {
        // Add any aliases
        newNameList = newNameList.concat(this.aliases.map((alias) => PnhEntry.#tighten(alias)));
      }

      // The following code sets up alternate search names as outlined in the PNH dataset.
      // Formula, with P = PNH primary; A1, A2 = PNH aliases; B1, B2 = base terms; M1, M2 = mid terms; E1, E2 = end terms
      // Search list will build: P, A, B, PM, AM, BM, PE, AE, BE, PME, AME, BME.
      // Multiple M terms are applied singly and in pairs (B1M2M1E2).  Multiple B and E terms are applied singly (e.g B1B2M1 not used).
      // Any doubles like B1E2=P are purged at the end to eliminate redundancy.
      if (!isNullOrWhitespace(parseResult.searchnamebase)) {
        // If base terms exist, otherwise only the primary name is matched
        newNameList = newNameList.concat(PnhEntry.#stripNonAlphaKeepCommas(parseResult.searchnamebase).split(','));

        if (!isNullOrWhitespace(parseResult.searchnamemid)) {
          let pnhSearchNameMid = PnhEntry.#stripNonAlphaKeepCommas(parseResult.searchnamemid).split(',');
          if (pnhSearchNameMid.length > 1) {
            // if there are more than one mid terms, it adds a permutation of the first 2
            pnhSearchNameMid = pnhSearchNameMid.concat([pnhSearchNameMid[0] + pnhSearchNameMid[1], pnhSearchNameMid[1] + pnhSearchNameMid[0]]);
          }
          const midLen = pnhSearchNameMid.length;
          // extend the list by adding Mid terms onto the SearchNameBase names
          for (let extix = 1, len = newNameList.length; extix < len; extix++) {
            for (let midix = 0; midix < midLen; midix++) {
              newNameList.push(newNameList[extix] + pnhSearchNameMid[midix]);
            }
          }
        }

        if (!isNullOrWhitespace(parseResult.searchnameend)) {
          const pnhSearchNameEnd = PnhEntry.#stripNonAlphaKeepCommas(parseResult.searchnameend).split(',');
          const endLen = pnhSearchNameEnd.length;
          // extend the list by adding End terms onto all the SearchNameBase & Base+Mid names
          for (let extix = 1, len = newNameList.length; extix < len; extix++) {
            for (let endix = 0; endix < endLen; endix++) {
              newNameList.push(newNameList[extix] + pnhSearchNameEnd[endix]);
            }
          }
        }
      }

      // Clear out any empty entries
      newNameList = newNameList.filter((name) => name.length > 1);

      // Next, add extensions to the search names based on the WME place category
      const categoryInfo = this.primaryCategory;
      const appendWords = [];
      if (categoryInfo) {
        if (categoryInfo.id === 'HOTEL') {
          appendWords.push('HOTEL');
        } else if (categoryInfo.id === 'BANK_FINANCIAL' && !this.notABank) {
          appendWords.push('BANK', 'ATM');
        } else if (categoryInfo.id === 'SUPERMARKET_GROCERY') {
          appendWords.push('SUPERMARKET');
        } else if (categoryInfo.id === 'GYM_FITNESS') {
          appendWords.push('GYM');
        } else if (categoryInfo.id === 'GAS_STATION') {
          appendWords.push('GAS', 'GASOLINE', 'FUEL', 'STATION', 'GASSTATION');
        } else if (categoryInfo.id === 'CAR_RENTAL') {
          appendWords.push('RENTAL', 'RENTACAR', 'CARRENTAL', 'RENTALCAR');
        }
        appendWords.forEach((word) => {
          newNameList = newNameList.concat(newNameList.map((name) => name + word));
        });
      }

      // Add entries for word/spelling variations
      Pnh.WORD_VARIATIONS.forEach((variationsList) => addSpellingVariants(newNameList, variationsList));

      this.searchNameList = uniq(newNameList);
    }

    /**
     *  Function that checks current place against the Harmonization Data.  Returns place data or "NoMatch"
     * @param {string} name
     * @param {string} state2L
     * @param {string} region3L
     * @param {string} country
     * @param {string[]} categories
     * @param {venue} venue
     * @returns
     */
    getMatchInfo(name, state2L, region3L, country, categories, venue, venueNameSpace) {
      const matchInfo = {
        isMatch: false,
        allowMultiMatch: true, // TODO: This can probably be removed
        matchOutOfRegion: false,
      };
      let nameMatch = false;

      // Name Matching
      if (this.regexNameMatch) {
        nameMatch = this.regexNameMatch.test(venue.name);
      } else if (this.strMatchAny || this.primaryCategory === 'HOTEL') {
        // Match any part of WME name with either the PNH name or any spaced names
        matchInfo.allowMultiMatch = true; // TODO: This can probably be removed

        for (let nmix = 0; nmix < this.spaceMatchList.length; nmix++) {
          if (venueNameSpace.includes(` ${this.spaceMatchList[nmix]} `)) {
            nameMatch = true;
            break;
          }
        }
      } else {
        // Split all possible search names for the current PNH entry
        const { searchNameList } = this;

        // Clear non-letter characters for alternate match ( HOLLYIVYPUB23 --> HOLLYIVYPUB )
        const venueNameNoNum = name.replace(/[^A-Z]/g, '');

        /*
         * I could not find strMatchStart or strMatchEnd in the PNH spreadsheet. Assuming these
         * are no longer needed.
         */
        if (searchNameList.includes(name) || searchNameList.includes(venueNameNoNum)) {
          // full match of any term only
          nameMatch = true;
        }
      }

      // if a match was found:
      if (nameMatch) {
        // Compare WME place name to PNH search name list
        logDev(`Matched PNH Order No.: ${this.order}`);

        const PNHPriCat = this.primaryCategory; // Primary category of PNH data
        let PNHForceCat = this.forceCategoryMatching; // Primary category of PNH data

        // Gas stations only harmonized if the WME place category is already gas station (prevents Costco Gas becoming Costco Store)
        if (categories[0] === 'GAS_STATION' || PNHPriCat === 'GAS_STATION') {
          PNHForceCat = Pnh.ForceCategoryMatchingType.PRIMARY;
        }

        // Name and primary category match
        matchInfo.isMatch =
          (PNHForceCat === Pnh.ForceCategoryMatchingType.PRIMARY && categories.indexOf(PNHPriCat) === 0) ||
          // Name and any category match
          (PNHForceCat === Pnh.ForceCategoryMatchingType.ANY && categories.includes(PNHPriCat)) ||
          // Name only match
          PNHForceCat === Pnh.ForceCategoryMatchingType.NONE;
      }

      if (
        !(
          this.regions.includes(state2L) ||
          this.regions.includes(region3L) || // if the WME-selected venue matches the state, region
          this.regions.includes(country) || //  OR if the country code is in the data then it is approved for all regions therein
          $('#WMEPH-RegionOverride').prop('checked')
        )
      ) {
        // OR if region override is selected (dev setting)
        matchInfo.matchOutOfRegion = true;
      }

      return matchInfo;
    }
  }
  /** "Namespace" for classes and methods related to handling PNH spreadsheet data */
  class Pnh {
    static #SPREADSHEET_ID = '1pBz4l4cNapyGyzfMJKqA4ePEFLkmz2RryAt1UV39B4g';
    static #SPREADSHEET_RANGE = '2019.01.20.001!A2:L';
    static #SPREADSHEET_MODERATORS_RANGE = 'Moderators!A1:F';
    static #API_KEY = 'YTJWNVBVRkplbUZUZVVObU1YVXpSRVZ3ZW5OaFRFSk1SbTR4VGxKblRURjJlRTFYY3pOQ2NXZElPQT09';
    /** Columns that can be ignored when importing */
    static COLUMNS_TO_IGNORE = ['temp_field', 'ph_services', 'ph_national', 'logo', ''];
    static WORD_VARIATIONS = null;
    static MODERATORS = {};
    // vars for category name checking
    /** @type {string[]} */
    static HOSPITAL_PART_MATCH;
    /** @type {string[]} */
    static HOSPITAL_FULL_MATCH;
    /** @type {string[]} */
    static ANIMAL_PART_MATCH;
    /** @type {string[]} */
    static ANIMAL_FULL_MATCH;
    /** @type {string[]} */
    static SCHOOL_PART_MATCH;
    /** @type {string[]} */
    static SCHOOL_FULL_MATCH;

    static ForceCategoryMatchingType = Object.freeze({
      NONE: Symbol('none'),
      PRIMARY: Symbol('primary'),
      ANY: Symbol('any'),
    });

    static SSHeader = Object.freeze({
      order: 'ph_order',
      name: 'ph_name',
      aliases: 'ph_aliases',
      category1: 'ph_category1',
      category2: 'ph_category2',
      description: 'ph_description',
      url: 'ph_url',
      notes: 'ph_notes',
      region: 'ph_region',
      disable: 'ph_disable',
      forcecat: 'ph_forcecat',
      displaynote: 'ph_displaynote',
      speccase: 'ph_speccase',
      searchnamebase: 'ph_searchnamebase',
      searchnamemid: 'ph_searchnamemid',
      searchnameend: 'ph_searchnameend',
      searchnameword: 'ph_searchnameword',
      sfurl: 'ph_sfurl',
      sfurllocal: 'ph_sfurllocal',
      toValueArray: () => Object.values(Pnh.SSHeader).filter((v) => typeof v === 'string'),
    });

    /**
     * Function that checks current place against the Harmonization Data. Returns place data, "NoMatch", or "Approval Needed"
     * @param {string} name The venue's base name, i.e. everything before a hyphen or parentheses
     * @param {string} state2L The 2-letter state abbreviation
     * @param {string} region3L The 3-letter region abbreviation
     * @param {string} country The country code
     * @param {string[]} categories The venue's current category array
     * @param {venue} venue The venue object
     * @param {boolean} [closedChainsOnly] Use true if only finding closed chains, i.e. when doing map highlights
     * @returns
     */
    static findMatch(name, state2L, region3L, country, categories, venue, closedChainsOnly) {
      if (country !== PNH_DATA.USA.countryCode && country !== PNH_DATA.CAN.countryCode) {
        return ['NoMatch'];
      }
      if (isVenueParkingLot(venue)) {
        return ['NoMatch'];
      }
      /** @type {PnhEntry[]} */
      const pnhData = closedChainsOnly ? PNH_DATA[country].closedChains : PNH_DATA[country].pnh;
      const matchPNHRegionData = []; // array of matched data with regional approval
      const pnhOrderNum = [];
      const pnhNameTemp = [];
      let matchOutOfRegion = false; // tracks match status
      let matchInRegion = false;

      name = name.toUpperCase().replace(/ AND /g, ' ').replace(/^THE /g, '');
      const venueNameSpace = ` ${name.replace(/[^A-Z0-9 ]/g, ' ').replace(/ {2,}/g, ' ')} `;
      name = name.replace(/[^A-Z0-9]/g, ''); // Clear all non-letter and non-number characters ( HOLLYIVY PUB #23 -- > HOLLYIVYPUB23 )

      // for each entry in the PNH list (skipping headers at index 0)
      for (let pnhIdx = 0; pnhIdx < pnhData.length; pnhIdx++) {
        const pnhEntry = pnhData[pnhIdx];
        const matchInfo = pnhEntry.getMatchInfo(name, state2L, region3L, country, categories, venue, venueNameSpace);
        if (matchInfo.isMatch) {
          // if (!matchInfo.allowMultiMatch) {
          //     return [pnhEntry];
          // }
          if (matchInfo.matchOutOfRegion) {
            // PNH match found (once true, stays true)
            matchOutOfRegion = true;
            // temp name for approval return
            pnhNameTemp.push(pnhEntry.name);

            // temp order number for approval return
            pnhOrderNum.push(pnhEntry.order);
          } else {
            matchInRegion = true;
            matchPNHRegionData.push(pnhEntry);
          }
        }
      } // END loop through PNH entries

      // If name & region match was found:
      if (matchInRegion) {
        return matchPNHRegionData;
      }
      if (matchOutOfRegion) {
        // if a name match was found but not for region, prod the user to get it approved
        return ['ApprovalNeeded', pnhNameTemp, pnhOrderNum];
      }
      if (matchPNHRegionData.length) {
        return matchOutOfRegion;
      }
      // if no match was found, suggest adding the place to the sheet if it's a chain
      return ['NoMatch'];
    }

    static #validatePnhSSColumnHeaders(headers) {
      let valid = true;
      const expectedHeaders = Pnh.SSHeader.toValueArray();

      // Warn if extra headers are found in the spreadsheet.
      headers.forEach((header) => {
        // temp_field currently exists on the USA sheet but may not be needed
        if (header.length && header !== 'temp_field' && !expectedHeaders.includes(header) && !Pnh.COLUMNS_TO_IGNORE.includes(header)) {
          logDev(`Unexpected column header found in PNH spreadsheet: ${header}`);
        }
      });

      // Return invalid if expected headers are not found in spreadsheet.
      expectedHeaders.forEach((header) => {
        if (!headers.includes(header)) {
          logDev(`Column header missing from PNH spreadsheet data: ${header}`);
          valid = false;
        }
      });

      return valid;
    }

    /**
     *
     * @param {string[]} rows
     * @param {Country} country
     * @returns {PnhEntry[]}
     */
    static processPnhSSRows(allData, columnIndex, country) {
      const rows = this.processImportedDataColumn(allData, columnIndex);
      const columnHeaders = rows
        .splice(0, 1)[0]
        .split('|')
        .map((h) => h.trim());

      // Canada's spreadsheet is missing 'ph_order' in the first column header.
      if (!columnHeaders[0].length) columnHeaders[0] = Pnh.SSHeader.order;

      if (!Pnh.#validatePnhSSColumnHeaders(columnHeaders)) {
        throw new Error('WMEPH: WMEPH exiting due to missing spreadsheet column headers.');
      }
      return rows.map((row) => new PnhEntry(columnHeaders, row, country)).filter((entry) => !entry.disabled && !entry.invalid);
    }

    static processImportedDataColumn(allData, columnIndex) {
      return allData.filter((row) => row.length >= columnIndex + 1).map((row) => row[columnIndex]);
    }

    static #getSpreadsheetUrl(id, range, key) {
      return `https://sheets.googleapis.com/v4/spreadsheets/${id}/values/${range}?${dec(key)}`;
    }

    static async downloadAllData() {
      await this.downloadPnhData();
      await this.#downloadPnhModerators();
      logDev(`✓ All PNH categories loaded: USA=${Object.keys(PNH_DATA.USA.categoryInfos.toArray()).length}, CAN=${Object.keys(PNH_DATA.CAN.categoryInfos.toArray()).length}`);
    }

    static downloadPnhData() {
      log('PNH data download started...');
      return new Promise((resolve, reject) => {
        const url = this.#getSpreadsheetUrl(this.#SPREADSHEET_ID, this.#SPREADSHEET_RANGE, this.#API_KEY);

        $.getJSON(url)
          .done((res) => {
            const { values } = res;
            if (values[0][0].toLowerCase() === 'obsolete') {
              WazeWrap.Alerts.error(SCRIPT_NAME, "You are using an outdated version of WMEPH that doesn't work anymore. Update or disable the script.");
              return;
            }

            // This needs to be performed before makeNameCheckList() is called.
            Pnh.WORD_VARIATIONS = Pnh.processImportedDataColumn(values, 11)
              .slice(1)
              .map((row) =>
                row
                  .toUpperCase()
                  .replace(/[^A-z0-9,]/g, '')
                  .split(','),
              );

            logDev('Creating USA Country - category data column 3');
            const usaCategoryData = Pnh.processImportedDataColumn(values, 3);
            logDev('USA category rows:', usaCategoryData.length);

            PNH_DATA.USA = new Country('USA', 'USA', values, 3, 0, {
              NWR: new Region('NWR', '1hv5hXBlGr1pTMmo4n3frUx1DovUODbZodfDBwwTc7HE'),
              SWR: new Region('SWR', '1Qf2N4fSkNzhVuXJwPBJMQBmW0suNuy8W9itCo1qgJL4'),
              HI: new Region('HI', '1K7Dohm8eamIKry3KwMTVnpMdJLaMIyDGMt7Bw6iqH_A', null, ['entry.1497446659', 'entry.50214576', 'entry.1749047694']),
              PLN: new Region('PLN', '1ycXtAppoR5eEydFBwnghhu1hkHq26uabjUu8yAlIQuI'),
              SCR: new Region('SCR', '1KZzLdlX0HLxED5Bv0wFB-rWccxUp2Mclih5QJIQFKSQ'),
              GLR: new Region('GLR', '19btj-Qt2-_TCRlcS49fl6AeUT95Wnmu7Um53qzjj9BA'),
              SAT: new Region(
                'SAT',
                '1bxgK_20Jix2ahbmUvY1qcY0-RmzUBT6KbE5kjDEObF8',
                ['entry.2063110249', 'entry.2018912633', 'entry.1924826395'],
                ['entry.2063110249', 'entry.123778794', 'entry.1924826395'],
              ),
              SER: new Region(
                'SER',
                '1jYBcxT3jycrkttK5BxhvPXR240KUHnoFMtkZAXzPg34',
                ['entry.822075961', 'entry.1422079728', 'entry.1891389966'],
                ['entry.822075961', 'entry.607048307', 'entry.1891389966'],
              ),
              ATR: new Region('ATR', '1v7JhffTfr62aPSOp8qZHA_5ARkBPldWWJwDeDzEioR0'),
              NER: new Region('NER', '1UgFAMdSQuJAySHR0D86frvphp81l7qhEdJXZpyBZU6c'),
              NOR: new Region('NOR', '1iYq2rd9HRd-RBsKqmbHDIEBGuyWBSyrIHC6QLESfm4c'),
              MAR: new Region('MAR', '1PhL1iaugbRMc3W-yGdqESoooeOz-TJIbjdLBRScJYOk'),
            });
            PNH_DATA.CAN = new Country('CAN', 'Canada', values, 3, 2, {
              CA_EN: new Region(
                'CA_EN',
                '13JwXsrWPNmCdfGR5OVr5jnGZw-uNGohwgjim-JYbSws',
                ['entry_839085807', 'entry_1067461077', 'entry_318793106', 'entry_1149649663'],
                ['entry_839085807', 'entry_1125435193', 'entry_318793106', 'entry_1149649663'],
              ),
              QC: new Region(
                'QC',
                '13JwXsrWPNmCdfGR5OVr5jnGZw-uNGohwgjim-JYbSws',
                ['entry_839085807', 'entry_1067461077', 'entry_318793106', 'entry_1149649663'],
                ['entry_839085807', 'entry_1125435193', 'entry_318793106', 'entry_1149649663'],
              ),
            });
            PNH_DATA.states = Pnh.processImportedDataColumn(values, 1).filter((row) => row && row.trim());

            const WMEPHuserList = Pnh.processImportedDataColumn(values, 4)[1].split('|');
            const betaix = WMEPHuserList.indexOf('BETAUSERS');
            _wmephDevList = [];
            _wmephBetaList = [];
            for (let ulix = 1; ulix < betaix; ulix++) _wmephDevList.push(WMEPHuserList[ulix].toLowerCase().trim());
            for (let ulix = betaix + 1; ulix < WMEPHuserList.length; ulix++) _wmephBetaList.push(WMEPHuserList[ulix].toLowerCase().trim());

            const processTermsCell = (termsValues, colIdx) =>
              Pnh.processImportedDataColumn(termsValues, colIdx)[1]
                .toLowerCase()
                .split('|')
                .map((value) => value.trim());
            this.HOSPITAL_PART_MATCH = processTermsCell(values, 5);
            this.HOSPITAL_FULL_MATCH = processTermsCell(values, 6);
            this.ANIMAL_PART_MATCH = processTermsCell(values, 7);
            this.ANIMAL_FULL_MATCH = processTermsCell(values, 8);
            this.SCHOOL_PART_MATCH = processTermsCell(values, 9);
            this.SCHOOL_FULL_MATCH = processTermsCell(values, 10);

            log('PNH data download completed');
            resolve();
          })
          .fail((res) => {
            const message = res.responseJSON && res.responseJSON.error ? res.responseJSON.error : 'See response error message above.';
            logDev('Failed to load spreadsheet:', message);
            reject();
          });
      });
    }

    static #downloadPnhModerators() {
      log('PNH moderators download started...');
      return new Promise((resolve) => {
        const url = Pnh.#getSpreadsheetUrl(Pnh.#SPREADSHEET_ID, Pnh.#SPREADSHEET_MODERATORS_RANGE, Pnh.#API_KEY);

        $.getJSON(url)
          .done((res) => {
            const { values } = res;

            try {
              values.forEach((regionArray) => {
                const region = regionArray[0];
                const mods = regionArray.slice(3);
                Pnh.MODERATORS[region] = mods;
              });
            } catch (ex) {
              Pnh.MODERATORS['?'] = ['Error downloading moderators!'];
            }

            // delete Texas region, if it exists
            delete Pnh.MODERATORS.TX;

            log('PNH moderators download completed');
            resolve();
          })
          .fail((res) => {
            const message = res.responseJSON && res.responseJSON.error ? res.responseJSON.error : 'See response error message above.';
            logDev('Failed to load moderator list:', message);
            Pnh.MODERATORS['?'] = ['Error downloading moderators!'];
            resolve();
          });
      });
    }

    static processCategories(categoryDataRows, categoryInfos) {
      let headers;
      let pnhServiceKeys;
      let wmeServiceIds;
      const splitValues = (value) => (value.trim() ? value.split(',').map((v) => v.trim()) : []);
      categoryDataRows.forEach((row, iRow) => {
        row = row.split('|');
        if (iRow === 0) {
          headers = row;
        } else if (iRow === 1) {
          pnhServiceKeys = row;
        } else if (iRow === 2) {
          wmeServiceIds = row;
        } else {
          const categoryInfo = {
            services: [],
          };
          row.forEach((value, iCol) => {
            const headerValue = headers[iCol].trim();
            value = value.trim();
            switch (headerValue) {
              case 'pc_wmecat':
                categoryInfo.id = value;
                break;
              case 'pc_transcat':
                categoryInfo.name = value;
                break;
              case 'pc_catparent':
                categoryInfo.parent = value;
                break;
              case 'pc_point':
                categoryInfo.point = value;
                break;
              case 'pc_area':
                categoryInfo.area = value;
                break;
              case 'pc_regpoint':
                categoryInfo.regPoint = splitValues(value);
                break;
              case 'pc_regarea':
                categoryInfo.regArea = splitValues(value);
                break;
              case 'pc_lock1':
                categoryInfo.lock1 = splitValues(value);
                break;
              case 'pc_lock2':
                categoryInfo.lock2 = splitValues(value);
                break;
              case 'pc_lock3':
                categoryInfo.lock3 = splitValues(value);
                break;
              case 'pc_lock4':
                categoryInfo.lock4 = splitValues(value);
                break;
              case 'pc_lock5':
                categoryInfo.lock5 = splitValues(value);
                break;
              case 'pc_rare':
                categoryInfo.rare = splitValues(value);
                break;
              case 'pc_parent':
                categoryInfo.disallowedParent = splitValues(value);
                break;
              case 'pc_message':
                categoryInfo.messagae = value;
                break;
              case 'ps_valet':
              case 'ps_drivethru':
              case 'ps_wifi':
              case 'ps_restrooms':
              case 'ps_cc':
              case 'ps_reservations':
              case 'ps_outside':
              case 'ps_ac':
              case 'ps_parking':
              case 'ps_deliveries':
              case 'ps_takeaway':
              case 'ps_wheelchair':
                if (value) {
                  categoryInfo.services.push({ wmeId: wmeServiceIds[iCol], pnhKey: PNH_TO_BANNER_SERVICE_KEY_MAP[pnhServiceKeys[iCol]] || pnhServiceKeys[iCol] });
                }
                break;
              case '':
                // ignore blank column
                break;
              default:
                throw new Error(`WMEPH: Unexpected category data from PNH sheet: ${headerValue}`);
            }
          });

          // Debug output for specific categories
          if (categoryInfo.name === 'RIVER_STREAM' || categoryInfo.name === 'ISLAND') {
            log(`Parsed ${categoryInfo.name}:`, {
              id: categoryInfo.id,
              name: categoryInfo.name,
              parent: categoryInfo.parent,
              point: categoryInfo.point,
              area: categoryInfo.area,
              regPoint: categoryInfo.regPoint,
              regArea: categoryInfo.regArea,
              services: categoryInfo.services
            });
          }

          categoryInfos.add(categoryInfo);
        }
      });
    }
  }
  // Abstract flag classes.  Must be declared outside the "Flag" namespace.
  class FlagBase {
    static defaultSeverity = SEVERITY.GREEN;
    static defaultMessage = '';
    static currentFlags;
    #severity;
    #message;
    #noLock;
    /** @type {HarmonizationArgs} */
    args;

    get name() {
      return this.constructor.name;
    }

    get severity() {
      return this.#severity ?? this.constructor.defaultSeverity;
    }
    set severity(value) {
      this.#severity = value;
    }

    get message() {
      return this.#message ?? this.constructor.defaultMessage;
    }
    set message(value) {
      this.#message = value;
    }

    get noLock() {
      return this.#noLock ?? this.severity > SEVERITY.BLUE;
    }
    set noLock(value) {
      this.#noLock = value;
    }

    constructor() {
      FlagBase.currentFlags.add(this);
    }

    /**
     *
     * @param {HarmonizationArgs} args
     * @returns
     */
    static eval(args) {
      if (this.venueIsFlaggable(args) && !FlagBase.currentFlags.hasFlag(this)) {
        const flag = new this(args);
        flag.args = args;
        return flag;
      }
      return null;
    }
  }
  class ActionFlag extends FlagBase {
    static defaultButtonTooltip = '';
    #buttonText;
    #buttonTooltip;

    get buttonText() {
      return this.#buttonText ?? this.constructor.defaultButtonText;
    }
    set buttonText(value) {
      this.#buttonText = value;
    }
    get buttonTooltip() {
      return this.#buttonTooltip ?? this.constructor.defaultButtonTooltip;
    }
    set buttonTooltip(value) {
      this.#buttonTooltip = value;
    }
  }
  class WLFlag extends FlagBase {
    static defaultWLTooltip = 'Whitelist this message';
    #showWL;

    get severity() {
      return this.constructor.isWhitelisted(this.args) ? SEVERITY.GREEN : super.severity;
    }
    set severity(value) {
      super.severity = value;
    }

    get showWL() {
      return this.#showWL ?? !this.constructor.isWhitelisted(this.args);
    }
    set showWL(value) {
      this.#showWL = value;
    }

    get wlTooltip() {
      return this.constructor.defaultWLTooltip;
    }

    WLaction() {
      const venue = getSelectedVenue();
      if (whitelistAction(venue.id, this.constructor.WL_KEY)) {
        harmonizePlaceGo(venue, 'harmonize');
      }
    }

    /**
     *
     * @param {HarmonizationArgs} args
     * @returns
     */
    static isWhitelisted(args) {
      return !!args.wl[this.WL_KEY];
    }
  }
  class WLActionFlag extends WLFlag {
    static defaultButtonTooltip = '';
    #buttonText;
    #buttonTooltip;

    get buttonText() {
      return this.#buttonText ?? this.constructor.defaultButtonText;
    }
    set buttonText(value) {
      this.#buttonText = value;
    }

    get buttonTooltip() {
      return this.#buttonTooltip ?? this.constructor.defaultButtonTooltip;
    }
    set buttonTooltip(value) {
      this.#buttonTooltip = value;
    }
  }
  /** Namespace to keep flags grouped. */
  const Flag = {
    // 2020-10-5 Disabling HN validity checks for now. See note on HnNonStandard flag for details.
    // HnDashRemoved: class extends FlagBase {
    //     constructor() { super(SEVERITY.GREEN, 'Dash removed from house number. Verify'); }
    // },
    ChainIsClosed: class extends WLFlag {
      static defaultSeverity = SEVERITY.ORANGE;
      static WL_KEY = 'chainIsClosed';

      /**
       *
       * @param {HarmonizationArgs} args
       * @returns
       */
      static venueIsFlaggable(args) {
        return args.chainIsClosed;
      }

      get message() {
        const pnhName = this.args.pnhMatch.name;
        return (
          `Place matched to PNH entry "${pnhName}", which is no longer in business.<br/><br/>` +
          'Follow the <a target="_blank" href="https://www.waze.com/wiki/USA/Places#Closed">wiki instructions</a> for closed places.'
        );
      }
    },
    FullAddressInference: class extends FlagBase {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Missing address was inferred from nearby segments. Verify the address and run WMEPH again.';

      constructor(inferredAddress) {
        super();
        this.inferredAddress = inferredAddress;
      }

      static eval(args) {
        let result = null;

        // Detect if user intentionally set city to "None"
        // When city is set to "None", WME assigns it placeholder id=999909
        const cityIsIntentionallyEmpty = args.addr.city?.id === 999909;

        // Only attempt address inference if the venue should have street/city information
        // StreetMissing and CityMissing already have all the logic to determine this
        const shouldHaveStreet = Flag.StreetMissing.venueIsFlaggable(args);
        const shouldHaveCity = Flag.CityMissing.venueIsFlaggable(args);
        const shouldInferAddress = shouldHaveStreet || shouldHaveCity;

        const hasMissingAddress = !args.addr.state || !args.addr.country || !args.addr.street?.name || !args.addr.city?.name;

        if (!args.highlightOnly) {
          if (hasMissingAddress && shouldInferAddress) {
            // Only infer if venue type should have address info (not a natural feature, bridge, etc.)
            if (cityIsIntentionallyEmpty) {
              // Don't auto-infer; user made an intentional choice
              result = null;
            } else {
              if (sdk.Map.getZoomLevel() < 4) {
                if ($('#WMEPH-EnableIAZoom').prop('checked')) {
                  const coords = getVenueLonLat(args.venue);
                  sdk.Map.setMapCenter({ lonLat: coords, zoomLevel: 5 });
                } else {
                  WazeWrap.Alerts.error(
                    SCRIPT_NAME,
                    'No address and the state cannot be determined. Please zoom in and rerun the script. ' + 'You can enable autozoom for this type of case in the options.',
                  );
                }
                result = { exit: true }; // Don't bother returning a Flag. This will exit the rest of the harmonizePlaceGo function.
              } else {
                let inferredAddress = inferAddress(args.venue, 7); // Pull address info from nearby segments

                if (inferredAddress?.street?.id && inferredAddress?.state && inferredAddress?.country) {
                  if ($('#WMEPH-AddAddresses').prop('checked')) {
                    // update the venue's address if option is enabled
                    updateAddress(args.venue, inferredAddress, args.actions);
                    UPDATED_FIELDS.address.updated = true;
                    result = new this(inferredAddress);
                  } else if (!['JUNCTION_INTERCHANGE'].includes(args.categories[0]) && !FlagBase.currentFlags.hasFlag(Flag.CityMissing)) {
                    new Flag.CityMissing(args);
                  }
                } else {
                  //  if the inference doesn't work...
                  WazeWrap.Alerts.error(SCRIPT_NAME, 'This place has no address data and the address cannot be inferred from nearby segments. Please edit the address and run WMEPH again.');
                  result = { exit: true }; // Don't bother returning a Flag. This will exit the rest of the harmonizePlaceGo function.
                }
              }
            }
          }
        } else if (hasMissingAddress && shouldInferAddress) {
          // only highlighting (and only if venue should have address info)
          result = { exit: true };
          if (args.venue.adLocked) {
            result.severity = 'adLock';
          } else {
            const cat = args.venue.categories;
            if (containsAny(cat, ['HOSPITAL_MEDICAL_CARE', 'HOSPITAL_URGENT_CARE', 'GAS_STATION'])) {
              result.severity = SEVERITY.PINK;
            } else if (cat.includes('JUNCTION_INTERCHANGE')) {
              result.severity = SEVERITY.GREEN;
            } else {
              result.severity = SEVERITY.RED;
            }
          }
        }
        return result;
      }
    },
    NameMissing: class extends FlagBase {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Name is missing.';

      static venueIsFlaggable(args) {
        const EXCLUDED_FIRST_CATEGORIES = ['ISLAND', 'FOREST_GROVE', 'SEA_LAKE_POOL', 'RIVER_STREAM', 'CANAL', 'PARKING_LOT', 'NATURAL_FEATURES', 'SWAMP_MARSH'];
        const hasAlphanumericName = args.nameBase?.replace(/[^A-Za-z0-9]/g, '').length > 0;
        const isResidential = args.categories.includes('RESIDENCE_HOME') || args.categories.includes('RESIDENTIAL');
        const isBrandedGasStation = args.categories.includes('GAS_STATION') && args.brand;

        return !isResidential && !hasAlphanumericName && !EXCLUDED_FIRST_CATEGORIES.includes(args.categories[0]) && !isBrandedGasStation;
      }
    },
    GasNameMissing: class extends ActionFlag {
      static defaultSeverity = SEVERITY.RED;
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Use gas brand as station name';

      get message() {
        return `Name is missing. Use "${this.args.brand}"?`;
      }

      static venueIsFlaggable(args) {
        return args.categories.includes('GAS_STATION') && isNullOrWhitespace(args.nameBase) && !isNullOrWhitespace(args.brand);
      }

      action() {
        addUpdateAction(this.args.venue, { name: this.args.brand }, null, true);
      }
    },
    ClearThisUrl: class extends FlagBase {
      static defaultSeverity = SEVERITY.YELLOW;

      // Use this to highlight yellow any venues that have an invalid value and will be
      // auto-corrected when WMEPH is run.
      static venueIsFlaggable(args) {
        return args.categories.includes('CHARGING_STATION') && args.url && ['https://www.nissan-europe.com/', 'https://www.eco-movement.com/'].includes(args.url);
      }
    },
    ClearThisPhone: class extends FlagBase {
      static defaultSeverity = SEVERITY.YELLOW;

      // Use this to highlight yellow any venues that have an invalid value and will be
      // auto-corrected when WMEPH is run.
      static venueIsFlaggable(args) {
        return args.categories.includes('CHARGING_STATION') && args.phone === '+33-1-72676914'; // Nissan Europe ph#
      }
    },
    PlaIsPublic: class extends FlagBase {
      static get defaultMessage() {
        // Add the buttons to the message.
        let msg =
          'If this does not meet the requirements for a <a href="https://wazeopedia.waze.com/wiki/USA/Places/Parking_lot#Lot_Type" ' +
          'target="_blank" style="color:5a5a73">public parking lot</a>, change to:<br>';
        msg += [
          ['RESTRICTED', 'Restricted'],
          ['PRIVATE', 'Private'],
        ]
          .map((btnInfo) => $('<button>', { class: 'wmeph-pla-lot-type-btn btn btn-default btn-xs wmeph-btn', 'data-lot-type': btnInfo[0] }).text(btnInfo[1]).prop('outerHTML'))
          .join('');
        return msg;
      }

      static venueIsFlaggable(args) {
        if (!args.categories.includes('PARKING_LOT')) return false;
        try {
          const parkingType = sdk.DataModel.Venues.ParkingLot.getParkingLotType({ venueId: args.venue.id });
          return parkingType === 'PUBLIC';
        } catch {
          return false;
        }
      }

      postProcess() {
        $('.wmeph-pla-lot-type-btn').click((evt) => {
          const lotType = $(evt.currentTarget).data('lot-type');
          const categoryAttrClone = this.args.venue.categoryAttributes ? JSON.parse(JSON.stringify(this.args.venue.categoryAttributes)) : {};
          categoryAttrClone.PARKING_LOT = categoryAttrClone.PARKING_LOT ?? {};
          categoryAttrClone.PARKING_LOT.parkingType = lotType;
          UPDATED_FIELDS.lotType.updated = true;
          addUpdateAction(this.args.venue, { categoryAttributes: categoryAttrClone }, null, true);
        });
      }
    },
    PlaNameMissing: class extends FlagBase {
      static defaultSeverity = SEVERITY.BLUE;
      static get defaultMessage() {
        return `Name is missing. ${USER.rank < 3 ? 'Request an R3+ lock' : 'Lock to 3+'} to confirm unnamed parking lot.`;
      }
      noLock = true;

      static venueIsFlaggable(args) {
        return args.categories.includes('PARKING_LOT') && !args.nameBase?.replace(/[^A-Za-z0-9]/g, '').length && args.venue.lockRank < 2;
      }
    },
    PlaNameNonStandard: class extends WLFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Parking lot names typically contain words like "Parking", "Lot", and/or "Garage"';
      static WL_KEY = 'plaNameNonStandard';
      static defaultWLTooltip = 'Whitelist non-standard PLA name';

      static venueIsFlaggable(args) {
        if (!this.isWhitelisted(args) && isVenueParkingLot(args.venue)) {
          const name = args.venue.name;
          if (name) {
            const addr = getVenueAddress(args.venue);
            const state = addr?.state?.name;
            const re = state === 'Quebec' ? /\b(parking|stationnement)\b/i : /\b((park[ -](and|&|'?n'?)[ -]ride)|parking|lot|garage|ramp)\b/i;
            if (!re.test(name)) {
              return true;
            }
          }
        }
        return false;
      }
    },
    IndianaLiquorStoreHours: class extends WLFlag {
      static defaultMessage = 'If this is a liquor store, check the hours. As of Feb 2018, liquor stores in Indiana are allowed ' + 'to be open between noon and 8 pm on Sunday.';

      static WL_KEY = 'indianaLiquorStoreHours';
      static defaultWLTooltip = 'Whitelist Indiana liquor store hours';

      static venueIsFlaggable(args) {
        return (
          !args.highlightOnly &&
          !this.isWhitelisted(args) &&
          !args.categories.includes('RESIDENTIAL') &&
          args.addr?.state.name === 'Indiana' &&
          /\b(beers?|wines?|liquors?|spirits)\b/i.test(args.nameBase) &&
          !args.openingHours.some((entry) => entry.days.includes(0))
        );
      }
    },
    HoursOverlap: class extends FlagBase {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Overlapping hours of operation. Place might not save.';

      static venueIsFlaggable(args) {
        return args.hoursOverlap;
      }
    },
    UnmappedRegion: class extends WLFlag {
      static WL_KEY = 'unmappedRegion';
      static defaultWLTooltip = 'Whitelist unmapped category';
      static #regionsToFlagOther = ['HI', 'NER', 'NOR', 'NWR', 'PLN', 'ATR'];

      get noLock() {
        return Flag.UnmappedRegion.#getRareCategoryInfos(this.args).some(
          (categoryInfo) =>
            (categoryInfo.id === 'OTHER' && Flag.UnmappedRegion.#regionsToFlagOther.includes(this.args.regionCode) && !this.args.isLocked) || !Flag.UnmappedRegion.isWhitelisted(this.args),
        );
      }

      constructor(args) {
        let showWL = true;
        let severity = SEVERITY.GREEN;
        // let noLock = false;
        let message;
        const categoryNames = [];
        let addOtherMessage = false;

        Flag.UnmappedRegion.#getRareCategoryInfos(args).forEach((categoryInfo) => {
          if (categoryInfo.id === 'OTHER') {
            if (Flag.UnmappedRegion.#regionsToFlagOther.includes(args.region) && !args.isLocked) {
              addOtherMessage = true;
              severity = Math.max(severity, SEVERITY.BLUE);
              showWL = false;
              // noLock = true;
            }
          } else {
            if (Flag.UnmappedRegion.isWhitelisted(args)) {
              showWL = false;
              severity = Math.max(severity, SEVERITY.GREEN);
            } else {
              severity = SEVERITY.YELLOW;
              // noLock = true;
            }
            if (!args.highlightOnly) categoryNames.push(categoryInfo.name);
          }
        });
        if (!args.highlightOnly) {
          const messages = [];
          if (categoryNames.length === 1) {
            messages.push(`The <b>${categoryNames[0]}</b> category is usually not mapped in this region.`);
          } else if (categoryNames.length > 1) {
            messages.push(`These categories are usually not mapped in this region: ${categoryNames.map((name) => `<b>${name}</b>`).join(', ')}`);
          }
          if (addOtherMessage) {
            messages.push('The <b>Other</b> category should only be used if no other category applies. ' + 'Manually lock the place to override this flag.');
          }
          message = messages.join('<br><br>');
        }
        super();
        this.message = message;
        this.severity = severity;
        // this.noLock = noLock;
        this.showWL = showWL;
      }

      static venueIsFlaggable(args) {
        return !args.categories.includes('REST_AREAS') && !!this.#getRareCategoryInfos(args).length;
      }

      static #getRareCategoryInfos(args) {
        return args.categories
          .filter((cat) => cat !== 'RESIDENTIAL') // SDK has RESIDENTIAL as separate type, not a subcategory
          .map((cat) => args.pnhCategoryInfos.getById(cat))
          .filter((pnhCategoryInfo) => pnhCategoryInfo) // Filter out undefined category infos
          .filter((pnhCategoryInfo) => {
            const rareLocalities = pnhCategoryInfo.rare;
            if (rareLocalities.includes(args.state2L) || rareLocalities.includes(args.region) || rareLocalities.includes(args.countryCode)) {
              if (pnhCategoryInfo.id === 'OTHER' && this.#regionsToFlagOther.includes(args.region)) {
                if (!args.isLocked) {
                  return true;
                }
              } else {
                return true;
              }
            }
            return false;
          });
      }
    },
    RestAreaName: class extends WLFlag {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Rest area name is out of spec. Use the Rest Area wiki button below to view formats.';
      static WL_KEY = 'restAreaName';
      static defaultWLTooltip = 'Whitelist rest area name';

      static venueIsFlaggable(args) {
        return args.countryCode === PNH_DATA.USA.countryCode && args.categories.includes('REST_AREAS') && !/^Rest Area.* - /.test(args.nameBase + (args.nameSuffix ?? ''));
      }
    },
    RestAreaNoTransportation: class extends ActionFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Rest areas should not use the Transportation category.';
      static defaultButtonText = 'Remove it?';

      static venueIsFlaggable(args) {
        return args.categories.includes('REST_AREAS') && args.categories.includes('TRANSPORTATION');
      }

      action() {
        const categories = this.args.venue.categories.slice(); // create a copy
        const index = categories.indexOf('TRANSPORTATION');
        if (index > -1) {
          categories.splice(index, 1); // remove the category
          addUpdateAction(this.args.venue, { categories }, null, true);
        } else {
          harmonizePlaceGo(this.args.venue, 'harmonize');
        }
      }
    },
    RestAreaGas: class extends FlagBase {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Gas stations at Rest Areas should be separate area places.';

      static venueIsFlaggable(args) {
        return args.categories.includes('REST_AREAS') && args.categories.includes('GAS_STATION');
      }
    },
    RestAreaScenic: class extends WLActionFlag {
      static WL_KEY = 'restAreaScenic';
      static defaultWLTooltip = 'Whitelist place';
      static defaultMessage = 'Verify that the "Scenic Overlook" category is appropriate for this rest area. If not: ';
      static defaultButtonText = 'Remove it';
      static defaultButtonTooltip = 'Remove "Scenic Overlook" category.';

      static venueIsFlaggable(args) {
        return !this.isWhitelisted(args) && args.categories.includes('REST_AREAS') && args.categories.includes('SCENIC_LOOKOUT_VIEWPOINT');
      }

      action() {
        const categories = this.args.venue.categories.slice(); // create a copy
        const index = categories.indexOf('SCENIC_LOOKOUT_VIEWPOINT');
        if (index > -1) {
          categories.splice(index, 1); // remove the category
          addUpdateAction(this.args.venue, { categories }, null, true);
        }
      }
    },
    RestAreaSpec: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.RED;
      static WL_KEY = 'restAreaSpec';
      static defaultWLTooltip = 'Whitelist place';
      static defaultMessage = 'Is this a rest area?';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Update with proper categories and services.';

      static venueIsFlaggable(args) {
        return !this.isWhitelisted(args) && !args.categories.includes('REST_AREAS') && /rest (?:area|stop)|service plaza/i.test(args.nameBase);
      }

      action() {
        const categories = insertAtIndex(this.args.venue.categories, 'REST_AREAS', 0);
        // make it 24/7
        const openingHours = [new OpeningHour({ days: [1, 2, 3, 4, 5, 6, 0], fromHour: '00:00', toHour: '00:00' })];
        addUpdateAction(this.args.venue, { categories, openingHours }, null, true);
      }
    },
    EVChargingStationWarning: class extends FlagBase {
      static defaultMessage =
        'Please do not delete EV Charging Stations. Be sure you are completely up to date with the latest guidelines in ' +
        '<a href="https://wazeopedia.waze.com/wiki/USA/Places/EV_charging_station" target="_blank">wazeopedia</a>.';

      static venueIsFlaggable(args) {
        return !args.highlightOnly && args.categories.includes('CHARGING_STATION');
      }
    },
    EVCSAltNameMissing: class extends ActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = 'Public and restricted EV charging stations should have an alternate name of "EV Charging Station"';
      static defaultButtonText = 'Add it';
      static defaultButtonTooltip = 'Add EVCS alternate name';

      static venueIsFlaggable(args) {
        if (!args.categories.includes('CHARGING_STATION')) return false;
        if (args.aliases.some((alias) => alias.toLowerCase() === 'ev charging station')) return false;
        if (args.venue.name.toLowerCase().includes('(private)')) return false;
        try {
          const accessType = sdk.DataModel.Venues.ChargingStation.getChargersAccessType({ venueId: args.venue.id });
          return accessType !== 'PRIVATE';
        } catch {
          return false;
        }
      }

      action() {
        let aliases = this.args.venue.aliases.slice();
        aliases = insertAtIndex(aliases, 'EV Charging Station', 0);
        addUpdateAction(this.args.venue, { aliases }, null);
      }
    },
    EVCSPriceMissing: class extends FlagBase {
      static defaultSeverity = SEVERITY.BLUE;
      static get defaultMessage() {
        let msg = 'EVCS price: ';
        [
          ['FREE', 'Free', 'Free'],
          ['FEE', 'Paid', 'Paid'],
        ].forEach((btnInfo) => {
          msg += $('<button>', {
            id: `wmeph_${btnInfo[0]}`,
            class: 'wmeph-evcs-cost-type-btn btn btn-default btn-xs wmeph-btn',
            title: btnInfo[2],
          })
            .text(btnInfo[1])
            .css({
              padding: '3px',
              height: '20px',
              lineHeight: '0px',
              marginRight: '2px',
              marginBottom: '1px',
              minWidth: '18px',
            })
            .prop('outerHTML');
        });
        return msg;
      }

      constructor() {
        super();
        this.noLock = true;
      }

      static venueIsFlaggable(args) {
        if (!args.categories.includes('CHARGING_STATION')) return false;
        try {
          const costType = sdk.DataModel.Venues.ChargingStation.getCostType({ venueId: args.venue.id });
          return !costType || costType === 'COST_TYPE_UNSPECIFIED';
        } catch {
          return false;
        }
      }

      postProcess() {
        $('.wmeph-evcs-cost-type-btn').click((evt) => {
          const selectedValue = $(evt.currentTarget).attr('id').replace('wmeph_', '');
          let attrClone;
          if (this.args.venue.categoryAttributes) {
            attrClone = JSON.parse(JSON.stringify(this.args.venue.categoryAttributes));
          } else {
            attrClone = {};
          }
          attrClone.CHARGING_STATION ??= {};
          attrClone.CHARGING_STATION.costType = selectedValue;
          addUpdateAction(this.args.venue, { categoryAttributes: attrClone }, null, true);
          UPDATED_FIELDS.evCostType.updated = true;
        });
      }
    },
    GasMismatch: class extends WLFlag {
      static defaultSeverity = SEVERITY.RED;
      static WL_KEY = 'gasMismatch';
      static defaultWLTooltip = 'Whitelist gas brand / name mismatch';
      static defaultMessage = '<a href="https://wazeopedia.waze.com/wiki/USA/Places/Gas_station#Name" target="_blank" class="red">' + 'Gas brand should typically be included in the place name.</a>';

      static venueIsFlaggable(args) {
        // For gas stations, check to make sure brand exists somewhere in the place name.
        // Remove non - alphanumeric characters first, for more relaxed matching.
        if (args.categories[0] === 'GAS_STATION' && args.brand) {
          const compressedName = (args.nameBase + args.nameSuffix ?? '').toUpperCase().replace(/[^a-zA-Z0-9]/g, '');
          // Some brands may have more than one acceptable name, or the brand listed in WME doesn't match what we want to see in the name.
          // Ideally, this would be addressed in the PNH spreadsheet somehow, but for now hardcoding is the only option.
          const compressedBrands = [args.brand.toUpperCase().replace(/[^a-zA-Z0-9]/g, '')];
          if (args.brand === 'Diamond Gasoline') {
            compressedBrands.push('DIAMONDOIL');
          } else if (args.brand === 'Murphy USA') {
            compressedBrands.push('MURPHY');
          } else if (args.brand === 'Mercury Fuel') {
            compressedBrands.push('MERCURY', 'MERCURYPRICECUTTER');
          } else if (args.brand === 'Carrollfuel') {
            compressedBrands.push('CARROLLMOTORFUEL', 'CARROLLMOTORFUELS');
          }
          if (!compressedBrands.some((compressedBrand) => compressedName.includes(compressedBrand))) {
            return true;
          }
        }
        return false;
      }
    },
    GasUnbranded: class extends FlagBase {
      //  Unbranded is not used per wiki
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = '"Unbranded" should not be used for the station brand. Change to the correct brand or delete the brand.';

      static venueIsFlaggable(args) {
        return args.categories.includes('GAS_STATION') && args.brand === 'Unbranded';
      }
    },
    GasMkPrim: class extends ActionFlag {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Gas Station should be the primary category';
      static defaultButtonText = 'Fix';
      static defaultButtonTooltip = 'Make the Gas Station category the primary category.';

      static venueIsFlaggable(args) {
        return args.categories.indexOf('GAS_STATION') > 0;
      }

      action() {
        // Move Gas category to the first position
        const categories = insertAtIndex(this.args.venue.categories, GAS_STATION, 0);
        addUpdateAction(this.args.venue, { categories }, null, true);
      }
    },
    IsThisAPilotTravelCenter: class extends ActionFlag {
      static defaultMessage = 'Is this a "Travel Center"?';
      static defaultButtonText = 'Yes';

      static venueIsFlaggable(args) {
        return !args.highlightOnly && args.state2L === 'TN' && args.nameBase.toLowerCase().trim() === 'pilot food mart';
      }

      action() {
        addUpdateAction(this.args.venue, { name: 'Pilot Travel Center' }, null, true);
      }
    },
    HotelMkPrim: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Hotel category is not first';
      static defaultButtonText = 'Fix';
      static defaultButtonTooltip = 'Make the Hotel category the primary category.';
      static WL_KEY = 'hotelMkPrim';
      static defaultWLTooltip = 'Whitelist hotel as secondary category';

      static venueIsFlaggable(args) {
        return args.priPNHPlaceCat === 'HOTEL' && args.categories.indexOf('HOTEL') !== 0;
      }

      action() {
        // Insert/move Hotel category in the first position
        const categories = insertAtIndex(this.args.venue.categories.slice(), 'HOTEL', 0);
        addUpdateAction(this.args.venue, { categories }, null, true);
      }
    },
    ChangeToPetVet: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Key words suggest this should be a Pet/Veterinarian category. Change?';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Change to Pet/Veterinarian Category';
      static WL_KEY = 'changeHMC2PetVet';
      static defaultWLTooltip = 'Whitelist Pet/Vet category';

      static venueIsFlaggable(args) {
        if (!this.isWhitelisted(args)) {
          const testName = name.toLowerCase().replace(/[^a-z]/g, ' ');
          const testNameWords = testName.split(' ');
          if (
            (args.categories.includes('HOSPITAL_URGENT_CARE') || args.categories.includes('DOCTOR_CLINIC')) &&
            (containsAny(testNameWords, Pnh.ANIMAL_FULL_MATCH) || Pnh.ANIMAL_PART_MATCH.some((match) => testName.includes(match)))
          ) {
            return true;
          }
        }
        return false;
      }

      action() {
        let updated = false;
        let categories = uniq(this.args.venue.categories.slice());
        categories.forEach((cat, idx) => {
          if (cat === 'HOSPITAL_URGENT_CARE' || cat === 'DOCTOR_CLINIC') {
            categories[idx] = 'PET_STORE_VETERINARIAN_SERVICES';
            updated = true;
          }
        });
        if (updated) {
          categories = uniq(categories);
        }
        addUpdateAction(this.args.venue, { categories }, null, true);
      }
    },
    NotASchool: class extends WLFlag {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Key words suggest this should not be School category.';
      static WL_KEY = 'changeSchool2Offices';
      static defaultWLTooltip = 'Whitelist School category';

      static venueIsFlaggable(args) {
        if (!this.isWhitelisted(args)) {
          const testName = args.nameBase.toLowerCase().replace(/[^a-z]/g, ' ');
          const testNameWords = testName.split(' ');

          if (args.categories.includes('SCHOOL') && (containsAny(testNameWords, Pnh.SCHOOL_FULL_MATCH) || Pnh.SCHOOL_PART_MATCH.some((match) => testName.includes(match)))) {
            return true;
          }
        }
        return false;
      }
    },
    PointNotArea: class extends WLActionFlag {
      static defaultButtonText = 'Change to point';
      static defaultButtonTooltip = 'Change to Point Place';
      static WL_KEY = 'pointNotArea';
      static defaultWLTooltip = 'Whitelist point (not area)';

      get message() {
        if (this.args.maxAreaSeverity === SEVERITY.RED) {
          return 'This category should be a point place.';
        }
        return 'This category is usually a point place, but can be an area in some cases. Verify if area is appropriate.';
      }

      constructor(args) {
        let severity;
        let showWL = true;

        const makeGreen = Flag.PointNotArea.isWhitelisted(args) || args.venue.lockRank >= args.defaultLockLevel;

        if (makeGreen) {
          showWL = false;
          severity = SEVERITY.GREEN;
        } else {
          severity = args.maxAreaSeverity;
        }

        super();
        this.showWL = showWL;
        this.severity = severity;
      }

      static venueIsFlaggable(args) {
        // Check if any category is configured in PNH data as an area
        const hasCategoryConfiguredAsArea = args.categories.some(cat =>
          isCategoryConfiguredAsArea(cat, args.pnhCategoryInfos, args.regionCode)
        );
        if (hasCategoryConfiguredAsArea) {
          return false;
        }
        return !isVenuePoint(args.venue) && (args.categories.includes('RESIDENCE_HOME') || (args.maxAreaSeverity > SEVERITY.BLUE && !args.categories.includes('REST_AREAS')));
      }

      action() {
        const { venue } = this.args;
        if (isVenueResidential(venue)) {
          // Residential areas cannot be converted to points
          return;
        }

        // Convert area polygon to point at polygon centroid
        const coords = venue.geometry.coordinates[0]; // First ring of polygon
        const lon = coords.reduce((sum, [x]) => sum + x, 0) / coords.length;
        const lat = coords.reduce((sum, [, y]) => sum + y, 0) / coords.length;

        const point = {
          type: 'Point',
          coordinates: [lon, lat],
        };

        sdk.DataModel.Venues.updateVenue({ venueId: venue.id, geometry: point });
        harmonizePlaceGo(venue, 'harmonize');
      }
    },
    AreaNotPoint: class extends WLActionFlag {
      static defaultButtonText = 'Change to area';
      static defaultButtonTooltip = 'Change to Area Place';
      static WL_KEY = 'areaNotPoint';
      static defaultWLTooltip = 'Whitelist area (not point)';
      static #collegeAbbrRegExps;

      get message() {
        if (this.args.maxPointSeverity === SEVERITY.RED) {
          return 'This category should be an area place.';
        }
        return 'This category is usually an area place, but can be a point in some cases. Verify if point is appropriate.';
      }

      constructor(args) {
        let severity;
        let showWL = true;

        const makeGreen =
          Flag.AreaNotPoint.isWhitelisted(args) || args.venue.lockRank >= args.defaultLockLevel || (args.maxPointSeverity === SEVERITY.BLUE && Flag.AreaNotPoint.#hasCollegeInName(args.nameBase));

        if (makeGreen) {
          showWL = false;
          severity = SEVERITY.GREEN;
        } else {
          severity = args.maxPointSeverity;
        }

        super();
        this.severity = severity;
        this.showWL = showWL;
      }

      static venueIsFlaggable(args) {
        // If category is configured as area in PNH data, flag point venues
        const hasCategoryConfiguredAsArea = args.categories.some(cat =>
          isCategoryConfiguredAsArea(cat, args.pnhCategoryInfos, args.regionCode)
        );
        if (hasCategoryConfiguredAsArea) {
          return isVenuePoint(args.venue);
        }
        return isVenuePoint(args.venue) && (args.maxPointSeverity > SEVERITY.GREEN || args.categories.includes('REST_AREAS'));
      }

      static #hasCollegeInName(name) {
        if (!this.#collegeAbbrRegExps) {
          this.#collegeAbbrRegExps = COLLEGE_ABBREVIATIONS.map((abbr) => new RegExp(`\\b${abbr}\\b`, 'g'));
        }
        return this.#collegeAbbrRegExps.some((re) => re.test(name));
      }

      action() {
        const { venue } = this.args;
        if (!isVenuePoint(venue)) return; // Already an area

        // Convert point to small square polygon (like WME does)
        const [lon, lat] = venue.geometry.coordinates;
        const offset = 0.0001; // ~10 meters
        const square = {
          type: 'Polygon',
          coordinates: [
            [
              [lon - offset, lat - offset],
              [lon + offset, lat - offset],
              [lon + offset, lat + offset],
              [lon - offset, lat + offset],
              [lon - offset, lat - offset], // Close polygon
            ],
          ],
        };

        // Update venue geometry (SDK tracks as unsaved; user commits via WME Save button)
        sdk.DataModel.Venues.updateVenue({ venueId: venue.id, geometry: square });
        harmonizePlaceGo(venue, 'harmonize');
      }
    },
    HnMissing: class extends WLActionFlag {
      static defaultButtonText = 'Add';
      static defaultButtonTooltip = 'Add HN to place';
      static WL_KEY = 'HNWL';
      static defaultWLTooltip = 'Whitelist empty HN';

      static #TEXTBOX_ID = 'WMEPH-HNAdd';
      noBannerAssemble = true;

      get message() {
        let msg = `No HN: <input type="text" id="${Flag.HnMissing.#TEXTBOX_ID}" autocomplete="off" ` + 'style="font-size:0.85em;width:100px;padding-left:2px;color:#000;" > ';

        if (this.args.categories.includes('PARKING_LOT') && this.args.venue.lockRank < 2) {
          if (USER.rank < 3) {
            msg += 'Request an R3+ lock to confirm no HN.';
          } else {
            msg += 'Lock to R3+ to confirm no HN.';
          }
        }
        return msg;
      }

      constructor(args) {
        let showWL = true;
        let severity = SEVERITY.RED;
        let noLock = false;
        if (args.state2L === 'PR' || args.categories[0] === 'SCENIC_LOOKOUT_VIEWPOINT') {
          severity = SEVERITY.GREEN;
          showWL = false;
        } else if (args.categories.includes('PARKING_LOT')) {
          showWL = false;
          if (args.venue.lockRank < 2) {
            noLock = true;
            severity = SEVERITY.BLUE;
          } else {
            severity = SEVERITY.GREEN;
          }
        } else if (Flag.HnMissing.isWhitelisted(args)) {
          severity = SEVERITY.GREEN;
          showWL = false;
        } else {
          noLock = true;
        }

        super();
        this.severity = severity;
        this.showWL = showWL;
        this.noLock = noLock;
      }

      static venueIsFlaggable(args) {
        const CATEGORIES_TO_IGNORE = ['BRIDGE', 'ISLAND', 'FOREST_GROVE', 'SEA_LAKE_POOL', 'RIVER_STREAM', 'CANAL', 'DAM', 'TUNNEL', 'JUNCTION_INTERCHANGE', 'SWAMP_MARSH', 'NATURAL_FEATURES'];
        const hasNoHN = !args.currentHN?.replace(/\D/g, '');
        const isRestArea = args.categories.includes('REST_AREAS');
        return args.hasStreet && args.hasCity && hasNoHN && !CATEGORIES_TO_IGNORE.includes(args.categories[0]) && !isRestArea;
      }

      static #getTextbox() {
        return $(`#${Flag.HnMissing.#TEXTBOX_ID}`);
      }

      action() {
        const newHN = $('#WMEPH-HNAdd').val().replace(/\s+/g, '');
        logDev(newHN);
        const hnTemp = newHN.replace(/[^\d]/g, '');
        const hnTempDash = newHN.replace(/[^\d-]/g, '');
        if (hnTemp > 0 && hnTemp < 1000000) {
          sdk.DataModel.Venues.updateAddress({
            venueId: this.args.venue.id,
            houseNumber: hnTempDash,
          });
          harmonizePlaceGo(this.args.venue, 'harmonize', []); // Rerun the script to update fields and lock
          UPDATED_FIELDS.address.updated = true;
        } else {
          Flag.HnMissing.#getTextbox().css({ backgroundColor: '#FDD' }).attr('title', 'Must be a number between 0 and 1000000');
        }
      }

      postProcess() {
        // If pressing enter in the HN entry box, add the HN
        const textbox = Flag.HnMissing.#getTextbox();
        textbox.keyup((evt) => {
          if (evt.keyCode === 13 && textbox.val()) {
            this.action();
          }
        });
      }
    },
    HnTooManyDigits: class extends WLFlag {
      static defaultMessage = 'HN contains more than 6 digits. Please verify.';
      static defaultSeverity = SEVERITY.YELLOW;
      static WL_KEY = 'hnTooManyDigits';
      static defaultWLTooltip = 'Whitelist long HN';

      static venueIsFlaggable(args) {
        return !this.isWhitelisted(args) && args.currentHN?.replace(/[^0-9]/g, '').length > 6;
      }
    },
    // 2020-10-5 HN's with letters have been allowed since last year.  Currently, RPPs can be saved with a number
    // followed by up to 4 letters but it's not clear if the app actually searches if only 1, 2, or more letters
    // are present.  Other places can have a more flexible HN (up to 15 characters long, total. A single space between
    // the # and letters. Etc)

    /*
        ARCHIVED: House Number validation logic (v2020-10-5)
        This code was disabled due to complexity and unclear requirements.
        If re-enabling HN validation, review the following:
        - HnNonStandard class logic for determining valid HN patterns
        - State-specific HN formats (NY Queens format, HI format, etc.)
        - Integration with whitelist system (_wl.hnNonStandard)
        - Verify compatibility with current SDK approach to address harmonization

        See git history for full implementation details.
        */
    HNRange: class extends WLFlag {
      static defaultMessage = 'House number seems out of range for the street name. Verify.';
      static defaultSeverity = SEVERITY.YELLOW;
      static WL_KEY = 'HNRange';
      static defaultWLTooltip = 'Whitelist HN range';

      static venueIsFlaggable(args) {
        if (!this.isWhitelisted(args) && _dupeHNRangeList.length > 3) {
          let dhnix;
          const dupeHNRangeListSorted = [];
          sortWithIndex(_dupeHNRangeDistList);
          for (dhnix = 0; dhnix < _dupeHNRangeList.length; dhnix++) {
            dupeHNRangeListSorted.push(_dupeHNRangeList[_dupeHNRangeDistList.sortIndices[dhnix]]);
          }
          // Calculate HN/distance ratio with other venues
          // var sumHNRatio = 0;
          const arrayHNRatio = [];
          for (dhnix = 0; dhnix < dupeHNRangeListSorted.length; dhnix++) {
            arrayHNRatio.push(Math.abs((parseInt(args.currentHN, 10) - dupeHNRangeListSorted[dhnix]) / _dupeHNRangeDistList[dhnix]));
          }
          sortWithIndex(arrayHNRatio);
          // Examine either the median or the 8th index if length is >16
          const arrayHNRatioCheckIX = Math.min(Math.round(arrayHNRatio.length / 2), 8);
          if (arrayHNRatio[arrayHNRatioCheckIX] > 1.4) {
            // show stats if HN out of range
            logDev(`HNs: ${dupeHNRangeListSorted}`);
            logDev(`Distances: ${_dupeHNRangeDistList}`);
            logDev(`arrayHNRatio: ${arrayHNRatio}`);
            logDev(`HN Ratio Score: ${arrayHNRatio[Math.round(arrayHNRatio.length / 2)]}`);
            return true;
          }
        }
        return false;
      }
    },
    StreetMissing: class extends ActionFlag {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'No street:';
      static defaultButtonText = 'Edit address';
      static defaultButtonTooltip = 'Edit address to add street.';

      constructor(args) {
        super();
        if (args.categories[0] === 'SCENIC_LOOKOUT_VIEWPOINT') {
          this.severity = SEVERITY.BLUE;
        }
      }

      static venueIsFlaggable(args) {
        const CATEGORIES_TO_IGNORE = ['BRIDGE', 'ISLAND', 'FOREST_GROVE', 'SEA_LAKE_POOL', 'RIVER_STREAM', 'CANAL', 'DAM', 'TUNNEL', 'JUNCTION_INTERCHANGE', 'NATURAL_FEATURES'];
        return (
          args.addr.city &&
          (!args.addr.street || args.addr.street.isEmpty) &&
          !CATEGORIES_TO_IGNORE.includes(args.categories[0]) &&
          !args.categories.includes('REST_AREAS')
        );
      }

      // eslint-disable-next-line class-methods-use-this
      action() {
        clickGeneralTab();
        $('.venue .full-address').click();
        setTimeout(() => {
          if ($('.empty-street').prop('checked')) {
            $('.empty-street').click();
          }
          setTimeout(() => {
            const streetAutocomplete = document.querySelector('#venue-edit-general > div:nth-child(1) > div > div > wz-card > form > div:nth-child(2) > div > wz-autocomplete');
            const input = streetAutocomplete?.shadowRoot?.querySelector('wz-text-input')?.shadowRoot?.querySelector('input');
            input?.focus();
          }, 100);
        }, 100);
      }
    },
    CityMissing: class extends ActionFlag {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'No city:';
      static defaultButtonText = 'Edit address';
      static defaultButtonTooltip = 'Edit address to add city.';

      constructor(args) {
        super();
        if (args.categories.includes('RESIDENCE_HOME') && args.highlightOnly) {
          this.severity = SEVERITY.BLUE;
        }
      }

      static venueIsFlaggable(args) {
        const CATEGORIES_TO_IGNORE = ['BRIDGE', 'ISLAND', 'FOREST_GROVE', 'SEA_LAKE_POOL', 'RIVER_STREAM', 'CANAL', 'DAM', 'TUNNEL', 'JUNCTION_INTERCHANGE', 'NATURAL_FEATURES'];
        return (
          (!args.addr.city || args.addr.city.isEmpty) &&
          !CATEGORIES_TO_IGNORE.includes(args.categories[0]) &&
          !args.categories.includes('REST_AREAS')
        );
      }

      // eslint-disable-next-line class-methods-use-this
      action() {
        clickGeneralTab();
        $('.venue .full-address').click();
        setTimeout(() => {
          if ($('.empty-city').prop('checked')) {
            $('.empty-city').click();
          }
          setTimeout(() => {
            const cityAutocomplete = document.querySelector('#venue-edit-general > div:nth-child(1) > div > div > wz-card > form > div:nth-child(4) > wz-autocomplete');
            const input = cityAutocomplete?.shadowRoot?.querySelector('wz-text-input')?.shadowRoot?.querySelector('input');
            input?.focus();
          }, 100);
        }, 100);
      }
    },
    BankType1: class extends FlagBase {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Clarify the type of bank: the name has ATM but the primary category is Offices';

      static venueIsFlaggable(args) {
        return (
          (!args.pnhNameRegMatch || (args.pnhNameRegMatch && args.priPNHPlaceCat === 'BANK_FINANCIAL' && !args.pnhMatch.notABank)) && args.categories[0] === 'OFFICES' && /\batm\b/i.test(name)
        );
      }
    },
    // TODO: Fix if the name has "(ATM)" or " - ATM" or similar. This flag is not currently catching those.
    BankBranch: class extends ActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = 'Is this a bank branch office? ';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Is this a bank branch?';

      static venueIsFlaggable(args) {
        let flaggable = false;
        if (!args.priPNHPlaceCat || (args.priPNHPlaceCat === 'BANK_FINANCIAL' && !args.pnhMatch.notABank)) {
          const ixBank = args.categories.indexOf('BANK_FINANCIAL');
          const ixATM = args.categories.indexOf('ATM');
          const ixOffices = args.categories.indexOf('OFFICES');

          if (/\batm\b/gi.test(args.nameBase)) {
            flaggable = ixOffices === 0 || (ixBank === -1 && ixATM === -1) || (ixATM === 0 && ixBank > 0) || ixBank > -1;
          } else if (ixBank > -1 || ixATM > -1) {
            flaggable = ixOffices === 0 || (ixATM === 0 && ixBank === -1) || (ixBank > 0 && ixATM > 0);
          } else if (args.priPNHPlaceCat) {
            flaggable = ixBank === -1 && !(/\bcorporate offices\b/i.test(args.nameSuffix) && ixOffices === 0);
          }
        }
        return flaggable;
      }

      action() {
        const newAttributes = {};

        const originalCategories = this.args.venue.categories;
        const newCategories = insertAtIndex(originalCategories, ['BANK_FINANCIAL', 'ATM'], 0); // Change to bank and atm cats
        if (!arraysAreEqual(originalCategories, newCategories)) {
          newAttributes.categories = newCategories;
        }

        // strip ATM from name if present
        const originalName = this.args.venue.name;
        const newName = originalName
          .replace(/[- (]*ATM[- )]*/gi, ' ')
          .replace(/^ /g, '')
          .replace(/ $/g, '');
        if (originalName !== newName) {
          newAttributes.name = newName;
        }

        addUpdateAction(this.args.venue, newAttributes, null, true);
      }
    },
    StandaloneATM: class extends ActionFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Or is this a standalone ATM? ';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Is this a standalone ATM with no bank branch?';

      static venueIsFlaggable(args) {
        let flaggable = false;
        if (!args.priPNHPlaceCat || (args.priPNHPlaceCat === 'BANK_FINANCIAL' && !args.pnhMatch.notABank)) {
          const ixBank = args.categories.indexOf('BANK_FINANCIAL');
          const ixATM = args.categories.indexOf('ATM');
          const ixOffices = args.categories.indexOf('OFFICES');

          if (/\batm\b/gi.test(args.nameBase)) {
            flaggable = ixOffices === 0 || (ixBank === -1 && ixATM === -1) || ixBank > -1;
          } else if (ixBank > -1 || ixATM > -1) {
            flaggable = ixOffices === 0 || (ixATM === 0 && ixBank === -1) || (ixBank > 0 && ixATM > 0);
          } else {
            flaggable = args.priPNHPlaceCat && !(/\bcorporate offices\b/i.test(args.nameSuffix) && ixOffices === 0);
          }
        }
        return flaggable;
      }

      action() {
        const newAttributes = {};

        const originalName = this.args.venue.name;
        if (!/\bATM\b/i.test(originalName)) {
          newAttributes.name = `${originalName} ATM`;
        }

        const atmCategory = ['ATM'];
        if (!arraysAreEqual(this.args.venue.categories, atmCategory)) {
          newAttributes.categories = atmCategory; // Change to ATM only
        }

        addUpdateAction(this.args.venue, newAttributes, null, true);
      }
    },
    BankCorporate: class extends ActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = "Or is this the bank's corporate offices?";
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = "Is this the bank's corporate offices?";

      static venueIsFlaggable(args) {
        let flaggable = false;
        if (!args.priPNHPlaceCat) {
          flaggable = /\batm\b/gi.test(args.nameBase) && args.categories.indexOf('OFFICES') === 0;
        } else if (args.priPNHPlaceCat === 'BANK_FINANCIAL' && !args.pnhMatch.notABank) {
          flaggable = !containsAny(args.categories, ['BANK_FINANCIAL', 'ATM']) && !/\bcorporate offices\b/i.test(args.nameSuffix);
        }
        return flaggable;
      }

      action() {
        const newAttributes = {};

        const officesCategory = ['OFFICES'];
        if (!arraysAreEqual(this.args.venue.categories, officesCategory)) {
          newAttributes.categories = officesCategory;
        }

        // strip ATM from name if present
        const originalName = this.args.venue.name;
        let newName = originalName
          .replace(/[- (]*atm[- )]*/gi, ' ')
          .replace(/^ /g, '')
          .replace(/ $/g, '')
          .replace(/ {2,}/g, ' ')
          .replace(/\s*-\s*corporate\s*offices\s*$/i, '');
        const suffix = ' - Corporate Offices';
        if (!newName.endsWith(suffix)) newName += suffix;
        if (originalName !== newName) {
          newAttributes.name = newName;
        }

        addUpdateAction(this.args.venue, newAttributes, null, true);
      }
    },
    CatPostOffice: class extends FlagBase {
      static defaultMessage = `The Post Office category is reserved for certain USPS locations. Please be sure to follow <a href="${
        URLS.uspsWiki
      }" style="color:#3a3a3a;" target="_blank">the guidelines</a>.`;

      static venueIsFlaggable(args) {
        return !args.highlightOnly && args.isUspsPostOffice;
      }
    },
    IgnEdited: class extends FlagBase {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Last edited by an IGN editor';

      static venueIsFlaggable(args) {
        return !args.categories.includes('RESIDENCE_HOME') && args.venue.modificationData.updatedBy && /^ign_/i.test(args.venue.modificationData.updatedBy);
      }
    },
    WazeBot: class extends ActionFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Edited last by an automated process. Please verify information is correct.';
      static defaultButtonText = 'Nudge';
      static defaultButtonTooltip = 'If no other properties need to be updated, click to nudge the place (force an edit).';
      static #botNames = [/^waze-maint/i, /^waze3rdparty$/i, /^WazeParking1$/i, /^admin$/i, /^avsus$/i];

      static venueIsFlaggable(args) {
        const isUnchanged = !args.venue.isNew && !args.venue.modificationData.updatedBy;
        let flaggable = isUnchanged && !args.categories.includes('RESIDENCE_HOME');
        if (flaggable) {
          const lastUpdatedByName = args.venue.modificationData.updatedBy ?? args.venue.modificationData.createdBy;
          flaggable = this.#botNames.some((botName) => botName.test(lastUpdatedByName));
        }
        return flaggable;
      }

      action() {
        nudgeVenue(this.args.venue);
        harmonizePlaceGo(this.args.venue, 'harmonize');
      }
    },
    ParentCategory: class extends WLFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static WL_KEY = 'parentCategory';
      static defaultWLTooltip = 'Whitelist parent Category';

      get message() {
        let msg;
        const badCatInfos = this.args.categories
          .filter((category) => Flag.ParentCategory.categoryIsDisallowedParent(category, this.args))
          .map((category) => this.args.pnhCategoryInfos.getById(category));
        if (badCatInfos.length === 1) {
          msg = `The <b>${badCatInfos[0].name}</b> parent category is usually not mapped in this region.`;
        } else {
          msg = 'These parent categories are usually not mapped in this region: ';
          msg += badCatInfos.map((catInfo) => `<b>${catInfo.name}</b>`).join(', ');
        }
        return msg;
      }

      static categoryIsDisallowedParent(category, args) {
        const pnhCategoryInfo = args.pnhCategoryInfos.getById(category);
        const localities = pnhCategoryInfo.disallowedParent;
        return localities.includes(args.state2L) || localities.includes(args.region) || localities.includes(args.countryCode);
      }

      static venueIsFlaggable(args) {
        return args.categories.some((category) => this.categoryIsDisallowedParent(category, args));
      }
    },
    CheckDescription: class extends FlagBase {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Description field already contained info; PNH description was added in front of existing. Check for inconsistency or duplicate info.';

      static venueIsFlaggable(args) {
        return args.descriptionInserted;
      }
    },
    Overlapping: class extends FlagBase {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Place points are stacked up.';
    },
    SuspectDesc: class extends WLFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Description field might contain copyrighted info.';
      static WL_KEY = 'suspectDesc';
      static defaultWLTooltip = 'Whitelist description';

      static venueIsFlaggable(args) {
        // args.description is only available in WMEPH mode (not during scanning)
        // Only check for copyrighted content if description is accessible
        return !isVenueResidential(args.venue) && args.totalSeverity < SEVERITY.RED && !this.isWhitelisted(args) && args.description && /(google|yelp)/i.test(args.description);
      }
    },
    ResiTypeName: class extends WLFlag {
      static defaultMessage = 'The place name suggests a residential place or personalized place of work.  Please verify.';
      static WL_KEY = 'resiTypeName';
      static defaultWLTooltip = 'Whitelist Residential-type name';

      constructor(likelyResidential) {
        super();
        if (likelyResidential) this.severity = SEVERITY.YELLOW;
      }

      // TODO: make this a public method and pass the result to args so args can be passed into vanueIsFlaggable
      static #likelyResidentialName(alphaName) {
        return /^((my|mi|moms|dads)?\s*(home|work|office|casa|house))|(mom|dad)$/i.test(alphaName);
      }

      static #possiblyResidentialName(alphaName, categories) {
        return /('?s|my)\s+(house|home|work)/i.test(alphaName) && !containsAny(categories, ['RESTAURANT', 'DESSERT', 'BAR']);
      }

      static #isPreflaggable(args) {
        return !args.categories.includes('RESIDENCE_HOME') && !args.pnhNameRegMatch && !this.isWhitelisted(args) && args.totalSeverity < SEVERITY.RED;
      }

      // TODO
      static #venueIsFlaggable(preflaggable, likelyResidential, alphaName, categories) {
        return preflaggable && (likelyResidential || this.#possiblyResidentialName(alphaName, categories));
      }

      static eval(args) {
        const preflaggable = this.#isPreflaggable(args);
        if (preflaggable) {
          const alphaName = name.replace(/[^A-Z ]/i, ''); // remove non-alpha characters
          const likelyResidential = this.#likelyResidentialName(alphaName);
          if (this.#venueIsFlaggable(preflaggable, likelyResidential, alphaName, args.categories)) return new this(likelyResidential);
        }
        return null;
      }
    },
    Mismatch247: class extends FlagBase {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Hours of operation listed as open 24hrs but not for all 7 days.';

      static venueIsFlaggable(args) {
        return (
          args.openingHours.length === 1 &&
          args.openingHours[0].days.length < 7 &&
          /^0?0:00$/.test(args.openingHours[0].fromHour) &&
          (/^0?0:00$/.test(args.openingHours[0].toHour) || args.openingHours[0].toHour === '23:59')
        );
      }
    },
    PhoneInvalid: class extends FlagBase {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Phone # is invalid.';

      static venueIsFlaggable(args) {
        if (!args.phone) return false;
        const normalizedPhone = normalizePhone(args.phone, args.outputPhoneFormat);
        return (args.highlightOnly && normalizedPhone !== args.phone) || (!args.highlightOnly && normalizedPhone === BAD_PHONE);
      }
    },
    UrlMismatch: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = "Existing URL doesn't match the suggested PNH URL. Use the Website button below to verify the existing URL is valid. If not:";
      static defaultButtonText = 'Use PNH URL';
      static defaultButtonTooltip = 'Change URL to the PNH standard';
      static WL_KEY = 'longURL';
      static defaultWLTooltip = 'Whitelist existing URL';

      static venueIsFlaggable(args) {
        // for cases where there is an existing URL in the WME place, and there is a PNH url on queue:
        return !isNullOrWhitespace(args.url) && !isNullOrWhitespace(args.pnhUrl) && args.url !== args.pnhUrl && args.pnhUrl !== BAD_URL;
      }

      action() {
        if (!isNullOrWhitespace(this.args.pnhUrl)) {
          addUpdateAction(this.args.venue, { url: this.args.pnhUrl }, null, true);
        } else {
          WazeWrap.Alerts.error(SCRIPT_NAME, 'URL Matching Error!');
        }
      }
    },
    UrlAnalytics: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'URL contains analytics queries. Strip them?';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Strip analytics queries from the URL';
      static WL_KEY = 'urlAnalytics';
      static defaultWLTooltip = 'Whitelist existing URL';
      static URL_ANALYTICS_REGEX = /(?<=&|\?)(utm_|y_|(wtextnd)?source=|cmpid=|cid=|otppartnerid=|campaignid=|ref=|cmp=).*?(&|$)/gi;

      static venueIsFlaggable(args) {
        return !isNullOrWhitespace(args.url) && args.url !== args.pnhUrl && Flag.UrlAnalytics.URL_ANALYTICS_REGEX.test(args.url);
      }

      action() {
        const url = Flag.UrlAnalytics.#stripUrlAnalyticsQueries(this.args.url);
        addUpdateAction(this.args.venue, { url }, null, true);
      }

      static #stripUrlAnalyticsQueries(url) {
        // utm_* queries are generally used by Google.
        // y_* queries are used by yext.
        url = url.replace(Flag.UrlAnalytics.URL_ANALYTICS_REGEX, '');

        // Strip the ending ? if all queries were removed.
        url = url.replace(/\?$/, '');

        return url;
      }
    },
    GasNoBrand: class extends FlagBase {
      static defaultSeverity = SEVERITY.BLUE;

      get message() {
        return `Lock to L${this.args.levelToLock + 1}+ to verify no gas brand.`;
      }

      constructor() {
        super();
        this.noLock = true;
      }

      static venueIsFlaggable(args) {
        // If gas station is missing brand, don't flag if place is locked as high as user can lock it.
        return args.categories.includes('GAS_STATION') && !args.brand && args.venue.lockRank < args.levelToLock;
      }
    },
    SubFuel: class extends WLFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = 'Make sure this place is for the gas station itself and not the main store building. Otherwise undo and check the categories.';
      static WL_KEY = 'subFuel';
      static defaultWLTooltip = 'Whitelist no gas brand';

      static venueIsFlaggable(args) {
        return !this.isWhitelisted(args) && args.pnhMatch.subFuel && !/\bgas(oline)?\b/i.test(args.venue.name) && !/\bfuel\b/i.test(args.venue.name);
      }
    },
    AddCommonEVPaymentMethods: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultButtonText = 'Add network payment methods';
      static defaultButtonTooltip = 'Please verify first! If any are not needed, click the WL button and manually add any needed payment methods.';
      static WL_KEY = 'addCommonEVPaymentMethods';
      static defaultWLTooltip = 'Whitelist common EV payment types';

      get message() {
        const network = sdk.DataModel.Venues.ChargingStation.getNetwork({ venueId: this.args.venue.id });
        let msg = `These common payment methods for the ${network} network are missing. Verify if they are needed here:`;
        this.originalNetwork = network;
        const translations = I18n.translations[I18n.locale].edit.venue.category_attributes.payment_methods;
        const paymentMethods = sdk.DataModel.Venues.ChargingStation.getPaymentMethods({ venueId: this.args.venue.id });
        const list = COMMON_EV_PAYMENT_METHODS[network]
          .filter((method) => !paymentMethods?.includes(method))
          .map((method) => `- ${translations[method]}`)
          .join('<br>');
        msg += `<br>${list}<br>`;
        return msg;
      }

      static venueIsFlaggable(args) {
        if (args.categories.includes('CHARGING_STATION') && !this.isWhitelisted(args)) {
          try {
            const network = sdk.DataModel.Venues.ChargingStation.getNetwork({ venueId: args.venue.id });
            if (!network || !COMMON_EV_PAYMENT_METHODS[network]) return false;
            const paymentMethods = sdk.DataModel.Venues.ChargingStation.getPaymentMethods({ venueId: args.venue.id });
            return !!COMMON_EV_PAYMENT_METHODS[network]?.some((method) => !paymentMethods?.includes(method));
          } catch (e) {
            logDev(`AddCommonEVPaymentMethods.venueIsFlaggable error: ${e.message}`);
            return false;
          }
        }
        return false;
      }

      action() {
        if (!isVenueChargingStation(this.args.venue)) {
          WazeWrap.Alerts.info(SCRIPT_NAME, 'This is no longer a charging station. Please run WMEPH again.', false, false);
          return;
        }

        try {
          const network = sdk.DataModel.Venues.ChargingStation.getNetwork({ venueId: this.args.venue.id });
          if (network !== this.originalNetwork) {
            WazeWrap.Alerts.info(SCRIPT_NAME, 'EV charging station network has changed. Please run WMEPH again.', false, false);
            return;
          }

          const currentPaymentMethods = sdk.DataModel.Venues.ChargingStation.getPaymentMethods({ venueId: this.args.venue.id }) ?? [];
          const newPaymentMethods = currentPaymentMethods.slice();
          const commonPaymentMethods = COMMON_EV_PAYMENT_METHODS[network];
          commonPaymentMethods.forEach((method) => {
            if (!newPaymentMethods.includes(method)) newPaymentMethods.push(method);
          });

          const categoryAttributes = this.args.venue.categoryAttributes || {};
          const categoryAttrClone = JSON.parse(JSON.stringify(categoryAttributes));
          categoryAttrClone.CHARGING_STATION ??= {};
          categoryAttrClone.CHARGING_STATION.paymentMethods = newPaymentMethods;

          UPDATED_FIELDS.evPaymentMethods.updated = true;
          addUpdateAction(this.args.venue, { categoryAttributes: categoryAttrClone }, null, true);
        } catch (e) {
          logDev(`AddCommonEVPaymentMethods.action error: ${e.message}`);
          WazeWrap.Alerts.error(SCRIPT_NAME, 'Error updating payment methods', false, false);
        }
      }
    },
    RemoveUncommonEVPaymentMethods: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultButtonText = 'Remove network payment methods';
      static defaultButtonTooltip = 'Please verify first! If any should NOT be removed, click the WL button and manually remove any unneeded payment methods.';
      static WL_KEY = 'removeUncommonEVPaymentMethods';
      static defaultWLTooltip = 'Whitelist uncommon EV payment types';

      #network;
      #paymentMethods;

      get message() {
        try {
          this.#network = sdk.DataModel.Venues.ChargingStation.getNetwork({ venueId: this.args.venue.id });
          this.originalNetwork = this.#network;
          let msg = `These payment methods are uncommon for the ${this.#network} network. Verify if they are needed here:`;
          this.#paymentMethods = sdk.DataModel.Venues.ChargingStation.getPaymentMethods({ venueId: this.args.venue.id }) ?? [];
          const translations = I18n.translations[I18n.locale].edit.venue.category_attributes.payment_methods;
          const list = this.#paymentMethods
            ?.filter((method) => !COMMON_EV_PAYMENT_METHODS[this.#network]?.includes(method))
            .map((method) => `- ${translations[method]}`)
            .join('<br>');
          msg += `<br>${list}<br>`;
          return msg;
        } catch (e) {
          logDev(`RemoveUncommonEVPaymentMethods.message error: ${e.message}`);
          return 'Error retrieving payment method information';
        }
      }

      static venueIsFlaggable(args) {
        if (args.categories.includes('CHARGING_STATION') && !this.isWhitelisted(args)) {
          try {
            const network = sdk.DataModel.Venues.ChargingStation.getNetwork({ venueId: args.venue.id });
            if (!network || !COMMON_EV_PAYMENT_METHODS.hasOwnProperty(network)) return false;
            const paymentMethods = sdk.DataModel.Venues.ChargingStation.getPaymentMethods({ venueId: args.venue.id });
            return !!paymentMethods?.some((method) => !COMMON_EV_PAYMENT_METHODS[network]?.includes(method));
          } catch (e) {
            logDev(`RemoveUncommonEVPaymentMethods.venueIsFlaggable error: ${e.message}`);
            return false;
          }
        }
        return false;
      }

      action() {
        if (!isVenueChargingStation(this.args.venue)) {
          WazeWrap.Alerts.info(SCRIPT_NAME, 'This is no longer a charging station. Please run WMEPH again.', false, false);
          return;
        }

        try {
          // Verify network hasn't changed, but use cached payment methods from message getter
          const currentNetwork = sdk.DataModel.Venues.ChargingStation.getNetwork({ venueId: this.args.venue.id });
          if (currentNetwork !== this.originalNetwork) {
            WazeWrap.Alerts.info(SCRIPT_NAME, 'EV charging station network has changed. Please run WMEPH again.', false, false);
            return;
          }

          const commonPaymentMethods = COMMON_EV_PAYMENT_METHODS[currentNetwork];
          const newPaymentMethods = this.#paymentMethods.slice().filter((method) => commonPaymentMethods?.includes(method));

          const categoryAttributes = this.args.venue.categoryAttributes || {};
          const categoryAttrClone = JSON.parse(JSON.stringify(categoryAttributes));
          categoryAttrClone.CHARGING_STATION ??= {};
          categoryAttrClone.CHARGING_STATION.paymentMethods = newPaymentMethods;

          UPDATED_FIELDS.evPaymentMethods.updated = true;
          addUpdateAction(this.args.venue, { categoryAttributes: categoryAttrClone }, null, true);
        } catch (e) {
          logDev(`RemoveUncommonEVPaymentMethods.action error: ${e.message}`);
          WazeWrap.Alerts.error(SCRIPT_NAME, 'Error updating payment methods', false, false);
        }
      }
    },
    FormatUSPS: class extends FlagBase {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = `Name the post office according to this region's <a href="${URLS.uspsWiki}" style="color:#3232e6" target="_blank">standards for USPS post offices</a>`;

      static venueIsFlaggable(args) {
        return args.isUspsPostOffice && !this.isNameOk(this.getCleanNameParts(args.nameBase, args.nameSuffix).join(''), args.state2L, args.addr);
      }

      static getCleanNameParts(name, nameSuffix) {
        name = name.trimLeft().replace(/ {2,}/, ' ');
        if (nameSuffix) {
          nameSuffix = nameSuffix
            .trimRight()
            .replace(/\bvpo\b/i, 'VPO')
            .replace(/\bcpu\b/i, 'CPU')
            .replace(/ {2,}/, ' ');
        }
        return [name, nameSuffix || ''];
      }

      static isNameOk(name, state2L, addr) {
        return this.#getPostOfficeRegEx(state2L, addr).test(name);
      }

      static #getPostOfficeRegEx(state2L, addr) {
        return state2L === 'KY' || (state2L === 'NY' && ['Queens', 'Bronx', 'Manhattan', 'Brooklyn', 'Staten Island'].includes(addr.city?.name))
          ? /^post office \d{5}( [-–](?: cpu| vpo)?(?: [a-z0-9]+){1,})?$/i
          : /^post office [-–](?: cpu| vpo)?(?: [a-z0-9]+){1,}$/i;
      }
    },
    MissingUSPSAlt: class extends ActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = 'USPS post offices must have an alternate name of "USPS".';
      static defaultButtonText = 'Add it';
      static defaultButtonTooltip = 'Add USPS alternate name';

      static venueIsFlaggable(args) {
        return args.isUspsPostOffice && !args.aliases.some((alias) => alias.toUpperCase() === 'USPS');
      }

      action() {
        const aliases = this.args.venue.aliases.slice();
        if (!aliases.some((alias) => alias === 'USPS')) {
          aliases.push('USPS');
          addUpdateAction(this.args.venue, { aliases }, null, true);
        } else {
          harmonizePlaceGo(this.args.venue, 'harmonize');
        }
      }
    },
    MissingUSPSZipAlt: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = `No <a href="${URLS.uspsWiki}" style="color:#3232e6;" target="_blank">ZIP code alt name</a>: <input type="text" \
id="WMEPH-zipAltNameAdd"autocomplete="off" style="font-size:0.85em;width:65px;padding-left:2px;color:#000;" title="Enter the ZIP code and click Add">`;

      static defaultButtonText = 'Add';
      static WL_KEY = 'missingUSPSZipAlt';
      static defaultWLTooltip = 'Whitelist missing USPS zip alt name';
      static #TEXTBOX_ID = 'WMEPH-zipAltNameAdd';
      noBannerAssemble = true;

      static venueIsFlaggable(args) {
        return args.isUspsPostOffice && !args.aliases.some((alias) => /\d{5}/.test(alias));
      }

      action() {
        const $input = $(`input#${Flag.MissingUSPSZipAlt.#TEXTBOX_ID}`);
        const zip = $input.val().trim();
        if (zip) {
          if (/^\d{5}/.test(zip)) {
            const aliases = [].concat(this.args.venue.aliases);
            // Make sure zip hasn't already been added.
            if (!aliases.includes(zip)) {
              aliases.push(zip);
              addUpdateAction(this.args.venue, { aliases }, null, true);
            } else {
              $input.css({ backgroundColor: '#FDD' }).attr('title', 'Zip code alt name already exists');
            }
          } else {
            $input.css({ backgroundColor: '#FDD' }).attr('title', 'Zip code format error');
          }
        }
      }

      postProcess() {
        // If pressing enter in the USPS zip code alt entry box...
        const $textbox = $(`#${Flag.MissingUSPSZipAlt.#TEXTBOX_ID}`);
        $textbox.keyup((evt) => {
          if (evt.keyCode === 13 && $(evt.currentTarget).val() !== '') {
            $('#WMEPH_MissingUSPSZipAlt').click();
          }
        });

        // Prefill zip code text box
        const zipMatch = (this.args.nameBase + (this.args.nameSuffix ?? '')).match(/\d{5}/);
        if (zipMatch) {
          $textbox.val(zipMatch[0]);
        }
      }
    },
    MissingUSPSDescription: class extends WLFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = `The first line of the description for a <a href="${
        URLS.uspsWiki
      }" style="color:#3232e6" target="_blank">USPS post office</a> must be CITY, STATE(2-letter) ZIP, e.g. "Lexington, KY 40511"`;

      static WL_KEY = 'missingUSPSDescription';
      static defaultWLTooltip = 'Whitelist missing USPS address line in description';

      static venueIsFlaggable(args) {
        // Only validate description during harmonization, not during scanning.
        // Description is not available in the SDK Venue object, so we read it from the DOM during harmonization.
        // During scanning (highlightOnly=true), the edit form is not open so the description is inaccessible.
        if (args.isUspsPostOffice && !args.highlightOnly) {
          const lines = args.description?.split('\n');
          return !lines?.length || !/^.{2,}, [A-Z]{2}\s{1,2}\d{5}$/.test(lines[0]);
        }
        return false;
      }
    },
    CatHotel: class extends FlagBase {
      constructor(args) {
        const pnhName = args.pnhMatch.name;
        super(`Check hotel website for any name localization (e.g. ${pnhName} - Tampa Airport).`);
      }

      static venueIsFlaggable(args) {
        return args.priPNHPlaceCat === 'HOTEL' && (args.nameBase + (args.nameSuffix || '')).toUpperCase() === args.pnhMatch.name.toUpperCase();
      }
    },
    LocalizedName: class extends WLFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static WL_KEY = 'localizedName';
      static defaultWLTooltip = 'Whitelist localization';

      get message() {
        return this.args.pnhMatch.displaynote || 'Place needs localization information';
      }

      static venueIsFlaggable(args) {
        if (args.pnhMatch.localizationRegEx) {
          const testName = args.nameBase + (args.nameSuffix || '');
          // Reset lastIndex for regex with global flag (known JS bug: .test() with /g alternates results)
          args.pnhMatch.localizationRegEx.lastIndex = 0;
          const matches = args.pnhMatch.localizationRegEx.test(testName);
          return !matches;
        }
        return false;
      }
    },
    SpecCaseMessage: class extends FlagBase {
      static #teslaSC = /tesla supercharger/i;
      static #teslaDC = /tesla destination charger/i;
      static #rivianAN = /<b>rivian adventure network<\/b> charger/i;
      static #rivianW = /<b>rivian waypoints<\/b> charger/i;

      constructor(args) {
        let message = args.pnhMatch.displaynote;

        // 3/23/2023 - This is a temporary solution to add a disambiguator for Tesla & Rivian chargers.
        let isRivian = false;
        const isTesla = Flag.SpecCaseMessage.#teslaSC.test(message) && Flag.SpecCaseMessage.#teslaDC.test(message);
        if (isTesla) {
          message = message.replace(Flag.SpecCaseMessage.#teslaSC, '<button id="wmeph-tesla-supercharger" class="btn wmeph-btn">Tesla SuperCharger</button>');
          message = message.replace(Flag.SpecCaseMessage.#teslaDC, '<button id="wmeph-tesla-destination-charger" class="btn wmeph-btn">Tesla Destination Charger</button>');
        } else {
          isRivian = Flag.SpecCaseMessage.#rivianAN.test(message) && Flag.SpecCaseMessage.#rivianW.test(message);
          if (isRivian) {
            message = message.replace(Flag.SpecCaseMessage.#rivianAN, '<button id="wmeph-rivian-adventure-network" class="btn wmeph-btn">Rivian Adventure Network charger</button>');
            message = message.replace(Flag.SpecCaseMessage.#rivianW, '<button id="wmeph-rivian-waypoints" class="btn wmeph-btn">Rivian Waypoints charger</button>');
          }
        }

        super();
        this.message = message;

        if (isTesla) {
          this.postProcess = () => {
            $('#wmeph-tesla-supercharger').click(() => {
              addUpdateAction(args.venue, { name: 'Tesla Supercharger' }, null, true);
            });
            $('#wmeph-tesla-destination-charger').click(() => {
              addUpdateAction(args.venue, { name: 'Tesla Destination Charger' }, null, true);
            });
          };
          this.severity = SEVERITY.RED;
        } else if (isRivian) {
          this.postProcess = () => {
            $('#wmeph-rivian-adventure-network').click(() => {
              addUpdateAction(args.venue, { name: 'Rivian Adventure Network' }, null, true);
            });
            $('#wmeph-rivian-waypoints').click(() => {
              addUpdateAction(args.venue, { name: 'Rivian Waypoints' }, null, true);
            });
          };
          this.severity = SEVERITY.RED;
        }
      }

      static venueIsFlaggable(args) {
        // TODO: Are the pharmhours and drivethruhours checks really needed?
        // They hide the displaynote message if the key words exist in the
        // venue description, but it could be argued it's ok if the message
        // shows up regardless.
        const message = args.pnhMatch.displaynote;
        let showFlag = false;
        if (args.showDispNote && !isNullOrWhitespace(message)) {
          if (args.pnhMatch.pharmhours) {
            // args.description is only available in WMEPH mode (not during scanning)
            // If not available, assume we should show the flag to be safe
            showFlag = !args.description || !/\bpharmacy\b\s*\bh(ou)?rs\b/i.test(args.description);
            // TODO: figure out what drivethruhours was supposed to be in PNH speccase column
          } else if (args.pnhMatch.drivethruhours) {
            // args.description is only available in WMEPH mode (not during scanning)
            // If not available, assume we should show the flag to be safe
            showFlag = !args.description || !/\bdrive[\s-]?(thru|through)\b\s*\bh(ou)?rs\b/i.test(args.description);
          } else {
            showFlag = true;
          }
        }
        return showFlag;
      }
    },
    PnhCatMess: class extends ActionFlag {
      constructor(venue, pnhCategoryInfo, categories) {
        super();
        this.message = pnhCategoryInfo.message;
        if (categories.includes('HOSPITAL_URGENT_CARE')) {
          this.buttonText = 'Change to Doctor/Clinic';
          this.actionType = 'changeToDoctorClinic';
        }
        this.venue = venue;
      }

      static #venueIsFlaggable(highlightOnly, pnhCategoryInfo) {
        return !highlightOnly && !isNullOrWhitespace(pnhCategoryInfo.message);
      }

      static eval(venue, pnhCategoryInfo, categories, highlightOnly) {
        return this.#venueIsFlaggable(highlightOnly, pnhCategoryInfo) ? new this(venue, pnhCategoryInfo, categories) : null;
      }

      action() {
        if (this.actionType === 'changeToDoctorClinic') {
          const categories = uniq(this.venue.categories.slice());
          const indexOfHospital = categories.indexOf('HOSPITAL_URGENT_CARE');
          if (indexOfHospital > -1) {
            categories[indexOfHospital] = 'DOCTOR_CLINIC';
            addUpdateAction(this.venue, { categories }, null, true);
          }
        }
      }
    },
    ExtProviderMissing: class extends ActionFlag {
      static defaultButtonTooltip = 'If no other properties need to be updated, click to nudge the place (force an edit).';
      static #categoriesToIgnore = ['BRIDGE', 'TUNNEL', 'JUNCTION_INTERCHANGE', 'NATURAL_FEATURES', 'ISLAND', 'SEA_LAKE_POOL', 'RIVER_STREAM', 'CANAL', 'SWAMP_MARSH'];

      get message() {
        let msg = 'No Google link';
        msg += this.makeRed() ? ' and place has not been edited for over 6 months. Edit a property (or nudge) and save to reset the 6 month timer: ' : ': ';
        return msg;
      }

      get severity() {
        return this.makeRed() ? SEVERITY.RED : super.severity;
      }
      set severity(value) {
        super.severity = value;
      }

      get buttonText() {
        return this.makeRed() ? 'Nudge' : '';
      }

      set buttonText(value) {
        super.buttonText = value;
      }

      constructor() {
        super();
        this.value2 = 'Add';
        this.title2 = 'Add a link to a Google place';
      }

      makeRed() {
        const { venue } = this.args;
        if (this.args.isLocked) {
          let lastUpdated;
          if (venue.isNew) {
            lastUpdated = Date.now();
          } else if (venue.updatedOn) {
            lastUpdated = venue.updatedOn;
          } else {
            lastUpdated = venue.createdOn;
          }
          const weeksSinceLastUpdate = (Date.now() - lastUpdated) / 604800000;
          if (weeksSinceLastUpdate >= 26 && !venue.isUpdated() && (!this.args.actions || this.args.actions.length === 0)) {
            return true;
          }
        }
        return false;
      }

      static venueIsFlaggable(args) {
        if (USER.rank >= 2 && args.venue.externalProviderIds && !(args.categories.includes('PARKING_LOT') && args.ignoreParkingLots)) {
          if (!args.categories.some((cat) => this.#categoriesToIgnore.includes(cat))) {
            const provIDs = args.venue.externalProviderIds;
            if (!(provIDs && provIDs.length)) {
              return true;
            }
          }
        }
        return false;
      }

      action() {
        nudgeVenue(this.args.venue);
        harmonizePlaceGo(this.args.venue, 'harmonize'); // Rerun the script to update fields and lock
      }

      action2() {
        clickGeneralTab();
        const venueName = this.args.venue.name;
        $('wz-button.external-provider-add-new').click();
        setTimeout(() => {
          clickGeneralTab();
          setTimeout(() => {
            const autocomplete = document.querySelector('div.external-provider-edit-form wz-autocomplete');
            const input = autocomplete?.shadowRoot?.querySelector('wz-text-input')?.shadowRoot?.querySelector('input');
            if (input) {
              input.focus();
              input.value = venueName;
              input.dispatchEvent(new Event('input', { bubbles: true })); // NOTE: jquery trigger('input') and other event calls did not work.
            }
          }, 100);
        }, 100);
      }

      preProcess() {
        // If no Google link and severity would otherwise allow locking, ask if user wants to lock anyway.
        const { args } = this;
        if (!args.isLocked && this.severity <= SEVERITY.YELLOW) {
          this.severity = SEVERITY.RED;
          args.totalSeverity = SEVERITY.RED;
          if (args.lockOK) {
            this.buttonText = `Lock anyway? (${args.levelToLock + 1})`;
            this.buttonTooltip =
              'If no Google link exists, lock this place.\nIf there is still no Google link after ' + '6 months from the last update date, it will turn red as a reminder to search again.';
            this.action = () => {
              addUpdateAction(args.venue, { lockRank: args.levelToLock }, null, true);
            };
          }
        }
      }
    },
    UrlMissing: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static get defaultMessage() {
        return `No URL: <input type="text" id="${Flag.UrlMissing.#TEXTBOX_ID}" autocomplete="off"` + ' style="font-size:0.85em;width:100px;padding-left:2px;color:#000;">';
      }

      static defaultButtonText = 'Add';
      static defaultButtonTooltip = 'Add URL to place';
      static WL_KEY = 'urlWL';
      static defaultWLTooltip = 'Whitelist empty URL';
      static #TEXTBOX_ID = 'WMEPH-UrlAdd';
      noBannerAssemble = true;

      static isWhitelisted(args) {
        return (
          super.isWhitelisted(args) ||
          getPrimaryCatsToFlagGreenMissingPhoneUrl().includes(args.categories[0]) ||
          getAnyCatsToFlagGreenMissingPhoneUrl().some((category) => args.categories.includes(category))
        );
      }

      static venueIsFlaggable(args) {
        return (
          !args.url?.trim().length &&
          (!isVenueParkingLot(args.venue) || (isVenueParkingLot(args.venue) && REGIONS_THAT_WANT_PLA_PHONE_URL.includes(args.region))) &&
          !getPrimaryCatsToIgnoreMissingPhoneUrl().includes(args.categories[0])
        );
      }

      static #getTextbox() {
        return $(`#${Flag.UrlMissing.#TEXTBOX_ID}`);
      }

      action() {
        const $textbox = Flag.UrlMissing.#getTextbox();
        const newUrl = normalizeURL($textbox.val());
        if (!newUrl?.trim().length || newUrl === BAD_URL) {
          $textbox.css({ backgroundColor: '#FDD' }).attr('title', 'Invalid URL format');
        } else {
          logDev(newUrl);
          addUpdateAction(this.args.venue, { url: newUrl }, null, true);
        }
      }

      postProcess() {
        // If pressing enter in the URL entry box, add the URL
        const textbox = Flag.UrlMissing.#getTextbox();
        textbox.keyup((evt) => {
          if (evt.keyCode === 13 && textbox.val() !== '') {
            this.action();
          }
        });
      }
    },
    InvalidUrl: class extends WLFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'URL appears to be invalid.';
      static WL_KEY = 'invalidUrl';
      static defaultWLTooltip = 'Whitelist bad URL';

      static venueIsFlaggable(args) {
        return args.normalizedUrl === BAD_URL && !this.isWhitelisted(args);
      }
    },
    BadAreaCode: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultButtonText = 'Update';
      static defaultButtonTooltip = 'Update phone #';
      static WL_KEY = 'aCodeWL';
      static defaultWLTooltip = 'Whitelist the area code';
      noBannerAssemble = true;

      get message() {
        return (
          'Area Code appears to be invalid for this region:<br><input type="text" id="WMEPH-PhoneAdd" autocomplete="off" ' +
          `style="font-size:0.85em;width:100px;padding-left:2px;color:#000;" value="${this.args.phone || ''}">`
        );
      }

      static venueIsFlaggable(args) {
        return (
          args.phone &&
          !this.isWhitelisted(args) &&
          // && ['USA', 'CAN'].includes(args.countryCode) // This check shouldn't be needed here.
          !_areaCodeList.includes(args.phone.match(/[2-9]\d{2}/)?.[0])
        );
      }

      action() {
        const newPhone = normalizePhone($('#WMEPH-PhoneAdd').val(), this.args.outputPhoneFormat);
        if (newPhone === BAD_PHONE) {
          $('input#WMEPH-PhoneAdd').css({ backgroundColor: '#FDD' }).attr('title', 'Invalid phone # format');
        } else {
          addUpdateAction(this.args.venue, { phone: newPhone }, null, true);
        }
      }
    },
    AddRecommendedPhone: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultButtonText = 'Add';
      static defaultButtonTooltip = 'Add recommended chain phone #';
      static WL_KEY = 'addRecommendedPhone';
      static defaultWLTooltip = 'Whitelist recommended phone #';

      get message() {
        return `Recommended phone #:<br>${this.args.recommendedPhone}`;
      }

      static venueIsFlaggable(args) {
        return args.recommendedPhone && !this.isWhitelisted(args) && args.recommendedPhone !== BAD_PHONE && args.recommendedPhone !== normalizePhone(args.phone, args.outputPhoneFormat);
      }

      action() {
        addUpdateAction(this.args.venue, { phone: this.args.recommendedPhone }, null, true);
      }
    },
    PhoneMissing: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = 'No ph#: <input type="text" id="WMEPH-PhoneAdd" autocomplete="off" style="font-size:0.85em;width:100px;padding-left:2px;color:#000;">';
      static defaultButtonText = 'Add';
      static defaultButtonTooltip = 'Add phone to place';
      static WL_KEY = 'phoneWL';
      static defaultWLTooltip = 'Whitelist empty phone';
      noBannerAssemble = true;

      static isWhitelisted(args) {
        return (
          super.isWhitelisted(args) ||
          getPrimaryCatsToFlagGreenMissingPhoneUrl().includes(args.categories[0]) ||
          getAnyCatsToFlagGreenMissingPhoneUrl().some((category) => args.categories.includes(category))
        );
      }

      static venueIsFlaggable(args) {
        return (
          !args.phone &&
          !FlagBase.currentFlags.hasFlag(Flag.AddRecommendedPhone) &&
          (!isVenueParkingLot(args.venue) || (isVenueParkingLot(args.venue) && REGIONS_THAT_WANT_PLA_PHONE_URL.includes(args.region))) &&
          !getPrimaryCatsToIgnoreMissingPhoneUrl().includes(args.categories[0])
        );
      }

      action() {
        const newPhone = normalizePhone($('#WMEPH-PhoneAdd').val(), this.args.outputPhoneFormat);
        if (newPhone === BAD_PHONE || !newPhone) {
          $('input#WMEPH-PhoneAdd').css({ backgroundColor: '#FDD' }).attr('title', 'Invalid phone # format');
        } else {
          logDev(newPhone);
          addUpdateAction(this.args.venue, { phone: newPhone }, null, true);
        }
      }

      // eslint-disable-next-line class-methods-use-this
      postProcess() {
        // TODO: Is this needed???
        // If pressing enter in the phone entry box, add the phone
        $('#WMEPH-PhoneAdd').keyup((evt) => {
          if (evt.keyCode === 13 && $('#WMEPH-PhoneAdd').val() !== '') {
            $('#WMEPH_PhoneMissing').click();
            $('#WMEPH_BadAreaCode').click();
          }
        });
      }
    },
    NoHours: class extends WLFlag {
      static WL_KEY = 'noHours';
      static defaultSeverity = SEVERITY.BLUE;
      static defaultWLTooltip = 'Whitelist "No hours"';

      get message() {
        let msg;
        if (!this.args.openingHours.length) {
          msg = Flag.NoHours.#getHoursHtml();
        } else {
          msg = Flag.NoHours.#getHoursHtml(true, isAlwaysOpen(this.args.venue));
        }
        return msg;
      }

      static venueIsFlaggable(args) {
        return !containsAny(args.categories, [
          'STADIUM_ARENA',
          'CEMETERY',
          'TRANSPORTATION',
          'FERRY_PIER',
          'SUBWAY_STATION',
          'BRIDGE',
          'TUNNEL',
          'JUNCTION_INTERCHANGE',
          'ISLAND',
          'SEA_LAKE_POOL',
          'RIVER_STREAM',
          'FOREST_GROVE',
          'CANAL',
          'SWAMP_MARSH',
          'DAM',
          'NATURAL_FEATURES',
        ]);
      }

      static isWhitelisted(args) {
        return (
          super.isWhitelisted(args) ||
          args.openingHours.length ||
          $('#WMEPH-DisableHoursHL').prop('checked') ||
          containsAny(args.categories, [
            'SCHOOL',
            'CONVENTIONS_EVENT_CENTER',
            'CAMPING_TRAILER_PARK',
            'COTTAGE_CABIN',
            'COLLEGE_UNIVERSITY',
            'GOLF_COURSE',
            'SPORTS_COURT',
            'MOVIE_THEATER',
            'SHOPPING_CENTER',
            'RELIGIOUS_CENTER',
            'PARKING_LOT',
            'PARK',
            'PLAYGROUND',
            'AIRPORT',
            'FIRE_DEPARTMENT',
            'POLICE_STATION',
            'SEAPORT_MARINA_HARBOR',
            'FARM',
            'SCENIC_LOOKOUT_VIEWPOINT',
          ])
        );
      }

      static #getHoursHtml(hasExistingHours = false, alwaysOpen = false) {
        return $('<span>').append(
          `${hasExistingHours ? 'Hours' : 'No hours'}:`,
          !alwaysOpen
            ? $('<input>', {
                class: 'btn btn-default btn-xs wmeph-btn',
                id: 'WMEPH_noHours',
                title: `Add pasted hours${hasExistingHours ? ' to existing hours' : ''}`,
                type: 'button',
                value: 'Add hours',
                style: 'margin-bottom:4px; margin-right:0px; margin-left:3px;',
              })
            : '',
          hasExistingHours
            ? $('<input>', {
                class: 'btn btn-default btn-xs wmeph-btn',
                id: 'WMEPH_noHours_2',
                title: 'Replace existing hours with pasted hours',
                type: 'button',
                value: 'Replace all hours',
                style: 'margin-bottom:4px; margin-right:0px; margin-left:3px;',
              })
            : '',
          // jquery throws an error when setting autocomplete="off" in a jquery object (must use .autocomplete() function), so just use a string here.
          // eslint-disable-next-line max-len
          `<textarea id="WMEPH-HoursPaste" wrap="off" autocomplete="off" style="overflow:auto;width:84%;max-width:84%;min-width:84%;font-size:0.85em;height:24px;min-height:24px;max-height:300px;margin-bottom:-2px;padding-left:3px;color:#AAA;position:relative;z-index:1;">${DEFAULT_HOURS_TEXT}`,
        )[0].outerHTML;
      }

      static #getTitle(parseResult) {
        let title;
        if (parseResult.overlappingHours) {
          title = 'Overlapping hours.  Check the existing hours.';
        } else if (parseResult.sameOpenAndCloseTimes) {
          title = 'Open/close times cannot be the same.';
        } else {
          title = "Can't parse, try again";
        }
        return title;
      }

      applyHours(replaceAllHours) {
        if (!this.args?.venue) {
          logDev('applyHours: No venue in args');
          return;
        }

        let pasteHours = $('#WMEPH-HoursPaste').val();
        if (pasteHours === DEFAULT_HOURS_TEXT) {
          return;
        }
        logDev(pasteHours);
        const existingHours = getOpeningHours(this.args.venue);
        pasteHours += !replaceAllHours && existingHours ? `,${existingHours.join(',')}` : '';
        $('.nav-tabs a[href="#venue-edit-more-info"]').tab('show');
        const parser = new HoursParser();
        const parseResult = parser.parseHours(pasteHours);
        if (parseResult.hours && !parseResult.overlappingHours && !parseResult.sameOpenAndCloseTimes && !parseResult.parseError) {
          logDev(parseResult.hours);
          addUpdateAction(this.args.venue, { openingHours: parseResult.hours }, null, true);
          $('#WMEPH-HoursPaste').val(DEFAULT_HOURS_TEXT);
        } else {
          log("Can't parse those hours");
          this.severity = SEVERITY.BLUE;
          this.WLactive = true;
          $('#WMEPH-HoursPaste')
            .css({ 'background-color': '#FDD' })
            .attr({ title: Flag.NoHours.#getTitle(parseResult) });
        }
      }

      onAddHoursClick() {
        this.applyHours();
      }

      onReplaceHoursClick() {
        this.applyHours(true);
      }

      static #getDaysString(days) {
        const dayEnum = {
          1: 'Mon',
          2: 'Tue',
          3: 'Wed',
          4: 'Thu',
          5: 'Fri',
          6: 'Sat',
          7: 'Sun',
        };
        const dayGroups = [];
        let lastGroup;
        let lastGroupDay = -1;
        days.forEach((day) => {
          if (day !== lastGroupDay + 1) {
            // Not a consecutive day. Start a new group.
            lastGroup = [];
            dayGroups.push(lastGroup);
          }
          lastGroup.push(day);
          lastGroupDay = day;
        });

        // Process the groups into strings
        const groupString = [];
        dayGroups.forEach((group) => {
          if (group.length < 3) {
            group.forEach((day) => {
              groupString.push(dayEnum[day]);
            });
          } else {
            const firstDay = dayEnum[group[0]];
            const lastDay = dayEnum[group[group.length - 1]];
            groupString.push(`${firstDay}–${lastDay}`);
          }
        });
        if (groupString.length === 1 && groupString[0] === 'Mon–Sun') return 'Every day';
        return groupString.join(', ');
      }

      static #formatAmPm(time24Hrs) {
        const re = /^(\d{1,2}):(\d{2})/;
        const match = time24Hrs.match(re);
        if (match) {
          let hour = parseInt(match[1], 10);
          const minute = match[2];
          let suffix;
          if (hour === 12 && minute === '00') {
            return 'noon';
          }
          if (hour === 0) {
            if (minute === '00') {
              return 'midnight';
            }
            hour = 12;
            suffix = 'am';
          } else if (hour < 12) {
            suffix = 'am';
          } else {
            suffix = 'pm';
            if (hour > 12) hour -= 12;
          }
          return `${hour}${minute === '00' ? '' : `:${minute}`} ${suffix}`;
        }
        return time24Hrs;
      }

      static #getHoursString(hoursObject) {
        if (hoursObject.allDay === true) return 'All day';
        const fromHour = this.#formatAmPm(hoursObject.fromHour);
        const toHour = this.#formatAmPm(hoursObject.toHour);
        return `${fromHour}–${toHour}`;
      }

      static #getOrderedDaysArray(hoursObject) {
        const days = hoursObject.days.slice();
        // Change Sunday value from 0 to 7
        const sundayIndex = days.indexOf(0);
        if (sundayIndex > -1) {
          days.splice(sundayIndex, 1);
          days.push(7);
        }
        days.sort(); // Maybe not needed, but just in case
        return days;
      }

      static #getHoursStringArray(hoursObjects) {
        const daysWithHours = [];
        const outputArray = hoursObjects.map((hoursObject) => {
          const days = this.#getOrderedDaysArray(hoursObject);
          daysWithHours.push(...days);

          // Concatenate the group strings and append hours range
          const daysString = this.#getDaysString(days);
          const hoursString = this.#getHoursString(hoursObject);
          return `${daysString}:&nbsp&nbsp${hoursString}`;
        });

        // Find closed days
        const closedDays = [1, 2, 3, 4, 5, 6, 7].filter((day) => !daysWithHours.includes(day));
        if (closedDays.length) {
          outputArray.push(`${this.#getDaysString(closedDays)}:&nbsp&nbspCLOSED`);
        }
        return outputArray;
      }

      postProcess() {
        if (this.args.openingHours.length) {
          const hoursStringArray = Flag.NoHours.#getHoursStringArray(this.args.openingHours);
          const $hoursTable = $('<div>', {
            id: 'wmeph-hours-list',
            style:
              'display: inline-block;font-size: 13px;border: 1px solid #aaa;margin: -6px 2px 2px 0px;border-radius: 0px 0px 5px 5px;background-color: #f5f5f5;color: #727272;' +
              'padding: 3px 10px 0px 5px !important;z-index: 0;position: relative;min-width: 84%',
            title: 'Current hours',
          }).append(hoursStringArray.map((entry, idx) => `<div${idx < hoursStringArray.length - 1 ? ' style="border-bottom: 1px solid #ddd;"' : ''}>${entry}</div>`).join(''));

          $('#WMEPH-HoursPaste').after($hoursTable);
        }
        // NOTE: Leave these wrapped in the "() => ..." functions, to make sure "this" is bound properly.
        $('#WMEPH_noHours').click(() => this.onAddHoursClick());
        $('#WMEPH_noHours_2').click(() => this.onReplaceHoursClick());

        // If pasting or dropping into hours entry box
        function resetHoursEntryHeight() {
          const $sel = $('#WMEPH-HoursPaste');
          if ($sel.length) $sel.focus();
          const oldText = $sel.val();
          if (oldText === DEFAULT_HOURS_TEXT) {
            $sel.val('');
          }

          // A small delay to allow window to process pasted text before running.
          setTimeout(() => {
            const text = $sel.val();
            const elem = $sel[0];
            const lineCount = (text.match(/\n/g) || []).length + 1;
            const height = lineCount * 18 + 6 + (elem.scrollWidth > elem.clientWidth ? 20 : 0);
            $sel.css({ height: `${height}px` });
          }, 0);
        }

        $('#WMEPH-HoursPaste').after(
          $('<i>', {
            id: 'wmeph-paste-hours-btn',
            class: 'fa fa-paste',
            style: 'font-size: 17px;position: relative;vertical-align: top;top: 2px;right: -5px;margin-right: 3px;color: #6c6c6c;cursor: pointer;',
            title: 'Paste from the clipboard',
          }),
        ); // , $('<i>', {
        //     id: 'wmeph-clear-hours-btn',
        //     class: 'fa fa-trash-o',
        //     style: 'font-size: 17px;position: relative;right: -5px;bottom: 6px;color: #6c6c6c;cursor: pointer;margin-left: 5px;',
        //     title: 'Clear pasted hours'
        // }));

        $('#wmeph-paste-hours-btn').click(() => {
          navigator.clipboard.readText().then(
            (cliptext) => {
              $('#WMEPH-HoursPaste').val(cliptext);
              resetHoursEntryHeight();
            },
            (err) => logDev(err),
          );
        });

        // $('#wmeph-clear-hours-btn').click(() => {
        //     $('#WMEPH-HoursPaste').val(null);
        //     resetHoursEntryHeight();
        // });

        $('#WMEPH-HoursPaste')
          .bind('paste', resetHoursEntryHeight)
          .bind('drop', resetHoursEntryHeight)
          .bind('dragenter', (evt) => {
            const $control = $(evt.currentTarget);
            const text = $control.val();
            if (text === DEFAULT_HOURS_TEXT) {
              $control.val('');
            }
          })
          .keydown((evt) => {
            // If pressing enter in the hours entry box then parse the entry, or newline if CTRL or SHIFT.
            resetHoursEntryHeight();
            if (evt.keyCode === 13) {
              if (evt.ctrlKey) {
                // Simulate a newline event (shift + enter)
                const target = evt.currentTarget;
                const text = target.value;
                const selStart = target.selectionStart;
                target.value = `${text.substr(0, selStart)}\n${text.substr(target.selectionEnd, text.length - 1)}`;
                target.selectionStart = selStart + 1;
                target.selectionEnd = selStart + 1;
                return true;
              }
              if (!(evt.shiftKey || evt.ctrlKey) && $(evt.currentTarget).val().length) {
                evt.stopPropagation();
                evt.preventDefault();
                evt.returnValue = false;
                evt.cancelBubble = true;
                $('#WMEPH_noHours').click();
                return false;
              }
            }
            return true;
          })
          .focus((evt) => {
            const target = evt.currentTarget;
            if (target.value === DEFAULT_HOURS_TEXT) {
              target.value = '';
            }
            target.style.color = 'black';
          })
          .blur((evt) => {
            const target = evt.currentTarget;
            if (target.value === '') {
              target.value = DEFAULT_HOURS_TEXT;
              target.style.color = '#999';
            }
          });
      }
    },
    OldHours: class extends ActionFlag {
      static defaultSeverity = SEVERITY.YELLOW;
      static #categoriesToCheck;
      static #parentCategoriesToCheck = ['SHOPPING_AND_SERVICES', 'FOOD_AND_DRINK', 'CULTURE_AND_ENTERTAINEMENT'];

      get message() {
        let msg = 'Last updated over 3 years ago. Verify hours are correct.';
        const isUnchanged = !this.args.venue.isNew && !this.args.venue.updatedBy;
        if (isUnchanged) msg += ' If everything is current, nudge this place and save.';
        return msg;
      }

      get buttonText() {
        const isUnchanged = !this.args.venue.isNew && !this.args.venue.updatedBy;
        return isUnchanged ? 'Nudge' : null;
      }

      get severity() {
        const isUnchanged = !this.args.venue.isNew && !this.args.venue.updatedBy;
        return isUnchanged ? super.severity : SEVERITY.GREEN;
      }

      static venueIsFlaggable(args) {
        this.#initializeCategoriesToCheck(args.pnhCategoryInfos);
        return (
          !isVenueResidential(args.venue) &&
          this.#venueIsOld(args.venue) && // Check uses the updated logic now
          args.openingHours?.length &&
          args.categories.some((cat) => this.#categoriesToCheck.includes(cat))
        );
      }

      static #initializeCategoriesToCheck(pnhCategoryInfos) {
        if (!this.#categoriesToCheck) {
          this.#categoriesToCheck = pnhCategoryInfos
            .toArray()
            .filter((pnhCategoryInfo) => this.#parentCategoriesToCheck.includes(pnhCategoryInfo.parent))
            .map((catInfo) => catInfo.id);
          this.#categoriesToCheck.push(...this.#parentCategoriesToCheck);
        }
      }

      static #venueIsOld(venue) {
        // Get the timestamp, prioritizing updatedOn, falling back to createdOn
        const lastUpdatedTimestamp = venue.updatedOn ?? venue.createdOn;

        // If neither timestamp exists, we can't determine age, so return false
        if (!lastUpdatedTimestamp) {
          return false;
        }

        const lastUpdatedDate = new Date(lastUpdatedTimestamp);

        // Calculate the date exactly 3 years ago from the current time
        const threeYearsAgo = new Date(); // Gets current date and time
        threeYearsAgo.setFullYear(threeYearsAgo.getFullYear() - 3); // Sets the year back by 3

        // Check if the last updated date is before the date 3 years ago
        return lastUpdatedDate < threeYearsAgo;
      }

      action() {
        nudgeVenue(this.args.venue);
        harmonizePlaceGo(this.args.venue, 'harmonize');
      }
    },
    PlaLotTypeMissing: class extends FlagBase {
      static defaultSeverity = SEVERITY.RED;
      static get defaultMessage() {
        return `Lot type: ${[
          ['PUBLIC', 'Public'],
          ['RESTRICTED', 'Restricted'],
          ['PRIVATE', 'Private'],
        ]
          .map((btnInfo) => $('<button>', { class: 'wmeph-pla-lot-type-btn btn btn-default btn-xs wmeph-btn', 'data-lot-type': btnInfo[0] }).text(btnInfo[1]).prop('outerHTML'))
          .join('')}`;
      }

      static venueIsFlaggable(args) {
        if (!isVenueParkingLot(args.venue)) return false;
        try {
          const parkingType = sdk.DataModel.Venues.ParkingLot.getParkingLotType({ venueId: args.venue.id });
          return !parkingType;
        } catch {
          return true;
        }
      }

      postProcess() {
        $('.wmeph-pla-lot-type-btn').click((evt) => {
          const lotType = $(evt.currentTarget).data('lot-type');
          const categoryAttrClone = this.args.venue.categoryAttributes ? JSON.parse(JSON.stringify(this.args.venue.categoryAttributes)) : {};
          categoryAttrClone.PARKING_LOT = categoryAttrClone.PARKING_LOT ?? {};
          categoryAttrClone.PARKING_LOT.parkingType = lotType;
          UPDATED_FIELDS.lotType.updated = true;
          addUpdateAction(this.args.venue, { categoryAttributes: categoryAttrClone }, null, true);
        });
      }
    },
    PlaCostTypeMissing: class extends FlagBase {
      static defaultSeverity = SEVERITY.BLUE;
      static get defaultMessage() {
        return `Parking cost: ${[
          ['FREE', 'Free', 'Free'],
          ['LOW', '$', 'Low'],
          ['MODERATE', '$$', 'Moderate'],
          ['EXPENSIVE', '$$$', 'Expensive'],
        ]
          .map((btnInfo) =>
            $('<button>', { id: `wmeph_${btnInfo[0]}`, class: 'wmeph-pla-cost-type-btn btn btn-default btn-xs wmeph-btn', title: btnInfo[2] })
              .text(btnInfo[1])
              .css({
                padding: '3px',
                height: '20px',
                lineHeight: '0px',
                marginRight: '2px',
                marginBottom: '1px',
                minWidth: '18px',
              })
              .prop('outerHTML'),
          )
          .join('')}`;
      }

      static venueIsFlaggable(args) {
        if (!isVenueParkingLot(args.venue)) return false;
        try {
          const costType = sdk.DataModel.Venues.ParkingLot.getCostType({ venueId: args.venue.id });
          return !costType || costType === 'UNKNOWN';
        } catch {
          return false;
        }
      }

      postProcess() {
        $('.wmeph-pla-cost-type-btn').click((evt) => {
          const selectedValue = $(evt.currentTarget).attr('id').replace('wmeph_', '');
          let attrClone;
          if (this.args.venue.categoryAttributes) {
            attrClone = JSON.parse(JSON.stringify(this.args.venue.categoryAttributes));
          } else {
            attrClone = {};
          }
          attrClone.PARKING_LOT ??= {};
          attrClone.PARKING_LOT.costType = selectedValue;
          addUpdateAction(this.args.venue, { categoryAttributes: attrClone }, null, true);
          UPDATED_FIELDS.cost.updated = true;
        });
      }
    },
    PlaPaymentTypeMissing: class extends ActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = 'Parking isn\'t free. Select payment type(s) from the "More info" tab. ';
      static defaultButtonText = 'Go there';

      static venueIsFlaggable(args) {
        if (!isVenueParkingLot(args.venue)) return false;

        try {
          const costType = sdk.DataModel.Venues.ParkingLot.getCostType({ venueId: args.venue.id });
          if (!costType || costType === 'FREE' || costType === 'UNKNOWN') return false;

          const paymentMethods = sdk.DataModel.Venues.ParkingLot.getPaymentMethods({ venueId: args.venue.id });
          return !paymentMethods || paymentMethods.length === 0;
        } catch {
          return false;
        }
      }

      // eslint-disable-next-line class-methods-use-this
      action() {
        document.querySelector('#edit-panel wz-tab.venue-edit-tab-more-info').isActive = true;
        // The setTimeout is necessary to allow the previous action to do its thing. A pause isn't needed, just a new thread.
        setTimeout(() => document.querySelector('#venue-edit-more-info wz-select[name="costType"]').scrollIntoView(), 0);
      }
    },
    PlaLotElevationMissing: class extends ActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = 'No lot elevation. Is it street level?';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Click if street level parking only, or select other option(s) in the More Info tab.';
      noLock = true;

      static venueIsFlaggable(args) {
        if (!isVenueParkingLot(args.venue)) return false;
        try {
          const lotTypes = sdk.DataModel.Venues.ParkingLot.getLotTypes({ venueId: args.venue.id });
          return !lotTypes || lotTypes.length === 0;
        } catch {
          return false;
        }
      }

      action() {
        const attrClone = this.args.venue.categoryAttributes ? JSON.parse(JSON.stringify(this.args.venue.categoryAttributes)) : {};
        attrClone.PARKING_LOT = attrClone.PARKING_LOT ?? {};
        attrClone.PARKING_LOT.lotType = ['STREET_LEVEL'];
        addUpdateAction(this.args.venue, { categoryAttributes: attrClone }, null, true);
      }
    },
    PlaSpaces: class extends FlagBase {
      static get defaultMessage() {
        const msg = '# of parking spaces is set to 1-10.<br><b><i>If appropriate</i></b>, select another option:';
        const $btnDiv = $('<div>');
        let btnIdx = 0;
        [
          ['R_11_TO_30', '11-30'],
          ['R_31_TO_60', '31-60'],
          ['R_61_TO_100', '61-100'],
          ['R_101_TO_300', '101-300'],
          ['R_301_TO_600', '301-600'],
          ['R_600_PLUS', '601+'],
        ].forEach((btnInfo) => {
          if (btnIdx === 3) $btnDiv.append('<br>');
          $btnDiv.append(
            $('<button>', { id: `wmeph_${btnInfo[0]}`, class: 'wmeph-pla-spaces-btn btn btn-default btn-xs wmeph-btn' })
              .text(btnInfo[1])
              .css({
                padding: '3px',
                height: '20px',
                lineHeight: '0px',
                marginTop: '2px',
                marginRight: '2px',
                marginBottom: '1px',
                width: '64px',
              }),
          );
          btnIdx++;
        });
        return msg + $btnDiv.prop('outerHTML');
      }

      static venueIsFlaggable(args) {
        if (args.highlightOnly || !isVenueParkingLot(args.venue)) return false;
        try {
          const spotEstimate = sdk.DataModel.Venues.ParkingLot.getEstimatedNumberOfSpots({ venueId: args.venue.id });
          return !spotEstimate || spotEstimate === 'R_1_TO_10';
        } catch {
          return false;
        }
      }

      static setupClickHandlers() {
        $('.wmeph-pla-spaces-btn').click((evt) => {
          const selectedVenue = getSelectedVenue();
          const selectedValue = $(evt.currentTarget).attr('id').replace('wmeph_', '');
          try {
            sdk.DataModel.Venues.ParkingLot.setEstimatedNumberOfSpots({
              venueId: selectedVenue.id,
              estimatedNumberOfSpots: selectedValue,
            });
            UPDATED_FIELDS.parkingSpots.updated = true;
            addUpdateAction(selectedVenue, {}, null, true);
          } catch (e) {
            logDev('Error setting parking spaces:', e);
          }
        });
      }
    },
    NoPlaStopPoint: class extends ActionFlag {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = 'Entry/exit point has not been created.';
      static defaultButtonText = 'Add point';
      static defaultButtonTooltip = 'Add an entry/exit point';

      static venueIsFlaggable(args) {
        if (!isVenueParkingLot(args.venue)) return false;

        const hasNavPoints = args.venue.navigationPoints?.length > 0;
        return !hasNavPoints;
      }

      action() {
        const { venue } = this.args;

        // Create new entry point at venue geometry center
        const center = turf.centroid(venue.geometry).geometry.coordinates;

        const newNavigationPoints = [
          {
            point: {
              type: 'Point',
              coordinates: center,
            },
            isEntry: true,
            isExit: true,
            isPrimary: true,
            name: '',
          },
        ];

        sdk.DataModel.Venues.replaceNavigationPoints({
          venueId: venue.id,
          navigationPoints: newNavigationPoints,
        });

        harmonizePlaceGo(venue, 'harmonize');
      }
    },
    PlaStopPointUnmoved: class extends FlagBase {
      static defaultSeverity = SEVERITY.BLUE;
      static defaultMessage = 'Entry/exit point has not been moved.';

      static venueIsFlaggable(args) {
        if (!isVenueParkingLot(args.venue)) return false;

        const { navigationPoints, geometry } = args.venue;
        if (!navigationPoints?.length) return false;

        // Get the primary navigation point, if one exists. If none, get the first point.
        const primaryPoint = navigationPoints.find((pt) => pt.isPrimary === true) || navigationPoints[0];
        const stopPoint = primaryPoint.point.coordinates;

        const areaCenter = turf.centroid(geometry).geometry.coordinates;
        return stopPoint[0] === areaCenter[0] && stopPoint[1] === areaCenter[1];
      }
    },
    PlaCanExitWhileClosed: class extends ActionFlag {
      static defaultMessage = 'Can cars exit when lot is closed? ';
      static defaultButtonText = 'Yes';

      static venueIsFlaggable(args) {
        if (args.highlightOnly || !isVenueParkingLot(args.venue)) return false;
        if (!($('#WMEPH-ShowPLAExitWhileClosed').prop('checked') || !(args.openingHours.length === 0 || is247Hours(args.openingHours)))) return false;
        try {
          const canExit = sdk.DataModel.Venues.ParkingLot.canExitWhileClosed({ venueId: args.venue.id });
          return !canExit;
        } catch {
          return false;
        }
      }

      action() {
        const attrClone = this.args.venue.categoryAttributes ? JSON.parse(JSON.stringify(this.args.venue.categoryAttributes)) : {};
        attrClone.PARKING_LOT = attrClone.PARKING_LOT ?? {};
        attrClone.PARKING_LOT.canExitWhileClosed = true;
        addUpdateAction(this.args.venue, { categoryAttributes: attrClone }, null, true);
      }
    },
    PlaHasAccessibleParking: class extends ActionFlag {
      static defaultMessage = 'Does this lot have disability parking? ';
      static defaultButtonText = 'Yes';

      static venueIsFlaggable(args) {
        if (args.highlightOnly || !isVenueParkingLot(args.venue)) return false;
        return !args.venue.services?.includes('DISABILITY_PARKING');
      }

      action() {
        const services = this.args.venue.services?.slice() ?? [];
        services.push('DISABILITY_PARKING');
        addUpdateAction(this.args.venue, { services }, null, true);
        UPDATED_FIELDS.services_DISABILITY_PARKING.updated = true;
      }
    },
    AllDayHoursFixed: class extends FlagBase {
      static defaultSeverity = SEVERITY.YELLOW;
      static defaultMessage = 'Hours were changed from 00:00-23:59 to "All Day"';

      // If highlightOnly, flag place yellow. Running WMEPH on a place will automatically fix the hours, so
      // then this can be green and just display the message.
      get severity() {
        return this.args.highlightOnly ? super.severity : SEVERITY.GREEN;
      }

      static venueIsFlaggable(args) {
        return args.almostAllDayHoursEntries.length > 0;
      }
    },
    LocalURL: class extends FlagBase {
      static defaultMessage = 'Some locations for this business have localized URLs, while others use the primary corporate site.' + ' Check if a local URL applies to this location.';

      static venueIsFlaggable(args) {
        return args.localUrlRegexString && !new RegExp(args.localUrlRegexString, 'i').test(args.url);
      }
    },
    LockRPP: class extends ActionFlag {
      static defaultButtonText = 'Lock';
      static defaultButtonTooltip = 'Lock the residential point';

      get message() {
        let msg = 'Lock at <select id="RPPLockLevel">';
        let ddlSelected = false;
        for (let llix = 1; llix < 6; llix++) {
          if (llix < USER.rank + 1) {
            if (!ddlSelected && (this.args.defaultLockLevel === llix - 1 || llix === USER.rank)) {
              msg += `<option value="${llix}" selected="selected">${llix}</option>`;
              ddlSelected = true;
            } else {
              msg += `<option value="${llix}">${llix}</option>`;
            }
          }
        }
        msg += '</select>';
        msg = `Current lock: ${parseInt(this.args.venue.lockRank, 10) + 1}. ${msg} ?`;
        return msg;
      }

      static venueIsFlaggable(args) {
        // Allow residential point locking by R3+
        return !args.highlightOnly && (args.categories.includes('RESIDENCE_HOME') || args.categories.includes('RESIDENTIAL')) && (USER.isDevUser || USER.isBetaUser || USER.rank >= 3);
      }

      action() {
        let levelToLock = $('#RPPLockLevel :selected').val() || this.args.defaultLockLevel + 1;
        logDev(`RPPlevelToLock: ${levelToLock}`);

        levelToLock -= 1;
        if (this.args.venue.lockRank !== levelToLock) {
          addUpdateAction(this.args.venue, { lockRank: levelToLock }, null, true);
        }
      }
    },
    AddAlias: class extends ActionFlag {
      static defaultButtonText = 'Yes';

      get message() {
        return `Is there a ${this.args.pnhMatch.optionalAlias} at this location?`;
      }
      get buttonTooltip() {
        return `Add ${this.args.pnhMatch.optionalAlias}`;
      }

      static venueIsFlaggable(args) {
        return args.pnhMatch.optionalAlias && !args.aliases.includes(args.pnhMatch.optionalAlias);
      }

      action() {
        const venue = this.args.venue;
        const alias = this.args.pnhMatch.optionalAlias;
        let aliases = insertAtIndex(venue.aliases?.slice() || [], alias, 0);
        if (this.args.pnhMatch.altName2Desc && !venue.description?.toUpperCase?.().includes(alias.toUpperCase())) {
          const description = `${alias}\n${venue.description}`;
          addUpdateAction(venue, { description }, null, false);
        }
        aliases = removeUnnecessaryAliases(this.args.nameBase, aliases);
        addUpdateAction(venue, { aliases }, null, true);
      }
    },
    AddCat2: class extends ActionFlag {
      static defaultButtonText = 'Yes';

      get message() {
        return `Is there a ${_catTransWaze2Lang[this.altCategory]} at this location?`;
      }
      get buttonTooltip() {
        return `Add ${_catTransWaze2Lang[this.altCategory]}`;
      }

      constructor(venue, altCategory) {
        super();
        this.altCategory = altCategory;
        this.venue = venue;
      }

      static eval(args, altCategory) {
        let result = null;
        if (args.pnhMatch.flagsToAdd?.addCat2 && !args.categories.includes(altCategory)) {
          result = new this(args.venue, altCategory);
        }
        return result;
      }

      action() {
        const categories = insertAtIndex(this.venue.categories, this.altCategory, 1);
        addUpdateAction(this.venue, { categories }, null, true);
      }
    },
    AddPharm: class extends ActionFlag {
      static defaultMessage = 'Is there a Pharmacy at this location?';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Add Pharmacy category';

      static venueIsFlaggable(args) {
        return args.pnhMatch.flagsToAdd?.addPharm && !args.categories.includes('PHARMACY');
      }

      action() {
        const categories = insertAtIndex(this.args.venue.categories, 'PHARMACY', 1);
        addUpdateAction(this.args.venue, { categories }, null, true);
      }
    },
    AddSuper: class extends ActionFlag {
      static defaultMessage = 'Does this location have a supermarket?';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Add Supermarket category';

      static venueIsFlaggable(args) {
        return args.pnhMatch.flagsToAdd?.addSuper && !args.categories.includes('SUPERMARKET_GROCERY');
      }

      action() {
        const categories = insertAtIndex(this.args.venue.categories, 'SUPERMARKET_GROCERY', 1);
        addUpdateAction(this.args.venue, { categories }, null, true);
      }
    },
    AppendAMPM: class extends ActionFlag {
      // Only used on the ARCO gas station PNH entry.
      static defaultMessage = 'Is there an ampm at this location?';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Add ampm to the place';

      static venueIsFlaggable(args) {
        // No need to check for name/catgory. After the action is run, the name will match the "ARCO ampm"
        // PNH entry, which doesn't have this flag.
        return args.pnhMatch.flagsToAdd?.appendAMPM;
      }

      action() {
        const categories = insertAtIndex(this.args.venue.categories, 'CONVENIENCE_STORE', 1);
        addUpdateAction(this.args.venue, { name: 'ARCO ampm', url: 'ampm.com', categories }, null, true);
      }
    },
    AddATM: class extends ActionFlag {
      static defaultMessage = 'ATM at location? ';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Add the ATM category to this place';

      static venueIsFlaggable(args) {
        let flaggable = false;
        if (args.pnhMatch.flagsToAdd?.addATM) {
          flaggable = true;
        } else if (args.pnhMatch.notABank) {
          // do nothing
        } else if (!args.categories.includes('ATM') && args.categories.includes('BANK_FINANCIAL')) {
          if (args.priPNHPlaceCat === 'BANK_FINANCIAL') {
            if (args.categories.indexOf('OFFICES') !== 0) {
              flaggable = true;
            }
          } else {
            flaggable = true;
          }
        }
        return flaggable;
      }

      action() {
        const categories = insertAtIndex(this.args.venue.categories, 'ATM', 1); // Insert ATM category in the second position
        addUpdateAction(this.args.venue, { categories }, null, true);
      }
    },
    AddConvStore: class extends ActionFlag {
      static defaultMessage = 'Add convenience store category? ';
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Add the Convenience Store category to this place';

      static venueIsFlaggable(args) {
        return (
          (args.categories.includes('GAS_STATION') && !args.categories.includes('CONVENIENCE_STORE') && !this.currentFlags.hasFlag(Flag.SubFuel)) || // Don't flag if already asking if this is really a gas station
          args.pnhMatch?.flagsToAdd?.addConvStore
        );
      }

      action() {
        // Insert C.S. category in the second position
        const categories = insertAtIndex(this.args.venue.categories, 'CONVENIENCE_STORE', 1);
        addUpdateAction(this.args.venue, { categories }, null, true);
      }
    },
    IsThisAPostOffice: class extends ActionFlag {
      static defaultMessage = `Is this a <a href="${URLS.uspsWiki}" target="_blank" style="color:#3a3a3a">USPS post office</a>? `;
      static defaultButtonText = 'Yes';
      static defaultButtonTooltip = 'Is this a USPS location?';

      static venueIsFlaggable(args) {
        return (
          !args.highlightOnly &&
          args.countryCode === PNH_DATA.USA.countryCode &&
          !isVenueParkingLot(args.venue) &&
          !args.categories.includes('POST_OFFICE') &&
          /\bUSP[OS]\b|\bpost(al)?\s+(service|office)\b/i.test(args.nameBase.replace(/[/\-.]/g, ''))
        );
      }

      action() {
        const categories = insertAtIndex(this.args.venue.categories, 'POST_OFFICE', 0);
        addUpdateAction(this.args.venue, { categories }, null, true);
      }
    },
    ChangeToHospitalUrgentCare: class extends ActionFlag {
      static defaultMessage = 'If this place provides emergency medical care:';
      static defaultButtonText = 'Change to Hospital / Urgent Care';
      static defaultButtonTooltip = 'Change category to Hospital / Urgent Care';

      static venueIsFlaggable(args) {
        return !args.highlightOnly && args.categories.includes('DOCTOR_CLINIC');
      }

      action() {
        let categories = this.args.venue.categories;
        if (!categories.includes('HOSPITAL_MEDICAL_CARE')) {
          const indexToReplace = categories.indexOf('DOCTOR_CLINIC');
          if (indexToReplace > -1) {
            categories = categories.slice(); // create a copy
            categories[indexToReplace] = 'HOSPITAL_URGENT_CARE';
          }
          addUpdateAction(this.args.venue, { categories });
        }
        harmonizePlaceGo(this.args.venue, 'harmonize');
      }
    },
    NotAHospital: class extends WLActionFlag {
      static defaultSeverity = SEVERITY.RED;
      static defaultMessage = 'Key words suggest this location may not be a hospital or urgent care location.';
      static defaultButtonText = 'Change to Doctor / Clinic';
      static defaultButtonTooltip = 'Change category to Doctor / Clinic';
      static WL_KEY = 'notAHospital';
      static defaultWLTooltip = 'Whitelist category';

      static venueIsFlaggable(args) {
        if (args.categories.includes('HOSPITAL_URGENT_CARE') && !this.isWhitelisted(args)) {
          const testName = args.nameBase.toLowerCase().replace(/[^a-z]/g, ' ');
          const testNameWords = testName.split(' ');
          return containsAny(testNameWords, Pnh.HOSPITAL_FULL_MATCH) || Pnh.HOSPITAL_PART_MATCH.some((match) => testName.includes(match));
        }
        return false;
      }

      action() {
        let categories = this.args.venue.categories.slice();
        let updateIt = false;
        if (categories.length) {
          const idx = categories.indexOf('HOSPITAL_URGENT_CARE');
          if (idx > -1) {
            categories[idx] = 'DOCTOR_CLINIC';
            updateIt = true;
          }
          categories = uniq(categories);
        } else {
          categories.push('DOCTOR_CLINIC');
          updateIt = true;
        }
        if (updateIt) {
          addUpdateAction(this.args.venue, { categories }, null, true);
        } else {
          harmonizePlaceGo(this.args.venue, 'harmonize');
        }
      }
    },
    ChangeToDoctorClinic: class extends ActionFlag {
      static defaultMessage = 'If this place provides non-emergency medical care: ';
      static defaultButtonText = 'Change to Doctor / Clinic';
      static defaultButtonTooltip = 'Change category to Doctor / Clinic';

      static venueIsFlaggable(args) {
        // Show the Change To Doctor / Clinic button for places with PERSONAL_CARE or OFFICES category
        // The date criteria was added because Doctor/Clinic category was added around then, and it's assumed if the
        // place has been edited since then, people would have already updated the category.
        return (
          !args.highlightOnly &&
          args.venue.updatedOn < new Date('3/28/2017').getTime() &&
          ((args.categories.includes('PERSONAL_CARE') && !args.pnhNameRegMatch) || args.categories.includes('OFFICES'))
        );
      }

      action() {
        let categories = this.args.venue.categories.slice();
        let updateIt = false;
        if (categories.length) {
          ['OFFICES', 'PERSONAL_CARE'].forEach((cat) => {
            const idx = categories.indexOf(cat);
            if (idx > -1) {
              categories[idx] = 'DOCTOR_CLINIC';
              updateIt = true;
            }
          });
          categories = uniq(categories);
        } else {
          categories.push('DOCTOR_CLINIC');
          updateIt = true;
        }
        if (updateIt) {
          addUpdateAction(this.args.venue, { categories });
        }
        harmonizePlaceGo(this.args.venue, 'harmonize');
      }
    },
    TitleCaseName: class extends ActionFlag {
      static defaultButtonText = 'Force Title Case?';
      #confirmChange = false;
      #originalName;
      #titleCaseName;
      noBannerAssemble = true;

      get message() {
        return `${this.#titleCaseName}${this.args.nameSuffix || ''}`;
      }
      get buttonTooltip() {
        return `Rename to: ${this.#titleCaseName}${this.args.nameSuffix || ''}`;
      }

      constructor(args) {
        super();
        this.#titleCaseName = titleCase(args.nameBase);
        this.#originalName = args.nameBase + (args.nameSuffix || '');
      }

      static venueIsFlaggable(args) {
        return !args.pnhNameRegMatch && args.nameBase !== titleCase(args.nameBase);
      }

      action() {
        let name = this.args.venue.name;
        if (name === this.#originalName || this.#confirmChange) {
          const parts = getNameParts(this.#originalName);
          name = titleCase(parts.base);
          if (parts.base !== name) {
            addUpdateAction(this.args.venue, { name: name + (parts.suffix || '') }, undefined, true);
          } else {
            harmonizePlaceGo(this.args.venue, 'harmonize');
          }
        } else {
          $('button#WMEPH_titleCaseName').text('Are you sure?').after(' The name has changed. This will overwrite the new name.');
          this.#confirmChange = true;
        }
      }
    },
    SFAliases: class extends FlagBase {
      static defaultMessage = 'Unnecessary aliases were removed.';

      static venueIsFlaggable(args) {
        return args.aliasesRemoved;
      }
    },
    PlaceMatched: class extends FlagBase {
      static defaultMessage = 'Place matched from PNH data.';

      static venueIsFlaggable(args) {
        return args.pnhNameRegMatch;
      }
    },
    PlaceLocked: class extends FlagBase {
      static defaultMessage = 'Place locked.';
      constructor(args) {
        super();

        if (args.venue.lockRank < args.levelToLock) {
          if (!args.highlightOnly) {
            logDev(`Venue locked! Current: ${args.venue.lockRank}, Target: ${args.levelToLock}`);
            // Use SDK to update venue directly - wrap in try-catch since locking may fail due to permissions
            try {
              addUpdateAction(args.venue, { lockRank: args.levelToLock }, args.actions);
            } catch (e) {
              logDev('Could not lock venue - you may not have permission', e);
            }
          } else {
            this.hlLockFlag = true;
          }
        }
      }

      static venueIsFlaggable(args) {
        return args.lockOK && args.totalSeverity < SEVERITY.YELLOW;
      }
    },
    NewPlaceSubmit: class extends ActionFlag {
      static defaultMessage = "No PNH match. If it's a chain: ";
      static defaultButtonText = 'Submit new chain data';
      static defaultButtonTooltip = 'Submit info for a new chain through the linked form';
      #formUrl;

      constructor(args) {
        super();

        // Make PNH submission link
        const encodedName = encodeURIComponent(args.nameBase);
        const encodedPermalink = encodeURIComponent(args.placePL);
        const encodedUrl = encodeURIComponent(args.newUrl?.trim() ?? '');
        const regionSettings = PNH_DATA[args.countryCode].regions[args.regionCode];
        let entryValues;
        if (['CA_EN', 'QC'].includes(args.region)) {
          entryValues = [encodedName, encodedUrl, USER.name, encodedPermalink];
        } else {
          entryValues = [encodedName, encodedUrl, USER.name + args.gFormState];
        }
        this.#formUrl = regionSettings.getNewChainFormUrl(entryValues);
      }

      static venueIsFlaggable(args) {
        return (
          !args.highlightOnly &&
          args.pnhMatch[0] === 'NoMatch' &&
          !isVenueParkingLot(args.venue) &&
          !getChainApprovalPrimaryCatsToIgnore().includes(args.categories[0]) &&
          !args.categories.includes('REST_AREAS')
        );
      }

      action() {
        window.open(this.#formUrl);
      }
    },
    ApprovalSubmit: class extends ActionFlag {
      static defaultMessage = 'PNH data exists but is not approved for this region: ';
      static defaultButtonText = 'Request approval';
      static defaultButtonTooltip = 'Request region/country approval of this place';
      #formUrl;

      constructor(args) {
        super();

        const encodedName = encodeURIComponent(args.pnhMatch[1][0]); // Just do the first match
        const pnhOrderNum = args.pnhMatch[2].join(',');
        const approvalMessage = `Submitted via WMEPH. PNH order number ${pnhOrderNum}`;
        const encodedPermalink = encodeURIComponent(args.placePL);
        const regionSettings = PNH_DATA[args.countryCode].regions[args.regionCode];
        let entryValues;
        if (['CA_EN', 'QC'].includes(args.region)) {
          entryValues = [encodedName, approvalMessage, USER.name, encodedPermalink];
        } else {
          entryValues = [encodedName, approvalMessage, USER.name + args.gFormState];
        }
        this.#formUrl = regionSettings.getApproveChainFormUrl(entryValues);
      }

      static venueIsFlaggable(args) {
        return (
          !args.highlightOnly &&
          args.pnhMatch[0] === 'ApprovalNeeded' &&
          !isVenueParkingLot(args.venue) &&
          !getChainApprovalPrimaryCatsToIgnore().includes(args.categories[0]) &&
          !args.categories.includes('REST_AREAS')
        );
      }

      action() {
        window.open(this.#formUrl);
      }
    },
    LocationFinder: class extends ActionFlag {
      static defaultButtonTooltip = "Look up details about this location on the chain's finder web page.";
      static #USPS_LOCATION_FINDER_URL = 'https://tools.usps.com/find-location.htm';
      #storeFinderUrl;
      #isCustom = false;

      get buttonText() {
        return `Location Finder${this.isCustom ? ' (L)' : ''}`;
      }

      constructor(venue, storeFinderUrl, isCustom, addr, state2L, venueGPS) {
        super();
        this.isCustom = isCustom;
        this.venue = venue;
        this.#isCustom = isCustom;
        this.#storeFinderUrl = storeFinderUrl;
        this.#processUrl(venue, addr, state2L, venueGPS);
      }

      static #venueIsFlaggable(highlightOnly, storeFinderUrl) {
        return !highlightOnly && storeFinderUrl;
      }

      // TODO: Can this be put into venueIsFlaggable?
      static eval(args) {
        const isUsps = args.countryCode === PNH_DATA.USA.countryCode && !args.categories.includes('PARKING_LOT') && args.categories.includes('POST_OFFICE');
        let storeFinderUrl;
        let isCustom = false;
        if (isUsps) {
          storeFinderUrl = this.#USPS_LOCATION_FINDER_URL;
        } else {
          storeFinderUrl = args.pnhMatch.sfurllocal;
          if (storeFinderUrl) {
            isCustom = true;
          } else {
            storeFinderUrl = args.pnhMatch.sfurl;
          }
        }

        return this.#venueIsFlaggable(args.highlightOnly, storeFinderUrl) ? new this(args.venue, storeFinderUrl, isCustom, args.addr, args.state2L, args.venueGPS) : null;
      }

      #processUrl(venue, addr, state2L, venueGPS) {
        if (this.#isCustom) {
          const houseNumber = venue.houseNumber;

          const urlParts = this.#storeFinderUrl.replace(/ /g, '').split('<>');
          let searchStreet = '';
          let searchCity = '';
          let searchState = '';
          if (typeof addr.street?.name === 'string') {
            searchStreet = addr.street.name;
          }
          const searchStreetPlus = searchStreet.replace(/ /g, '+');
          searchStreet = searchStreet.replace(/ /g, '%20');
          if (typeof addr.city?.name === 'string') {
            searchCity = addr.city.name;
          }
          const searchCityPlus = searchCity.replace(/ /g, '+');
          searchCity = searchCity.replace(/ /g, '%20');
          if (typeof addr.state?.name === 'string') {
            searchState = addr.state.name;
          }
          const searchStatePlus = searchState.replace(/ /g, '+');
          searchState = searchState.replace(/ /g, '%20');

          // venueGPS is already in WGS84; location object already contains correct coordinates
          this.#storeFinderUrl = '';
          for (let tlix = 1; tlix < urlParts.length; tlix++) {
            let part = '';
            switch (urlParts[tlix]) {
              case 'ph_streetName':
                part = searchStreet;
                break;
              case 'ph_streetNamePlus':
                part = searchStreetPlus;
                break;
              case 'ph_cityName':
                part = searchCity;
                break;
              case 'ph_cityNamePlus':
                part = searchCityPlus;
                break;
              case 'ph_stateName':
                part = searchState;
                break;
              case 'ph_stateNamePlus':
                part = searchStatePlus;
                break;
              case 'ph_state2L':
                part = state2L;
                break;
              case 'ph_latitudeEW':
                // customStoreFinderLocalURL = customStoreFinderLocalURL + venueGPS[0];
                break;
              case 'ph_longitudeNS':
                // customStoreFinderLocalURL = customStoreFinderLocalURL + venueGPS[1];
                break;
              case 'ph_latitudePM':
                part = venueGPS ? (venueGPS.latitude ?? venueGPS.lat ?? '').toString() : '';
                break;
              case 'ph_longitudePM':
                part = venueGPS ? (venueGPS.longitude ?? venueGPS.lon ?? '').toString() : '';
                break;
              case 'ph_latitudePMBuffMin':
                part = venueGPS ? ((venueGPS.latitude ?? venueGPS.lat ?? 0) - 0.025).toString() : '';
                break;
              case 'ph_longitudePMBuffMin':
                part = venueGPS ? ((venueGPS.longitude ?? venueGPS.lon ?? 0) - 0.025).toString() : '';
                break;
              case 'ph_latitudePMBuffMax':
                part = venueGPS ? ((venueGPS.latitude ?? venueGPS.lat ?? 0) + 0.025).toString() : '';
                break;
              case 'ph_longitudePMBuffMax':
                part = venueGPS ? ((venueGPS.longitude ?? venueGPS.lon ?? 0) + 0.025).toString() : '';
                break;
              case 'ph_houseNumber':
                part = houseNumber ?? '';
                break;
              default:
                part = urlParts[tlix];
            }
            this.#storeFinderUrl += part;
          }
        }
        if (!/^https?:\/\//.test(this.#storeFinderUrl)) {
          this.#storeFinderUrl = `http://${this.#storeFinderUrl}`;
        }
      }

      #openStoreFinderWebsite() {
        if ($('#WMEPH-WebSearchNewTab').prop('checked')) {
          window.open(this.#storeFinderUrl);
        } else {
          window.open(this.#storeFinderUrl, SEARCH_RESULTS_WINDOW_NAME, _searchResultsWindowSpecs);
        }
      }

      action() {
        // If the user has 'never' opened a localized store finder URL, then warn them (just once)
        if (localStorage.getItem(SETTING_IDS.sfUrlWarning) === '0' && this.#isCustom) {
          WazeWrap.Alerts.confirm(
            SCRIPT_NAME,
            '***Localized store finder sites often show multiple nearby results. Please make sure you pick the right location.' + '<br>Click OK to agree and continue.',
            () => {
              localStorage.setItem(SETTING_IDS.sfUrlWarning, '1'); // prevent future warnings
              this.#openStoreFinderWebsite();
            },
            () => {},
          );
          return;
        }
        this.#openStoreFinderWebsite();
      }
    },
  }; // END Flag namespace
  class FlagContainer {
    static #flagOrder = [
      Flag.ChainIsClosed,
      Flag.EVChargingStationWarning,
      Flag.PnhCatMess,
      Flag.NotAHospital,
      Flag.NotASchool,
      Flag.FullAddressInference,
      Flag.NameMissing,
      Flag.GasNameMissing,
      Flag.PlaIsPublic,
      Flag.PlaNameMissing,
      Flag.PlaNameNonStandard,
      Flag.IndianaLiquorStoreHours,
      Flag.HoursOverlap,
      Flag.UnmappedRegion,
      Flag.RestAreaName,
      Flag.RestAreaNoTransportation,
      Flag.RestAreaGas,
      Flag.RestAreaScenic,
      Flag.RestAreaSpec,
      Flag.GasMismatch,
      Flag.GasUnbranded,
      Flag.GasMkPrim,
      Flag.IsThisAPilotTravelCenter,
      Flag.HotelMkPrim,
      Flag.ChangeToPetVet,
      Flag.PointNotArea,
      Flag.AreaNotPoint,
      Flag.HnMissing,
      Flag.HnTooManyDigits,
      Flag.HNRange,
      Flag.StreetMissing,
      Flag.CityMissing,
      Flag.BankType1,
      Flag.BankBranch,
      Flag.StandaloneATM,
      Flag.BankCorporate,
      Flag.CatPostOffice,
      Flag.IgnEdited,
      Flag.WazeBot,
      Flag.ParentCategory,
      Flag.CheckDescription,
      Flag.Overlapping,
      Flag.SuspectDesc,
      Flag.ResiTypeName,
      Flag.PhoneInvalid,
      Flag.UrlMismatch,
      Flag.UrlAnalytics,
      Flag.GasNoBrand,
      Flag.SubFuel,
      Flag.FormatUSPS,
      Flag.MissingUSPSAlt,
      Flag.MissingUSPSZipAlt,
      Flag.MissingUSPSDescription,
      Flag.CatHotel,
      Flag.LocalizedName,
      Flag.SpecCaseMessage,
      Flag.ChangeToDoctorClinic,
      Flag.ExtProviderMissing,
      Flag.AddCommonEVPaymentMethods,
      Flag.RemoveUncommonEVPaymentMethods,
      Flag.UrlMissing,
      Flag.InvalidUrl,
      Flag.AddRecommendedPhone,
      Flag.BadAreaCode,
      Flag.PhoneMissing,
      Flag.OldHours,
      Flag.Mismatch247,
      Flag.NoHours,
      Flag.AllDayHoursFixed,
      Flag.EVCSPriceMissing,
      Flag.PlaLotTypeMissing,
      Flag.PlaCostTypeMissing,
      Flag.PlaPaymentTypeMissing,
      Flag.PlaLotElevationMissing,
      Flag.PlaSpaces,
      Flag.NoPlaStopPoint,
      Flag.PlaStopPointUnmoved,
      Flag.PlaCanExitWhileClosed,
      Flag.PlaHasAccessibleParking,
      Flag.LocalURL,
      Flag.LockRPP,
      Flag.AddAlias,
      Flag.EVCSAltNameMissing,
      Flag.AddCat2,
      Flag.AddPharm,
      Flag.AddSuper,
      Flag.AppendAMPM,
      Flag.AddATM,
      Flag.AddConvStore,
      Flag.IsThisAPostOffice,
      Flag.TitleCaseName,
      Flag.ChangeToHospitalUrgentCare,
      Flag.SFAliases,
      Flag.ClearThisPhone,
      Flag.ClearThisUrl,
      Flag.PlaceMatched,
      Flag.PlaceLocked,
      Flag.NewPlaceSubmit,
      Flag.ApprovalSubmit,
      Flag.LocationFinder,
    ];

    static #isIndexed = false;
    #flags = [];

    constructor() {
      FlagContainer.#indexFlags();
    }

    static #indexFlags() {
      if (!this.#isIndexed) {
        let displayIndex = 1;
        this.#flagOrder.forEach((flagClass) => (flagClass.displayIndex = displayIndex++));
        this.#isIndexed = true;
      }
    }

    add(flag) {
      if (flag) this.#flags.push(flag);
    }

    remove(flagClass) {
      const idx = this.#flags.indexOf(flagClass);
      if (idx > -1) this.#flags.splice(idx, 1);
    }

    getOrderedFlags() {
      return this.#flags.slice().sort((f1, f2) => {
        const idx1 = f1.constructor.displayIndex;
        const idx2 = f2.constructor.displayIndex;

        if (idx1 > idx2) return 1;
        if (idx1 < idx2) return -1;
        return 0;
      });
    }

    hasFlag(flagClass) {
      return this.#flags.some((flag) => flag.constructor === flagClass);
    }
  }
  class HarmonizationArgs {
    venue = null;
    countryCode = null;
    actions = null;
    highlightOnly = true;
    /** @type {SEVERITY} */
    totalSeverity = SEVERITY.GREEN;
    /** @type {number} */
    levelToLock = null;
    lockOK = true;
    isLocked = false;

    // Current venue attributes
    /** @type {string[]} */
    categories = null;
    /** @type {string} */
    nameSuffix = null;
    /** @type {string} */
    nameBase = null;
    /** @type {string[]} */
    aliases = null;
    /** @type {string} */
    description = null;
    /** @type {string} */
    url = null;
    /** @type {string} */
    phone = null;
    /** @type {[]} */
    openingHours = null;

    /**
     * Will temporarily contain an array of information
     * during matching, but eventually contains a single PnhEntry object.
     * @type {PnhEntry}
     */
    pnhMatch = null;
    showDispNote = true;
    hoursOverlap = false;
    descriptionInserted = false;
    aliasesRemoved = false;
    isUspsPostOffice = false;
    maxPointSeverity = SEVERITY.GREEN;
    maxAreaSeverity = SEVERITY.RED;
    almostAllDayHoursEntries = [];
    defaultLockLevel = LOCK_LEVEL_2;
    state2L = 'Unknown';
    regionCode = 'Unknown';
    gFormState = '';
    wl = {};
    outputPhoneFormat = '({0}) {1}-{2}';

    constructor(venue, actions, highlightOnly) {
      this.venue = venue;

      this.highlightOnly = highlightOnly;
      this.addr = getVenueAddress(venue);

      this.actions = actions;
      this.categories = venue.categories?.slice() || [];
      const nameParts = getNameParts(venue.name);
      this.nameSuffix = nameParts?.suffix;
      this.nameBase = nameParts?.base;
      this.aliases = venue.aliases?.slice() || [];
      // Only read description from DOM during harmonization (when edit form is open), not during scanning
      this.description = !highlightOnly ? this.getDescriptionFromDOM() : null;
      this.url = venue.url;
      this.phone = venue.phone;
      this.openingHours = venue.openingHours;
      // Set up a variable (newBrand) to contain the brand. When harmonizing, it may be forced to a new value.
      // Other brand flags should use it since it won't be updated on the actual venue until later.
      this.brand = venue.brand;
    }

    getDescriptionFromDOM() {
      try {
        const descField = UPDATED_FIELDS.description;
        const element = document.querySelector(descField.selector);
        if (element && descField.shadowSelector) {
          const textarea = element.shadowRoot?.querySelector(descField.shadowSelector);
          return textarea?.value || null;
        }
      } catch (e) {
        logDev(`Error reading description from DOM: ${e}`);
      }
      return null;
    }
  }
  class GooglePlaceContainer {
    places = new Map();
    pendingRequests = new Map();

    addPlace(placeId, placeData) {
      this.places.set(placeId, placeData);

      const requestsForId = this.pendingRequests.get(placeId);
      if (requestsForId && requestsForId.length > 0) {
        requestsForId.forEach((request) => {
          clearTimeout(request.timeoutId);
          request.resolve(placeData);
        });
        this.pendingRequests.delete(placeId);
      }
    }

    #removePendingRequest(placeId, requestToRemove) {
      const requests = this.pendingRequests.get(placeId);
      if (!requests) return;

      const index = requests.indexOf(requestToRemove);
      if (index > -1) {
        requests.splice(index, 1);
      }

      if (requests.length === 0) {
        this.pendingRequests.delete(placeId);
      }
    }

    getPlace(placeId, timeoutMs = 3000) {
      if (this.places.has(placeId)) {
        return Promise.resolve(this.places.get(placeId));
      }

      return new Promise((resolve, reject) => {
        let pendingRequest;

        const timeoutId = setTimeout(() => {
          const error = new Error(`Request for place ID "${placeId}" timed out after ${timeoutMs / 1000} seconds.`);
          this.#removePendingRequest(placeId, pendingRequest);
          reject(error);
        }, timeoutMs);

        pendingRequest = { resolve, reject, timeoutId };

        if (!this.pendingRequests.has(placeId)) {
          this.pendingRequests.set(placeId, []);
        }
        this.pendingRequests.get(placeId).push(pendingRequest);
      });
    }
  }
  const _googlePlaces = new GooglePlaceContainer();

  // **************************************************************************************************************
  // UI / UX Functions
  // **************************************************************************************************************

  /**
   * Toggles the color highlighting checkbox and updates map display.
   */
  function toggleHighlightCheckbox() {
    const checkbox = $('#WMEPH-ColorHighlighting');
    if (checkbox.length) {
      checkbox.prop('checked', !checkbox.prop('checked'));
      // Call the handler directly to update the map highlighting
      bootstrapWmephColorHighlights();
      log(`Color highlighting ${checkbox.prop('checked') ? 'enabled' : 'disabled'}`);
    }
  }

  /**
   * Creates a wz-button element with attributes and optional click handler.
   * @param {object} attrs Button attributes (color, size, disabled, textContent, etc.)
   * @param {Function|null} clickHandler Optional click event handler
   * @returns {HTMLElement} The created wz-button element
   */
  function createWzButton(attrs = {}, clickHandler = null) {
    const btn = document.createElement('wz-button');
    const propertyKeys = ['color', 'size', 'disabled', 'textContent'];
    Object.keys(attrs).forEach((key) => {
      if (attrs[key] !== undefined && attrs[key] !== null) {
        if (propertyKeys.includes(key)) {
          btn[key] = attrs[key];
        } else {
          btn.setAttribute(key, attrs[key]);
        }
      }
    });
    if (clickHandler) {
      btn.addEventListener('click', clickHandler);
    }
    return btn;
  }

  function initializeCategories() {
    try {
      const subCategories = sdk.DataModel.Venues.getVenueSubCategories();
      subCategories.forEach((subCat) => {
        SUBCATEGORIES_BY_ID[subCat.subCategoryId] = subCat;
      });
      log(`✓ Loaded ${Object.keys(SUBCATEGORIES_BY_ID).length} venue categories from SDK`);
    } catch (e) {
      logDev('Failed to initialize categories from SDK:', e);
      throw e;
    }
  }

  function getCategoryLocalizedName(categoryId) {
    return SUBCATEGORIES_BY_ID[categoryId]?.localizedName ?? categoryId;
  }

  /**
   * Injects a Google search icon button into Place Update Request (PUR) popup headers.
   * Monitors for PUR panel openings and adds a search button that performs a Google search
   * for the place name and address extracted from the PUR popup. Respects user setting to hide button.
   * The button is icon-aligned with other header action buttons (recenter, streetview, email, star).
   */
  function addPURWebSearchButton() {
    const purLayerObserver = new MutationObserver(panelContainerChanged);
    purLayerObserver.observe($('#waze-map-container')[0], { childList: true, subtree: true });

    function panelContainerChanged() {
      if (!$('#WMEPH-HidePURWebSearch').prop('checked')) {
        // Target the panel-header-actions div where the icon buttons are
        const $headerActions = $('.place-update-edit .panel-header-actions');
        if ($('#PHPURWebSearchButton').length === 0 && $headerActions.length) {
          const $btn = $('<wz-button>', {
            id: 'PHPURWebSearchButton',
            color: 'clear-icon',
            size: 'sm',
            type: 'button',
            title: `WMEPH${IS_BETA_VERSION ? '-β' : ''}: Search Google for this place. Do not copy info from 3rd party sources!`,
          })
            .css({
              marginLeft: '8px',
              marginRight: '4px',
            })
            .append($('<i>', { class: 'w-icon w-icon-search' }))
            .click(() => {
              logDev('PUR Google button clicked');
              openWebSearch();
            });
          $headerActions.append($btn);
        }
      }
    }

    /**
     * Constructs a Google search URL from a place name and address.
     * Normalizes highway abbreviations (CR-, SR-, US- → County Rd, State Hwy, US Hwy).
     * Returns null if searchName is empty.
     * @param {string} searchName - The place name to search for
     * @param {string} address - The place address (optional, can be empty)
     * @returns {string|null} A properly encoded Google search URL, or null if searchName is falsy
     */
    function buildSearchUrl(searchName, address) {
      if (!searchName) return null;

      searchName = searchName.replace(/[/]/g, ' ').trim();

      // Handle null or undefined address
      if (!address) {
        address = '';
      } else {
        address = address
          .replace(/No street, /, '')
          .replace(/No address/, '')
          .replace(/CR-/g, 'County Rd ')
          .replace(/SR-/g, 'State Hwy ')
          .replace(/US-/g, 'US Hwy ')
          .replace(/ CR /g, ' County Rd ')
          .replace(/ SR /g, ' State Hwy ')
          .replace(/ US /g, ' US Hwy ')
          .replace(/$CR /g, 'County Rd ')
          .replace(/$SR /g, 'State Hwy ')
          .replace(/$US /g, 'US Hwy ')
          .trim();
      }

      searchName = encodeURIComponent(searchName + (address.length > 0 ? `, ${address}` : ''));
      return `http://www.google.com/search?q=${searchName}`;
    }

    /**
     * Extracts place name and address from the PUR (Place Update Request) popup,
     * falls back to viewport location (via SDK) if address is missing, then opens a Google search.
     * If opening in new tab, uses window.open(url). Otherwise, opens in SEARCH_RESULTS_WINDOW_NAME.
     */
    function openWebSearch() {
      let name = null;
      let addr = null;
      // Find place name and address within the PUR popup using stable selectors
      // Look for h3 or div with 'name' class (place name), and div with 'address' class
      const $nameElem = $('.place-update-edit h3.name, .place-update-edit [class*="name--"]').first();
      const $addrElem = $('.place-update-edit [class*="address"], .place-update-edit div.address').first();

      if ($nameElem.length) {
        name = $nameElem.text().trim();
      }
      if ($addrElem.length) {
        addr = $addrElem.text().trim();
      }

      // If address is missing from PUR popup, try to get city/state from viewport
      if (!addr) {
        try {
          const topState = sdk.DataModel.States.getTopState();
          const topCountry = sdk.DataModel.Countries.getTopCountry();
          const viewportState = topState?.name ? `${topState.name}` : '';
          const viewportCountry = topCountry?.name ? `${topCountry.name}` : '';
          const fallbackAddr = `${viewportState}${viewportCountry ? ', ' + viewportCountry : ''}`.trim();

          if (fallbackAddr.length > 0) {
            logDev(`PUR Google Search - Address missing from popup, using viewport location: "${fallbackAddr}"`);
            addr = fallbackAddr;
          }
        } catch (e) {
          logDev('PUR Google Search - Error getting viewport state/country:', e);
        }
      }

      logDev(`PUR Google Search - Name: "${name}", Address: "${addr}"`);

      if (!name) {
        logDev('PUR Google Search - Aborting: name is empty');
        return;
      }
      const searchUrl = buildSearchUrl(name, addr);
      if (!searchUrl) {
        logDev('PUR Google Search - Failed to build search URL');
        return;
      }
      logDev(`PUR Google Search - URL: ${searchUrl}`);
      if ($('#WMEPH-WebSearchNewTab').prop('checked')) {
        logDev('PUR Google Search - Opening in new tab');
        window.open(searchUrl);
      } else {
        logDev('PUR Google Search - Opening in search results window');
        window.open(searchUrl, SEARCH_RESULTS_WINDOW_NAME, _searchResultsWindowSpecs);
      }
    }
  }

  /**
   * Generates and adds spelling variants to a name list by replacing each variant spelling with all others.
   * For example, if ['St', 'Street'] are variants, names containing 'St' will also get 'Street' versions.
   * Modifies nameList in place; does not add duplicates.
   * @param {string[]} nameList - Array of names to expand with variants (modified in place)
   * @param {string[]} spellingVariantList - Array of spelling variants to cross-apply
   */
  function addSpellingVariants(nameList, spellingVariantList) {
    for (let spellingOneIdx = 0; spellingOneIdx < spellingVariantList.length; spellingOneIdx++) {
      const spellingOne = spellingVariantList[spellingOneIdx];
      const namesToCheck = nameList.filter((name) => name.includes(spellingOne));
      for (let spellingTwoIdx = 0; spellingTwoIdx < spellingVariantList.length; spellingTwoIdx++) {
        if (spellingTwoIdx !== spellingOneIdx) {
          const spellingTwo = spellingVariantList[spellingTwoIdx];
          namesToCheck.forEach((name) => {
            const newName = name.replace(spellingOne, spellingTwo);
            if (!nameList.includes(newName)) nameList.push(newName);
          });
        }
      }
    }
  }

  /**
   * Clicks the General tab in the venue edit panel via shadow DOM traversal.
   * Uses specific CSS selectors targeting the first tab button inside wz-tabs shadow root.
   */
  function clickGeneralTab() {
    // Make sure the General tab is selected before clicking on the external provider element.
    // These selector strings are very specific.  Could probably make them more generalized for robustness.
    const containerSelector = '#edit-panel > div > div.venue-feature-editor > div > div.venue-edit-section > wz-tabs';
    const shadowSelector = 'div > div > div > div > div:nth-child(1)';
    document.querySelector(containerSelector).shadowRoot.querySelector(shadowSelector).click();
  }

  /**
   * Zooms the map to center on the currently selected venue (zoom 19), or falls back to mouse position (zoom 18).
   */
  function zoomPlace() {
    const venue = getSelectedVenue();
    if (venue) {
      const { longitude, latitude } = getVenueLonLat(venue);
      sdk.Map.setMapCenter({ lonLat: { lon: longitude, lat: latitude }, zoomLevel: 19 });
    } else if (_wmephMousePosition) {
      sdk.Map.setMapCenter({ lonLat: _wmephMousePosition, zoomLevel: 18 });
    }
  }

  /**
   * Slightly nudges a venue's geometry by a random distance (+/- 0.00000001 degrees).
   * For points, moves the first coordinate. For polygons, moves the second vertex of the outer ring.
   * Updates venue via SDK; changes are tracked as unsaved until user clicks WME Save.
   * @param {Object} venue - The venue object to nudge (must have geometry and id)
   */
  function nudgeVenue(venue) {
    const newGeometry = structuredClone(venue.geometry);
    const moveNegative = Math.random() > 0.5;
    const nudgeDistance = 0.00000001 * (moveNegative ? -1 : 1);
    if (isVenuePoint(venue)) {
      newGeometry.coordinates[0] += nudgeDistance;
    } else {
      // Be sure to edit the 2nd coordinate. Editing the 1st would also require editing the last,
      // otherwise the polygon is not "complete" and another point (geonode) may be added behind the scenes
      // to complete it.
      newGeometry.coordinates[0][1][0] += nudgeDistance;
    }
    // SDK tracks changes as unsaved; user commits via WME Save button
    sdk.DataModel.Venues.updateVenue({ venueId: venue.id, geometry: newGeometry });
  }

  /**
   * Clears all duplicate label features from the _dupeLayer map layer.
   * Silently catches and logs any errors to prevent script interruption.
   */
  function destroyDupeLabels() {
    try {
      sdk.Map.removeAllFeaturesFromLayer({ layerName: _dupeLayer });
    } catch (e) {
      logDev('Error clearing dupe labels layer:', e);
    }
  }

  /**
   * Removes the associated dupe label when a duplicate is deleted.
   * This is a stub function: dupe removal is now primarily detected via SDK event listeners.
   * Clears all dupe labels if the _dupeIDList is empty.
   */
  function deleteDupeLabel() {
    // Stub: dupe removal is now detected via SDK event listeners
    if (_dupeIDList.length === 0) {
      destroyDupeLabels();
    }
  }

  /**
   * Whitelists a flag for a specific venue, storing venue metadata (city, state, country, GPS).
   * Requires the venue to have a country-level address. Shows error alert if address is missing.
   * Saves whitelist to localStorage and removes venue from results cache for re-evaluation.
   * @param {string} venueID - The venue ID to whitelist
   * @param {string} wlKeyName - The whitelist flag key name to add
   * @returns {boolean} true if successful, false if venue lacks a country address
   */
  function whitelistAction(venueID, wlKeyName) {
    const venue = getSelectedVenue();
    const addressTemp = getVenueAddress(venue);
    if (!addressTemp?.country) {
      WazeWrap.Alerts.error(SCRIPT_NAME, "Whitelisting requires an address. Enter the place's address and try again.");
      return false;
    }
    const centroidPt = turf.centroid(venue.geometry);
    const venueGPS = { longitude: centroidPt.geometry.coordinates[0], latitude: centroidPt.geometry.coordinates[1] };
    if (!_venueWhitelist.hasOwnProperty(venueID)) {
      // If venue is NOT on WL, then add it.
      _venueWhitelist[venueID] = {};
    }
    _venueWhitelist[venueID][wlKeyName] = { active: true }; // WL the flag for the venue
    _venueWhitelist[venueID].city = addressTemp.city?.name; // Store city for the venue
    _venueWhitelist[venueID].state = addressTemp.state?.name; // Store state for the venue
    _venueWhitelist[venueID].country = addressTemp.country?.name; // Store country for the venue
    _venueWhitelist[venueID].gps = venueGPS; // Store GPS coords for the venue
    saveWhitelistToLS(true); // Save the WL to local storage
    wmephWhitelistCounter();
    _buttonBanner2.clearWL.active = true;

    // Remove venue from the results cache so it can be updated again.
    delete _resultsCache[venue.id];
    return true;
  }

  /**
   * Increments the whitelist addition counter in localStorage and alerts user when it exceeds 50.
   * Resets counter to 2 after alert to remind user to back up their whitelist data periodically.
   */
  function wmephWhitelistCounter() {
    // eslint-disable-next-line camelcase
    localStorage.WMEPH_WLAddCount = parseInt(localStorage.WMEPH_WLAddCount, 10) + 1;
    if (localStorage.WMEPH_WLAddCount > 50) {
      WazeWrap.Alerts.warning(SCRIPT_NAME, "Don't forget to periodically back up your Whitelist data using the Pull option in the WMEPH settings tab.");
      // eslint-disable-next-line camelcase
      localStorage.WMEPH_WLAddCount = 2;
    }
  }

  /**
   * Creates and attaches a MutationObserver to the edit-panel that removes scrolling artifacts
   * (separator-line class and tab-scroll-gradient display) when DOM nodes are added.
   * This prevents visual glitches that occur when WMEPH adds content outside the venue div.
   */
  function createObserver() {
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        // Mutation is a NodeList and doesn't support forEach like an array
        for (let i = 0; i < mutation.addedNodes.length; i++) {
          const addedNode = mutation.addedNodes[i];
          // Only fire up if it's a node
          if (addedNode.querySelector && addedNode.querySelector('.tab-scroll-gradient')) {
            // Normally, scrolling happens inside the tab-content div.  When WMEPH adds stuff outside the venue div, it effectively breaks that
            // and causes scrolling to occur at the main content div under edit-panel.  That's actually OK, but need to disable a couple
            // artifacts that "stick around" with absolute positioning.
            $('#edit-panel .venue').removeClass('separator-line');
            $('#edit-panel .tab-scroll-gradient').css({ display: 'none' });
          }
        }
      });
    });
    observer.observe(document.getElementById('edit-panel'), { childList: true, subtree: true });
  }

  function appendServiceButtonIconCss() {
    const cssArray = [
      '.serv-airportshuttle { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAD4UlEQVR4AeyZv28TMRTHk0xMCITK0GQoE1IRvwRSxdZOSPzoxgCCCgkGBkBCdG/yB8AAAhZ+FJBgYCuVADG0AwKGMjDAgJDokJQfFSxMCNTyeW6cni93ZzuXSwMKel/es/3es79nn89xC7n/5F+PSLdNZG9GejOS0RPwXVo7isXihVKp9AA9kxHukPcMfPcAZ/EhcoIO3pL58vLy8lH0cEY4Sd4b9DXX399/BdtJnIiQdArcc8rYRqd8Pn+O2b/qktJKBAIXSXQYaPmB8YROKlmA3PfBV6CE2T+LMQYSxUqE6ONACUlv1mq1IXCgWq2WswC5x8BuOmysAB7mOOVEsRHZQPQuoGRhYeE5xkeQtXxmtl8EOtmOvRHEio1IgwQZFlmvg6As4EWcEO0CH1+dj/6K4DvQslMbUdpGJBjTx9Ka0OCJlbVt0z6+wVx0vgk4iSZSYh3KzvSJqEOg22WU8c6BGQbaB3KayD4KsjMN0Ji0Q8zi10nQXbMwxv3UygdzmGV7BHuFCOtymxTqUAzrtqHYTUY6CaNzszCoi4VCYbPYekbE/qfRI9Jt06dmhC/0u8DAFgO2YfKSJZ14295mdG4W3uvi0tLSN7EVEYxX4DGY52VuHA0ohyWrE29c3nD/qswYn2G8AbOcNh6hV3YtjCqNo2AL9jTodplirHvBCANVK0jPCGW78IVOdeLlq10O54iq0z72Ea16eBHhXUp14mUZVMI5ouq0z+ow7ZYXEXu6tfNoFxE5MhxsgYbEyYveQqgZkpoIW/JL8BRMg9dm+vgSvjpObdvxnm4taYmU6EYOnCglQ/w/AGwSjpNZUYc/W2Bce1oifyIS/46oC1dFxaltNOzoWk5L5Atb5S3dGfZt7BqwSTjuGgHy8wDVmqQlkmOrPM2HaZ0A+5TrMPDVceux5abENTTSLzWRetZfaAHKSyTmp1dEjHO7iMSk71y1FxF+SaobFJvm56fzDUtSLp/H4EWEc1HjFiXJ5qV3vmFJypMZEZ/Enfb1mhGedNPpNzDg2aj2YB1P3zj9EtvYcoN+2qbdWbyIsE2GT7+NgUiPEe2Gf/ikKzFBhOODbTbbi4gt2Vq2+xKRM1EQ7R67zq21c34bEVk68zobJ1Z1Ug1q3caX/bq2XTUxd7Uv789EMK/Yug0tY5CxYEaLjUiOzirRoU216hKgqTa5YpJmuURAxYvLGKxESD9Joq3oS0CeigF2mPO052lrSYiVS4RjBBt5pUzuCu1yISKEqYoXFyIS/YGE46Dp7pedxulvfJIkAQ9jcpeJkWWFShZXIslZuqC1R6QLJsEYQm9GjMfRBYW/AAAA///d8GdKAAAABklEQVQDAOjZlZI1xoAxAAAAAElFTkSuQmCC) center/contain no-repeat; }',
      '.serv-airportshuttle-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAECElEQVR4AexZO28TQRDeTWG7QiAEDSlChRTES4kU0YUitsQjHQUIIiRQLgUgIdIn+QFQgIA44hFAgoIuRILYRVIgoEgKCigQEilAPCJoaGKDsnyz9lp7l3vs+uz4QLbm887uzszOd3ser88d7D95tYkkbSPbO9LekSZdAdtba282v3opN1V6NDBVnm8GcpOle7nJ8gj49gDGYkPkdDZfesMYvyoEO8GF6G8GBGdnBBe3sNZidqp8jRm+jIgg6AzwwDBm48yEuDCQL183CRhJJJdfvYxAxwAlP6E8AyaaAcHYQ8b4N1Z9cSbOQx0CQiWSiGD8lIqAoLcLTroPOAyMNwNFJz1UcFIHGGe1OwB3w6jKIaiNIrIZjvsBKXNOpgjlA9Bs+cIZf6Etsgf6FiBQoojUSDAmVnBluoHxKsaqreqHtTa2Mo4QYgey/gEo2acUvzaKiObDt6EzpmFc0/VxP93GVvffijWMRBHpxNWlyvQRXkeBpMsg8l2k7zEkSheYKSIHMUCVqQsGgRVCcL6wkUBOvpKdXM1hooe+x/Bdcxx6hQiS300dAhKVDEn3ojicOrSR8K6v+qKjo1vpTIjtpKsdIf2fRptI0rZP7kjBSb9VieEDtKJ0b0tVIgQNPw1711d9vrb2TumM8++kSyJQXgFPgWWQqh0N0HcJSDblxBsU17W41imMZObQXUJhWigMp55Ar1QtKJ9AYBDYCX0WSLrMINdeqqBIVN5BakfQN5K4J176hvfG8BtTNkZJkZEVEVyFuCfeCZ8YfmNyHUrQFFZETIO2wq5RROjIcKQOAuTXX4ffOpfYRHC8eQk8B2aB1+tWCBiArfKTZTvAzHg4LpFOrEQHTjRS+vDeBUSJy4/KLxzk4Q9tXRKXyB+fVX/7jHmH/PxkGfUamvbjEvnKuLijLXYX+mcgSlx++Fl7Aw4LQN0SlwgrDGfOoaRmqjhrmonmt2nOSdGTElNXX7vYRKpRS2gJaKyEfH5ZeQQYN4pIQPiNG7YigpIpn3AYtGMGNpGxbC6DFREE1p9whOl0fgqbN53DkmZiS8QsagusbImoU6neyrTptwEUfdxPp52qjVd94CalNo6e0qGaiRURlFh5KtVaV+3Xxr12qu866XpT9Pp758P6VkTCArV6zpYInVR1NDp/FVu1xvGjiNCts6yioaTOe6Hm8LPzptJNW/jc12ypZLvia3OUA+WiDbnVKCIM9y198Nxe/j35EMB/KnB0GjNLQKiY5BBJBCtMI9Au/OFzhaqMF3hkeRHzHHZ1CXx7gZPeuNRHQCoO9ECECKMbLCZEyPt90UmN4lZY9+wXj2aM/uOjICF47BvbSVO5ptsqxLUyZUqkYp3g9zaRpG1Oe0eStiN/AQAA//+zXvm6AAAABklEQVQDAD4ni5Jd4gdxAAAAAElFTkSuQmCC) center/contain no-repeat; }',
      '.serv-carwash { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAEz0lEQVR4AeyZP4hdRRTG71osEgisJDb71koMUUkRiEhMikgIBBuLsIIQg1hIEC22EMHG3SqxSEgjKWx0IYImELGxCEEtJGBjEyFC0Oa9t80SkjIQdvP7bs7czBvuezP3byBkOd+dc87MnHO+uffN/bPPZU/JX9dEnmedPjBIR+1GuiSydzAYXBsMBuuGa1DYCzqRrogcp/gbVHwYODlsvuPO0WbbOhGKPQ1+pcgFEMqC+sDpsKOp3SqRpaWlLyjoIvDlSwyBppCLNrZwNFXaJLK8vb191i9obm7u49FodEaQ7vfZ2GXf10Rvi8gyl8tPfiEU/+JwOPzW+aTL52y1NqcVMm0QKSPxAoVuglA2IaO+wt8WmaZEjlkhRWEU+grGXTBN7tqYot9iHCscNZQmRHZTwNd+Tgp8C/s2iMltG1uMs1i7C0dFpTYRdh2R2O/yUdi76Lp30CTJDZvjBu+3mM6u1NYisri4+Cm7zkdeJm2vv3h2qqo5mpuPV0zFzo2KhzpEDrGV6mzkqUh+lZU9kxs1DpqrGG6qxT7k7NS2MhGuZa3gDkswGo/Hss2s11iMkc3eYTnMTGuqEtGe/44LzWqKxC1nN2hvWSwXQjmUy9nRthIRVuoTF5FL4Bv0ddCWrFvMPJ6fK3dEDlWIfEisI0CyyZ164nFEzqawmO5GqlzKmRQ2mYi/Qqzcd0QfgrZlaLHzuH7O3DHjkErkBDHeAJIHrNz3UrqAxX5gsZVTuc2c3iQR4UblB/uBcDdBKG3Ziq0cebwgd+4rO6QQ2ck+XxBhd/m5LFCbPj+H5d4Zix8lwnX6PkHmgURb7VUpHUM5lEtp5q0G6VMRJcJMEaHJMn6IP3KqV/uAcmWP/4oaHrsmtRQiAzeF0/xVn3B5aYsa0EulEpHSCP04GxPR25x7rtKltcYprw3O5mo4v8znxnhrpBpUi+eaVGNnZGIl2ONXm4CHw7VwfpnPjZksNZuoJejLYkS0EuGcJ2XPrCVG5N+gaj3/9Ak/fViL3xc9I/cYvQFyYT//rU/kSR8dVINqeWSVHGNnRFNmroQG9IBoDVEi7CB/eIX+jt4nSJdpt/RryMr+okTYQf7yJh7hOegUeLtjnCKnfos0WRbUkPvCQ5QIE/4GhfCV4/PC6EgpyTFRQ1naFCL6oZ1zk7nUPnN6V22QQ7lVw8x0KUQyLqNzBP/fRWLn2u4SLo9yKrezZ7VJRAiwsbW1pZVB7U8sZ/RsqKJUIhmPEvpqclmTesJly5mULpmIonGa36MNPzroG+5R+uZq4igxw2/GQ2IpF11pUomIQpLgJVr39oaaHeT3ch2cx6jyNV1f888z7zrzDgIn+linHM5OaisTUVTIvEp7AfiyQlF/8vao/yPqHf81v9N0+U5ojMbiWwG+XLDYvi9Jr0VEkUm4wq6yhj4GTvbwfnGWIq+Af8B97gk3BelAvisaw4Q9wMlYsRTTOaq2tYkoEXfcVZIfUBHYPiHMXObpe13Ach8wUAtxBA4oVuGtoTQiYvk2VASE9oGTrPYl/HfANLmjMRoL9mkuA5O2WMZNlTaIuOAq/hJb5kkK3AUWwMvgTYN0+XZpDJNihBmSLm0SCbPq/eE/nHroFKTLh6t96ZJI+9XOiPiMyIzFeSJdDwEAAP//kXRIQgAAAAZJREFUAwDBxSiSrKvqZAAAAABJRU5ErkJggg==) center/contain no-repeat; }',
      '.serv-carwash-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAE8UlEQVR4AexZQYgcRRT95WEmBAIrWU/iSZSoeBAiEuMhEnYnePGwrCDEEHLYCaKHHETIxc3J5KDkImbWiy5EiAlEcpHMIupBFrx4WSFCSC6b7GUJyS2zBCrvVVd1upveqeru6lkIWf7r+v9X1fv/9cx0ujvPyVPy17aQXThPn1jQh9uOtSlkX2/wcGV2MFom6EPCPqAVa0vIETS/qkW957qmzxziI0B0iy6kd2HrJBr+DZ1OAUWb4hzXFCeaxlGF9C6MvtRKf59rSslpITJJruHaTKqxG1PIvFZyNtuRUmphuND9mqCfnbNr57O5Jn4sIfP4yvySbWTY775wfaHzg8vRZ87FHO2eKGJiCCkT8Twa3QSKtgkxnEvzscQ0FTJjG0kbQ6OvILgPbGf37Zp03nLMpIkaThMh073B6Fy2Jhp8F/FNwGc37dp0neWaThMVndpCcBbPaZG3XD009iH8VSDUVu0es55c5DRBjUMtIb3Bw89Q6wSQWHJ5vZYElY7XJNnrNp2w3C4OHusIOahFPflKabnKy2twxcJCsxccLm25D7o4dKwsZHZpdBrkuwHaneHJLmP6tWE57liC3baGDcOGqkLmRcsHjhrfcYq44eIG4w3LlVAkNSr9+1JJyMzS1qdJJcFXW30nIstALFtWYjgNX7aWSXgOVYQcV1ofIp8SvXm938ndjjDfFOQkN3lsreP0QxAsZGYwSj8NrdWPIF8HYtu65Ta82ZomMeYQKmROibxteR7hx/mT9aMPlvsRiW3NOfo+BAnpLY2ekCn5GaRrQNFixWv4AbKG4cvVNpnyQ4iQPVpLKgTX/V/LqeJlszVs7T0+dq+Q2cHWxyDpADReaq/SaRmswVos07E90N8WXiF4AKIQR3AJ90OLkwAKXgKMFXowueLBKwSXwRczm76CP0mgnEihByn78wrBpqwQhDti3h58Qvg05+6rqOAMDk2wWLK/LOdqYLkx9sBeTFB28AnJnQncDy02xJmS/WU5U6fQcK6Xwpz4hPBMFPfsVDy2F5+Q/wtd815rksiWL/aSnfN+Ig+wegMwhsvuH5OEKZoc2AN7SaKSo+8TEa3U2DNRwhk9FdKDVwiu4X+5zkD45yTh6mZ7cLni6BWCq8w/bhMID60sdI4B77eMY6zl6mZ7cLni6BWCDf8Cqc0ubX2RBi05JTVyPZSVDRGyoUV9k27W+vPUb8vJ1LC1+WMfWy1EiKz0OxRy2zHhyqXbhKuD8batDXe8BQkBBT4VTTFwJ2daTE3vp8GOQoXgU9nFtyaXuWlCuLzSNzWDygULIRuuHh9hLL504Dvcw5hTNXEYnMV3xuvgYi1MhVklIaREgZcwuqc3uHIAv5ff8RT3LYIqb9OnuYd7se8A4Iwv61jDxUFjZSFkhZjXRNR5yf3pU73B6G/7f4N8xn89N50EzM1xDdeK6FNJ2h3V+YTbxeFjLSGkH/Y7bILPDXcZE1rkVa3kLM7yFeA/YASsWdBn7grXcC33WJADt/OG06aqDbWFsMyw3+Vzw374OUGInfGlxRsICPpwc2YFdPeTKzdTMWgkxNbaYBPAm8BR0XIR+XvAdnaPa7gW4J5FLAy6xGLdthZDiCNn8xfxpvAoGtwLTAEvA+9Y0GduL9dgk08wloRbTCHFqnx+uIUkbzoJ+swhFd/aFBK/2zGMz4SMOTk7MvUYAAD//4gLCk0AAAAGSURBVAMAlZ4AktVwHWoAAAAASUVORK5CYII=) center/contain no-repeat; }',
      '.serv-carpool { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-carpool-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-covered { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-covered-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-ev { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAADrUlEQVR4AeyYPWgUQRTH764OYuVH7kQbG6OViAQCKtiJwU/8AgkBm1gpErDKpdKgxEosLAIGNNgEQQkIkthYSARRtBHB4u7sRDQIFhJ//8nNMnfZHHvZmVwu3PH++97M3r55/3nztZvLbJBfh8h6S2QnI52MBOqBZodWvlAojIHpfD4/mxIT+Cn64tUMkX4Cf7e4uDgMThDA4ZQYwM8IPt/ipwBSSWIiNHiNlrYC33IA39fTOk1K5BgNKQMoI5PZbHY0mw5PjKelizpp15K57LqJITgG2fvc2QNiJRGR7u7unc7TM+Vy+XKpVCqmxEV8/gFW3I6yddK9DMFhjCHieIqOJZOISC6X24IDI2RBY9rYHi6RL3p9pYz8te3Qds9KZBIRsY5apOcgMGrbxo4l0w5EMhrCEIgjE2WxLYgoG3FkWABGdE8IRUQTV1Ab3lBPBscDwEx+70SYtEV6yuz6NBKEDH5/ACu9MrwTYak8LceB8cH6p+N2yPZN5BRO9wLJApc5sCbilQhD6qwT9SPHDm76JLKbaM8DI+z+E8ZIeGFIHqIjzNyqah1b9id8POONCI1HJGj8DZgHzYgWBhfj+JxnJ7+RxIk3IjR2FViZtEZazUZ4Bx99oKH4InKGVuwRf4HGt7GaFC241w8aCs/UnKbdP5OZu245zvZChIbcbHQx3kdccP8ZjV8CDUUbngVzrMf580Hs6DiCvUy8ECHoj8s811WQHS0GdbUNi5/r7oYnUqlUbtUPDZXdQOjph27Zt+0lIwT1nUBrXrSoMzsuWjLDpQyCiS8i9QFuZrgN2krGe9BsqJ0gRJjc5+S8ii/oaRBUghAh4ivACHPF/chg6kJcQhDRsUJQvP+YO2ty5vJOhGX2uBhUoWx8rdpBlXcibrRM8im3vAo7eg2gg3QOkwvtJ9bW+/x7VXonwlAqQuCIQAMvwKqF+fXaPswqqM+rEywks7YOXQKGrHciOJbIuSB71aBTxiHwyXGgd3RlxFaNY/wE/o7xchYAvzg16Ltw9GrrtPGArN+z5VAZsf596JcE3AcuMNRuogfBUTDkOm8HIor3N5cphtpttN48X6FrpF2I1AQdV1iJyHb+rCXOgmKN2Pq0usYpBetPuotyYoklwhJXAdGHAFaO6NOkbPdeGpsoFTAqk4nx+9jcSHiJI+IubwndBPnbvma8xhH5xoqgY4b2gZaBGE6mJaLnn+PI7M6t0gRhjh7oRBKXkUQPrrc/dYh0MhKoBzbM0PoPAAD//zKQVJkAAAAGSURBVAMAy97udAescRQAAAAASUVORK5CYII=) center/contain no-repeat; }',
      '.serv-ev-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAD4UlEQVR4AexYTWgUMRR+8dDtQcSTP6jYixerJxEpFGzBbg/S4i/+gUjBttSTIgVPW08qSj2JnXooKGjxUgSluEVaLx6kgih6EcGDPzcRLdJdkPi92c00O03bbCdpu2WH9817SSYv70symWTW0Cq5qkRW2kBWR6Q6Ip56oNyptaU1yN1IB7mRlsH8eBK0DuSG4KfPFa9yiLSng/wbSdSLxg8JKZuSQAo6R0SZliD3GnorkEisiaD3LxLJjYlaM1QWRHvRQZcMRWVl2RI5yL2vPEuiB7CvJoEQ9Aj1iyLRSVRH5mtdK0/ngek7KN4JGMWKSHowv12rPTrWlTqb7Ur1JcHzztRp+PwLKGlSRkw3oON6SYgevFOPUWYkY0WEpNwAB0p4Tis7kZZCRL4Q5FwjktMaqcdzRjJ2RDRPy2BOoE2exlChGMlUAhHiKQwKJjLRKFYEEZAwksG3KMNlDF9E+MVlcBvOEB8ZWfgWhS+/cyJ4GfuAcQYY+CLzE76VNLDhnAicHgW8ihTinWoAHbaNbddEjsDpLoBlCjdecaD8i1Mi6J3jUchS3o/sJTBcEtmBeE8CoWS7a4dCw/KG6bIf+7loR50OpnnbsseyOjkjgl1sRAKNvwImAWvhvZwOItGfDnKT6cHpy2RxOSMiSFxQ7QkpeFOpksm0FDfhoBGYV1wROUYzW/wpKeQm9CYvwyEQQTuwkPCXW0f0PHzdihJzGE6IYG5Ho4F21gIZHQjkCdJngHmFP3ga6rWH98GOtiOwZ4kTIoLo/SzPsQyQ4cUgljtv8mOs1D+RbGfNNTSqTwtlI7sg6Ol7BcvP3cmIILQfCLTkoIW88IsLzTKK2zfAm7giEg9wPTI6gFBA0utocCNeiLQE+RPsvIhP0COAV/FCRJA8r6KWUv/JoHLdax9EeFvB4Gj/jXWnlmTP5ZwIltk2ZlAE//L5XLS9KudE9Gjxkg/r6XJtbCSjYwA6SB3S6nhPpnyhjbdsOycCx7wMN0M3o4FnwKIFAb/UKmdwRuf/xeNa3lfYIVnnROCYhZ0z2F400Bn9qPwBCEUWzujaF15y+S8u9EWEfbvAb5Dh/8LR0XbGqbyb7aq9rdIrnQjHmQWZRuAUElegO4ADINGDdCSVQISD/YPbMAhch+aT5wvoEqkUIiVBmxJzEdmMh3m5U0CyRFR+Ul3iFAnljzWfa5BlJ0YiWLO/A+FPNtZwxQclqFAynOcCWF454NApbnG/D5FnLSYi2vJm7cfHg7vLcWoi8gUvVZsUYmI5gRgOJyXC9Z+OddY0LycQRLj1gLYS04hYVVxpD1WJVEfEUw+smqn1HwAA//9ZOh+jAAAABklEQVQDADwdJYOEIx4pAAAAAElFTkSuQmCC) center/contain no-repeat; }',
      '.serv-attendant { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-attendant-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-parkandride { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAF8UlEQVR4AeyZXYhVVRTHzw2KgsiCQJ2ZIJrILEcEJRsfMh8sQpt6MXwPFBNSKIIgyB6CgqwkihTqraLxIRrzIRNKiWYKrWzKNJwQ5iMliAzB0Ifx99/sddxz7vm891wVmWH9z1pn7fV59jn77DP3uuga+Ztt5GqbyNkZKZiRGxmfB+4FD3pIlk5jqOqlOmdkSVdX1wvd3d1HwDnwF/gdDHtIlk5jR2RLK0tALdR2IxS5AYyAnxqNxutUtRgU0WLZygfId0ORQ9F4y41QgBo4RIKdYDlIowsof/WQjNhE8t1JvEOg5YZaaoSEX1COGlgKd8QV/g9hP3h1cnJyAPSCG0Cfh2TpBmQD9nsfREeKpYYU2ymqHCo3QhO/kGANCGnHxMTEIgpeDV5iYA/4EyRJuj2yAavlg8EOENIanyPUFcqVGiHBv0TsA0ZDFLQSbEUxDqrSuHzBShyHgFGfz2Xnhbx0IwQ+RbQ5wGg7BTzByUHQLh30sbYHgeb4nIEqWyzVSE9Pz5eEmAsckXQFeN6d1HhQTLAiCDnX5w5U6WJhI1yVwenp6UfMnUR3Iw+DTtGwz+HiK7dqcCc5h9xGCKDlcJ35E3Qz8hjoNI35XJZnna/Fzpt4biNYqxGYo6Gpqan3nJR90O33MMNZuJ2xUuRzhQtAWEtTjMxG/BXQ2u6cmO7wQXS65AGfU+DrHPzN2HFQdEFc6ETOpfhlNpPZCJGeBkZa64tWJ82C2efxexjcRFH/wLWhhGWSciq3GYQ1mc7xrEaU4AFnEUVnE1fGqwvZN1gkMYnO6Daa0aJxvSnSuM991o+pJtXmTy+x1EbYmer94Kx46H5EqPyyo4BVKehhW/IK8WJieX0rPkkXxn0NbjSszSn8IbURkj3pxyNkNWKnebzUGNuSbTQYL+cUqZXwzjznsAbkuLbQJ60RffjoI8jZkfSAE+o9fEU47cdgju5zx4xDogbVphpnWKc1cusMiyjSJjGhquX0D4vC7XK/yRk8WUOyxqioEX1DaMeaEb91NbdUvG/jnXGyIJJqUC1mVrmR4+ZZM5/Pva4PKgv7mwk5PKylVCM5sWoZWsCyq48y+xw4TNSjoC1Ku7X0zWFBF5hQlbOsbkuCBgbBt8R6HDjiQX7TCcWHsJawRudZ1IheVnc5y4oHnoGXkyCENqDhfmsvuo9BEakG1WJ2lRuRY9GKIpuqOMdMrAdrSzom/21UqpH/CR4vd9wK4UPJUGlKbk90rs3iJhrQNuPTspFYnh8LbFWbagxUUeryG3FLfBRYtdQIxaZtUTajf5/YKgZWjljh4kYStcUB0p6RiHV9n1kQRBu1O+z8CnDdft2WN6zNdOKpjTDwM/geaHZu4fZ6TvKVALnDbxDVpNqaSslqRIYf6uCxBf4QyCM9A+4NzSx+kGdYYUyzES/V+IU1cXqJMhvhXt6FmV5WsCjiyhTOCj6raOJZdrivOac2D+QMZ+Mw8VVTatTMRrx16DhA4De8PoudpIl3GDwB2iKfK5yNsJam2LmN+Cugl5Y5alb67aSDXDmUy1Ls9bXYeRPPbUTWBNB9OipZ4Ep9B+8FnaJen8Pij/oa7DyVFzYiLwLpN48zkgUSneAl9YzkOqGYih3EPONzB6p0sVQjciWgts6nJQs81O+S9HPkotUMk0JaS6whxQwsT/ucgSpbLN2IQhB4HsnilyU6LQAHKOJt5FZemq4B/PXZGz/YjUZjn3IRszRVakRRWZUehe8GIW2hmKPcGmpK/xXRPwi0Yw1tJEunf39+gq1+T5zRgAzAbp8DsTxVbkShuVpPwTeC+D2DfDOzpdtsK019BsbAeTDqIVm6QWzXY6tfeBFjUqyNPnasLCu01IiCk3AXWIashn6Ap5G+IRYxIEhGbCL5qoFlxMt9VzR5BoqWG7EYSg6Wg352pnqjH7OxHH5MtvIB8m25AcvRdiMWCD7CzvRFClsIbgLzgeR+uCBZOo0tlK18QC1UZyNhQfrw0U91mp0RBgTJ0mkMVb3UqUbqrbJEtNlGSlyky2pyEQAA//++mbE1AAAABklEQVQDABzSRYPbHwvNAAAAAElFTkSuQmCC) center/contain no-repeat; }',
      '.serv-parkandride-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-security { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAEsElEQVR4AeyZXYhNURTHz8yboiiKmXlQ5EWhKE+KB4V8f6R8FAoPIiGFNKMmpWjKg1DyWTyYfOUBY5AURRohCpPmi8nIg2YePIzfOvbe7XPm7HPPOffe7m1crf/Za6+z1trrv/c+5+y5qr1h8q9CpNwWsrIilRUp0gz8d1trLhNZSjB8vORakS21tbXN4FGJ0Q6NTcApcURmUfxxIleCUstEajlPEVNApDiJ1NTUbCRiDCgbgcw2VzFOIlVVVdN0EPqRrq6uqhJhnq6DdiaIFCeRSO8yNlaIlNviVFaksiJFmoFy2FoTC8GtpETq6uoa+Mi1CyCTF6FCEqGW5MLJoX5wcLBeRQgJOZSqbvqmJESEBKeFBl0uhN6h3waZJY7IgM7KQCO1nm8bJkG+tu7u7lW0P0GU2GObmsKOTiIUbxIzezXhwCz9CBKvOL8JiY+ufDw/Zmy7prC/kwiOhggJJtDPS+TBZkLMdiLZC0XiM7pTiDFEcDI1oQfESaS6utoOspMFEjDLV8BbjGtBpAgJJkM/2OLzDBKrUb6CWCHOTGKopkCck0hnZ+cHy9Mks2yeFMiMrQdT2QLXuLcFBER8KMYm8USR6Aw4ujtm7FBNgQgnEbzsfTuK/pC/zkh8BbsRyJyj8J3agN4QIvFQkfimfRK09t8gdk2B0KREPIpcEoj81/lEYctQ+4EvFH4S3wNhEtjv4Svb6YfvmOwyGzd7W2ci0s+WuU8iLYu1EmpvU+BybHaBRync3k53ecUKiV/4JRYmY6F2VrWYCdN23catiEcxzdqRVr68k2mj5AFk5EeKjvBNCrjFvTXYf4NUwvhm8tDtWobkiSVCAS12hD1Dtl3pT/EXMvbyN/McCQnnh0zFRjWyrczzQe5ALeGAWCI4fwGXgC/Myi6U8cAlLxlQPnCncDiBLiT+oKcWnrO9VpDUILVYpqCai4hHMRetkMl8Mw5a/Sj1HTE7wD5uDoIsspkgmQQaL1yDbwtfchIhoJV9fpPWF3R5vZqH0DcW9jKe1TCTxXgydmuuIZIQ8djnh0jUA3zhWTngK0W4qBXXL5UeNXbOkRIRIct7toqQQfXkbTaHWbvsdwp4YYIaWAFZcT+rGvO938lxSUpE0shvr2dEUdgAmdNKz7sRErxM6q1EMpaMaZncahoi8tDJqrRZ6bZDRn7otkzp1QgSbWo1EidLRYSsfQwg34rn6Fr2SiG6k7IdzUQ0hVbiuRqjL02utEQk9xcGWsbg96QjQK+noLvo00FSWU7MI5x3A1/II+cxObvFfjN859AlCxFJ0cvZaQXKdaBlEYW1sDpbtcHR4lbbxOUG92cALddVzl5tSNNmJSJjDLAy8tFqlI7CWGb1LEW2gu3KpptRkDyG/TUGswroIo0qV5ajjMR7+RDxE1DAYbCOjvnOoMv/aZym6A5wCDSC75Dcz71xQEuPxILD2pC1zZuIGvgqxSxAvwBsqaMjKyZvuxHotlxQMVdtY1a9UERk/DcUthnMp3MHuOSO+AA5T71xOaW1F5KIHruFIpeCSRj2gMcKe8QGltKPPZJzP7UUg4guQl7TTRQ+T6GJG6lfq8QkkmISSVRAoZyGDZG/AAAA//+OS8+lAAAABklEQVQDAHPjHIP2ioUiAAAAAElFTkSuQmCC) center/contain no-repeat; }',
      '.serv-security-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAFC0lEQVR4AexYTYgcRRR+NYedFQwoKMSfQ8DgRVBBwZPgHrZXRZOYGISoYAJuL4giRoS4yK6wCEIk4EG2F8S/QDxkURNymN41UUSIoEhCEhJINoQE8kMScgjJzARS+V71X3VPV09Pz0xmSGZ4X9V71a9eva+quqt7SnSH/AZE+m0hBysyWJEuzcBdt7VewET2Ehg+W5qtyKZRpzZvObV9PcZJ0HgHMEoWkWeR/FZBtNbY+/ZdWIFcvsNwjwOpYiRiOfW30eN+oG9k1KmPm5IxEpGCntQ6fe7aZdEjjIR5CHom1BOKkUjCr+/NAZF+W6LBigxWpEsz0A9ba0UnuPWUCE7raeAkA2TaItRJIsglvyD5KXgzUBGT4JdSKvrrCRGfxLSW9GHou4DCYiQipLweRJUk7g30dusUEgfw6rMOcS8DaRKOreeUdDQSIUlh4JKQDyc7FrFTSPznkzhmioeX12hsLaekv5lISYREbpJ4KNmxVRskeCsxgq7/+CROBA3ptTaJWk5JXzMRKUMiJakFS0SwZmvbkeQhNL8BpAquM4HgxiYS8m+QeJ2ITgFNREaTqOWU7GQkgoGOBs6SKAoWNKJWCQp6E+oT0H9GvQmICdrjJEj+6Y4PM4kzMUejEe0GPaeku5EIHPV9uwx2w9cZAm9HeyhI+ltrtvp+0AA7RkKQ+N21FYlzgU+OWv8G0XOKdc1LhMac6quxnp5xHGRWQ70GeCLE19ZcbUsDCUmVij3EK3HRc8xVPgev6GYnKkSEk3MRSMlNUXpFKY3FLpBZI0hGCUr6Am7hPYGtuacyUWYSV9CeWzAZL2nOnAvnpDVFataKwEvMo1CCZzifvCuV0VgsVOxh/pPidMMlQb8t2OX1aL8KtCra5EW5pAXJJOLaQ4t6J+x/fYb0S6z/5dplJqMv/7w7rkiEhys75gRvq/D+cBO5JGNkEoHzEgn6EbUnQnwAZTlgkn9du7yOpPxGkvgKOq/EDZNzVju21ebwupfDUminKM2IEGb0B63fSmuu/qlmp6mH3Ynh9xbsoY9xUQJFZCM68SSgomQOqi1ZNCWCDnuBXwFPpOTHa9YW8/yKl8uxGvpk8dicQ2bEPEQIW2QSUc4CSnCvbFFKFwp/xYOHyll/7KYj5SKCKEdiAYV4HrP2E9o7Kog5jfuLV1zF9cc8oowmRV4iHAb/vUqHFR9vWU511tfbriynFnsLIFJjYcx8oVshgi02zFvsQBRa2Pg/dmtkF9MaSRC+UdRYuQO2RARRL2G51+JbZT90JTjRN/uJKLvF4j7LqW9DnynAE0n71RhEl7yGfGWrRDjqkjtRXi0kVdjwMYX3qz3QnwLyyhpMwD4i+WHQgWNybCLKPDMCf70uQoT7X8C702s4LHeyoSDp5TGnujg2V39X2ebiEQurABK/wOVpwBNBO1VMogteQ2tlUSI8ynUclutBZoYNhiTxgJRyDonuxb1jc5uGZWNO7UvLqf6vr4K6LmhGxSIq8iqjQrRDRAVAAp9hT2+AEZ4zSHQE986s5dROA5PADHBeEn1CJB6k6MfnxAaOETUV09om4g+7A2RexB7/3reD6lEovGKTqO8BQmFf7oOGHUDb0ikinMhB7PGNSG4Uxm7AJLvZh33hcBDoiHSSSJDQIhJdBTyGLfaRFOIPBuvcBqyCY+zzAHbb0g0iQVJL+D7ftjA+NMJgHRdafqyiTy7pJpFcCXTK6Y4hcgsAAP//MkEDHgAAAAZJREFUAwDH0wWD2/EQZwAAAABJRU5ErkJggg==) center/contain no-repeat; }',
      '.serv-valet-service { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-valet-service-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAGQ0lEQVR4AeyZe4hUVRzHz1loZyjTgp4G/lH9UbRGoqGVZVk7IybSy7JCe88s9MDKDXuxu0GGpkYE4RUjycRSeoiVzqyi+UgsEYmEHmRFkBSSbhHNbOXp85uZczsze8fu7OxcRXb5fef3O79z7jm/7z3n3PPYJnWc/A0SOdY6crBHBqBHrqYOi7Ox65LIeyTh9aYSXn4X2OTgp9YlvZLu7C+bSIlAYpFSxiPY0aBMtDHSOx0JLyf5ZXlhEpERSXj5tyDxqBPUn9h7wY/AES099prjCGVGRaSFaG4DVtZn07GRoAWMAOPI6AJW7sWQHkKFk0iI0Bt3OuF8ReAzSH8LrOzEJ/PjoHUwZxhm+U6eFf99+E8EVSUSIrTeCoqizVKMA6CPQOYJ67RzhrQQWgqh/a1e7yukAyUqIv7nNZuKy7wICuYcgl0QlFHyDdXKPESZVaV0mYqKyHCn1dMc25otBLiFxDBgZa1ReiHBSw+65KdRtg+ZqIisttElFueT1raawGZinwsKwhC7B0ztTjfPzqTjD2CPJ2MPsDINYwzwJSIieoPfolYy8V0yJ5HnzyGt1HzSy4ArhyAzymi92TpZk+SDYZMqEiLZdPMKpdWntlV6YD1Yl/Ryj+OTt3sJuiCZdGxFwQj40ebwe9ZttLrY2qIjIUJDf2RTsUfQrkxiDiyA0OuuE/tzECjZdLxqXlREJDBZK8ZprVZK4ggoe9NuuYSXq5oXJRGJaWcmFbsjm44lGWpP4XjbFMe9vzgy3B7GHyRDlNG3+xnGdPs2RtREaLIgWYbaCxCa3p1qvgbdXvDyw3C7HyXzBuXLFUkvvwLyspUpOLvTsWzBKP0MJJFSlf1SMonX2icTXn4V+BXIdv8b9Daj1FSbj5Z92S60L8cKEUWvzGLx2+ZHptSp2LLdPx/tShdlZf/l+ur6/CaSXm4rb+sHYEKiJ7k4L59eWSvOKItEqX2ZdHwKvndAXzFmKwRuAX1ISOF+9UhiSW42gWeM0rLijpCKQmKo0SpJ2Xae/xh9LXClh0Al2LPQM+mh2egZYFK2LX4VBYNJklEzEQLo5OvxIs/WKxdQ1xtUcjKolJ9xLKeHFqLfBBlwRKmJCA1Lt3Y4NWZ5W+OBDgnZPEodtorhDM+UTdSjQxMJILGa4GWYbK8hgP0806WN8vdSh3WTzAt1hL9R5LHjzckx+XLsQAlFpAqJWwNrDOE0Wu+oUqwZ/8RWL/9swuvtpt1/wG7Atl0vQm9n9/wuZfzzDXZBLJHLKDCX4+WmIFDSHU7SE/0mQV1MMfXfCo0j6eXXEOTvIA82aqWeU8pcR5aND7MkWt1ImT4nRSk4loxPlFZPaq5kglCqQlRYEqdTWC4PKsEQya+SNsgviNimuNgNKTj6/vxFD24WOFk3E7M711QT3eifk52CgSbjO0xP3E0jvwC5cKsEQ0RVbj8q29qPg8+seYz2xoJm2cYI8MuKjqK/tJ5QMEo/TXTjTSVbVlfZ95TB5oXVLHhlDYR47gvC8gh4JjgPDAesJfGXeNY/w2BLfP7BStIuZGi5abGlsIWka0KmLdaljHlVhkI1UOHzBHs9OAVwvxVvw7cc7APVZAzzt8NmanXY3zGLL4iI+OvB96zCD8pQqAaCf4YGPgI94P9kMsN0JfhM5pMtzG3M+9YW3QgiUm+9kLjuSizu3QiBD6lsOnBF5soHrkMecNNH2z4z4eVkH7Yn4eWXKW0mVgTEfbFZQo+WfbGkzNEmMpQgkgTdmSzuir9WSsvOeKQq/9urjZoDgYs4t6fLs4qpqIn4gSeKw+YAJNYTSocp7ooln6Qvawn+BtDCR2Qe3u9AoDSaiNwcTiHY+ewcdqB7QCHw0rA5ISgqbfTLBH8hkFPhmqAylT4hss46aUQWMPeQtMnmiSbfzQtjH+IZOcK2s3Pwz9tSl4Ne7HUQayfw0UBn2ppn4fsShJYmHqy8Vwr9cP8Lmi1cC3XQ9pUgBibzOZUL7N2lOmtW0iMbWN3n8mTFf47wDJzsYg7MI+AkOIEJO4FrITaGyj2j19WaEDnINeXTNCD/OSrbnuAbiPQw6rm0OxWbQ6RyhfM3esBFiLiV2q3JQOrf3AYaZVcSaVQ7Da93kEjDX3GNDQz2SI0vrOHFj5se+RcAAP//1ECp0QAAAAZJREFUAwCQQuaDBKlVlAAAAABJRU5ErkJggg==) center/contain no-repeat; }',
      '.serv-247 { width: 73px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEkAAAAyCAYAAAAQlvbeAAAPPElEQVR4AeyaCXSVxRXHvxBlhxASMOxlVdYKCBTZBJEd2VFEaCoIngIt0LIU22MQD1jxHEAWRRZ7RFBZm4IphWBYDJSdsii7RBKWEHYhLOG9/v7T98X3Xt6WQBd7yLn3zZ25y9y533wzd+ZLPuvhX9AIPAxS0BBZ1sMgPQxSCBEIQeT/ZSaFMVaNRQj5YOHfYvTBuhiSNSdSDhdSPFgINUh6UkLJC0ULH6w3llkjw3Jh1MjWr1+/VHh4+Ivo9a9QoUJZSoHhibhf1IAD2VBHQj0pof20RAvFEwaykRue7MtubnSs/Pnzd4iJifmoXLlyo+rVq1fQpSy/7heNqUBBUgdy2MlfeMeOHYujUVrYsGHDiKSkpEegDZ9SshR5hyFDhjyKdulixYpFv/HGG7JNNSiof+v06dMxSCo455966qlL0AI74JLJK8qOmd6G8PpR8Jxjx44tRqfdypcv/97+/fs/5UmtBtecP39+af/+/ec0atSof1xcXDS6ciKvgTJ6ycnJvWU7IiJi3rVr136CTYHhifCBhtenT5/8YWFhteBngesnTpx4hVI8jSEc+n5QdnwGSQxH165do5csWTLl7Nmz8+jol2AnsDHYiJnVDsdehTfnww8/nBEbG6tB5SVQGoizZ8+eZa5cufILl+0nb9y4UQhaIF9U+kLDK1GixE/wp5FlWVkE+RSlwNmsWbMu1OeDC0C7FO0P3WX06v5x5MiRlWRMTqq0UR07+/XrF71nz55JdP4ajFLgSYKyIDMzc1BaWtpA2t+j7ShlcdpfSkxM/AMGS9CWm0CpLwc28m3fvn0QdpqgL7iVL18+2REdFHfu3FkW3ccQvEjAzlIaSElJaQARC/4ctEvR/tBdZgA6/QoWLBhF6TGT5LRz6dKlhTZv3vx7mK+CmTiwoFatWi/w3r926dKlhbQtOnPmzK8bN248HHovaDHQHsuXLx8oGgx1gOrP6tatWyt0XsOG1jxID59U94emnwsXLjRFQK/8Hl7VQ9AG2OWS8H1iKIjCZOTiKc+Dem0t6idu3bp1kbqHQ8bp2bNnPwmjJ6h3eUXLli3HrF+/fhdKUtbMU7u1atWq9a1atRqLnAIVySAHsj5Upi6QnEp/qL4cCQkJBXbt2vUSQuVALbQUIYH06dKZj58q0sC/dB5wpmgwbNu2bZtSU1PjQkHejtfbtWv3FnppoDaN8wR8wfTp083r6z4Y82SOHTvWDMFydPptmTJl/sS6dJm6AiPHNJB71EWHwUssXbr0LOpyrtrBgwcVYKpBQfrWO++804J+nnNJy7aLDK1o0aJFBPplkL5HsA5C276ZsdCufkJBa+3atZ2RfwK8A3769ttvL6MUhNlBkiH6ceajo+pw9IT28krtghZoAHbHqouWjsWimIiipnnE9evX20CrXfKS84WGHxcXV5gHolmk12wDgmfAUEE2rAIFCuihaC1LrV69+lcuZcNz0fIzEErW2bRp06dZB/uiU5jxbyQ5fZeN6yZ1xcepH2hLwlaDBg1iGKQWPLXd4/XRKybaF6pzi5zpDB3sdwlEzZw5M7+LNjZddI5ixYoVWovawfj7gAEDxlBuBwXGrohgyC6ovE1BvlKzZk07PwpVX/45hg0bFsUi/1vGrTTiapEiRT5bs2aNXjvDlw92kIzhypUraybpiWyDua9169a3KAWGL8IbyUuy6OCq2h38sbj7lUVG/TmVMly+fPl31AuWLFnyI6b2XmycpB4qmJnKeqOBaQ3ZkJGRkRKqsrscu2N3Zk8bV9uW559//q8uOruQ06qYga1cuTJ18ODBY3r37t2DNH+2GMGQ7Fg27LwmmLgZHCnDiwSlOcKbmjdvnkhp4ajWAkPqJwDqCVu8rtHY0M6mpeHksmXLvNejACYs2XBOmDDhMQLdB8EI8AqTZNnUqVPPQRs+pQEN0BD2j2bGjBkzzu/evdvMDrvdX8mCVwpn9UQtBhrOOqYOfImbdmbRE8jLsUts04vff/99bQzSNQFE0TwwSn9g7KhfBCqA33Mi0OsBGTIYG5s2beqAzz9zaW1t27btWhftUeQIkosrI0JX1Wdh+CyezeioniQY/KlOnTrdFg16D9bUSSd6w5P8hu7du2vBpuoBxq5Hi2fF2GGTUJat7f/rwoUL2xuMp6Tvmuw7mDFFTp061QkRzaKrlSpV+ozZmU5dfNMHtAF/QZKQ0Aj5+JEhx+rVqwufOHGiO3xl2xd4ohuhBd52Tb1z5841YfYCv2dqfzJ+/HjNVqUXNGVDoH4lZPisacrJtElkcEK4LkZukKWlBvLmDeDhbnvmmWfWUfcJxnmfnMCNCpI1bdq0Z+nA5DnMpsN169Y97FIzA3HRktXxI2zv3r3aZn9K+3p2NHsWucvCsiSv0hca3pB/3RiUlwD9Hho6dKiCraq3LbW5o/R1qgj/7rvv9LCUF11k81gyefJkZduG764gOi9Bko5jyvgpkUeOHHkZJ2MwdINgrVqwYIG9w+Rwtn379q2R7Y/sUY45sxlYdh5Cmzvk0HVjahDWxYsXa2CrJXiNDcbf7HVTyyaNPlt8VVo6gNoZj1WsWHEztF/QgP0yfTDUiVlgP47/WE/C3jr/1qNHj89d8pJxkWZWOMeNGxdx6NChYTRWAOexLm2iFPgKiLu+ZHIgVyk6q2l3u8EAL+QQCNLAkaUOIuaEz8PdERsbm0pd4Msfj7ObhEJCznNtcPRXCMvZbx5//PEZs2bNUsasAeboaOPGjV2R1Wu5j4R1JbTApyyMHPq0eQDHH50KitP4Fde231AKgumpPwfnu/CbN28+i4KCfK5atWrxffv2Vfrgd8L4ZWDEGyTrJAtvymI9GWZdTXdw2pdffqnpKifcHTV1ZbTkIi8gr1xqKYv9t9CGR+kLxPPXrkHmJ8PX+fIR+v6W/Oh7X8L+2ubPn18RntZFC/2D3ITupS5w9131bNTAsysBCMk5RowYUX7r1q2jkdN5SVckCSzA9syg2QPMYJlFmkE6guxhd/uLS8LwXHSuikWLFmkG6ZJPenZ+JHt+BylBUDLW1atXdeyqTV1BSv7iiy9MnkbdQ596Nmjw2RU/hGQcAwcOjOK8pTNWe+R4lZ2rSOFfnzJliu5c5IB7J6o7WKeiuHHUYq2t+lNuMU+ga2/5smujxYnG1nfevXsXMQPiG4If2bR4zetCa5ApkZGRydACwxPhB8XXLAxnVmsWKmVJJ5nd4pJ378fV9EMRkImY+A5tubxSI5ieutUrRnsiB8rxZMs6b0nGHiAsSw6ZbZYbx1do0OJ+cvjw4X+GFuj91+Jvo+pq10W+ykdwXruOaPuAbWyqAdQs0lp4ifusXB1qeQt0INZMwox1mvt7c19Exd1/qp6gAXq2/FATz8HZ7JGEhITfMHVGwFJ2urVKlSoTN2zYcJS6kaF0Bw3I4jWrTFA1iwqjG8ntwFCE9G1MuZLWKLvsw3XLUGT1WiJixbBuqK9+VLoXLVpUA9MghNbx48eVHzmRPxwdHR3qzmb79Bh6uo7WUpFIfqT1kW4CgwbpS0JGHXPnzn103rx5oxikbiBLIpjOvc3MLVu2aJpLxjhOew7IysrSQq2gWjgWA44nGPri8jnlZ6BdLkX5A7AhKChCf6/AXwLOLVSokBI+tVsjR44sA68tFc3CpLi4OC3aAf1AVuDUDzcFLdFXjpSFP6c4p8pOUH1fQTJKbJX5J02aNBpj4+ggEtSnmuls9e4LtekcnjuYNs50ShZ3w9gD6q5IuBPaG3fguC7tbsATKDs/B7GPdu082UcOrlkj8UfJ650aNWpIBjFL/qr0h+IbnxDQNXEBytSyZct+TSkQX6Vf9BUkY5CnNgStCaC+GFzE4cnx8fHv1qlT5w5tMmzkoL3BtB84cCCFQ+gQBtYB7Ap2ATt5YWfV+bDQESNrQEEGN4Wv096a9pe56FcA1Z+1Y8cOrUf6MrKH/EgPQPKmPxGBkF1YM1SzSGJf165d274oDKrvHSTjDN+savPEYgmMttvL0FMXL148jYVO245kghpmTcpiJ9LCqnVDmIF33qh27Y6n6ctOCi9XrVpVl36auZLXQzH98Qo/jY0IZFO485Iu1aAgfy3GoAOtrnulcL1Xr16yKzoougdJxpyjRo0qyRXCeDSNQRxKGD169HvcUmqnkbxxGH4oIJvSCYYWCaJkZTOMtMHe3ZQumHaSWKURZiYgm8bZTw9M8iH5k56eXgThoqBgu+sOW7aD6st5KWXjunXrdO+se5ZwArSrSZMmHxAkfQ2xHZeOP1Sn2bYg5IAWx0AoGUR/AIJgt6k0NplFClB9pK6zmCvDhwz9WMXXZlv+SvHixQ+oAhrblAFBg5WAhJ2DBg0qyT2NtmbtZNoB4vm+pjtvyWgmBRqseBqUZB84kmDqcKx/q7neoEEDXY7lqo+jR48qldBMusuh2L4YDMmGe5AszmT1eHfNkQNtDbgl60MsdA9QHyz9ofi9o6KimvBa6BVBPHfglnE7WJTVt23A0BxqFaBH8e8EXzTs44jh2YJ+Su2WYeREulNXWuIoVapUrny0g2Q64ztYCzqSMxSW/hXmOT716p8HVpCzLA+Cy/h2PpIPhvZrqdkpOyEhg1d/ki3IrLH9Ul3Ze6E7d+4o2SzIErBp4cKFZ8QIAY0PbCJaj6pJHn2N9ZroUFHOyJAzKSlJU1HbvbZGba/Kb+xyHwYDIp0fAlOCfFLCjF9IRf8QwfoHwbY/Zck3Kzk5WbusMm1lyu75kQbs16DNII3Q0UcnhMPYj+cEcdzFC0lfQTKCc+bMySxRosSb5CftQeUvNiq36UhbIOxETvNsZmbmFLLgXO06OOsELex/go22nNIHM3s9zlRc0pVhcEoEj+Cjsn2pmACKCIDGNkvALT5kvkUfbW7fvj2GB2nPJMP31M9ZU5BMK/cy97g9VF6jJ6X7XrsUHRKy6OuuOaSOTaeePzpinCMBzeC2wCPQpARaT7S7nSNXO+1SC7kfAuzEhvKusxkZGdkZvMtO0CI7SC5JPR215RWl7zKV60K66lelrWwCwSALgDqM7uvSpYuOOzY/N6XsetsPSV9K7oJySlt5XlH67vZyQ0tX/ap01wtLS0tbxLqiZeBNrlqVs4nvLae2QCh5X/YD6Ried5BM4//YjwaXzlpyDL+0HFD8Z+HHECRFJM+vipTvF38sQdJsytOrcr8Bkv6PJUjy9b+GD4MUQuj/CQAA//81Flx8AAAABklEQVQDAD2WK79gE9wkAAAAAElFTkSuQmCC) center/contain no-repeat }',
      '.serv-247-active { width: 73px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-ac { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAPuUlEQVR4AayZB7CVRZaA//tI4uBumZbZhyg6zrhQWtQyOwWy5IwriCMgQVC3EIYgKCqgkrMgApKTICgGRBREB0RgBsdAFHZxCAojSaDUqbJEQODd+b7m/tf7wiU4vurzus/p0yd0n+4+f9+c6Of7SyCqGGBNdba0atWqTIcOHf7tLJb+L4+8P5v+n0uQhiUx8wxgrVxp0QcffDD1vffemw49LvbJI29eTPxna4X+FBkaKTjWOtmiRYtfNWvWrEUymVRmpoG/TiQS/y4jIG8ePMVq1arV/tZbb60PLbPYL2TSLqit0gtiLMDkjAoqFaL169c33bx585Ly5cuPxlBpVEnrkzR+SI13TATPxM8///yFffv29U3RreS1X7At7YLhJzlyxx13lGf2r0KLSqmiiBl+i8YOjH4MQ5+lHbES9hejdj9IisqVKzcOnu4gf61SpUoXaot2JLt27Xp5r169ykJw3EU5owDGZS0Kk8daCIwbN25ctGXLlsEBOfsv5+WXX/5b1apVm4DuxNAeGjx//vyq4OWBS/fv319aGu3ewA54my5btmwvbeVTRRF4r9dee+2jSpUq/RKCzsR9TkRaP32FSsxYqCNFUJjxbh14n3322VL0XYWxrgjNyD55ir3++utf1KtXrxHEHUDvxx9/3FW6hvaN1apV+xO1TuyEp7G84MUBx1JFUV5eXjkaFfbu3XsDtSXopOHBoJ6szsSM8OYrYYBL3ahRo3rPPPNMaXoVRhXKGRzJxCWKF1+wYMG+mjVr6sxfIcbOlqH9O2AHfQ3loa0Tp6nThRAMOPWpFFGZUY0aNW7nTyezOnNOR7788svK27dvf2/cuHHzZ8yYEYw6ffp0GJOTkxOcTSmMKw0pTpjtr169eggzOuKw2AmtsX3QCjkBzRJklilTpoQIkLzmmmsGs0LLDh48WBPcEvTbyIQiiTDoedSuXbt1zM5k8JZDhw5dOGfOnNzevXsfh1aCMAg89BUswZlFixbta968uZv5OxiONWzY8D5ptLM5QdfZcvTo0W9t5ebmjmLlB9H+8Oqrr15NbSlSbzZHokGDBuW0bt36zIEDBx7E8DFIaDhw4MD5bNiuCC8PLT5S6SpUQkhMmzbNfbGd3k/nzZv3EbUl9NnIBtxJVVmJIejoB8/u+/lbuXLlUdqu7kU5khwyZEh6E+KM5/0wBHmBTaUujjNxHIdwgFZkwZgfgBNFdv5IDMYhM0zshg0bptAeSPfm7t273z58+PCdtC1OQuAVyYQwMJMQt1999dWSHLMlPKXWrFlT/MknnxxP32YgFIyL4zjg5/qHUed0lrGhH5n5jASf/8QTT+zysBk8eHBJQVuKkpfpSBBGknct4fPhww8/vJ2L7/+eeuqprffcc8+nI0aMcFaqoDQuvx0zZsyvQVw5l5xm0QWD8hlYgEsblBFh4LWpPo948VHY8jcOmy2zZs3aJmDL9ltuuaVhis+xoZluBIx/J06c+J7Kmd+EAZtTsBHaCmAe+ErAzXjLxIkTZ0yePPk66C75OZ2Bp6ii/uAEBg9HrifdIRjn4dRs6kXUf4Ee7ADfAnx8/fXXH6S2pCdIQRIEiQlu16846roDbdgb7WIA7wDcP3PmzObt27evhfApDKo7atSo2RMmTHAmL9YZdQcn2NhmCU8ibwcz3kg9hw4deoD6Xur2GTa0hdYRGz1AYA+XsXWksNDgn6ElSItBPB/cdtttJw03hPdgzAigwdixY+dyyZnhXogzTpjygxO5ubmDmHWP2PU40QbZGunqypNPN7rEpccgDjlKO5IAU4HCBdDIWlpBSLCPVBQxO/1hHAXU69u373PEsOnIGfpVBLnIYp+yI5wYyMq6Gp898sgjbXFiKyOU7YTIU1C3OCzBtrhf24MjNpLM9HXkQ7/v0aNHLpwyKZBmoZLkYlORBunME3CMx6AmnCrPTZkypXxmP32ZJeiSQDh5Twyhva1jx47NuGj30FansmkWWezP69OnT27t2rXbcuHeBJfOJTRGiL755ptqZKiLlyxZsqJnz55xzHsLw1tk0dkwlpUxGTTMGo4cOXLu008/7QFgv4mgijyBYieSbOyhhJOrsZdwasc+24EGdWV1glXWiTOsetkXX3xx8Weffbbw8OHDZtcMjXI0JChs3LjxYoTPgnrz4sWLl6ecCekGtGzFscpwZeIwq8+5T2o2wz3jN8klyM1hxYJDOoGwAcCOLl26NCac4j2hLsiFi064ytxl17HqL8FRDXlzHn300RdpW/I0QgU53OSnOSE6o9Rj72bS7HdYQmNeBc6WA5zVgpBnOkNngpUxzEaipDG52UukNPdCd3XKjhs3rhrhZN6mE1/ceeedHQYMGLCbfmU7IQXlBrxOnTrFdYKZuYo0ZxH8dZE/h8OmkykUePDBf7Sj4IwNnHmAegYOVVq4cOEKYtcPI51RsHwFIWISNIRhkSvjMToSpDZJ5jxqv/gqsEorkemXoblTY+4f7ybDRdkFZcZ4tHbt2tOkKOWZmCXI+h0yZukEbYs2qTuR6YiPAnZEjz322Fy4vmFQpVdeeWUF+c5vwBVOlbWk+0krHP91ilNFzvplKXwVhpkliGbdE3YCyf79+19P8vkc7RrAMU4325l7DnKUzGHnX0bcLgA+YOnXUK/jXniF3n8FVFTxjTfeWA19Df1/5sj807lAHja8X4b/wnhL5mTpbFtkbVSGvILtTMikzZ0716y5AYK0pfT48eMXMn4dsDYFH8LfJKdUqVK/IOacLRW61K7K3xlomi5OMyoHT3hkow6PCQVqNzPfWjkJVlE5yjOp1HDHC7aV7ffJGcaXgFd+mokwnkaopdMuDigjtiHIoE85wQYI9pXMy8srkcNGOkzMtWCjVgNqAjXAq8H0F8ByoGLFivdyc1eGXsv+IsBxNTm+a9vXqVOnpgw0tDRcxaDhzkpyAj0MT1Vk1aAO47LU/w29ev369asz2M9mjT7CAdREOuPjsb9lXy9z9uBLF5kjTpTaUGoBeQ0aNPjDqlWr5tetW9dNCSlrYQITwejZs2e3gutKwKIzhsVxkAQT14VDIBdm905B/bDkKwleYnaRhTeG/1N6cseMGfMotce6upQd0FiQBNtn7r777t9v2LBhGQNP3HDDDc2ff/755XDqoDzZwLGwRb5bDaXhnbKLehpyzJT3cmd4bHr8NuBYnh9fmqzQuWQjIio+derU/exlM2NXpjP7aaYdgM6oOzxvgodlz+OZpsr777//KoRjTZs27bBu3TqdAA25jYOKAp1zdsONDbP3xB7Cqxkh0I2Y9q44gvEfEw4Pgo+Gpz6bdtaECROuZYVcrWAM9KLkGwnBmbvuuis4w+Q8wEaP35PVnVCAcIZnn5t37tzpvsjj8ewuUoGlCPbY1FAVgBYqjlVQxMlh8jcAJZ+yqi25W1wRj8lj0NJHO/H8OFJMNBuOHTt2Dkerzzw648rQVWQJzvC1uq9FixZNmQyzgS44MyHFHRwJ7e+///6XKNzAXmjErb4Gog4o4LxOIHAAwk3Fv+AnhFZcfn4ABcM4ypSTGdNxBmBu1oA7ZS63vpfuhThTjKT0C53BPm95PwJpRiG0wowSUqtY+lovvPDCWnvOA+mVIF59JHBfbCaLbUIC6KZ0JTWsoBgnJThG2PVn4hzXkDCbTQpibuaYMAEFB6Zw+3PcM4xvDZgS2RUcsZGGVN6kodJUWhDsC84bThhjKr6fLLYjTuxgUDYn6ArFscqImLhBjB/KajYaNmzYPGY7Xhn7BXVbxyCuEHHrGA+bXEIMCWJbRYKzVxTY5+k0HAMMp11s7AapLFYnDMdYXlG1ypURjNEZmJTViIxgLg75jWG/oH7rGDJx5YgzPMrnSAJKkgvnMma6J3E/nXoS9bRUHXDak6G9Da/J4U6Oz7Y478a+ECcYli4aF5whRAYwKU/RU3/69OnLkf8cMAOYngmE8UxgNO/HV8OrE9pMMyrkSMQ7awWETqS3C7Xf5X9I1QGnbQbrzX2I47DlhAkTfHExrs+3EogsVDKd8TT7EI5fAfcDnQGfXNNAGD4A9OUAqUCfJeE/IcyIDUCh0XfffbeLr8Wq5GA1jx49WvXSSy+tTduXdI86N5s5GOzRSo7D/6ehE9Jp/qSiXnOsJEZ6UCjkpP+Axej/T1bsVp6pqmJPtZMnT/4XtG30WRxrnW9FAuGdd945efz48fV79ux5/9SpU+t37979Z9obUaIDGm0i5/0Qr4BLHMZm+8cqpmcuC0+QQfIXG6YedZRh7+1nzEdff/31euz5+KuvvtqkjdDylcwVyexQkDEfDCAuR2FMHxg2URtup3AsOAQtGEGdtcAb8wR52Rjhix2ZQnss0JjHkNc5DeNQUqe2FRKRzRFDxRlP4oRHZD9GHmjZsuX/cjP7iH0QhxQKuXDhAAhy+/Xr5377DbwV77vvPp9XdahIQ5SC4eqMLrnkkgWcZn3AxwC1Jk2aNG306NF+H/lwHjvrkDQEhWnsx0ZQ5i9FCDL12MRl13DixInbuLX99eoHNlyRs4sTxcyffGzmcvXh20/dq8ig3+jWrZv3hMa62j9qS7VwONjDL75hknDGXwHCU+rSpUvvTLEFnlQ7XRVJpDd4XbJkyS1XXnllO35pup3l3QE9+vbbb12tJPHs7ErKBL9vzmzduvUXPKuOw7AWdCrLXKvSm2++uYKfOrI6w6TJG5UuXTrUjDWdGUAW3vLyyy//GNwS99lOQzZHNDKxevXqg9u2bXuJGT7MiLBKV1xxhSvhF1o8VpzucHAEJTz2jcConhIB+TwozL8q8oPNymzOMDkl4bfEMoNOsvDFy5cvN4WXrm3y5AOV5CNkIA5woMLkC0bypO+K+MONxgX2VFqTx28qZbzAIPYCvF+G4ZDfI9vffvttfwOcBv0/3n333T927tzZR0DDTNnqiQhXP7FNMKXDGl53rLVBHm0SLwQKKUTMIDhQw3XCdlSnTp0zN91007382G8qLmv4dYs7pdRDDz3kM5AX2Mv88P8/nP8DCS+/R45Xrlz5GHg3BoSnJmb4j23atKkArmyNjG688cZJ8Hfi07rgPaENQT/8RZbzOVJoEDOcJOQ287Qf9gwMwYi33nqrJX0Pgi/lI6ojqY7h6F3g90jaCDZwV3hcmYqEjD9NgIYPtwRvWAc4Feds2rTJ00l6epzIueCiHUkJc1xwADwoO3LkyCEcGcurfGs+azVEHsPEje6swyqaSOJMd36saccvtWYL0pUhKNMwknZREJRd1IizzBqmYrFQc1KtwcA+vBmbXihXHvsv459AFWI+pCN8/7z0ySefvCsxA5RlGGWQLqypwgvjPD+Xs6k8a52wjipUqDCmbNmyZrZKkBb3OfPyS/+n4R8AAAD//y9mawUAAAAGSURBVAMAjeD8vpRj5RIAAAAASUVORK5CYII=) center/contain no-repeat }',
      '.serv-ac-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-credit { width: 73px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-credit-active { width: 75px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-deliveries { width: 86px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-deliveries-active { width: 86px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-drivethru { width: 78px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-drivethru-active { width: 78px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-outdoor { width: 73px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-outdoor-active { width: 73px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-parking { width: 46px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-parking-active { width: 46px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-reservations { width: 55px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-reservations-active { width: 55px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-restrooms { width: 49px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAyCAYAAAD1CDOyAAAP80lEQVR4AZyaCXSVxRXH570ECEuBBBEEJSGIoCwGkC0BDItHoKABG9RSLKDE5WhVjlq1R4FzaltaoByORQ+LcooUWVop+xo2WYQCStmEsojsEAiyC3mvv/8138d7yUuIcua+uXPn3jv33pm5M/OFoLv1P59n2LBh8T169KgQDocDEWJ+fwTtx6K+DukeOHBggsaKUBIXgRdDfeFiPc7JUEEoKyuret26dd+aPHly7vbt29fceeedczt16vT4zJkzpTzknCtND92lFsmGpKtly5aD7rrrrsXLli1bPWnSpEWM89LgwYN/hnQBID6q4qXEDlgDQDgzM7P25s2b/wH+B6KUQl0V6LF///5Phw4d+jq4yk91ROOHGCP+1VdfHXXy5MmPGKMlCssBrcDHLVmyZPKTTz55G22NoaCBRhcpiab80BKzhNzevXvfhtRDCpmNFvfee2+71NTUdNr/Ad65//77O9CvUpIu9cUCBcnGOH/+/FMwvIK+xQ0aNGiTnp6ecd9997WFNhXIXrNmzW+oVTQjkhPuQ0kDGyOK6gWDwV5SfuzYsZc3bdqUt3z58vNr167dxGCvoKXCmTNnnqZWucFPSfroKlZsDGahyqlTp2TkoZ49ew7E4IOzZs26wpLal56e/gZSx4F+vXv31myAFl+6pQ6an59fCQeuI7kBUKnYtm1bLSeXmJi4HcIO+lNzcnI0/Q6DStUHf7HyzTff1IDYANjCPjhJHczIyNA+cDhzgvYyIKmQD9T2qmofSh30tttuC8NZKRAI1KVWufLFF19cE3L16tWK1In03ejWrZstC9o/pthMVKxYUfouEYyahcKhdevWiea02aHVA64nJSUZDbxYKdUJ1r4icxipfmSjdGoVU7Zz585hNDTAqn79+mmtulWrVlkNvSxFAXK7du06gQMrCEanFi1aPFYo+L1qNvtL1JlALroPUasUG6MkJ8QYnDNnTj5p7j0kq5ONcuvUqTORzf0OsALaCwz877S0tPfBVZQMzDA1ygDilYwjWYyFP4+9MRvdcxlnGOMqI/4V+u6GDRvKBlAXz4/kqG6WkpwIt2rVygZg+SxkRrIR2YrRA6jfApoBH6akpAxasGDBOXDtiWLKoZdUtJQEtgxXrFixmVnoDfMMoBnjvMbsdAefkZyc3JtZ2AOuouDKLsmqbRDLCTEEt2zZog1tTGSj2a1bt+5Zv379XhxGTxC5rkePHn3+888/lwPiEa8MiqVP/ZEg/XJY/KpddnZ2xfnz529gWQ5ijCxm4ynG6csSfpH9cZD+KjjlyckRyflj+UjhKB5jCMHaTZs2fRqFH2hqSa+fHjx4cPi333779u7du8dBnwX8HRjDWdG5UF6GFdVZ2GWV+mSA69ixYxv0jmTpLF6/fv1W9OxhI69njAkE6M3Dhw//hXSbC8/X9G+jXgHvux06dGhlmpzzx5LSQtrN1JWZmZmG4KJz585NovMRotCYKdYmTqKtFFubuhHQFHiWsyIXIz4YMGDA7bSlXFMOGlUUIPW55s2bP3bgwIHl6H0Dvcp831MrwglIVAWvAlQGj4dHieQS+B3QRuDkRsYaSltF+oKRTggP6560b98+bdY0hEY1aNAgkynu2qZNm45s4oxCSMeQrrVr1+5Mv9byFjQ+l5ubq/0C6mRQpCNyQHTHXsvKy8ubTuMqxvQmwtKZyWx2YMm2R3+6cOoM2umcGZ2oMxs3btyBPfgEckeB0cjmUKuEZLgQ1RrYkYX6QMiQA0eOHHmdKd2ntf/ZZ5/laRN7sGjRotPsm/P05z700EM61Xci92sGbUKtIp2qBXIizBKtxv3oPXRfadas2aMs0fkkju88ncqGHq5abQ68s6rZ/Hnsjxk48jAKtRffZtw64P4RrkG0weLOnj37SzqOkC1GUqtoipV9FNko4FpSHob4KVOmnKD+EEhkur39oc1ueqFbfejQoSYsj2Tgz4sXL7ZbAEZJf5Re+Iu1C/kcjnxNv1JvPVKy7le+E9Cd4350N8j9DLJ63rx5Z8BVrvIjg7T+NFs+cFDpUNKdyT3wwANr4MsjyhnUXvFmQzLu+PHjHemoTNrUkgB1DsekX/0lgY1byKdzwpUvX34VwpdZll2ozQlFSQpcKBTSNbgK63SpOoE4Nnl1QMKWVaD5BXoVlkgVEebOnau7lKLbsm/fvneIBsgJ6QclnYRC2sSucuXK0idaEHldX4THAsnauPCVh8HwhIQEBbUc9jYfM2ZMxahBuGQ1hPEa0dT6du3atevJJl/AEusEXcWbZuGOvhFksfEMUM0Izu2lvocU2YJaRVFUbYBem7W4uDgzhn3RHflP2EfKdOKR0ao9sDaz3Aa+qe3bt39IHcyEBQF9YQIS8J3QsxPigzCduPvuu/dRO86En1OnM6jNFHgAg6n8kgI2gGltRe3IVhupwyQE755VwGzJcchWNJ4h+kHuAeq+N27caEytIqMFwsVrQWAZdoPQj7ND/A47FQTxhRMTE8NipN+5S5cuKcen0vhq2rRp31GrpCJwMCUlZYcaQHjWrFlyyJNbDu0C50QtapeUlLSOWrOR5r2RL1y4oMEgO8de8+SsjW6bGZaFGQtRxlE5N2zYMKv1g5wSi4zXMnLcoD2+AGeZzYQpuH79urxUZLW2HVOsw+0eFOxRepUywBO26LJ3/gvtOhH1ZuI87YtAa1KiAuK2bNERAiVGwQkbOz4+3tMbg8vJeHPWFf8XJvg2E6aAqWoNT5ijXdEEde35SQa2AiqKqPFyYKnteG/sxEm9vDoo3U6dOvUSHbK6Wn5+vuVw2iUWZG1mqKW7RD46jI/aSiG/bBFYdrIOonIvyFecD5upHfle6/QKN9iVagNSZEK9evUqoO10ICG3C7wuG+xOai2p1dQVmGZvX2gJlGSk6YO/1FJotM/DQ0py0hlgNfzgRPfu3VNgbAbX/gkTJlym1hTqYDpftWrVg2qzQSUk1I0YMUJK5JT4NFM1kbeMVFBQsBsmGd4Fmidjyw+6FRKF1fx4/aA/qfh7wjH1cqA+Ud0vVdoPGNCR9nEeJGdF405v61c44DtBRvqKdoCD0tIwM7kf2b3IpvJsVbKgO7rgaDThFi0+VljAIth858lcP8wEqVTr9xKXucVivHz5ckOM0E01d+zYsfmiATKcKrrg5FaMVkpuRB1UZkN2GXjtK1euKFlIwJafkCIQU6d4uA34htKOxGm6KDnPQ10VzpO6tL4dd5Lm4uRhYvsDXIdLlCDnhbX79OlzBqOPAY26dOmiw1JL7AAyFTk821GrGC88kbMpPqOLoSiQKCL7DCcVWw2v51SI5R4OstarQ0wD9nOj1IcBUKfBL5UrV07r28EjWhRwXphCXmMFGKezIeX06dPmBAYoS4m/6JVCVweHXjOC2YraKxLwIHImPOMZx/iZYbFp/HIszWDw2rVrmg0NVmfcuHEVdBeBoxUCW/nGZHukyH6g24qiqhlypGV9OHBkJF0gHV9C7HCCy4yl9kqeEE7oK6pxQg8eoZEgGUEkzcONHzldPMWTz5XkWnDDhg3auPPgajhy5MhPRo8e/TvwVGD7qFGjvIjKa0jFihQ5so1m7DS9T6Slpekqry93rl69emuhqVgESYe66To+Smv5ajnZRZGAefqlT8HxbgaSjQQF27FnfwFRgZrXuXPnG5oFx4Ncb4eP6VCnnJCyI7RVhKsuBiwzGxzjdODpatKWJTUNxso1a9YcQoDmgGs5mo5atWptJIpLoD3Py2w99SDgRoQTId7QqXwd78snTD+z0a/Iw+oGMtYadChIG5s0aWL65URw+vTpJ5/hH1zPApeBfCKqQwvUMpgZq0YksMws67A/dN3Q50Z1r8aIrl9++aXe52oH4NO1IcB1/QJfSvTd9WMMqUpnIhACNyfBdch24/X3Puu+gdoC+iUvtB77Iwmn/sYTNmfp0qWnRJQTmr7yHGAhbp8TIf4POFWtWjW7joNHFi0LgUeTcxXUYIMpslqzvKfmmSwzpT0jHrFYzZ1qL1f1wUOGDFH61UcxRAvEJx4tMb30Elgyss1o/JiTGD8xJyenJXa+uHDhQt3b6HJ2ARRiEeVBfg8N3Ui3Tbt5k1WfKaFPuADUiugyXB8ANKiyT1Pr4YcZMMNBI4sZTNCuVq9eXQnhciAQMJqYiLSifpUDzh+HfgscfXuGDx/uLS1Pxi6AMqRACnhs6OpQC6FmLKdnmLKHs7Oz9XIzY9hEjQC7sYof0JfCxo0aNcpm+QymjWigTdeuXfWl25FqbXDokaWgcIYcU+DrjmCQPXEJCQmqI8iGVrJf5+SAnLVm0H5v/lwAPcYavIsNOpF3wihedTrNIfNs27s3hz+6jMYInS1G49h/5OLFizNpPAnozbBftaDwoiY0CpghCxoeaynLWAtSBFOYvqI0dYtfteQlJ9zftEZgJlbXqFGjNzdSvei0UWtxiiuVGTM/yhjJ/GXHi4jWsDaojP+IjNOJU/63RFgbXW8JP1rIllZs/EgGNnZksygexe/NhHlNhC6Sw7cSbeV3pcLLrE3rK9SizHWBA9KLiMjqL0hOTp7Bd6S1Gzdu3I0eGa+B1CeeW0GZ+EqYHZsJfwCWidaa55gOliCbScYYD0riBNYo/GHpaaYCZBNv/Vu2ortMhsGn4o+hRknAWDH5PINNjggqwgJrx/pB0XWuDYq0dTNT0hFgKaoWze9To4xQJocJYEw+b+BYY8UUKMrITGmTRW7EmNG6KRcTK5MMAYzJV5oTMUcrSvSiU9IARfnL2iazRbKa8d5YkR3Cb+lEDOMC5HBfDsXaR8pSZZo5DXoLMIMjebBB+y6SFIX7xkRRbzZC3FCL7pGozc4AOkF/qgOSk37V3qjCQ5w9WqZGI1Be0lCf0SJ/SnQC4yRYvlKlSn5koOkDQFXuVV4Gki6dGXHsjRJ1iSkWkBSU7SpwrvjdGKwxavB213PAo+ujs84im3WIUc6UNrDeGXfs2LEjCyHXp08fXSX0WafeoUOHdBi6rKws3TSF3+BzovfVUOy3AgsMj6mzBKYCp74+U3oy+t5Vnr+T6A+PbuXKlfE41kadOK2/SwiVvO9IUSc0tZoB/Vl2Ftz65vR7TuJ/cpAtov0goPX/J2izN2/e/C/aujQu5IOBvnro7QCp1OIPzlcVveEXwP0cDi3mraADdiBtlV9BW9S/f/8VOPoohNl8PZFNoNGlqBPqlSOOK/PJlJSU5yHoXvQItf44Mg7Fg1Gqt4b+cF4f+odc9F4bP368Pl96bwfIpRY5EtCNFNk30TeJaLembks9nYAMQvqP4MmA/tww4fbbb39hypQp+dBls9kIbkUEQyJ+NICaAf1VhrdSfz7Bpzdt2jSDb0svE/2P+TjwOLRu0DJ5Gzyv/0yCQNQU0y6taAxBQLLoHcLfBDvwXk7n81H/VatWTUHvuzjThQdWa/Bnt23bpuevxohyQIP8HwAA//8Caz7WAAAABklEQVQDAF1MXeBZmgnUAAAAAElFTkSuQmCC) center/contain no-repeat }',
      '.serv-restrooms-active { width: 49px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAyCAYAAAD1CDOyAAAQAElEQVR4AZxaCXSVRZa+94VsgIFEZNPBiCKMCrIvIcCDAAEJGIIgICCgJKDSM6en21H7tMg5My4zaHs4tkIIS7OILA1M2AJJIKxBMKB0qzQ2I84RgkBIAmQjyV/zffXeC3nJSzr6Tt3/3rpb1a3lVv1/4pJ//KvRWbRoUbOxY8eGGmO0llmNvBbv55I1Puh79uzZYWyrlpOgWnQ9ssa4nkSEHSU4iYmJrUenVrye2+H1A9UTdxyOX34nfejQoc9u3ryZzh0RacwPxI0W2jr01bt37znxqXcyLscsP5Tb8Xd7Ry8rXzh37tx7YF0NoB5Q/dKgAKoKMG63u33p2E2fipG3RTUaOEJUxoY9l/lZWuHTv4UOyy8NhO07aKNZWmHikjYpuatUTG81Eixi+ojq0h8HfLJy2rRpbdAI2+CggfQvdOLP8dSoTCMJmb7/DbDGiujSopVDejlbxg0sWz8yRkS+APz+ySefjAVmacgXZYFAwbRtFBcXz0Kn/xX1jLINo/tHZM0YbLYmDDAi68CbfMO95lfALNV40A7obmmoYav42GOPdRJjEqCesT8l5F9OnjxZkJWVVXzkyJGTCISNhrZfcPIFyFmq8GjIH0T1im0Ds9Dyvvkn2MmLnU69PPvw4cPfb9mypSwzM/O71lkzXoVVPoKZMn78eM4GqvWXbqONFhUVNUdLlaqSS2tA+IABAyKAJTIy8izwX42azsnJyZh+EXSoUX/Qr1d++OGHe8F8GJCXlpb2E7Br8ODB3AeCYK6ISiZmKcqrBzE4fNaCRhtt06aNwSg0R8a432tT9vnnn1eQLi8vDweOFOOqGjlypF0WqP+cgvERCQ8Phz8tQUfv8xo7x44dA0+Em92IdhLRyqioKMuTAL9Gg+jcuTNH5v9EdAqyEfeB4OdxlpS+CHQnzETOlClTuFYlJyfHYvCbUgyVvvnmmysiJltEh/bq1WuSeH53iFYWTlioxrhBH4Dvi8As9dpoKAgqunbs2FFUvGrof8KyddhzmQfil5evGLW84vejl93JFtWXROV/QtKTPoKchcnAkGgiUJc2Ur35qQ9hU4C9sXX08op0wKL41IpPjegfwP/2zsZ49gGkNMPDAPxKQ0GYPn362AawfPZgE0+G1WkjOhNr4HVRpztmYFnp2rg5u3fvLoSMe8IAN7XADYZAxC7D7OzsUyE7J42H8SZAd8BvsI7HAG+6/afh4zEL50CzcHDZL9qzbiFQEFRw5eXlVVoNPJCNtjbf++xTpeviEm6uHjbV2ZIQl5kctuDo0aMMABpCXXYokD/KawP9M2DqE8vkyZPDd+3alfv4udfmoI1EpPJZaCfp3pzZr2B/fA95S+xLnx0DoV1NWzWEtxWfogPD9k888cQLo1LLP+HUlo3Z9FnzWVlvRcw59IZO2bN0dGrFFiyttaOX3/kAZ8Vwrz07VtenV2QRZeyADBkypH/88or34pdVZBSPXH8aS+jc193ePd58VnZq6xePvIZ2/rvAveZAfOqdv0F+Jj61Mhv9eDM2NraP9eSZRfrzy7kMwMrdbndPGO7tuDAvTY1OwCndDbclnBkaBYUIbLb24HWFhydETEq7l04eYLAzZ85sCzkD4ZSD9Cv0T5n06NFjUviMrCxE8yr8MvPdAc0RDoPfCCyllrBsAeAeYCJh9uoA/uLmM7NPjFpW/mvIWOjPhX6QtkDa8J4UMj2Dm7UnHC4p3zDKXbIuLq5FxtQhwelJg70QE7QjMe56asxwyLmW8xDs/J9iV75uPYmwQ7UDYQBW1KdPn8T2L5/aiODLsWzGI3HQpxsJIhZLdhD8x5AGHox6TKusGUOB3dj8sSVrR0xFsJdU9f3Ry+8kW4ciDjtOmpgNy4ULFyaK0cFiZMn++aG/xQn6Hdf+9u3bC7iJfbB3795reXl5xZAf6JibwlP9axHzPA6qx+kQQJ9AtjAIgyXaKir5ODNNmWt74tMnT57chcRx0+eT2dBHE7OOA+8GMTZ/AfbHptK1I+LhsdCIeWPUqFEdQdcsJzbCDRbU4ZW86RD8GLr7mfeAWcLwYPbhyPoBriUhkDVbs2bNFRWzDHRky1nZvv3BzW79gm/xxYsXHwfxIOr/lZGRYW8B0dHR9O/nF/J6da+eIJC/YTb+AD+drl69OgC6NUGQlsuXLz8i4jyJWTi0c+fO65YpUg7MDnH9cbZqAAcVDyXemSR095TD0CswglkE4S2+2aCNRL1wdAj4LZA2LwHbgsDon/KGwLbr1eMekb+/2y0HxqVtXzo1AtgGgaDsGhbHcXqLaMuiVUP2i+cXhE3eGkBj42HdfYLfEkuEm1DS09PPIvhcOOudlJTUwavFIMDy1IxLuYmlRYsW9EemC/a8vpAOBLS17UKPs27psLAwDmqwitPjgw8+CPdrJGJOThd4qsDGwfoWGThw4FMh0/btvnHjxlDwWXzTTFpCp+9fXDRy/cdooBUZ6tLzaOXRS5cu9WIdwFEE8hYjdtaCgoKgJtK9e/cxsF+PfdTVq8FOe0mLbL1v3779i+LWrxs0aNAockNCQnyDYDAgWhOEfe10dBiUrjzyyCPfAQuCGgccg0Y51SBF0WFiC44x0WhlZkFBgc3d15cPOgGBaTX3sO+eVY3ZYuBgi6ixMy++X8eFX/SFfVJVVVU3Lw9Ve5Kzyr45JCLnHRupKlPueT6nL+uq8CRwpy4TGRlpqCj8lZSUtAW7M+ivNmzYcBNYRF2sfx8dHf1X8fwMsgUDsnYqmgX2revXr7cDlqioqGPAnI2evnfkW7duKXi2oDVrZyt4ON6ZwTK2nQXLAGxZtGiRxXzAARMLo+MyEtygrZ4aRwsLC+1MWAeVlZWMMhpSvicIprgTjvpH4eQc0yswC8REYke3eNWQv6BW2e7lL+xMtG/fvhj12yraDymRAyBIw2AFLoosQkmzZs18flkNBHYZ1hUYzAQGH2Mj2I6QYqr6AZmitFiOJkgZhEaYDk+zAkDVo4sDC1URvG9w7+SrmFim23Xr1pXAXR6gVVFRkc3hVrGBB3puZwaDRd8NaMGb+C9Drz7uoJ7+WCfWWuWfgb/Cnf4UsLSYdYDrtAw32IOsA6iLdkUSEhKqURceSMDfAO7HBnsAWPI/6ncIOLTDwjzfvuASUPDqFUX09ZgBGC7lSrorwIsU+6G4sugDDzzgiXDMmDHRUOkOuJCamloKzMKTtzgiIuJ7VrBBlZiwePFiOmFQrHKm7sPo2IxUXV39LZiVLtER4Pls7PID3xYkCotxF/LJbf3nPmrvCcHUM4CH4PECHXE/AA9BPb9Lly43QAvu9HbvkAbUBHFjxeCvUNeoecdtGsZM0gc2t+mM19a2kNUrCLQerzEGgnXVkaNrHk5+fr5nJu6Zc5jrt6T809EZFJWWlvK8aO+IHvjwww+LyAOw40D+BUFyJr5TY7pi5F2ezKaZ0GpfVlbGZAHSc5iSqA2NLSfcBmo6iiBqaK+9X1+8EZrBEBYjdXF9y33zc3ugLrdWD7X7AzQPFz9DnBe2PnHixOuiehnrs+uIESMYPHai87+wCW85++BAYBarq47nTY4MC7jFWRzggURhbSjyBevc3UPeoFwOlrtxud3u1uD0RMsXcKPkhwF8alI2XhIcHMz1LdChLz/AeWEb4UcCNXIewuhr1655gtg2AVkKHo36XSmMOrw6CPyiSViIJ1Vbqs6j9kxAZNtCMHZvYYbBEoybE4yl6XJVVFRwNtCYdly6dGko7yJQ7oNITuMbE9e31NkPdEBw8OAMSeHK2GzQcv/CL3CBROcn7bKHU92sghkroB5O6DJiHK584bFkrYeCJgD5F+MYq49ly4snVrAW4UpS4crNzcXG1Z1Q77IrLGV9RouXfwe6s6rr7JIlS+yIom5HArhusY0h22DGzDUjOrVnz57TMev8cic3Vw874jWwI1i8cihvulKduJ3LV1Rc9qKonmuE4Ed/HBzfzQCsuwV6GGyRh/7tL8+AGyyqO4cPH17FWZB7c55/z4isFiMUMgisEPMjFFnomLgeuN1umIkgV+eLuHg1GdB2wecbRLTF1U8GzMMA7RD8oKdA0q5duxNwvE9EF+Cd+jhGdI6IVKFz1g9oB+/QnfF1PCkmJuZuZlPhyEMss/Gl/DCUX0VfT8ifE6x/BuHauHHjTzGX335RRFNEpBRQhE8oPLRA2gwGO5L+gGVmDz3sj2IsDWYkKJhDobsmxX355ZdpqLAo9HhtUFzXb1VteepXcLYagghAJMBBMDZI0NJ85oGR+Dr+Edb9w6xbMIb2JDth9KNU9I/N0icm79+//yqZDILTF4IDzNmXHLwCzL+ryNVWrVp9Dbpu4bIg+Pjoj4SycvbNB48DV8AW71M7rS1mgHuGOhBh7PDEnep8Zkro3EGX32b65asq9mY19SBlcfimF4Y0z76RAUOFWxEVXRGT/07vfSkhr+zZs4f3NsHPXgCBPXm8X79+j4poO7R6ZoPvJitWpuL5ceQJnprAr4jdbLhPsdEQMYovIGJ/mAG4smTth+0wBq380tLeTAilqmp5VkldHPVyl8tV046qL4s559566y3f0vLZ2AsgO1gt+OFlA1cHwyC6Y4O+iO9J8TgP+OZmO4NN1BVgb6xQZ+GXwm5du3adXDlh21wwFLm8f1xcHL90C3J97VmD2JZq7wwJpqClIg1arvdhcL0W0SC8vbFfUvvniDb31hkAg7VVjp4lvI9bwJdh/U/YoCvwPWkJ3up4moMtgre85OCpe99HJ1pbBh74ejHhwV+f3QxyGoBTc4GY4L2okfQDzJAdNFV1jCiaE+OngCytihRQhwmW42XRnna2yiDowDIwE4eu/LHf+Ivvdx8H39ioph1O8WCriYcxBhlDHywuLvaNiMCQGxTNmlVFK4cMvbl62L9jhIuhzneJmtFivRGAG38pNrY/w7/mp88gKGYgghG6ffbs2dPnz58/osbZZ0RLsTYNFTxgmLlu4YD0jYggCsqrb/1p+CZ8Rzpy4sSJb+GHnWdDlEkTfk3Sw9wE1PMFYdtxu91ca5ZncGVAL1x4dQSyYkGH+YYfJLV+8MqZUmQTH99mK6hAhGfTyt02GtHHPSOgnu2wzw4jyBEm+Fj1MLxU4trAkbYyjA59KJYiMXk1MlaaCE0KGG0F1PM1XL8tpI36zIAcbjJTayMizoB6XmZA1CQbo1gLAcwbDiKAckCW9zqNTa8B5b+QicxWY2mMp/M/fya8LgJ0TpHD7wavwn0ktWbCa/nLEBzXGwwwuO8adAibBmVM3g5uqHX3iP9mF3s5Mw17aVQCO0P/wB49VEAb5/bt21ymlokggkjgUIGMlD80GISqBME4pHlz/ilb7A8e+OUiAvcqXwYC356iQchiDfqCUsCC9I3OaSjOlRq5iott3ItvWJ3F+0Ng/OgsmG4764JTCVBTGm7YGLxnSAeTlJ5IbbyG3os1yc86nVrMOjiOvMTExIex/0lX4XOitUSPZAAAAXdJREFU56shBf8YMD4iHTt2ZBuhkclHR/pM1JhBoEPCp2eOAZaDBw82w4D2J+1S9f2NkPYYU3LFXrM9lOeJoAWjI1K1edwWEeU3p//A3+b+fHvMZ3tRHyb2Z97F+8DW0rGbtsHToyqyBx8M+NUj4KusNbn7gImngq8qp1DZrUbn82938LkPGWg2pcAzUN/7zvlB2VjXT4vK1g7Hk9EnSv0h0EwwEMGV+aeStcMXIDNsRicnqCi+BurSwrRYXvT4rjEJrh4yapY5WxN+8/HHH99G3ffuALLRgr6L2hvp1oTXVEwaOt0PFgMw6hvxd+s5KvIO6mwTf24wqdc+GfjSmjVrisBjn20fQdtChiVqPdgAq8q/ysTkv/2ca/vTMbpt/GD+k8qpU6dWP37utWfBG6nbJrj5p2D+MwkM0C7GDEQTCtsgKG33pYTNw98EY8P3TInJmBfyXE5Ozpp9KaFvIpgRobue6bc/JSzlzJkz1+CXbfgFAJ78PwAAAP//1eSkSgAAAAZJREFUAwA9dZ3ggvz3hgAAAABJRU5ErkJggg==) center/contain no-repeat }',
      '.serv-takeaway { width: 34px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAyCAYAAAA5kQlZAAAJxElEQVR4AayYCXCOSRrH+/sSxx6pdZQj2HXMYpMIggoiGXGso8RdKOMot9qpxYhSQ8UIYdcwllpxHyWuHVdRCllHTRI2ZZUN1tgZdjGOiKPcwViSSfb373xvNl+Okeur/n9P99P9HO/T3c/b/bpNMb+lS5f6zZs3r2V0dHSLuXPnlohFixY137t3b9VCKnxoCy5oqUtBR6zg6NGjB61aterK1q1bL+3Zs+fb7du3l4Rv1q9ff3XmzJlfN2zYMGbq1KmBW7Zs8cPyDx7kQqVfsLppl1g0SJ0amJuYmFgtJSUlGkZDkOZyuf4AXVoClsDfAOqBxUeOHLk0f/78c40aNVo2cuTIzjxMNfg5HlinYmNjHXuwvYvtYIAcMdevXze5ublS/K9Dhw71unv3bmxGRkZMCZgP/+OBAwcGNG/efBxOH0V1M+Rnnz59OpXpPU6koonwb/WA9OUsXLhQjlE1sudAbSNHXAyQx+by5cvt4TYBDzt06PA99L1l7dq1D4jiNpweNGnSpOBOnTqNQeg16Ar+lJycnAg/FadmTZs2rQ08FdlzIB+sI5YxYsSIKDxPYJQWXxiCiWAbof4LdDfYUwi7GzRosMvhqb558+a4s2fP/g4dWisQW3yJVgdqyw8cOCCHpPdL5NZMnz69FXxFyW29wdjS1NTUwzCbg6fgFegLxhLqKGgE6FQIERjoDy8SRFH/CNobaH1dhV7xIB0dDzz1/0KldwT04/37958cOnRoCPUc91y2JwOn0shG2RdDhgzpybS0Z+679+/fv2OPHj0C+vTpEwzfCwMGDGjVvXv3VvQHRkVFtR08eHCPfv36BYI2jO8CPwIaHhYWFt6rV68PQ0NDI5AJZ1w3oh+CrdWgPhGMw7Zx37p1qwqV6uC79PT0z+Lj4y+yUNOZ+2S25zm2bwbb8in8zIJYt27dsx07dtyh/8mGDRuurV69Omnjxo33wQuNF1903759dxISEq4dPHjwCTL/ZlzKihUrLrFBtBu1loKxbdx16tR5SUUha8G8TRs3blwEiGS1R4qOHz++a0ko2K/6mDFjugoTJ078UEBHhKh4gvRMmDCh25QpU3p5HPkZtv8OjJv9fickJGQ6jSxC9cXJkye/Aiz25GTREydOpJSEgv2qJyUlpQjHjh07JaDktKh4gvQcP3486ejRo3/FltbU9UGDBq3Ctt01hmS0vWfPnoNhPAaaqlQGzqAeDRazhj6lPR184gDebOp6gBhoHO1Y8CkQX/O+EtkE2jK0CKq+z+E9BG7ap8eOHRu2Zs0aRcRldw0dvtu2bTuKwt3UTbdu3WaQF1aRsFaCz+7du7eMdjz4swN4y6nH0/9HaCztOLAMiK9EGE3feNozoPOh6puLA1/JRkRERMKSJUseUfcFuY4jyiW0zX/0d+PGjbqi7du3V3T0AqsMyKDhYa1NPz+/LNkA1rZl0rClbdu2r1S5fft2a1EPnJdYZVC9QvQOeoEjZz36vRyxDXaQ9dLtdms1m/Pnz1u+R6CixNH1U6KS0blz55sehZbvFRG8VDZ8xzz+xjNI6dd5OVWEyo7kNTXV0P9Ds2bNxPOYMXm7hpb1ysfH51vq98HPgYocUV9FYfWkpaVpzdVGcRbrTzyqecXLq9atW8vgc7pCYmJi9KJyLViwoCqZswoJyJfTmM/7oHEkrCqSkaxArtK6MGTdYegOJiJXmB6tOW0C2cyPCP3GPHz4UAIuGv4JCQnJZNqvEb4QFxd3gTfreU5j7wXj0khY5yUjWdoXOZv8Q7rgb0G3pkcOqJqrP8GJiIzrPNIdpnK/Uv431J/j+QuooB2ld0NpoNdGJrLPefpnyNs2VDplPFwvW9qaHuuD/YNhHUlJSdEiRd61jCTUCUSALiAciJYVkhMkF75z504dGZKw1+jmzZsdoSrWtuOIGIJlspAS1QCF+2GVv5CxFVVtBk2Pl26vBiYURsPRoAV1ExkZqX45VxmQLqnVCdDUrVtX06+2psp7sXbp0sWeU588edJAI16+fCkHNLAyoPUgtT/h7xk5Kw2aXxwvZcjUrl3bRoQF5q8RZFZHWM2KQA/kyPuh/x6p4paHYW07jlier6+vXsl6TX9gGcZor3uqlUaqshtyhg0b5mXbaVivatWqpW2mBWXbmC74JDTLXawerrE6nAfgyH1g32uORscR26ZTU/GGRl3Cp6Qjh6wSeOUuZFsr++jRo19RqYnuDKiKjgay4b1Y/f39xVTCasP1sZ1GoqTCjnCBkyrz4MEDZW7V7xsjYmTPVrwiwq6RUXVWf/z48S/siMr5k145oh1jmjZtqgzrpdnLkaCgoBymR5ch8+KFsroxztN4SZW9YR3huqKbnalZs6ZNE6jRQ0NM/tSI4caJbObvuHpwRGtEVZf+KgmNpIfrqTaFqvnIjwjbyRps167dXfW+fftWC0lV8SsC2ci2ilyuD3jYm/Xr17+oNlAAICY/IoYbmWXWqFHDhu3q1at97AhjpER95YV2ouFYoLd6GyJ+n+nW/dqjPo/I27yayVvBOTk5Sr3XEPg9Z4hEwrgTurcC2IOOXVxfv8RQLfAdKFIKO+Kza9euzJYtW8Z4RvblIK0bv76blAdKAR2Zjo9AEDpfcbPTxYuqnQ1FWXXbsBX9kTNsR5MmTew6gZcxatSoMM4RLckrwStXrmxVFiDTGtlf8xVgDLpU/sbN7oIqwE4Z1JaCEbEM/bGY3kLfMj3PuTxf4xyRzce674cPH/6mLJCMZM+cOXMOfTqD2LMI9SJ2vRgsIhsRvnfoWvFPwlnt1KlT9vzArtJ2dqGk1OCApVO7ef36tU7uuly9Q17F2lHFgZcjMDXAzceVN0TjBm1eyL42hOwqUfWXGs4xAods/kCfPWZA9TCQ/5fCjuT3EA1t22rcgxUJ8YsIi1ka8FnslxoXEBBwQhQU0fVjjug17Z+ZmRmAYEWLoqipcfKHbRdUWqIjTI2dz8OHD+tjW0GZMteJrn3rVq9eXdNbrHxxjlgewvoyqJVulSBdJJzwfqxovD3h8VDKJ++45Gs3FitjjRbqsWEjG54RHyXOPVhjtV5KBW4AGmc2bdqkL9n6NJrNW9fqJl9JtRek3ItBww5u3Lix9R5HmsBT0ZrRE5YKXNa02E1WVpamWF8tH9WrV0916SqCEh0JDAzUcS6dKZrEB+FlvGtmglkgupSYhdyMxYsXx2O1Djg1Z86cO1B9ci+yVkpyxE1yexoaGvoJghlEZTZU30X1MU60NPgcueU8iL4eXiM3LUeHitaOqBeKc0QD5LGLj7QHJk+eHNK3b9/A3r17B4NWZQFfn4OQDeLrYWcS4mUUywnppupd/gcAAP//XOtk0QAAAAZJREFUAwCONGCXkxCKagAAAABJRU5ErkJggg==) center/contain no-repeat }',
      '.serv-takeaway-active { width: 34px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-valet { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-valet-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat; }',
      '.serv-wheelchair { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAP7UlEQVR4AcyZCXSN1xbHv3sTlLS0tBTVxhBSrVbRaBERqm0o0iqJsUqDPkNp+x612mrV0oFHqVkQjSERRJCgnilCzWKeWrOgxmeW5Cbv9z/u50Uq5EnfWu46/7vPsM8+Z5+zzz7D57T++3Mo2qlTp6KtWrXybd68+XNCSEjI861bty6nMjcMnzv+wBBbEdHMZs2a1Vy0aNH2xMTErWvXrt0GticlJW1euXLl/tKlS49x9zrTTR8oIgVudWjjxo3dSZQCV8BeN45Dxde1evXqdYgreOjvQYI6qP7Y5nJUCfAYkDmVh5YECheKFy9+ThHwwM2KrUgGnbMwrUHQfpmZmYnQzdBfwW/ELejxXr16HbZu/h5YRdQxx+jRoy8fP37825SUlIBjx475iw4YMKAufT/kcDjSXC5XOnHNnkwrr/BElj2QRPMWsgoyyiDO5NFxF3Hrgw8+kDmdJ16gUaNGN6Dik0IqzwskQ5Zg2kNunkJ2IeqkhFstWrTQiFtfffXVk7RQDPiUKlXqy6ZNm74H7fbUU0+F4ck+hH4EepLXHXQjrzPoqjj5PYh3hoYpTbwr6NK4ceOOOI7gevXqeSNX7WXvB9n/W8hJgCMmJsbVpEmTxydMmDAXkU8DT2bp602bNkVAR7JmxpM3GvojGE7eT2AkeePAGMXJH0F8HHS80sTlwscmJydPPHnyZOz+/fs31qpV62Xy86xMToqY/EuXLjWjkZogHrN6MzAwMCggIKCxUL9+/Tf5BUD9X3/99dqvvfZaHWg96GvkNWjYsGGgygMDA+sKdlmDBg0avvHGG/VR7AfkFjt06FBnqILWnuh9wXQ4p5rXr1+/pDIaTT916lQqo5h57tw5i3jGiRMnPA8ePOiFc3jk8OHDRY4ePVr4yJEjBXES+cgroLjKcRgPC+R7wZMfmo90WkZGRkG3bGPCxGXWkPsLOSmiqbZeeOGFBYhdgmk0w6SW7d69e9H27duj9+zZE0M8au/evTNBtA3lC+60ygSVz9y1a5eoEIeMVQxOD2SfrVq16mRonkNOimh0nOPHj7/69ddfh9LKIXChbt26/tWqVSvr7+9fCXOpKBD3zQI731DMywdUZB34iBdaMTg42AdZM4GFvFbx8fGriGtWzOARv6+QkyISJmVs93uBjFMzZsxImj9//pmoqKiUqVOnnhAUzwrl2YiIiDgp4DhOKk901KhRh319fSORd8PT09NstsRNW9D7DndTxAjFrDRa2rwyd+zYkd9kuv8oc/bu3btCu3btnm/Tpk0V4jrWuEtvESeuNh8pJ+5WcqwrV66o3UwPDw+TpizPQQLvJUTeRCNWiNko1bZt2wrsBV+wLyxif9gyc+bMvcuWLdu+YsWKbcT3ULYOJIAvcN9lEZ7B+koThUeyiN4M6enpknszkcf/HBXp37+/aZRFqR34Mu08PXLkyOTly5fvJz7A6XSWYUZ2UB5BejzQ3hFJ+nfi2ugGbN68+QAKJeGOg8hTkEIWCihuFShQwNC/4i8nRRwscjNaLE51ooa7sSN0LIwFXx03+hxutA20E+62C/ib4qA18covvvjiS8xYJ+pVwItphpLYS7QnWZiUBsdx44ZOPHD8BeFOiihPSmTS6V4bNmxIoJ0U8CW/6uvXrw/HxDaPGDGiAMeYstooMbP+8A7g+NIUxctzrCmUkJCQvG7dukl9+vR5hpn7O/Vr79y5c21QUFBHlK1F2uP/OSMyJ+MG6dhQGhtGJ2LZiaswyt906dIljZ35ZcrGff/992fXrFmze+vWrQsxp6/g/YK1EIfiOznWnIXnZ/H27NnzBjM3BDdcFr6527ZtmwjvZ+CKy+VSe0TzHjT6thQJ1UxYdOKfZPam4VF04p1JkyZdYuH6kp/Ahreeso4oGFe+fPn3cKVBNWrUqP3qq6/WrlSpUqNy5cq1pTwatBMvdaI5qnjjhg8xE2+Tr7OYE9mOtLQ0eTOL9Uh23kJWRUwcu+6JyI9BBA3r6ivFPmTh7ibPH/Rjc3saBdtwt49eunTpori4uDWzZs1ag/dauGrVqlnMXoeaNWuWgVcD0pKdPBmTakLaoqwrdDIDUXjFihVSzGI9mrbJv+9gBGDr2itc2LcfDQxH2jKU6ASVEp9AR4N1dKYCHflWmxtpzaDqSUZWKM8xZ86cY/B+ygJ/Dt49mNQ81pLWipTRjMaSP7B58+ZyAFr8kkHW/QVVNkd2Vce+f4LKjNoz9RmYxYekh6BcLKPuHx4efoq0NjHVkxnqYqU1lRXKU5l4nJMnT941bNiw2sibC35g3QQjw0JBzbrFS43WorIkQ/S+YBpTTUwqDOoHPh07duxx3rICiWsm1sybNy+EdZDm3pk1erlpVDwZqtOyZUtXZGRkC+StxxVPsdcM6U9BLZR7E2pVrlxZJwfNqA3NuoruCSniwp0WgrMX+H3gwIE/Qy3esr6B/psHiea2Eti0lCA790F1pAzrKh3P9R6zW5g1o7uIxU1RF63zKNdPEjkhp0I1ozY0s7lSRopYnHLL0kBlMPb999+/zuVIF6rapAfxIHES4Z7qEDR7UCOSISrY8dv43HWdeK49FAwDLXDp1Wj3KnHdNv2xiI80MyjbVGB/eou1+zDluVJGDVtbtmypTwXrlVdeWSaK25SHuYKgKUoDmQnktqCOqxGViQp2XGW3MduJKlWq6PprsTl2Ux7mtFiUQfuRdhcyK3EC+9P8M2fOvKMykKM8ykwwinBbe5eFeHD27NlbBg8e7EW6MaWLhw8frsUte1UHyboVJJi2Mx3YeyOcQgxIYAG/6+aQUuJxJw2RDAdPsrrbRFG5FSeA/NxR5NaVd468Pn5+fp3oi+7+FianM5upfK8/HcNLU7EuQnZBM5l+P+iTzz77bFwOldVBddQqU6ZMOPYeD19T0JBRjsHF6gBJ0hKPeBW3YQauZMmSs8lwwl+aPeQc7SWTPpSSkvJDbGzspKFDh2rnv8TBVGuGonsHJwc3IxzWM0ChpP4ee+yxP0TvAMNfp06dViiv/SA8LCysbLdu3UqRDqdTYexHzd31sisi5Sxka20U4E3AV3xYgNr27du3bzmlOerIClKRV0Lp3MDJS4kaS/Xx8UlShfPnz5eG6ti+HapgGlfEDZPm9SOE9B9sep0xkZR+/fqd7t69u7xPGo/h7ShTMLyKZAUjbR41OC08q3yU195U6PTp0/KeyhJU136iVR+VlyPM6KqUNyY7rkoPcTKVx1DRHcFoXafAi+vrQ1ATLly4oLOTg7K7ns+ZAe0XFgtfr5imbta/ggULSomMEiVKXMiaf7e485FHHlElCX5FjI8++qg+I3impqZWVBpIMcjtwdvbW/uNF9fbsezeDzEr+adNm/YtXJ5cbe2XkTvWvXbtmkZel6zN8Fsorv3p6hNPPCFLsChXPRtiuSecdFxCxFhCfwjVDc/i3cqklZcNxvusXr06AZMYTFn7zz///DxH92uKU/97TgKLiCuIV9SGOmdx6vUiw0XbF6EWcopAt3733XfyXtbFixdlHU74yM5dcA4aNOgErP8CPnTCwcLdSjzlwIED5kxEXDMG+XPgYPkPvJQ81gxKpyqO5+lLXMF0WpEsMIpRrxV5GVwBUrjne6CIbqC3zAyT0s6ekS+fLBXOXARprqmNhdcnNDS0xscff3wNheIR7s8C1qyoccMHjx1s5RwcNOez4DuC9xSHQQoINg9ZJkhGZnBwsD4eSflIzDF1zJgxNWhPly69bxnGq1evZq9r8u/2J+EWNr1STHwvfF2URRiP8MIRERG6OyjL8CmSDWpQrjIrlCdkY7WknMVVuacKaEM3RWvfvn3mVMFGOE35AoOo9gy/0rmBKli8S+2HWZtSFzajgosXL45DkUQEfkaZXuLTdfCD505BZpAVf+Jx13Xp7QuZUiSeHX5D586dZTu6vK3Q/YWKpj9ur2XBqwHSJw6KLCl2J6jMUkUPjtmpVatW1Wm3DIp0VgnPmdpdC3Dri9HpWAc/d4dUnGsw2/lUd9y4cYU4UUdR8QabqbmLLFiwQPtOKc5bA8m3kK/+WG7TcuBw5E0tXLzMW7N8J5g6+tNoOniDnaNZAF+2b9++WFRU1BpcbAca8OvatWu8rQxpbV6qp9EhmWMQjye7dJqU4BPefGRXZsBaR0dH7+vQoYM3I96f2suXLFmyFOpAYXWYqCW4KG+0fPnyh6nnZJPNlx1iAqrjUGOaOkO5Z2vai3IPj8ab5MfFTkGYZqgeyiSGhoZ6U1HuWpU1Opp6QfVFbUhJ8aTzaFEWJdZQT2vhEw0Ysj3ovM5kLt7I9CpPsTEdybQKFSqk+jrGNMYc13LET+JetNoNxRMV56A6mzuNTgeZ6oCmTrPiwYFNrlfT3oCNThuehaucULFixTa0VJ2HhQO42O84iuhbPFmW6gnqtKiNzI4dO5aiIfPaCKMPnyja4tnMtRbZU8lryCNee2Z6J3ENgGQQtayzZ8/qCdL0jYHUnV+btb5sCbrF2ul3kpOT57HWihhmU9uyJMhBY8OoPIC8EDoSjVnkY3qn8+ypF5SVlPVB4d8YpUSU6oY7DeK9qxlOoREPdI2p0xssWbx4sTbWL+Cfy+2w2sKFC6fx7ONJ2Sxkh4KhPOJp/1EfNABkmVnRhqkZsY9IOlCuo3CTG3qOEraQVqhw+fLlYhKihGCmVRFmoT8dkDItMYuFWjORkZFJKBmIKTTCZieAZ+AZyd6RwKKdi1OIZz3ow9AQZPiCSSz0AGS9zavLXkatfHh4+C/k62T8DbL0OkPSHPdFBTOYvBn8QULXYIj1OH96aakOFTQjwkukFcZNnz79QFZFlCllTB4d6E9n9XzTgDWzl5HsxQb2MKawkN37I+72lch7OX/+/C2LFy/+AZW7YttN2JUr8bRaiY5246iS2KNHj8LM3kesjd3wBAK9E38JVVBbalPx20D9z5Ffq0iRIu28vLxCixUrFlS0aNGWpNtytGlVuHDh9sh9FT6z10nQbQJImFGBOujwED8/v6rEt4Fh3LH3Y07fYkK1p0yZks7mtpHvhDFclScicBwn6AUczX/D1K6zCGvR0ET2hz0MyI/UXxQQEFADPi1ymY6gtii6LdiKOZD/K9feqWya0byL6bNfDOlpXMii+PQXydvyWmpKzk2vRSJ7sIUZB0Dj9VmsLTClw6AvJpREpw8zYkvobCzKjSc+BkSBX8ARFuFqFNDFa6u3t3ddZDTFBGTn9uDZbWRv206r3APz1KYpl29Qr149T8GdLychvptey66ZjYrBxQOEmB0s1lmYW20EyP71cKfFpityMMqFUbcL0GWrPjQZJbqjfAVmNQg3rnOU0y1LsyDZsN0zuBg0Hffl8g3Ya9IFd77tJMzOfldp7KpiVsM6pbqw+72M7ljwFj7+8ZCQkKfx6cIzfM16hvijKkOBUSgvz6Wp12BkuGXdtb37LfwPAAAA//+DkbztAAAABklEQVQDAFJcpDq+p9vMAAAAAElFTkSuQmCC) center/contain no-repeat }',
      '.serv-wheelchair-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-wifi { width: 67px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-wifi-active { width: 67px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-curbside { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      '.serv-curbside-active { width: 50px; height: 50px; display: inline-block; background: transparent url(data:image/png;base64,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) center/contain no-repeat }',
      /* Inactive service icons - reduced brightness & saturation for lower contrast */
      '[class^="serv-"]:not([class*="-active"]) { opacity: 0.6}',
      /* Active service icons - enhanced brightness & full saturation for higher contrast */
      '[class*="-active"] { opacity: 1; filter: brightness(1) saturate(2);}',
      /* Dark theme inactive - even more reduced for visibility */
      '[wz-theme="dark"] [class^="serv-"]:not([class*="-active"]) { filter: brightness(5) saturate(1); }',
      /* Dark theme active - enhanced for visibility */
      '[wz-theme="dark"] [class*="-active"] { filter: brightness(1) saturate(1); }', //filter: brightness(1.8) saturate(1.3);
    ];
    $('head').append($('<style>', { type: 'text/css' }).html(cssArray.join('\n')));
  }

  function onVenuesChanged(venueProxies) {
    deleteDupeLabel();
    _previousVenueServices = null; // Reset when venue selection changes

    const venue = getSelectedVenue();
    if (venueProxies.map((proxy) => proxy.id).includes(venue?.id)) {
      if ($('#WMEPH_banner').length && venue?.id && !_isHarmonizing) {
        // Compare current services with previous state to detect services-only changes
        const currentServices = JSON.stringify((venue.services || []).sort());
        const isServicesOnlyChange = _previousVenueServices !== null && _previousVenueServices === currentServices;

        // Skip harmonization if ONLY services changed (UI sync handles it)
        if (!isServicesOnlyChange) {
          // Auto-harmonize when venue with banner is modified (but not if already harmonizing)
          harmonizePlaceGo(venue, 'harmonize');
          // Refresh all highlights to sync layer features with updated venue properties
          refreshAllHighlights();
        } else if (_previousVenueServices !== null) {
          // Log for dev visibility
          logDev('Skipped full re-run — services UI sync only');
        }

        // Update tracker for next change
        _previousVenueServices = currentServices;
      }

      updateWmephPanel();
    }
  }

  // This should be called after new venues are saved (using venues'objectssynced' event), so the new IDs can be retrieved and used
  // to replace the temporary IDs in the whitelist.  If WME errors during save, this function may not run.  At that point, the
  // temporary IDs can no longer be traced to the new IDs so the WL for those new venues will be orphaned, and the temporary IDs
  // will be removed from the WL store the next time the script starts.
  function syncWL(newVenues) {
    newVenues.forEach((newVenue) => {
      const oldID = newVenue._prevID;
      const newID = newVenue.id;
      if (oldID && newID && _venueWhitelist[oldID]) {
        _venueWhitelist[newID] = _venueWhitelist[oldID];
        delete _venueWhitelist[oldID];
      }
    });
    saveWhitelistToLS(true);
  }

  function toggleXrayMode(enable) {
    setWMEPHSetting('WMEPH_xrayMode_enabled', enable);

    const layersToControl = [
      { name: 'Roads', setter: 'setRoadsLayerCheckboxChecked' },
      { name: 'Paths', setter: 'setPathsLayerCheckboxChecked' },
      { name: 'JunctionBoxes', setter: 'setJunctionBoxesLayerCheckboxChecked' },
      { name: 'Hazards', setter: 'setHazardsLayerCheckboxChecked' },
      { name: 'Closures', setter: 'setClosuresLayerCheckboxChecked' },
    ];

    if (enable) {
      // X-ray mode: Hide background layers to see details underneath
      logDev('X-Ray: Enabling - hiding background layers');

      layersToControl.forEach((layer) => {
        try {
          sdk.LayerSwitcher[layer.setter]({ isChecked: false });
          logDev(`X-Ray: Hid ${layer.name} layer`);
        } catch (e) {
          logDev(`X-Ray: Could not hide ${layer.name} layer:`, e);
        }
      });
    } else {
      // Disable X-ray mode: Restore all background layers
      logDev('X-Ray: Disabling - restoring all background layers');

      layersToControl.forEach((layer) => {
        try {
          sdk.LayerSwitcher[layer.setter]({ isChecked: true });
          logDev(`X-Ray: Restored ${layer.name} layer`);
        } catch (e) {
          logDev(`X-Ray: Could not restore ${layer.name} layer:`, e);
        }
      });

      // Restore editable data layers to normal opacity
      /*
            try {
                sdk.Map.addStyleRuleToLayer({
                    layerName: 'segments',
                    styleRules: [{
                        style: { strokeOpacity: 0, fillOpacity: 0 }
                    }]
                });
            } catch (e) {
                logDev('X-Ray: Could not restore segments layer:', e);
            }

            try {
                sdk.Map.addStyleRuleToLayer({
                    layerName: 'venues',
                    styleRules: [{
                        style: { fillOpacity: 1, strokeOpacity: 1 }
                    }]
                });
            } catch (e) {
                logDev('X-Ray: Could not restore venues layer:', e);
            }
            */

      redrawLayer(_dupeLayer);
    }
    if (!enable) return;
  }

  /**
   * Applies highlighting to venues based on harmonization results and cache state.
   * Sets wmephSeverity on each venue; clears and rebuilds color highlight layer features.
   * Respects rank lock filtering and current UI checkbox states (ColorHighlighting, PLATypeFill, ShowFilterHighlight).
   * Trims results cache if it exceeds MAX_CACHE_SIZE.
   * @param {Array|Object} venues - Single venue or array of venues to highlight
   * @param {boolean} force - If true, bypass cache and recalculate severity for all venues
   */
  function applyHighlightsTest(venues, force) {
    if (!_layer) return;

    // Don't highlight if WME Venues layer is hidden
    if (!sdk.Map.isLayerVisible({ layerName: 'venues' })) {
      return;
    }

    // Make sure venues is an array, or convert it to one if not.
    if (venues) {
      if (!Array.isArray(venues)) {
        venues = [venues];
      }
    } else {
      venues = [];
    }

    const storedBannServ = _servicesBanner;
    const storedBannButt2 = _buttonBanner2;
    const t0 = performance.now();
    const doHighlight = $('#WMEPH-ColorHighlighting').prop('checked');
    const disableRankHL = $('#WMEPH-DisableRankHL').prop('checked');

    // Clear layer once at the beginning if needed
    const shouldRefreshLayer = doHighlight || $('#WMEPH-PLATypeFill').prop('checked') || $('#WMEPH-ShowFilterHighlight').prop('checked');
    if (shouldRefreshLayer) {
      try {
        sdk.Map.removeAllFeaturesFromLayer({ layerName: _layer });
      } catch (e) {
        logDev('Error clearing highlights layer:', e);
      }
    }

    const colorFeaturesToAdd = [];
    venues.forEach((venue) => {
      if (venue && venue.id) {
        // Don't artificially add PARKING_LOT category based on services
        // Only venues that actually have PARKING_LOT as their primary category should be treated as parking lots

        // Highlighting logic would go here
        // Severity can be: 0, 'lock', 1, 2, 3, 4, or 'high'. Set to
        // anything else to use default WME style.
        if (doHighlight && !(disableRankHL && venue.lockRank > USER.rank - 1)) {
          try {
            const id = venue.id;
            let severity;
            let cachedResult;
            // eslint-disable-next-line no-cond-assign
            if (force || (cachedResult = _resultsCache[id]) === undefined || venue.updatedOn > cachedResult.u) {
              severity = harmonizePlaceGo(venue, 'highlight', undefined, false); // false = cache miss
              addToResultsCache(id, { s: severity, u: venue.updatedOn || -1 });
            } else {
              severity = cachedResult.s;
              wmephStats.cacheHits++; // Direct cache hit (no harmonizePlaceGo call)
            }
            venue.wmephSeverity = severity;
            venue.wmephParkingType = sdk.DataModel.Venues.ParkingLot.getParkingLotType({ venueId: venue.id }) || null;

            // Add color feature to layer for visualization
            if (venue.geometry && severity !== undefined) {
              colorFeaturesToAdd.push({
                type: 'Feature',
                id: `color_${venue.id}`,
                geometry: venue.geometry,
                properties: {
                  wmephSeverity: severity,
                  venueId: venue.id,
                  name: venue.name,
                  isPoint: venue.geometry?.type === 'Point',
                  isResidential: venue.residential === true || venue.categories?.includes('RESIDENTIAL'),
                },
              });
            }
          } catch (err) {
            logDev('highlight error:', err);
          }
        } else {
          venue.wmephSeverity = 'default';
          venue.wmephParkingType = null;
        }
      }
    });

    // Add color highlight features to layer
    if (colorFeaturesToAdd.length > 0) {
      try {
        colorFeaturesToAdd.forEach((feature) => {
          sdk.Map.addFeatureToLayer({
            layerName: _layer,
            feature: feature,
          });
        });
      } catch (e) {
        logDev('Error adding color highlights:', e);
      }
    }

    // Trim the cache if it's over the max size limit.
    const keys = Object.keys(_resultsCache);
    if (keys.length > MAX_CACHE_SIZE) {
      const trimSize = MAX_CACHE_SIZE * 0.8;
      for (let i = keys.length - 1; i > trimSize; i--) {
        delete _resultsCache[keys[i]];
      }
    }

    const venue = getSelectedVenue();
    if (venue) {
      venue.wmephSeverity = harmonizePlaceGo(venue, 'highlight');
      _servicesBanner = storedBannServ;
      _buttonBanner2 = storedBannButt2;
    }
  }

  /**
   * Sets up SDK event listeners for the color highlights system.
   * Listens for venue data changes, additions, removals, map zoom, and map movement.
   * Triggers refreshAllHighlights() on each event (with harmonization state check for data-changed events).
   */
  function bootstrapWmephColorHighlights() {
    // Listen for venue data changes (when existing venues are modified)
    sdk.Events.on({
      eventName: 'wme-data-model-objects-changed',
      eventHandler: () => {
        errorHandler(() => {
          // Only refresh highlights if not currently harmonizing (avoid clearing cache during harmonization)
          // harmonizePlaceGo will handle cache clearing and refresh for its own venue
          if (!_isHarmonizing && !_disableHighlightTest) {
            refreshAllHighlights();
          }
        });
      },
    });

    // Listen for new venues being added
    sdk.Events.on({
      eventName: 'wme-data-model-objects-added',
      eventHandler: () => {
        refreshAllHighlights();
      },
    });

    // Listen for venues being removed
    sdk.Events.on({
      eventName: 'wme-data-model-objects-removed',
      eventHandler: () => {
        refreshAllHighlights();
      },
    });

    // Listen for map zoom changes to refresh highlights for newly visible venues
    sdk.Events.on({
      eventName: 'wme-map-zoom-changed',
      eventHandler: () => {
        refreshAllHighlights();
      },
    });

    // Listen for map movement to refresh highlights for newly visible venues
    sdk.Events.on({
      eventName: 'wme-map-move-end',
      eventHandler: () => {
        refreshAllHighlights();
      },
    });

    // Rebuild all highlights based on current checkbox states
    refreshAllHighlights();
  }

  /**
   * Converts string to title case (first letter uppercase, rest lowercase).
   * Special handling for single-letter abbreviations and common short words.
   * @param {string} str - String to convert to title case
   * @returns {string} Title-cased string, or original value if null/empty
   */
  function titleCase(str) {
    if (!str) {
      return str;
    }
    str = str.trim();
    const parensParts = str.match(/\(.*?\)/g);
    if (parensParts) {
      for (let i = 0; i < parensParts.length; i++) {
        str = str.replace(parensParts[i], `%${i}%`);
      }
    }

    // Get indexes of Mac followed by a cap, as in MacMillan.
    const macIndexes = [];
    const macRegex = /\bMac[A-Z]/g;
    let macMatch;
    // eslint-disable-next-line no-cond-assign
    while ((macMatch = macRegex.exec(str)) !== null) {
      macIndexes.push(macMatch.index);
    }

    const allCaps = str === str.toUpperCase();
    // Cap first letter of each word
    str = str
      .replace(/([A-Za-z\u00C0-\u017F][^\s-/]*) */g, (txt) => {
        // If first letter is lower case, followed by a cap, then another lower case letter... ignore it.  Example: iPhone
        if (/^[a-z][A-Z0-9][a-z]/.test(txt)) {
          return txt;
        }
        // If word starts with De/Le/La followed by uppercase then lower case, is 5+ characters long... assume it should be like "DeBerry".
        if (/^([dDlL]e|[lL]a)[A-Z][a-zA-Z\u00C0-\u017F]{2,}/.test(txt)) {
          return txt.charAt(0).toUpperCase() + txt.charAt(1).toLowerCase() + txt.charAt(2) + txt.substr(3).toLowerCase();
        }
        return txt === txt.toUpperCase() && !allCaps ? txt : txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
      })
      // Cap O'Reilley's, L'Amour, D'Artagnan as long as 5+ letters
      .replace(/\b[oOlLdD]'[A-Za-z']{3,}/g, (txt) =>
        txt === txt.toUpperCase() && !allCaps ? txt : txt.charAt(0).toUpperCase() + txt.charAt(1) + txt.charAt(2).toUpperCase() + txt.substr(3).toLowerCase(),
      )
      // Cap McFarley's, as long as 5+ letters long
      .replace(/\b[mM][cC][A-Za-z']{3,}/g, (txt) =>
        txt === txt.toUpperCase() && !allCaps ? txt : txt.charAt(0).toUpperCase() + txt.charAt(1).toLowerCase() + txt.charAt(2).toUpperCase() + txt.substr(3).toLowerCase(),
      )
      // anything with an "&" sign, cap the word after &
      .replace(/&\w+/g, (txt) => (txt === txt.toUpperCase() && !allCaps ? txt : txt.charAt(0) + txt.charAt(1).toUpperCase() + txt.substr(2)))
      // lowercase any from the ignoreWords list
      .replace(/[^ ]+/g, (txt) => {
        const txtLC = txt.toLowerCase();
        return TITLECASE_SETTINGS.ignoreWords.includes(txtLC) ? txtLC : txt;
      })
      // uppercase any from the capWords List
      .replace(/[^ ]+/g, (txt) => {
        const txtLC = txt.toUpperCase();
        return TITLECASE_SETTINGS.capWords.includes(txtLC) ? txtLC : txt;
      })
      // preserve any specific words
      .replace(/[^ ]+/g, (txt) => {
        const txtUC = txt.toUpperCase();
        return TITLECASE_SETTINGS.specWords.find((specWord) => specWord.toUpperCase() === txtUC) || txt;
      })
      // Fix 1st, 2nd, 3rd, 4th, etc.
      .replace(/\b(\d*1)st\b/gi, '$1st')
      .replace(/\b(\d*2)nd\b/gi, '$1nd')
      .replace(/\b(\d*3)rd\b/gi, '$1rd')
      .replace(/\b(\d+)th\b/gi, '$1th');

    // Cap first letter of entire name if it's not something like iPhone or eWhatever.
    if (!/^[a-z][A-Z0-9][a-z]/.test(str)) str = str.charAt(0).toUpperCase() + str.substr(1);
    if (parensParts) {
      for (let i = 0, len = parensParts.length; i < len; i++) {
        str = str.replace(`%${i}%`, parensParts[i]);
      }
    }

    // Fix any Mac... words.
    macIndexes.forEach((idx) => {
      str = str.substr(0, idx + 3) + str.substr(idx + 3, 1).toUpperCase() + str.substr(idx + 4);
    });

    return str;
  }

  /**
   * Normalizes and validates phone numbers (USA/CAN specific).
   * Removes formatting, validates area/exchange codes, strips leading 1, and handles extensions.
   * @param {string} s - Phone number string to normalize
   * @param {string} [outputFormat] - Optional output format preference
   * @returns {string} Normalized phone number, or original input if null/empty, or BAD_PHONE if invalid
   */
  function normalizePhone(s, outputFormat) {
    if (isNullOrWhitespace(s)) return s;
    s = s.replace(/(\d{3}.*[0-9A-Z]{4})\W+(?:extension|ext|xt|x).*/i, '$1');
    let s1 = s.replace(/\D/g, ''); // remove non-number characters

    // Ignore leading 1, and also don't allow area code or exchange to start with 0 or 1 (***USA/CAN specific)
    let m = s1.match(/^1?([2-9]\d{2})([2-9]\d{2})(\d{4})$/);

    if (!m) {
      // then try alphanumeric matching
      if (s) {
        s = s.toUpperCase();
      }
      s1 = s
        .replace(/[^0-9A-Z]/g, '')
        .replace(/^\D*(\d)/, '$1')
        .replace(/^1?([2-9][0-9]{2}[0-9A-Z]{7,10})/g, '$1');
      s1 = replaceLetters(s1);

      // Ignore leading 1, and also don't allow area code or exchange to start with 0 or 1 (***USA/CAN specific)
      m = s1.match(/^([2-9]\d{2})([2-9]\d{2})(\d{4})(?:.{0,3})$/);

      if (!m) {
        return BAD_PHONE;
      }
    }
    return phoneFormat(outputFormat, m[1], m[2], m[3]);
  }

  // Alphanumeric phone conversion
  function replaceLetters(number) {
    const conversionMap = _({
      2: /A|B|C/,
      3: /D|E|F/,
      4: /G|H|I/,
      5: /J|K|L/,
      6: /M|N|O/,
      7: /P|Q|R|S/,
      8: /T|U|V/,
      9: /W|X|Y|Z/,
    });
    number = typeof number === 'string' ? number.toUpperCase() : '';
    return number.replace(/[A-Z]/g, (letter) => conversionMap.findKey((re) => re.test(letter)));
  }

  /**
   * Executes multi-action updates. Currently a stub; changes accumulate as unsaved via SDK.
   * User commits changes via WME Save button.
   */
  function executeMultiAction() {
    // Changes accumulate as unsaved; user commits via WME Save button
  }

  /**
   * Splits a place name into base and suffix/localizer parts.
   * Suffix is anything after whitespace followed by dash or en-dash, e.g., "SUBWAY - inside Walmart" → base: "SUBWAY", suffix: " - inside Walmart".
   * @param {string} name - The place name to split
   * @returns {Object} Object with { base: string, suffix: string }; both empty strings if name is falsy
   */
  function getNameParts(name) {
    if (!name) return { base: '', suffix: '' };
    const splits = name.match(/(.*?)(\s+[-(–].*)*$/);
    return { base: splits[1], suffix: splits[2] };
  }

  /**
   * Updates a venue with new attributes via SDK, marks changed fields in UPDATED_FIELDS.
   * Optionally runs harmonizer after update (asynchronous via setTimeout) and refreshes the WMEPH panel.
   * Changes accumulate as unsaved; user commits via WME Save button.
   * @param {Object} venue - The venue object to update
   * @param {Object} newAttributes - Object containing attributes to update
   * @param {Array} _actions - Actions array (used to check for existing services updates)
   * @param {boolean} [runHarmonizer=false] - If true, run harmonizePlaceGo after update
   * @param {boolean} [dontHighlightFields=false] - If true, skip field-change tracking in UPDATED_FIELDS
   */
  function addUpdateAction(venue, newAttributes, _actions, runHarmonizer = false, dontHighlightFields = false) {
    if (Object.keys(newAttributes).length) {
      if (!dontHighlightFields) {
        UPDATED_FIELDS.checkNewAttributes(newAttributes, venue);
      }

      // SDK tracks changes as unsaved; no immediate save needed
      try {
        // SDK updateVenue supports all attributes including lockRank
        const updateableAttributes = { ...newAttributes };

        if (Object.keys(updateableAttributes).length > 0) {
          sdk.DataModel.Venues.updateVenue({ venueId: venue.id, ...updateableAttributes });
          logDev(`Updated venue ${venue.id} with:`, updateableAttributes);
        }
        // Changes accumulate for user to save via WME Save button
      } catch (e) {
        logDev('addUpdateAction: Failed to update venue', venue.id, newAttributes, e);
      }
    }
    if (runHarmonizer) {
      setTimeout(() => {
        // Get fresh venue object to ensure updated attributes are reflected
        const freshVenue = sdk.DataModel.Venues.getById({ venueId: venue.id });
        if (freshVenue) {
          harmonizePlaceGo(freshVenue, 'harmonize');
        }
        updateWmephPanel(); // Refresh banner to reflect changes
      }, 0);
    }
  }

  /**
   * Toggles a service checkbox for a venue. Adds or removes service from venue.services array.
   * If checked state differs from UI, marks field as updated via UPDATED_FIELDS.
   * Calls addUpdateAction to persist the service change.
   * @param {Object} servBtn - Service button object with servIDIndex and checked state
   * @param {boolean} [checked] - Desired checked state; if undefined, toggles current state
   * @param {Array} actions - Actions array to check for existing service updates
   */
  function setServiceChecked(servBtn, checked, actions) {
    const servID = WME_SERVICES_ARRAY[servBtn.servIDIndex];
    const checkboxChecked = $(`wz-checkbox[value="${servID}"]`).prop('checked');
    const venue = getSelectedVenue();

    if (checkboxChecked !== checked && UPDATED_FIELDS[`services_${servID}`]) {
      UPDATED_FIELDS[`services_${servID}`].updated = true;
    }
    const toggle = typeof checked === 'undefined';
    let noAdd = false;
    checked = toggle ? !servBtn.checked : checked;
    if (checkboxChecked === servBtn.checked && checkboxChecked !== checked) {
      servBtn.checked = checked;
      let services;
      if (actions) {
        for (let i = 0; i < actions.length; i++) {
          const existingAction = actions[i];
          if (existingAction.newAttributes && existingAction.newAttributes.services) {
            ({ services } = existingAction.newAttributes);
          }
        }
      }
      if (!services) {
        services = venue.services.slice();
      } else {
        noAdd = services.includes(servID);
      }
      if (checked) {
        services.push(servID);
      } else {
        const index = services.indexOf(servID);
        if (index > -1) {
          services.splice(index, 1);
        }
      }
      if (!noAdd) {
        addUpdateAction(venue, { services }, actions);
      }
    }
    updateServicesChecks(_servicesBanner);
    if (!toggle) servBtn.active = checked;
  }

  /**
   * Normalizes URLs by removing formatting, protocols, and redundant paths.
   * Removes parenthetical content, spaces, http:// prefix, and common default pages.
   * @param {string} url - URL string to normalize
   * @param {boolean} [makeLowerCase=true] - If true, lowercase entire domain; if false, only lowercase www and .com
   * @returns {string} Normalized URL, or BAD_URL constant if validation fails
   */
  function normalizeURL(url, makeLowerCase = true) {
    if (!url?.trim().length) {
      return url;
    }

    url = url.replace(/ \(.*/g, ''); // remove anything with parentheses after it
    url = url.replace(/ /g, ''); // remove any spaces
    let m = url.match(/^http:\/\/(.*)$/i); // remove http://
    if (m) {
      [, url] = m;
    }
    if (makeLowerCase) {
      // lowercase the entire domain
      url = url.replace(/[^/]+/i, (txt) => (txt === txt.toLowerCase() ? txt : txt.toLowerCase()));
    } else {
      // lowercase only the www and com
      url = url.replace(/www\./i, 'www.');
      url = url.replace(/\.com/i, '.com');
    }
    m = url.match(/^(.*)\/pages\/welcome.aspx$/i); // remove unneeded terms
    if (m) {
      [, url] = m;
    }
    m = url.match(/^(.*)\/pages\/default.aspx$/i); // remove unneeded terms
    if (m) {
      [, url] = m;
    }
    m = url.match(/^(.*)\/$/i); // remove final slash
    if (m) {
      [, url] = m;
    }
    if (!url || url.trim().length === 0 || !/(^https?:\/\/)?\w+\.\w+/.test(url)) url = BAD_URL;
    return url;
  }

  /**
   * Main entry point for place harmonization.
   * Runs Place Name Harmonization checks and actions on the currently selected venue.
   * Validates beta version access, checks for disabled categories, and processes all harmonization rules.
   * @returns {void} Updates are applied directly to the venue via harmonizePlaceGo()
   */
  function harmonizePlace() {
    logDev('harmonizePlace');
    // Beta version for approved users only
    if (IS_BETA_VERSION && !USER.isBetaUser) {
      WazeWrap.Alerts.error(SCRIPT_NAME, 'Please sign up to beta-test this script version.<br>Contact MapOMatic or Tonestertm in Discord, or post in the WMEPH forum thread. Thanks.');
      return;
    }
    // Only run if a single place is selected
    const venue = getSelectedVenue();
    if (venue) {
      UPDATED_FIELDS.reset();
      blurAll(); // focus away from current cursor position
      _disableHighlightTest = true;
      harmonizePlaceGo(venue, 'harmonize');
      _disableHighlightTest = false;
      refreshAllHighlights();
    } else {
      // Remove duplicate labels
      destroyDupeLabels();
    }
  }

  /**
   * Returns configuration for service action buttons in the WMEPH banner.
   * Each service button has: active (enabled in UI), checked (service exists on venue),
   * icon (CSS class name), value (text), title (tooltip), action (click handler),
   * actionOn/actionOff (convenience methods).
   * @returns {Object} Object mapping service names (addValet, addDriveThru, etc.) to button configs
   */
  function getServicesBanner() {
    return {
      addValet: {
        active: true,
        checked: false,
        icon: 'serv-valet',
        w2hratio: 50 / 50,
        value: 'Valet',
        title: 'Valet service',
        serviceId: 'VALLET_SERVICE',
        servIDIndex: 0,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addDriveThru: {
        active: true,
        checked: false,
        icon: 'serv-drivethru',
        w2hratio: 78 / 50,
        value: 'DriveThru',
        title: 'Drive-thru',
        serviceId: 'DRIVETHROUGH',
        servIDIndex: 1,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addWiFi: {
        active: true,
        checked: false,
        icon: 'serv-wifi',
        w2hratio: 67 / 50,
        value: 'WiFi',
        title: 'Wi-Fi',
        serviceId: 'WI_FI',
        servIDIndex: 2,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addRestrooms: {
        active: true,
        checked: false,
        icon: 'serv-restrooms',
        w2hratio: 49 / 50,
        value: 'Restroom',
        title: 'Restrooms',
        serviceId: 'RESTROOMS',
        servIDIndex: 3,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addCreditCards: {
        active: true,
        checked: false,
        icon: 'serv-credit',
        w2hratio: 73 / 50,
        value: 'CC',
        title: 'Accepts credit cards',
        serviceId: 'CREDIT_CARDS',
        servIDIndex: 4,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addReservations: {
        active: true,
        checked: false,
        icon: 'serv-reservations',
        w2hratio: 55 / 50,
        value: 'Reserve',
        title: 'Reservations',
        serviceId: 'RESERVATIONS',
        servIDIndex: 5,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addOutside: {
        active: true,
        checked: false,
        icon: 'serv-outdoor',
        w2hratio: 73 / 50,
        value: 'OusideSeat',
        title: 'Outdoor seating',
        serviceId: 'OUTSIDE_SEATING',
        servIDIndex: 6,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addAC: {
        active: true,
        checked: false,
        icon: 'serv-ac',
        w2hratio: 50 / 50,
        value: 'AC',
        title: 'Air conditioning',
        serviceId: 'AIR_CONDITIONING',
        servIDIndex: 7,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addParking: {
        active: true,
        checked: false,
        icon: 'serv-parking',
        w2hratio: 46 / 50,
        value: 'Customer parking',
        title: 'Parking',
        serviceId: 'PARKING_FOR_CUSTOMERS',
        servIDIndex: 8,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addDeliveries: {
        active: true,
        checked: false,
        icon: 'serv-deliveries',
        w2hratio: 86 / 50,
        value: 'Delivery',
        title: 'Deliveries',
        serviceId: 'DELIVERIES',
        servIDIndex: 9,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addTakeAway: {
        active: true,
        checked: false,
        icon: 'serv-takeaway',
        w2hratio: 34 / 50,
        value: 'Take-out',
        title: 'Take-out',
        serviceId: 'TAKE_AWAY',
        servIDIndex: 10,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addCurbside: {
        active: true,
        checked: false,
        icon: 'serv-curbside',
        w2hratio: 50 / 50,
        value: 'Curbside pickup',
        title: 'Curbside pickup',
        serviceId: 'CURBSIDE_PICKUP',
        servIDIndex: 11,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addWheelchair: {
        active: true,
        checked: false,
        icon: 'serv-wheelchair',
        w2hratio: 50 / 50,
        value: 'WhCh',
        title: 'Wheelchair accessible',
        serviceId: 'WHEELCHAIR_ACCESSIBLE',
        servIDIndex: 12,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addDisabilityParking: {
        active: true,
        checked: false,
        icon: 'serv-wheelchair',
        w2hratio: 50 / 50,
        value: 'DisabilityParking',
        title: 'Disability parking',
        serviceId: 'DISABILITY_PARKING',
        servIDIndex: 17,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addAirportShuttle: {
        active: true,
        checked: false,
        icon: 'serv-airportshuttle',
        w2hratio: 50 / 50,
        value: 'Airport shuttle',
        title: 'Airport shuttle',
        serviceId: 'AIRPORT_SHUTTLE',
        servIDIndex: 13,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addCarWash: {
        active: true,
        checked: false,
        icon: 'serv-carwash',
        w2hratio: 50 / 50,
        value: 'Car wash',
        title: 'Car wash',
        serviceId: 'CAR_WASH',
        servIDIndex: 14,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addCarpoolParking: {
        active: true,
        checked: false,
        icon: 'serv-carpool',
        w2hratio: 50 / 50,
        value: 'Carpool parking',
        title: 'Carpool parking',
        serviceId: 'CARPOOL_PARKING',
        servIDIndex: 15,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addCovered: {
        active: true,
        checked: false,
        icon: 'serv-covered',
        w2hratio: 50 / 50,
        value: 'Covered parking',
        title: 'Covered parking',
        serviceId: 'COVERED',
        servIDIndex: 16,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addEVChargingStation: {
        active: true,
        checked: false,
        icon: 'serv-ev',
        w2hratio: 50 / 50,
        value: 'EV charging',
        title: 'EV charging station',
        serviceId: 'EV_CHARGING_STATION',
        servIDIndex: 18,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addOnSiteAttendant: {
        active: true,
        checked: false,
        icon: 'serv-attendant',
        w2hratio: 50 / 50,
        value: 'On-site attendant',
        title: 'On-site attendant',
        serviceId: 'ON_SITE_ATTENDANT',
        servIDIndex: 19,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addParkAndRide: {
        active: true,
        checked: false,
        icon: 'serv-parkandride',
        w2hratio: 50 / 50,
        value: 'Park and ride',
        title: 'Park and ride',
        serviceId: 'PARK_AND_RIDE',
        servIDIndex: 20,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addSecurity: {
        active: true,
        checked: false,
        icon: 'serv-security',
        w2hratio: 50 / 50,
        value: 'Security',
        title: 'Security',
        serviceId: 'SECURITY',
        servIDIndex: 22,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addReservationsPL: {
        active: true,
        checked: false,
        icon: 'serv-reservations',
        w2hratio: 55 / 50,
        value: 'Reserve',
        title: 'Reservations accepted (Parking Lot)',
        serviceId: 'RESERVATIONS',
        servIDIndex: 21,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addValetPL: {
        active: true,
        checked: false,
        icon: 'serv-valet',
        w2hratio: 50 / 50,
        value: 'Valet',
        title: 'Valet parking (Parking Lot)',
        serviceId: 'VALET',
        servIDIndex: 23,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      addValletServicePL: {
        active: true,
        checked: false,
        icon: 'serv-valet-service',
        w2hratio: 50 / 50,
        value: 'Vallet',
        title: 'Vallet service (Parking Lot)',
        serviceId: 'VALLET_SERVICE',
        servIDIndex: 24,
        action(actions, checked) {
          setServiceChecked(this, checked, actions);
        },
        pnhOverride: false,
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
      add247: {
        active: true,
        checked: false,
        icon: 'serv-247',
        w2hratio: 73 / 50,
        value: '247',
        title: 'Hours: Open 24/7',
        action(actions, checked) {
          const toggle = typeof checked === 'undefined';
          const venue = getSelectedVenue();
          checked = toggle ? !_servicesBanner.add247.checked : checked;

          if (checked) {
            addUpdateAction(venue, { openingHours: [new OpeningHour({ days: [1, 2, 3, 4, 5, 6, 0], fromHour: '00:00', toHour: '00:00', allDay: true })] }, actions);
          } else {
            addUpdateAction(venue, { openingHours: [] }, actions);
          }
        },
        actionOn(actions) {
          this.action(actions, true);
        },
        actionOff(actions) {
          this.action(actions, false);
        },
      },
    };
  } // END getServicesBanner()

  /**
   * Returns configuration for utility action buttons in the WMEPH banner (wiki links, whitelist management, etc.).
   * Each button has: active (enabled in UI), severity (highlighting level), message (status text),
   * value (button text), title (tooltip), and action (click handler).
   * @param {Object} venue - The currently selected venue (used for whitelist operations)
   * @param {Object} placePL - Place page info (for generating links or context)
   * @returns {Object} Object mapping button names (placesWiki, restAreaWiki, clearWL, etc.) to button configs
   */
  function getButtonBanner2(venue, placePL) {
    return {
      placesWiki: {
        active: true,
        severity: 0,
        message: '',
        value: 'Places wiki',
        title: 'Open the places Wazeopedia (wiki) page',
        action() {
          window.open(URLS.placesWiki);
        },
      },
      restAreaWiki: {
        active: true,
        severity: 0,
        message: '',
        value: 'Rest Area wiki',
        title: 'Open the Rest Area wiki page',
        action() {
          window.open(URLS.restAreaWiki);
        },
      },
      clearWL: {
        active: true,
        severity: 0,
        message: '',
        value: 'Clear place whitelist',
        title: 'Clear all Whitelisted fields for this place',
        action() {
          WazeWrap.Alerts.confirm(
            SCRIPT_NAME,
            'Are you sure you want to clear all whitelisted fields for this place?',
            () => {
              delete _venueWhitelist[venue.id];
              // Remove venue from the results cache so it can be updated again.
              delete _resultsCache[venue.id];
              saveWhitelistToLS(true);
              harmonizePlaceGo(venue, 'harmonize');
            },
            () => {},
            'Yes',
            'No',
          );
        },
      },
      //PlaceErrorForumPost: {
      //    active: true,
      //    severity: 0,
      //    message: '',
      //    value: 'Report script error',
      //    title: 'Report a script error',
      //    action() {
      //        window.open(URLS.forum, '_blank');
      //    }
      //}
    };
  } // END getButtonBanner2()

  /**
   * Core implementation of place harmonization. Validates venue data against PNH database,
   * applies harmonization rules (names, categories, services, hours, contact info), evaluates
   * all Flag conditions, and updates venue via SDK. Supports two modes: full harmonization
   * (useFlag='harmonize') or highlight-only (for performance). Prevents recursive harmonization
   * via _isHarmonizing flag. Cache is cleared for harmonized venues; results are cached for highlighting.
   * @param {Object} venue - The venue object to harmonize
   * @param {string} useFlag - Mode flag: 'harmonize' for full update, 'highlight' for severity-only
   * @param {Array} [actions] - Optional array to collect venue update actions
   * @returns {string|undefined} Severity value (for highlight-only mode) or undefined (for full harmonization)
   */
  function harmonizePlaceGo_impl(venue, useFlag, actions) {
    if (useFlag === 'harmonize') logDev('harmonizePlaceGo: useFlag="harmonize"');

    const venueID = venue.id;

    // Used for collecting all actions to be applied to the model.
    actions = actions || [];

    // Prevent recursive harmonization when venue data changes during harmonization
    const wasHarmonizing = _isHarmonizing;
    _isHarmonizing = true;

    try {
      FlagBase.currentFlags = new FlagContainer();
      const args = new HarmonizationArgs(venue, actions, !useFlag.includes('harmonize'));

      let pnhLockLevel;
      if (!args.highlightOnly) {
        // Get permalink for form submissions (Canada/Quebec only)
        args.placePL = sdk.Map.getPermalink({ includeLayers: false });

        _buttonBanner2 = getButtonBanner2(venue, args.placePL);
        _servicesBanner = getServicesBanner();

        // Update icons to reflect current WME place services
        updateServicesChecks(_servicesBanner);

        // Setting switch for the Places Wiki button
        if ($('#WMEPH-HidePlacesWiki').prop('checked')) {
          _buttonBanner2.placesWiki.active = false;
        }

        // NOTE: PlaceErrorForumPost button is not implemented (commented out in getButtonBanner2),
        // so we don't try to access it

        // reset PNH lock level
        pnhLockLevel = -1;

        // Calculate GPS coordinates early so all downstream code has access
        if (!args.venueGPS) {
          const centroidPt = turf.centroid(venue.geometry);
          args.venueGPS = { longitude: centroidPt.geometry.coordinates[0], latitude: centroidPt.geometry.coordinates[1] };
        }
      }

      // Some user submitted places have no data in the country, state and address fields.
      const result = Flag.FullAddressInference.eval(args);
      if (result?.exit) return result.severity;
      const inferredAddress = result?.inferredAddress;
      args.addr = inferredAddress ?? args.addr;

      // Whitelist breakout if place exists on the Whitelist and the option is enabled
      if (_venueWhitelist.hasOwnProperty(venueID) && (!args.highlightOnly || (args.highlightOnly && !$('#WMEPH-DisableWLHL').prop('checked')))) {
        // Enable the clear WL button if any property is true
        Object.keys(_venueWhitelist[venueID]).forEach((wlKey) => {
          // loop thru the venue WL keys
          if (_venueWhitelist[venueID].hasOwnProperty(wlKey) && (_venueWhitelist[venueID][wlKey].active || false)) {
            if (!args.highlightOnly) _buttonBanner2.clearWL.active = true;
            args.wl[wlKey] = _venueWhitelist[venueID][wlKey];
          }
        });
        if (_venueWhitelist[venueID].hasOwnProperty('dupeWL') && _venueWhitelist[venueID].dupeWL.length > 0) {
          if (!args.highlightOnly) _buttonBanner2.clearWL.active = true;
          args.wl.dupeWL = _venueWhitelist[venueID].dupeWL;
        }
        // Update address info for the place
        if (!args.highlightOnly) {
          _venueWhitelist[venueID].city = args.addr.city?.name; // Store city for the venue
          _venueWhitelist[venueID].state = args.addr.state?.name; // Store state for the venue
          _venueWhitelist[venueID].country = args.addr.country?.name; // Store country for the venue
          _venueWhitelist[venueID].gps = args.venueGPS; // Store GPS coords for the venue (calculated earlier)
        }
      }

      // Country restrictions (note that FullAddressInference should guarantee country/state exist if highlightOnly is true)
      if (!args.addr.country || !args.addr.state) {
        WazeWrap.Alerts.error(SCRIPT_NAME, 'Country and/or state could not be determined.  Edit the place address and run WMEPH again.');
        return undefined;
      }

      const countryName = args.addr.country?.name;
      const stateName = args.addr.state?.name;
      if (['United States', 'American Samoa', 'Guam', 'Northern Mariana Islands', 'Puerto Rico', 'Virgin Islands (U.S.)'].includes(countryName)) {
        args.countryCode = PNH_DATA.USA.countryCode;
      } else if (countryName === PNH_DATA.CAN.countryName) {
        args.countryCode = PNH_DATA.CAN.countryCode;
      } else {
        if (!args.highlightOnly) {
          WazeWrap.Alerts.error(SCRIPT_NAME, `This script is not currently supported in ${countryName}.`);
        }
        return SEVERITY.RED;
      }

      args.pnhCategoryInfos = PNH_DATA[args.countryCode].categoryInfos;

      // Parse state-based data
      for (let usdix = 1; usdix < PNH_DATA.states.length; usdix++) {
        _stateDataTemp = PNH_DATA.states[usdix].split('|');
        if (stateName === _stateDataTemp[_psStateIx]) {
          args.state2L = _stateDataTemp[_psState2LetterIx];
          args.regionCode = _stateDataTemp[_psRegionIx];
          args.gFormState = _stateDataTemp[_psGoogleFormStateIx];
          if (_stateDataTemp[_psDefaultLockLevelIx].match(/[1-5]{1}/) !== null) {
            args.defaultLockLevel = _stateDataTemp[_psDefaultLockLevelIx] - 1; // normalize by -1
          } else if (!args.highlightOnly) {
            WazeWrap.Alerts.warning(SCRIPT_NAME, 'Lock level sheet data is not correct');
          } else {
            return 3;
          }
          _areaCodeList = `${_areaCodeList},${_stateDataTemp[_psAreaCodeIx]}`;
          break;
        }
        // If State is not found, then use the country
        if (countryName === _stateDataTemp[_psStateIx]) {
          args.state2L = _stateDataTemp[_psState2LetterIx];
          args.regionCode = _stateDataTemp[_psRegionIx];
          args.gFormState = _stateDataTemp[_psGoogleFormStateIx];
          if (_stateDataTemp[_psDefaultLockLevelIx].match(/[1-5]{1}/) !== null) {
            args.defaultLockLevel = _stateDataTemp[_psDefaultLockLevelIx] - 1; // normalize by -1
          } else if (!args.highlightOnly) {
            WazeWrap.Alerts.warning(SCRIPT_NAME, 'Lock level sheet data is not correct');
          } else {
            return 3;
          }
          _areaCodeList = `${_areaCodeList},${_stateDataTemp[_psAreaCodeIx]}`;
          break;
        }
      }
      if (args.state2L === 'Unknown' || args.regionCode === 'Unknown') {
        // if nothing found:
        if (!args.highlightOnly) {
          WazeWrap.Alerts.error(SCRIPT_NAME, 'WMEPH: Localization Error!');
        }
        return SEVERITY.RED;
      }

      // Gas station treatment (applies to all including PNH)

      if (!args.highlightOnly && args.state2L === 'TN' && args.nameBase.toLowerCase().trim() === 'pilot') {
        // TODO: check what happens here if there's a name suffix.
        args.nameBase = 'Pilot Food Mart';
        addUpdateAction(venue, { name: args.nameBase }, actions);
      }

      // Clear attributes from residential places
      if (venue.residential) {
        if (!args.highlightOnly) {
          if (!$('#WMEPH-AutoLockRPPs').prop('checked')) {
            args.lockOK = false;
          }
          if (venue.name !== '') {
            // Set the residential place name to the address (to clear any personal info)
            logDev('Residential Name reset');
            try {
              actions.push(sdk.DataModel.Venues.updateVenue({ venueId: venue.id, name: '' }));
            } catch (e) {
              if (e.name === 'InvalidStateError') {
                logDev('InvalidStateError updating name - skipping');
              } else throw e;
            }
          }
          args.categories = ['RESIDENCE_HOME'];
          if (venue.description !== null && venue.description !== '') {
            // remove any description
            logDev('Residential description cleared');
            try {
              actions.push(sdk.DataModel.Venues.updateVenue({ venueId: venue.id, description: null }));
            } catch (e) {
              if (e.name === 'InvalidStateError') {
                logDev('InvalidStateError updating description - skipping');
              } else throw e;
            }
          }
          if (venue.phone !== null && venue.phone !== '') {
            // remove any phone info
            logDev('Residential Phone cleared');
            try {
              actions.push(sdk.DataModel.Venues.updateVenue({ venueId: venue.id, phone: null }));
            } catch (e) {
              if (e.name === 'InvalidStateError') {
                logDev('InvalidStateError updating phone - skipping');
              } else throw e;
            }
          }
          if (venue.url !== null && venue.url !== '') {
            // remove any url
            logDev('Residential URL cleared');
            try {
              actions.push(sdk.DataModel.Venues.updateVenue({ venueId: venue.id, url: null }));
            } catch (e) {
              if (e.name === 'InvalidStateError') {
                logDev('InvalidStateError updating url - skipping');
              } else throw e;
            }
          }
          if (venue.services.length > 0) {
            logDev('Residential services cleared');
            try {
              actions.push(sdk.DataModel.Venues.updateVenue({ venueId: venue.id, services: [] }));
            } catch (e) {
              if (e.name === 'InvalidStateError') {
                logDev('InvalidStateError updating services - skipping');
              } else throw e;
            }
          }
        }
      } else if (isVenueParkingLot(venue) || args.nameBase?.trim().length || containsAny(args.categories, ['SEA_LAKE_POOL'])) {
        // for non-residential places
        // Phone formatting
        if (containsAny(['CA', 'CO'], [args.regionCode, args.state2L]) && /^\d{3}-\d{3}-\d{4}$/.test(venue.phone)) {
          args.outputPhoneFormat = '{0}-{1}-{2}';
        } else if (args.regionCode === 'SER' && !/^\(\d{3}\) \d{3}-\d{4}$/.test(venue.phone)) {
          args.outputPhoneFormat = '{0}-{1}-{2}';
        } else if (args.regionCode === 'GLR') {
          args.outputPhoneFormat = '{0}-{1}-{2}';
        } else if (args.state2L === 'NV') {
          args.outputPhoneFormat = '{0}-{1}-{2}';
        } else if (args.countryCode === PNH_DATA.CAN.countryCode) {
          args.outputPhoneFormat = '+1-{0}-{1}-{2}';
        }

        args.almostAllDayHoursEntries = args.openingHours.filter((hoursEntry) => hoursEntry.toHour === '23:59' && /^0?0:00$/.test(hoursEntry.fromHour));
        if (!args.highlightOnly && args.almostAllDayHoursEntries.length) {
          const newHoursEntries = [];
          args.openingHours.forEach((hoursEntry) => {
            const isInvalid = args.almostAllDayHoursEntries.includes(hoursEntry);
            const newHoursEntry = new OpeningHour({
              days: hoursEntry.days.slice(),
              fromHour: isInvalid ? '00:00' : hoursEntry.fromHour,
              toHour: isInvalid ? '00:00' : hoursEntry.toHour,
            });
            newHoursEntries.push(newHoursEntry);
          });
          args.openingHours = newHoursEntries;
          addUpdateAction(venue, { openingHours: args.openingHours }, actions);
        }

        // Place Harmonization
        if (!args.highlightOnly) {
          if (isVenueParkingLot(venue) || isVenueResidential(venue)) {
            args.pnhMatch = ['NoMatch'];
          } else {
            // check against the PNH list
            args.pnhMatch = Pnh.findMatch(args.nameBase, args.state2L, args.regionCode, args.countryCode, args.categories, venue);
          }
        } else {
          args.pnhMatch = Pnh.findMatch(args.nameBase, args.state2L, args.regionCode, args.countryCode, args.categories, venue, true);
        }

        // DEBUG: Log what findMatch returned for multiple match detection
        if (args.pnhMatch?.length > 1 && args.pnhMatch[0] !== 'NoMatch' && args.pnhMatch[0] !== 'ApprovalNeeded') {
          logDev(`Multiple PNH matches found for "${args.nameBase}" (state: ${args.state2L}, region: ${args.regionCode})`);
          args.pnhMatch.forEach((match, idx) => {
            logDev(`  Match ${idx}: Order ${match.order}, Name: ${match.name}, Category: ${match.primaryCategory}, Regions: ${match.regions.join(', ')}, BrandParentLevel: ${match.brandParentLevel}`);
          });
        }

        args.pnhNameRegMatch = args.pnhMatch?.length && args.pnhMatch[0] !== 'NoMatch' && args.pnhMatch[0] !== 'ApprovalNeeded' && args.pnhMatch[0] !== 'Highlight';

        if (args.pnhNameRegMatch) {
          // *** Replace place data with PNH data
          let updatePNHName = true;

          // Retrieve the data from the PNH line(s)
          let nsMultiMatch = false;
          const orderList = [];
          if (args.pnhMatch.length > 1) {
            // If multiple matches, then
            let maxBrandParentLevel = -1;
            let pnhMatchHold = args.pnhMatch[0];
            for (let pnhEntryIdx = 0; pnhEntryIdx < args.pnhMatch.length; pnhEntryIdx++) {
              // For each of the matches,
              const pnhEntry = args.pnhMatch[pnhEntryIdx];
              orderList.push(pnhEntry.order); // Add Order number to a list
              if (pnhEntry.brandParentLevel > -1) {
                // If there is a brandParent flag, prioritize by highest match
                if (pnhEntry.brandParentLevel > maxBrandParentLevel) {
                  // if the match is more specific than the previous ones:
                  maxBrandParentLevel = pnhEntry.brandParentLevel; // Update the brandParent level
                  pnhMatchHold = pnhEntry; // Update the PNH data line
                }
              } else {
                // if any venue has no brandParent structure, use highest brandParent match but post an error
                nsMultiMatch = true;
              }
            }
            args.pnhMatch = pnhMatchHold;
          } else {
            [args.pnhMatch] = args.pnhMatch; // Single match
          }

          args.priPNHPlaceCat = args.pnhMatch.primaryCategory;

          // if the location has multiple matches, then pop an alert
          if (nsMultiMatch && !args.highlightOnly) {
            WazeWrap.Alerts.error(SCRIPT_NAME, 'WMEPH: Multiple matches found!<br>Double check the script changes.');
          }

          // Check special cases
          if (args.pnhMatch.hasSpecialCases) {
            // If the special cases column exists
            // find any button/message flags in the special case (format: buttOn_xyzXyz, etc.)
            if (args.pnhMatch.flagsToRemove.addConvStore) {
              FlagBase.currentFlags.remove(Flag.AddConvStore);
            }
            // } else if (match = specCase.match(/^messOn_(.+)/i)) {
            //    [, scFlag] = match;
            //    _buttonBanner[scFlag].active = true;
            // } else if (match = specCase.match(/^messOff_(.+)/i)) {
            //    [, scFlag] = match;
            //    _buttonBanner[scFlag].active = false;
            args.pnhMatch.servicesToAdd.forEach((scFlag) => {
              if (_servicesBanner[scFlag]) {
                _servicesBanner[scFlag].actionOn(actions);
                _servicesBanner[scFlag].pnhOverride = true;
              }
            });
            args.pnhMatch.servicesToRemove.forEach((scFlag) => {
              if (_servicesBanner[scFlag]) {
                _servicesBanner[scFlag].actionOff(actions);
                _servicesBanner[scFlag].pnhOverride = true;
              }
            });
            if (args.pnhMatch.forceBrand) {
              // If brand is going to be forced, use that.  Otherwise, use existing brand.
              [, args.brand] = args.pnhMatch.forceBrand;
            }
            if (args.pnhMatch.forceBrand && args.priPNHPlaceCat === 'GAS_STATION' && venue.brand !== args.pnhMatch.forceBrand) {
              try {
                actions.push(sdk.DataModel.Venues.updateVenue({ venueId: venue.id, brand: args.pnhMatch.forceBrand }));
              } catch (e) {
                if (e.name === 'InvalidStateError') {
                  logDev('InvalidStateError updating brand - skipping');
                } else throw e;
              }
              UPDATED_FIELDS.brand.updated = true;
              logDev('Gas brand updated from PNH');
            }
            if (args.pnhMatch.localizationRegEx) {
              args.showDispNote = false;
            }
            if (args.pnhMatch.recommendedPhone) {
              args.recommendedPhone = normalizePhone(args.pnhMatch.recommendedPhone, args.outputPhoneFormat);
            }
            if (args.pnhMatch.keepName) {
              // Prevent name change
              updatePNHName = false;
            }
            if (args.pnhMatch.chainIsClosed && !Flag.ChainIsClosed.isWhitelisted(args)) {
              args.chainIsClosed = true;
            }
          }

          if (!args.chainIsClosed) {
            // Category translations
            const { altCategories } = args.pnhMatch;

            // name parsing with category exceptions
            if (args.priPNHPlaceCat === 'HOTEL') {
              const nameToCheck = args.nameBase + (args.nameSuffix || '');
              if (nameToCheck.toUpperCase() === args.pnhMatch.name.toUpperCase()) {
                // If no localization
                args.nameBase = args.pnhMatch.name;
              } else {
                // Replace PNH part of name with PNH name
                const splix = args.nameBase.toUpperCase().replace(/[-/]/g, ' ').indexOf(args.pnhMatch.name.toUpperCase().replace(/[-/]/g, ' '));
                if (splix > -1) {
                  const frontText = args.nameBase.slice(0, splix);
                  const backText = args.nameBase.slice(splix + args.pnhMatch.name.length);
                  args.nameBase = args.pnhMatch.name;
                  if (frontText.length > 0) {
                    args.nameBase = `${frontText} ${args.nameBase}`;
                  }
                  if (backText.length > 0) {
                    args.nameBase = `${args.nameBase} ${backText}`;
                  }
                  args.nameBase = args.nameBase.replace(/ {2,}/g, ' ');
                } else {
                  args.nameBase = args.pnhMatch.name;
                }
              }
              if (altCategories && altCategories.length) {
                // if PNH alts exist
                insertAtIndex(args.categories, altCategories, 1); //  then insert the alts into the existing category array after the GS category
              }
              if (args.categories.includes('HOTEL')) {
                // Remove LODGING if it exists
                const lodgingIdx = args.categories.indexOf('LODGING');
                if (lodgingIdx > -1) {
                  args.categories.splice(lodgingIdx, 1);
                }
              }
              // If PNH match, set wifi service.
              if (args.pnhMatch && _servicesBanner?.addWiFi && !_servicesBanner.addWiFi.checked) {
                _servicesBanner.addWiFi.action();
              }
              // Set hotel hours to 24/7 for all hotels.
              if (_servicesBanner?.add247 && !_servicesBanner.add247.checked) {
                _servicesBanner.add247.action();
              }
            } else if (args.priPNHPlaceCat === 'BANK_FINANCIAL' && !args.pnhMatch.notABank) {
              if (/\batm\b/gi.test(args.nameBase)) {
                args.nameBase = `${args.pnhMatch.name} ATM`;
              } else {
                args.nameBase = args.pnhMatch.name;
              }
            } else if (args.priPNHPlaceCat === 'GAS_STATION') {
              // for PNH gas stations, don't replace existing sub-categories
              if (altCategories?.length) {
                // if PNH alts exist
                insertAtIndex(args.categories, altCategories, 1); //  then insert the alts into the existing category array after the GS category
              }
              args.nameBase = args.pnhMatch.name;
            } else if (updatePNHName) {
              // if not a special category then update the name
              args.nameBase = args.pnhMatch.name;
              args.categories = insertAtIndex(args.categories, args.priPNHPlaceCat, 0);
              if (altCategories?.length && !args.pnhMatch.flagsToAdd?.addCat2 && !args.pnhMatch.optionCat2) {
                args.categories = insertAtIndex(args.categories, altCategories, 1);
              }
            } else if (!updatePNHName) {
              // Strong title case option for non-PNH places
              Flag.TitleCaseName.eval(venue, args.nameBase, args.nameSuffix);
            }

            // *** need to add a section above to allow other permissible categories to remain? (optional)

            // Parse URL data
            if (!args.pnhMatch.localUrlCheckRegEx?.test(args?.url)) {
              args.pnhUrl = args.pnhMatch.url;
            }

            // Parse PNH Aliases
            if (!args.pnhMatch.noUpdateAlias && !containsAll(args.aliases, args.pnhMatch.aliases) && args.pnhMatch.aliases?.length && !args.pnhMatch.optionName2) {
              args.aliases = insertAtIndex(args.aliases, args.pnhMatch.aliases, 0);
              addUpdateAction(venue, { aliases: args.aliases }, actions);
            }

            // Remove unnecessary parent categories
            // TODO: This seems like it could be made more efficient.
            const parentCats = uniq(args.categories.map((category) => args.pnhCategoryInfos.getById(category).parent)).filter((parent) => parent.trim().length > 0);
            args.categories = args.categories.filter((cat) => !parentCats.includes(cat));

            // update categories if different and no Cat2 option
            if (!matchSets(uniq(venue.categories), uniq(args.categories))) {
              if (!args.pnhMatch.optionCat2 && !args.pnhMatch.flagsToAdd?.addCat2) {
                logDev(`Categories updated with ${args.categories}`);
                addUpdateAction(venue, { categories: args.categories }, actions);
              } else {
                // if second cat is optional
                logDev(`Primary category updated with ${args.priPNHPlaceCat}`);
                args.categories = insertAtIndex(args.categories, args.priPNHPlaceCat, 0);
                addUpdateAction(venue, { categories: args.categories });
              }
            }
            // Enable optional 2nd category button
            Flag.AddCat2.eval(args, altCategories?.[0]);

            // Description update
            args.description = args.pnhMatch.description;
            if (!isNullOrWhitespace(args.description) && !venue.description?.toUpperCase?.().includes(args.description.toUpperCase())) {
              if (!isNullOrWhitespace(venue.description)) {
                args.descriptionInserted = true;
              }
              args.description = `${args.description}\n${venue.description}`;
              try {
                actions.push(sdk.DataModel.Venues.updateVenue({ venueId: venue.id, description: args.description }));
                logDev('Description updated');
                UPDATED_FIELDS.description.updated = true;
              } catch (e) {
                if (e.name === 'InvalidStateError') {
                  logDev('InvalidStateError updating description - skipping (feed-controlled field)');
                } else throw e;
              }
            }

            // Special Lock by PNH
            if (args.pnhMatch.lockAt) {
              pnhLockLevel = args.pnhMatch.lockAt - 1;
            }
          }
        } // END PNH match/no-match updates

        if (!args.chainIsClosed) {
          const isPoint = isVenuePoint(venue);
          // Determine if venue is an area by checking if it's not a point (more reliable than is2D())
          const isArea = !isPoint;
          let highestCategoryLock = -1;
          // Category/Name-based Services, added to any existing services:
          args.categories.forEach((category) => {
            const pnhCategoryInfo = args.pnhCategoryInfos.getById(category);

            if (!pnhCategoryInfo) {
              throw new Error(`WMEPH: Unexpected category: ${category}`);
            }

            let pvaPoint = pnhCategoryInfo.point;
            let pvaArea = pnhCategoryInfo.area;
            if (pnhCategoryInfo.regPoint.includes(args.state2L) || pnhCategoryInfo.regPoint.includes(args.regionCode) || pnhCategoryInfo.regPoint.includes(args.countryCode)) {
              pvaPoint = '1';
              pvaArea = '';
            } else if (pnhCategoryInfo.regArea.includes(args.state2L) || pnhCategoryInfo.regArea.includes(args.regionCode) || pnhCategoryInfo.regArea.includes(args.countryCode)) {
              pvaPoint = '';
              pvaArea = '1';
            }

            // If Post Office and VPO or CPU is in the name, always a point.
            if (args.categories.includes('POST_OFFICE') && /\b(?:cpu|vpo)\b/i.test(venue.name)) {
              pvaPoint = '1';
              pvaArea = '';
            }


            const pointSeverity = getPvaSeverity(pvaPoint, venue);
            const areaSeverity = getPvaSeverity(pvaArea, venue);

            if (isPoint && pointSeverity > SEVERITY.GREEN) {
              args.maxPointSeverity = Math.max(pointSeverity, args.maxPointSeverity);
            } else if (isArea) {
              args.maxAreaSeverity = Math.min(areaSeverity, args.maxAreaSeverity);
            }

            // TODO: Process this flag outside the loop.
            Flag.PnhCatMess.eval(venue, pnhCategoryInfo, args.categories, args.highlightOnly);

            // Set lock level
            for (let lockix = 1; lockix < 6; lockix++) {
              const categoryLock = pnhCategoryInfo[`lock${lockix}`];
              if (lockix - 1 > highestCategoryLock && (categoryLock.includes(args.state2L) || categoryLock.includes(args.regionCode) || categoryLock.includes(args.countryCode))) {
                highestCategoryLock = lockix - 1; // Offset by 1 since lock ranks start at 0
              }
            }
          });

          if (highestCategoryLock > -1) {
            args.defaultLockLevel = highestCategoryLock;
          }

          if (!args.highlightOnly) {
            // Update name:
            if (args.nameBase + (args.nameSuffix || '') !== venue.name) {
              logDev('Name updated');
              addUpdateAction(venue, { name: args.nameBase + (args.nameSuffix || '') }, actions);
            }

            // Update aliases
            const tempAliases = removeUnnecessaryAliases(args.nameBase, args.aliases);
            if (tempAliases !== null) {
              args.aliasesRemoved = true;
              args.aliases = tempAliases;
              logDev('Alt Names updated');
              addUpdateAction(venue, { aliases: args.aliases }, actions);
            }

            // PNH specific Services: Display available services for this category
            // These are services defined in the PNH Google Sheet for each category (ps_* columns).
            // They are optional suggestions - user controls whether to enable them on the venue.
            /*
            args.categories.forEach((category) => {
              const pnhCategoryInfo = args.pnhCategoryInfos.getById(category);
              pnhCategoryInfo.services.forEach((service) => {
                const serviceButton = _servicesBanner[service.pnhKey];
                // Just make the button active (visible) - user decides to enable or disable
                if (serviceButton) {
                  serviceButton.active = true;
                }
              });
            });
           */
          }

          args.hoursOverlap = venueHasOverlappingHours(args.openingHours);

          args.isUspsPostOffice = args.countryCode === PNH_DATA.USA.countryCode && !args.categories.includes('PARKING_LOT') && args.categories.includes('POST_OFFICE');

          if (!args.highlightOnly) {
            // Highlight 24/7 button if hours are set that way, and add button for all places
            if (_servicesBanner && _servicesBanner.add247) {
              if (isAlwaysOpen(venue)) {
                _servicesBanner.add247.checked = true;
              }
              _servicesBanner.add247.active = true;
            }

            if (!args.hoursOverlap) {
              const tempHours = args.openingHours.slice();
              for (let ohix = 0; ohix < args.openingHours.length; ohix++) {
                if (tempHours[ohix].days.length === 2 && tempHours[ohix].days[0] === 1 && tempHours[ohix].days[1] === 0) {
                  // separate hours
                  logDev('Correcting M-S entry...');
                  tempHours.push(new OpeningHour({ days: [0], fromHour: tempHours[ohix].fromHour, toHour: tempHours[ohix].toHour }));
                  tempHours[ohix].days = [1];
                  args.openingHours = tempHours;
                  addUpdateAction(venue, { openingHours: tempHours }, actions);
                }
              }
            }

            // URL updating
            // Invalid EVCS URL imported from PURs. Clear it.
            if (Flag.ClearThisUrl.venueIsFlaggable(args)) {
              args.url = null;
              addUpdateAction(venue, { url: args.url }, actions);
            }
            args.normalizedUrl = normalizeURL(args.url);
            if (args.isUspsPostOffice && args.url !== 'usps.com') {
              args.url = 'usps.com';
              addUpdateAction(venue, { url: args.url }, actions);
            } else if (!args.pnhUrl && args.normalizedUrl !== args.url) {
              if (args.normalizedUrl !== BAD_URL) {
                args.url = args.normalizedUrl;
                logDev('URL formatted');
                addUpdateAction(venue, { url: args.url }, actions);
              }
            } else if (args.pnhUrl && isNullOrWhitespace(args.url)) {
              args.url = args.pnhUrl;
              logDev('URL updated');
              addUpdateAction(venue, { url: args.url }, actions);
            }

            if (args.phone) {
              // Invalid EVCS phone # imported from PURs. Clear it.
              if (Flag.ClearThisPhone.venueIsFlaggable(args)) {
                args.phone = null;
              }
              const normalizedPhone = normalizePhone(args.phone, args.outputPhoneFormat);
              if (normalizedPhone !== BAD_PHONE) args.phone = normalizedPhone;
              if (args.phone !== venue.phone) {
                logDev('Phone updated');
                addUpdateAction(venue, { phone: args.phone }, actions);
              }
            }

            if (args.isUspsPostOffice) {
              const cleanNameParts = Flag.FormatUSPS.getCleanNameParts(args.nameBase, args.nameSuffix);
              const nameToCheck = cleanNameParts.join('');
              if (Flag.FormatUSPS.isNameOk(nameToCheck, args.state2L, args.addr)) {
                if (nameToCheck !== venue.name) {
                  [args.nameBase, args.nameSuffix] = cleanNameParts;
                  actions.push(sdk.DataModel.Venues.updateVenue({ venueId: venue.id, name: nameToCheck }));
                }
              }
            }
          }
        }
      } // END if (!residential && has name)

      if (!args.chainIsClosed) {
        if (!args.highlightOnly && args.categories.includes('REST_AREAS')) {
          if (venue.name.match(/^Rest Area.* - /) !== null && args.countryCode === PNH_DATA.USA.countryCode) {
            const newSuffix = args.nameSuffix.replace(/\bMile\b/i, 'mile');
            if (args.nameBase + newSuffix !== venue.name) {
              addUpdateAction(venue, { name: args.nameBase + newSuffix }, actions);
              logDev('Lower case "mile"');
            }
            // If names match after lowercasing "Mile", no action is needed
            // (would only have been a capitalization change, which is not desired)
          }

          // switch to rest area wiki button (only when not in highlight-only mode)
          if (!args.highlightOnly) {
            _buttonBanner2.restAreaWiki.active = true;
            _buttonBanner2.placesWiki.active = false;
          }
        } else {
          // For non-rest-area venues, ensure rest area button is hidden and places wiki is shown (only when not in highlight-only mode)
          if (!args.highlightOnly) {
            _buttonBanner2.restAreaWiki.active = false;
            _buttonBanner2.placesWiki.active = !$('#WMEPH-HidePlacesWiki').prop('checked');
          }
        }

        args.isLocked = venue.lockRank >= (pnhLockLevel > -1 ? pnhLockLevel : args.defaultLockLevel);
        args.currentHN = args.addr?.houseNumber;
        // Check to see if there's an action that is currently updating the house number.
        const updateHnAction = actions && actions.find((action) => action && action.newAttributes && action.newAttributes.houseNumber);
        if (updateHnAction) args.currentHN = updateHnAction.newAttributes.houseNumber;
        // Check if venue has a street and city (use actual address objects, not outdated venue properties)
        args.hasStreet = args.addr?.street && !args.addr.street.isEmpty;
        args.hasCity = args.addr?.city && !args.addr.city.isEmpty;
        args.ignoreParkingLots = $('#WMEPH-DisablePLAExtProviderCheck').prop('checked');

        if (!isVenueResidential(venue) && (isVenueParkingLot(venue) || args.nameBase?.trim().length)) {
          if (args.pnhNameRegMatch) {
            Flag.HotelMkPrim.eval(args);
            Flag.LocalizedName.eval(args);
            Flag.AddAlias.eval(args);
            Flag.AddRecommendedPhone.eval(args);
            Flag.SubFuel.eval(args);
            Flag.SpecCaseMessage.eval(args);
            Flag.LocalURL.eval(args);
            Flag.UrlMismatch.eval(args);
            Flag.CheckDescription.eval(args);
            try {
              Flag.LocationFinder.eval(args);
            } catch (e) {
              logDev('LocationFinder error (needs SDK migration):', e.message);
            }
            Flag.AddPharm.eval(args);
            Flag.AddSuper.eval(args);
            Flag.AppendAMPM.eval(args);
            Flag.PlaceMatched.eval(args);
          } else if (!args.highlightOnly && args.categories.includes('POST_OFFICE')) {
            try {
              Flag.LocationFinder.eval(args);
            } catch (e) {
              logDev('LocationFinder error (needs SDK migration):', e.message);
            }
          }
          Flag.InvalidUrl.eval(args);
          Flag.SFAliases.eval(args);
          Flag.CatHotel.eval(args);
          Flag.ExtProviderMissing.eval(args);
          Flag.NewPlaceSubmit.eval(args);
          Flag.ApprovalSubmit.eval(args);
          Flag.TitleCaseName.eval(args);
          Flag.BankType1.eval(args);
          Flag.BankBranch.eval(args);
          Flag.StandaloneATM.eval(args);
          Flag.BankCorporate.eval(args);
          Flag.AddATM.eval(args);
          Flag.NoHours.eval(args);
          Flag.Mismatch247.eval(args);
          Flag.HoursOverlap.eval(args);
          Flag.OldHours.eval(args);
          Flag.AllDayHoursFixed.eval(args);
          Flag.IsThisAPostOffice.eval(args);
          Flag.MissingUSPSZipAlt.eval(args);
          Flag.FormatUSPS.eval(args);
          Flag.CatPostOffice.eval(args);
          Flag.MissingUSPSDescription.eval(args);
          Flag.MissingUSPSAlt.eval(args);
          Flag.UrlMissing.eval(args);
          Flag.PhoneInvalid.eval(args);
          Flag.PhoneMissing.eval(args);
          Flag.BadAreaCode.eval(args);
          Flag.ParentCategory.eval(args);
          Flag.ClearThisPhone.eval(args);
          Flag.ClearThisUrl.eval(args);
          Flag.UrlAnalytics.eval(args);
          Flag.EVCSAltNameMissing.eval(args);
        }
        Flag.UnmappedRegion.eval(args);
        Flag.PlaCostTypeMissing.eval(args);
        Flag.PlaLotElevationMissing.eval(args);
        Flag.PlaSpaces.eval(args);
        Flag.PlaLotTypeMissing.eval(args);
        Flag.NoPlaStopPoint.eval(args);
        Flag.PlaStopPointUnmoved.eval(args);
        Flag.PlaCanExitWhileClosed.eval(args);
        Flag.PlaPaymentTypeMissing.eval(args);
        Flag.PlaHasAccessibleParking.eval(args);
        Flag.ChangeToHospitalUrgentCare.eval(args);
        Flag.IsThisAPilotTravelCenter.eval(args);
        Flag.GasMkPrim.eval(args);
        Flag.AddConvStore.eval(args);
        Flag.IndianaLiquorStoreHours.eval(args);
        Flag.PointNotArea.eval(args);
        Flag.GasMismatch.eval(args);
        Flag.EVChargingStationWarning.eval(args);
        Flag.AddCommonEVPaymentMethods.eval(args);
        Flag.RemoveUncommonEVPaymentMethods.eval(args);
        Flag.EVCSPriceMissing.eval(args);
        Flag.NameMissing.eval(args);
        Flag.PlaNameMissing.eval(args);
        Flag.PlaNameNonStandard.eval(args);
        Flag.GasNameMissing.eval(args);
        Flag.PlaIsPublic.eval(args);
        Flag.HnMissing.eval(args);
        Flag.HnTooManyDigits.eval(args);
        Flag.CityMissing.eval(args);
        Flag.StreetMissing.eval(args);
        Flag.NotAHospital.eval(args);
        Flag.ChangeToPetVet.eval(args);
        Flag.ChangeToDoctorClinic.eval(args);
        Flag.NotASchool.eval(args);

        Flag.RestAreaSpec.eval(args);
        Flag.RestAreaScenic.eval(args);
        Flag.RestAreaNoTransportation.eval(args);
        Flag.RestAreaGas.eval(args);
        Flag.RestAreaName.eval(args);
        Flag.AreaNotPoint.eval(args);
      } else {
        Flag.ChainIsClosed.eval(args);
      }

      // update Severity for banner messages
      const orderedFlags = FlagBase.currentFlags.getOrderedFlags();
      orderedFlags.forEach((flag) => {
        args.totalSeverity = Math.max(flag.severity, args.totalSeverity);
      });

      let placeLockedFlag;
      if (!args.chainIsClosed) {
        // final updating of desired lock levels
        if (pnhLockLevel !== -1 && !args.highlightOnly) {
          logDev(`PNHLockLevel: ${pnhLockLevel}`);
          args.levelToLock = pnhLockLevel;
        } else {
          args.levelToLock = args.defaultLockLevel;
        }
        if (args.regionCode === 'SER') {
          if (args.categories.includes('COLLEGE_UNIVERSITY') && args.categories.includes('PARKING_LOT')) {
            args.levelToLock = LOCK_LEVEL_4;
          } else if (
            isVenuePoint(venue) &&
            args.categories.includes('COLLEGE_UNIVERSITY') &&
            (!args.categories.includes('HOSPITAL_MEDICAL_CARE') || !args.categories.includes('HOSPITAL_URGENT_CARE'))
          ) {
            args.levelToLock = LOCK_LEVEL_4;
          }
        }

        if (args.levelToLock > USER.rank - 1) {
          args.levelToLock = USER.rank - 1;
        } // Only lock up to the user's level

        // Brand checking (be sure to check this after determining if brand will be forced, when harmonizing)
        Flag.GasNoBrand.eval(args);
        Flag.GasUnbranded.eval(args);

        Flag.IgnEdited.eval(args);
        Flag.WazeBot.eval(args);
        Flag.LockRPP.eval(args);

        // Allow flags to do any additional work before assigning severity and locks
        orderedFlags.forEach((flag) => flag.preProcess?.(args));

        if (!args.highlightOnly) {
          // Update the lockOK value if "noLock" is set on any flag.
          args.lockOK &&= !orderedFlags.some((flag) => flag.noLock);
          logDev(`Severity: ${args.totalSeverity}; lockOK: ${args.lockOK}`);
        }

        placeLockedFlag = Flag.PlaceLocked.eval(args);

        // Turn off unnecessary buttons
        // TODO: handle this in the flag class
        if (args.categories.includes('PHARMACY')) {
          FlagBase.currentFlags.remove(Flag.AddPharm);
        }
        if (args.categories.includes('SUPERMARKET_GROCERY')) {
          FlagBase.currentFlags.remove(Flag.AddSuper);
        }

        // Final alerts for non-severe locations
        Flag.ResiTypeName.eval(args);
        Flag.SuspectDesc.eval(args);

        _dupeHNRangeList = [];
        _dupeBanner = {};
        if (!args.highlightOnly) {
          try {
            runDuplicateFinder(venue, args.nameBase, args.aliases, args.addr, args.placePL);
          } catch (e) {
            logDev('Duplicate finder error (needs SDK migration):', e.message);
          }
        }
        // Check HN range (this depends on the returned dupefinder data, so must run after it)
        Flag.HNRange.eval(args);
      }

      // Return severity for highlighter (no dupe run))
      if (args.highlightOnly) {
        // get severities from the banners
        args.totalSeverity = SEVERITY.GREEN;
        orderedFlags.forEach((flag) => {
          args.totalSeverity = Math.max(flag.severity, args.totalSeverity);
        });

        // Special case flags
        if (venue.lockRank === 0 && venue.categories.some((cat) => ['HOSPITAL_MEDICAL_CARE', 'HOSPITAL_URGENT_CARE', 'GAS_STATION'].includes(cat))) {
          args.totalSeverity = SEVERITY.PINK;
        }

        if (args.totalSeverity === SEVERITY.GREEN && placeLockedFlag?.hlLockFlag) {
          args.totalSeverity = 'lock';
        }
        if (args.totalSeverity === SEVERITY.BLUE && placeLockedFlag?.hlLockFlag) {
          args.totalSeverity = 'lock1';
        }
        if (venue.adLocked) {
          args.totalSeverity = 'adLock';
        }

        return args.totalSeverity;
      }

      if (!args.highlightOnly) {
        // Update icons to reflect current WME place services
        updateServicesChecks(_servicesBanner);

        // Add green highlighting to edit panel fields that have been updated by WMEPH
        UPDATED_FIELDS.updateEditPanelHighlights();

        assembleBanner(args.chainIsClosed); // Run async without awaiting - Google links process in background

        executeMultiAction(actions);
      }

      // After full harmonization, invalidate this venue's cache and refresh highlights to show updated color
      if (!args.highlightOnly) {
        delete _resultsCache[venueID];
      }

      // Highlighting will return a value, but no need to return a value here (for end of harmonization).
      // Adding this line to satisfy eslint.
      return undefined;
    } finally {
      // Restore harmonization flag
      _isHarmonizing = wasHarmonizing;
      _previousVenueServices = null; // Reset after harmonization completes

      // After harmonization flag is restored, refresh highlights only if this was a full harmonization (not highlight-only)
      if (!wasHarmonizing && useFlag === 'harmonize') {
        refreshAllHighlights();
      }
    }
  } // END harmonizePlaceGo_impl function

  /**
   * Wrapper around harmonizePlaceGo_impl that adds performance tracking and statistics collection.
   * Measures execution time and updates wmephStats with harmonization metrics. Separately tracks
   * cache hits vs misses for performance analysis (highlight-only calls from cache system).
   * @param {Object} venue The WME venue object to harmonize.
   * @param {string} useFlag Flag type controlling harmonization behavior ('harmonize', 'flag', etc.).
   * @param {Array<Object>} actions Optional array of venue update actions to apply.
   * @param {boolean} isCacheHit Optional flag indicating if this is a highlight from cache lookup.
   * @returns {*} Result from harmonizePlaceGo_impl implementation.
   */
  function harmonizePlaceGo(venue, useFlag, actions, isCacheHit) {
    const startTime = performance.now();
    try {
      return harmonizePlaceGo_impl(venue, useFlag, actions);
    } finally {
      const endTime = performance.now();
      const duration = endTime - startTime;
      wmephStats.harmonizeCount++;
      wmephStats.totalHarmonizeTime += duration;
      wmephStats.lastHarmonizeTime = duration;
      wmephStats.maxHarmonizeTime = Math.max(wmephStats.maxHarmonizeTime, duration);

      // Track cache performance separately for highlight-only calls
      if (useFlag === 'highlight' && isCacheHit !== undefined) {
        if (isCacheHit) {
          wmephStats.cacheHits++;
          wmephStats.totalCacheHitTime += duration;
          wmephStats.lastCacheHitTime = duration;
          wmephStats.maxCacheHitTime = Math.max(wmephStats.maxCacheHitTime, duration);
        } else {
          wmephStats.cacheMisses++;
          wmephStats.totalCacheMissTime += duration;
          wmephStats.lastCacheMissTime = duration;
          wmephStats.maxCacheMissTime = Math.max(wmephStats.maxCacheMissTime, duration);
        }
      }
    }
  }

  /**
   * Finds and processes nearby duplicate venues, then populates the duplicate banner.
   * Calls findNearbyDuplicate to search for matches and creates whitelisting UI for each found duplicate.
   * Updates _dupeBanner object which is later rendered in the harmonization banner.
   * @param {Object} venue The current WME venue object being checked for duplicates.
   * @param {string} name The venue name to search for duplicates of.
   * @param {Array<string>} aliases Alternative names to also check for duplicates.
   * @param {Object} addr The venue's address object (street, city, state, country).
   * @param {string} placePL The place permalink.
   */
  function runDuplicateFinder(venue, name, aliases, addr, placePL) {
    const venueID = venue.id;
    // Run nearby duplicate place finder function
    if (name.replace(/[^A-Za-z0-9]/g, '').length > 0 && !venue.residential && !isEmergencyRoom(venue) && !isRestArea(venue)) {
      // don't zoom and pan for results outside of FOV
      let duplicateName = findNearbyDuplicate(name, aliases, venue);
      if (duplicateName[1]) {
        new Flag.Overlapping();
      }
      [duplicateName] = duplicateName;
      if (duplicateName.length) {
        if (duplicateName.length + 1 !== _dupeIDList.length && USER.isDevUser) {
          // If there's an issue with the data return, allow an error report
          WazeWrap.Alerts.error(SCRIPT_NAME, 'WMEPH: Dupefinder Error!');
        } else {
          const wlAction = (dID) => {
            const wlKey = 'dupeWL';
            if (!_venueWhitelist.hasOwnProperty(venueID)) {
              // If venue is NOT on WL, then add it.
              _venueWhitelist[venueID] = { dupeWL: [] };
            }
            if (!_venueWhitelist[venueID].hasOwnProperty(wlKey)) {
              // If dupeWL key is not in venue WL, then initialize it.
              _venueWhitelist[venueID][wlKey] = [];
            }
            _venueWhitelist[venueID].dupeWL.push(dID); // WL the id for the duplicate venue
            _venueWhitelist[venueID].dupeWL = uniq(_venueWhitelist[venueID].dupeWL);
            // Make an entry for the opposite venue
            if (!_venueWhitelist.hasOwnProperty(dID)) {
              // If venue is NOT on WL, then add it.
              _venueWhitelist[dID] = { dupeWL: [] };
            }
            if (!_venueWhitelist[dID].hasOwnProperty(wlKey)) {
              // If dupeWL key is not in venue WL, then initialize it.
              _venueWhitelist[dID][wlKey] = [];
            }
            _venueWhitelist[dID].dupeWL.push(venueID); // WL the id for the duplicate venue
            _venueWhitelist[dID].dupeWL = uniq(_venueWhitelist[dID].dupeWL);
            saveWhitelistToLS(true); // Save the WL to local storage
            wmephWhitelistCounter();
            _buttonBanner2.clearWL.active = true;
            harmonizePlaceGo(venue, 'harmonize');
          };
          for (let ijx = 1; ijx < duplicateName.length + 1; ijx++) {
            _dupeBanner[_dupeIDList[ijx]] = {
              active: true,
              severity: SEVERITY.YELLOW,
              message: duplicateName[ijx - 1],
              WLactive: true,
              WLvalue: WL_BUTTON_TEXT,
              wlTooltip: 'Whitelist Duplicate',
              WLaction: wlAction,
            };
            if (_venueWhitelist.hasOwnProperty(venueID) && _venueWhitelist[venueID].hasOwnProperty('dupeWL') && _venueWhitelist[venueID].dupeWL.includes(_dupeIDList[ijx])) {
              // if the dupe is on the whitelist then remove it from the banner
              _dupeBanner[_dupeIDList[ijx]].active = false;
            } else {
              // Otherwise, activate the WL button
              _dupeBanner[_dupeIDList[ijx]].WLactive = true;
            }
          } // END loop for duplicate venues
        }
      }
    }
  }

  /**
   * Builds and renders the main harmonization banner UI containing all flags, duplicates, services, and tool buttons.
   * Retrieves current flags from FlagBase, constructs color-coded HTML rows, and attaches event handlers.
   * Also calls processGoogleLinks asynchronously to populate Google Places links.
   * @async
   * @param {boolean} chainIsClosed Whether the venue is part of a closed chain (affects display).
   */
  async function assembleBanner(chainIsClosed) {
    const flags = FlagBase.currentFlags.getOrderedFlags();
    const venue = getSelectedVenue();
    if (!venue) return;
    logDev('Building banners');
    let dupesFound = 0;
    let $rowDiv;
    let rowDivs = [];
    let totalSeverity = SEVERITY.GREEN;

    const func = (elem) => ({ id: elem.getAttribute('id'), val: elem.value });
    _textEntryValues = $('#WMEPH_banner input[type="text"]').toArray().map(func);
    _textEntryValues = _textEntryValues.concat($('#WMEPH_banner textarea').toArray().map(func));

    // Setup duplicates banners
    $rowDiv = $('<div class="banner-row yellow">');
    Object.keys(_dupeBanner).forEach((tempKey) => {
      const rowData = _dupeBanner[tempKey];
      if (rowData.active) {
        dupesFound += 1;
        const $dupeDiv = $('<div class="dupe">').appendTo($rowDiv);
        $dupeDiv.append($('<span style="margin-right:4px">').html(`&bull; ${rowData.message}`));
        if (rowData.value) {
          // Nothing happening here yet.
        }
        if (rowData.WLactive && rowData.WLaction) {
          // If there's a WL option, enable it
          totalSeverity = Math.max(rowData.severity, totalSeverity);
          $dupeDiv.append(
            $('<button>', {
              class: 'btn btn-success btn-xs wmephwl-btn',
              id: `WMEPH_WL${tempKey}`,
              title: rowData.wlTooltip,
            }).text(rowData.WLvalue),
          );
        }
      }
    });
    if (dupesFound) {
      // if at least 1 dupe
      $rowDiv.prepend(`Possible duplicate${dupesFound > 1 ? 's' : ''}:`);
      rowDivs.push($rowDiv);
    }

    // Build banners above the Services
    flags.forEach((flag) => {
      $rowDiv = $('<div class="banner-row">');
      let colorClass;
      switch (flag.severity) {
        case SEVERITY.RED:
          colorClass = 'red';
          break;
        case SEVERITY.YELLOW:
          colorClass = 'yellow';
          break;
        case SEVERITY.BLUE:
          colorClass = 'blue';
          break;
        case SEVERITY.GREEN:
          colorClass = 'gray';
          break;
        case SEVERITY.ORANGE:
          colorClass = 'orange';
          break;
        default:
          throw new Error(`WMEPH: Unexpected severity value while building banner: ${flag.severity}`);
      }
      $rowDiv.addClass(colorClass);
      if (flag.divId) {
        $rowDiv.attr('id', flag.divId);
      }
      if (flag.message && flag.message.length) {
        $rowDiv.append($('<span>').css({ 'margin-right': '4px' }).append(`&bull; ${flag.message}`));
      }
      if (flag.buttonText) {
        $rowDiv.append(
          $('<button>', {
            class: 'btn btn-default btn-xs wmeph-btn',
            id: `WMEPH_${flag.name}`,
            title: flag.title || '',
          })
            .css({ 'margin-right': '4px' })
            .html(flag.buttonText),
        );
      }
      if (flag.value2) {
        $rowDiv.append(
          $('<button>', {
            class: 'btn btn-default btn-xs wmeph-btn',
            id: `WMEPH_${flag.name}_2`,
            title: flag.title2 || '',
          })
            .css({ 'margin-right': '4px' })
            .html(flag.value2),
        );
      }
      if (flag.showWL) {
        if (flag.WLaction) {
          // If there's a WL option, enable it
          totalSeverity = Math.max(flag.severity, totalSeverity);
          $rowDiv.append($('<button>', { class: 'btn btn-success btn-xs wmephwl-btn', id: `WMEPH_WL${flag.name}`, title: flag.wlTooltip }).text('WL'));
        }
      } else {
        totalSeverity = Math.max(flag.severity, totalSeverity);
      }
      if (flag.suffixMessage) {
        $rowDiv.append($('<div>').css({ 'margin-top': '2px' }).append(flag.suffixMessage));
      }

      rowDivs.push($rowDiv);
    });

    if ($('#WMEPH-ColorHighlighting').prop('checked')) {
      venue.wmephSeverity = totalSeverity;
    }

    if ($('#WMEPH_banner').length === 0) {
      $('<div id="WMEPH_banner">').prependTo('#wmeph-panel');
    } else {
      $('#WMEPH_banner').empty();
    }
    const bgColor = SEVERITY_COLORS[totalSeverity] || SEVERITY_COLORS[SEVERITY.GREEN];
    $('#WMEPH_banner').css({ 'background-color': bgColor }).append(rowDivs);

    assembleServicesBanner(chainIsClosed);

    //  Build general banners (below the Services)
    rowDivs = [];
    const $buttonContainer = $('<div>');
    Object.keys(_buttonBanner2).forEach((tempKey) => {
      const banner2RowData = _buttonBanner2[tempKey];
      if (banner2RowData.active) {
        //  If the particular message is active
        if (banner2RowData.action) {
          $buttonContainer.append(
            ` <input class="btn btn-info btn-xs wmeph-clone-btn" id="WMEPH_${tempKey}" title="${
              banner2RowData.title
            }" type="button" value="${banner2RowData.value}" style="font-weight: normal; margin-left: 3px;">`,
          );
        }
        totalSeverity = Math.max(_buttonBanner2[tempKey].severity, totalSeverity);
      }
    });

    if ($buttonContainer.children().length > 0) {
      rowDivs.push($buttonContainer);
    }

    if ($('#WMEPH_tools').length === 0) {
      $('#WMEPH_services').after($('<div id="WMEPH_tools">'));
    } else {
      $('#WMEPH_tools').empty();
    }
    $('#WMEPH_tools').append(rowDivs);

    // Set up Duplicate onclicks
    if (dupesFound) {
      setupButtonsOld(_dupeBanner);
    }
    // Setup bannButt onclicks
    setupButtons(flags);

    // Setup bannButt2 onclicks
    setupButtonsOld(_buttonBanner2);

    // Set up click handlers for parking lot space buttons
    Flag.PlaSpaces.setupClickHandlers();

    // Format "no hours" section and hook up button events.
    $('#WMEPH_WLnoHours').css({ 'vertical-align': 'top' });

    if (_textEntryValues) {
      _textEntryValues.forEach((entry) => $(`#${entry.id}`).val(entry.val));
    }

    // Allow flags to do any additional work (hook up events, etc);
    flags.forEach((flag) => {
      flag.postProcess?.();
    });

    processGoogleLinks(venue); // Start Google links async without waiting
  } // END assemble Banner function

  /**
   * Processes Google Places links for external provider IDs and builds an informational banner row.
   * Fetches Google Place data asynchronously and renders clickable links with status indicators (open, closed, invalid).
   * Only renders if the original requesting venue is still selected. Includes hover interactions for map visualization.
   * @async
   * @param {Object} venue The WME venue object containing externalProviderIds.
   */
  async function processGoogleLinks(venue) {
    try {
      if (!venue?.externalProviderIds || !venue.externalProviderIds.length) {
        return; // No external provider IDs to process
      }

      const promises = venue.externalProviderIds.map((placeId) => _googlePlaces.getPlace(placeId));
      const googleResults = await Promise.all(promises);
      const selectedVenue = getSelectedVenue();
      $('#wmeph-google-link-info').remove();
      // Compare to venue to make sure a different place hasn't been selected since the results were requested.
      if (googleResults.length && venue?.id === selectedVenue?.id) {
        const $bannerDiv = $('<div>', { id: 'wmeph-google-link-info' });
        const googleLogoLetter = (letter, colorClass) => $('<span>', { class: 'google-logo' }).addClass(colorClass).text(letter);
        $bannerDiv.append(
          $('<div>', {
            class: 'banner-row gray',
            style: 'padding-top: 4px;color: #646464;padding-left: 8px;',
          })
            .text(' Links')
            .prepend(
              googleLogoLetter('G', 'blue'),
              googleLogoLetter('o', 'red'),
              googleLogoLetter('o', 'orange'),
              googleLogoLetter('g', 'blue'),
              googleLogoLetter('l', 'green'),
              googleLogoLetter('e', 'red'),
            )
            .prepend(
              $('<i>', {
                id: 'wmeph-ext-prov-jump',
                title: 'Jump to external providers section',
                class: 'fa fa-level-down',
                style: 'font-size: 15px;float: right;color: cadetblue;cursor: pointer;padding-left: 6px;',
              }),
            ),
        );
        venue.externalProviderIds.forEach((placeId) => {
          const result = googleResults.find((r) => r.placeId === placeId);
          if (result) {
            const linkStyle = 'margin-left: 5px;text-decoration: none;color: cadetblue;';
            let $nameSpan;
            const $row = $('<div>', { class: 'banner-row', style: 'border-top: 1px solid #ccc;' }).append(
              $('<table>', { style: 'width: 100%' }).append(
                $('<tbody>').append(
                  $('<tr>').append(
                    $('<td>').append(
                      '&bull;',
                      ($nameSpan = $('<span>', {
                        class: 'wmeph-google-place-name',
                        style: 'margin-left: 3px;font-weight: normal;',
                      }).text(`${result.requestStatus !== 'NOT_FOUND' ? result.name : result.placeId}`)),
                    ),
                    $('<td>', { style: 'text-align: right;font-weight: 500;padding: 2px 2px 2px 0px;min-width: 65px;' }).append(
                      result.website && result.requestStatus !== 'NOT_FOUND'
                        ? [
                            $('<a>', {
                              style: linkStyle,
                              href: result.website,
                              target: '_blank',
                              title: "Open the place's website, according to Google",
                            }).append(
                              $('<i>', {
                                class: 'fa fa-external-link',
                                style: 'font-size: 16px;position: relative;top: 1px;',
                              }),
                            ),
                            $('<span>', {
                              style: 'text-align: center;margin-left: 8px;margin-right: 4px;color: #c5c5c5;cursor: default;',
                            }).text('|'),
                          ]
                        : null,
                      result.requestStatus !== 'NOT_FOUND'
                        ? $('<a>', {
                            style: linkStyle,
                            href: result.url,
                            target: '_blank',
                            title: 'Open the place in Google Maps',
                          }).append(
                            $('<i>', {
                              class: 'fa fa-map-o',
                              style: 'font-size: 16px;',
                            }),
                          )
                        : null,
                    ),
                  ),
                ),
              ),
            );

            if (result.requestStatus === 'NOT_FOUND') {
              $row.addClass('red');
              $row.attr('title', 'This Google place ID was not found. Please update the link in the External Providers section.');
            } else if (result.business_status === 'CLOSED_PERMANENTLY') {
              $nameSpan.append(' [CLOSED]');
              $row.addClass('red');
              $row.attr('title', 'Google indicates this linked place is permanently closed. Please verify.');
            } else if (result.business_status === 'CLOSED_TEMPORARILY') {
              $nameSpan.append(' [TEMPORARILY&nbsp;CLOSED]');
              $row.addClass('yellow');
              $row.attr('title', 'Google indicates this linked place is TEMPORARILY closed. Please verify.');
            } else if (googleResults.filter((otherResult) => otherResult.placeId === result.placeId).length > 1) {
              $nameSpan.append(' [DUPLICATE]');
              $row.css('background-color', '#fde5c8');
              $row.attr('title', 'This place is linked more than once. Please remove extra links.');
            } else {
              $row.addClass('lightgray');
            }

            $bannerDiv.append($row);

            $row.attr('uuid', result.placeId);
            if (result.requestStatus !== 'NOT_FOUND') {
              addGoogleLinkHoverEvent($row);
            }
          }
        });
        $('#WMEPH_banner').append($bannerDiv);
        $('#wmeph-ext-prov-jump').click(() => {
          const extProvSelector = '#venue-edit-general > div.external-providers-control.form-group';
          document.querySelector('#edit-panel wz-tab.venue-edit-tab-general').isActive = true;
          setTimeout(() => {
            document.querySelector(extProvSelector).scrollIntoView({ behavior: 'smooth' });
            setTimeout(() => {
              $(extProvSelector).addClass('highlight');
              setTimeout(() => {
                $(extProvSelector).removeClass('highlight');
              }, 1500);
            }, 250);
          }, 0);
        });
      }
    } catch (err) {
      logDev('processGoogleLinks error:', err);
    }
  }

  /**
   * Intercepts calls to Google Maps PlacesService.getDetails to cache results locally.
   * Wraps the original method to capture place data and store in _googlePlaces cache for later use.
   * Retries initialization if Google Maps API hasn't loaded yet. Called during script initialization.
   */
  function interceptGoogleGetDetails() {
    if (typeof google === 'undefined' || !google.maps || !google.maps.places || !google.maps.places.PlacesService) {
      logDev('Google Maps PlacesService not loaded yet.');
      setTimeout(interceptGoogleGetDetails, 500); // Retry until it loads
      return;
    }

    const originalGetDetails = google.maps.places.PlacesService.prototype.getDetails;
    google.maps.places.PlacesService.prototype.getDetails = function interceptedGetDetails(request, callback) {
      logDev('Intercepted getDetails call:', request);
      const { placeId } = request;
      const customCallback = function (result, status) {
        const googleResult = { ...result };
        googleResult.placeId = placeId;
        googleResult.requestStatus = status;
        _googlePlaces.addPlace(placeId, googleResult);
        callback(result, status); // Pass the result to the original callback
      };

      return originalGetDetails.call(this, request, customCallback);
    };

    logDev('Google Maps PlacesService.getDetails intercepted successfully.');
  }

  /**
   * Draws a line and point on the map between the selected venue and a Google Places external provider.
   * Creates GeoJSON features and adds them to the wmeph_google_link layer. Includes distance label.
   * Automatically clears visualization after 4 seconds via timeoutDestroyGooglePlacePoint.
   * @async
   * @param {string} uuid Google Place ID to visualize on the map.
   */
  async function drawGooglePlacePoint(uuid) {
    if (!uuid) return;
    const link = await _googlePlaces.getPlace(uuid);
    if (link?.geometry) {
      const selectedVenue = getSelectedVenue();
      if (!selectedVenue?.geometry) {
        logDev('drawGooglePlacePoint: No selected venue');
        return;
      }

      const coord = link.geometry.location;
      // Google coords are already WGS84 [lng, lat]
      const poiPt = turf.point([coord.lng(), coord.lat()]);
      const placeCentroid = getVenueCentroid(selectedVenue);
      if (!placeCentroid) return;

      const placePt = turf.point(placeCentroid);
      const bbox = getMapBoundingBox();
      if (!bbox) return;

      // Create line from place to POI
      let lineCoords = [placeCentroid, [coord.lng(), coord.lat()]];

      // Check if line crosses bbox boundary - if so, only draw within bounds
      // This is a simplified check: if start or end is outside bbox, clip it
      const [minLon, minLat, maxLon, maxLat] = bbox;
      const startInBounds = placeCentroid[0] >= minLon && placeCentroid[0] <= maxLon && placeCentroid[1] >= minLat && placeCentroid[1] <= maxLat;
      const endInBounds = coord.lng() >= minLon && coord.lng() <= maxLon && coord.lat() >= minLat && coord.lat() <= maxLat;

      if (!startInBounds || !endInBounds) {
        // Line crosses boundary - for now just show it anyway (Turf will handle clipping)
        logDev('Line crosses map boundary');
      }

      let label = '';
      // Calculate distance in meters
      const distanceMeters = calculatePointDistance(placeCentroid, [coord.lng(), coord.lat()]);
      let unitConversion;
      let unit1;
      let unit2;

      // Check if using imperial units
      const isImperial = sdk.Settings?.getUserSettings?.()?.isImperial ?? false;
      let distance = distanceMeters;

      if (isImperial) {
        distance *= 3.28084; // Convert to feet
        unitConversion = 5280;
        unit1 = ' ft';
        unit2 = ' mi';
      } else {
        unitConversion = 1000;
        unit1 = ' m';
        unit2 = ' km';
      }

      if (distance > unitConversion * 10) {
        label = Math.round(distance / unitConversion) + unit2;
      } else if (distance > 1000) {
        label = Math.round(distance / (unitConversion / 10)) / 10 + unit2;
      } else {
        label = Math.round(distance) + unit1;
      }

      logDev('drawGooglePlacePoint: distance=', distanceMeters, 'label=', label);

      destroyGooglePlacePoint(); // Just in case it still exists.

      // Create GeoJSON features for SDK
      _googlePlacePtFeature = {
        type: 'Feature',
        id: 'google_place_pt',
        geometry: poiPt.geometry,
        properties: { poiCoord: true, label: '' },
      };

      _googlePlaceLineFeature = {
        type: 'Feature',
        id: 'google_place_line',
        geometry: {
          type: 'LineString',
          coordinates: lineCoords,
        },
        properties: { label },
      };

      // Add to custom layer
      try {
        sdk.Map.addFeatureToLayer({
          layerName: 'wmeph_google_link',
          feature: _googlePlacePtFeature,
        });
        sdk.Map.addFeatureToLayer({
          layerName: 'wmeph_google_link',
          feature: _googlePlaceLineFeature,
        });
      } catch (e) {
        logDev('drawGooglePlacePoint: Failed to add features', e);
      }

      timeoutDestroyGooglePlacePoint();
    }
  }

  /**
   * Schedules destruction of Google Place point visualization after 4 seconds.
   * Clears any existing timeout first to prevent multiple simultaneous timers.
   */
  function timeoutDestroyGooglePlacePoint() {
    if (_destroyGooglePlacePointTimeoutId) clearTimeout(_destroyGooglePlacePointTimeoutId);
    _destroyGooglePlacePointTimeoutId = setTimeout(() => destroyGooglePlacePoint(), 4000);
  }

  /**
   * Removes Google Place point and line features from the wmeph_google_link map layer.
   * Called when visualization timeout expires or when user interaction ends.
   */
  function destroyGooglePlacePoint() {
    if (_googlePlacePtFeature || _googlePlaceLineFeature) {
      try {
        // Remove features from the layer using SDK
        if (_googlePlacePtFeature?.id) {
          sdk.Map.removeAllFeaturesFromLayer({ layerName: 'wmeph_google_link' });
        }
      } catch (e) {
        logDev('destroyGooglePlacePoint: Failed to remove features', e);
      }
      _googlePlacePtFeature = null;
      _googlePlaceLineFeature = null;
    }
  }

  /**
   * Attaches hover event handlers to a Google Places link element.
   * On hover-in, draws the place point on map. On hover-out, destroys visualization.
   * @param {jQuery} $el jQuery element containing the Google place link row.
   */
  function addGoogleLinkHoverEvent($el) {
    $el.hover(
      () => drawGooglePlacePoint(getGooglePlaceUuidFromElement($el)),
      () => destroyGooglePlacePoint(),
    );
  }

  /**
   * Extracts the Google Place UUID from an HTML element's 'uuid' attribute.
   * @param {jQuery} $el jQuery element with a uuid attribute.
   * @returns {string} The Google Place ID.
   */
  function getGooglePlaceUuidFromElement($el) {
    return $el.attr('uuid');
  }

  /**
   * Builds and renders the services banner UI showing applicable services for the selected venue.
   * Populates checkboxes and buttons for adding/toggling services. Hidden for residential venues.
   * @param {boolean} chainIsClosed Whether the venue is part of a closed chain (affects display).
   */
  function assembleServicesBanner(chainIsClosed) {
    if ($('#WMEPH_services').length === 0) {
      $('#WMEPH_banner').after(
        $('<div id="WMEPH_services">').css({
          color: document.body.getAttribute('wz-theme') === 'dark' ? '#e8eaed' : '#202124',
          'font-size': '15px',
          'margin-left': '6px',
          'background-color': 'transparent',
          padding: '4px 0',
        }),
      );
    } else {
      $('#WMEPH_services').empty();
    }

    const venue = getSelectedVenue();
    if (venue && !chainIsClosed && !$('#WMEPH-HideServicesButtons').prop('checked')) {
      // setup Add Service Buttons for suggested services
      const rowDivs = [];
      if (!isVenueResidential(venue)) {
        const $rowDiv = $('<div id="WMEPH_servicesIconsContainer">').css({
          display: 'flex',
          flexWrap: 'wrap',
          gap: '6px',
          alignItems: 'center',
        });
        const servButtHeight = '27';
        const buttons = [];
        const applicableServices = getApplicableServices(venue);
        const isParkingLot = isVenueParkingLot(venue);
        Object.keys(_servicesBanner).forEach((tempKey) => {
          const rowData = _servicesBanner[tempKey];
          const isApplicable = !rowData.serviceId || applicableServices.includes(rowData.serviceId);
          // Exclude non-PL versions on parking lots, and PL versions on general venues
          const isBtnForWrongVenueType =
            (isParkingLot && (tempKey === 'addValet' || tempKey === 'addReservations')) ||
            (!isParkingLot && (tempKey === 'addValetPL' || tempKey === 'addReservationsPL' || tempKey === 'addValletServicePL'));
          if (rowData.active && isApplicable && !isBtnForWrongVenueType) {
            //  If the particular service is active AND applicable to this venue type
            const $input = $('<input>', {
              class: rowData.icon,
              id: `WMEPH_${tempKey}`,
              type: 'button',
              title: rowData.title,
            }).css({
              border: 0,
              'background-size': 'contain',
              height: '27px',
              width: `${Math.ceil(servButtHeight * rowData.w2hratio).toString()}px`,
            });
            buttons.push($input);
            // Swap class based on checked state
            if (rowData.checked) {
              $input.removeClass(rowData.icon).addClass(rowData.icon + '-active');
            }
            $rowDiv.append($input);
          }
        });
        if ($rowDiv.length) {
          $rowDiv.prepend('<span class="control-label" title="Verify all Place services before saving">Services (select any that apply):</span><br>');
        }
        rowDivs.push($rowDiv);
      }
      $('#WMEPH_services').append(rowDivs);

      // Setup bannServ onclicks
      if (!isVenueResidential(venue)) {
        setupButtonsOld(_servicesBanner);
      }
    }
  }

  /**
   * Attaches onclick handlers to banner flag buttons using modern Flag class methods.
   * Loops through flags and binds primary action, secondary action, and whitelist buttons.
   * @param {Array<FlagBase>} flags Array of Flag objects from FlagBase.currentFlags.getOrderedFlags().
   */
  function setupButtons(flags) {
    flags.forEach((flag) => {
      // Loop through the banner possibilities
      if (flag.action && flag.buttonText) {
        // If there is an action, set onclick
        buttonAction(flag);
      }
      if (flag.action2 && flag.value2) {
        // If there is an action2, set onclick
        buttonAction2(flag);
      }
      // If there's a WL option, set up onclick
      if (flag.showWL && flag.WLaction) {
        buttonWhitelist(flag);
      }
    });
  }

  /**
   * Attaches onclick handlers to banner buttons using legacy object-based banner data.
   * Used for duplicate and service banners which use object key-value structure instead of Flag classes.
   * @param {Object} banner Object with banner data keyed by flagKey (e.g., _dupeBanner, _buttonBanner2).
   */
  function setupButtonsOld(banner) {
    Object.keys(banner).forEach((flagKey) => {
      const flag = banner[flagKey];
      if (flag?.active && flag.action && flag.value) {
        buttonActionOld(flagKey, flag);
      }
      if (flag?.WLactive && flag.WLaction) {
        buttonWhitelistOld(flagKey, flag);
      }
    });
  }

  /**
   * Sets onclick handler for a legacy banner button that executes the flag's action.
   * Calls harmonizePlaceGo to refresh the banner after action unless noBannerAssemble flag is set.
   * @param {string} flagKey The key identifying this flag (used to find the button element).
   * @param {Object} flag The legacy banner flag object containing the action callback.
   */
  function buttonActionOld(flagKey, flag) {
    const button = document.getElementById(`WMEPH_${flagKey}`);
    if (!button) return;
    button.onclick = () => {
      // Service buttons expect (actions, checked) parameters
      // Pass undefined for actions (no action list for service toggles) and undefined for checked (auto-toggle)
      flag.action(undefined, undefined);
      if (!flag.noBannerAssemble) harmonizePlaceGo(getSelectedVenue(), 'harmonize');
    };
  }
  /**
   * Sets onclick handler for a legacy banner whitelist button.
   * Passes flagKey as parameter to WLaction if it matches a venue ID pattern (5+ digits), otherwise no param.
   * Deactivates the button and sets severity to green after whitelisting.
   * @param {string} flagKey The key identifying this flag (venue ID or other identifier).
   * @param {Object} flag The legacy banner flag object containing the WLaction callback.
   * @returns {HTMLElement} The button element.
   */
  function buttonWhitelistOld(flagKey, flag) {
    const button = document.getElementById(`WMEPH_WL${flagKey}`);
    button.onclick = () => {
      if (flagKey.match(/^\d{5,}/) !== null) {
        flag.WLaction(flagKey);
      } else {
        flag.WLaction();
      }
      flag.WLactive = false;
      flag.severity = SEVERITY.GREEN;
    };
    return button;
  }

  /**
   * Sets onclick handler for a modern Flag class button that executes the flag's primary action.
   * Triggers banner refresh via harmonizePlaceGo unless noBannerAssemble is set.
   * @param {FlagBase} flag The modern Flag object containing the action callback and name.
   * @returns {HTMLElement} The button element.
   */
  function buttonAction(flag) {
    const button = document.getElementById(`WMEPH_${flag.name}`);
    button.onclick = () => {
      flag.action();
      if (!flag.noBannerAssemble) harmonizePlaceGo(getSelectedVenue(), 'harmonize');
    };
    return button;
  }

  /**
   * Sets onclick handler for a modern Flag class button that executes the flag's secondary action.
   * Triggers banner refresh via harmonizePlaceGo unless noBannerAssemble is set.
   * @param {FlagBase} flag The modern Flag object containing the action2 callback and name.
   * @returns {HTMLElement} The button element.
   */
  function buttonAction2(flag) {
    const button = document.getElementById(`WMEPH_${flag.name}_2`);
    button.onclick = () => {
      flag.action2();
      if (!flag.noBannerAssemble) harmonizePlaceGo(getSelectedVenue(), 'harmonize');
    };
    return button;
  }

  /**
   * Sets onclick handler for a modern Flag class whitelist button.
   * Passes flag.name to WLaction if it matches a venue ID pattern (5+ digits), otherwise no param.
   * @param {FlagBase} flag The modern Flag object containing the WLaction callback and name.
   * @returns {HTMLElement} The button element.
   */
  function buttonWhitelist(flag) {
    const button = document.getElementById(`WMEPH_WL${flag.name}`);
    button.onclick = () => {
      if (flag.name.match(/^\d{5,}/) !== null) {
        flag.WLaction(flag.name);
      } else {
        flag.WLaction();
      }
    };
    return button;
  }

  /**
   * Checks if a checkbox/setting ID is enabled by retrieving its stored value.
   * @param {string} id The setting ID to check.
   * @returns {boolean} True if the setting value equals '1', false otherwise.
   */
  function isChecked(id) {
    return getWMEPHSetting(id) === '1';
  }

  /**
   * Updates a checkbox's stored setting value and applies visual styling.
   * Toggles color (#0075e3 for checked, #999 for unchecked) and opacity.
   * Only updates if the current state differs from the target state.
   * @param {string} id The setting ID to update.
   * @param {boolean} checkedState The target checked state.
   */
  function setCheckbox(id, checkedState) {
    const currentState = isChecked(id);
    if (currentState !== checkedState) {
      setWMEPHSetting(id, checkedState ? '1' : '0');
      const $button = $(`#${id}`);
      $button.css({
        color: checkedState ? '#0075e3' : '#999',
        opacity: checkedState ? '1' : '0.5',
      });
    }
  }
  /**
   * Updates multiple checkboxes to the same checked state by calling setCheckbox for each ID.
   * @param {Array<string>} ids Array of setting IDs to update.
   * @param {boolean} checkedState The target checked state for all IDs.
   */
  function setCheckboxes(ids, checkedState) {
    ids.forEach((id) => {
      setCheckbox(id, checkedState);
    });
  }

  /**
   * Copies selected place information to clipboard as JSON for later pasting.
   * Captures address, URL, phone, description, services, hours, and parking lot flag.
   * Called when user clicks the "Copy" button in clone tool.
   */
  function onCopyClicked() {
    const venue = getSelectedVenue();
    const addr = getVenueAddress(venue);

    _cloneMaster = {
      addr: {
        houseNumber: venue.houseNumber,
        street: { name: addr?.street?.name || '' },
        city: { name: addr?.city?.name || '' },
        state: { name: addr?.state?.name || '' },
      },
      url: venue.url || '',
      phone: venue.phone || '',
      description: venue.description || '',
      services: venue.services || [],
      openingHours: venue.openingHours || [],
      isPLA: isVenueParkingLot(venue),
    };

    GM_setClipboard(JSON.stringify(_cloneMaster, null, 2));
    logDev('Place Cloned');
  }
  /**
   * Pastes previously copied place information into the selected venue.
   * Called when user clicks the "Paste" button in clone tool.
   */
  function onPasteClicked() {
    clonePlace();
  }

  /**
   * Enables all clone checkboxes (house number, street, city, URL, phone, services, description, hours).
   * Called when user clicks the "All" preset button in clone tool.
   */
  function onCheckAllCloneClicked() {
    setCheckboxes(['WMEPH_CPhn', 'WMEPH_CPstr', 'WMEPH_CPcity', 'WMEPH_CPurl', 'WMEPH_CPph', 'WMEPH_CPserv', 'WMEPH_CPdesc', 'WMEPH_CPhrs'], true);
  }

  /**
   * Enables only address-related clone checkboxes (house number, street, city).
   * Called when user clicks the "Addr" preset button in clone tool.
   */
  function onCheckAddrCloneClicked() {
    setCheckboxes(['WMEPH_CPhn', 'WMEPH_CPstr', 'WMEPH_CPcity'], true);
    setCheckboxes(['WMEPH_CPurl', 'WMEPH_CPph', 'WMEPH_CPserv', 'WMEPH_CPdesc', 'WMEPH_CPhrs'], false);
  }

  /**
   * Disables all clone checkboxes.
   * Called when user clicks the "None" preset button in clone tool.
   */
  function onCheckNoneCloneClicked() {
    setCheckboxes(['WMEPH_CPhn', 'WMEPH_CPstr', 'WMEPH_CPcity', 'WMEPH_CPurl', 'WMEPH_CPph', 'WMEPH_CPserv', 'WMEPH_CPdesc', 'WMEPH_CPhrs'], false);
  }

  /**
   * Creates and displays the clone tool UI with copy/paste buttons and field checkboxes.
   * Buttons allow selecting which place fields to copy/paste. Only shown if clone mode is enabled.
   * Updates paste button visibility based on user edit permissions.
   */
  function showCloneButton() {
    if (!$('#clonePlace').length) {
      // Row 2: Copy, Paste, All, Addr, None buttons
      const $row2 = $('<div>', { class: 'wmeph-clone-row' });
      $row2.append(
        $('<input>', {
          class: 'btn btn-warning btn-xs wmeph-clone-btn',
          id: 'clonePlace',
          title: 'Copy place info',
          type: 'button',
          value: 'Copy',
          style: 'font-weight: normal',
        }).click(onCopyClicked),
        $('<input>', {
          class: 'btn btn-warning btn-xs wmeph-clone-btn',
          id: 'pasteClone',
          title: 'Apply the Place info. (Ctrl-Alt-O)',
          type: 'button',
          value: 'Paste (for ✓ boxes):',
          style: 'font-weight: normal; margin-left: 3px;',
        }).click(onPasteClicked),
        $('<input>', {
          class: 'btn btn-info btn-xs wmeph-clone-btn',
          id: 'checkAllClone',
          title: 'Check all',
          type: 'button',
          value: 'All',
          style: 'font-weight: normal; margin-left: 3px;',
        }).click(onCheckAllCloneClicked),
        $('<input>', {
          class: 'btn btn-info btn-xs wmeph-clone-btn',
          id: 'checkAddrClone',
          title: 'Check address',
          type: 'button',
          value: 'Addr',
          style: 'font-weight: normal; margin-left: 3px;',
        }).click(onCheckAddrCloneClicked),
        $('<input>', {
          class: 'btn btn-info btn-xs wmeph-clone-btn',
          id: 'checkNoneClone',
          title: 'Check none',
          type: 'button',
          value: 'None',
          style: 'font-weight: normal; margin-left: 3px;',
        }).click(onCheckNoneCloneClicked),
      );

      // Row 3: All checkboxes (HN, Str, City, URL, Ph, Desc, Serv, Hrs)
      const $row3 = $('<div>', { class: 'wmeph-clone-row' });
      $row3.append(
        createCloneCheckbox('wmeph-clone-row', 'WMEPH_CPhn', 'HN'),
        createCloneCheckbox('wmeph-clone-row', 'WMEPH_CPstr', 'Str'),
        createCloneCheckbox('wmeph-clone-row', 'WMEPH_CPcity', 'City'),
        createCloneCheckbox('wmeph-clone-row', 'WMEPH_CPurl', 'URL'),
        createCloneCheckbox('wmeph-clone-row', 'WMEPH_CPph', 'Ph'),
        createCloneCheckbox('wmeph-clone-row', 'WMEPH_CPdesc', 'Desc'),
        createCloneCheckbox('wmeph-clone-row', 'WMEPH_CPserv', 'Serv'),
        createCloneCheckbox('wmeph-clone-row', 'WMEPH_CPhrs', 'Hrs'),
      );

      $('#wmeph-run-panel').append($row2, $row3);
    }
    const venue = getSelectedVenue();
    const canEdit = venue?.approved && venue?.lockRank < USER.rank;
    updateElementEnabledOrVisible($('#pasteClone'), canEdit);
  }

  /**
   * Opens PlugShare website centered on the selected venue's location.
   * Respects user setting for opening in new tab or search results window.
   * Called when user clicks the "PS" (PlugShare) button in the main panel.
   */
  function onPlugshareSearchClick() {
    const venue = getSelectedVenue();
    const centroid = getVenueCentroid(venue);
    if (!centroid) {
      logDev('onPlugshareSearchClick: Unable to get venue centroid');
      return;
    }
    // centroid is already [lon, lat] in WGS84, no conversion needed
    const url = `https://www.plugshare.com/?latitude=${centroid[1]}&longitude=${centroid[0]}&spanLat=.005&spanLng=.005`;
    if ($('#WMEPH-WebSearchNewTab').prop('checked')) {
      window.open(url);
    } else {
      window.open(url, 'WMEPH - PlugShare Search', _searchResultsWindowSpecs);
    }
  }

  /**
   * Opens the venue's website URL in a browser.
   * Automatically prepends http:// if URL doesn't start with http/https. Shows error alert if no URL is set.
   * Respects user setting for opening in new tab or search results window.
   * Called when user clicks the "Website" button in the main panel.
   */
  function onOpenWebsiteClick() {
    const venue = getSelectedVenue();
    let url = venue.url;
    if (!url) {
      WazeWrap.Alerts.error(SCRIPT_NAME, 'No website set for this place.');
      return;
    }
    if (url.match(/^http/i) === null) {
      url = `http://${url}`;
    }
    try {
      if ($('#WMEPH-WebSearchNewTab').prop('checked')) {
        window.open(url);
      } else {
        window.open(url, SEARCH_RESULTS_WINDOW_NAME, _searchResultsWindowSpecs);
      }
    } catch (ex) {
      logDev(ex);
      WazeWrap.Alerts.error(SCRIPT_NAME, "Possible invalid URL. Check the place's Website field.");
    }
  }

  /**
   * Opens a Google search for the selected venue by name, address, and house number.
   * Shows error alert if state and country are not set. Respects user setting for tab vs. window.
   * Called when user clicks the "Google" button in the main panel.
   */
  function onGoogleSearchClick() {
    const venue = getSelectedVenue();
    const addr = getVenueAddress(venue);
    if (addr?.state && addr?.country) {
      const url = buildGLink(venue.name, addr, venue.houseNumber);
      if ($('#WMEPH-WebSearchNewTab').prop('checked')) {
        window.open(url);
      } else {
        window.open(url, SEARCH_RESULTS_WINDOW_NAME, _searchResultsWindowSpecs);
      }
    } else {
      WazeWrap.Alerts.error(SCRIPT_NAME, "The state and country haven't been set for this place yet.  Edit the address first.");
    }
  }

  /**
   * Controls visibility and enabled/disabled state of a jQuery DOM element.
   * Accepts an object with 'visible' and/or 'enabled' boolean properties.
   * @param {jQuery} $elem jQuery element to modify.
   * @param {Object} props Object with optional 'visible' and 'enabled' boolean properties.
   */
  function updateElementEnabledOrVisible($elem, props) {
    if (props.hasOwnProperty('visible')) {
      if (props.visible) {
        $elem.show();
      } else {
        $elem.hide();
      }
    }
    if (props.hasOwnProperty('enabled')) {
      $elem.prop('disabled', !props.enabled);
    }
  }

  /**
   * Creates or updates the main WMEPH panel UI in the edit pane when a venue is selected.
   * Builds buttons (Run, Website, Google, PlugShare), banner areas, and clone tool if enabled.
   * Checks for backend feeds and shows warning if place is connected to external data sources.
   * @param {boolean} clearBanner Whether to remove the entire WMEPH section (triggered when venue loses edit access).
   */
  function updateWmephPanel(clearBanner = false) {
    logDev(`updateWmephPanel: clearBanner=${clearBanner}`);

    const venue = getSelectedVenue();

    if (!venue) {
      $('#wmeph-section').remove();
      destroyDupeLabels(); // Clear dupe labels when no place is selected
      _dupeIDList = []; // Reset dupe list
      return;
    }

    if (!venue.approved || venue.lockRank >= USER.rank) {
      clearBanner = true;
    }

    if (clearBanner) {
      $('#wmeph-section').remove();
      destroyDupeLabels(); // Clear dupe labels when banner is cleared
    }

    let $wmephPanel;
    let $wmephPrePanel;
    let $wmephRunPanel;
    let $runButton;
    let $websiteButton;
    let $googleSearchButton;
    let $plugshareSearchButton;

    if (!$('#wmeph-section').length) {
      const devVersSuffix = IS_BETA_VERSION ? '-β' : '';

      // Create section wrapper and header
      const $wmephSection = $('<div>', { id: 'wmeph-section', class: 'wmeph-section' });
      const $sectionHeader = $('<div>', { class: 'wmeph-section-header' }).html('<span>⚙️ WMEPH</span>');
      const $sectionBody = $('<div>', { class: 'wmeph-section-body' });

      // Create panels
      $wmephPrePanel = $('<div>', { id: 'wmeph-pre-panel' });
      $wmephPanel = $('<div>', { id: 'wmeph-panel' });
      $wmephRunPanel = $('<div>', { id: 'wmeph-run-panel' });

      // Create Bootstrap buttons
      $runButton = $('<input>', {
        class: 'btn btn-primary btn-sm wmeph-run-btn',
        id: 'runWMEPH',
        title: `Run WMEPH${devVersSuffix} on Place`,
        type: 'button',
        value: `Run WMEPH${devVersSuffix}`,
      }).click(() => {
        harmonizePlace();
      });

      $websiteButton = $('<input>', {
        class: 'btn btn-success btn-sm wmeph-run-btn',
        id: 'WMEPHurl',
        title: 'Open place URL',
        type: 'button',
        value: 'Website',
      }).click(onOpenWebsiteClick);

      $googleSearchButton = $('<input>', {
        class: 'btn btn-danger btn-sm wmeph-run-btn',
        id: 'wmephSearch',
        title: 'Search the web for this place.  Do not copy info from 3rd party sources!',
        type: 'button',
        value: 'Google',
      }).click(onGoogleSearchClick);

      $plugshareSearchButton = $('<input>', {
        class: 'btn btn-danger btn-sm wmeph-run-btn',
        id: 'wmephPlugShareSearch',
        title: 'Open PlugShare website',
        type: 'button',
        value: 'PS',
      }).click(onPlugshareSearchClick);

      // Build panel hierarchy - buttons first in run panel
      $wmephRunPanel.append($runButton, $websiteButton, $googleSearchButton, $plugshareSearchButton);

      // Add panels to section body - run panel first (always at top), then the banner/services panel
      $sectionBody.append($wmephRunPanel, $wmephPrePanel, $wmephPanel);

      // Build section
      $wmephSection.append($sectionHeader, $sectionBody);

      // Insert section into edit panel
      $('#edit-panel > .contents').prepend($wmephSection);
    } else {
      $wmephPrePanel = $('#wmeph-pre-panel');
      $wmephPanel = $('#wmeph-panel');
      $wmephRunPanel = $('#wmeph-run-panel');
      $runButton = $('#runWMEPH');
      $websiteButton = $('#WMEPHurl');
      $googleSearchButton = $('#wmephSearch');
      $plugshareSearchButton = $('#wmephPlugShareSearch');
    }

    const canEdit = venue.approved && venue.lockRank < USER.rank;
    updateElementEnabledOrVisible($runButton, { enabled: canEdit });
    updateElementEnabledOrVisible($websiteButton, { enabled: venue.url?.trim().length, visible: !isVenueResidential(venue) });
    updateElementEnabledOrVisible($googleSearchButton, { enabled: !isVenueResidential(venue), visible: !isVenueResidential(venue) });
    updateElementEnabledOrVisible($plugshareSearchButton, { visible: isVenueChargingStation(venue) });

    if (getWMEPHSetting('WMEPH-EnableCloneMode') === '1') {
      showCloneButton();
    }
    // If the user selects a place in the dupe list, don't clear the labels yet
    if (_dupeIDList.includes(venue.id)) {
      destroyDupeLabels();
    }

    // Check if there's a backend feed
    // TODO: put this in a separate function?
    if (venue) {
      const venueID = venue.id; // Capture venue ID to verify response is for current venue
      $wmephPrePanel.empty(); // Clear old feed banners before fetching new ones

      // Abort previous request if still pending (prevents duplicate banners from race conditions)
      if (_pendingFeedRequest) _pendingFeedRequest.abort();

      // It doesn't seem to matter what we pass for lon/lat, so use first geometry point.
      const firstPoint = isVenuePoint(venue) ? venue.geometry.coordinates : venue.geometry.coordinates[0][0];
      const lon = firstPoint[0];
      const lat = firstPoint[1];
      const url = `https://${location.host}/SearchServer/mozi?lon=${lon}&lat=${lat}&format=PROTO_JSON_FULL&venue_id=venues.${venue.id}`;
      _pendingFeedRequest = $.getJSON(url)
        .done((res) => {
          // Only append if still on same venue (prevents stale responses from accumulating)
          const currentVenue = getSelectedVenue();
          if (!currentVenue || currentVenue.id !== venueID) return;

          let feedNames = res.venue.external_providers?.filter((prov) => !FEEDS_TO_SKIP.some((skipRegex) => skipRegex.test(prov.provider))).map((prov) => prov.provider);
          if (feedNames) feedNames = [...new Set(feedNames)]; // Remove duplicates
          if (feedNames?.length) {
            const $rowDiv = $('<div>').css({ padding: '3px 4px 0px 4px', 'background-color': 'yellow' });
            $rowDiv.append(
              $('<div>').text('PLEASE DO NOT DELETE').css({ 'font-weight': '500' }),
              $('<div>')
                .text(`Place is connected to the following feed${feedNames.length > 1 ? 's' : ''}:`)
                .css({ 'font-size': '13px' }),
              $('<div>').text(feedNames.join(', ')).css({ 'font-size': '13px' }),
            );
            $wmephPrePanel.append($rowDiv);
            // Potential code to hide the delete key if needed.
            // setTimeout(() => $('#delete-button').setAttribute('disabled', true), 200);
          }
          _pendingFeedRequest = null; // Clear request tracker when done
        })
        .fail(() => {
          _pendingFeedRequest = null;
        }); // Clear on error too
    }
  }

  /**
   * Applies previously copied place information to the selected venue based on enabled checkboxes.
   * Updates URL, phone, description, services, hours, and address fields as selected.
   * Shows log message if no data has been copied yet or if copy/paste is successful.
   */
  function clonePlace() {
    log('Cloning info...');
    if (_cloneMaster !== null && _cloneMaster.hasOwnProperty('url')) {
      const venue = getSelectedVenue();
      const cloneItems = {};
      let updateItem = false;
      if (isChecked('WMEPH_CPurl')) {
        cloneItems.url = _cloneMaster.url;
        updateItem = true;
      }
      if (isChecked('WMEPH_CPph')) {
        cloneItems.phone = _cloneMaster.phone;
        updateItem = true;
      }
      if (isChecked('WMEPH_CPdesc')) {
        cloneItems.description = _cloneMaster.description;
        updateItem = true;
      }
      if (isChecked('WMEPH_CPserv') && isVenueParkingLot(venue) === _cloneMaster.isPLA) {
        cloneItems.services = _cloneMaster.services;
        updateItem = true;
      }
      if (isChecked('WMEPH_CPhrs')) {
        cloneItems.openingHours = _cloneMaster.openingHours;
        updateItem = true;
      }
      if (updateItem) {
        addUpdateAction(venue, cloneItems);
        logDev('Venue details cloned');
      }

      const copyStreet = isChecked('WMEPH_CPstr');
      const copyCity = isChecked('WMEPH_CPcity');
      const copyHn = isChecked('WMEPH_CPhn');

      if (copyStreet || copyCity || copyHn) {
        const originalAddress = sdk.DataModel.Venues.getAddress({ venueId: venue.id });
        const newAddress = {
          street: copyStreet ? _cloneMaster.addr.street : originalAddress.street,
          city: copyCity ? _cloneMaster.addr.city : originalAddress.city,
          state: copyCity ? _cloneMaster.addr.state : originalAddress.state,
          country: copyCity ? _cloneMaster.addr.country : originalAddress.country,
          houseNumber: copyHn ? _cloneMaster.addr.houseNumber : originalAddress.houseNumber,
        };
        updateAddress(venue, newAddress);
        logDev('Venue address cloned');
      }
    } else {
      log('Please copy a place');
    }
  }

  /**
   * Converts an opening hours object into a human-readable string (e.g., "Monday 09:00-17:00").
   * @param {Object} hourEntry WME opening hours entry with fromHour, toHour, and days array.
   * @returns {string} Formatted hours string with day names and times.
   */
  function formatOpeningHour(hourEntry) {
    const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    const hours = `${hourEntry.fromHour}-${hourEntry.toHour}`;
    return hourEntry.days.map((day) => `${dayNames[day]} ${hours}`).join(', ');
  }

  /**
   * Converts a venue's opening hours into an array of human-readable strings.
   * Returns null if venue or openingHours are missing.
   * @param {Object} venue The WME venue object.
   * @returns {Array<string>|null} Array of formatted hour strings, or null if hours unavailable.
   */
  function getOpeningHours(venue) {
    return venue && venue.openingHours && venue.openingHours.map(formatOpeningHour);
  }

  /**
   * Detects if a venue has overlapping opening hours on any day of the week.
   * Compares all hour ranges for each day and returns true if any overlap is found.
   * @param {Array<Object>} openingHours Array of WME opening hours objects.
   * @returns {boolean} True if any overlapping hours detected, false otherwise.
   */
  function venueHasOverlappingHours(openingHours) {
    if (openingHours.length < 2) {
      return false;
    }

    for (let day2Ch = 0; day2Ch < 7; day2Ch++) {
      // Go thru each day of the week
      const daysObj = [];
      for (let hourSet = 0; hourSet < openingHours.length; hourSet++) {
        // For each set of hours
        if (openingHours[hourSet].days.includes(day2Ch)) {
          // pull out hours that are for the current day, add 2400 if it goes past midnight, and store
          const fromHourTemp = openingHours[hourSet].fromHour.replace(/:/g, '');
          let toHourTemp = openingHours[hourSet].toHour.replace(/:/g, '');
          if (toHourTemp <= fromHourTemp) {
            toHourTemp = parseInt(toHourTemp, 10) + 2400;
          }
          daysObj.push([fromHourTemp, toHourTemp]);
        }
      }
      if (daysObj.length > 1) {
        // If there's multiple hours for the day, check them for overlap
        for (let hourSetCheck2 = 1; hourSetCheck2 < daysObj.length; hourSetCheck2++) {
          for (let hourSetCheck1 = 0; hourSetCheck1 < hourSetCheck2; hourSetCheck1++) {
            if (daysObj[hourSetCheck2][0] > daysObj[hourSetCheck1][0] && daysObj[hourSetCheck2][0] < daysObj[hourSetCheck1][1]) {
              return true;
            }
            if (daysObj[hourSetCheck2][1] > daysObj[hourSetCheck1][0] && daysObj[hourSetCheck2][1] < daysObj[hourSetCheck1][1]) {
              return true;
            }
          }
        }
      }
    }
    return false;
  }

  /**
   * Finds nearby duplicate venues by name/alias similarity and physical proximity.
   * Searches within 800m radius and performs name matching with variations (letters only, no numbers).
   * Returns array of duplicate venue names and a flag indicating overlapping geometry.
   * Populates _dupeIDList and draws map labels for all found duplicates.
   * @param {string} selectedVenueName The primary name of the venue to search duplicates for.
   * @param {Array<string>} selectedVenueAliases Alternative names for the venue.
   * @param {Object} selectedVenue The WME venue object with geometry and metadata.
   * @returns {Array} [duplicateNameArray, overlappingFlag] - duplicate names and overlap indicator.
   */
  function findNearbyDuplicate(selectedVenueName, selectedVenueAliases, selectedVenue) {
    const formatName = (name) =>
      name
        .toUpperCase()
        .replace(/ AND /g, '')
        .replace(/^THE /g, '')
        .replace(/[^A-Z0-9]/g, '');

    const allowedTwoLetters = ['BP', 'DQ', 'BK', 'BW', 'LQ', 'QT', 'DB', 'PO'];

    let overlappingFlag = false;

    const selectedCentroid = getVenueCentroid(selectedVenue);
    if (!selectedCentroid) {
      logDev('findNearbyDuplicate: Unable to get selected venue centroid');
      return [[], false];
    }

    let minLon = selectedCentroid[0];
    let minLat = selectedCentroid[1];
    let maxLon = minLon;
    let maxLat = minLat;

    const dupeNames = [];

    const selectedVenueNameRF = formatName(selectedVenueName);
    let currNameList = [];
    if (selectedVenueNameRF.length > 2 || allowedTwoLetters.includes(selectedVenueNameRF)) {
      currNameList.push(selectedVenueNameRF);
    } else {
      currNameList.push('PRIMNAMETOOSHORT_PJZWX');
    }

    const venueNameNoNum = selectedVenueNameRF.replace(/[^A-Z]/g, '');
    if (((venueNameNoNum.length > 2 && !NO_NUM_SKIP.includes(venueNameNoNum)) || allowedTwoLetters.includes(venueNameNoNum)) && !selectedVenue.categories?.includes('PARKING_LOT')) {
      currNameList.push(venueNameNoNum);
    }

    if (selectedVenueAliases.length > 0) {
      for (let aliix = 0; aliix < selectedVenueAliases.length; aliix++) {
        const aliasNameRF = formatName(selectedVenueAliases[aliix]);
        if ((aliasNameRF.length > 2 && !NO_NUM_SKIP.includes(aliasNameRF)) || allowedTwoLetters.includes(aliasNameRF)) {
          currNameList.push(aliasNameRF);
        }
        const aliasNameNoNum = aliasNameRF.replace(/[^A-Z]/g, '');
        if (((aliasNameNoNum.length > 2 && !NO_NUM_SKIP.includes(aliasNameNoNum)) || allowedTwoLetters.includes(aliasNameNoNum)) && !selectedVenue.categories?.includes('PARKING_LOT')) {
          currNameList.push(aliasNameNoNum);
        }
      }
    }
    currNameList = uniq(currNameList);

    let selectedVenueAddr = getVenueAddress(selectedVenue);
    const selectedVenueHN = selectedVenue.houseNumber;

    const selectedVenueAddrIsComplete = selectedVenueAddr?.street && selectedVenueAddr.street.name && selectedVenueHN && selectedVenueHN.match(/\d/g) !== null;

    const venues = sdk.DataModel.Venues.getAll();
    const selectedVenueId = selectedVenue.id;

    _dupeIDList = [selectedVenueId];
    _dupeHNRangeList = [];
    _dupeHNRangeDistList = [];

    const selectedVenueWL = _venueWhitelist[selectedVenueId];
    const whitelistedDupes = selectedVenueWL && selectedVenueWL.dupeWL ? selectedVenueWL.dupeWL : [];

    const excludePLADupes = $('#WMEPH-ExcludePLADupes').prop('checked');
    let randInt = 100;

    venues.forEach((testVenue) => {
      if ((!excludePLADupes || (excludePLADupes && !(isVenueParkingLot(selectedVenue) || isVenueParkingLot(testVenue)))) && !isEmergencyRoom(testVenue)) {
        const testVenueId = testVenue.id;

        const testCentroid = getVenueCentroid(testVenue);
        if (!testCentroid) return;

        const pt2ptDistance = calculatePointDistance(selectedCentroid, testCentroid);
        if (isVenuePoint(selectedVenue) && isVenuePoint(testVenue) && pt2ptDistance < 2 && selectedVenueId !== testVenueId) {
          overlappingFlag = true;
        }

        const testVenueHN = testVenue.houseNumber;
        let testVenueAddr = getVenueAddress(testVenue);

        if (
          selectedVenueAddrIsComplete &&
          testVenueAddr?.street &&
          testVenueAddr.street.name &&
          testVenueHN &&
          testVenueHN !== '' &&
          testVenueId !== selectedVenueId &&
          selectedVenueAddr.street.name === testVenueAddr.street.name &&
          testVenueHN < 1000000
        ) {
          _dupeHNRangeList.push(parseInt(testVenueHN, 10));
          _dupeHNRangeDistList.push(pt2ptDistance);
        }

        if (
          !whitelistedDupes.includes(testVenueId) &&
          _dupeIDList.length < 6 &&
          pt2ptDistance < 800 &&
          !isVenueResidential(testVenue) &&
          testVenueId !== selectedVenueId &&
          !testVenue.isNew &&
          testVenue.name &&
          testVenue.name.length > 1
        ) {
          let suppressMatch = false;
          if (selectedVenueAddrIsComplete && testVenueAddr?.street && testVenueAddr.street.name && testVenueHN && testVenueHN.match(/\d/g) !== null) {
            if (selectedVenue.lockRank > 0 && testVenue.lockRank > 0) {
              if (selectedVenue.houseNumber !== testVenueHN || selectedVenueAddr.street.name !== testVenueAddr.street.name) {
                suppressMatch = true;
              }
            } else if (selectedVenueHN !== testVenueHN && selectedVenueAddr.street.name !== testVenueAddr.street.name) {
              suppressMatch = true;
            }
          }

          if (!suppressMatch) {
            let testNameList;
            const strippedTestName = formatName(testVenue.name).replace(/\s+[-(].*$/, '');
            if ((strippedTestName.length > 2 && !NO_NUM_SKIP.includes(strippedTestName)) || allowedTwoLetters.includes(strippedTestName)) {
              testNameList = [strippedTestName];
            } else {
              testNameList = [`TESTNAMETOOSHORTQZJXS${randInt}`];
              randInt++;
            }

            const testNameNoNum = strippedTestName.replace(/[^A-Z]/g, '');
            if (((testNameNoNum.length > 2 && !NO_NUM_SKIP.includes(testNameNoNum)) || allowedTwoLetters.includes(testNameNoNum)) && !testVenue.categories?.includes('PARKING_LOT')) {
              testNameList.push(testNameNoNum);
            }

            let nameMatch = false;
            for (let tnlix = 0; tnlix < testNameList.length; tnlix++) {
              for (let cnlix = 0; cnlix < currNameList.length; cnlix++) {
                if (testNameList[tnlix].includes(currNameList[cnlix]) || currNameList[cnlix].includes(testNameList[tnlix])) {
                  nameMatch = true;
                  break;
                }
              }
              if (nameMatch) break;
            }

            let altNameMatch = -1;
            if (!nameMatch && testVenue.aliases?.length > 0) {
              for (let aliix = 0; aliix < testVenue.aliases.length; aliix++) {
                const aliasNameRF = formatName(testVenue.aliases[aliix]);
                if ((aliasNameRF.length > 2 && !NO_NUM_SKIP.includes(aliasNameRF)) || allowedTwoLetters.includes(aliasNameRF)) {
                  testNameList = [aliasNameRF];
                } else {
                  testNameList = [`ALIASNAMETOOSHORTQOFUH${randInt}`];
                  randInt++;
                }
                const aliasNameNoNum = aliasNameRF.replace(/[^A-Z]/g, '');
                if (((aliasNameNoNum.length > 2 && !NO_NUM_SKIP.includes(aliasNameNoNum)) || allowedTwoLetters.includes(aliasNameNoNum)) && !testVenue.categories?.includes('PARKING_LOT')) {
                  testNameList.push(aliasNameNoNum);
                } else {
                  testNameList.push(`111231643239${randInt}`);
                  randInt++;
                }
              }
              for (let tnlix = 0; tnlix < testNameList.length; tnlix++) {
                for (let cnlix = 0; cnlix < currNameList.length; cnlix++) {
                  if (testNameList[tnlix].includes(currNameList[cnlix]) || currNameList[cnlix].includes(testNameList[tnlix])) {
                    altNameMatch = Math.floor(tnlix / 2);
                    break;
                  }
                }
                if (altNameMatch > -1) break;
              }
            }

            if (nameMatch || altNameMatch > -1) {
              _dupeIDList.push(testVenue.id);
              const labelText = nameMatch ? testVenue.name : `${testVenue.aliases[altNameMatch]} (Alt)`;
              logDev(`Possible duplicate found. WME place: ${selectedVenueName} / Nearby place: ${labelText}`);

              dupeNames.push(labelText);

              // Add Point feature to dupe labels layer
              try {
                const dupeFeature = {
                  id: `dupe_${testVenue.id}`,
                  type: 'Feature',
                  geometry: { type: 'Point', coordinates: testCentroid },
                  properties: {
                    label: labelText || 'Unknown',
                    venueId: testVenue.id,
                    dupeType: nameMatch ? 'name' : 'alias',
                  },
                };
                logDev(`Adding dupe feature: ${labelText} at [${testCentroid}]`);
                sdk.Map.addFeatureToLayer({
                  layerName: _dupeLayer,
                  feature: dupeFeature,
                });
              } catch (e) {
                logDev('Error adding dupe feature to layer:', e);
              }
            }
          }
        }
      }
    });

    if (_dupeIDList.length > 1) {
      // Add Point feature for the selected venue (primary place)
      try {
        const selectedFeature = {
          id: `dupe_primary_${selectedVenueId}`,
          type: 'Feature',
          geometry: { type: 'Point', coordinates: selectedCentroid },
          properties: {
            label: selectedVenueName || 'Primary',
            venueId: selectedVenueId,
            dupeType: 'primary',
          },
        };
        logDev(`Adding primary feature: ${selectedVenueName} at [${selectedCentroid}]`);
        sdk.Map.addFeatureToLayer({
          layerName: _dupeLayer,
          feature: selectedFeature,
        });
      } catch (e) {
        logDev('Error adding selected venue feature to layer:', e);
      }
    }

    return [dupeNames, overlappingFlag];
  } // END findNearbyDuplicate function

  /**
   * Infers a venue's address from nearby road segments using node-based search algorithm.
   * Algorithm:
   * 1. Find closest node to venue (point-to-point distance via Turf.js)
   * 2. Build segment index for O(1) node->segments lookups
   * 3. Search recursively from closest node outward, collecting all named streets at each depth
   * 4. Rank candidates by distance first (closest within 10m tolerance), then by road type priority
   * 5. Road type priority: PRIVATE_ROAD > STREET > PRIMARY_STREET > MAJOR_HIGHWAY > MINOR_HIGHWAY > ALLEY
   * 6. Excludes: freeways, ramps, walkways, railroads, parking lot roads, and other non-drivable types
   * @param {Object} venue The WME venue object with navigationPoints or geometry/centroid.
   * @param {number} maxRecursionDepth Maximum connectivity depth to search through segments.
   * @returns {Object|null} Address object with country, city, state, street fields, or null if unavailable.
   */
  function inferAddress(venue, maxRecursionDepth) {
    // Excluded road types (non-addressable/non-drivable): freeways, ramps, walkways, railroads,
    // pedestrian paths, ferries, and other non-street segments.
    // NOTE: Parking lot roads (18) are traversable for connectivity but won't be selected as final addresses.
    const IGNORE_ROAD_TYPES = [3, 4, 5, 8, 9, 10, 15, 16, 19, 20];
    let inferredAddress = {
      country: null,
      city: null,
      state: null,
      street: null,
    };

    // Get segments and nodes from SDK
    let segments, nodes;
    try {
      segments = sdk.DataModel.Segments.getAll() || [];
    } catch (e) {
      logDev('inferAddress: Unable to access SDK data', e);
      segments = [];
    }

    let stopPoint;

    // Make sure a place is selected and data is loaded.
    if (!(venue && segments.length)) {
      logDev('inferAddress: No venue or segment data available');
      return undefined;
    }

    // Check if segment has a named street (vs unnamed segment like connector roads).
    const hasStreetName = (segment) => {
      if (!segment) return false;
      const addr = getSegmentAddress(segment);
      return addr && !addr.isEmpty && addr.street?.name;
    };


    // Get venue's starting point: prefer primary navigation point, else first, else centroid.
    const { navigationPoints } = venue;
    if (navigationPoints?.length) {
      const primaryPoint = navigationPoints.find((pt) => pt.isPrimary === true) || navigationPoints[0];
      stopPoint = primaryPoint.point.coordinates;
      logDev('inferAddress: Using navigation point:', stopPoint);
    } else {
      const centroid = getVenueCentroid(venue);
      if (!centroid) {
        logDev('inferAddress: Unable to get venue centroid');
        return null;
      }
      stopPoint = centroid;
      logDev('inferAddress: Using centroid:', stopPoint);
    }

    // Scan all nearby segments and rank by distance to venue.
    // This avoids the node-connectivity problem and directly finds the closest named street.
    const ptCoords = [stopPoint.longitude || stopPoint[0], stopPoint.latitude || stopPoint[1]];
    const segmentsByDistance = [];

    logDev('inferAddress: Scanning', segments.length, 'segments for closest named street, venue coords:', ptCoords);

    for (const seg of segments) {
      if (!IGNORE_ROAD_TYPES.includes(seg.roadType)) {
        const dist = turf.pointToLineDistance(
          turf.point(stopPoint),
          turf.lineString(seg.geometry.coordinates),
          { units: 'meters' }
        );
        segmentsByDistance.push({
          segment: seg,
          distance: dist,
        });
      }
    }

    // Sort by distance to find closest segments first
    segmentsByDistance.sort((a, b) => a.distance - b.distance);

    logDev('inferAddress: Scanned', segmentsByDistance.length, 'valid segments. Top 3 by distance:');
    segmentsByDistance.slice(0, 3).forEach((item, idx) => {
      const addr = getSegmentAddress(item.segment);
      logDev(`  ${idx + 1}. segment:${item.segment.id} street:"${addr.street?.name || '(unnamed)'}" dist:${item.distance.toFixed(1)}m type:${item.segment.roadType}`);
    });

    // Find first named street within a reasonable search range
    const searchRadiusLimit = 1000; // meters
    for (const { segment, distance } of segmentsByDistance) {
      if (distance > searchRadiusLimit) {
        logDev('inferAddress: Reached search radius limit (' + searchRadiusLimit + 'm) without finding named street');
        break;
      }
      if (hasStreetName(segment)) {
        const addr = getSegmentAddress(segment);
        logDev('inferAddress: FOUND - segment:', segment.id, 'street:', addr.street?.name, 'roadType:', segment.roadType, 'distance:', distance.toFixed(1) + 'm');
        inferredAddress = addr;
        break;
      }
    }

    if (!inferredAddress.street?.name) {
      logDev('inferAddress: No named street found within', searchRadiusLimit + 'm');
    }

    return inferredAddress;
  } // END inferAddress function

  /**
   * Updates a venue's address using the WME SDK DataModel.
   * Supports updating country, state, city, street name, and house number.
   * @param {Object} feature The WME venue object to update (must have id property).
   * @param {Object} address Object containing country, state, city, street, houseNumber properties (IDs/names as required by SDK).
   */
  function updateAddress(feature, address) {
    logDev('updateAddress: Called with:', {
      venueId: feature?.id,
      street: address?.street?.name,
      streetId: address?.street?.id,
      city: address?.city?.name,
      cityId: address?.city?.id,
      state: address?.state,
      country: address?.country,
      houseNumber: address?.houseNumber
    });

    if (!feature || !address || !address.street?.id) {
      logDev('updateAddress: Validation failed - missing feature, address, or street ID');
      return;
    }

    try {
      const updatePayload = {
        venueId: feature.id,
        streetId: address.street.id,
        houseNumber: address.houseNumber,
      };
      logDev('updateAddress: Sending update payload:', updatePayload);
      sdk.DataModel.Venues.updateAddress(updatePayload);
      logDev('updateAddress: Address inferred and updated successfully');
    } catch (e) {
      logDev('updateAddress error:', e.message, e.stack);
    }
  }

  /**
   * Constructs a Google search URL for a venue based on name, address, and house number.
   * Formats street/state abbreviations (CR- → County Rd, SR- → State Hwy, etc.) for better search results.
   * @param {string} searchName The venue name to search for.
   * @param {Object} addr The venue's address object with street, city, state properties.
   * @param {string} HN The house number (optional, included if street name exists).
   * @returns {string} A fully-formed Google search URL with encoded parameters.
   */
  function buildGLink(searchName, addr, HN) {
    if (!addr) return null;
    let searchHN = '';
    let searchStreet = '';
    let searchCity = '';
    searchName = searchName.replace(/\//g, ' ');

    // Handle SDK SegmentAddress objects (property-based) vs legacy address objects (method-based)
    const hasStreetName = addr.street?.name && !addr.street.isEmpty;
    if (hasStreetName) {
      searchStreet = `${addr.street.name}, `
        .replace(/CR-/g, 'County Rd ')
        .replace(/SR-/g, 'State Hwy ')
        .replace(/US-/g, 'US Hwy ')
        .replace(/ CR /g, ' County Rd ')
        .replace(/ SR /g, ' State Hwy ')
        .replace(/ US /g, ' US Hwy ')
        .replace(/$CR /g, 'County Rd ')
        .replace(/$SR /g, 'State Hwy ')
        .replace(/$US /g, 'US Hwy ');
      if (HN && searchStreet !== '') {
        searchHN = `${HN} `;
      }
    }

    const city = addr.city;
    if (city?.name) {
      searchCity = `${city.name}, `;
    }

    const stateName = addr.state?.name || '';
    searchName = searchName + (searchName ? ', ' : '') + searchHN + searchStreet + searchCity + stateName;
    return `http://www.google.com/search?q=${encodeURIComponent(searchName)}`;
  }

  /**
   * Compares two arrays for equality regardless of element order.
   * Arrays must have same length and contain same elements (in any order).
   * @param {Array} array1 First array to compare.
   * @param {Array} array2 Second array to compare.
   * @returns {boolean} True if arrays contain same elements, false otherwise.
   */
  function matchSets(array1, array2) {
    if (array1.length !== array2.length) {
      return false;
    } // compare lengths
    for (let i = 0; i < array1.length; i++) {
      if (!array2.includes(array1[i])) {
        return false;
      }
    }
    return true;
  }

  /**
   * Checks if all elements from target array exist in source array.
   * Converts single strings to arrays automatically. Returns false if target is null/undefined.
   * @param {Array|string} source The source array to check against.
   * @param {Array|string} target The target item(s) to check for in source.
   * @returns {boolean} True if all target elements exist in source.
   */
  function containsAll(source, target) {
    if (typeof target === 'undefined' || target === null) return false;
    if (typeof target === 'string') {
      target = [target];
    } // if a single string, convert to an array
    for (let ixx = 0; ixx < target.length; ixx++) {
      if (!source.includes(target[ixx])) {
        return false;
      }
    }
    return true;
  }

  /**
   * Checks if any element from target array exists in source array.
   * Converts single strings to arrays automatically.
   * @param {Array|string} source The source array to check against.
   * @param {Array|string} target The target item(s) to check for in source.
   * @returns {boolean} True if any target element exists in source.
   */
  function containsAny(source, target) {
    if (typeof source === 'string') {
      source = [source];
    } // if a single string, convert to an array
    if (typeof target === 'string') {
      target = [target];
    } // if a single string, convert to an array
    return source.some((item) => target.includes(item));
  }

  /**
   * Creates a new array with items inserted at a specified index and duplicates removed.
   * Original array is not modified. Can be used to reposition items by removing then inserting.
   * @param {Array} sourceArray Original array (not modified).
   * @param {*|Array} toInsert Item or array of items to insert.
   * @param {number} atIndex The index position to insert at.
   * @returns {Array} New array with inserted item(s) and duplicates removed.
   */
  function insertAtIndex(sourceArray, toInsert, atIndex) {
    const sourceCopy = sourceArray.slice();
    if (!Array.isArray(toInsert)) toInsert = [toInsert];
    sourceCopy.splice(atIndex, 0, ...toInsert);
    return uniq(sourceCopy);
  }

  /**
   * Checks if two arrays are equal by comparing length and all elements at each index.
   * @param {Array} array1 First array to compare.
   * @param {Array} array2 Second array to compare.
   * @returns {boolean} True if arrays have same length and matching elements at all indices.
   */
  function arraysAreEqual(array1, array2) {
    return array1.legth === array2.length && array1.every((item, index) => item === array2[index]);
  }

  /**
   * Removes aliases that are substrings or prefixes of the main venue name.
   * Useful for eliminating redundant aliases. Returns null if no aliases are removed.
   * @param {string} venueName The primary venue name to compare against.
   * @param {Array<string>} aliases Array of alias names to filter.
   * @returns {Array<string>|null} Filtered aliases array, or null if no changes made.
   */
  function removeUnnecessaryAliases(venueName, aliases) {
    if (!venueName || !aliases?.length) return null;
    const newAliases = [];
    let aliasesRemoved = false;
    venueName = String(venueName).replace(/['=\\/]/i, '');
    venueName = venueName
      .toUpperCase()
      .replace(/'/g, '')
      .replace(/(-|\/ | \/| {2,})/g, ' ');
    for (let naix = 0; naix < aliases.length; naix++) {
      if (
        !venueName.startsWith(
          String(aliases[naix])
            .toUpperCase()
            .replace(/'/g, '')
            .replace(/(-|\/ | \/| {2,})/g, ' '),
        )
      ) {
        newAliases.push(aliases[naix]);
      } else {
        aliasesRemoved = true;
      }
    }
    return aliasesRemoved ? newAliases : null;
  }

  /**
   * String formatter for phone numbers using numbered placeholder syntax.
   * Replaces {0}, {1}, etc. in format string with corresponding arguments from rest array.
   * Returns null for undefined arguments, allowing flexible partial formatting.
   * @param {string} format String containing {0}, {1}, {2} placeholders for substitution.
   * @param {...*} rest Arguments to substitute into the format string by index.
   * @returns {string} Formatted string with placeholders replaced.
   */
  function phoneFormat(format, ...rest) {
    return format.replace(/{(\d+)}/g, (name, number) => (typeof rest[number] !== 'undefined' ? rest[number] : null));
  }

  /**
   * Loads a checkbox's persisted state and attaches a click handler to save changes.
   * Loads state BEFORE attaching handler to avoid spurious saves on initialization.
   * Skips handler attachment for checkboxes with custom handlers (PLATypeFill, ShowFilterHighlight).
   * @param {string} settingID The HTML id of the checkbox element (also used as the settings key).
   */
  function initSettingsCheckbox(settingID) {
    const $checkbox = $(`#${settingID}`);

    // Load Setting from WMEPH settings first (before attaching click handler to avoid triggering save)
    if (getWMEPHSetting(settingID) === '1') {
      $checkbox.prop('checked', true);
    }

    // Attach click handler AFTER loading state so initial prop() doesn't trigger a save
    // Skip for checkboxes that have custom handlers (they'll save and update themselves)
    if (settingID !== 'WMEPH-PLATypeFill' && settingID !== 'WMEPH-ShowFilterHighlight') {
      $checkbox.click(() => {
        saveSettingToLocalStorage(settingID);
      });
    }
  }

  /**
   * Creates a checkbox element with associated label and appends to a parent container.
   * Returns the checkbox jQuery object for further event binding or state management.
   * Used to build the settings UI in the PlaceHarmonizer tab.
   * @param {jQuery} $div Parent container to append the checkbox and label to.
   * @param {string} settingID The HTML id for the checkbox element (used as settings key).
   * @param {string} textDescription The label text displayed next to the checkbox.
   * @returns {jQuery} The created checkbox element (useful for attaching additional handlers).
   */
  function createSettingsCheckbox($div, settingID, textDescription) {
    const $checkbox = $('<input>', { type: 'checkbox', id: settingID });
    $div.append(
      $('<div>', { class: 'controls-container' })
        .css({ paddingTop: '2px' })
        .append($checkbox, $('<label>', { for: settingID }).text(textDescription).css({ whiteSpace: 'pre-line' })),
    );
    return $checkbox;
  }

  /**
   * Handles modifier key toggle (Ctrl vs Alt) for the harmonize keyboard shortcut.
   * Unregisters the old shortcut, updates _modifKey, re-registers with new modifier, and updates UI display.
   * Also updates the SDK keyboard shortcut registry to stay in sync.
   * Called when user clicks the modifier key checkbox in settings.
   */
  function onKBShortcutModifierKeyClick() {
    const $modifKeyCheckbox = $('#WMEPH-KBSModifierKey');
    const $shortcutInput = $('#WMEPH-KeyboardShortcut');
    const $warn = $('#PlaceHarmonizerKBWarn');
    const modifKeyNew = $modifKeyCheckbox.prop('checked') ? 'Ctrl+' : 'Alt+';

    _shortcutParse = parseKBSShift($shortcutInput.val());
    $warn.empty(); // remove any warning
    SHORTCUT.remove(_modifKey + _shortcutParse);
    _modifKey = modifKeyNew;
    SHORTCUT.add(_modifKey + _shortcutParse, harmonizePlace);
    $('#PlaceHarmonizerKBCurrent').empty().append(`<span style="font-weight:bold">Current shortcut: ${_modifKey}${_shortcutParse}</span>`);

    // Update SDK shortcut to match
    const newKey = loadHarmonizeShortcut();
    if (newKey) {
      registerShortcut('wmeph_harmonize_place', 'WMEPH: Harmonize selected place', newKey, () => {
        harmonizePlace();
      });
    }
  }

  /**
   * Handles keyboard shortcut key changes in the settings input field.
   * Validates that input is a single letter; if invalid, reverts to old value and shows error.
   * If valid, unregisters old shortcut, updates _shortcutParse, re-registers with new key, and updates SDK.
   * Called when user changes the keyboard shortcut key in settings.
   */
  function onKBShortcutChange() {
    const keyId = 'WMEPH-KeyboardShortcut';
    const $warn = $('#PlaceHarmonizerKBWarn');
    const $key = $(`#${keyId}`);
    const oldKey = getWMEPHSetting(keyId);
    const newKey = $key.val();

    $warn.empty(); // remove old warning
    if (newKey.match(/^[a-z]{1}$/i) !== null) {
      // If a single letter...
      _shortcutParse = parseKBSShift(oldKey);
      const shortcutParseNew = parseKBSShift(newKey);
      SHORTCUT.remove(_modifKey + _shortcutParse);
      _shortcutParse = shortcutParseNew;
      SHORTCUT.add(_modifKey + _shortcutParse, harmonizePlace);
      setWMEPHSetting(keyId, newKey);
      $('#PlaceHarmonizerKBCurrent').empty().append(`<span style="font-weight:bold">Current shortcut: ${_modifKey}${_shortcutParse}</span>`);

      // Update SDK shortcut to match
      const newSdkKey = loadHarmonizeShortcut();
      if (newSdkKey) {
        registerShortcut('wmeph_harmonize_place', 'WMEPH: Harmonize selected place', newSdkKey, () => {
          harmonizePlace();
        });
      }
    } else {
      // if not a letter then reset and flag
      $key.val(oldKey);
      $warn.append('<p style="color:red">Only letters are allowed<p>');
    }
  }

  /**
   * Sets a checkbox setting to checked ('1') if it has never been set before.
   * Used to initialize default checked state for various feature toggles.
   * @param {string} id The setting ID to initialize with default checked state.
   */
  function setCheckedByDefault(id) {
    if (getWMEPHSetting(id) === null) {
      setWMEPHSetting(id, '1');
    }
  }

  /**
   * Initializes the keyboard shortcut system from persisted settings.
   * Loads shortcut key and modifier from storage (with defaults: 'S' for beta, 'A' for prod).
   * Registers the shortcut globally if not already initialized, and attaches event handlers for future changes.
   * Called during script initialization to set up keyboard shortcut functionality.
   */
  function initShortcutKey() {
    const $current = $('#PlaceHarmonizerKBCurrent');
    const defaultShortcutKey = IS_BETA_VERSION ? 'S' : 'A';
    const shortcutID = 'WMEPH-KeyboardShortcut';
    let shortcutKey = getWMEPHSetting(shortcutID);
    const $shortcutInput = $(`#${shortcutID}`);

    // Set settings to default if none
    if (shortcutKey === null || !/^[a-z]{1}$/i.test(shortcutKey)) {
      setWMEPHSetting(shortcutID, defaultShortcutKey);
      shortcutKey = defaultShortcutKey;
    }
    $shortcutInput.val(shortcutKey);

    if (getWMEPHSetting('WMEPH-KBSModifierKey') === '1') {
      // Change modifier key code if checked
      _modifKey = 'Ctrl+';
    }
    _shortcutParse = parseKBSShift(shortcutKey);
    if (!_initAlreadyRun) SHORTCUT.add(_modifKey + _shortcutParse, harmonizePlace);
    $current.empty().append(`<span style="font-weight:bold">Current shortcut: ${_modifKey}${_shortcutParse}</span>`);

    $('#WMEPH-KBSModifierKey').click(onKBShortcutModifierKeyClick);

    // Upon change of the KB letter:
    $shortcutInput.change(onKBShortcutChange);
  }

  /**
   * Handles whitelist merge/reset operations from the WL Tools tab.
   * If input is 'resetWhitelist', prompts user for confirmation before wiping all WL data.
   * Otherwise, attempts to merge pasted whitelist data (tries uncompressed first, then compressed with LZString).
   * Shows colored feedback messages indicating success or error.
   */
  function onWLMergeClick() {
    const $wlToolsMsg = $('#PlaceHarmonizerWLToolsMsg');
    const $wlInput = $('#WMEPH-WLInput');

    $wlToolsMsg.empty();
    if ($wlInput.val() === 'resetWhitelist') {
      /* if (confirm('***Do you want to reset all Whitelist data?\nClick OK to erase.')) {
                // if the category doesn't translate, then pop an alert that will make a forum post to the thread
                _venueWhitelist = { '1.1.1': { Placeholder: {} } }; // Populate with a dummy place
                saveWhitelistToLS(true);
            } */
      WazeWrap.Alerts.confirm(
        // if the category doesn't translate, then pop an alert that will make a forum post to the thread
        SCRIPT_NAME,
        '***Do you want to reset all Whitelist data?<br>Click OK to erase.',
        () => {
          _venueWhitelist = { '1.1.1': { Placeholder: {} } }; // Populate with a dummy place
          saveWhitelistToLS(true);
        },
        () => {},
      );
    } else {
      // try to merge uncompressed WL data
      let wlStringToMerge = validateWLS($('#WMEPH-WLInput').val());
      if (wlStringToMerge) {
        log('Whitelists merged!');
        _venueWhitelist = mergeWL(_venueWhitelist, wlStringToMerge);
        saveWhitelistToLS(true);
        $wlToolsMsg.append('<p style="color:green">Whitelist data merged<p>');
        $wlInput.val('');
      } else {
        // try compressed WL
        wlStringToMerge = validateWLS(LZString.decompressFromUTF16($('#WMEPH-WLInput').val()));
        if (wlStringToMerge) {
          log('Whitelists merged!');
          _venueWhitelist = mergeWL(_venueWhitelist, wlStringToMerge);
          saveWhitelistToLS(true);
          $wlToolsMsg.append('<p style="color:green">Whitelist data merged<p>');
          $wlInput.val('');
        } else {
          $wlToolsMsg.append('<p style="color:red">Invalid Whitelist data<p>');
        }
      }
    }
  }

  /**
   * Extracts the current whitelist from localStorage (tries compressed first, falls back to uncompressed).
   * Used for backing up or sharing whitelist data. Shows instructions to copy/paste data to safe location.
   * Resets the add-count reminder to show it once per session.
   */
  function onWLPullClick() {
    let wlToPull = '';
    const compressedWL = localStorage.getItem(WL_LOCAL_STORE_NAME_COMPRESSED);
    const uncompressedWL = localStorage.getItem(WL_LOCAL_STORE_NAME);

    if (compressedWL) {
      try {
        wlToPull = LZString.decompressFromUTF16(compressedWL);
        if (!wlToPull || wlToPull.length === 0) {
          throw new Error('Decompressed data is empty');
        }
      } catch (e) {
        logDev('Error decompressing WL for pull:', e.message);
        if (uncompressedWL) {
          wlToPull = uncompressedWL;
          logDev('Using uncompressed backup for pull');
        }
      }
    } else if (uncompressedWL) {
      wlToPull = uncompressedWL;
      logDev('Using uncompressed WL for pull (no compressed version found)');
    }

    if (wlToPull) {
      $('#WMEPH-WLInput').val(wlToPull);
      $('#PlaceHarmonizerWLToolsMsg').empty().append('<p style="color:green">To backup the data, copy & paste the text in the box to a safe location.<p>');
    } else {
      $('#PlaceHarmonizerWLToolsMsg').empty().append('<p style="color:red">Error: No whitelist data found to pull<p>');
    }
    setWMEPHSetting('WMEPH_WLAddCount', 1);
  }

  /**
   * Displays whitelist statistics showing count of whitelisted venues by state and country.
   * Decompresses the stored whitelist and generates summary tables of regional data.
   * Excludes the placeholder entry (1.1.1) from counts.
   */
  function onWLStatsClick() {
    let currWLData;
    try {
      const compressedWL = localStorage.getItem(WL_LOCAL_STORE_NAME_COMPRESSED);
      const uncompressedWL = localStorage.getItem(WL_LOCAL_STORE_NAME);

      if (compressedWL) {
        const decompressed = LZString.decompressFromUTF16(compressedWL);
        currWLData = JSON.parse(decompressed);
      } else if (uncompressedWL) {
        logDev('Using uncompressed WL for stats (no compressed version found)');
        currWLData = JSON.parse(uncompressedWL);
      } else {
        throw new Error('No whitelist data found');
      }
    } catch (e) {
      logDev('Error loading whitelist for stats:', e.message);
      $('#PlaceHarmonizerWLToolsMsg').empty().append(`<p style="color:red">Error: Could not load whitelist - ${e.message}<p>`);
      return;
    }
    const countryWL = {};
    const stateWL = {};
    const entries = Object.keys(currWLData).filter((key) => key !== '1.1.1');

    $('#WMEPH-WLInputBeta').val('');
    entries.forEach((venueKey) => {
      const country = currWLData[venueKey].country || 'None';
      const state = currWLData[venueKey].state || 'None';
      countryWL[country] = countryWL[country] + 1 || 1;
      stateWL[state] = stateWL[state] + 1 || 1;
    });

    const getSectionDiv = (title, list) =>
      $('<div>', { style: 'margin-bottom: 10px;' }).append(
        $('<div>', { style: 'font-weight: bold; text-decoration: underline' }).text(title),
        Object.keys(list).map((key) => $('<div>').text(`${key}: ${list[key]}`)),
      );

    $('#PlaceHarmonizerWLToolsMsg')
      .empty()
      .append($('<div>', { style: 'margin-bottom: 10px;' }).text(`Number of WL places: ${entries.length}`), getSectionDiv('States', stateWL), getSectionDiv('Countries', countryWL));
  }

  /**
   * Removes all whitelisted venues for a specified state from the user's whitelist.
   * Requires user confirmation before deletion. Input state name must match exactly as shown in Stats.
   * First creates a backup before deletion, and shows count of venues removed or error messages.
   */
  function onWLStateFilterClick() {
    const $wlInput = $('#WMEPH-WLInput');
    const stateToRemove = $wlInput.val().trim();
    let msgColor;
    let msgText;

    if (stateToRemove.length < 2) {
      msgColor = 'red';
      msgText = 'Invalid state. Enter the state name in the "Whitelist string" box above, ' + 'exactly as it appears in the Stats output.';
    } else {
      const currWLData = JSON.parse(LZString.decompressFromUTF16(localStorage.getItem(WL_LOCAL_STORE_NAME_COMPRESSED)));
      const venuesToRemove = Object.keys(currWLData).filter(
        (venueKey) => venueKey !== '1.1.1' && (currWLData[venueKey].state === stateToRemove || (!currWLData[venueKey].state && stateToRemove === 'None')),
      );
      if (venuesToRemove.length > 0) {
        if (getWMEPHSetting('WMEPH_WLAddCount') === 1) {
          WazeWrap.Alerts.confirm(
            SCRIPT_NAME,
            `Are you sure you want to clear all whitelist data for ${stateToRemove}? This CANNOT be undone. ` + 'Press OK to delete, cancel to preserve the data.',
            () => {
              backupWhitelistToLS(true);
              venuesToRemove.forEach((venueKey) => {
                delete _venueWhitelist[venueKey];
              });
              saveWhitelistToLS(true);
              $wlInput.val('');
              $('#PlaceHarmonizerWLToolsMsg')
                .empty()
                .append($('<p>').css({ color: 'green' }).text(`${venuesToRemove.length} venues removed from WL`));
            },
            () => {
              $('#PlaceHarmonizerWLToolsMsg')
                .empty()
                .append($('<p>').css({ color: 'blue' }).text('No changes made'));
            },
          );
          return;
        } // else {
        msgColor = 'red';
        msgText = 'Please backup your WL using the Pull button before removing state data';
        // }
      } else {
        msgColor = 'red';
        msgText = `No data for "${stateToRemove}". Use the state name exactly as listed in the Stats`;
      }
    }
    $('#PlaceHarmonizerWLToolsMsg')
      .empty()
      .append($('<p>').css({ color: msgColor }).text(msgText));
  }

  /**
   * Opens a Google Form to submit/share the user's whitelist data to a public repository.
   * Prepopulates the form with the user's name from WME SDK.
   */
  function onWLShareClick() {
    window.open(`https://docs.google.com/forms/d/1k_5RyOq81Fv4IRHzltC34kW3IUbXnQqDVMogwJKFNbE/viewform?entry.1173700072=${USER.name}`);
  }

  /**
   * Initializes all settings checkboxes and button handlers in the WMEPH settings tab.
   * Sets default values, attaches click handlers, and configures feature-specific behavior.
   * Handles role-based settings visibility (dev/beta users see additional options).
   * Called after the settings tab is added to the UI.
   */
  function initWmephTab() {
    const multicall = (func, names) => names.forEach((name) => func(name));

    // Enable certain settings by default if not set by the user:
    multicall(setCheckedByDefault, ['WMEPH-ColorHighlighting', 'WMEPH-ExcludePLADupes', 'WMEPH-DisablePLAExtProviderCheck']);

    // Initialize settings checkboxes
    multicall(initSettingsCheckbox, [
      'WMEPH-WebSearchNewTab',
      'WMEPH-EnableIAZoom',
      'WMEPH-HidePlacesWiki',
      'WMEPH-HideServicesButtons',
      'WMEPH-HidePURWebSearch',
      'WMEPH-ExcludePLADupes',
      'WMEPH-ShowPLAExitWhileClosed',
    ]);
    if (USER.isDevUser || USER.isBetaUser || USER.rank >= 2) {
      multicall(initSettingsCheckbox, ['WMEPH-DisablePLAExtProviderCheck', 'WMEPH-AddAddresses', 'WMEPH-EnableCloneMode', 'WMEPH-AutoLockRPPs']);
    }
    multicall(initSettingsCheckbox, [
      'WMEPH-ColorHighlighting',
      'WMEPH-DisableHoursHL',
      'WMEPH-DisableRankHL',
      'WMEPH-DisableWLHL',
      'WMEPH-PLATypeFill',
      'WMEPH-KBSModifierKey',
      'WMEPH-ShowFilterHighlight',
    ]);

    if (USER.isDevUser) {
      initSettingsCheckbox('WMEPH-RegionOverride');
    }

    // Turn this setting on one time.
    if (!_initAlreadyRun) {
      const runOnceDefaultIgnorePlaGoogleLinkChecks = getWMEPHSetting('WMEPH-runOnce-defaultToOff-plaGoogleLinkChecks');
      if (!runOnceDefaultIgnorePlaGoogleLinkChecks) {
        const $chk = $('#WMEPH-DisablePLAExtProviderCheck');
        if (!$chk.prop('checked')) {
          $chk.trigger('click');
        }
      }
      setWMEPHSetting('WMEPH-runOnce-defaultToOff-plaGoogleLinkChecks', true);
    }

    initShortcutKey();

    if (getWMEPHSetting('WMEPH_WLAddCount') === null) {
      setWMEPHSetting('WMEPH_WLAddCount', 2); // Counter to remind of WL backups
    }

    // Reload Data button click event
    $('#WMEPH-ReloadDataBtn').click(async () => {
      $('#WMEPH-ReloadDataBtn').attr('disabled', true);
      _resultsCache = {};
      _resultsCacheOrder = [];
      wmephStats = {
        harmonizeCount: 0,
        totalHarmonizeTime: 0,
        lastHarmonizeTime: 0,
        maxHarmonizeTime: 0,
        cacheHits: 0,
        cacheMisses: 0,
        totalCacheHitTime: 0,
        totalCacheMissTime: 0,
        lastCacheHitTime: 0,
        lastCacheMissTime: 0,
        maxCacheHitTime: 0,
        maxCacheMissTime: 0,
      };
      await Pnh.downloadPnhData();
      redrawLayer(_layer);
      $('#WMEPH-ReloadDataBtn').attr('disabled', false);
    });

    // WL button click events
    $('#WMEPH-WLMerge').click(onWLMergeClick);
    $('#WMEPH-WLPull').click(onWLPullClick);
    $('#WMEPH-WLStats').click(onWLStatsClick);
    $('#WMEPH-WLStateFilter').click(onWLStateFilterClick);
    $('#WMEPH-WLShare').click(onWLShareClick);

    // Color highlighting
    $('#WMEPH-ColorHighlighting').click(bootstrapWmephColorHighlights);
    $('#WMEPH-DisableHoursHL').click(bootstrapWmephColorHighlights);
    $('#WMEPH-DisableRankHL').click(bootstrapWmephColorHighlights);
    $('#WMEPH-DisableWLHL').click(bootstrapWmephColorHighlights);
    $('#WMEPH-PLATypeFill').click(() => {
      saveSettingToLocalStorage('WMEPH-PLATypeFill');
      refreshAllHighlights();
    });
    $('#WMEPH-ShowFilterHighlight').click(() => {
      saveSettingToLocalStorage('WMEPH-ShowFilterHighlight');
      refreshAllHighlights();
    });

    _initAlreadyRun = true;
  }

  /**
   * Creates and registers the WMEPH settings tab in the WME sidebar.
   * Builds four tab panes: Harmonize settings, Highlighter settings, Whitelist Tools, and Moderators list.
   * Populates UI with checkboxes, buttons, and text inputs for user configuration.
   * Calls initWmephTab() to initialize event handlers after UI is built.
   * @async
   */
  async function addWmephTab() {
    // Set up the CSS
    GM_addStyle(_CSS);

    const $container = $('<div>');
    const $reloadDataBtn = $(
      '<div style="margin-bottom:6px; text-align:center;"><div style="position:relative; display:inline-block; width:75%"><input id="WMEPH-ReloadDataBtn" style="min-width:90px; width:50%" class="btn btn-success wmeph-fat-btn" type="button" title="Refresh Data" value="Refresh Data"/><div class="checkmark draw"></div></div></div>',
    );
    const $navTabs = $(
      '<ul class="nav nav-tabs"><li class="active"><a data-toggle="tab" href="#sidepanel-harmonizer">Harmonize</a></li>' +
        '<li><a data-toggle="tab" href="#sidepanel-highlighter">HL / Scan</a></li>' +
        '<li><a data-toggle="tab" href="#sidepanel-wltools">WL Tools</a></li>' +
        '<li><a data-toggle="tab" href="#sidepanel-pnh-moderators">Moderators</a></li></ul>',
    );
    const $tabContent = $('<div class="tab-content">');
    const $versionDiv = $('<div>').text(`WMEPH ${BETA_VERSION_STR} v${SCRIPT_VERSION}`).css({ color: '#999', fontSize: '13px' });
    const $harmonizerTab = $('<div class="tab-pane wmeph-pane active" id="sidepanel-harmonizer"></div>');
    const $highlighterTab = $('<div class="tab-pane wmeph-pane" id="sidepanel-highlighter"></div>');
    const $wlToolsTab = $('<div class="tab-pane wmeph-pane" id="sidepanel-wltools"></div>');
    const $moderatorsTab = $('<div class="tab-pane wmeph-pane" id="sidepanel-pnh-moderators"></div>');
    $tabContent.append($harmonizerTab, $highlighterTab, $wlToolsTab, $moderatorsTab);
    $container.append($reloadDataBtn, $navTabs, $tabContent, $versionDiv);

    // Harmonizer settings
    createSettingsCheckbox($harmonizerTab, 'WMEPH-WebSearchNewTab', 'Open URL & Search Results in new tab instead of new window');
    createSettingsCheckbox($harmonizerTab, 'WMEPH-EnableIAZoom', 'Enable zoom & center for places with no address');
    createSettingsCheckbox($harmonizerTab, 'WMEPH-HidePlacesWiki', 'Hide "Places Wiki" button in results banner');
    createSettingsCheckbox($harmonizerTab, 'WMEPH-HideServicesButtons', 'Hide services buttons in results banner');
    createSettingsCheckbox($harmonizerTab, 'WMEPH-HidePURWebSearch', 'Hide "Web Search" button on PUR popups');
    createSettingsCheckbox($harmonizerTab, 'WMEPH-ExcludePLADupes', 'Exclude parking lots when searching for duplicate places');
    createSettingsCheckbox($harmonizerTab, 'WMEPH-ShowPLAExitWhileClosed', 'Always ask if cars can exit parking lots');
    if (USER.isDevUser || USER.isBetaUser || USER.rank >= 2) {
      createSettingsCheckbox($harmonizerTab, 'WMEPH-DisablePLAExtProviderCheck', 'Disable check for "Google place link" on Parking Lot Areas');
      createSettingsCheckbox($harmonizerTab, 'WMEPH-AddAddresses', 'Add detected address fields to places with no address');
      createSettingsCheckbox($harmonizerTab, 'WMEPH-EnableCloneMode', 'Enable place cloning tools');
      createSettingsCheckbox($harmonizerTab, 'WMEPH-AutoLockRPPs', 'Lock residential place points to region default');
    }

    $harmonizerTab.append('<hr class="wmeph-hr" align="center" width="100%">');

    // Add Letter input box
    const $phShortcutDiv = $('<div id="PlaceHarmonizerKB">');
    // eslint-disable-next-line max-len
    $phShortcutDiv.append(
      '<div id="PlaceHarmonizerKBWarn"></div>Shortcut Letter (a-Z): <input type="text" maxlength="1" id="WMEPH-KeyboardShortcut" style="width: 30px;padding-left:8px"><div id="PlaceHarmonizerKBCurrent"></div>',
    );
    createSettingsCheckbox($phShortcutDiv, 'WMEPH-KBSModifierKey', 'Use Ctrl instead of Alt'); // Add Alt-->Ctrl checkbox

    if (USER.isDevUser) {
      // Override script regionality (devs only)
      $phShortcutDiv.append('<hr class="wmeph-hr" align="center" width="100%"><p>Dev Only Settings:</p>');
      createSettingsCheckbox($phShortcutDiv, 'WMEPH-RegionOverride', 'Disable Region Specificity');
    }

    $harmonizerTab.append(
      $phShortcutDiv,
      '<hr class="wmeph-hr" align="center" width="100%">',
      `<div><a href="${URLS.placesWiki}" target="_blank">Open the WME Places Wiki page</a></div>`,
      `<div><a href="${URLS.forum}" target="_blank">Submit script feedback & suggestions</a></div>`,
      '<hr class="wmeph-hr" align="center" width="95%">',
    );

    // Highlighter settings
    $highlighterTab.append('<p>Highlighter Settings:</p>');
    createSettingsCheckbox($highlighterTab, 'WMEPH-ColorHighlighting', 'Enable color highlighting of map to indicate places needing work');
    createSettingsCheckbox($highlighterTab, 'WMEPH-DisableHoursHL', 'Disable highlighting for missing hours');
    createSettingsCheckbox($highlighterTab, 'WMEPH-DisableRankHL', 'Disable highlighting for places locked above your rank');
    createSettingsCheckbox($highlighterTab, 'WMEPH-DisableWLHL', 'Disable Whitelist highlighting (shows all missing info regardless of WL)');
    createSettingsCheckbox($highlighterTab, 'WMEPH-PLATypeFill', 'Fill parking lots based on type (public=blue, restricted=yellow, private=red)');
    createSettingsCheckbox($highlighterTab, 'WMEPH-ShowFilterHighlight', 'Highlight places without Customer Parking service');
    if (USER.isDevUser || USER.isBetaUser || USER.rank >= 3) {
      // createSettingsCheckbox($highlighterTab 'WMEPH-UnlockedRPPs','Highlight unlocked residential place points');
    }

    // Scanner settings
    // $highlighterTab.append('<hr align="center" width="90%">');
    // $highlighterTab.append('<p>Scanner Settings (coming !soon)</p>');
    // createSettingsCheckbox($highlighterTab, 'WMEPH-PlaceScanner','Placeholder, under development!');

    // Whitelisting settings
    const phWLContentHtml = $(
      '<div id="PlaceHarmonizerWLTools">Whitelist string: <input onClick="this.select();" type="text" id="WMEPH-WLInput" style="width:100%;padding-left:1px;display:block">' +
        '<div style="margin-top:3px;">' +
        '<input class="btn btn-success btn-xs wmeph-fat-btn" id="WMEPH-WLMerge" title="Merge the string into your existing Whitelist" type="button" value="Merge">' +
        '<input class="btn btn-success btn-xs wmeph-fat-btn" id="WMEPH-WLPull" title="Pull your existing Whitelist for backup or sharing" type="button" value="Pull">' +
        '<input class="btn btn-success btn-xs wmeph-fat-btn" id="WMEPH-WLShare" title="Share your Whitelist to a public Google sheet" type="button" value="Share your WL">' +
        '</div>' +
        '<div style="margin-top:12px;">' +
        '<input class="btn btn-info btn-xs wmeph-fat-btn" id="WMEPH-WLStats" title="Display WL stats" type="button" value="Stats">' +
        '<input class="btn btn-danger btn-xs wmeph-fat-btn" id="WMEPH-WLStateFilter" title="Remove all WL items for a state.  Enter the state in the \'Whitelist string\' box." ' +
        '     type="button" value="Remove data for 1 State">' +
        '</div>' +
        '</div>' +
        '<div id="PlaceHarmonizerWLToolsMsg" style="margin-top:10px;"></div>',
    );
    $wlToolsTab.append(phWLContentHtml);

    $moderatorsTab.append(
      $('<div>', { style: 'margin-bottom: 10px;' }).text(
        'Moderators are responsible for reviewing chain submissions for their region.' + ' If you have questions or suggestions regarding a chain, please contact any of your regional moderators.',
      ),
      $('<table>').append(
        Object.keys(Pnh.MODERATORS)
          .sort()
          .map((region) =>
            $('<tr>').append(
              $('<td>', { class: 'wmeph-mods-table-cell title' }).append($('<div>').text(region)),
              $('<td>', { class: 'wmeph-mods-table-cell' }).append($('<div>').text(Pnh.MODERATORS[region].join(', '))),
            ),
          ),
      ),
    );

    const { tabLabel, tabPane } = await sdk.Sidebar.registerScriptTab();
    tabLabel.innerHTML = `<span title="WME Place Harmonizer">WMEPH${IS_BETA_VERSION ? '-β' : ''}</span>`;
    tabPane.innerHTML = $container.html();
    tabPane.classList.add('wmeph-pane');
    // Fix tab content div spacing.
    $(tabPane).parent().css({ width: 'auto', padding: '8px !important' });
    initWmephTab();
  }

  /**
   * Creates an icon-based toggle button for clone tool field selection.
   * Uses Font Awesome icons for visual representation of each field (house, road, map, etc.).
   * Persists checkbox state to settings storage and updates button appearance on toggle.
   * @param {string} divID Not currently used, kept for compatibility.
   * @param {string} settingID The setting key for this clone field (e.g., WMEPH_CPhn for house number).
   * @param {string} textDescription Tooltip text describing what field this button controls.
   * @returns {jQuery} The created button element.
   */
  function createCloneCheckbox(divID, settingID, textDescription) {
    const iconMap = {
      WMEPH_CPhn: 'fa-home', // House Number
      WMEPH_CPstr: 'fa-road', // Street
      WMEPH_CPcity: 'fa-map-marker', // City
      WMEPH_CPurl: 'fa-link', // URL
      WMEPH_CPph: 'fa-phone', // Phone
      WMEPH_CPdesc: 'fa-file-text', // Description
      WMEPH_CPserv: 'fa-cog', // Services
      WMEPH_CPhrs: 'fa-clock-o', // Hours
    };

    const icon = iconMap[settingID];
    const isChecked = getWMEPHSetting(settingID) === '1';

    const $button = $('<button>', {
      id: settingID,
      type: 'button',
      class: 'wmeph-icon-toggle',
      title: textDescription,
      style: `
                background: none;
                border: none;
                padding: 4px 6px;
                cursor: pointer;
                font-size: 16px;
                color: ${isChecked ? '#0075e3' : '#999'};
                transition: all 0.2s ease;
                opacity: ${isChecked ? '1' : '0.5'};
            `,
    })
      .html(icon ? `<i class="fa ${icon}"></i>` : textDescription)
      .click(function () {
        const checked = getWMEPHSetting(settingID) === '1';
        const newState = checked ? '0' : '1';
        setWMEPHSetting(settingID, newState);
        $(this).css({
          color: newState === '1' ? '#0075e3' : '#999',
          opacity: newState === '1' ? '1' : '0.5',
        });
      });

    return $button;
  }

  /**
   * Prepends 'Shift+' to keyboard shortcut if key is uppercase letter.
   * Used to build complete keyboard shortcut combinations (e.g., 'Shift+A' or 'a').
   * @param {string} kbs Single keyboard character (uppercase triggers Shift modifier).
   * @returns {string} Keyboard shortcut with optional Shift prefix.
   */
  function parseKBSShift(kbs) {
    return (/^[A-Z]{1}$/g.test(kbs) ? 'Shift+' : '') + kbs;
  }

  /**
   * Persists a checkbox's state to WMEPH settings storage.
   * Reads current checked state from DOM and saves as '1' or '0'.
   * @param {string} settingID The HTML id and settings key for the checkbox.
   */
  function saveSettingToLocalStorage(settingID) {
    setWMEPHSetting(settingID, $(`#${settingID}`).prop('checked') ? '1' : '0');
  }

  /**
   * Validates that a string is valid JSON and returns parsed object if valid.
   * Returns false if JSON parsing fails or if parsed value is not an object.
   * @param {string} jsonString String to validate as JSON.
   * @returns {Object|boolean} Parsed JSON object if valid, false otherwise.
   */
  function validateWLS(jsonString) {
    try {
      const objTry = JSON.parse(jsonString);
      if (objTry && typeof objTry === 'object' && objTry !== null) {
        return objTry;
      }
    } catch (e) {
      // do nothing
    }
    return false;
  }

  /**
   * Merges whitelist data from wl2 into wl1, with wl2 data taking precedence.
   * For venues that exist in both, merges active whitelisting rules.
   * For array-based WL keys, appends wl2 data to wl1 arrays at index 100 and deduplicates.
   * Used when user pastes a shared whitelist to combine it with their existing WL.
   * @param {Object} wl1 The target whitelist object to merge into.
   * @param {Object} wl2 The source whitelist object to merge from.
   * @returns {Object} Updated wl1 with wl2 data merged in.
   */
  function mergeWL(wl1, wl2) {
    let wlVenue1;
    let wlVenue2;
    Object.keys(wl2).forEach((venueKey) => {
      if (wl1.hasOwnProperty(venueKey)) {
        // if the wl2 venue is in wl1, then update any keys
        wlVenue1 = wl1[venueKey];
        wlVenue2 = wl2[venueKey];
        // loop thru the venue WL keys
        Object.keys(wlVenue2).forEach((wlKey) => {
          // Only update if the wl2 key is active
          if (wlVenue2.hasOwnProperty(wlKey) && wlVenue2[wlKey].active) {
            // if the key is in the wl1 venue and it is active, then push any array data onto the key
            if (wlVenue1.hasOwnProperty(wlKey) && wlVenue1[wlKey].active) {
              if (wlVenue1[wlKey].hasOwnProperty('WLKeyArray')) {
                wl1[venueKey][wlKey].WLKeyArray = insertAtIndex(wl1[venueKey][wlKey].WLKeyArray, wl2[venueKey][wlKey].WLKeyArray, 100);
              }
            } else {
              // if the key isn't in the wl1 venue, or if it's inactive, then copy the wl2 key across
              wl1[venueKey][wlKey] = wl2[venueKey][wlKey];
            }
          }
        }); // END subLoop for venue keys
      } else {
        // if the venue doesn't exist in wl1, then add it
        wl1[venueKey] = wl2[venueKey];
      }
    });
    return wl1;
  }

  /**
   * Returns an array of boolean flags indicating which services are present on a venue.
   * Array index corresponds to service type in WME_SERVICES_ARRAY.
   * @param {Object} venue The WME venue object to check services for.
   * @returns {Array<boolean>} Boolean array where index i is true if service i is present.
   */
  function getServicesChecks(venue) {
    const servArrayCheck = [];
    const services = venue.services || [];
    for (let wsix = 0; wsix < WME_SERVICES_ARRAY.length; wsix++) {
      if (services.includes(WME_SERVICES_ARRAY[wsix])) {
        servArrayCheck[wsix] = true;
      } else {
        servArrayCheck[wsix] = false;
      }
    }
    return servArrayCheck;
  }

  /**
   * Updates service button UI states to reflect services currently on the selected venue.
   * Marks buttons as checked/unchecked based on venue's services array.
   * Also checks the 24/7 button if venue has 24/7 hours, and ensures it's visible.
   */
  function updateServicesChecks() {
    const venue = getSelectedVenue();
    if (venue) {
      if (!_servicesBanner) return;
      const services = venue.services || [];
      Object.keys(_servicesBanner).forEach((keys) => {
        if (_servicesBanner.hasOwnProperty(keys)) {
          const button = _servicesBanner[keys];
          const serviceId = button.serviceId;
          // Check if this service is in the venue's services array
          if (serviceId) {
            button.checked = services.includes(serviceId);
            button.active = button.active || button.checked; // display any manually checked non-active icons
          }
        }
      });
      // Highlight 24/7 button if hours are set that way, and add button for all places
      if (_servicesBanner && _servicesBanner.add247) {
        if (isAlwaysOpen(venue)) {
          _servicesBanner.add247.checked = true;
        }
        _servicesBanner.add247.active = true;
      }
    }
  }

  /**
   * Removes focus from the currently focused DOM element.
   * Creates a temporary input element, focuses it (moving focus away), then removes it.
   * Used to trigger onchange/blur events for text inputs and finalize pending edits.
   */
  function blurAll() {
    const tmp = document.createElement('input');
    document.body.appendChild(tmp);
    tmp.focus();
    document.body.removeChild(tmp);
  }

  /**
   * Retrieves user information from SDK and populates the USER object.
   * Converts SDK rank (0-based) to 1-based (1-7) for script logic.
   * Checks if user is in dev or beta lists to enable feature access and extra settings.
   * Shows warning if beta/dev lists are inaccessible (only for beta version builds).
   */
  function updateUserInfo() {
    const userInfo = sdk.State.getUserInfo();
    if (!userInfo) {
      logDev('updateUserInfo: SDK user info not available yet');
      return;
    }
    USER.name = userInfo.userName;
    USER.rank = userInfo.rank + 1; // SDK rank is 0-based (0-6), convert to 1-based (1-7)
    if (!_wmephBetaList || _wmephBetaList.length === 0) {
      if (IS_BETA_VERSION) {
        WazeWrap.Alerts.warning(SCRIPT_NAME, 'Beta user list access issue.  Please post in the GHO or PM/DM MapOMatic about this message.  Script should still work.');
      }
      USER.isBetaUser = false;
      USER.isDevUser = false;
    } else {
      const lcName = USER.name.toLowerCase();
      USER.isDevUser = _wmephDevList.includes(lcName) || lcName === 'js55ct'; // Allow JS55CT for testing
      USER.isBetaUser = _wmephBetaList.includes(lcName);
    }
    if (USER.isDevUser) {
      USER.isBetaUser = true; // dev users are beta users
    }
  }

  /**
   * Keyboard shortcut handler to toggle the "Show Filter Highlight" setting.
   * Programmatically clicks the checkbox to trigger its event handlers and state changes.
   */
  function onFilterHighlightToggleShortcutKey() {
    $('#WMEPH-ShowFilterHighlight').click();
  }

  /**
   * Keyboard shortcut handler to toggle the "Color Highlighting" setting.
   * Programmatically clicks the checkbox to trigger its event handlers and state changes.
   */
  function onShowHighlightColorsToggleShortcutKey() {
    $('#WMEPH-ColorHighlighting').click();
  }

  /**
   * Window beforeunload event handler for cleanup and state persistence.
   * SDK shortcuts are automatically saved via saveShortcut() in registerShortcut(),
   * so no manual save is required here. Handler is registered for potential future use.
   */
  function onWindowBeforeUnload() {
    // SDK shortcuts are saved automatically via saveShortcut() in registerShortcut()
    // No manual save needed on unload
  }

  /**
   * Shows a script update notification with release notes when script version changes.
   * Uses WazeWrap.Interface.ShowScriptUpdate if available, falls back to debug log.
   * Updates the stored version number and displays formatted "What's New" list.
   */
  function showScriptInfoAlert() {
    const lastVersion = getWMEPHSetting('WMEPH_lastVersion');

    /* Check version and alert on update */
    if (SCRIPT_UPDATE_MESSAGE && SCRIPT_VERSION !== lastVersion) {
      let releaseNotes = '';
      releaseNotes += "<p>What's New:</p>";
      if (SCRIPT_UPDATE_MESSAGE.length > 0) {
        releaseNotes += '<ul>';
        for (let idx = 0; idx < SCRIPT_UPDATE_MESSAGE.length; idx++) releaseNotes += `<li>${SCRIPT_UPDATE_MESSAGE[idx]}`;
        releaseNotes += '</ul>';
      } else {
        releaseNotes += '<ul><li>Nothing major.</ul>';
      }
      if (WazeWrap?.Interface?.ShowScriptUpdate) {
        WazeWrap.Interface.ShowScriptUpdate(SCRIPT_NAME, SCRIPT_VERSION, releaseNotes, GF_URL);
      } else {
        logDev('WazeWrap.Interface.ShowScriptUpdate not available');
      }
    }
    setWMEPHSetting('WMEPH_lastVersion', SCRIPT_VERSION);
  }

  /**
   * Migrates legacy individual localStorage keys into a centralized WMEPH-Settings JSON object.
   * One-time migration that consolidates scattered settings for cleaner storage and easier management.
   * Removes migrated keys from localStorage and cleans up any stray WMEPH keys left behind.
   * Tracks migration version to avoid re-running on subsequent scripts loads.
   */
  function migrateSettingsToObject() {
    const MIGRATION_VERSION = 1;
    wmephSettings = JSON.parse(localStorage.getItem('WMEPH-Settings') || '{}');
    const currentVersion = wmephSettings._migrationVersion || 0;
    logDev(`migrateSettingsToObject: current version=${currentVersion}, target version=${MIGRATION_VERSION}`);

    // If already migrated, skip
    if (currentVersion === MIGRATION_VERSION) {
      logDev('Migration already complete, skipping');
      return;
    }

    const oldKeys = [
      'WMEPH-KeyboardShortcut',
      'WMEPH-KBSModifierKey',
      'WMEPH-WebSearchNewTab',
      'WMEPH-EnableCloneMode',
      'WMEPH-EnableIAZoom',
      'WMEPH-HidePlacesWiki',
      'WMEPH-HideServicesButtons',
      'WMEPH-HidePURWebSearch',
      'WMEPH-ExcludePLADupes',
      'WMEPH-ShowPLAExitWhileClosed',
      'WMEPH-DisablePLAExtProviderCheck',
      'WMEPH-AddAddresses',
      'WMEPH-AutoLockRPPs',
      'WMEPH-ColorHighlighting',
      'WMEPH-DisableHoursHL',
      'WMEPH-DisableRankHL',
      'WMEPH-DisableWLHL',
      'WMEPH-PLATypeFill',
      'WMEPH-ShowFilterHighlight',
      'WMEPH-RegionOverride',
      'WMEPH-featuresExamined',
      'WMEPH-runOnce-defaultToOff-plaGoogleLinkChecks',
      'WMEPH-OneTimeWLBU',
      'WMEPH_xrayMode_enabled',
      'WMEPH_WLAddCount',
      'WMEPH_lastVersion',
      'WMEPH_ColorHighlighting',
      'WMEPH_FilterHighlightShortcut',
      'WMEPH_CPcity',
      'WMEPH_CPdesc',
      'WMEPH_CPhn',
      'WMEPH_CPhrs',
      'WMEPH_CPph',
      'WMEPH_CPserv',
      'WMEPH_CPstr',
      'WMEPH_CPurl',
      'WMEPH_shortcut_wmeph_color_highlighting',
      'WMEPH_shortcut_wmeph_harmonize_place',
      'WMEPH_shortcut_wmeph_zoom_place',
      // Note: WMEPH-venueWhitelistCompressed and WMEPH-venueWhitelistNew are kept separate
      // as independent localStorage keys since they're large data, not user settings
    ];

    // Collect all existing WMEPH settings
    oldKeys.forEach((key) => {
      const value = localStorage.getItem(key);
      if (value !== null) {
        wmephSettings[key] = value;
      }
    });

    // Mark migration as complete
    wmephSettings._migrationVersion = MIGRATION_VERSION;
    localStorage.setItem('WMEPH-Settings', JSON.stringify(wmephSettings));
    logDev(`Migrated settings to WMEPH-Settings object`);

    // Clean up old keys
    const removedKeys = [];
    oldKeys.forEach((key) => {
      if (localStorage.getItem(key) !== null) {
        removedKeys.push(key);
        localStorage.removeItem(key);
      }
    });
    logDev(`Removed ${removedKeys.length} old keys: ${removedKeys.join(', ')}`);

    // Additional cleanup: remove any remaining top-level WMEPH keys (catch-all for missed keys)
    const strayKeys = [];
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      if (key && (key.startsWith('WMEPH-') || key.startsWith('WMEPH_')) && key !== 'WMEPH-Settings' && key !== 'WMEPH-venueWhitelistNew' && key !== 'WMEPH-venueWhitelistCompressed') {
        strayKeys.push(key);
        localStorage.removeItem(key);
        i--; // Adjust index since we removed an item
      }
    }
    if (strayKeys.length > 0) {
      logDev(`Removed ${strayKeys.length} stray keys: ${strayKeys.join(', ')}`);
    }
  }

  /**
   * Loads centralized WMEPH settings from localStorage into wmephSettings cache.
   * Ensures large whitelist data is never included in settings (kept as separate keys).
   * Removes whitelist entries if found as safeguard against bloated settings object.
   */
  function loadWMEPHSettings() {
    wmephSettings = JSON.parse(localStorage.getItem('WMEPH-Settings') || '{}');
    // Ensure whitelist data is never stored in settings object (kept as separate localStorage keys to avoid bloating)
    // These are removed here as a safeguard in case they are mistakenly added.
    delete wmephSettings['WMEPH-venueWhitelistCompressed'];
    delete wmephSettings['WMEPH-venueWhitelistNew'];
  }

  /**
   * Main initialization function called when script first runs.
   * Sets up UI, keyboard shortcuts, event listeners, data layers, and PNH data loading.
   * Creates map layers for venue highlighting, duplicate labels, and Google Places links.
   * Registers SDK event handlers for selection changes and data model updates.
   * Initializes settings tab, whitelist system, and color highlighting layer.
   * @async
   */
  async function placeHarmonizerInit() {
    interceptGoogleGetDetails();
    updateUserInfo();
    logDev('placeHarmonizerInit'); // Be sure to update User info before calling logDev()

    // Migrate legacy localStorage keys to centralized settings object
    migrateSettingsToObject();
    loadWMEPHSettings(); // Ensure settings are loaded into cache

    // Initialize custom keyboard shortcut (used by harmonize shortcut below)
    initShortcutKey();

    // Register SDK shortcuts with normalized key binding storage
    registerShortcut('wmeph_zoom_place', 'WMEPH: Zoom to selected place', 'A+Z', () => {
      zoomPlace();
    });
    registerShortcut('wmeph_color_highlighting', 'WMEPH: Toggle color highlighting', 'A+C', () => {
      toggleHighlightCheckbox();
    });
    // Third shortcut: Harmonize place with user-configurable key (from UI settings)
    const harmonizeKey = loadHarmonizeShortcut();
    logDev(`Harmonize shortcut key loaded: ${harmonizeKey}`);
    if (harmonizeKey) {
      registerShortcut('wmeph_harmonize_place', 'WMEPH: Harmonize selected place', harmonizeKey, () => {
        harmonizePlace();
      });
    } else {
      logDev('loadHarmonizeShortcut returned null or empty - harmonize shortcut not registered');
    }

    // Layer displays venues based on severity flags and parking lot types
    // Priority: wmephHighlight > lock types > (parking + severity) > severity alone > parking alone
    _layer = 'wmeph_highlights';
    try {
      sdk.Map.addLayer({
        layerName: _layer,
        zIndexing: true,
        styleContext: {
          getColor: ({ feature }) => {
            const parkingType = feature?.properties?.parkingType;
            return PARKING_TYPE_COLORS[parkingType];
          },
          getSeverityColor: ({ feature }) => {
            const severity = feature?.properties?.wmephSeverity;
            return SEVERITY_COLORS[severity];
          },
          getPointRadius: ({ zoomLevel }) => {
            return zoomLevel > 17 ? 13 : 8;
          },
          getGraphicName: ({ feature }) => {
            return feature?.properties?.isResidential ? 'triangle' : 'circle';
          },
          getStrokeDashstyle: ({ feature }) => {
            return feature?.properties?.isResidential ? '2 4' : '2 10';
          },
        },
        styleRules: [
          // Rule 1: Filter highlight (wmephHighlight = '1') - magenta stroke only, highest priority
          {
            predicate: (props, zoomLevel) => props.wmephHighlight === '1',
            style: {
              pointRadius: '${getPointRadius}',
              graphicName: '${getGraphicName}',
              fillOpacity: 0,
              strokeWidth: 5,
              strokeColor: '#F0F',
              strokeOpacity: 0.8,
            },
          },
          // Rule 2: Lock severity types (lock, lock1, adLock) - points with dashed style
          {
            predicate: (props, zoomLevel) => props.wmephHighlight !== '1' && (props.wmephSeverity === 'lock' || props.wmephSeverity === 'lock1' || props.wmephSeverity === 'adLock') && props.isPoint === true,
            style: {
              pointRadius: '${getPointRadius}',
              graphicName: '${getGraphicName}',
              fillOpacity: 0,
              strokeColor: '${getSeverityColor}',
              strokeWidth: 5,
              strokeOpacity: 1,
              strokeDashstyle: '${getStrokeDashstyle}'
            },
          },
          // Rule 2b: Lock severity types (lock, lock1, adLock) - polygons with dashed style
          {
            predicate: (props, zoomLevel) => props.wmephHighlight !== '1' && (props.wmephSeverity === 'lock' || props.wmephSeverity === 'lock1' || props.wmephSeverity === 'adLock') && props.isPoint !== true,
            style: {
              fillOpacity: 0,
              strokeColor: '${getSeverityColor}',
              strokeWidth: 8,
              strokeOpacity: 1,
              strokeDashstyle: '${getStrokeDashstyle}'
            },
          },
          // Rule 3: Parking lot with severity - both fill (parking type) and stroke (severity severity), excluding lock severities
          {
            predicate: (props, zoomLevel) => props.wmephHighlight !== '1' && props.parkingType !== undefined && props.wmephSeverity !== undefined && props.wmephSeverity !== 'lock' && props.wmephSeverity !== 'lock1' && props.wmephSeverity !== 'adLock',
            style: {
              pointRadius: '${getPointRadius}',
              graphicName: '${getGraphicName}',
              fillColor: '${getColor}',
              fillOpacity: 0.5,
              strokeColor: '${getSeverityColor}',
              strokeWidth: 5,
              strokeOpacity: 1,
            },
          },
          // Rule 4: Severity only (no parking type) - stroke only, excluding lock severities
          {
            predicate: (props, zoomLevel) => props.wmephHighlight !== '1' && props.wmephSeverity !== undefined && props.wmephSeverity !== 'lock' && props.wmephSeverity !== 'lock1' && props.wmephSeverity !== 'adLock',
            style: {
              pointRadius: '${getPointRadius}',
              graphicName: '${getGraphicName}',
              fillOpacity: 0,
              strokeColor: '${getSeverityColor}',
              strokeWidth: 5,
              strokeOpacity: 1,
            },
          },
          // Rule 5: Parking lot only (no severity) - fill only
          {
            predicate: (props, zoomLevel) => props.wmephHighlight !== '1' && props.parkingType !== undefined,
            style: {
              pointRadius: '${getPointRadius}',
              graphicName: '${getGraphicName}',
              fillColor: '${getColor}',
              fillOpacity: 0.5,
              strokeOpacity: 0,
            },
          },
        ],
      });
    } catch (e) {
      logDev(`${_layer} layer error:`, e);
    }

    // Create layer for Google place links visualization
    try {
      sdk.Map.addLayer({
        layerName: 'wmeph_google_link',
        styleContext: {
          getLabel: (context) => context.feature?.properties?.label ?? '',
        },
        styleRules: [
          {
            predicate: (props) => props.poiCoord === true,
            style: {
              pointRadius: 6,
              strokeWidth: 30,
              strokeColor: '#FF0',
              fillColor: '#FF0',
              strokeOpacity: 0.5,
            },
          },
          {
            predicate: (props) => !props.poiCoord,
            style: {
              strokeColor: '#FF0',
              strokeWidth: 3,
              strokeOpacity: 1.0,
              strokeDashstyle: '12 8',
              label: '${getLabel}',
              labelOutlineWidth: 4,
              labelOutlineColor: '#000',
              labelYOffset: +45,
              fontColor: '#FF0',
              fontSize: '18px',
              fontWeight: 'bold',
            },
          },
        ],
      });
    } catch (e) {
      logDev('wmeph_google_link layer error:', e);
    }

    // Add CSS stuff here
    const css = ['.wmeph-mods-table-cell { border: solid 1px #bdbdbd; padding-left: 3px; padding-right: 3px; }', '.wmeph-mods-table-cell.title { font-weight: bold; }'].join('\n');
    $('head').append(`<style type="text/css">${css}</style>`);

    OpeningHour = require('Waze/Model/Objects/OpeningHour');

    // Append a form div for submitting to the forum, if it doesn't exist yet:
    const tempDiv = document.createElement('div');
    tempDiv.id = 'WMEPH_formDiv';
    tempDiv.style.display = 'none';
    $('body').append(tempDiv);

    _userLanguage = I18n.locale;

    appendServiceButtonIconCss();
    UPDATED_FIELDS.init();
    addPURWebSearchButton();

    // Use SDK layer for duplicate place names (created during initialization)
    _dupeLayer = 'wmeph_dupe_labels';

    // Create the dupe labels layer if it doesn't exist
    try {
      sdk.Map.addLayer({
        layerName: _dupeLayer,
        zIndexing: true,
        styleContext: {
          getColor: ({ feature }) => {
            if (feature?.properties?.dupeType === 'primary') {
              return '#00FF00'; // GREEN for primary
            }
            return '#ffff00'; // YELLOW for duplicates
          },
          getPointRadius: ({ zoomLevel }) => {
            return zoomLevel > 17 ? 12 : 8;
          },
          getLabel: ({ feature }) => {
            return feature?.properties?.label || 'Unknown';
          },
        },
        styleRules: [
          {
            style: {
              pointRadius: 20,
              fillColor: '${getColor}',
              fillOpacity: 0.8,
              strokeColor: '${getColor}',
              strokeWidth: 2,
              strokeOpacity: 1,
              label: '${getLabel}',
              labelYOffset: -15,
              fontColor: '#000000',
              fontSize: '11px',
              fontWeight: 'bold',
            },
          },
        ],
      });
    } catch (e) {
      logDev('wmeph_dupe_labels layer error:', e.message);
    }

    // Don't Add checkbox for dupe labels layer using LayerSwitcher
    // sdk.LayerSwitcher.addLayerCheckbox({ name: 'WMEPH Dupe Labels', isChecked: true });

    if (getWMEPHSetting('WMEPH-featuresExamined') === null) {
      setWMEPHSetting('WMEPH-featuresExamined', '0'); // Storage for whether the User has pressed the button to look at updates
    }

    createObserver();

    const xrayMode = getWMEPHSetting('WMEPH_xrayMode_enabled') === 'true';

    // X-ray Mode: Fade roads/satellite/mapComments to see map details underneath
    // Uses sdk.Map.addStyleRuleToLayer() to reduce opacity of background layers
    sdk.LayerSwitcher.addLayerCheckbox({ name: 'WMEPH x-ray mode', isChecked: xrayMode });
    if (xrayMode) setTimeout(() => toggleXrayMode(true), 2000);
    sdk.Events.on({
      eventName: 'wme-layer-checkbox-toggled',
      eventHandler: (payload) => {
        if (payload.name === 'WMEPH x-ray mode') {
          toggleXrayMode(payload.checked);
        } else if (payload.name === 'WMEPH Dupe Labels') {
          if (payload.checked) redrawLayer(_dupeLayer);
          else sdk.Map.removeAllFeaturesFromLayer({ layerName: _dupeLayer });
        }
      },
    });

    // Whitelist initialization - use only compressed version (uncompressed is optional backup)
    const compressedWL = localStorage.getItem(WL_LOCAL_STORE_NAME_COMPRESSED);
    const uncompressedWL = localStorage.getItem(WL_LOCAL_STORE_NAME);

    if (compressedWL) {
      // Try to decompress and load compressed version
      try {
        const decompressed = LZString.decompressFromUTF16(compressedWL);
        if (validateWLS(decompressed)) {
          loadWhitelistFromLS(true);
          // Log successful decompression with state breakdown
          const stateCount = {};
          let totalVenues = 0;
          for (const venueID in _venueWhitelist) {
            if (venueID !== '1.1.1') {
              totalVenues++;
              const state = _venueWhitelist[venueID].state || 'Unknown';
              stateCount[state] = (stateCount[state] || 0) + 1;
            }
          }
          logDev(`✓ Compressed whitelist decompressed OK - ${totalVenues} venues, by state: ${JSON.stringify(stateCount)}`);
        } else {
          throw new Error('Decompressed data failed validation');
        }
      } catch (e) {
        logDev('✗ Error with compressed whitelist:', e.message);
        if (uncompressedWL && validateWLS(uncompressedWL)) {
          // Fallback to uncompressed backup (exists only if WL was < 4.8MB when saved)
          loadWhitelistFromLS(false);
          const stateCount = {};
          let totalVenues = 0;
          for (const venueID in _venueWhitelist) {
            if (venueID !== '1.1.1') {
              totalVenues++;
              const state = _venueWhitelist[venueID].state || 'Unknown';
              stateCount[state] = (stateCount[state] || 0) + 1;
            }
          }
          logDev(`⚠ Using uncompressed backup - ${totalVenues} venues, by state: ${JSON.stringify(stateCount)}`);
        } else {
          logDev('✗ No valid whitelist found, creating new');
          _venueWhitelist = { '1.1.1': { Placeholder: {} } };
          saveWhitelistToLS(true);
        }
      }
    } else if (uncompressedWL && validateWLS(uncompressedWL)) {
      // Legacy: only uncompressed exists (shouldn't happen with current code)
      loadWhitelistFromLS(false);
      const stateCount = {};
      let totalVenues = 0;
      for (const venueID in _venueWhitelist) {
        if (venueID !== '1.1.1') {
          totalVenues++;
          const state = _venueWhitelist[venueID].state || 'Unknown';
          stateCount[state] = (stateCount[state] || 0) + 1;
        }
      }
      logDev(`⚠ Loading legacy uncompressed whitelist (no compressed found) - ${totalVenues} venues, by state: ${JSON.stringify(stateCount)}`);
      saveWhitelistToLS(true); // Create compressed version for future
    } else {
      // No whitelist found
      logDev('✗ No whitelist found, creating new');
      _venueWhitelist = { '1.1.1': { Placeholder: {} } };
      saveWhitelistToLS(true);
    }

    if (USER.name === 'ggrane') {
      _searchResultsWindowSpecs = `"resizable=yes, top=${Math.round(window.screen.height * 0.1)}, left=${Math.round(window.screen.width * 0.3)}, width=${Math.round(
        window.screen.width * 0.86,
      )}, height=${Math.round(window.screen.height * 0.8)}"`;
    }

    // Settings setup
    if (!localStorage.getItem(SETTING_IDS.gLinkWarning)) {
      // store settings so the warning is only given once
      localStorage.setItem(SETTING_IDS.gLinkWarning, '0');
    }
    if (!localStorage.getItem(SETTING_IDS.sfUrlWarning)) {
      // store settings so the warning is only given once
      localStorage.setItem(SETTING_IDS.sfUrlWarning, '0');
    }

    sdk.Events.on({
      eventName: 'wme-map-mouse-move',
      eventHandler: (e) =>
        errorHandler(() => {
          _wmephMousePosition = { lat: e.lat, lon: e.lon };
        }),
    });

    // Add zoom shortcut
    SHORTCUT.add('Control+Alt+Z', zoomPlace);

    // Add Color Highlighting shortcut
    SHORTCUT.add('Control+Alt+h', () => {
      $('#WMEPH-ColorHighlighting').trigger('click');
    });

    await addWmephTab(); // initialize the settings tab

    // Event listeners
    sdk.Events.on({
      eventName: 'wme-selection-changed',
      eventHandler: () => {
        logDev('selectionchanged');
        errorHandler(updateWmephPanel, true);
      },
    });
    sdk.Events.on({
      eventName: 'wme-data-model-objects-saved',
      eventHandler: () => errorHandler(destroyDupeLabels),
    });
    sdk.Events.on({
      eventName: 'wme-data-model-objects-saved',
      eventHandler: (event) =>
        errorHandler(() => {
          // SDK passes {dataModelName, objectIds}, convert to venue objects
          if (event?.objectIds && Array.isArray(event.objectIds)) {
            const savedVenues = event.objectIds.map((id) => sdk.DataModel.Venues.getById({ venueId: id })).filter((v) => v);
            syncWL(savedVenues);
          }
        }),
    });
    sdk.Events.on({
      eventName: 'wme-data-model-objects-changed',
      eventHandler: (event) =>
        errorHandler(() => {
          // SDK passes {dataModelName, objectIds}, convert to venue objects
          if (event?.objectIds && Array.isArray(event.objectIds)) {
            const changedVenues = event.objectIds.map((id) => sdk.DataModel.Venues.getById({ venueId: id })).filter((v) => v);
            onVenuesChanged(changedVenues);
          }
        }),
    });
    window.addEventListener('beforeunload', onWindowBeforeUnload, false);

    // Remove any temporary ID values (ID < 0) from the WL store at startup.
    let removedWLCount = 0;
    Object.keys(_venueWhitelist).forEach((venueID) => {
      if (venueID < 0) {
        delete _venueWhitelist[venueID];
        removedWLCount += 1;
      }
    });
    if (removedWLCount > 0) {
      saveWhitelistToLS(true);
      logDev(`Removed ${removedWLCount} venues with temporary ID's from WL store`);
    }

    _catTransWaze2Lang = I18n.translations[_userLanguage].venues.categories; // pulls the category translations

    // Split out state-based data
    const _stateHeaders = PNH_DATA.states[0].split('|');
    _psStateIx = _stateHeaders.indexOf('ps_state');
    _psState2LetterIx = _stateHeaders.indexOf('ps_state2L');
    _psRegionIx = _stateHeaders.indexOf('ps_region');
    _psGoogleFormStateIx = _stateHeaders.indexOf('ps_gFormState');
    _psDefaultLockLevelIx = _stateHeaders.indexOf('ps_defaultLockLevel');
    // ps_requirePhone_ix = _stateHeaders.indexOf('ps_requirePhone');
    // ps_requireURL_ix = _stateHeaders.indexOf('ps_requireURL');
    _psAreaCodeIx = _stateHeaders.indexOf('ps_areacode');

    // Set up Run WMEPH button once place is selected
    updateWmephPanel();

    sdk.Events.on({
      eventName: 'wme-data-model-objects-changed',
      eventHandler: () => {
        errorHandler(() => {
          if ($('#WMEPH_banner').length > 0) {
            updateServicesChecks();
            assembleServicesBanner();
          }
        });
      },
    });

    log('Starting Highlighter');

    // CRITICAL: Activate data model event tracking before setting up listeners
    sdk.Events.trackDataModelEvents({ dataModelName: 'venues' });

    bootstrapWmephColorHighlights();

    // Apply initial filter highlights
    if ($('#WMEPH-ShowFilterHighlight').prop('checked')) {
      updateFilterHighlights();
    }
  } // END placeHarmonizer_init function

  /**
   * Clears all highlights from the map layer and rebuilds based on enabled settings.
   * Calls individual highlight update functions (color severity, parking lot type, filter) if enabled.
   * Performs single layer redraw at the end for efficiency.
   * Called when user toggles highlight settings or when data changes significantly.
   */
  function refreshAllHighlights() {
    // Don't highlight if WME Venues layer is hidden
    const venuesLayerVisible = sdk.Map.isLayerVisible({ layerName: 'venues' });
    if (!venuesLayerVisible) {
      try {
        sdk.Map.removeAllFeaturesFromLayer({ layerName: _layer });
      } catch (e) {
        logDev('Error clearing highlights layer:', e);
      }
      return;
    }

    // Clear layer once
    try {
      sdk.Map.removeAllFeaturesFromLayer({ layerName: _layer });
    } catch (e) {
      logDev('Error clearing highlights layer:', e);
    }

    // Rebuild all enabled highlight types
    const colorHighlightingEnabled = getWMEPHSetting('WMEPH-ColorHighlighting') === '1';
    const parkingLotHighlightingEnabled = $('#WMEPH-PLATypeFill').prop('checked');
    const filterHighlightingEnabled = $('#WMEPH-ShowFilterHighlight').prop('checked');

    if (colorHighlightingEnabled) {
      applyHighlightsTest(sdk.DataModel.Venues.getAll(), false); // Use cache to avoid unnecessary recalculation on pan events
    }
    if (parkingLotHighlightingEnabled) {
      updateParkingLotHighlights(true); // Pass true to skip internal clear
    }
    if (filterHighlightingEnabled) {
      updateFilterHighlights(true); // Pass true to skip internal clear
    }

    // Redraw once after all updates
    if (_layer) {
      redrawLayer(_layer);
    }
  }

  /**
   * Updates map highlighting for parking lots based on their type (public, restricted, private).
   * Queries all venues, identifies parking lot types via SDK, and adds colored features to map layer.
   * Optionally skips clearing layer for efficiency when called from refreshAllHighlights.
   * Reapplies filter highlights on top if they are also enabled.
   * @param {boolean} skipClear If true, skips clearing the layer (assume caller will handle it).
   */
  function updateParkingLotHighlights(skipClear = false) {
    if (!$('#WMEPH-PLATypeFill').prop('checked')) {
      return;
    }

    // Don't highlight if WME Venues layer is hidden
    if (!sdk.Map.isLayerVisible({ layerName: 'venues' })) {
      return;
    }

    try {
      const venues = sdk.DataModel.Venues.getAll();
      const parkingLotsToAdd = [];

      venues.forEach((v) => {
        if (!v || !v.geometry || !v.id) return;
        try {
          const parkingType = sdk.DataModel.Venues.ParkingLot.getParkingLotType({ venueId: v.id });
          if (parkingType) {
            parkingLotsToAdd.push({ venue: v, parkingType });
          }
        } catch (e) {
          logDev(`Error checking ${v.name}:`, e.message);
        }
      });

      // Remove old parking lot features before adding new ones (unless skipClear from refreshAllHighlights)
      if (!skipClear) {
        try {
          // We need to identify and remove parking lot features
          // For now, we'll clear and rebuild both parking + filter
          sdk.Map.removeAllFeaturesFromLayer({ layerName: _layer });
        } catch (e) {
          logDev('Error clearing highlights layer:', e);
        }
      }

      parkingLotsToAdd.forEach(({ venue, parkingType }) => {
        try {
          const feature = {
            type: 'Feature',
            id: `parking_${venue.id}`,
            geometry: venue.geometry,
            properties: {
              name: venue.name,
              parkingType: parkingType,
              isPoint: venue.geometry?.type === 'Point',
              highlightType: 'parking',
              isResidential: venue.residential === true || venue.categories?.includes('RESIDENTIAL'),
            },
          };
          sdk.Map.addFeatureToLayer({
            layerName: _layer,
            feature: feature,
          });
        } catch (err) {
          logDev(`Error adding parking lot ${venue.id}:`, err);
        }
      });

      // Reapply filter highlights on top if enabled
      if ($('#WMEPH-ShowFilterHighlight').prop('checked')) {
        updateFilterHighlights(true);
      }
    } catch (err) {
      logDev('Error updating parking lot highlights:', err);
    }
  }

  /**
   * Updates map highlighting for venues that don't have PARKING_FOR_CUSTOMERS service.
   * Identifies places missing customer parking and highlights them on the map layer.
   * Excludes certain venue categories that shouldn't require parking service.
   * Optionally skips clearing layer for efficiency when called from refreshAllHighlights.
   * @param {boolean} skipClear If true, skips clearing the layer (assume caller will handle it).
   */
  function updateFilterHighlights(skipClear = false) {
    if (!$('#WMEPH-ShowFilterHighlight').prop('checked')) {
      return;
    }

    // Don't highlight if WME Venues layer is hidden
    if (!sdk.Map.isLayerVisible({ layerName: 'venues' })) {
      return;
    }

    try {
      const venues = sdk.DataModel.Venues.getAll();
      const featuresToAdd = [];
      // Do not highlight places if any of these are the primary category.
      const CATS_TO_IGNORE_CUSTOMER_PARKING_HIGHLIGHT = [
        'BRIDGE',
        'CANAL',
        'CHARGING_STATION',
        'CONSTRUCTION_SITE',
        'ISLAND',
        'JUNCTION_INTERCHANGE',
        'NATURAL_FEATURES',
        'PARKING_LOT',
        'RESIDENCE_HOME',
        'RIVER_STREAM',
        'SEA_LAKE_POOL',
        'SWAMP_MARSH',
        'TUNNEL',
        'RESIDENTIAL', // SDK residential category
      ];

      venues.forEach((v) => {
        // Filter: exclude venues with PARKING_FOR_CUSTOMERS service or certain categories (including PARKING_LOT)
        if (v.services?.includes('PARKING_FOR_CUSTOMERS') || v.categories?.some((cat) => CATS_TO_IGNORE_CUSTOMER_PARKING_HIGHLIGHT.includes(cat))) {
          return;
        }

        // Convert SDK geometry to GeoJSON feature
        const feature = {
          type: 'Feature',
          id: `filter_highlight_${v.id}`,
          geometry: v.geometry,
          properties: {
            wmephHighlight: '1',
            venueId: v.id,
            isPoint: v.geometry?.type === 'Point',
            highlightType: 'filter',
            isResidential: v.residential === true || v.categories?.includes('RESIDENTIAL'),
          },
        };
        featuresToAdd.push(feature);
      });

      // Add all filter features to highlights layer
      featuresToAdd.forEach((feature) => {
        sdk.Map.addFeatureToLayer({
          layerName: _layer,
          feature: feature,
        });
      });
    } catch (e) {
      logDev('Error updating filter highlights:', e);
    }
  }

  /*******************************************
   *   Performance monitoring FUNCTION
   ******************************************/
  let wmephStats = {
    harmonizeCount: 0,
    totalHarmonizeTime: 0,
    lastHarmonizeTime: 0,
    maxHarmonizeTime: 0,
    cacheHits: 0,
    cacheMisses: 0,
    totalCacheHitTime: 0,
    totalCacheMissTime: 0,
    lastCacheHitTime: 0,
    lastCacheMissTime: 0,
    maxCacheHitTime: 0,
    maxCacheMissTime: 0,
  };

  function estimateObjectSize(obj) {
    if (!obj) return 0;
    try {
      const json = JSON.stringify(obj);
      return (new TextEncoder().encode(json).length / 1048576).toFixed(1);
    } catch (e) {
      return 'N/A';
    }
  }

  function initPerformancePanel() {
    const panel = document.createElement('div');
    panel.id = 'wmeph-debug-panel';
    panel.style.cssText = `
      position: fixed; bottom: 20px; left: 20px;
      background: rgba(0, 0, 0, 0.9);
      color: #0f0;
      font-family: monospace;
      font-size: 11px;
      padding: 10px;
      border: 1px solid #0f0;
      z-index: 10000;
      max-width: 250px;
      cursor: move;
      user-select: none;
    `;

    // Make panel draggable
    let isDragging = false;
    let dragOffsetX = 0;
    let dragOffsetY = 0;

    panel.addEventListener('mousedown', (e) => {
      isDragging = true;
      dragOffsetX = e.clientX - panel.getBoundingClientRect().left;
      dragOffsetY = e.clientY - panel.getBoundingClientRect().top;
    });

    document.addEventListener('mousemove', (e) => {
      if (isDragging) {
        panel.style.left = `${e.clientX - dragOffsetX}px`;
        panel.style.top = `${e.clientY - dragOffsetY}px`;
        panel.style.bottom = 'auto';
        panel.style.right = 'auto';
      }
    });

    document.addEventListener('mouseup', () => {
      isDragging = false;
    });

    document.body.appendChild(panel);

    setInterval(() => {
      const totalMem = performance.memory ? (performance.memory.usedJSHeapSize / 1048576).toFixed(1) : 'N/A';
      const pnhMem = estimateObjectSize(PNH_DATA);
      const cacheMem = estimateObjectSize(_resultsCache);
      const wlMem = estimateObjectSize(_venueWhitelist);
      const scriptMem = (parseFloat(pnhMem || 0) + parseFloat(cacheMem || 0) + parseFloat(wlMem || 0)).toFixed(1);
      const totalCacheOps = wmephStats.cacheHits + wmephStats.cacheMisses;
      const cacheHitRate = totalCacheOps > 0 ? ((wmephStats.cacheHits / totalCacheOps) * 100).toFixed(1) : 'N/A';
      const avgCacheHitTime = wmephStats.cacheHits > 0 ? (wmephStats.totalCacheHitTime / wmephStats.cacheHits).toFixed(0) : 0;
      const avgCacheMissTime = wmephStats.cacheMisses > 0 ? (wmephStats.totalCacheMissTime / wmephStats.cacheMisses).toFixed(0) : 0;

      panel.innerHTML = `
        <div>🔧 WMEPH Debug</div>
        <div>Harmonize count: ${wmephStats.harmonizeCount}</div>
        <div>Last time: ${wmephStats.lastHarmonizeTime.toFixed(0)}ms</div>
        <div>Max time: ${wmephStats.maxHarmonizeTime.toFixed(0)}ms</div>
        <div>Avg time: ${wmephStats.harmonizeCount > 0 ? (wmephStats.totalHarmonizeTime / wmephStats.harmonizeCount).toFixed(0) : 0}ms</div>
        <div style="border-top: 1px solid #0f0; margin-top: 5px; padding-top: 5px;">
          <div><strong>Cache Performance</strong></div>
          <div>Hits: ${wmephStats.cacheHits} | Misses: ${wmephStats.cacheMisses} (${cacheHitRate}%)</div>
          <div>Hit avg: ${avgCacheHitTime}ms | Miss avg: ${avgCacheMissTime}ms</div>
          <div>Hit max: ${wmephStats.maxCacheHitTime.toFixed(0)}ms | Miss max: ${wmephStats.maxCacheMissTime.toFixed(0)}ms</div>
        </div>
        <div style="border-top: 1px solid #0f0; margin-top: 5px; padding-top: 5px;">
          <div>Total heap: ${totalMem}MB</div>
          <div>Script memory: ${scriptMem}MB</div>
          <div style="font-size: 10px;">PNH: ${pnhMem}MB | Cache: ${cacheMem}MB | WL: ${wlMem}MB</div>
        </div>
      `;
    }, 1000);
  }

  function devTestCode() {
    const userInfo = sdk.State.getUserInfo();
    if (userInfo && (userInfo.userName === 'MapOMatic' || userInfo.userName === 'JS55CT')) {
      // For debugging purposes.  May be removed when no longer needed.
      unsafeWindow.PNH_DATA = PNH_DATA;
      unsafeWindow.WMEPH_FLAG = Flag;
      unsafeWindow._wmephBetaList = _wmephBetaList;
      initPerformancePanel();

      // Log full PNH data structure
      console.log('PNH_DATA:', PNH_DATA);
      console.log('_wmephBetaList:', _wmephBetaList);
    }
  }

  /*******************************************
   *   MAIN BOOTSTRAP FUNCTION
   ******************************************/

  async function wmephbootstrap() {
    // Quit if another version of WMEPH is already running.
    if (unsafeWindow.wmephRunning) {
      // Don't use WazeWrap alerts here. It isn't loaded yet.
      alert('Multiple versions of WME Place Harmonizer are turned on. Only one will be enabled.');
      return;
    }
    unsafeWindow.wmephRunning = 1;

    // Initialize SDK early and populate CAT before PNH data is downloaded
    log('Initializing SDK and categories...');
    sdk = await bootstrap({
      scriptName: SCRIPT_NAME,
      scriptUpdateMonitor: {
        downloadUrl: IS_BETA_VERSION ? dec(BETA_DOWNLOAD_URL) : PROD_DOWNLOAD_URL,
        scriptVersion: SCRIPT_VERSION,
      },
    });
    try {
      initializeCategories();
    } catch (e) {
      logDev('Failed to initialize categories:', e);
    }

    // Start downloading the PNH spreadsheet data in the background.  Starts the script once data is ready.
    await Pnh.downloadAllData();

    log('Starting Place Harmonizer initialization');
    await placeHarmonizerInit();
    //devTestCode();
    showScriptInfoAlert();
  }

  wmephbootstrap();
})();