import Foundation

public enum PreferenceKey: String, CaseIterable, Equatable, Sendable {
    case schemaVersion = "schemaVersion"
    case showMinimizedWindows = "quickSwitch.showMinimizedWindows"
    case showFullscreenWindows = "quickSwitch.showFullscreenWindows"
    case quickSwitchWaterfallViewMode = "quickSwitch.waterfallViewMode"
    case theme = "appearance.theme"
    case language = "appearance.language"
    case spaceDisplayStrategy = "spaces.displayStrategy"

    public var storageKey: String {
        "com.ar.Aligner.preferences.\(rawValue)"
    }
}

public enum ThemePreference: String, CaseIterable, Equatable, Sendable {
    case system
    case light
    case dark
}

public enum LanguagePreference: String, CaseIterable, Equatable, Sendable {
    case system
    case english
    case simplifiedChinese
}

public enum SpaceDisplayStrategy: String, CaseIterable, Equatable, Sendable {
    case includeVisibleAndFullscreen
    case currentSpaceOnly
}

public enum QuickSwitchWaterfallViewMode: String, CaseIterable, Equatable, Sendable {
    case verticalColumns
    case horizontalMasonry
}

public struct AlignerPreferences: Equatable, Sendable {
    public let showMinimizedWindows: Bool
    public let showFullscreenWindows: Bool
    public let quickSwitchWaterfallViewMode: QuickSwitchWaterfallViewMode
    public let theme: ThemePreference
    public let language: LanguagePreference
    public let spaceDisplayStrategy: SpaceDisplayStrategy

    public init(
        showMinimizedWindows: Bool,
        showFullscreenWindows: Bool,
        quickSwitchWaterfallViewMode: QuickSwitchWaterfallViewMode,
        theme: ThemePreference,
        language: LanguagePreference,
        spaceDisplayStrategy: SpaceDisplayStrategy
    ) {
        self.showMinimizedWindows = showMinimizedWindows
        self.showFullscreenWindows = showFullscreenWindows
        self.quickSwitchWaterfallViewMode = quickSwitchWaterfallViewMode
        self.theme = theme
        self.language = language
        self.spaceDisplayStrategy = spaceDisplayStrategy
    }
}

public enum PreferenceSchema {
    public static let version = 2

    public static let defaults = AlignerPreferences(
        showMinimizedWindows: true,
        showFullscreenWindows: true,
        quickSwitchWaterfallViewMode: .verticalColumns,
        theme: .system,
        language: .system,
        spaceDisplayStrategy: .includeVisibleAndFullscreen
    )

    public static var registrationDictionary: [String: Any] {
        [
            PreferenceKey.schemaVersion.storageKey: version,
            PreferenceKey.showMinimizedWindows.storageKey: defaults.showMinimizedWindows,
            PreferenceKey.showFullscreenWindows.storageKey: defaults.showFullscreenWindows,
            PreferenceKey.quickSwitchWaterfallViewMode.storageKey: defaults.quickSwitchWaterfallViewMode.rawValue,
            PreferenceKey.theme.storageKey: defaults.theme.rawValue,
            PreferenceKey.language.storageKey: defaults.language.rawValue,
            PreferenceKey.spaceDisplayStrategy.storageKey: defaults.spaceDisplayStrategy.rawValue
        ]
    }
}

public protocol PreferenceStoreProtocol {
    func registerDefaults()
    func storedSchemaVersion() -> Int
    func needsMigration() -> Bool
    func read() -> AlignerPreferences
    func write(_ preferences: AlignerPreferences)
    func reset()
}

public final class UserDefaultsPreferenceStore: PreferenceStoreProtocol {
    private let userDefaults: UserDefaults

    public init(userDefaults: UserDefaults = .standard) {
        self.userDefaults = userDefaults
    }

    public func registerDefaults() {
        userDefaults.register(defaults: PreferenceSchema.registrationDictionary)
    }

    public func storedSchemaVersion() -> Int {
        registerDefaults()
        return userDefaults.integer(forKey: PreferenceKey.schemaVersion.storageKey)
    }

    public func needsMigration() -> Bool {
        storedSchemaVersion() < PreferenceSchema.version
    }

    public func read() -> AlignerPreferences {
        registerDefaults()

        return AlignerPreferences(
            showMinimizedWindows: userDefaults.bool(forKey: PreferenceKey.showMinimizedWindows.storageKey),
            showFullscreenWindows: userDefaults.bool(forKey: PreferenceKey.showFullscreenWindows.storageKey),
            quickSwitchWaterfallViewMode: rawValue(
                for: .quickSwitchWaterfallViewMode,
                default: PreferenceSchema.defaults.quickSwitchWaterfallViewMode
            ),
            theme: rawValue(for: .theme, default: PreferenceSchema.defaults.theme),
            language: rawValue(for: .language, default: PreferenceSchema.defaults.language),
            spaceDisplayStrategy: rawValue(
                for: .spaceDisplayStrategy,
                default: PreferenceSchema.defaults.spaceDisplayStrategy
            )
        )
    }

    public func write(_ preferences: AlignerPreferences) {
        userDefaults.set(PreferenceSchema.version, forKey: PreferenceKey.schemaVersion.storageKey)
        userDefaults.set(preferences.showMinimizedWindows, forKey: PreferenceKey.showMinimizedWindows.storageKey)
        userDefaults.set(preferences.showFullscreenWindows, forKey: PreferenceKey.showFullscreenWindows.storageKey)
        userDefaults.set(
            preferences.quickSwitchWaterfallViewMode.rawValue,
            forKey: PreferenceKey.quickSwitchWaterfallViewMode.storageKey
        )
        userDefaults.set(preferences.theme.rawValue, forKey: PreferenceKey.theme.storageKey)
        userDefaults.set(preferences.language.rawValue, forKey: PreferenceKey.language.storageKey)
        userDefaults.set(preferences.spaceDisplayStrategy.rawValue, forKey: PreferenceKey.spaceDisplayStrategy.storageKey)
    }

    public func reset() {
        for key in PreferenceKey.allCases {
            userDefaults.removeObject(forKey: key.storageKey)
        }
        registerDefaults()
    }

    private func rawValue<T: RawRepresentable>(for key: PreferenceKey, default defaultValue: T) -> T where T.RawValue == String {
        guard let value = userDefaults.string(forKey: key.storageKey) else {
            return defaultValue
        }

        return T(rawValue: value) ?? defaultValue
    }
}
