/* Chat: Kanäle je Arbeitsbereich und Projekt, Gespräche zu zweit.
   Lädt nach app.js und trägt sich dort ein (ZEICHNER, LADER, OHNE_PROJEKT).
   Nachrichten gehen per REST; die Live-Verbindung /ws bringt sie zu den
   Mitlesenden. Kein Markdown-Aufbereiter: Verweise werden erkannt, sonst
   steht der Text, wie er kam. */
"use strict";

S.chat = { kanaele: [], offen: null, nachrichten: [], suche: "" };
OHNE_PROJEKT.push("chat");
TITEL.chat = "Chat";
UNTERTITEL.chat = "Kanäle je Arbeitsbereich und Projekt, Gespräche zu zweit";

/* -- Ereignisse über die Live-Verbindung (app.js) --------------------------- */
document.addEventListener("live", (ev) => chatEreignis(ev.detail));
document.addEventListener("live-verbunden", () => {
  // Was in der Lücke kam, holt ein Neuladen des offenen Kanals nach.
  if (S.chat.offen && S.ansicht === "chat") kanalOeffnen(S.chat.offen);
});

function chatEreignis(e) {
  if (e.kanal_neu) {
    S.chat.kanaele.push(e.kanal_neu);
    abonnieren(`kanal:${e.kanal_neu.id}`);
    if (S.ansicht === "chat") zeichnen();
    return;
  }
  if (!e.kanal) return;
  const kanal = S.chat.kanaele.find((k) => k.id === e.kanal);
  const offen = S.chat.offen && S.chat.offen.id === e.kanal && S.ansicht === "chat";
  if (e.nachricht) {
    if (kanal) kanal.last_at = e.nachricht.created_at;
    if (offen) {
      S.chat.nachrichten.push(e.nachricht);
      nachrichtAnhaengen(e.nachricht);
      jsonRuf(`/api/chat/kanaele/${e.kanal}/gelesen`, "POST", {}).catch(() => {});
    } else if (kanal && (!e.nachricht.author || e.nachricht.author.id !== S.me.id)) {
      kanal.unread = (kanal.unread || 0) + 1;
      if (kanal.kind === "dm") hinweisImBrowser(`${kanal.name}: ${e.nachricht.body.slice(0, 80)}`);
      if (S.ansicht === "chat") zeichneKanalliste();
    }
    chatZahl();
  } else if (e.geaendert && offen) {
    const i = S.chat.nachrichten.findIndex((m) => m.id === e.geaendert.id);
    if (i >= 0) { S.chat.nachrichten[i] = e.geaendert; zeichneNachrichten(); }
  } else if (e.geloescht && offen) {
    S.chat.nachrichten = S.chat.nachrichten.filter((m) => m.id !== e.geloescht);
    zeichneNachrichten();
  }
}

document.addEventListener("live", (ev) => {
  const e = ev.detail;
  if (e.erwaehnung) hinweisImBrowser(`${e.kanal_name}: ${e.erwaehnung.author.name} erwähnt dich`);
});

/** Systemhinweis, wenn die Anwendung nicht im Vordergrund ist. */
function hinweisImBrowser(text) {
  if (!document.hidden || !("Notification" in window)) return;
  if (Notification.permission === "granted") new Notification("Minerva", { body: text });
}

/* -- Daten ------------------------------------------------------------------ */
async function kanaeleLaden() {
  S.chat.kanaele = await api("/api/chat/kanaele");
  S.chat.kanaele.forEach((k) => abonnieren(`kanal:${k.id}`));
}

function chatZahl() {
  const offen = S.chat.kanaele.reduce((s, k) => s + (k.unread || 0), 0);
  navZahl("chat", offen, `${offen} ungelesene Nachricht(en)`);
}

async function kanalOeffnen(k) {
  S.chat.offen = k;
  S.chat.suche = "";
  try {
    S.chat.nachrichten = await api(`/api/chat/kanaele/${k.id}/nachrichten`);
  } catch (e) { meldung(e.message, true); return; }
  if (S.chat.offen !== k) return;           // inzwischen woanders
  k.unread = 0;
  jsonRuf(`/api/chat/kanaele/${k.id}/gelesen`, "POST", {}).catch(() => {});
  chatZahl();
  if (S.ansicht === "chat") zeichnen();
  if ("Notification" in window && Notification.permission === "default") {
    Notification.requestPermission().catch(() => {});
  }
}

/** Gespräch zu zweit beginnen - aus Profil oder Teamliste. */
async function direktnachricht(uid) {
  try {
    const k = await jsonRuf("/api/chat/dm", "POST", { user_id: uid });
    if (!S.chat.kanaele.some((x) => x.id === k.id)) S.chat.kanaele.push(k);
    abonnieren(`kanal:${k.id}`);
    document.querySelectorAll("dialog[open]").forEach((d) => d.close());
    S.chat.offen = S.chat.kanaele.find((x) => x.id === k.id);
    await wechsle("chat");
  } catch (e) { meldung(e.message, true); }
}

document.addEventListener("profil-geoeffnet", (ev) => {
  const { person, ziel } = ev.detail;
  if (!S.me.module.chat || person.id === S.me.id) return;
  const b = knopf("btn", "comment-dots", "Direktnachricht");
  b.onclick = () => direktnachricht(person.id);
  ziel.append(b);
});

/* -- Ansicht ---------------------------------------------------------------- */
function zeichneChat(ziel) {
  const raster = el("div", "chat");
  raster.append(el("div", "chat__liste"), el("div", "chat__raum"));
  ziel.append(raster);
  // Nach jedem Laden der Kanalliste sind die Objekte neu: den offenen Kanal
  // darüber wiederfinden und seinen Verlauf frisch holen - sonst stünde der
  // Verlauf des vorigen Kanals unter der neuen Überschrift.
  const offen = S.chat.offen && S.chat.kanaele.find((k) => k.id === S.chat.offen.id);
  if (offen !== S.chat.offen || (!offen && S.chat.kanaele.length)) {
    const ziel2 = offen || [...S.chat.kanaele]
      .sort((a, b) => (b.last_at || "").localeCompare(a.last_at || ""))[0];
    if (ziel2) { kanalOeffnen(ziel2); return; }
    S.chat.offen = null;
  }
  zeichneKanalliste();
  zeichneRaum();
}

function zeichneKanalliste() {
  const liste = $(".chat__liste");
  if (!liste) return;
  liste.replaceChildren();
  const suche = el("input");
  suche.type = "search";
  suche.placeholder = "Nachrichten durchsuchen";
  suche.value = S.chat.suche;
  suche.onchange = () => suchen(suche.value.trim());
  liste.append(suche);

  const eintrag = (k, symbol, einzug) => {
    const z = el("div", "chat__kanal" + (S.chat.offen && S.chat.offen.id === k.id ? " is-active" : ""));
    if (einzug) z.style.paddingLeft = "26px";
    z.append(ico(symbol), el("span", "", k.name));
    if (k.unread) z.append(el("span", "nav__zahl", String(k.unread)));
    z.onclick = () => kanalOeffnen(k);
    liste.append(bedienbar(z, k.name));
  };
  const bereiche = S.chat.kanaele.filter((k) => k.kind === "workspace");
  bereiche.forEach((w) => {
    eintrag(w, "layer-group", false);
    S.chat.kanaele.filter((k) => k.kind === "project" && projektInfo(k.project_id)
      && projektInfo(k.project_id).ws.id === w.workspace_id)
      .forEach((p) => eintrag(p, "diagram-project", true));
  });
  // Projekte, deren Arbeitsbereich man nicht sieht (nur Projektmitglied)
  S.chat.kanaele.filter((k) => k.kind === "project" && !bereiche.some((w) =>
    projektInfo(k.project_id) && projektInfo(k.project_id).ws.id === w.workspace_id))
    .forEach((p) => eintrag(p, "diagram-project", false));
  const direkt = S.chat.kanaele.filter((k) => k.kind === "dm");
  const kopf = el("div", "chat__gruppe", "Direkt");
  const neu = el("button", "btn btn--ghost");
  neu.type = "button";
  neu.append(ico("plus"));
  neu.title = "Neues Gespräch";
  neu.onclick = neuesGespraech;
  kopf.append(neu);
  liste.append(kopf);
  direkt.forEach((d) => eintrag(d, "user", false));
  if (!S.chat.kanaele.length) {
    liste.append(el("div", "muted", "Noch keine Kanäle - sie entstehen mit Arbeitsbereichen und Projekten."));
  }
}

async function neuesGespraech() {
  let leute = [];
  try { leute = (await api("/api/people")).filter((p) => p.id !== S.me.id); }
  catch (e) { meldung(e.message, true); return; }
  if (!leute.length) { meldung("Noch niemand bekannt - über ein gemeinsames Projekt oder die Börse.", true); return; }
  const raum = $(".chat__raum");
  raum.replaceChildren(el("h3", "", "Gespräch beginnen mit …"));
  leute.forEach((p) => {
    const z = el("div", "chat__kanal");
    z.append(avatarEl(p), el("span", "", p.name));
    z.onclick = () => direktnachricht(p.id);
    raum.append(bedienbar(z, p.name));
  });
}

function zeichneRaum() {
  const raum = $(".chat__raum");
  if (!raum) return;
  raum.replaceChildren();
  const k = S.chat.offen;
  if (!k) {
    raum.append(el("div", "empty", "Links einen Kanal wählen."));
    return;
  }
  const kopf = el("div", "chat__kopf");
  kopf.append(el("b", "", k.name), el("span", "muted", k.kind === "dm" ? "Gespräch zu zweit"
    : k.kind === "project" ? "Projektkanal" : "Arbeitsbereich"), el("div", "spacer"));
  // Erweiterungen (Agenten-Zentrale: Zusammenfassen) hängen hier Knöpfe an.
  document.dispatchEvent(new CustomEvent("kanal-kopf", { detail: { kanal: k, ziel: kopf } }));
  raum.append(kopf);
  const verlauf = el("div", "chat__verlauf");
  verlauf.id = "chat-verlauf";
  raum.append(verlauf);
  zeichneNachrichten();

  const form = el("form", "chat__eingabe");
  const feld = el("textarea");
  feld.rows = 2;
  feld.maxLength = 8000;
  feld.placeholder = "Nachricht … @name erwähnt, Enter sendet, Umschalt+Enter bricht um";
  feld.onkeydown = (e) => {
    if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); form.requestSubmit(); }
  };
  form.append(feld);
  if (k.kind === "project") {
    const datei = el("input");
    datei.type = "file";
    datei.style.display = "none";
    datei.onchange = async () => {
      const daten = new FormData();
      daten.append("datei", datei.files[0]);
      try {
        const d = await api(`/api/projects/${k.project_id}/documents`, { method: "POST", body: daten });
        feld.value = (feld.value ? feld.value + "\n" : "") + `Anhang: ${d.title} /api/documents/${d.id}/download`;
        feld.focus();
      } catch (e) { meldung(e.message, true); }
    };
    const anhang = knopf("btn btn--ghost", "paperclip", "");
    anhang.title = "Datei anhängen (landet bei den Dokumenten des Projekts)";
    anhang.onclick = () => datei.click();
    form.append(anhang, datei);
  }
  const senden = knopf("btn btn--primary", "paper-plane", "");
  senden.type = "submit";
  senden.title = "Senden";
  form.append(senden);
  form.onsubmit = async (e) => {
    e.preventDefault();
    const text = feld.value.trim();
    if (!text) return;
    try {
      const m = await jsonRuf(`/api/chat/kanaele/${k.id}/nachrichten`, "POST", { body: text });
      feld.value = "";
      if (!S.chat.nachrichten.some((x) => x.id === m.id)) {   // das Echo über /ws kann schon da sein
        S.chat.nachrichten.push(m);
        nachrichtAnhaengen(m);
      }
      k.last_at = m.created_at;
    } catch (err) { meldung(err.message, true); }
  };
  raum.append(form);
  feld.focus();
}

function zeichneNachrichten() {
  const verlauf = $("#chat-verlauf");
  if (!verlauf) return;
  verlauf.replaceChildren();
  if (!S.chat.nachrichten.length) {
    verlauf.append(el("div", "komm__leer", "Noch keine Nachrichten."));
  }
  S.chat.nachrichten.forEach((m) => verlauf.append(nachrichtEl(m)));
  verlauf.scrollTop = verlauf.scrollHeight;
}

function nachrichtAnhaengen(m) {
  const verlauf = $("#chat-verlauf");
  if (!verlauf) return;
  const leer = verlauf.querySelector(".komm__leer");
  if (leer) leer.remove();
  verlauf.append(nachrichtEl(m));
  verlauf.scrollTop = verlauf.scrollHeight;
}

/** Verweise (http…, /api/documents/…) und @Namen erkennen - ohne innerHTML. */
function textMitVerweisen(text) {
  const box = el("span", "chat__text");
  const muster = /(https?:\/\/[^\s<>"]+|\/api\/documents\/\d+\/download|@[A-Za-z0-9_]{3,40})/g;
  let rest = 0, treffer;
  while ((treffer = muster.exec(text)) !== null) {
    box.append(document.createTextNode(text.slice(rest, treffer.index)));
    const wert = treffer[0];
    if (wert.startsWith("@")) {
      box.append(el("b", wert.slice(1).toLowerCase() === S.me.username.toLowerCase() ? "chat__ich" : "", wert));
    } else {
      const a = el("a", "", wert.startsWith("/api/") ? "Dokument öffnen" : wert);
      a.href = wert;
      a.target = "_blank";
      a.rel = "noopener";
      box.append(a);
    }
    rest = treffer.index + wert.length;
  }
  box.append(document.createTextNode(text.slice(rest)));
  return box;
}

function nachrichtEl(m) {
  const z = el("div", "chat__nachricht");
  const autor = m.author || { name: "unbekannt", id: 0 };
  z.append(avatarEl(autor));
  const rechts = el("div", "chat__inhalt");
  const kopf = el("div", "komm__kopf");
  const name = el("b", "", autor.name);
  if (m.author && m.author.id) {
    name.style.cursor = "pointer";
    name.onclick = () => oeffneProfil(m.author.id);
  }
  kopf.append(name, el("span", "", new Date(m.created_at).toLocaleString("de-DE",
    { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })));
  if (m.edited) kopf.append(el("span", "", "· bearbeitet"));
  const k = S.chat.offen;
  const leitung = k && k.kind !== "dm" && projektInfo(k.project_id || 0)
    && ["admin", "manager"].includes(projektInfo(k.project_id).projekt.role);
  const aktionen = el("span", "chat__aktionen");
  if (k && k.kind === "project") {
    const vorgang = el("span", "weg");
    vorgang.append(ico("list-check"));
    vorgang.title = "Vorgang daraus machen";
    vorgang.onclick = () => vorgangAusNachricht(m);
    aktionen.append(vorgang);
  }
  if (m.author && m.author.id && (m.author.id === S.me.id || leitung)) {
    const stift = el("span", "weg");
    stift.append(ico("pen"));
    stift.title = "Bearbeiten";
    stift.onclick = async () => {
      const neu = prompt("Nachricht bearbeiten", m.body);
      if (neu === null || !neu.trim()) return;
      try { await jsonRuf(`/api/chat/nachrichten/${m.id}`, "PATCH", { body: neu.trim() }); }
      catch (e) { meldung(e.message, true); }
    };
    const weg = el("span", "weg");
    weg.append(ico("trash"));
    weg.title = "Löschen";
    weg.onclick = async () => {
      if (!confirm("Nachricht löschen?")) return;
      try { await api(`/api/chat/nachrichten/${m.id}`, { method: "DELETE" }); }
      catch (e) { meldung(e.message, true); }
    };
    aktionen.append(stift, weg);
  }
  kopf.append(aktionen);
  rechts.append(kopf, textMitVerweisen(m.body));
  z.append(rechts);
  return z;
}

/** Aus einer Nachricht wird ein Vorgang - der Dialog kommt vorbelegt. */
async function vorgangAusNachricht(m) {
  const k = S.chat.offen;
  if (S.pid !== k.project_id) {
    S.pid = k.project_id;
    await projektLaden();
    baueBaum();
  }
  if (!S.daten) return;
  await oeffneVorgang(null);
  $("#v-title").value = m.body.split("\n")[0].slice(0, 200);
  const wann = new Date(m.created_at).toLocaleString("de-DE");
  $("#v-note").value = `Aus dem Chat, ${m.author ? m.author.name : "unbekannt"} ${wann}:\n${m.body}`;
}

async function suchen(q) {
  S.chat.suche = q;
  const raum = $(".chat__raum");
  if (!q) { zeichneRaum(); return; }
  let treffer = [];
  try { treffer = await api(`/api/chat/suche?q=${encodeURIComponent(q)}`); }
  catch (e) { meldung(e.message, true); return; }
  raum.replaceChildren(el("h3", "", `${treffer.length} Treffer für „${q}“`));
  treffer.forEach((m) => {
    const kanal = S.chat.kanaele.find((x) => x.id === m.channel_id);
    const z = el("div", "chat__nachricht");
    z.style.cursor = "pointer";
    const rechts = el("div", "chat__inhalt");
    rechts.append(el("div", "komm__kopf", `${kanal ? kanal.name : "?"} · ${m.author ? m.author.name : "?"} · ${kurzDatum(m.created_at)}`),
                  textMitVerweisen(m.body));
    z.append(rechts);
    z.onclick = () => { if (kanal) kanalOeffnen(kanal); };
    raum.append(z);
  });
}

/* -- Eintragen -------------------------------------------------------------- */
ZEICHNER.chat = zeichneChat;
LADER.chat = kanaeleLaden;
document.addEventListener("app-gestartet", () => {
  if (S.me && S.me.module.chat && S.ansicht !== "chat") {
    kanaeleLaden().then(chatZahl).catch(() => { /* Chat ist keine Startbedingung */ });
  }
});
