// /lib/buildOverlaps.ts import type { BuilderSlotKey } from "@/types/builderSlots"; type CategoryId = BuilderSlotKey; export type OverlapChipModel = { key: string; tone: "warning" | "info"; label: string; detail?: string; }; export const COMPLETE_UPPER_CATEGORY: CategoryId = "complete-upper"; export const COMPLETE_LOWER_CATEGORY: CategoryId = "complete-lower"; export const UPPER_OVERLAP_CATEGORIES = new Set([ "upper-receiver", "bcg", "barrel", "gas-block", "gas-tube", "muzzle-device", "suppressor", "handguard", "charging-handle", ]); export const LOWER_OVERLAP_CATEGORIES = new Set([ "lower-receiver", "lower-parts-kit", "trigger", "grip", "safety-selector", "buffer", "stock", ]); export type BuildState = Partial>; function selectedCount(build: BuildState, set: Set) { let count = 0; for (const cid of set) if (build[cid]) count++; return count; } function selectedList(build: BuildState, set: Set) { const list: CategoryId[] = []; for (const cid of set) if (build[cid]) list.push(cid); return list; } /** * Chips to show on a given category row (education / “you may not need both”) */ export function getOverlapChipsForCategory( categoryId: CategoryId, build: BuildState ): OverlapChipModel[] { const chips: OverlapChipModel[] = []; const hasCompleteUpper = !!build[COMPLETE_UPPER_CATEGORY]; const hasCompleteLower = !!build[COMPLETE_LOWER_CATEGORY]; // ---- Upper overlap ---- if (categoryId === COMPLETE_UPPER_CATEGORY) { const count = selectedCount(build, UPPER_OVERLAP_CATEGORIES); if (count > 0) { chips.push({ key: "upper-complete-overlaps-subparts", tone: "warning", label: "Overlaps selected upper parts", detail: `${count} upper part${count === 1 ? "" : "s"} also selected`, }); } } if (UPPER_OVERLAP_CATEGORIES.has(categoryId) && hasCompleteUpper) { chips.push({ key: "upper-subpart-in-complete", tone: "info", label: "Included in Complete Upper", detail: "You have both selected (compare if you want)", }); } // ---- Lower overlap ---- if (categoryId === COMPLETE_LOWER_CATEGORY) { const count = selectedCount(build, LOWER_OVERLAP_CATEGORIES); if (count > 0) { chips.push({ key: "lower-complete-overlaps-subparts", tone: "warning", label: "Overlaps selected lower parts", detail: `${count} lower part${count === 1 ? "" : "s"} also selected`, }); } } if (LOWER_OVERLAP_CATEGORIES.has(categoryId) && hasCompleteLower) { chips.push({ key: "lower-subpart-in-complete", tone: "info", label: "Included in Complete Lower", detail: "You have both selected (compare if you want)", }); } return chips; } /** * Non-blocking “heads up” messages when selecting a part. * (Use for toasts / shareStatus strip — no auto-removal) */ export function getSelectionHints( nextCategoryId: CategoryId, build: BuildState ): string[] { const hints: string[] = []; if (nextCategoryId === COMPLETE_UPPER_CATEGORY) { const overlaps = selectedList(build, UPPER_OVERLAP_CATEGORIES); if (overlaps.length > 0) { hints.push( "Heads up: Complete Upper overlaps with some selected upper parts. Nothing was removed — compare options and keep what you want." ); } } if (UPPER_OVERLAP_CATEGORIES.has(nextCategoryId) && build[COMPLETE_UPPER_CATEGORY]) { hints.push( "Heads up: You also have a Complete Upper selected. Nothing was removed — compare options and keep what you want." ); } if (nextCategoryId === COMPLETE_LOWER_CATEGORY) { const overlaps = selectedList(build, LOWER_OVERLAP_CATEGORIES); if (overlaps.length > 0) { hints.push( "Heads up: Complete Lower overlaps with some selected lower parts. Nothing was removed — compare options and keep what you want." ); } } if (LOWER_OVERLAP_CATEGORIES.has(nextCategoryId) && build[COMPLETE_LOWER_CATEGORY]) { hints.push( "Heads up: You also have a Complete Lower selected. Nothing was removed — compare options and keep what you want." ); } return hints; }