// Copyright 2022-present 650 Industries. All rights reserved.

import Photos
import ExpoModulesCore
internal import React

public class CameraPermissionRequester: NSObject, EXPermissionsRequester {
  static public func permissionType() -> String {
    return "camera"
  }

  public func requestPermissions(resolver resolve: @escaping EXPromiseResolveBlock, rejecter reject: EXPromiseRejectBlock) {
    AVCaptureDevice.requestAccess(for: AVMediaType.video) { [weak self] _ in
      resolve(self?.getPermissions())
    }
  }

  public func getPermissions() -> [AnyHashable: Any] {
    var systemStatus: AVAuthorizationStatus
    var status: EXPermissionStatus
    let cameraUsageDescription = Bundle.main.object(forInfoDictionaryKey: "NSCameraUsageDescription")
    if cameraUsageDescription == nil {
      RCTFatal(RCTErrorWithMessage("""
      This app is missing 'NSCameraUsageDescription', video services will fail. \
      Ensure this key exists in the app's Info.plist
      """))
      systemStatus = AVAuthorizationStatus.denied
    } else {
      systemStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
    }

    switch systemStatus {
    case .authorized:
      status = EXPermissionStatusGranted
    case .restricted,
         .denied:
      status = EXPermissionStatusDenied
    case .notDetermined:
      fallthrough
    @unknown default:
      status = EXPermissionStatusUndetermined
    }

    return [
      "status": status.rawValue
    ]
  }
}

public class MediaLibraryPermissionRequester: DefaultMediaLibraryPermissionRequester,
                                              EXPermissionsRequester {
  public static func permissionType() -> String {
    return "mediaLibrary"
  }
}

public class MediaLibraryWriteOnlyPermissionRequester: DefaultMediaLibraryPermissionRequester,
                                                       EXPermissionsRequester {
  public static func permissionType() -> String {
    return "mediaLibraryWriteOnly"
  }

  override internal func accessLevel() -> PHAccessLevel {
    return PHAccessLevel.addOnly
  }
}

// MARK: - Permission requesters shared implementation extracted to an extension (mixin pattern)

/**
 * Dummy class just to prevent extending NSObject publicly/globally.
 */
public class DefaultMediaLibraryPermissionRequester: NSObject {}

/**
 * This extension is adding default implmentation for EXPermissionsRequester that can be shared by many classe.
 * In Swift language you cannot override static methods in subclasses, so you cannot subclass any already implemented
 * PermissionRequester as instances of this class are registered by the unique name coming from `static func permissionType()`.
 * To prevent repeating the similar code for every MediaLibrary PermissionRequester (the only differences so far are
 * aforementioned permissionType and accessLevel, while the latter can be easily overritten) I've extracted the code
 * to this extension. I'm using as a mixin that implements major part of EXPermissionsRequester protocol.
 */
extension DefaultMediaLibraryPermissionRequester {
  @objc
  public func requestPermissions(resolver resolve: @escaping EXPromiseResolveBlock, rejecter reject: EXPromiseRejectBlock) {
    PHPhotoLibrary.requestAuthorization(for: self.accessLevel()) { [weak self] (_: PHAuthorizationStatus) in
      resolve(self?.getPermissions())
    }
  }

  @objc
  public func getPermissions() -> [AnyHashable: Any] {
    let authorizationStatus = PHPhotoLibrary.authorizationStatus(for: self.accessLevel())
    var status: EXPermissionStatus
    var scope: String

    switch authorizationStatus {
    case .authorized:
      status = EXPermissionStatusGranted
      scope = "all"
    case .limited:
      status = EXPermissionStatusGranted
      scope = "limited"
    case .denied, .restricted:
      status = EXPermissionStatusDenied
      scope = "none"
    case .notDetermined:
      fallthrough
    @unknown default:
      status = EXPermissionStatusUndetermined
      scope = "none"
    }

    return [
      "status": status.rawValue,
      "accessPrivileges": scope
    ]
  }

  @objc
  internal func accessLevel() -> PHAccessLevel {
    return PHAccessLevel.readWrite
  }
}
