/* Werkstatt, Teil 2: Dateien als Reiter. Text im Monaco-Editor (vendored,
   geladen beim ersten Öffnen), Bilder als Bild, alles andere zum Herunterladen.
   Gespeichert wird mit dem sha des gelesenen Stands: hat jemand die Datei
   inzwischen geändert, fragt der Konflikt-Dialog. Die Markdown-Vorschau läuft
   in einem abgeschotteten iframe - Dateien schreiben auch andere. */
"use strict";

let monacoLadung = null;
function monacoLaden() {
  if (monacoLadung) return monacoLadung;
  monacoLadung = new Promise((ok, nein) => {
    window.MonacoEnvironment = {
      getWorkerUrl: () => "/static/vendor/monaco/min/vs/base/worker/workerMain.js" };
    const s = el("script");
    s.src = "/static/vendor/monaco/min/vs/loader.js";
    s.onload = () => {
      window.require.config({ paths: { vs: "/static/vendor/monaco/min/vs" }, "vs/nls": { availableLanguages: { "*": "de" } } });
      window.require(["vs/editor/editor.main"], () => ok(window.monaco),
                     () => nein(new Error("Der Editor lädt nicht")));
    };
    s.onerror = () => nein(new Error("Der Editor lädt nicht"));
    document.head.append(s);
  });
  return monacoLadung;
}

let markedLadung = null;
function markedLaden() {
  if (markedLadung) return markedLadung;
  markedLadung = new Promise((ok, nein) => {
    // Monacos Lader definiert define.amd - das UMD-Paket meldet sich dann dort
    // an statt als window.marked. Also über denselben Lader holen.
    if (window.require && window.define && window.define.amd) {
      window.require.config({ paths: { marked: "/static/vendor/marked/marked.umd" } });
      window.require(["marked"], (m) => ok(m), () => nein(new Error("Die Vorschau lädt nicht")));
      return;
    }
    const s = el("script");
    s.src = "/static/vendor/marked/marked.umd.js";
    s.onload = () => ok(window.marked);
    s.onerror = () => nein(new Error("Die Vorschau lädt nicht"));
    document.head.append(s);
  });
  return markedLadung;
}

/** Sprache aus dem Dateinamen - Monaco kennt die Endungen selbst. */
function sprache(name) {
  const n = name.toLowerCase();
  const ext = n.includes(".") ? "." + n.split(".").pop() : "";
  const l = window.monaco.languages.getLanguages().find((x) =>
    (x.extensions || []).includes(ext) || (x.filenames || []).includes(n));
  return l ? l.id : "plaintext";
}

function dateiTab(pfad) {
  return { id: "datei:" + pfad, art: "datei", pfad, pid: S.pid, titel: dateiname(pfad),
           symbol: dateiSymbol("", pfad), hash: "datei/" + encodeURIComponent(pfad),
           render: dateiRendern, schliessen: dateiSchliessen, aufraeumen: dateiAufraeumen };
}

function oeffneDatei(pfad, ausGeschichte = false, zeile = 0) {
  if (!S.pid) return null;
  const tab = oeffneTab(dateiTab(pfad));
  if (zeile) {
    tab.zielZeile = zeile;
    if (tab.daten && tab.daten.editor) zeileZeigen(tab, zeile);
  }
  return tab;
}

function zeileZeigen(tab, zeile) {
  const e = tab.daten.editor;
  e.revealLineInCenter(zeile);
  e.setPosition({ lineNumber: zeile, column: 1 });
  e.focus();
  tab.zielZeile = 0;
}

async function dateiRendern(ziel, tab) {
  ziel.replaceChildren();
  const leiste = el("div", "editor__leiste");
  leiste.append(el("span", "editor__pfad", tab.pfad), el("span", "spacer"));
  const koerper = el("div", "editor__koerper");
  ziel.append(leiste, koerper);
  const url = `/api/projects/${S.pid}/dateien/inhalt?pfad=${encodeURIComponent(tab.pfad)}`;
  let antwort;
  try {
    antwort = await fetch(url);
  } catch (e) {
    koerper.append(el("div", "empty", e.message));
    return;
  }
  if (!antwort.ok) {
    let d = {};
    try { d = await antwort.json(); } catch (e) { /* kein JSON */ }
    koerper.append(el("div", "empty", d.detail || `Fehler ${antwort.status}`));
    return;
  }
  const typ = antwort.headers.get("content-type") || "";
  if (typ.startsWith("application/json")) {
    await textEditor(tab, leiste, koerper, await antwort.json());
    return;
  }
  if (typ.startsWith("image/")) {
    const bild = el("img", "editor__bild");
    bild.src = url;
    bild.alt = tab.pfad;
    koerper.classList.add("editor__koerper--mitte");
    koerper.append(bild);
    return;
  }
  const leer = el("div", "empty");
  leer.append(ico("file", "solid", "gross"), document.createTextNode(
    `${tab.titel} (${typ.split(";")[0]}) lässt sich hier nicht anzeigen.`), el("br"));
  const link = el("a", "btn");
  link.href = url;
  link.append(ico("download"), document.createTextNode(" Herunterladen"));
  leer.append(link);
  koerper.append(leer);
}

async function textEditor(tab, leiste, koerper, d) {
  let monaco;
  try {
    monaco = await monacoLaden();
  } catch (e) {
    // Ohne Editor wenigstens lesen und schreiben können.
    const feld = el("textarea", "editor__monaco");
    feld.value = d.inhalt;
    koerper.append(feld);
    meldung(e.message, true);
    return;
  }
  const flaeche = el("div", "editor__monaco");
  koerper.append(flaeche);
  const modell = monaco.editor.createModel(d.inhalt, sprache(tab.titel));
  const editor = monaco.editor.create(flaeche, {
    model: modell, automaticLayout: true, theme: "vs", fontSize: 13, tabSize: 2,
    scrollBeyondLastLine: false, minimap: { enabled: true }, renderWhitespace: "selection",
    wordWrap: /\.(md|markdown|txt)$/i.test(tab.titel) ? "on" : "off" });
  tab.daten = { sha: d.sha, editor, modell, gespeichert: modell.getAlternativeVersionId(),
                flaeche, vorschau: null };
  modell.onDidChangeContent(() =>
    tabAendern(tab.id, { dreckig: modell.getAlternativeVersionId() !== tab.daten.gespeichert }));
  editor.onDidChangeCursorPosition((e) => {
    const s = $("#status-info");
    if (s && S.tabs.aktiv === tab) s.textContent = `Zeile ${e.position.lineNumber}, Spalte ${e.position.column}`;
  });
  editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => speichern(tab));
  const sichern = knopf("btn btn--primary", "floppy-disk", "Speichern");
  sichern.onclick = () => speichern(tab);
  leiste.append(sichern);
  if (/\.(md|markdown)$/i.test(tab.titel)) {
    const vorschau = knopf("btn", "eye", "Vorschau");
    vorschau.onclick = () => vorschauUmschalten(tab, koerper, vorschau);
    leiste.append(vorschau);
  }
  if (tab.zielZeile) zeileZeigen(tab, tab.zielZeile);
  else editor.focus();
}

async function speichern(tab, erzwingen = false) {
  const d = tab && tab.daten;
  if (!d || !d.modell) return;
  const text = d.modell.getValue();
  try {
    const r = await jsonRuf(`/api/projects/${S.pid}/dateien/inhalt`, "PUT",
                            { pfad: tab.pfad, inhalt: text, basis_sha: erzwingen ? (d.serverSha || null) : d.sha });
    d.sha = r.sha;
    d.gespeichert = d.modell.getAlternativeVersionId();
    tabAendern(tab.id, { dreckig: false });
    meldung(`${tab.titel} gespeichert`);
  } catch (e) {
    if (e.status !== 409) { meldung(e.message, true); return; }
    d.serverSha = e.daten && e.daten.sha;
    d.serverText = e.daten && e.daten.inhalt;
    const wahl = await konfliktFragen();
    if (wahl === "ueberschreiben") {
      await speichern(tab, true);
    } else if (wahl === "laden") {
      d.modell.setValue(d.serverText || "");
      d.sha = d.serverSha || null;
      d.gespeichert = d.modell.getAlternativeVersionId();
      tabAendern(tab.id, { dreckig: false });
      meldung("Stand vom Server geladen");
    }
  }
}

function konfliktFragen() {
  const dlg = $("#dlg-konflikt");
  return new Promise((fertig) => {
    dlg.onclose = () => fertig(dlg.returnValue);
    dlg.showModal();
  });
}

async function vorschauUmschalten(tab, koerper, knopfEl) {
  const d = tab.daten;
  if (d.vorschau) {
    d.vorschau.remove();
    d.vorschau = null;
    d.flaeche.hidden = false;
    knopfEl.replaceChildren(ico("eye"), document.createTextNode(" Vorschau"));
    d.editor.focus();
    return;
  }
  let marked;
  try { marked = await markedLaden(); } catch (e) { meldung(e.message, true); return; }
  const rahmen = el("iframe", "editor__vorschau");
  rahmen.setAttribute("sandbox", "");            // keine Skripte, kein Zugriff auf die Anwendung
  rahmen.title = "Vorschau " + tab.titel;
  rahmen.srcdoc = '<!doctype html><meta charset="utf-8"><style>body{font:15px/1.6 -apple-system,'
    + 'Segoe UI,Roboto,sans-serif;max-width:820px;margin:24px auto;padding:0 20px;color:#0f172a}'
    + 'pre{background:#f1f5f9;padding:12px;overflow:auto;border-radius:8px}code{background:#f1f5f9;'
    + 'padding:1px 4px;border-radius:4px}img{max-width:100%}table{border-collapse:collapse}'
    + 'td,th{border:1px solid #e2e8f0;padding:4px 8px}blockquote{border-left:3px solid #cbd5e1;'
    + 'margin:0;padding-left:12px;color:#475569}</style>' + marked.parse(d.modell.getValue());
  d.flaeche.hidden = true;
  koerper.append(rahmen);
  d.vorschau = rahmen;
  knopfEl.replaceChildren(ico("pen"), document.createTextNode(" Bearbeiten"));
}

function dateiSchliessen(tab) {
  if (tab.dreckig && !confirm(`Ungespeicherte Änderungen in „${tab.titel}“ verwerfen?`)) return false;
  return true;
}

function dateiAufraeumen(tab) {
  const d = tab.daten;
  if (!d) return;
  if (d.editor) d.editor.dispose();
  if (d.modell) d.modell.dispose();
  tab.daten = null;
}

BEFEHLE["werkstatt.speichern"] = () => {
  const t = S.tabs.aktiv;
  if (t && t.art === "datei") speichern(t);
};

window.addEventListener("beforeunload", (e) => {
  if (S.tabs.liste.some((t) => t.dreckig)) { e.preventDefault(); e.returnValue = ""; }
});

document.addEventListener("DOMContentLoaded", () => {
  $("#konflikt-laden").onclick = () => { const dlg = $("#dlg-konflikt"); dlg.returnValue = "laden"; dlg.close("laden"); };
});
