import { findCatalogProduct3DElementForInstance } from './product3dIdentity';

export type ElementVariationBrowserEntry = {
  key: string;
  element: any;
  objectId: string;
  elementId?: string;
  elementName: string;
  variationId?: string;
  objectSrc?: string;
  count: number;
};

const clean = (value: unknown): string => String(value ?? '').trim();

export const flattenElementCatalog = (catalogInput: any[]): any[] => {
  const out: any[] = [];
  const walk = (items: any[]) => {
    for (const element of Array.isArray(items) ? items : []) {
      if (!element || typeof element !== 'object') continue;
      out.push(element);
      walk(Array.isArray(element.children) ? element.children : []);
    }
  };
  walk(catalogInput);
  return out;
};

export const findElementInCatalog = (catalogInput: any[], identifierInput: unknown, fallbackNameInput?: unknown): any | null => {
  const catalog = flattenElementCatalog(catalogInput);
  const identifier = clean(identifierInput);
  const fallbackName = clean(fallbackNameInput);
  if (identifier) {
    const byId = catalog.find((element) => clean(element?.id) === identifier || clean(element?.familyId) === identifier);
    if (byId) return byId;
  }
  if (fallbackName) {
    const byName = catalog.filter((element) => clean(element?.name) === fallbackName);
    if (byName.length === 1) return byName[0];
  }
  return null;
};

const getElementKey = (element: any): string => clean(element?.id) || clean(element?.familyId) || clean(element?.name);

const upsertEntry = (
  entries: Map<string, ElementVariationBrowserEntry>,
  entry: ElementVariationBrowserEntry,
  preferIncoming = false,
) => {
  const previous = entries.get(entry.key);
  if (!previous) {
    entries.set(entry.key, entry);
    return;
  }
  entries.set(entry.key, {
    ...(preferIncoming ? previous : entry),
    ...(preferIncoming ? entry : previous),
    count: previous.count + 1,
  });
};

/** Build the contextual Elements list used by the persistent Variations module. */
export function buildElementVariationBrowserEntries(options: {
  elements: any[];
  activeViewId?: string | null;
  activeRenderEngineKind?: unknown;
  documentObjectsByView?: Record<string, any[]> | null;
  document3dScenesByView?: Record<string, any> | null;
}): ElementVariationBrowserEntry[] {
  const activeViewId = clean(options.activeViewId);
  if (!activeViewId) return [];

  const catalog = flattenElementCatalog(options.elements);
  const byId = new Map<string, any>(
    catalog
      .map((element): [string, any] => [clean(element?.id), element])
      .filter(([id]) => !!id),
  );
  const byUniqueName = new Map<string, any>();
  const nameCounts = new Map<string, number>();
  for (const element of catalog) {
    const name = clean(element?.name);
    if (!name) continue;
    nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1);
    byUniqueName.set(name, element);
  }
  for (const [name, count] of nameCounts) {
    if (count !== 1) byUniqueName.delete(name);
  }

  const entries = new Map<string, ElementVariationBrowserEntry>();
  const engineKind = clean(options.activeRenderEngineKind).toLowerCase();

  if (engineKind === '3d' || engineKind === 'babylon') {
    const scene = options.document3dScenesByView?.[activeViewId];
    const products = Array.isArray(scene?.products) ? scene.products : [];
    const selectedProductId = clean(scene?.selectedProductId);

    for (const product of products) {
      if (!product || typeof product !== 'object') continue;
      const catalogId = clean(product?.catalogId ?? product?.metadata?.elementId ?? product?.metadata?.catalogId);
      const productName = clean(product?.name);
      const element = findCatalogProduct3DElementForInstance(catalog, product)
        ?? (catalogId ? byId.get(catalogId) : null)
        ?? (productName ? byUniqueName.get(productName) : null)
        ?? null;
      if (!element) continue;

      const key = getElementKey(element);
      if (!key) continue;
      const productId = clean(product?.id);
      upsertEntry(entries, {
        key,
        element,
        objectId: productId ? `product:${productId}` : `product:${key}`,
        elementId: clean(element?.id) || undefined,
        elementName: clean(element?.name) || key,
        variationId: clean(product?.variationId) || undefined,
        objectSrc: '',
        count: 1,
      }, !!selectedProductId && productId === selectedProductId);
    }

    return Array.from(entries.values());
  }

  const objects = Array.isArray(options.documentObjectsByView?.[activeViewId])
    ? options.documentObjectsByView?.[activeViewId] ?? []
    : [];
  for (let index = 0; index < objects.length; index += 1) {
    const object = objects[index];
    const elementId = clean(object?._asukaElementId);
    const elementName = clean(object?._asukaElementName);
    if (!elementId && !elementName) continue;
    const element = (elementId ? byId.get(elementId) : null)
      ?? (elementName ? byUniqueName.get(elementName) : null)
      ?? null;
    if (!element) continue;

    const key = getElementKey(element);
    if (!key) continue;
    upsertEntry(entries, {
      key,
      element,
      objectId: clean(object?._asukaObjectId ?? object?.id) || `element-object-${index + 1}`,
      elementId: clean(element?.id) || elementId || undefined,
      elementName: clean(element?.name) || elementName || key,
      variationId: clean(object?._asukaElementVariationId) || undefined,
      objectSrc: clean(object?.src) || undefined,
      count: 1,
    });
  }

  return Array.from(entries.values());
}
