{
  "version": 3,
  "sources": ["basemap_manage_core.js"],
  "sourcesContent": ["/*\n * static/js/basemap_manage_core.js \u2014 pure helpers behind the\n * \"Manage downloads\" sheet (SNOW-588).\n *\n * The sheet answers two questions the map could not: *what have I stored\n * offline on this device*, and *what is it costing me*. This module holds\n * every part of that answer which is arithmetic or ordering rather than\n * DOM \u2014 the same split ``basemap_download_core.js`` uses, and for the same\n * reason: ``static/js/map.js`` is one file of IIFEs with no module\n * boundary and is not unit-tested, so logic that matters has to live\n * somewhere a test can reach it. ``static/js/map_downloads_manager.js`` is\n * the DOM half and does nothing this module could do instead.\n *\n * It works on areas as ``static/js/map.js``'s ``basemapDownloadedAreas()``\n * hands them over \u2014 ``{id, name?, bytes, savedAt}``, keyed by the id that\n * also names the area's Cache Storage bucket. ``name`` is always present\n * except for an orphaned bucket with no record at all (SNOW-612) \u2014\n * SNOW-635 review: an unrenamed custom area's numbered default is filled\n * in there too, so this module never has to special-case it.\n *\n * That normalising step is SNOW-586's, and this surface consumes it rather\n * than reading storage itself, because a download is recorded in **two**\n * places: ``basemap.regions`` (an array, one entry per downloaded region,\n * keyed by ``region_id``) and (SNOW-635) ``basemap.customAreas`` (an\n * array too now \u2014 any number of user-framed areas, not just one). Neither\n * key is the bucket id \u2014 that is ``areaIdForRegion(region_id)`` or the\n * custom area's own minted id (``generateCustomAreaId`` /\n * ``isCustomAreaId``) \u2014 so a second reader here would have to re-derive\n * the mapping and would be free to drift from the eviction path that\n * already owns it.\n *\n * Sizes are the STORED byte figure, never a live measurement. An area is\n * thousands of cache entries, so summing them at render time would mean a\n * ``cache.match()`` per entry every time the sheet opens. SNOW-586 already\n * has to keep the standing total for ``planEviction``, so the number\n * exists and this surface consumes it rather than deriving a second,\n * subtly different one.\n *\n * Exports (frozen ``self.pwaBasemapManageCore``):\n *\n *   BUDGET_CHOICES_MB\n *     The offerable budgets, in megabytes. A fixed set of presets rather\n *     than a free-entry field or a slider: SNOW-586 settled that a budget\n *     should be \"predictable and explainable\", and four named sizes are\n *     legible in a way \"you have chosen 743 MB\" is not.\n *   MIN_BUDGET_MB\n *     The smallest offerable budget, pinned to\n *     ``basemap_download_core.js``'s ``DOWNLOAD_CEILING_MB`` \u2014 a standing\n *     budget below the per-run ceiling would make some individual\n *     downloads structurally impossible to keep, which is exactly the\n *     \"two limits that do not talk to each other\" bug SNOW-586 exists to\n *     fix. Re-declared rather than imported: these are two separate\n *     page-scope IIFEs with no module system between them, so the\n *     round-trip assertion in tests/js/test_basemap_manage_core.js is what\n *     keeps the two honest.\n *   megabytesToBytes(mb) / formatMegabytes(bytes)\n *     The MB \u2194 byte conversion and its display form. \"MB\" here means\n *     1024\u00D71024, matching ``basemap_download_core.js``'s ``buildBlob`` and\n *     ``apps/regions/services/basemap_tiles.py`` \u2014 every size the user has\n *     ever been shown for a download is computed that way, so a manage\n *     surface using decimal megabytes would report a different number for\n *     the same download.\n *   clampBudgetMb(mb)\n *     The nearest offerable budget to ``mb``, for reading a stored (or\n *     absent, or corrupt) ``basemap.budgetMb`` back.\n *   budgetSummary(areas, budgetBytes)\n *     Standing usage against the budget \u2014 the sheet's running total.\n *   manageRows(areas, options)\n *     The ordered, labelled row model the sheet renders. SNOW-635: every\n *     row's ``label`` is ``area.name || id`` uniformly \u2014 an unrenamed\n *     custom area's numbered \"Custom area N\" default is filled into\n *     ``name`` upstream, by ``map.js``'s ``basemapDownloadedAreas()``\n *     (which has the translation catalogue this module does not), not\n *     built here.\n *   groupRowsByPresence(rows)\n *     SNOW-832: manageRows' own rows grouped by the BASEMAP each was\n *     downloaded under, in the picker's own order \u2014 the sheet's group\n *     headings \u2014 without re-sorting any group. Replaces SNOW-645's\n *     groupRowsByKind; a row's kind moved down into its own meta line.\n *   reconcileAreas(recorded, storedAreaIds, bytesById, accountAreas)\n *     The recorded areas unioned with the pinned buckets actually on\n *     disk, so a download that failed partway is visible rather than\n *     stranded (SNOW-612), and (SNOW-749) with the areas on the user's\n *     ACCOUNT, so an area downloaded on another device \u2014 or evicted here\n *     to make room \u2014 is listed as a one-tap re-download rather than\n *     vanishing. Every entry carries ``onDevice`` and ``synced``; with no\n *     account list the output is what it always was.\n *   budgetSegments(areas)\n *     SNOW-645 review: the budget bar's segments, one per basemap\n *     actually stored (grouped and summed, not one per area), largest\n *     first with the keyless group always last.\n */\n\n(function () {\n  'use strict';\n\n  var BYTES_PER_MB = 1024 * 1024;\n\n  // Mirrors basemap_download_core.js's DOWNLOAD_CEILING_MB. See the module\n  // header for why this is a re-declaration rather than a read of that\n  // module, and which test holds the two together.\n  var MIN_BUDGET_MB = 200;\n\n  // The offerable budgets. MIN_BUDGET_MB is the floor by construction (a\n  // budget under the per-run ceiling could never hold a maximal download);\n  // 500 is SNOW-586's default and the middle option rather than the\n  // smallest, so the control reads as a dial with room in both directions\n  // rather than a ladder the user starts at the bottom of.\n  var BUDGET_CHOICES_MB = Object.freeze([MIN_BUDGET_MB, 500, 1000, 2000]);\n\n  // SNOW-586's default, and the value clampBudgetMb falls back to when it\n  // is handed something that is not a number at all.\n  var DEFAULT_BUDGET_MB = 500;\n\n  /**\n   * ``mb`` megabytes as a byte count.\n   *\n   * @param {number} mb\n   * @returns {number} ``0`` for anything non-finite or negative \u2014 a\n   *   corrupt stored budget must never produce a NaN the caller then\n   *   compares against, which would make every comparison false and read\n   *   as \"always under budget\".\n   */\n  function megabytesToBytes(mb) {\n    var n = Number(mb);\n    if (!Number.isFinite(n) || n < 0) return 0;\n    return n * BYTES_PER_MB;\n  }\n\n  /**\n   * ``bytes`` as a display string in megabytes.\n   *\n   * One decimal place below 100 MB and none at or above it: the decimal\n   * is what distinguishes two small downloads from each other, and is\n   * noise on a figure in the hundreds. Rounds to nearest rather than up \u2014\n   * ``buildBlob`` rounds a download's ESTIMATE up (an estimate that\n   * under-reads is the harmful direction), but this reports a size already\n   * on disk, where nearest is simply the truthful answer.\n   *\n   * Sub-megabyte sizes report as ``< 0.1 MB`` rather than ``0.0 MB``, so a\n   * real download never renders as costing nothing.\n   *\n   * @param {number} bytes\n   * @returns {string}\n   */\n  function formatMegabytes(bytes) {\n    var n = Number(bytes);\n    if (!Number.isFinite(n) || n <= 0) return '0 MB';\n    var mb = n / BYTES_PER_MB;\n    if (mb < 0.1) return '< 0.1 MB';\n    if (mb < 100) return mb.toFixed(1) + ' MB';\n    return String(Math.round(mb)) + ' MB';\n  }\n\n  /**\n   * The offerable budget nearest to ``mb``.\n   *\n   * Snapping to a choice rather than clamping to a range is what lets the\n   * stored ``basemap.budgetMb`` row be read straight into a ``<select>``\n   * without a \"no option matches\" state: an absent row, a hand-edited one,\n   * or one written by a future build offering different presets all\n   * resolve to something the control can actually show. Ties go to the\n   * larger choice \u2014 rounding a user's budget DOWN without being asked is\n   * the direction that silently evicts their downloads.\n   *\n   * ``null`` and ``''`` are rejected BEFORE the numeric coercion, not\n   * left to it. ``Number(null)`` and ``Number('')`` are both ``0``, which\n   * is perfectly finite and would snap to the SMALLEST offered budget \u2014\n   * so an unset or blanked-out row would hand the user the minimum\n   * instead of the default, shrinking their budget by the same unasked\n   * step the tie-break rule above exists to prevent.\n   *\n   * @param {number} mb\n   * @returns {number} One of ``BUDGET_CHOICES_MB``.\n   */\n  function clampBudgetMb(mb) {\n    if (mb === null || mb === undefined || mb === '') return DEFAULT_BUDGET_MB;\n    var n = Number(mb);\n    if (!Number.isFinite(n)) return DEFAULT_BUDGET_MB;\n    var best = BUDGET_CHOICES_MB[0];\n    var bestDistance = Math.abs(n - best);\n    for (var i = 1; i < BUDGET_CHOICES_MB.length; i += 1) {\n      var choice = BUDGET_CHOICES_MB[i];\n      var distance = Math.abs(n - choice);\n      if (distance <= bestDistance) {\n        best = choice;\n        bestDistance = distance;\n      }\n    }\n    return best;\n  }\n\n  /**\n   * Standing usage across every stored area, against the budget.\n   *\n   * @param {Array<{bytes?: number}>} areas The ``basemap.areas`` record.\n   *   Best-effort: a malformed entry counts as ``0`` rather than poisoning\n   *   the total with ``NaN``.\n   * @param {number} budgetBytes\n   * @returns {{usedBytes: number, budgetBytes: number, freeBytes: number,\n   *   pct: number, overBudget: boolean, count: number}} ``pct`` is clamped\n   *   to 0\u2013100 so it can drive a bar's width directly; ``overBudget``\n   *   carries the un-clamped truth, since a total above budget must still\n   *   be sayable once the bar has run out of room to say it. ``freeBytes``\n   *   floors at 0 for the same reason.\n   */\n  function budgetSummary(areas, budgetBytes) {\n    var list = Array.isArray(areas) ? areas : [];\n    var budget = Number(budgetBytes);\n    if (!Number.isFinite(budget) || budget < 0) budget = 0;\n\n    var used = 0;\n    for (var i = 0; i < list.length; i += 1) {\n      var bytes = Number(list[i] && list[i].bytes);\n      if (Number.isFinite(bytes) && bytes > 0) used += bytes;\n    }\n\n    var pct = budget > 0 ? (used / budget) * 100 : 0;\n    return {\n      usedBytes: used,\n      budgetBytes: budget,\n      freeBytes: Math.max(0, budget - used),\n      pct: Math.max(0, Math.min(100, pct)),\n      overBudget: used > budget,\n      count: list.length,\n    };\n  }\n\n  /**\n   * The ordered, labelled rows the sheet renders.\n   *\n   * Ordered **regions first, then custom areas, alphabetically within\n   * each** (SNOW-832), tie-broken by ``id`` so a set of identically-named\n   * areas renders in a stable order across re-opens.\n   *\n   * It was largest-first through SNOW-831, on the reasoning that the\n   * surface exists so a user can decide what to remove and the download\n   * worth removing is the big one. Hugo's handoff settles it the other\n   * way, and the reason is that the sheet stopped being a list: it is\n   * grouped by basemap now, so a row is FOUND before it is judged, and a\n   * list a reader has to scan for a name cannot be ordered by a number.\n   * The sizes are still on every row, and ``budgetSegments()`` re-sorts by\n   * bytes itself for the bar, so nothing that reads by size lost its\n   * ordering \u2014 only the list did.\n   *\n   * @param {Array<{id: string, name?: string, bytes?: number,\n   *   savedAt?: string, basemapKey?: string|null}>} areas Areas as\n   *   ``map.js``'s ``basemapDownloadedAreas()`` normalises them \u2014 the union\n   *   of the ``basemap.regions`` array and (SNOW-635) the\n   *   ``basemap.customAreas`` array, each already keyed by the id that\n   *   names its Cache Storage bucket. ``name`` is always populated for a\n   *   non-orphaned area \u2014 a region's is stored by the download itself; an\n   *   unrenamed custom area's default \"Custom area N\" is filled in by\n   *   ``basemapDownloadedAreas()`` itself (SNOW-635 review \u2014 see that\n   *   function's own comment for why the defaulting lives there and not\n   *   here or in the DOM layer). Only ``reconcileAreas``' orphan entries\n   *   (SNOW-612 \u2014 a bucket with no record at all) ever leave it unset.\n   *   ``basemapKey`` (SNOW-645) is null on a record written before this\n   *   ticket shipped, and always null on an orphan \u2014 both read as an\n   *   unknown basemap, never a wrong one. ``onDevice`` / ``synced`` /\n   *   ``bbox`` / ``regionId`` (SNOW-749) come from ``reconcileAreas``;\n   *   an area list built without it reads as on-device and unsynced,\n   *   which is what every such list was before that ticket.\n   * @param {{isCustomAreaId?: function(string): boolean}} [options]\n   *   ``isCustomAreaId`` is ``pwaBasemapDownloadCore.isCustomAreaId`` \u2014 an\n   *   area whose id it accepts is a user-framed download rather than a\n   *   region. A predicate rather than a single id (SNOW-586's\n   *   ``customAreaId`` string) because SNOW-635 gives every custom area\n   *   its OWN id (``generateCustomAreaId``); asking the core rather than\n   *   comparing against one fixed value keeps that id family private to\n   *   the module that mints it, the same way the ``region-<id>`` format\n   *   is never parsed out elsewhere either. There is no ``customLabel``\n   *   option any more (SNOW-635 review) \u2014 a custom area's label comes\n   *   from ``area.name`` like every other row's does, so this module no\n   *   longer needs a caller-supplied fallback string for it.\n   * @returns {Array<{id: string, kind: string, deletable: boolean,\n   *   orphaned: boolean, label: string, renameable: boolean,\n   *   bytes: number, savedAt: string, size: string, basemapKey: string,\n   *   onDevice: boolean, synced: boolean, redownloadable: boolean,\n   *   bbox: number[]|null, regionId: string, deps: string[],\n   *   z: Object|null, band: number[]|null, contentIncomplete: boolean,\n   *   contentAt: string, terrain: boolean, storedName: string}>}\n   *   SNOW-1139: ``contentAt`` is when the area's bulletins were fetched,\n   *   '' when never (and always '' for an orphan or account-only row).\n   *   SNOW-1072: ``terrain`` is whether the area includes terrain data.\n   *   SNOW-1136: ``storedName`` is the name as stored, '' when never\n   *   renamed \u2014 unlike ``label``, which is always something to show.\n   *   SNOW-936: ``deletable``, ``deps``, ``z`` and ``band`` were emitted\n   *   here for tickets' worth of releases without being named above. This\n   *   file is not ``@ts-check``'d, so this block is the ONLY contract a\n   *   caller has and nothing was checking it against the object literal a\n   *   screen below. The three data fields are not incidental \u2014\n   *   ``deps`` (SNOW-844) and ``z``/``band`` (SNOW-692) are what the sheet\n   *   judges render-completeness and slope coverage from, so a caller\n   *   reading only this would not know they were available to read.\n   *   SNOW-932: ``contentIncomplete`` says the area's BULLETINS are\n   *   behind, not that its map is broken \u2014 the two are independent halves,\n   *   and the sheet must not dim a row for it (the area IS available\n   *   offline). SNOW-749:\n   *   ``onDevice`` and ``synced`` are the\n   *   two independent facts a row now carries, and the four combinations\n   *   are all reachable \u2014 on both, on the device only (a download made\n   *   before the account gate, or one whose sync has not drained yet),\n   *   on the account only (another device's, or one evicted here), and\n   *   neither (an orphaned bucket on a signed-out device).\n   *   ``redownloadable`` is the account-only row's own affordance: the\n   *   sheet offers \"Download here\" instead of a size, and it is false\n   *   unless the row carries enough to act on \u2014 a region id, or a bbox.\n   */\n  function manageRows(areas, options) {\n    var list = Array.isArray(areas) ? areas : [];\n    var opts = options || {};\n    var rows = [];\n\n    for (var i = 0; i < list.length; i += 1) {\n      var area = list[i];\n      // No id means nothing could name its bucket, so nothing could\n      // delete it \u2014 a row that cannot be acted on is worse than absent.\n      if (!area || !area.id) continue;\n\n      var id = String(area.id);\n      var isCustom = typeof opts.isCustomAreaId === 'function' && opts.isCustomAreaId(id);\n      // SNOW-856 / SNOW-863 / SNOW-867: the shared z0-7 overview map is\n      // not a row on this panel, and not in its budget either.\n      //\n      // This took two wrong turns before it settled, and both are worth\n      // stating so the third does not repeat them. It was a row beside the\n      // user's own areas, which made it look like one of them and then\n      // denied it the one control they all have. It was briefly a neutral\n      // \"system files\" segment on the budget bar, which was closer but\n      // still charged the user's allowance for it.\n      //\n      // What it is: the app's own map data. Fetched once per basemap when\n      // that basemap is first SHOWN (`warmBaseLayerWideBand`), shared by\n      // every area, never chosen, never removable on its own \u2014 every area\n      // reads it once the camera passes their z10 floor. So it is not a\n      // download, and this panel \u2014 which is the user's downloads and the\n      // budget they set for them \u2014 is the wrong place to account for it.\n      // Account settings' \"Reset local data\" summary is the right one:\n      // that row is where the app's own storage is stated and cleared.\n      var isBase = typeof opts.isBaseLayerAreaId === 'function' && opts.isBaseLayerAreaId(id);\n      if (isBase) continue;\n      // SNOW-749: default true, so an area list built before this ticket\n      // (or by a caller that does not reconcile) reads as on-device \u2014\n      // which is what it was.\n      var onDevice = area.onDevice !== false;\n      // SNOW-635: a region's name is its real name; a custom area's own\n      // name (stored or defaulted \u2014 see this function's own docstring) is\n      // still something the user can override \u2014 REGIONS are never\n      // renameable, and neither is an ORPHANED custom bucket (SNOW-612):\n      // there is no record entry left to write a name onto, only a bare\n      // bucket id.\n      //\n      // SNOW-749: nor is an ACCOUNT-ONLY row. The rename this flag gates\n      // is the local one (`renameCustomArea` writes to\n      // `basemap.customAreas`), and there is no local record to write to\n      // for an area this device has never downloaded \u2014 the editor would\n      // accept a name and drop it. Renaming it where it does exist is a\n      // download away.\n      var renameable = isCustom && !area.orphaned && onDevice;\n\n      // Uniform for every row now: the record's own name (always present\n      // except for an orphan \u2014 see the docstring) falls back to the id,\n      // the only identifier left for something with no name at all.\n      //\n      // SNOW-1133: an ACCOUNT-ONLY area with no name \u2014 a custom area that\n      // was never renamed, whose \"Custom area N\" default exists only on\n      // the device that made it \u2014 asks the caller instead, so the row\n      // never reads as a raw `custom-<uuid>`.\n      var unnamed =\n        !area.name && !onDevice && typeof opts.unnamedLabel === 'function'\n          ? opts.unnamedLabel(area)\n          : '';\n      var label = area.name || unnamed || id;\n\n      var bytes = Number(area.bytes);\n      if (!Number.isFinite(bytes) || bytes < 0) bytes = 0;\n\n      rows.push({\n        id: id,\n        // SNOW-612: an orphaned bucket is still a region or a custom\n        // area \u2014 `orphaned` says the record is missing, not that it is a\n        // third kind of thing, so the sheet can label it without the\n        // caller having to re-derive which it was.\n        // SNOW-XXX: the design names the kind in words on every row \u2014\n        // \"Region \u00B7 Swisstopo (CH) \u00B7 13.6 MB\" \u2014 and a drop zone is its own\n        // kind, not a custom area with a particular name. The record says\n        // so (`type`), and an older record that predates the field falls\n        // back to the shape it was: a custom id is a custom area.\n        kind: isCustom ? (area.type === 'dropzone' ? 'dropzone' : 'custom') : 'region',\n        // Every row here is a download the user made and can remove. The\n        // flag stays because each renderer reads it, and because the next\n        // row type that cannot be removed should have somewhere to say so.\n        deletable: true,\n        orphaned: !!area.orphaned,\n        label: String(label),\n        renameable: renameable,\n        bytes: bytes,\n        savedAt: area.savedAt || '',\n        size: formatMegabytes(bytes),\n        // SNOW-645: absent on a record written before this ticket shipped,\n        // or on an orphan (no record at all) \u2014 both read as '', never a\n        // wrong basemap, so map_downloads_manager.js's buildRow removes\n        // the swatch+name line rather than showing an unknown one.\n        basemapKey: area.basemapKey || '',\n        // SNOW-749: the two facts the sheet paints four row states from.\n        // `synced` says the account knows about this area, which is NOT\n        // the same as it being available offline \u2014 the sheet must never\n        // let it green a sync dot.\n        onDevice: onDevice,\n        synced: !!area.synced,\n        // Only an account-only row is offered a download; one already on\n        // the device has nothing to fetch. A region needs its id, a\n        // custom area its box \u2014 without either there is nothing to hand\n        // the download control, so the affordance is withheld rather than\n        // rendered dead.\n        redownloadable:\n          !onDevice && (isCustom ? Array.isArray(area.bbox) : !!area.regionId),\n        bbox: Array.isArray(area.bbox) ? area.bbox : null,\n        regionId: area.regionId || '',\n        // SNOW-844: the render dependencies this area recorded, carried\n        // onto the row so the sheet can ask whether they are on disk. The\n        // check itself is not here \u2014 it needs Cache Storage, which this\n        // module deliberately never touches.\n        deps: Array.isArray(area.deps) ? area.deps : [],\n        // SNOW-692: the ground the area covers, carried for the same\n        // reason and checked by the same surface \u2014 but DERIVED rather than\n        // recorded, because a region's ~273 slope tiles would be 27.4 KB\n        // of record holding nothing that cannot be recomputed from this.\n        // A region carries the run's own `z` row spans; a custom area\n        // carries `bbox` (above) plus the `band` fetched over it.\n        z: area.z || null,\n        band: Array.isArray(area.band) ? area.band : null,\n        // SNOW-932: whether this area's CONTENT half fell short \u2014 its\n        // bulletins and weather, not its tiles. Unlike `incomplete`\n        // (SNOW-844), which the sheet derives from Cache Storage, this is\n        // a STORED fact: no cache read can tell a bulletin absent because\n        // the boundary contains none from one absent because the fetch\n        // fell over, so the run records it and this carries it through.\n        //\n        // Suppressed for an ORPHAN and for an ACCOUNT-ONLY row. Neither\n        // has anything on this device that could be short \u2014 an orphan is a\n        // bucket with no record behind it to have been written, and an\n        // account-only row was never downloaded here at all \u2014 and both\n        // would otherwise be offered a Refresh with nothing to refresh.\n        contentIncomplete: !area.orphaned && onDevice && !!area.contentIncomplete,\n        // SNOW-1139: when this area's bulletins were fetched \u2014 the sheet\n        // dates a row whose content is from an earlier day. Suppressed\n        // for an orphan and an account-only row on the same grounds as\n        // `contentIncomplete`: neither holds content on this device.\n        contentAt: !area.orphaned && onDevice && area.contentAt ? String(area.contentAt) : '',\n        // SNOW-1072: whether the area includes terrain data. Carried for\n        // two readers: the completeness check, which judges an opted-in\n        // row on its class tiles too, and \"Download here\", which repeats\n        // the account row's choice on this device.\n        terrain: !!area.terrain,\n        // SNOW-1136: the name as STORED \u2014 '' for an area never renamed,\n        // whose `label` is a localised default. Read by \"Download here\",\n        // which restores an account-only area under its stored name.\n        storedName: typeof area.storedName === 'string' ? area.storedName : '',\n      });\n    }\n\n    // SNOW-832: kind, then name. See this function's own docstring for why\n    // the size axis stopped being the ordering one.\n    //\n    // SNOW-856 puts `base` FIRST rather than folding it in alphabetically:\n    // it is the ground every other row in its group sits on, and a shared\n    // foundation listed between two of the things that depend on it reads\n    // as a peer of theirs. A group has at most one.\n    // SNOW-1133: every kind `rows.push` above can produce has a rank. A\n    // drop zone had none, so the subtraction was NaN, which `sort` reads\n    // as \"equal\" for one pair and not another \u2014 the order then depended\n    // on the order the areas were read in. Unknown kinds sort last.\n    var KIND_ORDER = { base: 0, region: 1, custom: 2, dropzone: 3 };\n    function kindRank(kind) {\n      return Object.prototype.hasOwnProperty.call(KIND_ORDER, kind) ? KIND_ORDER[kind] : 99;\n    }\n    rows.sort(function (a, b) {\n      if (a.kind !== b.kind) return kindRank(a.kind) - kindRank(b.kind);\n      var byLabel = a.label.localeCompare(b.label);\n      if (byLabel !== 0) return byLabel;\n      return a.id.localeCompare(b.id);\n    });\n\n    return rows;\n  }\n\n  /**\n   * Split the rows into the two groups the panel lists (SNOW-XXX).\n   *\n   * By PRESENCE \u2014 is this area on this device \u2014 where SNOW-832 grouped by\n   * basemap with a coloured heading and a per-basemap total.\n   *\n   * The basemap has not stopped mattering; it moved. It is a dot on each\n   * row and a word in its subtitle, which scales as areas accumulate in a\n   * way headings do not: a user with eight areas across three basemaps had\n   * three headings, three totals and three rules between them and their\n   * list. What they are scanning for is \"what have I got here\", and the\n   * one split that changes what a row can DO is whether it is here at all\n   * \u2014 an account-only row can be downloaded, an on-device row can be\n   * removed, and never both.\n   *\n   * An empty group is omitted rather than rendered with a heading and\n   * nothing under it.\n   *\n   * @param {Array<{onDevice?: boolean}>} rows As ``manageRows`` returns\n   *   them, in its own order \u2014 preserved within each group.\n   * @returns {Array<{key: string, rows: Array<Object>}>} ``device`` first,\n   *   then ``account``; either may be absent.\n   */\n  function groupRowsByPresence(rows) {\n    var list = Array.isArray(rows) ? rows : [];\n    var device = [];\n    var account = [];\n    for (var i = 0; i < list.length; i += 1) {\n      var row = list[i];\n      if (!row) continue;\n      if (row.onDevice === false) account.push(row);\n      else device.push(row);\n    }\n    var groups = [];\n    if (device.length) groups.push({ key: 'device', rows: device });\n    if (account.length) groups.push({ key: 'account', rows: account });\n    return groups;\n  }\n\n  /**\n   * The union of what is RECORDED as downloaded, what is actually stored\n   * in Cache Storage (SNOW-612), and what is on the user's ACCOUNT\n   * (SNOW-749).\n   *\n   * Three sources, one keyed join on the area id \u2014 the id that names the\n   * Cache Storage bucket and is also what the server stores verbatim, so\n   * there is no id mapping to maintain anywhere.\n   *\n   * **The record.** A download that fails partway leaves its pinned bucket\n   * on disk with no ``basemap.regions`` / ``basemap.customAreas`` record,\n   * because the record is only written when a run completes. The byte\n   * budget never counted that bucket and the manage sheet could not list\n   * it, so the stranded quota was invisible to the user and to the\n   * planner \u2014 accumulating silently across failed attempts until the\n   * origin ran out of room.\n   *\n   * **The account.** An area the user downloaded on another device, or\n   * downloaded here and later evicted to make room, has a row on the\n   * account and nothing on this device. It becomes an entry with\n   * ``onDevice: false`` and ``bytes: 0``, carrying the account row's own\n   * name, basemap and geometry \u2014 enough for the sheet to offer it as a\n   * one-tap re-download. It is NOT ``orphaned``: an orphan is a failed\n   * download's leftovers on THIS device, which is a different fact with a\n   * different remedy.\n   *\n   * Reading the buckets and the account is what makes both visible; this\n   * is the arithmetic half, kept here so it can be tested without Cache\n   * Storage or a network.\n   *\n   * Precedence is local-first for everything except ``synced``. The\n   * recorded entry always wins for an id present in more than one source,\n   * because it carries the name, the timestamp and the byte figure a\n   * bucket id cannot and an account row does not have.\n   *\n   * With ``accountAreas`` empty or absent the output is what it was before\n   * SNOW-749 plus two flags \u2014 ``onDevice: true`` and ``synced: false`` on\n   * every entry, which is the truth for a device whose account list could\n   * not be read. That is the anonymous path, the flag-off path and the\n   * offline path, and it is the one that must not move.\n   *\n   * @param {Array<{id: string, name?: string, bytes?: number,\n   *   savedAt?: string, basemapKey?: string|null, bbox?: number[]|null,\n   *   regionId?: string}>} recorded Areas derived from the stored records.\n   * @param {string[]} storedAreaIds Area ids with a pinned bucket present\n   *   in Cache Storage, as ``map.js``'s ``pinnedBucketAreaIds()`` reads\n   *   them back off ``caches.keys()``.\n   * @param {Object<string, number>} [bytesById] Measured sizes for the\n   *   orphans, keyed by area id. An orphan with no measurement counts as\n   *   0 bytes \u2014 visible and deletable, but not guessed at.\n   * @param {Array<{area_id: string, name?: string, basemap_key?: string,\n   *   bbox?: number[]|null, region_id?: string, created_at?: string,\n   *   terrain?: boolean, shape?: string}>}\n   *   [accountAreas] The rows on the user's account, in the server's own\n   *   JSON shape (``downloads/areas.json``) \u2014 snake_case, because\n   *   translating it in the transport layer would put the same mapping in\n   *   two places. Absent for an anonymous, offline or flag-off page.\n   * @returns {Array<{id: string, name?: string, bytes: number,\n   *   savedAt?: string, orphaned: boolean, basemapKey: string|null,\n   *   onDevice: boolean, synced: boolean, bbox: number[]|null,\n   *   regionId: string, deps: string[], type?: string, z?: Object|null,\n   *   band?: number[]|null, contentIncomplete?: boolean,\n   *   contentAt?: string, principal?: string|null}>} The recorded\n   *   areas in their original order, then any orphans id-ascending, then\n   *   any account-only areas id-ascending \u2014 so the sheet's own sort has a\n   *   stable input.\n   *\n   *   SNOW-936: ``deps``, ``type``, ``z`` and ``band`` were emitted here\n   *   without being named, and of the two typedefs this ticket corrects\n   *   it is the one that matters. This function REBUILDS every entry\n   *   field by field rather than spreading it, so a field it does not\n   *   name is a field that silently never reaches the sheet \u2014 which is\n   *   exactly how SNOW-932 lost one, having read this block and believed\n   *   it. A typedef that under-describes a rebuild is not a cosmetic\n   *   error; it is the documentation that hides the hop.\n   *\n   *   ``type``, ``z``, ``band``, ``contentIncomplete`` and (SNOW-1139) ``contentAt`` are OPTIONAL and\n   *   the marker is load-bearing: only the recorded branch carries them \u2014\n   *   except ``type``, which SNOW-1136 also sets on an account-only row\n   *   whose ``shape`` is ``'CIRCLE'`` (a drop zone), alongside that row's\n   *   ``storedName``.\n   *   SNOW-1142: ``principal`` is recorded-branch only too \u2014 the account\n   *   the download was made under ('' anonymous, null legacy). An orphan\n   *   and an account-only row are never pushed from this list, so neither\n   *   needs one.\n   *   An orphan has no record to read a band or a tile-row span off, and\n   *   an account-only row is not on this device for its content to be\n   *   behind. ``deps`` is on all three (``[]`` where there is nothing to\n   *   name), so it is not optional.\n   *\n   *   ``contentIncomplete`` proves the point rather than being an\n   *   afterthought: SNOW-932 added it to the object literal below and to\n   *   ``manageRows``' typedef, but not to this one. Rebasing this branch\n   *   onto that merge produced NO conflict here \u2014 the field arrived and\n   *   the contract describing it did not, silently, in the one function\n   *   whose field-by-field rebuild makes that expensive. Caught by\n   *   re-reading the literal after the rebase, which is the manual check\n   *   ``@ts-check`` would make unnecessary.\n   */\n  function reconcileAreas(recorded, storedAreaIds, bytesById, accountAreas) {\n    var list = Array.isArray(recorded) ? recorded : [];\n    var stored = Array.isArray(storedAreaIds) ? storedAreaIds : [];\n    var sizes = bytesById || {};\n    var account = Array.isArray(accountAreas) ? accountAreas : [];\n\n    // The account rows, keyed by area id. Built first because every\n    // branch below asks it the same question \u2014 \"does the account know\n    // about this one\" \u2014 and it is what decides `synced`.\n    var accountById = Object.create(null);\n    for (var a = 0; a < account.length; a += 1) {\n      var row = account[a];\n      if (!row || !row.area_id) continue;\n      accountById[String(row.area_id)] = row;\n    }\n\n    var out = [];\n    var known = Object.create(null);\n    for (var i = 0; i < list.length; i += 1) {\n      var area = list[i];\n      if (!area || !area.id) continue;\n      var recordedId = String(area.id);\n      known[recordedId] = true;\n      out.push({\n        id: recordedId,\n        name: area.name,\n        bytes: Number(area.bytes) || 0,\n        savedAt: area.savedAt,\n        orphaned: false,\n        // SNOW-XXX: 'custom' or 'dropzone' for a user-made area, absent\n        // for a region (whose kind its id already gives). `manageRows`\n        // reads it to name the kind in words on the row.\n        type: area.type,\n        // SNOW-1133: the name as STORED \u2014 '' for an area never renamed,\n        // whose `name` is the reader's in-memory default. What the\n        // account is told, so that default never freezes into it.\n        storedName: area.storedName,\n        // SNOW-645: absent on a pre-SNOW-645 record \u2014 see manageRows.\n        basemapKey: area.basemapKey || null,\n        // SNOW-749: the tiles are here.\n        onDevice: true,\n        synced: recordedId in accountById,\n        // SNOW-749: what a re-download on another device would need. Null\n        // for a region, whose tiles are computed server-side from its real\n        // boundary \u2014 a box would be a second, coarser answer to a question\n        // already answered.\n        bbox: Array.isArray(area.bbox) ? area.bbox : null,\n        regionId: area.regionId || '',\n        // SNOW-844: the render dependencies the download recorded \u2014 the\n        // style document, the sprite and each vector source's TileJSON.\n        // Empty on a record written before that ticket, which the sheet\n        // reads as UNKNOWN (skip the check) rather than as \"none needed\".\n        deps: Array.isArray(area.deps) ? area.deps : [],\n        // SNOW-692: the ground the area covers, carried so its slope-angle\n        // tiles can be DERIVED rather than recorded. A region has the run's\n        // own `z` row spans; a custom area has `bbox` (above) plus `band`.\n        // Both are passed through unnormalised \u2014 see `manageRows`.\n        z: area.z || null,\n        band: Array.isArray(area.band) ? area.band : null,\n        // SNOW-932: the content half's shortfall, passed straight through\n        // from the record. This join rebuilds every entry field by field,\n        // so a field not named here is a field the sheet never sees.\n        contentIncomplete: !!area.contentIncomplete,\n        // SNOW-1139: when this area's content was last fetched, passed\n        // through for the same reason \u2014 this join names every field.\n        contentAt: area.contentAt || '',\n        // SNOW-1072: whether the area includes terrain data \u2014 the flag the\n        // sheet's completeness check derives the class tiles from.\n        terrain: !!area.terrain,\n        // SNOW-1142: whose download this is \u2014 the account id it was made\n        // under, '' for an anonymous one, null for a legacy record. Passed\n        // through for `downloads_sync.js`'s `recordHeld`, which reads it\n        // off this list to decide what may be recorded to the account.\n        principal: typeof area.principal === 'string' ? area.principal : null,\n      });\n    }\n\n    var orphans = [];\n    for (var j = 0; j < stored.length; j += 1) {\n      var id = stored[j] ? String(stored[j]) : '';\n      if (!id || known[id]) continue;\n      known[id] = true;\n      var bytes = Number(sizes[id]);\n      orphans.push({\n        id: id,\n        // No record means no stored name \u2014 the id is all there is, and\n        // manageRows already falls back to it.\n        name: undefined,\n        bytes: Number.isFinite(bytes) && bytes > 0 ? bytes : 0,\n        // No record means no completion timestamp either. Left empty\n        // rather than stamped with \"now\", which would sort an orphan as\n        // the newest download on a surface ordered partly by recency.\n        savedAt: undefined,\n        orphaned: true,\n        // No record means no known basemap either.\n        basemapKey: null,\n        // The bucket is on this device, whatever state it is in \u2014 which is\n        // exactly why the sheet has to be able to delete it.\n        onDevice: true,\n        synced: id in accountById,\n        bbox: null,\n        regionId: '',\n        // SNOW-844: no record means no dependency list either. An orphan\n        // is already labelled \"Incomplete\" for a stronger reason \u2014 there\n        // is nothing behind it at all \u2014 and is remove-only.\n        deps: [],\n      });\n    }\n    orphans.sort(function (a2, b2) {\n      return a2.id.localeCompare(b2.id);\n    });\n\n    // SNOW-749: whatever the account knows about that this device does\n    // not. Sorted by id for the same stable-render reason as the orphans,\n    // and appended after them so the two never interleave \u2014 they are\n    // different kinds of \"not a normal download\" and the sheet labels them\n    // differently.\n    var accountOnly = [];\n    for (var k = 0; k < account.length; k += 1) {\n      var accountRow = account[k];\n      if (!accountRow || !accountRow.area_id) continue;\n      var accountId = String(accountRow.area_id);\n      if (known[accountId]) continue;\n      known[accountId] = true;\n      accountOnly.push({\n        id: accountId,\n        name: accountRow.name || undefined,\n        // Nothing of it is on this device, so it costs this device\n        // nothing \u2014 which is what keeps it out of the byte budget.\n        bytes: 0,\n        // The account's own timestamp, not a local one: there is no local\n        // event to date it by, and leaving it unset would sort every\n        // account-only row against every other by id alone.\n        savedAt: accountRow.created_at,\n        // Emphatically not an orphan: nothing failed here, and there is\n        // nothing on disk to reclaim.\n        orphaned: false,\n        basemapKey: accountRow.basemap_key || null,\n        onDevice: false,\n        synced: true,\n        bbox: Array.isArray(accountRow.bbox) ? accountRow.bbox : null,\n        regionId: accountRow.region_id || '',\n        // SNOW-844: nothing of this area is on this device, so there is\n        // nothing here to be missing. The sheet skips the completeness\n        // check for an off-device row entirely.\n        deps: [],\n        // SNOW-1072: the account's record of the opt-in, so \"Download\n        // here\" fetches the same tile families the first device did.\n        terrain: !!accountRow.terrain,\n        // SNOW-1136: the account's `shape` read back as the local record's\n        // `type` \u2014 the inverse of `downloads_sync.js`'s `shapeFor`. A\n        // circle is a drop zone, so `manageRows` names the row \"Drop zone\"\n        // and \"Download here\" restores it as one rather than as the box\n        // around it. Absent for a box, which reads as what it always did.\n        type: accountRow.shape === 'CIRCLE' ? 'dropzone' : undefined,\n        // SNOW-1136: the name as the account STORES it \u2014 '' for an area\n        // never renamed \u2014 so a restore writes the stored name back under\n        // the stored id rather than freezing a localised default into it.\n        storedName: accountRow.name || '',\n      });\n    }\n    accountOnly.sort(function (a3, b3) {\n      return a3.id.localeCompare(b3.id);\n    });\n\n    return out.concat(orphans, accountOnly);\n  }\n\n  /**\n   * The budget bar's segments \u2014 one per basemap actually stored.\n   *\n   * SNOW-645 review: the bar used to be one flat fill for the whole used\n   * total. Grouped by basemap here, one segment per KEY (summed across\n   * however many areas share it), not one per area \u2014 a device with a\n   * dozen small custom areas under the same basemap would otherwise\n   * render a dozen slivers, which reads as noise rather than as \"here is\n   * what OpenFreeMap is costing you\".\n   *\n   * @param {Array<{bytes?: number, basemapKey?: string|null}>} areas As\n   *   ``manageRows`` returns them (or anything shaped the same \u2014 only\n   *   ``bytes`` and ``basemapKey`` are read).\n   * @returns {Array<{basemapKey: string, bytes: number}>} Sorted largest\n   *   first, with the keyless group (legacy records, orphaned buckets \u2014\n   *   ``basemapKey`` falsy) always LAST regardless of its size. Stable\n   *   across renders for the same underlying data: two groups of equal\n   *   size never swap because sort itself is stable and the input order\n   *   (``manageRows``' own largest-first, tie-broken by recency then id)\n   *   is already deterministic. ``basemapKey`` is always a string, never\n   *   ``null`` \u2014 ``''`` for the keyless group \u2014 so a caller can hand it\n   *   straight to a ``data-basemap-key`` attribute or a MapLibre ``match``\n   *   arm without an extra null check.\n   */\n  function budgetSegments(areas) {\n    var list = Array.isArray(areas) ? areas : [];\n    var totals = Object.create(null);\n    var order = [];\n\n    for (var i = 0; i < list.length; i += 1) {\n      var area = list[i];\n      var bytes = Number(area && area.bytes);\n      if (!Number.isFinite(bytes) || bytes <= 0) continue;\n      // The shared overview map counts under its own basemap, like every\n      // other download of it \u2014 a pass in this session split it out as a\n      // neutral \"system files\" segment, which said it was something the\n      // user never chose. It is the z0-9 half of the areas they did.\n      var key = (area && area.basemapKey) || '';\n      if (!(key in totals)) {\n        totals[key] = 0;\n        order.push(key);\n      }\n      totals[key] += bytes;\n    }\n\n    var keyed = order.filter(function (key) {\n      return key !== '';\n    });\n    keyed.sort(function (a, b) {\n      return totals[b] - totals[a];\n    });\n\n    var segments = keyed.map(function (key) {\n      return { basemapKey: key, bytes: totals[key] };\n    });\n    if ('' in totals) segments.push({ basemapKey: '', bytes: totals[''] });\n\n    return segments;\n  }\n\n  self.pwaBasemapManageCore = Object.freeze({\n    megabytesToBytes: megabytesToBytes,\n    formatMegabytes: formatMegabytes,\n    clampBudgetMb: clampBudgetMb,\n    budgetSummary: budgetSummary,\n    manageRows: manageRows,\n    groupRowsByPresence: groupRowsByPresence,\n    reconcileAreas: reconcileAreas,\n    budgetSegments: budgetSegments,\n    BUDGET_CHOICES_MB: BUDGET_CHOICES_MB,\n    MIN_BUDGET_MB: MIN_BUDGET_MB,\n    DEFAULT_BUDGET_MB: DEFAULT_BUDGET_MB,\n    BYTES_PER_MB: BYTES_PER_MB,\n  });\n})();\n"],
  "mappings": "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",
  "names": ["BYTES_PER_MB", "MIN_BUDGET_MB", "BUDGET_CHOICES_MB", "DEFAULT_BUDGET_MB", "megabytesToBytes", "mb", "n", "formatMegabytes", "bytes", "clampBudgetMb", "best", "bestDistance", "i", "choice", "distance", "budgetSummary", "areas", "budgetBytes", "list", "budget", "used", "pct", "manageRows", "options", "opts", "rows", "area", "id", "isCustom", "isBase", "onDevice", "renameable", "unnamed", "label", "KIND_ORDER", "kindRank", "kind", "a", "b", "byLabel", "groupRowsByPresence", "device", "account", "row", "groups", "reconcileAreas", "recorded", "storedAreaIds", "bytesById", "accountAreas", "stored", "sizes", "accountById", "out", "known", "recordedId", "orphans", "j", "a2", "b2", "accountOnly", "k", "accountRow", "accountId", "a3", "b3", "budgetSegments", "totals", "order", "key", "keyed", "segments"]
}
