<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

namespace App\Tools\Support;

use App\Tools\Exceptions\ToolInvalidInputException;
use DateTimeImmutable;
use DateTimeZone;

/**
 * Tool 日期区间。两端均为闭区间日历日（inclusive/inclusive），跨度按日历日差计算。
 */
final class ToolDateRange
{
    public const DEFAULT_MAX_SPAN_DAYS = 366;

    /**
     * @return array{from: ?string, until: ?string}
     */
    public static function parseOptional(
        mixed $from,
        mixed $until,
        string $fromField,
        string $untilField,
        int $maxSpanDays = self::DEFAULT_MAX_SPAN_DAYS,
    ): array {
        $fromDate = self::optionalDate($from, $fromField);
        $untilDate = self::optionalDate($until, $untilField);

        if ($fromDate !== null && $untilDate !== null) {
            if ($fromDate > $untilDate) {
                throw ToolInvalidInputException::forField($fromField, 'from must not be after to.');
            }

            $start = DateTimeImmutable::createFromFormat('!'.ToolBusinessDate::FORMAT, $fromDate, new DateTimeZone('UTC'));
            $end = DateTimeImmutable::createFromFormat('!'.ToolBusinessDate::FORMAT, $untilDate, new DateTimeZone('UTC'));

            if (! $start instanceof DateTimeImmutable || ! $end instanceof DateTimeImmutable) {
                throw ToolInvalidInputException::forField($fromField, 'Date must use Y-m-d.');
            }

            $span = (int) $start->diff($end)->format('%a');

            if ($span > $maxSpanDays) {
                throw ToolInvalidInputException::forField($untilField, sprintf(
                    'Date range must not exceed %d days.',
                    $maxSpanDays,
                ));
            }
        }

        return [
            'from' => $fromDate,
            'until' => $untilDate,
        ];
    }

    private static function optionalDate(mixed $value, string $field): ?string
    {
        if ($value === null) {
            return null;
        }

        if (! is_string($value) && ! is_numeric($value)) {
            throw ToolInvalidInputException::forField($field, 'Date must use Y-m-d.');
        }

        $trimmed = trim((string) $value);

        if ($trimmed === '') {
            return null;
        }

        return ToolBusinessDate::parse($trimmed, $field);
    }
}
