import { h, render } from 'preact';
import { AsukaRoot } from '../components/layout/AsukaRoot';
import { configurePdfSupport } from './pdfRuntime';
import { useAsukaStore, type AsukaState, type UploadedAsset, type RegisterUploadedAssetInput } from '../store/asukaStore';
import type { LayoutZoneId } from '../types/layout';
import type { ModuleId } from '../types/moduleIds';
import type { OverlayType } from '../types/layout';
import type { AsukaListenerName, ListenerOptions } from '../types/listeners';
import type { AsukaBootMode, AsukaBootOptions, AsukaCustomFontConfig, AsukaDebugOptions, AsukaDesignerOptions, AsukaExportsFeatureOptions, AsukaFontsConfig, AsukaGoogleFontConfig, AsukaObservabilityErrorRecord, AsukaObservabilityOptions, AsukaRegisteredFont, AsukaSystemFontConfig, AsukaUploadModuleConfig } from '../types/options';
import { DEFAULT_ASUKA_UPLOAD_STRATEGY } from '../types/options';
import type { AsukaLicenseActivateInput, AsukaLicenseActivationState, AsukaLicenseEntitlements, AsukaLicenseFeature, AsukaLicensePlan, AsukaLicenseResolutionResult } from './license/license.types';
import type { DesignerInstance } from './designer';
import { notifyModuleRegistryChanged, registerModule, updateModuleDisplayMode, updateModuleMountTarget, type ModuleDisplayMode, type AsukaModuleDefinition, type AsukaModuleUiHelpers } from './modules';
import { emitAsukaEvent, emitAsukaEventCancelable, emitAfter, onAsukaEvent } from './events';
import {
  PROD_PERMISSIONS,
  mergePermissions,
  permissionsForRole,
  defineRole,
  getEffectivePermissions,
  canOpenModuleId,
  type AsukaPermissions,
  type AsukaRole,
} from './permissions';
import { getPrice, getPricingRules, getStandardPrices, getPriceUiBreakdown, type PriceFormat, type PriceUiBreakdown } from './pricingEngine';
import { normalizePriceInput, type AsukaPriceInput } from './pricingValue';
import { startPricingController } from './pricingController';
import { captureFullAsukaState, restoreFullAsukaState, captureAsukaStatePart, restoreAsukaStatePart, rebuildActiveViewFromDocument, normalizeLayoutConfigToRuntimeState, extractRuntimeLayoutAsConfig, type AsukaExportedState, type AsukaImportOptions, type AsukaStatePart } from './stateManager';
import {
  createAsukaPortableProductDocument,
  extractAsukaPortableProductState,
  normalizeAsukaPortableProductDocument,
  type AsukaPortableProductCreateOptions,
  type AsukaPortableProductDocument,
} from './portableProduct';
import {
  createAsukaPortableProductPackage,
  materializeAsukaPortableProductPackage,
  readAsukaPortableProductPackage,
  type AsukaPortableAssetPublisher,
  type AsukaPortableProductPackageCreateOptions,
} from './portableProductPackage';
import { applyCanvasEditability } from './viewSnapshots';
import { saveStateToStorage, loadStateFromStorage, clearStateStorage } from './stateStorage';
import { initI18n, getI18n, type Translations } from './i18n';
import { initTheme, setThemeColor, setThemeColors, getThemeColor, resetTheme, resetThemeColor, resetThemeSection, getThemeState, applyThemePreset, getThemePresets, type ThemeColorMap, type ThemePresetId, type ThemeToken } from './theme';
import { dedupeFonts, getDefaultSystemFonts, loadRegisteredFont, normalizeCustomFont, normalizeGoogleFont, normalizeSystemFont } from './fonts';
import { getVanillaUiHelpers } from './moduleUi';
import { DEFAULT_LICENSE, DEFAULT_LICENSE_RESOLUTION } from './license/license.defaults';
import { normalizeLicenseOptions } from './license/public-options';
import { ASUKA_LICENSE_FEATURE_CATALOG, ASUKA_LICENSE_PLAN_CATALOG, getEnabledLicenseFeatures } from './license/license.catalog';
import { getCommercialQuotaState, type AsukaCommercialQuotaKey } from './license/commercial-limits';
import { canUseFeature, hasPlan, isLicenseUsable } from './license/feature-gates';
import { LicenseManager } from './license/license.manager';
import { ensurePremiumRuntimeLoaded, shouldLoadPremiumRuntime } from './license/premium-runtime';
import { setCurrentLicenseResolution } from './license/runtime';
import { createLicenseRefreshScheduler } from './license/scheduler';
import { createAsukaAgencyRuntime, type AsukaAgencyApi, type AsukaAgencyOptions } from './agency';
import { createAsukaAiRuntime, type AsukaAiApi, type AsukaAiOptions } from './ai';
import { createAsukaExportRuntime, type AsukaExportApi, type AsukaExportsOptions } from './exports';
import { DEFAULT_RESPONSIVE_CONFIG, normalizeResponsiveConfig, type AsukaResponsiveBreakpoint, type AsukaResponsiveConfig, type NormalizedAsukaResponsiveConfig } from './responsive';
import { normalizeAsukaUiOptions, type AsukaUiOptions, type NormalizedAsukaUiOptions } from './uiOptions';
import { AsukaRendererError, getCurrentRenderEngineKind, getRuntimeRenderEngine, isRuntimeRenderEngineViewReady, measureRenderEngineLayer, normalizeRenderEngineCapabilityList, normalizeRenderEngineKind, resolveViewEngineKind, setCurrentRenderEngineKind, subscribeRenderEngineEvents, updateRenderEngineObjectTransform, type AsukaRenderEngine, type AsukaRenderEngineCapabilityKey, type AsukaRenderEngineKind, type AsukaRenderEngineCapabilities, type AsukaRenderEngineOptionsByKind, type AsukaSceneGraphNode, type AsukaObjectSummary, type AsukaObjectRef, type AsukaObjectPatch, type AsukaImageInput, type AsukaTextInput, type AsukaDesignInput, type AsukaElementInput, type AsukaExportOptions, type AsukaDownloadOptions, type AsukaObjectTransform, type AsukaObjectDuplicateOptions, type AsukaObjectMutationOptions, type AsukaMutationPromise, type AsukaPlacementBeginInput, type AsukaPlacementStatus, type AsukaPlacementUpdateInput } from './renderEngine';
import { convertPhysicalLength, type AsukaLayerMeasurement, type AsukaLayerMeasurementOptions, type AsukaPhysicalLayerResizeInput, type AsukaPhysicalSize, type AsukaProductionMeasurementExport } from './physicalMeasurement';
import { createLiveModuleRenderEngine } from './engineManager';
import { asukaHistoryDebug, getAsukaHistoryDebugBuild } from './historyDebug';
import type { AsukaCameraLimits, AsukaCameraMoveOptions, AsukaCameraView } from '../types/camera';
import type { AsukaParametricMutationOptions, AsukaParametricProductDescriptor, AsukaParametricValue } from '../types/parametric3d';
import { getVariationCamera } from './camera';
import { resolveUploadedFileUrl } from './images';
import { ensureFontFamilyLoaded } from './fontRegistry';
import { registerInlineLayoutModules } from './inlineLayoutModules';
import { applyElementVariationAcrossRenderers } from './elementVariationRuntime';
import { synchronizeStoredElementVariations, type AsukaElementVariationApplyInput } from './elementVariationSync';
import type { AsukaRenderEngineAssembly } from './renderEngine';
import {
  listElementVariationDescriptors,
  normalizeVariationApplyInput,
  type AsukaVariationApplyResult,
  type AsukaVariationsApi,
} from './variations';

// Ensure built-in modules (Colors, Pricing, etc.) are registered so the default layout
// doesn't fall back to placeholder modules.
import { registerBuiltinModules } from '../registerModules';

registerBuiltinModules();

function serializeIconOverride(input: any): string | null {
  if (typeof input === 'string') {
    return input.trim() || null;
  }
  if (!input || typeof input !== 'object') return null;
  if (typeof input.svg === 'string' && input.svg.trim()) return `svg:${input.svg.trim()}`;
  if (typeof input.url === 'string' && input.url.trim()) return `url:${input.url.trim()}`;
  if (typeof input.name === 'string' && input.name.trim()) return input.name.trim();
  return null;
}

function preprocessLayoutIcons(layoutConfig: any, iconOverrides: Record<string, any> | undefined): any {
  if (!layoutConfig || typeof layoutConfig !== 'object') return layoutConfig;

  const next = { ...(layoutConfig as any) };
  const zones: LayoutZoneId[] = ['top', 'bottom', 'left', 'right'];

  zones.forEach((zone) => {
    const zoneConfig = (next as any)[zone];
    if (!zoneConfig || typeof zoneConfig !== 'object' || !Array.isArray(zoneConfig.items)) return;

    (next as any)[zone] = {
      ...zoneConfig,
      items: zoneConfig.items.map((item: any) => {
        if (!item || typeof item !== 'object') return item;
        const explicitIcon = serializeIconOverride((item as any).icon);
        const overrideIcon = serializeIconOverride(
          (iconOverrides ?? {})[(item as any).id] ?? (iconOverrides ?? {})[(item as any).kind]
        );
        return {
          ...item,
          icon: overrideIcon ?? explicitIcon ?? (item as any).icon,
        };
      }),
    };
  });

  return next;
}

function applyRuntimeIconOverrides(iconOverrides: Record<string, any> | undefined): void {
  if (!iconOverrides || typeof iconOverrides !== 'object') return;
  try {
    const current = useAsukaStore.getState() as any;
    const nextBlocksByZone = { ...(current.blocksByZone ?? {}) } as any;
    let changed = false;

    for (const zone of ['top', 'bottom', 'left', 'right'] as LayoutZoneId[]) {
      const blocks = Array.isArray(nextBlocksByZone[zone]) ? nextBlocksByZone[zone] : [];
      nextBlocksByZone[zone] = blocks.map((block: any) => {
        const override = serializeIconOverride(iconOverrides[block?.id] ?? iconOverrides[block?.kind]);
        if (!override) return block;
        changed = true;
        return { ...block, icon: override };
      });
    }

    if (changed) {
      useAsukaStore.setState({ blocksByZone: nextBlocksByZone } as any);
    }
  } catch (_e) {
    void 0;
  }
}

function applyLayoutModuleDisplayModes(layoutConfig: any): void {
  if (!layoutConfig || typeof layoutConfig !== 'object') {
    return;
  }

  const zones: LayoutZoneId[] = ['top', 'bottom', 'left', 'right'];


  const normalizeType = (raw: unknown, _zone: LayoutZoneId): ModuleDisplayMode | null => {
    if (typeof raw !== 'string') return null;

    const t = raw.trim();

    switch (t) {
      case 'floating':
        return 'floating';
      case 'drawer':
        return 'drawer';
      case 'modal':
        return 'modal';
      case 'panel-bottom':
        return 'panel-bottom';
      case 'panel-top':
        return 'panel-top';
      case 'panel-left':
        return 'panel-left';
      case 'panel-right':
        return 'panel-right';
      case 'popover':
        return 'popover';
      default:
        return null;
    }
  };


  zones.forEach((zone) => {
    const zoneConfig = (layoutConfig as any)[zone];
    if (!zoneConfig || typeof zoneConfig !== 'object') {
      return;
    }

    const items = (zoneConfig as any).items;
    if (!Array.isArray(items)) {
      return;
    }

    items.forEach((raw: any) => {
      if (!raw || typeof raw !== 'object') return;

      const kind = raw.kind as ModuleId | undefined;
      if (!kind) return;
      // Note: even if the zone is "inline", we still allow setting a display mode.
      // This enables configs like { type: 'modal' } for inline zones.
      const rawType = raw.type as unknown;
      const mode = normalizeType(rawType, zone);
      if (mode) {
        updateModuleDisplayMode(kind, mode);
      }

      const rawMountTarget = (raw as any).mountTarget;
      if (typeof rawMountTarget === 'string' && rawMountTarget.trim().length > 0) {
        updateModuleMountTarget(kind, rawMountTarget.trim());
      }
    });
  });
}

export interface AsukaLayoutItemRegistration {
  /** Module id associated with the layout block. */
  kind: ModuleId;
  /** Small label rendered under the icon in the sidebar/toolbar. */
  label?: string;
  /** Icon name or short string rendered for the block. */
  icon?: string;
  /** Preferred display mode when the module is opened from this block. */
  type?: ModuleDisplayMode | OverlayType;
  /** Optional menu-level engine filter. When omitted, the module definition controls compatibility. */
  supportedEngines?: AsukaRenderEngineKind[];
  /** Optional menu-level capability filter. When omitted, the module definition controls capability compatibility. */
  requiredCapabilities?: AsukaRenderEngineCapabilityKey[];
  /** Optional DOM selector used as the portal/mount target for this module overlay. */
  mountTarget?: string;
  /** Initial collapsed state for inline/panel-like renderers. */
  collapsed?: boolean;
  /** Raw render mode override used by panel zones or persistent page portals. */
  renderMode?: 'inline' | 'portal';
  /** Optional stable block id used for drag and persistence. */
  id?: string;
  /** Optional insertion index when adding the block programmatically. */
  index?: number;
}

/** Additional options accepted by {@link AsukaDesigner.registerModule}. */
export interface AsukaRegisterModuleOptions {
  /**
   * Also inject a matching menu item into the current layout.
   * Pass `true` to auto-create a minimal item from the module definition, or
   * provide a full registration object to control icon, display mode and placement.
   */
  addToLayout?: boolean | AsukaLayoutItemRegistration;
  /** Target layout zone used when `addToLayout` is enabled. Defaults to `left`. */
  layoutZone?: LayoutZoneId;
}

/** Runtime configuration used by `designer.api.setSnapping(...)`. */
export interface AsukaSnappingConfig {
  /** Enable or disable object snapping. */
  enabled?: boolean;
  /** Distance in pixels before snapping engages. */
  threshold?: number;
  /** Distance in pixels before snapping is released again. */
  release?: number;
  /** Which target family wins first when multiple snap targets are available. */
  priority?: 'zones-first' | 'objects-first';
}

/** Effective snapping state returned by `designer.api.getSnapping()`. */
export interface AsukaSnappingState {
  enabled: boolean;
  threshold: number;
  release: number;
  priority: 'zones-first' | 'objects-first';
}

/** Rectangular drawing zone definition used by the canvas API. */
export interface AsukaDrawingZone {
  id: string;
  x: number;
  y: number;
  width: number;
  height: number;
}

/** Payload accepted by `designer.api.addView(...)`. */
export interface AsukaViewCreatePayload {
  /** Human-readable view name shown in the UI. */
  name?: string;
  /** Export/canvas width. */
  width?: number;
  /** Export/canvas height. */
  height?: number;
  /** Dots per inch metadata used by exports and size conversions. */
  dpi?: number;
  /** Unit used by the view dimensions. */
  unit?: 'px' | 'cm' | 'mm' | 'in';
  /** Real-world extent represented by the view when no drawing area has its own calibration. */
  physicalSize?: AsukaPhysicalSize;
  /** Rendering engine used by this view. Defaults to the root engine. */
  engine?: AsukaRenderEngineKind;
  /** Initial 3D scene state for 3D views. */
  scene3d?: any;
}

/** Patch object accepted by `designer.api.updateView(...)`. */
export interface AsukaViewUpdatePatch extends AsukaViewCreatePayload {}

export interface AsukaResolvedBootPlan {
  mode: AsukaBootMode;
  state?: unknown;
}

/** Selection menu runtime controls exposed through {@link AsukaDesigner.layout}. */
export interface AsukaSelectionMenuApi {
  /** Replace the modules shown in the contextual selection menu. */
  setItems: (items: ModuleId[]) => void;
  /** Change the container used by the selection menu, usually `popover`. */
  setType: (type: ModuleDisplayMode | OverlayType) => void;
  /** Open the contextual selection menu. */
  open: () => void;
  /** Close the contextual selection menu. */
  close: () => void;
  /** Navigate directly to a module inside the contextual stack. */
  goTo: (id: ModuleId) => void;
  /** Navigate one step back inside the contextual stack. */
  back: () => void;
  /** Reset the selection menu stack to its initial state. */
  reset: () => void;
}

/** Overlay navigation helpers exposed through {@link AsukaDesigner.layout}. */
export interface AsukaLayoutOverlayApi {
  /** Open a module inside the current overlay stack. */
  push: (id: ModuleId) => void;
  /** Go back to the previous overlay entry. */
  back: () => void;
  /** Close the current overlay stack. */
  close: () => void;
}

/** Layout-level controls used to rearrange blocks and open modules. */
export interface AsukaLayoutApi {
  /** Open a module using its registered display mode. */
  openModule: (id: ModuleId) => void;
  /** Overlay stack controls used by floating modules and popovers. */
  overlay: AsukaLayoutOverlayApi;
  /** Move an existing layout block to another zone and optional index. */
  moveBlock: (blockId: string, targetZone: LayoutZoneId, index?: number) => void;
  /** Insert a new block in one of the layout zones. */
  addItem: (zone: LayoutZoneId, item: AsukaLayoutItemRegistration, index?: number) => void;
  /** Remove a layout block by its block id. */
  removeItem: (blockId: string) => void;
  /** Update an existing layout block in place. */
  updateItem: (blockId: string, patch: Partial<Omit<AsukaLayoutItemRegistration, 'id'>>) => void;
  /** Read the rendering style currently applied to a layout zone. */
  getZoneType: (zone: LayoutZoneId) => 'default' | 'panels' | 'inline';
  /** Change the rendering style used by a layout zone. */
  setZoneType: (zone: LayoutZoneId, type: 'default' | 'panels' | 'inline') => void;
  /** Read whether a layout zone is currently collapsed. */
  getZoneCollapsed: (zone: LayoutZoneId) => boolean;
  /** Force a layout zone to collapsed/opened state. */
  setZoneCollapsed: (zone: LayoutZoneId, value: boolean) => void;
  /** Toggle the collapsed state of a layout zone. */
  toggleZoneCollapsed: (zone: LayoutZoneId) => void;
  /** Read whether an individual block is collapsed. */
  getBlockCollapsed: (blockId: string) => boolean;
  /** Force an individual block to collapsed/opened state. */
  setBlockCollapsed: (blockId: string, value: boolean) => void;
  /** Force whether an individual block can be collapsed. */
  setBlockCollapsible: (blockId: string, value: boolean) => void;
  /** Toggle the collapsed state of an individual block. */
  toggleBlockCollapsed: (blockId: string) => void;
  /** Contextual selection menu controls. */
  selectionMenu: AsukaSelectionMenuApi;
}

/** Engine-neutral product operations exposed through {@link AsukaDesigner.api}.
 *
 * Renderer-specific capabilities remain grouped on `designer.engine`, for
 * example `canvas2d`, `products`, `materials`, or `surface`.
 */
export interface AsukaDesignerApi {
  /** Return objects from the active render engine. */
  getObjects: () => AsukaObjectSummary[];
  /** Preferred engine-neutral object registry list used by generic modules. */
  listObjects: (options?: Record<string, unknown>) => AsukaObjectSummary[];
  /** Return one object from the active engine registry. */
  getObject: (id: string) => AsukaObjectSummary | null;
  /** Return selected objects from the active engine registry. */
  getSelectedObjects: () => AsukaObjectSummary[];
  /** Return the active render engine scene graph when available. */
  getSceneGraph: () => AsukaSceneGraphNode[];
  /** Add an image to the active engine. */
  addImage: (input: AsukaImageInput) => AsukaMutationPromise<AsukaObjectRef>;
  /** Add a text object/sticker to the active engine. */
  addText: (input: AsukaTextInput) => AsukaMutationPromise<AsukaObjectRef>;
  /** Add a design/SVG/image design to the active engine. */
  addDesign: (input: AsukaDesignInput) => AsukaMutationPromise<AsukaObjectRef>;
  /** Add an element to the active engine. */
  addElement: (input: AsukaElementInput) => AsukaMutationPromise<AsukaObjectRef>;
  /** Apply one element variation and synchronize every linked 2D/3D view. */
  applyElementVariation: (input: AsukaElementVariationApplyInput) => Promise<boolean>;
  /** Remove an object from the active engine. */
  removeObject: AsukaRenderEngine['objects']['remove'];
  /** Patch an object in the active engine. */
  updateObject: AsukaRenderEngine['objects']['update'];
  /** Duplicate an object in the active engine. */
  duplicateObject: AsukaRenderEngine['objects']['duplicate'];
  /** Set object visibility through the active engine. */
  setObjectVisible: AsukaRenderEngine['objects']['setVisible'];
  /** Set object lock state through the active engine. */
  setObjectLocked: AsukaRenderEngine['objects']['setLocked'];
  /** Move object one step toward the front in the active engine. */
  bringObjectForward: AsukaRenderEngine['objects']['bringForward'];
  /** Move object one step toward the back in the active engine. */
  sendObjectBackward: AsukaRenderEngine['objects']['sendBackward'];
  /** Read normalized transform values from the active engine. */
  getObjectTransform: (id: string) => AsukaObjectTransform | null;
  /** Patch normalized transform values through the active engine. */
  updateObjectTransform: AsukaRenderEngine['objects']['updateTransform'];
  /** Inspect the current cross-renderer placement lifecycle. */
  getPlacementStatus: () => AsukaPlacementStatus | null;
  /** Start a placement lifecycle without exposing native renderer objects. */
  beginPlacement: (input: AsukaPlacementBeginInput) => AsukaPlacementStatus;
  /** Report a renderer-owned placement candidate and its validity. */
  updatePlacement: (input: AsukaPlacementUpdateInput) => AsukaPlacementStatus;
  /** Commit the current candidate or its last valid fallback. */
  commitPlacement: (metadata?: Record<string, unknown>) => AsukaPlacementStatus | null;
  /** Cancel placement and resolve to its initial transform. */
  cancelPlacement: (metadata?: Record<string, unknown>) => AsukaPlacementStatus | null;
  /** Measure a layer in renderer-native, relative and real-world units. */
  measureLayer: (id: string, options?: AsukaLayerMeasurementOptions) => AsukaLayerMeasurement | null;
  /** Resize a calibrated layer using real-world dimensions and reject print-area overflow. */
  resizeLayerPhysical: (id: string, input: AsukaPhysicalLayerResizeInput) => Promise<AsukaLayerMeasurement>;
  /** Export calibrated areas and exact layer dimensions for a production workflow. */
  exportProductionMeasurements: () => AsukaProductionMeasurementExport;
  /** Select an object in the active engine. */
  selectObject: (id: string | null) => void;
  /** Camera/canvas zoom helpers delegated to the active engine. */
  zoomIn: () => void;
  zoomOut: () => void;
  fitView: () => void;
  /** Read and mutate history through the same asynchronous renderer contract. */
  getHistoryInfo: AsukaRenderEngine['history']['getInfo'];
  commitHistorySnapshot: AsukaRenderEngine['history']['commit'];
  undo: AsukaRenderEngine['history']['undo'];
  redo: AsukaRenderEngine['history']['redo'];
  reset: AsukaRenderEngine['history']['reset'];
  goToHistory: AsukaRenderEngine['history']['goTo'];
  /** Export the active engine as a PNG blob when supported. */
  exportPng: (options?: AsukaExportOptions) => Promise<Blob>;
  /** Download an export target through the active renderer. */
  download: (target?: unknown, options?: AsukaDownloadOptions) => Promise<void> | void;
  /** Return whether the active renderer supports an export format. */
  isExportFormatAvailable: (format: string, options?: Record<string, unknown>) => boolean;
  /** Load optional support required by an export format. */
  ensureExportSupport: (format: string, options?: Record<string, unknown>) => Promise<boolean> | boolean;
  /** Return every registered view. */
  getViews: () => AsukaState['views'];
  /** Return the id of the active view. */
  getActiveViewId: () => AsukaState['activeViewId'];
  /** Switch the editor to another view. */
  setActiveView: (id: string) => Promise<void>;
  /** Create a new view from a name or a full payload. */
  addView: (payload?: string | AsukaViewCreatePayload) => string;
  /** Update view metadata such as size, unit or label. */
  updateView: (id: string, patch: AsukaViewUpdatePatch) => void;
  /** Remove a view by id. */
  removeView: (id: string) => void;
  /** Return the font registry available to text-related modules. */
  getFonts: () => AsukaRegisteredFont[];
  /** Register a system font family in the runtime registry. */
  addSystemFont: (payload: string | AsukaSystemFontConfig) => string;
  /** Register a Google Font in the runtime registry and optionally load it. */
  addGoogleFont: (payload: string | AsukaGoogleFontConfig) => Promise<string>;
  /** Register a custom web font in the runtime registry and optionally load it. */
  addCustomFont: (payload: AsukaCustomFontConfig) => Promise<string>;
  /** Remove a font from the runtime registry. */
  removeFont: (id: string) => void;
  /** Load a font that is already present in the runtime registry. */
  loadFont: (id: string) => Promise<boolean>;
  /** Pricing helpers remain engine-aware through the active runtime/store. */
  getPrice: (externalPrice?: AsukaPriceInput) => number;
  getPriceUiBreakdown: (externalPrice?: AsukaPriceInput) => PriceUiBreakdown;
  getStandardPrices: (objects?: unknown[]) => number;
  getPricingRules: (baseTotal?: number) => number;
  recalcPricing: () => void;
}

/**
 * Generic spatial-assembly operations exposed through {@link AsukaDesigner.assembly}.
 *
 * The contract uses serializable transforms, anchors, rules, BOM lines and
 * validation results. Native Babylon objects never cross this boundary.
 */
export interface AsukaAssemblyApi {
  /** Return the persisted plan, snapping settings and current connections. */
  getState: AsukaRenderEngineAssembly['getState'];
  /** Return the active mouse construction tool and selected surface. */
  getBuildState: AsukaRenderEngineAssembly['getBuildState'];
  /** Return the active novice-friendly placement tool. */
  getInteractionState: AsukaRenderEngineAssembly['getInteractionState'];
  /** Enter or leave the persistent guided construction session. */
  setConstructionMode: AsukaRenderEngineAssembly['setConstructionMode'];
  /** Enable regular drag placement without requiring the Ctrl modifier. */
  setMoveTool: AsukaRenderEngineAssembly['setMoveTool'];
  /** Configure the room/plan and placement policies for the active 3D view. */
  configure: AsukaRenderEngineAssembly['configure'];
  /** List the floors and walls explicitly created in build mode. */
  listSurfaces: AsukaRenderEngineAssembly['listSurfaces'];
  /** Measure one floor outline in renderer world units. */
  getSurfaceMetrics: AsukaRenderEngineAssembly['getSurfaceMetrics'];
  /** Read the absolute center and orientation of one floor or wall. */
  getSurfaceTransform: AsukaRenderEngineAssembly['getSurfaceTransform'];
  /** Select one floor or wall for appearance editing. */
  selectSurface: AsukaRenderEngineAssembly['selectSurface'];
  /** Start a mouse tool for selecting, drawing or erasing surfaces. */
  startDrawing: AsukaRenderEngineAssembly['startDrawing'];
  /** Stop the active mouse construction tool. */
  cancelDrawing: AsukaRenderEngineAssembly['cancelDrawing'];
  /** Update geometry or appearance for one floor or wall. */
  updateSurface: AsukaRenderEngineAssembly['updateSurface'];
  /** Move or rotate a complete floor or wall without changing its dimensions. */
  transformSurface: AsukaRenderEngineAssembly['transformSurface'];
  /** Set one floor edge length while keeping its direction and stable corner ids. */
  setFloorEdgeLength: AsukaRenderEngineAssembly['setFloorEdgeLength'];
  /** Set one floor edge angle while preserving its length. */
  setFloorEdgeAngle: AsukaRenderEngineAssembly['setFloorEdgeAngle'];
  /** Lock or unlock the exact length and angle of one stable floor edge. */
  setFloorEdgeConstraint: AsukaRenderEngineAssembly['setFloorEdgeConstraint'];
  /** Add or remove the wall linked to one stable room edge. */
  setRoomWallEnabled: AsukaRenderEngineAssembly['setRoomWallEnabled'];
  /** List built-in and user-created reusable surface materials. */
  listMaterialPresets: AsukaRenderEngineAssembly['listMaterialPresets'];
  /** Create or replace a reusable surface material. */
  saveMaterialPreset: AsukaRenderEngineAssembly['saveMaterialPreset'];
  /** Remove a user-created reusable surface material. */
  removeMaterialPreset: AsukaRenderEngineAssembly['removeMaterialPreset'];
  /** Apply a reusable material to a face, surface or complete room. */
  applyMaterialPreset: AsukaRenderEngineAssembly['applyMaterialPreset'];
  /** Remove one floor or wall. */
  removeSurface: AsukaRenderEngineAssembly['removeSurface'];
  /** Remove every explicitly drawn floor and wall. */
  clearSurfaces: AsukaRenderEngineAssembly['clearSurfaces'];
  /** List every assembly-enabled Product 3D instance in the active view. */
  listComponents: AsukaRenderEngineAssembly['listComponents'];
  /** Return one assembly component by runtime product id. */
  getComponent: AsukaRenderEngineAssembly['getComponent'];
  /** Evaluate snapping, collisions, clearances and adjacency without mutating. */
  previewPlacement: AsukaRenderEngineAssembly['previewPlacement'];
  /** Place a component, applying the last valid transform when required. */
  place: AsukaRenderEngineAssembly['place'];
  /** Find the nearest compatible anchor or valid plan position automatically. */
  autoPlace: AsukaRenderEngineAssembly['autoPlace'];
  /** Create an explicit connection between two compatible anchors. */
  connect: AsukaRenderEngineAssembly['connect'];
  /** Remove one persisted anchor connection. */
  disconnect: AsukaRenderEngineAssembly['disconnect'];
  /** List vertical instance links in the active 3D view. */
  listStackLinks: AsukaRenderEngineAssembly['listStackLinks'];
  /** Center one component above another and keep their widths synchronized. */
  linkStack: AsukaRenderEngineAssembly['linkStack'];
  /** Remove a vertical instance link without deleting either component. */
  unlinkStack: AsukaRenderEngineAssembly['unlinkStack'];
  /** Validate the whole scene against collision, clearance and adjacency rules. */
  validate: AsukaRenderEngineAssembly['validate'];
  /** Build the normalized bill of materials for the current assembly. */
  getBom: AsukaRenderEngineAssembly['getBom'];
  /** Build a quote from BOM pricing and optional adjustments. */
  getQuote: AsukaRenderEngineAssembly['getQuote'];
  /** Export BOM CSV/JSON, quote JSON or a complete production package. */
  export: AsukaRenderEngineAssembly['export'];
}

/** Listener toggles and runtime settings exposed through {@link AsukaDesigner.util}. */
export interface AsukaListenersApi {
  /** Return the enabled/disabled map for every registered listener. */
  getEnabled: () => AsukaState['listenersEnabled'];
  /** Return whether a listener is currently enabled. */
  isEnabled: (id: AsukaListenerName) => boolean;
  /** Enable or disable a specific listener. */
  setEnabled: (id: AsukaListenerName, enabled: boolean) => void;
  /** Apply multiple listener enabled flags at once. */
  setMany: (patch: ListenerOptions) => void;
  /** Return advanced runtime settings used by listeners. */
  getSettings: () => AsukaState['listenerSettings'];
  /** Merge advanced runtime settings used by listeners. */
  setSettings: (patch: Record<string, unknown>) => void;
}

/** Internationalization helpers exposed through {@link AsukaDesigner.util}. */
export interface AsukaI18nApi {
  /** Translate a key using the active locale and fallback chain. */
  t: (key: string, params?: Record<string, string | number>) => string;
  /** Return the current locale. */
  getLocale: () => string;
  /** Change the current locale. */
  setLocale: (locale: string) => void;
  /** Add or replace a locale bundle at runtime. */
  addTranslations: (locale: string, bundle: Translations) => void;
  /** Return the fallback locale used for missing translations. */
  getFallbackLocale: () => string;
  /** Change the fallback locale used for missing translations. */
  setFallbackLocale: (locale: string) => void;
}

/** State import/export helpers exposed through {@link AsukaDesigner.util}. */
export interface AsukaStateApi {
  /** Export the complete serializable app state. */
  exportFull: () => AsukaExportedState;
  /** Import a complete serializable app state. */
  importFull: (state: AsukaExportedState, options?: AsukaImportOptions) => void;
  /** Export a platform-neutral product document that can move between integrations. */
  exportPortableProduct: (
    options: Omit<AsukaPortableProductCreateOptions, 'state'>,
  ) => AsukaPortableProductDocument;
  /** Import the canonical product state from a platform-neutral product document. */
  importPortableProduct: (
    document: AsukaPortableProductDocument | unknown,
    options?: AsukaImportOptions,
  ) => void;
  /** Create a complete checksummed `.asuka.zip` package, including resolvable media. */
  exportPortablePackage: (
    options: Omit<AsukaPortableProductCreateOptions, 'state'>
      & Omit<AsukaPortableProductPackageCreateOptions, 'document'>,
  ) => Promise<Uint8Array>;
  /** Read a complete package, publish its media, then restore its product state. */
  importPortablePackage: (
    input: Blob | ArrayBuffer | Uint8Array,
    options?: {
      publishAsset?: AsukaPortableAssetPublisher;
      state?: AsukaImportOptions;
    },
  ) => Promise<void>;
  /** Export one named part such as `template`, `theme`, `document`, or `pricing`. */
  exportPart: (part: AsukaStatePart) => AsukaExportedState;
  /** Import only one named part from a part export or a complete state payload. */
  importPart: (part: AsukaStatePart, state: AsukaExportedState, options?: AsukaImportOptions) => void;
  /** Save a canonical state payload to local storage. */
  saveToStorage: (state: AsukaExportedState, options?: { key?: string }) => void;
  /** Load a canonical state payload from local storage. */
  loadFromStorage: (options?: { key?: string }) => AsukaExportedState | null;
  /** Clear a previously persisted state payload from local storage. */
  clearStorage: (options?: { key?: string }) => void;
  /** Replace the current product/session with a complete state payload. */
  replaceProduct: (state: AsukaExportedState, options?: { recreate?: boolean }) => AsukaDesigner;
  /** Rebuild the active Fabric view from the current document store. */
  rebuildActiveViewFromDocument: (targetViewId?: string | null) => void;
  /** Convert init-time layout config into runtime layout state. */
  normalizeLayoutConfigToRuntimeState: (config: import('../types/layout').LayoutConfig, fallbackState?: any) => any;
  /** Convert runtime layout state back into init-time layout config. */
  extractRuntimeLayoutAsConfig: (layoutState?: any) => import('../types/layout').LayoutConfig;
}


/** Simple camera helpers exposed through `designer.util.camera`. */
export interface AsukaCameraApi {
  /** Read the active 3D camera. Returns null outside a mounted 3D view. */
  get: () => AsukaCameraView | null;
  /** Move the active 3D camera. */
  set: (view: Partial<AsukaCameraView>, options?: AsukaCameraMoveOptions) => AsukaCameraView | null;
  /** Read the active view orbit limits. */
  getLimits: () => AsukaCameraLimits | null;
  /** Update and persist the active view orbit limits. */
  setLimits: (limits: Partial<AsukaCameraLimits>) => AsukaCameraLimits | null;
  /** Read the camera preset stored on one variation. */
  getSaved: (elementId: string, variationId: string) => AsukaCameraView | null;
  /** Capture the current camera and save it on one variation. */
  save: (elementId: string, variationId: string) => AsukaCameraView | null;
  /** Remove the camera preset from one variation. */
  clear: (elementId: string, variationId: string) => boolean;
  /** Load the camera preset stored on one variation. */
  load: (elementId: string, variationId: string, options?: AsukaCameraMoveOptions) => AsukaCameraView | null;
}

/** Runtime rendering-engine helpers exposed through `designer.util.engine`. */
export interface AsukaEngineApi {
  /** Return the active view engine kind. */
  getKind: () => AsukaRenderEngineKind;
  /** Return the default engine used by new views. */
  getDefaultKind: () => AsukaRenderEngineKind;
  /** Change the default engine used by new views. */
  setDefaultKind: (kind: AsukaRenderEngineKind) => AsukaRenderEngineKind;
  /** Return the engine kind for a specific view, or the active view when omitted. */
  getViewKind: (viewId?: string | null) => AsukaRenderEngineKind;
  /** Change one view engine without changing the document content. */
  setViewKind: (viewId: string, kind: AsukaRenderEngineKind) => void;
  /** Return the common renderer contract for the active view. */
  getCurrent: () => AsukaRenderEngine;
  /** Return the common renderer contract for a specific view. */
  getForView: (viewId?: string | null) => AsukaRenderEngine;
  /** Return true when an engine of this kind currently has a mounted runtime. */
  isLoaded: (kind?: AsukaRenderEngineKind | null) => boolean;
  /** Return high-level capabilities exposed by the active engine. */
  getCapabilities: () => AsukaRenderEngineCapabilities;
}

/** Runtime helpers used to override module presentation. */
export interface AsukaModulesApi {
  /** Override the display mode of a module at runtime. */
  setDisplayMode: (id: ModuleId, mode: ModuleDisplayMode) => void;
  /** Clear the runtime display mode override of a module. */
  clearDisplayMode: (id: ModuleId) => void;
  /** Force a module to mount into a specific DOM target. */
  setMountTarget: (id: ModuleId, target: string | null) => void;
  /** Remove the runtime mount target override of a module. */
  clearMountTarget: (id: ModuleId) => void;
  /** Return the effective runtime display mode override, if any. */
  getDisplayMode: (id: ModuleId) => ModuleDisplayMode | null;
  /** Return the effective runtime mount target override, if any. */
  getMountTarget: (id: ModuleId) => string | null;
}

/** Runtime UI behavior helpers exposed through `designer.util.uiConfig`. */
export interface AsukaRuntimeUiApi {
  /** Return the normalized UI behavior config. */
  getConfig: () => NormalizedAsukaUiOptions;
  /** Replace the UI behavior config at runtime. */
  setConfig: (config: AsukaUiOptions | null | undefined) => NormalizedAsukaUiOptions;
  /** Configure the view pagination bar at runtime. */
  setViewsPagination: (config: NonNullable<AsukaUiOptions['viewsPagination']> | false) => NormalizedAsukaUiOptions['viewsPagination'];
  /** Show or hide the view pagination bar at runtime. */
  setViewsPaginationEnabled: (enabled: boolean) => NormalizedAsukaUiOptions['viewsPagination'];
  /** Configure the undo, redo and reset controls at runtime. */
  setHistoryControls: (config: NonNullable<AsukaUiOptions['historyControls']>) => NormalizedAsukaUiOptions['historyControls'];
  /** Show or hide all history controls at runtime. */
  setHistoryControlsEnabled: (enabled: boolean) => NormalizedAsukaUiOptions['historyControls'];
}

/** Runtime responsive helpers exposed through `designer.util.responsive`. */
export interface AsukaResponsiveApi {
  /** Return the current responsive breakpoint. */
  getBreakpoint: () => AsukaResponsiveBreakpoint;
  /** Return whether the current shell width is mobile. */
  isMobile: () => boolean;
  /** Return whether the current shell width is tablet. */
  isTablet: () => boolean;
  /** Return whether the current shell width is desktop. */
  isDesktop: () => boolean;
  /** Return the latest measured shell width in pixels. */
  getWidth: () => number;
  /** Return the normalized responsive config. */
  getConfig: () => NormalizedAsukaResponsiveConfig;
  /** Merge and apply a responsive config patch at runtime. */
  setConfig: (config: AsukaResponsiveConfig | null | undefined) => NormalizedAsukaResponsiveConfig;
}

export interface AsukaUploadsApi {
  /** Return the shared uploaded assets list shown by the Upload module. */
  list: () => UploadedAsset[];
  /** Manually register one uploaded asset in the shared Upload module list. */
  register: (input: RegisterUploadedAssetInput) => UploadedAsset;
  /** Remove one uploaded asset from the shared Upload module list. */
  remove: (id: string) => void;
  /** Clear the shared uploaded assets list. */
  clear: () => void;
}

export interface AsukaObservabilityApi {
  /** Whether a host error callback is currently enabled. */
  isEnabled: () => boolean;
  /** Forward a caught error to the same host callback used for global errors. */
  report: (error: unknown, context?: Record<string, unknown>) => AsukaObservabilityErrorRecord;
}

/** Generic Product 3D parameter controls exposed through `designer.util.parametric3d`. */
export interface AsukaParametric3DApi {
  /** Return the selected or requested Product 3D parameter descriptor. */
  get: (productId?: string | null) => AsukaParametricProductDescriptor | null;
  /** Normalize values and evaluate constraints without changing the product. */
  validate: (productId: string | null | undefined, values?: Record<string, unknown>) => AsukaParametricProductDescriptor | null;
  /** Change one parameter on a Product 3D instance. */
  set: (productId: string | null | undefined, parameterId: string, value: AsukaParametricValue, options?: AsukaParametricMutationOptions) => AsukaMutationPromise<AsukaParametricProductDescriptor | null> | null;
  /** Change several parameters atomically. */
  setMany: (productId: string | null | undefined, values: Record<string, unknown>, options?: AsukaParametricMutationOptions) => AsukaMutationPromise<AsukaParametricProductDescriptor | null> | null;
}

/** Utility namespace exposed through {@link AsukaDesigner.util}. */
export interface AsukaUtilApi {
  /** Read the full Zustand state. */
  getState: () => AsukaState;
  /** Subscribe to raw Zustand state changes. */
  subscribe: typeof useAsukaStore.subscribe;
  /** Enable/disable built-in runtime listeners and read their settings. */
  listeners: AsukaListenersApi;
  /** Internationalization helpers with fallback support. */
  i18n: AsukaI18nApi;
  /** Export and import serialized designer state. */
  state: AsukaStateApi;
  /** Runtime module presentation overrides. */
  modules: AsukaModulesApi;
  /** Runtime UI behavior helpers. */
  uiConfig: AsukaRuntimeUiApi;
  /** Camera controls and variation camera presets. */
  camera: AsukaCameraApi;
  /** Generic Product 3D parameter controls generated from the active product recipe. */
  parametric3d: AsukaParametric3DApi;
  /** Rendering-engine helpers. */
  engine: AsukaEngineApi;
  /** Responsive layout helpers. */
  responsive: AsukaResponsiveApi;
  /** Shared upload list helpers used by the Upload module. */
  uploads: AsukaUploadsApi;
  /** Host-controlled error reporting helpers. */
  observability: AsukaObservabilityApi;
  /** Runtime licensing helpers and entitlement checks. */
  license: AsukaLicenseApi;
  /** Agency-only helpers: branding, advanced permissions and white-label/export adapters. */
  agency: AsukaAgencyApi;
  /** AI helpers: text-to-image, background removal and AI credit helpers. */
  ai: AsukaAiApi;
  /** Paid export helpers: PDF credit consumption and server-side HD rendering. */
  exports: AsukaExportApi;
  /** Return the currently effective permissions. */
  getPermissions: () => AsukaPermissions;
  /** Return the currently active role. */
  getRole: () => AsukaRole;
  /** Switch the active role. */
  setRole: (role: AsukaRole) => void;
  /** Apply a view-specific permission override. */
  setForView: (viewId: string, patch: Partial<AsukaPermissions>) => void;
  /** Remove a view-specific permission override. */
  clearForView: (viewId: string) => void;
  /** Return the permission overrides applied to the active view. */
  getForActiveView: () => Partial<AsukaPermissions>;
  /** Test one permission flag against the current effective permission set. */
  can: (key: keyof AsukaPermissions) => boolean;
  /** Throw when a permission is not granted. */
  require: (key: keyof AsukaPermissions, payload?: unknown) => void;
  /** Merge new permission overrides into the current runtime state. */
  setPermissions: (patch: Partial<AsukaPermissions>) => void;
  /** Evaluate one supported pricing/operator helper. */
  operators: (operator: string, a: number, b: number) => boolean | number;
  /** Format a price using the active pricing format. */
  formatPrice: (amount: number | string, overrides?: Partial<PriceFormat>) => string;
}

export interface AsukaLicenseApi {
  /** Return the effective resolved license payload. */
  get: () => AsukaLicenseEntitlements;
  /** Return the low-level resolution metadata (source, signature validity...). */
  getResolution: () => AsukaLicenseResolutionResult;
  /** Return the static feature catalog used by the built-in commercial tiers. */
  getCatalog: () => typeof ASUKA_LICENSE_FEATURE_CATALOG;
  /** Return the static Starter / Pro / Agency plan catalog used by the plugin defaults. */
  getPlanCatalog: () => typeof ASUKA_LICENSE_PLAN_CATALOG;
  /** Return the list of currently enabled feature keys after plan defaults are merged. */
  getEnabledFeatures: () => string[];
  /** Return the current commercial limits payload when present. */
  getLimits: () => Record<string, unknown>;
  /** Return the current commercial usage counters payload when present. */
  getUsage: () => Record<string, unknown>;
  /** Return a normalized quota state for one known commercial counter. */
  getQuota: (key: AsukaCommercialQuotaKey) => { limit: number | null; used: number | null; remaining: number | null; reached: boolean; key: AsukaCommercialQuotaKey };
  /** Return the current commercial status string (`trial_active`, `active`, `grace`, `expired`, `revoked`, `invalid`). */
  getStatus: () => AsukaLicenseEntitlements['status'];
  /** Return the current client activation capabilities and which public input source is currently configured. */
  getActivationState: () => AsukaLicenseActivationState;
  /** Return whether the current license is usable (`trial_active`, `active`, or `grace` when still within grace). */
  isActive: () => boolean;
  /** Test one premium feature gate. */
  canUse: (feature: AsukaLicenseFeature | string) => boolean;
  /** Test one commercial plan level. */
  hasPlan: (plan: Extract<AsukaLicensePlan, 'starter' | 'pro' | 'agency'>) => boolean;
  /** Store a raw key client-side for the next refresh/activation. */
  setKey: (key: string | null | undefined) => void;
  /** Resolve a key or token immediately and apply the result to the runtime. */
  activate: (input?: AsukaLicenseActivateInput) => Promise<AsukaLicenseResolutionResult>;
  /** Re-run the configured license resolution flow. */
  refresh: () => Promise<AsukaLicenseResolutionResult>;
  /** Forget the current runtime input and optionally clear the signed cache, then fall back to free mode. */
  reset: (options?: { clearCache?: boolean }) => Promise<AsukaLicenseResolutionResult>;
  /** Clear the locally cached signed license token. */
  clearCache: () => void;
  /** Start the built-in license auto-refresh scheduler. */
  startAutoRefresh: () => void;
  /** Stop the built-in license auto-refresh scheduler. */
  stopAutoRefresh: () => void;
  /** Deactivate the current instance through the configured HTTP backend when available. */
  deactivate: () => Promise<boolean>;
  /** Load the lazily split premium runtime package immediately. */
  loadPremiumRuntime: () => Promise<void>;
}

/** Theme helpers exposed through {@link AsukaDesigner.theme}. */
export interface AsukaThemeApi {
  /** Override one theme token at runtime. */
  setColor: (token: ThemeToken, value: string) => boolean;
  /** Override multiple theme tokens at runtime. */
  setColors: (colors: Partial<ThemeColorMap>) => string[];
  /** Read one effective theme token value. */
  getColor: (token: ThemeToken) => string | null;
  /** Reset the theme, optionally starting from a named preset. */
  reset: (preset?: ThemePresetId) => ThemeColorMap;
  /** Reset one token to its preset/default value. */
  resetColor: (token: ThemeToken) => boolean;
  /** Reset every token in one section prefix such as `layout` or `text`. */
  resetSection: (section: string) => void;
  /** Apply one bundled preset by id. */
  applyPreset: (preset: ThemePresetId) => ThemeColorMap;
  /** Return the bundled preset identifiers. */
  getPresets: () => ThemePresetId[];
}

/**
 * Public runtime instance returned by {@link createAsukaDesigner}.
 *
 * It groups the API into focused namespaces:
 * - {@link AsukaDesigner.layout} for menus and layout blocks
 * - {@link AsukaDesigner.api} for canvas/document actions
 * - {@link AsukaDesigner.util} for state, i18n and permissions
 * - {@link AsukaDesigner.theme} for theme token overrides
 */
export interface AsukaDesigner {
  /** Root DOM element used by the designer instance. */
  rootElement: HTMLElement;

  /** Expose the zustand store for advanced integrations (read-only usage recommended). */
  store: typeof useAsukaStore;

  /** Vanilla-friendly UI helpers to build custom modules without importing every component manually. */
  ui: AsukaModuleUiHelpers;

  /** Register a custom module at runtime and optionally add it to the current layout. */
  registerModule: (definition: AsukaModuleDefinition, options?: AsukaRegisterModuleOptions) => void;

  /** Layout and contextual menu controls. */
  layout: AsukaLayoutApi;

  /** Canvas, views, exports, fonts and history helpers. */
  api: AsukaDesignerApi;

  /** Typed element-variation catalog and synchronized 2D/3D mutations. */
  variations: AsukaVariationsApi;

  /** Generic plans, anchors, snapping, validation, BOM and production exports for 3D assemblies. */
  assembly: AsukaAssemblyApi;

  /** Shorthand for `designer.api.measureLayer(...)`. */
  measureLayer: AsukaDesignerApi['measureLayer'];

  /** Active render-engine facade. Fabric by default, 3D when the active view uses the 3D engine. */
  engine: AsukaRenderEngine;

  /** State, permissions, translations and low-level runtime helpers. */
  util: AsukaUtilApi;

  /** Theme token helpers for live UI customization. */
  theme: AsukaThemeApi;

  /** Shorthand for `designer.theme.setColor(...)`. */
  setColor: (token: ThemeToken, value: string) => boolean;
  /** Shorthand for `designer.theme.getColor(...)`. */
  getColor: (token: ThemeToken) => string | null;
  /** Shorthand for `designer.theme.setColors(...)`. */
  setColors: (colors: Partial<ThemeColorMap>) => string[];
  /** Shorthand for `designer.theme.reset()`. */
  resetColors: (preset?: ThemePresetId) => ThemeColorMap;

  /** Unmount the UI and release global resources associated with the instance. */
  destroy: () => void;
}

/** Return the active low-level designer instance stored in Zustand. */
function getDesignerInstance(): DesignerInstance | null {
  return useAsukaStore.getState().designerInstance;
}


function normalizeLayoutItemSupportedEngines(value: any): AsukaRenderEngineKind[] | undefined {
  if (!Array.isArray(value)) return undefined;
  const engines = value
    .map((item: any) => String(item ?? '').trim().toLowerCase())
    .filter((item: string): item is AsukaRenderEngineKind => item === 'fabric' || item === '3d');
  return engines.length > 0 ? Array.from(new Set(engines)) : undefined;
}

function normalizeLayoutItemRequiredCapabilities(value: any): AsukaRenderEngineCapabilityKey[] | undefined {
  return normalizeRenderEngineCapabilityList(value);
}

function addLayoutItemToZone(zone: LayoutZoneId, item: AsukaLayoutItemRegistration, index?: number): void {
  const kind = item?.kind as ModuleId;
  if (!kind) return;

  (useAsukaStore as any).setState?.((state: any) => {
    const byZone = { ...(state?.blocksByZone ?? {}) } as Record<LayoutZoneId, any[]>;
    const current = Array.isArray(byZone[zone]) ? [...byZone[zone]] : [];

    const nextBlock = {
      id: item?.id ?? `block-${zone}-${String(kind)}-${Date.now()}`,
      kind,
      label: (typeof item?.label === 'string' && item.label.trim()) || String(kind),
      icon: (typeof item?.icon === 'string' && item.icon.trim()) || 'views',
      type: item?.type,
      supportedEngines: normalizeLayoutItemSupportedEngines((item as any)?.supportedEngines),
      requiredCapabilities: normalizeLayoutItemRequiredCapabilities((item as any)?.requiredCapabilities),
      mountTarget: typeof item?.mountTarget === 'string' && item.mountTarget.trim() ? item.mountTarget.trim() : undefined,
      collapsed: item?.collapsed,
      renderMode: item?.renderMode,
      order: 0,
    } as any;

    const existingIndex = current.findIndex((block) => String(block?.id) === String(nextBlock.id));
    if (existingIndex >= 0) current.splice(existingIndex, 1);

    const safeIndex = typeof index === 'number' && Number.isFinite(index)
      ? Math.max(0, Math.min(current.length, index))
      : current.length;

    current.splice(safeIndex, 0, nextBlock);
    byZone[zone] = current.map((block, blockIndex) => ({ ...block, order: blockIndex }));
    return { blocksByZone: byZone } as any;
  });

  const mode = normalizeLayoutItemTypeForModule(item?.type, zone);
  if (mode && kind) {
    try {
      updateModuleDisplayMode(kind, mode);
    } catch {
      // ignore display mode sync errors
    }
  }

  if (kind && Object.prototype.hasOwnProperty.call(item ?? {}, 'mountTarget')) {
    try {
      updateModuleMountTarget(kind, typeof item?.mountTarget === 'string' ? item.mountTarget : null);
    } catch {
      // ignore mount target sync errors
    }
  }
}

function removeLayoutItemById(blockId: string): void {
  if (!blockId) return;
  (useAsukaStore as any).setState?.((state: any) => {
    const byZone = { ...(state?.blocksByZone ?? {}) } as Record<LayoutZoneId, any[]>;
    for (const zone of ['top', 'bottom', 'left', 'right'] as LayoutZoneId[]) {
      const current = Array.isArray(byZone[zone]) ? [...byZone[zone]] : [];
      const filtered = current.filter((block) => String(block?.id) !== String(blockId));
      if (filtered.length !== current.length) {
        byZone[zone] = filtered.map((block, idx) => ({ ...block, order: idx }));
      }
    }
    return { blocksByZone: byZone } as any;
  });
}



function normalizeLayoutItemTypeForModule(
  rawType: unknown,
  zone: LayoutZoneId,
): ModuleDisplayMode | null {
  const value = typeof rawType === 'string' ? rawType.trim() : '';
  if (!value) return null;
  switch (value) {
    case 'floating':
    case 'modal':
    case 'drawer':
    case 'popover':
      return value as ModuleDisplayMode;
    case 'panel':
      return zone === 'bottom'
        ? 'panel-bottom'
        : zone === 'top'
          ? 'panel-top'
          : zone === 'left'
            ? 'panel-left'
            : 'panel-right';
    case 'panel-left':
    case 'panel-right':
    case 'panel-top':
    case 'panel-bottom':
      return value as ModuleDisplayMode;
    default:
      return null;
  }
}

function updateLayoutItemById(
  blockId: string,
  patch: Partial<Omit<AsukaLayoutItemRegistration, 'id'>>,
): void {
  if (!blockId || !patch) return;

  let updatedBlock: any = null;
  let updatedZone: LayoutZoneId | null = null;

  (useAsukaStore as any).setState?.((state: any) => {
    const byZone = { ...(state?.blocksByZone ?? {}) } as Record<LayoutZoneId, any[]>;
    const zones: LayoutZoneId[] = ['top', 'bottom', 'left', 'right'];
    let found: any = null;
    let foundZone: LayoutZoneId | null = null;
    let foundIndex = -1;

    for (const zone of zones) {
      const current = Array.isArray(byZone[zone]) ? [...byZone[zone]] : [];
      const needle = String(blockId);
      const index = current.findIndex((block) => {
        const blockIdValue = String(block?.id ?? '');
        const blockKindValue = String(block?.kind ?? '');
        return blockIdValue === needle || blockKindValue === needle;
      });
      if (index >= 0) {
        found = current[index];
        foundZone = zone;
        foundIndex = index;
        current.splice(index, 1);
        byZone[zone] = current.map((block, idx) => ({ ...block, order: idx }));
        break;
      }
    }

    if (!found || !foundZone) return {} as any;

    const targetZone = foundZone;
    const targetCurrent = Array.isArray(byZone[targetZone]) ? [...byZone[targetZone]] : [];
    const explicitOrder = Number.isFinite(Number((patch as any)?.order)) ? Number((patch as any).order) : null;
    const { order: _patchOrder, ...blockPatch } = patch as any;
    const nextBlock = {
      ...found,
      ...blockPatch,
      id: found.id,
      kind: (patch as any)?.kind ?? found.kind,
      label:
        typeof (patch as any)?.label === 'string'
          ? ((patch as any).label.trim() || found.label)
          : found.label,
      icon:
        typeof (patch as any)?.icon === 'string'
          ? ((patch as any).icon.trim() || found.icon)
          : found.icon,
      supportedEngines: Array.isArray((patch as any)?.supportedEngines)
        ? normalizeLayoutItemSupportedEngines((patch as any).supportedEngines)
        : found.supportedEngines,
      requiredCapabilities: Array.isArray((patch as any)?.requiredCapabilities)
        ? normalizeLayoutItemRequiredCapabilities((patch as any).requiredCapabilities)
        : found.requiredCapabilities,
    } as any;

    updatedBlock = nextBlock;
    updatedZone = targetZone;

    let insertIndex = targetCurrent.length;
    if (explicitOrder !== null) {
      insertIndex = Math.max(0, Math.min(targetCurrent.length, explicitOrder));
    } else if (targetZone === foundZone) {
      insertIndex = Math.max(0, Math.min(targetCurrent.length, foundIndex));
    }

    targetCurrent.splice(insertIndex, 0, nextBlock);
    byZone[targetZone] = targetCurrent.map((block, idx) => ({ ...block, order: idx }));

    return { blocksByZone: byZone } as any;
  });

  if (updatedBlock && updatedZone) {
    const mode = normalizeLayoutItemTypeForModule(updatedBlock.type, updatedZone);
    if (mode && updatedBlock.kind) {
      try {
        updateModuleDisplayMode(updatedBlock.kind as ModuleId, mode);
      } catch {
        // ignore display mode sync errors
      }
      const current = (useAsukaStore as any).getState?.();
      if ((current?.activeModuleId ?? null) === updatedBlock.kind) {
        try {
          (useAsukaStore as any).setState?.({ moduleOverlayType: mode } as any);
        } catch {
          // ignore overlay sync errors
        }
      }
    }

    if (updatedBlock.kind && Object.prototype.hasOwnProperty.call(patch as any, 'mountTarget')) {
      try {
        updateModuleMountTarget(updatedBlock.kind as ModuleId, typeof updatedBlock.mountTarget === 'string' ? updatedBlock.mountTarget : null);
      } catch {
        // ignore mount target sync errors
      }
    }
  }
}

/**
 * Create and mount a new AsukaDesigner instance.
 *
 * @param target CSS selector or existing DOM element where the designer should mount.
 * @param options Initial configuration for layout, permissions, integrations, fonts and listeners.
 * @returns The public runtime instance used to control the designer.
 */

const ASUKA_INSTANCE_REGISTRY = new WeakMap<HTMLElement, AsukaDesigner>();
let ACTIVE_ASUKA_INSTANCE: { rootElement: HTMLElement; instance: AsukaDesigner } | null = null;

function resolveAsukaMountTarget(target: string | HTMLElement): HTMLElement {
  if (typeof target === 'string') {
    const el = document.querySelector(target);
    if (!el) throw new Error(`[AsukaDesigner] Target not found: ${target}`);
    return el as HTMLElement;
  }
  if (!target) throw new Error('[AsukaDesigner] Target element is required');
  return target;
}

function isAsukaStatePayload(input: any): input is AsukaExportedState {
  return Boolean(input && typeof input === 'object' && (input as any).format === 'asuka-state');
}


function normalizeRenderEngineOptionsByKind(options: any, defaultKind: AsukaRenderEngineKind): AsukaRenderEngineOptionsByKind {
  const out: AsukaRenderEngineOptionsByKind = {};
  const assign = (kind: unknown, value: unknown) => {
    const normalized = normalizeRenderEngineKind(kind, defaultKind);
    if (!value || typeof value !== 'object') return;
    out[normalized] = { ...((out as any)[normalized] ?? {}), ...(value as any), kind: normalized } as any;
  };

  const engines = options?.engines;
  if (engines && typeof engines === 'object') {
    assign('fabric', (engines as any).fabric);
    assign('3d', (engines as any)['3d']);
  }

  if (options?.engine && typeof options.engine === 'object') {
    const kind = normalizeRenderEngineKind((options.engine as any).kind ?? (options.engine as any).engine, defaultKind);
    assign(kind, options.engine);
  }

  return out;
}

function getOrCreateLicenseInstanceId(storageKey = 'asuka-license-instance-id'): string {
  try {
    const storage = globalThis.localStorage;
    const existing = storage?.getItem(storageKey);
    if (existing && existing.trim()) return existing.trim();
    const generated = typeof crypto?.randomUUID === 'function'
      ? crypto.randomUUID()
      : `asuka-${Math.random().toString(36).slice(2)}-${Date.now()}`;
    storage?.setItem(storageKey, generated);
    return generated;
  } catch {
    return `asuka-${Math.random().toString(36).slice(2)}-${Date.now()}`;
  }
}


function normalizeInitialViewsForEngine(input: unknown, fallbackEngine: AsukaRenderEngineKind): any[] {
  if (!Array.isArray(input)) return [];
  return input.map((raw: any, index) => {
    const id = String(raw?.id ?? `view-${index + 1}`).trim() || `view-${index + 1}`;
    const name = String(raw?.name ?? `View ${index + 1}`).trim() || `View ${index + 1}`;
    const width = Math.max(1, Math.floor(Number(raw?.width ?? 800) || 800));
    const height = Math.max(1, Math.floor(Number(raw?.height ?? 600) || 600));
    const dpi = Math.max(10, Math.floor(Number(raw?.dpi ?? 96) || 96));
    const unit = raw?.unit === 'cm' || raw?.unit === 'mm' || raw?.unit === 'in' || raw?.unit === 'px' ? raw.unit : 'px';
    const engine = normalizeRenderEngineKind(raw?.engine, fallbackEngine);
    return { ...(raw && typeof raw === 'object' ? raw : {}), id, name, width, height, dpi, unit, engine };
  });
}

export function replaceAsukaDesigner(target: string | HTMLElement, stateOrOptions: AsukaExportedState | AsukaDesignerOptions, options: AsukaDesignerOptions = {}): AsukaDesigner {
  const root = resolveAsukaMountTarget(target);
  try { ASUKA_INSTANCE_REGISTRY.get(root)?.destroy?.(); } catch {}
  root.innerHTML = '';
  if (isAsukaStatePayload(stateOrOptions)) {
    return createAsukaDesigner(root, { ...options, initialState: stateOrOptions, boot: { mode: 'replace-product', state: stateOrOptions } });
  }
  return createAsukaDesigner(root, stateOrOptions as AsukaDesignerOptions);
}

export function prepareAsukaRendererForMount(
  defaultRenderEngineKind: AsukaRenderEngineKind,
  renderEngineOptionsByKind: AsukaRenderEngineOptionsByKind,
): void {
  try {
    const initialState = typeof (useAsukaStore as any).getInitialState === 'function'
      ? (useAsukaStore as any).getInitialState()
      : {};
    const initialHistoryInfo = initialState?.documentHistoryInfo ?? {};

    // The Zustand store is shared by successive designer instances. A product
    // replacement must therefore clear the complete renderable document before
    // the next engine mounts, otherwise a 2D product can inherit a previous 3D
    // scene (and vice versa) until the imported state is touched by the user.
    (useAsukaStore as any).setState?.({
      designerInstance: null,
      views: [],
      activeViewId: null,
      documentViews: [],
      documentDrawingAreas: [],
      documentViewportByView: {},
      documentSelection: { activeObjectIds: [], activeDrawingAreaId: null },
      documentObjectsByView: {},
      document3dScenesByView: {},
      document3dHistoryByView: {},
      documentFabricHistoryByView: {},
      documentHistoryInfo: {
        index: -1,
        length: 0,
        canUndo: false,
        canRedo: false,
        maxSnapshots: Number(initialHistoryInfo?.maxSnapshots ?? 8) || 8,
      },
      documentHistoryInfoByView: {},
      viewThumbnails: {},
      elements: [],
      elementSelection: null,
      elementEditor: null,
      elementVariationPicker: null,
      fixedSlots: [],
      fixedSlotsPlacing: null,
      fixedSlotActiveId: null,
      markerEditorState: null,
      price: Number(initialState?.price ?? 0) || 0,
      pricingRules: [],
      pricingTotals: {
        ...(initialState?.pricingTotals ?? { base: 0, standard: 0, rules: 0, total: 0, updatedAt: 0 }),
      },
      renderEngineOptionsByKind,
      activeRenderEngineKind: defaultRenderEngineKind,
    });
    (useAsukaStore.getState() as any).setRenderEngineOptionsByKind?.(renderEngineOptionsByKind);
    (useAsukaStore.getState() as any).setDefaultRenderEngineKind?.(defaultRenderEngineKind);
    setCurrentRenderEngineKind(defaultRenderEngineKind);
  } catch {}
}

export function createAsukaDesigner(target: string | HTMLElement, options?: AsukaDesignerOptions): AsukaDesigner {
  if (typeof window === 'undefined' || typeof document === 'undefined') {
    throw new Error('createAsukaDesigner must be used in a browser environment.');
  }

  const rootElement = resolveAsukaMountTarget(target);
  const mountedOnTarget = ASUKA_INSTANCE_REGISTRY.get(rootElement);
  if (mountedOnTarget) {
    mountedOnTarget.destroy();
    rootElement.innerHTML = '';
  }
  if (ACTIVE_ASUKA_INSTANCE && ACTIVE_ASUKA_INSTANCE.rootElement !== rootElement) {
    throw new Error(
      '[AsukaDesigner] Version 1 supports one mounted designer per browser context. '
      + 'Destroy the active instance before mounting another target.',
    );
  }
  const mode = (options as any)?.mode as ('dev' | 'prod' | undefined);
  const defaultRenderEngineKind = normalizeRenderEngineKind((options as any)?.engine, 'fabric');
  const renderEngineOptionsByKind = normalizeRenderEngineOptionsByKind(options as any, defaultRenderEngineKind);
  prepareAsukaRendererForMount(defaultRenderEngineKind, renderEngineOptionsByKind);
  const optionsArgSnapshot = { ...(options ?? {}) } as AsukaDesignerOptions;
  const instanceRef: { current: AsukaDesigner | null } = { current: null };
  let destroyed = false;
  let bootReadyTimer: ReturnType<typeof setTimeout> | null = null;
  const observabilityOptions = (((options as any)?.observability ?? {}) as AsukaObservabilityOptions);
  const observabilityEnabled = observabilityOptions.enabled !== false && typeof observabilityOptions.onError === 'function';
  const reportRuntimeError = (
    error: unknown,
    context: Record<string, unknown> = {},
    type: AsukaObservabilityErrorRecord['type'] = 'manual',
  ): AsukaObservabilityErrorRecord => {
    const normalized = error instanceof Error ? error : new Error(String(error ?? 'Unknown error'));
    const record: AsukaObservabilityErrorRecord = {
      type,
      message: normalized.message,
      ...(normalized.stack ? { stack: normalized.stack } : null),
      timestamp: new Date().toISOString(),
      context: { ...(observabilityOptions.context ?? {}), ...context },
    };
    if (!destroyed && observabilityEnabled) {
      try { observabilityOptions.onError?.(record); } catch {}
      try { emitAsukaEvent('after:runtime.error', { record }); } catch {}
    }
    return record;
  };
  const onRuntimeWindowError = (event: ErrorEvent) => {
    reportRuntimeError(event.error ?? event.message, { source: event.filename, line: event.lineno, column: event.colno }, 'error');
  };
  const onRuntimeUnhandledRejection = (event: PromiseRejectionEvent) => {
    reportRuntimeError(event.reason, { source: 'unhandledrejection' }, 'unhandledrejection');
  };
  if (observabilityEnabled && observabilityOptions.captureGlobalErrors !== false) {
    window.addEventListener('error', onRuntimeWindowError);
    window.addEventListener('unhandledrejection', onRuntimeUnhandledRejection);
  }
  try {
    asukaHistoryDebug('boot.createAsukaDesigner', {
      build: getAsukaHistoryDebugBuild(),
      defaultRenderEngineKind,
      hasInitialState: Boolean((options as any)?.initialState),
      views: Array.isArray((options as any)?.views) ? (options as any).views.length : undefined,
      note: 'If this log does not appear, the browser is not running this ZIP/build.',
    });
  } catch {}

  const debugOptions = (((options as any)?.debug ?? {}) as AsukaDebugOptions | undefined) ?? {};
  const debugLog = (channel: keyof AsukaDebugOptions, message: string, payload?: unknown) => {
    if (!(debugOptions as any)?.[channel]) return;
    if (typeof payload === 'undefined') void 0;
    else void 0;
  };

  const rawLicenseOptions = (((options as any)?.license ?? {}) as any) ?? {};
  const licenseOptions = normalizeLicenseOptions(rawLicenseOptions);
  const licenseDebugLog = (message: string, payload?: unknown) => {
    if (!licenseOptions.debug) return;
    if (typeof payload === 'undefined') void 0;
    else void 0;
  };

  let agencyRuntime: AsukaAgencyApi | null = null;
  let aiRuntime: AsukaAiApi | null = null;
  let exportRuntime: AsukaExportApi | null = null;

const normalizeLicenseDomainValue = (value: unknown): string | undefined => {
  if (typeof value !== 'string') return undefined;
  const trimmed = value.trim();
  if (!trimmed) return undefined;
  try {
    if (/^https?:\/\//i.test(trimmed)) return new URL(trimmed).hostname || undefined;
    return new URL(`http://${trimmed}`).hostname || trimmed.split(':')[0] || undefined;
  } catch {
    return trimmed.split('/')[0]?.split(':')[0] || undefined;
  }
};

  let runtimeLicenseInput: AsukaLicenseActivateInput = {
    key: licenseOptions.key,
    token: licenseOptions.token,
    domain: licenseOptions.domain,
    instanceId: licenseOptions.instanceId,
  };

  const normalizeRuntimeLicenseInput = (patch: Partial<AsukaLicenseActivateInput> = {}): Partial<AsukaLicenseActivateInput> => ({
    ...(Object.prototype.hasOwnProperty.call(patch, 'key')
      ? { key: typeof patch.key === 'string' ? (patch.key.trim() || undefined) : (patch.key === null ? undefined : patch.key) }
      : null),
    ...(Object.prototype.hasOwnProperty.call(patch, 'token')
      ? { token: patch.token ?? undefined }
      : null),
    ...(Object.prototype.hasOwnProperty.call(patch, 'domain')
      ? { domain: normalizeLicenseDomainValue(patch.domain) }
      : null),
    ...(Object.prototype.hasOwnProperty.call(patch, 'instanceId')
      ? { instanceId: typeof patch.instanceId === 'string' ? (patch.instanceId.trim() || undefined) : undefined }
      : null),
  });

  const setRuntimeLicenseInput = (patch: Partial<AsukaLicenseActivateInput> = {}) => {
    runtimeLicenseInput = {
      ...runtimeLicenseInput,
      ...normalizeRuntimeLicenseInput(patch),
    };
  };

  const resetRuntimeLicenseInput = () => {
    runtimeLicenseInput = {};
  };

  const getLicenseActivationState = (): AsukaLicenseActivationState => {
    const key = typeof runtimeLicenseInput.key === 'string' ? runtimeLicenseInput.key.trim() : '';
    const source = key ? 'key' : runtimeLicenseInput.token ? 'token' : 'none';
    return {
      hasResolver: !!resolvedLicenseHttpOptions,
      hasHttp: !!resolvedLicenseHttpOptions,
      hasKey: !!key,
      hasToken: !!runtimeLicenseInput.token,
      source,
      domain:
        normalizeLicenseDomainValue(runtimeLicenseInput.domain) ||
        normalizeLicenseDomainValue(licenseOptions.domain) ||
        window.location.hostname,
      instanceId:
        (typeof runtimeLicenseInput.instanceId === 'string' && runtimeLicenseInput.instanceId.trim()) ||
        (typeof licenseOptions.instanceId === 'string' && licenseOptions.instanceId.trim()) ||
        getOrCreateLicenseInstanceId(),
    };
  };

  const resolvedLicenseHttpOptions = licenseOptions.enabled ? licenseOptions.http : undefined;

  licenseDebugLog('Normalized public license options', {
    enabled: licenseOptions.enabled,
    hasKey: !!licenseOptions.key,
    hasToken: !!licenseOptions.token,
    hasHttp: !!resolvedLicenseHttpOptions,
    audience: licenseOptions.audience,
    channel: licenseOptions.channel,
  });

  const licenseManager = new LicenseManager({
    storageKey: undefined,
    audience: typeof licenseOptions.audience === 'string' && licenseOptions.audience.trim() ? licenseOptions.audience.trim() : undefined,
    http: resolvedLicenseHttpOptions,
    version: (import.meta as any)?.env?.PACKAGE_VERSION ?? undefined,
  });

  const resolveLicenseInput = (overrides?: Partial<AsukaLicenseActivateInput>) => {
    const next = {
      ...licenseOptions,
      ...runtimeLicenseInput,
      ...normalizeRuntimeLicenseInput(overrides ?? {}),
    } as any;
    return {
      token: typeof next.token === 'undefined' ? undefined : (next.token ?? undefined),
      key: typeof next.key === 'string' && next.key.trim() ? next.key.trim() : undefined,
      domain: normalizeLicenseDomainValue(next.domain) || window.location.hostname,
      instanceId:
        typeof next.instanceId === 'string' && next.instanceId.trim()
          ? next.instanceId.trim()
          : getOrCreateLicenseInstanceId(),
    };
  };

  const applyResolvedLicense = async (resolution: AsukaLicenseResolutionResult, token: any = null) => {
    if (destroyed) return;
    licenseDebugLog('applyResolvedLicense() start', {
      ok: resolution?.ok,
      source: resolution?.source,
      signatureValid: resolution?.signatureValid,
      plan: resolution?.payload?.plan,
      status: resolution?.payload?.status,
      active: resolution?.payload?.active,
      tokenPresent: !!token,
    });
    if (shouldLoadPremiumRuntime(resolution.payload)) {
      licenseDebugLog('applyResolvedLicense() premium runtime requested', {
        plan: resolution?.payload?.plan,
        status: resolution?.payload?.status,
        active: resolution?.payload?.active,
      });
      try { await ensurePremiumRuntimeLoaded(resolution.payload); } catch (_error) { void 0; }
    }
    if (destroyed) return;
    try { setCurrentLicenseResolution(resolution); } catch (error) { licenseDebugLog('applyResolvedLicense() setCurrentLicenseResolution() failed', error instanceof Error ? { message: error.message, stack: error.stack } : { value: String(error) }); }
    try {
      (useAsukaStore.getState() as any).setLicenseState?.({
        license: resolution.payload,
        resolution,
        token: token ?? null,
      });
    } catch (error) {
      licenseDebugLog('applyResolvedLicense() setLicenseState() failed', error instanceof Error ? { message: error.message, stack: error.stack } : { value: String(error) });
    }
    try {
      const appliedLicense = (((useAsukaStore.getState() as any).license) ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements;
      const appliedResolution = (((useAsukaStore.getState() as any).licenseResolution) ?? DEFAULT_LICENSE_RESOLUTION) as AsukaLicenseResolutionResult;
      licenseDebugLog('applyResolvedLicense() store snapshot after apply', {
        appliedPlan: appliedLicense?.plan,
        appliedStatus: appliedLicense?.status,
        appliedActive: appliedLicense?.active,
        appliedResolutionOk: appliedResolution?.ok,
        appliedResolutionSource: appliedResolution?.source,
        appliedResolutionSignatureValid: appliedResolution?.signatureValid,
      });
    } catch (error) {
      licenseDebugLog('applyResolvedLicense() store snapshot failed', error instanceof Error ? { message: error.message, stack: error.stack } : { value: String(error) });
    }
    try { notifyModuleRegistryChanged(); } catch (error) { licenseDebugLog('applyResolvedLicense() notifyModuleRegistryChanged() failed', error instanceof Error ? { message: error.message, stack: error.stack } : { value: String(error) }); }
    try { agencyRuntime?.refreshEntitlements?.(); } catch (error) { licenseDebugLog('applyResolvedLicense() agencyRuntime.refreshEntitlements() failed', error instanceof Error ? { message: error.message, stack: error.stack } : { value: String(error) }); }
    try { emitAsukaEvent('after:license.changed', { resolution }); } catch (error) { licenseDebugLog('applyResolvedLicense() emit after:license.changed failed', error instanceof Error ? { message: error.message, stack: error.stack } : { value: String(error) }); }
  };

  const initializeLicenseState = async (overrides?: Partial<AsukaLicenseActivateInput>) => {
    const input = resolveLicenseInput(overrides);
    licenseDebugLog('Initializing license state', { domain: input.domain, instanceId: input.instanceId, source: getLicenseActivationState().source });
    const resolution = await licenseManager.init(input);
    licenseDebugLog('initializeLicenseState() manager.init result', {
      ok: resolution?.ok,
      source: resolution?.source,
      signatureValid: resolution?.signatureValid,
      plan: resolution?.payload?.plan,
      status: resolution?.payload?.status,
      active: resolution?.payload?.active,
      managerHasRefreshToken: licenseManager.hasRefreshToken(),
      currentCompactTokenPresent: !!licenseManager.getCurrentCompactToken(),
    });
    await applyResolvedLicense(resolution, licenseManager.getCurrentCompactToken() ?? input.token ?? null);
    return resolution;
  };

  const refreshLicenseState = async (overrides?: Partial<AsukaLicenseActivateInput>) => {
    const input = resolveLicenseInput(overrides);
    if (!licenseManager.hasRefreshToken()) {
      licenseDebugLog('Skipping refresh because no license token is currently available; falling back to init.', { domain: input.domain, instanceId: input.instanceId });
      return await initializeLicenseState(overrides);
    }
    licenseDebugLog('Refreshing license state', { domain: input.domain, instanceId: input.instanceId, source: getLicenseActivationState().source });
    const resolution = await licenseManager.refresh(input);
    await applyResolvedLicense(resolution, licenseManager.getCurrentCompactToken() ?? input.token ?? null);
    return resolution;
  };

  const activateLicenseState = async (inputOverrides: AsukaLicenseActivateInput = {}) => {
    const normalizedInput = { ...inputOverrides } as AsukaLicenseActivateInput;
    if (Object.prototype.hasOwnProperty.call(inputOverrides, 'key') && typeof inputOverrides.key === 'string' && inputOverrides.key.trim()) {
      normalizedInput.token = null;
    }
    if (Object.prototype.hasOwnProperty.call(inputOverrides, 'token') && inputOverrides.token) {
      normalizedInput.key = null;
    }
    setRuntimeLicenseInput(normalizedInput);
    const resolvedInput = resolveLicenseInput();
    try { emitAsukaEvent('before:license.activate', { input: { ...resolvedInput, token: resolvedInput.token ? '[token]' : undefined } }); } catch {}
    try {
      licenseDebugLog('Activating license state', { source: getLicenseActivationState().source, hasKey: !!resolvedInput.key, hasToken: !!resolvedInput.token });
      const resolution = await initializeLicenseState();
      try { if (licenseOptions.refresh.enabled !== false && licenseManager.hasRefreshToken() && getLicenseActivationState().hasHttp) licenseRefreshScheduler.start(); } catch {}
      try { emitAsukaEvent('after:license.activate', { resolution }); } catch {}
      return resolution;
    } catch (error) {
      try { emitAsukaEvent('after:license.activateFailed', { error }); } catch {}
      throw error;
    }
  };

  const resetLicenseState = async (options: { clearCache?: boolean } = {}) => {
    resetRuntimeLicenseInput();
    if (options.clearCache !== false) {
      try { licenseManager.clearCache(); } catch {}
    }
    try { licenseRefreshScheduler.stop(); } catch {}
    const resolution = { ...DEFAULT_LICENSE_RESOLUTION, payload: DEFAULT_LICENSE, resolvedAt: new Date().toISOString() } as AsukaLicenseResolutionResult;
    await applyResolvedLicense(resolution, null);
    return resolution;
  };

  const licenseRefreshScheduler = createLicenseRefreshScheduler({
    enabled:
      licenseOptions.enabled !== false &&
      licenseOptions.refresh.enabled !== false &&
      !!(resolvedLicenseHttpOptions),
    intervalMs: Number(licenseOptions.refresh.intervalMs ?? 6 * 60 * 60 * 1000),
    onFocus: licenseOptions.refresh.onFocus !== false,
    onVisibilityChange: licenseOptions.refresh.onVisibilityChange !== false,
    refresh: async (reason) => {
      try { emitAsukaEvent('before:license.refresh', { reason }); } catch {}
      try {
        licenseDebugLog('Running scheduled license refresh', { reason, source: getLicenseActivationState().source });
        const resolution = await refreshLicenseState();
        try { emitAsukaEvent('after:license.refresh', { reason, resolution }); } catch {}
      } catch (error) {
        try { emitAsukaEvent('after:license.refreshFailed', { reason, error }); } catch {}
      }
    },
  });

  void applyResolvedLicense({ ...DEFAULT_LICENSE_RESOLUTION, payload: DEFAULT_LICENSE, resolvedAt: new Date().toISOString() }, null);
  if (!licenseOptions.enabled) {
    licenseDebugLog('License support disabled by configuration; runtime starts in free mode.');
  }

  try {
    initTheme((options as any)?.theme ?? {});
  } catch (_e) {
    void 0;
  }

  // i18n init (before UI render)
  try {
    const i18nOpt = (options as any)?.i18n ?? {};
    const locale = (i18nOpt.locale ?? (options as any)?.locale) as string | undefined;
    const fallbackLocale = (i18nOpt.fallbackLocale ?? (options as any)?.fallbackLocale) as string | undefined;
    const translations = (i18nOpt.translations ?? (options as any)?.translations) as Record<string, any> | undefined;
    initI18n({
      locale,
      fallbackLocale,
      translations,
      warnOnMissing: mode !== 'prod',
    });
  } catch (_e) {
    void 0;
  }

// Custom roles (boot-time)
try {
  const roles = (options as any)?.roles;
  if (roles && typeof roles === 'object') {
    for (const [name, patch] of Object.entries(roles)) {
      defineRole(String(name), (patch ?? {}) as any);
    }
  }
} catch {
  // ignore
}

const initialRole = ((options as any)?.defaultRole ?? (mode === 'prod' ? 'viewer' : 'dev')) as AsukaRole;
try { (useAsukaStore.getState() as any).setRole?.(initialRole as any); } catch {}

const basePermissions = mode === 'prod'
    ? mergePermissions(permissionsForRole(initialRole), PROD_PERMISSIONS)
    : permissionsForRole(initialRole);

  const permissions: AsukaPermissions = getEffectivePermissions(
    basePermissions,
    (options as any)?.permissions ?? null,
  );

  // Expose permissions to UI through the store
  try {
    useAsukaStore.getState().setPermissions?.(permissions as any);
  } catch {
    // ignore
  }

  try {
    configurePdfSupport((options as any)?.pdf ?? null);
  } catch (_e) {
    void 0;
  }

  // Integrations
  try {
    const integrations = (options as any)?.integrations;
    if (integrations) (useAsukaStore.getState() as any).setIntegrations?.(integrations);
  } catch (_e) {
    void 0;
  }

  // Global colors scope (designer-level)
  try {
    const scope = (options as any)?.colors?.scope;
    if (scope) (useAsukaStore.getState() as any).setColorScope?.(scope);
  } catch (_e) {
    void 0;
  }

  // Initial colors list (global store)
  try {
    const items = (options as any)?.colors?.items;
    if (Array.isArray(items)) (useAsukaStore.getState() as any).setColors?.(items);
  } catch (_e) {
    void 0;
  }

  // Initial printing methods (global store)
  try {
    const methods = (options as any)?.printingMethods;
    if (Array.isArray(methods)) (useAsukaStore.getState() as any).setPrintingMethods?.(methods);
  } catch (_e) {
    void 0;
  }

  // Pricing init
  try {
    const p = normalizePriceInput((options as any)?.price);
    if (typeof p === 'number') (useAsukaStore.getState() as any).setPrice?.(p);
  } catch (_e) {
    void 0;
  }

  try {
    const rules = (options as any)?.pricingRules;
    if (Array.isArray(rules)) (useAsukaStore.getState() as any).setPricingRules?.(rules);
  } catch (_e) {
    void 0;
  }

  try {
    const uiScaling = (options as any)?.uiScaling;
    if (uiScaling && typeof uiScaling === 'object') (useAsukaStore.getState() as any).setDefaults?.({ uiScaling });
  } catch (_e) {
    void 0;
  }

  try {
    const fmt = (options as any)?.priceFormat;
    if (fmt && typeof fmt === 'object') (useAsukaStore.getState() as any).setDefaults?.({ priceFormat: fmt });
  } catch (_e) {
    void 0;
  }


  try {
    (useAsukaStore.getState() as any).setUiConfig?.((options as any)?.ui ?? null);
    (useAsukaStore.getState() as any).setResponsiveConfig?.((options as any)?.responsive ?? DEFAULT_RESPONSIVE_CONFIG);
  } catch (_e) {
    void 0;
  }

  if (options && (options as any).layout) {
    const iconOverrides = ((options as any)?.layout?.iconOverrides ?? (options as any)?.icons) as Record<string, any> | undefined;
    const layoutWithRegisteredModules = registerInlineLayoutModules((options as any).layout);
    const layoutConfig = preprocessLayoutIcons(layoutWithRegisteredModules, iconOverrides);
    const state = useAsukaStore.getState() as any;
    const initLayout = state.initializeLayoutFromConfig;
    if (typeof initLayout === 'function') {
      try {
        initLayout(layoutConfig);
      } catch (_e) {
        void 0;
      }
    }
    try {
      applyLayoutModuleDisplayModes(layoutConfig);
    } catch (_e) {
      void 0;
    }

    // Selection menu config (layout.selectionMenu)
    try {
      const sel = (layoutConfig as any)?.selectionMenu;
      if (sel && typeof sel === 'object') {
        const state = useAsukaStore.getState() as any;
        if (Array.isArray(sel.items)) {
          state.setSelectionMenuItems?.(sel.items as ModuleId[]);
        }
        if (typeof sel.type === 'string') {
          state.setSelectionMenuType?.(sel.type);
        }
        // optional: start closed
        if (sel.open === false) {
          state.closeSelectionMenu?.();
        }
      }
    } catch {
      // ignore
    }
  }

  try {
    applyRuntimeIconOverrides((((options as any)?.layout?.iconOverrides ?? (options as any)?.icons) as Record<string, any> | undefined));
  } catch (_e) {
    void 0;
  }


const bootStateRestore = (() => {
  const boot = ((options as any)?.boot ?? null) as AsukaBootOptions | null;
  const initialState = (options as any)?.initialState;
  const state = typeof boot?.state !== 'undefined'
    ? boot.state
    : initialState;
  if (typeof state === 'undefined') {
    debugLog('boot', 'resolved boot plan', { mode: 'none' });
    return null;
  }
  const plan: AsukaResolvedBootPlan = {
    mode: (boot?.mode ?? 'initialState') as AsukaBootMode,
    state,
  };
  debugLog('boot', 'resolved boot plan', plan);
  return plan;
})();



  try {
    const initialViewsFromOptions = normalizeInitialViewsForEngine((options as any)?.views, defaultRenderEngineKind);
    if (!bootStateRestore && initialViewsFromOptions.length > 0) {
      const activeViewId = initialViewsFromOptions[0]?.id ?? null;
      const viewportByView = Object.fromEntries(initialViewsFromOptions.map((view: any) => [String(view.id), { zoom: 1, panX: 0, panY: 0 }]));
      const activeKind = resolveViewEngineKind(initialViewsFromOptions[0], defaultRenderEngineKind);
      (useAsukaStore as any).setState?.({
        views: initialViewsFromOptions,
        documentViews: initialViewsFromOptions,
        activeViewId,
        documentViewportByView: viewportByView,
        activeRenderEngineKind: activeKind,
      });
      setCurrentRenderEngineKind(activeKind);
    }
  } catch {}

  try {
    const bootState = useAsukaStore.getState() as any;
    bootState.setBootReady?.(false);
    bootState.setBootStatus?.('Preparing editor…');
  } catch {}

  const shellOptions = ((options as any)?.shell ?? {}) as any;
  render(h(AsukaRoot, {
    width: shellOptions?.width ?? null,
    height: shellOptions?.height ?? null,
    maxWidth: shellOptions?.maxWidth ?? null,
    maxHeight: shellOptions?.maxHeight ?? null,
  } as any), rootElement);

  let licenseBootPromise: Promise<void> | null = null;
  if (licenseOptions.enabled) {
    try { (useAsukaStore.getState() as any).setBootStatus?.('Preparing your workspace…'); } catch {}
    licenseBootPromise = initializeLicenseState()
      .then(() => {
        try { if (!destroyed && licenseOptions.refresh.enabled !== false && licenseManager.hasRefreshToken()) licenseRefreshScheduler.start(); } catch {}
      })
      .catch((error) => {
        reportRuntimeError(error, { phase: 'license.boot' });
      });
  }

function isDenied(key: keyof AsukaPermissions, payload: any): boolean {
      const s = useAsukaStore.getState() as any;
      const effectivePermissions: AsukaPermissions = getEffectivePermissions(
        s.permissions as AsukaPermissions,
        (s.permissionsByView?.[s.activeViewId ?? ''] ?? null) as any,
      );
      // Special case: per-module open override
      if (key === 'canOpenModule' && payload?.id) {
        try {
          if (canOpenModuleId(effectivePermissions, payload.id)) return false;
        } catch {}
      } else {
        if ((effectivePermissions as any)[key]) return false;
      }

      const reason = payload?.reason ?? `Permission denied: ${String(key)}`;
  const ok = emitAsukaEventCancelable('before:permission.denied', { key, reason, ...payload });
  if (!ok) return false; // listener chose to override denial
  emitAsukaEvent('after:permission.denied', { key, reason, ...payload });
  return true;
}

  const layout = {
    openModule: (id: ModuleId) => {
      if (isDenied('canOpenModule', { action: 'layout.openModule', id, reason: `Cannot open module: ${id}` })) return;
      useAsukaStore.getState().openModule(id);
    },

/**
     * Generic internal navigation API for module overlays (F8).
     * It allows pushing a module into the current overlay stack and navigating back.
     */
    overlay: {
      push: (id: ModuleId) => {
        if (isDenied('canOpenModule', { action: 'layout.overlay.push', id, reason: `Cannot open module: ${id}` })) return;
        useAsukaStore.getState().pushModuleOverlay(id);
      },
      back: () => {
        useAsukaStore.getState().backModuleOverlay();
      },
      close: () => {
        useAsukaStore.getState().closeModule();
      },
    },
    moveBlock: (blockId: string, targetZone: LayoutZoneId, index?: number) => {
      if (isDenied('canMovePanels', { action: 'layout.moveBlock', blockId, targetZone, index, reason: 'Cannot move panels' })) return;
      useAsukaStore.getState().moveBlock(blockId, targetZone, index);
    },
    addItem: (zone: LayoutZoneId, item: AsukaLayoutItemRegistration, index?: number) => {
      if (isDenied('canMovePanels', { action: 'layout.addItem', zone, item, index, reason: 'Cannot edit layout items' })) return;
      addLayoutItemToZone(zone, item, index);
      emitAfter('layout.blockAdded', { zone, item, index });
    },
    removeItem: (blockId: string) => {
      if (isDenied('canMovePanels', { action: 'layout.removeItem', blockId, reason: 'Cannot edit layout items' })) return;
      removeLayoutItemById(blockId);
      emitAfter('layout.blockRemoved', { blockId });
    },
    updateItem: (blockId: string, patch: Partial<Omit<AsukaLayoutItemRegistration, 'id'>>) => {
      if (isDenied('canMovePanels', { action: 'layout.updateItem', blockId, patch, reason: 'Cannot edit layout items' })) return;
      updateLayoutItemById(blockId, patch);
      emitAfter('layout.blockUpdated', { blockId, patch });
    },
    getZoneType: (zone: LayoutZoneId) => {
      const state = useAsukaStore.getState() as any;
      return (state.zoneType?.[zone] ?? 'default') as 'default' | 'panels' | 'inline';
    },
    setZoneType: (zone: LayoutZoneId, type: 'default' | 'panels' | 'inline') => {
      if (isDenied('canMovePanels', { action: 'layout.setZoneType', zone, type, reason: 'Cannot edit layout zones' })) return;
      useAsukaStore.getState().setZoneType(zone, type);
      emitAfter('layout.zoneTypeChanged', { zone, type });
    },
    getZoneCollapsed: (zone: LayoutZoneId) => {
      const state = useAsukaStore.getState() as any;
      return Boolean(state.zoneCollapsed?.[zone]);
    },
    setZoneCollapsed: (zone: LayoutZoneId, value: boolean) => {
      if (isDenied('canCollapsePanels', { action: 'layout.setZoneCollapsed', zone, value, reason: 'Cannot collapse layout zones' })) return;
      useAsukaStore.getState().setZoneCollapsed(zone, value);
      emitAfter('layout.zoneCollapsedChanged', { zone, value: !!value });
    },
    toggleZoneCollapsed: (zone: LayoutZoneId) => {
      if (isDenied('canCollapsePanels', { action: 'layout.toggleZoneCollapsed', zone, reason: 'Cannot collapse layout zones' })) return;
      const state = useAsukaStore.getState() as any;
      const next = !state.zoneCollapsed?.[zone];
      useAsukaStore.getState().toggleZoneCollapsed(zone);
      emitAfter('layout.zoneCollapsedChanged', { zone, value: next, toggled: true });
    },
    getBlockCollapsed: (blockId: string) => {
      const state = useAsukaStore.getState() as any;
      return Boolean(state.collapsedBlocks?.[blockId]);
    },
    setBlockCollapsed: (blockId: string, value: boolean) => {
      if (isDenied('canCollapsePanels', { action: 'layout.setBlockCollapsed', blockId, value, reason: 'Cannot collapse layout blocks' })) return;
      (useAsukaStore.getState() as any).setBlockCollapsed?.(blockId, !!value);
      emitAfter('layout.blockCollapsedChanged', { blockId, value: !!value });
    },
    setBlockCollapsible: (blockId: string, value: boolean) => {
      if (isDenied('canMovePanels', { action: 'layout.setBlockCollapsible', blockId, value, reason: 'Cannot edit layout blocks' })) return;
      (useAsukaStore.getState() as any).setBlockCollapsible?.(blockId, !!value);
      emitAfter('layout.blockCollapsibleChanged', { blockId, value: !!value });
    },
    toggleBlockCollapsed: (blockId: string) => {
      if (isDenied('canCollapsePanels', { action: 'layout.toggleBlockCollapsed', blockId, reason: 'Cannot collapse layout blocks' })) return;
      const state = useAsukaStore.getState() as any;
      const next = !state.collapsedBlocks?.[blockId];
      (useAsukaStore.getState() as any).toggleBlockCollapsed?.(blockId);
      emitAfter('layout.blockCollapsedChanged', { blockId, value: next, toggled: true });
    },
    selectionMenu: {
      setItems: (items: ModuleId[]) => {
        if (isDenied('canMovePanels', { action: 'layout.selectionMenu.setItems', items, reason: 'Cannot edit selection menu' })) return;
        (useAsukaStore.getState() as any).setSelectionMenuItems?.(items);
      },
      setType: (type: any) => {
        if (isDenied('canMovePanels', { action: 'layout.selectionMenu.setType', type, reason: 'Cannot edit selection menu' })) return;
        (useAsukaStore.getState() as any).setSelectionMenuType?.(type);
      },
      open: () => {
        (useAsukaStore.getState() as any).openSelectionMenu?.();
      },
      close: () => {
        (useAsukaStore.getState() as any).closeSelectionMenu?.();
      },
      goTo: (id: ModuleId, options?: { type?: any }) => {
        (useAsukaStore.getState() as any).selectionMenuGoTo?.(id, options);
      },
      back: () => {
        (useAsukaStore.getState() as any).selectionMenuBack?.();
      },
      reset: () => {
        (useAsukaStore.getState() as any).resetSelectionMenuNav?.();
      },
    },
  };


  const getActiveRenderEngineFacade = (): AsukaRenderEngine => createLiveModuleRenderEngine();

  const engine: AsukaRenderEngine = createLiveModuleRenderEngine();

  const assertApiAllowed = (permission: keyof AsukaPermissions, payload: Record<string, unknown>) => {
    if (isDenied(permission, payload)) {
      throw new AsukaRendererError('UNSUPPORTED_OPERATION', `[AsukaDesigner] Permission denied: ${String(permission)}`, {
        engine: getActiveRenderEngineFacade().kind,
        operation: String(payload.action ?? 'api.mutation'),
        details: { permission },
      });
    }
  };

  const assertLicensedFeature = (feature: AsukaLicenseFeature, operation: string) => {
    const license = ((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements;
    if (canUseFeature(license, feature)) return;
    throw new AsukaRendererError(
      'UNSUPPORTED_OPERATION',
      `[AsukaDesigner] The ${feature} feature requires an active license.`,
      {
        engine: getActiveRenderEngineFacade().kind,
        operation,
        details: { feature },
      },
    );
  };

  const resolveLayerMeasurementOptions = (input: AsukaLayerMeasurementOptions = {}): AsukaLayerMeasurementOptions => {
    const state = useAsukaStore.getState() as any;
    const activeView = (Array.isArray(state?.views) ? state.views : []).find((view: any) => String(view?.id ?? '') === String(state?.activeViewId ?? '')) ?? null;
    return {
      ...input,
      physicalSize: input.physicalSize ?? activeView?.physicalSize ?? null,
      physicalSource: input.physicalSource ?? 'view',
      variationId: input.variationId ?? activeView?.physicalSize?.activeVariantId ?? null,
    };
  };

  const measureLayer = (id: string, input: AsukaLayerMeasurementOptions = {}): AsukaLayerMeasurement | null => (
    measureRenderEngineLayer(getActiveRenderEngineFacade(), id, resolveLayerMeasurementOptions(input))
  );

  const resizeLayerPhysical = async (id: string, input: AsukaPhysicalLayerResizeInput): Promise<AsukaLayerMeasurement> => {
    const key = String(id ?? '').trim();
    if (!key) throw new AsukaRendererError('INVALID_INPUT', 'A layer id is required.', { operation: 'api.resizeLayerPhysical' });
    const current = measureLayer(key, input);
    if (!current?.physical.calibrated || !current.physical.unit || !(current.physical.width! > 0) || !(current.physical.height! > 0)) {
      throw new AsukaRendererError('INVALID_INPUT', 'The layer must belong to a physically calibrated view or print area.', {
        operation: 'api.resizeLayerPhysical',
        details: { objectId: key },
      });
    }
    const requestedWidth = Number(input.width);
    const requestedHeight = Number(input.height);
    const hasWidth = Number.isFinite(requestedWidth) && requestedWidth > 0;
    const hasHeight = Number.isFinite(requestedHeight) && requestedHeight > 0;
    if (!hasWidth && !hasHeight) {
      throw new AsukaRendererError('INVALID_INPUT', 'A positive physical width or height is required.', {
        operation: 'api.resizeLayerPhysical',
        details: { objectId: key },
      });
    }
    const unit = current.physical.unit;
    const width = hasWidth ? convertPhysicalLength(requestedWidth, input.unit, unit) : undefined;
    const height = hasHeight ? convertPhysicalLength(requestedHeight, input.unit, unit) : undefined;
    const factorX = width ? width / current.physical.width! : 1;
    const factorY = height ? height / current.physical.height! : 1;
    const preserveAspectRatio = input.preserveAspectRatio !== false;
    const uniformFactor = width ? factorX : factorY;
    const finalWidth = preserveAspectRatio ? current.physical.width! * uniformFactor : (width ?? current.physical.width!);
    const finalHeight = preserveAspectRatio ? current.physical.height! * uniformFactor : (height ?? current.physical.height!);
    const maxWidth = Number(current.constraints.maxWidth);
    const maxHeight = Number(current.constraints.maxHeight);
    if ((maxWidth > 0 && finalWidth > maxWidth + 1e-7) || (maxHeight > 0 && finalHeight > maxHeight + 1e-7)) {
      throw new AsukaRendererError('INVALID_INPUT', 'The requested physical size exceeds the calibrated print area.', {
        operation: 'api.resizeLayerPhysical',
        details: { objectId: key, requested: { width: finalWidth, height: finalHeight, unit }, maximum: { width: maxWidth, height: maxHeight, unit } },
      });
    }
    const engine = getActiveRenderEngineFacade();
    const transform = engine.objects.getTransform(key);
    const patch = preserveAspectRatio
      ? { scaleBy: uniformFactor }
      : {
          scaleX: (Number(transform?.scaleX ?? 1) || 1) * factorX,
          scaleY: (Number(transform?.scaleY ?? 1) || 1) * factorY,
        };
    await updateRenderEngineObjectTransform(engine, key, patch as any, { source: 'api.resizeLayerPhysical' });
    const next = measureLayer(key, input);
    if (!next) throw new AsukaRendererError('RUNTIME_FAILURE', 'The resized layer could not be measured.', { operation: 'api.resizeLayerPhysical', details: { objectId: key } });
    return next;
  };

  const exportProductionMeasurements = (): AsukaProductionMeasurementExport => {
    const state = useAsukaStore.getState() as any;
    const activeViewId = String(state?.activeViewId ?? '');
    const activeView = (Array.isArray(state?.views) ? state.views : []).find((view: any) => String(view?.id ?? '') === activeViewId) ?? null;
    const renderEngine = getActiveRenderEngineFacade();
    const options = resolveLayerMeasurementOptions();
    const layers = renderEngine.objects.list({ includeTechnical: false, includeReadonly: false, source: 'production-measurements' })
      .filter((object) => !['element', 'product'].includes(String(object?.pricing?.category ?? '').toLowerCase()))
      .map((object) => {
        const measurement = measureRenderEngineLayer(renderEngine, object.id, options);
        if (!measurement) return null;
        return {
          id: object.id,
          ...(object.kind ? { kind: object.kind } : null),
          ...(object.label ? { label: object.label } : null),
          printAreaId: object.targetPrintAreaId ?? null,
          measurement,
        };
      })
      .filter(Boolean) as AsukaProductionMeasurementExport['layers'];

    return {
      schema: 'asuka-production-measurements@1',
      view: {
        id: activeViewId,
        engine: String(renderEngine.kind ?? activeView?.engine ?? ''),
        ...(activeView?.physicalSize ? { physicalSize: activeView.physicalSize } : null),
      },
      printAreas: (renderEngine.areas?.list?.() ?? []).map((area) => ({
        id: String(area.id),
        ...(area.label ? { label: String(area.label) } : null),
        ...((area.physicalSize ?? (area.metadata as any)?.physicalSize ?? (area.metadata as any)?.policy?.physicalSize)
          ? { physicalSize: (area.physicalSize ?? (area.metadata as any)?.physicalSize ?? (area.metadata as any)?.policy?.physicalSize) as AsukaPhysicalSize }
          : null),
      })),
      layers,
    };
  };

  const previewVariationImpact = async (input: AsukaElementVariationApplyInput): Promise<string[]> => {
    const state = useAsukaStore.getState() as any;
    const preview = await synchronizeStoredElementVariations({
      catalog: Array.isArray(state?.elements) ? state.elements : [],
      input,
      documentObjectsByView: state?.documentObjectsByView ?? {},
      document3dScenesByView: state?.document3dScenesByView ?? {},
      views: Array.isArray(state?.views) ? state.views : [],
      activeViewId: state?.activeViewId ?? null,
      resolveImageGeometry: async () => null,
    });
    return Array.from(new Set([
      ...preview.impactedFabricViewIds,
      ...preview.impacted3DViewIds,
    ].map((viewId) => String(viewId ?? '').trim()).filter(Boolean)));
  };

  const variations: AsukaVariationsApi = {
    list: (query) => listElementVariationDescriptors(
      ((useAsukaStore.getState() as any)?.elements ?? []) as any[],
      query,
    ),
    getActive: (query) => listElementVariationDescriptors(
      ((useAsukaStore.getState() as any)?.elements ?? []) as any[],
      query,
    ).find((variation) => variation.active) ?? null,
    apply: async (input): Promise<AsukaVariationApplyResult> => {
      const stateBefore = useAsukaStore.getState() as any;
      const normalized = normalizeVariationApplyInput(
        (Array.isArray(stateBefore?.elements) ? stateBefore.elements : []) as any[],
        input,
      );
      assertApiAllowed('canEditElementVariations', { action: 'variations.apply', input: normalized });
      const impactedViewIds = await previewVariationImpact(normalized);
      const activeViewId = String(stateBefore?.activeViewId ?? '').trim() || null;
      const activeEngine = getActiveRenderEngineFacade();
      const applied = await applyElementVariationAcrossRenderers(activeEngine, normalized);
      return {
        applied,
        scope: normalized.scope,
        elementId: normalized.elementId,
        familyId: normalized.familyId,
        partId: normalized.partId,
        variationId: normalized.variationId,
        variationPathIds: normalized.variationPathIds ?? [],
        variationPathNames: normalized.variationPathNames ?? [],
        activeViewId,
        engine: activeEngine.kind,
        impactedViewIds: applied ? impactedViewIds : [],
      };
    },
    subscribe: (listener) => {
      const offApplied = onAsukaEvent('after:elements.applyVariation', (payload: any) => listener({
        type: 'applied',
        input: payload && typeof payload === 'object' ? payload : {},
      }));
      const offError = onAsukaEvent('elements.applyVariation.error', (payload: any) => listener({
        type: 'error',
        input: payload && typeof payload === 'object' ? payload : {},
        error: String(payload?.error ?? 'Variation application failed.'),
      }));
      return () => {
        offApplied();
        offError();
      };
    },
  };

  const api = {
    getObjects: () => getActiveRenderEngineFacade().objects.list(),
    listObjects: (options?: Record<string, unknown>) => getActiveRenderEngineFacade().objects.list(options),
    getObject: (id: string) => getActiveRenderEngineFacade().objects.get(id),
    getSelectedObjects: () => getActiveRenderEngineFacade().selection.list(),
    getSceneGraph: () => getActiveRenderEngineFacade().scene.getGraph(),
    addImage: async (input: AsukaImageInput) => {
      assertApiAllowed('canAddImage', { action: 'api.addImage', input });
      let resolvedInput = input;
      if (typeof File !== 'undefined' && input?.file instanceof File) {
        assertApiAllowed('canUploadImage', { action: 'api.addImage', input });
        const url = await resolveUploadedFileUrl(input.file, {
          upload: (input as any).upload as AsukaUploadModuleConfig | undefined,
          defaultStrategy: DEFAULT_ASUKA_UPLOAD_STRATEGY,
          source: String((input as any).source ?? input.metadata?.source ?? input.metadata?.sourceModule ?? 'api'),
          mode: typeof (input as any).mode === 'string' ? (input as any).mode : undefined,
          registerUploadedAsset: (input as any).registerUploadedAsset !== false,
        });
        const { file: _file, ...rest } = input;
        resolvedInput = { ...rest, url };
      }
      return getActiveRenderEngineFacade().objects.addImage(resolvedInput);
    },
    addText: async (input: AsukaTextInput) => {
      assertApiAllowed('canAddText', { action: 'api.addText', input });
      if (input.fontFamily) await ensureFontFamilyLoaded(input.fontFamily);
      return getActiveRenderEngineFacade().objects.addText(input);
    },
    addDesign: async (input: AsukaDesignInput) => {
      assertApiAllowed('canAddDesigns', { action: 'api.addDesign', input });
      return getActiveRenderEngineFacade().objects.addDesign(input);
    },
    addElement: async (input: AsukaElementInput) => {
      assertApiAllowed('canAddElements', { action: 'api.addElement', input });
      return getActiveRenderEngineFacade().objects.addElement(input);
    },
    applyElementVariation: async (input: AsukaElementVariationApplyInput) => {
      const result = await variations.apply(input);
      return result.applied;
    },
    removeObject: (id: string) => {
      assertApiAllowed('canDeleteObject', { action: 'api.removeObject', id });
      return getActiveRenderEngineFacade().objects.remove(id);
    },
    updateObject: (id: string, patch: AsukaObjectPatch) => getActiveRenderEngineFacade().objects.update(id, patch),
    duplicateObject: (id: string, options?: AsukaObjectDuplicateOptions) => getActiveRenderEngineFacade().objects.duplicate(id, options),
    setObjectVisible: (id: string, visible: boolean, options?: AsukaObjectMutationOptions) => getActiveRenderEngineFacade().objects.setVisible(id, visible, options),
    setObjectLocked: (id: string, locked: boolean, options?: AsukaObjectMutationOptions) => getActiveRenderEngineFacade().objects.setLocked(id, locked, options),
    bringObjectForward: (id: string, options?: AsukaObjectMutationOptions) => getActiveRenderEngineFacade().objects.bringForward(id, options),
    sendObjectBackward: (id: string, options?: AsukaObjectMutationOptions) => getActiveRenderEngineFacade().objects.sendBackward(id, options),
    getObjectTransform: (id: string) => getActiveRenderEngineFacade().objects.getTransform(id),
    updateObjectTransform: (id: string, patch: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaObjectMutationOptions) => getActiveRenderEngineFacade().objects.updateTransform(id, patch, options),
    getPlacementStatus: () => getActiveRenderEngineFacade().placement.getStatus(),
    beginPlacement: (input: AsukaPlacementBeginInput) => getActiveRenderEngineFacade().placement.begin(input),
    updatePlacement: (input: AsukaPlacementUpdateInput) => getActiveRenderEngineFacade().placement.update(input),
    commitPlacement: (metadata?: Record<string, unknown>) => getActiveRenderEngineFacade().placement.commit(metadata),
    cancelPlacement: (metadata?: Record<string, unknown>) => getActiveRenderEngineFacade().placement.cancel(metadata),
    measureLayer,
    resizeLayerPhysical,
    exportProductionMeasurements,
    selectObject: (id: string | null) => getActiveRenderEngineFacade().selection.select(id),
    zoomIn: () => getActiveRenderEngineFacade().viewport.zoomIn(),
    zoomOut: () => getActiveRenderEngineFacade().viewport.zoomOut(),
    fitView: () => getActiveRenderEngineFacade().viewport.fit(),
    exportPng: (options?: AsukaExportOptions) => getActiveRenderEngineFacade().exports.png(options),
    download: (target?: unknown, options?: AsukaDownloadOptions) => getActiveRenderEngineFacade().exports.download(target, options),
    isExportFormatAvailable: (format: string, options?: Record<string, unknown>) => getActiveRenderEngineFacade().exports.isFormatAvailable(format, options),
    ensureExportSupport: (format: string, options?: Record<string, unknown>) => getActiveRenderEngineFacade().exports.ensureSupport(format, options),
    getHistoryInfo: () => getActiveRenderEngineFacade().history.getInfo(),
    commitHistorySnapshot: (options?: Record<string, unknown>) => getActiveRenderEngineFacade().history.commit(options),
    undo: () => getActiveRenderEngineFacade().history.undo(),
    redo: () => getActiveRenderEngineFacade().history.redo(),
    reset: () => getActiveRenderEngineFacade().history.reset(),
    goToHistory: (index: number) => getActiveRenderEngineFacade().history.goTo(index),

    getViews: () => {
      return useAsukaStore.getState().views;
    },
    getActiveViewId: () => {
      return useAsukaStore.getState().activeViewId;
    },
    setActiveView: async (id: string) => {
      if (isDenied('canSwitchView', { action: 'api.setActiveView', id })) return;
      const targetId = String(id ?? '').trim();
      if (!targetId) return;
      const state = useAsukaStore.getState() as any;
      const targetView = (Array.isArray(state?.views) ? state.views : [])
        .find((view: any) => String(view?.id ?? '').trim() === targetId);
      if (!targetView) return;

      const previousId = String(state?.activeViewId ?? '').trim();
      const previousKind = resolveViewEngineKind(
        (Array.isArray(state?.views) ? state.views : [])
          .find((view: any) => String(view?.id ?? '').trim() === previousId),
        state?.defaultRenderEngineKind ?? 'fabric',
      );
      const targetKind = resolveViewEngineKind(targetView, state?.defaultRenderEngineKind ?? 'fabric');
      if (previousId === targetId && isRuntimeRenderEngineViewReady(targetKind, targetId)) return;

      await new Promise<void>((resolve, reject) => {
        let settled = false;
        const finish = (error?: unknown) => {
          if (settled) return;
          settled = true;
          unsubscribe();
          globalThis.clearTimeout(timeoutId);
          if (error) reject(error);
          else resolve();
        };
        const unsubscribe = subscribeRenderEngineEvents((event) => {
          if (event.engine !== targetKind) return;
          if (String(event.viewId ?? '').trim() !== targetId) return;
          if (event.type === 'ready' && event.action === 'view.activate') {
            finish();
            return;
          }
          if (event.type === 'error' && event.action === 'view.activate') {
            finish(new AsukaRendererError(
              'RUNTIME_FAILURE',
              `The ${targetKind} renderer could not activate view "${targetId}".`,
              { engine: targetKind, operation: 'views.setActive', details: { viewId: targetId } },
            ));
          }
        });
        const timeoutId = globalThis.setTimeout(() => {
          finish(new AsukaRendererError(
            'RUNTIME_FAILURE',
            `Timed out while activating view "${targetId}".`,
            { engine: targetKind, operation: 'views.setActive', details: { viewId: targetId } },
          ));
        }, 60_000);

        try {
          const mountedEngine = getRuntimeRenderEngine(previousKind);
          if (mountedEngine) {
            void Promise.resolve(mountedEngine.lifecycle.activateView(targetId, { mode: 'switch' }))
              .catch((error) => finish(error));
          } else {
            useAsukaStore.getState().setActiveView(targetId);
          }
        } catch (error) {
          finish(error);
        }
      });
    },
    addView: (payload?: any) => {
      if (isDenied('canCreateView', { action: 'api.addView', payload })) return '';
      if (isDenied('canCreateView', { action: 'api.addView', payload })) return '';
      return useAsukaStore.getState().addView(payload);
    },

    updateView: (id: string, patch: any) => {
      if (isDenied('canEditView', { action: 'api.updateView', id, patch })) return;
      return useAsukaStore.getState().updateView(id, patch);
    },
    removeView: (id: string) => {
      if (isDenied('canDeleteView', { action: 'api.removeView', id })) return;
      return useAsukaStore.getState().removeView(id);
    },

    getFonts: () => ((useAsukaStore.getState() as any).fontsCatalog ?? []),
    addSystemFont: (payload: string | { id?: string; family: string }) => {
      if (isDenied('canEditFontFamily', { action: 'api.addSystemFont', payload })) return '';
      const instance = getDesignerInstance();
      return instance ? ((instance as any).addSystemFont?.(payload) ?? '') : '';
    },
    addGoogleFont: async (payload: string | { id?: string; family: string; cssUrl?: string; weights?: Array<string | number>; styles?: Array<'normal' | 'italic'> }) => {
      if (isDenied('canEditFontFamily', { action: 'api.addGoogleFont', payload })) return '';
      const instance = getDesignerInstance();
      return instance ? (((instance as any).addGoogleFont?.(payload) ?? Promise.resolve(''))) : '';
    },
    addCustomFont: async (payload: { id?: string; family: string; url?: string; cssUrl?: string; format?: 'woff2' | 'woff' | 'ttf' | 'otf'; weight?: string | number; style?: 'normal' | 'italic' }) => {
      if (isDenied('canEditFontFamily', { action: 'api.addCustomFont', payload })) return '';
      const instance = getDesignerInstance();
      return instance ? (((instance as any).addCustomFont?.(payload) ?? Promise.resolve(''))) : '';
    },
    removeFont: (id: string) => {
      if (isDenied('canEditFontFamily', { action: 'api.removeFont', id })) return;
      const instance = getDesignerInstance();
      (instance as any)?.removeFont?.(id);
    },
    loadFont: async (id: string) => {
      const instance = getDesignerInstance();
      return instance ? (((instance as any).loadFont?.(id) ?? Promise.resolve(false))) : false;
    },

    // Pricing
    // IMPORTANT: always resolve the current Designer instance at call time.
    // (The previous implementation referenced an undefined `designer` variable,
    //  which caused the Pricing panel refresh to silently fail.)
    getPrice: (externalPrice?: AsukaPriceInput) => getPrice({ designer: getDesignerInstance(), engine: getActiveRenderEngineFacade(), externalPrice }),
    getPriceUiBreakdown: (externalPrice?: AsukaPriceInput) =>
      getPriceUiBreakdown({ designer: getDesignerInstance(), engine: getActiveRenderEngineFacade(), externalPrice }),
    getStandardPrices: (objects?: any[]) => getStandardPrices({ designer: getDesignerInstance(), engine: getActiveRenderEngineFacade(), objects }),
    getPricingRules: (baseTotal?: number) =>
      getPricingRules({ designer: getDesignerInstance(), engine: getActiveRenderEngineFacade(), baseTotal: baseTotal ?? 0 }),

    /** Force a price recomputation and store the result in Zustand (used by the PricingPanel). */
    recalcPricing: () => {
      try {
        const instance = getDesignerInstance();
        const breakdown = getPriceUiBreakdown({ designer: instance, engine: getActiveRenderEngineFacade() });
        (useAsukaStore.getState() as any).setPricingTotals?.(breakdown);
      } catch {
        // ignore
      }
    },
  };

  const getAssemblyRuntime = (operation: string): AsukaRenderEngineAssembly => {
    assertLicensedFeature('assembly', `assembly.${operation}`);
    const renderEngine = getActiveRenderEngineFacade();
    const assemblyRuntime = renderEngine.assembly;
    if (!assemblyRuntime || renderEngine.capabilities.assembly3d !== true) {
      throw new AsukaRendererError(
        'UNSUPPORTED_OPERATION',
        'Spatial assembly is available only on an active 3D view with the Babylon renderer loaded.',
        {
          engine: renderEngine.kind,
          operation: `assembly.${operation}`,
        },
      );
    }

    const state = useAsukaStore.getState();
    assemblyRuntime.syncCatalog(Array.isArray(state.elements) ? state.elements : []);
    return assemblyRuntime;
  };

  const assembly: AsukaAssemblyApi = {
    getState: () => getAssemblyRuntime('getState').getState(),
    getBuildState: () => getAssemblyRuntime('getBuildState').getBuildState(),
    getInteractionState: () => getAssemblyRuntime('getInteractionState').getInteractionState(),
    setConstructionMode: (active) => {
      if (active) assertApiAllowed('canEditAssembly', { action: 'assembly.setConstructionMode' });
      return getAssemblyRuntime('setConstructionMode').setConstructionMode(active);
    },
    setMoveTool: (active) => {
      if (active) assertApiAllowed('canEditAssembly', { action: 'assembly.setMoveTool' });
      return getAssemblyRuntime('setMoveTool').setMoveTool(active);
    },
    configure: (patch, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.configure' });
      return getAssemblyRuntime('configure').configure(patch, mutationOptions);
    },
    listSurfaces: () => getAssemblyRuntime('listSurfaces').listSurfaces(),
    getSurfaceMetrics: (id) => getAssemblyRuntime('getSurfaceMetrics').getSurfaceMetrics(id),
    getSurfaceTransform: (id) => getAssemblyRuntime('getSurfaceTransform').getSurfaceTransform(id),
    selectSurface: (id) => getAssemblyRuntime('selectSurface').selectSurface(id),
    startDrawing: (input) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.startDrawing' });
      return getAssemblyRuntime('startDrawing').startDrawing(input);
    },
    cancelDrawing: () => getAssemblyRuntime('cancelDrawing').cancelDrawing(),
    updateSurface: (id, patch, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.updateSurface', id });
      return getAssemblyRuntime('updateSurface').updateSurface(id, patch, mutationOptions);
    },
    transformSurface: (id, input, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.transformSurface', id });
      return getAssemblyRuntime('transformSurface').transformSurface(id, input, mutationOptions);
    },
    setFloorEdgeLength: (floorId, edgeIndex, length, mutationOptions) => {
      assertApiAllowed('canEditAssembly', {
        action: 'assembly.setFloorEdgeLength',
        id: floorId,
      });
      return getAssemblyRuntime('setFloorEdgeLength').setFloorEdgeLength(
        floorId,
        edgeIndex,
        length,
        mutationOptions,
      );
    },
    setFloorEdgeAngle: (floorId, edgeIndex, angleDegrees, mutationOptions) => {
      assertApiAllowed('canEditAssembly', {
        action: 'assembly.setFloorEdgeAngle',
        id: floorId,
      });
      return getAssemblyRuntime('setFloorEdgeAngle').setFloorEdgeAngle(
        floorId,
        edgeIndex,
        angleDegrees,
        mutationOptions,
      );
    },
    setFloorEdgeConstraint: (floorId, edgeIndex, constraint, mutationOptions) => {
      assertApiAllowed('canEditAssembly', {
        action: 'assembly.setFloorEdgeConstraint',
        id: floorId,
      });
      return getAssemblyRuntime('setFloorEdgeConstraint').setFloorEdgeConstraint(
        floorId,
        edgeIndex,
        constraint,
        mutationOptions,
      );
    },
    setRoomWallEnabled: (floorId, edgeIndex, enabled, mutationOptions) => {
      assertApiAllowed('canEditAssembly', {
        action: 'assembly.setRoomWallEnabled',
        id: floorId,
      });
      return getAssemblyRuntime('setRoomWallEnabled').setRoomWallEnabled(
        floorId,
        edgeIndex,
        enabled,
        mutationOptions,
      );
    },
    listMaterialPresets: () => getAssemblyRuntime('listMaterialPresets').listMaterialPresets(),
    saveMaterialPreset: (preset, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.saveMaterialPreset' });
      return getAssemblyRuntime('saveMaterialPreset').saveMaterialPreset(preset, mutationOptions);
    },
    removeMaterialPreset: (presetId, mutationOptions) => {
      assertApiAllowed('canEditAssembly', {
        action: 'assembly.removeMaterialPreset',
        id: presetId,
      });
      return getAssemblyRuntime('removeMaterialPreset').removeMaterialPreset(
        presetId,
        mutationOptions,
      );
    },
    applyMaterialPreset: (surfaceId, presetId, applyOptions, mutationOptions) => {
      assertApiAllowed('canEditAssembly', {
        action: 'assembly.applyMaterialPreset',
        id: surfaceId,
      });
      return getAssemblyRuntime('applyMaterialPreset').applyMaterialPreset(
        surfaceId,
        presetId,
        applyOptions,
        mutationOptions,
      );
    },
    removeSurface: (id, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.removeSurface', id });
      return getAssemblyRuntime('removeSurface').removeSurface(id, mutationOptions);
    },
    clearSurfaces: (mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.clearSurfaces' });
      return getAssemblyRuntime('clearSurfaces').clearSurfaces(mutationOptions);
    },
    listComponents: () => getAssemblyRuntime('listComponents').listComponents(),
    getComponent: (id) => getAssemblyRuntime('getComponent').getComponent(id),
    previewPlacement: (componentId, input) => getAssemblyRuntime('previewPlacement').previewPlacement(componentId, input),
    place: (componentId, input, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.place', id: componentId });
      return getAssemblyRuntime('place').place(componentId, input, mutationOptions);
    },
    autoPlace: (componentId, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.autoPlace', id: componentId });
      return getAssemblyRuntime('autoPlace').autoPlace(componentId, mutationOptions);
    },
    connect: (connection, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.connect' });
      return getAssemblyRuntime('connect').connect(connection, mutationOptions);
    },
    disconnect: (connectionId, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.disconnect', id: connectionId });
      return getAssemblyRuntime('disconnect').disconnect(connectionId, mutationOptions);
    },
    listStackLinks: () => getAssemblyRuntime('listStackLinks').listStackLinks(),
    linkStack: (link, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.linkStack' });
      return getAssemblyRuntime('linkStack').linkStack(link, mutationOptions);
    },
    unlinkStack: (linkId, mutationOptions) => {
      assertApiAllowed('canEditAssembly', { action: 'assembly.unlinkStack', id: linkId });
      return getAssemblyRuntime('unlinkStack').unlinkStack(linkId, mutationOptions);
    },
    validate: () => getAssemblyRuntime('validate').validate(),
    getBom: (options) => getAssemblyRuntime('getBom').getBom(options),
    getQuote: (options) => getAssemblyRuntime('getQuote').getQuote(options),
    export: (kind, options) => getAssemblyRuntime('export').export(kind, options),
  };

  const agencyOptions: AsukaAgencyOptions = {
    ...(((options as any)?.agency ?? {}) as AsukaAgencyOptions),
  };

  const configuredAiOptions = (((options as any)?.ai ?? {}) as AsukaAiOptions);
  const aiOptions: AsukaAiOptions = {
    ...configuredAiOptions,
  };

  const exportOptions: AsukaExportsOptions = {
    ...(((options as any)?.exports ?? {}) as AsukaExportsFeatureOptions),
  };

  agencyRuntime = createAsukaAgencyRuntime({
    rootElement,
    options: agencyOptions,
    canUseFeature: (feature) => canUseFeature(((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements, feature),
    getPermissions: () => {
      const s = useAsukaStore.getState() as any;
      return getEffectivePermissions(
        (s.permissions ?? permissions) as any,
        (s.permissionsByView?.[s.activeViewId ?? ''] ?? null) as any,
      );
    },
    setPermissions: (patch) => {
      const next = mergePermissions(permissions, patch);
      Object.assign(permissions, next);
      try { useAsukaStore.getState().setPermissions?.(patch as any); } catch {}
      try { applyCanvasEditability(getDesignerInstance(), next as any); } catch {}
      emitAsukaEvent('after:permissions.changed', { permissions: { ...permissions }, source: 'agency' });
    },
    exportLayout: () => extractRuntimeLayoutAsConfig(),
    getThemeState: () => getThemeState(),
  });

  aiRuntime = createAsukaAiRuntime({
    options: aiOptions,
    canUseFeature: (feature) => canUseFeature(((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements, feature),
    getLicense: () => ((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements,
    getLicenseToken: () => ((useAsukaStore.getState() as any).licenseToken ?? null),
    patchLicense: (patch) => { try { useAsukaStore.getState().setLicenseState?.({ license: { ...(((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as any), ...(patch as any) } }); } catch {} },
    fallbackBackgroundRemoval: async (input) => await agencyRuntime!.backgroundRemoval.remove(input),
  });
  try { useAsukaStore.getState().setAiRuntime?.(aiRuntime); } catch {}

  exportRuntime = createAsukaExportRuntime({
    options: exportOptions,
    canUseFeature: (feature) => canUseFeature(((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements, feature),
    getLicense: () => ((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements,
    getLicenseToken: () => licenseManager.getCurrentCompactToken?.() ?? ((useAsukaStore.getState() as any).licenseToken ?? null),
    patchLicense: (patch) => { try { useAsukaStore.getState().setLicenseState?.({ license: { ...(((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as any), ...(patch as any) } }); } catch {} },
    exportState: () => ({
      document: captureAsukaStatePart('document'),
      state: captureFullAsukaState(),
    }),
    getDefaultTarget: () => {
      const s = useAsukaStore.getState() as any;
      return {
        scope: 'Views',
        mode: 'current',
        activeViewId: s.activeViewId ?? null,
        viewId: s.activeViewId ?? null,
      };
    },
  });
  try { useAsukaStore.getState().setExportRuntime?.(exportRuntime); } catch {}

  const syncDocumentBeforeStateExport = () => {
    const designer = getDesignerInstance() as any;
    try { designer?.document?.syncViewObjects?.(); } catch {}
    try { designer?.documentActions?.syncDocumentObjectsForView?.(); } catch {}
    try { designer?.syncDocumentObjectsForView?.(); } catch {}
  };

  const util = {
    getState: () => useAsukaStore.getState(),
    subscribe: useAsukaStore.subscribe,
    listeners: {
      getEnabled: () => ({ ...(useAsukaStore.getState() as any).listenersEnabled }),
      isEnabled: (id: any) => {
        const s: any = useAsukaStore.getState();
        if (typeof s.isListenerEnabled === 'function') return !!s.isListenerEnabled(id);
        const v = (s.listenersEnabled ?? {})[id];
        return v !== false;
      },
      setEnabled: (id: any, enabled: boolean) => {
        try { (useAsukaStore.getState() as any).setListenerEnabled?.(id, !!enabled); } catch {}
      },
      setMany: (patch: any) => {
        try { (useAsukaStore.getState() as any).setListenersEnabled?.(patch ?? {}); } catch {}
      },
      getSettings: () => ({ ...(useAsukaStore.getState() as any).listenerSettings }),
      setSettings: (patch: any) => {
        try { (useAsukaStore.getState() as any).setListenerSettings?.(patch ?? {}); } catch {}
      },
    },
    i18n: {
      t: (key: string, params?: Record<string, string | number>) => getI18n().t(key, params),
      getLocale: () => getI18n().getLocale(),
      setLocale: (locale: string) => {
        getI18n().setLocale(locale);
        try { (useAsukaStore.getState() as any).bumpI18nVersion?.(); } catch {}
        emitAsukaEvent('after:i18n.localeChanged', { locale });
      },
      getFallbackLocale: () => getI18n().getFallbackLocale(),
      setFallbackLocale: (locale: string) => {
        getI18n().setFallbackLocale(locale);
        try { (useAsukaStore.getState() as any).bumpI18nVersion?.(); } catch {}
        emitAsukaEvent('after:i18n.fallbackLocaleChanged', { locale });
      },
      addTranslations: (locale: string, bundle: Translations) => {
        getI18n().addTranslations(locale, bundle);
        try { (useAsukaStore.getState() as any).bumpI18nVersion?.(); } catch {}
        emitAsukaEvent('after:i18n.translationsAdded', { locale });
      },
    },
    state: {
      saveToStorage: (state: AsukaExportedState, opts: { key?: string } = {}) => {
        if (isDenied('canUseStateTools', { action: 'state.saveToStorage', opts }) || isDenied('canExportJson', { action: 'state.saveToStorage', opts })) return;
        return saveStateToStorage(state, opts);
      },
      loadFromStorage: (opts: { key?: string } = {}) => {
        if (isDenied('canUseStateTools', { action: 'state.loadFromStorage', opts }) || isDenied('canImportJson', { action: 'state.loadFromStorage', opts })) return null;
        return loadStateFromStorage(opts);
      },
      clearStorage: (opts: { key?: string } = {}) => {
        if (isDenied('canUseStateTools', { action: 'state.clearStorage', opts })) return;
        return clearStateStorage(opts);
      },
      replaceProduct: (state: AsukaExportedState, options: { recreate?: boolean } = {}) => {
        if (isDenied('canUseStateTools', { action: 'state.replaceProduct', options }) || isDenied('canImportJson', { action: 'state.replaceProduct', options })) return instanceRef.current as AsukaDesigner;
        debugLog('state', 'replaceProduct requested', { recreate: options.recreate !== false });
        const recreate = options.recreate !== false;
        if (recreate) {
          const nextOptions = { ...(optionsArgSnapshot as any), initialState: state, boot: { mode: 'replace-product', state } };
          destroy();
          rootElement.innerHTML = '';
          return createAsukaDesigner(rootElement, nextOptions);
        }
        restoreFullAsukaState(state, { replaceViews: true, replaceSnapshots: true });
        return instanceRef.current as AsukaDesigner;
      },
      normalizeLayoutConfigToRuntimeState: (config: any, fallbackState?: any) => { const out = normalizeLayoutConfigToRuntimeState(config, fallbackState); debugLog('layout', 'normalized layout config to runtime state', out); return out; },
      extractRuntimeLayoutAsConfig: (layoutState?: any) => { const out = extractRuntimeLayoutAsConfig(layoutState); debugLog('layout', 'extracted runtime layout as config', out); return out; },
      exportFull: () => {
        if (isDenied('canUseStateTools', { action: 'state.exportFull' }) || isDenied('canExportJson', { action: 'state.exportFull' })) return undefined as any;
        syncDocumentBeforeStateExport();
        const snapshot = captureFullAsukaState();
        emitAfter('state.exportFull', { snapshot });
        return snapshot;
      },
      exportPortableProduct: (options: Omit<AsukaPortableProductCreateOptions, 'state'>) => {
        if (isDenied('canUseStateTools', { action: 'state.exportPortableProduct' }) || isDenied('canExportJson', { action: 'state.exportPortableProduct' })) return undefined as any;
        syncDocumentBeforeStateExport();
        const document = createAsukaPortableProductDocument({
          ...options,
          state: captureFullAsukaState(),
        });
        emitAfter('state.exportPortableProduct', { document });
        return document;
      },
      exportPortablePackage: async (
        options: Omit<AsukaPortableProductCreateOptions, 'state'>
          & Omit<AsukaPortableProductPackageCreateOptions, 'document'>,
      ) => {
        if (isDenied('canUseStateTools', { action: 'state.exportPortablePackage' }) || isDenied('canExportJson', { action: 'state.exportPortablePackage' })) return undefined as any;
        syncDocumentBeforeStateExport();
        const {
          name,
          slug,
          designer,
          dependencies,
          ...packageOptions
        } = options;
        const document = createAsukaPortableProductDocument({
          name,
          slug,
          designer,
          dependencies,
          state: captureFullAsukaState(),
        });
        const archive = await createAsukaPortableProductPackage({
          ...packageOptions,
          document,
        });
        emitAfter('state.exportPortablePackage', { document, bytes: archive.byteLength });
        return archive;
      },
      importFull: (snapshot: AsukaExportedState, options: AsukaImportOptions = {}) => {
        if (isDenied('canUseStateTools', { action: 'state.importFull', options }) || isDenied('canImportJson', { action: 'state.importFull', options })) return;
        const ok = emitAsukaEventCancelable('before:state.restore', { snapshot, options, full: true });
        if (!ok) return;
        restoreFullAsukaState(snapshot, options);
        emitAfter('state.restore', { snapshot, options, full: true });
      },
      importPortableProduct: (document: AsukaPortableProductDocument | unknown, options: AsukaImportOptions = {}) => {
        if (isDenied('canUseStateTools', { action: 'state.importPortableProduct', options }) || isDenied('canImportJson', { action: 'state.importPortableProduct', options })) return;
        const normalizedDocument = normalizeAsukaPortableProductDocument(document);
        const snapshot = extractAsukaPortableProductState(normalizedDocument);
        const ok = emitAsukaEventCancelable('before:state.restore', {
          snapshot,
          options,
          portableProduct: normalizedDocument,
          full: true,
        });
        if (!ok) return;
        restoreFullAsukaState(snapshot, options);
        emitAfter('state.restore', {
          snapshot,
          options,
          portableProduct: normalizedDocument,
          full: true,
        });
      },
      importPortablePackage: async (
        input: Blob | ArrayBuffer | Uint8Array,
        options: { publishAsset?: AsukaPortableAssetPublisher; state?: AsukaImportOptions } = {},
      ) => {
        if (isDenied('canUseStateTools', { action: 'state.importPortablePackage' }) || isDenied('canImportJson', { action: 'state.importPortablePackage' })) return;
        const productPackage = await readAsukaPortableProductPackage(input);
        const materialized = await materializeAsukaPortableProductPackage(
          productPackage,
          options.publishAsset,
        );
        const snapshot = extractAsukaPortableProductState(materialized.document);
        const stateOptions = options.state ?? {};
        const ok = emitAsukaEventCancelable('before:state.restore', {
          snapshot,
          options: stateOptions,
          portableProduct: materialized.document,
          full: true,
        });
        if (!ok) return;
        restoreFullAsukaState(snapshot, stateOptions);
        emitAfter('state.restore', {
          snapshot,
          options: stateOptions,
          portableProduct: materialized.document,
          full: true,
        });
      },
      exportPart: (part: AsukaStatePart) => {
        if (isDenied('canUseStateTools', { action: 'state.exportPart', part }) || isDenied('canExportJson', { action: 'state.exportPart', part })) return undefined as any;
        if (part === 'document') syncDocumentBeforeStateExport();
        const snapshot = captureAsukaStatePart(part);
        emitAfter('state.exportPart', { part, snapshot });
        return snapshot;
      },
      importPart: (part: AsukaStatePart, snapshot: AsukaExportedState, options: AsukaImportOptions = {}) => {
        if (isDenied('canUseStateTools', { action: 'state.importPart', part, options }) || isDenied('canImportJson', { action: 'state.importPart', part, options })) return;
        const ok = emitAsukaEventCancelable('before:state.restore', { snapshot, options, part });
        if (!ok) return;
        restoreAsukaStatePart(part, snapshot, options);
        emitAfter('state.restore', { snapshot, options, part });
      },
      rebuildActiveViewFromDocument: (targetViewId?: string | null) => {
        if (isDenied('canUseStateTools', { action: 'state.rebuildActiveViewFromDocument', targetViewId }) || isDenied('canImportJson', { action: 'state.rebuildActiveViewFromDocument', targetViewId })) return;
        return rebuildActiveViewFromDocument(targetViewId);
      },
    },


    camera: {
      get: () => {
        return getActiveRenderEngineFacade().viewport.getCamera?.() ?? null;
      },
      set: (view: Partial<AsukaCameraView>, options?: AsukaCameraMoveOptions) => {
        return getActiveRenderEngineFacade().viewport.setCamera?.(view, options) ?? null;
      },
      getLimits: () => {
        return getActiveRenderEngineFacade().viewport.getCameraLimits?.() ?? null;
      },
      setLimits: (limits: Partial<AsukaCameraLimits>) => {
        return getActiveRenderEngineFacade().viewport.setCameraLimits?.(limits) ?? null;
      },
      getSaved: (elementId: string, variationId: string) => {
        const state: any = useAsukaStore.getState();
        const element = state.findElement?.(elementId) ?? null;
        return getVariationCamera(element, variationId);
      },
      save: (elementId: string, variationId: string) => {
        const camera = getActiveRenderEngineFacade().viewport.getCamera?.() ?? null;
        if (!camera) return null;
        const saved = (useAsukaStore.getState() as any).setElementVariationCamera?.(elementId, variationId, camera) === true;
        return saved ? camera : null;
      },
      clear: (elementId: string, variationId: string) => (useAsukaStore.getState() as any).setElementVariationCamera?.(elementId, variationId, null) === true,
      load: (elementId: string, variationId: string, options?: AsukaCameraMoveOptions) => {
        const state: any = useAsukaStore.getState();
        const element = state.findElement?.(elementId) ?? null;
        const camera = getVariationCamera(element, variationId);
        if (!camera) return null;
        return getActiveRenderEngineFacade().viewport.setCamera?.(camera, options) ?? null;
      },
    },

    parametric3d: {
      get: (productId?: string | null) => {
        assertLicensedFeature('productParameters', 'parametric3d.get');
        const state: any = useAsukaStore.getState();
        const products = getActiveRenderEngineFacade().products;
        try { products?.syncCatalog(Array.isArray(state?.elements) ? state.elements : []); } catch {}
        return products?.getParameters(productId) ?? null;
      },
      validate: (productId: string | null | undefined, values: Record<string, unknown> = {}) => {
        assertLicensedFeature('productParameters', 'parametric3d.validate');
        const state: any = useAsukaStore.getState();
        const products = getActiveRenderEngineFacade().products;
        try { products?.syncCatalog(Array.isArray(state?.elements) ? state.elements : []); } catch {}
        return products?.validateParameters(productId, values) ?? null;
      },
      set: (productId: string | null | undefined, parameterId: string, value: AsukaParametricValue, mutationOptions?: AsukaParametricMutationOptions) => {
        assertLicensedFeature('productParameters', 'parametric3d.set');
        const state: any = useAsukaStore.getState();
        const products = getActiveRenderEngineFacade().products;
        try { products?.syncCatalog(Array.isArray(state?.elements) ? state.elements : []); } catch {}
        return products?.setParameter(productId, parameterId, value, mutationOptions) ?? null;
      },
      setMany: (productId: string | null | undefined, values: Record<string, unknown>, mutationOptions?: AsukaParametricMutationOptions) => {
        assertLicensedFeature('productParameters', 'parametric3d.setMany');
        const state: any = useAsukaStore.getState();
        const products = getActiveRenderEngineFacade().products;
        try { products?.syncCatalog(Array.isArray(state?.elements) ? state.elements : []); } catch {}
        return products?.setParameters(productId, values ?? {}, mutationOptions) ?? null;
      },
    },

    engine: {
      getKind: () => ((useAsukaStore.getState() as any).activeRenderEngineKind ?? getCurrentRenderEngineKind()) as AsukaRenderEngineKind,
      getDefaultKind: () => ((useAsukaStore.getState() as any).defaultRenderEngineKind ?? 'fabric') as AsukaRenderEngineKind,
      setDefaultKind: (kind: AsukaRenderEngineKind) => {
        const next = normalizeRenderEngineKind(kind, 'fabric');
        try { (useAsukaStore.getState() as any).setDefaultRenderEngineKind?.(next); } catch {}
        return ((useAsukaStore.getState() as any).defaultRenderEngineKind ?? next) as AsukaRenderEngineKind;
      },
      getViewKind: (viewId?: string | null) => {
        const state: any = useAsukaStore.getState() as any;
        const targetId = String(viewId ?? state.activeViewId ?? '').trim();
        const view = (state.views ?? []).find((entry: any) => String(entry?.id ?? '') === targetId) ?? null;
        return resolveViewEngineKind(view, state.defaultRenderEngineKind ?? 'fabric');
      },
      setViewKind: (viewId: string, kind: AsukaRenderEngineKind) => {
        const next = normalizeRenderEngineKind(kind, 'fabric');
        try { (useAsukaStore.getState() as any).updateView?.(viewId, { engine: next }); } catch {}
      },
      getCurrent: () => getActiveRenderEngineFacade(),
      getForView: (viewId?: string | null) => {
        const state: any = useAsukaStore.getState() as any;
        const targetId = String(viewId ?? state.activeViewId ?? '').trim();
        const view = (state.views ?? []).find((entry: any) => String(entry?.id ?? '') === targetId) ?? null;
        return createLiveModuleRenderEngine(resolveViewEngineKind(view, state.defaultRenderEngineKind ?? 'fabric'));
      },
      isLoaded: (kind?: AsukaRenderEngineKind | null) => {
        const targetKind = normalizeRenderEngineKind(
          kind ?? ((useAsukaStore.getState() as any).activeRenderEngineKind ?? getCurrentRenderEngineKind()),
          'fabric',
        );
        const capabilities = createLiveModuleRenderEngine(targetKind).capabilities;
        return targetKind === '3d'
          ? capabilities.scene3d === true && capabilities.objects !== false
          : capabilities.canvas2d === true && capabilities.objects !== false;
      },
      getCapabilities: () => getActiveRenderEngineFacade().capabilities,
    },

    uiConfig: {
      getConfig: () => normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null),
      setConfig: (config: AsukaUiOptions | null | undefined) => {
        try { (useAsukaStore.getState() as any).setUiConfig?.(config); } catch {}
        return normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null);
      },
      setViewsPagination: (config: NonNullable<AsukaUiOptions['viewsPagination']> | false) => {
        const current = normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null);
        const nextInput: AsukaUiOptions = {
          ...current,
          viewsPagination: config === false
            ? false
            : { ...current.viewsPagination, ...(typeof config === 'object' ? config : {}) },
        };
        try { (useAsukaStore.getState() as any).setUiConfig?.(nextInput); } catch {}
        return normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null).viewsPagination;
      },
      setViewsPaginationEnabled: (enabled: boolean) => {
        const current = normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null);
        const nextInput: AsukaUiOptions = {
          ...current,
          viewsPagination: { ...current.viewsPagination, enabled: !!enabled },
        };
        try { (useAsukaStore.getState() as any).setUiConfig?.(nextInput); } catch {}
        return normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null).viewsPagination;
      },
      setHistoryControls: (config: NonNullable<AsukaUiOptions['historyControls']>) => {
        const current = normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null);
        const nextInput: AsukaUiOptions = {
          ...current,
          historyControls: typeof config === 'boolean'
            ? config
            : { ...current.historyControls, ...config },
        };
        try { (useAsukaStore.getState() as any).setUiConfig?.(nextInput); } catch {}
        return normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null).historyControls;
      },
      setHistoryControlsEnabled: (enabled: boolean) => {
        const current = normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null);
        const nextInput: AsukaUiOptions = { ...current, historyControls: !!enabled };
        try { (useAsukaStore.getState() as any).setUiConfig?.(nextInput); } catch {}
        return normalizeAsukaUiOptions((useAsukaStore.getState() as any).uiConfig ?? null).historyControls;
      },
    },

    responsive: {
      getBreakpoint: () => ((useAsukaStore.getState() as any).responsiveBreakpoint ?? 'desktop') as AsukaResponsiveBreakpoint,
      isMobile: () => ((useAsukaStore.getState() as any).responsiveBreakpoint ?? 'desktop') === 'mobile',
      isTablet: () => ((useAsukaStore.getState() as any).responsiveBreakpoint ?? 'desktop') === 'tablet',
      isDesktop: () => ((useAsukaStore.getState() as any).responsiveBreakpoint ?? 'desktop') === 'desktop',
      getWidth: () => Number((useAsukaStore.getState() as any).responsiveWidth ?? 0) || 0,
      getConfig: () => normalizeResponsiveConfig((useAsukaStore.getState() as any).responsiveConfig ?? DEFAULT_RESPONSIVE_CONFIG),
      setConfig: (config: AsukaResponsiveConfig | null | undefined) => {
        try { (useAsukaStore.getState() as any).setResponsiveConfig?.(config); } catch {}
        return normalizeResponsiveConfig((useAsukaStore.getState() as any).responsiveConfig ?? DEFAULT_RESPONSIVE_CONFIG);
      },
    },

    uploads: {
      list: () => [...(((useAsukaStore.getState() as any).uploadedAssets ?? []) as UploadedAsset[])],
      register: (input: RegisterUploadedAssetInput) => (useAsukaStore.getState() as any).registerUploadedAsset?.(input),
      remove: (id: string) => { try { (useAsukaStore.getState() as any).removeUploadedAsset?.(id); } catch {} },
      clear: () => { try { (useAsukaStore.getState() as any).clearUploadedAssets?.(); } catch {} },
    },

    observability: {
      isEnabled: () => observabilityEnabled,
      report: (error: unknown, context?: Record<string, unknown>) => reportRuntimeError(error, context),
    },

    modules: {
      setDisplayMode: (id: ModuleId, mode: any) => useAsukaStore.getState().setModuleDisplayModeOverride?.(id as any, mode ?? null),
      clearDisplayMode: (id: ModuleId) => useAsukaStore.getState().setModuleDisplayModeOverride?.(id as any, null),
      setMountTarget: (id: ModuleId, target: string | null) => useAsukaStore.getState().setModuleMountTargetOverride?.(id as any, target),
      clearMountTarget: (id: ModuleId) => useAsukaStore.getState().setModuleMountTargetOverride?.(id as any, null),
      getDisplayMode: (id: ModuleId) => (useAsukaStore.getState() as any).moduleDisplayModeOverrides?.[id as any] ?? null,
      getMountTarget: (id: ModuleId) => (useAsukaStore.getState() as any).moduleMountTargetOverrides?.[id as any] ?? null,
    },

    license: {
      get: () => ((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements,
      getResolution: () => ((useAsukaStore.getState() as any).licenseResolution ?? DEFAULT_LICENSE_RESOLUTION) as AsukaLicenseResolutionResult,
      getCatalog: () => ASUKA_LICENSE_FEATURE_CATALOG,
      getPlanCatalog: () => ASUKA_LICENSE_PLAN_CATALOG,
      getEnabledFeatures: () => getEnabledLicenseFeatures(((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements),
      getLimits: () => ((((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements).limits ?? {}),
      getUsage: () => ((((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements).usage ?? {}),
      getQuota: (key: AsukaCommercialQuotaKey) => getCommercialQuotaState((((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements), key),
      getStatus: () => ((((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements).status ?? 'invalid'),
      getActivationState: () => getLicenseActivationState(),
      isActive: () => isLicenseUsable(((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements),
      canUse: (feature: AsukaLicenseFeature | string) => canUseFeature(((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements, feature),
      hasPlan: (plan: Extract<AsukaLicensePlan, 'starter' | 'pro' | 'agency'>) => hasPlan(((useAsukaStore.getState() as any).license ?? DEFAULT_LICENSE) as AsukaLicenseEntitlements, plan),
      setKey: (key: string | null | undefined) => { setRuntimeLicenseInput({ key: key ?? undefined, token: null }); },
      activate: async (input: AsukaLicenseActivateInput = {}) => activateLicenseState(input),
      refresh: () => refreshLicenseState(),
      reset: (options?: { clearCache?: boolean }) => resetLicenseState(options),
      clearCache: () => { try { licenseManager.clearCache(); } catch {} },
      startAutoRefresh: () => { try { licenseRefreshScheduler.start(); } catch {} },
      stopAutoRefresh: () => { try { licenseRefreshScheduler.stop(); } catch {} },
      deactivate: async () => {
        const input = resolveLicenseInput();
        const ok = await licenseManager.deactivate({
          key: input.key,
          domain: input.domain,
          instanceId: input.instanceId,
        });
        if (ok) {
          resetRuntimeLicenseInput();
          try { licenseRefreshScheduler.stop(); } catch {}
          await applyResolvedLicense({ ...DEFAULT_LICENSE_RESOLUTION, payload: DEFAULT_LICENSE, resolvedAt: new Date().toISOString() }, null);
        }
        return ok;
      },
      loadPremiumRuntime: async () => {
        await ensurePremiumRuntimeLoaded();
        try { notifyModuleRegistryChanged(); } catch {}
      },
    },

    agency: agencyRuntime!,
    ai: aiRuntime!,
    exports: exportRuntime!,

    getPermissions: () => ({ ...permissions }),
    getRole: () => {
      try { return (useAsukaStore.getState() as any).role ?? 'dev'; } catch { return 'dev'; }
    },
    setRole: (role: AsukaRole) => {
      const preset = permissionsForRole(role);
      // Preserve per-module overrides if any
      const moduleOpen = (permissions as any).moduleOpen;
      const next = { ...preset, ...(moduleOpen ? { moduleOpen } : {}) } as any;
      Object.assign(permissions, next);
      try { (useAsukaStore.getState() as any).setRole?.(role as any); } catch {}
      try { applyCanvasEditability(getDesignerInstance(), next as any); } catch {}
      emitAsukaEvent('after:permissions.changed', { permissions: { ...permissions }, role });
    },

    setForView: (viewId: string, patch: Partial<AsukaPermissions>) => {
      try { (useAsukaStore.getState() as any).setPermissionsForView?.(viewId, patch as any); } catch {}
      try {
        const s = useAsukaStore.getState() as any;
        const eff = getEffectivePermissions((s.permissions ?? permissions) as any, (s.permissionsByView?.[s.activeViewId ?? ''] ?? null) as any);
        applyCanvasEditability(getDesignerInstance(), eff as any);
      } catch {}
      emitAsukaEvent('after:permissions.changed', { viewId, patch });
    },
    clearForView: (viewId: string) => {
      try { (useAsukaStore.getState() as any).clearPermissionsForView?.(viewId); } catch {}
      try {
        const s = useAsukaStore.getState() as any;
        const eff = getEffectivePermissions((s.permissions ?? permissions) as any, (s.permissionsByView?.[s.activeViewId ?? ''] ?? null) as any);
        applyCanvasEditability(getDesignerInstance(), eff as any);
      } catch {}
      emitAsukaEvent('after:permissions.changed', { viewId, cleared: true });
    },
    getForActiveView: () => {
      try {
        const s = useAsukaStore.getState() as any;
        return (s.permissionsByView?.[s.activeViewId ?? ''] ?? {}) as any;
      } catch {
        return {};
      }
    },
    can: (key: keyof AsukaPermissions) => {
      const s = useAsukaStore.getState() as any;
      const eff: AsukaPermissions = getEffectivePermissions(
        (s.permissions ?? permissions) as any,
        (s.permissionsByView?.[s.activeViewId ?? ''] ?? null) as any,
      );
      return Boolean((eff as any)[key]);
    },
    require: (key: keyof AsukaPermissions, payload: any = {}) => {
      const s = useAsukaStore.getState() as any;
      const eff: AsukaPermissions = getEffectivePermissions(
        (s.permissions ?? permissions) as any,
        (s.permissionsByView?.[s.activeViewId ?? ''] ?? null) as any,
      );
      if (key === 'canOpenModule' && payload?.id) {
        if (canOpenModuleId(eff, payload.id)) return;
      } else if ((eff as any)[key]) {
        return;
      }
      const reason = payload?.reason ?? `Permission denied: ${String(key)}`;
      const ok = emitAsukaEventCancelable('before:permission.denied', { key, reason, ...payload });
      if (!ok) return;
      emitAsukaEvent('after:permission.denied', { key, reason, ...payload });
      throw new Error(reason);
    },

    setPermissions: (patch: Partial<AsukaPermissions>) => {
      const next = mergePermissions(permissions, patch);
      Object.assign(permissions, next);
      try { useAsukaStore.getState().setPermissions?.(patch as any); } catch {}
      try { applyCanvasEditability(getDesignerInstance(), next as any); } catch {}
      emitAsukaEvent('after:permissions.changed', { permissions: { ...permissions } });
    },

    operators: (operator: string, a: number, b: number) => {
      switch (operator) {
        case '>': return a > b;
        case '<': return a < b;
        case '>=': return a >= b;
        case '<=': return a <= b;
        case '==': return a == b;
        case '%': return Math.floor(a / b);
        default: return false;
      }
    },

    formatPrice: (amount: number | string, overrides: Partial<PriceFormat> = {}) => {
      const FALLBACK: Required<PriceFormat> = {
        code: '',
        symbol: '',
        priceFormat: '%2$s',
        decimalSeparator: '.',
        thousandSeparator: '',
        precision: 2,
        trimZeros: false,
      };

      const fromStore = (() => {
        try {
          return (useAsukaStore.getState() as any)?.defaults?.priceFormat ?? {};
        } catch {
          return {};
        }
      })();

      const precision = Math.max(0, parseInt(String((overrides.precision ?? fromStore.precision ?? FALLBACK.precision)), 10) || 0);
      const s: Required<PriceFormat> = {
        ...FALLBACK,
        ...fromStore,
        ...overrides,
        precision,
      };

      const toNumber = (x: number | string) => {
        if (typeof x === 'number') return x;
        const normalized = String(x).trim().replace(/\s/g, '').replace(',', '.');
        const n = Number(normalized);
        return Number.isFinite(n) ? n : 0;
      };

      const escapeReg = (t: string) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

      const neg = toNumber(amount) < 0;
      const abs = Math.abs(toNumber(amount));
      let fixed = abs.toFixed(s.precision);

      let [intPart, decPart = ''] = fixed.split('.');
      if (s.thousandSeparator) {
        intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, s.thousandSeparator);
      }

      let num = s.precision > 0 ? intPart + s.decimalSeparator + decPart : intPart;
      if (s.trimZeros && s.precision > 0) {
        const re = new RegExp(escapeReg(s.decimalSeparator) + '0+$');
        num = num.replace(re, '');
      }
      if (neg) num = '-' + num;

      const fmt = s.priceFormat || '%2$s';
      return fmt.replace('%1$s', s.symbol ?? '').replace('%2$s', num);
    },


  };

  const theme: AsukaThemeApi = {
    setColor: (token: string, value: string) => setThemeColor(token, value),
    setColors: (colors: Record<string, string>) => setThemeColors(colors),
    getColor: (token: string) => getThemeColor(token),
    reset: (preset?: any) => resetTheme(preset),
    resetColor: (token: string) => resetThemeColor(token),
    resetSection: (section: any) => resetThemeSection(section),
    applyPreset: (preset: any) => applyThemePreset(preset),
    getPresets: () => getThemePresets(),
  };

  // Register fonts declared at boot.
  try {
    const initialFonts = (options as any)?.fonts as AsukaFontsConfig | undefined;
    const baseFonts = getDefaultSystemFonts();
    const extraFonts = [
      ...(initialFonts?.system ?? []).map((font) => normalizeSystemFont(font as any)),
      ...(initialFonts?.google ?? []).map((font) => normalizeGoogleFont(font as any)),
      ...(initialFonts?.custom ?? []).map((font) => normalizeCustomFont(font as any)),
    ];
    const mergedFonts = dedupeFonts([...(baseFonts ?? []), ...(extraFonts ?? [])]);
    (useAsukaStore as any).setState?.((state: any) => ({
      fontsCatalog: dedupeFonts([...(state?.fontsCatalog ?? []), ...mergedFonts]),
    }));

    for (const font of extraFonts) {
      if (font?.source === 'system') continue;
      (useAsukaStore as any).setState?.((state: any) => ({
        fontsCatalog: (state?.fontsCatalog ?? []).map((entry: any) =>
          String(entry?.id) === String(font.id) ? { ...entry, loading: true, error: undefined } : entry,
        ),
      }));
      void loadRegisteredFont(font as any)
        .then((loaded) => {
          (useAsukaStore as any).setState?.((state: any) => ({
            fontsCatalog: (state?.fontsCatalog ?? []).map((entry: any) =>
              String(entry?.id) === String(font.id)
                ? { ...entry, loading: false, loaded: !!loaded, error: loaded ? undefined : 'Failed to load font' }
                : entry,
            ),
          }));
        })
        .catch(() => {
          (useAsukaStore as any).setState?.((state: any) => ({
            fontsCatalog: (state?.fontsCatalog ?? []).map((entry: any) =>
              String(entry?.id) === String(font.id)
                ? { ...entry, loading: false, loaded: false, error: 'Failed to load font' }
                : entry,
            ),
          }));
        });
    }
  } catch (_e) {
    void 0;
  }

  // Apply moduleConfig once at init (ex: Upload multiple mode, Designs list, etc.)
  // NOTE: This must NOT live inside util.setPermissions.
  try {
    const mc = (options as any)?.moduleConfig;
    if (mc) (useAsukaStore.getState() as any).setModuleConfig?.(mc);
  } catch (_e) {
    void 0;
  }

  // Keep the Pricing panel/widget in sync with canvas + store.
  const stopPricingController = startPricingController();


const runBootStateRestore = () => {
  if (destroyed || !bootStateRestore) return;
  try { (useAsukaStore.getState() as any).setBootStatus?.('Restoring workspace…'); } catch {}
  try {
    debugLog('boot', 'applying canonical state boot payload');
    restoreFullAsukaState(bootStateRestore.state as AsukaExportedState, { replaceViews: true, replaceSnapshots: true });
  } catch (_e) {
    void 0;
  }
};


  const registerCustomModule = (definition: AsukaModuleDefinition, options: AsukaRegisterModuleOptions = {}) => {
    if (!definition || typeof definition !== 'object' || !definition.id) {
      throw new Error('registerModule: definition.id is required');
    }
    registerModule(definition);

    const addToLayout = options?.addToLayout;
    if (!addToLayout) return;

    const layoutItem: AsukaLayoutItemRegistration = typeof addToLayout === 'object'
      ? (addToLayout as AsukaLayoutItemRegistration)
      : ({
          kind: definition.id as ModuleId,
          label: definition.label ?? definition.title ?? String(definition.id),
          icon: definition.icon ?? 'views',
          type: definition.displayMode ?? 'floating',
        } satisfies AsukaLayoutItemRegistration);

    const targetZone = options.layoutZone === 'top' || options.layoutZone === 'right' || options.layoutZone === 'bottom' || options.layoutZone === 'left'
      ? options.layoutZone
      : 'left';

    addLayoutItemToZone(targetZone, {
      kind: layoutItem.kind,
      label: layoutItem.label,
      icon: layoutItem.icon,
      type: layoutItem.type,
      supportedEngines: normalizeLayoutItemSupportedEngines((layoutItem as any).supportedEngines ?? (definition as any).supportedEngines),
      requiredCapabilities: normalizeLayoutItemRequiredCapabilities((layoutItem as any).requiredCapabilities ?? (definition as any).requiredCapabilities),
      mountTarget: layoutItem.mountTarget,
      collapsed: layoutItem.collapsed,
      renderMode: layoutItem.renderMode,
      id: layoutItem.id,
    }, layoutItem.index);
  };

  const destroy = () => {
    if (destroyed) return;
    destroyed = true;
    if (bootReadyTimer !== null) {
      clearTimeout(bootReadyTimer);
      bootReadyTimer = null;
    }
    try {
      stopPricingController?.();
    } catch {
      // ignore
    }
    try {
      licenseRefreshScheduler.stop();
    } catch {
      // ignore
    }
    try {
      window.removeEventListener('error', onRuntimeWindowError);
      window.removeEventListener('unhandledrejection', onRuntimeUnhandledRejection);
    } catch {
      // ignore
    }
    try {
      useAsukaStore.getState().setAiRuntime?.(null);
      useAsukaStore.getState().setExportRuntime?.(null);
    } catch {
      // ignore
    }
    try {
      if (ASUKA_INSTANCE_REGISTRY.get(rootElement) === instance) ASUKA_INSTANCE_REGISTRY.delete(rootElement);
      if (ACTIVE_ASUKA_INSTANCE?.instance === instance) ACTIVE_ASUKA_INSTANCE = null;
    } catch {}
    render(null as any, rootElement);
    instanceRef.current = null;
  };

  const bootTasks: Promise<unknown>[] = [];
  if (licenseBootPromise) bootTasks.push(licenseBootPromise);

  try {
    const restorePromise = new Promise<void>((resolve) => {
      const schedule = typeof queueMicrotask === 'function'
        ? queueMicrotask
        : (cb: () => void) => setTimeout(cb, 0);
      schedule(() => {
        try {
          runBootStateRestore();
        } finally {
          resolve();
        }
      });
    });
    bootTasks.push(restorePromise);
  } catch (_e) {
    void 0;
  }

  const markBootReady = () => {
    if (destroyed) return;
    try {
      const state = useAsukaStore.getState() as any;
      state.setBootStatus?.('Ready');
      state.setBootReady?.(true);
    } catch {}
  };

  Promise.allSettled([
    ...bootTasks,
    new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))),
  ]).then(() => {
    markBootReady();
  });

  bootReadyTimer = setTimeout(() => {
    markBootReady();
    bootReadyTimer = null;
  }, 4000);

  const instance: AsukaDesigner = {
    rootElement,
    ui: getVanillaUiHelpers(),
    registerModule: registerCustomModule,
    layout,
    api,
    variations,
    assembly,
    measureLayer: api.measureLayer,
    engine,
    util,
    theme,
    setColor: theme.setColor,
    getColor: theme.getColor,
    setColors: theme.setColors,
    resetColors: theme.reset,
    // Expose the Zustand store for advanced integrations / debugging.
    store: useAsukaStore,
    destroy,
  };
  instanceRef.current = instance;
  try { ASUKA_INSTANCE_REGISTRY.set(rootElement, instance); } catch {}
  ACTIVE_ASUKA_INSTANCE = { rootElement, instance };
  return instance;
}
