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

import { registerModule, type AsukaModuleProps } from '../../core/modules';
import { useT } from '../../hooks/useT';
import { useAsukaStore } from '../../store/asukaStore';
import type { AsukaElement } from '../../types/elements';
import {
  countProduct3DInstancesForElement,
  findCatalogProduct3DElementForInstance,
  getProduct3DElementStableKey,
  product3DInstanceMatchesElement,
} from '../../core/product3dIdentity';
import { getCameraFromVariationPath } from '../../core/camera';
import { ensureProduct3DPartGroupsDeep } from '../../core/product3dParts';
import { applyElementVariationAcrossRenderers } from '../../core/elementVariationRuntime';

import {
  ElementListPanel,
  ElementsHeader,
  ElementVariationBrowser,
} from '../domain/elements';

type PlacedInstance = {
  id: string;
  elementName: string;
  variationId: string;
};

type VariationNode = any;

type Product3DInstanceElement = AsukaElement & {
  __asukaCatalogElement?: AsukaElement;
  __asukaProductInstance?: any;
  __asukaProductInstanceId?: string;
  __asukaProductInstanceIndex?: number;
  __asukaProductInstanceTotal?: number;
};

const getCatalogElement = (element: Product3DInstanceElement | null | undefined): AsukaElement | null => (
  (element as Product3DInstanceElement | null | undefined)?.__asukaCatalogElement
  ?? element
  ?? null
);

const getElementProductInstance = (element: Product3DInstanceElement | null | undefined): any | null => (
  (element as Product3DInstanceElement | null | undefined)?.__asukaProductInstance
  ?? null
);

const getElementProductInstanceId = (element: Product3DInstanceElement | null | undefined): string => String(
  (element as Product3DInstanceElement | null | undefined)?.__asukaProductInstanceId
  ?? getElementProductInstance(element)?.id
  ?? '',
).trim();

const isProduct3DElement = (element: any): boolean => String(element?.type ?? '').trim() === 'product-3d' || !!element?.product3d;

const is3DRenderEngineKind = (kind: any): boolean => {
  const value = String(kind ?? '').trim().toLowerCase();
  return value === '3d' || value === 'babylon';
};

const getProduct3DPartIdFromVariation = (variation: any): string => String(
  variation?.product3d?.partId
  ?? variation?.metadata?.asukaProduct3DPartId
  ?? variation?.metadata?.partId
  ?? '',
).trim();

const findProduct3DPartIdFromVariationPath = (variationPath: any[]): string => {
  for (let i = (Array.isArray(variationPath) ? variationPath.length : 0) - 1; i >= 0; i -= 1) {
    const partId = getProduct3DPartIdFromVariation(variationPath[i]);
    if (partId) return partId;
  }
  return '';
};
const getProduct3DPartLabelFromVariation = (variation: any): string => String(
  variation?.metadata?.asukaProduct3DPartLabel
  ?? variation?.product3d?.partLabel
  ?? variation?.name
  ?? variation?.label
  ?? variation?.id
  ?? '',
).trim();

const findProduct3DPartLabelFromVariationPath = (variationPath: any[]): string => {
  for (let i = (Array.isArray(variationPath) ? variationPath.length : 0) - 1; i >= 0; i -= 1) {
    const label = getProduct3DPartLabelFromVariation(variationPath[i]);
    if (label) return label;
  }
  return '';
};


const getActiveVariation = (variations: any[]): any | null => {
  const walk = (list: any[]): any | null => {
    for (const item of Array.isArray(list) ? list : []) {
      if (!item) continue;
      if (item.active && item.id) return item;
      const found = walk(item.variations);
      if (found) return found;
    }
    return null;
  };
  return walk(variations) || (Array.isArray(variations) ? variations[0] : null) || null;
};

const getActiveVariationId = (variations: any[]): string => String(getActiveVariation(variations)?.id ?? '');

type VariationLevel = {
  label: string;
  variationId?: string;
  variation?: VariationNode;
  variations: VariationNode[];
};

const hasChildren = (variation: any): boolean => Array.isArray(variation?.variations) && variation.variations.length > 0;

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

const getFirstLeafVariationPath = (variations: any[]): any[] => {
  const list = Array.isArray(variations) ? variations : [];
  if (!list.length) return [];
  const path: any[] = [];
  let cursor: any = list[0];
  while (cursor) {
    path.push(cursor);
    const children = Array.isArray(cursor.variations) ? cursor.variations : [];
    if (!children.length) break;
    cursor = children[0];
  }
  return path;
};

const getActiveVariationPath = (variations: any[], trail: any[] = []): any[] => {
  for (const variation of Array.isArray(variations) ? variations : []) {
    if (!variation) continue;
    const nextTrail = [...trail, variation];
    if (variation.active && variation.id) return nextTrail;
    const found = getActiveVariationPath(Array.isArray(variation.variations) ? variation.variations : [], nextTrail);
    if (found.length) return found;
  }
  return trail.length ? [] : getFirstLeafVariationPath(variations);
};

const getCurrentVariationPath = (levels: VariationLevel[]): any[] => (Array.isArray(levels) ? levels : [])
  .map((level) => level?.variation)
  .filter(Boolean);

const mergeMaterials = (base: any, incoming: any): Record<string, any> => {
  const next: Record<string, any> = { ...(base && typeof base === 'object' ? base : {}) };
  for (const [key, value] of Object.entries(incoming && typeof incoming === 'object' ? incoming : {})) {
    const id = String(key ?? '').trim();
    if (!id || !value || typeof value !== 'object') continue;
    next[id] = { ...((next[id] && typeof next[id] === 'object') ? next[id] : {}), ...(value as any) };
  }
  return next;
};

const mergeProduct3DVariationPath = (variationPath: any[]): any => {
  const merged: any = {};
  for (const variation of Array.isArray(variationPath) ? variationPath : []) {
    const product3d = variation?.product3d && typeof variation.product3d === 'object' ? variation.product3d : null;
    if (!product3d) continue;
    const { materials, model, ...rest } = product3d;
    Object.assign(merged, rest);
    if (model && typeof model === 'object') merged.model = { ...(merged.model ?? {}), ...model };
    if (materials && typeof materials === 'object') merged.materials = mergeMaterials(merged.materials, materials);
  }
  const materialKeys = Object.keys(merged.materials ?? {});
  if (!String(merged.materialTarget ?? '').trim()) merged.materialTarget = materialKeys[0] ?? 'product';
  return merged;
};

const resolveProduct3DVariationForPath = (variationPath: any[]): any | null => {
  const path = Array.isArray(variationPath) ? variationPath.filter(Boolean) : [];
  const leaf = path[path.length - 1];
  if (!leaf) return null;
  return {
    ...(leaf ?? {}),
    product3d: mergeProduct3DVariationPath(path),
    metadata: {
      ...((leaf as any)?.metadata ?? {}),
      variationPath: path.map((node: any) => ({ id: String(node?.id ?? ''), name: String(node?.name ?? '') })),
    },
  };
};

const getVariationPathLabel = (variationPath: any[]): string => (Array.isArray(variationPath) ? variationPath : [])
  .map((node: any) => String(node?.name ?? node?.id ?? '').trim())
  .filter(Boolean)
  .join(' / ');

const getVariationPathIds = (variationPath: any[]): string[] => (Array.isArray(variationPath) ? variationPath : [])
  .map((node: any) => String(node?.id ?? '').trim())
  .filter(Boolean);

const getProductId = (product: any): string => String(product?.id ?? '').trim();

const variationPathEquals = (left: string[], right: string[]): boolean => left.length === right.length && left.every((id, index) => id === right[index]);

const variationPathStartsWith = (fullPath: string[], prefix: string[]): boolean => prefix.length > 0 && prefix.length <= fullPath.length && prefix.every((id, index) => id === fullPath[index]);

const getElementIdentityKeys = (element: any): Set<string> => {
  const keys = new Set<string>();
  for (const value of [element?.id, element?.name]) {
    const key = String(value ?? '').trim();
    if (key) keys.add(key);
  }
  return keys;
};

export const ElementsModule: FunctionalComponent<AsukaModuleProps> = ({ context }) => {
  const t = useT();

  const openModule = useAsukaStore((s: any) => s.openModule);
  const openElementEditor = useAsukaStore((s: any) => s.openElementEditor);

  const permissions = useAsukaStore((s: any) => s.permissions);
  const storedElements = useAsukaStore((s: any) => s.elements) as AsukaElement[] | undefined;
  // Imported and programmatically-created Product 3D definitions can contain
  // real parts without an explicit variation group for each part. Normalize the
  // browse model defensively so every logical part is visible in this module.
  const elements = useMemo(
    () => ensureProduct3DPartGroupsDeep(storedElements) as AsukaElement[],
    [storedElements],
  );
  const removeElement = useAsukaStore((s: any) => s.removeElement);
  const applyElementVariation = useAsukaStore((s: any) => s.applyElementVariation);
  const setElementActiveVariation = useAsukaStore((s: any) => s.setElementActiveVariation);
  const setDocument3dSceneForView = useAsukaStore((s: any) => s.setDocument3dSceneForView);
  const activeViewId = useAsukaStore((s: any) => s.activeViewId);
  const activeRenderEngineKind = useAsukaStore((s: any) => s.activeRenderEngineKind ?? 'fabric');
  const documentObjectsByView = useAsukaStore((s: any) => s.documentObjectsByView ?? {});
  const document3dScenesByView = useAsukaStore((s: any) => s.document3dScenesByView ?? {});
  const [mode, setMode] = useState<'elements' | 'variations'>('elements');
  const [selectedElementKey, setSelectedElementKey] = useState<string>('');
  const [selectedProductInstanceId, setSelectedProductInstanceId] = useState<string>('');
  const [path, setPath] = useState<VariationLevel[]>([]);

  const canViewElements = Boolean((permissions as any)?.canViewElements ?? true);
  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 canDeleteElements = Boolean((permissions as any)?.canDeleteElements ?? true);
  const canMoveElements = Boolean((permissions as any)?.canMoveElements ?? true)
    && Boolean((permissions as any)?.canMoveObject ?? true)
    && Boolean((permissions as any)?.canEditElements ?? true);

  const instances = useMemo<PlacedInstance[]>(() => {
    const objects = ((activeViewId ? documentObjectsByView?.[String(activeViewId)] : []) ?? []) as any[];
    return objects
      .filter((o: any) => !!(o as any)?._asukaElementName)
      .map((o: any, index: number) => ({
        id: String((o as any)?._asukaObjectId ?? o?.id ?? index),
        elementName: String((o as any)?._asukaElementName ?? ''),
        variationId: String((o as any)?._asukaElementVariationId ?? ''),
      }));
  }, [activeViewId, documentObjectsByView]);

  const active3DProducts = useMemo(() => {
    if (String(activeRenderEngineKind ?? 'fabric') !== '3d' || !activeViewId) return [] as any[];
    const scene = (document3dScenesByView as any)?.[String(activeViewId)];
    return (Array.isArray(scene?.products) ? scene.products : []).filter((product: any) => product && typeof product === 'object');
  }, [activeRenderEngineKind, activeViewId, document3dScenesByView]);

  const active3DProduct = useMemo(() => {
    const scene = activeViewId ? (document3dScenesByView as any)?.[String(activeViewId)] : null;
    const selectedId = String(scene?.selectedProductId ?? '').trim();
    const preferredId = mode === 'variations' && selectedProductInstanceId
      ? selectedProductInstanceId
      : selectedId;
    return active3DProducts.find((product: any) => getProductId(product) === preferredId)
      ?? active3DProducts[active3DProducts.length - 1]
      ?? null;
  }, [active3DProducts, activeViewId, document3dScenesByView, mode, selectedProductInstanceId]);

  const active3DProductKeys = useMemo(() => {
    const keys = new Set<string>();
    for (const product of active3DProducts as any[]) {
      for (const value of [product?.catalogId, product?.name]) {
        const key = String(value ?? '').trim();
        if (key) keys.add(key);
      }
    }
    return keys;
  }, [active3DProducts]);

  const active3DProductVariationLabel = useMemo(() => {
    const product = active3DProduct as any;
    return String(product?.variationPathLabel ?? product?.variationName ?? product?.variationId ?? '').trim();
  }, [active3DProduct]);

  const active3DProductVariationPathIds = useMemo(() => {
    const product = active3DProduct as any;
    const path = Array.isArray(product?.variationPath) ? product.variationPath : [];
    const ids = getVariationPathIds(path);
    const fallback = String(product?.variationId ?? '').trim();
    return ids.length ? ids : (fallback ? [fallback] : []);
  }, [active3DProduct]);

  const syncActive3DSceneFromEngine = async (engine: any) => {
    try {
      const state = useAsukaStore.getState() as any;
      const viewId = String(state?.activeViewId ?? activeViewId ?? '').trim();
      if (!viewId || !engine?.lifecycle) {
        return;
      }
      const scene = await Promise.resolve(engine.lifecycle.exportView(viewId));
      (state?.setDocument3dSceneForView ?? setDocument3dSceneForView)?.(viewId, scene);
    } catch (_error) {
      // The regular AsukaRoot persistence listener will still sync the scene.
    }
  };

  const getRuntime3DProductInstancesForApply = async (engine: any): Promise<any[]> => {
    try {
      const state = useAsukaStore.getState() as any;
      const viewId = String(state?.activeViewId ?? activeViewId ?? '').trim();
      const scene = viewId && engine?.lifecycle
        ? await Promise.resolve(engine.lifecycle.exportView(viewId))
        : null;
      const products = Array.isArray((scene as any)?.products) ? (scene as any).products : [];
      if (products.length) return products.filter((product: any) => product && typeof product === 'object');
    } catch {}
    return active3DProducts as any[];
  };


  const placedElementNames = useMemo(() => {
    const set = new Set<string>();
    for (const instance of instances ?? []) {
      const name = String(instance.elementName ?? '').trim();
      if (name) set.add(name);
    }
    for (const key of active3DProductKeys) set.add(key);
    return set;
  }, [instances, active3DProductKeys]);

  const active3DInstanceElements = useMemo<Product3DInstanceElement[]>(() => {
    if (!is3DRenderEngineKind(activeRenderEngineKind)) return [];
    const totals = new Map<string, number>();
    const positions = new Map<string, number>();

    for (const product of active3DProducts as any[]) {
      const catalogElement = findCatalogProduct3DElementForInstance(elements, product) as AsukaElement | null;
      const catalogKey = catalogElement ? getProduct3DElementStableKey(catalogElement) : '';
      if (catalogKey) totals.set(catalogKey, (totals.get(catalogKey) ?? 0) + 1);
    }

    return (active3DProducts as any[]).flatMap((product: any) => {
      const catalogElement = findCatalogProduct3DElementForInstance(elements, product) as AsukaElement | null;
      const productId = getProductId(product);
      const catalogKey = catalogElement ? getProduct3DElementStableKey(catalogElement) : '';
      if (!catalogElement || !productId || !catalogKey) return [];
      const position = (positions.get(catalogKey) ?? 0) + 1;
      const total = totals.get(catalogKey) ?? 1;
      positions.set(catalogKey, position);
      const baseName = String((catalogElement as any)?.name ?? product?.name ?? t('elements.unnamed', 'Element')).trim();
      return [{
        ...(catalogElement as any),
        id: `${catalogKey}::instance::${productId}`,
        name: total > 1 ? `${baseName} #${position}` : baseName,
        __asukaCatalogElement: catalogElement,
        __asukaProductInstance: product,
        __asukaProductInstanceId: productId,
        __asukaProductInstanceIndex: position,
        __asukaProductInstanceTotal: total,
      } as Product3DInstanceElement];
    });
  }, [active3DProducts, activeRenderEngineKind, elements, t]);

  // Only show Elements that exist on the CURRENT view.
  // Other views are stored as snapshots; this module intentionally stays contextual.
  const elementsForActiveView = useMemo(() => {
    if (is3DRenderEngineKind(activeRenderEngineKind)) {
      return active3DInstanceElements;
    }

    const unique = new Map<string, AsukaElement>();
    for (const element of elements ?? []) {
      const name = String((element as any)?.name ?? '').trim();
      if (!name) continue;
      if (!placedElementNames.has(name) || unique.has(name)) continue;
      unique.set(name, element);
    }
    return Array.from(unique.values());
  }, [active3DInstanceElements, elements, placedElementNames, activeRenderEngineKind]);

  const instanceCountByElement = useMemo(() => {
    const map = new Map<string, number>();
    for (const instance of instances ?? []) {
      const key = String(instance.elementName ?? '').trim();
      if (!key) continue;
      map.set(key, (map.get(key) ?? 0) + 1);
    }

    if (String(activeRenderEngineKind ?? 'fabric') === '3d' && active3DProducts.length) {
      const counts = new Map<string, number>();
      for (const instance of active3DProducts as any[]) {
        const product = instance;
        const keys = [product?.catalogId, product?.name]
          .map((value) => String(value ?? '').trim())
          .filter(Boolean);
        for (const key of keys) counts.set(key, (counts.get(key) ?? 0) + 1);
      }
      for (const [key, count] of counts) map.set(key, Math.max(count, map.get(key) ?? 0));
    }

    return map;
  }, [instances, activeRenderEngineKind, active3DProducts]);

  useEffect(() => {
  }, [activeRenderEngineKind, activeViewId, instances, active3DProducts, active3DProductKeys, placedElementNames, elementsForActiveView, instanceCountByElement]);

  const selectedElement: any = useMemo(() => {
    if (!selectedElementKey) return null;
    return (elements ?? []).find((element: any) => String(element?.id ?? '').trim() === selectedElementKey)
      ?? (elements ?? []).find((element: any) => String(element?.familyId ?? '').trim() === selectedElementKey)
      ?? (elements ?? []).find((element: any) => String(element?.name ?? '').trim() === selectedElementKey)
      ?? null;
  }, [elements, selectedElementKey]);

  const currentLevel = path[path.length - 1];
  const currentVariations = currentLevel?.variations ?? [];
  const currentVariationPath = useMemo(() => getCurrentVariationPath(path), [path]);

  const focusVariationRoot = () => {
    if (!is3DRenderEngineKind(activeRenderEngineKind)) return;
    try {
      context?.getEngine?.()?.parts?.focus?.({ scope: 'root', animate: true });
    } catch {
      void 0;
    }
  };

  const focusPartForVariationPath = (variationPathInput: any[]): string => {
    if (!is3DRenderEngineKind(activeRenderEngineKind)) return '';
    const variationPath = Array.isArray(variationPathInput) ? variationPathInput.filter(Boolean) : [];
    const partId = findProduct3DPartIdFromVariationPath(variationPath);
    const partLabel = findProduct3DPartLabelFromVariationPath(variationPath);
    const savedCamera = getCameraFromVariationPath(variationPath);
    if (!partId && !partLabel && !savedCamera) return '';
    try {
      const engine = context?.getEngine?.() as any;
      const selectionKey = partId || partLabel;
      if (selectionKey && engine?.parts) {
        try { engine.parts.select(selectionKey); } catch { void 0; }
      }
      if (savedCamera && engine?.viewport?.setCamera) {
        engine.viewport.setCamera(savedCamera, { animate: true, duration: 520, source: 'variation.camera' });
      } else {
        engine?.parts?.focus({
          partId,
          partLabel,
          productId: getProductId(active3DProduct) || undefined,
          animate: true,
        });
      }
    } catch {
      void 0;
    }
    return partId || partLabel;
  };

  const applyProduct3DElement = async (element: any, variationOverride?: any, variationPathOverride?: any[]) => {
    const catalogElement = getCatalogElement(element) ?? element;
    if (String(activeRenderEngineKind ?? 'fabric') !== '3d' || !isProduct3DElement(catalogElement)) {
      return false;
    }
    try {
      const engine = context?.getEngine?.() as any;
      if (!engine?.objects && !engine?.parts) return false;
      const variations = Array.isArray((catalogElement as any)?.variations) ? (catalogElement as any).variations : [];
      const leafId = String(variationOverride?.id ?? '').trim();
      const variationPath = Array.isArray(variationPathOverride) && variationPathOverride.length
        ? variationPathOverride
        : (leafId ? (findVariationPathById(variations, leafId) ?? [variationOverride]) : getActiveVariationPath(variations));
      const resolvedVariation = resolveProduct3DVariationForPath(variationPath) ?? variationOverride ?? null;
      const variationId = String(resolvedVariation?.id ?? leafId ?? getActiveVariationId(variations) ?? '');
      const partId = focusPartForVariationPath(variationPath);
      const elementKey = String((catalogElement as any)?.id ?? (catalogElement as any)?.name ?? '').trim();
      const targetProductId = String(selectedProductInstanceId || '').trim();
      const variationPathMetadata = variationPath.map((node: any) => ({ id: String(node?.id ?? ''), name: String(node?.name ?? '') }));
      const variationPathLabel = getVariationPathLabel(variationPath);

      if (partId && variationId && engine?.parts) {
        const applied = await applyElementVariationAcrossRenderers(engine, {
          scope: targetProductId ? 'object' : 'document',
          objectId: targetProductId ? `product:${targetProductId}` : undefined,
          elementId: String((catalogElement as any)?.id ?? '').trim() || undefined,
          familyId: String((catalogElement as any)?.familyId ?? '').trim() || undefined,
          name: String((catalogElement as any)?.name ?? '').trim() || undefined,
          variationId,
          variationPathIds: variationPath.map((node: any) => String(node?.id ?? '').trim()).filter(Boolean),
          variationPathNames: variationPath.map((node: any) => String(node?.name ?? node?.label ?? '').trim()).filter(Boolean),
          variationSyncKey: String(resolvedVariation?.syncKey ?? resolvedVariation?.metadata?.syncKey ?? '').trim() || undefined,
          partId,
          source: 'module.elements',
        });
        if (applied) return true;
      }

      const runtimeProductInstances = await getRuntime3DProductInstancesForApply(engine);
      const alreadyPlaced = runtimeProductInstances.some((instance: any) =>
        targetProductId
          ? getProductId(instance) === targetProductId
          : product3DInstanceMatchesElement(instance, catalogElement));
      if (alreadyPlaced) {
        await syncActive3DSceneFromEngine(engine);
        return true;
      }
      if (!engine?.objects) return false;
      const added = await engine.objects.addElement({
        element: catalogElement,
        variation: resolvedVariation,
        variationId,
        variationPath,
        source: 'module.elements',
        metadata: {
          variationPath: variationPathMetadata,
          variationPathLabel,
        },
      });
      const addedProductId = String((added as any)?.id ?? '').trim().replace(/^product:/i, '');
      if (addedProductId) setSelectedProductInstanceId(addedProductId);
      if (variationId) setElementActiveVariation?.(elementKey, variationId);
      await syncActive3DSceneFromEngine(engine);
      return true;
    } catch (_error) {
      return false;
    }
  };

  const openElement = async (element: any) => {
    const catalogElement = getCatalogElement(element) ?? element;
    const productInstanceId = getElementProductInstanceId(element);
    const variations = Array.isArray((catalogElement as any)?.variations) ? (catalogElement as any).variations : [];
    if (productInstanceId && is3DRenderEngineKind(activeRenderEngineKind)) {
      try {
        const engine = context?.getEngine?.() as any;
        engine?.products?.select?.(productInstanceId, { emit: true, source: 'module.elements.open-instance' });
        await syncActive3DSceneFromEngine(engine);
      } catch {
        void 0;
      }
    }
    setSelectedProductInstanceId(productInstanceId);
    setSelectedElementKey(
      getProduct3DElementStableKey(catalogElement)
      || String((catalogElement as any)?.name ?? '').trim(),
    );
    setPath([
      {
        label: String((catalogElement as any)?.name ?? t('elements.unnamed', 'Element')),
        variations,
      },
    ]);
    setMode('variations');
    focusVariationRoot();
  };

  const goBack = () => {
    setMode('elements');
    setSelectedElementKey('');
    setSelectedProductInstanceId('');
    setPath([]);
    focusVariationRoot();
  };

  const goUpOneLevel = () => {
    setPath((currentPath) => {
      if (currentPath.length <= 1) return currentPath;
      if (currentPath.length === 2) focusVariationRoot();
      return currentPath.slice(0, -1);
    });
  };

  const applyOrDive = async (variation: any) => {
    const variationId = String(variation?.id ?? '');
    const children = Array.isArray(variation?.variations) ? variation.variations : [];

    const selectedVariationPath = [...currentVariationPath, variation];

    if (children.length > 0) {
      focusPartForVariationPath(selectedVariationPath);
      const labelRaw = (variation as any)?.name ?? variationId;
      const label = String(labelRaw || t('elements.variation', 'Variation'));
      setPath((currentPath) => [
        ...currentPath,
        {
          label,
          variationId,
          variation,
          variations: children,
        },
      ]);
      return;
    }

    if (!selectedElement || !variationId) return;
    focusPartForVariationPath(selectedVariationPath);
    if (await applyProduct3DElement(selectedElement, variation, selectedVariationPath)) return;
    try {
      const targetProductId = String(selectedProductInstanceId || '').trim();
      await applyElementVariation?.({
        scope: is3DRenderEngineKind(activeRenderEngineKind) && targetProductId ? 'object' : 'document',
        objectId: is3DRenderEngineKind(activeRenderEngineKind) && targetProductId ? `product:${targetProductId}` : undefined,
        elementId: String(selectedElement?.id ?? '').trim() || undefined,
        familyId: String(selectedElement?.familyId ?? '').trim() || undefined,
        name: String(selectedElement?.name ?? '').trim(),
        variationId,
        variationPathIds: selectedVariationPath.map((node: any) => String(node?.id ?? '').trim()).filter(Boolean),
        variationPathNames: selectedVariationPath.map((node: any) => String(node?.name ?? node?.label ?? '').trim()).filter(Boolean),
        variationSyncKey: String(variation?.syncKey ?? variation?.metadata?.syncKey ?? '').trim() || undefined,
        partId: findProduct3DPartIdFromVariationPath(selectedVariationPath) || undefined,
        source: 'module.elements',
      });
    } catch (error) {
      void error;
    }
  };

  const editElement = (element: any) => {
    const catalogElement = getCatalogElement(element);
    if (!canEditElements || !canEditElementVariations || !catalogElement) return;
    openElementEditor?.('edit', catalogElement);
    openModule?.('element-editor');
  };

  const editSelectedElement = () => editElement(selectedElement);

  const getProductObjectIdentityKeys = (object: any): Set<string> => {
    const metadata = object?.metadata && typeof object.metadata === 'object' ? object.metadata : {};
    const product = metadata?.product && typeof metadata.product === 'object'
      ? metadata.product
      : null;
    return new Set([
      String(object?.id ?? '').replace(/^product:/, ''),
      metadata?.productId,
      metadata?.asukaProductId,
      product?.id,
      product?.catalogId,
      product?.name,
    ].map((value) => String(value ?? '').trim()).filter(Boolean));
  };

  const getSelectableProductObjectsForElement = (element: any): any[] => {
    const catalogElement = getCatalogElement(element) ?? element;
    if (String(activeRenderEngineKind ?? 'fabric') !== '3d' || !isProduct3DElement(catalogElement)) return [];
    const elementKeys = getElementIdentityKeys(catalogElement);
    if (!elementKeys.size) return [];
    try {
      const engine = context?.getEngine?.() as any;
      const listed = engine?.objects
        ? engine.objects.list({
            source: 'elements.product-selection',
            includeReadonly: true,
            includeTechnical: false,
            includeScreenBox: false,
          })
        : [];
      const objects = Array.isArray(listed) ? listed : [];
      return objects.filter((object: any) => {
        const id = String(object?.id ?? '').trim();
        const kind = String(object?.kind ?? object?.type ?? '').trim().toLowerCase();
        if (!id.startsWith('product:') && kind !== 'product-3d') return false;
        const keys = getProductObjectIdentityKeys(object);
        for (const key of elementKeys) {
          if (keys.has(key)) return true;
        }
        return false;
      });
    } catch {
      return [];
    }
  };

  const canSelectProduct = (element: any): boolean => {
    const catalogElement = getCatalogElement(element) ?? element;
    if (!canMoveElements || String(activeRenderEngineKind ?? 'fabric') !== '3d' || !isProduct3DElement(catalogElement)) return false;
    const instanceId = getElementProductInstanceId(element);
    if (instanceId) return active3DProducts.some((product: any) => getProductId(product) === instanceId);
    return active3DProducts.some((product: any) => product3DInstanceMatchesElement(product, catalogElement));
  };

  const selectProductForKeyboardMove = (element: any) => {
    const candidates = getSelectableProductObjectsForElement(element);
    if (!candidates.length) return;
    try {
      const engine = context?.getEngine?.() as any;
      const explicitInstanceId = getElementProductInstanceId(element);
      const selectedIndex = candidates.findIndex((object: any) => object?.selected === true);
      const next = (explicitInstanceId
        ? candidates.find((object: any) => {
            const objectId = String(object?.id ?? '').trim();
            const instanceId = String((object?.metadata as any)?.productId ?? objectId.replace(/^product:/, '')).trim();
            return instanceId === explicitInstanceId;
          })
        : candidates[(selectedIndex + 1) % candidates.length]) ?? candidates[0];
      const objectId = String(next?.id ?? '').trim();
      const instanceId = String((next?.metadata as any)?.productId ?? objectId.replace(/^product:/, '')).trim();
      if (instanceId) setSelectedProductInstanceId(instanceId);
      if (engine?.products) {
        engine.products.select(instanceId || objectId, { emit: true, source: 'module.elements.select-product' });
        void syncActive3DSceneFromEngine(engine);
        return;
      }
      engine?.selection?.select(objectId);
      void syncActive3DSceneFromEngine(engine);
    } catch {
      // Selection is only a convenience for keyboard move shortcuts; keep the module resilient.
    }
  };

  const deleteElement = async (element: any) => {
    if (!canDeleteElements) return;
    const catalogElement = getCatalogElement(element) ?? element;
    const identifier = String(
      (catalogElement as any)?.id
      ?? (catalogElement as any)?.familyId
      ?? (catalogElement as any)?.name
      ?? '',
    ).trim();
    if (!identifier) return;

    if (is3DRenderEngineKind(activeRenderEngineKind) && isProduct3DElement(catalogElement)) {
      try {
        const engine = context?.getEngine?.() as any;
        if (engine?.objects) {
          const productIds = (active3DProducts as any[])
            .filter((product: any) => product3DInstanceMatchesElement(product, catalogElement))
            .map(getProductId)
            .filter(Boolean);
          for (const productId of productIds) {
            await engine.objects.removeElement(productId, {
              keepDecorations: false,
              source: 'module.elements.delete',
            });
          }
          await syncActive3DSceneFromEngine(engine);
        }
      } catch {
        // The store deletion below remains authoritative if the live engine is unavailable.
      }
    }

    removeElement?.(identifier);
    setSelectedProductInstanceId('');
    setSelectedElementKey('');
    setMode('elements');
  };

  const addElement = () => {
    if (!canAddElements) return;
    openElementEditor?.('add', null);
    openModule?.('element-editor');
  };

  const getInstanceCount = (element: AsukaElement) => {
    if (getElementProductInstanceId(element)) return 1;
    if (String(activeRenderEngineKind ?? 'fabric') === '3d' && isProduct3DElement(element)) {
      return countProduct3DInstancesForElement(active3DProducts as any[], element);
    }
    const name = String((element as any)?.name ?? '').trim();
    return name ? (instanceCountByElement.get(name) ?? 0) : 0;
  };


  const isActiveProduct = (element: any): boolean => {
    const catalogElement = getCatalogElement(element) ?? element;
    if (String(activeRenderEngineKind ?? 'fabric') !== '3d' || !active3DProduct || !isProduct3DElement(catalogElement)) return false;
    const instanceId = getElementProductInstanceId(element);
    return instanceId
      ? getProductId(active3DProduct) === instanceId
      : product3DInstanceMatchesElement(active3DProduct, catalogElement);
  };

  const getElementStatusLabel = (element: any): string | undefined => {
    const instance = getElementProductInstance(element);
    if (instance) {
      const index = Number((element as Product3DInstanceElement)?.__asukaProductInstanceIndex ?? 1);
      const total = Number((element as Product3DInstanceElement)?.__asukaProductInstanceTotal ?? 1);
      const variationLabel = String(instance?.variationPathLabel ?? instance?.variationName ?? instance?.variationId ?? '').trim();
      const instanceLabel = total > 1
        ? `${t('elements.instance', 'Instance')} ${index}/${total}`
        : t('elements.instance', 'Instance');
      return variationLabel ? `${instanceLabel} - ${variationLabel}` : instanceLabel;
    }
    if (!isActiveProduct(element)) return undefined;
    return active3DProductVariationLabel
      ? `${t('elements.activeVariation', 'Active variation')}: ${active3DProductVariationLabel}`
      : t('elements.activeProduct', 'Active product');
  };

  const getElementStatusBadge = (element: any): string | undefined => (isActiveProduct(element) ? t('elements.activeProduct', 'Active product') : undefined);

  if (!canViewElements) {
    return (
      <div className="ask-p-3">
        <div className="ask-text-sm ask-text-textMuted">{t('permissions.denied', 'Permission denied')}</div>
      </div>
    );
  }

  if (mode === 'variations' && selectedElement) {
    const onBack = () => {
      if (path.length > 1) {
        goUpOneLevel();
        return;
      }
      goBack();
    };

    const selectedMatchesActiveProduct = isActiveProduct(selectedElement);
    const selectedCatalogElement = getCatalogElement(selectedElement) ?? selectedElement;
    const selectedRootVariations = Array.isArray((selectedCatalogElement as any)?.variations)
      ? (selectedCatalogElement as any).variations
      : [];
    const getActiveVariationPathIds = (variationPath: any[]) => {
      const partId = findProduct3DPartIdFromVariationPath(variationPath);
      const activePartVariationId = String((active3DProduct as any)?.partVariationIds?.[partId] ?? '').trim();
      if (partId && activePartVariationId) {
        const activePartPath = findVariationPathById(selectedRootVariations, activePartVariationId);
        if (activePartPath?.length) return getVariationPathIds(activePartPath);
      }
      return active3DProductVariationPathIds;
    };
    const isVariationPathActive = (variationPath: any[]) => selectedMatchesActiveProduct
      && variationPathEquals(getVariationPathIds(variationPath), getActiveVariationPathIds(variationPath));
    const isVariationPathInActiveBranch = (variationPath: any[]) => selectedMatchesActiveProduct
      && variationPathStartsWith(getActiveVariationPathIds(variationPath), getVariationPathIds(variationPath));

    return (
      <ElementVariationBrowser
        t={t}
        selectedElement={selectedElement}
        variations={currentVariations}
        parentVariationPath={currentVariationPath}
        pathLabel={getVariationPathLabel(currentVariationPath)}
        atTop={path.length <= 1}
        canEditElementVariations={canEditElements && canEditElementVariations}
        onBack={onBack}
        onEdit={editSelectedElement}
        onSelectVariation={applyOrDive}
        hasChildren={hasChildren}
        isVariationPathActive={isVariationPathActive}
        isVariationPathInActiveBranch={isVariationPathInActiveBranch}
      />
    );
  }

  return (
    <div className="ask-flex ask-flex-col ask-gap-4">
      <ElementsHeader t={t} canEditElements={canAddElements} onAdd={addElement} />

      <div className="ask-px-3 ask-pb-3">
        <ElementListPanel
          t={t}
          uncategorized={elementsForActiveView}
          categorized={[]}
          getCount={getInstanceCount}
          getStatusLabel={getElementStatusLabel}
          getStatusBadge={getElementStatusBadge}
          isActiveProduct={isActiveProduct}
          canSelectProduct={canSelectProduct}
          onSelectProduct={selectProductForKeyboardMove}
          canEditElementVariations={canEditElements && canEditElementVariations}
          canDeleteElements={canDeleteElements}
          onOpen={openElement}
          onEdit={editElement}
          onDelete={deleteElement}
        />
      </div>
    </div>
  );
};

registerModule({
  id: 'elements',
  title: 'Elements',
  icon: 'puzzle',
  displayMode: 'floating',
  keepMounted: true,
  supportedEngines: ['fabric', '3d'],
  requiredCapabilities: ['addElement'],
  render: (props) => <ElementsModule {...props} />,
});
