/**
 * Rendering-engine abstraction used to keep AsukaDesigner product/UI code
 * independent from the concrete rendering backend.
 *
 * Fabric remains the default 2D engine. The 3D engine is intentionally loaded
 * through an addon/runtime bridge so Babylon/3D dependencies do not have to live
 * in the base bundle.
 */
import type { AsukaElement, ElementVariation } from '../types/elements';
import type { AsukaCameraLimits, AsukaCameraMoveOptions, AsukaCameraView } from '../types/camera';
import type {
  AsukaParametricMutationOptions,
  AsukaParametricProductDescriptor,
  AsukaParametricState,
  AsukaParametricValue,
} from '../types/parametric3d';
import type {
  AsukaAssemblyBom,
  AsukaAssemblyComponent,
  AsukaAssemblyConnection,
  AsukaAssemblyExportKind,
  AsukaAssemblyExportOptions,
  AsukaAssemblyFloorMetrics,
  AsukaAssemblyFloorEdgeConstraint,
  AsukaAssemblyFloorSurface,
  AsukaAssemblyBuildState,
  AsukaAssemblyInteractionState,
  AsukaAssemblyMutationOptions,
  AsukaAssemblyMaterialApplyOptions,
  AsukaAssemblyMaterialPreset,
  AsukaAssemblyPlacementInput,
  AsukaAssemblyPlacementResult,
  AsukaAssemblyQuote,
  AsukaAssemblyQuoteOptions,
  AsukaAssemblyStackLink,
  AsukaAssemblyStartDrawingInput,
  AsukaAssemblyState,
  AsukaAssemblySurface,
  AsukaAssemblySurfacePatch,
  AsukaAssemblySurfaceTransform,
  AsukaAssemblySurfaceTransformInput,
  AsukaAssemblyValidation,
} from '../types/assembly3d';
import { collectDeclaredPricingColors, collectPricingColorsFromSvg } from './pricingColors';
import {
  createAsukaLayerMeasurement,
  type AsukaLayerMeasurement,
  type AsukaLayerMeasurementOptions,
  type AsukaPhysicalLayerMeasurement,
  type AsukaPhysicalSize,
} from './physicalMeasurement';
import {
  createAsukaPlacementSessionController,
  type AsukaPlacementSessionBeginInput,
  type AsukaPlacementSessionStatus,
  type AsukaPlacementSessionUpdateInput,
} from './placementSession';

export type AsukaRenderEngineKind = 'fabric' | '3d';

export type AsukaCoordinateSpace =
  | 'screen'
  | 'canvas'
  | 'texture'
  | 'uv'
  | 'world'
  | 'surface-tangent';

export type AsukaCoordinateUnit =
  | 'css-px'
  | 'canvas-px'
  | 'texture-px'
  | 'normalized'
  | 'world-unit';

/** Every public coordinate DTO declares both its reference space and unit. */
export interface AsukaCoordinateFrame {
  space: AsukaCoordinateSpace;
  unit: AsukaCoordinateUnit;
}

export type AsukaRendererErrorCode =
  | 'INVALID_COORDINATE_FRAME'
  | 'RENDERER_CONTRACT_VIOLATION'
  | 'RENDERER_NOT_MOUNTED'
  | 'UNSUPPORTED_OPERATION'
  | 'INVALID_INPUT'
  | 'NOT_FOUND'
  | 'RUNTIME_FAILURE';

export interface AsukaRendererErrorContext {
  engine?: AsukaRenderEngineKind | string;
  operation?: string;
  details?: Record<string, unknown>;
}

export class AsukaRendererError extends Error {
  readonly code: AsukaRendererErrorCode;
  readonly engine?: AsukaRenderEngineKind | string;
  readonly operation?: string;
  readonly details?: Record<string, unknown>;

  constructor(code: AsukaRendererErrorCode, message: string, context: AsukaRendererErrorContext = {}) {
    super(message);
    this.name = 'AsukaRendererError';
    this.code = code;
    this.engine = context.engine;
    this.operation = context.operation;
    this.details = context.details;
  }
}

export interface AsukaMutationResult<T = undefined> {
  ok: true;
  changed: boolean;
  engine: AsukaRenderEngineKind;
  operation: string;
  value: T;
  id?: string | null;
}

export type AsukaMutationPromise<T = undefined> = Promise<AsukaMutationResult<T>>;

export interface AsukaMutationRunOptions<T> {
  changed?: boolean | ((value: T) => boolean);
  id?: string | null | ((value: T) => string | null | undefined);
}

export async function runAsukaRendererMutation<T>(
  engine: AsukaRenderEngineKind,
  operation: string,
  action: () => T | Promise<T>,
  options: AsukaMutationRunOptions<T> = {},
): AsukaMutationPromise<T> {
  try {
    const value = await action();
    const changed = typeof options.changed === 'function'
      ? options.changed(value)
      : options.changed ?? true;
    const rawId = typeof options.id === 'function' ? options.id(value) : options.id;
    const result: AsukaMutationResult<T> = {
      ok: true,
      changed: Boolean(changed),
      engine,
      operation,
      value,
      ...(rawId !== undefined ? { id: rawId } : null),
    };
    emitRenderEngineEvent({
      type: 'mutation',
      engine,
      action: operation,
      id: rawId ?? null,
      payload: { ok: true, changed: result.changed },
    });
    return result;
  } catch (error) {
    const rendererError = error instanceof AsukaRendererError
      ? error
      : new AsukaRendererError(
          'RUNTIME_FAILURE',
          error instanceof Error ? error.message : String(error ?? `Renderer mutation ${operation} failed.`),
          {
            engine,
            operation,
            details: error instanceof Error ? { causeName: error.name } : undefined,
          },
        );
    emitRenderEngineEvent({
      type: 'error',
      engine,
      action: operation,
      payload: { code: rendererError.code, message: rendererError.message },
    });
    throw rendererError;
  }
}

export function assertCompatibleCoordinateFrame(
  input: Partial<AsukaCoordinateFrame> | null | undefined,
  expected: AsukaCoordinateFrame,
  context: AsukaRendererErrorContext = {},
): void {
  if (input?.space == null && input?.unit == null) return;
  if (input?.space === expected.space && input?.unit === expected.unit) return;
  throw new AsukaRendererError(
    'INVALID_COORDINATE_FRAME',
    `Expected ${expected.space}/${expected.unit} coordinates, received ${String(input?.space ?? 'missing')}/${String(input?.unit ?? 'missing')}.`,
    { ...context, details: { ...context.details, expected, received: { space: input?.space, unit: input?.unit } } },
  );
}

export interface AsukaObjectRef {
  id: string;
  kind?: string;
}

export interface AsukaPricingSize extends AsukaCoordinateFrame {
  width: number;
  height: number;
  /** Available drawing-area extent expressed in the same space/unit as width and height. */
  referenceWidth?: number;
  referenceHeight?: number;
  /** Real-world dimensions when a view or print area has been calibrated. */
  physical?: AsukaPhysicalLayerMeasurement;
}

export interface AsukaObjectPricing {
  /** Stable billing identity used to deduplicate one logical object across snapshots/views. */
  key?: string;
  category?: 'layer' | 'element' | 'product' | string;
  price?: number | string;
  basePrice?: number | string;
  variationPrice?: number | string;
  text?: string;
  fill?: string | null;
  fills?: string[];
  printingMethodId?: string | null;
  width?: number;
  height?: number;
  scaleX?: number;
  scaleY?: number;
  viewId?: string;
  /** Explicit renderer metric used by cross-engine size and coverage pricing rules. */
  size?: AsukaPricingSize;
}

export interface AsukaObjectSummary extends AsukaObjectRef {
  label?: string;
  visible?: boolean;
  locked?: boolean;
  selected?: boolean;
  parentId?: string | null;
  /** Engine-neutral parent print/drawing area, when this object is attached to one. */
  targetPrintAreaId?: string | null;
  targetMeshId?: string | null;
  printAreaLabel?: string | null;
  children?: AsukaObjectSummary[];
  /** Engine-neutral pricing payload. Native Fabric/Babylon objects never cross this boundary. */
  pricing?: AsukaObjectPricing;
  metadata?: Record<string, unknown>;
}

export interface AsukaObjectListOptions {
  /** Include technical/runtime objects. Layers modules should keep this false. */
  includeTechnical?: boolean;
  /** Limit user objects to one engine-neutral print/drawing area. */
  printAreaId?: string | null;
  /** Include readonly product summaries. Individual runtime meshes still require includeTechnical. */
  includeReadonly?: boolean;
  /** Compute viewport screen boxes. This can be expensive for large Babylon scenes. */
  includeScreenBox?: boolean;
  /** Internal targeted lookup used by getObject/getSelectedObjects without scanning full scenes. */
  objectId?: string | null;
  /** Optional source string used by adapters for analytics/debugging. */
  source?: string;
  [key: string]: unknown;
}

export interface AsukaObjectOrderOptions {
  /** `display` means top-to-bottom UI order; `canvas` means native render order. */
  order?: 'display' | 'canvas';
  source?: string;
  [key: string]: unknown;
}

export interface AsukaObjectPreviewOptions {
  mode?: 'outline' | 'fade-others' | string;
  dimOpacity?: number;
  [key: string]: unknown;
}

export interface AsukaSelectionAnchorBox extends AsukaCoordinateFrame {
  left: number;
  top: number;
  width: number;
  height: number;
  objectId?: string | null;
  engine?: AsukaRenderEngineKind | string;
  metadata?: Record<string, unknown>;
}

export interface AsukaBoundsBox extends AsukaCoordinateFrame {
  x: number;
  y: number;
  width: number;
  height: number;
  left: number;
  top: number;
  right: number;
  bottom: number;
  centerX: number;
  centerY: number;
  z?: number;
  depth?: number;
  engine?: AsukaRenderEngineKind | string;
  objectId?: string | null;
  printAreaId?: string | null;
  metadata?: Record<string, unknown>;
}

export interface AsukaPrintAreaConstraintStatus {
  objectId: string;
  printAreaId: string | null;
  inside: boolean;
  intersects: boolean;
  objectBounds?: AsukaBoundsBox | null;
  printAreaBounds?: AsukaBoundsBox | null;
  engine?: AsukaRenderEngineKind | string;
  metadata?: Record<string, unknown>;
}


export type AsukaSurfacePickTargetKind = 'object' | 'print-area' | 'mesh' | 'product' | 'none' | string;

export interface AsukaSurfacePickTarget {
  kind: AsukaSurfacePickTargetKind;
  objectId?: string | null;
  printAreaId?: string | null;
  meshId?: string | null;
  label?: string | null;
  point?: [number, number, number] | null;
  pointSpace?: 'world';
  pointUnit?: 'world-unit';
  normal?: [number, number, number] | null;
  uv?: { x: number; y: number } | null;
  uvSpace?: 'uv';
  uvUnit?: 'normalized';
  distance?: number | null;
  priority?: number;
  metadata?: Record<string, unknown>;
}

export interface AsukaSurfacePickOptionsBase {
  source?: string;
  [key: string]: unknown;
}

export type AsukaSurfacePickOptions =
  | (AsukaSurfacePickOptionsBase & {
      space: 'screen';
      unit: 'css-px';
      clientX: number;
      clientY: number;
    })
  | (AsukaSurfacePickOptionsBase & {
      space: 'canvas';
      unit: 'canvas-px';
      x: number;
      y: number;
    });

export interface AsukaSurfaceInteractionDebug {
  engine?: AsukaRenderEngineKind | string;
  selectedObjectId?: string | null;
  activePrintAreaId?: string | null;
  pickedObjectId?: string | null;
  pickedPrintAreaId?: string | null;
  pickedMeshId?: string | null;
  lastPick?: AsukaSurfacePickTarget | null;
  occlusion?: {
    depthTest?: boolean;
    depthWrite?: boolean;
    renderingGroupId?: number | null;
    textureSlotsOpaque?: boolean;
    overlaysMatchProductRenderingGroup?: boolean;
    [key: string]: unknown;
  };
  metadata?: Record<string, unknown>;
}

export interface AsukaPrintAreaConstraintOptions {
  printAreaId?: string | null;
  targetPrintAreaId?: string | null;
  source?: string;
  commit?: boolean;
  [key: string]: unknown;
}

export type AsukaPrintAreaKind = 'drawing-area' | 'print-area' | 'print-surface' | string;

/**
 * Engine-neutral customizable area.
 * Fabric maps this to drawing zones; Babylon maps it to GLB print surfaces / texture slots.
 */
export interface AsukaPrintArea extends AsukaCoordinateFrame {
  id: string;
  label?: string;
  engine?: AsukaRenderEngineKind | string;
  kind?: AsukaPrintAreaKind;
  targetMeshId?: string;
  targetMeshIds?: string[];
  x?: number;
  y?: number;
  left?: number;
  top?: number;
  width?: number;
  height?: number;
  shape?: string;
  mode?: string;
  textureSlot?: string;
  canvasSize?: { width?: number; height?: number };
  /** Real-world printable extent represented by this area. */
  physicalSize?: AsukaPhysicalSize;
  objects?: unknown[];
  visible?: boolean;
  locked?: boolean;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface AsukaPrintAreaInput extends Partial<AsukaPrintArea> {
  id?: string;
  label?: string;
  targetMeshId?: string;
  targetMeshIds?: string[];
  metadata?: Record<string, unknown>;
}

export interface AsukaPrintAreaPatch extends Partial<AsukaPrintArea> {
  metadata?: Record<string, unknown>;
}


export interface Asuka3DPart {
  id: string;
  label?: string;
  engine?: AsukaRenderEngineKind | string;
  meshIds?: string[];
  meshNames?: string[];
  materialIds?: string[];
  materialNames?: string[];
  materialTarget?: string;
  printAreaIds?: string[];
  editable?: boolean;
  printable?: boolean;
  visible?: boolean;
  locked?: boolean;
  selected?: boolean;
  activeVariationId?: string | null;
  activeVariationLabel?: string | null;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface Asuka3DPartVariation {
  id: string;
  label?: string;
  name?: string;
  partId?: string;
  previewUrl?: string;
  image?: string;
  icon?: string;
  active?: boolean;
  path?: Array<{ id?: string; name?: string }>;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface Asuka3DPartVariationApplyOptions {
  element?: AsukaElement | Record<string, unknown>;
  variation?: ElementVariation | Record<string, unknown> | null;
  variationPath?: Array<ElementVariation | Record<string, unknown>>;
  source?: string;
  focus?: boolean;
  animate?: boolean;
  metadata?: Record<string, unknown>;
  productId?: string;
  /** Defer history while a higher-level variation action updates several linked targets. */
  commitHistory?: boolean;
  /** Suppress intermediate change events while a higher-level batch is still running. */
  emitChange?: boolean;
}


export interface AsukaRenderEngineCapabilities {
  /** Engine renders and edits a Fabric/2D canvas. */
  canvas2d?: boolean;
  /** Engine renders and edits a 3D scene. */
  scene3d?: boolean;
  /** Engine can expose selectable/editable objects. */
  objects?: boolean;
  /** Engine can expose native 3D meshes. */
  meshes?: boolean;
  /** Engine can expose or edit materials. */
  materials?: boolean;
  /** Engine can expose logical product parts. */
  parts?: boolean;
  /** Engine can apply variations to a single product part without re-applying the whole element. */
  partVariations?: boolean;
  /** Engine can evaluate and apply generic Product 3D parameter recipes. */
  parametricProducts?: boolean;
  /** Engine can place and connect generic components in a constrained 3D assembly. */
  assembly3d?: boolean;
  /** Engine can expose print/decal areas. */
  printAreas?: boolean;
  /** Engine can constrain layer movement/transforms to drawing/print areas. */
  surfaceConstraints?: boolean;
  /** Engine can pick objects/print surfaces on a rendered product. */
  surfacePicking?: boolean;
  /** Engine keeps surface-bound layers occluded by product geometry instead of rendering through opposite faces. */
  surfaceVisibility?: boolean;
  /** Engine can expose decorative objects such as text, images or designs. */
  decorations?: boolean;
  addImage?: boolean;
  addText?: boolean;
  addDesign?: boolean;
  addElement?: boolean;
  selectObject?: boolean;
  transform?: boolean;
  color?: boolean;
  visibility?: boolean;
  locking?: boolean;
  removeObject?: boolean;
  reorderObjects?: boolean;
  duplicateObject?: boolean;
  objectTransform?: boolean;
  zOrder?: boolean;
  previewObject?: boolean;
  selectionToolbar?: boolean;
  exportPng?: boolean;
  exportJpeg?: boolean;
  exportWebp?: boolean;
  exportSvg?: boolean;
  exportPdf?: boolean;
  exportJson?: boolean;
  exportState?: boolean;
  download?: boolean;
  batchExport?: boolean;
  history?: boolean;
  [key: string]: boolean | undefined;
}

/** String key accepted by module/layout capability gates. */
export type AsukaRenderEngineCapabilityKey = keyof AsukaRenderEngineCapabilities & string;

export type AsukaSceneGraphNodeCategory =
  | 'object'
  | 'decoration'
  | 'product'
  | 'mesh'
  | 'material'
  | 'print-area'
  | 'camera'
  | 'light'
  | 'group'
  | string;

export interface AsukaSceneGraphNode extends AsukaObjectSummary {
  category?: AsukaSceneGraphNodeCategory;
  depth?: number;
  readonly?: boolean;
  selectable?: boolean;
  actions?: {
    select?: boolean;
    hide?: boolean;
    lock?: boolean;
    delete?: boolean;
    transform?: boolean;
    color?: boolean;
    [key: string]: boolean | undefined;
  };
}

export interface Asuka3DMeshSummary {
  id: string;
  label?: string;
  name?: string;
  materialId?: string;
  materialName?: string;
  vertices?: number;
  visible?: boolean;
  metadata?: Record<string, unknown>;
}

export interface AsukaImageInput {
  url?: string;
  src?: string;
  file?: File;
  name?: string;
  color?: string;
  fill?: string;
  printingMethodId?: string | null;
  price?: number | string;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface AsukaTextInput {
  text: string;
  fontFamily?: string;
  fontSize?: number;
  color?: string;
  printingMethodId?: string | null;
  price?: number | string;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface AsukaDesignInput extends AsukaImageInput {
  svg?: string;
  svgText?: string;
}

export interface AsukaElementInput {
  element: AsukaElement | Record<string, unknown>;
  variation?: ElementVariation | Record<string, unknown> | null;
  /** Full recursive variation path from root to selected leaf. Used by 3D products to inherit parent material values. */
  variationPath?: Array<ElementVariation | Record<string, unknown>>;
  variationId?: string;
  source?: string;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}


export interface AsukaObjectPatch {
  space?: AsukaCoordinateSpace;
  unit?: AsukaCoordinateUnit;
  visible?: boolean;
  locked?: boolean;
  opacity?: number;
  x?: number;
  y?: number;
  z?: number;
  width?: number;
  height?: number;
  rotation?: number;
  rotationX?: number;
  rotationY?: number;
  rotationZ?: number;
  scale?: number;
  scaleX?: number;
  scaleY?: number;
  scaleZ?: number;
  color?: string;
  printingMethodId?: string | null;
  price?: number | string;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface AsukaObjectTransform extends AsukaCoordinateFrame {
  id: string;
  x: number;
  y: number;
  z?: number;
  width?: number;
  height?: number;
  rotation?: number;
  rotationX?: number;
  rotationY?: number;
  rotationZ?: number;
  scale?: number;
  scaleX?: number;
  scaleY?: number;
  scaleZ?: number;
  opacity?: number;
  visible?: boolean;
  locked?: boolean;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface AsukaObjectMutationOptions {
  source?: string;
  select?: boolean;
  commitHistory?: boolean;
  [key: string]: unknown;
}

export type AsukaPlacementStatus = AsukaPlacementSessionStatus<AsukaObjectTransform>;
export type AsukaPlacementBeginInput = AsukaPlacementSessionBeginInput<AsukaObjectTransform>;
export type AsukaPlacementUpdateInput = AsukaPlacementSessionUpdateInput<AsukaObjectTransform>;

export interface AsukaHistoryInfo {
  index: number;
  length: number;
  canUndo: boolean;
  canRedo: boolean;
  maxSnapshots: number;
  label?: string;
  engine?: AsukaRenderEngineKind | string;
  [key: string]: unknown;
}

export interface AsukaHistoryOptions {
  source?: string;
  reason?: string;
  force?: boolean;
  reset?: boolean;
  state?: unknown;
  [key: string]: unknown;
}

export interface AsukaObjectDuplicateOptions extends AsukaObjectMutationOptions {
  offsetX?: number;
  offsetY?: number;
  offsetZ?: number;
}

export interface AsukaExportOptions {
  filename?: string;
  name?: string;
  format?: string;
  viewId?: string;
  scale?: number;
  quality?: number;
  [key: string]: unknown;
}

export interface AsukaDownloadTarget {
  __asukaDownloadKind?: 'canvas' | 'view' | 'zone' | 'drawing-area' | 'object' | string;
  id?: string;
  viewId?: string;
  zoneId?: string;
  printAreaId?: string;
  objectId?: string;
  [key: string]: unknown;
}

export interface AsukaDownloadOptions extends AsukaExportOptions {
  zip?: boolean;
  dpi?: number;
  width?: number | null;
  unit?: string;
  transparent?: boolean;
  cropMark?: boolean;
  marks?: Record<string, unknown>;
}

export interface AsukaExportSupportOptions {
  format?: string;
  autoLoad?: boolean;
  [key: string]: unknown;
}


export interface AsukaRenderEngineEvent {
  type: 'changed' | 'selection' | 'history' | 'viewport' | 'ready' | 'error' | string;
  engine: AsukaRenderEngineKind;
  action?: string;
  id?: string | null;
  viewId?: string | null;
  payload?: unknown;
}

export type AsukaRenderEngineEventListener = (event: AsukaRenderEngineEvent) => void;

export interface AsukaRenderEngineEvents {
  subscribe(listener: AsukaRenderEngineEventListener): () => void;
}

export interface AsukaViewActivationOptions {
  mode?: 'mount' | 'switch' | 'restore';
}

export interface AsukaRenderEngineLifecycle {
  mount(container: HTMLElement): void | Promise<void>;
  unmount(): void;
  activateView(viewId: string, options?: AsukaViewActivationOptions): void | Promise<void>;
  deactivateView(viewId: string): void | Promise<void>;
  loadView(viewId: string, state: unknown): Promise<void>;
  exportView(viewId: string): Promise<unknown>;
}

export interface AsukaRenderEngineHistory {
  getInfo(): AsukaHistoryInfo;
  commit(options?: AsukaHistoryOptions): AsukaMutationPromise<AsukaHistoryInfo>;
  exportTrack(viewId?: string | null): unknown | null;
  importTrack(viewId: string, track?: unknown | null): AsukaMutationPromise<AsukaHistoryInfo>;
  undo(): AsukaMutationPromise<AsukaHistoryInfo>;
  redo(): AsukaMutationPromise<AsukaHistoryInfo>;
  reset(): AsukaMutationPromise<AsukaHistoryInfo>;
  goTo(index: number): AsukaMutationPromise<AsukaHistoryInfo>;
}

export interface AsukaRenderEngineScene {
  getGraph(): AsukaSceneGraphNode[];
}

export interface AsukaRenderEngineObjects {
  list(options?: AsukaObjectListOptions): AsukaObjectSummary[];
  get(id: string): AsukaObjectSummary | null;
  addImage(input: AsukaImageInput): AsukaMutationPromise<AsukaObjectRef>;
  addText(input: AsukaTextInput): AsukaMutationPromise<AsukaObjectRef>;
  addDesign(input: AsukaDesignInput): AsukaMutationPromise<AsukaObjectRef>;
  addElement(input: AsukaElementInput): AsukaMutationPromise<AsukaObjectRef>;
  removeElement(id?: string, options?: Record<string, unknown>): AsukaMutationPromise<AsukaObjectRef | null>;
  remove(id: string): AsukaMutationPromise<AsukaObjectSummary | null>;
  update(id: string, patch: AsukaObjectPatch): AsukaMutationPromise<AsukaObjectSummary | null>;
  reorder(orderedIds: string[], options?: AsukaObjectOrderOptions): AsukaMutationPromise<string[]>;
  duplicate(id: string, options?: AsukaObjectDuplicateOptions): AsukaMutationPromise<AsukaObjectRef | null>;
  setVisible(id: string, visible: boolean, options?: AsukaObjectMutationOptions): AsukaMutationPromise<AsukaObjectSummary | null>;
  setLocked(id: string, locked: boolean, options?: AsukaObjectMutationOptions): AsukaMutationPromise<AsukaObjectSummary | null>;
  bringForward(id: string, options?: AsukaObjectMutationOptions): AsukaMutationPromise<AsukaObjectSummary | null>;
  sendBackward(id: string, options?: AsukaObjectMutationOptions): AsukaMutationPromise<AsukaObjectSummary | null>;
  getTransform(id: string): AsukaObjectTransform | null;
  updateTransform(id: string, patch: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaObjectMutationOptions): AsukaMutationPromise<AsukaObjectTransform | null>;
  getBounds(id: string): AsukaBoundsBox | null;
  /** Measure one layer in native, relative and calibrated physical units. */
  measure(id: string, options?: AsukaLayerMeasurementOptions): AsukaLayerMeasurement | null;
  preview(id: string, options?: AsukaObjectPreviewOptions): void;
  clearPreview(): void;
}

export interface AsukaRenderEngineSelection {
  list(): AsukaObjectSummary[];
  getAnchorBox(): AsukaSelectionAnchorBox | null;
  select(id: string | null): void;
}

/**
 * Shared interaction lifecycle. Geometry remains renderer-owned while this
 * service guarantees the same last-valid, commit and cancel semantics.
 */
export interface AsukaRenderEnginePlacement {
  getStatus(): AsukaPlacementStatus | null;
  begin(input: AsukaPlacementBeginInput): AsukaPlacementStatus;
  update(input: AsukaPlacementUpdateInput): AsukaPlacementStatus;
  commit(metadata?: Record<string, unknown>): AsukaPlacementStatus | null;
  cancel(metadata?: Record<string, unknown>): AsukaPlacementStatus | null;
  clear(): void;
}

export interface AsukaViewportFrame extends AsukaCoordinateFrame {
  left: number;
  top: number;
  width: number;
  height: number;
  documentWidth: number;
  documentHeight: number;
  zoomX: number;
  zoomY: number;
}

export interface AsukaRenderEngineAreas {
  list(): AsukaPrintArea[];
  get(id: string): AsukaPrintArea | null;
  pickForAdd(): Promise<string | null>;
  cancelPickForAdd?(): void;
  create(input?: AsukaPrintAreaInput): AsukaMutationPromise<AsukaPrintArea | null>;
  update(id: string, patch: AsukaPrintAreaPatch): AsukaMutationPromise<AsukaPrintArea | null>;
  remove(id: string): AsukaMutationPromise<AsukaPrintArea | null>;
  select(id: string | null): void;
  getActive(): AsukaPrintArea | null;
  focus(id: string): void | Promise<void>;
  getBounds(id: string): AsukaBoundsBox | null;
  contains(objectId: string, printAreaId?: string | null, options?: AsukaPrintAreaConstraintOptions): AsukaPrintAreaConstraintStatus;
  constrain(objectId: string, printAreaId?: string | null, options?: AsukaPrintAreaConstraintOptions): AsukaMutationPromise<AsukaPrintAreaConstraintStatus>;
  moveObject(objectId: string, delta: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaPrintAreaConstraintOptions): AsukaMutationPromise<AsukaPrintAreaConstraintStatus>;
  transformObject(objectId: string, patch: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaPrintAreaConstraintOptions): AsukaMutationPromise<AsukaPrintAreaConstraintStatus>;
  startDrawing?(input?: {
    id?: string;
    label?: string;
    targetMeshId?: string;
    physicalSize?: AsukaPhysicalSize;
    metadata?: Record<string, unknown>;
    onCreate?: (area: Asuka3DPrintArea) => void;
    onCancel?: () => void;
  }): void;
  cancelDrawing?(): void;
  startFacePicking?(input?: {
    targetMeshId?: string;
    primaryFace?: string;
    faces?: string[];
    onChange?: (selection: { pickedFace: string; primaryFace: string; faces: string[]; meshId?: string; point?: [number, number, number]; normal?: [number, number, number] }) => void;
    onCancel?: () => void;
  }): void;
  cancelFacePicking?(): void;
  classifyMesh?(meshId: string): unknown;
}

export interface AsukaRenderEngineViewport {
  zoomIn(): void;
  zoomOut(): void;
  fit(): void;
  resize?(): void;
  setDocumentSize?(width: number, height: number): void;
  getZoom?(): number;
  setZoom?(zoom: number): void;
  reset?(): void;
  getFrame?(): AsukaViewportFrame | null;
  getCamera?(): AsukaCameraView | null;
  setCamera?(view: Partial<AsukaCameraView>, options?: AsukaCameraMoveOptions): AsukaCameraView | null;
  getCameraLimits?(): AsukaCameraLimits;
  setCameraLimits?(limits: Partial<AsukaCameraLimits>): AsukaCameraLimits;
}

export interface AsukaRenderEngineExports {
  isFormatAvailable(format: string, options?: AsukaExportSupportOptions): boolean;
  canLoadSupport(format: string, options?: AsukaExportSupportOptions): boolean;
  ensureSupport(format: string, options?: AsukaExportSupportOptions): Promise<boolean> | boolean;
  download(target?: AsukaDownloadTarget | unknown, options?: AsukaDownloadOptions): Promise<void> | void;
  downloadAllViews(options?: AsukaDownloadOptions): Promise<void> | void;
  downloadAllAreas(options?: AsukaDownloadOptions): Promise<void> | void;
  downloadAllObjects(options?: AsukaDownloadOptions): Promise<void> | void;
  png(options?: AsukaExportOptions): Promise<Blob>;
}

export interface AsukaRenderEngineProducts {
  list(): Asuka3DProduct[];
  get(id: string): Asuka3DProduct | null;
  add(input: AsukaElementInput): AsukaMutationPromise<AsukaObjectRef>;
  remove(id?: string, options?: AsukaObjectMutationOptions): AsukaMutationPromise<AsukaObjectRef | null>;
  select(id: string | null, options?: { emit?: boolean; source?: string; selectObject?: boolean }): void;
  update(element: unknown, options?: AsukaObjectMutationOptions): AsukaMutationPromise<AsukaObjectRef | null>;
  syncCatalog(elements: AsukaElement[]): void;
  getParameters(productId?: string | null): AsukaParametricProductDescriptor | null;
  validateParameters(productId: string | null | undefined, values?: Record<string, unknown>): AsukaParametricProductDescriptor | null;
  setParameter(productId: string | null | undefined, parameterId: string, value: AsukaParametricValue, options?: AsukaParametricMutationOptions): AsukaMutationPromise<AsukaParametricProductDescriptor | null>;
  setParameters(productId: string | null | undefined, values: Record<string, unknown>, options?: AsukaParametricMutationOptions): AsukaMutationPromise<AsukaParametricProductDescriptor | null>;
}

export interface AsukaRenderEngineAssembly {
  syncCatalog(elements: AsukaElement[]): void;
  getState(): AsukaAssemblyState;
  getBuildState(): AsukaAssemblyBuildState;
  getInteractionState(): AsukaAssemblyInteractionState;
  setConstructionMode(active: boolean): AsukaAssemblyInteractionState;
  setMoveTool(active: boolean): AsukaAssemblyInteractionState;
  configure(patch: Partial<AsukaAssemblyState>, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyState>;
  listSurfaces(): AsukaAssemblySurface[];
  getSurfaceMetrics(id: string): AsukaAssemblyFloorMetrics | null;
  getSurfaceTransform(id: string): AsukaAssemblySurfaceTransform | null;
  selectSurface(id: string | null): AsukaAssemblySurface | null;
  startDrawing(input: AsukaAssemblyStartDrawingInput): void;
  cancelDrawing(): void;
  updateSurface(id: string, patch: AsukaAssemblySurfacePatch, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblySurface | null>;
  transformSurface(id: string, input: AsukaAssemblySurfaceTransformInput, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblySurface | null>;
  setFloorEdgeLength(floorId: string, edgeIndex: number, length: number, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyFloorSurface | null>;
  setFloorEdgeAngle(floorId: string, edgeIndex: number, angleDegrees: number, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyFloorSurface | null>;
  setFloorEdgeConstraint(floorId: string, edgeIndex: number, constraint: AsukaAssemblyFloorEdgeConstraint, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyFloorSurface | null>;
  setRoomWallEnabled(floorId: string, edgeIndex: number, enabled: boolean, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyFloorSurface | null>;
  listMaterialPresets(): AsukaAssemblyMaterialPreset[];
  saveMaterialPreset(preset: Partial<AsukaAssemblyMaterialPreset>, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyMaterialPreset | null>;
  removeMaterialPreset(presetId: string, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<boolean>;
  applyMaterialPreset(surfaceId: string, presetId: string, applyOptions?: AsukaAssemblyMaterialApplyOptions, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblySurface | null>;
  removeSurface(id: string, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblySurface | null>;
  clearSurfaces(options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyState>;
  listComponents(): AsukaAssemblyComponent[];
  getComponent(id: string): AsukaAssemblyComponent | null;
  previewPlacement(componentId: string, input?: AsukaAssemblyPlacementInput): AsukaAssemblyPlacementResult;
  place(componentId: string, input?: AsukaAssemblyPlacementInput, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyPlacementResult>;
  autoPlace(componentId: string, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyPlacementResult>;
  connect(connection: Partial<AsukaAssemblyConnection>, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyConnection | null>;
  disconnect(connectionId: string, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyConnection | null>;
  listStackLinks(): AsukaAssemblyStackLink[];
  linkStack(link: Partial<AsukaAssemblyStackLink>, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyStackLink | null>;
  unlinkStack(linkId: string, options?: AsukaAssemblyMutationOptions): AsukaMutationPromise<AsukaAssemblyStackLink | null>;
  validate(): AsukaAssemblyValidation;
  getBom(options?: { currency?: string }): AsukaAssemblyBom;
  getQuote(options?: AsukaAssemblyQuoteOptions): AsukaAssemblyQuote;
  export(kind: AsukaAssemblyExportKind, options?: AsukaAssemblyExportOptions): Promise<Blob>;
}

export interface AsukaRenderEngineModels {
  listMeshes(): Asuka3DMeshSummary[];
  inspectParts(input: { model: Asuka3DModel }): Promise<Asuka3DMeshSummary[]>;
  preload(input: { model: Asuka3DModel }): Promise<boolean>;
}

export interface Asuka3DPartFocusOptions {
  partId?: string;
  partLabel?: string;
  meshId?: string;
  materialId?: string;
  productId?: string;
  /** `root` recenters the complete product group without changing zoom or orientation. */
  scope?: 'part' | 'root';
  animate?: boolean;
  normal?: [number, number, number];
  point?: [number, number, number];
}

export interface AsukaRenderEngineParts {
  list(): Asuka3DPart[];
  get(id: string): Asuka3DPart | null;
  select(id: string | null): void;
  getSelected(): Asuka3DPart | null;
  listVariations(partId: string): Asuka3DPartVariation[];
  getActiveVariation(partId: string): Asuka3DPartVariation | null;
  applyVariation(partId: string, variationId: string, options?: Asuka3DPartVariationApplyOptions): AsukaMutationPromise<Asuka3DPartVariation | null>;
  focus(input?: Asuka3DPartFocusOptions): void;
}

export interface AsukaRenderEngineMaterials {
  list(): Asuka3DMaterialSummary[];
  select(id: string | null): void;
  update(id: string, patch: Asuka3DMaterialPatch): AsukaMutationPromise<Asuka3DMaterialSummary | null>;
}

export interface AsukaRenderEngineSurface {
  pick(options?: AsukaSurfacePickOptions): AsukaSurfacePickTarget | null;
  getInteractionDebug(): AsukaSurfaceInteractionDebug;
  getLayerRenderingDebug?(id?: string): unknown;
  getLayerRenderingDebugReport?(id?: string): unknown;
}

export type AsukaCanvas2DTextCurveType = 'arch-up' | 'arch-down' | 'arch-left' | 'arch-right' | 'wave' | 'inflate' | 'deflate' | 'circle' | string;

export interface AsukaCanvas2DTextSelection {
  objectId: string;
  text: string;
  fontFamily: string;
  fontSize: number;
  lineHeight: number;
  charSpacing: number;
  fill: string;
  textAlign: string;
  fontWeight: string | number;
  fontStyle: string;
  underline: boolean;
  curved: boolean;
  curveType?: AsukaCanvas2DTextCurveType;
  curveBend?: number;
  allowedFonts?: string[];
}

export interface AsukaCanvas2DTextPatch {
  text?: string;
  fontFamily?: string;
  fontSize?: number;
  lineHeight?: number;
  charSpacing?: number;
  fill?: string;
  textAlign?: string;
  fontWeight?: string | number;
  fontStyle?: string;
  underline?: boolean;
}

export interface AsukaCanvas2DTextCurvePatch {
  type?: AsukaCanvas2DTextCurveType;
  bend?: number;
  text?: string;
}

export interface AsukaCanvas2DColorPart {
  index: number;
  fill: string;
}

export interface AsukaCanvas2DColorSelection {
  objectId: string;
  fill: string | null;
  printingMethodId: string | null;
  parts: AsukaCanvas2DColorPart[];
}

export interface AsukaCanvas2DColorPatch {
  fill?: string;
  printingMethodId?: string | null;
  partIndex?: number | null;
}

export type AsukaCanvas2DShapeKind = 'rect' | 'circle' | 'triangle' | 'line' | 'star';

export interface AsukaCanvas2DShapeInput {
  kind: AsukaCanvas2DShapeKind;
  fill?: string;
  stroke?: string;
  strokeWidth?: number;
  opacity?: number;
  rectRadius?: number;
  starSpikes?: number;
  starInnerRatio?: number;
  width?: number;
  height?: number;
  printAreaId?: string | null;
  metadata?: Record<string, unknown>;
}

export type AsukaCanvas2DAreaDraftKind = 'rect' | 'ellipse' | 'triangle' | 'star' | 'svg';

export interface AsukaCanvas2DAreaDraftInput {
  kind: AsukaCanvas2DAreaDraftKind;
  idPrefix?: string;
  svgText?: string;
  bounds?: { left: number; top: number; width: number; height: number };
}

export interface AsukaCanvas2DSnappingState {
  enabled: boolean;
  threshold: number;
  release?: number;
  priority?: 'zones-first' | 'objects-first';
}

export type AsukaCanvas2DInteractionType = 'selection' | 'transform' | 'object' | 'viewport' | 'hover';
export type AsukaCanvas2DInteractionPhase = 'start' | 'update' | 'end' | 'clear';

export interface AsukaCanvas2DPointer extends AsukaCoordinateFrame {
  x: number;
  y: number;
}

export interface AsukaCanvas2DInteractionEvent {
  type: AsukaCanvas2DInteractionType;
  phase: AsukaCanvas2DInteractionPhase;
  object?: AsukaObjectSummary | null;
  selection?: AsukaObjectSummary[];
  anchor?: AsukaSelectionAnchorBox | null;
  pointer?: AsukaCanvas2DPointer | null;
  userInitiated?: boolean;
}

export interface AsukaCanvas2DInteractionOptions {
  dimHoveredObject?: boolean;
}

export interface AsukaCanvas2DMagnifierState {
  active: boolean;
  x: number;
  y: number;
}

export interface AsukaCanvas2DMagnifierOptions {
  overlay: HTMLCanvasElement;
  enabled: boolean;
  size?: number;
  zoom?: number;
  onStateChange?: (state: AsukaCanvas2DMagnifierState) => void;
}

export interface AsukaCanvas2DTextMutationOptions {
  preview?: boolean;
  commit?: boolean;
  source?: string;
}

/** Optional engine-neutral commands for features that only exist on the 2D renderer. */
export interface AsukaCanvas2DExtension {
  getSelectedText(): AsukaCanvas2DTextSelection | null;
  updateSelectedText(patch: AsukaCanvas2DTextPatch, options?: AsukaCanvas2DTextMutationOptions): AsukaMutationPromise<AsukaCanvas2DTextSelection | null>;
  updateSelectedTextCurve(patch: AsukaCanvas2DTextCurvePatch, options?: AsukaCanvas2DTextMutationOptions): AsukaMutationPromise<AsukaCanvas2DTextSelection | null>;
  setSelectedTextCurved(curved: boolean, patch?: AsukaCanvas2DTextCurvePatch): AsukaMutationPromise<AsukaCanvas2DTextSelection | null>;
  getColorSelection(): AsukaCanvas2DColorSelection | null;
  updateColorSelection(patch: AsukaCanvas2DColorPatch): AsukaMutationPromise<AsukaCanvas2DColorSelection | null>;
  addShape(input: AsukaCanvas2DShapeInput): AsukaMutationPromise<AsukaObjectRef | null>;
  createAreaDraft(input: AsukaCanvas2DAreaDraftInput): AsukaMutationPromise<AsukaObjectRef | null>;
  startAreaDraft(input: Omit<AsukaCanvas2DAreaDraftInput, 'svgText' | 'bounds'>, onComplete?: (object: AsukaObjectRef | null) => void): () => void;
  clear(): AsukaMutationPromise<boolean>;
  enterTextEditing(objectId: string): void;
  getSnapping(): AsukaCanvas2DSnappingState;
  setSnapping(patch: Partial<AsukaCanvas2DSnappingState>): AsukaCanvas2DSnappingState;
  subscribeTextEditRequests(listener: (selection: AsukaCanvas2DTextSelection) => void): () => void;
  subscribeInteractions(listener: (event: AsukaCanvas2DInteractionEvent) => void, options?: AsukaCanvas2DInteractionOptions): () => void;
  attachMagnifier(options: AsukaCanvas2DMagnifierOptions): () => void;
}

export interface AsukaRenderEngine {
  readonly kind: AsukaRenderEngineKind;
  readonly capabilities: Readonly<AsukaRenderEngineCapabilities>;
  readonly lifecycle: AsukaRenderEngineLifecycle;
  readonly events: AsukaRenderEngineEvents;
  readonly history: AsukaRenderEngineHistory;
  readonly scene: AsukaRenderEngineScene;
  readonly objects: AsukaRenderEngineObjects;
  readonly selection: AsukaRenderEngineSelection;
  readonly placement: AsukaRenderEnginePlacement;
  readonly viewport: AsukaRenderEngineViewport;
  readonly exports: AsukaRenderEngineExports;
  readonly areas?: AsukaRenderEngineAreas;
  readonly products?: AsukaRenderEngineProducts;
  readonly assembly?: AsukaRenderEngineAssembly;
  readonly models?: AsukaRenderEngineModels;
  readonly parts?: AsukaRenderEngineParts;
  readonly materials?: AsukaRenderEngineMaterials;
  readonly surface?: AsukaRenderEngineSurface;
  readonly canvas2d?: AsukaCanvas2DExtension;
}

export type AsukaRenderEngineFactory = (options?: AsukaRenderEngineOptions | Asuka3DEngineOptions, context?: { viewId?: string | null; view?: unknown }) => AsukaRenderEngine | Promise<AsukaRenderEngine>;

export type AsukaRenderEngineLoader = () => Promise<
  | AsukaRenderEngine
  | AsukaRenderEngineFactory
  | {
      default?: AsukaRenderEngine | AsukaRenderEngineFactory;
      createAsukaRenderEngine?: AsukaRenderEngineFactory;
      createAsukaFabricRenderEngine?: AsukaRenderEngineFactory;
      createAsuka2DRenderEngine?: AsukaRenderEngineFactory;
      createAsuka3DEngine?: AsukaRenderEngineFactory;
      createAsukaBabylonRenderEngine?: AsukaRenderEngineFactory;
    }
  | unknown
>;

export interface AsukaRenderEngineOptionsByKind {
  fabric?: AsukaRenderEngineOptions;
  '3d'?: Asuka3DEngineOptions;
}

export interface Asuka3DCameraOptions {
  /** Lowest allowed public tilt in degrees above the horizon. */
  minTilt?: number;
  /** Highest allowed public tilt in degrees above the horizon. */
  maxTilt?: number;
  /** Maximum horizontal turn on either side of turnCenter. 180 allows a full orbit. */
  maxTurn?: number;
  /** Horizontal center angle used by maxTurn. */
  turnCenter?: number;
  /** Number of sample rays used to prefer a less obstructed module-focus view. Defaults to 7. */
  focusOcclusionSamples?: number;
  /** Number of candidate camera directions cheaply pre-scored around the focused part. Defaults to 17. */
  focusViewCandidateSamples?: number;
  /** Number of pre-scored directions that receive expensive occlusion raycasts. Defaults to 4. */
  focusOcclusionTopCandidates?: number;
  /** Fraction of the group-to-product offset used while focusing a part in multi-product scenes. Defaults to 0.58. */
  focusMultiProductTargetBlend?: number;
  /** Weight applied to occlusion while choosing the module-focus view. Higher means stricter clear sight lines. */
  focusOcclusionWeight?: number;
  /** Weight applied to the projected visible area of the focused part. Higher values prefer angles showing a larger portion of the selected component. */
  focusVisibleAreaWeight?: number;
  /** Weight applied to the ratio of target samples visible from the chosen view. Higher values reject partially hidden components more aggressively. */
  focusTargetCoverageWeight?: number;
  /** Optional manual fit radius multiplier used by the Fit view action and initial model fit. Defaults to 1.35. */
  fitViewRadiusRatio?: number;
  /**
   * Opt-in only: automatically call Fit view after adding a Product 3D element.
   * Defaults to false so adding an element never forces the camera zoom.
   */
  fitOnAdd?: boolean;
  /** Optional radius multiplier used only when fitOnAdd is true. Falls back to fitViewRadiusRatio. */
  addFitRadiusRatio?: number;
}

export interface Asuka3DProductOptions {
  /** Keep the complete product group centered around the scene origin. Defaults to true. */
  centerGroup?: boolean;
  /** Recenter the product group immediately after adding a product. */
  centerOnAdd?: boolean;
  /** Recenter the product group whenever one product is moved. */
  centerOnTransform?: boolean;
  /** Keep the camera target on the product group center. */
  centerInView?: boolean;
  /** Disable manual camera target panning while preserving orbit and zoom. */
  lockCameraTarget?: boolean;
  /** Keep the camera target centered during part-focus animations. */
  centerDuringFocus?: boolean;
}

export interface Asuka3DEngineOptions {
  /** Lazy Babylon addon loader. */
  loader?: AsukaRenderEngineLoader;
  /** Canonical initial 3D scene. */
  scene?: Asuka3DSceneState;
  /** Global 3D camera/orbit/focus configuration. */
  camera?: Asuka3DCameraOptions;
  /** Product layout and centering configuration. */
  products?: Asuka3DProductOptions;
  /** Babylon engine antialiasing. */
  antialias?: boolean;
  /** Babylon clear color. */
  clearColor?: string;
  /** Primitive displayed when the scene has no product yet. */
  defaultModel?: 'box' | 'sphere' | 'plane';
  /** Optional history configuration. */
  history?: { maxSnapshots?: number };
  /** Optional license feature gate used by commercial builds. */
  licenseFeature?: string;
}

export interface AsukaRenderEngineOptions {
  kind?: AsukaRenderEngineKind;
  loader?: AsukaRenderEngineLoader;
  licenseFeature?: string;
  [key: string]: unknown;
}

export interface Asuka3DModel {
  url: string;
  format?: 'glb' | 'gltf' | string;
  metadata?: Record<string, unknown>;
}

export interface Asuka3DProductTransform {
  position?: { x: number; y: number; z: number };
  rotation?: { x: number; y: number; z: number };
  scale?: { x: number; y: number; z: number };
}

export interface Asuka3DProduct {
  /** Stable id of this placed product. */
  id: string;
  /** Optional id of the catalog item used to create it. */
  catalogId?: string;
  name?: string;
  model?: Asuka3DModel;
  parts?: Asuka3DPart[];
  variationId?: string;
  variationName?: string;
  variationPath?: Array<{ id?: string; name?: string }>;
  variationPathLabel?: string;
  partVariationIds?: Record<string, string>;
  partVariationPrices?: Record<string, number>;
  price?: number | string;
  basePrice?: number | string;
  variationPrice?: number | string;
  /** Per-instance values. The product recipe remains in the element catalog. */
  parametric?: AsukaParametricState;
  transform?: Asuka3DProductTransform;
  meshIds?: string[];
  metadata?: Record<string, unknown>;
}

export interface Asuka3DSceneState {
  /** The only persisted product collection. Singular product/model fields are intentionally unsupported. */
  products: Asuka3DProduct[];
  selectedProductId?: string | null;
  selectedPartId?: string | null;
  camera?: {
    position?: [number, number, number];
    target?: [number, number, number];
    fov?: number;
    /** ArcRotateCamera spherical pose, preserved across view switches. */
    alpha?: number;
    beta?: number;
    radius?: number;
  };
  /** Persisted orbit constraints for this 3D view. */
  cameraLimits?: AsukaCameraLimits;
  /** Scene-wide material state. Product-scoped material ids are prefixed with the product id. */
  materials?: Record<string, {
    color?: string;
    textureUrl?: string;
    metallic?: number;
    roughness?: number;
    [key: string]: unknown;
  }>;
  decorations?: Asuka3DDecoration[];
  printAreas?: Asuka3DPrintArea[];
  activePrintAreaId?: string | null;
  selectedObjectId?: string | null;
  selectedMaterialId?: string | null;
  /** Optional generic spatial assembly state for this 3D view. */
  assembly?: AsukaAssemblyState;
}


export interface Asuka3DHistoryTrackState {
  engine?: '3d' | string;
  scope?: 'view' | string;
  viewId?: string | null;
  index: number;
  snapshots: Asuka3DSceneState[];
  /** Model-bearing product records stored once per track, not once per snapshot. */
  assets?: Record<string, Asuka3DProduct>;
  lastSignature?: string;
  maxSnapshots?: number;
  [key: string]: unknown;
}

export interface Asuka3DDecoration {
  id: string;
  type: 'image' | 'text' | 'design' | string;
  targetMeshId?: string;
  targetPrintAreaId?: string;
  placement?: {
    mode?: 'surface' | 'texture' | 'plane';
    position?: [number, number, number];
    rotation?: [number, number, number];
    scale?: [number, number, number];
    [key: string]: unknown;
  };
  source?: {
    url?: string;
    text?: string;
    svg?: string;
    [key: string]: unknown;
  };
  price?: number | string;
  printingMethodId?: string | null;
  [key: string]: unknown;
}

export interface Asuka3DPrintAreaObject {
  id: string;
  type: 'image' | 'text' | 'design' | string;
  x?: number;
  y?: number;
  width?: number;
  height?: number;
  rotation?: number;
  opacity?: number;
  visible?: boolean;
  locked?: boolean;
  source?: {
    url?: string;
    text?: string;
    svg?: string;
    color?: string;
    fontFamily?: string;
    fontSize?: number;
    [key: string]: unknown;
  };
  price?: number | string;
  printingMethodId?: string | null;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export type Asuka3DPrintAreaMode = 'texture' | 'surface';

export interface Asuka3DPrintArea extends AsukaCoordinateFrame {
  id: string;
  label?: string;
  targetMeshId?: string;
  targetMeshIds?: string[];
  /** `texture` composes objects in UV space; `surface` projects objects on the visible mesh surface. */
  mode: Asuka3DPrintAreaMode;
  textureSlot?: string;
  edgeWrap?: 'none' | 'horizontal' | 'vertical' | 'both';
  uvBounds?: { x: number; y: number; width: number; height: number };
  canvasSize?: { width: number; height: number };
  /** Real-world printable extent represented by this surface. */
  physicalSize?: AsukaPhysicalSize;
  objects?: Asuka3DPrintAreaObject[];
  worldBounds?: { center: [number, number, number]; size: [number, number, number] };
  placement?: {
    position?: [number, number, number];
    rotation?: [number, number, number];
    scale?: [number, number, number];
    [key: string]: unknown;
  };
  visible?: boolean;
  locked?: boolean;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}


export interface Asuka3DMaterialSummary {
  id: string;
  label?: string;
  color?: string;
  textureUrl?: string;
  normalTextureUrl?: string;
  metallic?: number;
  roughness?: number;
  selected?: boolean;
  targetMeshIds?: string[];
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface Asuka3DMaterialPatch {
  color?: string;
  textureUrl?: string;
  normalTextureUrl?: string;
  metallic?: number;
  roughness?: number;
  label?: string;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}


const ENGINE_KINDS: AsukaRenderEngineKind[] = ['fabric', '3d'];

export function isAsukaRenderEngineKind(value: unknown): value is AsukaRenderEngineKind {
  return typeof value === 'string' && (ENGINE_KINDS as string[]).includes(value);
}

export function normalizeRenderEngineKind(value: unknown, fallback: AsukaRenderEngineKind = 'fabric'): AsukaRenderEngineKind {
  const raw = typeof value === 'string'
    ? value
    : value && typeof value === 'object'
      ? String((value as any).kind ?? (value as any).engine ?? '')
      : '';
  const normalized = raw.trim().toLowerCase();
  return isAsukaRenderEngineKind(normalized) ? normalized : fallback;
}

let currentRenderEngineKind: AsukaRenderEngineKind = 'fabric';
const engineKindListeners = new Set<(kind: AsukaRenderEngineKind) => void>();
const runtimeRenderEngines = new Map<AsukaRenderEngineKind, AsukaRenderEngine>();
const runtimeReadyViews = new Map<AsukaRenderEngineKind, {
  engine: AsukaRenderEngine;
  viewId: string;
}>();
const renderEngineEventListeners = new Set<AsukaRenderEngineEventListener>();

export function emitRenderEngineEvent(event: AsukaRenderEngineEvent): void {
  const normalized: AsukaRenderEngineEvent = {
    ...event,
    engine: normalizeRenderEngineKind(event?.engine, currentRenderEngineKind),
  };
  const readyViewId = String(normalized.viewId ?? '').trim();
  if (normalized.type === 'ready' && normalized.action === 'view.activate' && readyViewId) {
    const runtime = runtimeRenderEngines.get(normalized.engine);
    if (runtime) runtimeReadyViews.set(normalized.engine, { engine: runtime, viewId: readyViewId });
  } else if (normalized.type === 'error' && normalized.action === 'view.activate') {
    const current = runtimeReadyViews.get(normalized.engine);
    if (!readyViewId || current?.viewId === readyViewId) runtimeReadyViews.delete(normalized.engine);
  }
  for (const listener of Array.from(renderEngineEventListeners)) {
    try { listener(normalized); } catch {}
  }
}

export function subscribeRenderEngineEvents(listener: AsukaRenderEngineEventListener): () => void {
  renderEngineEventListeners.add(listener);
  return () => renderEngineEventListeners.delete(listener);
}

export function createRenderEngineEvents(kind: AsukaRenderEngineKind): AsukaRenderEngineEvents {
  const normalizedKind = normalizeRenderEngineKind(kind, 'fabric');
  return {
    subscribe: (listener) => subscribeRenderEngineEvents((event) => {
      if (event.engine === normalizedKind) listener(event);
    }),
  };
}

export function getCurrentRenderEngineKind(): AsukaRenderEngineKind {
  return currentRenderEngineKind;
}

export function setCurrentRenderEngineKind(kind: AsukaRenderEngineKind): void {
  const next = normalizeRenderEngineKind(kind, 'fabric');
  if (currentRenderEngineKind === next) return;
  currentRenderEngineKind = next;
  engineKindListeners.forEach((listener) => {
    try { listener(next); } catch {}
  });
}

export function subscribeRenderEngineKind(listener: (kind: AsukaRenderEngineKind) => void): () => void {
  engineKindListeners.add(listener);
  return () => engineKindListeners.delete(listener);
}

export function setRuntimeRenderEngine(kind: AsukaRenderEngineKind, engine: AsukaRenderEngine | null | undefined): void {
  const normalizedKind = normalizeRenderEngineKind(kind, 'fabric');
  const previous = runtimeRenderEngines.get(normalizedKind);
  if (!engine) runtimeRenderEngines.delete(normalizedKind);
  else runtimeRenderEngines.set(normalizedKind, engine);
  if (!engine || previous !== engine) runtimeReadyViews.delete(normalizedKind);
}

export function getRuntimeRenderEngine(kind: AsukaRenderEngineKind = getCurrentRenderEngineKind()): AsukaRenderEngine | null {
  return runtimeRenderEngines.get(normalizeRenderEngineKind(kind, 'fabric')) ?? null;
}

export function isRuntimeRenderEngineViewReady(
  kind: AsukaRenderEngineKind,
  viewId: string | null | undefined,
): boolean {
  const normalizedKind = normalizeRenderEngineKind(kind, 'fabric');
  const runtime = runtimeRenderEngines.get(normalizedKind);
  const ready = runtimeReadyViews.get(normalizedKind);
  return Boolean(
    runtime
    && ready?.engine === runtime
    && ready.viewId === String(viewId ?? '').trim(),
  );
}

export function resolveViewEngineKind(view: unknown, fallback: AsukaRenderEngineKind = 'fabric'): AsukaRenderEngineKind {
  const raw = view && typeof view === 'object'
    ? ((view as any).engine ?? (view as any).renderEngine ?? (view as any).engineKind)
    : undefined;
  return normalizeRenderEngineKind(raw, fallback);
}

export function getActiveViewEngineKind(
  views: Array<{ id?: string | null; engine?: unknown; renderEngine?: unknown; engineKind?: unknown }> | null | undefined,
  activeViewId: string | null | undefined,
  fallback: AsukaRenderEngineKind = 'fabric',
): AsukaRenderEngineKind {
  const viewId = String(activeViewId ?? '').trim();
  const list = Array.isArray(views) ? views : [];
  const active = list.find((view) => String(view?.id ?? '') === viewId) ?? list[0] ?? null;
  return resolveViewEngineKind(active, fallback);
}

export function isModuleSupportedByEngine(
  supportedEngines: AsukaRenderEngineKind[] | readonly AsukaRenderEngineKind[] | null | undefined,
  engineKind: AsukaRenderEngineKind = getCurrentRenderEngineKind(),
): boolean {
  if (!Array.isArray(supportedEngines) || supportedEngines.length === 0) return engineKind === 'fabric';
  return supportedEngines.includes(engineKind);
}


export function getDefaultRenderEngineCapabilities(kind: AsukaRenderEngineKind): AsukaRenderEngineCapabilities {
  if (kind === '3d') {
    return {
      scene3d: true,
      objects: true,
      meshes: true,
      materials: true,
      parts: true,
      partVariations: true,
      parametricProducts: true,
      assembly3d: true,
      printAreas: true,
      surfaceConstraints: true,
      surfacePicking: true,
      surfaceVisibility: true,
      decorations: true,
      addImage: true,
      addText: true,
      addDesign: true,
      addElement: true,
      selectObject: true,
      transform: true,
      color: true,
      visibility: true,
      locking: true,
      removeObject: true,
      reorderObjects: true,
      duplicateObject: true,
      objectTransform: true,
      zOrder: true,
      previewObject: true,
      selectionToolbar: true,
      exportPng: true,
      exportJson: true,
      exportState: true,
      download: true,
      history: true,
    };
  }

  return {
    canvas2d: true,
    objects: true,
    printAreas: true,
    surfaceConstraints: true,
    addImage: true,
    addText: true,
    addDesign: true,
    addElement: true,
    selectObject: true,
    transform: true,
    color: true,
    visibility: true,
    locking: true,
    removeObject: true,
    reorderObjects: true,
    duplicateObject: true,
    objectTransform: true,
    zOrder: true,
    previewObject: true,
    selectionToolbar: true,
    exportPng: true,
    exportJpeg: true,
    exportWebp: true,
    exportSvg: true,
    exportPdf: true,
    exportJson: true,
    exportState: true,
    download: true,
    batchExport: true,
    history: true,
  };
}

export function getRenderEngineCapabilities(engine: AsukaRenderEngine | null | undefined): AsukaRenderEngineCapabilities {
  if (!engine) return {};
  return { ...getDefaultRenderEngineCapabilities(engine.kind), ...(engine.capabilities ?? {}) };
}

/** Return the best-known capabilities for an engine kind, even before its addon is mounted. */
export function getRenderEngineCapabilitiesForKind(kind: AsukaRenderEngineKind = getCurrentRenderEngineKind()): AsukaRenderEngineCapabilities {
  const normalized = normalizeRenderEngineKind(kind, 'fabric');
  const runtime = getRuntimeRenderEngine(normalized);
  if (runtime) return getRenderEngineCapabilities(runtime);
  return getDefaultRenderEngineCapabilities(normalized);
}

/** Normalize module/layout capability requirements into unique string keys. */
export function normalizeRenderEngineCapabilityList(value: unknown): AsukaRenderEngineCapabilityKey[] | undefined {
  if (!Array.isArray(value)) return undefined;
  const keys = value
    .map((item) => String(item ?? '').trim())
    .filter((item): item is AsukaRenderEngineCapabilityKey => item.length > 0);
  return keys.length > 0 ? Array.from(new Set(keys)) : undefined;
}

/** Return whether a capability bag satisfies every requested capability. */
export function areRenderEngineCapabilitiesSatisfied(
  requiredCapabilities: AsukaRenderEngineCapabilityKey[] | readonly AsukaRenderEngineCapabilityKey[] | null | undefined,
  capabilities: AsukaRenderEngineCapabilities | null | undefined,
): boolean {
  if (!Array.isArray(requiredCapabilities) || requiredCapabilities.length === 0) return true;
  const bag = capabilities ?? {};
  return requiredCapabilities.every((capability) => bag[String(capability)] === true);
}


export function normalizeRenderEngineHistoryInfo(input: unknown, fallback: Partial<AsukaHistoryInfo> = {}): AsukaHistoryInfo {
  const raw = input && typeof input === 'object' ? input as any : {};
  const index = Number(raw.index ?? fallback.index ?? -1);
  const length = Number(raw.length ?? fallback.length ?? 0);
  const maxSnapshots = Number(raw.maxSnapshots ?? fallback.maxSnapshots ?? 8) || 8;
  const safeIndex = Number.isFinite(index) ? Math.max(-1, Math.min(index, Math.max(-1, length - 1))) : -1;
  const safeLength = Number.isFinite(length) ? Math.max(0, length) : 0;
  return {
    ...(fallback as any),
    ...(raw as any),
    index: safeIndex,
    length: safeLength,
    canUndo: Boolean(raw.canUndo ?? fallback.canUndo ?? (safeIndex > 0)),
    canRedo: Boolean(raw.canRedo ?? fallback.canRedo ?? (safeLength > 0 && safeIndex >= 0 && safeIndex < safeLength - 1)),
    maxSnapshots,
  };
}

export function getRenderEngineHistoryInfo(engine: AsukaRenderEngine | null | undefined): AsukaHistoryInfo {
  if (!engine) return normalizeRenderEngineHistoryInfo(null);
  try {
    return normalizeRenderEngineHistoryInfo(engine.history.getInfo(), { engine: engine.kind });
  } catch {}
  return normalizeRenderEngineHistoryInfo(null, { engine: engine.kind });
}

export function listRenderEngineObjects(engine: AsukaRenderEngine | null | undefined, options?: AsukaObjectListOptions): AsukaObjectSummary[] {
  if (!engine) return [];
  try {
    const list = engine.objects.list(options);
    return Array.isArray(list) ? list.filter(Boolean) as AsukaObjectSummary[] : [];
  } catch {
    return [];
  }
}

export function getRenderEngineObject(engine: AsukaRenderEngine | null | undefined, id: string | null | undefined): AsukaObjectSummary | null {
  const key = String(id ?? '').trim();
  if (!engine || !key) return null;
  try {
    return engine.objects.get(key) ?? null;
  } catch {}
  return listRenderEngineObjects(engine).find((object) => String(object?.id ?? '') === key) ?? null;
}

function measureRenderEngineObjectSummary(
  engine: AsukaRenderEngine,
  object: AsukaObjectSummary,
  options: AsukaLayerMeasurementOptions = {},
): AsukaLayerMeasurement | null {
  const transform = getRenderEngineObjectTransform(engine, object.id);
  const declaredSize = object.pricing?.size;
  const width = Math.max(0, Number(declaredSize?.width ?? transform?.width ?? object.pricing?.width) || 0);
  const height = Math.max(0, Number(declaredSize?.height ?? transform?.height ?? object.pricing?.height) || 0);
  if (!(width > 0) || !(height > 0)) return null;
  const printAreaId = String(object.targetPrintAreaId ?? object.parentId ?? object.metadata?.targetPrintAreaId ?? object.metadata?.printAreaId ?? '').trim();
  const area = printAreaId ? engine.areas?.get(printAreaId) ?? null : null;
  const areaPhysicalSize = (area?.physicalSize
    ?? (area?.metadata as any)?.physicalSize
    ?? (area?.metadata as any)?.policy?.physicalSize) as AsukaPhysicalSize | undefined;
  const metadata = object.metadata && typeof object.metadata === 'object' ? object.metadata as Record<string, any> : {};
  const source = metadata.source && typeof metadata.source === 'object' ? metadata.source : {};
  const sourcePixelWidth = Number(metadata.sourcePixelWidth ?? source.pixelWidth ?? source.width);
  const sourcePixelHeight = Number(metadata.sourcePixelHeight ?? source.pixelHeight ?? source.height);
  const kind = String(object.kind ?? '').toLowerCase();
  const vector = metadata.vector === true || metadata.sourceVector === true || typeof source.svg === 'string' || kind === 'svg';
  return createAsukaLayerMeasurement({
    objectId: object.id,
    native: {
      width,
      height,
      referenceWidth: Number(declaredSize?.referenceWidth) || undefined,
      referenceHeight: Number(declaredSize?.referenceHeight) || undefined,
      space: declaredSize?.space ?? transform?.space ?? (engine.kind === '3d' ? 'world' : 'canvas'),
      unit: declaredSize?.unit ?? transform?.unit ?? (engine.kind === '3d' ? 'world-unit' : 'canvas-px'),
    },
    ...options,
    physicalSize: areaPhysicalSize ?? options.physicalSize,
    physicalSource: areaPhysicalSize ? 'print-area' : (options.physicalSource ?? 'view'),
    sourcePixelWidth: Number.isFinite(sourcePixelWidth) && sourcePixelWidth > 0 ? sourcePixelWidth : undefined,
    sourcePixelHeight: Number.isFinite(sourcePixelHeight) && sourcePixelHeight > 0 ? sourcePixelHeight : undefined,
    vector,
  });
}

/** Engine-neutral layer measurement used by the public API and shared UI. */
export function measureRenderEngineLayer(
  engine: AsukaRenderEngine | null | undefined,
  id: string | null | undefined,
  options: AsukaLayerMeasurementOptions = {},
): AsukaLayerMeasurement | null {
  const key = String(id ?? '').trim();
  if (!engine || !key) return null;
  try {
    const measured = engine.objects.measure(key, options);
    if (measured) return measured;
  } catch {}
  const object = getRenderEngineObject(engine, key);
  return object ? measureRenderEngineObjectSummary(engine, object, options) : null;
}

export function getRenderEngineSelectedObjects(engine: AsukaRenderEngine | null | undefined): AsukaObjectSummary[] {
  if (!engine) return [];
  try {
    const list = engine.selection.list();
    return Array.isArray(list) ? list.filter(Boolean) as AsukaObjectSummary[] : [];
  } catch {}
  return listRenderEngineObjects(engine).filter((object) => !!object?.selected);
}

export function getRenderEngineSelectionAnchorBox(engine: AsukaRenderEngine | null | undefined): AsukaSelectionAnchorBox | null {
  if (!engine) return null;
  try {
    {
      const box = engine.selection.getAnchorBox();
      if (box && typeof box === 'object') {
        const left = Number((box as any).left);
        const top = Number((box as any).top);
        const width = Number((box as any).width);
        const height = Number((box as any).height);
        if ([left, top, width, height].every((value) => Number.isFinite(value))) {
          return {
            ...(box as any),
            left,
            top,
            width: Math.max(0, width),
            height: Math.max(0, height),
            space: (box as any).space ?? 'screen',
            unit: (box as any).unit ?? 'css-px',
            engine: (box as any).engine ?? engine.kind,
          };
        }
      }
    }
  } catch {}

  const selected = getRenderEngineSelectedObjects(engine)[0] as any;
  const screenBox = selected?.metadata?.screenBox ?? selected?.metadata?.selectionBox ?? selected?.screenBox;
  if (screenBox && typeof screenBox === 'object') {
    const left = Number(screenBox.left);
    const top = Number(screenBox.top);
    const width = Number(screenBox.width);
    const height = Number(screenBox.height);
    if ([left, top, width, height].every((value) => Number.isFinite(value))) {
      return {
        left,
        top,
        width: Math.max(0, width),
        height: Math.max(0, height),
        space: 'screen',
        unit: 'css-px',
        objectId: String(selected?.id ?? '') || null,
        engine: selected?.metadata?.engine ? String(selected.metadata.engine) : engine.kind,
        metadata: selected?.metadata && typeof selected.metadata === 'object' ? selected.metadata : undefined,
      };
    }
  }

  return null;
}

export function getRenderEngineObjectTransform(engine: AsukaRenderEngine | null | undefined, id: string | null | undefined): AsukaObjectTransform | null {
  const key = String(id ?? '').trim();
  if (!engine || !key) return null;
  try {
    return engine.objects.getTransform(key) ?? null;
  } catch {}
  const object = getRenderEngineObject(engine, key);
  const placement = (object as any)?.metadata?.placement ?? {};
  if (!object && !placement) return null;
  const position = Array.isArray(placement?.position) ? placement.position : [];
  const rotation = Array.isArray(placement?.rotation) ? placement.rotation : [];
  const scale = Array.isArray(placement?.scale) ? placement.scale : [];
  return {
    id: key,
    space: engine.kind === '3d' ? 'world' : 'canvas',
    unit: engine.kind === '3d' ? 'world-unit' : 'canvas-px',
    x: Number(position[0] ?? (object as any)?.metadata?.x ?? 0) || 0,
    y: Number(position[1] ?? (object as any)?.metadata?.y ?? 0) || 0,
    z: Number(position[2] ?? (object as any)?.metadata?.z ?? 0) || 0,
    rotation: Number(rotation[2] ?? (object as any)?.metadata?.rotation ?? 0) || 0,
    rotationX: Number(rotation[0] ?? 0) || 0,
    rotationY: Number(rotation[1] ?? 0) || 0,
    rotationZ: Number(rotation[2] ?? 0) || 0,
    scaleX: Number(scale[0] ?? 1) || 1,
    scaleY: Number(scale[1] ?? 1) || 1,
    scaleZ: Number(scale[2] ?? 1) || 1,
    visible: object?.visible,
    locked: object?.locked,
    metadata: object?.metadata,
  };
}

export async function updateRenderEngineObjectTransform(engine: AsukaRenderEngine | null | undefined, id: string | null | undefined, patch: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaObjectMutationOptions): AsukaMutationPromise<AsukaObjectTransform | null> {
  const key = String(id ?? '').trim();
  if (!engine || !key) {
    throw new AsukaRendererError('INVALID_INPUT', 'A mounted renderer and object id are required.', {
      engine: engine?.kind,
      operation: 'objects.updateTransform',
    });
  }
  const object = getRenderEngineObject(engine, key) as any;
  const printAreaId = String((options as any)?.printAreaId ?? (options as any)?.targetPrintAreaId ?? object?.targetPrintAreaId ?? object?.metadata?.targetPrintAreaId ?? object?.metadata?.printAreaId ?? '').trim();
  if (printAreaId && engine.areas) {
    const result = await engine.areas.transformObject(key, patch, {
      ...(options as any),
      printAreaId,
      source: (options as any)?.source ?? 'updateRenderEngineObjectTransform',
    });
    return { ...result, operation: 'objects.updateTransform', value: getRenderEngineObjectTransform(engine, key) };
  }
  return engine.objects.updateTransform(key, patch, options);
}

export function getRenderEngineObjectBounds(engine: AsukaRenderEngine | null | undefined, id: string | null | undefined): AsukaBoundsBox | null {
  const key = String(id ?? '').trim();
  if (!engine || !key) return null;
  try {
    return engine.objects.getBounds(key) ?? null;
  } catch {}
  const transform = getRenderEngineObjectTransform(engine, key);
  if (!transform) return null;
  return makeBoundsBox({
    x: Number(transform.x ?? 0) || 0,
    y: Number(transform.y ?? 0) || 0,
    width: Math.max(0, Number(transform.width ?? 0) || 0),
    height: Math.max(0, Number(transform.height ?? 0) || 0),
    z: typeof transform.z === 'number' ? transform.z : undefined,
    space: transform.space,
    unit: transform.unit,
    engine: engine.kind,
    objectId: key,
    metadata: transform.metadata,
  });
}

export function getRenderEnginePrintAreaBounds(engine: AsukaRenderEngine | null | undefined, id: string | null | undefined): AsukaBoundsBox | null {
  const key = String(id ?? '').trim();
  if (!engine || !key) return null;
  try {
    if (engine.areas) return engine.areas.getBounds(key) ?? null;
  } catch {}
  try {
    const area = engine.areas?.get(key) ?? null;
    if (!area) return null;
    return makeBoundsBox({
      x: Number(area.left ?? area.x ?? 0) || 0,
      y: Number(area.top ?? area.y ?? 0) || 0,
      width: Math.max(0, Number(area.width ?? 0) || 0),
      height: Math.max(0, Number(area.height ?? 0) || 0),
      space: engine.kind === '3d'
        ? (String(area.mode ?? '').toLowerCase() === 'texture' ? 'texture' : 'surface-tangent')
        : 'canvas',
      unit: engine.kind === '3d'
        ? (String(area.mode ?? '').toLowerCase() === 'texture' ? 'texture-px' : 'world-unit')
        : 'canvas-px',
      engine: engine.kind,
      printAreaId: key,
      metadata: area.metadata,
    });
  } catch {}
  return null;
}

function cloneEnginePlain<T>(value: T): T {
  try { return JSON.parse(JSON.stringify(value)); } catch { return value; }
}

function makeBoundsBox(input: {
  x?: number;
  y?: number;
  left?: number;
  top?: number;
  width?: number;
  height?: number;
  z?: number;
  depth?: number;
  space: AsukaCoordinateSpace;
  unit: AsukaCoordinateUnit;
  engine?: AsukaRenderEngineKind | string;
  objectId?: string | null;
  printAreaId?: string | null;
  metadata?: Record<string, unknown>;
}): AsukaBoundsBox {
  const left = Number(input.left ?? input.x ?? 0) || 0;
  const top = Number(input.top ?? input.y ?? 0) || 0;
  const width = Math.max(0, Number(input.width ?? 0) || 0);
  const height = Math.max(0, Number(input.height ?? 0) || 0);
  const right = left + width;
  const bottom = top + height;
  return {
    x: left,
    y: top,
    left,
    top,
    width,
    height,
    right,
    bottom,
    centerX: left + width / 2,
    centerY: top + height / 2,
    space: input.space,
    unit: input.unit,
    ...(typeof input.z === 'number' ? { z: input.z } : null),
    ...(typeof input.depth === 'number' ? { depth: Math.max(0, input.depth) } : null),
    ...(input.engine ? { engine: input.engine } : null),
    ...(input.objectId != null ? { objectId: input.objectId } : null),
    ...(input.printAreaId != null ? { printAreaId: input.printAreaId } : null),
    ...(input.metadata ? { metadata: input.metadata } : null),
  };
}

function makeConstraintStatus(input: {
  objectId: string;
  printAreaId?: string | null;
  objectBounds?: AsukaBoundsBox | null;
  printAreaBounds?: AsukaBoundsBox | null;
  engine?: AsukaRenderEngineKind | string;
  metadata?: Record<string, unknown>;
}): AsukaPrintAreaConstraintStatus {
  const objectBounds = input.objectBounds ?? null;
  const printAreaBounds = input.printAreaBounds ?? null;
  const inside = !!objectBounds && !!printAreaBounds
    && objectBounds.left >= printAreaBounds.left - 0.001
    && objectBounds.top >= printAreaBounds.top - 0.001
    && objectBounds.right <= printAreaBounds.right + 0.001
    && objectBounds.bottom <= printAreaBounds.bottom + 0.001;
  const intersects = !!objectBounds && !!printAreaBounds
    && objectBounds.right >= printAreaBounds.left
    && objectBounds.left <= printAreaBounds.right
    && objectBounds.bottom >= printAreaBounds.top
    && objectBounds.top <= printAreaBounds.bottom;
  return {
    objectId: String(input.objectId ?? ''),
    printAreaId: String(input.printAreaId ?? '') || null,
    inside,
    intersects,
    objectBounds,
    printAreaBounds,
    ...(input.engine ? { engine: input.engine } : null),
    ...(input.metadata ? { metadata: input.metadata } : null),
  };
}

function makeObjectRefFromNative(native: any, fallbackKind?: string): AsukaObjectRef {
  const id = String(native?._asukaObjectId ?? native?.id ?? native?.uuid ?? '').trim() || `object-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  return { id, kind: String(native?._asukaSourceModule ?? native?.itemType ?? native?.type ?? fallbackKind ?? '').trim() || undefined };
}


function normalizeExportFormat(format: unknown, fallback = 'png'): string {
  const raw = String(format ?? fallback).trim().toLowerCase();
  if (raw === 'jpg') return 'jpeg';
  return raw || fallback;
}

export interface AsukaFabricRenderEngineRuntime {
  activateView?: (viewId: string, options?: AsukaViewActivationOptions) => void | Promise<void>;
  deactivateView?: (viewId: string) => void | Promise<void>;
}

export function createFabricRenderEngineAdapter(instance: any, runtime: AsukaFabricRenderEngineRuntime = {}): AsukaRenderEngine {
  const getCanvas = () => instance?.getCanvas?.() ?? null;
  const getNativeObjectId = (obj: any, index = 0): string => String(obj?._asukaObjectId ?? obj?.id ?? obj?.uuid ?? `object-${index + 1}`);
  const isFabricElementObject = (obj: any): boolean => Boolean(obj?._asukaElementIsInstance || obj?._asukaElementName);
  const isFabricRegistryObject = (obj: any): boolean => {
    if (!obj || obj.type === 'activeSelection') return false;
    if (obj._asukaElementName) return false;
    if (obj.excludeFromExport) return false;
    const role = String(obj._asukaRole ?? '').trim();
    if (role && role !== 'user') return false;
    const itemGroup = String(obj.itemGroup ?? obj._asukaItemGroup ?? '').trim();
    if (itemGroup === 'Tools' || itemGroup === 'Elements') return false;
    return true;
  };
  const getFabricActiveObjectSet = (): Set<any> => {
    const canvas = getCanvas();
    try {
      const activeObjects = canvas?.getActiveObjects?.() ?? [];
      if (Array.isArray(activeObjects) && activeObjects.length) return new Set(activeObjects);
    } catch {}
    const active = canvas?.getActiveObject?.();
    return active ? new Set([active]) : new Set();
  };
  const makeFabricObjectSummary = (obj: any, index = 0): AsukaObjectSummary => {
    const type = String(obj?._asukaSourceModule ?? obj?.itemType ?? obj?.type ?? 'object');
    const id = getNativeObjectId(obj, index);
    const activeObjects = getFabricActiveObjectSet();
    const printAreaId = getFabricObjectPrintAreaId(obj);
    const printArea = printAreaId ? getFabricPrintArea(printAreaId) : null;
    const printAreaLabel = printArea ? String(printArea?.label ?? printAreaId).trim() : '';
    const elementId = String(obj?._asukaElementId ?? '').trim();
    const elementFamilyId = String(obj?._asukaElementFamilyId ?? '').trim();
    const isElement = isFabricElementObject(obj);
    // Local scaled dimensions remain stable when the object rotates. A viewport
    // AABB would incorrectly make the physical print size grow with the angle.
    const renderedSize = {
      width: Math.max(0, Math.abs(Number(obj?.getScaledWidth?.() ?? obj?.width) || 0)),
      height: Math.max(0, Math.abs(Number(obj?.getScaledHeight?.() ?? obj?.height) || 0)),
    };
    const fillValue = (fill: any): string => {
      if (typeof fill === 'string') return fill;
      return '';
    };
    const explicitPricingColor = String(obj?._asukaPricingColor ?? '').trim();
    const fills = new Set<string>(collectDeclaredPricingColors(
      explicitPricingColor,
      obj?._asukaPricingColors,
      obj?.metadata?.pricingColors,
      obj?.fill,
      obj?.stroke,
    ));
    const collectObjectColors = (candidate: any, depth = 0) => {
      if (!candidate || depth > 6) return;
      collectDeclaredPricingColors(candidate?.fill, candidate?.stroke).forEach((color) => fills.add(color));
      if (Array.isArray(candidate?._objects)) candidate._objects.forEach((child: any) => collectObjectColors(child, depth + 1));
    };
    collectObjectColors(obj);
    collectPricingColorsFromSvg(obj?.svg ?? obj?.source?.svg ?? obj?.metadata?.source?.svg).forEach((color) => fills.add(color));
    const canvas = getCanvas();
    const referenceWidth = Math.max(0, Number(printArea?.width ?? canvas?.getWidth?.() ?? canvas?.width) || 0);
    const referenceHeight = Math.max(0, Number(printArea?.height ?? canvas?.getHeight?.() ?? canvas?.height) || 0);
    const areaPhysicalSize = (printArea?.physicalSize
      ?? (printArea?.metadata as any)?.physicalSize
      ?? (printArea?.metadata as any)?.policy?.physicalSize) as AsukaPhysicalSize | undefined;
    const imageElement = (() => {
      try { return obj?.getElement?.() ?? obj?._originalElement ?? obj?._element ?? null; } catch { return null; }
    })();
    const sourcePixelWidth = Math.max(0, Number(imageElement?.naturalWidth ?? imageElement?.videoWidth ?? 0) || 0);
    const sourcePixelHeight = Math.max(0, Number(imageElement?.naturalHeight ?? imageElement?.videoHeight ?? 0) || 0);
    const vector = typeof obj?._asukaStudioSvgText === 'string' || typeof obj?._asukaQrSvg === 'string' || String(obj?.type ?? '').toLowerCase() === 'svg';
    const physical = areaPhysicalSize
      ? createAsukaLayerMeasurement({
          objectId: id,
          native: {
            width: renderedSize.width,
            height: renderedSize.height,
            referenceWidth,
            referenceHeight,
            space: 'canvas',
            unit: 'canvas-px',
          },
          physicalSize: areaPhysicalSize,
          physicalSource: 'print-area',
        }).physical
      : undefined;
    return {
      ...makeObjectRefFromNative(obj, type),
      id,
      kind: type,
      label: String(obj?._asukaElementName ?? obj?._asukaName ?? obj?._asukaLabel ?? obj?.name ?? obj?.text ?? obj?.type ?? '').trim() || undefined,
      visible: obj?.visible !== false,
      locked: !!(obj?.lockMovementX || obj?.lockMovementY || obj?._asukaLocked || obj?.selectable === false || obj?.evented === false),
      selected: activeObjects.has(obj) || !!obj?.active,
      parentId: printAreaId || null,
      targetPrintAreaId: printAreaId || null,
      printAreaLabel: printAreaLabel || null,
      pricing: {
        key: isElement ? `element:${elementFamilyId || elementId || id}` : id,
        category: isElement ? 'element' : 'layer',
        price: obj?.price,
        basePrice: obj?._asukaElementBasePrice,
        variationPrice: obj?._asukaElementVariationPrice,
        text: typeof obj?.text === 'string' ? obj.text : undefined,
        fill: explicitPricingColor || fillValue(obj?.fill) || null,
        fills: [...fills],
        printingMethodId: obj?.printingMethodId ?? null,
        width: renderedSize.width,
        height: renderedSize.height,
        scaleX: Number(obj?.scaleX ?? 1) || 1,
        scaleY: Number(obj?.scaleY ?? 1) || 1,
        viewId: String(obj?.viewId ?? obj?._viewId ?? '').trim() || undefined,
         size: {
          width: renderedSize.width,
          height: renderedSize.height,
          referenceWidth,
          referenceHeight,
          space: 'canvas',
           unit: 'canvas-px',
           ...(physical ? { physical } : null),
         },
      },
      metadata: {
        ...((cloneEnginePlain(obj?.metadata ?? {}) as Record<string, unknown>) ?? {}),
        engine: 'fabric',
        itemGroup: obj?.itemGroup ?? obj?._asukaItemGroup ?? undefined,
        sourceModule: obj?._asukaSourceModule ?? undefined,
        shapeKind: obj?._asukaShapeKind ?? undefined,
        role: obj?._asukaRole ?? undefined,
        type: obj?.type ?? undefined,
        printingMethodId: obj?.printingMethodId ?? undefined,
        elementId: elementId || undefined,
        elementFamilyId: elementFamilyId || undefined,
        elementVariationId: obj?._asukaElementVariationId ?? undefined,
        elementName: obj?._asukaElementName ?? undefined,
        textLike: obj?._asukaText === true,
        imageLike: obj?._asukaImage === true,
        sourcePixelWidth: sourcePixelWidth || undefined,
        sourcePixelHeight: sourcePixelHeight || undefined,
        sourceVector: vector || undefined,
        designLike: obj?._asukaDesign === true,
        printAreaId: printAreaId || undefined,
        targetPrintAreaId: printAreaId || undefined,
        printAreaLabel: printAreaLabel || undefined,
      },
    };
  };
  const listFabricRegistryObjects = (options?: AsukaObjectListOptions): AsukaObjectSummary[] => {
    try {
      const objects = (getCanvas()?.getObjects?.() ?? []) as any[];
      const includeTechnical = options?.includeTechnical === true;
      const includeReadonly = options?.includeReadonly === true;
      const requestedPrintAreaId = String(options?.printAreaId ?? '').trim();
      return objects
        .map((obj, index) => ({ obj, index }))
        .filter(({ obj }) => includeTechnical || isFabricRegistryObject(obj) || (includeReadonly && isFabricElementObject(obj)))
        .filter(({ obj }) => !requestedPrintAreaId || getFabricObjectPrintAreaId(obj) === requestedPrintAreaId)
        .map(({ obj, index }) => makeFabricObjectSummary(obj, index));
    } catch {
      return [];
    }
  };
  const getFabricObjectById = (id: string): any => {
    const key = String(id ?? '').trim();
    if (!key) return null;
    try {
      const direct = instance?.getObjectById?.(key);
      if (direct) return direct;
    } catch {}
    try {
      return ((getCanvas()?.getObjects?.() ?? []) as any[]).find((obj: any, index: number) => getNativeObjectId(obj, index) === key) ?? null;
    } catch {
      return null;
    }
  };

  const getFabricDrawingZones = (): any[] => {
    try {
      const zones = instance?.getDrawingZones?.();
      return Array.isArray(zones) ? zones.filter(Boolean) : [];
    } catch {
      return [];
    }
  };

  const setFabricDrawingZones = (zones: any[]) => {
    try { instance?.setDrawingZones?.(Array.isArray(zones) ? zones : []); } catch {}
  };

  const getFabricObjectPrintAreaId = (obj: any): string => String(
    obj?._asukaZoneId
    ?? obj?._asukaDrawingAreaId
    ?? obj?._asukaPrintAreaId
    ?? obj?.targetPrintAreaId
    ?? obj?.printAreaId
    ?? obj?.metadata?.printAreaId
    ?? obj?.metadata?.targetPrintAreaId
    ?? '',
  ).trim();

  const tagFabricObjectPrintArea = (obj: any, explicitAreaId?: string | null) => {
    if (!obj) return;
    const areaId = String(explicitAreaId ?? getFabricObjectPrintAreaId(obj) ?? instance?.getActiveDrawingZone?.() ?? '').trim();
    if (!areaId) return;
    try { obj._asukaZoneId = areaId; } catch {}
    try { obj._asukaPrintAreaId = areaId; } catch {}
    try { obj.targetPrintAreaId = areaId; } catch {}
    try {
      const currentMetadata = obj.metadata && typeof obj.metadata === 'object' ? obj.metadata : {};
      obj.metadata = { ...currentMetadata, printAreaId: areaId, targetPrintAreaId: areaId };
    } catch {}
  };

  const applyFabricObjectInputMetadata = (obj: any, input: Record<string, unknown> | null | undefined) => {
    if (!obj || !input || typeof input !== 'object') return;
    const metadata = input.metadata && typeof input.metadata === 'object'
      ? cloneEnginePlain(input.metadata)
      : {};
    try { obj.metadata = { ...((obj.metadata && typeof obj.metadata === 'object') ? obj.metadata : {}), ...metadata }; } catch {}
    if (Object.prototype.hasOwnProperty.call(input, 'price')) {
      try { obj.price = (input as any).price; } catch {}
    }
    if (Object.prototype.hasOwnProperty.call(input, 'printingMethodId')) {
      try { obj.printingMethodId = (input as any).printingMethodId ?? null; } catch {}
    }
    const pricingColor = String((input as any).color ?? (input as any).fill ?? '').trim();
    if (pricingColor) {
      try { obj._asukaPricingColor = pricingColor; } catch {}
    }
    const sourceModule = String((input as any).sourceModule ?? (metadata as any)?.sourceModule ?? '').trim();
    const itemType = String((input as any).itemType ?? (metadata as any)?.itemType ?? '').trim();
    const name = String((input as any).name ?? (metadata as any)?.name ?? '').trim();
    if (sourceModule) try { obj._asukaSourceModule = sourceModule; } catch {}
    if (itemType) try { obj.itemType = itemType; } catch {}
    if (name) try { obj.name = name; } catch {}
    if ((input as any).qrCode === true || (metadata as any)?.qrCode === true) {
      const svg = String((input as any).qrSvg ?? (metadata as any)?.qrSvg ?? (input as any).svgText ?? (input as any).svg ?? '').trim();
      try { obj._asukaQrCode = true; } catch {}
      if (svg) {
        try { obj._asukaQrSvg = svg; } catch {}
        try { obj._asukaStudioSvgText = svg; } catch {}
      }
    }
    try { obj.setCoords?.(); } catch {}
  };

  const makeFabricPrintArea = (zone: any, index = 0): AsukaPrintArea => {
    const id = String(zone?.id ?? `drawing-area-${index + 1}`).trim() || `drawing-area-${index + 1}`;
    const left = Number(zone?.left ?? zone?.x ?? 0) || 0;
    const top = Number(zone?.top ?? zone?.y ?? 0) || 0;
    const width = Math.max(1, Number(zone?.width ?? 1) || 1);
    const height = Math.max(1, Number(zone?.height ?? 1) || 1);
    const shape = String(zone?.shape ?? 'rect').trim() || 'rect';
    const policy = cloneEnginePlain(instance?.getDrawingAreaPolicy?.(id) ?? {});
    const runtime = cloneEnginePlain(instance?.getDrawingAreaRuntime?.(id) ?? {});
    return {
      ...cloneEnginePlain(zone ?? {}),
      id,
      label: String(zone?.label ?? zone?.name ?? id).trim() || id,
      engine: 'fabric',
      space: 'canvas',
      unit: 'canvas-px',
      kind: 'drawing-area',
      x: left,
      y: top,
      left,
      top,
      width,
      height,
      shape,
      visible: zone?.visible !== false,
      locked: zone?.locked === true,
      metadata: {
        ...(((zone?.metadata && typeof zone.metadata === 'object') ? zone.metadata : {}) as Record<string, unknown>),
        engine: 'fabric',
        drawingZone: cloneEnginePlain(zone ?? {}),
        policy,
        runtime,
      },
    };
  };

  const listFabricPrintAreas = (): AsukaPrintArea[] => getFabricDrawingZones().map((zone, index) => makeFabricPrintArea(zone, index));

  const getFabricPrintArea = (id: string | null | undefined): AsukaPrintArea | null => {
    const key = String(id ?? '').trim();
    if (!key) return null;
    return listFabricPrintAreas().find((area) => String(area?.id ?? '') === key) ?? null;
  };

  const createFabricPrintArea = (input: AsukaPrintAreaInput = {}): AsukaPrintArea | null => {
    assertCompatibleCoordinateFrame(input, { space: 'canvas', unit: 'canvas-px' }, {
      engine: 'fabric',
      operation: 'areas.create',
    });
    const current = getFabricDrawingZones();
    const id = String(input.id ?? `area-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`).trim();
    if (!id) return null;
    const left = Number(input.left ?? input.x ?? 0) || 0;
    const top = Number(input.top ?? input.y ?? 0) || 0;
    const width = Math.max(1, Number(input.width ?? 220) || 220);
    const height = Math.max(1, Number(input.height ?? 160) || 160);
    const zone = {
      ...(input as any),
      id,
      x: left,
      y: top,
      left,
      top,
      width,
      height,
      shape: String(input.shape ?? 'rect').trim() || 'rect',
    };
    setFabricDrawingZones([...current.filter((item) => String(item?.id ?? '') !== id), zone]);
    try { instance?.setActiveDrawingZone?.(id); } catch {}
    return makeFabricPrintArea(zone, current.length);
  };

  const updateFabricPrintArea = (id: string, patch: AsukaPrintAreaPatch = {}): AsukaPrintArea | null => {
    const key = String(id ?? '').trim();
    if (!key) return null;
    assertCompatibleCoordinateFrame(patch, { space: 'canvas', unit: 'canvas-px' }, {
      engine: 'fabric',
      operation: 'areas.update',
      details: { printAreaId: key },
    });
    let updated: any = null;
    const next = getFabricDrawingZones().map((zone) => {
      if (String(zone?.id ?? '') !== key) return zone;
      const left = patch.left ?? patch.x;
      const top = patch.top ?? patch.y;
      updated = {
        ...zone,
        ...(patch as any),
        id: key,
        ...(left != null ? { x: Number(left) || 0, left: Number(left) || 0 } : null),
        ...(top != null ? { y: Number(top) || 0, top: Number(top) || 0 } : null),
        ...(patch.width != null ? { width: Math.max(1, Number(patch.width) || 1) } : null),
        ...(patch.height != null ? { height: Math.max(1, Number(patch.height) || 1) } : null),
        metadata: {
          ...(((zone?.metadata && typeof zone.metadata === 'object') ? zone.metadata : {}) as Record<string, unknown>),
          ...(((patch?.metadata && typeof patch.metadata === 'object') ? patch.metadata : {}) as Record<string, unknown>),
        },
      };
      return updated;
    });
    if (!updated) return null;
    setFabricDrawingZones(next);
    const policy = patch?.metadata && typeof patch.metadata === 'object' ? (patch.metadata as any).policy : undefined;
    if (policy && typeof policy === 'object') {
      try { instance?.setDrawingAreaPolicy?.(key, cloneEnginePlain(policy)); } catch {}
    }
    return makeFabricPrintArea(updated, next.findIndex((zone) => String(zone?.id ?? '') === key));
  };

  const removeFabricPrintArea = (id: string): boolean => {
    const key = String(id ?? '').trim();
    if (!key) return false;
    const current = getFabricDrawingZones();
    const next = current.filter((zone) => String(zone?.id ?? '') !== key);
    if (next.length === current.length) return false;
    setFabricDrawingZones(next);
    try {
      const active = String(instance?.getActiveDrawingZone?.() ?? '').trim();
      if (active === key) instance?.setActiveDrawingZone?.(String(next[0]?.id ?? '').trim() || null);
    } catch {}
    return true;
  };

  const applyFabricPrintAreaInput = (input: any): string => {
    const id = String(
      input?.printAreaId
      ?? input?.targetPrintAreaId
      ?? input?.metadata?.printAreaId
      ?? input?.metadata?.targetPrintAreaId
      ?? '',
    ).trim();
    if (id) {
      try { instance?.setActiveDrawingZone?.(id); } catch {}
      return id;
    }
    try { return String(instance?.getActiveDrawingZone?.() ?? '').trim(); } catch {}
    return '';
  };

  const getFabricObjectBounds = (id: string): AsukaBoundsBox | null => {
    const obj = getFabricObjectById(id);
    if (!obj) return null;
    try { obj.setCoords?.(); } catch {}
    let left = Number(obj?.left ?? 0) || 0;
    let top = Number(obj?.top ?? 0) || 0;
    let width = Math.max(0, Number(obj?.getScaledWidth?.() ?? 0) || 0);
    let height = Math.max(0, Number(obj?.getScaledHeight?.() ?? 0) || 0);
    try {
      const rect = obj.getBoundingRect?.(true, true) ?? obj.getBoundingRect?.() ?? null;
      if (rect) {
        left = Number(rect.left ?? left) || left;
        top = Number(rect.top ?? top) || top;
        width = Math.max(0, Number(rect.width ?? width) || width);
        height = Math.max(0, Number(rect.height ?? height) || height);
      }
    } catch {}
    return makeBoundsBox({
      left,
      top,
      width,
      height,
      space: 'canvas',
      unit: 'canvas-px',
      engine: 'fabric',
      objectId: String(id ?? '').trim(),
      printAreaId: getFabricObjectPrintAreaId(obj) || null,
      metadata: { nativeType: obj?.type },
    });
  };

  const measureFabricObject = (id: string, options: AsukaLayerMeasurementOptions = {}): AsukaLayerMeasurement | null => {
    const obj = getFabricObjectById(id);
    if (!obj) return null;
    const summary = makeFabricObjectSummary(obj);
    const size = summary.pricing?.size;
    if (!size) return null;
    const printAreaId = String(summary.targetPrintAreaId ?? '').trim();
    const area = printAreaId ? getFabricPrintArea(printAreaId) : null;
    const areaPhysicalSize = (area?.physicalSize
      ?? (area?.metadata as any)?.physicalSize
      ?? (area?.metadata as any)?.policy?.physicalSize) as AsukaPhysicalSize | undefined;
    return createAsukaLayerMeasurement({
      objectId: summary.id,
      native: {
        width: size.width,
        height: size.height,
        referenceWidth: size.referenceWidth,
        referenceHeight: size.referenceHeight,
        space: size.space,
        unit: size.unit,
      },
      ...options,
      physicalSize: areaPhysicalSize ?? options.physicalSize,
      physicalSource: areaPhysicalSize ? 'print-area' : (options.physicalSource ?? 'view'),
      sourcePixelWidth: Number(summary.metadata?.sourcePixelWidth) || undefined,
      sourcePixelHeight: Number(summary.metadata?.sourcePixelHeight) || undefined,
      vector: summary.metadata?.sourceVector === true,
    });
  };

  const getFabricPrintAreaBoundsBox = (id: string | null | undefined): AsukaBoundsBox | null => {
    const area = getFabricPrintArea(id);
    if (!area) return null;
    return makeBoundsBox({
      left: Number(area.left ?? area.x ?? 0) || 0,
      top: Number(area.top ?? area.y ?? 0) || 0,
      width: Math.max(0, Number(area.width ?? 0) || 0),
      height: Math.max(0, Number(area.height ?? 0) || 0),
      space: 'canvas',
      unit: 'canvas-px',
      engine: 'fabric',
      printAreaId: String(area.id ?? '').trim(),
      metadata: area.metadata,
    });
  };

  const resolveFabricConstraintAreaId = (objectId: string, printAreaId?: string | null, options?: AsukaPrintAreaConstraintOptions): string => {
    const obj = getFabricObjectById(objectId);
    return String(
      options?.printAreaId
      ?? options?.targetPrintAreaId
      ?? printAreaId
      ?? getFabricObjectPrintAreaId(obj)
      ?? instance?.getActiveDrawingZone?.()
      ?? '',
    ).trim();
  };

  const getFabricConstraintStatus = (objectId: string, printAreaId?: string | null, options?: AsukaPrintAreaConstraintOptions): AsukaPrintAreaConstraintStatus => {
    const key = String(objectId ?? '').trim();
    const areaId = resolveFabricConstraintAreaId(key, printAreaId, options);
    return makeConstraintStatus({
      objectId: key,
      printAreaId: areaId || null,
      objectBounds: getFabricObjectBounds(key),
      printAreaBounds: areaId ? getFabricPrintAreaBoundsBox(areaId) : null,
      engine: 'fabric',
    });
  };

  const constrainFabricObjectToPrintArea = (objectId: string, printAreaId?: string | null, options?: AsukaPrintAreaConstraintOptions): AsukaPrintAreaConstraintStatus => {
    const key = String(objectId ?? '').trim();
    const areaId = resolveFabricConstraintAreaId(key, printAreaId, options);
    const obj = getFabricObjectById(key);
    const areaBounds = areaId ? getFabricPrintAreaBoundsBox(areaId) : null;
    let objectBounds = getFabricObjectBounds(key);
    if (!obj || !areaBounds || !objectBounds) return makeConstraintStatus({ objectId: key, printAreaId: areaId || null, objectBounds, printAreaBounds: areaBounds, engine: 'fabric' });

    let dx = 0;
    let dy = 0;
    if (objectBounds.width <= areaBounds.width) {
      if (objectBounds.left < areaBounds.left) dx = areaBounds.left - objectBounds.left;
      else if (objectBounds.right > areaBounds.right) dx = areaBounds.right - objectBounds.right;
    } else {
      dx = areaBounds.centerX - objectBounds.centerX;
    }
    if (objectBounds.height <= areaBounds.height) {
      if (objectBounds.top < areaBounds.top) dy = areaBounds.top - objectBounds.top;
      else if (objectBounds.bottom > areaBounds.bottom) dy = areaBounds.bottom - objectBounds.bottom;
    } else {
      dy = areaBounds.centerY - objectBounds.centerY;
    }

    if (dx || dy) {
      try { obj.set?.({ left: Number(obj.left ?? 0) + dx, top: Number(obj.top ?? 0) + dy }); } catch {
        try { obj.left = Number(obj.left ?? 0) + dx; obj.top = Number(obj.top ?? 0) + dy; } catch {}
      }
      try { obj.setCoords?.(); } catch {}
      try { getCanvas()?.requestRenderAll?.(); } catch {}
      objectBounds = getFabricObjectBounds(key);
    }
    tagFabricObjectPrintArea(obj, areaId);
    return makeConstraintStatus({ objectId: key, printAreaId: areaId || null, objectBounds, printAreaBounds: areaBounds, engine: 'fabric', metadata: { constrained: !!(dx || dy) } });
  };

  const moveFabricObjectWithinPrintArea = (objectId: string, delta: Partial<AsukaObjectTransform> & AsukaObjectPatch = {}, options?: AsukaPrintAreaConstraintOptions) => {
    const key = String(objectId ?? '').trim();
    if (!key) return;
    updateFabricObjectTransform(key, delta, { ...(options ?? {}), constrainToPrintArea: false } as any);
    constrainFabricObjectToPrintArea(key, null, options);
  };

  const transformFabricObjectWithinPrintArea = (objectId: string, patch: Partial<AsukaObjectTransform> & AsukaObjectPatch = {}, options?: AsukaPrintAreaConstraintOptions) => {
    const key = String(objectId ?? '').trim();
    if (!key) return;
    updateFabricObjectTransform(key, patch, { ...(options ?? {}), constrainToPrintArea: false } as any);
    constrainFabricObjectToPrintArea(key, null, options);
  };

  const getFabricObjectTransform = (id: string): AsukaObjectTransform | null => {
    const obj = getFabricObjectById(id);
    if (!obj) return null;
    const normalizeScalar = (value: unknown, fallback = 0) => {
      const numeric = Number(value);
      return Number.isFinite(numeric) ? Number(numeric.toFixed(4)) : fallback;
    };
    const scaleX = normalizeScalar(obj?.scaleX, 1) || 1;
    const scaleY = normalizeScalar(obj?.scaleY, 1) || 1;
    const rawWidth = Number(obj?.width ?? 0) || 0;
    const rawHeight = Number(obj?.height ?? 0) || 0;
    let width = rawWidth * Math.abs(scaleX);
    let height = rawHeight * Math.abs(scaleY);
    try { width = Number(obj?.getScaledWidth?.() ?? width) || width; } catch {}
    try { height = Number(obj?.getScaledHeight?.() ?? height) || height; } catch {}
    return {
      id: String(id),
      space: 'canvas',
      unit: 'canvas-px',
      x: normalizeScalar(obj?.left),
      y: normalizeScalar(obj?.top),
      z: 0,
      width: normalizeScalar(width),
      height: normalizeScalar(height),
      rotation: normalizeScalar(obj?.angle),
      rotationZ: normalizeScalar(obj?.angle),
      scaleX,
      scaleY,
      scaleZ: 1,
      opacity: typeof obj?.opacity === 'number' ? obj.opacity : undefined,
      visible: obj?.visible !== false,
      locked: !!(obj?.lockMovementX || obj?.lockMovementY || obj?._asukaLocked || obj?.selectable === false || obj?.evented === false),
      metadata: { engine: 'fabric', nativeType: obj?.type },
    };
  };

  const updateFabricObject = (id: string, patch: AsukaObjectPatch): boolean => {
    const normalizedPatch: Record<string, unknown> = { ...(patch as any) };
    if (typeof patch.x === 'number' && Number.isFinite(patch.x)) normalizedPatch.left = patch.x;
    if (typeof patch.y === 'number' && Number.isFinite(patch.y)) normalizedPatch.top = patch.y;
    if (typeof patch.rotation === 'number' && Number.isFinite(patch.rotation)) normalizedPatch.angle = patch.rotation;
    delete normalizedPatch.x;
    delete normalizedPatch.y;
    delete normalizedPatch.rotation;
    if (typeof patch.color === 'string' && patch.color.trim()) {
      normalizedPatch.fill = patch.color;
      normalizedPatch._asukaPricingColor = patch.color;
    }
    delete normalizedPatch.color;
    if (typeof patch.locked === 'boolean') {
      normalizedPatch.selectable = !patch.locked;
      normalizedPatch.evented = !patch.locked;
      normalizedPatch.hasControls = !patch.locked;
      normalizedPatch.hasBorders = !patch.locked;
      normalizedPatch.lockMovementX = patch.locked;
      normalizedPatch.lockMovementY = patch.locked;
      normalizedPatch.lockScalingX = patch.locked;
      normalizedPatch.lockScalingY = patch.locked;
      normalizedPatch.lockRotation = patch.locked;
      normalizedPatch._asukaLocked = patch.locked;
      delete normalizedPatch.locked;
    }
    try {
      const handled = instance?.updateObject?.(id, normalizedPatch);
      if (handled !== undefined) return handled != null && handled !== false;
    } catch {}
    try {
      const obj = getFabricObjectById(id);
      const canvas = getCanvas();
      if (!obj) return false;
      if (typeof patch.visible === 'boolean') obj.visible = patch.visible;
      if (typeof patch.locked === 'boolean') {
        obj.selectable = !patch.locked;
        obj.evented = !patch.locked;
        obj.hasControls = !patch.locked;
        obj.hasBorders = !patch.locked;
        obj.lockMovementX = patch.locked;
        obj.lockMovementY = patch.locked;
        obj.lockScalingX = patch.locked;
        obj.lockScalingY = patch.locked;
        obj.lockRotation = patch.locked;
        obj._asukaLocked = patch.locked;
      }
      if (Object.prototype.hasOwnProperty.call(normalizedPatch, 'price')) obj.price = normalizedPatch.price;
      if (Object.prototype.hasOwnProperty.call(normalizedPatch, 'printingMethodId')) obj.printingMethodId = normalizedPatch.printingMethodId ?? null;
      if (typeof normalizedPatch._asukaPricingColor === 'string') {
        obj._asukaPricingColor = normalizedPatch._asukaPricingColor;
        obj.fill = normalizedPatch.fill;
      }
      const numericMap: Record<string, string> = { x: 'left', y: 'top', rotation: 'angle', scaleX: 'scaleX', scaleY: 'scaleY', opacity: 'opacity' };
      for (const [patchKey, objectKey] of Object.entries(numericMap)) {
        const value = (patch as any)[patchKey];
        if (typeof value === 'number' && Number.isFinite(value)) {
          if (typeof obj.set === 'function') obj.set(objectKey, value);
          else obj[objectKey] = value;
        }
      }
      obj.setCoords?.();
      canvas?.requestRenderAll?.();
      return true;
    } catch {
      return false;
    }
  };

  const updateFabricObjectTransform = (id: string, patch: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaPrintAreaConstraintOptions & { constrainToPrintArea?: boolean }) => {
    const obj = getFabricObjectById(id);
    if (!obj || !patch || typeof patch !== 'object') return;
    assertCompatibleCoordinateFrame(patch, { space: 'canvas', unit: 'canvas-px' }, {
      engine: 'fabric',
      operation: 'objects.updateTransform',
      details: { objectId: id },
    });
    const nextPatch: Record<string, unknown> = {};
    const current = getFabricObjectTransform(id);
    if (typeof (patch as any).deltaX === 'number') nextPatch.x = Number(current?.x ?? obj?.left ?? 0) + Number((patch as any).deltaX);
    if (typeof (patch as any).deltaY === 'number') nextPatch.y = Number(current?.y ?? obj?.top ?? 0) + Number((patch as any).deltaY);
    if (typeof patch.x === 'number') nextPatch.x = patch.x;
    if (typeof patch.y === 'number') nextPatch.y = patch.y;
    if (typeof patch.rotation === 'number') nextPatch.rotation = patch.rotation;
    else if (typeof patch.rotationZ === 'number') nextPatch.rotation = patch.rotationZ;
    if (typeof (patch as any).rotationBy === 'number') nextPatch.rotation = Number(obj?.angle ?? 0) + Number((patch as any).rotationBy);
    if (typeof patch.scale === 'number') { nextPatch.scaleX = patch.scale; nextPatch.scaleY = patch.scale; }
    if (typeof (patch as any).scaleBy === 'number') {
      const factor = Number((patch as any).scaleBy) || 1;
      nextPatch.scaleX = (Number(obj?.scaleX ?? 1) || 1) * factor;
      nextPatch.scaleY = (Number(obj?.scaleY ?? 1) || 1) * factor;
    }
    if (typeof patch.scaleX === 'number') nextPatch.scaleX = patch.scaleX;
    if (typeof patch.scaleY === 'number') nextPatch.scaleY = patch.scaleY;
    if (typeof patch.opacity === 'number') nextPatch.opacity = patch.opacity;
    if (typeof patch.visible === 'boolean') nextPatch.visible = patch.visible;
    if (typeof patch.locked === 'boolean') nextPatch.locked = patch.locked;
    if (patch.metadata && typeof patch.metadata === 'object') nextPatch.metadata = patch.metadata;
    if (Object.keys(nextPatch).length) {
      updateFabricObject(id, nextPatch as AsukaObjectPatch);
    }
    if (options?.constrainToPrintArea !== false) {
      const areaId = resolveFabricConstraintAreaId(id, null, options);
      if (areaId) constrainFabricObjectToPrintArea(id, areaId, options);
    }
  };

  const getFabricSelectionAnchorBox = (): AsukaSelectionAnchorBox | null => {
    try {
      const canvas = getCanvas();
      const active = canvas?.getActiveObject?.();
      if (!canvas || !active) return null;

      active.setCoords?.();

      const lowerCanvasEl: HTMLCanvasElement | undefined = canvas.lowerCanvasEl;
      if (!lowerCanvasEl || typeof lowerCanvasEl.getBoundingClientRect !== 'function') return null;

      const coords = (active as any).aCoords;
      const vpt = canvas.viewportTransform;
      if (!vpt || !coords?.tl || !coords?.tr || !coords?.bl || !coords?.br) return null;

      const canvasRect = lowerCanvasEl.getBoundingClientRect();
      const logicalWidth = Number(canvas.getWidth?.() ?? canvas.width ?? lowerCanvasEl.clientWidth) || canvasRect.width || 1;
      const logicalHeight = Number(canvas.getHeight?.() ?? canvas.height ?? lowerCanvasEl.clientHeight) || canvasRect.height || 1;
      const cssScaleX = canvasRect.width / Math.max(1, logicalWidth);
      const cssScaleY = canvasRect.height / Math.max(1, logicalHeight);
      const transformPoint = (pt: any) => ({
        x: Number(vpt[0] ?? 1) * Number(pt.x ?? 0) + Number(vpt[2] ?? 0) * Number(pt.y ?? 0) + Number(vpt[4] ?? 0),
        y: Number(vpt[1] ?? 0) * Number(pt.x ?? 0) + Number(vpt[3] ?? 1) * Number(pt.y ?? 0) + Number(vpt[5] ?? 0),
      });
      const viewportPoints = [coords.tl, coords.tr, coords.bl, coords.br].map(transformPoint);
      const xs = viewportPoints.map((pt) => pt.x);
      const ys = viewportPoints.map((pt) => pt.y);
      const minX = Math.min(...xs);
      const maxX = Math.max(...xs);
      const minY = Math.min(...ys);
      const maxY = Math.max(...ys);
      const id = getNativeObjectId(active, 0);

      return {
        left: canvasRect.left + minX * cssScaleX,
        top: canvasRect.top + minY * cssScaleY,
        width: Math.max(0, (maxX - minX) * cssScaleX),
        height: Math.max(0, (maxY - minY) * cssScaleY),
        space: 'screen',
        unit: 'css-px',
        objectId: id || null,
        engine: 'fabric',
        metadata: { nativeType: String((active as any).type ?? '') },
      };
    } catch {
      return null;
    }
  };

  const duplicateFabricObject = async (id: string, options?: AsukaObjectDuplicateOptions): Promise<AsukaObjectRef | null> => {
    const key = String(id ?? '').trim();
    if (!key) return null;
    const canvas = getCanvas();
    const source = getFabricObjectById(key);
    if (!canvas || !source) return null;
    const beforeIds = new Set(((canvas.getObjects?.() ?? []) as any[]).map((obj: any, index: number) => getNativeObjectId(obj, index)));
    try { canvas.discardActiveObject?.(); canvas.setActiveObject?.(source); } catch {}
    try {
      const maybe = instance?.duplicateActiveObject?.({ offsetX: options?.offsetX, offsetY: options?.offsetY });
      if (maybe && typeof maybe.then === 'function') await maybe;
      else await new Promise((resolve) => setTimeout(resolve, 0));
    } catch { return null; }
    const created = ((canvas.getObjects?.() ?? []) as any[]).find((obj: any, index: number) => !beforeIds.has(getNativeObjectId(obj, index))) ?? null;
    if (created) {
      const areaId = getFabricObjectPrintAreaId(source);
      if (areaId) {
        tagFabricObjectPrintArea(created, areaId);
        constrainFabricObjectToPrintArea(getNativeObjectId(created), areaId, { source: options?.source ?? 'duplicateObject' } as any);
      }
    }
    return created ? makeObjectRefFromNative(created, String(created?._asukaSourceModule ?? created?.itemType ?? created?.type ?? 'object')) : null;
  };

  const removeFabricObject = (id: string): boolean => {
    try {
      const handled = instance?.removeObject?.(id);
      if (handled !== undefined) return handled !== false;
    } catch {}
    try {
      const canvas = getCanvas();
      const obj = getFabricObjectById(id);
      if (!canvas || !obj) return false;
      canvas.remove?.(obj);
      canvas.requestRenderAll?.();
      return true;
    } catch {
      return false;
    }
  };

  const reorderFabricObjects = (orderedIds: string[], options?: AsukaObjectOrderOptions): boolean => {
    const ids = Array.isArray(orderedIds) ? orderedIds.map((id) => String(id ?? '').trim()).filter(Boolean) : [];
    if (ids.length === 0) return false;
    try {
      const handled = instance?.reorderObjects?.(ids, { ...(options ?? {}), order: options?.order ?? 'display' });
      if (handled) return true;
    } catch {}
    try {
      const canvas = getCanvas();
      if (!canvas) return false;
      const nativeOrderIds = options?.order === 'canvas' ? ids : ids.slice().reverse();
      const objectsById = new Map(((canvas.getObjects?.() ?? []) as any[]).map((obj: any, index: number) => [getNativeObjectId(obj, index), obj]));
      nativeOrderIds.forEach((id, index) => {
        const obj = objectsById.get(id);
        if (!obj) return;
        if (typeof canvas.moveObjectTo === 'function') canvas.moveObjectTo(obj, index);
        else if (typeof obj.moveTo === 'function') obj.moveTo(index);
      });
      canvas.requestRenderAll?.();
      return true;
    } catch {
      return false;
    }
  };

  const moveFabricObjectRelative = (id: string, delta: number): boolean => {
    const objects = listFabricRegistryObjects({ includeTechnical: false });
    const displayIds = objects.slice().reverse().map((obj) => String(obj?.id ?? '')).filter(Boolean);
    const index = displayIds.indexOf(String(id ?? ''));
    if (index < 0) return false;
    const nextIndex = Math.max(0, Math.min(displayIds.length - 1, index + delta));
    if (nextIndex === index) return false;
    const [moved] = displayIds.splice(index, 1);
    displayIds.splice(nextIndex, 0, moved);
    return reorderFabricObjects(displayIds, { order: 'display', source: 'object-mutation' });
  };

  const getFabricSelectedTextState = (): AsukaCanvas2DTextSelection | null => {
    const active = getCanvas()?.getActiveObject?.() ?? null;
    if (!active) return null;
    const children = Array.isArray(active?._objects) ? active._objects : [];
    const curvedNode = active?._asukaCurvedText === true
      ? active
      : children.find((child: any) => child?._asukaCurvedText === true && child?._asukaCurvedTextData) ?? null;
    const curveData = curvedNode?._asukaCurvedTextData ?? active?._asukaCurvedTextData ?? null;
    const effective = curveData?.source ?? curvedNode ?? active;
    const type = String(effective?.type ?? '').toLowerCase();
    const curved = !!curveData;
    if (!curved && !['text', 'i-text', 'itext', 'textbox'].includes(type)) return null;
    const allowedFonts = effective?._asukaFixedSlotConstraints?.allowedFonts
      ?? active?._asukaFixedSlotConstraints?.allowedFonts;
    return {
      objectId: getNativeObjectId(active),
      text: String(curveData?.text ?? effective?.text ?? ''),
      fontFamily: String(effective?.fontFamily ?? 'Arial'),
      fontSize: Number(effective?.fontSize ?? 16) || 16,
      lineHeight: Number(effective?.lineHeight ?? 1.2) || 1.2,
      charSpacing: Number(effective?.charSpacing ?? 0) || 0,
      fill: typeof effective?.fill === 'string' ? effective.fill : '#111827',
      textAlign: String(effective?.textAlign ?? 'left'),
      fontWeight: typeof effective?.fontWeight === 'number' ? effective.fontWeight : String(effective?.fontWeight ?? 'normal'),
      fontStyle: String(effective?.fontStyle ?? 'normal'),
      underline: effective?.underline === true,
      curved,
      ...(curved ? {
        curveType: String(curveData?.type ?? 'arch-up'),
        curveBend: Number(curveData?.bend ?? 30) || 0,
      } : null),
      ...(Array.isArray(allowedFonts) ? {
        allowedFonts: allowedFonts.map((value: unknown) => String(value ?? '').trim()).filter(Boolean),
      } : null),
    };
  };

  const getFabricFillValue = (fill: any): string => {
    if (typeof fill === 'string') return fill;
    if (typeof fill?.source?.src === 'string') return fill.source.src;
    return '';
  };

  const getFabricColorSelection = (): AsukaCanvas2DColorSelection | null => {
    const active = getCanvas()?.getActiveObject?.() ?? null;
    if (!active) return null;
    const parts = Array.isArray(active?._objects)
      ? active._objects.map((part: any, index: number) => ({ index, fill: getFabricFillValue(part?.fill) }))
      : [];
    let fill = getFabricFillValue(active?.fill);
    if (!fill) {
      try { fill = String(instance?.getActiveObjectFill?.() ?? '').trim(); } catch {}
    }
    return {
      objectId: getNativeObjectId(active),
      fill: fill || null,
      printingMethodId: String(active?.printingMethodId ?? '').trim() || null,
      parts,
    };
  };

  const getFabricObjectScreenBox = (target: any): AsukaSelectionAnchorBox | null => {
    try {
      const canvas = getCanvas();
      const canvasElement = canvas?.upperCanvasEl ?? canvas?.lowerCanvasEl ?? null;
      const canvasRect = canvasElement?.getBoundingClientRect?.() ?? null;
      if (!canvas || !canvasRect || !target) return null;
      const width = Number(canvas.getWidth?.() ?? canvasElement?.width ?? canvasRect.width) || canvasRect.width || 1;
      const height = Number(canvas.getHeight?.() ?? canvasElement?.height ?? canvasRect.height) || canvasRect.height || 1;
      const scaleX = canvasRect.width / Math.max(1, width);
      const scaleY = canvasRect.height / Math.max(1, height);
      const rect = target.getBoundingRect?.(true, true) ?? target.getBoundingRect?.() ?? null;
      if (!rect) return null;
      const transform = Array.isArray(canvas.viewportTransform) ? canvas.viewportTransform : [1, 0, 0, 1, 0, 0];
      const zoomX = Number(transform[0] ?? 1) || 1;
      const zoomY = Number(transform[3] ?? zoomX) || zoomX;
      const panX = Number(transform[4] ?? 0) || 0;
      const panY = Number(transform[5] ?? 0) || 0;
      return {
        left: canvasRect.left + (Number(rect.left ?? 0) * zoomX + panX) * scaleX,
        top: canvasRect.top + (Number(rect.top ?? 0) * zoomY + panY) * scaleY,
        width: Math.max(12, Number(rect.width ?? 0) * Math.abs(zoomX) * scaleX),
        height: Math.max(12, Number(rect.height ?? 0) * Math.abs(zoomY) * scaleY),
        space: 'screen',
        unit: 'css-px',
        objectId: getNativeObjectId(target),
        engine: 'fabric',
      };
    } catch {
      return null;
    }
  };

  const getFabricObjectSummary = (target: any): AsukaObjectSummary | null => {
    if (!target) return null;
    const objects = (getCanvas()?.getObjects?.() ?? []) as any[];
    return makeFabricObjectSummary(target, Math.max(0, objects.indexOf(target)));
  };

  const getFabricInteractionPointer = (eventInfo?: any): AsukaCanvas2DPointer | null => {
    const event = eventInfo?.e as MouseEvent | PointerEvent | undefined;
    const x = Number(event?.clientX);
    const y = Number(event?.clientY);
    if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
    return { x, y, space: 'screen', unit: 'css-px' };
  };

  const subscribeFabricInteractions = (
    listener: (event: AsukaCanvas2DInteractionEvent) => void,
    options: AsukaCanvas2DInteractionOptions = {},
  ): (() => void) => {
    const canvas = getCanvas();
    if (!canvas?.on || !canvas?.off) return () => {};
    const emit = (event: AsukaCanvas2DInteractionEvent) => {
      try { listener(event); } catch {}
    };
    const selected = () => listFabricRegistryObjects({ includeTechnical: false }).filter((object) => object.selected);
    const activeSummary = () => selected()[0] ?? null;
    const emitSelection = (phase: AsukaCanvas2DInteractionPhase, eventInfo?: any) => emit({
      type: 'selection',
      phase,
      object: activeSummary(),
      selection: selected(),
      anchor: getFabricSelectionAnchorBox(),
      pointer: getFabricInteractionPointer(eventInfo),
      userInitiated: !!eventInfo?.e,
    });
    const emitTransform = (phase: AsukaCanvas2DInteractionPhase, eventInfo?: any) => emit({
      type: 'transform',
      phase,
      object: getFabricObjectSummary(eventInfo?.target) ?? activeSummary(),
      selection: selected(),
      anchor: getFabricSelectionAnchorBox(),
      pointer: getFabricInteractionPointer(eventInfo),
      userInitiated: !!eventInfo?.e,
    });
    const emitObject = (phase: AsukaCanvas2DInteractionPhase, eventInfo?: any) => emit({
      type: 'object',
      phase,
      object: getFabricObjectSummary(eventInfo?.target),
      selection: selected(),
      anchor: getFabricSelectionAnchorBox(),
      pointer: getFabricInteractionPointer(eventInfo),
      userInitiated: !!eventInfo?.e,
    });

    let hoveredTarget: any = null;
    let hoveredOpacity: number | null = null;
    const restoreHover = (emitClear = false) => {
      const target = hoveredTarget;
      const opacity = hoveredOpacity;
      hoveredTarget = null;
      hoveredOpacity = null;
      try { if (canvas.upperCanvasEl) canvas.upperCanvasEl.style.cursor = ''; } catch {}
      if (target && Number.isFinite(opacity as number) && options.dimHoveredObject !== false) {
        try {
          if (typeof target.set === 'function') target.set('opacity', opacity);
          else target.opacity = opacity;
          canvas.requestRenderAll?.();
        } catch {}
      }
      if (emitClear) emit({ type: 'hover', phase: 'clear', object: null, pointer: null });
    };
    const applyHover = (target: any) => {
      if (!target || hoveredTarget === target) return;
      restoreHover(false);
      hoveredTarget = target;
      const opacity = Number(target.opacity ?? 1);
      hoveredOpacity = Number.isFinite(opacity) ? opacity : 1;
      if (options.dimHoveredObject !== false) {
        try {
          const nextOpacity = Math.max(0.35, Math.min(0.78, (hoveredOpacity ?? 1) * 0.72));
          if (typeof target.set === 'function') target.set('opacity', nextOpacity);
          else target.opacity = nextOpacity;
          canvas.requestRenderAll?.();
        } catch {}
      }
    };
    const handleHover = (eventInfo?: any) => {
      const target = eventInfo?.target ?? null;
      if (!target) {
        restoreHover(true);
        return;
      }
      applyHover(target);
      try { canvas.hoverCursor = 'pointer'; if (canvas.upperCanvasEl) canvas.upperCanvasEl.style.cursor = 'pointer'; } catch {}
      const box = getFabricObjectScreenBox(target);
      const pointer = getFabricInteractionPointer(eventInfo) ?? (box ? {
        x: box.left + box.width / 2,
        y: box.top,
        space: 'screen' as const,
        unit: 'css-px' as const,
      } : null);
      emit({
        type: 'hover',
        phase: 'update',
        object: getFabricObjectSummary(target),
        pointer,
        anchor: box,
        userInitiated: !!eventInfo?.e,
      });
    };
    const handleHoverMove = (eventInfo?: any) => {
      if (!hoveredTarget) return;
      const pointer = getFabricInteractionPointer(eventInfo);
      if (!pointer) return;
      emit({ type: 'hover', phase: 'update', object: getFabricObjectSummary(hoveredTarget), pointer, anchor: getFabricObjectScreenBox(hoveredTarget), userInitiated: true });
    };
    const clearHover = () => restoreHover(true);

    const handleSelectionCreated = (eventInfo?: any) => emitSelection('update', eventInfo);
    const handleSelectionCleared = (eventInfo?: any) => emitSelection('clear', eventInfo);
    const handleTransform = (eventInfo?: any) => {
      if (!eventInfo?.e) return;
      emitTransform('update', eventInfo);
    };
    const handleModified = (eventInfo?: any) => {
      restoreHover(true);
      emitTransform('end', eventInfo);
    };
    const handleAdded = (eventInfo?: any) => emitObject('start', eventInfo);
    const handleRemoved = (eventInfo?: any) => emitObject('end', eventInfo);
    const handlePathCreated = (eventInfo?: any) => emitObject('end', eventInfo);
    let viewportFrame = 0;
    const scheduleViewport = () => {
      if (viewportFrame) return;
      viewportFrame = requestAnimationFrame(() => {
        viewportFrame = 0;
        emit({ type: 'viewport', phase: 'update', object: activeSummary(), selection: selected(), anchor: getFabricSelectionAnchorBox() });
      });
    };

    canvas.on('selection:created', handleSelectionCreated);
    canvas.on('selection:updated', handleSelectionCreated);
    canvas.on('selection:cleared', handleSelectionCleared);
    canvas.on('object:moving', handleTransform);
    canvas.on('object:scaling', handleTransform);
    canvas.on('object:rotating', handleTransform);
    canvas.on('object:skewing', handleTransform);
    canvas.on('object:modified', handleModified);
    canvas.on('object:added', handleAdded);
    canvas.on('object:removed', handleRemoved);
    canvas.on('path:created', handlePathCreated);
    canvas.on('mouse:over', handleHover);
    canvas.on('mouse:move', handleHoverMove);
    canvas.on('mouse:down', clearHover);
    canvas.on('mouse:out', clearHover);
    canvas.on('mouse:wheel', scheduleViewport);
    canvas.on('after:render', scheduleViewport);

    return () => {
      canvas.off('selection:created', handleSelectionCreated);
      canvas.off('selection:updated', handleSelectionCreated);
      canvas.off('selection:cleared', handleSelectionCleared);
      canvas.off('object:moving', handleTransform);
      canvas.off('object:scaling', handleTransform);
      canvas.off('object:rotating', handleTransform);
      canvas.off('object:skewing', handleTransform);
      canvas.off('object:modified', handleModified);
      canvas.off('object:added', handleAdded);
      canvas.off('object:removed', handleRemoved);
      canvas.off('path:created', handlePathCreated);
      canvas.off('mouse:over', handleHover);
      canvas.off('mouse:move', handleHoverMove);
      canvas.off('mouse:down', clearHover);
      canvas.off('mouse:out', clearHover);
      canvas.off('mouse:wheel', scheduleViewport);
      canvas.off('after:render', scheduleViewport);
      if (viewportFrame) cancelAnimationFrame(viewportFrame);
      restoreHover(false);
    };
  };

  const attachFabricMagnifier = (options: AsukaCanvas2DMagnifierOptions): (() => void) => {
    const canvas = getCanvas();
    const overlay = options?.overlay;
    const lower: HTMLCanvasElement | null = canvas?.lowerCanvasEl ?? null;
    const context = overlay?.getContext?.('2d') ?? null;
    if (!canvas || !overlay || !lower || !context) return () => {};

    let active = false;
    let x = 0;
    let y = 0;
    let highQualitySource: HTMLImageElement | null = null;
    let highQualityDirty = true;
    let highQualityPending = false;
    let highQualityLastRequestedAt = 0;
    const notify = () => {
      try { options.onStateChange?.({ active, x, y }); } catch {}
    };
    const setState = (nextActive: boolean, nextX = x, nextY = y) => {
      const changed = active !== nextActive || x !== nextX || y !== nextY;
      active = nextActive;
      x = nextX;
      y = nextY;
      if (changed) notify();
    };

    const redraw = () => {
      const parent = overlay.parentElement;
      if (!parent) return;
      const parentRect = parent.getBoundingClientRect();
      const lowerRect = lower.getBoundingClientRect();
      overlay.style.left = `${lowerRect.left - parentRect.left}px`;
      overlay.style.top = `${lowerRect.top - parentRect.top}px`;
      overlay.width = Math.max(1, Math.round(lower.width));
      overlay.height = Math.max(1, Math.round(lower.height));
      overlay.style.width = `${Math.round(lowerRect.width)}px`;
      overlay.style.height = `${Math.round(lowerRect.height)}px`;
      context.clearRect(0, 0, overlay.width, overlay.height);
      if (!options.enabled || !active) return;

      const size = Math.max(60, Number(options.size) || 140);
      const zoom = Math.max(1, Number(options.zoom) || 2);
      const radius = size / 2;
      const sourceWidth = size / zoom;
      const sourceHeight = size / zoom;
      const sourceX = x - sourceWidth / 2;
      const sourceY = y - sourceHeight / 2;
      context.save();
      context.beginPath();
      context.arc(x, y, radius, 0, Math.PI * 2);
      context.closePath();
      context.clip();
      context.fillStyle = '#ffffff';
      context.fillRect(x - radius, y - radius, size, size);
      const highQuality = highQualitySource?.complete && highQualitySource.naturalWidth > 0 && highQualitySource.naturalHeight > 0
        ? highQualitySource
        : null;
      const source: CanvasImageSource = highQuality ?? lower;
      const sourceScaleX = highQuality ? highQuality.naturalWidth / Math.max(1, overlay.width) : 1;
      const sourceScaleY = highQuality ? highQuality.naturalHeight / Math.max(1, overlay.height) : 1;
      context.imageSmoothingEnabled = true;
      try { context.imageSmoothingQuality = 'high'; } catch {}
      context.drawImage(
        source,
        sourceX * sourceScaleX,
        sourceY * sourceScaleY,
        sourceWidth * sourceScaleX,
        sourceHeight * sourceScaleY,
        x - radius,
        y - radius,
        size,
        size,
      );
      context.restore();
      context.save();
      context.beginPath();
      context.arc(x, y, radius, 0, Math.PI * 2);
      context.closePath();
      context.lineWidth = 2;
      context.strokeStyle = 'rgba(2,6,23,0.75)';
      context.stroke();
      context.beginPath();
      context.moveTo(x - 10, y);
      context.lineTo(x + 10, y);
      context.moveTo(x, y - 10);
      context.lineTo(x, y + 10);
      context.lineWidth = 1;
      context.strokeStyle = 'rgba(2,6,23,0.55)';
      context.stroke();
      context.restore();
    };

    const refreshHighQualitySource = () => {
      if (!options.enabled || highQualityPending || (!highQualityDirty && highQualitySource?.complete)) return;
      const now = typeof performance !== 'undefined' ? performance.now() : Date.now();
      if (now - highQualityLastRequestedAt < 160) return;
      highQualityPending = true;
      highQualityDirty = false;
      highQualityLastRequestedAt = now;
      try {
        const bitmapWidth = Math.max(1, Number(lower.width || 1));
        const logicalWidth = Math.max(1, Number(canvas.getWidth?.() ?? bitmapWidth));
        const canvasScale = bitmapWidth / logicalWidth;
        const deviceScale = Math.max(1, Math.min(3, Math.ceil(window.devicePixelRatio || 1)));
        const zoomScale = Math.max(1, Math.min(2, Math.ceil((Number(options.zoom) || 2) / 2)));
        const multiplier = Math.max(canvasScale, Math.min(4, deviceScale * zoomScale));
        const dataUrl = canvas.toDataURL?.({ format: 'png', multiplier, enableRetinaScaling: true });
        if (!dataUrl || typeof dataUrl !== 'string') {
          highQualityPending = false;
          return;
        }
        const image = new Image();
        try { image.decoding = 'async'; } catch {}
        image.onload = () => {
          highQualitySource = image;
          highQualityPending = false;
          redraw();
        };
        image.onerror = () => {
          highQualityPending = false;
          highQualitySource = null;
        };
        image.src = dataUrl;
      } catch {
        highQualityPending = false;
        highQualitySource = null;
      }
    };

    const onMove = (eventInfo?: any) => {
      if (!options.enabled) return;
      const event = eventInfo?.e as MouseEvent | undefined;
      if (!event || !eventInfo?.target) {
        setState(false);
        redraw();
        return;
      }
      const rect = lower.getBoundingClientRect();
      const localX = event.clientX - rect.left;
      const localY = event.clientY - rect.top;
      if (localX < 0 || localY < 0 || localX > rect.width || localY > rect.height) {
        setState(false);
        redraw();
        return;
      }
      setState(true, localX * (lower.width / Math.max(1, rect.width)), localY * (lower.height / Math.max(1, rect.height)));
      refreshHighQualitySource();
      redraw();
    };
    const onOut = () => {
      setState(false);
      redraw();
    };
    const onAfterRender = () => {
      highQualityDirty = true;
      redraw();
    };

    const previousSelection = canvas.selection;
    const previousSkipTargetFind = canvas.skipTargetFind;
    const previousHoverCursor = canvas.hoverCursor;
    const previousDefaultCursor = canvas.defaultCursor;
    if (options.enabled) {
      canvas.selection = false;
      canvas.skipTargetFind = false;
      canvas.hoverCursor = 'zoom-in';
      canvas.defaultCursor = 'zoom-in';
    }
    canvas.on?.('mouse:move', onMove);
    canvas.on?.('mouse:out', onOut);
    canvas.on?.('after:render', onAfterRender);
    const observer = typeof ResizeObserver === 'function' ? new ResizeObserver(() => redraw()) : null;
    try { observer?.observe(lower); } catch {}
    redraw();

    return () => {
      try { canvas.off?.('mouse:move', onMove); } catch {}
      try { canvas.off?.('mouse:out', onOut); } catch {}
      try { canvas.off?.('after:render', onAfterRender); } catch {}
      try { observer?.disconnect(); } catch {}
      canvas.selection = previousSelection;
      canvas.skipTargetFind = previousSkipTargetFind;
      canvas.hoverCursor = previousHoverCursor;
      canvas.defaultCursor = previousDefaultCursor;
      highQualitySource = null;
      setState(false);
      context.clearRect(0, 0, overlay.width, overlay.height);
    };
  };

  const pickFabricAreaForAdd = async (): Promise<string | null> => {
    const zones = getFabricDrawingZones();
    if (zones.length > 1 && typeof instance?.pickDrawingZoneForAdd === 'function') {
      return String(await instance.pickDrawingZoneForAdd() ?? '').trim() || null;
    }
    return String(instance?.getActiveDrawingZone?.() ?? zones[0]?.id ?? '').trim() || null;
  };

  const canvas2d: AsukaCanvas2DExtension = {
    getSelectedText: getFabricSelectedTextState,
    updateSelectedText: (patch, options) => runAsukaRendererMutation('fabric', 'canvas2d.updateSelectedText', async () => {
      if (!getFabricSelectedTextState()) return null;
      const applied = instance?.patchActiveTextStyle?.(cloneEnginePlain(patch));
      if (applied && typeof applied.then === 'function') await applied;
      if (options?.commit === true) {
        try { instance?.commitHistory?.(options?.source ?? 'text-style'); } catch {}
      }
      try { getCanvas()?.requestRenderAll?.(); } catch {}
      return getFabricSelectedTextState();
    }, { id: (selection) => selection?.objectId ?? null, changed: (selection) => !!selection }),
    updateSelectedTextCurve: (patch, options) => runAsukaRendererMutation('fabric', 'canvas2d.updateSelectedTextCurve', async () => {
      if (!getFabricSelectedTextState()) return null;
      const applied = instance?.updateCurvedText?.({
        ...cloneEnginePlain(patch),
        ...(options?.preview === true ? { __preview: true } : null),
        ...(options?.commit === true ? { __commit: true } : null),
      });
      if (applied && typeof applied.then === 'function') await applied;
      return getFabricSelectedTextState();
    }, { id: (selection) => selection?.objectId ?? null, changed: (selection) => !!selection }),
    setSelectedTextCurved: (curved, patch) => runAsukaRendererMutation('fabric', 'canvas2d.setSelectedTextCurved', async () => {
      if (!getFabricSelectedTextState()) return null;
      const applied = curved
        ? instance?.curveActiveText?.(cloneEnginePlain(patch ?? {}))
        : instance?.uncurveActiveText?.();
      if (applied && typeof applied.then === 'function') await applied;
      return getFabricSelectedTextState();
    }, { id: (selection) => selection?.objectId ?? null, changed: (selection) => !!selection }),
    getColorSelection: getFabricColorSelection,
    updateColorSelection: (patch) => runAsukaRendererMutation('fabric', 'canvas2d.updateColorSelection', async () => {
      const active = getCanvas()?.getActiveObject?.() ?? null;
      if (!active) return null;
      if (typeof patch?.fill === 'string') {
        if (typeof patch?.partIndex === 'number' && typeof instance?.setPathFill === 'function') {
          await instance.setPathFill(patch.fill, patch.partIndex, patch.printingMethodId ?? null, active);
        } else {
          const applied = instance?.setActiveObjectFill?.(patch.fill);
          if (applied && typeof applied.then === 'function') await applied;
        }
      }
      if (patch?.printingMethodId !== undefined) active.printingMethodId = patch.printingMethodId;
      try { getCanvas()?.requestRenderAll?.(); } catch {}
      return getFabricColorSelection();
    }, { id: (selection) => selection?.objectId ?? null, changed: (selection) => !!selection }),
    addShape: (input) => runAsukaRendererMutation('fabric', 'canvas2d.addShape', async () => {
      const created = instance?.addShape?.(cloneEnginePlain(input));
      const native = created && typeof created.then === 'function' ? await created : created;
      return native ? makeObjectRefFromNative(native, input.kind) : null;
    }, { id: (object) => object?.id ?? null, changed: (object) => !!object }),
    createAreaDraft: (input) => runAsukaRendererMutation('fabric', 'canvas2d.createAreaDraft', async () => {
      const created = instance?.createDrawingAreaDraft?.(cloneEnginePlain(input));
      const native = created && typeof created.then === 'function' ? await created : created;
      return native ? makeObjectRefFromNative(native, 'drawing-area-draft') : null;
    }, { id: (object) => object?.id ?? null, changed: (object) => !!object }),
    startAreaDraft: (input, onComplete) => {
      const cleanup = instance?.startDrawingAreaDraft?.(cloneEnginePlain(input), (native: any) => {
        onComplete?.(native ? makeObjectRefFromNative(native, 'drawing-area-draft') : null);
      });
      return typeof cleanup === 'function' ? cleanup : () => {};
    },
    clear: () => runAsukaRendererMutation('fabric', 'canvas2d.clear', () => {
      instance?.clear?.();
      return true;
    }),
    enterTextEditing: (objectId: string) => {
      const canvas = instance?.getCanvas?.();
      const obj = instance?.getObjectById?.(objectId);
      if (!obj) return;
      try { canvas?.setActiveObject?.(obj); } catch {}
      try { obj?.enterEditing?.(); } catch {}
      try { canvas?.requestRenderAll?.(); } catch {}
    },
    setSnapping: (patch) => {
      instance?.setSnapping?.(cloneEnginePlain(patch));
      return canvas2d.getSnapping();
    },
    getSnapping: () => {
      const state = instance?.getSnapping?.() ?? {};
      return {
        enabled: state?.enabled !== false,
        threshold: Math.max(0, Number(state?.threshold ?? 8) || 0),
        ...(Number.isFinite(Number(state?.release)) ? { release: Math.max(0, Number(state.release)) } : null),
        ...(state?.priority === 'objects-first' || state?.priority === 'zones-first' ? { priority: state.priority } : null),
      };
    },
    subscribeTextEditRequests: (listener) => {
      const canvas = getCanvas();
      const element = canvas?.upperCanvasEl ?? canvas?.wrapperEl ?? null;
      if (!element?.addEventListener) return () => {};
      const onDoubleClick = () => {
        const selection = getFabricSelectedTextState();
        if (selection?.curved) listener(selection);
      };
      element.addEventListener('dblclick', onDoubleClick);
      return () => { try { element.removeEventListener('dblclick', onDoubleClick); } catch {} };
    },
    subscribeInteractions: subscribeFabricInteractions,
    attachMagnifier: attachFabricMagnifier,
  };

  const getFabricHistoryInfo = (): AsukaHistoryInfo => normalizeRenderEngineHistoryInfo(instance?.getHistoryInfo?.(), {
    engine: 'fabric',
    canUndo: Boolean(instance?.canUndo?.()),
    canRedo: Boolean(instance?.canRedo?.()),
    maxSnapshots: Number(instance?.getHistoryInfo?.()?.maxSnapshots ?? 8) || 8,
  });
  const fallbackFabricPlacement = createAsukaPlacementSessionController<AsukaObjectTransform>('fabric');
  const emitFallbackFabricPlacement = (action: string, status: AsukaPlacementStatus | null) => {
    emitRenderEngineEvent({
      type: 'placement',
      engine: 'fabric',
      action,
      id: status?.objectId ?? null,
      payload: status,
    });
    return status;
  };
  const fabricPlacement: AsukaRenderEnginePlacement = {
    getStatus: () => (
      typeof (instance as any)?.__asukaPlacement?.getStatus === 'function'
        ? (instance as any).__asukaPlacement.getStatus()
        : fallbackFabricPlacement.getStatus()
    ),
    begin: (input) => {
      if (typeof (instance as any)?.__asukaPlacement?.begin === 'function') {
        return (instance as any).__asukaPlacement.begin(input);
      }
      return emitFallbackFabricPlacement('placement.begin', fallbackFabricPlacement.begin(input))!;
    },
    update: (input) => {
      if (typeof (instance as any)?.__asukaPlacement?.update === 'function') {
        return (instance as any).__asukaPlacement.update(input);
      }
      return emitFallbackFabricPlacement('placement.update', fallbackFabricPlacement.update(input))!;
    },
    commit: (metadata) => {
      if (typeof (instance as any)?.__asukaPlacement?.commit === 'function') {
        return (instance as any).__asukaPlacement.commit(metadata);
      }
      return emitFallbackFabricPlacement('placement.commit', fallbackFabricPlacement.commit(metadata));
    },
    cancel: (metadata) => {
      if (typeof (instance as any)?.__asukaPlacement?.cancel === 'function') {
        return (instance as any).__asukaPlacement.cancel(metadata);
      }
      return emitFallbackFabricPlacement('placement.cancel', fallbackFabricPlacement.cancel(metadata));
    },
    clear: () => {
      if (typeof (instance as any)?.__asukaPlacement?.clear === 'function') {
        (instance as any).__asukaPlacement.clear();
        return;
      }
      fallbackFabricPlacement.clear();
      emitFallbackFabricPlacement('placement.clear', null);
    },
  };

  const engineAdapter: AsukaRenderEngine = {
    kind: 'fabric',
    capabilities: getDefaultRenderEngineCapabilities('fabric'),
    events: createRenderEngineEvents('fabric'),
    canvas2d,
    placement: fabricPlacement,
    scene: {
      getGraph: () => listFabricRegistryObjects({ includeTechnical: false }).map((obj) => ({
        ...obj,
        category: 'object',
        depth: 0,
        readonly: !!obj.locked,
        selectable: !obj.locked,
        actions: { select: true, hide: true, lock: true, delete: !obj.locked, transform: !obj.locked, color: true },
      })),
    },
    lifecycle: {
      mount: () => {},
      unmount: () => {},
      activateView: (viewId, options) => runtime.activateView?.(viewId, options),
      deactivateView: (viewId) => runtime.deactivateView?.(viewId),
      loadView: async (viewId: string, state: unknown) => {
      if (state && typeof instance?.util?.state?.importPart === 'function') {
        instance.util.state.importPart('document', state as any, { replaceViews: true, replaceSnapshots: true });
        return;
      }
      if (viewId && typeof instance?.util?.state?.rebuildActiveViewFromDocument === 'function') {
        instance.util.state.rebuildActiveViewFromDocument(viewId);
      }
      },
      exportView: async (viewId: string) => {
        const exported = instance?.util?.state?.exportPart?.('document');
        const documentState = exported?.document;
        if (!viewId) return documentState;
        return documentState?.views || documentState?.snapshots || documentState;
      },
    },
    history: {
      getInfo: () => getFabricHistoryInfo(),
      exportTrack: (viewId?: string | null) => typeof (instance as any)?.exportHistoryTrack === 'function' ? (instance as any).exportHistoryTrack(viewId) : null,
      importTrack: (viewId: string, track?: unknown) => runAsukaRendererMutation('fabric', 'history.importTrack', async () => {
        if (typeof (instance as any)?.importHistoryTrack === 'function') await (instance as any).importHistoryTrack(viewId, track);
        return getFabricHistoryInfo();
      }),
      commit: (options?: AsukaHistoryOptions) => runAsukaRendererMutation('fabric', 'history.commit', async () => {
        const reason = options?.reason ?? options?.source ?? 'engine.history.commit';
        if (options?.reset === true && typeof (instance as any)?.__asukaCaptureHistoryBaseline === 'function') {
          await (instance as any).__asukaCaptureHistoryBaseline({ force: true, reason });
        } else if (typeof instance?.commitHistorySnapshotNow === 'function') {
          await instance.commitHistorySnapshotNow({ force: options?.force !== false, reason });
        } else if (typeof (instance as any)?.__asukaCommitDocumentHistorySnapshot === 'function') {
          await (instance as any).__asukaCommitDocumentHistorySnapshot({ force: options?.force !== false, reason });
        } else if (typeof instance?.commitHistoryChange === 'function') {
          await instance.commitHistoryChange({ force: options?.force !== false, reason });
        }
        return getFabricHistoryInfo();
      }),
      undo: () => runAsukaRendererMutation('fabric', 'history.undo', async () => { await instance?.undo?.(); return getFabricHistoryInfo(); }),
      redo: () => runAsukaRendererMutation('fabric', 'history.redo', async () => { await instance?.redo?.(); return getFabricHistoryInfo(); }),
      reset: () => runAsukaRendererMutation('fabric', 'history.reset', async () => { await instance?.reset?.(); return getFabricHistoryInfo(); }),
      goTo: (index: number) => runAsukaRendererMutation('fabric', 'history.goTo', async () => { await instance?.goToHistory?.(index); return getFabricHistoryInfo(); }),
    },
    areas: {
      list: () => listFabricPrintAreas(),
      get: (id: string) => getFabricPrintArea(id),
      pickForAdd: pickFabricAreaForAdd,
      create: (input?: AsukaPrintAreaInput) => runAsukaRendererMutation('fabric', 'areas.create', () => createFabricPrintArea(input ?? {}), {
        changed: (area) => area != null,
        id: (area) => area?.id,
      }),
      update: (id: string, patch: AsukaPrintAreaPatch) => runAsukaRendererMutation('fabric', 'areas.update', () => updateFabricPrintArea(id, patch), {
        changed: (area) => area != null,
        id,
      }),
      remove: (id: string) => {
        const previous = getFabricPrintArea(id);
        let changed = false;
        return runAsukaRendererMutation('fabric', 'areas.remove', () => {
          changed = removeFabricPrintArea(id);
          return previous;
        }, { changed: () => changed, id });
      },
      select: (id: string | null) => { try { instance?.setActiveDrawingZone?.(id); } catch {} },
      getActive: () => getFabricPrintArea(instance?.getActiveDrawingZone?.() ?? null),
      focus: (id: string) => { try { instance?.setActiveDrawingZone?.(id); } catch {} },
      getBounds: (id: string) => getFabricPrintAreaBoundsBox(id),
      contains: (id: string, printAreaId?: string | null, options?: AsukaPrintAreaConstraintOptions) => getFabricConstraintStatus(id, printAreaId, options),
      constrain: (id: string, printAreaId?: string | null, options?: AsukaPrintAreaConstraintOptions) => runAsukaRendererMutation('fabric', 'areas.constrain', () => constrainFabricObjectToPrintArea(id, printAreaId, options), { id }),
      moveObject: (id: string, delta: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaPrintAreaConstraintOptions) => runAsukaRendererMutation('fabric', 'areas.moveObject', () => {
        moveFabricObjectWithinPrintArea(id, delta, options);
        return getFabricConstraintStatus(id, options?.printAreaId ?? options?.targetPrintAreaId ?? null, options);
      }, { id }),
      transformObject: (id: string, patch: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaPrintAreaConstraintOptions) => runAsukaRendererMutation('fabric', 'areas.transformObject', () => {
        transformFabricObjectWithinPrintArea(id, patch, options);
        return getFabricConstraintStatus(id, options?.printAreaId ?? options?.targetPrintAreaId ?? null, options);
      }, { id }),
    },
    objects: {
      list: (options?: AsukaObjectListOptions) => listFabricRegistryObjects(options),
      get: (id: string) => listFabricRegistryObjects({ includeTechnical: true }).find((obj) => String(obj?.id ?? '') === String(id ?? '')) ?? null,
      getBounds: (id: string) => getFabricObjectBounds(id),
      measure: (id: string, options?: AsukaLayerMeasurementOptions) => measureFabricObject(id, options),
      reorder: (orderedIds: string[], options?: AsukaObjectOrderOptions) => {
        const ids = Array.isArray(orderedIds) ? orderedIds.map((id) => String(id ?? '').trim()).filter(Boolean) : [];
        let changed = false;
        return runAsukaRendererMutation('fabric', 'objects.reorder', () => {
          changed = reorderFabricObjects(ids, options);
          return ids;
        }, { changed: () => changed });
      },
      preview: (id: string, options?: AsukaObjectPreviewOptions) => {
      try { instance?.previewObject?.(id, options); } catch {}
      },
      clearPreview: () => {
      try { instance?.clearObjectPreview?.(); } catch {}
      },
    addImage: (input: AsukaImageInput) => runAsukaRendererMutation('fabric', 'objects.addImage', async () => {
      const targetPrintAreaId = applyFabricPrintAreaInput(input);
      if (input?.file && typeof instance?.addImageFromFile === 'function') {
        const before = new Set((instance?.getCanvas?.()?.getObjects?.() ?? []).map((o: any) => o));
        await instance.addImageFromFile(input.file, input);
        const created = (instance?.getCanvas?.()?.getObjects?.() ?? []).find((o: any) => !before.has(o));
        applyFabricObjectInputMetadata(created, input);
        tagFabricObjectPrintArea(created, targetPrintAreaId);
        return makeObjectRefFromNative(created, 'image');
      }
      const url = String(input?.url ?? input?.src ?? '').trim();
      if (!url || typeof instance?.addImageFromUrl !== 'function') throw new Error('engine.addImage requires a url/src or file.');
      const before = new Set((instance?.getCanvas?.()?.getObjects?.() ?? []).map((o: any) => o));
      await instance.addImageFromUrl(url, input);
      const created = (instance?.getCanvas?.()?.getObjects?.() ?? []).find((o: any) => !before.has(o));
      applyFabricObjectInputMetadata(created, input);
      tagFabricObjectPrintArea(created, targetPrintAreaId);
      return makeObjectRefFromNative(created, 'image');
    }, { id: (object) => object.id }),
    addText: (input: AsukaTextInput) => runAsukaRendererMutation('fabric', 'objects.addText', async () => {
      const targetPrintAreaId = applyFabricPrintAreaInput(input);
      const fabricInput = typeof input.color === 'string' && (input as any).fill == null
        ? { ...input, fill: input.color }
        : input;
      const created = instance?.addText?.(input.text ?? '', fabricInput);
      applyFabricObjectInputMetadata(created, input);
      tagFabricObjectPrintArea(created, targetPrintAreaId);
      return makeObjectRefFromNative(created, 'text');
    }, { id: (object) => object.id }),
    addDesign: (input: AsukaDesignInput) => runAsukaRendererMutation('fabric', 'objects.addDesign', async () => {
      const targetPrintAreaId = applyFabricPrintAreaInput(input);
      const svgText = String(input?.svgText ?? input?.svg ?? '').trim();
      if (svgText && typeof instance?.addSvgFromString === 'function') {
        const before = new Set((instance?.getCanvas?.()?.getObjects?.() ?? []).map((o: any) => o));
        await instance.addSvgFromString(svgText, input);
        const created = (instance?.getCanvas?.()?.getObjects?.() ?? []).find((o: any) => !before.has(o));
        applyFabricObjectInputMetadata(created, input);
        tagFabricObjectPrintArea(created, targetPrintAreaId);
        return makeObjectRefFromNative(created, 'design');
      }
      const url = String(input?.url ?? input?.src ?? '').trim();
      if (!url) throw new Error('engine.addDesign requires a url/src or svg/svgText.');
      const before = new Set((instance?.getCanvas?.()?.getObjects?.() ?? []).map((o: any) => o));
      if (/\.svg(\?|#|$)/i.test(url) && typeof instance?.addSvgFromUrl === 'function') await instance.addSvgFromUrl(url, input);
      else if (typeof instance?.addImageFromUrl === 'function') await instance.addImageFromUrl(url, input);
      const created = (instance?.getCanvas?.()?.getObjects?.() ?? []).find((o: any) => !before.has(o));
      applyFabricObjectInputMetadata(created, input);
      tagFabricObjectPrintArea(created, targetPrintAreaId);
      return makeObjectRefFromNative(created, 'design');
    }, { id: (object) => object.id }),
    addElement: (input: AsukaElementInput) => runAsukaRendererMutation('fabric', 'objects.addElement', async () => {
      const element = (input?.element ?? {}) as any;
      const identifier = String(element?.id ?? element?.name ?? '').trim();
      if (!identifier || typeof instance?.addElementInstance !== 'function') throw new Error('engine.addElement requires a valid element and a Fabric designer instance.');
      await instance.addElementInstance(identifier, { variationId: input?.variationId ? String(input.variationId) : undefined } as any);
      return { id: identifier, kind: String(element?.type ?? 'element') || 'element' };
    }, { id: (object) => object.id }),
    removeElement: (id?: string) => runAsukaRendererMutation('fabric', 'objects.removeElement', async () => {
      const identifier = String(id ?? '').trim();
      if (identifier && typeof instance?.removeElement === 'function') await instance.removeElement(identifier);
      return identifier ? { id: identifier, kind: 'element' } : null;
    }, { changed: (object) => object != null, id: id ?? null }),
    remove: (id: string) => {
      const previous = listFabricRegistryObjects({ includeTechnical: true }).find((object) => object.id === id) ?? null;
      let changed = false;
      return runAsukaRendererMutation('fabric', 'objects.remove', () => {
        changed = removeFabricObject(id);
        return previous;
      }, { changed: () => changed, id });
    },
    update: (id: string, patch: AsukaObjectPatch) => {
      let changed = false;
      return runAsukaRendererMutation('fabric', 'objects.update', () => {
        changed = updateFabricObject(id, patch);
        return listFabricRegistryObjects({ includeTechnical: true }).find((object) => object.id === id) ?? null;
      }, { changed: () => changed, id });
    },
      duplicate: (id: string, options?: AsukaObjectDuplicateOptions) => runAsukaRendererMutation('fabric', 'objects.duplicate', () => duplicateFabricObject(id, options), { changed: (object) => object != null, id: (object) => object?.id ?? null }),
      setVisible: (id: string, visible: boolean) => {
        let changed = false;
        return runAsukaRendererMutation('fabric', 'objects.setVisible', () => {
          changed = updateFabricObject(id, { visible });
          return listFabricRegistryObjects({ includeTechnical: true }).find((object) => object.id === id) ?? null;
        }, { changed: () => changed, id });
      },
      setLocked: (id: string, locked: boolean) => {
        let changed = false;
        return runAsukaRendererMutation('fabric', 'objects.setLocked', () => {
          const object = getFabricObjectById(id);
          const wasLocked = object?._asukaLocked === true;
          if (object && typeof instance?.setObjectLocked === 'function') {
            instance.setObjectLocked(id, locked);
            const updated = getFabricObjectById(id);
            const isLocked = updated?._asukaLocked === true;
            changed = wasLocked !== isLocked && isLocked === locked;
          } else {
            changed = updateFabricObject(id, { locked });
            if (locked) {
              try {
                const canvas = getCanvas();
                const updated = getFabricObjectById(id);
                const activeObjects = canvas?.getActiveObjects?.() ?? [];
                if (updated && (canvas?.getActiveObject?.() === updated || activeObjects.includes(updated))) {
                  canvas?.discardActiveObject?.();
                  canvas?.requestRenderAll?.();
                }
              } catch {}
            }
          }
          return listFabricRegistryObjects({ includeTechnical: true }).find((object) => object.id === id) ?? null;
        }, { changed: () => changed, id });
      },
      bringForward: (id: string) => {
        let changed = false;
        return runAsukaRendererMutation('fabric', 'objects.bringForward', () => {
          changed = moveFabricObjectRelative(id, -1);
          return listFabricRegistryObjects({ includeTechnical: true }).find((object) => object.id === id) ?? null;
        }, { changed: () => changed, id });
      },
      sendBackward: (id: string) => {
        let changed = false;
        return runAsukaRendererMutation('fabric', 'objects.sendBackward', () => {
          changed = moveFabricObjectRelative(id, 1);
          return listFabricRegistryObjects({ includeTechnical: true }).find((object) => object.id === id) ?? null;
        }, { changed: () => changed, id });
      },
      getTransform: (id: string) => getFabricObjectTransform(id),
      updateTransform: (id: string, patch: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaObjectMutationOptions) => runAsukaRendererMutation('fabric', 'objects.updateTransform', () => {
        updateFabricObjectTransform(id, patch, options as any);
        return getFabricObjectTransform(id);
      }, { changed: (transform) => transform != null, id }),
    },
    selection: {
      list: () => listFabricRegistryObjects({ includeTechnical: false }).filter((obj) => !!obj.selected),
      getAnchorBox: () => getFabricSelectionAnchorBox(),
      select: (id: string | null) => {
      try {
        const canvas = getCanvas();
        canvas?.discardActiveObject?.();
        let obj: any = null;
        if (id) {
          obj = getFabricObjectById(id);
          if (obj) canvas?.setActiveObject?.(obj);
        }
        canvas?.requestRenderAll?.();
        try {
          if (obj) canvas?.fire?.('selection:updated', { target: obj, selected: [obj] });
          else canvas?.fire?.('selection:cleared', {});
        } catch {}
      } catch {}
      },
    },
    viewport: {
      zoomIn: () => { instance?.zoomIn?.(); },
      zoomOut: () => { instance?.zoomOut?.(); },
      fit: () => { instance?.fitToViewport?.(); },
      resize: () => { instance?.resizeToHost?.(); },
      setDocumentSize: (width: number, height: number) => { instance?.setDocumentSize?.(width, height); },
      getZoom: () => Math.max(0.0001, Number(instance?.getZoom?.() ?? getCanvas()?.getZoom?.() ?? 1) || 1),
      setZoom: (zoom: number) => { instance?.setZoom?.(Math.max(0.0001, Number(zoom) || 1)); },
      reset: () => { instance?.resetView?.(); },
      getFrame: () => {
        const canvas = getCanvas();
        const element = canvas?.lowerCanvasEl ?? null;
        if (!canvas || !element?.getBoundingClientRect) return null;
        const rect = element.getBoundingClientRect();
        const documentWidth = Math.max(1, Number(canvas?.getWidth?.() ?? element.width ?? 1) || 1);
        const documentHeight = Math.max(1, Number(canvas?.getHeight?.() ?? element.height ?? 1) || 1);
        return {
          left: rect.left,
          top: rect.top,
          width: Math.max(1, rect.width),
          height: Math.max(1, rect.height),
          documentWidth,
          documentHeight,
          zoomX: Math.max(0.0001, rect.width / documentWidth),
          zoomY: Math.max(0.0001, rect.height / documentHeight),
          space: 'screen',
          unit: 'css-px',
        } as AsukaViewportFrame;
      },
    },
    exports: {
      isFormatAvailable: (format: string) => {
      const normalized = normalizeExportFormat(format);
      if (normalized !== 'pdf') return true;
      try { return !!instance?.isPdfAvailable?.(); } catch { return false; }
    },
      canLoadSupport: (format: string) => {
      const normalized = normalizeExportFormat(format);
      if (normalized !== 'pdf') return false;
      try { return !!instance?.canLoadPdfSupport?.(); } catch { return false; }
    },
      ensureSupport: async (format: string) => {
      const normalized = normalizeExportFormat(format);
      if (normalized !== 'pdf') return true;
      try {
        if (instance?.isPdfAvailable?.()) return true;
        if (typeof instance?.ensurePdfSupport === 'function') return !!(await instance.ensurePdfSupport());
      } catch {}
      try { return !!instance?.isPdfAvailable?.(); } catch { return normalized !== 'pdf'; }
    },
      download: async (target?: unknown, options?: AsukaDownloadOptions) => {
      if (typeof instance?.download !== 'function') throw new Error('Fabric download runtime is not available.');
      await instance.download(target, options ?? {});
    },
      downloadAllViews: async (options?: AsukaDownloadOptions) => {
      if (typeof instance?.downloadAllViews !== 'function') throw new Error('Fabric batch view export is not available.');
      await instance.downloadAllViews(options ?? {});
    },
      downloadAllAreas: async (options?: AsukaDownloadOptions) => {
      if (typeof instance?.downloadAllDrawingZones !== 'function') throw new Error('Fabric drawing-area export is not available.');
      await instance.downloadAllDrawingZones(options ?? {});
    },
      downloadAllObjects: async (options?: AsukaDownloadOptions) => {
      if (typeof instance?.downloadAllObjects !== 'function') throw new Error('Fabric object export is not available.');
      await instance.downloadAllObjects(options ?? {});
    },
      png: async (options: AsukaExportOptions = {}) => {
      if (typeof instance?.exportPngBlob === 'function') return await instance.exportPngBlob(options);
      const canvas = instance?.getCanvas?.();
      const dataUrl = canvas?.toDataURL?.({ format: 'png', multiplier: options.scale ?? 1, quality: options.quality });
      if (!dataUrl || typeof fetch !== 'function') throw new Error('Fabric canvas cannot export PNG in this environment.');
      return await (await fetch(dataUrl)).blob();
      },
    },
  };

  return engineAdapter;
}

export function createUnavailableRenderEngine(kind: AsukaRenderEngineKind = '3d', reason = 'The render engine addon is not loaded yet.'): AsukaRenderEngine {
  const normalizedKind = normalizeRenderEngineKind(kind, '3d');
  const placement = createAsukaPlacementSessionController<AsukaObjectTransform>(normalizedKind);
  const fail = () => {
    throw new AsukaRendererError('RENDERER_NOT_MOUNTED', reason, { engine: normalizedKind });
  };
  const unavailableStatus = (objectId: string, printAreaId?: string | null): AsukaPrintAreaConstraintStatus => ({
    objectId: String(objectId ?? ''),
    printAreaId: String(printAreaId ?? '') || null,
    inside: false,
    intersects: false,
    engine: normalizedKind,
  });
  const unavailableMutation = <T,>(operation: string): AsukaMutationPromise<T> => runAsukaRendererMutation(
    normalizedKind,
    operation,
    () => fail() as T,
  );
  const unchangedMutation = <T,>(operation: string, value: T): AsukaMutationPromise<T> => runAsukaRendererMutation(
    normalizedKind,
    operation,
    () => value,
    { changed: false },
  );
  return {
    kind: normalizedKind,
    capabilities: normalizedKind === '3d'
      ? ({ scene3d: true, objects: false, meshes: false, materials: false, assembly3d: false, printAreas: false, decorations: false, selectionToolbar: false, exportPng: false, exportState: false })
      : ({ canvas2d: false, objects: false, selectionToolbar: false, exportPng: false, exportState: false }),
    events: createRenderEngineEvents(normalizedKind),
    lifecycle: {
      mount: () => {},
      unmount: () => {},
      activateView: () => {},
      deactivateView: () => {},
      loadView: async () => {},
      exportView: async () => ({ kind: normalizedKind, unavailable: true, reason }),
    },
    history: {
      getInfo: () => normalizeRenderEngineHistoryInfo(null, { engine: normalizedKind }),
      commit: () => unchangedMutation('history.commit', normalizeRenderEngineHistoryInfo(null, { engine: normalizedKind })),
      exportTrack: () => null,
      importTrack: () => unchangedMutation('history.importTrack', normalizeRenderEngineHistoryInfo(null, { engine: normalizedKind })),
      undo: () => unchangedMutation('history.undo', normalizeRenderEngineHistoryInfo(null, { engine: normalizedKind })),
      redo: () => unchangedMutation('history.redo', normalizeRenderEngineHistoryInfo(null, { engine: normalizedKind })),
      reset: () => unchangedMutation('history.reset', normalizeRenderEngineHistoryInfo(null, { engine: normalizedKind })),
      goTo: () => unchangedMutation('history.goTo', normalizeRenderEngineHistoryInfo(null, { engine: normalizedKind })),
    },
    scene: { getGraph: () => [] },
    placement: {
      getStatus: placement.getStatus,
      begin: placement.begin,
      update: placement.update,
      commit: placement.commit,
      cancel: placement.cancel,
      clear: placement.clear,
    },
    objects: {
      list: () => [],
      get: () => null,
      addImage: () => unavailableMutation('objects.addImage'),
      addText: () => unavailableMutation('objects.addText'),
      addDesign: () => unavailableMutation('objects.addDesign'),
      addElement: () => unavailableMutation('objects.addElement'),
      removeElement: () => unavailableMutation('objects.removeElement'),
      remove: () => unavailableMutation('objects.remove'),
      update: () => unavailableMutation('objects.update'),
      reorder: () => unavailableMutation('objects.reorder'),
      duplicate: () => unavailableMutation('objects.duplicate'),
      setVisible: () => unavailableMutation('objects.setVisible'),
      setLocked: () => unavailableMutation('objects.setLocked'),
      bringForward: () => unavailableMutation('objects.bringForward'),
      sendBackward: () => unavailableMutation('objects.sendBackward'),
      getTransform: () => null,
      updateTransform: () => unavailableMutation('objects.updateTransform'),
      getBounds: () => null,
      measure: () => null,
      preview: () => {},
      clearPreview: () => {},
    },
    selection: { list: () => [], getAnchorBox: () => null, select: () => {} },
    areas: {
      list: () => [],
      get: () => null,
      pickForAdd: async () => null,
      create: () => unavailableMutation('areas.create'),
      update: () => unavailableMutation('areas.update'),
      remove: () => unavailableMutation('areas.remove'),
      select: () => {},
      getActive: () => null,
      focus: () => {},
      getBounds: () => null,
      contains: unavailableStatus,
      constrain: (objectId: string, printAreaId?: string | null) => unchangedMutation('areas.constrain', unavailableStatus(objectId, printAreaId)),
      moveObject: (objectId: string, _delta: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaPrintAreaConstraintOptions) => unchangedMutation('areas.moveObject', unavailableStatus(objectId, options?.printAreaId ?? options?.targetPrintAreaId)),
      transformObject: (objectId: string, _patch: Partial<AsukaObjectTransform> & AsukaObjectPatch, options?: AsukaPrintAreaConstraintOptions) => unchangedMutation('areas.transformObject', unavailableStatus(objectId, options?.printAreaId ?? options?.targetPrintAreaId)),
    },
    surface: {
      pick: () => null,
      getInteractionDebug: () => ({ engine: normalizedKind, selectedObjectId: null, activePrintAreaId: null, lastPick: null }),
    },
    viewport: { zoomIn: () => {}, zoomOut: () => {}, fit: () => {} },
    exports: {
      isFormatAvailable: () => false,
      canLoadSupport: () => false,
      ensureSupport: async () => false,
      download: async () => { fail(); },
      downloadAllViews: async () => { fail(); },
      downloadAllAreas: async () => { fail(); },
      downloadAllObjects: async () => { fail(); },
      png: async () => fail(),
    },
  };
}

export function createUnavailable3DRenderEngine(reason = 'The 3D engine addon is not loaded yet.'): AsukaRenderEngine {
  return createUnavailableRenderEngine('3d', reason);
}
