import ComposableArchitecture
import Foundation
import Sharing
import StatsigClient
import UIKit
import Utilities

// MARK: - HapticsClient

@DependencyClient
public struct HapticsClient {
    public var impact: @Sendable (ImpactStyle) -> Void
    public var notification: @Sendable (NotificationType) -> Void
    public var selection: @Sendable () -> Void
    public var trigger: @Sendable (HapticStyle) -> Void = { _ in }
}

// MARK: - Dependency

public extension DependencyValues {
    var hapticsClient: HapticsClient {
        get { self[HapticsClient.self] }
        set { self[HapticsClient.self] = newValue }
    }
}

extension HapticsClient: TestDependencyKey {
    public static let testValue = Self(
        impact: { _ in },
        notification: { _ in },
        selection: {},
        trigger: { _ in }
    )
}

// MARK: - Models

public enum ImpactStyle: Sendable {
    case light
    case medium
    case heavy
    case soft
    case rigid

    var uiKitStyle: UIImpactFeedbackGenerator.FeedbackStyle {
        switch self {
        case .light: return .light
        case .medium: return .medium
        case .heavy: return .heavy
        case .soft: return .soft
        case .rigid: return .rigid
        }
    }
}

public enum NotificationType: Sendable {
    case success
    case warning
    case error

    var uiKitType: UINotificationFeedbackGenerator.FeedbackType {
        switch self {
        case .success: return .success
        case .warning: return .warning
        case .error: return .error
        }
    }
}

public enum HapticStyle: Sendable {
    case impact(ImpactStyle)
    case notification(NotificationType)
    case selection
}

// MARK: - Implementation

extension HapticsClient: DependencyKey {
    public static let liveValue: Self = {
        let impactGenerators: [ImpactStyle: UIImpactFeedbackGenerator] = [
            .light: UIImpactFeedbackGenerator(style: .light),
            .medium: UIImpactFeedbackGenerator(style: .medium),
            .heavy: UIImpactFeedbackGenerator(style: .heavy),
            .soft: UIImpactFeedbackGenerator(style: .soft),
            .rigid: UIImpactFeedbackGenerator(style: .rigid),
        ]

        let notificationGenerator = UINotificationFeedbackGenerator()
        let selectionGenerator = UISelectionFeedbackGenerator()

        if FeatureFlag.legacy.hapticsClientEnabled {
            impactGenerators.values.forEach { $0.prepare() }
            notificationGenerator.prepare()
            selectionGenerator.prepare()
        }

        return Self(
            impact: { style in
                @Shared(.appStorage(.hapticsEnabled)) var hapticsEnabled: Bool = true
                guard hapticsEnabled, FeatureFlag.legacy.hapticsClientEnabled else { return }
                Task {
                    await impactGenerators[style]?.impactOccurred()
                }
            },
            notification: { type in
                @Shared(.appStorage(.hapticsEnabled)) var hapticsEnabled: Bool = true
                guard hapticsEnabled, FeatureFlag.legacy.hapticsClientEnabled else { return }
                Task {
                    await notificationGenerator.notificationOccurred(type.uiKitType)
                }
            },
            selection: {
                @Shared(.appStorage(.hapticsEnabled)) var hapticsEnabled: Bool = true
                guard hapticsEnabled, FeatureFlag.legacy.hapticsClientEnabled else { return }
                Task {
                    await selectionGenerator.selectionChanged()
                }
            },
            trigger: { style in
                @Shared(.appStorage(.hapticsEnabled)) var hapticsEnabled: Bool = true
                guard hapticsEnabled, FeatureFlag.legacy.hapticsClientEnabled else { return }
                Task {
                    switch style {
                    case .impact(let impactStyle):
                        await impactGenerators[impactStyle]?.impactOccurred()
                    case .notification(let type):
                        await notificationGenerator.notificationOccurred(type.uiKitType)
                    case .selection:
                        await selectionGenerator.selectionChanged()
                    }
                }
            }
        )
    }()
}
