export type TaggedRecipe = {
  id: string;
  tags: string[];
  dietaryTags: Record<string, boolean> | null;
  createdAt: Date;
};

/** Collapses a recipe's tags + true dietary-tag keys into one flat tag list. */
function tagSet(recipe: Pick<TaggedRecipe, "tags" | "dietaryTags">): string[] {
  const dietary = Object.entries(recipe.dietaryTags ?? {})
    .filter(([, v]) => v)
    .map(([k]) => k);
  return [...recipe.tags, ...dietary];
}

/** Builds a tag → frequency map from the recipes a user has favorited/highly rated. */
export function buildPreferenceMap(likedRecipes: Array<Pick<TaggedRecipe, "tags" | "dietaryTags">>): Map<string, number> {
  const map = new Map<string, number>();
  for (const recipe of likedRecipes) {
    for (const tag of tagSet(recipe)) {
      map.set(tag, (map.get(tag) ?? 0) + 1);
    }
  }
  return map;
}

/**
 * Scores a candidate recipe by how many of its tags overlap with the user's
 * preference map (weighted by how often that tag shows up in their history).
 * Recency is used only as a tiebreaker (see rankForYou) — an empty preference
 * map scores everything 0, which the caller should treat as "fall back to
 * trending" rather than a meaningful ranking.
 */
export function scoreCandidate(candidate: Pick<TaggedRecipe, "tags" | "dietaryTags">, preferences: Map<string, number>): number {
  return tagSet(candidate).reduce((sum, tag) => sum + (preferences.get(tag) ?? 0), 0);
}

export function rankForYou<T extends TaggedRecipe>(candidates: T[], preferences: Map<string, number>): T[] {
  return [...candidates]
    .map((c) => ({ c, score: scoreCandidate(c, preferences) }))
    .sort((a, b) => b.score - a.score || b.c.createdAt.getTime() - a.c.createdAt.getTime())
    .map(({ c }) => c);
}
