"use client"; import { useMemo } from "react"; import { mergeCompactCardIdsWithPinnedSelected } from "../../../../lib/create/methodCardDisplayOrder"; import { formatRecommendationWhyLine } from "../../../../lib/create/formatRecommendationWhyLine"; import { deriveCompactCards, groupAnswersForRecommendations, type RecommendationSection, } from "./useFacetRecommendations"; import { deckData } from "../../../../lib/create/deckRecommend"; import { useCreateFlow } from "../context/CreateFlowContext"; import { useMessages } from "../../../contexts/MessagesContext"; type MethodEntry = { id: string; label: string; supportText: string }; function orderByIds( methods: readonly T[], orderedIds: readonly string[], ): T[] { const byId = new Map(methods.map((method) => [method.id, method])); const ordered = orderedIds.flatMap((id) => { const method = byId.get(id); return method ? [method] : []; }); const seen = new Set(ordered.map((method) => method.id)); return [...ordered, ...methods.filter((method) => !seen.has(method.id))]; } /** * Deck order and the compact stack come from the job rule. Compact shows * at most seven recommended cards. Selected cards still pin into the stack. */ export function useMethodCardDeckOrdering( section: RecommendationSection, methods: readonly MethodEntry[], selectedIds: readonly string[], ) { const { state } = useCreateFlow(); const copy = useMessages().create.customRule.recommendations; const compactLimit = deckData(section).config.displayLimit; const answers = useMemo( () => groupAnswersForRecommendations(state), [state], ); const deck = useMemo( () => deriveCompactCards(section, answers, compactLimit), [section, answers, compactLimit], ); const rankedMethods = useMemo( () => orderByIds(methods, deck.orderedIds), [methods, deck.orderedIds], ); const selectionShowcaseActive = selectedIds.length > 0; const { recommendedIds } = deck; const compactCardIds = useMemo( () => mergeCompactCardIdsWithPinnedSelected( rankedMethods.map((method) => method.id), deck.compactCardIds, selectedIds, selectionShowcaseActive, compactLimit, ), [ rankedMethods, deck.compactCardIds, selectedIds, selectionShowcaseActive, compactLimit, ], ); const compactIdSet = useMemo( () => new Set(compactCardIds), [compactCardIds], ); const sampleCards = useMemo( () => rankedMethods.map((entry) => { const recommendation = deck.recommendedById.get(entry.id); const whyLine = recommendation && recommendation.tag !== "everyRuleHas" ? formatRecommendationWhyLine(recommendation.reason, answers, copy) : ""; const alternativeId = recommendation?.alternative; const alternativeMethod = alternativeId ? rankedMethods.find((method) => method.id === alternativeId) ?? methods.find((method) => method.id === alternativeId) : undefined; return { id: entry.id, label: entry.label, supportText: entry.supportText, compactSupportText: whyLine && compactIdSet.has(entry.id) ? whyLine : undefined, recommended: recommendedIds.has(entry.id), alternative: alternativeMethod && compactIdSet.has(entry.id) ? { id: alternativeMethod.id, label: alternativeMethod.label } : undefined, }; }), [ answers, compactIdSet, copy, deck.recommendedById, methods, rankedMethods, recommendedIds, ], ); const methodById = useMemo( () => new Map(rankedMethods.map((entry) => [entry.id, entry])), [rankedMethods], ); return { rankedMethods, displayMethods: rankedMethods, compactCardIds, recommendedIds, sampleCards, methodById, showDeckLine: !deck.fallback, compactLimit, recommendationsReady: true, }; }