<?php

declare(strict_types=1);

namespace App\Support;

/**
 * DBErp 数字展示格式化。
 *
 * 金额与单价的小数位数、默认货币展示前缀优先读取系统档案；
 * 表不可用或异常时回退 config/dberp.php。
 *
 * 仅用于界面展示字符串，不参与金额运算；写入 decimal(18,4) 等持久化字段仍应保持库表标度。
 * 数值为 0 时，金额/数量等不再保留小数位后缀（统一为「0」；百分比为「0%」）。
 */
final class DberpNumberFormatter
{
    public static function money(mixed $value): string
    {
        return self::formatMonetary($value);
    }

    public static function price(mixed $value): string
    {
        return self::formatMonetary($value);
    }

    public static function costPrice(mixed $value): string
    {
        return self::formatMonetary($value);
    }

    public static function quantity(mixed $value): string
    {
        return self::formatWithDecimalPlaces($value, DberpSystemProfileSettings::quantityDecimalPlaces());
    }

    public static function weight(mixed $value): string
    {
        return self::formatWithDecimalPlaces($value, self::precisionFor('weight'));
    }

    public static function defaultCurrencyCode(): string
    {
        return DberpSystemProfileSettings::defaultCurrencyCode();
    }

    public static function amountDecimalPlaces(): int
    {
        return DberpSystemProfileSettings::amountDecimalPlaces();
    }

    public static function quantityDecimalPlaces(): int
    {
        return DberpSystemProfileSettings::quantityDecimalPlaces();
    }

    /**
     * Filament numeric 金额输入 step，与系统档案金额小数位一致。
     */
    public static function moneyInputStep(): float
    {
        $decimals = self::amountDecimalPlaces();

        return $decimals === 0 ? 1.0 : 10 ** (-$decimals);
    }

    /**
     * Filament numeric 数量输入 step，与系统档案数量小数位一致。
     */
    public static function quantityInputStep(): float
    {
        $decimals = self::quantityDecimalPlaces();

        return $decimals === 0 ? 1.0 : 10 ** (-$decimals);
    }

    /**
     * @param  bool  $valueIsRatio  为 true 时表示小数比（如 0.13 表示 13%）；为 false 时表示已为百分点数值（如 13 表示 13%）
     */
    public static function percent(mixed $value, bool $valueIsRatio = true): string
    {
        $numeric = self::normalizeFiniteNumber($value);

        if ($numeric === null) {
            return '-';
        }

        $decimals = self::precisionFor('rate');
        $scaled = $valueIsRatio ? $numeric * 100.0 : $numeric;

        return self::normalizePercentDisplay(number_format($scaled, $decimals, '.', '').'%');
    }

    private static function formatMonetary(mixed $value): string
    {
        $numeric = self::normalizeFiniteNumber($value);

        if ($numeric === null) {
            return self::fallbackDisplayScalar($value);
        }

        $decimals = self::amountDecimalPlaces();
        $amount = self::normalizeNumericZeroDisplay(
            number_format($numeric, max(0, min(12, $decimals)), '.', ''),
        );

        return self::withCurrencyDisplay($amount);
    }

    private static function withCurrencyDisplay(string $formattedAmount): string
    {
        $code = DberpSystemProfileSettings::defaultCurrencyCode();

        /** @var array<string, string> $prefixes */
        $prefixes = config('dberp.system_profile.currency_display_prefixes', []);
        $prefix = $prefixes[$code] ?? null;

        if (is_string($prefix) && $prefix !== '') {
            return $prefix.$formattedAmount;
        }

        return $formattedAmount.' '.$code;
    }

    private static function formatWithDecimalPlaces(mixed $value, int $decimals): string
    {
        $numeric = self::normalizeFiniteNumber($value);

        if ($numeric === null) {
            return self::fallbackDisplayScalar($value);
        }

        $safeDecimals = max(0, min(12, $decimals));

        return self::normalizeNumericZeroDisplay(number_format($numeric, $safeDecimals, '.', ''));
    }

    /**
     * 数值为 0 时，将 0.0 / 0.00 / 0.0000 等统一为 "0"（含负零字符串）。
     */
    private static function normalizeNumericZeroDisplay(string $formatted): string
    {
        if (! is_numeric($formatted)) {
            return $formatted;
        }

        return ((float) $formatted) == 0.0 ? '0' : $formatted;
    }

    /**
     * 百分比在值为 0 时将 "0.00%" 等统一为 "0%"。
     */
    private static function normalizePercentDisplay(string $formatted): string
    {
        if (! str_ends_with($formatted, '%')) {
            return $formatted;
        }

        $numeric = (float) substr($formatted, 0, -1);

        return $numeric == 0.0 ? '0%' : $formatted;
    }

    private static function precisionFor(string $key): int
    {
        /** @var array<string, int|string>|null $map */
        $map = config('dberp.number_precision');
        /** @var int|string|null $raw */
        $raw = \is_array($map) ? ($map[$key] ?? null) : null;

        if ($raw === null || $raw === '') {
            return match ($key) {
                'amount', 'price', 'rate' => 2,
                'cost_price', 'quantity' => 4,
                'weight' => 3,
                default => 2,
            };
        }

        return max(0, (int) $raw);
    }

    private static function normalizeFiniteNumber(mixed $value): ?float
    {
        if ($value === null) {
            return null;
        }

        if (\is_string($value)) {
            $trimmed = trim($value);

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

            if (is_numeric($trimmed)) {
                $value = $trimmed;
            } else {
                $stripped = self::stripCurrencyDisplayDecoration($trimmed);

                if (! is_numeric($stripped)) {
                    return null;
                }

                $value = $stripped;
            }
        }

        if (! is_numeric($value)) {
            return null;
        }

        $numeric = (float) $value;

        if (! is_finite($numeric)) {
            return null;
        }

        return $numeric;
    }

    /**
     * 去掉金额展示前缀/后缀，便于对已格式化展示串二次解析（如 ¥1234.56）。
     */
    private static function stripCurrencyDisplayDecoration(string $value): string
    {
        /** @var array<string, string> $prefixes */
        $prefixes = config('dberp.system_profile.currency_display_prefixes', []);

        foreach ($prefixes as $prefix) {
            if ($prefix !== '' && str_starts_with($value, $prefix)) {
                return trim(substr($value, strlen($prefix)));
            }
        }

        $code = DberpSystemProfileSettings::defaultCurrencyCode();

        if ($code !== '' && str_ends_with($value, ' '.$code)) {
            return trim(substr($value, 0, -strlen($code) - 1));
        }

        return $value;
    }

    /**
     * 无法解析为数字时，若已是非空展示串则原样返回，避免二次格式化变成「-」。
     */
    private static function fallbackDisplayScalar(mixed $value): string
    {
        if ($value === null) {
            return '-';
        }

        if (is_string($value)) {
            $trimmed = trim($value);

            return $trimmed === '' ? '-' : $trimmed;
        }

        if (is_int($value) || is_float($value)) {
            return (string) $value;
        }

        return '-';
    }
}
