(function () {
  const params = new URLSearchParams(window.location.search);
  if (params.get("edit") !== "1") return;

  const state = {
    content: null,
    textTargets: new Map(),
    imageTargets: new Map(),
    sharedTextTargets: new Map(),
    dirtyText: new Map(),
    dirtyImages: new Map(),
    undoStack: [],
    autoSaveTimer: null,
    saving: false,
    toolbar: null,
    statusEl: null,
    authToken: null,
    ownerEmail: "",
  };
  const apiBase = computeApiBasePath();
  const TOKEN_STORAGE_KEY = "ikfreunde_editor_token";

  const SECTION_ROOTS = {
    hero: "main section.module-hero-teaser",
    page_header: "main section.page-header",
    projects: "main section.module-projects-teaser",
    services: "main section:has(.services-teaser__content)",
    team: "main section.text-image",
    awards: "main section:has(.awards-teaser__content)",
    contact: "main section.contact-teaser",
    clients: "main section:has(.clients-teaser__content)",
  };

  const SHARED_ROOTS = {
    navigation: "header.page-head",
    cookie_layer: "#cookie-layer",
    footer: "footer.site-footer",
  };

  init().catch((error) => {
    console.error("[WYSIWYG] init failed", error);
    alert("WYSIWYG init failed. Check console.");
  });

  async function init() {
    injectCssClass();
    createToolbar();

    await handleResetTokenFlow();
    const ready = await ensureEditorSession();
    if (!ready) {
      setStatus("Viewer mode (not authorized)");
      return;
    }

    const response = await fetch(`${apiBase}/api/content`, { cache: "no-store" });
    if (!response.ok) throw new Error(`Failed to load ${apiBase}/api/content`);
    state.content = await response.json();

    mapSectionTextTargets();
    mapSharedTextTargets();
    mapSectionImageTargets();
    mapSharedImageTargets();

    setStatus("Edit mode ready");
  }

  function injectCssClass() {
    document.documentElement.classList.add("ce-edit-mode");
  }

  function createToolbar() {
    const toolbar = document.createElement("div");
    toolbar.className = "ce-toolbar";
    toolbar.innerHTML = [
      '<strong>Editor Mode</strong>',
      '<button type="button" data-ce-save>Save</button>',
      '<button type="button" data-ce-undo>Undo</button>',
      '<button type="button" data-ce-reset>Reset</button>',
      '<button type="button" data-ce-logout>Logout</button>',
      '<span class="ce-status" data-ce-status>Loading…</span>',
    ].join(" ");

    document.body.appendChild(toolbar);
    state.toolbar = toolbar;
    state.statusEl = toolbar.querySelector("[data-ce-status]");

    toolbar.querySelector("[data-ce-save]").addEventListener("click", () => saveNow());
    toolbar.querySelector("[data-ce-undo]").addEventListener("click", () => undoLast());
    toolbar.querySelector("[data-ce-reset]").addEventListener("click", () => triggerResetRequest());
    toolbar.querySelector("[data-ce-logout]").addEventListener("click", () => logoutEditor());
  }

  function setStatus(message) {
    if (state.statusEl) state.statusEl.textContent = message;
  }

  async function handleResetTokenFlow() {
    const resetToken = params.get("reset_token");
    if (!resetToken) return;

    const newPassword = window.prompt("Reset token erkannt. Neues Passwort (mind. 8 Zeichen):");
    if (!newPassword) return;
    if (newPassword.length < 8) {
      alert("Passwort zu kurz.");
      return;
    }

    const response = await fetch(`${apiBase}/api/editor/reset/confirm`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ token: resetToken, newPassword }),
    });

    if (!response.ok) {
      const text = await response.text();
      alert(`Reset fehlgeschlagen: ${text}`);
      return;
    }

    const url = new URL(window.location.href);
    url.searchParams.delete("reset_token");
    history.replaceState({}, "", url.toString());
    alert("Passwort wurde zurückgesetzt. Bitte neu einloggen.");
  }

  async function ensureEditorSession() {
    state.authToken = getStoredToken();

    let status = await fetchEditorStatus();
    if (!status.claimed) {
      const claimed = await runClaimOnboarding();
      if (!claimed) return false;
      status = await fetchEditorStatus();
    }

    if (!status.authenticated) {
      const loggedIn = await runLoginPrompt(status.owner_email || "");
      if (!loggedIn) return false;
      status = await fetchEditorStatus();
      if (!status.authenticated) return false;
    }

    state.ownerEmail = status.owner_email || "";
    return true;
  }

  async function fetchEditorStatus() {
    const response = await fetch(`${apiBase}/api/editor/status`, {
      headers: authHeaders(),
      cache: "no-store",
    });
    if (!response.ok) throw new Error("Failed to load editor status.");
    return response.json();
  }

  async function runClaimOnboarding() {
    const email = window.prompt("Ersteinrichtung: Editor E-Mail");
    if (!email) return false;
    const password = window.prompt("Ersteinrichtung: Editor Passwort (mind. 8 Zeichen)");
    if (!password || password.length < 8) {
      alert("Passwort zu kurz.");
      return false;
    }

    const response = await fetch(`${apiBase}/api/editor/claim`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });

    if (!response.ok) {
      const text = await response.text();
      alert(`Claim fehlgeschlagen: ${text}`);
      return false;
    }

    const data = await response.json();
    if (data.token) {
      state.authToken = data.token;
      setStoredToken(data.token);
    }
    return true;
  }

  async function runLoginPrompt(defaultEmail) {
    const email = window.prompt("Editor Login E-Mail", defaultEmail || "");
    if (!email) return false;
    const password = window.prompt("Editor Passwort");
    if (!password) return false;

    const response = await fetch(`${apiBase}/api/editor/login`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });

    if (!response.ok) {
      const wantsReset = window.confirm(
        "Login fehlgeschlagen. Passwort-Reset anfordern (Token wird in website/content/.editor-reset.json erzeugt)?"
      );
      if (wantsReset) {
        await triggerResetRequest(email);
      }
      return false;
    }

    const data = await response.json();
    if (data.token) {
      state.authToken = data.token;
      setStoredToken(data.token);
    }
    return true;
  }

  async function triggerResetRequest(prefilledEmail) {
    const email = prefilledEmail || window.prompt("Passwort-Reset: E-Mail");
    if (!email) return;

    const response = await fetch(`${apiBase}/api/editor/reset/request`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email }),
    });

    if (!response.ok) {
      alert("Reset konnte nicht angefordert werden.");
      return;
    }

    alert(
      "Reset angefordert. Prüfe im Container die Datei website/content/.editor-reset.json und nutze den reset_url Link."
    );
  }

  async function logoutEditor() {
    try {
      await fetch(`${apiBase}/api/editor/logout`, {
        method: "POST",
        headers: authHeaders(),
      });
    } catch (_e) {
      // ignore network failure here
    }
    clearStoredToken();
    state.authToken = null;
    alert("Abgemeldet. Seite neu laden für Viewer-Modus.");
  }

  function authHeaders() {
    const headers = {};
    if (state.authToken) {
      headers.Authorization = `Bearer ${state.authToken}`;
    }
    return headers;
  }

  function getStoredToken() {
    try {
      return window.sessionStorage.getItem(TOKEN_STORAGE_KEY);
    } catch (_e) {
      return null;
    }
  }

  function setStoredToken(token) {
    try {
      window.sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
    } catch (_e) {
      // ignore storage failures
    }
  }

  function clearStoredToken() {
    try {
      window.sessionStorage.removeItem(TOKEN_STORAGE_KEY);
    } catch (_e) {
      // ignore storage failures
    }
  }

  function mapSectionTextTargets() {
    const sections = state.content.sections || {};
    const textImageSections = Array.from(document.querySelectorAll("main section.text-image"));

    Object.keys(sections).forEach((sectionName) => {
      const sectionData = sections[sectionName];
      if (!sectionData || !sectionData.texts) return;

      let root = null;
      if (sectionName === "team") root = textImageSections[0] || null;
      else if (sectionName === "partners") root = textImageSections[1] || null;
      else root = queryFirstCompat(SECTION_ROOTS[sectionName]);

      if (!root) return;

      const nodes = collectVisibleTextNodes(root);
      const counters = {};

      nodes.forEach((node) => {
        const parent = node.parentElement;
        if (!parent) return;

        const tag = parent.tagName.toLowerCase();
        counters[tag] = (counters[tag] || 0) + 1;
        const key = `${tag}_${String(counters[tag]).padStart(3, "0")}`;

        if (!(key in sectionData.texts)) return;

        const id = `section:${sectionName}:${key}`;
        const initialValue = normalizeText(node.nodeValue || "");
        const wrapped = wrapTextNode(node, id);
        if (!wrapped) return;

        const target = {
          id,
          scope: "section",
          section: sectionName,
          key,
          el: wrapped,
          savedValue: initialValue,
          currentValue: initialValue,
        };

        state.textTargets.set(id, target);
        wireTextTarget(target);
      });
    });
  }

  function mapSharedTextTargets() {
    const shared = state.content.shared || {};

    Object.entries(SHARED_ROOTS).forEach(([area, selector]) => {
      const root = queryFirstCompat(selector);
      const areaData = shared[area];
      if (!root || !areaData || !Array.isArray(areaData.text_keys)) return;

      const nodes = collectVisibleTextNodes(root);
      nodes.forEach((node, index) => {
        const key = areaData.text_keys[index];
        if (!key) return;

        const initial = normalizeText(node.nodeValue || "");
        const id = `shared:${key}`;
        const wrapped = wrapTextNode(node, id);
        if (!wrapped) return;

        const target = {
          id,
          scope: "shared",
          area,
          key,
          el: wrapped,
          savedValue: initial,
          currentValue: initial,
        };

        if (!state.sharedTextTargets.has(key)) state.sharedTextTargets.set(key, []);
        state.sharedTextTargets.get(key).push(target);

        state.textTargets.set(`${id}:${area}:${index}`, target);
        wireTextTarget(target);
      });
    });
  }

  function mapSectionImageTargets() {
    const sections = state.content.sections || {};
    const textImageSections = Array.from(document.querySelectorAll("main section.text-image"));

    Object.keys(sections).forEach((sectionName) => {
      const sectionData = sections[sectionName];
      if (!sectionData || !sectionData.images) return;

      let root = null;
      if (sectionName === "team") root = textImageSections[0] || null;
      else if (sectionName === "partners") root = textImageSections[1] || null;
      else root = queryFirstCompat(SECTION_ROOTS[sectionName]);
      if (!root) return;

      const images = Array.from(root.querySelectorAll("img[src]"));
      images.forEach((img, i) => {
        const key = `img_${String(i + 1).padStart(3, "0")}`;
        if (!(key in sectionData.images)) return;

        const id = `section:${sectionName}:${key}`;
        const target = {
          id,
          scope: "section",
          section: sectionName,
          key,
          el: img,
          savedValue: {
            src: img.getAttribute("src") || "",
            alt: img.getAttribute("alt") || "",
          },
          currentValue: {
            src: img.getAttribute("src") || "",
            alt: img.getAttribute("alt") || "",
          },
        };

        state.imageTargets.set(id, target);
        wireImageTarget(target);
      });
    });
  }

  function mapSharedImageTargets() {
    const shared = state.content.shared || {};

    Object.entries(SHARED_ROOTS).forEach(([area, selector]) => {
      const root = queryFirstCompat(selector);
      const areaData = shared[area];
      if (!root || !areaData || !areaData.images) return;

      const images = Array.from(root.querySelectorAll("img[src]"));
      images.forEach((img, i) => {
        const key = `img_${String(i + 1).padStart(3, "0")}`;
        if (!(key in areaData.images)) return;

        const id = `shared:${area}:${key}`;
        const target = {
          id,
          scope: "shared",
          area,
          key,
          el: img,
          savedValue: {
            src: img.getAttribute("src") || "",
            alt: img.getAttribute("alt") || "",
          },
          currentValue: {
            src: img.getAttribute("src") || "",
            alt: img.getAttribute("alt") || "",
          },
        };

        state.imageTargets.set(id, target);
        wireImageTarget(target);
      });
    });
  }

  function wireTextTarget(target) {
    target.el.classList.add("ce-text-target");

    target.el.addEventListener("click", (event) => {
      event.preventDefault();
      event.stopPropagation();
    });

    target.el.addEventListener("dblclick", (event) => {
      event.preventDefault();
      event.stopPropagation();
      startInlineEdit(target);
    });
  }

  function startInlineEdit(target) {
    const el = target.el;
    if (el.isContentEditable) return;

    el.setAttribute("contenteditable", "true");
    el.classList.add("ce-text-editing");
    placeCaretAtEnd(el);

    const onKeyDown = (e) => {
      if (e.key === "Enter") {
        e.preventDefault();
        el.blur();
      } else if (e.key === "Escape") {
        e.preventDefault();
        el.textContent = target.currentValue;
        el.blur();
      }
    };

    const onBlur = () => {
      el.removeAttribute("contenteditable");
      el.classList.remove("ce-text-editing");
      el.removeEventListener("keydown", onKeyDown);
      el.removeEventListener("blur", onBlur);

      const nextValue = normalizeText(el.textContent || "");
      applyTextChange(target, nextValue, true);
      queueAutoSave();
    };

    el.addEventListener("keydown", onKeyDown);
    el.addEventListener("blur", onBlur);
  }

  function applyTextChange(target, nextValue, pushUndo) {
    const prevValue = target.currentValue;
    if (nextValue === prevValue) return;

    if (pushUndo) {
      state.undoStack.push({ type: "text", id: target.id, prevValue, nextValue });
    }

    if (target.scope === "shared") {
      const group = state.sharedTextTargets.get(target.key) || [];
      group.forEach((t) => {
        t.currentValue = nextValue;
        t.el.textContent = nextValue;
      });
      state.dirtyText.set(`shared:${target.key}`, nextValue);
    } else {
      target.currentValue = nextValue;
      target.el.textContent = nextValue;
      state.dirtyText.set(target.id, nextValue);
    }

    markDirtyStatus();
  }

  function wireImageTarget(target) {
    target.el.classList.add("ce-image-target");
    target.el.addEventListener("click", (event) => {
      event.preventDefault();
      event.stopPropagation();
      openImageOverlay(target);
    });
  }

  function openImageOverlay(target) {
    closeImageOverlay();

    const overlay = document.createElement("div");
    overlay.className = "ce-image-overlay";
    overlay.innerHTML = [
      '<div class="ce-image-overlay-card">',
      "<h4>Image Content</h4>",
      '<label>src <input type="text" data-ce-src /></label>',
      '<label>alt <input type="text" data-ce-alt /></label>',
      '<div class="ce-image-overlay-actions">',
      '<button type="button" data-ce-cancel>Cancel</button>',
      '<button type="button" data-ce-apply>Apply</button>',
      "</div>",
      "</div>",
    ].join("");

    const srcInput = overlay.querySelector("[data-ce-src]");
    const altInput = overlay.querySelector("[data-ce-alt]");
    srcInput.value = target.currentValue.src;
    altInput.value = target.currentValue.alt;

    overlay.querySelector("[data-ce-cancel]").addEventListener("click", () => closeImageOverlay());
    overlay.querySelector("[data-ce-apply]").addEventListener("click", async () => {
      const nextSrc = srcInput.value.trim();
      const nextAlt = normalizeText(altInput.value || "");

      const allow = await checkImageRatioWarning(target.currentValue.src, nextSrc);
      if (!allow) return;

      applyImageChange(target, { src: nextSrc || target.currentValue.src, alt: nextAlt }, true);
      closeImageOverlay();
      queueAutoSave();
    });

    document.body.appendChild(overlay);
  }

  function closeImageOverlay() {
    const existing = document.querySelector(".ce-image-overlay");
    if (existing) existing.remove();
  }

  async function checkImageRatioWarning(currentSrc, nextSrc) {
    if (!nextSrc || nextSrc === currentSrc) return true;

    try {
      const currentRatio = await loadImageRatio(currentSrc);
      const nextRatio = await loadImageRatio(nextSrc);

      if (!currentRatio || !nextRatio) return true;

      const diff = Math.abs(nextRatio - currentRatio) / currentRatio;
      if (diff > 0.15) {
        return window.confirm(
          "Warning: the new image ratio differs by more than 15%. Save anyway?"
        );
      }
      return true;
    } catch (_e) {
      return true;
    }
  }

  function loadImageRatio(src) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => {
        if (!img.naturalWidth || !img.naturalHeight) return resolve(null);
        resolve(img.naturalWidth / img.naturalHeight);
      };
      img.onerror = () => reject(new Error("image load failed"));
      img.src = src;
    });
  }

  function applyImageChange(target, nextValue, pushUndo) {
    const prevValue = { ...target.currentValue };
    if (prevValue.src === nextValue.src && prevValue.alt === nextValue.alt) return;

    if (pushUndo) {
      state.undoStack.push({
        type: "image",
        id: target.id,
        prevValue,
        nextValue: { ...nextValue },
      });
    }

    target.currentValue = { ...nextValue };
    target.el.setAttribute("src", nextValue.src);
    target.el.setAttribute("alt", nextValue.alt);
    state.dirtyImages.set(target.id, { ...nextValue });

    markDirtyStatus();
  }

  function undoLast() {
    const action = state.undoStack.pop();
    if (!action) return;

    if (action.type === "text") {
      const target = findTextTargetById(action.id);
      if (!target) return;
      applyTextChange(target, action.prevValue, false);
      return;
    }

    if (action.type === "image") {
      const target = state.imageTargets.get(action.id);
      if (!target) return;
      applyImageChange(target, action.prevValue, false);
    }
  }

  function findTextTargetById(id) {
    for (const target of state.textTargets.values()) {
      if (target.id === id) return target;
    }
    return null;
  }

  function queueAutoSave() {
    clearTimeout(state.autoSaveTimer);
    state.autoSaveTimer = setTimeout(() => saveNow(), 500);
  }

  async function saveNow() {
    if (state.saving) return;
    if (state.dirtyText.size === 0 && state.dirtyImages.size === 0) {
      setStatus("No changes");
      return;
    }

    state.saving = true;
    setStatus("Saving…");

    const textChanges = [];
    const imageChanges = [];

    for (const [id, value] of state.dirtyText.entries()) {
      if (id.startsWith("shared:")) {
        textChanges.push({ scope: "shared", key: id.replace("shared:", ""), value });
      } else {
        const [, section, key] = id.split(":");
        textChanges.push({ scope: "section", section, key, value });
      }
    }

    for (const [id, value] of state.dirtyImages.entries()) {
      const parts = id.split(":");
      if (parts[0] === "shared") {
        imageChanges.push({ scope: "shared", area: parts[1], key: parts[2], ...value });
      } else {
        imageChanges.push({ scope: "section", section: parts[1], key: parts[2], ...value });
      }
    }

    try {
      const response = await fetch(`${apiBase}/api/save`, {
        method: "POST",
        headers: { "Content-Type": "application/json", ...authHeaders() },
        body: JSON.stringify({ textChanges, imageChanges }),
      });

      if (!response.ok) {
        const text = await response.text();
        throw new Error(text || "Save failed");
      }

      markSaved();
      setStatus("Saved");
    } catch (error) {
      console.error("[WYSIWYG] save error", error);
      setStatus("Save failed");
      alert("Save failed. Check console.");
    } finally {
      state.saving = false;
    }
  }

  function markSaved() {
    for (const target of state.textTargets.values()) {
      target.savedValue = target.currentValue;
    }

    for (const target of state.imageTargets.values()) {
      target.savedValue = { ...target.currentValue };
    }

    state.dirtyText.clear();
    state.dirtyImages.clear();
  }

  function markDirtyStatus() {
    const total = state.dirtyText.size + state.dirtyImages.size;
    setStatus(total > 0 ? `${total} unsaved change(s)` : "No changes");
  }

  function collectVisibleTextNodes(root) {
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
      acceptNode(node) {
        if (!node || !node.parentElement) return NodeFilter.FILTER_REJECT;
        const parentTag = node.parentElement.tagName.toLowerCase();
        if (["script", "style", "noscript", "template", "svg", "path", "defs"].includes(parentTag)) {
          return NodeFilter.FILTER_REJECT;
        }
        if (normalizeText(node.nodeValue || "") === "") return NodeFilter.FILTER_REJECT;
        return NodeFilter.FILTER_ACCEPT;
      },
    });

    const nodes = [];
    let current = walker.nextNode();
    while (current) {
      nodes.push(current);
      current = walker.nextNode();
    }
    return nodes;
  }

  function wrapTextNode(node, id) {
    if (!node.parentNode) return null;
    const span = document.createElement("span");
    span.className = "ce-inline-text";
    span.dataset.ceTextId = id;
    span.textContent = node.nodeValue || "";
    node.parentNode.replaceChild(span, node);
    return span;
  }

  function placeCaretAtEnd(el) {
    el.focus();
    const range = document.createRange();
    range.selectNodeContents(el);
    range.collapse(false);
    const sel = window.getSelection();
    if (!sel) return;
    sel.removeAllRanges();
    sel.addRange(range);
  }

  function normalizeText(value) {
    return String(value || "").replace(/\s+/g, " ").trim();
  }

  function queryFirstCompat(selector) {
    if (!selector) return null;

    if (selector.includes(":has(")) {
      const base = selector.slice(0, selector.indexOf(":has("));
      const inner = selector.slice(selector.indexOf(":has(") + 5, -1);
      const candidates = Array.from(document.querySelectorAll(base));
      return candidates.find((el) => el.querySelector(inner)) || null;
    }

    return document.querySelector(selector);
  }

  function computeApiBasePath() {
    const path = window.location.pathname || "/";
    const parts = path.split("/").filter(Boolean);
    if (parts.length === 0) return "";
    return `/${parts[0]}`;
  }
})();
