Source: marks.js

let marks = []; // global; may be later replaced with a Promise

/**
 * Fetch marks from the API and initialize marks
 *
 * Expects the server response to be an array of marks,
 * where each mark is an object {what, name, id, lat0, lon0,, lat1, lon1, status}
 *
 * @param {L.Map} map - Leaflet map instance.
 */
async function getMarks (map) {
   if (!map) throw new Error("Leaflet map instance is required.");
   let response;
   try {
      response = await fetch(apiUrl, {
         method: "POST",
         headers: {
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
         },
         body: `type=${REQ.MARKS}`,
         cache: "no-store",
         credentials: "omit",
      });
   } catch (networkErr) {
      console.error("Network error while fetching marks:", networkErr);
      throw new Error("Network error while fetching polygons.");
   }

   if (!response.ok) {
      const msg = `Server error: ${response.status} ${response.statusText}`;
      console.error(msg);
      throw new Error(msg);
   }

   try {
      marks = await response.json(); // global variable
   } catch (parseErr) {
      console.error("Invalid JSON from server:", parseErr);
      throw new Error("Invalid JSON from server.");
   }

   // Basic validation and drawing
   if (!Array.isArray(marks)) {
      throw new Error("Unexpected response shape: expected an array of marks.");
   }
   console.log (JSON.stringify (marks, null, 2));
   displayMarks(map, marks);
}

/**
 * Render marks as a simple HTML table.
 * @param {MarkVR[]} marksArray
 * @param {number} decimals
 * @returns {string}
 */
function renderMarksTable(marksArray, decimals) {
   if (!Array.isArray(marksArray) || marksArray.length === 0) {
      return `<div style="padding:.5rem">No data.</div>`;
   }
   const rows = marksArray.map(m => `
      <tr>
         <td>${esc(m.what)}</td>
         <td>${esc(m.name)}</td>
         <td>${esc(m.id)}</td>
         <td style="text-align:right">${latLonToStr(m.lat0, m.lon0, DMSType)}</td>
         <td style="text-align:right">${latLonToStr(m.lat1, m.lon1, DMSType)}</td>
         <td>${esc(m.status)}</td>
      </tr>
   `).join('');
   return `
      <div style="max-height:60vh; overflow:auto; margin-top:0.5rem">
         <table style="width:100%; border-collapse:collapse">
            <thead>
               <tr>
                  <th style="text-align:center; border-bottom:1px solid #ddd; padding:4px">what</th>
                  <th style="text-align:center; border-bottom:1px solid #ddd; padding:4px">name</th>
                  <th style="text-align:center; border-bottom:1px solid #ddd; padding:4px">id</th>
                  <th style="text-align:center; border-bottom:1px solid #ddd; padding:4px">lat0 - lon0</th>
                  <th style="text-align:center; border-bottom:1px solid #ddd; padding:4px">lat1 - lon1</th>
                  <th style="text-align:center; border-bottom:1px solid #ddd; padding:4px">status</th>
               </tr>
            </thead>
            <tbody>
               ${rows}
            </tbody>
         </table>
      </div>
   `;
}

/**
 * Show the marks array in a SweetAlert2 modal as a table.
 * @param {MarkVR[]} marks
 */
function showMarks(marks) {
   const title = 'VR marks';
   const decimals = 6;

   if (! Array.isArray(marks) || marks.length === 0) return;

   Swal.fire({
      title,
      html: renderMarksTable(marks, decimals),
      width: Math.min(window.innerWidth * 0.95, 1000),
      showCloseButton: true,
      confirmButtonText: 'Close',
      customClass: { htmlContainer: 'swal2-overflow' }
   });
}

/**
 * Return the symbol (emoji) to use for a given `what`.
 * Falls back to 'x' for Invisible/unknown.
 * @param {string} what
 * @returns {string}
 */
function symbolFromWhat(what) {
   const lower = (what || '').trim().toLowerCase();
   if (lower.includes('start')) return '🚩';
   if (lower.includes('buoy')) return '🚩';
   if (lower.includes('end')) return '🏁';
   if (lower.includes('invisible')) return 'x';
   if (lower.includes('gate')) return 'G';
   return 'x';
}

/**
 * Check if a name is non-empty after trimming.
 * @param {string} name
 * @returns {boolean}
 */
function hasName(name) {
   return typeof name === 'string' && name.trim().length > 0;
}

/**
 * Add emoji markers for marksVR onto a Leaflet map.
 * - Places at (lat0, lon0)
 * - Shows ONLY the symbol on map (no tooltip)
 * - On click, opens a popup with the `name` (if present)
 *
 * Uses/replaces map._marksLayer to allow redraws.
 *
 * @param {L.Map} map
 * @param {array} marks
 */
function displayMarks(map, marks) {
   if (map._marksLayer) {
      map.removeLayer(map._marksLayer);
      map._marksLayer = undefined;
   }
   if (!marks || marks.length === 0) {
      console.warn('displayMarks: no data to display');
      return;
   }

   const group = L.layerGroup();
   map._marksLayer = group;
   group.addTo(map);
   
   for (const m of marks) {
      const lat = (typeof m.lat0 === 'number') ? m.lat0 : null;
      const lon = (typeof m.lon0 === 'number') ? m.lon0 : null;
      if (lat === null || lon === null || Number.isNaN(lat) || Number.isNaN(lon)) {
         continue;
      }
      const sym = symbolFromWhat(m.what);
      const icon = L.divIcon({
         className: 'mark-emoji',
         html: sym,
         iconSize: [22, 22],
         iconAnchor: [11, 11]
      });
      const marker = L.marker([lat, lon], { icon });
      marker.addTo(group);

      if (hasName(m.name)) {
         marker.bindPopup(`<strong>${esc (m.name.trim())}</strong>`, {
            closeButton: true,
            autoClose: true
         });
      }
   }
}