/* ============================================================
   BAN YARO — Wetter (7-Tage-Wettervorhersage)
   Seiten-Modul: Hunde-optimierte Wettervorhersage mit GPS.
   ============================================================ */

window.Page_wetter = (() => {

  // ----------------------------------------------------------
  // KONSTANTEN
  // ----------------------------------------------------------
  // WMO-Code → Phosphor-Icon-Name (aus Sprite)
  const WMO_ICON = {
    0:'sun', 1:'sun-dim', 2:'cloud-sun', 3:'cloud',
    45:'cloud-fog', 48:'cloud-fog',
    51:'cloud-rain', 53:'cloud-rain', 55:'cloud-rain',
    61:'cloud-rain', 63:'cloud-rain', 65:'cloud-rain',
    71:'cloud-snow', 73:'cloud-snow', 75:'cloud-snow', 77:'snowflake',
    80:'rainbow-cloud', 81:'cloud-rain', 82:'cloud-rain',
    85:'cloud-snow', 86:'cloud-snow',
    95:'cloud-lightning', 96:'cloud-lightning', 99:'cloud-lightning',
  };
  // Farben passend zum Wetter (für Icon-Tinting)
  const WMO_COLOR = {
    0:'#F59E0B', 1:'#F59E0B', 2:'#94A3B8', 3:'#64748B',
    45:'#94A3B8', 48:'#94A3B8',
    51:'#60A5FA', 53:'#3B82F6', 55:'#2563EB',
    61:'#3B82F6', 63:'#2563EB', 65:'#1D4ED8',
    71:'#BAE6FD', 73:'#7DD3FC', 75:'#38BDF8', 77:'#BAE6FD',
    80:'#60A5FA', 81:'#3B82F6', 82:'#2563EB',
    85:'#7DD3FC', 86:'#38BDF8',
    95:'#7C3AED', 96:'#6D28D9', 99:'#5B21B6',
  };
  function _wmoIcon(code, size = '2rem', extraStyle = '') {
    const name  = WMO_ICON[code] || 'cloud';
    const color = WMO_COLOR[code] || 'var(--c-text-secondary)';
    return `<svg class="ph-icon" aria-hidden="true"
                 style="width:${size};height:${size};color:${color};flex-shrink:0;${extraStyle}">
              <use href="/icons/phosphor.svg#${name}"></use>
            </svg>`;
  }

  const WMO_DESC = {
    0:'Klarer Himmel', 1:'Überwiegend klar', 2:'Teilweise bewölkt', 3:'Bedeckt',
    45:'Nebel', 48:'Gefrierender Nebel',
    51:'Leichter Sprühregen', 53:'Mäßiger Sprühregen', 55:'Starker Sprühregen',
    61:'Leichter Regen', 63:'Mäßiger Regen', 65:'Starker Regen',
    71:'Leichter Schneefall', 73:'Mäßiger Schneefall', 75:'Starker Schneefall', 77:'Schneekörner',
    80:'Leichte Regenschauer', 81:'Mäßige Regenschauer', 82:'Starke Regenschauer',
    85:'Leichte Schneeschauer', 86:'Starke Schneeschauer',
    95:'Gewitter', 96:'Gewitter mit leichtem Hagel', 99:'Gewitter mit starkem Hagel'
  };

  const DAY_NAMES = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];

  // ----------------------------------------------------------
  // MODUL-STATE
  // ----------------------------------------------------------
  let _container = null;
  let _appState  = null;
  let _data      = null;
  let _selDay    = 0;
  let _loading   = false;

  // ----------------------------------------------------------
  // INIT
  // ----------------------------------------------------------
  async function init(container, appState) {
    _container = container;
    _appState  = appState;
    _selDay    = 0;
    _renderShell();
    _tryAutoLocate();
  }

  // ----------------------------------------------------------
  // REFRESH
  // ----------------------------------------------------------
  async function refresh() {
    _selDay = 0;
    _renderShell();
    _tryAutoLocate();
  }

  // ----------------------------------------------------------
  // RENDER — Grundstruktur
  // ----------------------------------------------------------
  function _renderShell() {
    _container.innerHTML = `
      <div id="wttr-body">
        <div id="wttr-locating" style="text-align:center;padding:var(--space-10) var(--space-4)">
          <div style="margin-bottom:var(--space-3)">${_wmoIcon(2, '2.5rem')}</div>
          <p style="color:var(--c-text-secondary)">Standort wird ermittelt…</p>
        </div>
      </div>
    `;
  }

  // ----------------------------------------------------------
  // STANDORT AUTOMATISCH ERMITTELN
  // ----------------------------------------------------------
  async function _tryAutoLocate() {
    try {
      const pos = await API.getLocation({ timeout: 8000, maximumAge: 300_000 });
      await _loadData(pos.lat, pos.lon);
    } catch {
      _showLocationError();
    }
  }

  function _showLocationError() {
    const body = _container.querySelector('#wttr-body');
    if (!body) return;
    body.innerHTML = `
      <div style="text-align:center;padding:var(--space-10) var(--space-4)">
        <div style="font-size:2.5rem;margin-bottom:var(--space-3)">📍</div>
        <h3 style="margin-bottom:var(--space-2)">Standort nicht verfügbar</h3>
        <p style="color:var(--c-text-secondary);margin-bottom:var(--space-5);max-width:300px;margin-inline:auto">
          Bitte erlaube den Zugriff auf deinen Standort, um die Wettervorhersage zu laden.
        </p>
        <button class="btn btn-primary" id="wttr-btn-retry">
          ${UI.icon('map-pin')} Nochmal versuchen
        </button>
      </div>
    `;
    body.querySelector('#wttr-btn-retry')?.addEventListener('click', () => {
      _renderShell();
      _tryAutoLocate();
    });
  }

  // ----------------------------------------------------------
  // DATEN LADEN
  // ----------------------------------------------------------
  async function _loadData(lat, lon) {
    if (_loading) return;
    _loading = true;
    try {
      _data = await API.weather.forecast(lat, lon);
      _selDay = 0;
      _renderWeather();
    } catch {
      const body = _container.querySelector('#wttr-body');
      if (body) body.innerHTML = `
        <div style="text-align:center;padding:var(--space-10) var(--space-4)">
          <div style="font-size:2.5rem;margin-bottom:var(--space-3)">⚠️</div>
          <h3 style="margin-bottom:var(--space-2)">Wetter nicht verfügbar</h3>
          <p style="color:var(--c-text-secondary);margin-bottom:var(--space-5)">
            Die Wetterdaten konnten nicht geladen werden.
          </p>
          <button class="btn btn-primary" id="wttr-btn-reload">
            ${UI.icon('arrow-clockwise')} Erneut laden
          </button>
        </div>
      `;
      body?.querySelector('#wttr-btn-reload')?.addEventListener('click', () => {
        refresh();
      });
    } finally {
      _loading = false;
    }
  }

  // ----------------------------------------------------------
  // HAUPT-RENDER
  // ----------------------------------------------------------
  function _renderWeather() {
    const body = _container.querySelector('#wttr-body');
    if (!body || !_data) return;

    const days = _data.days || [];
    if (!days.length) return;

    body.innerHTML = `
      <!-- 7-Tage-Strip -->
      <div id="wttr-strip-wrap"
           style="overflow-x:auto;-webkit-overflow-scrolling:touch;
                  margin-bottom:var(--space-4);
                  scrollbar-width:none">
        <div id="wttr-strip"
             style="display:flex;gap:var(--space-2);padding-bottom:4px;min-width:max-content">
          ${days.map((d, i) => _dayCard(d, i)).join('')}
        </div>
      </div>

      <!-- Detail-Card -->
      <div id="wttr-detail" class="section-card"
           style="margin-bottom:var(--space-4)">
      </div>

      <!-- Hunde-Wetter -->
      <div id="wttr-dog" class="section-card">
      </div>
    `;

    // Strip-Klick-Events
    body.querySelectorAll('[data-wttr-day]').forEach(card => {
      card.addEventListener('click', () => {
        _selDay = parseInt(card.dataset.wttrDay);
        _updateStrip();
        _renderDetail();
        _renderDog();
      });
    });

    _renderDetail();
    _renderDog();
  }

  // ----------------------------------------------------------
  // STRIP AKTUALISIEREN (aktiver Tag)
  // ----------------------------------------------------------
  function _updateStrip() {
    const body = _container.querySelector('#wttr-body');
    if (!body) return;
    const days = _data?.days || [];
    body.querySelectorAll('[data-wttr-day]').forEach((card, i) => {
      const active = i === _selDay;
      card.style.background     = active ? 'var(--c-primary)' : 'var(--c-bg-card)';
      card.style.color          = active ? '#fff' : 'var(--c-text)';
      card.style.borderColor    = active ? 'var(--c-primary)' : 'var(--c-border)';
      card.style.transform      = active ? 'translateY(-2px)' : '';
      card.style.boxShadow      = active ? '0 4px 12px rgba(196,132,58,0.3)' : '0 1px 3px rgba(0,0,0,0.07)';
      // Temperatur-Farbe im aktiven Zustand
      const tempEl = card.querySelector('.wttr-temp');
      if (tempEl) tempEl.style.color = active ? 'rgba(255,255,255,0.85)' : 'var(--c-text-secondary)';
      const precipEl = card.querySelector('.wttr-precip');
      if (precipEl) precipEl.style.color = active ? 'rgba(255,255,255,0.75)' : 'var(--c-text-secondary)';
    });
  }

  // ----------------------------------------------------------
  // TAG-KARTE (Strip)
  // ----------------------------------------------------------
  function _dayCard(d, i) {
    const active  = i === _selDay;
    const dateObj = new Date(d.date);
    const dayName = i === 0 ? 'Heute' : DAY_NAMES[dateObj.getDay()];
    const bg      = active ? 'var(--c-primary)' : 'var(--c-bg-card)';
    const col     = active ? '#fff' : 'var(--c-text)';
    const shadow  = active
      ? '0 4px 12px rgba(196,132,58,0.3)'
      : '0 1px 3px rgba(0,0,0,0.07)';
    const border  = active ? 'var(--c-primary)' : 'var(--c-border)';
    const transform = active ? 'translateY(-2px)' : '';
    const textSec = active ? 'rgba(255,255,255,0.85)' : 'var(--c-text-secondary)';
    const textMut = active ? 'rgba(255,255,255,0.75)' : 'var(--c-text-secondary)';

    return `
      <div data-wttr-day="${i}"
           style="display:flex;flex-direction:column;align-items:center;
                  min-width:72px;padding:var(--space-3) var(--space-2);
                  border-radius:var(--radius);border:1.5px solid ${border};
                  background:${bg};color:${col};cursor:pointer;
                  box-shadow:${shadow};transform:${transform};
                  transition:all .15s;user-select:none">
        <span style="font-size:var(--text-xs);font-weight:600;
                     margin-bottom:var(--space-1)">${_esc(dayName)}</span>
        <div style="margin-bottom:var(--space-1)">${_wmoIcon(d.weathercode, '1.5rem', active ? 'filter:brightness(0) invert(1)' : '')}</div>
        <span class="wttr-temp"
              style="font-size:var(--text-xs);color:${textSec};white-space:nowrap">
          ${Math.round(d.temp_max)}°/<span style="opacity:.75">${Math.round(d.temp_min)}°</span>
        </span>
        <span class="wttr-precip"
              style="font-size:10px;color:${textMut};margin-top:2px">
          <svg class="ph-icon" style="width:10px;height:10px;vertical-align:-1px;color:#60A5FA"><use href="/icons/phosphor.svg#drop"></use></svg>${d.precip_prob ?? 0}%
        </span>
      </div>
    `;
  }

  // ----------------------------------------------------------
  // DETAIL-CARD
  // ----------------------------------------------------------
  function _renderDetail() {
    const el = _container.querySelector('#wttr-detail');
    if (!el || !_data) return;
    const d = (_data.days || [])[_selDay];
    if (!d) return;

    const desc     = WMO_DESC[d.weathercode]  || '';
    const [uvLabel, uvColor] = _uvLabel(d.uv_index ?? 0);
    const uvPct    = Math.min(100, ((d.uv_index ?? 0) / 11) * 100);
    const bft      = _beaufort(d.wind_kmh ?? 0);
    const windDir  = d.wind_dir_deg ?? 0;
    const compass  = d.wind_dir ?? _compass(windDir);

    // Sunrise/Sunset Balken
    const now        = new Date();
    const sunriseStr = d.sunrise || '';
    const sunsetStr  = d.sunset  || '';
    let sunPct = 0;
    if (sunriseStr && sunsetStr) {
      const [rH, rM] = sunriseStr.split(':').map(Number);
      const [sH, sM] = sunsetStr.split(':').map(Number);
      const riseMin  = rH * 60 + rM;
      const setMin   = sH * 60 + sM;
      const curMin   = now.getHours() * 60 + now.getMinutes();
      sunPct = _selDay === 0
        ? Math.min(100, Math.max(0, ((curMin - riseMin) / (setMin - riseMin)) * 100))
        : 0;
    }

    el.innerHTML = `
      <div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-4)">
        ${_wmoIcon(d.weathercode, '3.5rem')}
        <div>
          <div style="font-weight:700;font-size:var(--text-lg)">${_esc(desc)}</div>
          <div style="font-size:var(--text-2xl);font-weight:800;color:var(--c-primary);line-height:1.1">
            ${Math.round(d.temp_max)}°
            <span style="font-size:var(--text-base);font-weight:400;color:var(--c-text-secondary)">
              / ${Math.round(d.temp_min)}°
            </span>
          </div>
          ${d.feels_max != null ? `
          <div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
            Gefühlt ${Math.round(d.feels_max)}° / ${Math.round(d.feels_min ?? d.feels_max)}°
          </div>` : ''}
        </div>
      </div>

      <!-- Sonnenaufgang / -untergang -->
      ${sunriseStr && sunsetStr ? `
      <div style="margin-bottom:var(--space-4)">
        <div style="display:flex;justify-content:space-between;
                    font-size:var(--text-xs);color:var(--c-text-secondary);
                    margin-bottom:var(--space-1)">
          <span style="display:flex;align-items:center;gap:4px">
            <svg class="ph-icon" style="width:14px;height:14px;color:#F97316"><use href="/icons/phosphor.svg#sun-horizon"></use></svg>
            ${_esc(sunriseStr)}
          </span>
          <span style="display:flex;align-items:center;gap:4px">
            ${_esc(sunsetStr)}
            <svg class="ph-icon" style="width:14px;height:14px;color:#7C3AED"><use href="/icons/phosphor.svg#moon-stars"></use></svg>
          </span>
        </div>
        <div style="height:6px;border-radius:999px;background:var(--c-border);overflow:hidden">
          <div style="height:100%;width:${sunPct}%;
                      background:linear-gradient(90deg,#f97316,#facc15);
                      border-radius:999px;transition:width .4s"></div>
        </div>
      </div>` : ''}

      <!-- Wind -->
      <div style="display:flex;align-items:center;gap:var(--space-3);
                  padding:var(--space-3);border-radius:var(--radius);
                  background:var(--c-bg-card);border:1px solid var(--c-border);
                  margin-bottom:var(--space-3)">
        <span style="font-size:1.4rem;transform:rotate(${windDir}deg);display:inline-block;line-height:1">
          ${UI.icon('arrow-up')}
        </span>
        <div style="flex:1">
          <div style="font-size:var(--text-sm);font-weight:600">
            ${_esc(compass)} · ${Math.round(d.windspeed_max ?? 0)} km/h
          </div>
          <div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(bft)}</div>
        </div>
        ${d.precip_sum != null ? `
        <div style="text-align:right">
          <div style="font-size:var(--text-sm);font-weight:600">
            ${d.precip_sum} mm
          </div>
          <div style="font-size:var(--text-xs);color:var(--c-text-secondary)">Niederschlag</div>
        </div>` : ''}
      </div>

      <!-- UV-Index -->
      <div>
        <div style="display:flex;justify-content:space-between;
                    font-size:var(--text-xs);margin-bottom:4px">
          <span style="color:var(--c-text-secondary)">UV-Index</span>
          <span style="font-weight:600;color:${uvColor}">
            ${d.uv_index ?? 0} — ${_esc(uvLabel)}
          </span>
        </div>
        <div style="height:6px;border-radius:999px;background:var(--c-border);overflow:hidden">
          <div style="height:100%;width:${uvPct}%;background:${uvColor};
                      border-radius:999px;transition:width .4s"></div>
        </div>
      </div>
    `;
  }

  // ----------------------------------------------------------
  // HUNDE-WETTER
  // ----------------------------------------------------------
  function _renderDog() {
    const el = _container.querySelector('#wttr-dog');
    if (!el || !_data) return;
    const d = (_data.days || [])[_selDay];
    if (!d) return;

    const _POLLEN_NAMES = { erle:'Erle', birke:'Birke', graeser:'Gräser', beifuss:'Beifuß', ambrosia:'Ambrosia' };
    let html = `<h3 style="font-size:var(--text-base);font-weight:700;
                            margin-bottom:var(--space-4)">
                  <svg class="ph-icon" style="width:1.1em;height:1.1em;vertical-align:-2px;color:var(--c-primary)"><use href="/icons/phosphor.svg#paw-print"></use></svg>
                  Hunde-Wetter
                </h3>`;

    // Asphalt-Temperatur
    if (d.asphalt_temp != null) {
      const [aspText, aspColor, aspAdvice] = _asphaltLevel(d.asphalt_temp);
      html += `
        <div style="display:flex;align-items:flex-start;gap:var(--space-3);
                    padding:var(--space-3);border-radius:var(--radius);
                    background:${aspColor}1a;border:1px solid ${aspColor}55;
                    margin-bottom:var(--space-3)">
          <svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:var(--c-primary)"><use href="/icons/phosphor.svg#paw-print"></use></svg>
          <div style="flex:1">
            <div style="font-weight:600;font-size:var(--text-sm);color:${aspColor}">
              Asphalt ~${Math.round(d.asphalt_temp)}°C — ${_esc(aspText)}
            </div>
            ${aspAdvice ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
              ${_esc(aspAdvice)}
            </div>` : ''}
          </div>
        </div>
      `;
    }

    // Pfoten-Kälteschutz
    if (d.paw_cold) {
      html += `
        <div style="display:flex;align-items:center;gap:var(--space-3);
                    padding:var(--space-3);border-radius:var(--radius);
                    background:#3b82f61a;border:1px solid #3b82f655;
                    margin-bottom:var(--space-3)">
          <svg class="ph-icon" style="width:1.3rem;height:1.3rem;color:#38BDF8"><use href="/icons/phosphor.svg#snowflake"></use></svg>
          <div style="font-size:var(--text-sm)">
            <strong>Kälteschutz für Pfoten:</strong>
            Eis und Streusalz können die Pfoten reizen. Pfotenpflege empfohlen.
          </div>
        </div>
      `;
    }

    // Gewitter
    if (d.thunderstorm) {
      html += `
        <div style="display:flex;align-items:center;gap:var(--space-3);
                    padding:var(--space-3);border-radius:var(--radius);
                    background:#f59e0b1a;border:1px solid #f59e0b55;
                    margin-bottom:var(--space-3)">
          <svg class="ph-icon" style="width:1.3rem;height:1.3rem;color:#7C3AED"><use href="/icons/phosphor.svg#cloud-lightning"></use></svg>
          <div style="font-size:var(--text-sm)">
            <strong>Gewitter erwartet:</strong>
            Hunde können auf Gewitter sensibel reagieren. Sichere Umgebung schaffen.
          </div>
        </div>
      `;
    }

    // Pollenflug
    const pollen = d.pollen;
    if (pollen && typeof pollen === 'object' && Object.keys(pollen).length) {
      const pollenEntries = Object.entries(pollen)
        .filter(([, v]) => v != null && v.level > 0);
      if (pollenEntries.length) {
        html += `
          <div style="margin-bottom:var(--space-3)">
            <div style="font-size:var(--text-xs);font-weight:600;
                        color:var(--c-text-secondary);margin-bottom:var(--space-2)">
              <svg class="ph-icon" style="width:1em;height:1em;vertical-align:-1px;color:#16A34A"><use href="/icons/phosphor.svg#leaf"></use></svg>
              Pollenflug
            </div>
            <div style="display:flex;flex-wrap:wrap;gap:var(--space-2)">
              ${pollenEntries.map(([key, lvlObj]) => {
                const col  = _pollenColor(lvlObj?.level ?? 0);
                const name = _POLLEN_NAMES[key] || key;
                const lbl  = lvlObj?.label || '';
                return `<span style="display:inline-flex;align-items:center;gap:4px;
                                     font-size:var(--text-xs);border-radius:999px;
                                     padding:3px 10px;background:${col}22;
                                     border:1px solid ${col}55;color:${col};font-weight:600">
                          <span style="width:6px;height:6px;border-radius:50%;background:${col};display:inline-block"></span>
                          ${_esc(name)}: ${_esc(lbl)}
                        </span>`;
              }).join('')}
            </div>
          </div>
        `;
      }
    }

    // Zecken
    if (d.zecken != null) {
      const [tickLabel, tickColor] = _tickLevel(d.zecken);
      html += `
        <div style="display:flex;align-items:center;gap:var(--space-3);
                    padding:var(--space-3);border-radius:var(--radius);
                    background:${tickColor}1a;border:1px solid ${tickColor}55;
                    margin-bottom:var(--space-3)">
          <svg class="ph-icon" style="width:1.3rem;height:1.3rem;color:#92400E"><use href="/icons/phosphor.svg#bug"></use></svg>
          <div style="flex:1">
            <span style="font-size:var(--text-sm);font-weight:600">Zecken-Risiko: </span>
            <span style="font-size:var(--text-sm);color:${tickColor};font-weight:700">
              ${_esc(tickLabel)}
            </span>
          </div>
        </div>
      `;
    }

    // Wenn keine Hunde-Daten vorhanden
    if (!d.asphalt_temp && !d.paw_cold && !d.thunderstorm
        && !d.zecken && !(pollen && Object.keys(pollen).length)) {
      html += `
        <p style="font-size:var(--text-sm);color:var(--c-text-secondary)">
          Keine besonderen Hinweise für heute.
        </p>
      `;
    }

    el.innerHTML = html;
  }

  // ----------------------------------------------------------
  // HILFSFUNKTIONEN — Wetter
  // ----------------------------------------------------------
  function _beaufort(kmh) {
    if (kmh < 2)  return 'Windstille';
    if (kmh < 12) return 'leicht';
    if (kmh < 29) return 'mäßig';
    if (kmh < 50) return 'frisch';
    if (kmh < 62) return 'stark';
    if (kmh < 75) return 'stürmisch';
    return 'Sturm';
  }

  function _uvLabel(uv) {
    if (uv <= 2)  return ['niedrig',   '#4CAF50'];
    if (uv <= 5)  return ['mittel',    '#FFC107'];
    if (uv <= 7)  return ['hoch',      '#FF9800'];
    if (uv <= 10) return ['sehr hoch', '#F44336'];
    return              ['extrem',     '#9C27B0'];
  }

  function _compass(deg) {
    const dirs = ['N','NO','O','SO','S','SW','W','NW'];
    return dirs[Math.round(deg / 45) % 8];
  }

  function _asphaltLevel(temp) {
    if (temp < 40) return ['Pfoten sicher',                '#4CAF50', ''];
    if (temp < 50) return ['leicht erwärmt',               '#FFC107',
      'Kurze Kontaktzeiten sind unbedenklich.'];
    if (temp < 60) return ['Vorsicht — Pfoten schützen!',  '#FF9800',
      'Heiße Oberfläche! Auf Gras ausweichen oder Hundeschuhe verwenden.'];
    return               ['GEFAHR — Verbrennungsgefahr!',  '#F44336',
      'Asphalt kann Pfoten in Sekunden verbrennen. Spaziergang vermeiden!'];
  }

  function _pollenColor(level) {
    if (level === 0) return '#9E9E9E';
    if (level === 1) return '#4CAF50';
    if (level === 2) return '#FFC107';
    if (level === 3) return '#FF9800';
    return '#F44336'; // level 4+
  }

  function _tickLevel(risk) {
    const r = (risk || '').toLowerCase();
    if (r === 'niedrig') return ['niedrig', '#4CAF50'];
    if (r === 'mittel')  return ['mittel',  '#FF9800'];
    return                     ['hoch',     '#F44336'];
  }

  function _esc(s) {
    if (s == null) return '';
    return String(s)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

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

})();
