import { createSliderControl } from "./controls/slider.js";
import { createColorControl } from "./controls/color.js";

const defaultSettings = {
  themeVersion: "v7-toolbar-hub-icon",
  bgOuter: "#0f0f0f",
  canvasTop: "#1c1c1c",
  canvasBottom: "#1c1c1c",
  loadingTop: "#1c1c1c",
  loadingBottom: "#1c1c1c",
  accent: "#ffea00",
  accent2: "#ffea00",
  templateFill: "#292929",
  templateOutline: "#292929",
  templateOutlineHover: "#1c1c1c",
  projectBtnFill: "#292929",
  projectBtnBorder: "#292929",
  projectBtnHover: "#1c1c1c",
  modalBtnPrimary: "#c4ff67",
  modalBtnSecondary: "#292929",
  loaderColor: "#c4ff67",
  templateGlow: 80,
  toolbarBg: "#292929",
  toolbarBgActive: "#c4ff67",
  toolbarBorder: "#292929",
  toolbarOutline: "#292929",
  toolbarMinSize: 25,
  toolbarMaxSize: 87,
  toolbarLensCount: 5,
  toolbarAutoHide: false,
  modalBg: "#1c1c1c",
  modalBgAlpha: 1,
  modalFocus: "#292929",
  modalFocusAlpha: 0.35,
  modalBorder: "#1c1c1c",
  modalElement: "#262626",
  modalElementOutline: "#262626",
  modalRadius: "14px",
  border: "#233044",
  text: "#e6edf3",
  panel: "#1c1c1c",
  panel2: "#292929",
  highlight: "#c4ff67",
  cubeText: "#242424",
  cubeEdges: "#e0e7ff",
  plus: "#c4ff67",
  navCubeBase: "#ffffff",
  navCubeHover: "#ffffff",
  navCubeText: "#242424",
  navCubeEdge: "#e0e7ff",
  navCubeEdgeAlpha: 0.6,
  measureLine: "#c4ff67",
  measureLabel: "#c4ff67",
  measureLabelText: "#292929",
  measureDot: "#c4ff67",
  sectionAxisX: "#ff2b2b",
  sectionAxisY: "#35ff4f",
  sectionAxisZ: "#285bff",
  sectionGizmoDiameter: 1,
  sectionGizmoThickness: 1,
  sectionPlaneFill: "#606060",
  sectionPlaneAlpha: 0.32,
  sectionPlaneEdge: "#0d0d0d",
  sectionPlaneEdgeAlpha: 0.8,
  commentTargetActiveColor: "#c4ff67",
  commentTargetActiveAlpha: 1,
  commentTargetActiveSize: 30,
  commentTargetPassiveColor: "#f5f5fa",
  commentTargetPassiveAlpha: 0.58,
  commentTargetPassiveSize: 22,
};

const STORAGE_KEY = "bimdc-settings-v3";
const LEGACY_KEYS = ["bimdc-settings", "bimdc-settings-v2"];
const isSharedViewerPath = () => (
  typeof window !== "undefined" &&
  /^\/share\/[^/]+\/?$/.test(window.location.pathname || "")
);
const isReadonlySettingsContext = () => {
  if (typeof document !== "undefined") {
    const mode = document.body?.dataset?.viewerMode;
    if (mode) return mode === "guest";
  }
  return isSharedViewerPath();
};
const projectSettings = typeof window !== "undefined" && window.__BIMDC_THEME__
  ? { ...defaultSettings, ...window.__BIMDC_THEME__ }
  : { ...defaultSettings };

const parseColor = (value) => {
  if (typeof value !== "string") return null;
  const color = value.trim();
  const hex = color.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
  if (hex) {
    const raw = hex[1].length === 3
      ? hex[1].split("").map((c) => c + c).join("")
      : hex[1];
    return [
      parseInt(raw.slice(0, 2), 16),
      parseInt(raw.slice(2, 4), 16),
      parseInt(raw.slice(4, 6), 16),
    ];
  }
  const rgb = color.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/i);
  if (!rgb) return null;
  return rgb.slice(1, 4).map((channel) => Math.max(0, Math.min(255, Number(channel))));
};

const readableTextColor = (value) => {
  const rgb = parseColor(value);
  if (!rgb || rgb.some((channel) => !Number.isFinite(channel))) return "#15170f";
  const toLinear = (channel) => {
    const value = channel / 255;
    return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
  };
  const lum = 0.2126 * toLinear(rgb[0]) + 0.7152 * toLinear(rgb[1]) + 0.0722 * toLinear(rgb[2]);
  return (lum + 0.05) / 0.05 >= 1.05 / (lum + 0.05) ? "#15170f" : "#f5f5fa";
};

const clamp = (value, min, max) => Math.max(min, Math.min(max, value));

const colorParts = (value, fallback) => {
  const parsed = parseColor(value);
  if (parsed && parsed.every((channel) => Number.isFinite(channel))) return parsed;
  const parsedFallback = parseColor(fallback);
  if (parsedFallback && parsedFallback.every((channel) => Number.isFinite(channel))) return parsedFallback;
  return [28, 28, 28];
};

const alphaValue = (value, fallback) => {
  const numeric = Number(value);
  return Number.isFinite(numeric) ? clamp(numeric, 0, 1) : fallback;
};

const rgba = (parts, alpha) => {
  const [r, g, b] = parts.map((channel) => Math.round(clamp(channel, 0, 255)));
  return `rgba(${r}, ${g}, ${b}, ${clamp(alpha, 0, 1).toFixed(3)})`;
};

const applyGlassSettings = (root, s) => {
  const panelColor = colorParts(s.modalBg, defaultSettings.modalBg);
  const focusColor = colorParts(s.modalFocus, defaultSettings.modalFocus);
  const controlColor = colorParts(s.modalElement, s.modalFocus || defaultSettings.modalElement);
  const outlineColor = colorParts(s.modalBorder, s.modalElementOutline || defaultSettings.modalBorder);
  const controlOutlineColor = colorParts(s.modalElementOutline, s.modalBorder || defaultSettings.modalElementOutline);
  const modalBgAlpha = alphaValue(s.modalBgAlpha, defaultSettings.modalBgAlpha);
  const modalFocusAlpha = alphaValue(s.modalFocusAlpha, defaultSettings.modalFocusAlpha);
  const panelAlpha = clamp(0.24 + modalBgAlpha * 0.48, 0.32, 0.88);
  const panelStrongAlpha = clamp(panelAlpha + 0.1, 0.42, 0.92);
  const panelSoftAlpha = clamp(panelAlpha - 0.16, 0.2, 0.72);
  const sectionAlpha = clamp(modalFocusAlpha * 0.6, 0.04, 0.42);
  const controlAlpha = clamp(0.12 + modalFocusAlpha * 0.9, 0.16, 0.66);
  const controlHoverAlpha = clamp(controlAlpha + 0.08, 0.22, 0.74);
  const outlineAlpha = clamp(0.055 + modalFocusAlpha * 0.18, 0.065, 0.2);
  const controlOutlineAlpha = clamp(0.045 + modalFocusAlpha * 0.12, 0.055, 0.16);

  const panelBg = rgba(panelColor, panelAlpha);
  const panelBgStrong = rgba(panelColor, panelStrongAlpha);
  const panelBgSoft = rgba(panelColor, panelSoftAlpha);
  const sectionBg = rgba(focusColor, sectionAlpha);
  const outline = rgba(outlineColor, outlineAlpha);
  const outlineBright = rgba(outlineColor, clamp(outlineAlpha + 0.05, 0.08, 0.26));
  const outlineMid = rgba(outlineColor, clamp(outlineAlpha + 0.015, 0.07, 0.22));
  const outlineDim = rgba(outlineColor, clamp(outlineAlpha * 0.62, 0.04, 0.16));
  const controlBg = rgba(controlColor, controlAlpha);
  const controlHover = rgba(focusColor, controlHoverAlpha);
  const controlShadow = rgba(controlOutlineColor, controlOutlineAlpha);

  root.style.setProperty("--nodedc-glass-panel-bg", panelBg);
  root.style.setProperty("--nodedc-glass-panel-bg-strong", panelBgStrong);
  root.style.setProperty("--nodedc-glass-panel-bg-soft", panelBgSoft);
  root.style.setProperty(
    "--nodedc-glass-panel-surface",
    `linear-gradient(180deg, rgba(255, 255, 255, 0.052) 0%, rgba(255, 255, 255, 0.014) 100%), ${panelBg}`
  );
  root.style.setProperty(
    "--nodedc-glass-panel-surface-strong",
    `linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.016) 100%), ${panelBgStrong}`
  );
  root.style.setProperty(
    "--nodedc-glass-section-bg",
    `linear-gradient(180deg, rgba(255, 255, 255, 0.038) 0%, rgba(255, 255, 255, 0.01) 100%), ${sectionBg}`
  );
  root.style.setProperty("--nodedc-glass-outline", outline);
  root.style.setProperty(
    "--nodedc-glass-outline-gradient",
    `linear-gradient(180deg, ${outlineBright} 0%, ${outlineMid} 56%, ${outlineDim} 100%)`
  );
  root.style.setProperty("--nodedc-glass-panel-shadow", `inset 0 1px 0 ${outlineDim}, 0 22px 72px rgba(0, 0, 0, 0.42)`);
  root.style.setProperty(
    "--nodedc-glass-control-bg",
    `linear-gradient(180deg, rgba(255, 255, 255, 0.052) 0%, rgba(255, 255, 255, 0.018) 100%), ${controlBg}`
  );
  root.style.setProperty(
    "--nodedc-glass-control-hover",
    `linear-gradient(180deg, rgba(255, 255, 255, 0.075) 0%, rgba(255, 255, 255, 0.026) 100%), ${controlHover}`
  );
  root.style.setProperty("--nodedc-glass-control-shadow", `inset 0 1px 0 ${controlShadow}`);
  root.style.setProperty("--nodedc-header-dropdown-bg", panelBgStrong);
  root.style.setProperty("--nodedc-header-dropdown-item-bg", controlBg);
  root.style.setProperty("--nodedc-header-dropdown-item-hover-bg", controlHover);
  root.style.setProperty("--nodedc-header-dropdown-item-active-bg", s.toolbarBgActive);
  root.style.setProperty("--nodedc-header-dropdown-item-active-color", readableTextColor(s.toolbarBgActive));
};

const applySettings = (s) => {
  const root = document.documentElement;
  root.style.setProperty("--bg-outer", s.bgOuter);
  root.style.setProperty("--canvas-top", s.canvasTop);
  root.style.setProperty("--canvas-bottom", s.canvasBottom);
  root.style.setProperty("--loading-top", s.loadingTop);
  root.style.setProperty("--loading-bottom", s.loadingBottom);
  root.style.setProperty("--accent", s.accent);
  root.style.setProperty("--accent-2", s.accent2);
  root.style.setProperty("--template-fill", s.templateFill);
  root.style.setProperty("--template-outline", s.templateOutline);
  root.style.setProperty("--template-outline-hover", s.templateOutlineHover);
  root.style.setProperty("--project-btn-fill", s.projectBtnFill);
  root.style.setProperty("--project-btn-border", s.projectBtnBorder);
  root.style.setProperty("--project-btn-hover", s.projectBtnHover);
  root.style.setProperty("--modal-btn-primary", s.modalBtnPrimary);
  root.style.setProperty("--modal-btn-primary-contrast", readableTextColor(s.modalBtnPrimary));
  root.style.setProperty("--modal-btn-secondary", s.modalBtnSecondary);
  root.style.setProperty("--loader-color", s.loaderColor);
  root.style.setProperty("--template-glow-strength", `${s.templateGlow}%`);
  root.style.setProperty("--toolbar-bg", s.toolbarBg);
  root.style.setProperty("--toolbar-bg-active", s.toolbarBgActive);
  root.style.setProperty("--toolbar-bg-active-contrast", readableTextColor(s.toolbarBgActive));
  root.style.setProperty("--toolbar-border", s.toolbarBorder);
  root.style.setProperty("--toolbar-outline", s.toolbarOutline);
  const toolbarMinRaw = Number(s.toolbarMinSize ?? defaultSettings.toolbarMinSize);
  const toolbarMaxRaw = Number(s.toolbarMaxSize ?? defaultSettings.toolbarMaxSize);
  const toolbarLensRaw = Number(s.toolbarLensCount ?? defaultSettings.toolbarLensCount);
  const toolbarMinSize = clamp(Number.isFinite(toolbarMinRaw) ? toolbarMinRaw : defaultSettings.toolbarMinSize, 18, 88);
  const toolbarMaxSize = Math.max(toolbarMinSize, clamp(Number.isFinite(toolbarMaxRaw) ? toolbarMaxRaw : defaultSettings.toolbarMaxSize, 18, 96));
  const toolbarLensCount = Math.max(1, Math.min(15, Math.round(Number.isFinite(toolbarLensRaw) ? toolbarLensRaw : defaultSettings.toolbarLensCount)));
  root.style.setProperty("--tb-size", `${toolbarMinSize}px`);
  root.style.setProperty("--tb-min-size", `${toolbarMinSize}px`);
  root.style.setProperty("--tb-max-size", `${toolbarMaxSize}px`);
  root.style.setProperty("--tb-lens-count", `${toolbarLensCount % 2 === 0 ? toolbarLensCount + 1 : toolbarLensCount}`);
  root.style.setProperty("--tb-autohide", s.toolbarAutoHide ? "1" : "0");
  const bgAlphaPct = `${(s.modalBgAlpha ?? 1) * 100}%`;
  const focusAlphaPct = `${(s.modalFocusAlpha ?? 1) * 100}%`;
  root.style.setProperty("--modal-bg", s.modalBg);
  root.style.setProperty("--modal-bg-alpha", bgAlphaPct);
  root.style.setProperty("--modal-focus", s.modalFocus);
  root.style.setProperty("--modal-focus-alpha", focusAlphaPct);
  root.style.setProperty("--modal-border", s.modalBorder);
  root.style.setProperty("--modal-element", s.modalElement);
  root.style.setProperty("--modal-element-outline", s.modalElementOutline);
  root.style.setProperty("--modal-radius", s.modalRadius);
  applyGlassSettings(root, s);
  root.style.setProperty("--border", s.border);
  root.style.setProperty("--text", s.text);
  root.style.setProperty("--panel", s.panel);
  root.style.setProperty("--panel-2", s.panel2);
  root.style.setProperty("--highlight-color", s.highlight);
  root.style.setProperty("--cube-text", s.cubeText);
  root.style.setProperty("--cube-edges", s.navCubeBase || s.cubeEdges);
  root.style.setProperty("--cube-base", s.navCubeBase || s.cubeEdges);
  root.style.setProperty("--cube-hover", s.navCubeHover);
  root.style.setProperty("--cube-text", s.navCubeText || s.cubeText);
  root.style.setProperty("--cube-edge-color", s.navCubeEdge);
  root.style.setProperty("--cube-edge-alpha", s.navCubeEdgeAlpha);
  root.style.setProperty("--plus-color", s.plus);
  root.style.setProperty("--template-fill", s.templateFill);
  root.style.setProperty("--template-outline", s.templateOutline);
  root.style.setProperty("--measure-line", s.measureLine);
  root.style.setProperty("--measure-label", s.measureLabel);
  root.style.setProperty("--measure-label-text", s.measureLabelText);
  root.style.setProperty("--measure-dot", s.measureDot);
  root.style.setProperty("--section-axis-x", s.sectionAxisX);
  root.style.setProperty("--section-axis-y", s.sectionAxisY);
  root.style.setProperty("--section-axis-z", s.sectionAxisZ);
  root.style.setProperty("--section-gizmo-diameter", s.sectionGizmoDiameter);
  root.style.setProperty("--section-gizmo-thickness", s.sectionGizmoThickness);
  root.style.setProperty("--section-plane-fill", s.sectionPlaneFill);
  root.style.setProperty("--section-plane-alpha", s.sectionPlaneAlpha);
  root.style.setProperty("--section-plane-edge", s.sectionPlaneEdge);
  root.style.setProperty("--section-plane-edge-alpha", s.sectionPlaneEdgeAlpha);
  root.style.setProperty("--comment-target-active", s.commentTargetActiveColor);
  root.style.setProperty("--comment-target-active-alpha", s.commentTargetActiveAlpha);
  root.style.setProperty("--comment-target-active-size", `${s.commentTargetActiveSize}px`);
  root.style.setProperty("--comment-target-passive", s.commentTargetPassiveColor);
  root.style.setProperty("--comment-target-passive-alpha", s.commentTargetPassiveAlpha);
  root.style.setProperty("--comment-target-passive-size", `${s.commentTargetPassiveSize}px`);
  // подсветка объектов
  root.style.setProperty("--highlight-color", s.highlight);
};

const saveSettings = (s) => {
  if (isReadonlySettingsContext()) return;
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
  } catch (e) {
    // ignore
  }
};

const loadSettings = () => {
  if (isReadonlySettingsContext()) {
    return { ...projectSettings };
  }
  try {
    // purge legacy keys so старые палитры не мешали
    LEGACY_KEYS.forEach((k) => localStorage.removeItem(k));

    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) {
      // зашиваем проектные настройки обратно в storage, чтобы при чистом кэше они сразу подхватывались
      saveSettings(projectSettings);
      return { ...projectSettings };
    }
    const stored = JSON.parse(raw);
    if (stored.themeVersion && stored.themeVersion !== projectSettings.themeVersion) {
      saveSettings(projectSettings);
      return { ...projectSettings };
    }
    return { ...projectSettings, ...stored };
  } catch (e) {
    saveSettings(projectSettings);
    return { ...projectSettings };
  }
};

export function createSettingsMenu({ panel, controls = {}, onChange = null, onShow, onHide }) {
  let state = loadSettings();
  applySettings(state);

  const getState = () => ({ ...state });

  const applyStateExternal = (nextState = {}) => {
    state = { ...state, ...nextState };
    applySettings(state);
    saveSettings(state);
    if (typeof onChange === "function") {
      onChange({ key: null, state });
    }
    if (panel && !panel.classList.contains("hidden")) {
      render();
    }
  };

  const hide = () => {
    if (panel) {
      panel.classList.add("hidden");
      panel.style.display = "";
    }
    onHide?.();
  };

  const setVal = (key, val) => {
    state = { ...state, [key]: val };
    applySettings(state);
    saveSettings(state);
    if (typeof onChange === "function") {
      onChange({ key, value: val, state });
    }
  };

  const render = () => {
    if (!panel) return;
    panel.innerHTML = "";
    const header = document.createElement("div");
    header.className = "settings-header";
    header.innerHTML = `<span class="settings-title">Настройки</span>`;
    const closeBtn = document.createElement("button");
    closeBtn.className = "settings-close";
    closeBtn.setAttribute("aria-label", "Закрыть настройки");
    closeBtn.innerHTML = `
      <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
        <path d="M4.25 4.25 11.75 11.75M11.75 4.25 4.25 11.75" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path>
      </svg>
    `;
    closeBtn.addEventListener("click", hide);
    header.appendChild(closeBtn);
    panel.appendChild(header);

    const body = document.createElement("div");
    body.className = "settings-panel-body";
    panel.appendChild(body);

    const section = (title) => {
      const box = document.createElement("div");
      box.className = "settings-section";
      const t = document.createElement("div");
      t.className = "section-title";
      t.textContent = title;
      box.appendChild(t);
      return box;
    };

    const addColor = (parent, label, key) => {
      const row = document.createElement("div");
      row.className = "color-row";
      const lbl = document.createElement("div");
      lbl.textContent = label;
      const control = createColorControl({
        value: state[key],
        onChange: (nextValue) => {
          setVal(key, nextValue);
        },
      });
      row.appendChild(lbl);
      row.appendChild(control);
      parent.appendChild(row);
    };

    const addSettingsSlider = ({
      parent,
      label,
      key,
      value,
      min = 0,
      max = 100,
      step = 1,
      formatValue = (nextValue) => String(nextValue),
      onChange,
    }) => {
      const slider = createSliderControl({
        label,
        value,
        min,
        max,
        step,
        className: "env-range-control",
        showValue: true,
        formatValue,
        onChange: (nextValue) => {
          if (typeof onChange === "function") {
            onChange(nextValue);
            return;
          }
          setVal(key, nextValue);
        },
      });
      parent.appendChild(slider);
      return slider;
    };

    const addNumber = ({
      parent,
      label,
      value,
      min = 0,
      max,
      step = 1,
      onChange,
    }) => {
      const row = document.createElement("div");
      row.className = "color-row";
      const lbl = document.createElement("div");
      lbl.textContent = label;
      const input = document.createElement("input");
      input.className = "env-number-field";
      input.type = "number";
      input.value = value;
      input.min = min;
      input.step = step;
      if (max !== undefined) {
        input.max = max;
      }
      input.addEventListener("input", () => {
        const nextValue = Number(input.value);
        if (Number.isFinite(nextValue)) {
          onChange?.(nextValue);
        }
      });
      row.appendChild(lbl);
      row.appendChild(input);
      parent.appendChild(row);
      return input;
    };

    const addBoolean = (parent, label, key) => {
      const row = document.createElement("label");
      row.className = "checkbox-row";
      const text = document.createElement("span");
      text.className = "checkbox-row__label";
      text.textContent = label;
      const chk = document.createElement("input");
      chk.type = "checkbox";
      chk.checked = !!state[key];
      chk.addEventListener("change", () => {
        setVal(key, chk.checked);
      });
      row.appendChild(text);
      row.appendChild(chk);
      parent.appendChild(row);
      return chk;
    };

    const font = section("Фон");
    addColor(font, "Фон внешний", "bgOuter");
    addColor(font, "Фон холста верх", "canvasTop");
    addColor(font, "Фон холста низ", "canvasBottom");
    addColor(font, "Фон загрузки верх", "loadingTop");
    addColor(font, "Фон загрузки низ", "loadingBottom");
    body.appendChild(font);

    const buttons = section("Кнопки");
    addColor(buttons, "Плюс", "plus");
    addColor(buttons, "Кнопки (заливка)", "templateFill");
    addColor(buttons, "Кнопки (бордер)", "templateOutline");
    addColor(buttons, "Кнопки (outline hover)", "templateOutlineHover");
    addSettingsSlider({
      parent: buttons,
      label: "Яркость подсветки",
      key: "templateGlow",
      value: Number(state.templateGlow ?? 80),
      min: 0,
      max: 100,
      step: 1,
      formatValue: (val) => `${Math.round(val)}%`,
    });
    body.appendChild(buttons);

    const projectButtons = section("Кнопки проектов");
    addColor(projectButtons, "Проекты (заливка)", "projectBtnFill");
    addColor(projectButtons, "Проекты (бордер)", "projectBtnBorder");
    addColor(projectButtons, "Проекты (hover)", "projectBtnHover");
    body.appendChild(projectButtons);

    const modalButtons = section("Кнопки модалок");
    addColor(modalButtons, "Модальная primary", "modalBtnPrimary");
    addColor(modalButtons, "Модальная secondary", "modalBtnSecondary");
    addColor(modalButtons, "Лоудер", "loaderColor");
    body.appendChild(modalButtons);

    const toolbar = section("Toolbar");
    addColor(toolbar, "Фон", "toolbarBg");
    addColor(toolbar, "Актив", "toolbarBgActive");
    addColor(toolbar, "Бордер", "toolbarBorder");
    addColor(toolbar, "Outline", "toolbarOutline");
    addSettingsSlider({
      parent: toolbar,
      label: "Минимальный размер",
      value: Math.max(18, Math.min(42, Number(state.toolbarMinSize ?? defaultSettings.toolbarMinSize))),
      min: 18,
      max: 42,
      step: 1,
      formatValue: (val) => `${Math.round(val)}px`,
      onChange: (val) => {
        const next = Math.max(18, Math.min(42, Math.round(Number(val))));
        setVal("toolbarMinSize", next);
        if (Number(state.toolbarMaxSize) < next) {
          setVal("toolbarMaxSize", next);
        }
      },
    });
    addSettingsSlider({
      parent: toolbar,
      label: "Максимальный размер",
      value: Math.max(28, Math.min(88, Number(state.toolbarMaxSize ?? defaultSettings.toolbarMaxSize))),
      min: 28,
      max: 88,
      step: 1,
      formatValue: (val) => `${Math.round(val)}px`,
      onChange: (val) => {
        const minSize = Math.max(18, Math.min(42, Number(state.toolbarMinSize ?? defaultSettings.toolbarMinSize)));
        setVal("toolbarMaxSize", Math.max(minSize, Math.min(88, Math.round(Number(val)))));
      },
    });
    addSettingsSlider({
      parent: toolbar,
      label: "Линза",
      value: Math.max(1, Math.min(13, Number(state.toolbarLensCount ?? defaultSettings.toolbarLensCount))),
      min: 1,
      max: 13,
      step: 2,
      formatValue: (val) => `${Math.round(val)} иконок`,
      onChange: (val) => {
        const next = Math.max(1, Math.min(13, Math.round(Number(val))));
        setVal("toolbarLensCount", next % 2 === 0 ? next + 1 : next);
      },
    });
    addBoolean(toolbar, "Автоскрытие", "toolbarAutoHide");
    body.appendChild(toolbar);

    const navCube = section("NavCube");
    addColor(navCube, "Грани", "navCubeBase");
    addColor(navCube, "Hover", "navCubeHover");
    addColor(navCube, "Текст", "navCubeText");
    addColor(navCube, "Рёбра", "navCubeEdge");
    addSettingsSlider({
      parent: navCube,
      label: "Прозрачность рёбер",
      value: Math.round(Math.max(0, Math.min(1, Number(state.navCubeEdgeAlpha ?? 0.6))) * 100),
      min: 0,
      max: 100,
      step: 5,
      formatValue: (val) => `${Math.round(val)}%`,
      onChange: (val) => {
        setVal("navCubeEdgeAlpha", Math.max(0, Math.min(1, Number(val) / 100)));
      },
    });
    body.appendChild(navCube);

    const sectionCut = section("Разрез / сечение");
    addColor(sectionCut, "Ось X", "sectionAxisX");
    addColor(sectionCut, "Ось Y", "sectionAxisY");
    addColor(sectionCut, "Ось Z", "sectionAxisZ");
    addSettingsSlider({
      parent: sectionCut,
      label: "Диаметр гизмо",
      value: Math.round(Math.max(0.5, Math.min(1.8, Number(state.sectionGizmoDiameter ?? 1))) * 100),
      min: 50,
      max: 180,
      step: 5,
      formatValue: (val) => `${Math.round(val)}%`,
      onChange: (val) => {
        setVal("sectionGizmoDiameter", Math.max(0.5, Math.min(1.8, Number(val) / 100)));
      },
    });
    addSettingsSlider({
      parent: sectionCut,
      label: "Толщина гизмо",
      value: Math.round(Math.max(0.5, Math.min(2.5, Number(state.sectionGizmoThickness ?? 1))) * 100),
      min: 50,
      max: 250,
      step: 5,
      formatValue: (val) => `${Math.round(val)}%`,
      onChange: (val) => {
        setVal("sectionGizmoThickness", Math.max(0.5, Math.min(2.5, Number(val) / 100)));
      },
    });
    addColor(sectionCut, "Плоскость", "sectionPlaneFill");
    addSettingsSlider({
      parent: sectionCut,
      label: "Прозрачность плоскости",
      value: Math.round(Math.max(0, Math.min(1, Number(state.sectionPlaneAlpha ?? 0.32))) * 100),
      min: 0,
      max: 100,
      step: 5,
      formatValue: (val) => `${Math.round(val)}%`,
      onChange: (val) => {
        setVal("sectionPlaneAlpha", Math.max(0, Math.min(1, Number(val) / 100)));
      },
    });
    addColor(sectionCut, "Обводка", "sectionPlaneEdge");
    addSettingsSlider({
      parent: sectionCut,
      label: "Прозрачность обводки",
      value: Math.round(Math.max(0, Math.min(1, Number(state.sectionPlaneEdgeAlpha ?? 0.8))) * 100),
      min: 0,
      max: 100,
      step: 5,
      formatValue: (val) => `${Math.round(val)}%`,
      onChange: (val) => {
        setVal("sectionPlaneEdgeAlpha", Math.max(0, Math.min(1, Number(val) / 100)));
      },
    });
    body.appendChild(sectionCut);

    const commentTargets = section("Таргеты комментариев");
    addColor(commentTargets, "Активный: цвет", "commentTargetActiveColor");
    addSettingsSlider({
      parent: commentTargets,
      label: "Активный: прозрачность",
      value: Math.round(Math.max(0, Math.min(1, Number(state.commentTargetActiveAlpha ?? 1))) * 100),
      min: 0,
      max: 100,
      step: 5,
      formatValue: (val) => `${Math.round(val)}%`,
      onChange: (val) => {
        setVal("commentTargetActiveAlpha", Math.max(0, Math.min(1, Number(val) / 100)));
      },
    });
    addSettingsSlider({
      parent: commentTargets,
      label: "Активный: размер",
      value: Math.max(14, Math.min(64, Number(state.commentTargetActiveSize ?? 30))),
      min: 14,
      max: 64,
      step: 1,
      formatValue: (val) => `${Math.round(val)}px`,
      onChange: (val) => {
        setVal("commentTargetActiveSize", Math.max(14, Math.min(64, Math.round(Number(val)))));
      },
    });
    addColor(commentTargets, "Пассивный: цвет", "commentTargetPassiveColor");
    addSettingsSlider({
      parent: commentTargets,
      label: "Пассивный: прозрачность",
      value: Math.round(Math.max(0, Math.min(1, Number(state.commentTargetPassiveAlpha ?? 0.58))) * 100),
      min: 0,
      max: 100,
      step: 5,
      formatValue: (val) => `${Math.round(val)}%`,
      onChange: (val) => {
        setVal("commentTargetPassiveAlpha", Math.max(0, Math.min(1, Number(val) / 100)));
      },
    });
    addSettingsSlider({
      parent: commentTargets,
      label: "Пассивный: размер",
      value: Math.max(12, Math.min(48, Number(state.commentTargetPassiveSize ?? 22))),
      min: 12,
      max: 48,
      step: 1,
      formatValue: (val) => `${Math.round(val)}px`,
      onChange: (val) => {
        setVal("commentTargetPassiveSize", Math.max(12, Math.min(48, Math.round(Number(val)))));
      },
    });
    body.appendChild(commentTargets);

    const measure = section("Линейка");
    addColor(measure, "Линия/бордер", "measureLine");
    addColor(measure, "Плашка", "measureLabel");
    addColor(measure, "Текст плашки", "measureLabelText");
    addColor(measure, "Кружок", "measureDot");
    body.appendChild(measure);

    const viewport = section("Viewport");
    const addCheck = (label, checked, handler) => {
      const row = document.createElement("label");
      row.className = "checkbox-row";
      const text = document.createElement("span");
      text.className = "checkbox-row__label";
      text.textContent = label;
      const chk = document.createElement("input");
      chk.type = "checkbox";
      chk.checked = checked;
      chk.addEventListener("change", () => handler(chk.checked));
      row.appendChild(text);
      row.appendChild(chk);
      viewport.appendChild(row);
    };
    if (controls.toggleEdges) addCheck("Рёбра", controls.toggleEdges.checked, (v) => { controls.toggleEdges.checked = v; controls.toggleEdges.dispatchEvent(new Event("change")); });
    if (controls.toggleTransparent) addCheck("Прозрачный фон", controls.toggleTransparent.checked, (v) => { controls.toggleTransparent.checked = v; controls.toggleTransparent.dispatchEvent(new Event("change")); });
    if (controls.addMode) addCheck("Добавлять в сцену", controls.addMode.checked, (v) => { controls.addMode.checked = v; });
    body.appendChild(viewport);

    const modals = section("Модальные окна");
    addColor(modals, "Основной фон", "modalBg");
    addColor(modals, "Фокусный фон", "modalFocus");
    addSettingsSlider({
      parent: modals,
      label: "Прозрачность основного фона",
      min: 0,
      max: 1,
      step: 0.05,
      value: parseFloat(state.modalBgAlpha ?? 1),
      formatValue: (val) => `${Math.round(Number(val) * 100)}%`,
      onChange: (val) => {
        const num = Math.max(0, Math.min(1, parseFloat(val)));
        setVal("modalBgAlpha", num);
        document.documentElement.style.setProperty("--modal-bg-alpha", `${num * 100}%`);
      },
    });

    addSettingsSlider({
      parent: modals,
      label: "Прозрачность фокусного фона",
      min: 0,
      max: 1,
      step: 0.05,
      value: parseFloat(state.modalFocusAlpha ?? 1),
      formatValue: (val) => `${Math.round(Number(val) * 100)}%`,
      onChange: (val) => {
        const num = Math.max(0, Math.min(1, parseFloat(val)));
        setVal("modalFocusAlpha", num);
        document.documentElement.style.setProperty("--modal-focus-alpha", `${num * 100}%`);
      },
    });

    addColor(modals, "Аутлайн", "modalBorder");
    addColor(modals, "Контролы", "modalElement");
    addColor(modals, "Аутлайн элемента", "modalElementOutline");
    addNumber({
      parent: modals,
      label: "Радиус",
      value: parseInt(getComputedStyle(document.documentElement).getPropertyValue("--modal-radius")) || 14,
      min: 0,
      max: 48,
      step: 1,
      onChange: (nextValue) => {
        const val = `${Math.max(0, Math.min(48, Math.round(nextValue)))}px`;
        setVal("modalRadius", val);
      },
    });
    body.appendChild(modals);

    const cube = section("Выделение объектов");
    addColor(cube, "Подсветка объектов", "highlight");
    body.appendChild(cube);
  };

  const show = () => {
    render();
    if (panel) {
      panel.classList.remove("hidden");
      panel.style.display = "flex";
    }
    onShow?.();
  };

  const toggle = () => {
    if (!panel) return;
    if (panel.classList.contains("hidden")) {
      show();
    } else {
      hide();
    }
  };

  return { render, show, toggle, hide, getState, applyState: applyStateExternal };
}
