import { useAsukaStore, type AsukaState, type LayoutBlock } from '../store/asukaStore';
import { buildAsukaDocument, materializeStateDocumentFromAsukaDocument, shouldExpandScopeForAsukaDocument, type AsukaDocumentState } from './asukaDocument';
import { normalizeRenderEngineCapabilityList, type AsukaRenderEngineCapabilityKey, type AsukaRenderEngineKind } from './renderEngine';
import type { LayoutConfig, LayoutZoneConfig, LayoutZoneId } from '../types/layout';
import { exportViewSnapshots, importViewSnapshots, saveSnapshotForActiveView, switchViewWithCanvas, setStateRestoring } from './viewSnapshots';
import { normalizePriceInput } from './pricingValue';
import { isRuntimeHelperRole } from './runtime/runtimeRoles';
import { ensureProduct3DPartGroupsDeep } from './product3dParts';
import { getThemeState, setThemeState, type ThemeInit } from './theme';
import type { AsukaPhysicalSize } from './physicalMeasurement';

export type AsukaStateScope = {
  meta?: boolean;
  layout?: boolean;
  modules?: boolean;
  document?: {
    views?: boolean;
    drawingAreas?: boolean;
    snapshots?: boolean;
    objects?: boolean;
    selection?: boolean;
    viewportByView?: boolean;
    scenes3d?: boolean;
    /** Canonical engine-neutral Asuka document source. */
    asukaDocument?: boolean;
  };
  catalog?: {
    elements?: boolean;
    designs?: boolean;
    colors?: boolean;
    printingMethods?: boolean;
  };
  appearance?: {
    defaults?: boolean;
    /** Active theme preset and token overrides. */
    theme?: boolean;
  };
  pricing?: {
    basePrice?: boolean;
    rules?: boolean;
    totals?: boolean;
  };
  tools?: {
    fixedSlots?: boolean;
    magnifier?: boolean;
    ruler?: boolean;
    shapes?: boolean;
    colorScope?: boolean;
    selectionMenu?: boolean;
  };
  behavior?: {
    listeners?: boolean;
  };
  configuration?: {
    moduleConfig?: boolean;
    integrations?: boolean;
    fonts?: boolean;
  };
  permissions?: boolean;
  history?: boolean;
};

export type SerializedDrawingArea = { id: string; x: number; y: number; width: number; height: number; shape?: string; sourceSvg?: string; sourcePath?: string; physicalSize?: AsukaPhysicalSize };

export type AsukaExportedState = {
  format: 'asuka-state';
  schemaVersion: 2;
  exportedAt: string;
  scope: Required<AsukaStateScope> & {
    document: Required<NonNullable<AsukaStateScope['document']>>;
    catalog: Required<NonNullable<AsukaStateScope['catalog']>>;
    appearance: Required<NonNullable<AsukaStateScope['appearance']>>;
    pricing: Required<NonNullable<AsukaStateScope['pricing']>>;
    tools: Required<NonNullable<AsukaStateScope['tools']>>;
    behavior: Required<NonNullable<AsukaStateScope['behavior']>>;
    configuration: Required<NonNullable<AsukaStateScope['configuration']>>;
  };
  meta?: {
    app: 'AsukaDesigner';
    schemaVersion: 2;
    exportedAt: string;
  };
  layout?: {
    blocksByZone: AsukaState['blocksByZone'];
    zoneCollapsed: AsukaState['zoneCollapsed'];
    zoneCollapsible: AsukaState['zoneCollapsible'];
    zoneType: AsukaState['zoneType'];
  };
  modules?: {
    activeModuleId: AsukaState['activeModuleId'];
    moduleDisplayModeOverrides?: Record<string, any>;
    moduleMountTargetOverrides?: Record<string, string>;
    mountedModules?: AsukaState['mountedModules'];
    hiddenModules?: AsukaState['hiddenModules'];
    moduleOverlayStack?: AsukaState['moduleOverlayStack'];
    moduleOverlayType?: AsukaState['moduleOverlayType'];
    moduleOverlayAnchorBox?: AsukaState['moduleOverlayAnchorBox'];
    pendingRestoreSelectionMenu?: AsukaState['pendingRestoreSelectionMenu'];
    pendingOpenCreateViewModal?: AsukaState['pendingOpenCreateViewModal'];
  };
  document?: {
    views?: {
      views: AsukaState['views'];
      activeViewId: AsukaState['activeViewId'];
    };
    drawingAreas?: {
      /** Drawing areas for the active view. */
      zones: SerializedDrawingArea[];
      activeDrawingAreaId: string | null;
      /** Drawing areas per view. This is the authoritative map for multi-view documents. */
      byView?: Record<string, SerializedDrawingArea[]>;
      activeByView?: Record<string, string | null>;
    };
    snapshots?: {
      snapshotsByView: Record<string, any>;
    };
    objects?: {
      byView: Record<string, any[]>;
      source: 'document-store' | 'view-snapshots' | 'mixed' | 'asuka-document';
    };
    selection?: {
      activeObjectIds: string[];
      activeDrawingAreaId: string | null;
    };
    viewportByView?: Record<string, { zoom: number; panX: number; panY: number }>;
    scenes3d?: Record<string, any>;
    /** Canonical engine-neutral Asuka document source. */
    asukaDocument?: AsukaDocumentState;
    /** Element catalog required by element instances in this document. */
    elements?: AsukaState['elements'];
  };
  catalog?: {
    elements?: AsukaState['elements'];
    designs?: Array<{ url: string; name: string; price?: number | string; category?: string }>;
    colors?: AsukaState['colors'];
    printingMethods?: AsukaState['printingMethods'];
  };
  appearance?: {
    defaults?: AsukaState['defaults'];
    /** Active theme preset and effective token colors. */
    theme?: ThemeInit;
  };
  pricing?: {
    basePrice?: AsukaState['price'];
    rules?: AsukaState['pricingRules'];
    totals?: AsukaState['pricingTotals'];
  };
  tools?: {
    fixedSlots?: {
      slots: AsukaState['fixedSlots'];
      placing: AsukaState['fixedSlotsPlacing'];
      activeId: AsukaState['fixedSlotActiveId'];
      editor: AsukaState['markerEditorState'];
    };
    magnifier?: {
      enabled: AsukaState['magnifierEnabled'];
      active: AsukaState['magnifierActive'];
      x: AsukaState['magnifierX'];
      y: AsukaState['magnifierY'];
      size: AsukaState['magnifierSize'];
      zoom: AsukaState['magnifierZoom'];
    };
    ruler?: {
      enabled: AsukaState['rulerEnabled'];
      unit: AsukaState['rulerUnit'];
      dpi: AsukaState['rulerDpi'];
      product: AsukaState['rulerProduct'];
    };
    shapes?: {
      svgAssets: AsukaState['shapeSvgAssets'];
      style: AsukaState['shapeStyle'];
      editDefaults: AsukaState['shapeEditDefaults'];
      placing: AsukaState['shapePlacing'];
    };
    colorScope?: AsukaState['colorScope'];
    selectionMenu?: {
      type: AsukaState['selectionMenuType'];
      rootType: AsukaState['selectionMenuRootType'];
      items: AsukaState['selectionMenuItems'];
      open: AsukaState['selectionMenuOpen'];
      stack: AsukaState['selectionMenuStack'];
      anchorBox: AsukaState['selectionMenuAnchorBox'];
    };
  };
  behavior?: {
    listenersEnabled?: AsukaState['listenersEnabled'];
    listenerSettings?: AsukaState['listenerSettings'];
  };
  configuration?: {
    moduleConfig?: AsukaState['moduleConfig'];
    integrations?: AsukaState['integrations'];
    fontsCatalog?: AsukaState['fontsCatalog'];
  };
  permissions?: {
    role: any;
    permissions: AsukaState['permissions'];
    permissionsByView: Record<string, Partial<AsukaState['permissions']>>;
  };
  history?: {
    mode: 'document-state-history';
    info: {
      index: number;
      length: number;
      canUndo: boolean;
      canRedo: boolean;
      maxSnapshots: number;
    };
    tracks3dByView?: Record<string, any>;
    tracksFabricByView?: Record<string, any>;
  };
};

export type AsukaImportOptions = {
  scope?: AsukaStateScope;
  replaceViews?: boolean;
  replaceSnapshots?: boolean;
};

const DEFAULT_SCOPE: AsukaExportedState['scope'] = {
  meta: true,
  layout: true,
  modules: true,
  document: {
    views: true,
    drawingAreas: true,
    snapshots: true,
    objects: true,
    selection: true,
    viewportByView: true,
    scenes3d: true,
    asukaDocument: true,
  },
  catalog: {
    elements: true,
    designs: true,
    colors: true,
    printingMethods: true,
  },
  appearance: {
    defaults: true,
    theme: true,
  },
  pricing: {
    basePrice: true,
    rules: true,
    totals: true,
  },
  tools: {
    fixedSlots: true,
    magnifier: true,
    ruler: true,
    shapes: true,
    colorScope: true,
    selectionMenu: true,
  },
  behavior: {
    listeners: true,
  },
  configuration: {
    moduleConfig: true,
    integrations: true,
    fonts: true,
  },
  permissions: true,
  history: true,
};

/** Named partial-state presets exposed by `designer.util.state.exportPart/importPart`. */
export type AsukaStatePart =
  | 'template'
  | 'theme'
  | 'layout'
  | 'modules'
  | 'document'
  | 'catalog'
  | 'pricing'
  | 'tools'
  | 'behavior'
  | 'configuration'
  | 'permissions';

const DOCUMENT_PART_SCOPE: NonNullable<AsukaStateScope['document']> = {
  views: true,
  drawingAreas: true,
  snapshots: true,
  objects: true,
  selection: true,
  viewportByView: true,
  scenes3d: true,
  asukaDocument: true,
};

const STATE_PART_SCOPES: Record<AsukaStatePart, AsukaStateScope> = {
  theme: { appearance: { theme: true } },
  layout: { layout: true },
  modules: { modules: true },
  document: { document: DOCUMENT_PART_SCOPE, catalog: { elements: true } },
  catalog: { catalog: { elements: true, designs: true, colors: true, printingMethods: true } },
  pricing: { pricing: { basePrice: true, rules: true, totals: true } },
  tools: { tools: { fixedSlots: true, magnifier: true, ruler: true, shapes: true, colorScope: true, selectionMenu: true } },
  behavior: { behavior: { listeners: true } },
  configuration: { configuration: { moduleConfig: true, integrations: true, fonts: true } },
  permissions: { permissions: true },
  template: {
    layout: true,
    modules: true,
    document: DOCUMENT_PART_SCOPE,
    catalog: { elements: true, designs: true, colors: true, printingMethods: true },
    appearance: { defaults: true },
    tools: { fixedSlots: true, shapes: true },
    behavior: { listeners: true },
    configuration: { moduleConfig: true, fonts: true },
  },
};

/** Return a detached scope object for one named state part. */
export function getAsukaStatePartScope(part: AsukaStatePart): AsukaStateScope {
  const scope = STATE_PART_SCOPES[part];
  if (!scope) throw new Error(`Unknown Asuka state part: ${String(part)}`);
  return clonePlain(scope);
}


export type AsukaRuntimeLayoutState = {
  blocksByZone: AsukaState['blocksByZone'];
  zoneCollapsed: AsukaState['zoneCollapsed'];
  zoneCollapsible: AsukaState['zoneCollapsible'];
  zoneType: AsukaState['zoneType'];
};

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

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

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

function normalizeBlocksForZone(blocks: any[], zone: LayoutZoneId): LayoutBlock[] {
  return (Array.isArray(blocks) ? blocks : [])
    .filter((block: any) =>
      block
      && typeof block === 'object'
      && typeof block.kind === 'string',
    )
    .map((block: any, index: number) => ({
      id: typeof block.id === 'string' && block.id.trim() ? block.id.trim() : `block-${zone}-${String(block.kind)}-${index}`,
      kind: block.kind as LayoutBlock['kind'],
      label: typeof block.label === 'string' && block.label.trim() ? block.label.trim() : String(block.kind),
      icon: typeof block.icon === 'string' && block.icon.trim() ? block.icon.trim() : 'views',
      order: typeof block.order === 'number' && Number.isFinite(block.order) ? block.order : index,
      type: typeof block.type === 'string' && block.type.trim() ? block.type.trim() : undefined,
      supportedEngines: normalizeLayoutItemSupportedEngines((block as any).supportedEngines),
      requiredCapabilities: normalizeLayoutItemRequiredCapabilities((block as any).requiredCapabilities),
      mountTarget: typeof block.mountTarget === 'string' && block.mountTarget.trim() ? block.mountTarget.trim() : undefined,
      collapsed: typeof block.collapsed === 'boolean' ? block.collapsed : undefined,
      renderMode: block.renderMode === 'inline' || block.renderMode === 'portal' ? block.renderMode : undefined,
    }))
    .sort((a, b) => {
      const orderDelta = Number(a.order ?? 0) - Number(b.order ?? 0);
      return orderDelta !== 0 ? orderDelta : String(a.id ?? '').localeCompare(String(b.id ?? ''));
    })
    .map((block, order) => ({ ...block, order }));
}

function normalizeBlocksByZoneState(blocksByZone?: Partial<Record<LayoutZoneId, any[]>> | null): AsukaRuntimeLayoutState['blocksByZone'] {
  return {
    top: normalizeBlocksForZone(Array.isArray(blocksByZone?.top) ? blocksByZone!.top as any[] : [], 'top'),
    bottom: normalizeBlocksForZone(Array.isArray(blocksByZone?.bottom) ? blocksByZone!.bottom as any[] : [], 'bottom'),
    left: normalizeBlocksForZone(Array.isArray(blocksByZone?.left) ? blocksByZone!.left as any[] : [], 'left'),
    right: normalizeBlocksForZone(Array.isArray(blocksByZone?.right) ? blocksByZone!.right as any[] : [], 'right'),
  };
}

/** Convert init-time layout config into the runtime layout state shape used by state exports. */
export function normalizeLayoutConfigToRuntimeState(config?: LayoutConfig | null, fallbackState?: Partial<AsukaRuntimeLayoutState> | null): AsukaRuntimeLayoutState {
  const fallback = fallbackState ?? {};
  const blocksByZone = normalizeBlocksByZoneState(fallback.blocksByZone);
  const zoneCollapsed = { top: false, bottom: false, left: false, right: false, ...(fallback.zoneCollapsed ?? {}) } as AsukaRuntimeLayoutState['zoneCollapsed'];
  const zoneCollapsible = { top: false, bottom: false, left: true, right: true, ...(fallback.zoneCollapsible ?? {}) } as AsukaRuntimeLayoutState['zoneCollapsible'];
  const zoneType = { top: 'default', bottom: 'default', left: 'default', right: 'panels', ...(fallback.zoneType ?? {}) } as AsukaRuntimeLayoutState['zoneType'];

  if (!config || typeof config !== 'object') {
    return { blocksByZone, zoneCollapsed, zoneCollapsible, zoneType };
  }

  for (const zone of LAYOUT_ZONES) {
    const zoneConfig = (config as any)[zone] as LayoutZoneConfig | undefined;
    if (!zoneConfig || typeof zoneConfig !== 'object') continue;
    if (typeof zoneConfig.collapsed === 'boolean') zoneCollapsed[zone] = zoneConfig.collapsed;
    if (typeof zoneConfig.collapsible === 'boolean') zoneCollapsible[zone] = zoneConfig.collapsible;
    const rawType = typeof zoneConfig.type === 'string' ? zoneConfig.type.trim().toLowerCase() : '';
    if (rawType === 'inline') zoneType[zone] = 'inline';
    else if (rawType === 'panels') zoneType[zone] = 'panels';
    else if (rawType) zoneType[zone] = 'default';

    if (Array.isArray(zoneConfig.items)) {
      blocksByZone[zone] = normalizeBlocksForZone(zoneConfig.items.map((raw: any, index: number) => ({
        id: typeof raw?.id === 'string' && raw.id.trim() ? raw.id.trim() : `block-${zone}-${String(raw?.kind ?? 'item')}-${index}`,
        kind: raw?.kind,
        label: typeof raw?.label === 'string' && raw.label.trim() ? raw.label.trim() : String(raw?.kind ?? 'item'),
        icon: typeof raw?.icon === 'string' && raw.icon.trim() ? raw.icon : 'views',
        order: typeof raw?.order === 'number' && Number.isFinite(raw.order) ? raw.order : index,
        type: typeof raw?.type === 'string' && raw.type.trim() ? raw.type.trim() : undefined,
        supportedEngines: normalizeLayoutItemSupportedEngines((raw as any)?.supportedEngines),
        requiredCapabilities: normalizeLayoutItemRequiredCapabilities((raw as any)?.requiredCapabilities),
        mountTarget: typeof raw?.mountTarget === 'string' && raw.mountTarget.trim() ? raw.mountTarget.trim() : undefined,
        collapsed: typeof raw?.collapsed === 'boolean' ? raw.collapsed : undefined,
        renderMode: raw?.renderMode === 'inline' || raw?.renderMode === 'portal' ? raw.renderMode : undefined,
        collapsible: typeof raw?.collapsible === 'boolean' ? raw.collapsible : undefined,
      } as LayoutBlock)), zone);
    }
  }

  return { blocksByZone, zoneCollapsed, zoneCollapsible, zoneType };
}

/** Convert runtime layout state back into the init-time layout config shape. */
export function extractRuntimeLayoutAsConfig(layout?: Partial<AsukaRuntimeLayoutState> | null): LayoutConfig {
  const source = layout ?? {};
  const out: LayoutConfig = {};
  for (const zone of LAYOUT_ZONES) {
    const items = Array.isArray(source.blocksByZone?.[zone]) ? source.blocksByZone?.[zone] : [];
    out[zone] = {
      type: (source.zoneType?.[zone] as any) ?? undefined,
      collapsed: typeof source.zoneCollapsed?.[zone] === 'boolean' ? source.zoneCollapsed?.[zone] : undefined,
      collapsible: typeof source.zoneCollapsible?.[zone] === 'boolean' ? source.zoneCollapsible?.[zone] : undefined,
      items: (items ?? []).map((block: any) => ({
        id: block?.id,
        kind: block?.kind,
        label: block?.label,
        icon: block?.icon,
        order: block?.order,
        type: block?.type,
        supportedEngines: normalizeLayoutItemSupportedEngines(block?.supportedEngines),
        requiredCapabilities: normalizeLayoutItemRequiredCapabilities(block?.requiredCapabilities),
        mountTarget: block?.mountTarget,
        collapsed: block?.collapsed,
        renderMode: block?.renderMode,
      })),
    };
  }
  return out;
}

function resolveSection<T extends Record<string, boolean>>(section: Partial<T> | undefined, defaults: T, useDefaults: boolean): T {
  if (typeof section === 'undefined') {
    if (useDefaults) return { ...defaults };
    return Object.fromEntries(Object.keys(defaults).map((key) => [key, false])) as T;
  }
  const out: Record<string, boolean> = {};
  for (const key of Object.keys(defaults)) {
    out[key] = Boolean((section as any)?.[key] ?? false);
  }
  return out as T;
}

/**
 * Resolve a state scope.
 *
 * No argument means the complete state. An explicit scope is opt-in: every
 * omitted branch is false. This is important when importing only one part
 * from a full-state JSON, because `{ appearance: { theme: true } }` must not
 * silently restore layout, document, permissions, or any other branch.
 */
function resolveScope(scope?: AsukaStateScope): AsukaExportedState['scope'] {
  const useDefaults = typeof scope === 'undefined';
  return {
    meta: useDefaults ? DEFAULT_SCOPE.meta : Boolean(scope?.meta),
    layout: useDefaults ? DEFAULT_SCOPE.layout : Boolean(scope?.layout),
    modules: useDefaults ? DEFAULT_SCOPE.modules : Boolean(scope?.modules),
    document: resolveSection(scope?.document as any, DEFAULT_SCOPE.document, useDefaults),
    catalog: resolveSection(scope?.catalog as any, DEFAULT_SCOPE.catalog, useDefaults),
    appearance: resolveSection(scope?.appearance as any, DEFAULT_SCOPE.appearance, useDefaults),
    pricing: resolveSection(scope?.pricing as any, DEFAULT_SCOPE.pricing, useDefaults),
    tools: resolveSection(scope?.tools as any, DEFAULT_SCOPE.tools, useDefaults),
    behavior: resolveSection(scope?.behavior as any, DEFAULT_SCOPE.behavior, useDefaults),
    configuration: resolveSection(scope?.configuration as any, DEFAULT_SCOPE.configuration, useDefaults),
    permissions: useDefaults ? DEFAULT_SCOPE.permissions : Boolean(scope?.permissions),
    history: useDefaults ? DEFAULT_SCOPE.history : Boolean(scope?.history),
  };
}

function getDesignerRuntime() {
  try {
    return (useAsukaStore.getState() as any).designerInstance ?? null;
  } catch {
    return null;
  }
}

function clonePlain<T>(value: T): T {
  if (typeof value === 'undefined') return value;
  try {
    return JSON.parse(JSON.stringify(value)) as T;
  } catch {
    if (Array.isArray(value)) return value.map((item: any) => clonePlain(item)) as any;
    if (value && typeof value === 'object') {
      const out: Record<string, any> = {};
      for (const [key, child] of Object.entries(value as Record<string, any>)) {
        if (typeof child === 'function' || typeof child === 'undefined') continue;
        out[key] = clonePlain(child);
      }
      return out as T;
    }
    return value;
  }
}

/** Validate and clone the only state envelope accepted by the V1 runtime. */
export function normalizeAsukaStatePayload(payload: any): AsukaExportedState {
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
    throw new TypeError('Invalid Asuka state payload.');
  }

  if (payload.format !== 'asuka-state' || payload.schemaVersion !== 2) {
    throw new TypeError('Invalid Asuka state payload. Expected asuka-state schema v2.');
  }

  return clonePlain(payload) as AsukaExportedState;
}

function asRecord(value: any): Record<string, any> {
  return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
}


function normalizeDrawingAreaList(zones: any[] | null | undefined): SerializedDrawingArea[] {
  if (!Array.isArray(zones)) return [];
  const byKey = new Map<string, SerializedDrawingArea>();
  const order: string[] = [];

  for (const raw of zones) {
    if (!raw || typeof raw !== 'object') continue;
    const id = String(raw.id ?? '').trim();
    if (!id) continue;
    const next: SerializedDrawingArea = {
      id,
      x: Number(raw.x ?? raw.left ?? 0) || 0,
      y: Number(raw.y ?? raw.top ?? 0) || 0,
      width: Math.max(1, Number(raw.width ?? 0) || 1),
      height: Math.max(1, Number(raw.height ?? 0) || 1),
      shape: typeof raw.shape === 'undefined' ? undefined : raw.shape,
      sourceSvg: typeof raw.sourceSvg === 'string' ? raw.sourceSvg : undefined,
      sourcePath: typeof raw.sourcePath === 'string' ? raw.sourcePath : undefined,
      physicalSize: raw.physicalSize && typeof raw.physicalSize === 'object' ? clonePlain(raw.physicalSize) : undefined,
    };
    if (!byKey.has(id)) order.push(id);
    // Keep the last occurrence for a stable replace/patch behavior, but never
    // let repeated import sources produce multiple zones with the same id.
    byKey.set(id, next);
  }

  return order.map((id) => byKey.get(id)!).filter(Boolean);
}

function isSerializableDocumentObject(obj: any): boolean {
  if (!obj || typeof obj !== 'object') return false;
  const role = String(obj?._asukaRole ?? '').trim();
  if (isRuntimeHelperRole(role)) return false;
  if (obj?._asukaFixedSlotIsIndicator) return false;
  return true;
}

function sanitizeDocumentObjectList(objects: any[] | null | undefined): any[] {
  if (!Array.isArray(objects)) return [];
  return objects
    .filter(isSerializableDocumentObject)
    .map((obj: any) => ({ ...(obj ?? {}) }));
}

function sanitizeDocumentObjectsByView(input: Record<string, any[]> | null | undefined): Record<string, any[]> {
  const out: Record<string, any[]> = {};
  if (!input || typeof input !== 'object') return out;
  for (const [viewId, objects] of Object.entries(input)) {
    const key = String(viewId ?? '').trim();
    if (!key) continue;
    out[key] = sanitizeDocumentObjectList(objects as any[]);
  }
  return out;
}



function mergeReliableDocumentObjectsByView(
  storeObjectsByView: Record<string, any[]> | null | undefined,
  snapshotsByView: Record<string, any> | null | undefined,
  views: any[] | null | undefined,
): { byView: Record<string, any[]>; source: 'document-store' | 'view-snapshots' | 'mixed' } {
  const out: Record<string, any[]> = {};
  const keys = new Set<string>();
  for (const key of Object.keys(storeObjectsByView ?? {})) {
    const normalized = String(key ?? '').trim();
    if (normalized) keys.add(normalized);
  }
  for (const key of Object.keys(snapshotsByView ?? {})) {
    const normalized = String(key ?? '').trim();
    if (normalized) keys.add(normalized);
  }
  for (const view of Array.isArray(views) ? views : []) {
    const normalized = String(view?.id ?? '').trim();
    if (normalized) keys.add(normalized);
  }

  let usedStore = false;
  let usedSnapshots = false;

  for (const viewId of Array.from(keys)) {
    const storeObjects = sanitizeDocumentObjectList((storeObjectsByView as any)?.[viewId]);
    const snapshotObjects = sanitizeDocumentObjectList((snapshotsByView as any)?.[viewId]?.objects);

    // Empty arrays in documentObjectsByView are not authoritative on their own.
    // They can be transient stale buckets while the visible Fabric canvas/snapshot
    // still contains the real design. Prefer non-empty store data; otherwise fall
    // back to snapshots produced from the live canvas.
    if (storeObjects.length > 0) {
      out[viewId] = storeObjects;
      usedStore = true;
    } else if (snapshotObjects.length > 0) {
      out[viewId] = snapshotObjects;
      usedSnapshots = true;
    } else {
      out[viewId] = [];
    }
  }

  return {
    byView: out,
    source: usedStore && usedSnapshots ? 'mixed' : usedStore ? 'document-store' : 'view-snapshots',
  };
}


function resolveImportElementCatalog(payload: AsukaExportedState): any[] | null {
  const catalogElements = Array.isArray(payload.catalog?.elements) ? payload.catalog!.elements : null;
  const documentElements = Array.isArray((payload.document as any)?.elements) ? (payload.document as any).elements : null;
  if (!catalogElements && !documentElements) return null;

  // A document part intentionally carries the Product/Elements catalog next to
  // its renderable document. Never let a stale empty top-level catalog mask the
  // non-empty document catalog. When both branches are present, keep every
  // explicit element id and let the canonical top-level catalog win on an exact
  // id collision without collapsing unrelated elements that share a name.
  const merged = new Map<string, any>();
  const append = (items: any[] | null, source: 'document' | 'catalog') => {
    for (const [index, element] of (items ?? []).entries()) {
      const explicitId = String(element?.id ?? element?.catalogId ?? element?.elementId ?? '').trim();
      const key = explicitId ? `id:${explicitId}` : `${source}:${index}`;
      merged.set(key, element);
    }
  };
  append(documentElements, 'document');
  append(catalogElements, 'catalog');
  return clonePlain(Array.from(merged.values()));
}

function readRuntimeDrawingAreas(designer: any): SerializedDrawingArea[] {
  try {
    return normalizeDrawingAreaList(designer?.getDrawingZones?.() ?? []);
  } catch {
    return [];
  }
}

function readActiveDrawingAreaId(designer: any, fallback?: string | null): string | null {
  try {
    const runtimeId = String(designer?.getActiveDrawingZone?.() ?? '').trim();
    if (runtimeId) return runtimeId;
  } catch {}
  const fb = String(fallback ?? '').trim();
  return fb || null;
}

function cloneDrawingAreasByView(input: Record<string, any[] | null | undefined> | null | undefined): Record<string, SerializedDrawingArea[]> {
  const out: Record<string, SerializedDrawingArea[]> = {};
  if (!input || typeof input !== 'object') return out;
  for (const [viewId, zones] of Object.entries(input)) {
    const key = String(viewId ?? '').trim();
    if (!key) continue;
    out[key] = normalizeDrawingAreaList(zones as any[]);
  }
  return out;
}

function extractDrawingAreasByViewFromSnapshots(snapshotsByView: Record<string, any> | null | undefined): Record<string, SerializedDrawingArea[]> {
  const out: Record<string, SerializedDrawingArea[]> = {};
  if (!snapshotsByView || typeof snapshotsByView !== 'object') return out;
  for (const [viewId, snapshot] of Object.entries(snapshotsByView)) {
    const key = String(viewId ?? '').trim();
    if (!key) continue;
    out[key] = normalizeDrawingAreaList((snapshot as any)?.drawingAreas);
  }
  return out;
}

function extractActiveDrawingAreaByViewFromSnapshots(snapshotsByView: Record<string, any> | null | undefined): Record<string, string | null> {
  const out: Record<string, string | null> = {};
  if (!snapshotsByView || typeof snapshotsByView !== 'object') return out;
  for (const [viewId, snapshot] of Object.entries(snapshotsByView)) {
    const key = String(viewId ?? '').trim();
    if (!key) continue;
    const active = String((snapshot as any)?.activeDrawingAreaId ?? '').trim();
    out[key] = active || null;
  }
  return out;
}

function collectExportDrawingAreasByView(params: {
  snapshotsByView: Record<string, any>;
  activeViewId: string | null;
  activeZones: SerializedDrawingArea[];
  activeDrawingAreaId: string | null;
}): { byView: Record<string, SerializedDrawingArea[]>; activeByView: Record<string, string | null> } {
  const byView = extractDrawingAreasByViewFromSnapshots(params.snapshotsByView);
  const activeByView = extractActiveDrawingAreaByViewFromSnapshots(params.snapshotsByView);
  const activeViewId = String(params.activeViewId ?? '').trim();
  if (activeViewId) {
    byView[activeViewId] = normalizeDrawingAreaList(params.activeZones);
    activeByView[activeViewId] = params.activeDrawingAreaId && byView[activeViewId]?.some((z) => String(z.id) === String(params.activeDrawingAreaId))
      ? params.activeDrawingAreaId
      : null;
  }
  return { byView, activeByView };
}

function resolvePayloadDrawingAreasByView(
  payload: AsukaExportedState,
  snapshotsByView: Record<string, any>,
  _activeViewId: string | null,
): { byView: Record<string, SerializedDrawingArea[]>; activeByView: Record<string, string | null> } {
  const byView = extractDrawingAreasByViewFromSnapshots(snapshotsByView);
  const activeByView = extractActiveDrawingAreaByViewFromSnapshots(snapshotsByView);
  const drawingAreas = payload.document?.drawingAreas as any;

  if (drawingAreas?.byView && typeof drawingAreas.byView === 'object') {
    Object.assign(byView, cloneDrawingAreasByView(drawingAreas.byView));
  }

  if (drawingAreas?.activeByView && typeof drawingAreas.activeByView === 'object') {
    for (const [viewId, active] of Object.entries(drawingAreas.activeByView)) {
      const key = String(viewId ?? '').trim();
      if (!key) continue;
      const activeId = String(active ?? '').trim();
      activeByView[key] = activeId || null;
    }
  }


  return { byView, activeByView };
}

function pickDrawingAreasForView(byView: Record<string, SerializedDrawingArea[]>, viewId: string | null | undefined): SerializedDrawingArea[] {
  const key = String(viewId ?? '').trim();
  if (!key) return [];
  return normalizeDrawingAreaList(byView[key]);
}

function pruneActiveDrawingAreaId(activeId: string | null | undefined, zones: SerializedDrawingArea[]): string | null {
  const wanted = String(activeId ?? '').trim();
  if (!wanted) return null;
  return zones.some((z) => String(z.id) === wanted) ? wanted : null;
}

function refreshActiveViewSnapshotBeforeExport(designer: any): void {
  try { saveSnapshotForActiveView(designer ?? getDesignerRuntime()); } catch {}
}


function readViewportByView(activeViewId: string | null): Record<string, { zoom: number; panX: number; panY: number }> {
  const out: Record<string, { zoom: number; panX: number; panY: number }> = {};
  const designer = getDesignerRuntime();
  if (!designer || !activeViewId) return out;
  try {
    const canvas = designer.getCanvas?.();
    const vt = (canvas?.viewportTransform ?? [1, 0, 0, 1, 0, 0]) as number[];
    const zoom = Number(canvas?.getZoom?.() ?? vt?.[0] ?? 1) || 1;
    out[String(activeViewId)] = {
      zoom,
      panX: Number(vt?.[4] ?? 0) || 0,
      panY: Number(vt?.[5] ?? 0) || 0,
    };
  } catch {}
  return out;
}

function readSelectionState(): { activeObjectIds: string[]; activeDrawingAreaId: string | null } {
  const designer = getDesignerRuntime();
  const activeDrawingAreaId = (() => {
    try { return designer?.getActiveDrawingZone?.() ?? null; } catch { return null; }
  })();
  try {
    const canvas = designer?.getCanvas?.();
    const active = canvas?.getActiveObjects?.() ?? [];
    const ids = (Array.isArray(active) ? active : []).map((obj: any) => String(obj?._asukaObjectId ?? obj?.id ?? '')).filter(Boolean);
    return { activeObjectIds: ids, activeDrawingAreaId };
  } catch {
    return { activeObjectIds: [], activeDrawingAreaId };
  }
}

function applyViewportForActiveView(payload: Record<string, { zoom: number; panX: number; panY: number }> | undefined, activeViewId: string | null) {
  if (!payload || !activeViewId) return;
  const entry = payload[String(activeViewId)];
  if (!entry) return;
  const designer = getDesignerRuntime();
  const canvas = designer?.getCanvas?.();
  if (!canvas) return;
  try {
    designer?.setZoom?.(Number(entry.zoom ?? 1) || 1, { clamp: false });
    const vt = Array.isArray(canvas.viewportTransform) ? canvas.viewportTransform.slice() : [1, 0, 0, 1, 0, 0];
    vt[4] = Number(entry.panX ?? 0) || 0;
    vt[5] = Number(entry.panY ?? 0) || 0;
    canvas.setViewportTransform?.(vt as any);
    canvas.requestRenderAll?.();
  } catch {}
}

function buildSnapshotsFromDocumentObjects(
  byView: Record<string, any[]>,
  baseSnapshots?: Record<string, any>,
  drawingAreasByView?: Record<string, SerializedDrawingArea[]>,
  activeDrawingAreaByView?: Record<string, string | null>,
): Record<string, any> {
  const next: Record<string, any> = { ...(baseSnapshots ?? {}) };
  const viewIds = new Set<string>([
    ...Object.keys(baseSnapshots ?? {}),
    ...Object.keys(byView ?? {}),
    ...Object.keys(drawingAreasByView ?? {}),
  ]);

  for (const viewId of Array.from(viewIds)) {
    const existing = (baseSnapshots ?? {})[viewId] ?? {};
    const hasObjects = Object.prototype.hasOwnProperty.call(byView ?? {}, viewId);
    const objects = hasObjects && Array.isArray((byView as any)[viewId]) ? sanitizeDocumentObjectList((byView as any)[viewId]) : sanitizeDocumentObjectList((existing as any)?.objects ?? []);
    const hasIncomingAreas = Object.prototype.hasOwnProperty.call(drawingAreasByView ?? {}, viewId);
    const drawingAreas = hasIncomingAreas
      ? normalizeDrawingAreaList((drawingAreasByView as any)?.[viewId])
      : normalizeDrawingAreaList((existing as any)?.drawingAreas);
    const activeDrawingAreaId = hasIncomingAreas
      ? pruneActiveDrawingAreaId((activeDrawingAreaByView ?? {})[viewId], drawingAreas)
      : (((existing as any)?.activeDrawingAreaId ?? null) as string | null);

    next[viewId] = {
      ...(existing && typeof existing === 'object' ? existing : {}),
      objects: sanitizeDocumentObjectList(objects),
      drawingAreas,
      activeDrawingAreaId,
    };
  }
  return next;
}

function ensureEmptySnapshotsForViews(snapshots: Record<string, any>, views: any[] | null | undefined): Record<string, any> {
  const next: Record<string, any> = { ...(snapshots ?? {}) };
  for (const view of Array.isArray(views) ? views : []) {
    const viewId = String(view?.id ?? '').trim();
    if (!viewId) continue;
    if (!next[viewId]) next[viewId] = { objects: [] };
  }
  return next;
}

function clearDesignerCanvasForEmptyDocument(designer: any): void {
  try {
    const canvas = designer?.getCanvas?.();
    if (!canvas) return;
    try { (canvas as any).__asukaSnapshotLoading = true; } catch {}
    try { canvas.discardActiveObject?.(); } catch {}
    try { canvas.clear?.(); } catch {}
    try { canvas.backgroundColor = '#ffffff'; } catch {}
    try { canvas.requestRenderAll?.(); } catch {}
    try { canvas.renderAll?.(); } catch {}
    try { delete (canvas as any).__asukaSnapshotLoading; } catch {}
  } catch {}
}

export function rebuildActiveViewFromDocument(targetViewId?: string | null): void {
  const store = useAsukaStore.getState() as any;
  const designer = getDesignerRuntime();
  const viewId = String(targetViewId ?? store.activeViewId ?? '').trim();
  if (!viewId) return;
  const byView = store.documentObjectsByView ?? {};
  const snapshots = exportViewSnapshots();
  const runtimeZones = readRuntimeDrawingAreas(designer);
  const activeZones = normalizeDrawingAreaList(runtimeZones.length ? runtimeZones : store.documentDrawingAreas);
  const activeAreaId = readActiveDrawingAreaId(designer, store?.documentSelection?.activeDrawingAreaId ?? null);
  const rebuilt = buildSnapshotsFromDocumentObjects(
    byView,
    snapshots,
    activeZones.length ? { [viewId]: activeZones } : undefined,
    activeZones.length ? { [viewId]: activeAreaId } : undefined,
  );
  importViewSnapshots(rebuilt, { replace: true });
  if (designer) {
    try { switchViewWithCanvas(designer, viewId); } catch {}
    try { applyViewportForActiveView(store.documentViewportByView, viewId); } catch {}
  }
}

export function captureAsukaState(scope?: AsukaStateScope): AsukaExportedState {
  const resolved = resolveScope(scope);
  const store = useAsukaStore.getState() as any;
  const exportedAt = new Date().toISOString();
  const activeViewId = store.activeViewId ?? null;
  const designer = getDesignerRuntime();
  refreshActiveViewSnapshotBeforeExport(designer);
  const snapshotsByView = exportViewSnapshots();
  const documentViews = Array.isArray(store.documentViews) && store.documentViews.length ? store.documentViews : store.views;
  const runtimeDrawingAreas = readRuntimeDrawingAreas(designer);
  const documentDrawingAreas = normalizeDrawingAreaList(
    Array.isArray(store.documentDrawingAreas) && store.documentDrawingAreas.length ? store.documentDrawingAreas : runtimeDrawingAreas,
  );
  const documentSelection = store.documentSelection ?? readSelectionState();
  const activeDrawingAreaId = readActiveDrawingAreaId(designer, documentSelection?.activeDrawingAreaId ?? null);
  const exportDrawingAreas = collectExportDrawingAreasByView({
    snapshotsByView,
    activeViewId,
    activeZones: documentDrawingAreas,
    activeDrawingAreaId,
  });
  const documentViewportByView = (store.documentViewportByView && typeof store.documentViewportByView === 'object' && Object.keys(store.documentViewportByView).length)
    ? store.documentViewportByView
    : readViewportByView(activeViewId);
  const reliableDocumentObjects = mergeReliableDocumentObjectsByView(store.documentObjectsByView, snapshotsByView, documentViews);
  const documentObjectsByView = reliableDocumentObjects.byView;
  const scenes3dByView = clonePlain(store.document3dScenesByView ?? {});
  const asukaDocument = buildAsukaDocument({
    createdAt: exportedAt,
    views: { views: documentViews, activeViewId },
    objectsByView: documentObjectsByView,
    scenes3d: scenes3dByView,
    drawingAreas: {
      zones: documentDrawingAreas,
      activeDrawingAreaId,
      byView: exportDrawingAreas.byView,
      activeByView: exportDrawingAreas.activeByView,
    },
    viewportByView: documentViewportByView,
    selection: documentSelection,
    nativeSnapshots: {
      fabric: snapshotsByView,
      babylon: scenes3dByView,
    },
    elements: clonePlain(store.elements ?? []),
  });

  return {
    format: 'asuka-state',
    schemaVersion: 2,
    exportedAt,
    scope: resolved,
    meta: resolved.meta ? { app: 'AsukaDesigner', schemaVersion: 2, exportedAt } : undefined,
    layout: resolved.layout ? { blocksByZone: normalizeBlocksByZoneState(store.blocksByZone), zoneCollapsed: store.zoneCollapsed, zoneCollapsible: store.zoneCollapsible, zoneType: store.zoneType } : undefined,
    modules: resolved.modules ? {
      activeModuleId: store.activeModuleId,
      moduleDisplayModeOverrides: clonePlain(store.moduleDisplayModeOverrides ?? {}),
      moduleMountTargetOverrides: clonePlain(store.moduleMountTargetOverrides ?? {}),
      mountedModules: clonePlain(store.mountedModules ?? {}),
      hiddenModules: clonePlain(store.hiddenModules ?? {}),
      moduleOverlayStack: clonePlain(store.moduleOverlayStack ?? []),
      moduleOverlayType: store.moduleOverlayType,
      moduleOverlayAnchorBox: clonePlain(store.moduleOverlayAnchorBox ?? null),
      pendingRestoreSelectionMenu: !!store.pendingRestoreSelectionMenu,
      pendingOpenCreateViewModal: !!store.pendingOpenCreateViewModal,
    } : undefined,
    document: {
      views: resolved.document.views ? { views: documentViews, activeViewId } : undefined,
      drawingAreas: resolved.document.drawingAreas ? {
        zones: documentDrawingAreas,
        activeDrawingAreaId,
        byView: exportDrawingAreas.byView,
        activeByView: exportDrawingAreas.activeByView,
      } : undefined,
      snapshots: resolved.document.snapshots ? { snapshotsByView } : undefined,
      objects: resolved.document.objects ? { byView: documentObjectsByView, source: reliableDocumentObjects.source } : undefined,
      selection: resolved.document.selection ? documentSelection : undefined,
      viewportByView: resolved.document.viewportByView ? documentViewportByView : undefined,
      scenes3d: resolved.document.scenes3d ? scenes3dByView : undefined,
      asukaDocument: resolved.document.asukaDocument ? asukaDocument : undefined,
      elements: (resolved.document.objects || resolved.document.snapshots) ? clonePlain(store.elements ?? []) : undefined,
    },
    catalog: {
      elements: resolved.catalog.elements ? (store.elements ?? []) : undefined,
      designs: resolved.catalog.designs ? ((store.moduleConfig?.designs?.items ?? []) as any[]) : undefined,
      colors: resolved.catalog.colors ? (store.colors ?? []) : undefined,
      printingMethods: resolved.catalog.printingMethods ? (store.printingMethods ?? []) : undefined,
    },
    appearance: (resolved.appearance.defaults || resolved.appearance.theme) ? {
      defaults: resolved.appearance.defaults ? clonePlain(store.defaults ?? undefined) : undefined,
      theme: resolved.appearance.theme ? clonePlain(getThemeState()) : undefined,
    } : undefined,
    pricing: (resolved.pricing.basePrice || resolved.pricing.rules || resolved.pricing.totals) ? {
      basePrice: resolved.pricing.basePrice ? (Number(store.price ?? 0) || 0) : undefined,
      rules: resolved.pricing.rules ? clonePlain(store.pricingRules ?? []) : undefined,
      totals: resolved.pricing.totals ? clonePlain(store.pricingTotals ?? undefined) : undefined,
    } : undefined,
    tools: (resolved.tools.fixedSlots || resolved.tools.magnifier || resolved.tools.ruler || resolved.tools.shapes || resolved.tools.colorScope || resolved.tools.selectionMenu) ? {
      fixedSlots: resolved.tools.fixedSlots ? {
        slots: clonePlain(store.fixedSlots ?? []),
        placing: clonePlain(store.fixedSlotsPlacing ?? null),
        activeId: store.fixedSlotActiveId ?? null,
        editor: clonePlain(store.markerEditorState ?? null),
      } : undefined,
      magnifier: resolved.tools.magnifier ? {
        enabled: !!store.magnifierEnabled,
        active: !!store.magnifierActive,
        x: Number(store.magnifierX ?? 0) || 0,
        y: Number(store.magnifierY ?? 0) || 0,
        size: Number(store.magnifierSize ?? 140) || 140,
        zoom: Number(store.magnifierZoom ?? 2) || 2,
      } : undefined,
      ruler: resolved.tools.ruler ? {
        enabled: !!store.rulerEnabled,
        unit: store.rulerUnit ?? 'px',
        dpi: Number(store.rulerDpi ?? 96) || 96,
        product: clonePlain(store.rulerProduct ?? { unitLabel: 'cm', worldUnitsPerUnit: 10 }),
      } : undefined,
      shapes: resolved.tools.shapes ? {
        svgAssets: clonePlain(store.shapeSvgAssets ?? []),
        style: clonePlain(store.shapeStyle ?? {}),
        editDefaults: clonePlain(store.shapeEditDefaults ?? {}),
        placing: clonePlain(store.shapePlacing ?? null),
      } : undefined,
      colorScope: resolved.tools.colorScope ? (store.colorScope ?? 'object') : undefined,
      selectionMenu: resolved.tools.selectionMenu ? {
        type: store.selectionMenuType,
        rootType: store.selectionMenuRootType,
        items: clonePlain(store.selectionMenuItems ?? []),
        open: !!store.selectionMenuOpen,
        stack: clonePlain(store.selectionMenuStack ?? []),
        anchorBox: clonePlain(store.selectionMenuAnchorBox ?? null),
      } : undefined,
    } : undefined,
    behavior: resolved.behavior.listeners ? {
      listenersEnabled: clonePlain(store.listenersEnabled ?? {}),
      listenerSettings: clonePlain(store.listenerSettings ?? {}),
    } : undefined,
    configuration: (resolved.configuration.moduleConfig || resolved.configuration.integrations || resolved.configuration.fonts) ? {
      moduleConfig: resolved.configuration.moduleConfig ? clonePlain(store.moduleConfig ?? {}) : undefined,
      integrations: resolved.configuration.integrations ? clonePlain(store.integrations ?? {}) : undefined,
      fontsCatalog: resolved.configuration.fonts ? clonePlain(store.fontsCatalog ?? []) : undefined,
    } : undefined,
    permissions: resolved.permissions ? { role: store.role, permissions: clonePlain(store.permissions), permissionsByView: clonePlain(store.permissionsByView ?? {}) } : undefined,
    history: resolved.history ? {
      mode: 'document-state-history',
      tracks3dByView: clonePlain(store.document3dHistoryByView ?? {}),
      tracksFabricByView: clonePlain(store.documentFabricHistoryByView ?? {}),
      info: (() => {
        const storeInfo = store.documentHistoryInfo;
        if (storeInfo && typeof storeInfo === 'object' && Number.isFinite(storeInfo.length)) {
          return {
            index: Number(storeInfo.index ?? -1),
            length: Number(storeInfo.length ?? 0),
            canUndo: !!storeInfo.canUndo,
            canRedo: !!storeInfo.canRedo,
            maxSnapshots: Number(storeInfo.maxSnapshots ?? 8) || 8,
          };
        }
        try {
          const info = designer?.getHistoryInfo?.() ?? { index: -1, length: 0 };
          return { index: Number(info.index ?? -1), length: Number(info.length ?? 0), canUndo: !!designer?.canUndo?.(), canRedo: !!designer?.canRedo?.(), maxSnapshots: 8 };
        } catch {
          return { index: -1, length: 0, canUndo: false, canRedo: false, maxSnapshots: 8 };
        }
      })(),
    } : undefined,
  };
}

function markDesignerCanvasStateImporting(designer: any, value: boolean): void {
  try {
    const canvas = designer?.getCanvas?.();
    if (!canvas) return;
    if (value) (canvas as any).__asukaStateImporting = true;
    else delete (canvas as any).__asukaStateImporting;
  } catch {
    try {
      const canvas = designer?.getCanvas?.();
      if (canvas) (canvas as any).__asukaStateImporting = value;
    } catch {}
  }
}

function releaseStateImportGuard(designer: any): void {
  const release = () => {
    try { setStateRestoring(false); } catch {}
    try { markDesignerCanvasStateImporting(designer, false); } catch {}
  };
  try {
    const raf = typeof requestAnimationFrame === 'function'
      ? requestAnimationFrame
      : ((cb: FrameRequestCallback) => setTimeout(cb, 16) as any);
    raf(() => raf(() => release()));
  } catch {
    setTimeout(release, 50);
  }
  try { setTimeout(release, 200); } catch {}
}

export function restoreAsukaState(input: AsukaExportedState | unknown, options: AsukaImportOptions = {}): void {
  let payload = normalizeAsukaStatePayload(input);

  const scope = resolveScope(options.scope ?? payload.scope);
  if (scope.document.asukaDocument && shouldExpandScopeForAsukaDocument((payload as any)?.document)) {
    const neutral = (payload as any).document.asukaDocument as AsukaDocumentState;
    scope.document.views = scope.document.views || !!neutral.views;
    scope.document.objects = scope.document.objects || !!neutral.objectsByView;
    scope.document.snapshots = scope.document.snapshots || !!neutral.nativeSnapshots?.fabric;
    scope.document.drawingAreas = scope.document.drawingAreas || !!neutral.drawingAreas;
    scope.document.scenes3d = scope.document.scenes3d || !!neutral.scenes3d || !!neutral.nativeSnapshots?.babylon;
    scope.document.viewportByView = scope.document.viewportByView || !!neutral.viewportByView;
    scope.document.selection = scope.document.selection || !!neutral.selection;
    scope.document.asukaDocument = true;
    payload = { ...(payload as any), document: materializeStateDocumentFromAsukaDocument((payload as any).document) } as AsukaExportedState;
  }
  const touchesRenderableDocument = Boolean(
    scope.document.views
    || scope.document.drawingAreas
    || scope.document.snapshots
    || scope.document.objects
    || scope.document.viewportByView
    || scope.document.scenes3d
    || scope.document.asukaDocument
  );
  const store = useAsukaStore.getState() as any;
  const designer = getDesignerRuntime();
  if (touchesRenderableDocument) {
    try { setStateRestoring(true); } catch {}
    try { markDesignerCanvasStateImporting(designer, true); } catch {}
  }
  const incomingViews = Array.isArray(payload.document?.views?.views) ? payload.document!.views!.views : [];
  const shouldReplaceViews = options.replaceViews === true;
  const shouldReplaceSnapshots = options.replaceSnapshots !== false;
  const payloadSnapshotsByView = payload.document?.snapshots?.snapshotsByView ?? {};
  const requestedActiveViewId = payload.document?.views?.activeViewId ?? store.activeViewId ?? incomingViews[0]?.id ?? null;
  const incomingDrawingAreas = resolvePayloadDrawingAreasByView(payload, payloadSnapshotsByView, requestedActiveViewId);
  const incomingObjectsByView = scope.document.objects && payload.document?.objects
    ? sanitizeDocumentObjectsByView(payload.document.objects.byView ?? {})
    : {};
  const rawIncomingElementCatalog = resolveImportElementCatalog(payload);
  const rawIncomingScenes3d = ((payload.document as any)?.scenes3d && typeof (payload.document as any).scenes3d === 'object')
    ? (payload.document as any).scenes3d
    : {};
  const incomingElementCatalog = rawIncomingElementCatalog
    ? ensureProduct3DPartGroupsDeep(rawIncomingElementCatalog)
    : null;
  const incomingScenes3d = clonePlain(rawIncomingScenes3d);


  if (scope.layout && payload.layout) {
    try {
      useAsukaStore.setState({
        blocksByZone: normalizeBlocksByZoneState(payload.layout.blocksByZone),
        zoneCollapsed: clonePlain(payload.layout.zoneCollapsed),
        zoneCollapsible: clonePlain(payload.layout.zoneCollapsible),
        zoneType: clonePlain(payload.layout.zoneType),
      } as any);
    } catch {}
  }

  if (scope.document.snapshots && payload.document?.snapshots && !scope.document.objects) {
    try {
      importViewSnapshots(payload.document.snapshots.snapshotsByView ?? {}, { replace: shouldReplaceSnapshots });
    } catch {}
  }

  if (scope.document.views && payload.document?.views) {
    try {
      const currentViews = Array.isArray(store.views) ? store.views : [];
      const nextViews = shouldReplaceViews
        ? clonePlain(incomingViews)
        : Array.from(new Map([...currentViews, ...incomingViews].map((view: any) => [String(view?.id ?? ''), clonePlain(view)])).values()).filter((view: any) => String(view?.id ?? ''));
      const requested = String(payload.document.views.activeViewId ?? '').trim();
      const nextActiveViewId = requested && nextViews.some((view: any) => String(view?.id ?? '') === requested)
        ? requested
        : (nextViews[0]?.id ?? null);
      useAsukaStore.setState({
        views: nextViews,
        documentViews: nextViews,
        activeViewId: nextActiveViewId,
      } as any);
    } catch {}
  }

  if (scope.document.scenes3d && payload.document && Object.prototype.hasOwnProperty.call(payload.document, 'scenes3d')) {
    try {
      useAsukaStore.setState({ document3dScenesByView: clonePlain(incomingScenes3d) } as any);
    } catch {}
  }

  if (scope.document.views && payload.document?.views && shouldReplaceViews && incomingViews.length === 0) {
    try { importViewSnapshots({}, { replace: true }); } catch {}
    try {
      useAsukaStore.setState({
        views: [],
        documentViews: [],
        activeViewId: null,
        documentObjectsByView: {},
        document3dScenesByView: {},
        document3dHistoryByView: {},
        documentFabricHistoryByView: {},
        documentViewportByView: {},
        documentDrawingAreas: [],
        documentSelection: { activeObjectIds: [], activeDrawingAreaId: null },
      } as any);
    } catch {}
    clearDesignerCanvasForEmptyDocument(designer);
  }

  if (scope.modules && payload.modules) {
    const m = payload.modules as any;
    try {
      useAsukaStore.setState({
        activeModuleId: m.activeModuleId ?? null,
        moduleDisplayModeOverrides: clonePlain(asRecord(m.moduleDisplayModeOverrides)),
        moduleMountTargetOverrides: clonePlain(asRecord(m.moduleMountTargetOverrides)),
        mountedModules: clonePlain(asRecord(m.mountedModules)),
        hiddenModules: clonePlain(asRecord(m.hiddenModules)),
        moduleOverlayStack: Array.isArray(m.moduleOverlayStack) ? clonePlain(m.moduleOverlayStack) : [],
        moduleOverlayType: m.moduleOverlayType ?? store.moduleOverlayType ?? 'floating',
        moduleOverlayAnchorBox: clonePlain(m.moduleOverlayAnchorBox ?? null),
        pendingRestoreSelectionMenu: !!m.pendingRestoreSelectionMenu,
        pendingOpenCreateViewModal: !!m.pendingOpenCreateViewModal,
      } as any);
    } catch {}
  }

  if (scope.document.drawingAreas && payload.document?.drawingAreas) {
    const activeViewForAreas = String(requestedActiveViewId ?? incomingViews[0]?.id ?? store.activeViewId ?? '').trim() || null;
    const zonesForActiveView = pickDrawingAreasForView(incomingDrawingAreas.byView, activeViewForAreas);
    const activeAreaForView = pruneActiveDrawingAreaId(activeViewForAreas ? incomingDrawingAreas.activeByView[activeViewForAreas] : null, zonesForActiveView);
    try { useAsukaStore.setState({ documentDrawingAreas: zonesForActiveView } as any); } catch {}
    try { designer?.setDrawingZones?.(zonesForActiveView, { bypassPermissions: true, source: 'state.importDrawingAreas' }); } catch {}
    try { designer?.setActiveDrawingZone?.(activeAreaForView); } catch {}
  }

  if ((scope.catalog.elements && payload.catalog?.elements) || incomingElementCatalog) {
    try {
      useAsukaStore.setState({
        elements: incomingElementCatalog
          ? clonePlain(incomingElementCatalog)
          : ensureProduct3DPartGroupsDeep(Array.isArray(payload.catalog?.elements) ? clonePlain(payload.catalog!.elements) : []),
      } as any);
    } catch {}
  }
  if (scope.catalog.designs && payload.catalog?.designs) {
    try { store.setModuleConfig?.({ designs: { items: Array.isArray(payload.catalog.designs) ? payload.catalog.designs : [] } }); } catch {}
  }
  if (scope.catalog.colors && payload.catalog?.colors) {
    try { useAsukaStore.setState({ colors: clonePlain(payload.catalog.colors) } as any); } catch {}
  }
  if (scope.catalog.printingMethods && payload.catalog?.printingMethods) {
    try { useAsukaStore.setState({ printingMethods: clonePlain(payload.catalog.printingMethods) } as any); } catch {}
  }
  if (scope.appearance.defaults && payload.appearance?.defaults) {
    try { useAsukaStore.setState({ defaults: clonePlain(payload.appearance.defaults) } as any); } catch {}
  }
  if (scope.appearance.theme && payload.appearance?.theme) {
    try { setThemeState(clonePlain(payload.appearance.theme)); } catch {}
  }

  if (payload.pricing) {
    const pricingPatch: Record<string, any> = {};
    if (scope.pricing.basePrice && typeof payload.pricing.basePrice !== 'undefined') pricingPatch.price = normalizePriceInput(payload.pricing.basePrice) ?? 0;
    if (scope.pricing.rules && Array.isArray(payload.pricing.rules)) pricingPatch.pricingRules = clonePlain(payload.pricing.rules);
    if (scope.pricing.totals && payload.pricing.totals) pricingPatch.pricingTotals = clonePlain(payload.pricing.totals);
    if (Object.keys(pricingPatch).length) {
      try { useAsukaStore.setState(pricingPatch as any); } catch {}
    }
  }

  if (payload.tools) {
    const toolPatch: Record<string, any> = {};
    const fixedSlots = payload.tools.fixedSlots as any;
    if (scope.tools.fixedSlots && fixedSlots) {
      toolPatch.fixedSlots = Array.isArray(fixedSlots.slots) ? clonePlain(fixedSlots.slots) : [];
      toolPatch.fixedSlotsPlacing = clonePlain(fixedSlots.placing ?? null);
      toolPatch.fixedSlotActiveId = fixedSlots.activeId ?? null;
      toolPatch.markerEditorState = clonePlain(fixedSlots.editor ?? null);
    }
    const magnifier = payload.tools.magnifier as any;
    if (scope.tools.magnifier && magnifier) {
      toolPatch.magnifierEnabled = !!magnifier.enabled;
      toolPatch.magnifierActive = !!magnifier.active;
      toolPatch.magnifierX = Number(magnifier.x ?? 0) || 0;
      toolPatch.magnifierY = Number(magnifier.y ?? 0) || 0;
      toolPatch.magnifierSize = Number(magnifier.size ?? 140) || 140;
      toolPatch.magnifierZoom = Number(magnifier.zoom ?? 2) || 2;
    }
    const ruler = payload.tools.ruler as any;
    if (scope.tools.ruler && ruler) {
      toolPatch.rulerEnabled = !!ruler.enabled;
      toolPatch.rulerUnit = ruler.unit ?? 'px';
      toolPatch.rulerDpi = Number(ruler.dpi ?? 96) || 96;
      toolPatch.rulerProduct = clonePlain(ruler.product ?? { unitLabel: 'cm', worldUnitsPerUnit: 10 });
    }
    const shapes = payload.tools.shapes as any;
    if (scope.tools.shapes && shapes) {
      toolPatch.shapeSvgAssets = Array.isArray(shapes.svgAssets) ? clonePlain(shapes.svgAssets) : [];
      toolPatch.shapeStyle = clonePlain(shapes.style ?? {});
      toolPatch.shapeEditDefaults = clonePlain(shapes.editDefaults ?? {});
      toolPatch.shapePlacing = clonePlain(shapes.placing ?? null);
    }
    if (scope.tools.colorScope && typeof payload.tools.colorScope !== 'undefined') toolPatch.colorScope = payload.tools.colorScope;
    const selectionMenu = payload.tools.selectionMenu as any;
    if (scope.tools.selectionMenu && selectionMenu) {
      toolPatch.selectionMenuType = selectionMenu.type ?? store.selectionMenuType;
      toolPatch.selectionMenuRootType = selectionMenu.rootType ?? store.selectionMenuRootType;
      toolPatch.selectionMenuItems = Array.isArray(selectionMenu.items)
        ? clonePlain(selectionMenu.items)
        : [];
      toolPatch.selectionMenuOpen = !!selectionMenu.open;
      toolPatch.selectionMenuStack = Array.isArray(selectionMenu.stack) ? clonePlain(selectionMenu.stack) : [];
      toolPatch.selectionMenuAnchorBox = clonePlain(selectionMenu.anchorBox ?? null);
    }
    if (Object.keys(toolPatch).length) {
      try { useAsukaStore.setState(toolPatch as any); } catch {}
    }
  }

  if (scope.behavior.listeners && payload.behavior) {
    try {
      useAsukaStore.setState({
        listenersEnabled: clonePlain(payload.behavior.listenersEnabled ?? store.listenersEnabled ?? {}),
        listenerSettings: clonePlain(payload.behavior.listenerSettings ?? store.listenerSettings ?? {}),
      } as any);
    } catch {}
  }

  if (payload.configuration) {
    const configPatch: Record<string, any> = {};
    if (scope.configuration.moduleConfig && payload.configuration.moduleConfig) configPatch.moduleConfig = clonePlain(payload.configuration.moduleConfig);
    if (scope.configuration.integrations && payload.configuration.integrations) configPatch.integrations = clonePlain(payload.configuration.integrations);
    if (scope.configuration.fonts && Array.isArray(payload.configuration.fontsCatalog)) configPatch.fontsCatalog = clonePlain(payload.configuration.fontsCatalog);
    if (Object.keys(configPatch).length) {
      try { useAsukaStore.setState(configPatch as any); } catch {}
    }
  }

  if (scope.permissions && payload.permissions) {
    try { store.setRole?.(payload.permissions.role); } catch {}
    try { store.setPermissions?.(payload.permissions.permissions ?? {}); } catch {}
    const pv = payload.permissions.permissionsByView ?? {};
    try { Object.entries(pv).forEach(([viewId, patch]) => store.setPermissionsForView?.(viewId, patch)); } catch {}
  }

  const nextActiveViewId = scope.document.views && payload.document?.views
    ? (payload.document.views.activeViewId ?? incomingViews[0]?.id ?? null)
    : (store.activeViewId ?? null);
  if (scope.document.viewportByView && payload.document?.viewportByView) {
    try { useAsukaStore.setState({ documentViewportByView: payload.document.viewportByView } as any); } catch {}
  }
  if (scope.document.objects && payload.document?.objects) {
    const nextObjectsByView = incomingObjectsByView;
    try { useAsukaStore.setState({ documentObjectsByView: nextObjectsByView } as any); } catch {}
    // Document objects are authoritative for rebuilding view snapshots.
    // For replace imports, never seed from previous snapshots: otherwise a template/product with
    // empty objects can accidentally keep the previous product canvas on the frontend.
    const baseSnapshots = shouldReplaceSnapshots ? {} : exportViewSnapshots();
    const rebuilt = ensureEmptySnapshotsForViews(buildSnapshotsFromDocumentObjects(nextObjectsByView, baseSnapshots, incomingDrawingAreas.byView, incomingDrawingAreas.activeByView), incomingViews);
    importViewSnapshots(rebuilt, { replace: shouldReplaceSnapshots });
  } else if (scope.document.views && payload.document?.views && shouldReplaceSnapshots) {
    // A template can intentionally contain views but no canvas snapshots/objects yet.
    // Store explicit empty snapshots so switching/restoring clears the current canvas
    // instead of leaving the previously rendered product visible.
    const rebuilt = buildSnapshotsFromDocumentObjects({}, {}, incomingDrawingAreas.byView, incomingDrawingAreas.activeByView);
    importViewSnapshots(ensureEmptySnapshotsForViews(rebuilt, incomingViews), { replace: true });
    try { useAsukaStore.setState({ documentObjectsByView: {} } as any); } catch {}
  }
  if (scope.document.selection && payload.document?.selection) {
    try { useAsukaStore.setState({ documentSelection: payload.document.selection } as any); } catch {}
    try { designer?.setActiveDrawingZone?.(payload.document.selection.activeDrawingAreaId ?? null); } catch {}
  }
  if (scope.history && payload.history?.info) {
    try { useAsukaStore.setState({ documentHistoryInfo: payload.history.info } as any); } catch {}
    try {
      const tracks3d = (payload.history as any)?.tracks3dByView;
      if (tracks3d && typeof tracks3d === 'object') useAsukaStore.setState({ document3dHistoryByView: clonePlain(tracks3d) } as any);
      const tracksFabric = (payload.history as any)?.tracksFabricByView;
      if (tracksFabric && typeof tracksFabric === 'object') useAsukaStore.setState({ documentFabricHistoryByView: clonePlain(tracksFabric) } as any);
    } catch {}
  }

  const restoredViewId = nextActiveViewId ? String(nextActiveViewId) : null;
  if (touchesRenderableDocument) {
    if (!restoredViewId && scope.document.views && payload.document?.views && shouldReplaceViews) {
      clearDesignerCanvasForEmptyDocument(designer);
    }
    if (restoredViewId && designer) {
      try { store.setActiveView?.(restoredViewId); } catch {}
      try { switchViewWithCanvas(designer, restoredViewId); } catch {}
      try { rebuildActiveViewFromDocument(restoredViewId); } catch {}
    }
    if (scope.document.viewportByView && payload.document?.viewportByView) {
      applyViewportForActiveView(payload.document.viewportByView, restoredViewId);
    }
    if (restoredViewId) {
      try {
        const schedule = typeof queueMicrotask === 'function'
          ? queueMicrotask
          : (cb: () => void) => setTimeout(cb, 0);
        schedule(() => {
          try { rebuildActiveViewFromDocument(restoredViewId); } catch {}
        });
      } catch {}
    }
    releaseStateImportGuard(designer);
  }
}


/** Export a full serializable app state suitable for `initialState` / `boot.state`. */
export function captureFullAsukaState(): AsukaExportedState {
  return captureAsukaState(DEFAULT_SCOPE as any);
}

/** Import a full serializable app state previously produced by `captureFullAsukaState`. */
export function restoreFullAsukaState(payload: AsukaExportedState | unknown, options: AsukaImportOptions = {}): void {
  restoreAsukaState(payload, { replaceViews: true, replaceSnapshots: true, ...options });
}

/** Export one named, self-describing part of the current state. */
export function captureAsukaStatePart(
  part: AsukaStatePart,
): AsukaExportedState {
  return captureAsukaState(getAsukaStatePartScope(part));
}

/**
 * Import only one named part from a state payload, including a full-state JSON.
 *
 * Template and document imports replace views/snapshots by default. Every other
 * part preserves the mounted document and only updates its own state branches.
 */
export function restoreAsukaStatePart(
  part: AsukaStatePart,
  payload: AsukaExportedState,
  options: AsukaImportOptions = {},
): void {
  const replacesDocument = part === 'template' || part === 'document';
  restoreAsukaState(payload, {
    ...options,
    scope: getAsukaStatePartScope(part),
    replaceViews: options.replaceViews ?? replacesDocument,
    replaceSnapshots: options.replaceSnapshots ?? replacesDocument,
  });
}
