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

const clonePlain = <T>(value: T): T => {
  if (value == null) return value;
  try {
    if (typeof structuredClone === 'function') return structuredClone(value);
  } catch {}
  try { return JSON.parse(JSON.stringify(value)); } catch { return value; }
};

const asList = (value: unknown): string[] => (Array.isArray(value) ? value : String(value ?? '').split(','))
  .map((item) => String(item ?? '').trim())
  .filter(Boolean);

const slug = (value: unknown, fallback = 'part'): string => {
  const out = String(value ?? '')
    .trim()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .replace(/[^a-zA-Z0-9_-]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .toLowerCase();
  return out || fallback;
};

const humanize = (value: unknown): string => String(value ?? '')
  .replace(/^.*::/, '')
  .replace(/[_-]+/g, ' ')
  .replace(/\s+/g, ' ')
  .trim()
  .replace(/\b\w/g, (letter) => letter.toUpperCase());

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

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

const normalizePart = (part: any, index: number): AsukaProduct3DPart => {
  const meshNames = asList(part?.meshNames ?? part?.meshName ?? part?.targetMeshIds ?? part?.targetMeshId);
  const id = String(part?.id ?? meshNames[0] ?? `part-${index + 1}`).trim() || `part-${index + 1}`;
  return {
    ...(part && typeof part === 'object' ? clonePlain(part) : {}),
    id,
    label: String(part?.label ?? part?.name ?? humanize(id) ?? id).trim() || id,
    meshNames,
    materialNames: asList(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,
  };
};

const normalizeParts = (value: unknown): AsukaProduct3DPart[] => (Array.isArray(value) ? value : [])
  .map(normalizePart)
  .filter((part) => !!String(part.id ?? '').trim());

const getSourceMaterial = (variation: any, target: string): any | null => {
  const product3d = variation?.product3d && typeof variation.product3d === 'object' ? variation.product3d : {};
  const materials = product3d?.materials && typeof product3d.materials === 'object' ? product3d.materials : {};
  const sourceTarget = String(product3d?.materialTarget ?? '').trim();
  return (
    (target && materials[target] && typeof materials[target] === 'object' ? materials[target] : null)
    ?? (sourceTarget && materials[sourceTarget] && typeof materials[sourceTarget] === 'object' ? materials[sourceTarget] : null)
    ?? (materials.product && typeof materials.product === 'object' ? materials.product : null)
    ?? Object.values(materials).find((item: any) => item && typeof item === 'object')
    ?? null
  );
};

const ensureOneActive = (items: any[]): any[] => {
  let found = false;
  const next = (Array.isArray(items) ? items : []).map((item) => {
    const active = item?.active === true && !found;
    if (active) found = true;
    return { ...(item ?? {}), active };
  });
  if (!found && next[0]) next[0] = { ...next[0], active: true };
  return next;
};

const scopeVariationToPart = (
  variationInput: any,
  part: AsukaProduct3DPart,
  path: number[],
  preserveIds: boolean,
): ElementVariation => {
  const variation = clonePlain(variationInput ?? {}) as any;
  const partId = String(part.id ?? '').trim() || 'product';
  const partSlug = slug(partId);
  const target = String(part.materialTarget ?? partId).trim() || partId;
  const targetMeshIds = asList(part.meshNames);
  const children = (Array.isArray(variation?.variations) ? variation.variations : [])
    .map((child: any, index: number) => scopeVariationToPart(child, part, [...path, index], preserveIds));
  const sourceMaterial = getSourceMaterial(variation, target);
  const shouldOwnMaterial = children.length === 0 || sourceMaterial !== null;
  const rawId = String(variation?.id ?? `variation-${path.map((index) => index + 1).join('-')}`).trim()
    || `variation-${path.map((index) => index + 1).join('-')}`;
  const baseId = rawId.replace(/-part-[a-z0-9_-]+$/i, '');
  const nextProduct3D: any = {
    partId,
    materialTarget: target,
  };
  if (shouldOwnMaterial) {
    const material = sourceMaterial
      ? clonePlain(sourceMaterial)
      : { label: String(variation?.name ?? variation?.label ?? 'Default'), useNativeMaterial: true };
    nextProduct3D.materials = {
      [target]: {
        ...material,
        label: String(material?.label ?? variation?.name ?? part.label ?? target),
        targetMeshIds,
        metadata: {
          ...((material?.metadata && typeof material.metadata === 'object') ? material.metadata : {}),
          asukaProduct3DPartId: partId,
          asukaProduct3DPartLabel: String(part.label ?? partId),
          targetMeshIds,
        },
      },
    };
  }
  return {
    ...variation,
    id: preserveIds ? rawId : `${baseId}-part-${partSlug}`,
    product3d: nextProduct3D,
    metadata: {
      ...((variation?.metadata && typeof variation.metadata === 'object') ? variation.metadata : {}),
      asukaProduct3DPartId: partId,
      asukaProduct3DPartLabel: String(part.label ?? partId),
    },
    variations: ensureOneActive(children),
  } as ElementVariation;
};

const createDefaultVariation = (part: AsukaProduct3DPart): ElementVariation => {
  const partId = String(part.id ?? '').trim() || 'product';
  const partSlug = slug(partId);
  const target = String(part.materialTarget ?? partId).trim() || partId;
  const targetMeshIds = asList(part.meshNames);
  return {
    id: `default-part-${partSlug}`,
    name: 'Default',
    active: true,
    price: 0,
    product3d: {
      partId,
      materialTarget: target,
      materials: {
        [target]: {
          label: 'Default',
          useNativeMaterial: true,
          targetMeshIds,
          metadata: {
            asukaProduct3DPartId: partId,
            asukaProduct3DPartLabel: String(part.label ?? partId),
            targetMeshIds,
          },
        },
      },
    },
    metadata: {
      asukaProduct3DPartId: partId,
      asukaProduct3DPartLabel: String(part.label ?? partId),
      asukaProduct3DAutoDefaultVariation: true,
    },
    variations: [],
  } as any;
};

const createPartGroup = (
  part: AsukaProduct3DPart,
  templates: any[],
  existing?: any,
): ElementVariation => {
  const partId = String(part.id ?? '').trim() || 'product';
  const target = String(part.materialTarget ?? partId).trim() || partId;
  const sourceChildren = Array.isArray(existing?.variations) && existing.variations.length
    ? existing.variations
    : templates;
  const children = sourceChildren.length
    ? sourceChildren.map((variation: any, index: number) => scopeVariationToPart(variation, part, [index], Boolean(existing)))
    : [createDefaultVariation(part)];
  return {
    ...(existing && typeof existing === 'object' ? clonePlain(existing) : {}),
    id: String(existing?.id ?? `part-group-${slug(partId)}`).trim() || `part-group-${slug(partId)}`,
    name: String(existing?.name ?? part.label ?? humanize(partId) ?? partId).trim() || partId,
    active: false,
    price: existing?.price ?? 0,
    image: typeof existing?.image === 'string' ? existing.image : '',
    icon: typeof existing?.icon === 'string' ? existing.icon : '',
    product3d: {
      ...((existing?.product3d && typeof existing.product3d === 'object') ? clonePlain(existing.product3d) : {}),
      partGroup: true,
      partId,
      materialTarget: target,
    },
    metadata: {
      ...((existing?.metadata && typeof existing.metadata === 'object') ? clonePlain(existing.metadata) : {}),
      asukaProduct3DPartGroup: true,
      asukaProduct3DPartId: partId,
      asukaProduct3DPartLabel: String(part.label ?? partId),
    },
    variations: ensureOneActive(children),
  } as any;
};

/**
 * Ensures that every editable part of a multi-mesh Product 3D is represented by
 * a top-level variation group. This keeps the element editor, Elements module,
 * runtime variation picker and imported catalogs on the same canonical shape.
 */
export function ensureProduct3DPartGroups<T extends AsukaElement | any>(elementInput: T): T {
  if (!elementInput || typeof elementInput !== 'object') return elementInput;
  const element: any = clonePlain(elementInput);
  if (Array.isArray(element.children)) element.children = element.children.map(ensureProduct3DPartGroups);
  const isProduct3D = String(element?.type ?? '').trim() === 'product-3d' || !!element?.product3d;
  if (!isProduct3D) return element as T;

  const parts = normalizeParts(element?.product3d?.parts);
  element.product3d = { ...(element.product3d ?? {}), parts };
  if (parts.length <= 1) return element as T;

  const variations = Array.isArray(element?.variations) ? element.variations : [];
  const existingGroups = variations.filter(isProduct3DPartGroup);
  const templates = variations.filter((variation: any) => !isProduct3DPartGroup(variation));
  const firstExistingChildren = existingGroups.find((group: any) => Array.isArray(group?.variations) && group.variations.length)?.variations ?? [];
  const safeTemplates = templates.length ? templates : firstExistingChildren;

  element.variations = parts
    .filter((part) => part.editable !== false && part.visible !== false)
    .map((part) => {
      const existing = existingGroups.find((group: any) => getPartId(group) === String(part.id ?? '').trim());
      return createPartGroup(part, safeTemplates, existing);
    });
  return element as T;
}

/** Normalize a complete recursive element catalog. */
export function ensureProduct3DPartGroupsDeep(elements: any[] | null | undefined): any[] {
  return (Array.isArray(elements) ? elements : []).map(ensureProduct3DPartGroups);
}
