


  // scroll wheel zoom (optional)
  wrap.addEventListener("wheel", (e)=>{
    if(!e.ctrlKey) return;
    e.preventDefault();
    const delta = e.deltaY > 0 ? -0.06 : 0.06;
    setZoom(state.zoom + delta);
  }, {passive:false});

  // init
  loadStorage();
  setMode("add");
  setZoom(1);
  renderDots();
  selectDot(null);
  renderPreview();
})();
(function(){
  const STORAGE_KEY = "trainingMapDots.v1";

  const stage = document.getElementById("stage");
  const mapImg = document.getElementById("mapImg");
  const wrap = document.getElementById("wrap");

  const modeHint = document.getElementById("modeHint");
  const dotCount = document.getElementById("dotCount");
  const zoomPct = document.getElementById("zoomPct");
  const selectedName = document.getElementById("selectedName");

  const titleEl = document.getElementById("title");
  const categoryEl = document.getElementById("category");
  const styleEl = document.getElementById("style");
  const labelEl = document.getElementById("label");
  const notesEl = document.getElementById("notes");
  const linkEl = document.getElementById("link");
  const visibilityEl = document.getElementById("visibility");

  const saveBtn = document.getElementById("saveBtn");
  const deleteBtn = document.getElementById("deleteBtn");
  const duplicateBtn = document.getElementById("duplicateBtn");

  const modeAddBtn = document.getElementById("modeAdd");
  const modeSelectBtn = document.getElementById("modeSelect");

  const zoomInBtn = document.getElementById("zoomIn");
  const zoomOutBtn = document.getElementById("zoomOut");
  const zoomResetBtn = document.getElementById("zoomReset");

  const exportBtn = document.getElementById("exportBtn");
  const importBtn = document.getElementById("importBtn");
  const importFile = document.getElementById("importFile");
  const wipeBtn = document.getElementById("wipeBtn");

  const jsonPreview = document.getElementById("jsonPreview");
  const dotList = document.getElementById("dotList");

  const toast = document.getElementById("toast");

  let state = {
    zoom: 1,
    mode: "add", // add | select
    dots: [],
    selectedId: null
  };

  // --- helpers
  const uid = () => Math.random().toString(16).slice(2) + Date.now().toString(16);
  const clamp = (v,min,max)=>Math.max(min,Math.min(max,v));

  function showToast(msg){
    toast.textContent = msg;
    toast.classList.add("show");
    setTimeout(()=>toast.classList.remove("show"), 1400);
  }

  function saveStorage(){
    localStorage.setItem(STORAGE_KEY, JSON.stringify({dots: state.dots}));
    renderPreview();
  }

  function loadStorage(){
    try{
      const raw = localStorage.getItem(STORAGE_KEY);
      if(!raw) return;
      const parsed = JSON.parse(raw);
      if(parsed && Array.isArray(parsed.dots)) state.dots = parsed.dots;
    }catch(e){}
  }

  function setMode(mode){
    state.mode = mode;
    const isAdd = mode === "add";
    modeAddBtn.classList.toggle("primary", isAdd);
    modeSelectBtn.classList.toggle("primary", !isAdd);
    modeHint.textContent = "Mode: " + (isAdd ? "Add dots" : "Select/drag");
  }

  function setZoom(z){
    state.zoom = clamp(z, 0.5, 2.5);
    stage.style.transform = `scale(${state.zoom})`;
    zoomPct.textContent = Math.round(state.zoom*100) + "%";
  }

  function getImgRect(){
    // stage is scaled; we want coordinates in image space (unscaled)
    const rect = mapImg.getBoundingClientRect();
    return rect;
  }

  function screenToImageCoords(clientX, clientY){
    const rect = getImgRect();
    // clientX/Y are screen coords; convert to image pixels
    const x = (clientX - rect.left) / state.zoom;
    const y = (clientY - rect.top) / state.zoom;
    return {x, y};
  }

  function selectDot(id){
    state.selectedId = id;
    const d = state.dots.find(x=>x.id===id) || null;
    // update selected visuals
    document.querySelectorAll(".dot").forEach(el=>{
      el.classList.toggle("selected", el.dataset.id === id);
    });

    if(!d){
      selectedName.textContent = "None";
      titleEl.value = "";
      categoryEl.value = "General";
      styleEl.value = "gold";
      labelEl.value = "";
      notesEl.value = "";
      linkEl.value = "";
      visibilityEl.value = "Public";
      return;
    }
    selectedName.textContent = d.title?.trim() ? d.title : "(untitled)";
    titleEl.value = d.title || "";
    categoryEl.value = d.category || "General";
    styleEl.value = d.style || "gold";
    labelEl.value = d.label || "";
    notesEl.value = d.notes || "";
    linkEl.value = d.link || "";
    visibilityEl.value = d.visibility || "Public";
    renderPreview();
  }

  function renderDots(){
    // remove existing dot elements
    stage.querySelectorAll(".dot").forEach(el=>el.remove());

    state.dots.forEach(d=>{
      const el = document.createElement("div");
      el.className = "dot" + (d.style==="red" ? " alt" : "");
      el.style.left = d.x + "px";
      el.style.top = d.y + "px";
      el.dataset.id = d.id;
      el.title = (d.title || "Untitled") + (d.notes ? " — " + d.notes.slice(0,80) : "");
      el.textContent = d.label ? d.label : "";
      stage.appendChild(el);

      el.addEventListener("click",(e)=>{
        e.stopPropagation();
        selectDot(d.id);
      });

      // drag in select mode
      let dragging = false;
      let dragOffset = {x:0,y:0};

      el.addEventListener("pointerdown",(e)=>{
        if(state.mode !== "select") return;
        dragging = true;
        el.setPointerCapture(e.pointerId);
        selectDot(d.id);
        const p = screenToImageCoords(e.clientX, e.clientY);
        dragOffset.x = d.x - p.x;
        dragOffset.y = d.y - p.y;
      });

      el.addEventListener("pointermove",(e)=>{
        if(!dragging || state.mode !== "select") return;
        const p = screenToImageCoords(e.clientX, e.clientY);
        d.x = Math.round(p.x + dragOffset.x);
        d.y = Math.round(p.y + dragOffset.y);
        el.style.left = d.x + "px";
        el.style.top = d.y + "px";
      });

      el.addEventListener("pointerup",(e)=>{
        if(!dragging) return;
        dragging = false;
        saveStorage();
        showToast("Dot moved");
      });
    });

    dotCount.textContent = state.dots.length;
    renderList();
    // re-apply selection highlight
    if(state.selectedId) selectDot(state.selectedId);
  }

  function renderList(){
    dotList.innerHTML = "";
    const sorted = [...state.dots].sort((a,b)=>{
      const at = (a.title||"").toLowerCase();
      const bt = (b.title||"").toLowerCase();
      return at.localeCompare(bt);
    });
    sorted.forEach(d=>{
      const item = document.createElement("div");
      item.className = "item";
      item.innerHTML = `
        <div>
          <strong>${escapeHtml(d.title || "(untitled)")}</strong>
          <small>${escapeHtml(d.category || "General")} • ${escapeHtml(d.visibility || "Public")}</small>
        </div>
        <div class="tag">${escapeHtml(d.style==="red" ? "Red" : "Gold")}</div>
      `;
      item.addEventListener("click", ()=> selectDot(d.id));
      dotList.appendChild(item);
    });
  }

  function renderPreview(){
    const exportObj = {
      version: 1,
      image: mapImg.getAttribute("src"),
      dots: state.dots
    };
    jsonPreview.textContent = JSON.stringify(exportObj, null, 2);
  }

  function escapeHtml(str){
    return (str ?? "").replace(/[&<>"']/g, m => ({
      "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"
    }[m]));
  }

  // --- interactions
  stage.addEventListener("click",(e)=>{
    if(state.mode !== "add") return;
    // avoid clicks when image not loaded
    if(!mapImg.complete) return;

    const p = screenToImageCoords(e.clientX, e.clientY);

    const d = {
      id: uid(),
      x: Math.round(p.x),
      y: Math.round(p.y),
      title: "New location",
      category: "General",
      style: "gold",
      label: "",
      notes: "",
      link: "",
      visibility: "Public",
      createdAt: new Date().toISOString()
    };
    state.dots.push(d);
    saveStorage();
    renderDots();
    selectDot(d.id);
    showToast("Dot added");
  });

  saveBtn.addEventListener("click", ()=>{
    const d = state.dots.find(x=>x.id===state.selectedId);
    if(!d){ showToast("Select a dot first"); return; }
    d.title = titleEl.value.trim();
    d.category = categoryEl.value;
    d.style = styleEl.value;
    d.label = labelEl.value.trim();
    d.notes = notesEl.value.trim();
    d.link = linkEl.value.trim();
    d.visibility = visibilityEl.value;
    d.updatedAt = new Date().toISOString();
    saveStorage();
    renderDots();
    showToast("Saved");
  });

  deleteBtn.addEventListener("click", ()=>{
    if(!state.selectedId){ showToast("Select a dot first"); return; }
    state.dots = state.dots.filter(x=>x.id!==state.selectedId);
    state.selectedId = null;
    saveStorage();
    renderDots();
    selectDot(null);
    showToast("Deleted");
  });

  duplicateBtn.addEventListener("click", ()=>{
    const d = state.dots.find(x=>x.id===state.selectedId);
    if(!d){ showToast("Select a dot first"); return; }
    const copy = {...d, id: uid(), x: d.x + 18, y: d.y + 18, title: (d.title||"") + " (copy)"};
    state.dots.push(copy);
    saveStorage();
    renderDots();
    selectDot(copy.id);
    showToast("Duplicated");
  });

  modeAddBtn.addEventListener("click", ()=> setMode("add"));
  modeSelectBtn.addEventListener("click", ()=> setMode("select"));

  zoomInBtn.addEventListener("click", ()=> setZoom(state.zoom + 0.1));
  zoomOutBtn.addEventListener("click", ()=> setZoom(state.zoom - 0.1));
  zoomResetBtn.addEventListener("click", ()=> setZoom(1));

  exportBtn.addEventListener("click", ()=>{
    const exportObj = {
      version: 1,
      image: mapImg.getAttribute("src"),
      dots: state.dots
    };
    const blob = new Blob([JSON.stringify(exportObj, null, 2)], {type:"application/json"});
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = "training-map-dots.json";
    a.click();
    URL.revokeObjectURL(a.href);
    showToast("Exported JSON");
  });

  importBtn.addEventListener("click", ()=> importFile.click());

  importFile.addEventListener("change", async ()=>{
    const file = importFile.files[0];
    if(!file) return;
    try{
      const txt = await file.text();
      const obj = JSON.parse(txt);
      if(!obj || !Array.isArray(obj.dots)) throw new Error("Invalid JSON");
      // basic sanitise
      state.dots = obj.dots.map(d=>({
        id: d.id || uid(),
        x: Number(d.x)||0,
        y: Number(d.y)||0,
        title: d.title || "",
        category: d.category || "General",
        style: d.style==="red" ? "red" : "gold",
        label: d.label || "",
        notes: d.notes || "",
        link: d.link || "",
        visibility: d.visibility || "Public",
        createdAt: d.createdAt || new Date().toISOString(),
        updatedAt: d.updatedAt || ""
      }));
      state.selectedId = null;
      saveStorage();
      renderDots();
      selectDot(null);
      showToast("Imported");
    }catch(e){
      showToast("Import failed");
      console.error(e);
    }finally{
      importFile.value = "";
    }
  });

  wipeBtn.addEventListener("click", ()=>{
    // simple safety: require typing WIPE in prompt
    const ok = prompt("Type WIPE to delete all dots (this cannot be undone):");
    if(ok !== "WIPE") return;
    state.dots = [];
    state.selectedId = null;
    saveStorage();
    renderDots();
    selectDot(null);
    showToast("All dots wiped");
  });
