/* ============================================================
   BAN YARO — Forum (Sprint 11)
   Kategorien, Threads, Antworten, Likes, Reports,
   Foto-Upload, Mitgliederkarte, Moderations-Panel
   ============================================================ */

window.Page_forum = (() => {

  let _container  = null;
  let _appState   = null;
  let _threads    = [];
  let _aktivKat   = 'alle';
  let _offset     = 0;
  let _searchTimer = null;
  let _searching  = false;
  let _mapLoaded  = false;
  let _leafletLoaded = false;
  let _map        = null;
  let _clusterGroup = null;
  let _activeSection = 'list'; // 'list' | 'map'

  const LIMIT = 30;

  const KATEGORIEN = [
    { key: 'alle',         label: 'Alle' },
    { key: 'allgemein',    label: 'Allgemein' },
    { key: 'rasse',        label: 'Rasse' },
    { key: 'region',       label: 'Region' },
    { key: 'gesundheit',   label: 'Gesundheit' },
    { key: 'erziehung',    label: 'Erziehung' },
    { key: 'spaziergang',  label: 'Spaziergang' },
    { key: 'ausflug',      label: 'Ausflug' },
    { key: 'training',     label: 'Training & Lektionen' },
    { key: 'ernaehrung',   label: 'Ernährung & Rezepte' },
    { key: 'probleme',     label: 'Probleme' },
    { key: 'tauschboerse', label: 'Tauschbörse' },
  ];

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

  function _fmtDate(iso) {
    if (!iso) return '—';
    const d = new Date(iso);
    const now = new Date();
    const diff = (now - d) / 1000;
    if (diff < 60)    return 'gerade eben';
    if (diff < 3600)  return `vor ${Math.floor(diff / 60)} Min`;
    if (diff < 86400) return `vor ${Math.floor(diff / 3600)} Std`;
    return d.toLocaleDateString('de-DE');
  }

  function _initial(name) {
    return (name || '?').charAt(0).toUpperCase();
  }

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

  function refresh() {
    _loadThreads(true);
  }

  function onDogChange() {}

  // ----------------------------------------------------------
  // RENDER — Grundstruktur
  // ----------------------------------------------------------
  function _render() {
    const isMod = !!_appState.user?.is_moderator;

    _container.innerHTML = `
      <div class="forum-layout">

        <!-- Header -->
        <div class="forum-header">
          <h2 class="forum-header-title">Forum</h2>
          <div class="forum-header-actions">
            ${isMod ? `<button class="btn btn-ghost btn-sm" id="forum-mod-btn" title="Moderationsberichte">${UI.icon('warning')}</button>` : ''}
            <button class="btn btn-ghost btn-sm" id="forum-rules-btn" title="Regeln & Netiquette" style="color:var(--c-text-muted)">${UI.icon('info')} Regeln</button>
            <button class="btn btn-primary btn-sm" id="forum-new-btn">${UI.icon('plus')} Neues Thema</button>
          </div>
        </div>

        <!-- Kategorie-Tabs -->
        <div class="forum-category-tabs by-tabs" id="forum-tabs">
          ${KATEGORIEN.map(k => `
            <button class="by-tab ${k.key === _aktivKat ? 'active' : ''}"
                    data-kat="${k.key}"><span class="by-tab-text">${_esc(k.label)}</span></button>
          `).join('')}
          <button class="by-tab ${_activeSection === 'map' ? 'active' : ''}"
                  data-section="map"><span class="by-tab-text">${UI.icon('users')} Mitgliederkarte</span></button>
        </div>

        <!-- Rechte Spalte: HdM-Kachel + Suche + Threads -->
        <div class="forum-main-col">

          <div id="forum-hdm-card"></div>

          <div class="forum-search-wrap">
            <input type="search" class="forum-search" id="forum-search"
                   placeholder="Forum durchsuchen…" autocomplete="off">
          </div>

          <!-- Thread-Liste / Karte / Suche -->
          <div id="forum-main">
            <p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt…</p>
          </div>

        </div>

      </div>
    `;

    // Desktop: Tabs gleichmäßig auf 2 Zeilen verteilen
    const _tabsEl = document.getElementById('forum-tabs');
    const _tabCount = _tabsEl.querySelectorAll('.by-tab').length;
    _tabsEl.style.setProperty('--forum-tab-cols', Math.ceil(_tabCount / 2));

    // Marquee-Scroll: nur Tabs animieren, bei denen Text wirklich abgeschnitten ist
    _tabsEl.addEventListener('mouseenter', e => {
      const btn  = e.target.closest('.by-tab');
      const span = btn?.querySelector('.by-tab-text');
      if (!span) return;
      const style    = getComputedStyle(btn);
      const padH     = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
      const overflow = span.scrollWidth - (btn.clientWidth - padH);
      if (overflow <= 2) return;
      span.style.setProperty('--tab-scroll-px', `-${overflow}px`);
      span.classList.add('scrolling');
    }, true);
    _tabsEl.addEventListener('mouseleave', e => {
      const span = e.target.closest('.by-tab')?.querySelector('.by-tab-text');
      if (span) span.classList.remove('scrolling');
    }, true);

    // Tab-Klicks
    _tabsEl.addEventListener('click', e => {
      const btn = e.target.closest('[data-kat], [data-section]');
      if (!btn) return;

      if (btn.dataset.section === 'map') {
        _aktivKat      = 'alle';
        _activeSection = 'map';
        document.querySelectorAll('#forum-tabs .by-tab').forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
        _renderMembersMap();
        return;
      }

      _aktivKat      = btn.dataset.kat;
      _activeSection = 'list';
      document.querySelectorAll('#forum-tabs .by-tab').forEach(b => b.classList.remove('active'));
      btn.classList.add('active');
      _offset  = 0;
      _threads = [];
      _loadThreads(true);
    });

    // Suche
    document.getElementById('forum-search').addEventListener('input', e => {
      clearTimeout(_searchTimer);
      const q = e.target.value.trim();
      if (!q) {
        _searching = false;
        _renderList();
        return;
      }
      _searchTimer = setTimeout(() => _doSearch(q), 400);
    });

    // Neues Thema
    document.getElementById('forum-new-btn').addEventListener('click', () => {
      if (!_appState.user) { UI.toast.info('Bitte erst anmelden.'); return; }
      _showCreateForm();
    });

    // Moderations-Panel
    document.getElementById('forum-mod-btn')?.addEventListener('click', _showModPanel);

    // Regeln & Netiquette
    document.getElementById('forum-rules-btn').addEventListener('click', _showRules);
  }

  // ----------------------------------------------------------
  // Hund des Monats — Kachel + Modal
  // ----------------------------------------------------------
  async function _loadHdmCard() {
    const card = document.getElementById('forum-hdm-card');
    if (!card) return;
    try {
      const data = await API.get('/movies/hund-des-monats');
      const [year, month] = data.monat.split('-');
      const monthName = new Intl.DateTimeFormat('de-DE', { month: 'long' })
        .format(new Date(+year, +month - 1, 1));
      const top = data.top?.[0];
      const winnerLine = top
        ? `🥇 ${_esc(top.name)}${top.rasse ? ` · ${_esc(top.rasse)}` : ''}`
        : 'Noch keine Stimmen';
      const metaLine = top
        ? `${top.stimmen} Stimme${top.stimmen !== 1 ? 'n' : ''}`
        : 'Sei der Erste!';

      card.innerHTML = `
        <div class="forum-hdm-tile" id="forum-hdm-tile">
          <div class="forum-hdm-tile-trophy">🏆</div>
          <div class="forum-hdm-tile-body">
            <div class="forum-hdm-tile-title">Hund des Monats · ${_esc(monthName)}</div>
            <div class="forum-hdm-tile-winner">${winnerLine}</div>
            <div class="forum-hdm-tile-meta">${metaLine}</div>
          </div>
          <div class="forum-hdm-tile-cta">${UI.icon('arrow-right')}</div>
        </div>`;

      document.getElementById('forum-hdm-tile')?.addEventListener('click', () => _openHdmModal(data));
    } catch {
      // Kachel bleibt leer bei Fehler
    }
  }

  async function _openHdmModal(data) {
    try { data = await API.get('/movies/hund-des-monats'); } catch { /* gecachte Daten */ }

    const [year, month] = data.monat.split('-');
    const monthName = new Intl.DateTimeFormat('de-DE', { month: 'long', year: 'numeric' })
      .format(new Date(+year, +month - 1, 1));

    const topList = data.top?.length
      ? data.top.slice(0, 5).map((dog, i) => {
          const medal = ['🥇','🥈','🥉','4️⃣','5️⃣'][i];
          const av = dog.foto_url
            ? `<img src="${_esc(dog.foto_url)}" alt="${_esc(dog.name)}" class="hdm-top-av-img">`
            : `<span class="hdm-top-av-placeholder">${_esc(dog.name.charAt(0).toUpperCase())}</span>`;
          const vorname = dog.besitzer_name ? _esc(dog.besitzer_name.split(' ')[0]) : '';
          return `
            <div class="hdm-top-entry">
              <span class="hdm-top-medal">${medal}</span>
              <div class="hdm-top-av">${av}</div>
              <div class="hdm-top-info">
                <div class="hdm-top-name">${_esc(dog.name)}</div>
                ${dog.rasse ? `<div class="hdm-top-rasse">${_esc(dog.rasse)}</div>` : ''}
                ${vorname ? `<div class="hdm-top-besitzer">von ${vorname}</div>` : ''}
              </div>
              <div class="hdm-top-stimmen">${dog.stimmen} ${UI.icon('star')}</div>
            </div>`;
        }).join('')
      : `<p style="color:var(--c-text-secondary);padding:var(--space-4)">Noch keine Stimmen. Sei der Erste!</p>`;

    const voteHint = !_appState.user
      ? `<div class="hdm-section">
           <p style="color:var(--c-text-secondary);font-size:var(--text-sm)">
             <a href="#" id="hdm-login-link" style="color:var(--c-primary);font-weight:var(--weight-semibold)">Anmelden</a>
             um abstimmen zu können.
           </p>
         </div>`
      : `<div class="hdm-section">
           <h3 class="hdm-section-title">Für welchen Hund möchtest du abstimmen?</h3>
           <div class="hdm-kandidaten-search">
             <input type="search" id="hdm-search" class="form-control"
                    placeholder="Name oder Rasse suchen …" autocomplete="off"
                    style="font-size:var(--text-sm)">
           </div>
           <div id="hdm-kandidaten-grid" class="hdm-vote-grid">
             ${UI.skeleton(3)}
           </div>
         </div>`;

    const body = `
      <div class="hdm-header">
        <div class="hdm-trophy">🏆</div>
        <h2 class="hdm-title">Hund des Monats</h2>
        <div class="hdm-monat">${_esc(monthName)}</div>
      </div>
      ${voteHint}
      <div class="hdm-section">
        <h3 class="hdm-section-title">Top 5 diesen Monat</h3>
        <div id="hdm-top-list">${topList}</div>
      </div>`;

    UI.modal.open({ title: '🏆 Hund des Monats', body,
      footer: `<button class="btn btn-secondary flex-1" onclick="UI.modal.close()">Schließen</button>` });

    document.getElementById('hdm-login-link')?.addEventListener('click', e => {
      e.preventDefault(); UI.modal.close(); App.navigate('settings');
    });

    if (!_appState.user) return;

    // Kandidaten laden und rendern
    let _kandidaten = [];
    const _renderKandidaten = (list) => {
      const grid = document.getElementById('hdm-kandidaten-grid');
      if (!grid) return;
      if (!list.length) {
        grid.innerHTML = `<p style="color:var(--c-text-secondary);font-size:var(--text-sm);padding:var(--space-3) 0">Keine Hunde gefunden.</p>`;
        return;
      }
      grid.innerHTML = list.map(dog => {
        const isVoted = data.user_vote === dog.id;
        const av = dog.foto_url
          ? `<img src="${_esc(dog.foto_url)}" alt="${_esc(dog.name)}" class="hdm-vote-av-img">`
          : `<span class="hdm-vote-av-placeholder">${_esc(dog.name.charAt(0).toUpperCase())}</span>`;
        const vorname = dog.besitzer_name ? _esc(dog.besitzer_name.split(' ')[0]) : '';
        return `
          <div class="hdm-vote-card${isVoted ? ' hdm-vote-card--voted' : ''}">
            <div class="hdm-vote-av">${av}</div>
            <div class="hdm-vote-name">${_esc(dog.name)}</div>
            ${dog.rasse ? `<div class="hdm-vote-rasse">${_esc(dog.rasse)}</div>` : ''}
            ${vorname ? `<div class="hdm-vote-besitzer" style="font-size:var(--text-xs);color:var(--c-text-muted)">von ${vorname}</div>` : ''}
            ${dog.stimmen > 0 ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${dog.stimmen} ${UI.icon('star')}</div>` : ''}
            <button class="btn btn-sm ${isVoted ? 'btn-primary' : 'btn-secondary'} hdm-vote-btn"
                    data-dog-id="${dog.id}" ${isVoted ? 'disabled' : ''}>
              ${isVoted ? `${UI.icon('check-circle')} Gewählt` : 'Abstimmen'}
            </button>
          </div>`;
      }).join('');

      grid.querySelectorAll('.hdm-vote-btn:not([disabled])').forEach(btn => {
        btn.addEventListener('click', async () => {
          const dogId = parseInt(btn.dataset.dogId);
          await UI.asyncButton(btn, async () => {
            try {
              await API.post('/movies/hund-des-monats/vote', { dog_id: dogId });
              data.user_vote = dogId;
              UI.toast.success('Stimme abgegeben!');
              UI.modal.close();
              _loadHdmCard();
            } catch (err) {
              UI.toast.error(err.message || 'Fehler beim Abstimmen.');
            }
          });
        });
      });
    };

    try {
      _kandidaten = await API.get('/movies/hund-des-monats/kandidaten');
    } catch {
      document.getElementById('hdm-kandidaten-grid').innerHTML =
        `<p style="color:var(--c-danger);font-size:var(--text-sm)">Kandidaten konnten nicht geladen werden.</p>`;
      return;
    }
    _renderKandidaten(_kandidaten);

    document.getElementById('hdm-search')?.addEventListener('input', e => {
      const q = e.target.value.trim().toLowerCase();
      _renderKandidaten(q
        ? _kandidaten.filter(d =>
            (d.name  || '').toLowerCase().includes(q) ||
            (d.rasse || '').toLowerCase().includes(q))
        : _kandidaten
      );
    });
  }

  // ----------------------------------------------------------
  // Threads laden
  // ----------------------------------------------------------
  async function _loadThreads(reset = false) {
    if (reset) { _offset = 0; _threads = []; }

    const mainEl = document.getElementById('forum-main');
    if (mainEl && reset) {
      mainEl.innerHTML = `<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt…</p>`;
    }

    try {
      const params = { limit: LIMIT, offset: _offset };
      if (_aktivKat !== 'alle') params.kategorie = _aktivKat;

      const rows = await API.forum.threads(params);
      _threads   = reset ? rows : [..._threads, ...rows];
      _offset   += rows.length;
      _renderList(rows.length < LIMIT);
    } catch (err) {
      UI.toast.error(err.message || 'Fehler beim Laden.');
    }
  }

  // ----------------------------------------------------------
  // Thread-Liste rendern
  // ----------------------------------------------------------
  function _renderList(noMore = false) {
    if (_searching) return;
    const el = document.getElementById('forum-main');
    if (!el) return;

    if (!_threads.length) {
      el.innerHTML = `
        <div style="text-align:center;padding:var(--space-10) var(--space-4)">
          <div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('chat-circle-dots')}</div>
          <p style="color:var(--c-text-secondary)">Noch keine Beiträge in dieser Kategorie.</p>
          <button class="btn btn-primary" style="margin-top:var(--space-4)" id="forum-first-btn">
            Ersten Beitrag erstellen
          </button>
        </div>`;
      document.getElementById('forum-first-btn')?.addEventListener('click', () => {
        if (!_appState.user) { UI.toast.info('Bitte erst anmelden.'); return; }
        _showCreateForm();
      });
      return;
    }

    el.innerHTML = `
      <div class="forum-list-inner" id="forum-thread-list">
        ${_threads.map(_threadCardHTML).join('')}
      </div>
      ${!noMore ? `<div style="text-align:center;padding:var(--space-4)">
        <button class="btn btn-secondary btn-sm" id="forum-loadmore">Mehr laden</button>
      </div>` : ''}
    `;

    el.querySelectorAll('.forum-thread-card').forEach(card => {
      card.addEventListener('click', () => _openThread(parseInt(card.dataset.id)));
    });

    document.getElementById('forum-loadmore')?.addEventListener('click', () => {
      _loadThreads(false);
    });
  }

  function _threadCardHTML(t) {
    const preview = t.text_preview
      ? _esc(t.text_preview.slice(0, 120)) + (t.text_preview.length >= 120 ? '…' : '')
      : '';
    const pinBadge = t.is_pinned ? `<span class="forum-pin-badge" title="Angepinnt">${UI.icon('push-pin')}</span>` : '';
    const lockBadge = t.is_locked ? `<span class="forum-lock-badge" title="Gesperrt">${UI.icon('lock')}</span>` : '';
    const fotoHtml = t.foto_preview
      ? /\.(mp4|mov|webm|m4v|avi)$/i.test(t.foto_preview)
        ? `<div class="forum-card-thumb forum-card-thumb--video" style="display:flex;align-items:center;justify-content:center;background:var(--c-surface-2)">${UI.icon('video-camera')}</div>`
        : `<img class="forum-card-thumb" src="${_esc(t.foto_preview_url || t.foto_preview)}"
             ${(t.foto_preview_url && t.foto_preview) ? `srcset="${_esc(t.foto_preview_url)} 800w" sizes="120px"` : ''}
             alt="" loading="lazy"
             onerror="this.src='${_esc(t.foto_preview)}'">`
      : '';

    return `
      <div class="forum-thread-card" data-id="${t.id}">
        <div class="forum-card-top">
          <span class="forum-category-badge forum-category-badge--${_esc(t.kategorie)}">${_esc(t.kategorie)}</span>
          ${pinBadge}${lockBadge}
        </div>
        <div class="forum-card-content">
          <div class="forum-card-main">
            <div class="forum-card-title">${_esc(t.titel)}</div>
            ${preview ? `<div class="forum-card-preview">${preview}</div>` : ''}
            <div class="forum-card-meta">
              <span>${UI.icon('user')} ${_esc(t.autor_name || 'Unbekannt')}</span>
              <span>${UI.icon('calendar-dots')} ${_fmtDate(t.created_at)}</span>
              <span>${UI.icon('chat-circle-dots')} ${t.antworten || 0}</span>
              <span class="${t.user_liked ? 'forum-liked' : ''}">${UI.icon('heart')} ${t.likes || 0}</span>
            </div>
          </div>
          ${fotoHtml}
        </div>
      </div>`;
  }

  // ----------------------------------------------------------
  // Suche
  // ----------------------------------------------------------
  async function _doSearch(q) {
    _searching = true;
    const el = document.getElementById('forum-main');
    if (el) el.innerHTML = `<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-6)">Suche…</p>`;

    try {
      const results = await API.forum.search(q);
      if (!document.getElementById('forum-main')) return;
      if (!results.length) {
        document.getElementById('forum-main').innerHTML = `
          <div style="text-align:center;padding:var(--space-8)">
            <div style="font-size:2rem;margin-bottom:var(--space-2)">${UI.icon('magnifying-glass')}</div>
            <p style="color:var(--c-text-secondary)">Keine Ergebnisse für „${_esc(q)}"</p>
          </div>`;
        return;
      }
      document.getElementById('forum-main').innerHTML = `
        <div class="forum-list-inner">
          ${results.map(t => _threadCardHTML({ ...t, foto_preview: null, is_pinned: 0, is_locked: 0, user_liked: false })).join('')}
        </div>`;
      document.getElementById('forum-main').querySelectorAll('.forum-thread-card').forEach(card => {
        card.addEventListener('click', () => _openThread(parseInt(card.dataset.id)));
      });
    } catch (err) {
      UI.toast.error(err.message || 'Suchfehler.');
    }
  }

  // ----------------------------------------------------------
  // Thread-Detail-Modal
  // ----------------------------------------------------------
  async function _openThread(threadId) {
    let thread;
    try {
      thread = await API.forum.thread(threadId);
    } catch (err) {
      UI.toast.error(err.message);
      return;
    }

    const uid   = _appState.user?.id;
    const isMod = !!_appState.user?.is_moderator;
    const isOwn = uid && uid === thread.user_id;

    const modToolbar = (isMod) ? `
      <div class="forum-mod-toolbar">
        <button class="btn btn-ghost btn-sm forum-mod-pin" title="${thread.is_pinned ? 'Unpin' : 'Anpinnen'}">
          ${UI.icon('push-pin')} ${thread.is_pinned ? 'Unpin' : 'Pin'}
        </button>
        <button class="btn btn-ghost btn-sm forum-mod-lock" title="${thread.is_locked ? 'Entsperren' : 'Sperren'}">
          ${UI.icon('lock')} ${thread.is_locked ? 'Entsperren' : 'Sperren'}
        </button>
        <button class="btn btn-ghost btn-sm forum-mod-delete-thread" style="color:var(--c-danger)">${UI.icon('trash')} Thread</button>
      </div>` : '';

    const _forumMediaHtml = (u) => {
      if (u.endsWith('.pdf'))
        return `<a href="${_esc(u)}" target="_blank" rel="noopener" class="forum-pdf-card">
                  ${UI.icon('file-text')} <span>${_esc(u.split('/').pop())}</span></a>`;
      if (/\.(mp4|mov|webm|m4v|avi)$/i.test(u)) {
        const poster = u.replace(/\.[^.]+$/, '_thumb.jpg');
        return `<video src="${_esc(u)}" poster="${_esc(poster)}" controls playsinline
                       style="max-width:100%;max-height:320px;border-radius:var(--radius-md);display:block"></video>`;
      }
      return `<img src="${_esc(u)}" class="forum-foto-img" data-src="${_esc(u)}" alt="" loading="lazy">`;
    };
    const fotoGallery = (thread.foto_urls?.length)
      ? `<div class="forum-foto-grid">${thread.foto_urls.map(_forumMediaHtml).join('')}</div>`
      : '';

    const likeClass = thread.user_liked ? 'forum-like-btn active' : 'forum-like-btn';

    const postsHtml = (thread.posts?.length)
      ? thread.posts.map(p => _postHTML(p, uid, isMod)).join('')
      : `<p style="color:var(--c-text-muted);font-style:italic;padding:var(--space-3) 0">Noch keine Antworten.</p>`;

    const replySection = _appState.user && !thread.is_locked ? `
      <div class="forum-reply-form">
        <label class="form-label">Antwort schreiben</label>
        <textarea class="form-control" id="forum-reply-text" rows="3"
                  placeholder="Deine Antwort…"></textarea>
        <div class="forum-reply-actions">
          <label class="btn btn-ghost btn-sm forum-upload-label" title="Foto anhängen">
            ${UI.icon('camera')}
            <input type="file" accept="image/*" id="forum-reply-file" style="display:none">
          </label>
          <div id="forum-reply-previews" class="forum-upload-previews"></div>
        </div>
      </div>` : (!_appState.user
        ? `<p style="color:var(--c-text-muted);font-size:0.85rem;margin-top:var(--space-3)">Bitte anmelden um zu antworten.</p>`
        : `<p style="color:var(--c-text-muted);font-size:0.85rem;margin-top:var(--space-3)">${UI.icon('lock')} Dieser Thread ist gesperrt.</p>`
      );

    const body = `
      <div class="forum-thread-detail">
        ${modToolbar}
        <div class="forum-thread-header-row">
          <span class="forum-category-badge forum-category-badge--${_esc(thread.kategorie)}">${_esc(thread.kategorie)}</span>
          <span style="color:var(--c-text-muted);font-size:0.8rem">${_fmtDate(thread.created_at)}</span>
          ${thread.is_pinned ? `<span>${UI.icon('push-pin')}</span>` : ''}
          ${thread.is_locked ? `<span>${UI.icon('lock')}</span>` : ''}
        </div>

        <div class="forum-thread-body">
          <p style="white-space:pre-wrap;word-break:break-word">${_esc(thread.text)}</p>
          ${fotoGallery}
        </div>

        <div class="forum-thread-author-row">
          <div class="forum-avatar">${_esc(_initial(thread.autor_name))}</div>
          <span style="font-size:0.85rem;color:var(--c-text-secondary)">${_esc(thread.autor_name || 'Unbekannt')}</span>
          ${thread.autor_founder_number ? `<span style="font-size:10px;font-weight:700;background:#7c3aed;color:#fff;padding:1px 5px;border-radius:4px">Gründer #${thread.autor_founder_number}</span>` : ''}
          <div style="margin-left:auto;display:flex;gap:var(--space-2);align-items:center">
            <button class="${likeClass}" id="thread-like-btn" data-count="${thread.likes || 0}">
              ${UI.icon('heart')} <span id="thread-like-count">${thread.likes || 0}</span>
            </button>
            ${_appState.user && !isOwn ? `<button class="forum-report-btn" id="thread-report-btn">${UI.icon('flag')}</button>` : ''}
          </div>
        </div>

        <div class="forum-posts-section">
          <div class="forum-posts-divider">${thread.antworten || 0} Antworten</div>
          <div id="forum-posts-list">${postsHtml}</div>
        </div>

        ${replySection}
      </div>
    `;

    const footer = _appState.user ? `
      <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
        <div style="display:flex;gap:var(--space-2);align-items:center">
          ${(isOwn || isMod) ? `<button type="button" class="forum-icon-btn forum-icon-btn--danger" id="ft-delete-thread" title="Löschen">${UI.icon('trash')}</button>` : ''}
          ${isOwn ? `<button type="button" class="forum-icon-btn" id="ft-edit-thread" title="Bearbeiten">${UI.icon('pencil-simple')}</button>` : ''}
          <div style="margin-left:auto;display:flex;gap:var(--space-2)">
            <button type="button" class="btn btn-secondary btn-sm" id="ft-close">Schließen</button>
            ${(!thread.is_locked && _appState.user) ? `<button type="button" class="btn btn-primary btn-sm" id="ft-reply">Antworten</button>` : ''}
          </div>
        </div>
      </div>
    ` : `<button type="button" class="btn btn-primary w-full" id="ft-close">Schließen</button>`;

    UI.modal.open({ title: `${UI.icon('chat-circle-dots')} ${_esc(thread.titel)}`, body, footer });

    // Close
    document.getElementById('ft-close')?.addEventListener('click', UI.modal.close);

    // Like thread
    document.getElementById('thread-like-btn')?.addEventListener('click', async () => {
      if (!_appState.user) { UI.toast.info('Bitte erst anmelden.'); return; }
      const btn = document.getElementById('thread-like-btn');
      try {
        const res = await API.forum.like('thread', thread.id);
        thread.user_liked = res.liked;
        thread.likes      = res.count;
        btn.classList.toggle('active', res.liked);
        const countEl = document.getElementById('thread-like-count');
        if (countEl) countEl.textContent = res.count;
        // Update local state
        const idx = _threads.findIndex(t => t.id === thread.id);
        if (idx !== -1) { _threads[idx].likes = res.count; _threads[idx].user_liked = res.liked; }
      } catch (err) { UI.toast.error(err.message); }
    });

    // Report thread
    document.getElementById('thread-report-btn')?.addEventListener('click', () => {
      _showReportForm('thread', thread.id);
    });

    // Delete thread
    document.getElementById('ft-delete-thread')?.addEventListener('click', async () => {
      const ok = await UI.modal.confirm({
        title: 'Thread löschen?',
        message: 'Der Thread wird unwiderruflich entfernt.',
        confirmText: 'Löschen', danger: true,
      });
      if (!ok) return;
      try {
        await API.forum.deleteThread(thread.id);
        _threads = _threads.filter(t => t.id !== thread.id);
        UI.modal.close();
        _renderList(true);
        UI.toast.success('Thread gelöscht.');
      } catch (err) { UI.toast.error(err.message); }
    });

    // Edit thread (owner)
    document.getElementById('ft-edit-thread')?.addEventListener('click', () => {
      _showEditThreadModal(thread);
    });

    // Moderator: pin/lock/delete
    document.querySelector('.forum-mod-pin')?.addEventListener('click', async () => {
      try {
        await API.forum.patchThread(thread.id, { is_pinned: thread.is_pinned ? 0 : 1 });
        UI.toast.success('Gespeichert.');
        UI.modal.close();
        _loadThreads(true);
      } catch (err) { UI.toast.error(err.message); }
    });

    document.querySelector('.forum-mod-lock')?.addEventListener('click', async () => {
      try {
        await API.forum.patchThread(thread.id, { is_locked: thread.is_locked ? 0 : 1 });
        UI.toast.success('Gespeichert.');
        UI.modal.close();
        _loadThreads(true);
      } catch (err) { UI.toast.error(err.message); }
    });

    document.querySelector('.forum-mod-delete-thread')?.addEventListener('click', async () => {
      const ok = await UI.modal.confirm({ title: 'Thread löschen?', message: 'Moderator-Löschung.', confirmText: 'Löschen', danger: true });
      if (!ok) return;
      try {
        await API.forum.deleteThread(thread.id);
        _threads = _threads.filter(t => t.id !== thread.id);
        UI.modal.close();
        _renderList(true);
        UI.toast.success('Thread gelöscht.');
      } catch (err) { UI.toast.error(err.message); }
    });

    // Foto-Vollbild
    document.getElementById('modal-container')?.querySelectorAll('.forum-foto-img').forEach(img => {
      img.addEventListener('click', () => _showLightbox(img.dataset.src || img.src));
    });

    // Reply file preview
    const replyFileInput = document.getElementById('forum-reply-file');
    replyFileInput?.addEventListener('change', () => {
      const previews = document.getElementById('forum-reply-previews');
      if (!previews) return;
      previews.innerHTML = '';
      const files = Array.from(replyFileInput.files || []);
      files.slice(0, 5).forEach(file => {
        const url = URL.createObjectURL(file);
        const img = document.createElement('img');
        img.src       = url;
        img.className = 'forum-upload-thumb';
        previews.appendChild(img);
      });
    });

    // Post-Löschen-Buttons binden
    const postsListEl = document.getElementById('forum-posts-list');
    if (postsListEl) _bindPostActions(postsListEl, thread.id, uid, isMod);

    // Reply abschicken
    document.getElementById('ft-reply')?.addEventListener('click', async () => {
      const btn  = document.getElementById('ft-reply');
      const text = document.getElementById('forum-reply-text')?.value?.trim();
      if (!text) { UI.toast.warning('Bitte Text eingeben.'); return; }

      await UI.asyncButton(btn, async () => {
        const post = await API.forum.addPost(thread.id, { text, client_time: API.clientNow() });

        // Foto hochladen falls vorhanden
        const files = Array.from(document.getElementById('forum-reply-file')?.files || []);
        for (const file of files.slice(0, 5)) {
          try {
            await API.forum.uploadPostFoto(post.id, file);
          } catch (e) { /* Foto-Upload-Fehler ignorieren */ }
        }

        thread.antworten = (thread.antworten || 0) + 1;
        const idx = _threads.findIndex(t => t.id === thread.id);
        if (idx !== -1) _threads[idx].antworten = thread.antworten;

        const listEl = document.getElementById('forum-posts-list');
        if (listEl) {
          const placeholder = listEl.querySelector('p[style*="italic"]');
          if (placeholder) listEl.innerHTML = '';
          listEl.insertAdjacentHTML('beforeend', _postHTML(post, uid, isMod));
          _bindPostActions(listEl, thread.id, uid, isMod);
          listEl.lastElementChild?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
        }
        document.getElementById('forum-reply-text').value = '';
        const previews = document.getElementById('forum-reply-previews');
        if (previews) previews.innerHTML = '';
        UI.toast.success('Antwort gesendet.');
      });
    });
  }

  // ----------------------------------------------------------
  // Post HTML
  // ----------------------------------------------------------
  function _postHTML(p, uid, isMod) {
    if (p.is_deleted) {
      return `<div class="forum-post forum-post--deleted" data-post-id="${p.id}">
        <em>Beitrag wurde entfernt</em>
      </div>`;
    }

    const isOwn = uid && uid === p.user_id;
    const fotoHtml = (p.foto_urls?.length)
      ? `<div class="forum-foto-grid">${p.foto_urls.map(u =>
          `<img src="${_esc(u)}" class="forum-foto-img" data-src="${_esc(u)}" alt="" loading="lazy">`
        ).join('')}</div>`
      : '';

    const likeClass = p.user_liked ? 'forum-like-btn active' : 'forum-like-btn';
    const canDelete = isOwn || isMod;

    return `
      <div class="forum-post" data-post-id="${p.id}" data-user-id="${p.user_id || ''}">
        <div class="forum-post-header">
          <div class="forum-avatar forum-avatar--sm">${_esc(_initial(p.autor_name))}</div>
          <span class="forum-post-author">${_esc(p.autor_name || 'Unbekannt')}</span>
          ${p.autor_founder_number ? `<span style="font-size:10px;font-weight:700;background:#7c3aed;color:#fff;padding:1px 5px;border-radius:4px">Gründer #${p.autor_founder_number}</span>` : ''}
          <span class="forum-post-date">${_fmtDate(p.created_at)}</span>
        </div>
        <div class="forum-post-body">
          <div class="forum-post-text">${_esc(p.text)}</div>
          ${fotoHtml}
        </div>
        <div class="forum-post-actions">
          <button class="${likeClass} forum-post-like" data-post-id="${p.id}">
            ${UI.icon('heart')} <span class="forum-post-like-count">${p.likes || 0}</span>
          </button>
          ${(!isOwn && uid) ? `<button class="forum-icon-btn forum-post-report" data-post-id="${p.id}" title="Melden">${UI.icon('flag')}</button>` : ''}
          <div style="margin-left:auto;display:flex;gap:4px">
            ${isOwn ? `<button class="forum-icon-btn forum-post-edit" data-post-id="${p.id}" data-text="${_esc(p.text || '')}" title="Bearbeiten">${UI.icon('pencil-simple')}</button>` : ''}
            ${canDelete ? `<button class="forum-icon-btn forum-icon-btn--danger forum-post-delete" data-post-id="${p.id}" title="Löschen">${UI.icon('trash')}</button>` : ''}
          </div>
        </div>
      </div>`;
  }

  // ----------------------------------------------------------
  // Post-Aktionen binden
  // ----------------------------------------------------------
  function _bindPostActions(container, threadId, uid, isMod) {
    // Like
    container.querySelectorAll('.forum-post-like:not([data-bound])').forEach(btn => {
      btn.dataset.bound = '1';
      btn.addEventListener('click', async () => {
        if (!uid) { UI.toast.info('Bitte erst anmelden.'); return; }
        const postId = parseInt(btn.dataset.postId);
        try {
          const res = await API.forum.like('post', postId);
          btn.classList.toggle('active', res.liked);
          const countEl = btn.querySelector('.forum-post-like-count');
          if (countEl) countEl.textContent = res.count;
        } catch (err) { UI.toast.error(err.message); }
      });
    });

    // Report
    container.querySelectorAll('.forum-post-report:not([data-bound])').forEach(btn => {
      btn.dataset.bound = '1';
      btn.addEventListener('click', () => {
        _showReportForm('post', parseInt(btn.dataset.postId));
      });
    });

    // Edit (owner)
    container.querySelectorAll('.forum-post-edit:not([data-bound])').forEach(btn => {
      btn.dataset.bound = '1';
      btn.addEventListener('click', () => {
        const postId      = parseInt(btn.dataset.postId);
        const currentText = btn.dataset.text || '';
        _showEditPostModal(postId, currentText, container, threadId, uid, isMod);
      });
    });

    // Delete
    container.querySelectorAll('.forum-post-delete:not([data-bound])').forEach(btn => {
      btn.dataset.bound = '1';
      btn.addEventListener('click', async () => {
        const postId  = parseInt(btn.dataset.postId);
        const postEl  = container.querySelector(`[data-post-id="${postId}"]`);
        const ok = await UI.modal.confirm({
          title: 'Antwort löschen?',
          message: 'Dieser Beitrag wird entfernt.',
          confirmText: 'Löschen', danger: true,
        });
        if (!ok) return;
        try {
          await API.forum.deletePost(postId);
          if (postEl) {
            postEl.innerHTML = '<em style="color:var(--c-text-muted)">Beitrag wurde entfernt</em>';
            postEl.className = 'forum-post forum-post--deleted';
          }
          const idx = _threads.findIndex(t => t.id === threadId);
          if (idx !== -1 && _threads[idx].antworten > 0) _threads[idx].antworten--;
          UI.toast.success('Beitrag gelöscht.');
        } catch (err) { UI.toast.error(err.message); }
      });
    });

    // Foto-Fullscreen
    container.querySelectorAll('.forum-foto-img:not([data-bound])').forEach(img => {
      img.dataset.bound = '1';
      img.addEventListener('click', () => _showLightbox(img.dataset.src || img.src));
    });
  }

  // ----------------------------------------------------------
  // Report-Formular
  // ----------------------------------------------------------
  function _showReportForm(targetType, targetId) {
    const body = `
      <form id="forum-report-form">
        <div class="form-group">
          <label class="form-label">Grund der Meldung</label>
          <select class="form-control" name="grund">
            <option value="spam">Spam</option>
            <option value="beleidigung">Beleidigung / Hassrede</option>
            <option value="falschinfo">Falsche Informationen</option>
            <option value="sonstiges">Sonstiges</option>
          </select>
        </div>
      </form>`;
    const footer = `
      <button type="button" class="btn btn-secondary flex-1" id="rep-cancel">Abbrechen</button>
      <button type="submit" form="forum-report-form" class="btn btn-danger flex-1">Melden</button>`;

    UI.modal.open({ title: `${UI.icon('flag')} Inhalt melden`, body, footer });
    document.getElementById('rep-cancel')?.addEventListener('click', UI.modal.close);

    document.getElementById('forum-report-form')?.addEventListener('submit', async e => {
      e.preventDefault();
      const btn  = document.querySelector('[form="forum-report-form"][type="submit"]');
      const fd   = UI.formData(e.target);
      await UI.asyncButton(btn, async () => {
        await API.forum.report(targetType, targetId, fd.grund);
        UI.modal.close();
        UI.toast.success('Gemeldet. Danke!');
      });
    });
  }

  // ----------------------------------------------------------
  // Neues Thema
  // ----------------------------------------------------------
  // Regeln & Netiquette
  // ----------------------------------------------------------
  function _showRules() {
    UI.modal.open({
      title: `${UI.icon('info')} Regeln & Netiquette`,
      body: `
        <div style="display:flex;flex-direction:column;gap:var(--space-4)">

          <div>
            <p style="color:var(--c-text-secondary);font-size:var(--text-sm);margin-bottom:var(--space-3)">
              Das Ban-Yaro-Forum ist ein Ort für Hundehalter — freundlich, hilfsbereit und respektvoll.
              Bitte halte diese Grundregeln ein, damit es für alle schön bleibt.
            </p>
          </div>

          <div>
            <div style="font-weight:var(--weight-semibold);margin-bottom:var(--space-2);display:flex;align-items:center;gap:var(--space-2)">
              ${UI.icon('chat-circle-dots')} Ton & Umgang
            </div>
            <ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-1);font-size:var(--text-sm);color:var(--c-text-secondary)">
              <li>${UI.icon('check')} Freundlich und respektvoll bleiben — auch bei verschiedenen Meinungen</li>
              <li>${UI.icon('check')} Konstruktive Kritik statt persönliche Angriffe</li>
              <li>${UI.icon('check')} Andere Haltungsmethoden tolerieren — solange der Hund nicht leidet</li>
              <li>${UI.icon('prohibit')} Keine Beleidigungen, Drohungen oder Diskriminierung</li>
            </ul>
          </div>

          <div>
            <div style="font-weight:var(--weight-semibold);margin-bottom:var(--space-2);display:flex;align-items:center;gap:var(--space-2)">
              ${UI.icon('files')} Inhalte
            </div>
            <ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-1);font-size:var(--text-sm);color:var(--c-text-secondary)">
              <li>${UI.icon('check')} Thema passend zur gewählten Kategorie</li>
              <li>${UI.icon('check')} Aussagekräftiger Titel — kein "Hilfe!!!" ohne Kontext</li>
              <li>${UI.icon('prohibit')} Keine Werbung, Spam oder Affiliate-Links</li>
              <li>${UI.icon('prohibit')} Keine Doppelposts — bestehenden Thread suchen bevor du neu erstellst</li>
            </ul>
          </div>

          <div>
            <div style="font-weight:var(--weight-semibold);margin-bottom:var(--space-2);display:flex;align-items:center;gap:var(--space-2)">
              ${UI.icon('syringe')} Gesundheit & Notfälle
            </div>
            <ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-1);font-size:var(--text-sm);color:var(--c-text-secondary)">
              <li>${UI.icon('check')} Erfahrungen teilen ist wertvoll — bitte immer den Tierarzt empfehlen</li>
              <li>${UI.icon('prohibit')} Keine Diagnosen oder Medikamentendosierungen für fremde Hunde</li>
              <li>${UI.icon('warning-circle')} Bei Notfällen: direkt zum Tierarzt — nicht erst im Forum fragen</li>
            </ul>
          </div>

          <div>
            <div style="font-weight:var(--weight-semibold);margin-bottom:var(--space-2);display:flex;align-items:center;gap:var(--space-2)">
              ${UI.icon('flag')} Moderation
            </div>
            <ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-1);font-size:var(--text-sm);color:var(--c-text-secondary)">
              <li>${UI.icon('check')} Regelverstoß? Melde-Funktion nutzen statt selbst zu reagieren</li>
              <li>${UI.icon('check')} Moderatoren können Beiträge bearbeiten, verstecken oder löschen</li>
              <li>${UI.icon('check')} Bei Unklarheiten freundlich nachfragen</li>
            </ul>
          </div>

          <p style="font-size:var(--text-xs);color:var(--c-text-muted);border-top:1px solid var(--c-border-light);padding-top:var(--space-3);margin:0">
            Wer wiederholt gegen die Regeln verstößt, kann vorübergehend gesperrt werden.
            Das Ziel ist ein freundliches Forum — nicht Kontrolle um der Kontrolle willen. 🐾
          </p>

        </div>`,
      footer: `<button class="btn btn-primary flex-1" onclick="UI.modal.close()">Verstanden</button>`,
    });
  }

  // ----------------------------------------------------------
  function _showCreateForm() {
    const katOptions = KATEGORIEN.filter(k => k.key !== 'alle').map(k =>
      `<option value="${k.key}" ${k.key === _aktivKat ? 'selected' : ''}>${_esc(k.label)}</option>`
    ).join('');

    const body = `
      <form id="forum-thread-form" autocomplete="off">
        <div class="form-group">
          <label class="form-label">Kategorie</label>
          <select class="form-control" name="kategorie">${katOptions}</select>
        </div>
        <div class="form-group">
          <label class="form-label">Titel *</label>
          <input class="form-control" type="text" name="titel"
                 placeholder="Worum geht es?" required maxlength="200">
        </div>
        <div class="form-group">
          <label class="form-label">Text * (min. 20 Zeichen)</label>
          <textarea class="form-control" name="text" rows="5"
                    placeholder="Beschreibe dein Thema ausführlich…" required></textarea>
        </div>
        <div class="form-group">
          <label class="form-label">Standort <span style="color:var(--c-text-secondary)">(optional)</span></label>
          <div id="forum-location-picker"></div>
        </div>
        <div class="form-group">
          <label class="form-label">Fotos / Dateien (max. 5)</label>
          <div class="forum-upload-area">
            <label class="btn btn-secondary btn-sm" for="forum-thread-files">${UI.icon('image')} Fotos / Video / PDF</label>
            <input type="file" id="forum-thread-files" accept="image/*,video/*,application/pdf" multiple style="display:none">
          </div>
          <div id="forum-thread-previews" class="forum-upload-previews" style="margin-top:var(--space-2)"></div>
        </div>
      </form>
      <p style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-3)">
        ${UI.icon('info')} Bitte die
        <button type="button" style="background:none;border:none;padding:0;color:var(--c-primary);cursor:pointer;font-size:inherit;text-decoration:underline" id="ff-rules-link">Regeln & Netiquette</button>
        beachten.
      </p>`;

    const footer = `
      <button type="button" class="btn btn-secondary flex-1" id="ff-cancel">Abbrechen</button>
      <button type="submit" form="forum-thread-form" class="btn btn-primary flex-1">${UI.icon('chat-circle-dots')} Erstellen</button>`;

    UI.modal.open({ title: '+ Neues Thema', body, footer });

    let _picker = null;
    setTimeout(() => {
      _picker = UI.locationPicker({ containerId: 'forum-location-picker' });
    }, 50);

    document.getElementById('ff-cancel')?.addEventListener('click', UI.modal.close);
    document.getElementById('ff-rules-link')?.addEventListener('click', _showRules);

    // Foto-Vorschau — eigenes Array damit iOS-Mehrfachauswahl akkumuliert
    let _threadFiles = [];

    const _renderThreadPreviews = () => {
      const previews = document.getElementById('forum-thread-previews');
      if (!previews) return;
      previews.innerHTML = '';
      _threadFiles.forEach((file, i) => {
        const wrap = document.createElement('div');
        wrap.style.cssText = 'position:relative;display:inline-block';
        let thumb;
        if (file.type === 'application/pdf' || file.name.endsWith('.pdf')) {
          thumb = document.createElement('div');
          thumb.className = 'forum-upload-thumb';
          thumb.style.cssText = 'display:flex;align-items:center;justify-content:center;background:var(--c-surface-2);font-size:11px;color:var(--c-text-secondary);text-align:center;padding:4px';
          thumb.textContent = '📄 PDF';
        } else if (file.type.startsWith('video/')) {
          thumb = document.createElement('video');
          thumb.src = URL.createObjectURL(file);
          thumb.className = 'forum-upload-thumb';
          thumb.muted = true;
        } else {
          thumb = document.createElement('img');
          thumb.src = URL.createObjectURL(file);
          thumb.className = 'forum-upload-thumb';
        }
        const del = document.createElement('button');
        del.type      = 'button';
        del.textContent = '×';
        del.style.cssText = 'position:absolute;top:-4px;right:-4px;width:18px;height:18px;border-radius:50%;' +
          'background:var(--c-danger);color:#fff;border:none;font-size:12px;line-height:1;cursor:pointer;' +
          'display:flex;align-items:center;justify-content:center;padding:0';
        del.addEventListener('click', () => { _threadFiles.splice(i, 1); _renderThreadPreviews(); });
        wrap.appendChild(thumb);
        wrap.appendChild(del);
        previews.appendChild(wrap);
      });
    };

    document.getElementById('forum-thread-files')?.addEventListener('change', e => {
      const neu = Array.from(e.target.files || []);
      neu.forEach(f => {
        if (_threadFiles.length < 5) _threadFiles.push(f);
      });
      e.target.value = ''; // reset damit dieselbe Datei nochmal wählbar ist
      _renderThreadPreviews();
    });

    document.getElementById('forum-thread-form')?.addEventListener('submit', async e => {
      e.preventDefault();
      const btn = document.querySelector('[form="forum-thread-form"][type="submit"]');
      const fd  = UI.formData(e.target);

      if ((fd.text || '').trim().length < 20) {
        UI.toast.warning('Text muss mindestens 20 Zeichen lang sein.');
        return;
      }

      await UI.asyncButton(btn, async () => {
        const loc = _picker ? _picker.getValue() : { lat: null, lon: null, name: null };

        const created = await API.forum.create({
          kategorie:   fd.kategorie,
          titel:       (fd.titel || '').trim(),
          text:        (fd.text  || '').trim(),
          thread_lat:  loc.lat  ?? null,
          thread_lon:  loc.lon  ?? null,
          thread_ort:  loc.name ?? null,
          client_time: API.clientNow(),
        });

        // Fotos hochladen
        for (const file of _threadFiles.slice(0, 5)) {
          try {
            await API.forum.uploadThreadFoto(created.id, file);
          } catch (e) { /* ignorieren */ }
        }

        _threads.unshift({
          ...created,
          text_preview: created.text?.slice(0, 120) || '',
          foto_preview: null,
        });
        UI.modal.close();
        _renderList();
        UI.toast.success('Beitrag erstellt!');
        document.getElementById('forum-thread-list')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
      });
    });
  }

  // ----------------------------------------------------------
  // Mitgliederkarte
  // ----------------------------------------------------------
  async function _renderMembersMap() {
    const el = document.getElementById('forum-main');
    if (!el) return;

    el.innerHTML = `
      <div class="forum-members-section">
        <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-3)">
          <strong>Mitglieder auf der Karte</strong>
          ${_appState.user ? `
            <label class="forum-location-toggle">
              <input type="checkbox" id="forum-loc-toggle" ${_appState.user.forum_show_location ? 'checked' : ''}>
              <span>Meinen (ungefähren) Standort teilen</span>
            </label>` : ''}
        </div>
        <div id="forum-map" class="forum-map-container"></div>
      </div>`;

    // Location-Toggle
    document.getElementById('forum-loc-toggle')?.addEventListener('change', async e => {
      const show = e.target.checked;
      if (show) {
        try {
          const pos = await API.getLocation();
          // Ortsmitte via Nominatim: erst Ort (zoom=10), dann Nominatim-Suche nach Ortsname
          let lat = pos.lat, lon = pos.lon;
          try {
            const rev = await fetch(
              `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json&zoom=10&addressdetails=1&accept-language=de`,
              { cache: 'no-store' }
            );
            const d = await rev.json();
            const a = d.address || {};
            const ort = a.city || a.town || a.village || a.municipality || '';
            if (ort) {
              // Ortsname vorwärts-geocodieren um echte Ortsmitte zu bekommen
              const fwd = await fetch(
                `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(ort)}&format=json&limit=1&accept-language=de`,
                { cache: 'no-store' }
              );
              const results = await fwd.json();
              if (results[0]?.lat && results[0]?.lon) {
                lat = parseFloat(results[0].lat);
                lon = parseFloat(results[0].lon);
              }
            }
          } catch {}
          await API.forum.setLocation(lat, lon, true);
          UI.toast.success('Ortsmitte geteilt — dein genauer Standort bleibt privat.');
          _loadMembersOnMap();
        } catch (err) {
          e.target.checked = false;
          UI.toast.error('Standort konnte nicht ermittelt werden.');
        }
      } else {
        try {
          await API.forum.setLocation(null, null, false);
          UI.toast.success('Standort versteckt.');
          _loadMembersOnMap();
        } catch (err) { UI.toast.error(err.message); }
      }
    });

    await _loadLeaflet();
    const mapEl = document.getElementById('forum-map');
    if (!mapEl) return;

    _map = L.map(mapEl, { zoomControl: true }).setView([51.0, 10.0], 6);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '© OpenStreetMap',
      maxZoom: 18,
    }).addTo(_map);

    _loadMembersOnMap();
  }

  async function _loadMembersOnMap() {
    if (!_map) return;
    try {
      // MarkerCluster laden falls nicht vorhanden
      if (!window.L.markerClusterGroup) {
        await Promise.all([
          new Promise((res, rej) => {
            if (document.querySelector('link[href*="MarkerCluster"]')) { res(); return; }
            const l1 = document.createElement('link'); l1.rel='stylesheet'; l1.href='/css/MarkerCluster.css'; l1.onload=res; l1.onerror=rej; document.head.appendChild(l1);
          }),
          new Promise((res, rej) => {
            const s = document.createElement('script'); s.src='/js/leaflet.markercluster.js'; s.onload=res; s.onerror=rej; document.head.appendChild(s);
          }),
        ]);
      }

      const members = await API.forum.membersMap();

      // Alte Cluster-Gruppe sauber entfernen
      if (_clusterGroup) { _map.removeLayer(_clusterGroup); _clusterGroup = null; }

      _clusterGroup = L.markerClusterGroup({ maxClusterRadius: 60 });
      members.forEach(m => {
        const icon = L.divIcon({
          className: '',
          html: `<div style="width:32px;height:32px;border-radius:50%;
                   background:var(--c-primary);color:#fff;font-size:13px;font-weight:700;
                   display:flex;align-items:center;justify-content:center;
                   box-shadow:0 2px 5px rgba(0,0,0,0.35);
                   border:2px solid rgba(255,255,255,0.8)">${_esc((m.vorname||'?')[0].toUpperCase())}</div>`,
          iconSize: [32, 32], iconAnchor: [16, 16],
        });
        _clusterGroup.addLayer(
          L.marker([m.lat, m.lon], { icon })
            .bindPopup(`<strong>${_esc(m.vorname || '?')}</strong>`)
        );
      });
      _map.addLayer(_clusterGroup);
    } catch (err) {
      console.error('Mitgliederkarte Fehler:', err);
    }
  }

  async function _loadLeaflet() {
    if (_leafletLoaded || window.L) { _leafletLoaded = true; return; }

    // CSS
    if (!document.querySelector('link[href*="leaflet.css"]')) {
      const lCss = document.createElement('link');
      lCss.rel  = 'stylesheet';
      lCss.href = '/css/leaflet.css';
      document.head.appendChild(lCss);
    }

    // JS
    await new Promise((resolve, reject) => {
      if (window.L) { resolve(); return; }
      const s  = document.createElement('script');
      s.src    = '/js/leaflet.js';
      s.onload  = resolve;
      s.onerror = reject;
      document.head.appendChild(s);
    });

    _leafletLoaded = true;
  }

  // ----------------------------------------------------------
  // Moderations-Panel
  // ----------------------------------------------------------
  async function _showModPanel() {
    let reports;
    try {
      reports = await API.forum.reports();
    } catch (err) {
      UI.toast.error(err.message);
      return;
    }

    const body = reports.length
      ? `<div class="forum-mod-reports">
          ${reports.map(r => `
            <div class="forum-mod-report-item" data-id="${r.id}">
              <div style="font-size:var(--text-sm)">
                <strong>${_esc(r.target_type)} #${r.target_id}</strong>
                — ${_esc(r.grund)}
              </div>
              <div style="font-size:var(--text-xs);color:var(--c-text-muted)">
                von ${_esc(r.melder_name || '?')} · ${_fmtDate(r.created_at)}
              </div>
              <button class="btn btn-sm btn-secondary forum-resolve-btn" data-id="${r.id}" style="margin-top:var(--space-2)">
                ${UI.icon('check')} Erledigt
              </button>
            </div>`).join('')}
        </div>`
      : `<p style="color:var(--c-text-muted);text-align:center;padding:var(--space-6)">Keine offenen Berichte.</p>`;

    const footer = `<button type="button" class="btn btn-primary flex-1" id="mod-close">Schließen</button>`;
    UI.modal.open({ title: `${UI.icon('scales')} Moderationsberichte`, body, footer });
    document.getElementById('mod-close')?.addEventListener('click', UI.modal.close);

    document.querySelectorAll('.forum-resolve-btn').forEach(btn => {
      btn.addEventListener('click', async () => {
        const id = parseInt(btn.dataset.id);
        try {
          await API.forum.resolveReport(id);
          btn.closest('.forum-mod-report-item')?.remove();
          UI.toast.success('Erledigt.');
        } catch (err) { UI.toast.error(err.message); }
      });
    });
  }

  // ----------------------------------------------------------
  // Post bearbeiten (Besitzer)
  // ----------------------------------------------------------
  function _showEditPostModal(postId, currentText, container, threadId, uid, isMod) {
    const id = 'forum-edit-post-form';
    UI.modal.open({
      title: 'Antwort bearbeiten',
      body: `<form id="${id}">
        <div class="form-group">
          <textarea class="form-control" name="text" rows="5" required>${_esc(currentText)}</textarea>
        </div>
      </form>`,
      footer: `
        <button class="btn btn-ghost flex-1" onclick="UI.modal.close()">Abbrechen</button>
        <button type="submit" form="${id}" class="btn btn-primary flex-1">${UI.icon('floppy-disk')} Speichern</button>`,
    });
    document.getElementById(id)?.addEventListener('submit', async e => {
      e.preventDefault();
      const text = new FormData(e.target).get('text')?.trim();
      if (!text) return;
      const btn = document.querySelector(`[form="${id}"][type="submit"]`);
      await UI.asyncButton(btn, async () => {
        await API.forum.updatePost(postId, { text });
        UI.modal.close();
        // Post-Text im DOM aktualisieren
        if (container) {
          const postEl  = container.querySelector(`[data-post-id="${postId}"]`);
          const textEl  = postEl?.querySelector('.forum-post-text');
          if (textEl) textEl.textContent = text;
          // data-text auf Edit-Button aktualisieren
          const editBtn = postEl?.querySelector('.forum-post-edit');
          if (editBtn) editBtn.dataset.text = text;
        }
        UI.toast.success('Antwort aktualisiert.');
      });
    });
  }

  // ----------------------------------------------------------
  // Thread bearbeiten (Besitzer)
  // ----------------------------------------------------------
  function _showEditThreadModal(thread) {
    const id = 'forum-edit-thread-form';
    UI.modal.open({
      title: 'Beitrag bearbeiten',
      body: `<form id="${id}">
        <div class="form-group">
          <label class="form-label">Titel</label>
          <input class="form-control" name="titel" value="${_esc(thread.titel || '')}" required>
        </div>
        <div class="form-group">
          <label class="form-label">Text</label>
          <textarea class="form-control" name="text" rows="5">${_esc(thread.text || '')}</textarea>
        </div>
        <div class="form-group">
          <label class="form-label">Standort <span style="color:var(--c-text-secondary)">(optional)</span></label>
          <div id="forum-edit-location-picker"></div>
        </div>
      </form>`,
      footer: `
        <button class="btn btn-ghost flex-1" onclick="UI.modal.close()">Abbrechen</button>
        <button type="submit" form="${id}" class="btn btn-primary flex-1">${UI.icon('floppy-disk')} Speichern</button>`,
    });

    let _picker = null;
    setTimeout(() => {
      _picker = UI.locationPicker({ containerId: 'forum-edit-location-picker' });
      if (thread.thread_lat && thread.thread_lon) {
        _picker.setValue(thread.thread_lat, thread.thread_lon, thread.thread_ort || null);
      }
    }, 50);

    document.getElementById(id)?.addEventListener('submit', async e => {
      e.preventDefault();
      const fd  = new FormData(e.target);
      const loc = _picker ? _picker.getValue() : { lat: null, lon: null, name: null };
      const btn = document.querySelector(`[form="${id}"][type="submit"]`);
      await UI.asyncButton(btn, async () => {
        await API.forum.updateThread(thread.id, {
          titel:      fd.get('titel'),
          text:       fd.get('text'),
          thread_lat: loc.lat  ?? null,
          thread_lon: loc.lon  ?? null,
          thread_ort: loc.name ?? null,
        });
        UI.modal.close();
        _loadThreads(true);
        UI.toast.success('Beitrag aktualisiert.');
      });
    });
  }

  function openNew() {
    if (!_appState?.user) { UI.toast.info('Bitte erst anmelden.'); return; }
    _showCreateForm();
  }

  function _showLightbox(src) {
    const lb = document.createElement('div');
    lb.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.92);z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out';
    lb.innerHTML = `<img src="${UI.escape(src)}" style="max-width:100%;max-height:100%;object-fit:contain;touch-action:pinch-zoom">
      <button style="position:absolute;top:16px;right:16px;background:rgba(255,255,255,.2);border:none;border-radius:50%;width:40px;height:40px;color:#fff;font-size:22px;cursor:pointer;display:flex;align-items:center;justify-content:center">✕</button>`;
    lb.addEventListener('click', () => lb.remove());
    document.body.appendChild(lb);
  }

  return { init, refresh, onDogChange, openNew, openThread: _openThread };

})();
