import type { FunctionalComponent } from 'preact';
import { useEffect, useMemo, useState } from 'preact/hooks';

import { useT } from '../../hooks/useT';
import { registerModule } from '../../core/modules';
import { getRuntimeRenderEngine } from '../../core/renderEngine';
import { useAsukaStore } from '../../store/asukaStore';
import { getSnapshotForView } from '../../core/viewSnapshots';
import { normalizePriceInput } from '../../core/pricingValue';
import { validateElementEditorValues } from '../../core/elementEditorValidation';
import { beginAsuka3DPerfSpan } from '../../core/perf3dDebug';
import { normalizeParametricVariationScopeTarget } from '../../core/parametric3d';
import { normalizeAssemblyComponentDefinition } from '../../core/assembly3d';
import { ASUKA_PARAMETRIC_VARIATION_PRODUCT_SCOPE } from '../../types/parametric3d';

import type { AsukaElement, ElementVariation } from '../../types/elements';

import { Form } from '../core/Form';
import {
  ElementBehaviorFields,
  ElementCopyFromViewPanel,
  ElementDetailsFields,
  ElementEditorActions,
  ElementVariationTreeField,
  LazyProduct3DElementFields,
} from '../domain/elements';

const genId = (p: string) => `${p}-${Math.random().toString(36).slice(2, 8)}-${Date.now()}`;
const genElementId = () => genId('el');
const genFamilyId = () => genId('fam');

function firstMediaSource(...values: unknown[]): string {
  for (const value of values) {
    const normalized = String(value ?? '').trim();
    if (normalized) return normalized;
  }
  return '';
}

function splitProduct3DCsv(value: any): string[] {
  return String(Array.isArray(value) ? value.join(',') : (value ?? ''))
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean);
}


function normalizeProduct3DPartsConfig(raw: any): any[] {
  const list = Array.isArray(raw) ? raw : [];
  return list.map((part: any, index: number) => {
    const id = String(part?.id ?? `part-${index + 1}`).trim() || `part-${index + 1}`;
    const normalized = {
      ...(part && typeof part === 'object' ? part : {}),
      id,
      label: String(part?.label ?? part?.name ?? id).trim() || id,
      meshNames: splitProduct3DCsv(part?.meshNames ?? part?.meshName ?? part?.targetMeshIds ?? part?.targetMeshId),
      materialNames: splitProduct3DCsv(part?.materialNames ?? part?.materialName),
      materialTarget: String(part?.materialTarget ?? id).trim() || id,
      editable: part?.editable !== false,
      printable: part?.printable === true,
      visible: part?.visible !== false,
      locked: part?.locked === true,
      parentPartId: String(part?.parentPartId ?? '').trim() || undefined,
    };
    try { delete (normalized as any).variations; } catch {}
    return normalized;
  });
}

function normalizeProduct3DVariationConfig(raw: any, options: { force?: boolean; fallbackProduct3d?: any } = {}): any | undefined {
  const input = raw && typeof raw === 'object' ? raw : {};
  if (!options.force && (!raw || typeof raw !== 'object')) return undefined;

  const fallback = options.fallbackProduct3d && typeof options.fallbackProduct3d === 'object' ? options.fallbackProduct3d : {};
  // Print areas are managed by DrawingAreasModule. Variations own product appearance: optional model override, preview and material target.
  const fallbackTarget = String(fallback?.materialTarget ?? 'product').trim() || 'product';
  const materialTarget = String(input?.materialTarget ?? fallbackTarget).trim() || 'product';
  const inputMaterials = input?.materials && typeof input.materials === 'object' ? { ...(input.materials as any) } : {};
  const fallbackMaterials = fallback?.materials && typeof fallback.materials === 'object' ? fallback.materials : {};
  const fallbackMaterial = fallbackMaterials[materialTarget] && typeof fallbackMaterials[materialTarget] === 'object' ? fallbackMaterials[materialTarget] : {};
  const material = inputMaterials[materialTarget] && typeof inputMaterials[materialTarget] === 'object' ? { ...inputMaterials[materialTarget] } : {};
  const materials = { ...inputMaterials };
  materials[materialTarget] = {
    label: material.label ?? fallbackMaterial.label ?? materialTarget,
    color: typeof material.color === 'string' && material.color.trim() ? material.color : (typeof fallbackMaterial.color === 'string' && fallbackMaterial.color.trim() ? fallbackMaterial.color : '#d7dde8'),
    textureUrl: typeof material.textureUrl === 'string' ? material.textureUrl : (typeof fallbackMaterial.textureUrl === 'string' ? fallbackMaterial.textureUrl : ''),
    roughness: Number.isFinite(Number(material.roughness)) ? Number(material.roughness) : (Number.isFinite(Number(fallbackMaterial.roughness)) ? Number(fallbackMaterial.roughness) : 0.55),
    metallic: Number.isFinite(Number(material.metallic)) ? Number(material.metallic) : (Number.isFinite(Number(fallbackMaterial.metallic)) ? Number(fallbackMaterial.metallic) : 0.05),
    ...fallbackMaterial,
    ...material,
  };

  const out: any = { ...input, materialTarget, materials };
  const previewUrl = String(input?.preview?.url ?? input?.previewUrl ?? input?.thumbnailUrl ?? '').trim();
  if (previewUrl) {
    out.previewUrl = previewUrl;
    out.preview = { ...((input?.preview && typeof input.preview === 'object') ? input.preview : {}), url: previewUrl };
  }

  if (input?.model && typeof input.model === 'object') {
    const url = String(input.model.url ?? '').trim();
    const format = String(input.model.format ?? 'glb').trim() || 'glb';
    out.model = url ? { ...input.model, url, format } : undefined;
  }
  if (Array.isArray(input?.parts)) out.parts = normalizeProduct3DPartsConfig(input.parts);
  return out;
}


function normalizeVariationParametricControls(input: any): any | undefined {
  if (!input || typeof input !== 'object') return undefined;
  const parameterIds = Array.from(new Set((Array.isArray(input.parameterIds) ? input.parameterIds : [])
    .map((id: any) => String(id ?? '').trim())
    .filter(Boolean)));
  const title = String(input.title ?? '').trim();
  const description = String(input.description ?? '').trim();
  if (!parameterIds.length && input.enabled !== true && !title && !description) return undefined;
  return {
    enabled: input.enabled === false ? false : true,
    parameterIds,
    ...(title ? { title } : {}),
    ...(description ? { description } : {}),
  };
}

function normalizeVariation(v: any): ElementVariation {
  const product3d = normalizeProduct3DVariationConfig(v?.product3d);
  const image = firstMediaSource(
    v?.image,
    v?.icon,
    product3d?.preview?.url,
    product3d?.previewUrl,
  );
  return {
    id: String(v?.id ?? genId('var')),
    name: String(v?.name ?? ''),
    image,
    ...(product3d ? { product3d } : {}),
    icon: '',
    price: normalizePriceInput(v?.price) ?? 0,
    ...(normalizeVariationParametricControls(v?.parametricControls) ? { parametricControls: normalizeVariationParametricControls(v?.parametricControls) } : {}),
    active: Boolean(v?.active),
  } as any;
}

function normalizeVariationDeep(v: any): ElementVariation {
  const base = normalizeVariation(v) as any;
  const children = Array.isArray(v?.variations) ? v.variations : [];
  return {
    ...base,
    variations: children.map(normalizeVariationDeep),
  } as any;
}

function findVariationPathNamesLocal(variations: any[], vid: string, trail: string[] = []): string[] | null {
  const needle = String(vid ?? '').trim();
  if (!needle) return null;
  for (const v of Array.isArray(variations) ? variations : []) {
    const name = String((v as any)?.name ?? '').trim();
    const nextTrail = name ? [...trail, name] : [...trail];
    if (String((v as any)?.id ?? '').trim() === needle) return nextTrail;
    const kids = Array.isArray((v as any)?.variations) ? (v as any).variations : [];
    const found = kids.length ? findVariationPathNamesLocal(kids, needle, nextTrail) : null;
    if (found?.length) return found;
  }
  return null;
}

function findVariationByNamePathLocal(variations: any[], names: string[], depth = 0): any | null {
  if (!Array.isArray(names) || !names.length) return null;
  const wanted = String(names[depth] ?? '').trim().toLowerCase();
  if (!wanted) return null;
  for (const v of Array.isArray(variations) ? variations : []) {
    const name = String((v as any)?.name ?? '').trim();
    if (name.toLowerCase() !== wanted) continue;
    if (depth >= names.length - 1) return v as any;
    const kids = Array.isArray((v as any)?.variations) ? (v as any).variations : [];
    const found = kids.length ? findVariationByNamePathLocal(kids, names, depth + 1) : null;
    if (found) return found;
  }
  return null;
}

function findVariationByIdLocal(variations: any[], variationId?: string): any | null {
  const needle = String(variationId ?? '').trim();
  if (!needle) return null;
  for (const variation of Array.isArray(variations) ? variations : []) {
    if (!variation) continue;
    if (String((variation as any).id ?? '').trim() === needle) return variation;
    const found = findVariationByIdLocal(Array.isArray((variation as any).variations) ? (variation as any).variations : [], needle);
    if (found) return found;
  }
  return null;
}

function createDefaultVariationForElement(values: any): any {
  const isProduct3D = String(values?.type ?? '').trim() === 'product-3d';
  const base: any = { id: genId('var'), name: '', image: '', icon: '', price: 0, active: false, variations: [] };
  if (!isProduct3D) return { ...base, image: '' };

  // A newly-added Product 3D variation is usually the material choice the admin
  // is editing right now. Make it active immediately and let Form.List clear the
  // sibling active flag, so submitting the element applies the new color/texture
  // without requiring a manual variation switch afterwards.
  return {
    ...base,
    active: true,
    product3d: normalizeProduct3DVariationConfig(null, { force: true, fallbackProduct3d: values?.product3d }) as any,
  };
}

function cloneVariationsForCopy(list: any[]): any[] {
  return (Array.isArray(list) ? list : []).map((v: any) => ({
    ...(v ?? {}),
    id: genId('var'),
    variations: cloneVariationsForCopy((v as any)?.variations ?? []),
  }));
}


function getEditorFormValue(values: any, path: string): any {
  const parts = String(path ?? '')
    .replace(/\]/g, '')
    .split(/[.[]/)
    .filter(Boolean);
  let current = values;
  for (const part of parts) {
    if (current == null) return undefined;
    current = current[part as any];
  }
  return current;
}

function getCopyObjectKey(obj: any, index: number): string {
  return String(
    obj?._asukaObjectId
    ?? obj?.id
    ?? obj?.uuid
    ?? `${String(obj?._asukaElementId ?? obj?._asukaElementName ?? 'element')}-${index}`,
  );
}

function getElementObjectsForCopy(viewId: string, documentObjectsByView: Record<string, any[]> | null | undefined): any[] {
  const id = String(viewId ?? '').trim();
  if (!id) return [];
  const storeObjects = Array.isArray(documentObjectsByView?.[id]) ? documentObjectsByView?.[id] : null;
  if (storeObjects?.length) return storeObjects;
  const snap: any = getSnapshotForView(id);
  return Array.isArray(snap?.objects) ? snap.objects : [];
}


function normalizeProduct3DElementConfig(raw: any): any {
  const input = raw && typeof raw === 'object' ? raw : {};
  const modelUrl = String(input?.model?.url ?? '').trim();
  const modelFormat = String(input?.model?.format ?? 'glb').trim() || 'glb';
  const out: any = { ...input };
  // Product/support materials are variation-owned. Print areas are product-owned
  // and become scene texture slots when a product-3d element is applied.
  try { delete out.materialTarget; } catch {}
  try { delete out.materials; } catch {}
  const parts = normalizeProduct3DPartsConfig(input?.parts);
  const printAreas = Array.isArray(input?.printAreas)
    ? input.printAreas.map((area: any, index: number) => ({
      id: String(area?.id ?? `print-area-${index + 1}`).trim() || `print-area-${index + 1}`,
      label: String(area?.label ?? `Print area ${index + 1}`).trim() || `Print area ${index + 1}`,
      targetMeshId: String(area?.targetMeshId ?? 'product').trim() || 'product',
      mode: String(area?.mode ?? 'texture').trim() || 'texture',
      textureSlot: String(area?.textureSlot ?? 'diffuseTexture').trim() || 'diffuseTexture',
      edgeWrap: String(area?.edgeWrap ?? area?.metadata?.edgeWrap ?? 'both').trim() || 'both',
      canvasSize: {
        width: Math.max(16, Number(area?.canvasSize?.width ?? 1024) || 1024),
        height: Math.max(16, Number(area?.canvasSize?.height ?? 1024) || 1024),
      },
      objects: Array.isArray(area?.objects) ? area.objects : [],
      visible: area?.visible !== false,
      locked: area?.locked === true,
      metadata: area?.metadata && typeof area.metadata === 'object' ? area.metadata : undefined,
    }))
    : [];
  return {
    ...out,
    model: modelUrl ? { ...(input?.model ?? {}), url: modelUrl, format: modelFormat } : undefined,
    ...(parts.length ? { parts } : {}),
    ...(printAreas.length ? { printAreas } : {}),
  };
}

function ensureProduct3DVariationNodeDeep(variation: any, fallbackProduct3d: any): any {
  const next: any = { ...(variation ?? {}) };
  next.product3d = normalizeProduct3DVariationConfig(next.product3d, { force: true, fallbackProduct3d }) as any;
  next.image = typeof next.image === 'string' ? next.image : '';
  next.variations = Array.isArray(next.variations)
    ? next.variations.map((child: any) => ensureProduct3DVariationNodeDeep(child, fallbackProduct3d))
    : [];
  return next;
}

function ensureProduct3DVariationsDeep(vars: any[], fallbackProduct3d: any): any[] {
  const source = Array.isArray(vars) ? vars : [];
  return source.map((variation: any) => ensureProduct3DVariationNodeDeep(variation, fallbackProduct3d));
}

function stripProduct3DVariationConfigDeep(variation: any): any {
  const next: any = { ...(variation ?? {}) };
  try { delete next.product3d; } catch {}
  next.variations = Array.isArray(next.variations)
    ? next.variations.map((child: any) => stripProduct3DVariationConfigDeep(child))
    : [];
  return next;
}

function isProduct3DPartVariationGroup(variation: any): boolean {
  return Boolean(
    variation?.metadata?.asukaProduct3DPartGroup === true
    || variation?.product3d?.partGroup === true,
  );
}

function hasProduct3DPartVariationGroups(vars: any[]): boolean {
  const walk = (list: any[]): boolean => (Array.isArray(list) ? list : []).some((variation: any) => {
    if (!variation || typeof variation !== 'object') return false;
    if (isProduct3DPartVariationGroup(variation)) return true;
    return Array.isArray(variation.variations) ? walk(variation.variations) : false;
  });
  return walk(vars);
}

function ensureProduct3DPartGroupActives(vars: any[]): any[] {
  const list = Array.isArray(vars) ? vars : [];
  for (const item of list) {
    if (!item || typeof item !== 'object') continue;
    if (isProduct3DPartVariationGroup(item)) {
      item.active = false;
      item.variations = ensureTreeSingleActive(Array.isArray(item.variations) ? item.variations : []);
      continue;
    }
    if (Array.isArray(item.variations) && item.variations.length) {
      item.variations = ensureProduct3DPartGroupActives(item.variations);
    }
  }
  return list;
}


function findProduct3DPartConfigForGroup(product3d: any, group: any): any | null {
  const partId = String(group?.product3d?.partId ?? group?.metadata?.asukaProduct3DPartId ?? '').trim();
  if (!partId) return null;
  const parts = normalizeProduct3DPartsConfig(product3d?.parts ?? []);
  const normalizedPartId = partId.toLowerCase();
  return parts.find((part: any) => String(part?.id ?? '').trim().toLowerCase() === normalizedPartId) ?? null;
}

function buildProduct3DScopedMaterialDefaults(args: {
  variation: any;
  group: any;
  part: any | null;
  target: string;
}): any {
  const variation = args.variation && typeof args.variation === 'object' ? args.variation : {};
  const group = args.group && typeof args.group === 'object' ? args.group : {};
  const part = args.part && typeof args.part === 'object' ? args.part : null;
  const target = String(args.target ?? '').trim() || 'product';
  const product3d = variation?.product3d && typeof variation.product3d === 'object' ? variation.product3d : {};
  const materials = product3d?.materials && typeof product3d.materials === 'object' ? product3d.materials : {};
  const rawTarget = String(product3d?.materialTarget ?? '').trim();
  const sourceMaterial =
    (materials[target] && typeof materials[target] === 'object' ? materials[target] : null)
    ?? (rawTarget && materials[rawTarget] && typeof materials[rawTarget] === 'object' ? materials[rawTarget] : null)
    ?? (materials.product && typeof materials.product === 'object' ? materials.product : null)
    ?? Object.values(materials).find((item: any) => item && typeof item === 'object')
    ?? {};
  const partId = String(part?.id ?? group?.product3d?.partId ?? group?.metadata?.asukaProduct3DPartId ?? '').trim();
  const partLabel = String(part?.label ?? group?.metadata?.asukaProduct3DPartLabel ?? group?.name ?? partId ?? target).trim() || target;
  const targetMeshIds = splitProduct3DCsv(part?.meshNames ?? part?.meshName ?? part?.targetMeshIds ?? part?.targetMeshId ?? target);
  const color = typeof (sourceMaterial as any)?.color === 'string' && String((sourceMaterial as any).color).trim()
    ? String((sourceMaterial as any).color).trim()
    : '#d7dde8';
  const textureUrl = typeof (sourceMaterial as any)?.textureUrl === 'string' ? String((sourceMaterial as any).textureUrl) : '';
  const roughness = Number.isFinite(Number((sourceMaterial as any)?.roughness)) ? Number((sourceMaterial as any).roughness) : 0.55;
  const metallic = Number.isFinite(Number((sourceMaterial as any)?.metallic)) ? Number((sourceMaterial as any).metallic) : 0.05;
  return {
    ...(sourceMaterial as any),
    label: String((sourceMaterial as any)?.label ?? variation?.name ?? partLabel ?? target).trim() || target,
    color,
    textureUrl,
    roughness,
    metallic,
    ...(targetMeshIds.length ? { targetMeshIds } : {}),
    metadata: {
      ...(((sourceMaterial as any)?.metadata && typeof (sourceMaterial as any).metadata === 'object') ? (sourceMaterial as any).metadata : {}),
      ...(partId ? { asukaProduct3DPartId: partId, partId } : {}),
      asukaProduct3DPartLabel: partLabel,
      partLabel,
      materialTarget: target,
      ...(targetMeshIds.length ? { targetMeshIds } : {}),
    },
  };
}

function hydrateProduct3DVariationMaterialsForSubmit(values: any): any {
  const input = values && typeof values === 'object' ? values : {};
  if (String(input?.type ?? '').trim() !== 'product-3d') return input;
  const product3d = normalizeProduct3DElementConfig(input.product3d ?? {});

  const hydrateList = (list: any[], currentGroup: any | null = null): any[] => (Array.isArray(list) ? list : []).map((item: any) => {
    const next: any = { ...(item ?? {}) };
    const isGroup = isProduct3DPartVariationGroup(next);
    const group = isGroup ? next : currentGroup;

    if (isGroup) {
      const part = findProduct3DPartConfigForGroup(product3d, next);
      const partId = String(part?.id ?? next?.product3d?.partId ?? next?.metadata?.asukaProduct3DPartId ?? '').trim();
      const target = String(next?.product3d?.materialTarget ?? part?.materialTarget ?? partId).trim() || partId || 'product';
      next.active = false;
      next.product3d = {
        ...((next.product3d && typeof next.product3d === 'object') ? next.product3d : {}),
        partGroup: true,
        ...(partId ? { partId } : {}),
        materialTarget: target,
      };
      next.metadata = {
        ...((next.metadata && typeof next.metadata === 'object') ? next.metadata : {}),
        asukaProduct3DPartGroup: true,
        ...(partId ? { asukaProduct3DPartId: partId } : {}),
        asukaProduct3DPartLabel: String(part?.label ?? next?.metadata?.asukaProduct3DPartLabel ?? next?.name ?? partId ?? target).trim() || target,
      };
      next.variations = hydrateList(Array.isArray(next.variations) ? next.variations : [], next);
      return next;
    }

    if (group) {
      const part = findProduct3DPartConfigForGroup(product3d, group);
      const partId = String(part?.id ?? group?.product3d?.partId ?? group?.metadata?.asukaProduct3DPartId ?? '').trim();
      const target = String(group?.product3d?.materialTarget ?? part?.materialTarget ?? partId).trim() || partId || 'product';
      const scopedMaterial = buildProduct3DScopedMaterialDefaults({ variation: next, group, part, target });
      next.product3d = {
        ...((next.product3d && typeof next.product3d === 'object') ? next.product3d : {}),
        ...(partId ? { partId } : {}),
        materialTarget: target,
        materials: { [target]: scopedMaterial },
      };
      next.metadata = {
        ...((next.metadata && typeof next.metadata === 'object') ? next.metadata : {}),
        ...(partId ? { asukaProduct3DPartId: partId } : {}),
        asukaProduct3DPartLabel: String(part?.label ?? group?.metadata?.asukaProduct3DPartLabel ?? group?.name ?? partId ?? target).trim() || target,
      };
    }

    next.variations = hydrateList(Array.isArray(next.variations) ? next.variations : [], group);
    return next;
  });

  return {
    ...input,
    product3d,
    variations: hydrateList(Array.isArray(input.variations) ? input.variations : []),
  };
}


function collectVariationParametricScopes(variations: any[], hasPartGroupsInput: boolean): Array<{ target: string; parameterIds: string[]; title?: string; description?: string }> {
  const scopes: Array<{ target: string; parameterIds: string[]; title?: string; description?: string }> = [];
  const walk = (list: any[]) => {
    for (const variation of Array.isArray(list) ? list : []) {
      if (!variation || typeof variation !== 'object') continue;
      const controls = normalizeVariationParametricControls(variation.parametricControls);
      if (controls?.enabled !== false && Array.isArray(controls?.parameterIds) && controls.parameterIds.length) {
        const isPartGroup = Boolean(variation?.metadata?.asukaProduct3DPartGroup === true || variation?.product3d?.partGroup === true);
        const target = isPartGroup
          ? String(variation?.product3d?.materialTarget ?? variation?.product3d?.partId ?? variation?.metadata?.asukaProduct3DPartId ?? '').trim()
          : (hasPartGroupsInput ? '' : ASUKA_PARAMETRIC_VARIATION_PRODUCT_SCOPE);
        if (target) {
          scopes.push({
            target,
            parameterIds: controls.parameterIds,
            ...(controls.title ? { title: controls.title } : {}),
            ...(controls.description ? { description: controls.description } : {}),
          });
        }
      }
      walk(Array.isArray(variation.variations) ? variation.variations : []);
    }
  };
  walk(variations);
  return scopes;
}

function mergeParametricVariationScopes(rawScopes: any[], variationScopes: any[], validParameterIds: Set<string>): any[] {
  const scopeMap = new Map<string, any>();
  for (const rawScope of [...(Array.isArray(rawScopes) ? rawScopes : []), ...(Array.isArray(variationScopes) ? variationScopes : [])]) {
    const target = String(rawScope?.target ?? '').trim();
    const targetKey = normalizeParametricVariationScopeTarget(target);
    if (!target || !targetKey) continue;
    const parameterIds = Array.from(new Set((Array.isArray(rawScope?.parameterIds) ? rawScope.parameterIds : [])
      .map((id: any) => String(id ?? '').trim())
      .filter((id: string) => validParameterIds.has(id))));
    const previous = scopeMap.get(targetKey);
    const mergedParameterIds = Array.from(new Set([...(previous?.parameterIds ?? []), ...parameterIds]));
    if (!mergedParameterIds.length) continue;
    scopeMap.set(targetKey, {
      target: previous?.target || target,
      parameterIds: mergedParameterIds,
      title: String(previous?.title ?? rawScope?.title ?? '').trim() || undefined,
      description: String(previous?.description ?? rawScope?.description ?? '').trim() || undefined,
    });
  }
  return Array.from(scopeMap.values());
}

function normalizeElementDeep(raw: any): AsukaElement {
  const next: any = { ...(raw ?? {}) };

  next.id = String(next.id ?? genElementId());
  next.familyId = String(next.familyId ?? next.id ?? genFamilyId());
  next.name = String(next.name ?? '');
  next.category = typeof next.category === 'string' ? next.category : '';
  next.image = firstMediaSource(next.image, next.icon);
  next.icon = '';
  next.price = normalizePriceInput(next.price) ?? 0;

  next.selectable = Boolean(next.selectable);
  next.evented = next.evented !== false;
  next.bringToFront = Boolean(next.bringToFront);
  next.allowInDrawingAreas = Boolean(next.allowInDrawingAreas);
  const elementType = String(next.type ?? '').trim();
  if (elementType === 'product-3d') {
    next.type = 'product-3d';
    next.product3d = normalizeProduct3DElementConfig(next.product3d ?? {});
    if (next.parametric?.schemaVersion === 1 && Array.isArray(next.parametric?.parameters)) {
      const validParameterIds = new Set(
        next.parametric.parameters.map((parameter: any) => String(parameter?.id ?? '').trim()).filter(Boolean),
      );
      const controls = next.parametric?.ui?.variationControls;
      const scopeMap = new Map<string, any>();
      for (const rawScope of Array.isArray(controls?.scopes) ? controls.scopes : []) {
        const target = String(rawScope?.target ?? '').trim();
        const targetKey = normalizeParametricVariationScopeTarget(target);
        if (!target || !targetKey) continue;
        const parameterIds = Array.from(new Set((Array.isArray(rawScope?.parameterIds) ? rawScope.parameterIds : [])
          .map((id: any) => String(id ?? '').trim())
          .filter((id: string) => validParameterIds.has(id))));
        const previous = scopeMap.get(targetKey);
        const mergedParameterIds = Array.from(new Set([...(previous?.parameterIds ?? []), ...parameterIds]));
        if (!mergedParameterIds.length) continue;
        scopeMap.set(targetKey, {
          target: previous?.target || target,
          parameterIds: mergedParameterIds,
          title: String(previous?.title ?? rawScope?.title ?? '').trim() || undefined,
          description: String(previous?.description ?? rawScope?.description ?? '').trim() || undefined,
        });
      }
      next.parametric = {
        ...next.parametric,
        ...(controls && typeof controls === 'object' ? {
          ui: {
            ...(next.parametric.ui ?? {}),
            variationControls: {
              ...controls,
              enabled: controls.enabled === true,
              placement: controls.placement === 'after' ? 'after' : 'before',
              title: String(controls.title ?? '').trim(),
              description: String(controls.description ?? '').trim(),
              scopes: Array.from(scopeMap.values()),
              compact: controls.compact !== false,
            },
          },
        } : {}),
      };
    } else {
      delete next.parametric;
    }
    if (next.assembly?.schemaVersion === 1) {
      next.assembly = normalizeAssemblyComponentDefinition(next.assembly) ?? undefined;
    } else {
      delete next.assembly;
    }
  } else {
    next.type = elementType && elementType !== 'standard' ? elementType : undefined;
    delete next.product3d;
    delete next.parametric;
    delete next.assembly;
  }

  const vars = Array.isArray(next.variations) ? next.variations : [];
  const baseVars = vars.map(normalizeVariationDeep);
  const varsWithProduct3D = next.type === 'product-3d'
    ? ensureProduct3DVariationsDeep(baseVars, raw?.product3d ?? next.product3d)
    : baseVars.map(stripProduct3DVariationConfigDeep);
  const normalizedVars = next.type === 'product-3d' && hasProduct3DPartVariationGroups(varsWithProduct3D)
    ? ensureProduct3DPartGroupActives(varsWithProduct3D)
    : ensureTreeSingleActive(varsWithProduct3D);
  if (next.type === 'product-3d' && next.parametric?.schemaVersion === 1 && Array.isArray(next.parametric?.parameters)) {
    const validParameterIds = new Set<string>(
      next.parametric.parameters.map((parameter: any) => String(parameter?.id ?? '').trim()).filter(Boolean) as string[],
    );
    const controls = next.parametric?.ui?.variationControls;
    const mergedScopes = mergeParametricVariationScopes(
      Array.isArray(controls?.scopes) ? controls.scopes : [],
      collectVariationParametricScopes(normalizedVars, hasProduct3DPartVariationGroups(normalizedVars)),
      validParameterIds,
    );
    if (controls && typeof controls === 'object') {
      next.parametric = {
        ...next.parametric,
        ui: {
          ...(next.parametric.ui ?? {}),
          variationControls: {
            ...controls,
            enabled: controls.enabled === true,
            placement: controls.placement === 'after' ? 'after' : 'before',
            title: String(controls.title ?? '').trim(),
            description: String(controls.description ?? '').trim(),
            scopes: mergedScopes,
            compact: controls.compact !== false,
          },
        },
      };
    }
  }

  next.variations = normalizedVars;

  return next as AsukaElement;
}

const ElementEditor: FunctionalComponent = () => {
  const t = useT();

  const editor = useAsukaStore((s: any) => s.elementEditor);
  const permissions = useAsukaStore((s: any) => s.permissions ?? {});
  const closeElementEditor = useAsukaStore((s: any) => s.closeElementEditor);
  const closeModule = useAsukaStore((s: any) => s.closeModule);

  const addElement = useAsukaStore((s: any) => s.addElement);
  const editElement = useAsukaStore((s: any) => s.editElement);
  const findElement = useAsukaStore((s: any) => s.findElement);

  const designer = useAsukaStore((s: any) => s.designerInstance);
  const activeRenderEngineKind = useAsukaStore((s: any) => s.activeRenderEngineKind ?? 'fabric');
  const is3DContext = String(activeRenderEngineKind ?? 'fabric') === '3d';
  const automaticElementType = is3DContext ? 'product-3d' : '';
  const uploadConfig = useAsukaStore((s: any) => s.moduleConfig?.upload);
  const isOpen = Boolean(editor);

  const mode = (editor?.mode ?? 'add') as 'add' | 'edit';
  const isEdit = mode === 'edit';
  const canAddElements = Boolean((permissions as any)?.canAddElements ?? true);
  const canEditElements = Boolean((permissions as any)?.canEditElements ?? true);
  const canEditElementVariations = Boolean((permissions as any)?.canEditElementVariations ?? true);
  const canSubmit = isEdit ? (canEditElements && canEditElementVariations) : canAddElements;
  const editorInitialValues = editor?.initialValues;
  const initialValues = useMemo(() => {
    const initialValuesRaw = (editorInitialValues ?? {}) as any;
    const next: any = { ...(initialValuesRaw ?? {}) };
    next.bringToFront = Boolean(next.bringToFront);
    next.selectable = Boolean(next.selectable);
    next.evented = next.evented !== false;

    if (!isEdit) {
      next.type = automaticElementType;
      if (is3DContext) {
        next.product3d = normalizeProduct3DElementConfig(next.product3d ?? {});
      } else {
        try { delete next.product3d; } catch {}
      }
      return normalizeElementDeep(next) as Partial<AsukaElement>;
    }

    const initialType = String(next.type ?? '').trim();
    if (initialType === 'product-3d' || next.product3d) {
      return normalizeElementDeep(next) as Partial<AsukaElement>;
    }
    return next as Partial<AsukaElement>;
  }, [editorInitialValues, isEdit, automaticElementType, is3DContext]);

  const applyProduct3DToActiveEngine = async (element: any, variationId?: string, options: { mode?: 'add' | 'edit' } = {}) => {
    const elementId = String(element?.id ?? '').trim();
    const endSpan = beginAsuka3DPerfSpan('element-editor', options?.mode === 'edit' ? 'update-product' : 'add-product', {
      elementId,
      elementName: element?.name ?? null,
      variationId: variationId ?? null,
      activeRenderEngineKind,
      model: element?.product3d?.model,
      partCount: Array.isArray(element?.product3d?.parts) ? element.product3d.parts.length : 0,
    });
    if (String(activeRenderEngineKind ?? 'fabric') !== '3d') {
      endSpan({ ok: false, skipped: true, reason: 'not-3d' });
      return;
    }
    if (String(element?.type ?? '') !== 'product-3d') {
      endSpan({ ok: false, skipped: true, reason: 'not-product-3d' });
      return;
    }
    try {
      const engine = getRuntimeRenderEngine('3d') as any;
      const syncActiveScene = async () => {
        const state = useAsukaStore.getState() as any;
        const viewId = String(state?.activeViewId ?? '').trim();
        if (!viewId || !engine) return;
        const scene = await Promise.resolve(engine.lifecycle.exportView(viewId));
        state?.setDocument3dSceneForView?.(viewId, scene);
      };
      if (options?.mode === 'edit') {
        if (engine?.products) {
          await engine.products.update(element, { source: 'element-editor.edit' });
          await syncActiveScene();
          endSpan({ ok: true, mode: 'edit' });
          return;
        }
      } else if (engine?.objects) {
        const variation = findVariationByIdLocal(Array.isArray(element?.variations) ? element.variations : [], variationId);
        const result = await engine.objects.addElement({ element, variationId, variation, source: 'element-editor' });
        await syncActiveScene();
        endSpan({ ok: true, mode: 'add', result });
        return;
      }
      endSpan({ ok: false, skipped: true, reason: 'engine-method-missing' }, { level: 'warn' });
    } catch (error) {
      endSpan({ ok: false, error }, { level: 'error' });
    }
  };

  // --- Copy placement from another view (add mode only) --------------------
  const views = useAsukaStore((s: any) => s.views) as any[];
  const activeViewId = useAsukaStore((s: any) => s.activeViewId) as string | null;
  const documentObjectsByView = useAsukaStore((s: any) => s.documentObjectsByView ?? {}) as Record<string, any[]>;

  const [copyFromViewId, setCopyFromViewId] = useState<string>('');
  const [copyFromElementObjectId, setCopyFromElementObjectId] = useState<string>('');
  const [copiedSourceElementId, setCopiedSourceElementId] = useState<string>('');
  const [copiedVariationId, setCopiedVariationId] = useState<string>('');
  const [copiedVariationPathNames, setCopiedVariationPathNames] = useState<string[]>([]);
  const [copiedPlacementProps, setCopiedPlacementProps] = useState<any | null>(null);

  const copyViewOptions = useMemo(() => {
    const opts: { value: string; label: string }[] = [];
    for (const v of views ?? []) {
      if (!v?.id) continue;
      if (String(v.id) === String(activeViewId ?? '')) continue;
      opts.push({ value: String(v.id), label: String(v.name ?? v.id) });
    }
    return opts;
  }, [views, activeViewId]);

  const copyElementOptions = useMemo<{ value: string; label: string }[]>(() => {
    const viewId = String(copyFromViewId ?? '').trim();
    if (!viewId) return [] as { value: string; label: string }[];
    const objs = getElementObjectsForCopy(viewId, documentObjectsByView);
    const out = objs
      .map((o: any, index: number) => ({ o, index }))
      .filter(({ o }: any) => !!String(o?._asukaElementId ?? o?._asukaElementName ?? '').trim())
      .map(({ o, index }: any) => {
        const variation = String(o?._asukaElementVariationId ?? '').trim();
        const label = String(o?._asukaElementName ?? o?._asukaElementId ?? `Element ${index + 1}`);
        return {
          value: getCopyObjectKey(o, index),
          label: variation ? `${label} · ${variation}` : label,
        };
      });

    return out;
  }, [copyFromViewId, documentObjectsByView]);

  useEffect(() => {
    setCopyFromElementObjectId('');
    setCopiedSourceElementId('');
    setCopiedVariationId('');
    setCopiedVariationPathNames([]);
    setCopiedPlacementProps(null);
  }, [copyFromViewId]);

  const validateForm = useMemo(() => (values: any) => validateElementEditorValues(values, {
    required: t('form.validation.required', 'This field is required'),
    variationRequired: t('elements.validation.variationRequired', 'At least one variation is required'),
    invalidNumber: t('form.validation.number', 'Enter a valid number'),
  }), [t]);

  if (!isOpen) return null;

  const onClose = () => {
    closeElementEditor();
    closeModule();
  };

  const onSubmit = async (values: any) => {
    if (!canSubmit) return;
    const submitValues = !isEdit
      ? {
        ...(values ?? {}),
        type: automaticElementType,
        ...(is3DContext ? { product3d: normalizeProduct3DElementConfig((values as any)?.product3d ?? {}) } : {}),
      }
      : values;
    if (!isEdit && !is3DContext) {
      try { delete (submitValues as any).product3d; } catch {}
    }
    const preparedSubmitValues = String((submitValues as any)?.type ?? '').trim() === 'product-3d'
      ? hydrateProduct3DVariationMaterialsForSubmit(submitValues)
      : submitValues;
    const normalized = normalizeElementDeep(preparedSubmitValues);
    const liveStore = useAsukaStore.getState() as any;
    const liveDesigner = (liveStore?.designerInstance ?? designer) as any;

    const getDeepestActiveId = (vars: any[]): string | undefined => {
      let best: { id: string; depth: number } | null = null;
      const walk = (arr: any[], depth: number) => {
        for (const it of arr) {
          if (!it) continue;
          const isActive = Boolean((it as any).active);
          if (isActive) {
            const id = String((it as any).id ?? '');
            if (id) {
              if (!best || depth >= best.depth) best = { id, depth };
            }
            const kids = Array.isArray((it as any).variations) ? (it as any).variations : [];
            if (kids.length) walk(kids, depth + 1);
          }
        }
      };
      walk(Array.isArray(vars) ? vars : [], 1);
      return (best as any)?.id;
    };

    const sourceElementId = String(copiedSourceElementId ?? '').trim();
    const normalizedName = String((normalized as any).name ?? '').trim();
    const isCopyFlow = !isEdit && !!String(copyFromViewId ?? '').trim() && !!sourceElementId;
    let elementRecordForPlacement: any = normalized;
    let elementNameForPlacement = normalizedName;

    if (isEdit) {
      const keyId = String((initialValues as any)?.id ?? (normalized as any).id ?? '');
      (liveStore?.editElement ?? editElement)?.(keyId, normalized as any);
      elementRecordForPlacement = normalized;
      elementNameForPlacement = String((normalized as any).name ?? normalizedName);

      try {
        const vid = getDeepestActiveId((elementRecordForPlacement as any).variations ?? []);
        if (liveDesigner?.syncElementEditToActiveView) {
          await liveDesigner.syncElementEditToActiveView(keyId, normalized as any, {
            variationId: vid ? String(vid) : undefined,
            source: 'element-editor.edit',
          });
        }
      } catch (_e) {
        void 0;
      }
      try {
        const vid = getDeepestActiveId((elementRecordForPlacement as any).variations ?? []);
        await applyProduct3DToActiveEngine(elementRecordForPlacement, vid, { mode: 'edit' });
      } catch {}
    } else if (isCopyFlow) {
      const sourceElement = liveStore?.findElement?.(sourceElementId) ?? null;
      const familyId = String((sourceElement as any)?.familyId ?? sourceElementId ?? genFamilyId());
      const nextElement: any = normalizeElementDeep({
        ...(normalized as any),
        id: genElementId(),
        familyId,
        variations: cloneVariationsForCopy((normalized as any).variations ?? []),
      });
      (liveStore?.addElement ?? addElement)?.(String((nextElement as any).id), nextElement as any);
      elementRecordForPlacement = nextElement;
      elementNameForPlacement = String((nextElement as any).name ?? normalizedName);
    } else {
      const nextElement: any = normalizeElementDeep({
        ...(normalized as any),
        id: String((normalized as any).id ?? genElementId()),
        familyId: String((normalized as any).familyId ?? (normalized as any).id ?? genFamilyId()),
      });
      (liveStore?.addElement ?? addElement)?.(String((nextElement as any).id), nextElement as any);
      elementRecordForPlacement = nextElement;
      elementNameForPlacement = String((nextElement as any).name ?? normalizedName);
    }

    // Auto-add the created element to the canvas (catalog creation implies placement).
    // Elements are not meant to be added from the Elements module.
    try {
      const getDeepestActiveId = (vars: any[]): string | undefined => {
        let best: { id?: string; depth: number } | null = null;
        const walk = (list: any[], depth: number) => {
          for (const it of list ?? []) {
            if (!it) continue;
            const id = String(it.id ?? '');
            if (!!it.active && id) {
              if (!best || depth > best.depth) best = { id, depth };
            }
            const kids = Array.isArray(it.variations) ? it.variations : [];
            if (kids.length) walk(kids, depth + 1);
          }
        };
        walk(Array.isArray(vars) ? vars : [], 1);
        return (best as any)?.id;
      };
      await Promise.resolve();
      if (!isEdit && String((elementRecordForPlacement as any)?.type ?? '') === 'product-3d') {
        const vid = String(getDeepestActiveId((elementRecordForPlacement as any).variations ?? []) || '');
        await applyProduct3DToActiveEngine(elementRecordForPlacement, vid);
      } else if (!isEdit && liveDesigner?.addElementInstance) {
        const sourceViewName = String((views ?? []).find((v: any) => String(v?.id ?? '') === String(copyFromViewId ?? ''))?.name ?? '');
        const currentViewName = String((views ?? []).find((v: any) => String(v?.id ?? '') === String(activeViewId ?? ''))?.name ?? '');
        const basePathNames = Array.isArray(copiedVariationPathNames) ? copiedVariationPathNames.filter(Boolean) : [];
        const remappedPathNames = (() => {
          if (!basePathNames.length) return [] as string[];
          const next = [...basePathNames];
          if (sourceViewName && currentViewName && sourceViewName !== currentViewName) {
            if (String(next[0] ?? '').trim().toLowerCase() === sourceViewName.trim().toLowerCase()) next[0] = currentViewName;
          }
          return next;
        })();
        const remappedByPath = remappedPathNames.length
          ? findVariationByNamePathLocal(Array.isArray((elementRecordForPlacement as any).variations) ? (elementRecordForPlacement as any).variations : [], remappedPathNames)
          : null;
        const fallbackByOriginalPath = basePathNames.length
          ? findVariationByNamePathLocal(Array.isArray((elementRecordForPlacement as any).variations) ? (elementRecordForPlacement as any).variations : [], basePathNames)
          : null;
        const remappedCopiedVariationId = String((remappedByPath as any)?.id ?? (fallbackByOriginalPath as any)?.id ?? '');
        const vid = String(remappedCopiedVariationId || getDeepestActiveId((elementRecordForPlacement as any).variations ?? []) || '');
        const props = copiedPlacementProps && typeof copiedPlacementProps === 'object' ? copiedPlacementProps : undefined;
        await liveDesigner.addElementInstance(
          String((elementRecordForPlacement as any).id ?? elementNameForPlacement),
          {
            variationId: vid || undefined,
            props,
          } as any,
        );
      }
    } catch (_e) {
      void 0;
    }

    onClose();
  };

  return (
    <div className="ask-p-5 ask-w-full ask-max-w-[960px] ask-flex ask-flex-col ask-overflow-hidden">
      <div className="ask-mb-4 ask-flex ask-items-start ask-justify-between ask-gap-4">
        <div>
          <div className="ask-text-lg ask-font-semibold ask-text-textMain">
            {isEdit ? t('elements.edit', 'Edit element') : t('elements.add', 'Add element')}
          </div>
          <div className="ask-mt-1 ask-text-sm ask-text-textMuted">
            {t('elements.editor.subtitle', 'Create reusable catalog elements with clean metadata, media and variations.')}
          </div>
        </div>
      </div>

      <Form className="ask-flex ask-flex-col ask-gap-5"
        initialValues={{
          id: genElementId(),
          familyId: genFamilyId(),
          name: '',
          category: '',
          icon: '',
          image: '',
          price: 0,
          type: automaticElementType,
          ...(is3DContext ? { product3d: normalizeProduct3DElementConfig({}) } : {}),
          bringToFront: false,
          allowInDrawingAreas: false,
          selectable: false,
          evented: true,
          variations: is3DContext ? [] : [{ id: genId('var'), name: 'Default', image: '', icon: '', price: 0, active: true }],
          children: [],
          ...(initialValues as any),
        }}
        validate={validateForm}
        onSubmit={(values) => onSubmit(values)}
      >
        {(form) => (
          <>
            {!isEdit ? (
              <ElementCopyFromViewPanel
                t={t}
                copyViewOptions={copyViewOptions}
                copyElementOptions={copyElementOptions}
                copyFromViewId={copyFromViewId}
                copyFromElementObjectId={copyFromElementObjectId}
                copiedPlacementProps={copiedPlacementProps}
                copiedVariationId={copiedVariationId}
                onSourceViewChange={(value) => setCopyFromViewId(String(value ?? ''))}
                onSourceElementChange={(value) => {
                  const objectId = String(value ?? '').trim();
                  setCopyFromElementObjectId(objectId);
                  const viewId = String(copyFromViewId ?? '').trim();
                  if (!viewId || !objectId) {
                    setCopiedSourceElementId('');
                    setCopiedPlacementProps(null);
                    setCopiedVariationId('');
                    setCopiedVariationPathNames([]);
                    return;
                  }
                  const objs = getElementObjectsForCopy(viewId, documentObjectsByView);
                  const o = objs.find((x: any, index: number) => getCopyObjectKey(x, index) === objectId) ?? null;
                  const sourceElementId = String((o as any)?._asukaElementId ?? '').trim();
                  const sourceElementName = String((o as any)?._asukaElementName ?? '').trim();
                  const sourceElementIdentifier = sourceElementId || sourceElementName;
                  setCopiedSourceElementId(sourceElementIdentifier);
                  const selectedCopiedVariationId = String((o as any)?._asukaElementVariationId ?? '');
                  setCopiedVariationId(selectedCopiedVariationId);
                  setCopiedVariationPathNames([]);
                  setCopiedPlacementProps(
                    o ? {
                      left: (o as any).left, top: (o as any).top, scaleX: (o as any).scaleX, scaleY: (o as any).scaleY,
                      angle: (o as any).angle, opacity: (o as any).opacity, flipX: (o as any).flipX, flipY: (o as any).flipY,
                      skewX: (o as any).skewX, skewY: (o as any).skewY, originX: (o as any).originX, originY: (o as any).originY,
                    } : null,
                  );
                  try {
                    const existing = sourceElementIdentifier ? (findElement?.(sourceElementIdentifier) ?? null) : null;
                    const source = existing ? normalizeElementDeep(JSON.parse(JSON.stringify(existing))) : null;
                    if (source) {
                      const copiedPathNames = findVariationPathNamesLocal((source as any).variations ?? [], selectedCopiedVariationId) ?? [];
                      setCopiedVariationPathNames(copiedPathNames);
                      form.setValue('name', String((source as any).name ?? sourceElementName));
                      form.setValue('category', String((source as any).category ?? ''));
                      form.setValue('icon', '');
                      form.setValue('image', firstMediaSource((source as any).image, (source as any).icon));
                      form.setValue('type', automaticElementType);
                      if (is3DContext) {
                        form.setValue('product3d', (source as any).product3d ? JSON.parse(JSON.stringify((source as any).product3d)) : normalizeProduct3DElementConfig({}));
                        form.setValue('parametric', (source as any).parametric ? JSON.parse(JSON.stringify((source as any).parametric)) : undefined);
                        form.setValue('assembly', (source as any).assembly ? JSON.parse(JSON.stringify((source as any).assembly)) : undefined);
                      } else {
                        try { form.setValue('product3d', undefined); } catch {}
                        try { form.setValue('parametric', undefined); } catch {}
                        try { form.setValue('assembly', undefined); } catch {}
                      }
                      form.setValue('price', normalizePriceInput((source as any).price) ?? 0);
                      form.setValue('bringToFront', Boolean((source as any).bringToFront));
                      form.setValue('allowInDrawingAreas', Boolean((source as any).allowInDrawingAreas));
                      form.setValue('selectable', Boolean((source as any).selectable));
                      form.setValue('evented', (source as any).evented !== false);
                      form.setValue('variations', Array.isArray((source as any).variations) ? JSON.parse(JSON.stringify((source as any).variations)) : []);
                      form.setValue('children', Array.isArray((source as any).children) ? JSON.parse(JSON.stringify((source as any).children)) : []);
                    } else {
                      form.setValue('name', sourceElementName);
                    }
                  } catch {
                    try { form.setValue('name', sourceElementName); } catch {}
                  }
                }}
              />
            ) : null}

            <div className="ask-grid ask-grid-cols-2 ask-gap-5 max-xl:ask-grid-cols-1">
              <ElementDetailsFields t={t} form={form} isEdit={isEdit} />
              <ElementBehaviorFields t={t} form={form} />
            </div>

            <LazyProduct3DElementFields t={t} form={form} upload={uploadConfig} />

            <ElementVariationTreeField
              t={t}
              form={form}
              createDefaultVariation={() => createDefaultVariationForElement(form.values)}
              getFormValue={getEditorFormValue}
              upload={uploadConfig}
            />

            <ElementEditorActions t={t} isEdit={isEdit} onCancel={onClose} disabled={!canSubmit} />
          </>
        )}
      </Form>
    </div>
  );
};

registerModule({
  id: 'element-editor',
  title: 'Element editor',
  icon: 'element-editor',
  displayMode: 'modal',
  keepMounted: true,
  supportedEngines: ['fabric', '3d'],
  requiredCapabilities: ['addElement'],
  render: () => <ElementEditor />,
});
function collectActivePaths(vars: any[]): { path: number[]; depth: number }[] {
  const out: { path: number[]; depth: number }[] = [];
  const walk = (list: any[], path: number[]) => {
    for (let i = 0; i < list.length; i++) {
      const it = list[i] ?? {};
      const p = [...path, i];
      if (it.active) out.push({ path: p, depth: p.length });
      const kids = Array.isArray(it.variations) ? it.variations : [];
      if (kids.length) walk(kids, p);
    }
  };
  walk(Array.isArray(vars) ? vars : [], []);
  return out;
}

function setAllActiveFalse(vars: any[]) {
  const walk = (list: any[]) => {
    for (const it of list) {
      if (!it) continue;
      it.active = false;
      const kids = Array.isArray(it.variations) ? it.variations : [];
      if (kids.length) walk(kids);
    }
  };
  walk(Array.isArray(vars) ? vars : []);
}

function getFirstLeafPath(vars: any[]): number[] | null {
  if (!Array.isArray(vars) || !vars.length) return null;
  const path: number[] = [0];
  let cur: any = vars[0];
  while (cur && Array.isArray(cur.variations) && cur.variations.length) {
    path.push(0);
    cur = cur.variations[0];
  }
  return path;
}

function setActiveAtPath(vars: any[], path: number[]) {
  let list: any[] = vars;
  let cur: any = null;
  for (const idx of path) {
    cur = list?.[idx];
    if (!cur) return;
    list = Array.isArray(cur.variations) ? cur.variations : [];
  }
  if (cur) cur.active = true;
}

function ensureTreeSingleActive(vars: any[]) {
  const list = Array.isArray(vars) ? vars : [];
  const actives = collectActivePaths(list);
  let keepPath: number[] | null = null;

  if (actives.length) {
    actives.sort((a, b) => b.depth - a.depth);
    keepPath = actives[0].path;
  } else {
    keepPath = getFirstLeafPath(list);
  }

  setAllActiveFalse(list);
  if (keepPath) setActiveAtPath(list, keepPath);
  return list;
}
