"use strict";

const api = {
  async get(url) {
    const r = await fetch(url, { credentials: "same-origin" });
    return r.json();
  },
  async post(url, formData) {
    const r = await fetch(url, {
      method: "POST",
      body: formData,
      credentials: "same-origin",
    });
    return { ok: r.ok, status: r.status, data: await r.json().catch(() => ({})) };
  },
};

function escapeHtml(s) {
  const d = document.createElement("div");
  d.textContent = s ?? "";
  return d.innerHTML;
}

// avatarUrl liefert den Bildpfad oder das Platzhalterbild bei leerem Avatar.
function avatarUrl(path) {
  return path ? `/${path}` : "/static/assets/no_profile_pic.jpg";
}

function linkify(s) {
  const escaped = escapeHtml(s);
  const url = /https?:\/\/[^\s<]+/g;
  return escaped
    .replace(url, (m) => `<a href="${m}" target="_blank" rel="noopener">${m}</a>`)
    .replace(/\n/g, "<br>");
}

function renderEntry(e) {
  const el = document.createElement("article");
  el.className = "card";

  const when = new Date(e.created_at * 1000).toLocaleString("de-DE");
  const replyLabel =
    e.reply_count === 1 ? "1 Antwort" : `${e.reply_count} Antworten`;

  // Soft-gelöschter Beitrag: nur Platzhalter, damit der Thread navigierbar bleibt.
  if (e.deleted) {
    el.innerHTML = `
      <div class="muted byline"><span>[deleted] · ${when} · <a href="/e/${e.pid}">${replyLabel}</a></span></div>
      <div class="threadline-wrapper">
        <div class="threadline"></div>
        <div class="body">
          <div class="content">[deleted]</div>
        </div>
      </div>
    `;
    el.addEventListener("click", (ev) => {
      if (!ev.target.closest("a, button")) location.href = `/e/${e.pid}`;
    });
    return el;
  }

  const user = encodeURIComponent(e.username);
  let media = "";
  if (e.filepath) {
    media = `<img src="/${e.filepath}" alt="" loading="lazy">`;
  }

  el.innerHTML = `
    <div class="muted byline">
      <a href="/u/${user}"><img class="avatar avatar-sm" src="${avatarUrl(e.avatar)}" alt="" loading="lazy"></a>
      <span><a href="/u/${user}">${escapeHtml(e.username)}</a> · ${when} · <a href="/e/${e.pid}">${replyLabel}</a></span>
    </div>
    <div class="threadline-wrapper">
      <div class="threadline"></div>
      <div class="body">
        <div class="content">${linkify(e.content)}</div>
        ${media}
        <div class="interactions">
          <div class="row">
            <button class="ghost" data-mode="left">Links</button>
            <span class="muted">– / –</span>
            <button class="ghost" data-mode="right">Rechts</button>
          </div>
        </div>
      </div>
    </div>
  `;
  setupVoting(el, e.pid);
  el.addEventListener("click", (ev) => {
    if (!ev.target.closest("a, button")) location.href = `/e/${e.pid}`;
  });
  return el;
}

function setupVoting(el, pid) {
  const box = el.querySelector(".row");
  const counts = box.querySelector("span");
  const buttons = box.querySelectorAll(".ghost");

  function render(state) {
    if (state.error) {
      counts.textContent = state.error;
      return;
    }
    counts.textContent = `${state.left} / ${state.right}`;
    buttons.forEach((b) =>
      b.classList.toggle("selected", b.dataset.mode === state.selected)
    );
  }

  buttons.forEach((b) =>
    b.addEventListener("click", async () => {
      const fd = new FormData();
      fd.append("mode", b.dataset.mode);
      const res = await api.post(`/entry/${pid}/vote`, fd);
      render(res.data);
    })
  );

  api.get(`/entry/${pid}/votes`).then(render);
}

// createFeed hängt Infinite-Scroll an feedEl/sentinelEl und lädt Seiten über
// urlFor(page). Gibt { reset } zurück, um den Feed neu zu laden.
function createFeed(feedEl, sentinelEl, urlFor) {
  let page = 0;
  let loading = false;
  let done = false;

  async function loadMore() {
    if (loading || done) return;
    loading = true;

    const entries = await api.get(urlFor(page));
    if (!entries.length) {
      done = true;
      const end = document.createElement("p");
      end.className = "muted";
      end.textContent = "YOU REACHED THE END!";
      feedEl.appendChild(end);
    } else {
      entries.forEach((e) => feedEl.appendChild(renderEntry(e)));
      page++;
    }
    loading = false;

    // Der IntersectionObserver feuert nur bei Zustandswechseln. Füllt die
    // gerade geladene Seite den Viewport nicht über den Sentinel hinaus, gäbe
    // es keinen Wechsel und nie ein Nachladen -> hier aktiv weiterladen, bis
    // der Sentinel aus dem Sichtbereich wandert oder das Ende erreicht ist.
    if (!done && sentinelVisible()) loadMore();
  }

  function sentinelVisible() {
    return sentinelEl.getBoundingClientRect().top <= window.innerHeight;
  }

  function reset() {
    page = 0;
    done = false;
    feedEl.innerHTML = "";
    loadMore();
  }

  // rootMargin lädt schon kurz vor Erreichen des Sentinels nach (flüssiger und
  // robust gegen den Fall eines 0-hohen Sentinels exakt am Dokumentende).
  const observer = new IntersectionObserver(
    (items) => {
      if (items.some((i) => i.isIntersecting)) loadMore();
    },
    { rootMargin: "300px" }
  );
  observer.observe(sentinelEl);

  loadMore();
  return { reset };
}
