// @wuys/map · layers/labels.js
//
// Renders one <text> per country with the country's name. Position + size
// computed from the canonical label model:
//   { lon, lat, sizeRatio, rotate }
// where sizeRatio is the label's height as a fraction of the country's PROJECTED
// height — view-agnostic. The renderer's `projectedHeightOf(iso)` provides the
// per-frame height in viewBox units; multiplying gives a pixel size that scales
// naturally with zoom in BOTH flat and globe.

const SVG_NS = "http://www.w3.org/2000/svg";

export default function createLabelsLayer(opts = {}) {
  const texts = new Map();          // iso → SVGTextElement
  let group = null;
  const getName = opts.getCountryName || ((iso, fallback) => fallback || iso.toUpperCase());

  function build(ctx) {
    const { svg, state } = ctx;
    group = document.createElementNS(SVG_NS, "g");
    group.setAttribute("id", "layer-labels");
    group.setAttribute("class", "geo-country-labels");
    svg.appendChild(group);
    for (const iso of state.data.countries.keys()) {
      const t = document.createElementNS(SVG_NS, "text");
      t.setAttribute("data-iso-text", iso);
      t.setAttribute("text-anchor", "middle");
      t.setAttribute("dominant-baseline", "middle");
      t.setAttribute("pointer-events", "none");
      t.setAttribute("display", "none");
      const cm = state.data.countries.get(iso);
      const name = getName(iso, cm.name);
      t.textContent = name;
      group.appendChild(t);
      texts.set(iso, t);
    }
    applyTheme(ctx);
  }

  function patch(ctx) {
    const { state, theme, screenForLabel, projectedHeightOf, isVisibleAt } = ctx;
    // Labels visibility is a pure user toggle, independent of theme + flags.
    const showLabels = state.theme.labelsVisible === true;
    // FAST PATH: hide the whole group with one DOM write instead of iterating
    // 200+ text elements and setting display="none" on each.
    if (!showLabels) {
      if (group && group.style.display !== "none") group.style.display = "none";
      return;
    }
    if (group && group.style.display === "none") group.style.display = "";
    for (const [iso, t] of texts) {
      if (!isVisibleAt(iso)) {
        t.setAttribute("display", "none");
        continue;
      }
      const cm = state.data.countries.get(iso);
      const lbl = state.data.labels.get(iso) || null;
      const pos = screenForLabel(iso, lbl);
      if (!pos) { t.setAttribute("display", "none"); continue; }
      const projH = projectedHeightOf(iso);
      if (!projH || projH < 10) { t.setAttribute("display", "none"); continue; }
      // Canonical sizing: sizeRatio × projected country height. Default = 0.22.
      const sizeRatio = (lbl && lbl.sizeRatio) || autoSizeRatio(cm, projH);
      const fontSize = Math.max(5, Math.min(60, sizeRatio * projH));
      t.removeAttribute("display");
      t.setAttribute("x", pos.x.toFixed(2));
      t.setAttribute("y", pos.y.toFixed(2));
      t.setAttribute("font-size", fontSize.toFixed(1));
      const rot = (lbl && lbl.rotate) || 0;
      if (rot) t.setAttribute("transform", "rotate(" + rot + " " + pos.x.toFixed(2) + " " + pos.y.toFixed(2) + ")");
      else t.removeAttribute("transform");
    }
  }

  function autoSizeRatio(cm, projH) {
    // Inverse-derive from country aspect: roughly 22% of projected height, capped.
    // Wider countries get slightly bigger ratio because the bottleneck is height.
    const aspect = (cm.lonRange || 30) / (cm.latRange || 30);
    if (aspect > 3) return 0.18;
    if (aspect < 0.4) return 0.14;
    return 0.22;
  }

  function applyTheme(ctx) {
    if (!group) return;
    const t = ctx.theme.labels;
    group.style.setProperty("--label-fill", t.fill);
    group.style.setProperty("--label-stroke", t.stroke || "transparent");
    group.style.setProperty("--label-stroke-width", t.strokeWidth || 0);
    group.style.setProperty("--label-font-family", t.fontFamily);
    group.style.setProperty("--label-font-style", t.fontStyle || "normal");
    group.style.setProperty("--label-font-weight", t.fontWeight || 400);
    group.style.setProperty("--label-letter-spacing", t.letterSpacing || "normal");
  }

  function destroy() {
    if (group && group.parentNode) group.parentNode.removeChild(group);
    texts.clear();
    group = null;
  }

  return { id: "labels", build, patch, applyTheme, destroy };
}
