<?php

namespace App\Support;

/**
 * 将校验错误转为当前请求语言下可读文案，并生成适合 toast 的摘要 msg。
 */
final class ApiValidationErrors
{
    /**
     * @param  array<string, array<int, string>|string>  $errors
     * @return array<string, list<string>>
     */
    public static function normalize(array $errors, string $locale): array
    {
        $out = [];

        foreach ($errors as $field => $messages) {
            $fieldKey = (string) $field;
            $normalized = [];
            foreach ((array) $messages as $message) {
                $normalized[] = self::present($fieldKey, (string) $message, $locale);
            }
            $out[$fieldKey] = $normalized;
        }

        return $out;
    }

    /**
     * @param  array<string, list<string>>  $normalized
     */
    public static function summary(array $normalized, int $maxParts = 3): ?string
    {
        $parts = [];

        foreach ($normalized as $messages) {
            foreach ($messages as $message) {
                $parts[] = $message;
                if (count($parts) >= $maxParts) {
                    return implode('；', $parts).'…';
                }
            }
        }

        return $parts === [] ? null : implode('；', $parts);
    }

    private static function present(string $field, string $message, string $locale): string
    {
        $trimmed = trim($message);
        if ($trimmed === '') {
            return $message;
        }

        if (preg_match('/\p{Han}/u', $trimmed) === 1) {
            return $trimmed;
        }

        $exact = self::exactMessage($trimmed, $locale);
        if ($exact !== null) {
            return $exact;
        }

        if (preg_match(
            '/^(exceeds_actor|exceeds_parent_ceiling|exceeds_delegation_ceiling|permission_exceeds_actor|permission_catalog_incomplete)\s*:\s*(.+)$/u',
            $trimmed,
            $matches,
        ) === 1) {
            $detail = trim(preg_replace('/\s*\(run:.*$/i', '', $matches[2]) ?? $matches[2]);
            $line = trans('validation.business.'.$matches[1], ['detail' => $detail], $locale);
            if ($line !== 'validation.business.'.$matches[1]) {
                return $line;
            }
        }

        $attribute = self::attributeLabel($field, $locale);

        $businessKey = 'validation.business.'.$trimmed;
        $businessLine = trans($businessKey, ['attribute' => $attribute], $locale);
        if ($businessLine !== $businessKey) {
            return self::refineBusinessLine($field, $trimmed, $businessLine, $locale);
        }

        $customLine = self::customRuleLine($field, $trimmed, $attribute, $locale);
        if ($customLine !== null) {
            return $customLine;
        }

        $ruleLine = self::standardRuleLine($trimmed, $attribute, $locale);
        if ($ruleLine !== null) {
            return $ruleLine;
        }

        $humanized = self::humanizeLaravelEnglish($field, $trimmed, $locale, $attribute);
        if ($humanized !== null) {
            return $humanized;
        }

        $compact = self::humanizeCompactEnglish($field, $trimmed, $locale, $attribute);
        if ($compact !== null) {
            return $compact;
        }

        return $trimmed;
    }

    private static function refineBusinessLine(string $field, string $rule, string $line, string $locale): string
    {
        if ($rule === 'exceeds_limit') {
            $flat = self::flatFieldName($field);
            $specificKey = match ($flat) {
                'rebate_rate', 'extra_rebate_rate' => 'validation.business.exceeds_player_rebate_limit',
                'rebate_limit' => 'validation.business.exceeds_parent_rebate',
                'default_player_rebate' => 'validation.business.exceeds_default_rebate_limit',
                'total_share_rate', 'relative_share_rate' => 'validation.business.exceeds_parent',
                default => null,
            };
            if ($specificKey !== null) {
                $specific = trans($specificKey, [], $locale);
                if ($specific !== $specificKey) {
                    return $specific;
                }
            }
        }

        if ($rule === 'exceeds_available' && self::flatFieldName($field) === 'credit_limit') {
            $specific = trans('validation.business.exceeds_available', [], $locale);
            if ($specific !== 'validation.business.exceeds_available') {
                return $specific;
            }
        }

        $flat = self::flatFieldName($field);
        $contextKey = match (true) {
            $flat === 'bill' && in_array($rule, ['not_payable', 'not_confirmable', 'not_eligible', 'no_unpaid', 'locked'], true)
                => 'validation.business.'.$rule.'_bill',
            $flat === 'period' && $rule === 'completed'
                => 'validation.business.completed_period',
            $flat === 'credit' && in_array($rule, ['insufficient', 'overdue'], true)
                => 'validation.business.'.$rule.'_credit',
            $flat === 'amount' && $rule === 'zero'
                => 'validation.business.zero_amount',
            default => null,
        };
        if ($contextKey !== null) {
            $specific = trans($contextKey, [], $locale);
            if ($specific !== $contextKey) {
                return $specific;
            }
        }

        return $line;
    }

    private static function exactMessage(string $message, string $locale): ?string
    {
        $map = trans('validation.exact', [], $locale);
        if (! is_array($map)) {
            return null;
        }

        return $map[$message] ?? null;
    }

    private static function customRuleLine(
        string $field,
        string $rule,
        string $attribute,
        string $locale,
    ): ?string {
        $candidates = array_values(array_unique([
            'validation.custom.'.$field.'.'.$rule,
            'validation.custom.'.self::flatFieldName($field).'.'.$rule,
        ]));

        foreach ($candidates as $key) {
            $line = trans($key, ['attribute' => $attribute], $locale);
            if ($line !== $key) {
                return $line;
            }
        }

        return null;
    }

    private static function standardRuleLine(string $rule, string $attribute, string $locale): ?string
    {
        if (! preg_match('/^[a-z0-9_.]+$/i', $rule)) {
            return null;
        }

        $key = 'validation.'.$rule;
        $line = trans($key, ['attribute' => $attribute], $locale);

        return $line !== $key ? $line : null;
    }

    private static function humanizeLaravelEnglish(
        string $field,
        string $message,
        string $locale,
        string $attribute,
    ): ?string {
        if (preg_match('/^The (.+?) field (.+)$/i', $message, $matches) !== 1) {
            if (preg_match('/^The (.+?) has already been taken\.?$/i', $message, $taken) === 1) {
                $attribute = self::attributeLabelFromEnglish($taken[1], $field, $locale);

                return trans('validation.unique', ['attribute' => $attribute], $locale);
            }

            if (preg_match('/^The selected (.+?) is invalid\.?$/i', $message, $selected) === 1) {
                $attribute = self::attributeLabelFromEnglish($selected[1], $field, $locale);

                return trans('validation.exists', ['attribute' => $attribute], $locale);
            }

            return null;
        }

        $englishName = $matches[1];
        $tail = $matches[2];
        $attribute = self::attributeLabelFromEnglish($englishName, $field, $locale);

        $tailMap = [
            'is required' => 'validation.required',
            'must be a valid email address' => 'validation.email',
            'must be a valid UUID' => 'validation.uuid',
            'must be an integer' => 'validation.integer',
            'must be a string' => 'validation.string',
            'must be a number' => 'validation.numeric',
            'must be a valid JSON string' => 'validation.json',
            'must be true or false' => 'validation.boolean',
            'must be an array' => 'validation.array',
            'format is invalid' => null,
            'is not allowed' => 'validation.prohibited',
            'must be present' => 'validation.present',
        ];

        foreach ($tailMap as $suffix => $ruleKey) {
            if (stripos($tail, $suffix) === false) {
                continue;
            }

            if ($ruleKey === null) {
                return self::customRuleLine($field, 'regex', $attribute, $locale)
                    ?? trans('validation.regex', ['attribute' => $attribute], $locale);
            }

            $line = trans($ruleKey, ['attribute' => $attribute], $locale);

            return $line !== $ruleKey ? $line : null;
        }

        if (preg_match('/must be at least (\d+) characters/i', $tail, $min)) {
            $customKey = 'validation.custom.'.self::flatFieldName($field).'.min';
            $customLine = trans($customKey, ['attribute' => $attribute, 'min' => $min[1]], $locale);
            if ($customLine !== $customKey) {
                return $customLine;
            }

            return trans('validation.min.string', ['attribute' => $attribute, 'min' => $min[1]], $locale);
        }

        if (preg_match('/must not be greater than (\d+) characters/i', $tail, $max)) {
            return trans('validation.max.string', ['attribute' => $attribute, 'max' => $max[1]], $locale);
        }

        if (preg_match('/must contain (\d+) items/i', $tail, $size)) {
            return trans('validation.size.array', ['attribute' => $attribute, 'size' => $size[1]], $locale);
        }

        if (preg_match('/must have at least (\d+) items/i', $tail, $minItems)) {
            return trans('validation.min.array', ['attribute' => $attribute, 'min' => $minItems[1]], $locale);
        }

        if (preg_match('/must not have more than (\d+) items/i', $tail, $maxItems)) {
            return trans('validation.max.array', ['attribute' => $attribute, 'max' => $maxItems[1]], $locale);
        }

        if (preg_match('/must be between ([\d.]+) and ([\d.]+)/i', $tail, $between)) {
            return trans('validation.between.numeric', [
                'attribute' => $attribute,
                'min' => $between[1],
                'max' => $between[2],
            ], $locale);
        }

        if (preg_match('/must match the format (.+)$/i', $tail, $format)) {
            return trans('validation.date_format', [
                'attribute' => $attribute,
                'format' => trim($format[1]),
            ], $locale);
        }

        return null;
    }

    /**
     * Laravel 11 在 locale=en 时常用「{attribute} must not be greater than 1.」短句（无 "The … field" 前缀）。
     */
    private static function humanizeCompactEnglish(
        string $field,
        string $message,
        string $locale,
        string $attribute,
    ): ?string {
        if (preg_match('/^(.+?)\s+must not be greater than ([\d.]+)\.?$/i', $message, $max) === 1) {
            $attribute = self::attributeLabelFromEnglish($max[1], $field, $locale);
            $custom = self::customRuleLine($field, 'max', $attribute, $locale);
            if ($custom !== null) {
                return $custom;
            }

            return trans('validation.max.numeric', ['attribute' => $attribute, 'max' => $max[2]], $locale);
        }

        if (preg_match('/^(.+?)\s+may not be greater than ([\d.]+)\.?$/i', $message, $max) === 1) {
            $attribute = self::attributeLabelFromEnglish($max[1], $field, $locale);

            return trans('validation.max.numeric', ['attribute' => $attribute, 'max' => $max[2]], $locale);
        }

        if (preg_match('/^(.+?)\s+must be less than or equal to ([\d.]+)\.?$/i', $message, $lte) === 1) {
            $attribute = self::attributeLabelFromEnglish($lte[1], $field, $locale);

            return trans('validation.lte.numeric', ['attribute' => $attribute, 'value' => $lte[2]], $locale);
        }

        if (preg_match('/^(.+?)\s+must not be less than ([\d.]+)\.?$/i', $message, $min) === 1) {
            $attribute = self::attributeLabelFromEnglish($min[1], $field, $locale);
            $custom = self::customRuleLine($field, 'min', $attribute, $locale);
            if ($custom !== null) {
                return $custom;
            }

            return trans('validation.min.numeric', ['attribute' => $attribute, 'min' => $min[2]], $locale);
        }

        if (preg_match('/^(.+?)\s+must be at least ([\d.]+)\.?$/i', $message, $min) === 1) {
            $attribute = self::attributeLabelFromEnglish($min[1], $field, $locale);
            $custom = self::customRuleLine($field, 'min', $attribute, $locale);
            if ($custom !== null) {
                return $custom;
            }

            return trans('validation.min.numeric', ['attribute' => $attribute, 'min' => $min[2]], $locale);
        }

        if (preg_match('/^(.+?)\s+must be between ([\d.]+) and ([\d.]+)\.?$/i', $message, $between) === 1) {
            $attribute = self::attributeLabelFromEnglish($between[1], $field, $locale);

            return trans('validation.between.numeric', [
                'attribute' => $attribute,
                'min' => $between[2],
                'max' => $between[3],
            ], $locale);
        }

        $compactTails = [
            'must be a number' => 'validation.numeric',
            'must be an integer' => 'validation.integer',
            'must be a string' => 'validation.string',
            'must be a boolean' => 'validation.boolean',
            'must be an array' => 'validation.array',
            'is required' => 'validation.required',
        ];

        foreach ($compactTails as $suffix => $ruleKey) {
            $pattern = '/^(.+?)\s+'.preg_quote($suffix, '/').'\.?$/i';
            if (preg_match($pattern, $message, $match) !== 1) {
                continue;
            }

            $attribute = self::attributeLabelFromEnglish($match[1], $field, $locale);
            $line = trans($ruleKey, ['attribute' => $attribute], $locale);

            return $line !== $ruleKey ? $line : null;
        }

        return null;
    }

    private static function attributeLabelFromEnglish(string $englishName, string $field, string $locale): string
    {
        $normalized = strtolower(trim($englishName));
        if (preg_match('/^selected (.+)$/i', $normalized, $selected) === 1) {
            $normalized = $selected[1];
        }

        $guess = str_replace(' ', '_', $normalized);

        return self::attributeLabel($guess !== '' ? $guess : $field, $locale);
    }

    private static function attributeLabel(string $field, string $locale): string
    {
        $candidates = array_values(array_unique([
            $field,
            self::flatFieldName($field),
            preg_replace('/\.\d+\./', '.*.', $field) ?? $field,
            preg_replace('/\.\d+\./', '.', $field) ?? $field,
        ]));

        foreach ($candidates as $candidate) {
            $key = 'validation.attributes.'.$candidate;
            $label = trans($key, [], $locale);
            if ($label !== $key) {
                return $label;
            }
        }

        return self::flatFieldName($field);
    }

    private static function flatFieldName(string $field): string
    {
        if (preg_match('/\.([a-zA-Z0-9_]+)$/', $field, $matches) === 1) {
            return $matches[1];
        }

        return $field;
    }
}
