/** * @throws \InvalidArgumentException * * @return Collection|PotentialOffer[] */ private function collect() : Collection { /* * Create buckets for each offer, keeping duplicate offers. * Assign each product to the first applicable bucket with spaces left. * Filter the buckets to those that are full. */ return $this->components->sort(function (OfferComponent $a, OfferComponent $b) { $priceA = $a->product()->price()->asFloat(); $priceB = $b->product()->price()->asFloat(); return $priceA <=> $priceB; })->reduce(function (Collection $potentials, OfferComponent $component) { $potentials->first(function (PotentialOffer $potential) use($component) { return $potential->takeComponent($component); }); return $potentials; }, $this->potentials())->filter(function (PotentialOffer $offer) { return $offer->requirementsAreMet(); })->sort(function (PotentialOffer $offer) { return $offer->linePrice()->negative()->amount(); }); }