import THREE from "./init.js";

import { core } from "./core.js";
import { t } from "./i18n-utils.js";
import { toastHelper } from './viewer-utils.js';

export function getEditorToolbarIcon(icon) {
  const icons = {
    moveToolbar: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v18M3 12h18M12 3l-2.5 2.5M12 3l2.5 2.5M12 21l-2.5-2.5M12 21l2.5-2.5M3 12l2.5-2.5M3 12l2.5 2.5M21 12l-2.5-2.5M21 12l-2.5 2.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    orbit: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3a9 9 0 1 0 9 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M16.5 2.75 21 3.5l-.75 4.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="12" r="2.25" fill="currentColor"/></svg>',
    move: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v18M3 12h18M12 3l-2.5 2.5M12 3l2.5 2.5M12 21l-2.5-2.5M12 21l2.5-2.5M3 12l2.5-2.5M3 12l2.5 2.5M21 12l-2.5-2.5M21 12l-2.5 2.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    rotate: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 6.5A7.5 7.5 0 1 1 5 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M8 3.5v3H5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    scale: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 8h8v8H8zM5 5h4M5 5v4M19 19h-4M19 19v-4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    lightMove: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 5 13h5l-1 8 7-10h-5z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>',
    lightTarget: '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="7" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="12" cy="12" r="2.5" fill="currentColor"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    lights: '<svg viewBox="0 0 24 24" aria-hidden="true"> <circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" stroke-width="1.8"/> <path d="M12 4V7M12 17v3M4 12h3M17 12h3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/> <path d="M6.5 6.5l2 2M15.5 15.5l2 2M17.5 6.5l-2 2M8.5 15.5l-2 2" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg>',
    materials: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l8 4v8l-8 4-8-4V6l8-4z" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M12 6l8 4M12 6v8M12 14l-8-4" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>',
    ambientLight: '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="6" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    cameraLight: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 7h3l2-2h4l2 2h3v10H5z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><circle cx="12" cy="13" r="2.5" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>',
    environmentMap: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 19 7v10l-7 4-7-4V7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M12 3v18M5 7l7 4 7-4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><circle cx="18.25" cy="5.75" r="1.25" fill="currentColor"/></svg>',
    color: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3a5 5 0 0 0-5 5c0 2.8 5 9 5 9s5-6.2 5-9a5 5 0 0 0-5-5Z" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M12 14.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Z" fill="currentColor"/></svg>',
    intensity: '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    picking: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 3 8 8-4 1 2 5-2.5 1-2-5-3 3Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>',
    resetCamera: '<svg viewBox="0 0 24 24" aria-hidden="true"> <path d="M9 4H4v5M15 4h5v5M20 15v5h-5M4 15v5h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/> <circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" stroke-width="1.8"/> </svg>',
    preview: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16v12H4z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="m8 14 2.5-3 2.5 2 2-3 3 4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    save: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4h11l3 3v13H5z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M8 4v5h8M9 18h6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    mainMenu: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m12 3 2 2.2 3-.2.8 2.9 2.6 1.4-1 2.8 1 2.8-2.6 1.4-.8 2.9-3-.2L12 21l-2-2.2-3 .2-.8-2.9-2.6-1.4 1-2.8-1-2.8 2.6-1.4.8-2.9 3 .2Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><circle cx="12" cy="12" r="2.5" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>',
    advancedEditor: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h10M4 17h16M14 7h6M4 12h6M12 12h8M8 5v4M16 10v4M10 15v4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    fullScreen: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4h5M4 4v5M20 4h-5M20 4v5M4 20h5M4 20v-5M20 20h-5M20 20v-5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    displayHelperX: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 8l8 8M16 8 8 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    displayHelperY: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 7 12 13 17 7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 13v4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    displayHelperZ: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 7h10M7 17h10M17 7 7 17" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    visible: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12Z" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>',
    clippingPlanes: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 6h10v12H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M12 5v14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/><path d="M7 6h5v12H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="2.5 2.5" stroke-linejoin="round"/></svg>',
    ruler: '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="9" width="16" height="6" rx="1.8" fill="none" stroke="currentColor" stroke-width="1.8"/> <path d="M7 9v2.5 M9.5 9v1.6 M12 9v2.5 M14.5 9v1.6 M17 9v2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
    annotate: '<svg viewBox="0 0 24 24" aria-hidden="true"> <path d="M5 5h14v10H9l-4 4z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/> <path d="M9 9h6M9 12h4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/> </svg>',
    annotateAdd: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4h14v11H9l-4 4z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M12 8v5M9.5 10.5h5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>',
    annotateImport: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4h14v11H9l-4 4z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M12 6.8v7" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/><path d="M8.8 10.8 12 14l3.2-3.2" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    annotateExport: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4h14v11H9l-4 4z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M12 14.2V7.2" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/><path d="M8.8 10.2 12 7l3.2 3.2" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
  IIIFexport: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 3h9l3 3v12H6z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M15 3v3h3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M12 18V9" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/><path d="M8.8 12.2L12 9l3.2 3.2" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/><path d="M5 21h14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>',    hierarchy: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4h5v5H4zM15 4h5v5h-5zM4 15h5v5H4zM15 15h5v5h-5zM9 6h6M9 17h6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    loadingLogs: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 12h18M3 6h12M3 18h12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    performance: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3a9 9 0 1 1-9 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 3a9 9 0 0 1 9 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 12 15 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/></svg>',
    statistics: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 18h16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M7 14v4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 10v8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M17 6v12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    performanceDefault: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3a9 9 0 1 1-9 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 3a9 9 0 0 1 9 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 12 15 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/></svg>',
    performanceHigh: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3a9 9 0 1 1-9 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 3a9 9 0 0 1 9 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 12 15 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="12" r="1.5" fill="#FF4136"/></svg>',
    performanceLow: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3a9 9 0 1 1-9 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 3a9 9 0 0 1 9 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 12 15 9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="12" r="1.5" fill="#2ECC40"/></svg>',
    expand: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    collapse: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 9l5 5 5-5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    projection: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 8l5-3h7v14h-7l-5-3z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M11 5v14" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M6 8v8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
    wireframe: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 19 7v10l-7 4-7-4V7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M12 3v18M5 7l7 4 7-4M5 17l7-4 7 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>',
    screenshot: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 5H5a2 2 0 0 0-2 2v2M17 5h2a2 2 0 0 1 2 2v2M17 19h2a2 2 0 0 0 2-2v-2M7 19H5a2 2 0 0 1-2-2v-2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="12" r="3.2" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>',
    download: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12M5 12l7 7 7-7M4 19h16a1 1 0 0 1 1 1v2H3v-2a1 1 0 0 1 1-1z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
  };

  return icons[icon] || icons.advancedEditor;
}

export function syncEditorToolbarSecondaryTrayWidth(viewer) {
  if (!viewer.editorToolbarSecondaryTray) return;

const width =
  Array.from(
    viewer.editorToolbarSecondaryTray.children
  ).reduce(
    (sum, el) => sum + el.getBoundingClientRect().width + 10,
    0
  );

viewer.editorToolbarSecondaryTray.style.setProperty(
  "--viewer-toolbar-secondary-width",
  `${Math.ceil(width)}px`
);
}

export function getEditorToolbarHost(viewer) {
  return core.container || viewer.viewerWrapper || null;
}

function initializeEditorToolbarDrag(handle, viewer, toolbar, host) {
  let dragState = null;

  // persistent toolbar position
  let currentX = 0;
  let currentY = 0;

  const getScale = () => {
    const style = getComputedStyle(toolbar);
    const scale = parseFloat(
      style.getPropertyValue("--viewer-toolbar-scale")
    );

    return Number.isFinite(scale) ? scale : 1;
  };

  const clampPosition = (x, y) => {
    const hostRect = host.getBoundingClientRect();

    return {
      x: Math.min(
        Math.max(x, -hostRect.width),
        hostRect.width
      ),

      y: Math.min(
        Math.max(y, -hostRect.height),
        hostRect.height
      ),
    };
  };

  const applyPosition = () => {
    toolbar.style.setProperty("--drag-x", `${currentX}px`);
    toolbar.style.setProperty("--drag-y", `${currentY}px`);
  };

  const updateToolbarPosition = (event) => {
    if (!dragState) return;

    const scale = getScale();

    const dx = (event.clientX - dragState.startX) / scale;
    const dy = (event.clientY - dragState.startY) / scale;

    const pos = clampPosition(
      dragState.originX + dx,
      dragState.originY + dy
    );

    currentX = pos.x;
    currentY = pos.y;

    applyPosition();
  };

  const stopToolbarDrag = () => {
    if (!dragState) return;

    dragState = null;

    toolbar.classList.remove("viewer-editor-toolbar_dragging");

    document.removeEventListener(
      "pointermove",
      updateToolbarPosition
    );

    document.removeEventListener(
      "pointerup",
      stopToolbarDrag
    );

    document.removeEventListener(
      "pointercancel",
      stopToolbarDrag
    );

    requestAnimationFrame(() => {
      toolbar.style.removeProperty("transition");
    });
  };

  const startToolbarDrag = (event) => {
    if (event.button !== 0) return;

    event.preventDefault();
    event.stopPropagation();

    dragState = {
      startX: event.clientX,
      startY: event.clientY,
      originX: currentX,
      originY: currentY,
    };

    toolbar.classList.add("viewer-editor-toolbar_dragging");

    toolbar.style.transition = "none";

    document.addEventListener(
      "pointermove",
      updateToolbarPosition,
      {passive: true}
    );

    document.addEventListener(
      "pointerup",
      stopToolbarDrag
    );

    document.addEventListener(
      "pointercancel",
      stopToolbarDrag
    );
  };

  viewer.bindEventListener(
    handle,
    "pointerdown",
    startToolbarDrag
  );

  viewer.bindEventListener(handle, "click", (event) => {
    event.stopPropagation();
  });

  // keep position valid after resize
  const resizeObserver = new ResizeObserver(() => {
    const pos = clampPosition(currentX, currentY);

    currentX = pos.x;
    currentY = pos.y;

    applyPosition();
  });

  resizeObserver.observe(host);

  applyPosition();
}

export function attachEditorToolbar(viewer) {
  if (!core.editorToolbar || !core.container) return;
  if (getComputedStyle(core.container).position === 'static') {
    core.container.style.position = 'relative';
  }
  const host = getEditorToolbarHost(viewer);
  if (!host || core.editorToolbar.parentElement === host) return;
  host.appendChild(core.editorToolbar);
}

export function toggleToolbarExpanded(viewer) {
  if (!core.editorToolbar) return;

  syncEditorToolbarSecondaryTrayWidth(viewer);
  viewer.isToolbarExpanded = !viewer.isToolbarExpanded;
  core.editorToolbar.classList.toggle("expanded", viewer.isToolbarExpanded);
  viewer.editorToolbarButtons.expand.classList.toggle("expanded-icon", viewer.isToolbarExpanded);
  viewer.editorToolbarButtons.expand.setAttribute("aria-expanded", viewer.isToolbarExpanded ? "true" : "false");
  const icon = viewer.editorToolbarButtons.expand.querySelector(".viewer-editor-tool_icon");
  if (icon) {
    icon.innerHTML = getEditorToolbarIcon(viewer.isToolbarExpanded ? "collapse" : "expand");
  }

  viewer.updateEditorToolbarLabels();
}

async function downloadFile(fileName = "model.glb") {
  if (!core.downloadModel) return;

  const handle = await window.showSaveFilePicker({
    suggestedName: fileName,
  });

  const writable = await handle.createWritable();

  await writable.write(core.downloadModel);
  await writable.close();

  toastHelper("download", "success");
}

export function createEditorToolbar(viewer) {
  if (!core.EDITOR || viewer.urlOptions.hideUi || core.editorToolbar || !core.container) return;

  const toolbar = document.createElement("div");
  toolbar.id = "viewerEditorToolbar";
  toolbar.setAttribute("role", "toolbar");
  toolbar.setAttribute("aria-label", t("toolbar.editor", "Editor tools"));

  const tools = [
    { key: "moveToolbar", icon: "moveToolbar", onClick: () => {}, pressed:true, primary: true },
    { key: "orbit", icon: "orbit", onClick: () => viewer.setObjectTransformMode(""), primary: true },
    { key: "move", icon: "move", onClick: () => viewer.toggleObjectTransformMode("translate"), pressed: true, primary: true },
    { key: "rotate", icon: "rotate", onClick: () => viewer.toggleObjectTransformMode("rotate"), pressed: true, primary: true },
    { key: "scale", icon: "scale", onClick: () => viewer.toggleObjectTransformMode("scale"), pressed: true, primary: true },
    { key: "lights", icon: "lights", onClick: () => {}, pressed: false, primary: false },
    { key: "materials", icon: "materials", onClick: () => viewer.openMaterialsFolder(), pressed: false, primary: false },
    { key: "picking", icon: "picking", onClick: () => viewer.togglePickingMode(), pressed: true, primary: false },
    { key: "annotate", icon: "annotate", onClick: () => viewer.openAnnotationDialogWithAutoPicking(), primary: false },
    { key: "ruler", icon: "ruler", onClick: () => viewer.toggleDistanceMeasurement(), pressed: true, primary: false },
    { key: "fullScreen", icon: "fullScreen", onClick: () => viewer.toggleFullscreen(), pressed: true, primary: true },
    { key: "clippingPlanes", icon: "clippingPlanes", onClick: () => viewer.toggleClippingPlanesPanel(), pressed: true, primary: false },
    { key: "resetCamera", icon: "resetCamera", onClick: () => viewer.resetCamera(), primary: false },
    { key: "hierarchy", icon: "hierarchy", onClick: () => {}, pressed: true, primary: false },
    { key: "projection", icon: "projection", onClick: () => viewer.toggleCameraProjection(), pressed: true, primary: false },
    { key: "wireframe", icon: "wireframe", onClick: () => viewer.toggleWireframeMode(), pressed: true, primary: false },    
    { key: "statistics", icon: "statistics", onClick: () => {}, pressed: false, primary: false },
    
  ];

  if (!core.isLightweight || core.isLocalPreview) {
    tools.splice(tools.length - 1, 0,
      { key: "loadingLogs", icon: "loadingLogs", onClick: () => viewer.toggleLoadingLogs(), pressed: true, primary: false },
      { key: "download", icon: "download", onClick: () => downloadFile(core.fileObject.filename), pressed: true, primary: false },
      { key: "preview", icon: "preview", onClick: () => viewer.takeScreenshot(), primary: false },
      { key: "save", icon: "save", onClick: () => {}, primary: false }
    );
  }

  viewer.editorToolbarButtons = {};
  viewer.environmentMapPreset = viewer.environmentMapPreset || "neutral";

  const secondaryTray = document.createElement("div");
  secondaryTray.className = "viewer-editor-toolbar_secondary-tray";
  viewer.editorToolbarSecondaryTray = secondaryTray;

  tools.forEach((tool) => {
    const button = document.createElement("button");
    button.type = "button";
    button.className = "viewer-editor-tool";
    if (!tool.primary) {
      button.classList.add("viewer-editor-tool-not-primary");
    }
    button.dataset.tool = tool.key;
    button.dataset.pressed = tool.pressed ? "true" : "false";
    button.dataset.primary = tool.primary ? "true" : "false";
    if (tool.key === "materials") {
      const label = t("gui.materials", "Materials");
      button.setAttribute("title", label);
      button.setAttribute("aria-label", label);
    }
    button.innerHTML = `
      <span class="viewer-editor-tool_icon" aria-hidden="true">${getEditorToolbarIcon(tool.icon)}</span>
      <span class="viewer-editor-tool_sr"></span>
    `;
    if (tool.key === "moveToolbar") {
      initializeEditorToolbarDrag(button, viewer, toolbar, getEditorToolbarHost(viewer));
    }
    else if (tool.key === "clippingPlanes") {
      button.classList.add("has-submenu");
      const submenu = document.createElement("div");
      submenu.className = "viewer-editor-tool_submenu";
      const submenuItems = [
        { key: "displayHelperX", icon: "displayHelperX", label: t("gui.displayHelperX", "Show X helper"), onClick: () => viewer.toggleClippingPlaneHelper("x") },
        { key: "displayHelperY", icon: "displayHelperY", label: t("gui.displayHelperY", "Show Y helper"), onClick: () => viewer.toggleClippingPlaneHelper("y") },
        { key: "displayHelperZ", icon: "displayHelperZ", label: t("gui.displayHelperZ", "Show Z helper"), onClick: () => viewer.toggleClippingPlaneHelper("z") },
        { key: "visible", icon: "visible", label: t("gui.visible", "Visible"), onClick: () => viewer.toggleClippingPlaneVisible() },
      ];
      viewer.clippingPlaneSubmenuButtons = {};
      submenuItems.forEach((item) => {
        const subButton = document.createElement("button");
        subButton.type = "button";
        subButton.className = "viewer-editor-tool viewer-editor-tool_submenu-button";
        subButton.dataset.tool = item.key;
        subButton.innerHTML = `
          <span class="viewer-editor-tool_icon" aria-hidden="true">${getEditorToolbarIcon(item.icon)}</span>
        `;
        subButton.setAttribute("title", item.label);
        subButton.setAttribute("aria-label", item.label);
        viewer.bindEventListener(subButton, "click", (event) => {
          event.stopPropagation();
          item.onClick();
        });
        submenu.appendChild(subButton);
        viewer.clippingPlaneSubmenuButtons[item.key] = subButton;
      });
      button.appendChild(submenu);
    } else if (tool.key === "annotate") {
      button.classList.add("has-submenu");
      const submenu = document.createElement("div");
      submenu.className = "viewer-editor-tool_submenu";
      const submenuItems = [
        { key: "annotateAdd", icon: "annotateAdd", label: t("gui.annotateAdd", "Add Annotation"), onClick: () => viewer.openAnnotationDialogWithAutoPicking() },
        { key: "annotateImport", icon: "annotateImport", label: t("gui.annotateImport", "Import Annotations"), onClick: () => viewer.triggerAnnotationsXmlImport() },
        { key: "annotateExport", icon: "annotateExport", label: t("gui.annotateExport", "Export Annotations"), onClick: () => viewer.downloadAnnotationsXmlFile() },
        { key: "IIIFexport", icon: "IIIFexport", label: t("gui.IIIFexport", "Export to IIIF"), onClick: () => viewer.exportIIIFManifest() },
      ];
      viewer.annotateSubmenuButtons = {};
      submenuItems.forEach((item) => {
        const subButton = document.createElement("button");
        subButton.type = "button";
        subButton.className = "viewer-editor-tool viewer-editor-tool_submenu-button";
        subButton.dataset.tool = item.key;
        subButton.innerHTML = `
          <span class="viewer-editor-tool_icon" aria-hidden="true">${getEditorToolbarIcon(item.icon)}</span>
        `;
        subButton.setAttribute("title", item.label);
        subButton.setAttribute("aria-label", item.label);
        viewer.bindEventListener(subButton, "click", (event) => {
          event.stopPropagation();
          item.onClick();
        });
        submenu.appendChild(subButton);
        viewer.annotateSubmenuButtons[item.key] = subButton;
      });
      button.appendChild(submenu);
    } else if (tool.key === "materials") {
      button.classList.add("has-submenu");
      const submenu = document.createElement("div");
      submenu.className = "viewer-editor-tool_submenu viewer-editor-tool_submenu-materials";
      viewer.materialsSubmenu = submenu;
      viewer.refreshMaterialsToolbarMenu();
      button.appendChild(submenu);
    } else if (tool.key === "hierarchy") {
      button.classList.add("has-submenu");
      const submenu = document.createElement("div");
      submenu.className = "viewer-editor-tool_submenu viewer-editor-hierarchy-submenu";
      viewer.hierarchySubmenu = submenu;
      const hierarchyList = document.createElement("div");
      hierarchyList.className = "viewer-editor-hierarchy-submenu-list";
      viewer.hierarchySubmenuList = hierarchyList;
      const clearButton = document.createElement("button");
      clearButton.type = "button";
      clearButton.className = "viewer-editor-tool viewer-editor-tool_submenu-button viewer-editor-hierarchy-clear";
      viewer.bindEventListener(clearButton, "click", (event) => {
        event.stopPropagation();
        viewer.clearHierarchySelection();
      });
      viewer.hierarchyClearButton = clearButton;
      viewer.hierarchySubmenuButtons = {};
      submenu.appendChild(hierarchyList);
      submenu.appendChild(clearButton);
      button.appendChild(submenu);
    } else if (tool.key === "save") {
      button.classList.add("has-submenu");
      const submenu = document.createElement("div");
      submenu.className = "viewer-editor-tool_submenu viewer-editor-save-submenu";
      viewer.bindEventListener(submenu, "click", (event) => {
        event.stopPropagation();
      });
      const submenuItems = [
        { key: "Position", label: t("gui.position", "Position") },
        { key: "Rotation", label: t("gui.rotation", "Rotation") },
        { key: "Scale", label: t("gui.scale", "Scale") },
        { key: "Camera", label: t("gui.camera", "Camera") },
        { key: "DirectionalLight", label: t("gui.directionalLight", "Directional Light") },
        { key: "AmbientLight", label: t("gui.ambientLight", "Ambient Light") },
        { key: "CameraLight", label: t("gui.cameraLight", "Camera Light") },
        { key: "BackgroundColor", label: t("gui.backgroundColor", "Background Color") },
      ];
      viewer.saveSubmenuCheckboxes = {};
      submenuItems.forEach((item) => {
        const row = document.createElement("label");
        row.className = "viewer-editor-save-option";
        row.setAttribute("title", item.label);
        row.setAttribute("aria-label", item.label);

        const checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        checkbox.checked = Boolean(viewer.saveProperties[item.key]);
        checkbox.dataset.property = item.key;
        viewer.bindEventListener(checkbox, "click", (event) => {
          event.stopPropagation();
        });
        viewer.bindEventListener(checkbox, "change", (event) => {
          event.stopPropagation();
          viewer.saveProperties[item.key] = event.target.checked;
        });

        const text = document.createElement("span");
        text.className = "viewer-editor-save-option_label";
        text.textContent = item.label;

        row.appendChild(checkbox);
        row.appendChild(text);
        submenu.appendChild(row);
        viewer.saveSubmenuCheckboxes[item.key] = { row, checkbox, text };
      });

      const actions = document.createElement("div");
      actions.className = "viewer-editor-save-actions";

      const saveButton = document.createElement("button");
      saveButton.type = "button";
      saveButton.className = "viewer-editor-save-apply";
      saveButton.textContent = t("gui.saveSettings", "Save settings");
      viewer.bindEventListener(saveButton, "click", (event) => {
        event.stopPropagation();
        viewer.saveEditorMetadata();
      });
      viewer.saveSubmenuActionButton = saveButton;

      actions.appendChild(saveButton);
      submenu.appendChild(actions);
      button.appendChild(submenu);
    } else if (tool.key === "statistics") {
      button.classList.add("has-submenu");
      const submenu = document.createElement("div");
      submenu.className = "viewer-editor-tool_submenu";
      viewer.statisticsSubmenuButtons = {};

      const appendStatisticsSubmenuItems = (items, container) => {
        items.forEach((item) => {
          const subButton = document.createElement("button");
          subButton.type = "button";
          subButton.className = "viewer-editor-tool viewer-editor-tool_submenu-button";
          subButton.dataset.tool = item.key;
          subButton.setAttribute("title", item.label);
          subButton.setAttribute("aria-label", item.label);
          subButton.setAttribute("aria-pressed", item.pressed);

          const iconSpan = document.createElement("span");
          iconSpan.className = "viewer-editor-tool_icon";
          iconSpan.setAttribute("aria-hidden", "true");
          iconSpan.innerHTML = getEditorToolbarIcon(item.icon);
          subButton.appendChild(iconSpan);

          const srSpan = document.createElement("span");
          srSpan.className = "viewer-editor-tool_sr";
          srSpan.textContent = item.label;
          subButton.appendChild(srSpan);

          if (item.onClick) {
            viewer.bindEventListener(subButton, "click", (event) => {
              event.stopPropagation();
              item.onClick();
            });
          }

          if (item.children) {
            subButton.classList.add("has-submenu");
            const nested = document.createElement("div");
            nested.className = "viewer-editor-tool_submenu";
            appendStatisticsSubmenuItems(item.children, nested);
            subButton.appendChild(nested);
          }

          viewer.statisticsSubmenuButtons[item.key] = subButton;
          container.appendChild(subButton);
        });
      };

      appendStatisticsSubmenuItems([
        {
          key: "toggleStats",
          icon: "statistics",
          label: t("gui.statistics", "Statistics"),
          pressed: false,
          onClick: () => viewer.toggleStatsVisibility(),
        },
        {
          key: "performance",
          icon: "performance",
          label: t("gui.performance", "Performance"),
          children: [
            {
              key: "performanceDefault",
              icon: "statistics",
              label: t("gui.default", "Default"),
              onClick: () => viewer.setPerformanceMode("default"),
              pressed: true,
            },
            {
              key: "performanceHigh",
              icon: "performanceHigh",
              label: t("gui.highPerformance", "High-performance"),
              onClick: () => viewer.setPerformanceMode("high-performance"),
              pressed: true,
            },
            {
              key: "performanceLow",
              icon: "performanceLow",
              label: t("gui.lowPower", "Low-power"),
              onClick: () => viewer.setPerformanceMode("low-power"),
              pressed: true,
            },
          ],
        },
      ], submenu);

      button.appendChild(submenu);
    } else if (tool.key === "lights") {
      button.classList.add("has-submenu");
      const submenu = document.createElement("div");
      submenu.className = "viewer-editor-tool_submenu";
      viewer.lightsSubmenuButtons = {};

      const normalizeColorValue = (value) => {
        if (typeof value !== "string") return "#ffffff";
        if (value.startsWith("0x")) {
          return `#${value.slice(2).padStart(6, "0")}`;
        }
        return value.startsWith("#") ? value : `#${value}`;
      };

      const appendSubmenuItems = (items, container) => {
        items.forEach((item) => {
          const subButton = document.createElement("button");
          subButton.type = "button";
          subButton.className = "viewer-editor-tool viewer-editor-tool_submenu-button ";
          subButton.dataset.tool = item.key;
          subButton.setAttribute("title", item.label);
          subButton.setAttribute("aria-label", item.label);

          const iconSpan = document.createElement("span");
          iconSpan.className = "viewer-editor-tool_icon";
          iconSpan.setAttribute("aria-hidden", "true");
          iconSpan.innerHTML = item.iconHtml || getEditorToolbarIcon(item.icon);
          subButton.appendChild(iconSpan);

          if (item.type === "color") {
            subButton.classList.add("viewer-editor-tool_submenu-control");

            const colorInput = document.createElement("input");
            colorInput.type = "color";
            colorInput.value = normalizeColorValue(item.value());
            colorInput.className = "viewer-editor-tool_submenu-input";
            colorInput.addEventListener("click", (event) => event.stopPropagation());
            colorInput.addEventListener("input", (event) => {
              const value = event.target.value;
              item.onChange(value);
              colorInput.value = normalizeColorValue(value);
            });
            subButton.appendChild(colorInput);
          } else if (item.type === "slider") {
            subButton.classList.add("viewer-editor-tool_submenu-control");

            const slider = document.createElement("input");
            slider.type = "range";
            slider.min = item.min ?? 0;
            slider.max = item.max ?? 10;
            slider.step = item.step ?? 0.01;
            slider.value = String(item.value());
            slider.className = "viewer-editor-tool_submenu-input";
            slider.addEventListener("click", (event) => event.stopPropagation());
            slider.addEventListener("input", (event) => {
              const value = parseFloat(event.target.value);
              item.onChange(value);
              valueLabel.textContent = value.toFixed(2);
            });

            const valueLabel = document.createElement("span");
            valueLabel.className = "viewer-editor-tool_submenu-value";
            valueLabel.textContent = Number(item.value()).toFixed(2);
            valueLabel.setAttribute("aria-hidden", "true");

            subButton.appendChild(slider);
            subButton.appendChild(valueLabel);
          } else if (item.type === "toggle") {
            subButton.classList.add("viewer-editor-tool_submenu-control", "viewer-editor-tool_submenu-toggle");
            subButton.setAttribute("type", "button");

            const toggleState = document.createElement("span");
            toggleState.className = "viewer-editor-tool_submenu-toggle-state";
            const setToggleState = () => {
              const enabled = Boolean(item.value());
              toggleState.textContent = enabled ? t("gui.on", "ON") : t("gui.off", "OFF");
              subButton.setAttribute("aria-pressed", enabled ? "true" : "false");
              subButton.classList.toggle("is-active", enabled);
            };
            setToggleState();

            viewer.bindEventListener(subButton, "click", async (event) => {
              event.stopPropagation();
              const nextValue = !Boolean(item.value());
              if (item.onChange) {
                await item.onChange(nextValue);
              }
              setToggleState();
            });

            subButton.appendChild(toggleState);
          } else if (item.onClick) {
            viewer.bindEventListener(subButton, "click", (event) => {
              event.stopPropagation();
              item.onClick();
            });
          }

          if (item.children) {
            subButton.classList.add("has-submenu");
            const nested = document.createElement("div");
            nested.className = "viewer-editor-tool_submenu";
            appendSubmenuItems(item.children, nested);
            subButton.appendChild(nested);
          }

          if (
            item.key === "lightTargetTransformMove" ||
            item.key === "lightTargetTransformTarget" ||
            item.key.startsWith("environmentMap")
          ) {
            viewer.lightsSubmenuButtons[item.key] = subButton;
          }
          container.appendChild(subButton);
        });
      };

      appendSubmenuItems([
        {
          key: "environmentMap",
          icon: "environmentMap",
          label: t("gui.environmentMap", "Environment map"),
          children: [
            {
              key: "environmentMapToggle",
              icon: "environmentMap",
              label: t("gui.environmentMapToggle", "Environment map"),
              type: "toggle",
              value: () => (core.scene?.environmentIntensity ?? 0) > 0,
              onChange: async (value) => {
                if (!core.scene) return;
                core.scene.traverse((child) => {
                  const materials = child?.material
                    ? Array.isArray(child.material)
                      ? child.material
                      : [child.material]
                    : [];
                  materials.forEach((material) => {
                    if (material?.isMeshStandardMaterial || material?.isMeshPhysicalMaterial) {
                      material.needsUpdate = true;
                    }
                  });
                });
                viewer.updateEditorToolbarState();
              },
            },
            {
              key: "environmentMapIntensity",
              icon: "intensity",
              label: t("gui.intensity", "Intensity"),
              type: "slider",
              min: 0,
              max: 1,
              step: 0.01,
              value: () => core.environmentMapIntensity ?? 0.5,
              onChange: (value) => {
                if (!core.scene) return;
                core.scene.environmentIntensity = value;
                core.scene.traverse((child) => {
                  const materials = child?.material
                    ? Array.isArray(child.material)
                      ? child.material
                      : [child.material]
                    : [];
                  materials.forEach((material) => {
                    if (material?.isMeshStandardMaterial || material?.isMeshPhysicalMaterial) {
                      material.needsUpdate = true;
                    }
                  });
                });
              },
            },
            {
              key: "environmentMapStyleNeutral",
              iconHtml: "🌥",
              label: t("gui.environmentMapNeutral", "Neutral"),
              onClick: async () => {
                await viewer.setEnvironmentMapPreset("neutral");
                viewer.updateLightsSubmenuState();
              },
            },
            {
              key: "environmentMapStyleSunny",
              iconHtml: "☀️",
              label: t("gui.environmentMapSunny", "Sunny"),
              onClick: async () => {
                await viewer.setEnvironmentMapPreset("sunny");
                viewer.updateLightsSubmenuState();
              },
            },
            {
              key: "environmentMapStyleStudio",
              iconHtml: "📸",
              label: t("gui.environmentMapStudio", "Studio"),
              onClick: async () => {
                await viewer.setEnvironmentMapPreset("studio");
                viewer.updateLightsSubmenuState();
              },
            },
            {
              key: "environmentMapStyleGoldenHour",
              iconHtml: "🌅",
              label: t("gui.environmentMapGoldenHour", "Golden Hour"),
              onClick: async () => {
                await viewer.setEnvironmentMapPreset("goldenHour");
                viewer.updateLightsSubmenuState();
              },
            },
          ],
        },
        {
          key: "lightTarget",
          icon: "lightTarget",
          label: t("gui.target", "Target"),
          children: [
            {
              key: "lightTargetColor",
              icon: "color",
              label: t("gui.color", "Color"),
              type: "color",
              value: () => viewer.colors.DirectionalLight,
              onChange: (value) => {
                viewer.colors.DirectionalLight = value;
                core.lightObjects[0].color = new THREE.Color(value);
              },
            },
            {
              key: "lightTargetIntensity",
              icon: "intensity",
              label: t("gui.intensity", "Intensity"),
              type: "slider",
              min: 0,
              max: 10,
              step: 0.01,
              value: () => viewer.intensity.startIntensityDir,
              onChange: (value) => {
                viewer.intensity.startIntensityDir = value;
                core.lightObjects[0].intensity = value;
              },
            },
            {
              key: "lightTargetTransform",
              icon: "move",
              label: t("gui.transform", "Transform"),
              children: [
                { key: "lightTargetTransformMove", icon: "move", label: t("gui.move", "Move"), onClick: () => viewer.toggleLightTransformMode("translate") },
                { key: "lightTargetTransformTarget", icon: "lightTarget", label: t("gui.target", "Target"), onClick: () => viewer.toggleLightTransformMode("rotate") },
              ],
            },
          ],
        },
        {
          key: "lightAmbient",
          icon: "ambientLight",
          label: t("gui.ambient", "Ambient"),
          children: [
            {
              key: "lightAmbientColor",
              icon: "color",
              label: t("gui.color", "Color"),
              type: "color",
              value: () => viewer.colors.AmbientLight,
              onChange: (value) => {
                viewer.colors.AmbientLight = value;
                viewer.ambientLight.color = new THREE.Color(value);
              },
            },
            {
              key: "lightAmbientIntensity",
              icon: "intensity",
              label: t("gui.intensity", "Intensity"),
              type: "slider",
              min: 0,
              max: 10,
              step: 0.01,
              value: () => viewer.intensity.startIntensityAmbient,
              onChange: (value) => {
                viewer.intensity.startIntensityAmbient = value;
                viewer.ambientLight.intensity = value;
              },
            },
          ],
        },
        {
          key: "lightCamera",
          icon: "cameraLight",
          label: t("gui.camera", "Camera"),
          children: [
            {
              key: "lightCameraColor",
              icon: "color",
              label: t("gui.color", "Color"),
              type: "color",
              value: () => viewer.colors.CameraLight,
              onChange: (value) => {
                viewer.colors.CameraLight = value;
                viewer.cameraLight.color = new THREE.Color(value);
              },
            },
            {
              key: "lightCameraIntensity",
              icon: "intensity",
              label: t("gui.intensity", "Intensity"),
              type: "slider",
              min: 0,
              max: 10,
              step: 0.01,
              value: () => viewer.intensity.startIntensityCamera,
              onChange: (value) => {
                viewer.intensity.startIntensityCamera = value;
                viewer.cameraLight.intensity = value;
              },
            },
          ],
        },
      ], submenu);
      button.appendChild(submenu);
    } else if (tool.key === "materials") {
      button.classList.add("has-submenu");
      const submenu = document.createElement("div");
      submenu.className = "viewer-editor-tool_submenu viewer-editor-tool_submenu-materials";
      const submenuItems = [
        { key: "materialColor", icon: "color", label: t("gui.color", "Color"), onClick: () => viewer.openMaterialsFolder() },
        { key: "materialIntensity", icon: "intensity", label: t("gui.intensity", "Intensity"), onClick: () => viewer.openMaterialsFolder() },
      ];
      submenuItems.forEach((item) => {
        const subButton = document.createElement("button");
        subButton.type = "button";
        subButton.className = "viewer-editor-tool viewer-editor-tool_submenu-button";
        subButton.dataset.tool = item.key;
        subButton.innerHTML = `
          <span class="viewer-editor-tool_icon" aria-hidden="true">${getEditorToolbarIcon(item.icon)}</span>
        `;
        subButton.setAttribute("title", item.label);
        subButton.setAttribute("aria-label", item.label);
        viewer.bindEventListener(subButton, "click", (event) => {
          event.stopPropagation();
          item.onClick();
        });
        submenu.appendChild(subButton);
      });
      button.appendChild(submenu);
    } else if (tool.key === "download") {
      if (!core.isLightweight || core.isLocalPreview) {
        button.href = core.downloadModel;
        button.target = "_blank";
        button.rel = "noopener noreferrer";
        button.download = core.fileObject.filename;
      }  
    }
    viewer.bindEventListener(button, "click", () => {
      viewer.stopHandMode();
      if (tool.onClick) {
        tool.onClick();
      }
    });
    if (tool.primary) toolbar.appendChild(button);
    else secondaryTray.appendChild(button);
    viewer.editorToolbarButtons[tool.key] = button;
    if (!tool.primary) viewer.editorSecondaryKeys.push(button);
  });

  toolbar.appendChild(secondaryTray);

  const expandButton = document.createElement("button");
  expandButton.type = "button";
  expandButton.className = "viewer-editor-tool viewer-editor-expand";
  expandButton.innerHTML = `<span class="viewer-editor-tool_icon" aria-hidden="true">${getEditorToolbarIcon("expand")}</span>`;
  expandButton.dataset.primary = "true";
  expandButton.setAttribute("aria-expanded", "false");
  expandButton.setAttribute("title", t("gui.expand", "Expand toolbar"));
  expandButton.setAttribute("aria-label", t("gui.expand", "Expand toolbar"));
  viewer.bindEventListener(expandButton, "click", () => toggleToolbarExpanded(viewer));
  toolbar.appendChild(expandButton);
  viewer.editorToolbarButtons.expand = expandButton;

  if (viewer.actionMenu) {
    viewer.actionMenu.classList.add("viewer-action-menu_in-toolbar");
    toolbar.appendChild(viewer.actionMenu);
  }

  getEditorToolbarHost(viewer)?.appendChild(toolbar);
  core.editorToolbar = toolbar;
  core.editorToolbar.classList.add("editorToolbar-hidden");
  core.editorToolbar.classList.add("collapsed");
  viewer.updateFullscreenButtonIcon();
  viewer.updateEditorToolbarLabels();
  viewer.updateEditorToolbarState();
  syncEditorToolbarSecondaryTrayWidth(viewer);
  viewer.bindEventListener(window, "resize", () => syncEditorToolbarSecondaryTrayWidth(viewer));
}

export function updateHierarchySubmenuState(viewer) {
  if (!viewer.hierarchySubmenuButtons) return;

  const selectedIds = new Set(
    (core.selectedObjects || [])
      .filter((item) => item?.selected === true)
      .map((item) => String(item.id))
  );

  Object.entries(viewer.hierarchySubmenuButtons).forEach(([key, button]) => {
    const isActive = selectedIds.has(String(key));
    button.classList.toggle("is-active", isActive);
    button.setAttribute("aria-pressed", isActive ? "true" : "false");
  });

  viewer.hierarchyClearButton?.toggleAttribute("disabled", selectedIds.size === 0);
}

export function updateStatisticsSubmenuState(viewer) {
  if (!viewer.statisticsSubmenuButtons) return;
  const isVisible = typeof core.stats !== "undefined" && core.stats?.dom?.style?.visibility !== "hidden";
  viewer.statisticsSubmenuButtons.toggleStats?.classList.toggle("is-active", isVisible);
  viewer.statisticsSubmenuButtons.toggleStats?.setAttribute("aria-pressed", isVisible ? "true" : "false");

  const currentMode = core.renderer?.powerPreference || core.CONFIG.viewer?.performanceMode || "default";
  const performanceMap = {
    performanceHigh: "high-performance",
    performanceLow: "low-power",
    performanceDefault: "default",
  };
  Object.entries(performanceMap).forEach(([key, value]) => {
    const isActive = currentMode === value;
    viewer.statisticsSubmenuButtons[key]?.classList.toggle("is-active", isActive);
    viewer.statisticsSubmenuButtons[key]?.setAttribute("aria-pressed", isActive ? "true" : "false");
  });
}

export function updateClippingPlanesSubmenuState(viewer) {
  if (!viewer.clippingPlaneSubmenuButtons) return;
  const clippingMode = core.planeParams?.clippingMode || {};

  viewer.clippingPlaneSubmenuButtons.displayHelperX?.classList.toggle(
    "is-active",
    Boolean(clippingMode.x)
  );
  viewer.clippingPlaneSubmenuButtons.displayHelperY?.classList.toggle(
    "is-active",
    Boolean(clippingMode.y)
  );
  viewer.clippingPlaneSubmenuButtons.displayHelperZ?.classList.toggle(
    "is-active",
    Boolean(clippingMode.z)
  );
  viewer.clippingPlaneSubmenuButtons.visible?.classList.toggle(
    "is-active",
    Boolean(core.planeParams?.outline?.visible)
  );
}

export function updateLightsSubmenuState(viewer) {
  if (!viewer.lightsSubmenuButtons) return;
  const activeMode = viewer.transformText["Transform Light"];
  viewer.lightsSubmenuButtons.environmentMap?.classList.toggle(
    "is-active",
    viewer.environmentMapEnabled !== false
  );
  viewer.lightsSubmenuButtons.environmentMap?.setAttribute(
    "aria-pressed",
    viewer.environmentMapEnabled !== false ? "true" : "false"
  );

  const environmentMapToggle = viewer.lightsSubmenuButtons.environmentMapToggle;
  if (environmentMapToggle) {
    const toggleLabel = environmentMapToggle.querySelector('.viewer-editor-tool_submenu-toggle-state');
    const isEnabled = (core.scene?.environmentIntensity ?? 0) > 0;
    if (toggleLabel) toggleLabel.textContent = isEnabled ? t("gui.on", "ON") : t("gui.off", "OFF");
    environmentMapToggle.setAttribute("aria-pressed", isEnabled ? "true" : "false");
    environmentMapToggle.classList.toggle("is-active", isEnabled);
  }

  viewer.lightsSubmenuButtons.lightTargetTransformMove?.classList.toggle(
    "is-active",
    activeMode === "translate"
  );
  viewer.lightsSubmenuButtons.lightTargetTransformTarget?.classList.toggle(
    "is-active",
    activeMode === "rotate"
  );

  const environmentMapPreset = viewer.environmentMapPreset || "neutral";
  const environmentMapPresetStates = {
    environmentMapStyleNeutral: "neutral",
    environmentMapStyleSunny: "sunny",
    environmentMapStyleStudio: "studio",
    environmentMapStyleGoldenHour: "goldenHour",
  };

  Object.entries(environmentMapPresetStates).forEach(([key, value]) => {
    const isActive = environmentMapPreset === value;
    viewer.lightsSubmenuButtons[key]?.classList.toggle("is-active", isActive);
    viewer.lightsSubmenuButtons[key]?.setAttribute("aria-pressed", isActive ? "true" : "false");
  });
}

export function updateEditorToolbarLabels(viewer) {
  if (!viewer.editorToolbarButtons) return;

  const labels = {
    moveToolbar: t("gui.moveToolbar", "Move toolbar"),
    orbit: t("gui.orbit", "Navigation mode"),
    move: t("gui.move", "Move"),
    rotate: t("gui.rotate", "Rotate"),
    scale: t("gui.scale", "Scale"),
    lights: t("gui.lights", "Lights"),
    picking: viewer.pickingMode
      ? t("controls.disablePickingMode", "Disable picking mode")
      : t("controls.enablePickingMode", "Enable picking mode"),
    annotate: t("gui.addAnnotations", "Add annotations"),
    ruler: viewer.RULER_MODE
      ? t("controls.disableDistanceMeasurement", "Disable distance measurement")
      : t("controls.enableDistanceMeasurement", "Enable distance measurement"),
    resetCamera: t("gui.resetCameraPosition", "Reset camera position"),
    preview: t("gui.renderPreview", "Render preview"),
    save: t("gui.saveSettings", "Save settings"),
    advancedEditor: viewer.isEditorAdvancedPanelVisible()
      ? t("gui.hideAdvancedEditor", "Hide advanced editor")
      : t("gui.showAdvancedEditor", "Show advanced editor"),
    fullScreen: viewer.FULLSCREEN
      ? t("fullscreen.exit", "Exit fullscreen")
      : t("fullscreen.enter", "Enter fullscreen"),
    clippingPlanes: viewer.clippingMode
      ? t("gui.disableClippingPlanesMode", "Disable clipping planes mode")
      : t("gui.enableClippingPlanesMode", "Enable clipping planes mode"),
    projection: core.camera && core.camera.isPerspectiveCamera
      ? t("gui.orthographicProjection", "Switch to orthographic projection")
      : t("gui.perspectiveProjection", "Switch to perspective projection"),
    wireframe: viewer.wireframeMode
      ? t("gui.disableWireframeMode", "Disable wireframe mode")
      : t("gui.enableWireframeMode", "Enable wireframe mode"),
    loadingLogs: viewer.showLoadingLogs
      ? t("gui.hideLoadingLogs", "Hide loading logs")
      : t("gui.showLoadingLogs", "Show loading logs"),
    hierarchy: t("gui.hierarchy", "Hierarchy"),
    materials: t("gui.materials", "Materials"),
    statistics: t("gui.statistics", "Statistics"),
    expand: viewer.isToolbarExpanded
      ? t("gui.collapse", "Collapse toolbar")
      : t("gui.expand", "Expand toolbar"),
    download: t("gui.download", "Download model"),
  };

  Object.entries(viewer.editorToolbarButtons).forEach(([key, button]) => {
    const label = labels[key] || key;
    button.setAttribute("title", label);
    button.setAttribute("aria-label", label);
    const sr = button.querySelector(".viewer-editor-tool_sr");
    if (sr) sr.textContent = label;
  });

  if (viewer.clippingPlaneSubmenuButtons) {
    const clippingPlaneSubmenuLabels = {
      displayHelperX: t("gui.displayHelperX", "Show X helper"),
      displayHelperY: t("gui.displayHelperY", "Show Y helper"),
      displayHelperZ: t("gui.displayHelperZ", "Show Z helper"),
      visible: t("gui.visible", "Visible"),
    };
    Object.entries(viewer.clippingPlaneSubmenuButtons).forEach(([key, button]) => {
      const label = clippingPlaneSubmenuLabels[key] || key;
      button.setAttribute("title", label);
      button.setAttribute("aria-label", label);
    });
  }

  if (viewer.annotateSubmenuButtons) {
    const annotateSubmenuLabels = {
      annotateAdd: t("gui.addAnnotations", "Add Annotation"),
      annotateImport: t("gui.importAnnotationsXml", "Import Annotations"),
      annotateExport: t("gui.exportAnnotationsXml", "Export Annotations"),
      IIIFexport: t("gui.IIIFexport", "Export to IIIF"),
    };
    Object.entries(viewer.annotateSubmenuButtons).forEach(([key, button]) => {
      const label = annotateSubmenuLabels[key] || key;
      button.setAttribute("title", label);
      button.setAttribute("aria-label", label);
    });
  }

  if (viewer.statisticsSubmenuButtons) {
    const statisticsSubmenuLabels = {
      toggleStats: t("gui.statistics", "Statistics"),
      performance: t("gui.performance", "Performance"),
      performanceDefault: t("gui.default", "Default"),
      performanceHigh: t("gui.highPerformance", "High-performance"),
      performanceLow: t("gui.lowPower", "Low-power"),
    };
    Object.entries(viewer.statisticsSubmenuButtons).forEach(([key, button]) => {
      const label = statisticsSubmenuLabels[key] || key;
      button.setAttribute("title", label);
      button.setAttribute("aria-label", label);
    });
  }

  if (viewer.lightsSubmenuButtons) {
    const lightsSubmenuLabels = {
      environmentMap: t("gui.environmentMap", "Environment map"),
      lightTargetTransformMove: t("gui.move", "Move"),
      lightTargetTransformTarget: t("gui.target", "Target"),
      environmentMapToggle: t("gui.environmentMapToggle", "Environment map"),
      environmentMapIntensity: t("gui.intensity", "Intensity"),
      environmentMapStyleNeutral: t("gui.environmentMapNeutral", "Neutral"),
      environmentMapStyleSunny: t("gui.environmentMapSunny", "Sunny"),
      environmentMapStyleStudio: t("gui.environmentMapStudio", "Studio"),
      environmentMapStyleGoldenHour: t("gui.environmentMapGoldenHour", "Golden Hour"),
    };
    Object.entries(viewer.lightsSubmenuButtons).forEach(([key, button]) => {
      const label = lightsSubmenuLabels[key] || key;
      button.setAttribute("title", label);
      button.setAttribute("aria-label", label);
    });
  }

  if (viewer.hierarchyClearButton) {
    const label = t("gui.clearSelectedHierarchy", "Clear selected objects");
    viewer.hierarchyClearButton.setAttribute("title", label);
    viewer.hierarchyClearButton.setAttribute("aria-label", label);
    viewer.hierarchyClearButton.textContent = label;
  }

  if (viewer.saveSubmenuCheckboxes) {
    const saveSubmenuLabels = {
      Position: t("gui.position", "Position"),
      Rotation: t("gui.rotation", "Rotation"),
      Scale: t("gui.scale", "Scale"),
      Camera: t("gui.camera", "Camera"),
      DirectionalLight: t("gui.directionalLight", "Directional Light"),
      AmbientLight: t("gui.ambientLight", "Ambient Light"),
      CameraLight: t("gui.cameraLight", "Camera Light"),
      BackgroundColor: t("gui.backgroundColor", "Background Color"),
    };
    Object.entries(viewer.saveSubmenuCheckboxes).forEach(([key, elements]) => {
      const label = saveSubmenuLabels[key] || key;
      elements.row.setAttribute("title", label);
      elements.row.setAttribute("aria-label", label);
      elements.text.textContent = label;
      elements.checkbox.checked = Boolean(viewer.saveProperties[key]);
    });
  }

  if (viewer.saveSubmenuActionButton) {
    viewer.saveSubmenuActionButton.textContent = t("gui.saveSettings", "Save settings");
  }

  core.editorToolbar?.setAttribute("aria-label", t("toolbar.editor", "Editor tools"));
  viewer.editorToolbarButtons.expand?.setAttribute("aria-expanded", viewer.isToolbarExpanded ? "true" : "false");
}

export function updateEditorToolbarState(viewer) {
  if (!viewer.editorToolbarButtons) return;

  const activeMap = {
    moveToolbar: viewer.transformText["Transform 3D Object"] === "translate" || viewer.transformText["Transform 3D Object"] === "rotate" || viewer.transformText["Transform 3D Object"] === "scale",
    orbit: viewer.transformText["Transform 3D Object"] === "",
    move: viewer.transformText["Transform 3D Object"] === "translate",
    rotate: viewer.transformText["Transform 3D Object"] === "rotate",
    scale: viewer.transformText["Transform 3D Object"] === "scale",
    picking: viewer.pickingMode === true,
    ruler: viewer.RULER_MODE === true,
    clippingPlanes: viewer.clippingMode === true,
    advancedEditor: viewer.isEditorAdvancedPanelVisible(),
    fullScreen: viewer.FULLSCREEN === true,
    loadingLogs: viewer.showLoadingLogs === true,
    wireframe: viewer.wireframeMode === true,
    download: false,
  };

  Object.entries(viewer.editorToolbarButtons).forEach(([key, button]) => {
    const isActive = activeMap[key] === true;
    button.classList.toggle("is-active", isActive);
    if (button.dataset.pressed === "true") {
      button.setAttribute("aria-pressed", isActive ? "true" : "false");
    } else {
      button.removeAttribute("aria-pressed");
    }
  });

  updateHierarchySubmenuState(viewer);
  updateClippingPlanesSubmenuState(viewer);
  updateLightsSubmenuState(viewer);
  updateStatisticsSubmenuState(viewer);
}
