<?php

declare(strict_types=1);

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

namespace App\Services\Install;

use Collator;
use DateTimeImmutable;
use DateTimeZone;
use IntlDateFormatter;
use IntlException;
use Locale;
use Throwable;

final class InstallWizardTimezoneOptions
{
    /**
     * 中文界面下的城市名（不含国家前缀）。展示时统一加「中国·」。
     *
     * @var array<string, string>
     */
    private const ZH_NAME_OVERRIDES = [
        'Asia/Shanghai' => '上海时间',
        'PRC' => '上海时间',
        'Asia/Chongqing' => '重庆时间',
        'Asia/Harbin' => '哈尔滨时间',
        'Asia/Urumqi' => '乌鲁木齐时间',
        'Asia/Kashgar' => '乌鲁木齐时间',
        'Asia/Hong_Kong' => '香港时间',
        'Asia/Macau' => '澳门时间',
        'Asia/Taipei' => '台湾时间',
    ];

    /**
     * 属于中国的时区置顶顺序（内地、新疆、港澳台）。
     *
     * @var list<string>
     */
    private const CHINA_TIMEZONE_ORDER = [
        'Asia/Shanghai',
        'Asia/Urumqi',
        'Asia/Hong_Kong',
        'Asia/Macau',
        'Asia/Taipei',
        'Asia/Chongqing',
        'Asia/Harbin',
        'Asia/Kashgar',
        'PRC',
    ];

    /**
     * GMT/UTC 置顶；不含 Etc/GMT+8 这类固定偏移区。
     *
     * @var list<string>
     */
    private const UTC_TIMEZONE_ORDER = [
        'UTC',
        'GMT',
        'Etc/UTC',
        'Etc/GMT',
        'Etc/GMT+0',
        'Etc/GMT-0',
    ];

    /**
     * @return array<string, string>
     */
    public static function optionsForSelect(string $locale = 'zh_CN'): array
    {
        $options = [];

        foreach (self::sortedForSelect($locale) as $row) {
            $options[$row['value']] = $row['label'];
        }

        return $options;
    }

    /**
     * @return list<array{value: string, label: string}>
     */
    public static function sortedForSelect(string $locale = 'zh_CN'): array
    {
        $rows = [];
        foreach (timezone_identifiers_list() as $id) {
            $group = self::sortGroup($id);
            $countryName = match ($group) {
                1 => self::isZhLocale($locale) ? '中国' : self::countryNameForTimezone($id, $locale),
                2 => self::countryNameForTimezone($id, $locale),
                default => '',
            };
            $label = self::labelForIdentifier($id, $locale);
            if ($countryName !== '' && ! str_starts_with($label, $countryName.'·')) {
                $label = $countryName.'·'.$label;
            }

            $rows[] = [
                'value' => $id,
                'label' => $label,
                'group' => $group,
                'country' => $countryName,
                'offset' => self::currentOffsetSeconds($id),
            ];
        }

        usort($rows, static function (array $a, array $b) use ($locale): int {
            if ($a['group'] !== $b['group']) {
                return $a['group'] <=> $b['group'];
            }

            if ($a['group'] === 0) {
                return self::pinnedOrder($a['value'], self::UTC_TIMEZONE_ORDER)
                    <=> self::pinnedOrder($b['value'], self::UTC_TIMEZONE_ORDER)
                    ?: strcmp($a['value'], $b['value']);
            }

            if ($a['group'] === 1) {
                return self::pinnedOrder($a['value'], self::CHINA_TIMEZONE_ORDER)
                    <=> self::pinnedOrder($b['value'], self::CHINA_TIMEZONE_ORDER)
                    ?: strcmp($a['value'], $b['value']);
            }

            return self::compareByCountryThenOffset($a, $b, $locale);
        });

        return array_map(static fn (array $row): array => [
            'value' => $row['value'],
            'label' => $row['label'],
        ], $rows);
    }

    public static function labelForIdentifier(string $timezoneId, string $locale = 'zh_CN'): string
    {
        if ($timezoneId === '') {
            return $timezoneId;
        }

        $display = self::localizedLocationName($timezoneId, $locale);
        if (self::isZhLocale($locale) && self::isChinaTimezone($timezoneId) && $display !== '' && $display !== $timezoneId) {
            $display = '中国·'.$display;
        }

        if ($display === '' || $display === $timezoneId) {
            return $timezoneId;
        }

        return sprintf('%s（%s）', $display, $timezoneId);
    }

    /**
     * 名称优先用产品中文覆盖；其余尽量用 ICU 地点名。
     * ICU 落后于 PHP tzdata 时（如 America/Ciudad_Juarez），改用 PHP 时区表解析出的城市名。
     */
    private static function localizedLocationName(string $timezoneId, string $locale): string
    {
        if (self::isZhLocale($locale) && isset(self::ZH_NAME_OVERRIDES[$timezoneId])) {
            return self::ZH_NAME_OVERRIDES[$timezoneId];
        }

        if (extension_loaded('intl')) {
            try {
                $location = self::formatIntlPattern($timezoneId, $locale, 'VVVV');
                if ($location === '' || $location === $timezoneId || $location === '未知城市' || strcasecmp($location, 'Unknown City') === 0) {
                    $location = self::formatIntlPattern($timezoneId, $locale, 'zzzz');
                }

                if ($location !== '' && $location !== $timezoneId && $location !== '未知城市' && strcasecmp($location, 'Unknown City') !== 0) {
                    return $location;
                }
            } catch (Throwable) {
            }
        }

        return self::cityFromPhpTimezone($timezoneId);
    }

    private static function formatIntlPattern(string $timezoneId, string $locale, string $pattern): string
    {
        try {
            $formatter = new IntlDateFormatter(
                $locale,
                IntlDateFormatter::NONE,
                IntlDateFormatter::NONE,
                $timezoneId,
                IntlDateFormatter::GREGORIAN,
                $pattern,
            );
        } catch (IntlException) {
            return '';
        }

        if (! $formatter instanceof IntlDateFormatter) {
            return '';
        }

        $formatted = $formatter->format(self::nowInTimezone($timezoneId));

        return is_string($formatted) ? $formatted : '';
    }

    /**
     * 用 PHP 自带 tzdata 取「现在」的瞬时，避免 ICU 用 epoch 算出过时的夏令时名称。
     */
    private static function nowInTimezone(string $timezoneId): DateTimeImmutable
    {
        try {
            return new DateTimeImmutable('now', new DateTimeZone($timezoneId));
        } catch (Throwable) {
            return new DateTimeImmutable('@0');
        }
    }

    private static function cityFromPhpTimezone(string $timezoneId): string
    {
        try {
            new DateTimeZone($timezoneId);
        } catch (Throwable) {
            return $timezoneId;
        }

        $segments = explode('/', $timezoneId);
        $city = str_replace('_', ' ', (string) end($segments));

        return $city !== '' ? $city : $timezoneId;
    }

    /**
     * @param  array{value: string, label: string, country: string, offset: int}  $a
     * @param  array{value: string, label: string, country: string, offset: int}  $b
     */
    private static function compareByCountryThenOffset(array $a, array $b, string $locale): int
    {
        $missingCountryA = $a['country'] === '' ? 1 : 0;
        $missingCountryB = $b['country'] === '' ? 1 : 0;
        if ($missingCountryA !== $missingCountryB) {
            return $missingCountryA <=> $missingCountryB;
        }

        $byCountry = self::compareLocalized($a['country'], $b['country'], $locale);
        if ($byCountry !== 0) {
            return $byCountry;
        }

        if ($a['offset'] !== $b['offset']) {
            return $b['offset'] <=> $a['offset'];
        }

        $byLabel = self::compareLocalized($a['label'], $b['label'], $locale);
        if ($byLabel !== 0) {
            return $byLabel;
        }

        return strcmp($a['value'], $b['value']);
    }

    private static function compareLocalized(string $left, string $right, string $locale): int
    {
        if ($left === $right) {
            return 0;
        }

        if (extension_loaded('intl')) {
            $collator = Collator::create($locale) ?? Collator::create('root');
            if ($collator instanceof Collator) {
                $compared = $collator->compare($left, $right);
                if (is_int($compared)) {
                    return $compared;
                }
            }
        }

        return strcmp($left, $right);
    }

    private static function countryNameForTimezone(string $timezoneId, string $locale): string
    {
        $code = self::countryCode($timezoneId);
        if ($code === '') {
            return '';
        }

        if (! extension_loaded('intl')) {
            return $code;
        }

        foreach (['_'.$code, 'und_'.$code] as $tag) {
            $name = Locale::getDisplayRegion($tag, $locale);
            if (is_string($name) && $name !== '' && $name !== $code && $name !== $tag) {
                return $name;
            }
        }

        return $code;
    }

    private static function countryCode(string $timezoneId): string
    {
        $location = self::timezoneLocation($timezoneId);
        $code = $location['country_code'] ?? null;

        return is_string($code) && $code !== '' && $code !== '??' ? $code : '';
    }

    private static function currentOffsetSeconds(string $timezoneId): int
    {
        try {
            $timezone = new DateTimeZone($timezoneId);

            return $timezone->getOffset(new DateTimeImmutable('now', $timezone));
        } catch (Throwable) {
            return 0;
        }
    }

    /**
     * @return array<string, mixed>|null
     */
    private static function timezoneLocation(string $timezoneId): ?array
    {
        try {
            $location = (new DateTimeZone($timezoneId))->getLocation();
        } catch (Throwable) {
            return null;
        }

        return is_array($location) ? $location : null;
    }

    /**
     * 0 = GMT/UTC，1 = 中国，2 = 其他。
     */
    private static function sortGroup(string $timezoneId): int
    {
        if (self::pinnedOrder($timezoneId, self::UTC_TIMEZONE_ORDER) < 1000) {
            return 0;
        }

        if (self::pinnedOrder($timezoneId, self::CHINA_TIMEZONE_ORDER) < 1000 || self::isChinaLocation($timezoneId)) {
            return 1;
        }

        return 2;
    }

    /**
     * @param  list<string>  $order
     */
    private static function pinnedOrder(string $timezoneId, array $order): int
    {
        $index = array_search($timezoneId, $order, true);

        return $index === false ? 1000 : $index;
    }

    private static function isChinaTimezone(string $timezoneId): bool
    {
        return self::pinnedOrder($timezoneId, self::CHINA_TIMEZONE_ORDER) < 1000
            || self::isChinaLocation($timezoneId);
    }

    private static function isChinaLocation(string $timezoneId): bool
    {
        return in_array(self::countryCode($timezoneId), ['CN', 'HK', 'MO', 'TW'], true);
    }

    private static function isZhLocale(string $locale): bool
    {
        $normalized = strtolower(str_replace('-', '_', $locale));

        return str_starts_with($normalized, 'zh');
    }
}
