<?php

declare(strict_types=1);

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

namespace App\Support;

use App\Business\Inventory\InventorySerialNumberService;
use App\Models\DberpInventoryBatch;
use App\Models\DberpProduct;
use Closure;
use Filament\Forms\Components\Field;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Tables\Columns\Column;
use Illuminate\Database\Eloquent\Model;

/**
 * 商品库存精细化（批次 / 序列号 / 效期）表单与列表显隐。
 */
final class DberpProductInventoryRefinementFormFields
{
    public static function productManagesBatch(?DberpProduct $product): bool
    {
        return $product instanceof DberpProduct && $product->manages_batch;
    }

    public static function productManagesSerial(?DberpProduct $product): bool
    {
        return $product instanceof DberpProduct && $product->manages_serial_number;
    }

    public static function productManagesExpiry(?DberpProduct $product): bool
    {
        return $product instanceof DberpProduct && $product->manages_expiry;
    }

    public static function productFromLineRecord(Model $record): ?DberpProduct
    {
        $product = $record->relationLoaded('product') ? $record->product : $record->product()->first();

        return $product instanceof DberpProduct ? $product : null;
    }

    /**
     * @return Closure(Get): bool
     */
    public static function visibleWhenBatch(Closure $resolveProduct): Closure
    {
        return fn (Get $get): bool => self::productManagesBatch($resolveProduct($get));
    }

    /**
     * @return Closure(Get): bool
     */
    public static function visibleWhenSerial(Closure $resolveProduct): Closure
    {
        return fn (Get $get): bool => self::productManagesSerial($resolveProduct($get));
    }

    /**
     * @return Closure(Get): bool
     */
    public static function visibleWhenExpiry(Closure $resolveProduct): Closure
    {
        return fn (Get $get): bool => self::productManagesExpiry($resolveProduct($get));
    }

    /**
     * @template T of Field
     *
     * @param  T  $field
     * @return T
     */
    public static function constrainBatchField(Field $field, Closure $resolveProduct): Field
    {
        return $field->visible(self::visibleWhenBatch($resolveProduct));
    }

    /**
     * 出库/退货等明细：来源批次与批次号联动（选中后自动带出并锁定批次号）。
     *
     * @return list<Field>
     */
    public static function outboundSourceBatchAndBatchNoFields(
        Select $sourceBatchSelect,
        TextInput $batchNoInput,
        Closure $resolveProduct,
        ?Closure $resolveWarehouseId = null,
    ): array {
        self::wireSourceBatchToBatchNo($sourceBatchSelect, $batchNoInput);

        $sourceBatchSelect->afterStateUpdated(function (mixed $state, Set $set, Get $get) use ($resolveProduct, $resolveWarehouseId): void {
            if ($resolveWarehouseId === null) {
                $set(DberpDocumentLineOutboundSerialNumberFormFields::PICKER_FIELD, []);

                return;
            }

            DberpDocumentLineOutboundSerialNumberFormFields::pruneSerialNumbersForSourceBatch(
                $set,
                $get,
                $resolveProduct,
                $resolveWarehouseId,
            );
        });

        $batchNoFromSourceDisplay = Placeholder::make('_outbound_source_batch_no_display')
            ->label(__('dberp.fields.batch_no'))
            ->content(function (Get $get): string {
                $batchNo = self::batchNoForSourceBatchId($get('source_batch_id'));

                return $batchNo !== null && $batchNo !== '' ? $batchNo : '—';
            })
            ->helperText(__('dberp.hints.batch_no_locked_by_source_batch'))
            ->visible(function (Get $get) use ($resolveProduct): bool {
                return self::visibleWhenBatch($resolveProduct)($get) && filled($get('source_batch_id'));
            })
            ->columnSpanFull();

        $batchNoInput
            ->hidden(fn (Get $get): bool => filled($get('source_batch_id')))
            ->helperText(__('dberp.hints.batch_no_manual_when_no_source_batch'));

        return [
            self::constrainBatchField($sourceBatchSelect, $resolveProduct),
            $batchNoFromSourceDisplay,
            self::constrainBatchField($batchNoInput, $resolveProduct),
        ];
    }

    private static function wireSourceBatchToBatchNo(Select $sourceBatchSelect, TextInput $batchNoInput): void
    {
        $syncBatchNo = function (mixed $sourceBatchId, Set $set): void {
            $set('batch_no', self::batchNoForSourceBatchId($sourceBatchId));
        };

        $sourceBatchSelect
            ->live()
            ->afterStateUpdated($syncBatchNo)
            ->afterStateHydrated(function (mixed $state, Set $set) use ($syncBatchNo): void {
                $syncBatchNo($state, $set);
            });

        $batchNoInput
            ->dehydrated()
            ->dehydrateStateUsing(function (mixed $state, Get $get): ?string {
                if (filled($get('source_batch_id'))) {
                    return self::batchNoForSourceBatchId($get('source_batch_id'));
                }

                $resolved = trim((string) ($state ?? ''));

                return $resolved === '' ? null : strtoupper($resolved);
            });
    }

    /**
     * 保存明细前补齐批次号快照（来源批次选中时以档案为准）。
     *
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    public static function enrichOutboundLineBatchSnapshot(array $data): array
    {
        if (filled($data['source_batch_id'] ?? null)) {
            $data['batch_no'] = self::batchNoForSourceBatchId($data['source_batch_id']);
        }

        if (array_key_exists('serial_numbers', $data) && $data['serial_numbers'] !== null) {
            // TagsInput 在 separator(',') 下可能脱水为逗号分隔字符串；销售退货等入库表单若只接受数组会把已填序列号写成空，导致查看为空、无法确认。
            $raw = $data['serial_numbers'];
            $data['serial_numbers'] = app(InventorySerialNumberService::class)->normalizeSerialNumbers(
                DberpDocumentLineSerialNumberFormFields::expandSerialNumberEntries(
                    is_array($raw) || is_string($raw) ? $raw : [],
                ),
            );
        }

        return $data;
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    public static function finalizeOutboundDocumentLineFormData(array $data): array
    {
        return self::enrichOutboundLineBatchSnapshot(
            DberpLineProductSkuFormFields::finalizeLineSkuFormData($data),
        );
    }

    public static function batchNoForSourceBatchId(mixed $sourceBatchId): ?string
    {
        if (blank($sourceBatchId)) {
            return null;
        }

        $batch = DberpInventoryBatch::query()->find((int) $sourceBatchId);

        return $batch instanceof DberpInventoryBatch ? (string) $batch->batch_no : null;
    }

    /**
     * @template T of Field
     *
     * @param  T  $field
     * @return T
     */
    public static function constrainSerialField(Field $field, Closure $resolveProduct): Field
    {
        return $field
            ->visible(self::visibleWhenSerial($resolveProduct))
            ->dehydrated(true);
    }

    /**
     * 出库类明细「来源批次」下拉：按仓库与当前行商品过滤，避免选错批次导致序列号被裁剪为空。
     *
     * @return array<int, string>
     */
    public static function outboundSourceBatchSelectOptions(
        int $warehouseId,
        ?int $productId = null,
        bool $onlyPositiveAvailableQuantity = true,
    ): array {
        if ($warehouseId <= 0) {
            return [];
        }

        $query = DberpInventoryBatch::query()
            ->with('product')
            ->where('warehouse_id', $warehouseId)
            ->where('is_enabled', true);

        if ($productId !== null && $productId > 0) {
            $query->where('product_id', $productId);
        }

        if ($onlyPositiveAvailableQuantity) {
            $query->where('available_quantity', '>', 0);
        }

        return $query
            ->orderBy('expiry_date')
            ->orderBy('production_date')
            ->orderBy('id')
            ->get()
            ->mapWithKeys(fn (DberpInventoryBatch $batch): array => [
                $batch->getKey() => sprintf(
                    '%s / %s / %s %s',
                    (string) $batch->batch_no,
                    (string) $batch->product?->name,
                    (string) __('dberp.fields.available_quantity'),
                    DberpNumberFormatter::quantity($batch->available_quantity),
                ),
            ])
            ->all();
    }

    /**
     * @template T of Field
     *
     * @param  T  $field
     * @return T
     */
    public static function constrainExpiryField(Field $field, Closure $resolveProduct): Field
    {
        return $field->visible(self::visibleWhenExpiry($resolveProduct));
    }

    /**
     * @template T of Column
     *
     * @param  T  $column
     * @return T
     */
    public static function constrainBatchColumn(Column $column): Column
    {
        return $column->visible(fn (?Model $record): bool => ! $record instanceof Model
            || self::productManagesBatch(self::productFromLineRecord($record)));
    }

    /**
     * @template T of Column
     *
     * @param  T  $column
     * @return T
     */
    public static function constrainSerialColumn(Column $column): Column
    {
        return $column->visible(fn (?Model $record): bool => ! $record instanceof Model
            || self::productManagesSerial(self::productFromLineRecord($record)));
    }

    /**
     * @template T of Column
     *
     * @param  T  $column
     * @return T
     */
    public static function constrainExpiryColumn(Column $column): Column
    {
        return $column->visible(fn (?Model $record): bool => ! $record instanceof Model
            || self::productManagesExpiry(self::productFromLineRecord($record)));
    }
}
