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

import { registerModule, type AsukaModuleProps } from '../../core/modules';
import { useAsukaStore } from '../../store/asukaStore';
import { useT } from '../../hooks/useT';
import {
  product3DInstanceMatchesElement,
} from '../../core/product3dIdentity';
import { getCameraFromVariationPath } from '../../core/camera';
import { resolveParametricVariationControls, resolveParametricVariationScope } from '../../core/parametric3d';
import { ensureProduct3DPartGroupsDeep } from '../../core/product3dParts';
import { buildElementVariationBrowserEntries, findElementInCatalog } from '../../core/elementVariationBrowser';
import { isModuleEmbeddedInLayout } from '../../core/modulePlacement';
import { applyElementVariationAcrossRenderers } from '../../core/elementVariationRuntime';
import { resolveElementVariationPickerPartContext } from '../../core/elementVariationPickerContext';
import { getElementVariationExplicitPreviewSource, resolveElementVariationPreview } from '../../core/elementVariationPreview';
import { resolveContextualElementVariationScope } from '../../core/fabricElementInteraction';

import { Button } from '../core/Button';
import { Card, CardContent, CardHeader, CardTitle } from '../core/Card';
import { ElementListPanel } from '../domain/elements';
import { LazyProductParameterControls as ProductParameterControls } from '../domain/parametric/LazyProductParameterControls';

type Variation = {
  id?: string;
  name?: string;
  icon?: string;
  image?: string;
  variations?: Variation[];
  active?: boolean;
};

type PickerPayload = {
  objectId: string;
  elementId?: string;
  elementName: string;
  variationId?: string;
  objectSrc?: string;
  partId?: string;
  partLabel?: string;
  partMeshId?: string;
  partMaterialId?: string;
  focusOnOpen?: boolean;
  source?: string;
};

const getChildren = (v: any): Variation[] => (Array.isArray(v?.variations) ? v.variations : []);
const getLabel = (v: any): string => String(v?.name ?? v?.id ?? '').trim();
const getPreviewSrc = (variation: Variation): string => getElementVariationExplicitPreviewSource(variation);



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

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 getVariationPathNodes = (root: Variation[], path: string[]): Variation[] => {
  const out: Variation[] = [];
  let currentList: Variation[] = root;
  for (const id of path) {
    const currentNode = (currentList ?? []).find((x: any) => String(x?.id ?? '') === String(id)) ?? null;
    if (!currentNode) break;
    out.push(currentNode);
    currentList = getChildren(currentNode);
  }
  return out;
};

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

const getVariationNodeExplicitPartNames = (node: any): string[] => Array.from(new Set([
  node?.product3d?.partLabel,
  node?.product3d?.label,
  node?.product3d?.partId,
  node?.product3d?.materialTarget,
  node?.metadata?.asukaProduct3DPartLabel,
  node?.metadata?.asukaProduct3DPartId,
  node?.metadata?.partLabel,
  node?.metadata?.partId,
  node?.metadata?.originalMaterialTarget,
  node?.metadata?.materialTarget,
].map((value) => String(value ?? '').trim()).filter(Boolean)));



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

const getRuntime3DProductInstancesForApply = async (engine: any, viewIdInput?: string | null): Promise<any[]> => {
  try {
    const viewId = String(viewIdInput ?? '').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 [];
};

const syncActive3DSceneFromEngine = async (engine: any, viewIdInput?: string | null): Promise<void> => {
  try {
    const viewId = String(viewIdInput ?? '').trim();
    if (!viewId || !engine?.lifecycle) return;
    const scene = await Promise.resolve(engine.lifecycle.exportView(viewId));
    const state = useAsukaStore.getState() as any;
    state?.setDocument3dSceneForView?.(viewId, scene);
  } catch {}
};

const debugVariationPicker = (_phase: string, _payload: Record<string, any>) => {
  try {
    void 0;
    void 0;
    void 0;
  } catch {
    void 0;
  }
};

const getNodeByPath = (root: Variation[], path: string[]): Variation | null => {
  let currentList: Variation[] = root;
  let currentNode: Variation | null = null;
  for (const id of path) {
    currentNode = (currentList ?? []).find((x: any) => String(x?.id ?? '') === String(id)) ?? null;
    if (!currentNode) return null;
    currentList = getChildren(currentNode);
  }
  return currentNode;
};

const getLabelsByPath = (root: Variation[], path: string[]): string[] => {
  const labels: string[] = [];
  let currentList: Variation[] = root;
  for (const id of path) {
    const currentNode = (currentList ?? []).find((x: any) => String(x?.id ?? '') === String(id)) ?? null;
    if (!currentNode) break;
    labels.push(getLabel(currentNode));
    currentList = getChildren(currentNode);
  }
  return labels.filter(Boolean);
};

const findAllPathsByVariationId = (root: Variation[], variationId: string, trail: string[] = []): string[][] => {
  const wanted = String(variationId ?? '').trim();
  if (!wanted) return [];
  const out: string[][] = [];
  for (const item of root ?? []) {
    if (!item) continue;
    const id = String((item as any)?.id ?? '').trim();
    const nextTrail = id ? [...trail, id] : trail;
    if (id && id === wanted) out.push(nextTrail);
    const children = getChildren(item);
    if (children.length) out.push(...findAllPathsByVariationId(children, wanted, nextTrail));
  }
  return out;
};

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

const getProduct3DPartLabelFromVariation = (variation: any): string => String(
  variation?.metadata?.asukaProduct3DPartLabel
  ?? variation?.product3d?.partLabel
  ?? variation?.name
  ?? variation?.id
  ?? '',
).trim();

const isProduct3DPartGroupVariation = (variation: any): boolean => Boolean(
  variation?.metadata?.asukaProduct3DPartGroup === true
  || variation?.product3d?.partGroup === true
);

const findProduct3DPartGroupPath = (root: Variation[], partId: string, trail: string[] = []): string[] | null => {
  const wanted = String(partId ?? '').trim();
  if (!wanted) return null;
  for (const item of root ?? []) {
    if (!item) continue;
    const id = String((item as any)?.id ?? '').trim();
    const nextTrail = id ? [...trail, id] : trail;
    const itemPartId = getProduct3DPartIdFromVariation(item);
    if (isProduct3DPartGroupVariation(item) && itemPartId && itemPartId === wanted) return nextTrail;
    const found = findProduct3DPartGroupPath(getChildren(item), wanted, nextTrail);
    if (found) return found;
  }
  return null;
};

const findProduct3DPartIdFromPathNodes = (nodes: any[]): string => {
  for (let i = (Array.isArray(nodes) ? nodes.length : 0) - 1; i >= 0; i -= 1) {
    const partId = getProduct3DPartIdFromVariation(nodes[i]);
    if (partId) return partId;
  }
  return '';
};

const findProduct3DMaterialTargetFromPathNodes = (nodes: any[]): string => {
  for (let i = (Array.isArray(nodes) ? nodes.length : 0) - 1; i >= 0; i -= 1) {
    const target = String((nodes[i] as any)?.product3d?.materialTarget ?? '').trim();
    if (target) return target;
  }
  return '';
};

const normalizeProductPartFocusKey = (value: any): string => String(value ?? '')
  .trim()
  .toLowerCase()
  .replace(/^asuka_(part|surface)_/i, '')
  .replace(/_print$/i, '')
  .replace(/^part[_-]/i, '')
  .replace(/^surface[_-]/i, '')
  .replace(/[^a-z0-9]+/g, '_')
  .replace(/^_+|_+$/g, '');

const productPartFocusKeysMatch = (left: any, right: any): boolean => {
  const rawLeft = String(left ?? '').trim().toLowerCase();
  const rawRight = String(right ?? '').trim().toLowerCase();
  const normalizedLeft = normalizeProductPartFocusKey(left);
  const normalizedRight = normalizeProductPartFocusKey(right);
  if (!rawLeft || !rawRight) return false;
  return rawLeft === rawRight
    || rawLeft.includes(rawRight)
    || rawRight.includes(rawLeft)
    || (!!normalizedLeft && !!normalizedRight && (
      normalizedLeft === normalizedRight
      || normalizedLeft.includes(normalizedRight)
      || normalizedRight.includes(normalizedLeft)
    ));
};

const getVariationFocusSearchKeys = (nodes: any[]): string[] => {
  const values = new Set<string>();
  for (const node of Array.isArray(nodes) ? nodes : []) {
    for (const value of [
      node?.id,
      node?.name,
      node?.product3d?.partId,
      node?.product3d?.partLabel,
      node?.product3d?.materialTarget,
      node?.metadata?.asukaProduct3DPartId,
      node?.metadata?.asukaProduct3DPartLabel,
      node?.metadata?.partId,
    ]) {
      const key = String(value ?? '').trim().toLowerCase();
      const normalized = normalizeProductPartFocusKey(value);
      if (key) values.add(key);
      if (normalized) values.add(normalized);
    }
  }
  return Array.from(values);
};

const firstNonEmpty = (...values: any[]): string => {
  for (const value of values) {
    if (Array.isArray(value)) {
      const found = value.map((item) => String(item ?? '').trim()).find(Boolean);
      if (found) return found;
      continue;
    }
    const current = String(value ?? '').trim();
    if (current) return current;
  }
  return '';
};

const getPartFocusCandidateValues = (part: any): string[] => [
  part?.id,
  part?.label,
  part?.name,
  part?.meshId,
  part?.meshName,
  part?.materialId,
  part?.materialName,
  part?.materialTarget,
  ...(Array.isArray(part?.meshIds) ? part.meshIds : []),
  ...(Array.isArray(part?.meshNames) ? part.meshNames : []),
  ...(Array.isArray(part?.materialIds) ? part.materialIds : []),
  ...(Array.isArray(part?.materialNames) ? part.materialNames : []),
  ...(Array.isArray(part?.printAreaIds) ? part.printAreaIds : []),
  ...(Array.isArray(part?.metadata?.printAreaIds) ? part.metadata.printAreaIds : []),
  part?.metadata?.sourceMeshId,
  part?.metadata?.sourceMeshName,
  part?.metadata?.partId,
].map((value: any) => String(value ?? '').trim()).filter(Boolean);

const chooseBestPath = (
  root: Variation[],
  paths: string[][],
  activeViewName: string,
  objectSrc: string,
): string[] | null => {
  if (!paths.length) return null;
  const desiredRoot = String(activeViewName ?? '').trim();
  const desiredSrc = String(objectSrc ?? '').trim();

  const scored = paths.map((path) => {
    const labels = getLabelsByPath(root, path);
    const leaf = getNodeByPath(root, path);
    const rootLabel = String(labels[0] ?? '').trim();
    const leafSrc = getPreviewSrc(leaf as any);
    let score = 0;
    if (desiredRoot && rootLabel && rootLabel === desiredRoot) score += 100;
    if (desiredSrc && leafSrc && leafSrc === desiredSrc) score += 50;
    if (labels.length) score += Math.min(labels.length, 10);
    return { path, score };
  });

  scored.sort((a, b) => b.score - a.score);
  return scored[0]?.path ?? paths[0] ?? null;
};

const ElementVariationPickerModule: FunctionalComponent<AsukaModuleProps> = ({ context }) => {
  const t = useT();
  const picker = useAsukaStore((s: any) => (s as any).elementVariationPicker) as PickerPayload | null;
  const closeModule = useAsukaStore((s) => s.closeModule);
  const openElementVariationPicker = useAsukaStore((s: any) => (s as any).openElementVariationPicker);
  const closeElementVariationPicker = useAsukaStore((s: any) => (s as any).closeElementVariationPicker);
  const designer = useAsukaStore((s) => s.designerInstance);
  const applyElementVariation = useAsukaStore((s: any) => (s as any).applyElementVariation);
  const setElementActiveVariation = useAsukaStore((s: any) => (s as any).setElementActiveVariation);
  const activeRenderEngineKind = useAsukaStore((s: any) => (s as any).activeRenderEngineKind ?? 'fabric');
  const activeViewId = useAsukaStore((s: any) => (s as any).activeViewId) as string | null;
  const views = useAsukaStore((s: any) => (s as any).views) as any[];
  const storedElements = useAsukaStore((s: any) => (s as any).elements ?? []) as any[];
  const documentObjectsByView = useAsukaStore((s: any) => (s as any).documentObjectsByView ?? {}) as Record<string, any[]>;
  const document3dScenesByView = useAsukaStore((s: any) => (s as any).document3dScenesByView ?? {}) as Record<string, any>;
  const isEmbeddedPlacement = useAsukaStore((s: any) => isModuleEmbeddedInLayout(s, 'element-variation-picker'));

  const elements = useMemo(() => ensureProduct3DPartGroupsDeep(storedElements), [storedElements]);
  const element = useMemo(() => {
    const elementId = String(picker?.elementId ?? '').trim();
    const name = String(picker?.elementName ?? '').trim();
    if (!elementId && !name) return null;
    return findElementInCatalog(elements, elementId, name);
  }, [elements, picker?.elementId, picker?.elementName]);

  const parametricProductId = useMemo(() => String(picker?.objectId ?? '').trim().replace(/^product:/i, ''), [picker?.objectId]);

  const browserEntries = useMemo(() => buildElementVariationBrowserEntries({
    elements,
    activeViewId,
    activeRenderEngineKind,
    documentObjectsByView,
    document3dScenesByView,
  }), [elements, activeViewId, activeRenderEngineKind, documentObjectsByView, document3dScenesByView]);

  const browserEntryByElementKey = useMemo(() => new Map(browserEntries.map((entry) => [entry.key, entry])), [browserEntries]);
  const browserElements = useMemo(() => browserEntries.map((entry) => entry.element), [browserEntries]);
  const activeViewName = useMemo(() => {
    const wanted = String(activeViewId ?? '').trim();
    const view = (views ?? []).find((v: any) => String(v?.id ?? '').trim() === wanted);
    return String(view?.name ?? '').trim();
  }, [activeViewId, views]);

  const rootVariations = useMemo<Variation[]>(() => Array.isArray((element as any)?.variations) ? ((element as any).variations as any) : [], [element]);
  const [path, setPath] = useState<string[]>([]);

  useEffect(() => {
    if (!rootVariations.length) {
      setPath([]);
      return;
    }

    const pickedPartId = String(picker?.partId ?? '').trim();
    if (pickedPartId) {
      const partPath = findProduct3DPartGroupPath(rootVariations, pickedPartId) ?? [];
      debugVariationPicker('resolve-open-part-path', {
        picker,
        pickedPartId,
        partPath,
        partPathLabels: getLabelsByPath(rootVariations, partPath),
      });
      if (partPath.length) {
        setPath(partPath);
        return;
      }
    }

    const currentVariationId = String(picker?.variationId ?? '').trim();
    if (!currentVariationId) {
      setPath([]);
      return;
    }

    const allPaths = findAllPathsByVariationId(rootVariations, currentVariationId);
    const bestPath = chooseBestPath(rootVariations, allPaths, activeViewName, String(picker?.objectSrc ?? '')) ?? [];
    debugVariationPicker('resolve-open-path', {
      picker,
      activeViewId,
      activeViewName,
      currentVariationId,
      objectSrc: String(picker?.objectSrc ?? ''),
      allPaths,
      allPathLabels: allPaths.map((candidate) => getLabelsByPath(rootVariations, candidate)),
      bestPath,
      bestPathLabels: getLabelsByPath(rootVariations, bestPath),
      rootVariationNames: (rootVariations ?? []).map((v) => ({ id: String((v as any)?.id ?? ''), name: getLabel(v), image: getPreviewSrc(v) })),
    });
    if (!bestPath.length) {
      setPath([]);
      return;
    }

    const targetNode = getNodeByPath(rootVariations, bestPath);
    const targetChildren = targetNode ? getChildren(targetNode) : [];
    const branchPath = targetChildren.length > 0 ? bestPath : (bestPath.length > 1 ? bestPath.slice(0, -1) : []);
    debugVariationPicker('resolved-branch-path', {
      variationId: currentVariationId,
      targetNode: targetNode ? { id: String((targetNode as any)?.id ?? ''), name: getLabel(targetNode), image: getPreviewSrc(targetNode) } : null,
      branchPath,
      branchPathLabels: getLabelsByPath(rootVariations, branchPath),
      childCount: targetChildren.length,
    });
    setPath(branchPath);
  }, [picker, rootVariations, activeViewName, activeViewId]);

  const currentVariations = useMemo<Variation[]>(() => {
    if (!rootVariations.length) return [];
    if (path.length === 0) return rootVariations;
    const node = getNodeByPath(rootVariations, path);
    return node ? getChildren(node) : [];
  }, [rootVariations, path]);

  useEffect(() => {
    debugVariationPicker('render-current-variations', {
      picker,
      path,
      pathLabels: getLabelsByPath(rootVariations, path),
      currentVariations: (currentVariations ?? []).map((v) => ({
        id: String((v as any)?.id ?? ''),
        name: getLabel(v),
        image: getPreviewSrc(v),
        childCount: getChildren(v).length,
        active: Boolean((v as any)?.active),
      })),
    });
  }, [picker, path, rootVariations, currentVariations]);

  const currentNode = useMemo(() => (path.length === 0 ? null : getNodeByPath(rootVariations, path)), [path, rootVariations]);
  const currentNodeLabel = useMemo(() => (currentNode ? getLabel(currentNode) : ''), [currentNode]);
  const currentPathLabels = useMemo(() => getLabelsByPath(rootVariations, path), [rootVariations, path]);
  const currentVariationPathNodes = useMemo(() => getVariationPathNodes(rootVariations, path), [rootVariations, path]);
  const currentParametricPartId = useMemo(() => path.length
    ? String(findProduct3DPartIdFromPathNodes(currentVariationPathNodes) ?? '').trim()
    : '', [path.length, currentVariationPathNodes]);
  const currentParametricPartLabel = useMemo(() => path.length
    ? firstNonEmpty(
      ...currentVariationPathNodes.map((node) => getProduct3DPartLabelFromVariation(node)).filter(Boolean),
      currentNodeLabel,
      currentParametricPartId,
    )
    : '', [path.length, currentVariationPathNodes, currentNodeLabel, currentParametricPartId]);
  const parametricVariationScope = useMemo(() => {
    if (String(activeRenderEngineKind ?? '').toLowerCase() !== '3d') return null;
    const definition = (element as any)?.parametric;
    const controls = resolveParametricVariationControls(definition);
    const explicitNode = path.length
      ? [...currentVariationPathNodes].reverse().find((node: any) => {
        const ids = Array.isArray(node?.parametricControls?.parameterIds) ? node.parametricControls.parameterIds : [];
        return node?.parametricControls?.enabled !== false && ids.map((id: any) => String(id ?? '').trim()).filter(Boolean).length > 0;
      }) as any
      : null;
    if (controls && explicitNode) {
      const validIds = new Set<string>(((definition?.parameters ?? []) as any[]).map((parameter: any) => String(parameter?.id ?? '').trim()).filter(Boolean) as string[]);
      const parameterIds: string[] = Array.from(new Set<string>((Array.isArray(explicitNode?.parametricControls?.parameterIds) ? explicitNode.parametricControls.parameterIds : [])
        .map((id: any) => String(id ?? '').trim())
        .filter((id: string) => validIds.has(id)) as string[]));
      if (parameterIds.length) {
        const label = getLabel(explicitNode) || currentParametricPartLabel || currentNodeLabel;
        return {
          ...controls,
          scopeTarget: String(explicitNode?.id ?? currentParametricPartId ?? label ?? '').trim(),
          parameterIds,
          scopeTitle: String(explicitNode?.parametricControls?.title ?? '').trim() || (label ? `${label} · Parameters` : controls.title),
          scopeDescription: String(explicitNode?.parametricControls?.description ?? '').trim() || undefined,
        };
      }
    }
    return resolveParametricVariationScope(definition, {
      productRoot: path.length === 0,
      partId: currentParametricPartId || undefined,
      partLabel: currentParametricPartLabel || undefined,
      aliases: path.length ? [
        ...currentPathLabels,
        ...currentVariationPathNodes.flatMap((node) => getVariationNodeExplicitPartNames(node)),
        findProduct3DMaterialTargetFromPathNodes(currentVariationPathNodes),
      ].filter(Boolean) : [],
    });
  }, [
    activeRenderEngineKind,
    element,
    path.length,
    currentNodeLabel,
    currentParametricPartId,
    currentParametricPartLabel,
    currentPathLabels,
    currentVariationPathNodes,
  ]);

  const getBrowserEntryForElement = (browserElement: any) => {
    const key = String(browserElement?.id ?? browserElement?.familyId ?? browserElement?.name ?? '').trim();
    return key ? browserEntryByElementKey.get(key) ?? null : null;
  };

  const focusVariationRoot = useCallback(() => {
    if (String(activeRenderEngineKind ?? '').toLowerCase() !== '3d') return;
    try {
      const engine = context?.getEngine?.() as any;
      engine?.parts?.focus?.({ scope: 'root', animate: true });
    } catch {
      void 0;
    }
  }, [activeRenderEngineKind, context]);

  const openBrowserElement = (browserElement: any) => {
    const entry = getBrowserEntryForElement(browserElement);
    if (!entry) return;
    setPath([]);
    focusVariationRoot();
    openElementVariationPicker?.({
      objectId: entry.objectId,
      elementId: entry.elementId,
      elementName: entry.elementName,
      objectSrc: entry.objectSrc,
      focusOnOpen: false,
      source: 'module.variation-picker.element-list',
    });
  };

  const resetToElementList = () => {
    setPath([]);
    focusVariationRoot();
    closeElementVariationPicker?.();
  };

  const closePickerUi = () => {
    resetToElementList();
    const state = useAsukaStore.getState() as any;
    const stack = Array.isArray(state?.moduleOverlayStack) ? state.moduleOverlayStack : [];
    const currentOverlayId = String(stack[stack.length - 1] ?? state?.activeModuleId ?? '');
    if (currentOverlayId === 'element-variation-picker') closeModule();
  };

  const syncPickerVariationPath = (variationPath: any[], variationId?: string | null) => {
    const contextPatch = resolveElementVariationPickerPartContext(variationPath, variationId);
    useAsukaStore.setState((state: any) => ({
      elementVariationPicker: state?.elementVariationPicker
        ? { ...state.elementVariationPicker, ...contextPatch }
        : state?.elementVariationPicker,
    } as any));
  };

  const handleBack = () => {
    if (path.length > 0) {
      if (path.length === 1) {
        focusVariationRoot();
        syncPickerVariationPath([], null);
      }
      setPath((currentPath) => currentPath.slice(0, -1));
      return;
    }
    resetToElementList();
  };

  const finishSuccessfulApply = (variationId: string) => {
    if (isEmbeddedPlacement) {
      syncPickerVariationPath(currentVariationPathNodes, variationId);
      return;
    }
    closeElementVariationPicker?.();
    closeModule();
  };

  const focusPart = useCallback((partIdInput?: string, variationPathNodes?: any[]) => {
    const nodes = Array.isArray(variationPathNodes) ? variationPathNodes : [];
    try {
      const engine = context?.getEngine?.() as any;
      const engineKind = String(activeRenderEngineKind ?? engine?.kind ?? '').trim().toLowerCase();
      if (engineKind !== '3d' && engineKind !== 'babylon') return;

      const savedCamera = getCameraFromVariationPath(nodes);
      let partId = firstNonEmpty(partIdInput, findProduct3DPartIdFromPathNodes(nodes), picker?.partId);
      let partLabel = firstNonEmpty(
        ...nodes.map((node) => getProduct3DPartLabelFromVariation(node)).filter(Boolean),
        picker?.partLabel,
        partId,
      );
      let meshId = firstNonEmpty(picker?.partMeshId);
      let materialId = firstNonEmpty(picker?.partMaterialId, findProduct3DMaterialTargetFromPathNodes(nodes));

      if (engine?.parts) {
        const keys = Array.from(new Set([
          ...getVariationFocusSearchKeys(nodes),
          partId,
          meshId,
          materialId,
          picker?.partLabel,
          partLabel,
        ].map((value) => String(value ?? '').trim()).filter(Boolean)));
        const rawParts = engine.parts.list();
        const parts = Array.isArray(rawParts) ? rawParts : [];
        const matchedPart = parts.find((part: any) => {
          const candidates = getPartFocusCandidateValues(part);
          return keys.some((key) => candidates.some((candidate) => productPartFocusKeysMatch(key, candidate)));
        });
        if (matchedPart) {
          partId = firstNonEmpty(matchedPart?.id, partId);
          partLabel = firstNonEmpty(matchedPart?.label, matchedPart?.name, partLabel);
          meshId = firstNonEmpty(matchedPart?.meshId, matchedPart?.meshIds, matchedPart?.meshName, matchedPart?.meshNames, meshId);
          materialId = firstNonEmpty(
            matchedPart?.materialId,
            matchedPart?.materialTarget,
            matchedPart?.materialIds,
            matchedPart?.materialName,
            matchedPart?.materialNames,
            materialId,
          );
        }
      }

      if (!partId && !partLabel && !meshId && !materialId && !savedCamera) return;
      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 if (engine?.parts) {
        engine.parts.focus({
          partId,
          partLabel,
          meshId,
          materialId,
          productId: parametricProductId || undefined,
          animate: true,
        });
      }
    } catch {
      void 0;
    }
  }, [activeRenderEngineKind, context, parametricProductId, picker]);

  useEffect(() => {
    if (picker?.focusOnOpen === false) return;
    if (!path.length || !rootVariations.length) return;
    focusPart(undefined, getVariationPathNodes(rootVariations, path));
  }, [focusPart, path, picker?.focusOnOpen, rootVariations]);

  const openVariationGroup = (variation: Variation) => {
    const vid = String((variation as any)?.id ?? '').trim();
    if (!vid) return;
    const nextPathIds = [...path, vid].filter(Boolean);
    const nextVariationPath = getVariationPathNodes(rootVariations, nextPathIds);
    const children = getChildren(variation);
    const activeChild = children.find((child) => child?.active === true) ?? children[0] ?? null;
    syncPickerVariationPath(nextVariationPath, activeChild ? String(activeChild?.id ?? '').trim() || null : null);
    focusPart(getProduct3DPartIdFromVariation(variation), nextVariationPath);
    setPath(nextPathIds);
  };

  const applyVariation = async (variation: Variation) => {
    const vid = String((variation as any)?.id ?? '').trim();
    const label = getLabel(variation);
    if (!picker?.elementName || !vid) return;
    try {
      const pathIds = [...path, vid].filter(Boolean);
      const variationPath = getVariationPathNodes(rootVariations, pathIds);
      focusPart(getProduct3DPartIdFromVariation(variation), variationPath);
      const pathNames = getVariationPathLabel(variationPath).split('/').map((part) => part.trim()).filter(Boolean);
      debugVariationPicker('apply-click', {
        picker,
        objectId: picker?.objectId,
        elementName: picker?.elementName,
        chosenVariationId: vid,
        chosenLabel: label,
        currentPathLabels,
        pathNames,
      });

      if (String(activeRenderEngineKind ?? 'fabric') === '3d' && isProduct3DElement(element)) {
        const engine = context?.getEngine?.() as any;
        const resolvedVariation = resolveProduct3DVariationForPath(variationPath) ?? variation;
        const elementKey = String((element as any)?.id ?? (element as any)?.name ?? '').trim();
        const partId = String(findProduct3DPartIdFromPathNodes(variationPath) || picker?.partId || '').trim();

        if (partId && engine?.parts) {
          const applied = await applyElementVariationAcrossRenderers(engine, {
            scope: 'object',
            objectId: picker.objectId,
            elementId: String((element as any)?.id ?? picker.elementId ?? '').trim() || undefined,
            familyId: String((element as any)?.familyId ?? '').trim() || undefined,
            name: String((element as any)?.name ?? picker.elementName ?? '').trim() || undefined,
            variationId: vid,
            variationPathIds: pathIds,
            variationPathNames: pathNames,
            variationSyncKey: String((variation as any)?.syncKey ?? (variation as any)?.metadata?.syncKey ?? '').trim() || undefined,
            partId,
            source: 'overlay.picker',
          });
          if (applied) {
            finishSuccessfulApply(vid);
            return;
          }
        }

        if (engine?.objects) {
          const runtimeProducts = await getRuntime3DProductInstancesForApply(engine, activeViewId);
          const pickerProductId = String(picker?.objectId ?? '').trim().replace(/^product:/i, '');
          const existingProduct = runtimeProducts.find((instance: any) => {
            const runtimeId = getProductId(instance);
            return (!!pickerProductId && runtimeId === pickerProductId)
              || product3DInstanceMatchesElement(instance, element);
          });
          if (existingProduct) {
            // A variation without a resolvable part target must never create a
            // second product instance. Keep the picker open and wait for a valid
            // part mapping instead of turning a variation click into an add.
            await syncActive3DSceneFromEngine(engine, activeViewId);
            return;
          }
          await engine.objects.addElement({
            element,
            variation: resolvedVariation,
            variationId: vid,
            variationPath,
            source: 'overlay.picker',
            metadata: {
              variationPath: variationPath.map((node: any) => ({ id: String(node?.id ?? ''), name: String(node?.name ?? '') })),
              variationPathLabel: getVariationPathLabel(variationPath),
            },
          });
          setElementActiveVariation?.(elementKey, vid);
          await syncActive3DSceneFromEngine(engine, activeViewId);
          finishSuccessfulApply(vid);
          return;
        }
      }

      const ok = await (applyElementVariation ?? designer?.applyElementVariation)?.({
        scope: resolveContextualElementVariationScope(activeRenderEngineKind, picker.objectId),
        objectId: picker.objectId,
        elementId: picker.elementId,
        familyId: String((element as any)?.familyId ?? '').trim() || undefined,
        name: picker.elementName,
        variationId: vid,
        variationPathIds: pathIds,
        variationPathNames: pathNames,
        variationSyncKey: String((variation as any)?.syncKey ?? (variation as any)?.metadata?.syncKey ?? '').trim() || undefined,
        partId: String(findProduct3DPartIdFromPathNodes(variationPath) || picker?.partId || '').trim() || undefined,
        source: 'overlay.picker',
      });
      debugVariationPicker('apply-result', { ok, objectId: picker.objectId, elementName: picker.elementName, chosenVariationId: vid, pathNames });
      if (ok) finishSuccessfulApply(vid);
      return;
    } catch (_e: any) {
      void 0;
    }
  };

  if (!picker || !element) {
    return (
      <Card className="ask-w-full ask-max-w-full ask-overflow-hidden !ask-rounded-none !ask-border-0 !ask-bg-transparent !ask-shadow-none">
        <CardHeader className="ask-flex ask-items-center ask-justify-between ask-gap-3 ask-border-b ask-border-borderSubtle/70 !ask-px-0 !ask-pt-0 ask-pb-3">
          <div className="ask-min-w-0">
            <CardTitle>{t('elements.variations', 'Variations')}</CardTitle>
            <div className="ask-mt-0.5 ask-text-[11px] ask-text-textMuted">
              {t('elements.selectElementForVariations', 'Select an element in the current view to edit its variations.')}
            </div>
          </div>
          {!isEmbeddedPlacement ? <Button onClick={closePickerUi} size="sm">{t('common.close', 'Close')}</Button> : null}
        </CardHeader>
        <CardContent className="!ask-px-0 ask-pt-3 !ask-pb-0">
          <ElementListPanel
            t={t}
            uncategorized={browserElements as any}
            categorized={[]}
            getCount={(browserElement: any) => getBrowserEntryForElement(browserElement)?.count ?? 0}
            canEditElementVariations={false}
            canDeleteElements={false}
            onOpen={openBrowserElement}
            onEdit={() => undefined}
            onDelete={() => undefined}
          />
        </CardContent>
      </Card>
    );
  }

  return (
    <Card className="ask-w-full ask-max-w-full ask-overflow-hidden !ask-rounded-none !ask-border-0 !ask-bg-transparent !ask-shadow-none">
      <CardHeader className="ask-flex ask-items-start ask-justify-between ask-gap-3 ask-border-b ask-border-borderSubtle/70 !ask-px-0 !ask-pt-0 ask-pb-3">
        <div className="ask-min-w-0">
          <CardTitle>{t('elements.variations', 'Variations')}</CardTitle>
          <div className="ask-mt-1 ask-text-[12px] ask-text-textMuted ask-truncate">
            {picker.elementName || t('elements.unnamed', 'Element')}
            {currentNodeLabel ? ` / ${currentNodeLabel}` : ''}
          </div>
        </div>
        <div className="ask-flex ask-items-center ask-gap-2 ask-shrink-0">
          <Button variant="ghost" size="sm" onClick={handleBack} type="button">{t('common.back', 'Back')}</Button>
          {!isEmbeddedPlacement ? (
            <Button variant="ghost" size="sm" onClick={closePickerUi} type="button">{t('common.close', 'Close')}</Button>
          ) : null}
        </div>
      </CardHeader>

      <CardContent className="!ask-px-0 ask-pt-3 !ask-pb-0">
        <div className="ask-flex ask-flex-col ask-gap-3">
          {parametricVariationScope?.placement === 'before' ? (
            <ProductParameterControls
              context={context}
              productId={parametricProductId || undefined}
              parameterIds={parametricVariationScope.parameterIds}
              title={parametricVariationScope.scopeTitle || undefined}
              description={parametricVariationScope.scopeDescription || undefined}
              compact={parametricVariationScope.compact}
              source="element-variation-picker.parameters"
              collapsible
              defaultCollapsed
            />
          ) : null}

          {currentVariations.length === 0 ? (
            <div className="ask-rounded-[18px] ask-border ask-border-dashed ask-border-borderSubtle ask-bg-surface ask-p-5 ask-text-center">
              <div className="ask-text-[13px] ask-font-medium ask-text-textMain">{t('elements.noVariationsAvailable', 'No variations available')}</div>
              <div className="ask-mt-0.5 ask-text-[11px] ask-text-textMuted">{t('elements.noSelectableVariationsYet', 'This element does not have selectable variations yet.')}</div>
            </div>
          ) : (
            <div className="ask-flex ask-flex-col ask-gap-3">
              {currentVariations.map((v: any) => {
              const vid = String(v?.id ?? '');
              const label = getLabel(v) || t('elements.variation', 'Variation');
              const preview = resolveElementVariationPreview(v);
              const hasKids = getChildren(v).length > 0;
              const selectedVariationId = String(picker?.variationId ?? '');
              const isActive = selectedVariationId ? selectedVariationId === vid : Boolean(v?.active);
              return (
                <div
                  key={vid || label}
                  role="button"
                  tabIndex={0}
                  className={[
                    'ask-group ask-flex ask-w-full ask-items-center ask-gap-3 ask-overflow-hidden ask-rounded-[16px] ask-border ask-bg-surface ask-p-2.5 ask-text-left ask-transition',
                    isActive ? 'ask-border-primary/30 ask-bg-primary/5 ask-shadow-[0_8px_18px_rgba(37,99,235,0.06)]' : 'ask-border-borderSubtle/75 hover:ask-border-primary/25 hover:ask-shadow-[0_8px_18px_rgba(15,23,42,0.05)]',
                  ].join(' ')}
                  onClick={() => {
                    if (!vid) return;
                    if (hasKids) {
                      openVariationGroup(v);
                      return;
                    }
                    void applyVariation(v);
                  }}
                  onKeyDown={(event: any) => {
                    if (event?.key !== 'Enter' && event?.key !== ' ') return;
                    event?.preventDefault?.();
                    if (!vid) return;
                    if (hasKids) {
                      openVariationGroup(v);
                      return;
                    }
                    void applyVariation(v);
                  }}
                >
                  <div
                    className="ask-flex ask-h-12 ask-w-12 ask-shrink-0 ask-items-center ask-justify-center ask-overflow-hidden ask-rounded-[14px] ask-border ask-border-borderSubtle/70 ask-bg-surfaceAlt"
                    title={preview.kind === 'color' ? preview.value : undefined}
                    aria-hidden={preview.kind === 'image' || preview.kind === 'texture' ? undefined : true}
                  >
                    {preview.kind === 'image' ? <img src={preview.value} alt={label} className="ask-h-full ask-w-full ask-object-contain" /> : null}
                    {preview.kind === 'texture' ? <img src={preview.value} alt={label} className="ask-h-full ask-w-full ask-object-cover" /> : null}
                    {preview.kind === 'color' || preview.kind === 'fallback' ? <span className="ask-h-full ask-w-full" style={{ background: preview.value }} /> : null}
                  </div>
                  <div className="ask-min-w-0 ask-flex-1">
                    <div className="ask-flex ask-items-center ask-gap-2 ask-flex-wrap">
                      <div className="ask-font-medium ask-text-textMain">{label}</div>
                      {isActive ? <span className="ask-rounded-full ask-bg-primary/10 ask-px-2 ask-py-0.5 ask-text-[11px] ask-font-medium ask-text-primary">{t('common.active', 'Active')}</span> : null}
                    </div>
                    <div className="ask-mt-1 ask-text-[12px] ask-text-textMuted">{hasKids ? t('elements.openGroup', 'Open this group') : t('elements.applyVariation', 'Apply this variation')}</div>
                  </div>
                  <div className="ask-flex ask-h-8 ask-w-8 ask-shrink-0 ask-items-center ask-justify-center ask-rounded-full ask-bg-surfaceAlt ask-text-[15px] ask-text-textMuted ask-transition group-hover:ask-translate-x-0.5" aria-hidden>
                    {isActive && !hasKids ? '✓' : '›'}
                  </div>
                </div>
              );
              })}
            </div>
          )}

          {parametricVariationScope?.placement === 'after' ? (
            <ProductParameterControls
              context={context}
              productId={parametricProductId || undefined}
              parameterIds={parametricVariationScope.parameterIds}
              title={parametricVariationScope.scopeTitle || undefined}
              description={parametricVariationScope.scopeDescription || undefined}
              compact={parametricVariationScope.compact}
              source="element-variation-picker.parameters"
              collapsible
              defaultCollapsed
            />
          ) : null}
        </div>
      </CardContent>
    </Card>
  );
};

registerModule({
  id: 'element-variation-picker',
  label: 'Variations',
  icon: 'variations',
  displayMode: 'floating',
  supportedEngines: ['fabric', '3d'],
  render: (props) => <ElementVariationPickerModule {...props} />,
});

export default ElementVariationPickerModule;
