const DEFAULT_PREVIEW_PROGRESS = 0.48;

function clamp01(value) {
  return Math.min(1, Math.max(0, Number(value) || 0));
}

function representativeTime(recipe) {
  const phase = clamp01(recipe?.reducedMotion?.phase ?? 0.25);
  const cycleMs =
    Number(recipe?.timeline?.macroCycleMs) ||
    Number(recipe?.timeline?.durationMs) ||
    4000;
  return (Math.max(1, cycleMs) / 1000) * phase;
}

/**
 * One-shot recipes may advance to a different state while their card is active.
 * Atlas previews retain the requested recipe so their identity and resting frame
 * always match the card label.
 */
export function createThumbnailRecipe(recipe) {
  if (!recipe?.timeline?.next) return recipe;
  return {
    ...recipe,
    timeline: {
      ...recipe.timeline,
      next: null,
    },
  };
}

export function shouldAnimateThumbnail(
  id,
  {
    selectedId = null,
    mainRunning = false,
    hoveredIds = new Set(),
    focusedIds = new Set(),
  } = {},
) {
  return (
    (id === selectedId && mainRunning) ||
    hoveredIds.has(id) ||
    focusedIds.has(id)
  );
}

/**
 * Render a deterministic, authored representative phase without leaving the
 * preview in reduced-motion mode. JoanGlyphEngine maps time zero to each
 * recipe's reducedMotion.phase while reduced motion is enabled.
 */
export function renderThumbnailRestFrame(
  preview,
  { progress = DEFAULT_PREVIEW_PROGRESS } = {},
) {
  if (!preview || preview.destroyed) return null;

  const nextProgress = clamp01(progress);
  if (Math.abs(preview.progress - nextProgress) > 1e-6) {
    preview.setProgress(nextProgress);
  }

  // Neighbor-propagation previews otherwise retain their previous gate buffer,
  // making the supposedly static pose depend on how long the card was hovered.
  preview.stateElapsed = representativeTime(preview.currentRecipe);
  preview.gates?.fill(0);
  preview.neighborBuffer?.fill(0);
  preview.dwell?.fill(0);

  const previousReducedMotion = preview.reducedMotion;
  preview.reducedMotion = true;
  let stats;
  try {
    stats = preview.renderOnce(0);
  } finally {
    preview.reducedMotion = previousReducedMotion;
  }

  if (preview.canvas?.dataset) preview.canvas.dataset.previewState = "ready";
  return stats;
}
