<?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\Enums\ToolPeriodPreset;
use App\Tools\Exceptions\ToolInvalidInputException;
use App\Tools\ToolExecutionContext;
use DateTimeImmutable;
use DateTimeZone;

/**
 * 把 period_preset 解析为闭区间 Y-m-d。近 N 天与站点 subDays(N) 对齐（[today-N, today]）。
 */
final class ToolPeriodPresetResolver
{
    public const INPUT_FIELD = 'period_preset';

    /**
     * Schema 校验通过后展开预设。已写入的绝对日期与预设互斥。
     *
     * @param  array<string, mixed>  $input
     * @param  array<string, mixed>  $inputSchema
     * @return array<string, mixed>
     */
    public static function applyToValidatedInput(
        array $input,
        array $inputSchema,
        ToolExecutionContext $context,
    ): array {
        if (! array_key_exists(self::INPUT_FIELD, $input)) {
            return $input;
        }

        $raw = $input[self::INPUT_FIELD];
        unset($input[self::INPUT_FIELD]);

        if (! is_string($raw) || trim($raw) === '') {
            return $input;
        }

        $preset = ToolPeriodPreset::tryFrom(trim($raw));

        if (! $preset instanceof ToolPeriodPreset) {
            throw ToolInvalidInputException::forField(self::INPUT_FIELD, 'Unknown period_preset.');
        }

        $fields = self::rangeFields($inputSchema);

        if ($fields === null) {
            throw ToolInvalidInputException::forField(self::INPUT_FIELD, 'period_preset is not supported.');
        }

        [$fromField, $untilField] = $fields;

        if (self::hasDate($input[$fromField] ?? null) || self::hasDate($input[$untilField] ?? null)) {
            throw ToolInvalidInputException::forField(
                self::INPUT_FIELD,
                'Do not combine period_preset with explicit range dates.',
            );
        }

        $window = self::resolve($preset, ToolBusinessDate::today($context->timezone));
        $input[$fromField] = $window['from'];
        $input[$untilField] = $window['until'];

        return $input;
    }

    /**
     * @return array{from: string, until: string}
     */
    public static function resolve(ToolPeriodPreset $preset, string $today): array
    {
        $day = DateTimeImmutable::createFromFormat('!'.ToolBusinessDate::FORMAT, $today, new DateTimeZone('UTC'));

        if (! $day instanceof DateTimeImmutable || $day->format(ToolBusinessDate::FORMAT) !== $today) {
            throw ToolInvalidInputException::forField(self::INPUT_FIELD, 'Date must use Y-m-d.');
        }

        return match ($preset) {
            ToolPeriodPreset::Today => [
                'from' => $today,
                'until' => $today,
            ],
            ToolPeriodPreset::ThisWeek => [
                'from' => self::mondayOf($day)->format(ToolBusinessDate::FORMAT),
                'until' => $today,
            ],
            ToolPeriodPreset::ThisMonth => [
                'from' => $day->modify('first day of this month')->format(ToolBusinessDate::FORMAT),
                'until' => $today,
            ],
            ToolPeriodPreset::LastMonth => [
                'from' => $day->modify('first day of last month')->format(ToolBusinessDate::FORMAT),
                'until' => $day->modify('last day of last month')->format(ToolBusinessDate::FORMAT),
            ],
            ToolPeriodPreset::LastThreeMonths => [
                'from' => $day->modify('first day of this month')->modify('-3 months')->format(ToolBusinessDate::FORMAT),
                'until' => $day->modify('first day of this month')->modify('-1 day')->format(ToolBusinessDate::FORMAT),
            ],
            ToolPeriodPreset::Last7Days => [
                'from' => $day->modify('-7 days')->format(ToolBusinessDate::FORMAT),
                'until' => $today,
            ],
            ToolPeriodPreset::Last30Days => [
                'from' => $day->modify('-30 days')->format(ToolBusinessDate::FORMAT),
                'until' => $today,
            ],
            ToolPeriodPreset::Last90Days => [
                'from' => $day->modify('-90 days')->format(ToolBusinessDate::FORMAT),
                'until' => $today,
            ],
            ToolPeriodPreset::YearToDate => [
                'from' => $day->format('Y').'-01-01',
                'until' => $today,
            ],
        };
    }

    /**
     * @return array<string, array{from: string, until: string}>
     */
    public static function allWindows(string $today): array
    {
        $windows = [];

        foreach (ToolPeriodPreset::cases() as $preset) {
            $windows[$preset->value] = self::resolve($preset, $today);
        }

        return $windows;
    }

    /**
     * @param  array<string, mixed>  $inputSchema
     * @return array{0: string, 1: string}|null
     */
    private static function rangeFields(array $inputSchema): ?array
    {
        $properties = $inputSchema['properties'] ?? null;

        if (! is_array($properties)) {
            return null;
        }

        if (array_key_exists('date_from', $properties) && array_key_exists('date_until', $properties)) {
            return ['date_from', 'date_until'];
        }

        if (array_key_exists('business_time_from', $properties) && array_key_exists('business_time_until', $properties)) {
            return ['business_time_from', 'business_time_until'];
        }

        return null;
    }

    private static function mondayOf(DateTimeImmutable $day): DateTimeImmutable
    {
        $isoWeekday = (int) $day->format('N');

        return $day->modify('-'.($isoWeekday - 1).' days');
    }

    private static function hasDate(mixed $value): bool
    {
        if ($value === null) {
            return false;
        }

        if (! is_string($value) && ! is_numeric($value)) {
            return true;
        }

        return trim((string) $value) !== '';
    }
}
