{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "receivables-ar",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "Invoice combinations summing exactly to a lump sum.",
  "files": [
    {
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport type { MoneyValue } from \"@/lib/ledger/money.types\";\nimport { Button } from \"@/components/ledger/button\";\nimport { SparklesIcon } from \"@/components/ledger/icons\";\nimport { Money } from \"@/components/ledger/money\";\n\nexport type InvoiceCandidate = { id: string; number: string; balance: number; age: number };\n\nexport interface PaymentSuggestionProps {\n  candidates: InvoiceCandidate[];\n  className?: string;\n  lumpSum: MoneyValue;\n  onAccept?: (ids: string[]) => void;\n}\n\n/**\n * Given a lump sum, propose the sets of open invoices that sum to it\n * exactly. Multiple valid combinations are ranked oldest-first, and the\n * basis is shown — not a bare confidence score.\n */\nexport function PaymentSuggestion({ lumpSum, candidates, onAccept, className }: PaymentSuggestionProps) {\n  const target = Number(lumpSum.amount);\n  const combos = React.useMemo(() => exactSubsets(candidates, target).slice(0, 3), [candidates, target]);\n\n  if (combos.length === 0) {\n    return (\n      <p className={cn(\"text-body text-text-secondary\", className)}>\n        No combination of open invoices sums to <Money size=\"sm\" tone=\"inherit\" value={lumpSum} /> exactly.\n      </p>\n    );\n  }\n\n  return (\n    <div className={cn(\"space-y-2\", className)}>\n      <h3 className=\"flex items-center gap-2 text-label font-medium text-text-secondary\">\n        <SparklesIcon aria-hidden=\"true\" className=\"size-3.5 text-accent\" />\n        Combinations summing to <Money size=\"xs\" tone=\"inherit\" value={lumpSum} /> exactly\n      </h3>\n      <ul className=\"space-y-2\">\n        {combos.map((combo, i) => (\n          <li\n            className=\"flex flex-wrap items-center gap-3 rounded-md border border-border bg-surface p-3\"\n            key={i}\n          >\n            <span className=\"flex-1\">\n              <span className=\"reference-numeric block text-body\">\n                {combo.map((c) => c.number).join(\" + \")}\n              </span>\n              <span className=\"block text-caption text-text-secondary\">\n                {combo.length} invoices · oldest {Math.max(...combo.map((c) => c.age))} days\n              </span>\n            </span>\n            <Button onClick={() => onAccept?.(combo.map((c) => c.id))} size=\"sm\" variant=\"primary\">\n              Apply this set\n            </Button>\n          </li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n\n/** Small-n exact subset search, ranked oldest-first. */\nfunction exactSubsets(candidates: InvoiceCandidate[], target: number): InvoiceCandidate[][] {\n  const results: InvoiceCandidate[][] = [];\n  const sorted = [...candidates].sort((a, b) => b.age - a.age);\n  const limit = Math.min(sorted.length, 16);\n\n  const walk = (index: number, running: number, picked: InvoiceCandidate[]) => {\n    if (results.length >= 8) return;\n    if (running === target && picked.length > 0) {\n      results.push([...picked]);\n      return;\n    }\n    if (running > target || index >= limit) return;\n    picked.push(sorted[index]);\n    walk(index + 1, running + sorted[index].balance, picked);\n    picked.pop();\n    walk(index + 1, running, picked);\n  };\n\n  walk(0, 0, []);\n  return results;\n}\n",
      "path": "components/ledger/payment-suggestion.tsx",
      "target": "components/ledger/payment-suggestion.tsx",
      "type": "registry:component"
    }
  ],
  "name": "payment-suggestion",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/button",
    "@lopifi/cn",
    "@lopifi/icons",
    "@lopifi/money",
    "@lopifi/money.types"
  ],
  "title": "PaymentSuggestion",
  "type": "registry:component"
}
