// files-overlay/editor.js — URL-addressable editor only (post-Step 9).
//
// After Phase B Step 9 deleted the legacy <dialog id="files-editor-modal">,
// this module is single-purpose: it drives save / delete / replace /
// new-file flows through the URL-addressable modal swapped into
// #modal-content. The server template overlay_file_editor.html picks
// one of three modes based on the route + the target file:
//   * is_new       (GET /overlays/<id>/files/new?at=<folder>)
//   * text edit    (GET /overlays/<id>/files/edit?path=<rel>, editable)
//   * binary-replace (GET /overlays/<id>/files/edit?path=<rel>, !editable)
//
// Dispatch (registered into __filesOverlay):
//   * "new-file" → opens the new-file route via window.modals.openRouted
//   * "edit"     → opens the edit route via window.modals.openRouted;
//                  the server picks the template branch
//
// Click delegation on .files-editor-save branches on which panel is in
// #modal-content — text textarea → routedSaveClicked, binary panel →
// routedReplaceClicked. Delete and replace-zone clicks have a single
// path each.
//
// All listeners are document-level delegated. #modal-content is
// swapped on every modal open, so direct binding wouldn't survive a
// re-open. Module-scope routedReplacement holds the queued File for
// binary-replace mode; cleared on modal-container close so reopening
// starts fresh.

(function () {
  "use strict";

  const fo = window.__filesOverlay;
  if (!fo) return;

  const { overlayId, baseUrl, csrfToken } = fo;
  const {
    parentOf, humanSize,
    postJson, postForm, scheduleRefresh,
  } = fo.helpers;

  // ---------- routed binary-replace state ----------

  let routedReplacement = null;

  function inRoutedEditor(el) {
    const mc = document.getElementById("modal-content");
    return !!mc && mc.contains(el);
  }
  function isRoutedBinaryMode(modalContent) {
    return !!modalContent?.querySelector(".files-editor-binary");
  }

  function setRoutedReplacement(modalContent, file) {
    routedReplacement = file;
    const idle = modalContent.querySelector(".files-editor-replace-idle");
    const queued = modalContent.querySelector(".files-editor-replace-queued");
    if (file) {
      if (idle) idle.hidden = true;
      if (queued) queued.hidden = false;
      const name = modalContent.querySelector(".files-editor-replace-name");
      const size = modalContent.querySelector(".files-editor-replace-size");
      if (name) name.textContent = file.name;
      if (size) size.textContent = humanSize(file.size);
    } else {
      if (idle) idle.hidden = false;
      if (queued) queued.hidden = true;
    }
    updateRoutedBinarySaveEnabled(modalContent);
  }

  // Enables Save (labeled "Replace" in binary mode) when either a file
  // is queued OR the filename input has been edited. Rename-only is a
  // valid Replace and routes to /files/move below.
  function updateRoutedBinarySaveEnabled(modalContent) {
    const panel = modalContent.querySelector(".files-editor-binary[data-rel-path]");
    const saveBtn = modalContent.querySelector(".files-editor-save");
    if (!panel || !saveBtn) return;
    const relPath = panel.dataset.relPath || "";
    const originalLeaf = relPath.split("/").pop() || relPath;
    const filenameInput = modalContent.querySelector("[data-editor-filename]");
    const filenameChanged =
      filenameInput && filenameInput.value.trim() &&
      filenameInput.value.trim() !== originalLeaf;
    saveBtn.disabled = !routedReplacement && !filenameChanged;
  }

  // ---------- delegated handlers (#modal-content only) ----------

  document.addEventListener("dragover", (event) => {
    const zone = event.target?.closest?.(".files-editor-replace-zone");
    if (!zone || !inRoutedEditor(zone)) return;
    if (Array.from(event.dataTransfer.types).includes("Files")) {
      event.preventDefault();
      zone.classList.add("is-drop-target");
    }
  });
  document.addEventListener("dragleave", (event) => {
    const zone = event.target?.closest?.(".files-editor-replace-zone");
    if (!zone || !inRoutedEditor(zone)) return;
    zone.classList.remove("is-drop-target");
  });
  document.addEventListener("drop", (event) => {
    const zone = event.target?.closest?.(".files-editor-replace-zone");
    if (!zone || !inRoutedEditor(zone)) return;
    if (!Array.from(event.dataTransfer.types).includes("Files")) return;
    event.preventDefault();
    zone.classList.remove("is-drop-target");
    const f = event.dataTransfer.files && event.dataTransfer.files[0];
    if (f) setRoutedReplacement(document.getElementById("modal-content"), f);
  });

  document.addEventListener("change", (event) => {
    const input = event.target?.closest?.(".files-editor-replace-input");
    if (!input || !inRoutedEditor(input)) return;
    const f = input.files && input.files[0];
    if (f) setRoutedReplacement(document.getElementById("modal-content"), f);
  });

  // Filename input: re-evaluate Save enablement in routed binary mode
  // so rename-only Replace becomes reachable without queueing a file.
  document.addEventListener("input", (event) => {
    const input = event.target?.closest?.("[data-editor-filename]");
    if (!input || !inRoutedEditor(input)) return;
    const mc = document.getElementById("modal-content");
    if (mc && isRoutedBinaryMode(mc)) updateRoutedBinarySaveEnabled(mc);
  });

  document.addEventListener("click", async (event) => {
    const target = event.target;
    if (!target) return;

    const clearBtn = target.closest?.(".files-editor-replace-clear");
    if (clearBtn && inRoutedEditor(clearBtn)) {
      setRoutedReplacement(document.getElementById("modal-content"), null);
      return;
    }
    const browseBtn = target.closest?.(".files-editor-replace-browse");
    if (browseBtn && inRoutedEditor(browseBtn)) {
      const mc = document.getElementById("modal-content");
      const fileInput = mc?.querySelector(".files-editor-replace-input");
      fileInput?.click();
      return;
    }

    const saveBtn = target.closest?.(".files-editor-save");
    if (saveBtn && inRoutedEditor(saveBtn)) {
      const mc = document.getElementById("modal-content");
      if (isRoutedBinaryMode(mc)) {
        await routedReplaceClicked();
      } else {
        await routedSaveClicked();
      }
      return;
    }

    const delBtn = target.closest?.(".files-editor-delete");
    if (delBtn && inRoutedEditor(delBtn)) {
      await routedDeleteClicked();
      return;
    }
  });

  // Clear queued replacement when the modal closes (Esc, backdrop,
  // dismiss button, programmatic closeRouted). Otherwise reopening
  // would inherit a stale queued File from the prior session.
  document.getElementById("modal-container")?.addEventListener("close", () => {
    routedReplacement = null;
  });

  // ---------- save / replace / delete ----------

  async function routedSaveClicked() {
    const modalContent = document.getElementById("modal-content");
    if (!modalContent) return;
    const ta = modalContent.querySelector("textarea[data-rel-path]");
    if (!ta) return;
    const relPath = ta.dataset.relPath;
    const filenameInput = modalContent.querySelector("[data-editor-filename]");
    const editedFilename = filenameInput ? filenameInput.value.trim() : "";
    const content = (window.__filesEditor && window.__filesEditor.getValue)
      ? window.__filesEditor.getValue()
      : ta.value;

    // is_new mode: relPath is empty; compose path from data-at-folder
    // + filename. The /save endpoint creates the file when missing,
    // 409s when the destination already exists. On 409 we offer the
    // same overwrite / keep-both / cancel prompt that the legacy
    // create-new flow used (via askConflict in dialogs.js).
    if (!relPath) {
      if (!editedFilename) return;
      const atFolder = ta.dataset.atFolder || "";
      const fullPath = atFolder
        ? `${atFolder.replace(/\/+$/, "")}/${editedFilename}`
        : editedFilename;
      const r = await postJson(`${baseUrl}/files/save`, { path: fullPath, content });
      if (r.ok) {
        if (typeof window.modals?.closeRouted === "function") window.modals.closeRouted();
        scheduleRefresh(parentOf(fullPath));
        return;
      }
      if (r.status === 409 && typeof fo.askConflict === "function") {
        const action = await fo.askConflict(fullPath);
        if (action === "overwrite") {
          // /save overwrites in place when the destination is a file —
          // a plain re-POST does the right thing.
          const r2 = await postJson(`${baseUrl}/files/save`, { path: fullPath, content });
          if (r2.ok) {
            if (typeof window.modals?.closeRouted === "function") window.modals.closeRouted();
            scheduleRefresh(parentOf(fullPath));
          } else {
            alert((r2.body && r2.body.error) || `Save failed (HTTP ${r2.status}).`);
          }
        } else if (action === "keep-both" && typeof fo.withCollisionSuffix === "function") {
          const altered = fo.withCollisionSuffix(fullPath);
          const r2 = await postJson(`${baseUrl}/files/save`, { path: altered, content });
          if (r2.ok) {
            if (typeof window.modals?.closeRouted === "function") window.modals.closeRouted();
            scheduleRefresh(parentOf(altered));
          } else {
            alert((r2.body && r2.body.error) || `Save failed (HTTP ${r2.status}).`);
          }
        }
        // "cancel" → leave the modal open so the user can edit the
        // filename and try again without losing typed content.
        return;
      }
      alert((r.body && r.body.error) || r.rawText || `Create failed (HTTP ${r.status}).`);
      return;
    }

    // Edit mode: rename-on-save (sibling rename only — parent stays).
    const originalLeaf = relPath.split("/").pop() || relPath;
    const parent = relPath.includes("/") ? relPath.slice(0, relPath.lastIndexOf("/")) : "";
    let newPath = null;
    if (editedFilename && editedFilename !== originalLeaf) {
      newPath = parent ? `${parent}/${editedFilename}` : editedFilename;
    }
    const payload = { path: relPath, content };
    if (newPath) payload.new_path = newPath;
    const r = await postJson(`${baseUrl}/files/save`, payload);
    if (r.ok) {
      if (typeof window.modals?.closeRouted === "function") window.modals.closeRouted();
      scheduleRefresh(parentOf(newPath || relPath));
    } else if (r.status === 409) {
      alert(r.rawText || `Conflict: destination already exists.`);
    } else {
      alert((r.body && r.body.error) || r.rawText || `Save failed (HTTP ${r.status}).`);
    }
  }

  async function routedReplaceClicked() {
    const modalContent = document.getElementById("modal-content");
    if (!modalContent) return;
    const panel = modalContent.querySelector(".files-editor-binary[data-rel-path]");
    if (!panel) return;
    const relPath = panel.dataset.relPath;
    if (!relPath) return;

    const filenameInput = modalContent.querySelector("[data-editor-filename]");
    const editedFilename = filenameInput ? filenameInput.value.trim() : "";
    const originalLeaf = relPath.split("/").pop() || relPath;
    const parent = relPath.includes("/") ? relPath.slice(0, relPath.lastIndexOf("/")) : "";
    const renaming = !!editedFilename && editedFilename !== originalLeaf;
    const newPath = renaming
      ? (parent ? `${parent}/${editedFilename}` : editedFilename)
      : null;

    if (routedReplacement) {
      const fd = new FormData();
      fd.append("path", relPath);
      fd.append("csrf_token", csrfToken);
      if (newPath) fd.append("new_path", newPath);
      fd.append("file", routedReplacement);
      const r = await postForm(`${baseUrl}/files/replace`, fd);
      if (r.ok) {
        routedReplacement = null;
        if (typeof window.modals?.closeRouted === "function") window.modals.closeRouted();
        scheduleRefresh(parentOf(newPath || relPath));
      } else {
        alert((r.body && r.body.error) || `Replace failed (HTTP ${r.status}).`);
      }
      return;
    }
    if (renaming) {
      const r = await postJson(`${baseUrl}/files/move`, { src: relPath, dst: newPath });
      if (r.ok) {
        if (typeof window.modals?.closeRouted === "function") window.modals.closeRouted();
        scheduleRefresh(parentOf(newPath));
      } else {
        alert((r.body && r.body.error) || `Rename failed (HTTP ${r.status}).`);
      }
    }
  }

  async function routedDeleteClicked() {
    const modalContent = document.getElementById("modal-content");
    if (!modalContent) return;
    // rel-path lives on the text-mode textarea OR on the binary panel.
    const ta = modalContent.querySelector("textarea[data-rel-path]");
    const panel = modalContent.querySelector(".files-editor-binary[data-rel-path]");
    const relPath = ta?.dataset?.relPath || panel?.dataset?.relPath;
    if (!relPath) return;
    if (!confirm(`Delete ${relPath}?`)) return;
    const fd = new FormData();
    fd.append("path", relPath);
    fd.append("csrf_token", csrfToken);
    const r = await postForm(`${baseUrl}/files/delete`, fd);
    if (r.ok) {
      if (typeof window.modals?.closeRouted === "function") window.modals.closeRouted();
      scheduleRefresh(parentOf(relPath));
    } else {
      alert((r.body && r.body.error) || `Delete failed (HTTP ${r.status}).`);
    }
  }

  // ---------- register action-registry handlers ----------

  fo.registerHandler("new-file", (path) => {
    const url = `/overlays/${overlayId}/files/new?at=${encodeURIComponent(path)}`;
    if (typeof window.modals?.openRouted === "function") {
      window.modals.openRouted(url);
    } else {
      window.location.href = url;
    }
  });

  fo.registerHandler("edit", (path, _actionEl) => {
    // Both text and binary files use the same edit route — the server
    // picks the template branch based on is_editable(target).
    const url = `/overlays/${overlayId}/files/edit?path=${encodeURIComponent(path)}`;
    if (typeof window.modals?.openRouted === "function") {
      window.modals.openRouted(url);
    } else {
      window.location.href = url;
    }
  });
})();
