<?php

declare(strict_types=1);

namespace App\Support;

use App\Filament\Support\DberpFilamentRelationshipSelectLimits;
use App\Models\DberpProduct;
use App\Models\DberpProductSku;
use App\Models\DberpStocktakeOrder;
use Filament\Forms\Components\Select;
use Filament\Tables\Filters\SelectFilter;
use Illuminate\Database\Eloquent\Builder;
use InvalidArgumentException;

/**
 * 单据明细商品/规格组合选择：统一业务化展示文案与可浏览下拉（小数据全量、大数据预载+搜索）。
 */
final class DberpProductSkuSelectOptions
{
    public const SEARCHABLE_OPTION_THRESHOLD = 26;

    public const PRODUCT_SCOPE_PURCHASABLE = 'purchasable';

    public const PRODUCT_SCOPE_SALABLE = 'salable';

    public const PRODUCT_SCOPE_INVENTORY = 'inventory';

    public static function labelForSku(DberpProductSku $sku): string
    {
        $product = $sku->relationLoaded('product') ? $sku->product : $sku->product()->with('unit')->first();
        $code = $product !== null ? (string) $product->code : '—';
        $name = $product !== null ? (string) $product->name : '—';
        $spec = trim((string) ($sku->spec_text ?? ''));
        $comboCode = trim((string) ($sku->sku_code ?? ''));
        $unit = (string) ($sku->resolveEffectiveUnit()?->name ?? $product?->unit?->name ?? '—');

        return sprintf(
            '%s｜%s｜%s｜%s｜%s',
            $code,
            $name,
            $spec !== '' ? $spec : '—',
            $comboCode !== '' ? $comboCode : '—',
            $unit,
        );
    }

    /**
     * @return Builder<DberpProductSku>
     */
    public static function queryPurchasableSkus(): Builder
    {
        return DberpProductSku::query()
            ->with(['product.unit'])
            ->where('is_enabled', true)
            ->whereHas('product', fn ($query) => $query
                ->where('is_enabled', true)
                ->where('can_purchase', true)
                ->where('manages_inventory', true))
            ->orderBy('product_id')
            ->orderByDesc('is_default')
            ->orderBy('sort_order')
            ->orderBy('id');
    }

    /**
     * @return Builder<DberpProductSku>
     */
    public static function querySalableSkus(): Builder
    {
        return DberpProductSku::query()
            ->with(['product.unit'])
            ->where('is_enabled', true)
            ->whereHas('product', fn ($query) => $query
                ->where('is_enabled', true)
                ->where('can_sale', true)
                ->where('manages_inventory', true))
            ->orderBy('product_id')
            ->orderByDesc('is_default')
            ->orderBy('sort_order')
            ->orderBy('id');
    }

    /**
     * @return Builder<DberpProductSku>
     */
    public static function queryInventorySkus(): Builder
    {
        return DberpProductSku::query()
            ->with(['product.unit'])
            ->where('is_enabled', true)
            ->whereHas('product', fn ($query) => $query
                ->where('is_enabled', true)
                ->where('manages_inventory', true))
            ->orderBy('product_id')
            ->orderByDesc('is_default')
            ->orderBy('sort_order')
            ->orderBy('id');
    }

    /**
     * @param  iterable<int, DberpProductSku>  $skus
     * @return array<int|string, string>
     */
    public static function mapSkuIterableToOptions(iterable $skus): array
    {
        $out = [];
        foreach ($skus as $sku) {
            if ($sku instanceof DberpProductSku) {
                $out[$sku->getKey()] = self::labelForSku($sku);
            }
        }

        return $out;
    }

    public static function shouldUseSearchableSelect(int $optionCount): bool
    {
        return $optionCount >= self::SEARCHABLE_OPTION_THRESHOLD;
    }

    public static function labelForProduct(DberpProduct $product): string
    {
        $product->loadMissing('unit');

        $code = (string) $product->code;
        $name = (string) $product->name;
        $specModel = trim((string) ($product->spec_model ?? ''));
        $unitName = trim((string) ($product->unit?->name ?? ''));

        $segments = [$code, $name];
        if ($specModel !== '') {
            $segments[] = $specModel;
        }
        if ($unitName !== '') {
            $segments[] = $unitName;
        }

        return implode('｜', $segments);
    }

    public static function labelForProductId(?int $productId): ?string
    {
        if ($productId === null || $productId <= 0) {
            return null;
        }

        $product = DberpProduct::query()->with('unit')->find($productId);

        return $product instanceof DberpProduct ? self::labelForProduct($product) : null;
    }

    public static function labelForSkuId(?int $skuId): ?string
    {
        if ($skuId === null || $skuId <= 0) {
            return null;
        }

        $sku = DberpProductSku::query()->with(['product.unit'])->find($skuId);

        return $sku instanceof DberpProductSku ? self::labelForSku($sku) : null;
    }

    /**
     * @return array<int|string, string>
     */
    public static function searchSkuOptions(string $scope, string $search): array
    {
        $query = self::querySkusForScope($scope);

        if (filled($search)) {
            self::applySkuTextSearch($query, $search);
        } else {
            $query
                ->orderBy('product_id')
                ->orderByDesc('is_default')
                ->orderBy('sort_order')
                ->orderBy('id')
                ->limit(DberpFilamentRelationshipSelectLimits::PRELOAD_OPTION_LIMIT);
        }

        return self::mapSkuIterableToOptions($query->get());
    }

    /**
     * @return array<int|string, string>
     */
    public static function skuOptionsForScope(string $scope): array
    {
        return self::mapSkuIterableToOptions(self::querySkusForScope($scope)->get());
    }

    public static function skuCountForScope(string $scope): int
    {
        return self::querySkusForScope($scope)->count();
    }

    /**
     * @return Builder<DberpProductSku>
     */
    public static function querySkusForScope(string $scope): Builder
    {
        return match ($scope) {
            self::PRODUCT_SCOPE_SALABLE => self::querySalableSkus(),
            self::PRODUCT_SCOPE_PURCHASABLE => self::queryPurchasableSkus(),
            self::PRODUCT_SCOPE_INVENTORY => self::queryInventorySkus(),
            default => throw new InvalidArgumentException('Unknown SKU select scope: '.$scope),
        };
    }

    /**
     * 列表/报表规格筛选：不限销售/采购范围，仅启用商品及其规格。
     *
     * @return array<int|string, string>
     */
    public static function searchSkuOptionsForTableFilter(string $search): array
    {
        $query = DberpProductSku::query()
            ->with(['product.unit'])
            ->where('is_enabled', true)
            ->whereHas('product', fn (Builder $productQuery): Builder => $productQuery->where('is_enabled', true));

        if (filled($search)) {
            self::applySkuTextSearch($query, $search);
        } else {
            $query
                ->orderBy('product_id')
                ->orderByDesc('is_default')
                ->orderBy('sort_order')
                ->orderBy('id')
                ->limit(DberpFilamentRelationshipSelectLimits::PRELOAD_OPTION_LIMIT);
        }

        return self::mapSkuIterableToOptions($query->get());
    }

    /**
     * @param  Builder<DberpProductSku>  $query
     */
    public static function applySkuTextSearch(Builder $query, string $search): Builder
    {
        $like = '%'.$search.'%';

        return $query->where(function (Builder $builder) use ($like, $search): void {
            $builder
                ->where('sku_code', 'like', $like)
                ->orWhere('spec_text', 'like', $like)
                ->orWhereHas('product', fn (Builder $productQuery): Builder => self::applyProductTextSearch($productQuery, $search));
        });
    }

    /**
     * @return array<int|string, string>
     */
    public static function searchProductOptions(string $scope, string $search): array
    {
        $query = self::queryProductsForScope($scope)->with('unit');

        if (filled($search)) {
            self::applyProductTextSearch($query, $search);
        } else {
            DberpFilamentRelationshipSelectLimits::limitProductOptionsQuery($query, null);
        }

        return self::mapProductIterableToOptions($query->get());
    }

    /**
     * @return array<int|string, string>
     */
    public static function productOptionsForScope(string $scope): array
    {
        return self::mapProductIterableToOptions(
            self::queryProductsForScope($scope)->with('unit')->get(),
        );
    }

    public static function productCountForScope(string $scope): int
    {
        return self::queryProductsForScope($scope)->count();
    }

    /**
     * @param  iterable<int, DberpProduct>  $products
     * @return array<int|string, string>
     */
    public static function mapProductIterableToOptions(iterable $products): array
    {
        $out = [];
        foreach ($products as $product) {
            if ($product instanceof DberpProduct) {
                $out[$product->getKey()] = self::labelForProduct($product);
            }
        }

        return $out;
    }

    /**
     * @param  Builder<DberpProduct>  $query
     */
    public static function applyProductTextSearch(Builder $query, string $search): Builder
    {
        $like = '%'.$search.'%';

        return $query->where(function (Builder $builder) use ($like): void {
            $builder
                ->where('code', 'like', $like)
                ->orWhere('name', 'like', $like)
                ->orWhere('barcode', 'like', $like)
                ->orWhere('mnemonic_code', 'like', $like)
                ->orWhere('spec_model', 'like', $like)
                ->orWhere('brand_name', 'like', $like)
                ->orWhereHas('brand', fn (Builder $brandQuery): Builder => $brandQuery->where('name', 'like', $like));
        });
    }

    /**
     * @param  Builder<DberpProduct>  $query
     */
    public static function modifyProductRelationshipQuery(Builder $query, ?string $search): Builder
    {
        $query->with('unit');

        if (filled($search)) {
            self::applyProductTextSearch($query, $search);
        } else {
            DberpFilamentRelationshipSelectLimits::limitProductOptionsQuery($query, null);
        }

        return $query;
    }

    public static function configureSalableProductSelect(Select $field): Select
    {
        return self::configureDocumentProductSelect($field, self::PRODUCT_SCOPE_SALABLE);
    }

    public static function configurePurchasableProductSelect(Select $field): Select
    {
        return self::configureDocumentProductSelect($field, self::PRODUCT_SCOPE_PURCHASABLE);
    }

    public static function configureInventoryProductSelect(Select $field): Select
    {
        return self::configureDocumentProductSelect($field, self::PRODUCT_SCOPE_INVENTORY);
    }

    public static function configureStocktakeOrderItemProductSelect(Select $field, DberpStocktakeOrder $order): Select
    {
        $field = $field
            ->searchable()
            ->getOptionLabelUsing(
                fn ($value): ?string => self::labelForProductId($value !== null && $value !== '' ? (int) $value : null),
            );

        if (self::stocktakeOrderProductCount($order) < self::SEARCHABLE_OPTION_THRESHOLD) {
            return $field
                ->options(fn (): array => self::stocktakeOrderProductOptions($order))
                ->preload();
        }

        return $field
            ->options(fn (): array => self::searchStocktakeOrderProductOptions($order, ''))
            ->getSearchResultsUsing(fn (string $search): array => self::searchStocktakeOrderProductOptions($order, $search))
            ->preload();
    }

    /**
     * @return array<int|string, string>
     */
    public static function stocktakeOrderProductOptions(DberpStocktakeOrder $order): array
    {
        return self::mapProductIterableToOptions(
            self::queryInventoryProductsForStocktakeOrder($order)->with('unit')->get(),
        );
    }

    /**
     * @return array<int|string, string>
     */
    public static function searchStocktakeOrderProductOptions(DberpStocktakeOrder $order, string $search): array
    {
        $query = self::queryInventoryProductsForStocktakeOrder($order)->with('unit');

        if (filled($search)) {
            self::applyProductTextSearch($query, $search);
        } else {
            DberpFilamentRelationshipSelectLimits::limitProductOptionsQuery($query, null);
        }

        return self::mapProductIterableToOptions($query->get());
    }

    public static function stocktakeOrderProductCount(DberpStocktakeOrder $order): int
    {
        return self::queryInventoryProductsForStocktakeOrder($order)->count();
    }

    /**
     * @return Builder<DberpProduct>
     */
    public static function queryInventoryProductsForStocktakeOrder(DberpStocktakeOrder $order): Builder
    {
        return $order->applyScopeToProductQuery(self::queryInventoryProducts());
    }

    public static function configureDocumentProductSelect(Select $field, string $scope): Select
    {
        $field = $field
            ->searchable()
            ->getOptionLabelUsing(
                fn ($value): ?string => self::labelForProductId($value !== null && $value !== '' ? (int) $value : null),
            );

        if (self::productCountForScope($scope) < self::SEARCHABLE_OPTION_THRESHOLD) {
            return $field
                ->options(fn (): array => self::productOptionsForScope($scope))
                ->preload();
        }

        return $field
            ->options(fn (): array => self::searchProductOptions($scope, ''))
            ->getSearchResultsUsing(fn (string $search): array => self::searchProductOptions($scope, $search))
            ->preload();
    }

    public static function configureProductSkuMultiSelect(Select $field, string $scope): Select
    {
        $field = $field
            ->multiple()
            ->searchable()
            ->getOptionLabelsUsing(function (array $values): array {
                $labels = [];
                foreach ($values as $value) {
                    $id = (int) $value;
                    if ($id <= 0) {
                        continue;
                    }

                    $label = self::labelForSkuId($id);
                    if ($label !== null) {
                        $labels[$id] = $label;
                    }
                }

                return $labels;
            })
            ->getOptionLabelUsing(
                fn ($value): ?string => self::labelForSkuId($value !== null && $value !== '' ? (int) $value : null),
            );

        if (self::skuCountForScope($scope) < self::SEARCHABLE_OPTION_THRESHOLD) {
            return $field
                ->options(fn (): array => self::skuOptionsForScope($scope))
                ->preload();
        }

        return $field
            ->getSearchResultsUsing(fn (string $search): array => self::searchSkuOptions($scope, $search))
            ->preload();
    }

    public static function configureInventoryProductRelationshipSelect(Select $field): Select
    {
        return $field
            ->relationship(
                'product',
                'code',
                static fn (Builder $query, ?string $search): Builder => self::modifyProductRelationshipQuery($query, $search),
            )
            ->getOptionLabelFromRecordUsing(
                fn (DberpProduct $record): string => self::labelForProduct($record),
            )
            ->searchable()
            ->preload();
    }

    public static function configureProductRelationshipTableFilter(SelectFilter $filter): SelectFilter
    {
        return $filter
            ->relationship(
                'product',
                'code',
                static fn (Builder $query, ?string $search): Builder => self::modifyProductRelationshipQuery($query, $search),
            )
            ->getOptionLabelFromRecordUsing(
                fn (DberpProduct $record): string => self::labelForProduct($record),
            );
    }

    public static function configureProductSkuTableFilter(SelectFilter $filter): SelectFilter
    {
        return $filter
            ->searchable()
            ->preload()
            ->options(fn (): array => self::searchSkuOptionsForTableFilter(''))
            ->getSearchResultsUsing(fn (string $search): array => self::searchSkuOptionsForTableFilter($search))
            ->getOptionLabelUsing(
                fn ($value): ?string => self::labelForSkuId($value !== null && $value !== '' ? (int) $value : null),
            );
    }

    /**
     * @return Builder<DberpProduct>
     */
    public static function queryProductsForScope(string $scope): Builder
    {
        return match ($scope) {
            self::PRODUCT_SCOPE_SALABLE => self::querySalableProducts(),
            self::PRODUCT_SCOPE_PURCHASABLE => self::queryPurchasableProducts(),
            self::PRODUCT_SCOPE_INVENTORY => self::queryInventoryProducts(),
            default => throw new InvalidArgumentException('Unknown product select scope: '.$scope),
        };
    }

    /**
     * @return Builder<DberpProduct>
     */
    public static function querySalableProducts(): Builder
    {
        return DberpProduct::query()
            ->where('is_enabled', true)
            ->where('can_sale', true)
            ->where('manages_inventory', true)
            ->orderBy('sort_order')
            ->orderBy('id');
    }

    /**
     * @return Builder<DberpProduct>
     */
    public static function queryPurchasableProducts(): Builder
    {
        return DberpProduct::query()
            ->where('is_enabled', true)
            ->where('can_purchase', true)
            ->where('manages_inventory', true)
            ->orderBy('sort_order')
            ->orderBy('id');
    }

    /**
     * @return Builder<DberpProduct>
     */
    public static function queryInventoryProducts(): Builder
    {
        return DberpProduct::query()
            ->where('is_enabled', true)
            ->where('manages_inventory', true)
            ->orderBy('sort_order')
            ->orderBy('id');
    }
}
