import Tauri
import UserNotifications

public class NotificationHandler: NSObject, NotificationHandlerProtocol {

  public weak var plugin: Plugin?

  private var notificationsMap = [String: JSObject]()

  public func saveNotification(_ key: String, _ notification: JSObject) {
    notificationsMap.updateValue(notification, forKey: key)
  }

  public func requestPermissions(with completion: ((Bool, Error?) -> Void)? = nil) {
    let center = UNUserNotificationCenter.current()
    center.requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
      completion?(granted, error)
    }
  }

  public func checkPermissions(with completion: ((UNAuthorizationStatus) -> Void)? = nil) {
    let center = UNUserNotificationCenter.current()
    center.getNotificationSettings { settings in
      completion?(settings.authorizationStatus)
    }
  }

  public func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions {
    let notificationData = makeNotificationRequestJSObject(notification.request)
    self.plugin?.trigger("notification", data: notificationData)

    if let options = notificationsMap[notification.request.identifier] {
      let silent = options["silent"] as? Bool ?? false
      if silent {
        return UNNotificationPresentationOptions.init(rawValue: 0)
      }
    }

    return [
      .badge,
      .sound,
      .alert,
    ]
  }

  public func didReceive(response: UNNotificationResponse) {
    var data = JSObject()

    let originalNotificationRequest = response.notification.request
    let actionId = response.actionIdentifier

    // We turn the two default actions (open/dismiss) into generic strings
    if actionId == UNNotificationDefaultActionIdentifier {
      data["actionId"] = "tap"
    } else if actionId == UNNotificationDismissActionIdentifier {
      data["actionId"] = "dismiss"
    } else {
      data["actionId"] = actionId
    }

    // If the type of action was for an input type, get the value
    if let inputType = response as? UNTextInputNotificationResponse {
      data["inputValue"] = inputType.userText
    }

    data["notification"] = makeNotificationRequestJSObject(originalNotificationRequest)

    self.plugin?.trigger("actionPerformed", data: data)
  }

  /**
    * Turn a UNNotificationRequest into a JSObject to return back to the client.
    */
  func makeNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject {
    let notificationRequest = notificationsMap[request.identifier] ?? [:]
    var notification = makePendingNotificationRequestJSObject(request)
    notification["sound"] = notificationRequest["sound"] ?? ""
    notification["actionTypeId"] = request.content.categoryIdentifier
    notification["attachments"] = notificationRequest["attachments"] ?? [JSObject]()
    return notification
  }

  func makePendingNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject {
    var notification: JSObject = [
      "id": Int(request.identifier) ?? -1,
      "title": request.content.title,
      "body": request.content.body,
    ]

    if let userInfo = JSTypes.coerceDictionaryToJSObject(request.content.userInfo) {
      var extra = userInfo["__EXTRA__"] as? JSObject ?? userInfo

      // check for any dates and convert them to strings
      for (key, value) in extra {
        if let date = value as? Date {
          let dateString = ISO8601DateFormatter().string(from: date)
          extra[key] = dateString
        }
      }

      notification["extra"] = extra

      if var schedule = userInfo["__SCHEDULE__"] as? JSObject {
        // convert schedule at date to string
        if let date = schedule["at"] as? Date {
          let dateString = ISO8601DateFormatter().string(from: date)
          schedule["at"] = dateString
        }

        notification["schedule"] = schedule
      }
    }

    return notification
  }
}
