<?php

namespace App\Support;

use Illuminate\Support\Carbon;
use InvalidArgumentException;

class DateRange
{
    public const MAX_DAYS = 731;

    public function __construct(
        public readonly Carbon $from,
        public readonly Carbon $to,
    ) {
        if ($to->lessThan($from)) {
            throw new InvalidArgumentException('The end of a date range cannot precede its start.');
        }
    }

    public static function preset(string $preset): self
    {
        $now = Carbon::now();

        return match ($preset) {
            'today' => new self($now->copy()->startOfDay(), $now->copy()->endOfDay()),
            'week' => new self($now->copy()->startOfWeek(), $now->copy()->endOfWeek()),
            'month' => new self($now->copy()->startOfMonth(), $now->copy()->endOfMonth()),
            'year' => new self($now->copy()->startOfYear(), $now->copy()->endOfYear()),
            'all' => new self(Carbon::createFromTimestamp(0), $now->copy()->endOfDay()),
            default => throw new InvalidArgumentException("Unknown date range preset [{$preset}]."),
        };
    }

    /**
     * @return array<string, string>
     */
    public static function presets(): array
    {
        return [
            'today' => 'Today',
            'week' => 'This Week',
            'month' => 'This Month',
            'year' => 'This Year',
            'all' => 'All Time',
        ];
    }

    public function isBucketableByDay(): bool
    {
        return $this->from->copy()->startOfDay()->diffInDays($this->to->copy()->startOfDay()) < self::MAX_DAYS;
    }

    /**
     * Every day the range touches, as Y-m-d, so callers can zero-fill empty buckets.
     *
     * @return array<int, string>
     */
    public function days(): array
    {
        $days = [];
        $cursor = $this->from->copy()->startOfDay();
        $last = $this->to->copy()->startOfDay();

        if (! $this->isBucketableByDay()) {
            throw new InvalidArgumentException(
                'A range wider than '.self::MAX_DAYS.' days cannot be bucketed by day; bucket by month instead.'
            );
        }

        while ($cursor->lessThanOrEqualTo($last)) {
            $days[] = $cursor->toDateString();
            $cursor->addDay();
        }

        return $days;
    }
}
