import ComposableArchitecture
import ContactsClient
import Localization
import SwiftUI
import Utilities

@Reducer
public struct ContactsAuthorizationAlert {
    @ObservableState
    public struct State: Equatable {
        @Presents public var destination: Destination.State?

        public init() {}
    }

    @Reducer(state: .equatable)
    public enum Destination {
        case alert(AlertState<Alert>)

        public enum Alert {
            case openSettings
            case cancelOpenSettings
        }
    }

    public enum Action {
        case onAppear
        case didBecomeActive
        case requestPermission
        case openSettings
        case handleAuthorization
        case delegate(Delegate)
        case destination(PresentationAction<Destination.Action>)

        public enum Delegate {
            case authorized
            case denied
        }
    }

    public init() {}

    @Dependency(ContactsClient.self) var contactsClient
    @Dependency(\.openSettings) var openSettings
    @Dependency(\.dismiss) var dismiss

    public var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            // Display auth automatically when the alert gets created,
            // as well as when the user foregrounds the app after we direct them to settings
            case .onAppear, .didBecomeActive:
                return .send(.handleAuthorization)

            case .requestPermission:
                return .run { send in
                    let authorized = await contactsClient.requestPermission()
                    // Put the user back into the authorization pipeline
                    if !authorized {
                        await send(.delegate(.denied))
                        return
                    }
                    // iOS 18 contacts permissioning triggers `.didBecomeActive` when returning from it,
                    // which causes a race condition if we call `await send(.delegate(.authorized))`
                    // Hence, have `.didBecomeActive` handle the delegate send pipeline for iOS 18
                    if #unavailable(iOS 18) {
                        await send(.delegate(.authorized))
                    }
                }

            case .openSettings:
                return .run { _ in
                    await self.openSettings()
                }

            case .handleAuthorization:
                switch contactsClient.getAuthorizationStatus() {
                case .notDetermined:
                    return .send(.requestPermission)
                case .authorized:
                    return .send(.delegate(.authorized))
                case .denied:
                    state.destination = .alert(.init(
                        title: { TextState(L10n.FeatureSocial.openSettingsTitle) },
                        actions: {
                            ButtonState(action: .openSettings) { TextState(L10n.FeatureSocial.errorButton) }
                            ButtonState(role: .cancel, action: .cancelOpenSettings) { TextState(L10n.FeatureSocial.cancel) }

                        },
                        message: { TextState(L10n.FeatureSocial.openSettingsMessage) }
                    ))
                    return .none
                }

            case .destination(.presented(.alert(.openSettings))):
                return .send(.openSettings)

            case .destination(.presented(.alert(.cancelOpenSettings))):
                return .send(.delegate(.denied))

            case .destination:
                // Catch-all
                return .none

            case .delegate:
                // Clean up
                state.destination = nil
                return .run { _ in await self.dismiss() }
            }
        }
        .ifLet(\.$destination, action: \.destination)

        Analytics()
    }
}

public extension View {
    func contactsAuthorizationAlert(
        _ store: Binding<Store<ContactsAuthorizationAlert.State, ContactsAuthorizationAlert.Action>?>
    ) -> some View {
        self.modifier(ContactsAuthorizationAlertViewModifier(store: store))
    }
}

public struct ContactsAuthorizationAlertViewModifier: ViewModifier {
    @Binding var store: StoreOf<ContactsAuthorizationAlert>?
    @Environment(\.scenePhase) var scenePhase

    public func body(content: Content) -> some View {
        ZStack {
            content
            if let store {
                Color.clear
                    .presentationBackground(.clear)
                    .ignoresSafeArea()
                    .onAppear {
                        store.send(.onAppear)
                    }
                    .onChange(of: scenePhase) {
                        if scenePhase == .active {
                            store.send(.didBecomeActive)
                        }
                    }
                    .alert(store: store.scope(state: \.$destination.alert, action: \.destination.alert))
            }
        }
    }
}
