<?php

declare(strict_types=1);

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

namespace App\Business\DataImport;

use App\Business\DataImport\Enums\DataImportType;
use Illuminate\Support\Facades\Lang;

/**
 * 首批数据初始化导入的标准模板表头定义。
 */
final class DataImportTemplateRegistry
{
    public function __construct(
        private readonly DataImportDefinitionRegistry $definitionRegistry,
    ) {}

    /**
     * 客户/供应商模板中禁止出现的表头（期初应收/应付应独立导入）。
     *
     * @var list<string>
     */
    private const CUSTOMER_SUPPLIER_FORBIDDEN_HEADINGS = [
        'opening_receivable',
        'opening_payable',
        'opening_receivable_amount',
        'opening_payable_amount',
        'receivable_balance',
        'payable_balance',
        'opening_balance',
    ];

    /**
     * 商品模板中禁止出现的多规格列（单规格导入专用模板）。
     *
     * @var list<string>
     */
    private const PRODUCT_MULTI_SPEC_FORBIDDEN_HEADINGS = [
        'spec_group',
        'spec_value',
        'sku_code',
        'sku_price',
        'sku_barcode',
        'sku_matrix',
    ];

    /**
     * @return list<string>
     */
    public function headings(DataImportType|DataImportTypeReference|string $type): array
    {
        $reference = DataImportTypeReference::from($type);
        $enum = $reference->toEnum();

        if ($enum !== null) {
            return $this->builtinHeadings($enum);
        }

        $definition = $this->definitionRegistry->get($reference->key);

        if (! $definition->hasTemplateDefinition()) {
            throw new \InvalidArgumentException(sprintf(
                'Extension import type [%s] has no template headings registered.',
                $reference->key,
            ));
        }

        return $definition->headings;
    }

    /**
     * @return list<string>
     */
    private function builtinHeadings(DataImportType $type): array
    {
        return match ($type) {
            DataImportType::Unit => [
                'code',
                'name',
                'is_enabled',
                'sort_order',
                'remark',
            ],
            DataImportType::ProductCategory => [
                'code',
                'name',
                'parent_code',
                'is_enabled',
                'sort_order',
                'remark',
            ],
            DataImportType::ProductBrand => [
                'brand_code',
                'name',
                'is_enabled',
                'sort_order',
                'remark',
            ],
            DataImportType::CustomerCategory => [
                'code',
                'name',
                'status',
                'sort',
                'remark',
            ],
            DataImportType::SupplierCategory => [
                'code',
                'name',
                'status',
                'sort',
                'remark',
            ],
            DataImportType::Warehouse => [
                'code',
                'name',
                'warehouse_type',
                'is_default',
                'manager_employee_code',
                'region_code',
                'address',
                'is_enabled',
                'sort_order',
                'remark',
            ],
            DataImportType::Customer => [
                'code',
                'name',
                'customer_category_code',
                'settlement_method',
                'contact_person',
                'mobile',
                'phone',
                'email',
                'region_code',
                'address',
                'price_level_code',
                'default_payment_term_days',
                'credit_limit_amount',
                'salesperson_employee_code',
                'is_enabled',
                'sort_order',
                'remark',
            ],
            DataImportType::Supplier => [
                'code',
                'name',
                'supplier_category_code',
                'settlement_method',
                'contact_person',
                'mobile',
                'phone',
                'email',
                'region_code',
                'address',
                'is_enabled',
                'sort_order',
                'remark',
            ],
            DataImportType::Product => [
                'code',
                'name',
                'category_code',
                'brand_code',
                'spec_model',
                'unit_code',
                'barcode',
                'cost_price',
                'sales_price',
                'manages_inventory',
                'is_enabled',
                'remark',
            ],
            DataImportType::OpeningStock => [
                'external_key',
                'product_code',
                'sku_code',
                'warehouse_code',
                'quantity',
                'unit_cost',
                'safety_stock',
                'batch_no',
                'serial_no',
                'production_date',
                'expiry_date',
                'business_time',
                'remark',
            ],
        };
    }

    /**
     * @return list<DataImportType>
     */
    public function supportedTypes(): array
    {
        return DataImportType::firstStageTypes();
    }

    public function supports(DataImportType|DataImportTypeReference|string $type): bool
    {
        $reference = DataImportTypeReference::from($type);

        if (! $this->definitionRegistry->has($reference->key)) {
            return false;
        }

        $enum = $reference->toEnum();

        if ($enum !== null) {
            return in_array($enum, $this->supportedTypes(), true);
        }

        return $this->definitionRegistry->get($reference->key)->hasTemplateDefinition();
    }

    public function downloadFilename(DataImportType|DataImportTypeReference|string $type, string $extension = 'xlsx'): string
    {
        $reference = DataImportTypeReference::from($type);

        return sprintf('dberp_import_template_%s.%s', $reference->key, ltrim($extension, '.'));
    }

    /**
     * @return list<list<string|null>>
     */
    public function sampleRowsForExport(DataImportType|DataImportTypeReference|string $type, ?string $locale = null): array
    {
        return app(DataImportTemplateSamples::class)->rowsForExport($type, $this, $locale);
    }

    /**
     * @param  array<string, string|null>  $values
     */
    public function isExampleRow(DataImportType|DataImportTypeReference|string $type, array $values): bool
    {
        return app(DataImportTemplateSamples::class)->isExampleRow($type, $values);
    }

    /**
     * @return list<string>
     */
    public function displayHeadings(DataImportType|DataImportTypeReference|string $type, ?string $locale = null): array
    {
        return array_map(
            fn (string $fieldKey): string => $this->headingLabel($fieldKey, $locale),
            $this->headings($type),
        );
    }

    public function headingLabel(string $fieldKey, ?string $locale = null): string
    {
        $translationKey = 'dberp.data_import.template_columns.'.$fieldKey;
        $locale ??= app()->getLocale();

        if (Lang::has($translationKey, $locale)) {
            return (string) __($translationKey, [], $locale);
        }

        $pluginTranslationKey = 'plugin-dberp-advanced-import-export::import_columns.'.$fieldKey;

        if (Lang::has($pluginTranslationKey, $locale)) {
            return (string) __($pluginTranslationKey, [], $locale);
        }

        return $fieldKey;
    }

    /**
     * @param  list<string>  $fieldKeys
     */
    public function formatFieldKeysForDisplay(array $fieldKeys, ?string $locale = null): string
    {
        if ($fieldKeys === []) {
            return '';
        }

        return implode(', ', array_map(
            fn (string $fieldKey): string => $this->headingLabel($fieldKey, $locale),
            $fieldKeys,
        ));
    }

    public function resolveFieldKey(string $cell, ?string $locale = null): ?string
    {
        $trimmed = trim($cell);

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

        $normalizedKey = strtolower($trimmed);

        if (in_array($normalizedKey, $this->allKnownFieldKeys(), true)) {
            return $normalizedKey;
        }

        foreach ($this->templateLocales($locale) as $candidateLocale) {
            foreach ($this->allKnownFieldKeys() as $fieldKey) {
                if ($this->headingLabel($fieldKey, $candidateLocale) === $trimmed) {
                    return $fieldKey;
                }
            }
        }

        return null;
    }

    /**
     * @return list<string>
     */
    private function templateLocales(?string $locale = null): array
    {
        /** @var array<string, string> $configuredLocales */
        $configuredLocales = (array) config('dberp.admin.locales', [
            'zh_CN' => '中文',
            'en' => 'English',
        ]);

        return array_values(array_unique(array_filter([
            $locale,
            app()->getLocale(),
            (string) config('app.fallback_locale', 'en'),
            ...array_keys($configuredLocales),
        ])));
    }

    /**
     * @return list<string>
     */
    private function allKnownFieldKeys(): array
    {
        $fieldKeys = [];

        foreach (DataImportType::cases() as $type) {
            $fieldKeys = array_merge($fieldKeys, $this->builtinHeadings($type));
        }

        foreach ($this->definitionRegistry->extensions() as $definition) {
            $fieldKeys = array_merge($fieldKeys, $definition->headings, $definition->forbiddenHeadings);
        }

        $fieldKeys = array_merge(
            $fieldKeys,
            self::CUSTOMER_SUPPLIER_FORBIDDEN_HEADINGS,
            self::PRODUCT_MULTI_SPEC_FORBIDDEN_HEADINGS,
        );

        return array_values(array_unique($fieldKeys));
    }

    /**
     * @param  list<string|null>  $headingRow
     * @return array{
     *     expected: list<string>,
     *     actual: list<string>,
     *     missing: list<string>,
     *     extra: list<string>,
     *     forbidden: list<string>,
     * }
     */
    public function analyzeHeadingRow(DataImportType|DataImportTypeReference|string $type, array $headingRow, ?string $locale = null): array
    {
        $expected = $this->headings($type);
        $actual = $this->normalizeHeadingCells($headingRow, $locale);
        $missing = array_values(array_diff($expected, $actual));
        $extra = array_values(array_diff($actual, $expected));
        $forbidden = array_values(array_intersect($actual, $this->forbiddenHeadings($type)));

        return [
            'expected' => $expected,
            'actual' => $actual,
            'missing' => $missing,
            'extra' => $extra,
            'forbidden' => $forbidden,
        ];
    }

    /**
     * @param  list<string|null>  $headingRow
     */
    public function headingsMatchTemplate(DataImportType|DataImportTypeReference|string $type, array $headingRow, ?string $locale = null): bool
    {
        $analysis = $this->analyzeHeadingRow($type, $headingRow, $locale);

        return $analysis['missing'] === []
            && $analysis['extra'] === []
            && $analysis['forbidden'] === []
            && $analysis['actual'] === $analysis['expected'];
    }

    /**
     * @return list<string>
     */
    private function forbiddenHeadings(DataImportType|DataImportTypeReference|string $type): array
    {
        $reference = DataImportTypeReference::from($type);
        $enum = $reference->toEnum();

        if ($enum === DataImportType::Customer || $enum === DataImportType::Supplier) {
            return self::CUSTOMER_SUPPLIER_FORBIDDEN_HEADINGS;
        }

        if ($enum === DataImportType::Product) {
            return self::PRODUCT_MULTI_SPEC_FORBIDDEN_HEADINGS;
        }

        if ($enum === null) {
            return $this->definitionRegistry->get($reference->key)->forbiddenHeadings;
        }

        return [];
    }

    /**
     * @param  list<string|null>  $headingRow
     * @return list<string>
     */
    private function normalizeHeadingCells(array $headingRow, ?string $locale = null): array
    {
        $normalized = [];

        foreach ($headingRow as $cell) {
            if ($cell === null) {
                continue;
            }

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

            if ($trimmed === '') {
                continue;
            }

            $fieldKey = $this->resolveFieldKey($trimmed, $locale);
            $normalized[] = $fieldKey ?? strtolower($trimmed);
        }

        return $normalized;
    }
}
