<?php

declare(strict_types=1);

namespace App\Support;

use App\Business\Inventory\InventoryRefinementService;
use App\Business\Inventory\InventorySerialNumberService;
use App\Models\DberpProduct;
use Closure;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Illuminate\Validation\ValidationException;

/**
 * 单据明细序列号录入：与录入数量条数一致（启用序列号管理的商品）。
 */
final class DberpDocumentLineSerialNumberFormFields
{
    /**
     * @return Closure(Get): Closure(string, mixed, Closure): void
     */
    public static function serialNumbersQuantityValidationRule(
        Closure $resolveProduct,
        string $quantityField,
    ): Closure {
        return function (Get $get) use ($resolveProduct, $quantityField): Closure {
            return function (string $attribute, mixed $value, Closure $fail) use ($get, $resolveProduct, $quantityField): void {
                $product = $resolveProduct($get);

                if (! $product instanceof DberpProduct) {
                    return;
                }

                try {
                    self::assertMatchQuantity(
                        $product,
                        (string) ($get($quantityField) ?? '0'),
                        is_array($value) ? $value : [],
                    );
                } catch (ValidationException $exception) {
                    $messages = $exception->errors()['serial_numbers'] ?? [];
                    $fail((string) ($messages[0] ?? __('dberp.errors.inventory_serial_number_required')));
                }
            };
        };
    }

    /**
     * @deprecated 请使用 {@see DberpDocumentLineInboundSerialNumberFormFields::inboundSerialNumberFields()}
     */
    public static function inboundSerialNumbersTagsInput(
        Closure $resolveProduct,
        string $quantityField,
        string $label = 'dberp.fields.serial_numbers',
    ): TagsInput {
        $fields = DberpDocumentLineInboundSerialNumberFormFields::inboundSerialNumberFields(
            $resolveProduct,
            $quantityField,
            $label,
        );

        return $fields[1];
    }

    /**
     * 将 Tags 中误合并的「逗号/换行一整段」拆成多条序列号。
     *
     * @param  array<int, string>|string|null  $entries
     * @return list<string>
     */
    public static function expandSerialNumberEntries(array|string|null $entries): array
    {
        if ($entries === null || $entries === []) {
            return [];
        }

        $service = app(InventorySerialNumberService::class);
        $expanded = [];

        foreach (is_array($entries) ? $entries : [$entries] as $entry) {
            foreach ($service->normalizeSerialNumbers($entry) as $serialNo) {
                $expanded[] = $serialNo;
            }
        }

        return $service->normalizeSerialNumbers($expanded);
    }

    public static function configureTagsInput(
        TagsInput $field,
        Closure $resolveProduct,
        string $quantityField,
    ): TagsInput {
        return $field
            ->live()
            ->rules([
                self::serialNumbersQuantityValidationRule($resolveProduct, $quantityField),
            ])
            ->afterStateUpdated(function (mixed $state, Set $set, Get $get) use ($resolveProduct, $quantityField): void {
                if (! is_array($state)) {
                    return;
                }

                $expanded = self::expandSerialNumberEntries($state);
                $maxCount = self::maxSerialCountForQuantity($resolveProduct, $quantityField)($get);
                $trimmed = self::trimSerialsToMaxCount($expanded, $maxCount);

                if ($trimmed !== $state) {
                    $set('serial_numbers', $trimmed);
                }
            });
    }

    public static function documentLineQuantityTrimsSerialNumbers(
        TextInput $quantityField,
        Closure $resolveProduct,
    ): TextInput {
        $quantityFieldName = $quantityField->getName();

        return $quantityField->afterStateUpdated(function (mixed $state, Set $set, Get $get) use ($resolveProduct, $quantityFieldName): void {
            $serials = $get('serial_numbers');

            if (! is_array($serials) || $serials === []) {
                return;
            }

            $maxCount = self::maxSerialCountForQuantity($resolveProduct, $quantityFieldName)($get);
            $trimmed = self::trimSerialsToMaxCount(self::expandSerialNumberEntries($serials), $maxCount);

            if ($trimmed !== $serials) {
                $set('serial_numbers', $trimmed);
            }
        });
    }

    /**
     * @param  array<int, string>|string|null  $serialNumbers
     */
    public static function assertMatchQuantity(
        DberpProduct $product,
        string $quantity,
        array|string|null $serialNumbers,
    ): void {
        app(InventoryRefinementService::class)->assertDocumentLineSerialNumbersMatchQuantity(
            $product,
            $quantity,
            $serialNumbers,
        );
    }

    /**
     * 根据当前表单数量限制可录入序列号条数；无法解析时返回 null（交由保存校验提示）。
     */
    public static function maxSerialCountForQuantity(
        Closure $resolveProduct,
        string $quantityField,
    ): ?Closure {
        return function (Get $get) use ($resolveProduct, $quantityField): ?int {
            $product = $resolveProduct($get);

            if (! $product instanceof DberpProduct || ! $product->manages_serial_number) {
                return null;
            }

            $formattedQuantity = number_format((float) ($get($quantityField) ?? 0), 4, '.', '');

            if (bccomp($formattedQuantity, '0', 4) !== 1) {
                return null;
            }

            if (bccomp($formattedQuantity, (string) (int) (float) $formattedQuantity, 4) !== 0) {
                return null;
            }

            return (int) (float) $formattedQuantity;
        };
    }

    /**
     * @param  list<string>  $serials
     * @return list<string>
     */
    public static function trimSerialsToMaxCount(array $serials, ?int $maxCount): array
    {
        $normalized = app(InventorySerialNumberService::class)->normalizeSerialNumbers($serials);

        if ($maxCount === null || $maxCount < 0) {
            return $normalized;
        }

        if ($maxCount === 0) {
            return [];
        }

        return array_slice($normalized, 0, $maxCount);
    }
}
