import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from "react";
import { applyGlassMaterial, applyNodedcTheme, defaultGlassMaterial, type GlassMaterialSettings, type NodedcTheme, type RgbTuple } from "@nodedc/ui-core";
import { createTemplateFeatures, getPageTemplate, pageTemplates, type PageTemplateDefinition } from "@nodedc/page-patterns";
import {
  AdminNavigationPanel,
  AppHeader,
  ApplicationPanel,
  ApplicationShell,
  Button,
  Checker,
  ColorField,
  ConfirmationModal,
  ControlRow,
  Dropdown,
  DragDropRoot,
  DraggableItem,
  DropZone,
  FieldFrame,
  GlassSurface,
  GlassMaterialSurface,
  HeaderNavigation,
  HeaderProfile,
  HeaderWorkspace,
  Icon,
  IconButton,
  Inspector,
  InspectorSelectField,
  MediaSourceField,
  RangeControl,
  SegmentedControl,
  Select,
  ShareAccessModal,
  ShareLinkModal,
  SortableItem,
  SortableScope,
  SettingsCard,
  Switch,
  TextAreaField,
  TextField,
  ToastCard,
  ToastStack,
  Toolbar,
  UserProfileMenu,
  useApplicationWorkspace,
  Window,
  WindowFooterActions,
  WorkspaceWindow,
  type IconName,
  type ShareAccessMember,
  type ToastItem,
  type ToastTone,
  type ToolbarPlacement,
  type WorkspaceWindowRect,
} from "@nodedc/ui-react";
import { applyFaviconAssets, createIcoBlob, generateFavicons, type FaviconAssetUrls, type GeneratedFavicon } from "./favicon.js";
import {
  applicationIdFromView,
  applicationPageIdFromView,
  applicationPageViewId,
  applicationViewId,
  type ApplicationDraftSaveState,
  type ApplicationManifestV01,
  type ApplicationSummary,
  type DesignProfileSummary,
  type DesignProfileStatus,
} from "./applicationManifest.js";
import { createDefaultMapPageLayout, MapFixturePreview, type MapFixturePreviewHandle, type MapPageLayout } from "./MapFixturePreview.js";
import {
  mapDesignFragmentForLayout,
  mapDesignFragmentFromLayout,
  mapDesignOverridesFromResolved,
  mapDesignProfileKey,
  resolveMapDesignLayout,
  type MaterialDraft,
  type StoredLayout,
} from "./designProfile.js";
import { FoundrySettingsModal } from "./FoundrySettingsModal.js";

type CatalogSection = "controls" | "media" | "glass" | "status" | "modals" | "icons";
type StudioContext = "visual" | "pages" | "applications";
type StudioView = CatalogSection | `page-template:${string}` | `application:${string}` | `application:${string}/page:${string}`;
type DesignProfileSaveScope = { kind: "global" } | { kind: "page"; templateId: "map"; templateVersion: string };
type DesignProfileDocument = {
  id: string;
  name: string;
  version: string;
  status: DesignProfileStatus;
  layout: StoredLayout;
  timestamps: { updatedAt: string; publishedAt?: string };
};

const pageTemplateViewId = (id: string) => `page-template:${id}` as const;
const pageTemplateIdFromView = (view: string | null) => view?.startsWith("page-template:") ? view.slice("page-template:".length) : null;
const designProfileDocumentPath = (reference: { id: string; version: string; status: DesignProfileStatus }) => (
  reference.status === "published"
    ? `/api/design-profiles/${reference.id}/versions/${reference.version}`
    : `/api/design-profiles/${reference.id}`
);

const accents: Array<{ label: string; value: RgbTuple; hex: string }> = [
  { label: "NODE.DC", value: [255, 47, 146], hex: "#ff2f92" },
  { label: "Rose", value: [255, 62, 108], hex: "#ff3e6c" },
  { label: "Blush", value: [255, 108, 175], hex: "#ff6caf" },
  { label: "Magenta", value: [215, 70, 255], hex: "#d746ff" },
];

interface FoundrySessionProfile {
  user: {
    id: string;
    email: string;
    displayName: string;
    avatarUrl: string | null;
    initials: string;
  } | null;
  profileUrl: string | null;
  access?: {
    role: "admin" | "user";
  };
}

const materialDefaults: Record<NodedcTheme, MaterialDraft> = {
  dark: {
    panelHex: "#151517",
    panelOpacity: 100,
    fieldHex: "#2a2a2c",
    fieldOpacity: 100,
    nestedHex: "#0b0b0d",
  },
  light: {
    panelHex: "#ffffff",
    panelOpacity: 100,
    fieldHex: "#ffffff",
    fieldOpacity: 100,
    nestedHex: "#f4f4f4",
  },
};

type ShareRole = "viewer" | "editor" | "admin";

const shareRoleOptions = [
  { value: "viewer", label: "Просмотр" },
  { value: "editor", label: "Редактор" },
  { value: "admin", label: "Соавтор" },
] satisfies Array<{ value: ShareRole; label: string }>;

const selectOptions = [
  { value: "active", label: "Активен", description: "Сервис доступен пользователям" },
  { value: "maintenance", label: "Техработы", description: "Временно недоступен" },
  { value: "disabled", label: "Отключён", description: "Запуск запрещён" },
] as const;

const sectionDefinitions: Record<CatalogSection, { eyebrow: string; title: string; description: string; icon: IconName }> = {
  controls: {
    eyebrow: "01 / CONTROLS",
    title: "Контролы",
    description: "Кнопки, поля, выбор, оконные действия и вызываемый Inspector нового Engine.",
    icon: "sliders",
  },
  media: {
    eyebrow: "02 / MEDIA & SETTINGS",
    title: "Медиа и настройки",
    description: "Общий CMS/Launcher-контракт файла, URL, превью и нейтральных карточек настроек.",
    icon: "image",
  },
  glass: {
    eyebrow: "03 / GLASSY MATERIAL",
    title: "Glassy material",
    description: "Единый материал только для модальных окон и перемещаемого Inspector.",
    icon: "apps",
  },
  status: {
    eyebrow: "04 / STATUS NOTIFICATIONS",
    title: "Статусы",
    description: "Канонические стеклянные уведомления о сохранении, обновлении и ошибках — стек снизу справа.",
    icon: "activity",
  },
  modals: {
    eyebrow: "05 / MODALS",
    title: "Модалки",
    description: "Полная карта modal-паттернов Launcher, нового Engine и BIM Viewer.",
    icon: "clipboard",
  },
  icons: {
    eyebrow: "06 / ICONS",
    title: "Иконки",
    description: "Канонический общий набор по Launcher, SEO, BIM Viewer и новым участкам Engine.",
    icon: "grid",
  },
};

const iconGroups: Array<{ title: string; note: string; icons: IconName[] }> = [
  {
    title: "Окно и слой",
    note: "Launcher / Hub",
    icons: ["close", "plus", "expand", "minimize", "refresh", "panel", "apps"],
  },
  {
    title: "Навигация",
    note: "Launcher / Engine / BIM",
    icons: ["chevron-left", "chevron-right", "chevron-down", "grid", "list", "sliders", "search"],
  },
  {
    title: "Редактирование",
    note: "Launcher / SEO",
    icons: ["save", "edit", "trash", "copy", "upload", "download", "external"],
  },
  {
    title: "Состояние и доступ",
    note: "Вся платформа",
    icons: ["check", "alert", "activity", "lock", "key", "shield", "circle"],
  },
  {
    title: "Сущности",
    note: "Общий словарь",
    icons: ["profile", "users", "building", "globe", "target", "database", "network", "inbox", "mail"],
  },
  {
    title: "Контент",
    note: "SEO / BIM / Engine",
    icons: ["image", "video", "file", "folder", "clipboard", "settings"],
  },
];

const iconLabels: Record<IconName, string> = {
  activity: "Активность",
  alert: "Предупреждение",
  apps: "Приложения",
  building: "Компания",
  check: "Готово",
  "chevron-down": "Раскрыть",
  "chevron-left": "Назад",
  "chevron-right": "Вперёд",
  circle: "Маркер",
  clipboard: "Список",
  close: "Закрыть",
  copy: "Копировать",
  database: "База данных",
  download: "Скачать",
  edit: "Редактировать",
  expand: "Развернуть",
  external: "Открыть снаружи",
  file: "Файл",
  folder: "Папка",
  globe: "Публичный контур",
  grid: "Обзор",
  image: "Изображение",
  inbox: "Уведомления",
  key: "Ключ",
  list: "Список задач",
  lock: "Закрыто",
  mail: "Приглашение",
  minimize: "Свернуть",
  network: "Связи",
  panel: "Панель",
  target: "Таргеты",
  plus: "Добавить",
  profile: "Профиль",
  refresh: "Обновить",
  save: "Сохранить",
  search: "Поиск",
  settings: "Настройки",
  shield: "Доступ",
  sliders: "Параметры",
  trash: "Удалить",
  upload: "Загрузить",
  users: "Участники",
  video: "Видео",
};

function rgbFromHex(hex: string): RgbTuple | null {
  const normalized = hex.trim().replace(/^#/, "");
  if (!/^[0-9a-f]{6}$/i.test(normalized)) return null;
  return [
    Number.parseInt(normalized.slice(0, 2), 16),
    Number.parseInt(normalized.slice(2, 4), 16),
    Number.parseInt(normalized.slice(4, 6), 16),
  ];
}

function Preview({ title, note, children, className }: {
  title: string;
  note?: string;
  children: ReactNode;
  className?: string;
}) {
  return (
    <GlassSurface className={`catalog-preview ${className ?? ""}`} padding="lg">
      <div className="catalog-preview__head">
        <strong>{title}</strong>
        {note ? <span>{note}</span> : null}
      </div>
      <div className="catalog-preview__body">{children}</div>
    </GlassSurface>
  );
}

export function CatalogApp() {
  const [theme, setTheme] = useState<NodedcTheme>("dark");
  const [accentHex, setAccentHex] = useState("#ff2f92");
  const [materialByTheme, setMaterialByTheme] = useState<Record<NodedcTheme, MaterialDraft>>(() => ({
    dark: { ...materialDefaults.dark },
    light: { ...materialDefaults.light },
  }));
  const [layoutSaveState, setLayoutSaveState] = useState<"idle" | "loading" | "saving" | "saved" | "error">("loading");
  const [studioContext, setStudioContext] = useState<StudioContext>("visual");
  const [applicationSummaries, setApplicationSummaries] = useState<ApplicationSummary[]>([]);
  const [designProfiles, setDesignProfiles] = useState<DesignProfileSummary[]>([]);
  const [activeDesignProfileLayout, setActiveDesignProfileLayout] = useState<StoredLayout | null>(null);
  const [applicationDesignProfileLayout, setApplicationDesignProfileLayout] = useState<StoredLayout | null>(null);
  const [applicationDraft, setApplicationDraft] = useState<ApplicationManifestV01 | null>(null);
  const [applicationSaveState, setApplicationSaveState] = useState<ApplicationDraftSaveState>("idle");
  const [applicationError, setApplicationError] = useState("");
  const [toasts, setToasts] = useState<ToastItem[]>([]);
  const toastSequenceRef = useRef(0);
  const [sessionProfile, setSessionProfile] = useState<FoundrySessionProfile | null>(null);
  const [foundrySettingsOpen, setFoundrySettingsOpen] = useState(false);
  const [mapTemplateLayout, setMapTemplateLayout] = useState<MapPageLayout | null>(null);
  const [mapTemplateSaveState, setMapTemplateSaveState] = useState<"idle" | "loading" | "saving" | "saved" | "error">("loading");
  const mapTemplatePreviewRef = useRef<MapFixturePreviewHandle>(null);
  const applicationMapPreviewRef = useRef<MapFixturePreviewHandle>(null);
  const [createModuleOpen, setCreateModuleOpen] = useState(false);
  const [createModuleName, setCreateModuleName] = useState("");
  const [createModuleSlug, setCreateModuleSlug] = useState("");
  const [createModuleDescription, setCreateModuleDescription] = useState("");
  const [deleteModuleOpen, setDeleteModuleOpen] = useState(false);
  const [pageRemovalId, setPageRemovalId] = useState<string | null>(null);
  const [applicationMode, setApplicationMode] = useState<"edit" | "preview">("edit");
  const [designProfileSaveOpen, setDesignProfileSaveOpen] = useState(false);
  const [designProfileSaveMode, setDesignProfileSaveMode] = useState<"save" | "save-as">("save");
  const [designProfileSaveScope, setDesignProfileSaveScope] = useState<DesignProfileSaveScope>({ kind: "global" });
  const [designProfileName, setDesignProfileName] = useState("");
  const [designProfilePublishState, setDesignProfilePublishState] = useState<"idle" | "publishing" | "published" | "error">("idle");
  const [activeDesignProfileId, setActiveDesignProfileId] = useState("default");
  const workspace = useApplicationWorkspace<StudioView>();
  const { navigationOpen: guidelineOpen, activeView, contentExpanded: panelExpanded } = workspace;
  const activeSection = studioContext === "visual" && activeView && !activeView.startsWith("application:")
    ? activeView as CatalogSection
    : null;
  const activeApplicationId = applicationIdFromView(activeView);
  const activeApplicationPageId = applicationPageIdFromView(activeView);
  const activePageTemplateId = pageTemplateIdFromView(activeView);
  const [selectedStatus, setSelectedStatus] = useState<(typeof selectOptions)[number]["value"]>("active");
  const [brightness, setBrightness] = useState(49);
  const [glowDistance, setGlowDistance] = useState(105);
  const [usePortColors, setUsePortColors] = useState(false);
  const [connectionType, setConnectionType] = useState("spline");
  const [lightColor, setLightColor] = useState("#ff2f92");
  const [connectionColor, setConnectionColor] = useState("#404040");
  const [fillColor, setFillColor] = useState("#ff6caf");
  const [fillOpacity, setFillOpacity] = useState(100);
  const [strokeColor, setStrokeColor] = useState("#2b2b36");
  const [strokeOpacity, setStrokeOpacity] = useState(100);
  const [inspectorOpen, setInspectorOpen] = useState(false);
  const workspaceWindowDemoRef = useRef<HTMLDivElement>(null);
  const [workspaceWindowDemoOpen, setWorkspaceWindowDemoOpen] = useState(true);
  const [workspaceWindowDemoMaximized, setWorkspaceWindowDemoMaximized] = useState(false);
  const [workspaceWindowDemoRect, setWorkspaceWindowDemoRect] = useState<WorkspaceWindowRect>({ x: 24, y: 24, width: 340, height: 230 });
  const [createModalOpen, setCreateModalOpen] = useState(false);
  const [confirmOpen, setConfirmOpen] = useState(false);
  const [shareAccessOpen, setShareAccessOpen] = useState(false);
  const [shareLinkOpen, setShareLinkOpen] = useState(false);
  const [contextActionOpen, setContextActionOpen] = useState(false);
  const [historyOpen, setHistoryOpen] = useState(false);
  const [detailOpen, setDetailOpen] = useState(false);
  const [detailExpanded, setDetailExpanded] = useState(false);
  const [projectName, setProjectName] = useState("NODE.DC Platform");
  const [notes, setNotes] = useState("Общий контракт компонентов без доменной логики приложения.");
  const [mediaSource, setMediaSource] = useState<"file" | "url">("file");
  const [mediaUrl, setMediaUrl] = useState("");
  // Match the persisted production design-profile default from first paint.
  // This prevents the packaged pink sample clip from flashing before
  // /api/layout returns the same saved media configuration.
  const [mediaFileName, setMediaFileName] = useState("possible shapes.mp4");
  const [fileMediaSrc, setFileMediaSrc] = useState("/uploads/1783715822132-possible-shapes.mp4");
  const [mediaError, setMediaError] = useState("");
  const [mediaVisible, setMediaVisible] = useState(true);
  const mediaObjectUrlRef = useRef<string | null>(null);
  const pendingMediaFileRef = useRef<File | null>(null);
  const [logoSource, setLogoSource] = useState<"file" | "url">("file");
  const [logoUrl, setLogoUrl] = useState("");
  const [logoFileName, setLogoFileName] = useState("nodedc-mark.svg");
  const [logoFileSrc, setLogoFileSrc] = useState("/nodedc-mark.svg");
  const [logoError, setLogoError] = useState("");
  const logoObjectUrlRef = useRef<string | null>(null);
  const pendingLogoFileRef = useRef<File | null>(null);
  const [glassMaterial, setGlassMaterial] = useState<GlassMaterialSettings>({ ...defaultGlassMaterial });
  const [faviconFileName, setFaviconFileName] = useState("icon-adaptive.svg");
  const [faviconError, setFaviconError] = useState("");
  const [generatedFavicons, setGeneratedFavicons] = useState<GeneratedFavicon[]>([]);
  const [faviconAssets, setFaviconAssets] = useState<FaviconAssetUrls>({
    ico: "/favicon/favicon.ico",
    apple: "/favicon/apple-touch-icon.png",
    icon192: "/favicon/icon-192.png",
    icon512: "/favicon/icon-512.png",
  });
  const pendingFaviconIcoRef = useRef<Blob | null>(null);
  const [toolbarPlacement, setToolbarPlacement] = useState<ToolbarPlacement>("bottom");
  const [toolbarBg, setToolbarBg] = useState("#111115");
  const [toolbarBorder, setToolbarBorder] = useState("#111117");
  const [toolbarOutline, setToolbarOutline] = useState("#1c1c1c");
  const [toolbarMinSize, setToolbarMinSize] = useState(25);
  const [toolbarMaxSize, setToolbarMaxSize] = useState(87);
  const [toolbarLensCount, setToolbarLensCount] = useState(5);
  const [toolbarAutoHide, setToolbarAutoHide] = useState(false);
  const [shareEmail, setShareEmail] = useState("");
  const [shareRole, setShareRole] = useState<ShareRole>("editor");
  const [shareMessage, setShareMessage] = useState("");
  const [shareMembers, setShareMembers] = useState<Array<ShareAccessMember<ShareRole>>>([
    { id: "owner", name: "DC Constructions", email: "dc@nodedc.ru", role: "admin", roleLabel: "Владелец", immutable: true },
    { id: "editor", name: "Maria Petrova", email: "maria@nodedc.ru", role: "editor", roleLabel: "Редактор" },
  ]);

  const dismissToast = useCallback((id: string) => {
    setToasts((current) => current.filter((item) => item.id !== id));
  }, []);

  const pushToast = useCallback((tone: ToastTone, title: string, description?: string, durationMs?: number | null) => {
    toastSequenceRef.current += 1;
    const id = `foundry-toast-${toastSequenceRef.current}`;
    setToasts((current) => [...current.slice(-3), { id, tone, title, description, durationMs }]);
    return id;
  }, []);

  const updateToast = useCallback((id: string, patch: Partial<Omit<ToastItem, "id">>) => {
    setToasts((current) => current.map((item) => item.id === id ? { ...item, ...patch } : item));
  }, []);

  const applyDesignProfileLayout = useCallback((stored: StoredLayout) => {
    if (stored.theme === "dark" || stored.theme === "light") setTheme(stored.theme);
    if (stored.accentHex) setAccentHex(stored.accentHex);
    if (stored.materialByTheme) setMaterialByTheme((current) => ({
      dark: { ...current.dark, ...stored.materialByTheme?.dark },
      light: { ...current.light, ...stored.materialByTheme?.light },
    }));
    if (stored.environment) {
      if (stored.environment.lightColor) setLightColor(stored.environment.lightColor);
      if (typeof stored.environment.brightness === "number") setBrightness(stored.environment.brightness);
      if (typeof stored.environment.glowDistance === "number") setGlowDistance(stored.environment.glowDistance);
      if (stored.environment.connectionType) setConnectionType(stored.environment.connectionType);
      if (stored.environment.connectionColor) setConnectionColor(stored.environment.connectionColor);
      if (typeof stored.environment.usePortColors === "boolean") setUsePortColors(stored.environment.usePortColors);
      if (stored.environment.fillColor) setFillColor(stored.environment.fillColor);
      if (typeof stored.environment.fillOpacity === "number") setFillOpacity(stored.environment.fillOpacity);
      if (stored.environment.strokeColor) setStrokeColor(stored.environment.strokeColor);
      if (typeof stored.environment.strokeOpacity === "number") setStrokeOpacity(stored.environment.strokeOpacity);
    }
    if (stored.media) {
      if (stored.media.source === "file" || stored.media.source === "url") setMediaSource(stored.media.source);
      if (typeof stored.media.url === "string") setMediaUrl(stored.media.url);
      if (stored.media.fileName) setMediaFileName(stored.media.fileName);
      if (stored.media.fileSrc) setFileMediaSrc(stored.media.fileSrc);
      if (typeof stored.media.visible === "boolean") setMediaVisible(stored.media.visible);
      if (stored.media.logoSource === "file" || stored.media.logoSource === "url") setLogoSource(stored.media.logoSource);
      if (typeof stored.media.logoUrl === "string") setLogoUrl(stored.media.logoUrl);
      if (stored.media.logoFileName) setLogoFileName(stored.media.logoFileName);
      if (stored.media.logoFileSrc) setLogoFileSrc(stored.media.logoFileSrc);
      if (stored.media.faviconFileName) setFaviconFileName(stored.media.faviconFileName);
      if (stored.media.faviconAssets) setFaviconAssets(stored.media.faviconAssets);
    }
    if (stored.glass) setGlassMaterial({ ...defaultGlassMaterial, ...stored.glass });
    if (stored.toolbar?.placement) setToolbarPlacement(stored.toolbar.placement);
    if (stored.toolbar?.background) setToolbarBg(stored.toolbar.background);
    if (stored.toolbar?.border) setToolbarBorder(stored.toolbar.border);
    if (stored.toolbar?.outline) setToolbarOutline(stored.toolbar.outline);
    if (typeof stored.toolbar?.minSize === "number") setToolbarMinSize(stored.toolbar.minSize);
    if (typeof stored.toolbar?.maxSize === "number") setToolbarMaxSize(stored.toolbar.maxSize);
    if (typeof stored.toolbar?.lensCount === "number") setToolbarLensCount(stored.toolbar.lensCount);
    if (typeof stored.toolbar?.autoHide === "boolean") setToolbarAutoHide(stored.toolbar.autoHide);
  }, []);

  const externalMediaSrc = /^(https?:)?\/\//i.test(mediaUrl) && /\.(mp4|webm|mov|m4v)(\?.*)?$/i.test(mediaUrl)
    ? mediaUrl
    : null;
  const stageMediaSrc = mediaSource === "url" && externalMediaSrc ? externalMediaSrc : fileMediaSrc;
  const externalLogoSrc = /^(https?:)?\/\//i.test(logoUrl) && /\.(png|jpe?g|gif|webp)(\?.*)?$/i.test(logoUrl)
    ? logoUrl
    : null;
  const headerMarkSrc = logoSource === "url" && externalLogoSrc ? externalLogoSrc : logoFileSrc;

  const accent = useMemo(() => rgbFromHex(accentHex) ?? accents[0].value, [accentHex]);
  const material = materialByTheme[theme];
  const panelMaterial = useMemo(() => rgbFromHex(material.panelHex) ?? rgbFromHex(materialDefaults[theme].panelHex)!, [material.panelHex, theme]);
  const fieldMaterial = useMemo(() => rgbFromHex(material.fieldHex) ?? rgbFromHex(materialDefaults[theme].fieldHex)!, [material.fieldHex, theme]);

  useEffect(() => {
    applyNodedcTheme(document.documentElement, {
      theme,
      accent,
      material: {
        panel: panelMaterial,
        panelOpacity: material.panelOpacity / 100,
        field: fieldMaterial,
        fieldOpacity: material.fieldOpacity / 100,
      },
    });
    document.documentElement.style.setProperty("--nodedc-canvas-soft", material.nestedHex);
  }, [accent, fieldMaterial, material.fieldOpacity, material.nestedHex, material.panelOpacity, panelMaterial, theme]);

  useEffect(() => {
    applyGlassMaterial(document.documentElement, glassMaterial);
  }, [glassMaterial]);

  useEffect(() => {
    applyFaviconAssets(faviconAssets);
  }, [faviconAssets]);

  useEffect(() => {
    let active = true;
    fetch("/api/session/profile", { cache: "no-store" })
      .then((response) => response.ok ? response.json() as Promise<FoundrySessionProfile> : null)
      .then((profile) => { if (active) setSessionProfile(profile); })
      .catch(() => { if (active) setSessionProfile(null); });
    return () => { active = false; };
  }, []);

  const isFoundryAdmin = sessionProfile?.access?.role === "admin";

  useEffect(() => {
    let active = true;
    fetch("/api/layout", { cache: "no-store" })
      .then(async (response) => {
        if (!response.ok) throw new Error("layout_load_failed");
        return await response.json() as StoredLayout | null;
      })
      .then((stored) => {
        if (!active || !stored) return;
        if (stored.theme === "dark" || stored.theme === "light") setTheme(stored.theme);
        if (stored.accentHex) setAccentHex(stored.accentHex);
        if (stored.materialByTheme?.dark && stored.materialByTheme?.light) setMaterialByTheme(stored.materialByTheme);
        const environment = stored.environment;
        if (environment) {
          if (environment.lightColor) setLightColor(environment.lightColor);
          if (typeof environment.brightness === "number") setBrightness(environment.brightness);
          if (typeof environment.glowDistance === "number") setGlowDistance(environment.glowDistance);
          if (environment.connectionType) setConnectionType(environment.connectionType);
          if (environment.connectionColor) setConnectionColor(environment.connectionColor);
          if (typeof environment.usePortColors === "boolean") setUsePortColors(environment.usePortColors);
          if (environment.fillColor) setFillColor(environment.fillColor);
          if (typeof environment.fillOpacity === "number") setFillOpacity(environment.fillOpacity);
          if (environment.strokeColor) setStrokeColor(environment.strokeColor);
          if (typeof environment.strokeOpacity === "number") setStrokeOpacity(environment.strokeOpacity);
        }
        const media = stored.media;
        if (media) {
          if (media.source === "file" || media.source === "url") setMediaSource(media.source);
          if (typeof media.url === "string") setMediaUrl(media.url);
          if (media.fileName) setMediaFileName(media.fileName);
          if (media.fileSrc) setFileMediaSrc(media.fileSrc);
          if (typeof media.visible === "boolean") setMediaVisible(media.visible);
          if (media.logoSource === "file" || media.logoSource === "url") setLogoSource(media.logoSource);
          if (typeof media.logoUrl === "string") setLogoUrl(media.logoUrl);
          if (media.logoFileName) setLogoFileName(media.logoFileName);
          if (media.logoFileSrc) setLogoFileSrc(media.logoFileSrc);
          if (media.faviconFileName) setFaviconFileName(media.faviconFileName);
          if (media.faviconAssets) setFaviconAssets(media.faviconAssets);
        }
        if (stored.glass) {
          const legacyDefaultOpacity = stored.glass.version !== 4 && stored.glass.tintOpacity === 54;
          setGlassMaterial({
            ...defaultGlassMaterial,
            ...stored.glass,
            version: 4,
            tintOpacity: legacyDefaultOpacity ? defaultGlassMaterial.tintOpacity : stored.glass.tintOpacity,
          });
        }
        const toolbar = stored.toolbar;
        if (toolbar) {
          if (toolbar.placement === "left" || toolbar.placement === "right" || toolbar.placement === "bottom") setToolbarPlacement(toolbar.placement);
          if (toolbar.background) setToolbarBg(toolbar.background);
          if (toolbar.border) setToolbarBorder(toolbar.border);
          if (toolbar.outline) setToolbarOutline(toolbar.outline);
          if (typeof toolbar.minSize === "number") setToolbarMinSize(toolbar.minSize);
          if (typeof toolbar.maxSize === "number") setToolbarMaxSize(toolbar.maxSize);
          if (typeof toolbar.lensCount === "number") setToolbarLensCount(toolbar.lensCount);
          if (typeof toolbar.autoHide === "boolean") setToolbarAutoHide(toolbar.autoHide);
        }
        setLayoutSaveState("saved");
      })
      .catch(() => {
        if (active) setLayoutSaveState("error");
      })
      .finally(() => {
        if (active) setLayoutSaveState((current) => current === "loading" ? "idle" : current);
      });
    return () => { active = false; };
  }, []);

  useEffect(() => {
    let active = true;
    fetch("/api/page-layouts/map", { cache: "no-store" })
      .then(async (response) => {
        if (!response.ok) throw new Error("map_page_layout_load_failed");
        return await response.json() as MapPageLayout | null;
      })
      .then((layout) => {
        if (!active) return;
        setMapTemplateLayout(layout);
        setMapTemplateSaveState(layout ? "saved" : "idle");
      })
      .catch(() => {
        if (active) setMapTemplateSaveState("error");
      });
    return () => { active = false; };
  }, []);

  useEffect(() => {
    let active = true;
    fetch("/api/design-profiles", { cache: "no-store" })
      .then(async (response) => {
        if (!response.ok) throw new Error("design_profiles_load_failed");
        return await response.json() as DesignProfileSummary[];
      })
      .then((profiles) => {
        if (!active) return;
        setDesignProfiles(profiles);
        if (profiles.length > 0 && !profiles.some((profile) => profile.id === activeDesignProfileId)) setActiveDesignProfileId(profiles[0].id);
      })
      .catch(() => {
        if (active) setApplicationError("Не удалось загрузить Design Profiles.");
      });
    return () => { active = false; };
  }, []);

  useEffect(() => {
    let active = true;
    fetch(`/api/design-profiles/${activeDesignProfileId}`, { cache: "no-store" })
      .then(async (response) => {
        if (!response.ok) throw new Error("design_profile_load_failed");
        return await response.json() as DesignProfileDocument;
      })
      .then((profile) => {
        if (!active) return;
        const stored = profile.layout;
        setActiveDesignProfileLayout(stored);
        applyDesignProfileLayout(stored);
        setLayoutSaveState("saved");
      })
      .catch(() => {
        if (active) setLayoutSaveState("error");
      });
    return () => { active = false; };
  }, [activeDesignProfileId, applyDesignProfileLayout]);

  useEffect(() => {
    let active = true;
    fetch("/api/applications", { cache: "no-store" })
      .then(async (response) => {
        if (!response.ok) throw new Error("applications_load_failed");
        return await response.json() as ApplicationSummary[];
      })
      .then((summaries) => {
        if (active) setApplicationSummaries(summaries);
      })
      .catch(() => {
        if (active) setApplicationError("Не удалось загрузить Application Drafts.");
      });
    return () => { active = false; };
  }, []);

  useEffect(() => () => {
    if (mediaObjectUrlRef.current) URL.revokeObjectURL(mediaObjectUrlRef.current);
    if (logoObjectUrlRef.current) URL.revokeObjectURL(logoObjectUrlRef.current);
    generatedFavicons.forEach((item) => URL.revokeObjectURL(item.previewUrl));
  }, [generatedFavicons]);

  const handleStageMediaFile = (file?: File) => {
    if (!file) return;
    const isMp4 = file.type === "video/mp4" || /\.mp4$/i.test(file.name);
    if (!isMp4) {
      setMediaError("Для заставки нужен MP4-файл.");
      return;
    }
    if (mediaObjectUrlRef.current) URL.revokeObjectURL(mediaObjectUrlRef.current);
    const objectUrl = URL.createObjectURL(file);
    mediaObjectUrlRef.current = objectUrl;
    pendingMediaFileRef.current = file;
    setFileMediaSrc(objectUrl);
    setMediaFileName(file.name);
    setMediaSource("file");
    setMediaVisible(true);
    setMediaError("");
  };

  const handleLogoFile = (file?: File) => {
    if (!file) return;
    const isImage = /^(image\/(png|jpeg|gif|webp))$/i.test(file.type) || /\.(png|jpe?g|gif|webp)$/i.test(file.name);
    if (!isImage) {
      setLogoError("Для знака нужен PNG, JPG, GIF или WEBP.");
      return;
    }
    if (logoObjectUrlRef.current) URL.revokeObjectURL(logoObjectUrlRef.current);
    const objectUrl = URL.createObjectURL(file);
    logoObjectUrlRef.current = objectUrl;
    pendingLogoFileRef.current = file;
    setLogoFileSrc(objectUrl);
    setLogoFileName(file.name);
    setLogoSource("file");
    setLogoError("");
  };

  const handleFaviconFile = async (file?: File) => {
    if (!file) return;
    const isImage = /^(image\/(png|jpeg|webp))$/i.test(file.type) || /\.(png|jpe?g|webp)$/i.test(file.name);
    if (!isImage) {
      setFaviconError("Для генератора нужен PNG, JPG или WEBP.");
      return;
    }
    try {
      const next = await generateFavicons(file);
      setGeneratedFavicons((current) => {
        current.forEach((item) => URL.revokeObjectURL(item.previewUrl));
        return next;
      });
      pendingFaviconIcoRef.current = await createIcoBlob(next);
      setFaviconFileName(file.name);
      setFaviconError("");
    } catch {
      setFaviconError("Не удалось прочитать исходное изображение.");
    }
  };

  const inviteShareMember = () => {
    const email = shareEmail.trim().toLowerCase();
    if (!email) return;
    setShareMembers((current) => [
      ...current.filter((member) => member.email !== email),
      { id: email, name: email.split("@")[0], email, role: shareRole, roleLabel: shareRoleOptions.find((item) => item.value === shareRole)?.label },
    ]);
    setShareMessage(`Доступ выдан пользователю ${email}`);
    setShareEmail("");
  };

  const openSection = (next: string) => {
    setStudioContext("visual");
    workspace.openView(next as CatalogSection);
  };

  const openDesignProfile = (id: string) => {
    setActiveDesignProfileId(id);
  };

  const openApplicationDraft = async (id: string) => {
    setStudioContext("applications");
    setApplicationSaveState("loading");
    setApplicationError("");
    workspace.openView(applicationViewId(id));
    try {
      const response = await fetch(`/api/applications/${id}`, { cache: "no-store" });
      if (!response.ok) throw new Error("application_load_failed");
      const manifest = await response.json() as ApplicationManifestV01;
      const profileResponse = await fetch(designProfileDocumentPath(manifest.designProfile), { cache: "no-store" });
      if (!profileResponse.ok) throw new Error("application_design_profile_load_failed");
      const profile = await profileResponse.json() as DesignProfileDocument;
      setApplicationDraft(manifest);
      setApplicationDesignProfileLayout(profile.layout);
      applyDesignProfileLayout(profile.layout);
      setApplicationSaveState("saved");
    } catch {
      setApplicationDraft(null);
      setApplicationDesignProfileLayout(null);
      setApplicationSaveState("error");
      setApplicationError("Не удалось открыть Application Draft.");
    }
  };

  const selectApplicationDesignProfile = async (reference: string) => {
    if (!applicationDraft) return;
    const [profileId, version, statusValue] = reference.split("@");
    const profile = designProfiles.find((item) => item.id === profileId);
    if (!profile) return;
    const status: DesignProfileStatus = statusValue === "published" ? "published" : "draft";
    const release = status === "published" ? profile.versions.find((item) => item.version === version) : undefined;
    const profileTheme = release?.theme ?? profile.theme;
    setApplicationSaveState("loading");
    setApplicationError("");
    try {
      const response = await fetch(designProfileDocumentPath({ id: profile.id, version, status }), { cache: "no-store" });
      if (!response.ok) throw new Error("design_profile_load_failed");
      const document = await response.json() as DesignProfileDocument;
      setApplicationDesignProfileLayout(document.layout);
      applyDesignProfileLayout(document.layout);
      updateApplicationDraft((current) => ({
        ...current,
        designProfile: { id: profile.id, version, status, theme: profileTheme },
      }));
    } catch {
      setApplicationSaveState("error");
      setApplicationError("Не удалось применить выбранный Design Profile.");
    }
  };

  const openPageTemplate = (id: string) => {
    setStudioContext("pages");
    workspace.openView(pageTemplateViewId(id));
  };

  const createApplicationDraft = async (template?: PageTemplateDefinition, metadata?: { name: string; slug: string; description: string }) => {
    setStudioContext("applications");
    setApplicationSaveState("saving");
    setApplicationError("");
    try {
      const response = await fetch("/api/applications", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          templateId: template?.id,
          templateVersion: template?.version,
          name: metadata?.name || (template ? `NODE.DC ${template.title} Demo` : "Новый модуль"),
          slug: metadata?.slug || (template ? `nodedc-${template.id}-demo` : "new-module"),
          description: metadata?.description,
          theme,
        }),
      });
      if (!response.ok) throw new Error("application_create_failed");
      const manifest = await response.json() as ApplicationManifestV01;
      const profileResponse = await fetch(designProfileDocumentPath(manifest.designProfile), { cache: "no-store" });
      if (!profileResponse.ok) throw new Error("application_design_profile_load_failed");
      const profile = await profileResponse.json() as DesignProfileDocument;
      setApplicationDraft(manifest);
      setApplicationDesignProfileLayout(profile.layout);
      applyDesignProfileLayout(profile.layout);
      setApplicationSummaries((current) => [
        {
          id: manifest.id,
          name: manifest.metadata.name,
          slug: manifest.metadata.slug,
          status: manifest.status,
          version: manifest.version,
          theme: manifest.designProfile.theme,
          pageCount: manifest.pages.length,
          updatedAt: manifest.timestamps.updatedAt,
        },
        ...current.filter((item) => item.id !== manifest.id),
      ]);
      workspace.openView(applicationViewId(manifest.id));
      setApplicationSaveState("saved");
      setCreateModuleOpen(false);
      setCreateModuleName("");
      setCreateModuleSlug("");
      setCreateModuleDescription("");
    } catch {
      setApplicationSaveState("error");
      setApplicationError(`Не удалось создать ${template ? `draft из шаблона ${template.title}` : "модуль"}.`);
    }
  };

  const deleteApplicationDraft = async () => {
    if (!applicationDraft) return;
    const response = await fetch(`/api/applications/${applicationDraft.id}`, { method: "DELETE" });
    if (!response.ok) throw new Error("application_delete_failed");
    setApplicationSummaries((current) => current.filter((item) => item.id !== applicationDraft.id));
    setApplicationDraft(null);
    setDeleteModuleOpen(false);
    workspace.closeView();
  };

  const updateApplicationDraft = (update: (current: ApplicationManifestV01) => ApplicationManifestV01) => {
    setApplicationDraft((current) => current ? update(current) : current);
    setApplicationSaveState("idle");
    setApplicationError("");
  };

  const saveApplicationDraft = async () => {
    if (!applicationDraft) return;
    const toastId = pushToast("loading", "Сохраняем Application", applicationDraft.metadata.name, null);
    const activeMapLayout = activeApplicationPageId ? applicationMapPreviewRef.current?.getLayout() : null;
    const activePage = activeApplicationPageId
      ? applicationDraft.pages.find((page) => page.id === activeApplicationPageId)
      : undefined;
    const activeMapFragment = activePage?.template.id === "map"
      ? mapDesignFragmentForLayout(applicationDesignProfileLayout, activePage.template.version)
      : null;
    const activeMapOverrides = activeMapLayout && activeMapFragment
      ? mapDesignOverridesFromResolved(activeMapLayout, activeMapFragment)
      : undefined;
    const draftToSave = activeMapLayout && activeApplicationPageId ? {
      ...applicationDraft,
      pages: applicationDraft.pages.map((page) => page.id === activeApplicationPageId ? {
        ...page,
        layout: { ...page.layout, map: activeMapLayout },
        ...(activeMapFragment ? {
          designOverrides: activeMapOverrides
            ? { ...page.designOverrides, map: activeMapOverrides }
            : undefined,
        } : {}),
      } : page),
    } : applicationDraft;
    setApplicationSaveState("saving");
    setApplicationError("");
    try {
      const response = await fetch(`/api/applications/${applicationDraft.id}`, {
        method: "PUT",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(draftToSave),
      });
      if (!response.ok) throw new Error("application_save_failed");
      const saved = await response.json() as ApplicationManifestV01;
      setApplicationDraft(saved);
      setApplicationSummaries((current) => [
        {
          id: saved.id,
          name: saved.metadata.name,
          slug: saved.metadata.slug,
          status: saved.status,
          version: saved.version,
          theme: saved.designProfile.theme,
          pageCount: saved.pages.length,
          updatedAt: saved.timestamps.updatedAt,
        },
        ...current.filter((item) => item.id !== saved.id),
      ]);
      setApplicationSaveState("saved");
      updateToast(toastId, { tone: "success", title: "Application сохранён", description: saved.metadata.name, durationMs: 4200 });
    } catch {
      setApplicationSaveState("error");
      setApplicationError("Draft не сохранён. Проверьте название и slug.");
      updateToast(toastId, { tone: "error", title: "Application не сохранён", description: "Проверьте manifest и повторите попытку.", durationMs: 6500 });
    }
  };

  const openMapPageDesignSave = (templateVersion: string) => {
    const currentProfile = designProfiles.find((profile) => profile.id === activeDesignProfileId);
    setDesignProfileName(currentProfile?.name ?? "");
    setDesignProfileSaveScope({ kind: "page", templateId: "map", templateVersion });
    setDesignProfileSaveMode("save");
    setDesignProfileSaveOpen(true);
  };

  const changeStudioContext = (next: StudioContext) => {
    setStudioContext(next);
    workspace.openNavigation();
    if (next === "applications" && applicationSummaries.length > 0) {
      void openApplicationDraft(applicationSummaries[0].id);
    } else {
      workspace.closeView();
    }
    setApplicationError("");
  };

  const closeGuideline = workspace.closeNavigation;

  const updateMaterial = (changes: Partial<MaterialDraft>) => {
    setMaterialByTheme((current) => ({
      ...current,
      [theme]: { ...current[theme], ...changes },
    }));
  };

  const saveEnvironmentLayout = async () => {
    setLayoutSaveState("saving");
    try {
      let persistedFileSrc = fileMediaSrc;
      const pendingMedia = pendingMediaFileRef.current;
      if (pendingMedia) {
        const uploadResponse = await fetch("/api/layout/media", {
          method: "PUT",
          headers: { "content-type": "video/mp4", "x-file-name": encodeURIComponent(pendingMedia.name) },
          body: pendingMedia,
        });
        if (!uploadResponse.ok) throw new Error("media_upload_failed");
        const uploaded = await uploadResponse.json() as { url: string; fileName: string };
        persistedFileSrc = uploaded.url;
        setFileMediaSrc(uploaded.url);
        setMediaFileName(uploaded.fileName);
        pendingMediaFileRef.current = null;
      }
      let persistedLogoFileSrc = logoFileSrc;
      const pendingLogo = pendingLogoFileRef.current;
      if (pendingLogo) {
        const uploadResponse = await fetch("/api/layout/media", {
          method: "PUT",
          headers: { "content-type": pendingLogo.type || "application/octet-stream", "x-file-name": encodeURIComponent(pendingLogo.name) },
          body: pendingLogo,
        });
        if (!uploadResponse.ok) throw new Error("logo_upload_failed");
        const uploaded = await uploadResponse.json() as { url: string; fileName: string };
        persistedLogoFileSrc = uploaded.url;
        setLogoFileSrc(uploaded.url);
        setLogoFileName(uploaded.fileName);
        pendingLogoFileRef.current = null;
      }
      let persistedFaviconAssets = faviconAssets;
      if (generatedFavicons.length && pendingFaviconIcoRef.current) {
        const upload = async (blob: Blob, name: string) => {
          const response = await fetch("/api/layout/media", {
            method: "PUT",
            headers: { "content-type": blob.type || "application/octet-stream", "x-file-name": encodeURIComponent(name) },
            body: blob,
          });
          if (!response.ok) throw new Error("favicon_upload_failed");
          return await response.json() as { url: string };
        };
        const bySize = new Map(generatedFavicons.map((item) => [item.size, item]));
        const [ico, apple, icon192, icon512] = await Promise.all([
          upload(pendingFaviconIcoRef.current, "favicon.ico"),
          upload(bySize.get(180)!.blob, "apple-touch-icon.png"),
          upload(bySize.get(192)!.blob, "icon-192.png"),
          upload(bySize.get(512)!.blob, "icon-512.png"),
        ]);
        persistedFaviconAssets = { ico: ico.url, apple: apple.url, icon192: icon192.url, icon512: icon512.url };
        setFaviconAssets(persistedFaviconAssets);
        pendingFaviconIcoRef.current = null;
      }
      const draft: StoredLayout = {
      theme,
      accentHex,
      materialByTheme,
      environment: {
        lightColor,
        brightness,
        glowDistance,
        connectionType,
        connectionColor,
        usePortColors,
        fillColor,
        fillOpacity,
        strokeColor,
        strokeOpacity,
      },
      media: {
        source: mediaSource,
        url: mediaUrl,
        fileName: mediaFileName,
        fileSrc: persistedFileSrc,
        visible: mediaVisible,
        logoSource,
        logoUrl,
        logoFileName,
        logoFileSrc: persistedLogoFileSrc,
        faviconFileName,
        faviconAssets: persistedFaviconAssets,
      },
      glass: glassMaterial,
      toolbar: {
        placement: toolbarPlacement,
        background: toolbarBg,
        border: toolbarBorder,
        outline: toolbarOutline,
        minSize: toolbarMinSize,
        maxSize: toolbarMaxSize,
        lensCount: toolbarLensCount,
        autoHide: toolbarAutoHide,
      },
    };
      const response = await fetch("/api/layout", {
        method: "PUT",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(draft),
      });
      if (!response.ok) throw new Error("layout_save_failed");
      setLayoutSaveState("saved");
      return true;
    } catch {
      setLayoutSaveState("error");
      return false;
    }
  };

  const saveDesignProfile = async (mode = designProfileSaveMode) => {
    const selected = designProfiles.find((profile) => profile.id === activeDesignProfileId);
    if (!selected) return;
    const toastId = pushToast(
      "loading",
      designProfileSaveScope.kind === "page" ? "Сохраняем дизайн страницы" : "Сохраняем Design Profile",
      designProfileSaveScope.kind === "page" ? `Map@${designProfileSaveScope.templateVersion} · ${selected.name}` : selected.name,
      null,
    );
    if (designProfileSaveScope.kind === "page") setMapTemplateSaveState("saving");
    else setLayoutSaveState("saving");
    try {
      const currentResponse = await fetch(`/api/design-profiles/${activeDesignProfileId}`, { cache: "no-store" });
      if (!currentResponse.ok) throw new Error("design_profile_load_failed");
      const currentProfile = await currentResponse.json() as DesignProfileDocument;
      let layout: StoredLayout;
      if (designProfileSaveScope.kind === "page") {
        const pageLayout = mapTemplatePreviewRef.current?.getLayout();
        if (!pageLayout) throw new Error("map_page_layout_unavailable");
        const pageKey = mapDesignProfileKey(designProfileSaveScope.templateVersion);
        layout = {
          ...currentProfile.layout,
          pageTypes: {
            ...currentProfile.layout.pageTypes,
            [pageKey]: mapDesignFragmentFromLayout(pageLayout, designProfileSaveScope.templateVersion),
          },
        };
      } else {
        const layoutSaved = await saveEnvironmentLayout();
        if (!layoutSaved) throw new Error("global_layout_save_failed");
        const layoutResponse = await fetch("/api/layout", { cache: "no-store" });
        if (!layoutResponse.ok) throw new Error("global_layout_load_failed");
        const globalLayout = await layoutResponse.json() as StoredLayout;
        layout = { ...globalLayout, pageTypes: currentProfile.layout.pageTypes ?? {} };
      }
      const creating = mode === "save-as";
      const response = await fetch(creating ? "/api/design-profiles" : `/api/design-profiles/${activeDesignProfileId}`, {
        method: creating ? "POST" : "PUT",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ name: creating ? designProfileName : selected.name, layout }),
      });
      if (!response.ok) throw new Error("design_profile_save_failed");
      const saved = await response.json() as DesignProfileDocument;
      const previous = designProfiles.find((profile) => profile.id === saved.id);
      const summary: DesignProfileSummary = {
        id: saved.id,
        name: saved.name,
        version: saved.version,
        status: saved.status,
        theme: saved.layout.theme === "light" ? "light" : "dark",
        updatedAt: saved.timestamps.updatedAt,
        versions: previous?.versions ?? [],
        latestPublishedVersion: previous?.latestPublishedVersion ?? null,
      };
      setDesignProfiles((current) => [...current.filter((profile) => profile.id !== summary.id), summary].sort((left, right) => left.name.localeCompare(right.name)));
      setActiveDesignProfileId(summary.id);
      setActiveDesignProfileLayout(saved.layout);
      setDesignProfileSaveOpen(false);
      setDesignProfileName("");
      setLayoutSaveState("saved");
      if (designProfileSaveScope.kind === "page") setMapTemplateSaveState("saved");
      updateToast(toastId, {
        tone: "success",
        title: mode === "save-as" ? "Design Profile создан" : "Design Profile обновлён",
        description: `${saved.name} · v${saved.version}`,
        durationMs: 4200,
      });
    } catch {
      if (designProfileSaveScope.kind === "page") setMapTemplateSaveState("error");
      else setLayoutSaveState("error");
      updateToast(toastId, { tone: "error", title: "Design Profile не сохранён", description: "Текущий профиль не изменён.", durationMs: 6500 });
    }
  };

  const publishDesignProfile = async () => {
    const current = designProfiles.find((profile) => profile.id === activeDesignProfileId);
    if (!current || current.versions.some((release) => release.version === current.version)) return;
    const toastId = pushToast("loading", "Публикуем Design Profile", `${current.name} · v${current.version}`, null);
    setDesignProfilePublishState("publishing");
    const response = await fetch(`/api/design-profiles/${current.id}/publish`, { method: "POST" });
    if (!response.ok) {
      setDesignProfilePublishState("error");
      updateToast(toastId, { tone: "error", title: "Публикация не выполнена", description: "Draft остался доступен без изменений.", durationMs: 6500 });
      return;
    }
    const release = await response.json() as { version: string; status: DesignProfileStatus; layout: StoredLayout; timestamps: { publishedAt?: string } };
    setDesignProfiles((profiles) => profiles.map((profile) => profile.id !== current.id ? profile : {
      ...profile,
      versions: [{ version: release.version, status: release.status, theme: release.layout.theme === "light" ? "light" : "dark", publishedAt: release.timestamps.publishedAt }, ...profile.versions.filter((item) => item.version !== release.version)],
      latestPublishedVersion: release.version,
    }));
    setDesignProfilePublishState("published");
    setDesignProfileSaveOpen(false);
    updateToast(toastId, { tone: "success", title: "Design Profile опубликован", description: `${current.name} · v${release.version}`, durationMs: 4200 });
  };

  const addPageTemplateToApplication = (template: PageTemplateDefinition) => {
    updateApplicationDraft((current) => {
      const order = current.pages.length;
      const duplicateIndex = current.pages.filter((page) => page.template.id === template.id).length + 1;
      const instanceSuffix = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
      const instanceId = `${template.page.id}-${instanceSuffix}`;
      const instanceLabel = duplicateIndex === 1 ? template.page.navigationLabel : `${template.page.navigationLabel} ${duplicateIndex}`;
      return {
        ...current,
        pages: [...current.pages, {
          id: instanceId,
          title: duplicateIndex === 1 ? template.page.title : `${template.page.title} ${duplicateIndex}`,
          path: order === 0 ? "/" : `/${template.page.id}-${duplicateIndex}`,
          template: { id: template.id, version: template.version },
          navigation: { visible: true, label: instanceLabel, order },
          features: createTemplateFeatures(template),
        }],
      };
    });
  };

  const removePageFromApplication = (pageId: string) => {
    updateApplicationDraft((current) => ({
      ...current,
      pages: current.pages.filter((page) => page.id !== pageId).map((page, order) => ({ ...page, navigation: { ...page.navigation, order } })),
    }));
  };

  const confirmPageRemoval = () => {
    if (!pageRemovalId) return;
    removePageFromApplication(pageRemovalId);
    if (activeApplicationPageId === pageRemovalId && applicationDraft) {
      workspace.openView(applicationViewId(applicationDraft.id));
    }
    setPageRemovalId(null);
  };

  const reorderApplicationPages = (activeId: string, overId: string) => {
    updateApplicationDraft((current) => {
      const pages = [...current.pages];
      const index = pages.findIndex((page) => page.id === activeId);
      const target = pages.findIndex((page) => page.id === overId);
      if (index < 0 || target < 0 || index === target) return current;
      const [moved] = pages.splice(index, 1);
      pages.splice(target, 0, moved);
      return { ...current, pages: pages.map((page, order) => ({ ...page, navigation: { ...page.navigation, order } })) };
    });
  };

  const setApplicationPageOrder = (ids: string[]) => {
    updateApplicationDraft((current) => {
      const pagesById = new Map(current.pages.map((page) => [page.id, page]));
      const pages = ids.map((id) => pagesById.get(id)).filter((page): page is ApplicationManifestV01["pages"][number] => Boolean(page));
      if (pages.length !== current.pages.length) return current;
      return { ...current, pages: pages.map((page, order) => ({ ...page, navigation: { ...page.navigation, order } })) };
    });
  };

  const environmentSections = [
    {
      id: "application-material",
      label: "Материал приложения",
      description: "live draft",
      tone: "accent" as const,
      content: (
        <>
          <ControlRow label="Акцент">
            <ColorField label="Акцент приложения" value={accentHex} onChange={setAccentHex} />
          </ControlRow>
          <ControlRow label="Плашки">
            <ColorField label="Цвет плашек" value={material.panelHex} onChange={(panelHex) => updateMaterial({ panelHex })} />
          </ControlRow>
          <RangeControl label="Прозрачность плашки" value={material.panelOpacity} min={18} max={96} formatValue={(value) => `${value}%`} onChange={(panelOpacity) => updateMaterial({ panelOpacity })} />
          <ControlRow label="Поля ввода">
            <ColorField label="Цвет полей ввода" value={material.fieldHex} onChange={(fieldHex) => updateMaterial({ fieldHex })} />
          </ControlRow>
          <RangeControl label="Прозрачность полей" value={material.fieldOpacity} min={18} max={100} formatValue={(value) => `${value}%`} onChange={(fieldOpacity) => updateMaterial({ fieldOpacity })} />
          <ControlRow label="Вложенная область">
            <ColorField label="Цвет вложенной области" value={material.nestedHex} onChange={(nestedHex) => updateMaterial({ nestedHex })} />
          </ControlRow>
        </>
      ),
    },
    {
      id: "toolbar-settings",
      label: "Рабочее поле — Toolbar",
      tone: "accent" as const,
      content: (
        <>
          <InspectorSelectField
            label="Позиция Toolbar"
            value={toolbarPlacement}
            options={[
              { value: "left", label: "Слева" },
              { value: "right", label: "Справа" },
              { value: "bottom", label: "Снизу" },
            ]}
            onChange={(value) => setToolbarPlacement(value as ToolbarPlacement)}
          />
          <ControlRow label="Фон"><ColorField value={toolbarBg} onChange={setToolbarBg} /></ControlRow>
          <ControlRow label="Border"><ColorField value={toolbarBorder} onChange={setToolbarBorder} /></ControlRow>
          <ControlRow label="Outline"><ColorField value={toolbarOutline} onChange={setToolbarOutline} /></ControlRow>
          <RangeControl label="Минимальный размер" value={toolbarMinSize} min={18} max={42} formatValue={(value) => `${value}px`} onChange={(value) => {
            const next = Math.round(value);
            setToolbarMinSize(next);
            setToolbarMaxSize((current) => Math.max(current, next));
          }} />
          <RangeControl label="Максимальный размер" value={toolbarMaxSize} min={28} max={88} formatValue={(value) => `${value}px`} onChange={(value) => setToolbarMaxSize(Math.max(toolbarMinSize, Math.round(value)))} />
          <RangeControl label="Линза" value={toolbarLensCount} min={1} max={13} step={2} formatValue={(value) => `${value} иконок`} onChange={(value) => setToolbarLensCount(Math.round(value) % 2 === 0 ? Math.round(value) + 1 : Math.round(value))} />
          <Checker checked={toolbarAutoHide} label="Автоскрытие" onChange={setToolbarAutoHide} />
        </>
      ),
    },
    {
      id: "illumination",
      label: "Подсветка",
      tone: "accent" as const,
      content: (
        <>
          <ControlRow label="Цвет"><ColorField value={lightColor} onChange={setLightColor} /></ControlRow>
          <RangeControl label="Яркость" value={brightness} min={0} max={100} formatValue={(value) => `${value}%`} onChange={setBrightness} />
          <RangeControl label="Дистанция свечения" value={glowDistance} min={0} max={200} formatValue={(value) => `${value}%`} onChange={setGlowDistance} />
        </>
      ),
    },
    {
      id: "connections",
      label: "Связи",
      tone: "accent" as const,
      content: (
        <>
          <InspectorSelectField
            label="Связи по умолчанию"
            value={connectionType}
            options={[
              { value: "spline", label: "Сплайн" },
              { value: "straight", label: "Прямая" },
              { value: "step", label: "Ступенчатая" },
            ]}
            onChange={setConnectionType}
          />
          <Checker checked={usePortColors} label="Использовать цвет портов" onChange={setUsePortColors} />
          <ControlRow label="Цвет связей по умолчанию"><ColorField value={connectionColor} onChange={setConnectionColor} /></ControlRow>
          <ControlRow label="Применить ко всем"><Button variant="primary" shape="pill" width="full">Применить</Button></ControlRow>
        </>
      ),
    },
    {
      id: "ports",
      label: "Порты",
      tone: "accent" as const,
      content: (
        <>
          <ControlRow label="Заливка"><ColorField value={fillColor} onChange={setFillColor} /></ControlRow>
          <RangeControl label="Прозрачность заливки" value={fillOpacity} min={0} max={100} formatValue={(value) => `${value}%`} onChange={setFillOpacity} />
          <ControlRow label="Обводка"><ColorField value={strokeColor} onChange={setStrokeColor} /></ControlRow>
          <RangeControl label="Прозрачность обводки" value={strokeOpacity} min={0} max={100} formatValue={(value) => `${value}%`} onChange={setStrokeOpacity} />
        </>
      ),
    },
  ];

  const renderSectionContent = () => {
    switch (activeSection) {
      case "controls":
        return (
          <div className="catalog-grid">
            <Preview title="Selection" note="integrated / split">
              <div className="catalog-form">
                <FieldFrame label="Hub / integrated select">
                  <Select label="Статус" value={selectedStatus} options={[...selectOptions]} searchable onChange={setSelectedStatus} />
                </FieldFrame>
                <FieldFrame label="Engine / split select">
                  <Select
                    variant="split"
                    label="Тип связи"
                    value={connectionType}
                    options={[
                      { value: "spline", label: "Сплайн" },
                      { value: "straight", label: "Прямая" },
                      { value: "step", label: "Ступенчатая" },
                    ]}
                    onChange={setConnectionType}
                  />
                </FieldFrame>
                <Dropdown
                  trigger={({ open, toggle, setTriggerRef, surfaceId }) => (
                    <Button ref={setTriggerRef} aria-expanded={open} aria-controls={surfaceId} icon={<Icon name="chevron-down" />} onClick={toggle}>Действия</Button>
                  )}
                >
                  {({ close }) => (
                    <>
                      <button className="nodedc-dropdown-option" data-nodedc-option onClick={close}><Icon name="edit" /><span className="nodedc-dropdown-option__label">Переименовать</span></button>
                      <button className="nodedc-dropdown-option" data-nodedc-option onClick={close}><Icon name="copy" /><span className="nodedc-dropdown-option__label">Дублировать</span></button>
                      <button className="nodedc-dropdown-option" data-nodedc-option onClick={close}><Icon name="folder" /><span className="nodedc-dropdown-option__label">Архивировать</span></button>
                    </>
                  )}
                </Dropdown>
              </div>
            </Preview>
            <Preview title="Поля" note="label / hint / control">
              <div className="catalog-form">
                <TextField label="Название проекта" hint="обязательно" value={projectName} onChange={(event) => setProjectName(event.target.value)} />
                <TextAreaField label="Описание" value={notes} onChange={(event) => setNotes(event.target.value)} />
              </div>
            </Preview>
            <Preview title="Действия" note="общая геометрия" className="catalog-preview--compact">
              <div className="catalog-inline catalog-inline--wrap">
                <Button variant="primary" shape="pill" icon={<Icon name="save" />}>Сохранить</Button>
                <Button icon={<Icon name="refresh" />}>Обновить</Button>
                <Button variant="ghost" icon={<Icon name="external" />}>Открыть</Button>
                <Button variant="danger" icon={<Icon name="trash" />}>Удалить</Button>
                <IconButton label="Добавить"><Icon name="plus" /></IconButton>
              </div>
            </Preview>
            <Preview title="Оконные действия" note="круг `46 px`" className="catalog-preview--compact">
              <div className="catalog-window-actions-demo">
                <Button shape="pill" icon={<Icon name="refresh" />}>Обновить источник</Button>
                <IconButton label="Добавить"><Icon name="plus" /></IconButton>
                <IconButton label="Развернуть"><Icon name="expand" /></IconButton>
                <IconButton label="Закрыть"><Icon name="close" /></IconButton>
              </div>
            </Preview>
            <Preview title="Workspace window" note="inline / bounded / controlled" className="catalog-preview--wide catalog-workspace-window-preview">
              <div ref={workspaceWindowDemoRef} className="catalog-workspace-window-demo">
                {workspaceWindowDemoOpen ? (
                  <WorkspaceWindow
                    boundsRef={workspaceWindowDemoRef}
                    rect={workspaceWindowDemoRect}
                    onRectChange={setWorkspaceWindowDemoRect}
                    maximized={workspaceWindowDemoMaximized}
                    onMaximizedChange={setWorkspaceWindowDemoMaximized}
                    onActivate={() => undefined}
                    onClose={() => {
                      setWorkspaceWindowDemoOpen(false);
                      setWorkspaceWindowDemoMaximized(false);
                    }}
                    title="Визуальный канал"
                    subtitle="WORKSPACE / AUXILIARY VIEW"
                    status="LIVE"
                    footer={<span className="catalog-workspace-window-demo__hint">Стрелки — позиция · Shift + стрелки — 10 px</span>}
                    active
                    zIndex={2}
                  >
                    <div className="catalog-workspace-window-demo__media">
                      <Icon name="video" size={24} />
                      <strong>Рабочая поверхность</strong>
                      <span>Перемещается, изменяет размер и разворачивается только внутри сцены.</span>
                    </div>
                  </WorkspaceWindow>
                ) : (
                  <Button
                    className="catalog-workspace-window-demo__reopen"
                    shape="pill"
                    icon={<Icon name="plus" />}
                    onClick={() => setWorkspaceWindowDemoOpen(true)}
                  >Открыть окно</Button>
                )}
              </div>
              <p className="catalog-preview__explanation">Rectangle, maximize, close и stacking остаются состоянием приложения; дизайн-система владеет одинаковой bounded-геометрией и доступным управлением.</p>
            </Preview>
            <Preview title="Inspector" note="modeless / draggable" className="catalog-preview--wide catalog-preview--compact catalog-inspector-launcher">
              <p className="catalog-preview__explanation">Настройки приложения и Toolbar открываются в отдельном перемещаемом Inspector и сохраняются одним серверным layout.</p>
              <Button variant="primary" shape="pill" icon={<Icon name="panel" />} onClick={() => setInspectorOpen(true)}>Открыть Inspector</Button>
            </Preview>
          </div>
        );
      case "media":
        return (
          <div className="catalog-grid catalog-grid--single">
            <SettingsCard
              eyebrow="CONTENT"
              title="Видео-окно"
              description="MP4 или URL становятся частью единого server-side layout вместе с темой, материалом и Toolbar."
              actions={<Switch checked={mediaVisible} label="Показать" onChange={setMediaVisible} />}
            >
              <MediaSourceField
                label="Видео / картинка"
                source={mediaSource}
                url={mediaUrl}
                fileName={mediaFileName}
                previewSrc={stageMediaSrc}
                previewKind="video"
                accept="video/mp4,.mp4"
                path="stage.videoSrc → runtime media source"
                hint="Выбранный MP4 сразу показывается в stage и загружается на сервер общей кнопкой Save в шапке."
                error={mediaError}
                onSourceChange={setMediaSource}
                onUrlChange={(nextUrl) => {
                  setMediaUrl(nextUrl);
                  setMediaError(nextUrl && !/\.(mp4|webm|mov|m4v)(\?.*)?$/i.test(nextUrl) ? "Ссылка должна вести на видеофайл." : "");
                }}
                onFileChange={handleStageMediaFile}
              />
            </SettingsCard>
            <SettingsCard
              eyebrow="BRAND"
              title="Знак приложения"
              description="CMS media-контракт для центрального workspace mark в верхней шапке."
            >
              <MediaSourceField
                label="Логотип / изображение"
                source={logoSource}
                url={logoUrl}
                fileName={logoFileName}
                previewSrc={headerMarkSrc}
                previewKind="image"
                accept="image/png,image/jpeg,image/gif,image/webp,.png,.jpg,.jpeg,.gif,.webp"
                path="header.workspaceMark → runtime media source"
                hint="Изображение сразу меняет центральный знак в шапке и загружается на сервер общей кнопкой Save."
                error={logoError}
                onSourceChange={setLogoSource}
                onUrlChange={(nextUrl) => {
                  setLogoUrl(nextUrl);
                  setLogoError(nextUrl && !/\.(png|jpe?g|gif|webp)(\?.*)?$/i.test(nextUrl) ? "Ссылка должна вести на изображение." : "");
                }}
                onFileChange={handleLogoFile}
              />
            </SettingsCard>
            <SettingsCard
              eyebrow="BROWSER BRAND"
              title="Favicon generator"
              description="Один квадратный исходник превращается в ICO, Apple Touch и PWA-набор; общая Save сохраняет набор на сервере и применяет его к приложению."
            >
              <div className="catalog-favicon-generator">
                <label className="nodedc-media-file__button catalog-favicon-generator__upload">
                  Выбрать и сгенерировать
                  <input type="file" accept="image/png,image/jpeg,image/webp,.png,.jpg,.jpeg,.webp" onChange={(event) => { void handleFaviconFile(event.target.files?.[0]); }} />
                </label>
                <span className="catalog-favicon-generator__name">{faviconFileName}</span>
                <div className="catalog-favicon-grid">
                  {[16, 32, 64, 180, 192, 512].map((size) => {
                    const generated = generatedFavicons.find((item) => item.size === size);
                    const fallback = size === 180 ? faviconAssets.apple : size === 192 ? faviconAssets.icon192 : size === 512 ? faviconAssets.icon512 : faviconAssets.ico;
                    return <figure key={size}><span><img src={generated?.previewUrl ?? fallback} alt="" /></span><figcaption>{size} × {size}</figcaption></figure>;
                  })}
                </div>
                {faviconError ? <span className="nodedc-media-field__error" role="alert">{faviconError}</span> : null}
              </div>
            </SettingsCard>
          </div>
        );
      case "glass":
        return (
          <div className="catalog-glass-lab">
            <section className="catalog-glass-stage">
              <video key={stageMediaSrc} autoPlay muted loop playsInline aria-hidden="true"><source src={stageMediaSrc} type="video/mp4" /></video>
              <div className="catalog-glass-stage__shade" />
              <GlassMaterialSurface className="catalog-glass-stage__sample">
                <span>CANONICAL GLASS</span>
                <h2>Модальное окно и Inspector</h2>
                <p>Фон остаётся различимым, но текст и контролы сохраняют контраст. Этот материал не применяется к обычным панелям приложения.</p>
                <div className="catalog-inline"><TextField label="Пример поля" defaultValue="NODE.DC" /><Button variant="primary" shape="pill">Применить</Button></div>
              </GlassMaterialSurface>
            </section>
            <SettingsCard eyebrow="ENGINE / GLASS V4" title="Параметры материала" description="Общий token-контракт ui-core; изменения сразу видны на preview, модалках и Inspector.">
              <div className="catalog-glass-controls">
                <ControlRow label="Цвет тонировки"><ColorField value={glassMaterial.tintHex} onChange={(tintHex) => setGlassMaterial((current) => ({ ...current, tintHex }))} /></ControlRow>
                <RangeControl label="Плотность тонировки" value={glassMaterial.tintOpacity} min={0} max={100} formatValue={(value) => `${value}%`} onChange={(tintOpacity) => setGlassMaterial((current) => ({ ...current, version: 4, tintOpacity }))} />
                <RangeControl label="Blur" value={glassMaterial.blur} min={0} max={120} formatValue={(value) => `${value}px`} onChange={(blur) => setGlassMaterial((current) => ({ ...current, blur }))} />
                <RangeControl label="Saturation" value={glassMaterial.saturation} min={70} max={220} formatValue={(value) => `${value}%`} onChange={(saturation) => setGlassMaterial((current) => ({ ...current, saturation }))} />
                <RangeControl label="Brightness" value={glassMaterial.brightness} min={70} max={160} formatValue={(value) => `${value}%`} onChange={(brightness) => setGlassMaterial((current) => ({ ...current, brightness }))} />
                <RangeControl label="Контур" value={glassMaterial.outlineOpacity} min={0} max={40} formatValue={(value) => `${value}%`} onChange={(outlineOpacity) => setGlassMaterial((current) => ({ ...current, outlineOpacity }))} />
                <RangeControl label="Тень" value={glassMaterial.shadowOpacity} min={0} max={100} formatValue={(value) => `${value}%`} onChange={(shadowOpacity) => setGlassMaterial((current) => ({ ...current, shadowOpacity }))} />
              </div>
            </SettingsCard>
          </div>
        );
      case "status":
        return (
          <div className="catalog-status-library">
            <SettingsCard
              eyebrow="TASKER / PROPEL CANON"
              title="Статусные уведомления"
              description="Один стеклянный паттерн для сохранения, обновления, предупреждения и ошибки. Runtime-стек появляется снизу справа и не блокирует интерфейс."
            >
              <div className="catalog-toast-library-grid">
                <ToastCard item={{ id: "preview-success", tone: "success", title: "Проект сохранён", description: "Application manifest обновлён." }} />
                <ToastCard item={{ id: "preview-info", tone: "info", title: "Профиль применён", description: "Map-фрагмент будет использован страницами этого типа." }} />
                <ToastCard item={{ id: "preview-warning", tone: "warning", title: "Есть несохранённые изменения", description: "Текущий draft отличается от release." }} />
                <ToastCard item={{ id: "preview-error", tone: "error", title: "Сохранение не выполнено", description: "Предыдущее состояние не изменено." }} />
                <ToastCard item={{ id: "preview-loading", tone: "loading", title: "Сохраняем Design Profile", description: "Пожалуйста, не закрывайте окно." }} />
              </div>
            </SettingsCard>
            <SettingsCard eyebrow="LIVE STATES" title="Проверка стека" description="Кнопки используют тот же bottom-right viewport, что и реальные save/update flows.">
              <div className="catalog-inline catalog-inline--wrap">
                <Button onClick={() => pushToast("success", "Изменения сохранены", "Статус автоматически исчезнет.")}>Success</Button>
                <Button onClick={() => pushToast("info", "Профиль применён", "Application использует выбранную версию.")}>Info</Button>
                <Button onClick={() => pushToast("warning", "Нужна проверка", "Перед публикацией проверьте preview.")}>Warning</Button>
                <Button variant="danger" onClick={() => pushToast("error", "Операция не выполнена", "Состояние не изменено.", 6500)}>Error</Button>
                <Button icon={<Icon name="refresh" />} onClick={() => pushToast("loading", "Выполняется операция", "Закройте вручную после проверки.", null)}>Loading</Button>
              </div>
            </SettingsCard>
          </div>
        );
      case "modals":
        return (
          <div className="catalog-modal-groups">
            <section className="catalog-modal-group">
              <header><strong>Общие</strong><span>Launcher / Engine</span></header>
              <div className="catalog-modal-grid">
                <Preview title="Создание" note="адаптивная форма" className="catalog-modal-preview">
                  <p className="catalog-preview__explanation">Одна колонка на узком viewport; footer остаётся видимым.</p>
                  <Button variant="primary" shape="pill" icon={<Icon name="plus" />} onClick={() => setCreateModalOpen(true)}>Создать проект</Button>
                </Preview>
                <Preview title="Подтверждение" note="async-safe" className="catalog-modal-preview">
                  <p className="catalog-preview__explanation">Destructive-цвет появляется только у подтверждения.</p>
                  <Button variant="danger" shape="pill" icon={<Icon name="trash" />} onClick={() => setConfirmOpen(true)}>Проверить удаление</Button>
                </Preview>
                <Preview title="Workflow sharing" note="ENGINE / access" className="catalog-modal-preview">
                  <p className="catalog-preview__explanation">Участники, роли, приглашение и immutable-владелец.</p>
                  <Button variant="primary" shape="pill" icon={<Icon name="users" />} onClick={() => setShareAccessOpen(true)}>Поделиться графом</Button>
                </Preview>
                <Preview title="Сохранить / Сохранить как" note="DESIGN PROFILE" className="catalog-modal-preview">
                  <p className="catalog-preview__explanation">Обновляет текущий preset или создаёт новый именованный профиль.</p>
                  <Button shape="pill" icon={<Icon name="save" />} onClick={() => { setDesignProfileSaveScope({ kind: "global" }); setDesignProfileSaveMode("save"); setDesignProfileSaveOpen(true); }}>Открыть сохранение</Button>
                </Preview>
              </div>
            </section>
            <section className="catalog-modal-group">
              <header><strong>Работа с моделями</strong><span>BIM Viewer</span></header>
              <div className="catalog-modal-grid">
                <Preview title="Ссылка на модель" note="share link" className="catalog-modal-preview">
                  <p className="catalog-preview__explanation">Read-only ссылка и copy-state.</p>
                  <Button shape="pill" icon={<Icon name="copy" />} onClick={() => setShareLinkOpen(true)}>Открыть ссылку</Button>
                </Preview>
                <Preview title="Контекстные действия" note="measurement / object" className="catalog-modal-preview">
                  <p className="catalog-preview__explanation">Действия над выбранным размером или объектом.</p>
                  <Button shape="pill" icon={<Icon name="sliders" />} onClick={() => setContextActionOpen(true)}>Действия размера</Button>
                </Preview>
                <Preview title="История версий" note="large data" className="catalog-modal-preview">
                  <p className="catalog-preview__explanation">Прокручиваемая история BIM-версий.</p>
                  <Button shape="pill" icon={<Icon name="list" />} onClick={() => setHistoryOpen(true)}>Открыть историю</Button>
                </Preview>
                <Preview title="Комментарий" note="expandable detail" className="catalog-modal-preview">
                  <p className="catalog-preview__explanation">Контент, вложения и detail-состояние.</p>
                  <Button shape="pill" icon={<Icon name="clipboard" />} onClick={() => setDetailOpen(true)}>Открыть комментарий</Button>
                </Preview>
              </div>
            </section>
          </div>
        );
      case "icons":
        return (
          <div className="catalog-icon-catalog">
            <Preview title="Размеры и поверхности" note="glyph 16 px" className="catalog-preview--wide">
              <div className="catalog-window-actions-demo">
                <IconButton label="Добавить"><Icon name="plus" /></IconButton>
                <IconButton label="Обновить"><Icon name="refresh" /></IconButton>
                <IconButton label="Развернуть"><Icon name="expand" /></IconButton>
                <IconButton label="Закрыть"><Icon name="close" /></IconButton>
              </div>
              <p className="catalog-preview__explanation">Все glyph-иконки используют один размер 16 px и один stroke 1.6; surface меняет только фон и hit target.</p>
            </Preview>
            {iconGroups.map((group) => (
              <section className="catalog-icon-group" key={group.title}>
                <header><strong>{group.title}</strong><span>{group.note}</span></header>
                <div className="catalog-icon-grid">
                  {group.icons.map((name) => (
                    <div className="catalog-icon-tile" key={name}>
                      <span className="catalog-icon-tile__surface"><Icon name={name} /></span>
                      <span><strong>{iconLabels[name]}</strong><code>{name}</code></span>
                    </div>
                  ))}
                </div>
              </section>
            ))}
          </div>
        );
      default:
        return null;
    }
  };

  const renderApplicationDraft = () => {
    if (!applicationDraft || applicationDraft.id !== activeApplicationId) return null;
    const selectedProfile = designProfiles.find((profile) => profile.id === applicationDraft.designProfile.id) ?? designProfiles[0];
    const designProfileOptions = designProfiles.flatMap((profile) => [
      ...profile.versions.map((release) => ({
        value: `${profile.id}@${release.version}@published`,
        label: profile.name,
        description: `${release.version} · Published · ${release.theme}`,
      })),
      {
        value: `${profile.id}@${profile.version}@draft`,
        label: `${profile.name} — Draft`,
        description: `${profile.version} · Draft · ${profile.theme}`,
      },
    ]);
    const selectedProfileValue = `${applicationDraft.designProfile.id}@${applicationDraft.designProfile.version}@${applicationDraft.designProfile.status}`;
    const availableTemplates = pageTemplates;

    if (applicationMode === "preview") {
      return (
        <div className="catalog-module-preview">
          <div className="catalog-module-preview__navigation">
            {applicationDraft.pages.length ? applicationDraft.pages.map((page) => <span key={page.id}>{page.navigation.label}</span>) : <span>В приложении пока нет страниц</span>}
          </div>
          <div className="catalog-module-preview__stage">
            <Icon name="apps" />
            <strong>{applicationDraft.metadata.name}</strong>
            <span>{applicationDraft.pages.length ? "Выберите страницу в левой панели для полного preview." : "Вернитесь в режим настройки и добавьте готовый Page Template."}</span>
          </div>
        </div>
      );
    }

    return (
      <div className="catalog-application-draft">
        <SettingsCard
          eyebrow="APPLICATION MANIFEST v0.1"
          title="Основные настройки модуля"
          description="Root проекта: metadata, Design Profile и состав готовых страниц."
        >
          <div className="catalog-form catalog-application-draft__fields">
            <TextField
              label="Название приложения"
              hint="обязательно"
              value={applicationDraft.metadata.name}
              onChange={(event) => updateApplicationDraft((current) => ({
                ...current,
                metadata: { ...current.metadata, name: event.target.value },
              }))}
            />
            <TextField
              label="Slug"
              hint="латиница, цифры и дефис"
              value={applicationDraft.metadata.slug}
              onChange={(event) => updateApplicationDraft((current) => ({
                ...current,
                metadata: { ...current.metadata, slug: event.target.value.toLowerCase().replace(/[^a-z0-9-]+/g, "-") },
              }))}
            />
            <TextAreaField
              label="Описание"
              value={applicationDraft.metadata.description}
              onChange={(event) => updateApplicationDraft((current) => ({
                ...current,
                metadata: { ...current.metadata, description: event.target.value },
              }))}
            />
          </div>
        </SettingsCard>

        <SettingsCard
          eyebrow="DESIGN PROFILE"
          title="Визуальный пресет"
          description="Модуль только ссылается на сохранённый профиль Visual Library. Тема, favicon и материалы наследуются из него."
        >
          <Select
            label="Design Profile"
            value={designProfileOptions.some((option) => option.value === selectedProfileValue) ? selectedProfileValue : `${selectedProfile?.id ?? "default"}@${selectedProfile?.version ?? "0.6.0"}@draft`}
            options={designProfileOptions}
            onChange={(profileReference) => { void selectApplicationDesignProfile(profileReference); }}
          />
        </SettingsCard>

        <SettingsCard
          eyebrow="APPLICATION COMPOSITION"
          title="Состав страниц"
          description="Перетащите готовую страницу справа в конфигурацию приложения. Свободного canvas и произвольных компонентов нет."
        >
          <DragDropRoot onDragEnd={({ activeData, activeId, activeRect, overData, overId, overRect }) => {
            if (!overId) return;
            if (activeData?.type === "page-template" && (overId === "application-pages" || overData?.type === "application-page")) {
              const horizontalOverlap = activeRect && overRect
                ? Math.max(0, Math.min(activeRect.right, overRect.right) - Math.max(activeRect.left, overRect.left))
                : 0;
              const overlapRatio = activeRect?.width ? horizontalOverlap / activeRect.width : 0;
              if (overlapRatio < 0.2) return;
              const template = getPageTemplate(String(activeData.templateId));
              if (template) addPageTemplateToApplication(template);
              return;
            }
            if (activeData?.type === "application-page" && overData?.type === "application-page") {
              reorderApplicationPages(String(activeData.pageId ?? activeId), String(overData.pageId ?? overId));
            }
          }}>
            <div className="catalog-page-composer">
              <section className="catalog-page-composer__column">
                <header><span>01</span><div><strong>Доступные страницы</strong><small>Page Library · шаблон можно использовать многократно</small></div></header>
                <div className="catalog-page-composer__list">
                  {availableTemplates.map((template) => (
                    <DraggableItem key={template.id} id={`template:${template.id}`} data={{ type: "page-template", templateId: template.id }} className="catalog-page-composer__draggable">
                      {({ handle }) => (
                        <div className="catalog-page-composer__row">
                          <span className="catalog-page-composer__icon"><Icon name="globe" /></span>
                          <span><strong>{template.title}</strong><small>{template.version} · {template.category}</small></span>
                          {handle}
                        </div>
                      )}
                    </DraggableItem>
                  ))}
                </div>
              </section>

              <DropZone id="application-pages" data={{ type: "application-pages" }} className="catalog-page-composer__column">
                <header><span>02</span><div><strong>Конфигурация приложения</strong><small>{applicationDraft.pages.length} страниц</small></div></header>
                <SortableScope ids={applicationDraft.pages.map((page) => `page:${page.id}`)}>
                  <div className="catalog-page-composer__list">
                    {applicationDraft.pages.map((page) => (
                      <SortableItem key={page.id} id={`page:${page.id}`} data={{ type: "application-page", pageId: page.id }} className="catalog-page-composer__draggable">
                        {({ handle }) => (
                          <div className="catalog-page-composer__row catalog-page-composer__row--configured">
                            <span className="catalog-page-composer__icon"><Icon name="globe" /></span>
                            <span><strong>{page.title}</strong><small>{page.template.id} · {page.template.version}</small></span>
                            {handle}
                            <IconButton label="Убрать страницу" onClick={() => setPageRemovalId(page.id)}><Icon name="close" /></IconButton>
                          </div>
                        )}
                      </SortableItem>
                    ))}
                    {!applicationDraft.pages.length ? <div className="catalog-page-composer__empty">Перетащите сюда готовый Page Template.</div> : null}
                  </div>
                </SortableScope>
              </DropZone>
            </div>
          </DragDropRoot>
          <div className="catalog-page-composer__footer">
            <span data-state={applicationSaveState}>{applicationSaveState === "saving" ? "Сохранение…" : applicationSaveState === "saved" ? "Конфигурация сохранена" : applicationSaveState === "error" ? "Ошибка сохранения" : "Есть несохранённые изменения"}</span>
          </div>
        </SettingsCard>
        {applicationError ? <p className="nodedc-media-field__error" role="alert">{applicationError}</p> : null}
      </div>
    );
  };

  const renderPageTemplate = (template: PageTemplateDefinition) => {
    const mapProfileFragment = template.id === "map"
      ? mapDesignFragmentForLayout(activeDesignProfileLayout, template.version)
      : null;
    const mapDesignLayout = template.id === "map"
      ? resolveMapDesignLayout(mapTemplateLayout ?? createDefaultMapPageLayout(true), mapProfileFragment)
      : null;
    return template.id === "map" ? (
      <div className="catalog-page-template catalog-page-template--map">
        <MapFixturePreview
          key={`map-template-${activeDesignProfileId}-${mapDesignProfileKey(template.version)}-${designProfiles.find((profile) => profile.id === activeDesignProfileId)?.version ?? "draft"}`}
          ref={mapTemplatePreviewRef}
          expanded
          initialLayout={mapDesignLayout}
          features={Object.fromEntries(template.features.map((feature) => [feature.id, feature.required ? true : feature.defaultVisible]))}
        />
        <div className="catalog-page-template__actions">
          <Button variant="primary" shape="pill" icon={<Icon name="plus" />} onClick={() => { void createApplicationDraft(template); }}>Создать Application Draft</Button>
          <span>Карта масштабируется по высоте за правый нижний угол. Настройки — через Inspector.</span>
        </div>
      </div>
    ) : (
    <div className="catalog-page-template">
      <SettingsCard
        eyebrow={`PAGE TEMPLATE ${template.version}`}
        title={template.title}
        description={template.description}
      >
        <MapFixturePreview features={Object.fromEntries(template.features.map((feature) => [feature.id, feature.required ? true : feature.defaultVisible]))} />
        <div className="catalog-page-template__actions">
          <Button variant="primary" shape="pill" icon={<Icon name="plus" />} onClick={() => { void createApplicationDraft(template); }}>Создать Application Draft</Button>
          <span>Только утверждённые features и slots — без свободного canvas.</span>
        </div>
      </SettingsCard>

      <div className="catalog-grid">
        <SettingsCard eyebrow="FEATURE FLAGS" title="Разрешённые функции" description="Template владеет составом; пользователь только включает или скрывает разрешённое.">
          <div className="catalog-page-template__list">
            {template.features.map((feature) => (
              <div key={feature.id}><Icon name="check" /><span><strong>{feature.label}</strong><small>{feature.description}</small></span><code>{feature.required ? "required" : feature.defaultVisible ? "visible" : "hidden"}</code></div>
            ))}
          </div>
        </SettingsCard>
        <SettingsCard eyebrow="SLOT CONTRACT" title="Данные и команды" description="Runtime-neutral slots; Cesium и Engine не зашиты в визуальный шаблон.">
          <div className="catalog-page-template__list">
            {template.slots.map((slot) => (
              <div key={slot.id}><Icon name={slot.kind === "command" ? "activity" : "database"} /><span><strong>{slot.label}</strong><small>{slot.description}</small></span><code>{slot.kind}</code></div>
            ))}
          </div>
        </SettingsCard>
      </div>
    </div>
    );
  };

  const renderApplicationPage = () => {
    if (!applicationDraft || !activeApplicationPageId) return null;
    const page = applicationDraft.pages.find((item) => item.id === activeApplicationPageId);
    if (!page) return null;
    const template = getPageTemplate(page.template.id, page.template.version);
    if (!template) return null;
    const mapProfileFragment = page.template.id === "map"
      ? mapDesignFragmentForLayout(applicationDesignProfileLayout, page.template.version)
      : null;
    const resolvedMapLayout = page.template.id === "map"
      ? resolveMapDesignLayout(page.layout?.map ?? createDefaultMapPageLayout(true), mapProfileFragment, page.designOverrides?.map)
      : null;
    const setFeature = (featureId: string, value: boolean) => updateApplicationDraft((current) => ({
      ...current,
      pages: current.pages.map((item) => item.id === page.id ? { ...item, features: { ...item.features, [featureId]: value } } : item),
    }));
    return (
      <div className="catalog-application-page">
        <MapFixturePreview
          key={`${page.id}-${applicationDraft.designProfile.id}-${applicationDraft.designProfile.version}-${applicationDraft.designProfile.status}`}
          ref={applicationMapPreviewRef}
          applicationId={applicationDraft.id}
          pageId={page.id}
          initialLayout={resolvedMapLayout}
          features={page.features}
          expanded
        />
        {applicationMode === "edit" ? (
          <SettingsCard eyebrow="PAGE SETTINGS" title={page.title} description={`${template.title} · ${template.version}`}>
            <div className="catalog-application-draft__features">
              <Switch checked={page.navigation.visible} label="Показывать в навигации" onChange={(visible) => updateApplicationDraft((current) => ({ ...current, pages: current.pages.map((item) => item.id === page.id ? { ...item, navigation: { ...item.navigation, visible } } : item) }))} />
              {template.features.map((feature) => (
                <Switch key={feature.id} checked={Boolean(page.features[feature.id])} label={("required" in feature && feature.required) ? `${feature.label} · обязательно` : feature.label} onChange={(value) => setFeature(feature.id, ("required" in feature && feature.required) ? true : value)} />
              ))}
            </div>
          </SettingsCard>
        ) : null}
      </div>
    );
  };

  const activeDefinition = activeSection ? sectionDefinitions[activeSection] : null;
  const activeApplication = applicationDraft && applicationDraft.id === activeApplicationId ? applicationDraft : null;
  const activePageTemplate = activePageTemplateId ? getPageTemplate(activePageTemplateId) : undefined;
  const activeApplicationPage = activeApplicationPageId ? activeApplication?.pages.find((page) => page.id === activeApplicationPageId) : undefined;

  return (
    <>
    <ApplicationShell
      className="catalog-app nodedc-ui-root"
      style={{ "--catalog-accent": accentHex } as CSSProperties}
      navigationOpen={guidelineOpen}
      contentOpen={workspace.contentOpen}
      contentExpanded={panelExpanded}
      header={
        <AppHeader
          brand={<img src="/nodedc-logo.svg" alt="NODE.DC" />}
          brandHref="/"
          center={
            <>
              <HeaderWorkspace kind="mark" label="NODE.DC Design" imageUrl={headerMarkSrc} />
              <HeaderNavigation
                label="Рабочая область Module Foundry"
                value={guidelineOpen ? studioContext : undefined}
                items={[
                  { value: "visual", label: "Visual Library" },
                  { value: "pages", label: "Page Library" },
                  { value: "applications", label: "Applications" },
                ]}
                onChange={changeStudioContext}
              />
              <SegmentedControl
                className="catalog-header-theme-switch"
                label="Тема приложения"
                value={theme}
                items={[{ value: "dark", label: "Dark" }, { value: "light", label: "Light" }]}
                onChange={setTheme}
              />
            </>
          }
          right={
            <HeaderProfile>
              <IconButton label="Уведомления"><Icon name="inbox" size={20} strokeWidth={1.7} /></IconButton>
              <UserProfileMenu
                displayName={sessionProfile?.user?.displayName || "Профиль"}
                subtitle={sessionProfile?.user?.email || "NODE.DC Foundry"}
                avatarUrl={sessionProfile?.user?.avatarUrl || undefined}
                actions={[
                  { id: "profile", label: "Профиль", icon: "profile", href: sessionProfile?.profileUrl || undefined, disabled: !sessionProfile?.profileUrl },
                  { id: "settings", label: "Настройки", icon: "settings", onSelect: () => setFoundrySettingsOpen(true) },
                  { id: "logout", label: "Выйти", icon: "external", href: "/auth/logout" },
                ]}
              />
            </HeaderProfile>
          }
        />
      }
      stage={
        <section className="catalog-launcher-stage">
          <video key={stageMediaSrc} className="catalog-launcher-stage__media" hidden={!mediaVisible} autoPlay muted loop playsInline aria-hidden="true">
            <source src={stageMediaSrc} type="video/mp4" />
          </video>
          <div className="catalog-launcher-stage__shade" />
          <div className="catalog-launcher-stage__title">
            <span>NODE.DC</span>
            <strong>{studioContext === "visual" ? <>DESIGN<br />GUIDELINE</> : <>MODULE<br />STUDIO</>}</strong>
            <p>{studioContext === "applications" ? "Application Drafts на основе версионируемых шаблонов NODE.DC." : studioContext === "pages" ? "Готовые смысловые страницы с фиксированным контрактом функций и данных." : "Общий форм-фактор приложений, компоненты и оконная механика платформы."}</p>
          </div>
        </section>
      }
      navigation={
        <AdminNavigationPanel
          eyebrow="NODE.DC"
          title={studioContext === "applications" ? "Applications" : studioContext === "pages" ? "Page Library" : "Visual Library"}
          closeLabel="Закрыть Module Foundry"
          navigationLabel={studioContext === "applications" ? "Application Drafts" : studioContext === "pages" ? "Page Templates" : "Разделы Visual Library"}
          onClose={closeGuideline}
          headerActions={studioContext === "applications" ? (
            <IconButton label="Создать модуль" onClick={() => setCreateModuleOpen(true)}><Icon name="plus" /></IconButton>
          ) : undefined}
          contextSlot={studioContext === "applications" ? (
            <Select
              className="catalog-module-switcher"
              label="Текущий модуль"
              value={activeApplicationId ?? applicationSummaries[0]?.id ?? ""}
              options={applicationSummaries.map((application) => ({ value: application.id, label: application.name, description: `${application.version} · ${application.status}`, icon: <Icon name="apps" /> }))}
              searchable
              searchPlaceholder="Поиск модулей"
              emptyLabel="Модули не созданы"
              onChange={(id) => { void openApplicationDraft(id); }}
            />
          ) : studioContext === "visual" || studioContext === "pages" ? (
            <Select
              className="catalog-module-switcher"
              label="Design Profile"
              value={activeDesignProfileId}
              options={designProfiles.map((profile) => ({ value: profile.id, label: profile.name, description: `${profile.version} · Draft · ${profile.versions.length} releases`, icon: <Icon name="settings" /> }))}
              onChange={(id) => { void openDesignProfile(id); }}
            />
          ) : undefined}
          items={studioContext === "applications"
            ? activeApplication ? [
                { id: "__root__", label: "Общие настройки", icon: <Icon name="settings" /> },
                ...activeApplication.pages.filter((page) => page.navigation.visible).map((page) => ({ id: page.id, label: page.navigation.label, icon: <Icon name="globe" />, sortable: true })),
              ] : []
            : studioContext === "pages"
              ? pageTemplates.map((template) => ({
                  id: template.id,
                  label: template.title,
                  icon: <Icon name="globe" />,
                }))
            : (Object.entries(sectionDefinitions) as Array<[CatalogSection, (typeof sectionDefinitions)[CatalogSection]]>).map(([id, item]) => ({
                id,
                label: item.title,
                icon: <Icon name={item.icon} />,
              }))}
          activeId={studioContext === "applications" ? activeApplicationPageId ?? (activeApplication ? "__root__" : undefined) : studioContext === "pages" ? activePageTemplateId ?? undefined : activeSection ?? undefined}
          onItemChange={(id) => studioContext === "applications"
            ? activeApplication && workspace.openView(id === "__root__" ? applicationViewId(activeApplication.id) : applicationPageViewId(activeApplication.id, id))
            : studioContext === "pages" ? openPageTemplate(id) : openSection(id)}
          onItemsReorder={studioContext === "applications" ? setApplicationPageOrder : undefined}
          footer={
            <>
              <span className="nodedc-admin-panel__nav-icon" aria-hidden="true"><Icon name={studioContext === "applications" ? "apps" : studioContext === "pages" ? "globe" : "shield"} /></span>
              <span>{studioContext === "applications" ? activeApplication ? `${activeApplication.metadata.name} · ${activeApplication.version}` : `${applicationSummaries.length} drafts` : studioContext === "pages" ? `${pageTemplates.length} templates` : "Design Guideline 0.7.0"}</span>
            </>
          }
        />
      }
      content={activeDefinition ? (
        <ApplicationPanel
          key={activeSection}
          eyebrow={activeDefinition.eyebrow}
          title={activeDefinition.title}
          description={activeDefinition.description}
          expanded={panelExpanded}
          onExpandedChange={workspace.setContentExpanded}
          utilityActions={[{
            label: layoutSaveState === "saving" ? "Сохраняется на сервер" : layoutSaveState === "saved" ? "Layout сохранён на сервере" : layoutSaveState === "error" ? "Повторить сохранение layout" : "Сохранить layout на сервер",
            icon: "save",
            onClick: () => {
              const currentProfile = designProfiles.find((profile) => profile.id === activeDesignProfileId);
              setDesignProfileName(currentProfile?.name ?? "");
              setDesignProfileSaveScope({ kind: "global" });
              setDesignProfileSaveMode("save");
              setDesignProfileSaveOpen(true);
            },
            disabled: layoutSaveState === "saving" || layoutSaveState === "loading",
          }]}
          onClose={workspace.closeView}
        >
          <div className="catalog-panel-content">{renderSectionContent()}</div>
        </ApplicationPanel>
      ) : activePageTemplate ? (
        <ApplicationPanel
          key={`${activePageTemplate.id}@${activePageTemplate.version}`}
          eyebrow="PAGE LIBRARY / CANONICAL"
          title={activePageTemplate.title}
          description={`${activePageTemplate.category} · contract ${activePageTemplate.schemaVersion} · template ${activePageTemplate.version}`}
          expanded={panelExpanded}
          onExpandedChange={workspace.setContentExpanded}
          utilityActions={activePageTemplate.id === "map" ? [{
            label: mapTemplateSaveState === "saving" ? "Сохраняем Map-фрагмент в Design Profile" : mapTemplateSaveState === "error" ? "Повторить сохранение Map-фрагмента" : "Сохранить дизайн Map в Design Profile",
            icon: "save",
            onClick: () => openMapPageDesignSave(activePageTemplate.version),
            disabled: mapTemplateSaveState === "saving" || mapTemplateSaveState === "loading",
          }] : undefined}
          onClose={workspace.closeView}
        >
          <div className="catalog-panel-content">{renderPageTemplate(activePageTemplate)}</div>
        </ApplicationPanel>
      ) : activeApplication ? (
        <ApplicationPanel
          key={activeApplication.id}
          eyebrow={activeApplicationPage ? "APPLICATION / PAGE" : "APPLICATION / ROOT"}
          title={activeApplicationPage?.title ?? activeApplication.metadata.name}
          description={activeApplicationPage ? `${activeApplication.metadata.name} · ${activeApplicationPage.template.id}@${activeApplicationPage.template.version}` : `/${activeApplication.metadata.slug} · manifest ${activeApplication.schemaVersion}`}
          expanded={panelExpanded}
          onExpandedChange={workspace.setContentExpanded}
          utilityActions={[{
            label: applicationSaveState === "saving" ? "Сохранение…" : applicationSaveState === "error" ? "Повторить сохранение" : "Сохранить",
            icon: "save",
            onClick: () => { void saveApplicationDraft(); },
            disabled: applicationSaveState === "saving" || applicationSaveState === "loading",
          }, {
            label: "Удалить модуль",
            icon: "trash",
            onClick: () => setDeleteModuleOpen(true),
          }]}
          onClose={workspace.closeView}
        >
          <div className="catalog-panel-content">
            <div className="catalog-module-mode">
              <SegmentedControl label="Режим модуля" value={applicationMode} items={[{ value: "edit", label: "Настройка" }, { value: "preview", label: "Предпросмотр" }]} onChange={setApplicationMode} />
            </div>
            {activeApplicationPage ? renderApplicationPage() : renderApplicationDraft()}
          </div>
        </ApplicationPanel>
      ) : null}
    />

    {guidelineOpen && studioContext === "visual" ? (
      <Toolbar<CatalogSection>
        placement={toolbarPlacement}
        background={toolbarBg}
        border={toolbarBorder}
        outline={toolbarOutline}
        accent={accentHex}
        minSize={toolbarMinSize}
        maxSize={toolbarMaxSize}
        lensCount={toolbarLensCount}
        autoHide={toolbarAutoHide}
        items={(Object.entries(sectionDefinitions) as Array<[CatalogSection, (typeof sectionDefinitions)[CatalogSection]]>).map(([id, item]) => ({
          id,
          label: item.title,
          icon: item.icon,
          active: activeSection === id,
          onSelect: openSection,
        }))}
      />
    ) : null}

    <Window
      open={inspectorOpen}
      title="Настройки окружения"
      subtitle="ENGINE / draggable inspector"
      placement="end"
      draggable
      onClose={() => setInspectorOpen(false)}
    >
      <Inspector sections={environmentSections} defaultOpen={["application-material"]} />
    </Window>

    <Window
      open={createModuleOpen}
      title="Создать модуль"
      subtitle="APPLICATION / DRAFT"
      size="sm"
      onClose={() => setCreateModuleOpen(false)}
      footer={
        <>
          <Button variant="ghost" onClick={() => setCreateModuleOpen(false)}>Отмена</Button>
          <WindowFooterActions>
            <Button
              variant="primary"
              shape="pill"
              icon={<Icon name="plus" />}
              disabled={!createModuleName.trim() || !createModuleSlug.trim() || applicationSaveState === "saving"}
              onClick={() => { void createApplicationDraft(undefined, { name: createModuleName, slug: createModuleSlug, description: createModuleDescription }); }}
            >Создать модуль</Button>
          </WindowFooterActions>
        </>
      }
    >
      <div className="catalog-form">
        <TextField label="Название модуля" hint="обязательно" value={createModuleName} onChange={(event) => {
          const value = event.target.value;
          setCreateModuleName(value);
          setCreateModuleSlug((current) => current ? current : value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""));
        }} />
        <TextField label="Slug" hint="латиница, цифры и дефис" value={createModuleSlug} onChange={(event) => setCreateModuleSlug(event.target.value.toLowerCase().replace(/[^a-z0-9-]+/g, "-"))} />
        <TextAreaField label="Описание" value={createModuleDescription} onChange={(event) => setCreateModuleDescription(event.target.value)} />
      </div>
    </Window>

    <Window
      open={designProfileSaveOpen}
      title="Сохранить Design Profile"
      subtitle={designProfileSaveScope.kind === "page" ? `PAGE LIBRARY / ${designProfileSaveScope.templateId.toUpperCase()}@${designProfileSaveScope.templateVersion}` : "VISUAL LIBRARY / GLOBAL PRESET"}
      size="sm"
      onClose={() => setDesignProfileSaveOpen(false)}
      footer={
        <>
          <Button variant="ghost" onClick={() => setDesignProfileSaveOpen(false)}>Отмена</Button>
          <WindowFooterActions>
            {designProfileSaveMode === "save" ? (
              <>
                <Button onClick={() => { void saveDesignProfile("save"); }}>Сохранить</Button>
                <Button
                  disabled={designProfilePublishState === "publishing" || Boolean(designProfiles.find((profile) => profile.id === activeDesignProfileId)?.versions.some((release) => release.version === designProfiles.find((profile) => profile.id === activeDesignProfileId)?.version))}
                  onClick={() => { void publishDesignProfile(); }}
                >{designProfilePublishState === "publishing" ? "Публикуется" : `Опубликовать v${designProfiles.find((profile) => profile.id === activeDesignProfileId)?.version ?? ""}`}</Button>
                <Button variant="primary" shape="pill" onClick={() => { setDesignProfileName(""); setDesignProfileSaveMode("save-as"); }}>Сохранить как новый</Button>
              </>
            ) : (
              <Button variant="primary" shape="pill" disabled={!designProfileName.trim()} onClick={() => { void saveDesignProfile("save-as"); }}>Сохранить как новый</Button>
            )}
          </WindowFooterActions>
        </>
      }
    >
      <div className="catalog-form">
        <div className="catalog-save-profile-current">
          <Icon name={designProfileSaveScope.kind === "page" ? "globe" : "settings"} />
          <span>
            <strong>{designProfileSaveScope.kind === "page" ? "Фрагмент дизайна страницы" : "Глобальные визуальные настройки"}</strong>
            <small>{designProfileSaveScope.kind === "page" ? "В профиль попадут только Map settings и presentation profiles. Camera, bindings, subjects и credentials не сохраняются." : "Обновляются тема, материалы, media, favicon и toolbar. Уже сохранённые фрагменты страниц сохраняются без изменений."}</small>
          </span>
        </div>
        <SegmentedControl label="Режим сохранения" value={designProfileSaveMode} items={[{ value: "save", label: "Сохранить" }, { value: "save-as", label: "Сохранить как новый" }]} onChange={setDesignProfileSaveMode} />
        {designProfileSaveMode === "save-as" ? <TextField label="Название нового профиля" hint="обязательно" value={designProfileName} onChange={(event) => setDesignProfileName(event.target.value)} /> : (
          <div className="catalog-save-profile-current"><Icon name="settings" /><span><strong>{designProfiles.find((profile) => profile.id === activeDesignProfileId)?.name ?? "NODE.DC Default"}</strong><small>Сохранение создаёт новую draft-версию. Publish фиксирует текущую сохранённую версию неизменяемым release.</small></span></div>
        )}
      </div>
    </Window>

    <ConfirmationModal
      open={deleteModuleOpen}
      title="Удалить модуль?"
      description={<><strong>{applicationDraft?.metadata.name}</strong><p>Draft будет убран из Module Foundry. Опубликованные releases эта операция не затрагивает.</p></>}
      confirmLabel="Удалить draft"
      pendingLabel="Удаление…"
      danger
      onClose={() => setDeleteModuleOpen(false)}
      onConfirm={deleteApplicationDraft}
    />

    <ConfirmationModal
      open={Boolean(pageRemovalId)}
      title="Удалить страницу из модуля?"
      description={<><strong>{applicationDraft?.pages.find((page) => page.id === pageRemovalId)?.title ?? "Страница"}</strong><p>Экземпляр страницы и его настройки будут удалены из текущей конфигурации. Исходный Page Template останется доступен в библиотеке.</p></>}
      confirmLabel="Удалить страницу"
      pendingLabel="Удаление…"
      danger
      onClose={() => setPageRemovalId(null)}
      onConfirm={confirmPageRemoval}
    />

    <FoundrySettingsModal open={foundrySettingsOpen} isAdmin={isFoundryAdmin} onClose={() => setFoundrySettingsOpen(false)} />

    <Window
      open={createModalOpen}
      title="Создать проект"
      subtitle="Адаптивная форма"
      size="sm"
      onClose={() => setCreateModalOpen(false)}
      footer={
        <>
          <Button variant="ghost" onClick={() => setCreateModalOpen(false)}>Отмена</Button>
          <WindowFooterActions><Button variant="primary" shape="pill" icon={<Icon name="plus" />} onClick={() => setCreateModalOpen(false)}>Создать</Button></WindowFooterActions>
        </>
      }
    >
      <div className="catalog-form">
        <TextField label="Название проекта" hint="обязательно" value={projectName} onChange={(event) => setProjectName(event.target.value)} />
        <TextAreaField label="Описание" value={notes} onChange={(event) => setNotes(event.target.value)} />
        <FieldFrame label="Статус"><Select label="Статус" value={selectedStatus} options={[...selectOptions]} onChange={setSelectedStatus} /></FieldFrame>
      </div>
    </Window>

    <ShareAccessModal<ShareRole>
      open={shareAccessOpen}
      resourceName="Workflow / Rail infrastructure analysis"
      members={shareMembers}
      roleOptions={shareRoleOptions}
      inviteEmail={shareEmail}
      inviteRole={shareRole}
      message={shareMessage}
      messageTone="accent"
      onInviteEmailChange={(email) => {
        setShareEmail(email);
        setShareMessage("");
      }}
      onInviteRoleChange={setShareRole}
      onInvite={inviteShareMember}
      onMemberRoleChange={(member, role) => {
        setShareMembers((current) => current.map((item) => item.id === member.id
          ? { ...item, role, roleLabel: shareRoleOptions.find((option) => option.value === role)?.label }
          : item));
      }}
      onMemberRemove={(member) => setShareMembers((current) => current.filter((item) => item.id !== member.id))}
      onClose={() => setShareAccessOpen(false)}
    />

    <ShareLinkModal
      open={shareLinkOpen}
      link="https://bim.nodedc.ru/model/rail-hub?view=main"
      onCopy={() => undefined}
      onClose={() => setShareLinkOpen(false)}
    />

    <Window
      open={contextActionOpen}
      title="Размер"
      subtitle="BIM / contextual actions"
      size="sm"
      onClose={() => setContextActionOpen(false)}
      footer={<WindowFooterActions><Button onClick={() => setContextActionOpen(false)}>Закрыть</Button></WindowFooterActions>}
    >
      <div className="catalog-modal-action-list">
        <Button width="full" icon={<Icon name="activity" />}>Скрыть размер</Button>
        <Button width="full" variant="danger" icon={<Icon name="trash" />}>Удалить размер</Button>
      </div>
    </Window>

    <Window
      open={historyOpen}
      title="История версий"
      subtitle="rail-hub-model.glb"
      size="lg"
      onClose={() => setHistoryOpen(false)}
    >
      <div className="catalog-history-table" role="table" aria-label="История версий модели">
        <div className="catalog-history-row catalog-history-row--head" role="row">
          <span>Файл</span><span>Версия</span><span>Автор</span><span>Статус</span><span>Действия</span>
        </div>
        {[
          ["rail-hub-model.glb", "v12 · текущая", "DC", "Модель готова"],
          ["rail-hub-model.glb", "v11", "Maria", "Модель готова"],
          ["rail-hub-model.glb", "v10", "Alex", "Ожидает подготовки"],
        ].map((row) => (
          <div className="catalog-history-row" role="row" key={row[1]}>
            <strong>{row[0]}</strong><span>{row[1]}</span><span>{row[2]}</span><span>{row[3]}</span>
            <span className="catalog-history-actions">
              <IconButton label="Посмотреть"><Icon name="external" /></IconButton>
              <IconButton label="Скачать"><Icon name="download" /></IconButton>
            </span>
          </div>
        ))}
      </div>
    </Window>

    <Window
      open={detailOpen}
      title="Комментарий к модели"
      subtitle="Опора №42 · рабочая detail-modal"
      size={detailExpanded ? "lg" : "md"}
      onClose={() => setDetailOpen(false)}
      footer={
        <>
          <Button icon={<Icon name={detailExpanded ? "minimize" : "expand"} />} onClick={() => setDetailExpanded((current) => !current)}>
            {detailExpanded ? "Свернуть" : "Развернуть"}
          </Button>
          <WindowFooterActions>
            <Button onClick={() => setDetailOpen(false)}>Отмена</Button>
            <Button variant="primary" onClick={() => setDetailOpen(false)}>Сохранить</Button>
          </WindowFooterActions>
        </>
      }
    >
      <div className="catalog-detail-modal">
        <TextField label="Заголовок" value="Проверить узел крепления" readOnly />
        <TextAreaField label="Описание" value="Нужно сверить положение опоры с последней версией модели." readOnly />
        <SettingsCard title="Вложения" description="Файлы и изображения остаются BIM-domain content.">
          <Button icon={<Icon name="upload" />}>Выбрать файл</Button>
        </SettingsCard>
      </div>
    </Window>

    <ConfirmationModal
      open={confirmOpen}
      title="Удалить конфигурацию?"
      description={<>Конфигурация будет удалена. Это действие нельзя отменить.</>}
      confirmLabel="Удалить"
      danger
      onClose={() => setConfirmOpen(false)}
      onConfirm={() => setConfirmOpen(false)}
    />
    <ToastStack items={toasts} onDismiss={dismissToast} />
    </>
  );
}
