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

import Foundation

@objc
public class EXDevLauncherUncaughtExceptionHandler: NSObject {
  static private var defaultHandler: (@convention(c) (NSException) -> Swift.Void)?

  @objc
  public static var isInstalled: Bool = false {
    willSet {
      if isInstalled != newValue {
        if newValue {
          installHandler()
        } else {
          uninstallHandler()
        }
      }
    }
  }

  static func installHandler() {
    defaultHandler = NSGetUncaughtExceptionHandler()

    NSSetUncaughtExceptionHandler { exception in
      NSLog("DevLauncher tries to handle uncaught exception: %@", exception)
      NSLog("Stack Trace: %@", exception.callStackSymbols)

      EXDevLauncherUncaughtExceptionHandler.tryToSaveException(exception)
      EXDevLauncherUncaughtExceptionHandler.tryToSendExceptionToBundler(exception)
      EXDevLauncherUncaughtExceptionHandler.defaultHandler?(exception)
    }
  }

  static func uninstallHandler() {
    NSSetUncaughtExceptionHandler(defaultHandler)
  }

  static func tryToSendExceptionToBundler(_ exception: NSException) {
    let controller = EXDevLauncherController.sharedInstance()
    if controller.isAppRunning() {
      guard let url = getWebSocketUrl(controller) else {
        return
      }

      let logsManager = EXDevLauncherRemoteLogsManager(withUrl: url)
      logsManager.deferError(message: "Your app just crashed. See the error below.")
      logsManager.deferError(exception: exception)
      logsManager.sendSync()
    }
  }

  static func getWebSocketUrl(_ controller: EXDevLauncherController) -> URL? {
    // URL structure replicates
    // https://github.com/facebook/react-native/blob/0.69-stable/Libraries/Utilities/HMRClient.js#L164
    // but URLSessionWebSocketTask will crash if the scheme is not `ws` or `wss`
    guard let appUrl = controller.sourceUrl() else {
      return nil
    }
    guard let socketUrl = URL.init(string: "hot", relativeTo: appUrl) else {
      return nil
    }
    guard var components = URLComponents(url: socketUrl, resolvingAgainstBaseURL: true) else {
      return nil
    }
    components.scheme = "ws"
    return components.url
  }

  static func tryToSaveException(_ exception: NSException) {
    let registry = EXDevLauncherErrorRegistry()
    registry.storeException(exception)
  }
}
