/* ============================================================
   BAN YARO — Verlorener Hund (Sprint 11)
   Seiten-Modul: Leaflet-Karte + Meldungsliste + Melden-Formular.
   ============================================================ */

window.Page_lost = (() => {

  // ----------------------------------------------------------
  // MODUL-STATE
  // ----------------------------------------------------------
  let _container     = null;
  let _appState      = null;
  let _map           = null;
  let _markers       = [];
  let _userMarker    = null;
  let _reports       = [];
  let _userPos       = null;
  let _leafletLoaded = false;

  // ----------------------------------------------------------
  // INIT
  // ----------------------------------------------------------
  async function init(container, appState) {
    _container = container;
    _appState  = appState;
    await _render();
  }

  // ----------------------------------------------------------
  // REFRESH — Navigation zur bereits geladenen Seite
  // ----------------------------------------------------------
  async function refresh() {
    if (_userPos) await _loadReports();
  }

  // ----------------------------------------------------------
  // OPEN NEW — vom + Button
  // ----------------------------------------------------------
  function openNew() {
    _showReportForm();
  }

  // ----------------------------------------------------------
  // RENDER — Grundstruktur aufbauen
  // ----------------------------------------------------------
  async function _render() {
    _container.innerHTML = `
      <div style="display:flex;gap:var(--space-2);margin-bottom:var(--space-3);flex-wrap:wrap">
        <button class="btn btn-secondary" id="lost-btn-locate">${UI.icon('map-pin')} Mein Standort</button>
        <button class="btn btn-primary"   id="lost-btn-report">${UI.icon('magnifying-glass')} Hund vermisst melden</button>
      </div>

      <div id="lost-map"
           style="height:280px;border-radius:var(--radius-md);overflow:hidden;
                  background:var(--c-surface-2)">
      </div>
      <div style="font-size:10px;color:var(--c-text-secondary);
                  text-align:right;margin-bottom:var(--space-4);
                  padding:2px var(--space-2) 0">
        © OpenStreetMap-Mitwirkende
      </div>

      <p id="lost-info"
         style="font-size:var(--text-sm);color:var(--c-text-secondary);
                margin-bottom:var(--space-3)">
        Standort wird ermittelt…
      </p>

      <div id="lost-held"></div>
      <div id="lost-list"></div>
    `;

    document.getElementById('lost-btn-locate')
      ?.addEventListener('click', _locateUser);
    document.getElementById('lost-btn-report')
      ?.addEventListener('click', _showReportForm);

    await _loadLeaflet();
    _initMap();
    setTimeout(() => _map?.invalidateSize(), 100);
    await _locateAndLoad();
  }

  // ----------------------------------------------------------
  // LEAFLET DYNAMISCH LADEN
  // ----------------------------------------------------------
  async function _loadLeaflet() {
    if (_leafletLoaded || window.L) { _leafletLoaded = true; return; }

    await new Promise(resolve => {
      if (document.querySelector('link[href*="leaflet"]')) { resolve(); return; }
      const link  = document.createElement('link');
      link.rel    = 'stylesheet';
      link.href   = '/css/leaflet.css';
      link.onload = resolve;
      link.onerror = resolve;
      document.head.appendChild(link);
    });

    await new Promise((resolve, reject) => {
      if (document.querySelector('script[src*="leaflet"]')) { resolve(); return; }
      const s   = document.createElement('script');
      s.src     = '/js/leaflet.js';
      s.onload  = resolve;
      s.onerror = reject;
      document.head.appendChild(s);
    });

    _leafletLoaded = true;
  }

  // ----------------------------------------------------------
  // KARTE INITIALISIEREN
  // ----------------------------------------------------------
  function _initMap() {
    const mapEl = document.getElementById('lost-map');
    if (!mapEl || !window.L || _map) return;

    _map = L.map('lost-map', { zoomControl: true, attributionControl: false })
            .setView([51.1657, 10.4515], 6);

    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 19,
    }).addTo(_map);
  }

  // ----------------------------------------------------------
  // STANDORT ERMITTELN + LADEN
  // ----------------------------------------------------------
  async function _locateAndLoad() {
    try {
      _userPos = await API.getLocation({ timeout: 8000 });
      _showUserOnMap();
    } catch {
      _userPos = null;
    }
    await _loadReports();
  }

  async function _locateUser() {
    const btn = document.getElementById('lost-btn-locate');
    UI.setLoading(btn, true);
    try {
      _userPos = await API.getLocation({ timeout: 8000 });
      _showUserOnMap();
      if (_map) _map.setView([_userPos.lat, _userPos.lon], 13);
      await _loadReports();
    } catch {
      UI.toast.warning('Standort konnte nicht ermittelt werden.');
    }
    UI.setLoading(btn, false);
  }

  function _showUserOnMap() {
    if (!_map || !window.L || !_userPos) return;
    if (_userMarker) _map.removeLayer(_userMarker);
    _userMarker = L.circleMarker([_userPos.lat, _userPos.lon], {
      radius      : 9,
      fillColor   : '#3498db',
      color       : '#fff',
      weight      : 2,
      fillOpacity : 0.9,
    }).addTo(_map).bindPopup('<b>Du bist hier</b>');
    _map.setView([_userPos.lat, _userPos.lon], 13);
  }

  // ----------------------------------------------------------
  // MELDUNGEN LADEN
  // ----------------------------------------------------------
  async function _loadReports() {
    const infoEl = document.getElementById('lost-info');

    if (!_userPos) {
      _reports = [];
      _renderHeld();
      _renderList();
      if (infoEl) infoEl.textContent =
        'Standort unbekannt — bitte Standort freigeben (📍 Mein Standort).';
      return;
    }

    try {
      _reports = await API.lost.list(_userPos.lat, _userPos.lon, 25);
      _renderMarkers();
      _renderHeld();
      _renderList();
      _updateBadge(_reports.length);
      if (infoEl) {
        infoEl.textContent = _reports.length > 0
          ? `${_reports.length} vermisste${_reports.length !== 1 ? 'r Hund' : 'r Hund'} im Umkreis von 25 km`
          : 'Keine vermissten Hunde in deiner Nähe (25 km Radius). 🐾';
      }
    } catch {
      UI.toast.error('Meldungen konnten nicht geladen werden.');
    }
  }

  // ----------------------------------------------------------
  // KARTEN-MARKER
  // ----------------------------------------------------------
  function _renderMarkers() {
    if (!_map || !window.L) return;
    _markers.forEach(m => _map.removeLayer(m));
    _markers = [];

    _reports.forEach(r => {
      const icon = L.divIcon({
        className : '',
        html      : `<div style="
            background:#e74c3c;color:#fff;border-radius:50%;
            width:34px;height:34px;
            display:flex;align-items:center;justify-content:center;
            font-size:17px;box-shadow:0 2px 6px rgba(0,0,0,.35);
            border:2px solid #fff">🐕</div>`,
        iconSize   : [34, 34],
        iconAnchor : [17, 17],
      });

      const distStr = r.distanz_m !== undefined
        ? (r.distanz_m < 1000 ? `${r.distanz_m} m` : `${(r.distanz_m / 1000).toFixed(1)} km`)
        : '';

      const marker = L.marker([r.lat, r.lon], { icon })
        .addTo(_map)
        .bindPopup(`
          <b>🔍 ${_escape(r.name)}</b><br>
          ${r.rasse ? _escape(r.rasse) + '<br>' : ''}
          ${distStr ? `<small>📍 ${distStr} entfernt</small><br>` : ''}
          <small>📅 ${_fmtDate(r.created_at)}</small>
        `);

      marker.on('click', () => _openDetail(r));
      _markers.push(marker);
    });
  }

  // ----------------------------------------------------------
  // HELD DES TAGES
  // ----------------------------------------------------------
  function _renderHeld() {
    const heldEl = document.getElementById('lost-held');
    if (!heldEl) return;

    // Letzter gefundener Hund (is_active=0, gefunden_at gesetzt) — wir laden
    // sie nicht separat, daher nutzen wir die aktiven; für "Held" einen eigenen
    // API-Call wäre übertrieben. Stattdessen zeigen wir es nur wenn die Liste
    // kommt und wir einen kürzlich-gefundenen kennen. Wir überspringen hier
    // den separaten Endpunkt und blenden die Sektion aus wenn leer.
    heldEl.innerHTML = '';
  }

  // ----------------------------------------------------------
  // LISTE
  // ----------------------------------------------------------
  function _renderList() {
    const listEl = document.getElementById('lost-list');
    if (!listEl) return;

    if (_reports.length === 0) {
      listEl.innerHTML = _emptyState(
        'magnifying-glass',
        'Aktuell kein vermisster Hund gemeldet',
        'Wenn ein Hund vermisst wird, erscheint die Meldung hier. Du kannst auch selbst eine Meldung erstellen.',
        `<button class="btn btn-primary" id="lost-empty-report">Vermissten melden</button>`
      );
      listEl.querySelector('#lost-empty-report')
        ?.addEventListener('click', _showReportForm);
      return;
    }

    listEl.innerHTML = _reports.map(r => _reportCard(r)).join('');
    listEl.querySelectorAll('[data-lost-id]').forEach(card => {
      card.addEventListener('click', () => {
        const r = _reports.find(x => x.id === parseInt(card.dataset.lostId));
        if (r) _openDetail(r);
      });
    });
    listEl.querySelectorAll('.lost-note-btn').forEach(btn => {
      btn.addEventListener('click', e => {
        e.stopPropagation();
        const id   = parseInt(btn.dataset.lostNoteId);
        const name = btn.dataset.lostNoteName || '';
        _openNoteModal('lost', id, name, null);
      });
    });
  }

  function _reportCard(r) {
    const isOwn   = _appState.user && _appState.user.id === r.user_id;
    const distStr = r.distanz_m !== undefined
      ? (r.distanz_m < 1000 ? `${r.distanz_m} m` : `${(r.distanz_m / 1000).toFixed(1)} km`)
      : '';

    return `
      <div class="card" data-lost-id="${r.id}"
           style="cursor:pointer;margin-bottom:var(--space-3);
                  border-left:4px solid #e74c3c">
        <div style="display:flex;gap:var(--space-3);align-items:flex-start">
          ${r.foto_url
            ? `<img src="${r.foto_url}" alt="Foto"
                    loading="lazy"
                    style="width:72px;height:72px;object-fit:cover;
                           border-radius:var(--radius-md);flex-shrink:0">`
            : `<div style="width:72px;height:72px;background:var(--c-surface-2);
                           border-radius:var(--radius-md);flex-shrink:0;
                           display:flex;align-items:center;justify-content:center;
                           font-size:2rem">🐕</div>`}
          <div style="flex:1;min-width:0">
            <div style="display:flex;align-items:center;gap:var(--space-2);
                        margin-bottom:var(--space-1);flex-wrap:wrap">
              <span style="font-weight:var(--weight-semibold);font-size:var(--text-base)">
                ${_escape(r.name)}
              </span>
              ${r.rasse
                ? `<span class="badge">${_escape(r.rasse)}</span>`
                : ''}
              ${isOwn
                ? '<span class="badge badge-warning">Meine Meldung</span>'
                : ''}
              ${distStr
                ? `<span style="margin-left:auto;color:var(--c-text-secondary);
                               font-size:var(--text-sm);white-space:nowrap">
                     📍 ${distStr}
                   </span>`
                : ''}
            </div>
            <p style="margin:0 0 var(--space-1);font-size:var(--text-sm);
                      color:var(--c-text)">
              ${_escape(r.beschreibung.slice(0, 120))}${r.beschreibung.length > 120 ? '…' : ''}
            </p>
            <div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
              Gemeldet ${_fmtDate(r.created_at)}
              ${r.melder_name ? '· ' + _escape(r.melder_name.split(' ')[0]) : ''}
            </div>
            ${_appState.user ? `<div style="margin-top:var(--space-2)">
              <button class="btn btn-ghost btn-xs lost-note-btn"
                      data-lost-note-id="${r.id}"
                      data-lost-note-name="${_escape(r.name)}"
                      title="Notiz" onclick="event.stopPropagation()">
                <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz
              </button>
            </div>` : ''}
          </div>
        </div>
      </div>
    `;
  }

  // ----------------------------------------------------------
  // DETAIL-MODAL
  // ----------------------------------------------------------
  function _openDetail(r) {
    const isOwn   = _appState.user && _appState.user.id === r.user_id;
    const isAdmin = _appState.user?.rolle === 'admin';
    const distStr = r.distanz_m !== undefined
      ? (r.distanz_m < 1000 ? `${r.distanz_m} m` : `${(r.distanz_m / 1000).toFixed(1)} km`)
      : '';

    const body = `
      ${r.foto_url
        ? `<img src="${r.foto_url}" alt="Foto"
                style="width:100%;border-radius:var(--radius-md);margin-bottom:var(--space-4)">`
        : ''}

      <div style="display:flex;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-3)">
        <span class="badge badge-danger">🐕 ${_escape(r.name)}</span>
        ${r.rasse ? `<span class="badge">${_escape(r.rasse)}</span>` : ''}
      </div>

      <p style="white-space:pre-wrap;margin-bottom:var(--space-3)">
        ${_escape(r.beschreibung)}
      </p>

      <div style="font-size:var(--text-sm);color:var(--c-text-secondary);
                  margin-bottom:var(--space-4);line-height:1.8">
        <div>📍 ${r.lat.toFixed(5)}, ${r.lon.toFixed(5)}${distStr ? ' (' + distStr + ' entfernt)' : ''}</div>
        <div>📅 Gemeldet: ${_fmtDate(r.created_at)}</div>
        ${r.melder_name ? `<div>👤 Gemeldet von: ${_escape(r.melder_name.split(' ')[0])}</div>` : ''}
      </div>

      <div style="display:flex;gap:var(--space-2);flex-wrap:wrap">
        <button class="btn btn-secondary flex-1" id="detail-lost-map">🗺️ Auf Karte</button>
        ${isOwn || isAdmin
          ? `<button class="btn btn-nature flex-1" id="detail-lost-found">🎉 Gefunden!</button>`
          : ''}
        ${isOwn || isAdmin
          ? `<button class="btn btn-danger flex-1" id="detail-lost-delete">🗑 Löschen</button>`
          : ''}
      </div>
    `;

    UI.modal.open({ title: `🔍 ${_escape(r.name)} wird vermisst`, body });

    document.getElementById('detail-lost-map')?.addEventListener('click', () => {
      UI.modal.close();
      if (_map) {
        _map.setView([r.lat, r.lon], 16);
        document.getElementById('lost-map')
          ?.scrollIntoView({ behavior: 'smooth', block: 'start' });
        const marker = _markers[_reports.findIndex(x => x.id === r.id)];
        marker?.openPopup();
      }
    });

    document.getElementById('detail-lost-found')?.addEventListener('click', () => {
      _showFoundDialog(r);
    });

    document.getElementById('detail-lost-delete')?.addEventListener('click', async () => {
      if (!confirm(`Meldung für ${r.name} wirklich löschen?`)) return;
      try {
        await API.lost.delete(r.id);
        _reports = _reports.filter(x => x.id !== r.id);
        _renderMarkers();
        _renderList();
        _updateBadge(_reports.length);
        UI.modal.close();
        UI.toast.success('Meldung gelöscht.');
      } catch (err) {
        UI.toast.error(err.message || 'Fehler beim Löschen.');
      }
    });
  }

  // ----------------------------------------------------------
  // GEFUNDEN-DIALOG
  // ----------------------------------------------------------
  function _showFoundDialog(r) {
    UI.modal.open({
      title: `🎉 ${_escape(r.name)} gefunden?`,
      body: `
        <p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)">
          Wurde ${_escape(r.name)} wiedergefunden? Die Meldung wird als
          abgeschlossen markiert und aus der Liste entfernt.
        </p>
      `,
      footer: `
        <button class="btn btn-secondary" id="found-cancel">Abbrechen</button>
        <button class="btn btn-nature"    id="found-confirm">🎉 Ja, gefunden!</button>
      `,
    });

    document.getElementById('found-cancel')
      ?.addEventListener('click', UI.modal.close);

    document.getElementById('found-confirm')?.addEventListener('click', async () => {
      const btn = document.getElementById('found-confirm');
      await UI.asyncButton(btn, async () => {
        await API.lost.markFound(r.id);
        _reports = _reports.filter(x => x.id !== r.id);
        _renderMarkers();
        _renderList();
        _updateBadge(_reports.length);
        UI.modal.close();
        UI.toast.success(`${r.name} ist wieder da! 🎉`);
      });
    });
  }

  // ----------------------------------------------------------
  // MELDE-FORMULAR
  // ----------------------------------------------------------
  function _showReportForm() {
    if (!_appState.user) {
      UI.toast.warning('Bitte zuerst anmelden, um eine Meldung abzuschicken.');
      App.navigate('settings');
      return;
    }

    // Eigene registrierte Hunde für Dropdown
    const dogs      = _appState.dogs || [];
    const dogOpts   = dogs.length > 0
      ? `<option value="">— kein registrierter Hund —</option>` +
        dogs.map(d => `<option value="${d.id}">${_escape(d.name)}${d.rasse ? ' (' + _escape(d.rasse) + ')' : ''}</option>`).join('')
      : '';

    const body = `
      <form id="lost-form" autocomplete="off">

        ${dogs.length > 0 ? `
        <div class="form-group">
          <label class="form-label">
            Registrierter Hund
            <span style="color:var(--c-text-secondary)">(optional)</span>
          </label>
          <select class="form-control" name="dog_id" id="lf-dog-select">
            ${dogOpts}
          </select>
        </div>` : ''}

        <div class="form-group">
          <label class="form-label">Name des Hundes *</label>
          <input class="form-control" type="text" name="name" id="lf-name"
                 placeholder="z. B. Bello" required>
        </div>

        <div class="form-group">
          <label class="form-label">
            Rasse
            <span style="color:var(--c-text-secondary)">(optional)</span>
          </label>
          <input class="form-control" type="text" name="rasse"
                 placeholder="z. B. Labrador">
        </div>

        <div class="form-group">
          <label class="form-label">Beschreibung *</label>
          <textarea class="form-control" name="beschreibung" rows="3"
                    placeholder="Farbe, Merkmale, wo zuletzt gesehen, Halsband, …"
                    required></textarea>
        </div>

        <div class="form-group">
          <label class="form-label">Standort (letzter bekannter Ort)</label>
          <div style="display:flex;gap:var(--space-2);align-items:center;flex-wrap:wrap">
            <input class="form-control" type="text" id="lf-lat-disp"
                   placeholder="Breite" readonly style="flex:1;min-width:80px">
            <input class="form-control" type="text" id="lf-lon-disp"
                   placeholder="Länge"  readonly style="flex:1;min-width:80px">
            <button type="button" class="btn btn-secondary" id="lf-gps-btn"
                    style="white-space:nowrap">
              ${UI.icon('map-pin')} Standort
            </button>
          </div>
          <input type="hidden" name="lat" id="lf-lat">
          <input type="hidden" name="lon" id="lf-lon">
          <small id="lf-gps-hint" style="color:var(--c-text-secondary)">
            ${_userPos
              ? '✅ Aktueller Standort vorausgefüllt'
              : 'GPS-Button drücken um Standort zu ermitteln'}
          </small>
        </div>

        <div class="form-group">
          <label class="form-label">
            Foto
            <span style="color:var(--c-text-secondary)">(optional)</span>
          </label>
          <input class="form-control" type="file" name="photo"
                 accept="image/*" capture="environment">
          <img id="lf-photo-preview"
               style="display:none;width:100%;max-height:200px;object-fit:cover;
                      border-radius:var(--radius-md);margin-top:var(--space-2)">
        </div>

      </form>
    `;

    const footer = `
      <button type="button" class="btn btn-secondary flex-1" id="lf-cancel">Abbrechen</button>
      <button type="submit" form="lost-form"  class="btn btn-primary flex-1">
        🔍 Meldung abschicken
      </button>
    `;

    UI.modal.open({ title: '🔍 Hund vermisst melden', body, footer });

    // Standort vorausfüllen
    if (_userPos) {
      document.getElementById('lf-lat').value      = _userPos.lat;
      document.getElementById('lf-lon').value      = _userPos.lon;
      document.getElementById('lf-lat-disp').value = _userPos.lat.toFixed(6);
      document.getElementById('lf-lon-disp').value = _userPos.lon.toFixed(6);
    }

    // Wenn registrierter Hund gewählt → Name+Rasse vorausfüllen
    document.getElementById('lf-dog-select')?.addEventListener('change', e => {
      const dogId = parseInt(e.target.value);
      const dog   = dogs.find(d => d.id === dogId);
      if (dog) {
        document.getElementById('lf-name').value = dog.name;
        const rasseInput = document.querySelector('#lost-form [name="rasse"]');
        if (rasseInput && dog.rasse) rasseInput.value = dog.rasse;
      }
    });

    // GPS-Button
    document.getElementById('lf-gps-btn')?.addEventListener('click', async () => {
      const btn = document.getElementById('lf-gps-btn');
      UI.setLoading(btn, true);
      try {
        const pos = await API.getLocation({ timeout: 10000, enableHighAccuracy: true });
        document.getElementById('lf-lat').value      = pos.lat;
        document.getElementById('lf-lon').value      = pos.lon;
        document.getElementById('lf-lat-disp').value = pos.lat.toFixed(6);
        document.getElementById('lf-lon-disp').value = pos.lon.toFixed(6);
        document.getElementById('lf-gps-hint').textContent = '✅ Standort aktualisiert';
        _userPos = pos;
      } catch {
        UI.toast.error('GPS-Standort konnte nicht ermittelt werden.');
      }
      UI.setLoading(btn, false);
    });

    // Foto-Vorschau
    const photoInput   = document.querySelector('#lost-form [name="photo"]');
    const photoPreview = document.getElementById('lf-photo-preview');
    if (photoInput && photoPreview) {
      UI.setupPhotoPreview(photoInput, photoPreview);
      photoInput.addEventListener('change', () => {
        photoPreview.style.display = photoInput.files[0] ? 'block' : 'none';
      });
    }

    document.getElementById('lf-cancel')
      ?.addEventListener('click', UI.modal.close);

    // Formular absenden
    document.getElementById('lost-form')?.addEventListener('submit', async e => {
      e.preventDefault();
      const submitBtn = document.querySelector('[form="lost-form"][type="submit"]') ||
                        e.target.querySelector('[type="submit"]');
      const fd = UI.formData(e.target);

      if (!fd.lat || !fd.lon) {
        UI.toast.warning('Bitte zuerst den GPS-Standort ermitteln (📍).');
        return;
      }
      if (!fd.name?.trim()) {
        UI.toast.warning('Bitte den Namen des Hundes eingeben.');
        return;
      }

      await UI.asyncButton(submitBtn, async () => {
        const payload = {
          name         : fd.name.trim(),
          rasse        : fd.rasse?.trim() || null,
          beschreibung : fd.beschreibung?.trim() || '',
          lat          : parseFloat(fd.lat),
          lon          : parseFloat(fd.lon),
          dog_id       : fd.dog_id ? parseInt(fd.dog_id) : null,
          client_time  : API.clientNow(),
        };

        const created = await API.lost.report(payload);

        // Foto hochladen
        if (photoInput?.files[0]) {
          try {
            const formData = new FormData();
            formData.append('file', photoInput.files[0]);
            const media = await API.lost.uploadFoto(created.id, formData);
            created.foto_url = media.foto_url;
          } catch {
            UI.toast.warning('Meldung erstellt — Foto konnte nicht hochgeladen werden.');
          }
        }

        // Distanz client-seitig berechnen
        created.distanz_m = _userPos
          ? Math.round(_haversine(_userPos.lat, _userPos.lon, created.lat, created.lon))
          : 0;

        _reports.unshift(created);
        _renderMarkers();
        _renderList();
        _updateBadge(_reports.length);
        UI.toast.success('Hund als vermisst gemeldet. Wir drücken die Daumen!');
        UI.modal.close();
      });
    });
  }

  // ----------------------------------------------------------
  // BADGE
  // ----------------------------------------------------------
  function _updateBadge(count) {
    const b = document.getElementById('lost-badge');
    if (b) { b.textContent = count; b.style.display = count > 0 ? '' : 'none'; }
  }

  // ----------------------------------------------------------
  // HELPER
  // ----------------------------------------------------------
  function _haversine(lat1, lon1, lat2, lon2) {
    const R  = 6_371_000;
    const p1 = lat1 * Math.PI / 180;
    const p2 = lat2 * Math.PI / 180;
    const dp = (lat2 - lat1) * Math.PI / 180;
    const dl = (lon2 - lon1) * Math.PI / 180;
    const a  = Math.sin(dp / 2) ** 2 + Math.cos(p1) * Math.cos(p2) * Math.sin(dl / 2) ** 2;
    return 2 * R * Math.asin(Math.sqrt(a));
  }

  function _fmtDate(isoStr) {
    if (!isoStr) return '';
    const d = new Date(isoStr.replace(' ', 'T'));
    return d.toLocaleDateString('de-DE', {
      day: '2-digit', month: '2-digit', year: 'numeric'
    });
  }

  function _escape(str) {
    if (!str) return '';
    return String(str)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

  function _emptyState(icon, title, text, cta = '') {
    return `<div class="empty-state">
      <svg class="ph-icon empty-state-icon" aria-hidden="true">
        <use href="/icons/phosphor.svg#${icon}"></use>
      </svg>
      <div class="empty-state-title">${title}</div>
      ${text ? `<p class="empty-state-text">${text}</p>` : ''}
      ${cta ? `<div class="empty-state-cta">${cta}</div>` : ''}
    </div>`;
  }

  // ----------------------------------------------------------
  // NOTIZ-MODAL
  // ----------------------------------------------------------
  async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
    document.getElementById('by-note-modal')?.remove();

    const overlay = document.createElement('div');
    overlay.id = 'by-note-modal';
    overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';

    overlay.innerHTML = `
      <div style="background:var(--c-surface);border-radius:var(--radius-xl) var(--radius-xl) 0 0;
                  width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;
                  padding-bottom:env(safe-area-inset-bottom,0px)">
        <div style="padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--c-border);
                    display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
          <div>
            <div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
            <div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_escape(parentLabel)}</div>
          </div>
          <button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
        </div>
        <div style="padding:var(--space-4) var(--space-5);flex:1;overflow-y:auto">
          <form id="by-note-form">
            <textarea id="by-note-text" class="form-control" rows="5"
                      placeholder="Notiz eingeben…"
                      style="width:100%;resize:vertical"></textarea>
          </form>
        </div>
        <div style="padding:var(--space-3) var(--space-5);border-top:1px solid var(--c-border);
                    display:flex;gap:var(--space-2);flex-shrink:0">
          <button type="button" id="by-note-cancel" class="btn btn-secondary flex-1">Abbrechen</button>
          <button type="submit" form="by-note-form" id="by-note-save" class="btn btn-primary flex-1">Speichern</button>
        </div>
      </div>
    `;

    document.body.appendChild(overlay);

    const textarea  = document.getElementById('by-note-text');
    const saveBtn   = document.getElementById('by-note-save');
    const cancelBtn = document.getElementById('by-note-cancel');
    const closeBtn  = document.getElementById('by-note-close');

    let existingNoteId = null;

    try {
      const existing = await API.notes.get(parentType, String(parentId));
      if (existing?.id) {
        existingNoteId = existing.id;
        textarea.value = existing.text || '';
      }
    } catch (_) { /* keine Notiz vorhanden — ok */ }

    setTimeout(() => textarea.focus(), 100);

    const _close = () => overlay.remove();
    closeBtn.addEventListener('click', _close);
    cancelBtn.addEventListener('click', _close);
    overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });

    document.getElementById('by-note-form').addEventListener('submit', async e => {
      e.preventDefault();
      const text = textarea.value.trim();
      UI.setLoading(saveBtn, true);
      try {
        const payload = { text, parent_label: parentLabel, location_name: locationName };
        if (existingNoteId) {
          await API.notes.update(existingNoteId, payload);
        } else {
          await API.notes.create(parentType, String(parentId), payload);
        }
        UI.toast.success('Notiz gespeichert.');
        _close();
      } catch (err) {
        UI.toast.error(err.message || 'Fehler beim Speichern.');
        UI.setLoading(saveBtn, false);
      }
    });
  }

  // ----------------------------------------------------------
  // PUBLIC
  // ----------------------------------------------------------
  return { init, refresh, openNew };

})();
