public static function distribute(int $tiles, int $columns, float $tallRateTarget, float $wideRateTarget)
    {
        // 1. Approximate the number of rows where all tiles would fit
        $tallTilesTarget = (int)round($tallRateTarget * $tiles);
        $wideTilesTarget = (int)round($wideRateTarget * $tiles);
        $smallTilesTarget = $tiles - $tallTilesTarget - $wideTilesTarget;

        $cellsTarget = 2 * $tallTilesTarget + 2 * $wideTilesTarget + $smallTilesTarget;
        $rows = (int)round($cellsTarget / $columns);

        // Check $columns is reasonable
        if ($columns >= $cellsTarget || $tallTilesTarget > 0 && $rows < 2) {
            throw new InvalidArgumentException("Cannot fill {$tiles} items in {$columns} columns");
        }

        // 2. Adjust number of tiles to fit exactly the number of available cells
        $distribution = Distribution::fromLargeTiles($tiles, $tallTilesTarget, $wideTilesTarget);
        while (($cells = $distribution->getCells()) !== $rows * $columns) {
            // A. Not enough cells => decrement the number of tall/wide tiles
            if ($cells > $rows * $columns) {
                $distribution->decrementLargeTiles();
            // B. Too many cells => increment the number of tall/wide tiles
            } else {
                $distribution->incrementLargeTiles();
            }
        }

        return [$distribution, new Grid($rows, $columns)];
    }