import SwiftUI
import ObjectiveC

/// App 支持的界面语言。`system` = 跟随系统;其余对应 .lproj / String Catalog 语言。
enum AppLanguage: String, CaseIterable, Identifiable {
    case system
    case zhHans = "zh-Hans"
    case en
    case ja
    case ko

    var id: String { rawValue }

    /// 选择器里展示的名字。各语言用其**本族语**显示(行业惯例,不本地化),仅「跟随系统」随 App 语言。
    var displayName: String {
        switch self {
        case .system: return String(appLoc: "跟随系统")
        case .zhHans: return "简体中文"
        case .en:     return "English"
        case .ja:     return "日本語"
        case .ko:     return "한국어"
        }
    }

    /// nil = 跟随系统;否则为 .lproj / Locale 标识。
    var localeIdentifier: String? {
        self == .system ? nil : rawValue
    }

    /// 语言选择器图标。各语言用本族语代表字区分(中 / A / あ / 가),
    /// 「跟随系统」非具体语言,用地球符号。代表字与 `displayName` 一样不本地化。
    enum PickerIcon: Equatable {
        case symbol(String)   // SF Symbol 名
        case glyph(String)    // 本族语代表字
    }

    var pickerIcon: PickerIcon {
        switch self {
        case .system: return .symbol("globe")
        case .zhHans: return .glyph("中")
        case .en:     return .glyph("A")
        case .ja:     return .glyph("あ")
        case .ko:     return .glyph("가")
        }
    }
}

/// 全 App 单例。负责:持久化选择、维护当前语言的 lproj bundle 与 locale。
/// - `Text("…")` 走根视图注入的环境 `\.locale`(+ Bundle 重定向)即时切换;
/// - `String(appLoc:)` 显式绑定本管理器的 bundle/locale,不受 `.current` 限制,同样即时切换。
/// 切换后由根视图 `.id(current)` 触发整树重建,无需重启。
@Observable
final class LanguageManager {
    static let shared = LanguageManager()

    private let storageKey = "appLanguage"

    private(set) var current: AppLanguage
    /// 当前语言对应的 .lproj bundle(system 或缺失时为 .main)。缓存,切换时更新。
    private(set) var lprojBundle: Bundle = .main
    /// 当前解析后的 locale(system 时为 .autoupdatingCurrent)。
    private(set) var resolvedLocale: Locale = .autoupdatingCurrent

    /// 供 SwiftUI 环境使用(日期/数字格式化 + Text 语言)。
    var locale: Locale { resolvedLocale }

    private init() {
        let saved = UserDefaults.standard.string(forKey: storageKey)
        current = AppLanguage(rawValue: saved ?? "") ?? .system
        apply()
    }

    func set(_ language: AppLanguage) {
        guard language != current else { return }
        current = language
        UserDefaults.standard.set(language.rawValue, forKey: storageKey)
        // 同步 AppleLanguages:保证下次冷启动解析正确,并与系统「设置 → App → 语言」一致。
        if let id = language.localeIdentifier {
            UserDefaults.standard.set([id], forKey: "AppleLanguages")
        } else {
            UserDefaults.standard.removeObject(forKey: "AppleLanguages")
        }
        apply()
    }

    private func apply() {
        if let id = current.localeIdentifier {
            resolvedLocale = Locale(identifier: id)
            if let path = Bundle.main.path(forResource: id, ofType: "lproj"),
               let b = Bundle(path: path) {
                lprojBundle = b
            } else {
                lprojBundle = .main
            }
        } else {
            resolvedLocale = .autoupdatingCurrent
            lprojBundle = .main
        }
        Bundle.redirectMain(to: current.localeIdentifier)
        // 同步 nonisolated 快照,供 String(appLoc:) 在非 MainActor 上下文读取。
        appLocBundle = lprojBundle
        appLocLocale = resolvedLocale
    }
}

/// nonisolated 快照:`String(appLoc:)` 可能在非 MainActor 上下文被调用
/// (LocalizedError.errorDescription、nonisolated 枚举 label、static 解析器…)。
/// 只由 `LanguageManager.apply()`(MainActor)写入,切换语言时刷新;读为快照,无竞态影响。
nonisolated(unsafe) private var appLocBundle: Bundle = .main
nonisolated(unsafe) private var appLocLocale: Locale = .autoupdatingCurrent

extension String {
    /// 尊重「我的 · 语言」选择的本地化(可即时切换)。
    /// 等价 `String(localized:)`,但显式绑定当前所选语言的 bundle + locale,
    /// 因此不受 `Locale.current`(系统/启动时语言)限制。
    nonisolated init(appLoc key: String.LocalizationValue) {
        self = String(localized: key, bundle: appLocBundle, locale: appLocLocale)
    }
}

// MARK: - Bundle 运行时重定向(供 Text / NSLocalizedString 双保险)

/// 关联对象 key(需稳定地址,文件级全局即可)。
private var redirectBundleKey: UInt8 = 0

/// 把 `Bundle.main` 的字符串查表重定向到指定语言的 .lproj。
private final class LocalizedMainBundle: Bundle, @unchecked Sendable {
    override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        if let target = objc_getAssociatedObject(self, &redirectBundleKey) as? Bundle {
            return target.localizedString(forKey: key, value: value, table: tableName)
        }
        return super.localizedString(forKey: key, value: value, table: tableName)
    }
}

extension Bundle {
    /// language == nil → 跟随系统(走默认解析)。
    static func redirectMain(to language: String?) {
        if !(Bundle.main is LocalizedMainBundle) {
            object_setClass(Bundle.main, LocalizedMainBundle.self)
        }
        let target: Bundle?
        if let language,
           let path = Bundle.main.path(forResource: language, ofType: "lproj"),
           let bundle = Bundle(path: path) {
            target = bundle
        } else {
            target = nil
        }
        objc_setAssociatedObject(Bundle.main, &redirectBundleKey, target, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}
