import CameraClient
import ComponentLibrary
import ComposableArchitecture
import Localization
import PhotoLibraryClient
import Photos
import SwiftUI
import Utilities

@Reducer
public struct PhotoPicker {
    @Reducer(state: .equatable)
    public enum Destination {
        @Reducer public struct Camera {}
        @Reducer public struct PhotoLibrary {}

        case camera(Camera)
        case library(PhotoLibrary)
        case alert(AlertState<Alert>)

        public enum Alert {
            case openSettings
        }
    }

    public enum Action: BindableAction {
        case destination(PresentationAction<Destination.Action>)

        case cameraRowTapped
        case libraryRowTapped
        case dismissPicker

        case selectImage(Result<UIImage, Error>)

        case binding(BindingAction<State>)
        case `internal`(Internal)

        public enum Internal {
            case libraryPermissionResponse(Result<PHAuthorizationStatus, Error>)
            case cameraPermissionResponse(Result<Bool, Error>)
            case fetchPhotoLibraryImagesResult(Result<[UIImage], Error>)
        }
    }

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

        var isRequestingLibraryAccess = false
        var isRequestingCameraAccess = false
        var shouldAutoDismiss = true
        var shouldShowDividers = false
        var spacing: CGFloat?

        public init(shouldAutoDismiss: Bool = true, shouldShowDividers: Bool = false, spacing: CGFloat? = nil) {
            self.shouldAutoDismiss = shouldAutoDismiss
            self.shouldShowDividers = shouldShowDividers
            self.spacing = spacing
        }
    }

    @Dependency(\.mainQueue) var mainQueue
    @Dependency(PhotoLibraryClient.self) var photoLibraryClient
    @Dependency(CameraClient.self) var cameraClient
    @Dependency(\.telemetryClient) var telemetry
    @Dependency(\.dismiss) var dismiss

    public init() {}

    public var body: some ReducerOf<Self> {
        BindingReducer()
        Reduce { state, action in
            switch action {
            case .destination(.presented(.alert(.openSettings))):
                return .run { _ in
                    guard let url = URL(string: UIApplication.openSettingsURLString),
                          await UIApplication.shared.canOpenURL(url)
                    else {
                        assertionFailure(L10n.FeaturePhotoPicker.settingsFailure)
                        return
                    }

                    await UIApplication.shared.open(url, options: [:], completionHandler: nil)
                }

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

            case .cameraRowTapped:
                state.isRequestingCameraAccess = true
                return .run { send in
                    await send(.internal(.cameraPermissionResponse(Result(catching: { await cameraClient.requestAuthorization() }))))
                }

            case .internal(.cameraPermissionResponse(.success(let granted))):
                // TODO: Handle granted == false
                state.isRequestingCameraAccess = false
                let status = cameraClient.authorizationStatus()
                switch status {
                case .notDetermined:
                    return .run { send in
                        try await Task.sleep(for: .seconds(1))
                        await send(.internal(.cameraPermissionResponse(.success(granted))))
                    }

                case .restricted,
                     .denied:
                    state.destination = .alert(.init(
                        title: { TextState(L10n.FeaturePhotoPicker.cameraAccessTitle) },
                        actions: {
                            ButtonState(role: .destructive, action: .openSettings) { TextState(L10n.FeaturePhotoPicker.cameraAccessButtonSettings) }
                            ButtonState(role: .cancel) { TextState(L10n.FeaturePhotoPicker.cameraAccessButtonCancel) }
                        },
                        message: { TextState(L10n.FeaturePhotoPicker.cameraAccessMessage) }
                    ))
                    return .none

                case .authorized:
                    state.destination = .camera(.init())
                    return .none

                @unknown default:
                    return .none
                }

            case .internal(.cameraPermissionResponse(.failure(let error))):
                state.isRequestingCameraAccess = false
                log.telemetry.error(error)
                return .none

            case .libraryRowTapped:
                state.isRequestingLibraryAccess = true
                return .run { send in
                    await send(.internal(.libraryPermissionResponse(Result(catching: { await photoLibraryClient.requestAuthorization() }))))
                }

            case .internal(.libraryPermissionResponse(.success)):
                // TODO: Handle granted == false
                state.isRequestingLibraryAccess = false
                state.destination = .library(.init())
                return .none

            case .internal(.libraryPermissionResponse(.failure(let error))):
                state.isRequestingLibraryAccess = false
                log.telemetry.error(error)
                return .none

            case .selectImage(.success):
                state.destination = nil
                guard state.shouldAutoDismiss else { return .none }

                return .run { _ in await dismiss() }

            case .selectImage(.failure(let error)):
                log.telemetry.error(error)
                return .none

            case .dismissPicker:
                state.destination = nil
                return .none

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

            case .internal:
                // Catch-all
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

public struct PhotoPickerView: View {
    @Dependency(\.telemetryClient) var telemetry
    @Bindable var store: StoreOf<PhotoPicker>

    public init(store: StoreOf<PhotoPicker>) {
        self.store = store
    }

    public var body: some View {
        VStack(spacing: store.spacing) {
            VStack(spacing: 0) {
                MenuRowV1(
                    title: L10n.FeaturePhotoPicker.takePhoto,
                    isLoading: false,
                    icon: Image(systemName: "camera"),
                    action: {
                        store.send(.cameraRowTapped)
                    }
                )
                Divider()
            }

            VStack(spacing: 0) {
                MenuRowV1(
                    title: L10n.FeaturePhotoPicker.library,
                    isLoading: false,
                    icon: Image(systemName: "photo.on.rectangle.angled"),
                    action: {
                        store.send(.libraryRowTapped)
                    }
                )
                Divider()
            }
        }
        .padding(.horizontal, 24)
        .padding(.vertical, 8)
        .padding(.top, 16)
        .sheet(item: $store.scope(state: \.destination?.library, action: \.destination.library)) { _ in
            PHPickerViewRepresentable { result in
                store.send(.selectImage(Result(catching: { try result.get().image })))
            } cancel: {
                store.send(.dismissPicker)
            }
            .edgesIgnoringSafeArea(.bottom)
        }
        .fullScreenCover(item: $store.scope(state: \.destination?.camera, action: \.destination.camera)) { _ in
            CameraView { result in
                store.send(.selectImage(Result(catching: { try result.get() })))
            } cancel: {
                store.send(.dismissPicker)
            }
            .background(Color.black)
            .overlay(alignment: .topTrailing) {
                ToolbarButton(.close, background: Material.ultraThin) {
                    store.send(.dismissPicker)
                }
                .padding(.trailing, 20)
                .colorScheme(.dark)
            }
        }
        .alert($store.scope(state: \.destination?.alert, action: \.destination.alert))
    }
}

#Preview("Photo Picker") {
    PhotoPickerView(
        store: Store(initialState: PhotoPicker.State()) {
            PhotoPicker()
        }
    )
}
