/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation
import GRPC
import IDBCompanionUtilities
import IDBGRPCSwift

struct LaunchMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(requestStream: GRPCAsyncRequestStream<Idb_LaunchRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_LaunchResponse>, context: GRPCAsyncServerCallContext) async throws {
    var completions: [FBFuture<NSNull>] = []

    var request = try await requestStream.requiredNext
    guard case let .start(start) = request.control else {
      throw GRPCStatus(code: .failedPrecondition, message: "Application not started yet")
    }
    var stdOut = processOutputForNullDevice()
    var stdErr = processOutputForNullDevice()

    let responseWriter = FIFOStreamWriter(stream: responseStream)
    if start.waitFor {
      let stdOutConsumer = pipeOutput(interface: .stdout, responseWriter: responseWriter)
      completions.append(stdOutConsumer.finishedConsuming)
      stdOut = FBProcessOutput<AnyObject>(for: stdOutConsumer)

      let stdErrConsumer = pipeOutput(interface: .stderr, responseWriter: responseWriter)
      completions.append(stdErrConsumer.finishedConsuming)
      stdErr = FBProcessOutput<AnyObject>(for: stdErrConsumer)
    }
    let io = FBProcessIO<AnyObject, AnyObject, AnyObject>(stdIn: nil, stdOut: stdOut, stdErr: stdErr)
    let config = FBApplicationLaunchConfiguration(bundleID: start.bundleID,
                                                  bundleName: nil,
                                                  arguments: start.appArgs,
                                                  environment: start.env,
                                                  waitForDebugger: start.waitForDebugger,
                                                  io: io,
                                                  launchMode: start.foregroundIfRunning ? .foregroundIfRunning : .failIfRunning)
    let launchedApp = try await BridgeFuture.value(commandExecutor.launch_app(config))
    let response = Idb_LaunchResponse.with {
      $0.debugger.pid = UInt64(launchedApp.processIdentifier)
    }
    try await responseStream.send(response)

    guard start.waitFor else { return }

    request = try await requestStream.requiredNext
    guard case .stop = request.control else {
      throw GRPCStatus(code: .failedPrecondition, message: "Application has already started")
    }

    try await BridgeFuture.await(launchedApp.applicationTerminated.cancel())

    _ = try await BridgeFuture.values(completions)
  }

  private func processOutputForNullDevice() -> FBProcessOutput<AnyObject> {
    return FBProcessOutput<AnyObject>.forNullDevice() as! FBProcessOutput<AnyObject>
  }

  private func pipeOutput(interface: Idb_ProcessOutput.Interface, responseWriter: FIFOStreamWriter<GRPCAsyncResponseStreamWriter<Idb_LaunchResponse>>) -> (FBDataConsumer & FBDataConsumerLifecycle) {
    return FBBlockDataConsumer.asynchronousDataConsumer { data in
      let response = Idb_LaunchResponse.with {
        $0.output.data = data
        $0.output.interface = interface
      }
      try? responseWriter.send(response)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBControlCore
import Foundation
import GRPC
import IDBGRPCSwift

struct ListAppsMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_ListAppsRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ListAppsResponse {
    let persistedBundleIDs = commandExecutor.storageManager.application.persistedBundleIDs
    let fetchAppProcessState = !request.suppressProcessState
    let apps: [FBInstalledApplication: Any] = try await BridgeFuture.value(commandExecutor.list_apps(fetchAppProcessState))

    return .with {
      $0.apps = apps.map { app, processState in
        .with {
          $0.bundleID = app.bundle.identifier
          $0.name = app.bundle.name
          $0.installType = app.installTypeString
          $0.architectures = app.bundle.binary?.architectures.map(\.rawValue) ?? []
          if let processID = processState as? NSNumber {
            $0.processState = .running
            $0.processIdentifier = processID.uint64Value
          } else {
            $0.processState = .unknown
          }
          $0.debuggable = app.installType == .userDevelopment && persistedBundleIDs.contains(app.bundle.identifier)
        }
      }
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct ListSettingsMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_ListSettingRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ListSettingResponse {
    switch request.setting {
    case .locale:
      return .with {
        $0.values = commandExecutor.list_locale_identifiers()
      }
    case .any, .UNRECOGNIZED:
      throw GRPCStatus(code: .invalidArgument, message: "Unknown setting case")
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import GRPC
import IDBCompanionUtilities
import IDBGRPCSwift

struct LogMethodHandler {

  let target: FBiOSTarget
  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_LogRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_LogResponse>, context: GRPCAsyncServerCallContext) async throws {
    let writingDone = FBMutableFuture<NSNull>(name: nil)
    let streamWriter = FIFOStreamWriter(stream: responseStream)

    let consumer = FBBlockDataConsumer.synchronousDataConsumer { data in
      if writingDone.hasCompleted {
        return
      }
      let response = Idb_LogResponse.with {
        $0.output = data
      }
      do {
        try streamWriter.send(response)
      } catch {
        writingDone.resolveWithError(error)
      }
    }

    let operationFuture = request.source == .companion
      ? commandExecutor.tail_companion_logs(consumer)
      : target.tailLog(request.arguments, consumer: consumer)

    let operation = try await BridgeFuture.value(operationFuture)

    let completed = FBFuture(race: [BridgeFuture.convertToFuture(writingDone), operation.completed])

    try await BridgeFuture.await(completed)
    writingDone.resolve(withResult: NSNull())

    try await BridgeFuture.await(operation.completed.cancel())
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation
import GRPC
import IDBGRPCSwift

struct LsMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_LsRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_LsResponse {
    let fileContainer = FileContainerValueTransformer.rawFileContainer(from: request.container)

    if request.paths.isEmpty {
      return try await list(path: request.path, fileContainer: fileContainer)
    } else {
      return try await list(pathList: request.paths, fileContainer: fileContainer)
    }
  }

  private func list(path: String, fileContainer: String) async throws -> Idb_LsResponse {
    let paths: [String] = try await BridgeFuture.value(commandExecutor.list_path(path, containerType: fileContainer))

    return .with {
      $0.files = paths.map(toFileInfo)
    }
  }

  private func list(pathList: [String], fileContainer: String) async throws -> Idb_LsResponse {
    let pathsToPaths: [String: [String]] = try await BridgeFuture.value(commandExecutor.list_paths(pathList, containerType: fileContainer))

    return .with {
      $0.listings = pathsToPaths.map { containerPath, paths in
        .with {
          $0.parent = .with {
            $0.path = containerPath
          }
          $0.files = paths.map(toFileInfo)
        }
      }
    }
  }

  private func toFileInfo(path: String) -> Idb_FileInfo {
    .with { $0.path = path }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct MkdirMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_MkdirRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_MkdirResponse {
    let fileContainer = FileContainerValueTransformer.rawFileContainer(from: request.container)
    try await BridgeFuture.await(commandExecutor.create_directory(request.path, containerType: fileContainer))
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct MvMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_MvRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_MvResponse {
    let fileContainer = FileContainerValueTransformer.rawFileContainer(from: request.container)
    try await BridgeFuture.await(commandExecutor.move_paths(request.srcPaths, to_path: request.dstPath, containerType: fileContainer))
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct OpenUrlMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_OpenUrlRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_OpenUrlRequest {
    try await BridgeFuture.await(commandExecutor.open_url(request.url))
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import Foundation
import GRPC
import IDBGRPCSwift

struct PullMethodHandler {

  let target: FBiOSTarget
  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_PullRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_PullResponse>, context: GRPCAsyncServerCallContext) async throws {
    if request.dstPath.isEmpty {
      try await sendRawData(request: request, responseStream: responseStream)
    } else {
      try await sendFilePath(request: request, responseStream: responseStream)
    }
  }

  private func sendRawData(request: Idb_PullRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_PullResponse>) async throws {
    guard let logger = target.logger else {
      throw GRPCStatus(code: .internalError, message: "Internal logger not configured")
    }
    let path = request.srcPath as NSString
    let fileContainer = FileContainerValueTransformer.rawFileContainer(from: request.container)
    let url = commandExecutor.temporaryDirectory.temporaryDirectory()
    let tempPath = url.appendingPathComponent(path.lastPathComponent).path

    let filePath = try await BridgeFuture.value(
      commandExecutor.pull_file_path(request.srcPath,
                                     destination_path: tempPath,
                                     containerType: fileContainer)
    )

    let archiveFuture = FBArchiveOperations.createGzippedTar(forPath: filePath as String, logger: logger)

    try await FileDrainWriter.performDrain(taskFuture: archiveFuture) { data in
      let response = Idb_PullResponse.with { $0.payload.data = data }
      try await responseStream.send(response)
    }
  }

  private func sendFilePath(request: Idb_PullRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_PullResponse>) async throws {
    let fileContainer = FileContainerValueTransformer.rawFileContainer(from: request.container)

    let filePath = try await BridgeFuture.value(
      commandExecutor.pull_file_path(request.srcPath,
                                     destination_path: request.dstPath,
                                     containerType: fileContainer)
    ) as String
    let response = Idb_PullResponse.with {
      $0.payload = .with { $0.source = .filePath(filePath) }
    }
    try await responseStream.send(response)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBControlCore
import GRPC
import IDBGRPCSwift

struct PushMethodHandler {

  let target: FBiOSTarget
  let commandExecutor: FBIDBCommandExecutor

  func handle(requestStream: GRPCAsyncRequestStream<Idb_PushRequest>, context: GRPCAsyncServerCallContext) async throws -> Idb_PushResponse {
    let request = try await requestStream.requiredNext

    guard case let .inner(inner) = request.value
    else { throw GRPCStatus(code: .invalidArgument, message: "Expected inner as first request in stream") }

    let extractedFileURLs = try await MultisourceFileReader
      .filePathURLs(from: requestStream, temporaryDirectory: commandExecutor.temporaryDirectory, extractFromSubdir: false)

    let fileContainer = FileContainerValueTransformer.rawFileContainer(from: inner.container)
    try await BridgeFuture.await(
      commandExecutor.push_files(extractedFileURLs, to_path: inner.dstPath, containerType: fileContainer)
    )

    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct RecordMethodHandler {

  let target: FBiOSTarget
  let targetLogger: FBControlCoreLogger

  func handle(requestStream: GRPCAsyncRequestStream<Idb_RecordRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_RecordResponse>, context: GRPCAsyncServerCallContext) async throws {

    let request = try await requestStream.requiredNext
    guard case let .start(start) = request.control
    else { throw GRPCStatus(code: .failedPrecondition, message: "Expect start as initial request frame") }

    let filePath =
      start.filePath.isEmpty
      ? URL(fileURLWithPath: target.auxillaryDirectory).appendingPathComponent("idb_encode").appendingPathExtension("mp4").path
      : start.filePath

    _ = try await BridgeFuture.value(target.startRecording(toFile: filePath))

    _ = try await requestStream.requiredNext
    try await BridgeFuture.await(target.stopRecording())

    if start.filePath.isEmpty {
      let gzipTask = FBArchiveOperations.createGzip(forPath: filePath,
                                                    queue: BridgeQueues.miscEventReaderQueue,
                                                    logger: targetLogger)

      try await FileDrainWriter.performDrain(taskFuture: gzipTask) { data in
        let response = Idb_RecordResponse.with { $0.payload.data = data }
        try await responseStream.send(response)
      }
    } else {
      let response = Idb_RecordResponse.with {
        $0.output = .payload(.with { $0.source = .filePath(start.filePath) })
      }
      try await responseStream.send(response)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct RevokeMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_RevokeRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_RevokeResponse {

    // Swift implements custom bridging logic for NSNotificationName and this causes ALL string enums ended with "Notification"
    // suffix translates in this special way
    let notificationApprovalService = FBTargetSettingsService.FBTargetSettingsService

    let mapping: [Idb_RevokeRequest.Permission: FBTargetSettingsService] = [
      .microphone: .microphone,
      .photos: .photos,
      .camera: .camera,
      .contacts: .contacts,
      .url: .url,
      .location: .location,
      .notification: notificationApprovalService,
    ]

    var services = try Set(
      request.permissions.map { permission -> FBTargetSettingsService in
        guard let service = mapping[permission] else {
          throw GRPCStatus(code: .invalidArgument, message: "Unrecognized permission \(permission)")
        }
        return service
      }
    )
    if services.contains(.url) {
      services.remove(.url)
      try await BridgeFuture.await(commandExecutor.revoke_deeplink(request.scheme, for_application: request.bundleID))
    }

    if !services.isEmpty {
      try await BridgeFuture.await(commandExecutor.revoke(services, for_application: request.bundleID))
    }
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct RmMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_RmRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_RmResponse {
    let fileContainer = FileContainerValueTransformer.rawFileContainer(from: request.container)
    try await BridgeFuture.await(commandExecutor.remove_paths(request.paths, containerType: fileContainer))
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct ScreenshotMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_ScreenshotRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ScreenshotResponse {
    let screenshot = try await BridgeFuture.value(commandExecutor.take_screenshot(.PNG))
    return .with {
      $0.imageData = screenshot as Data
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct SendNotificationMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_SendNotificationRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SendNotificationResponse {
    try await BridgeFuture.await(commandExecutor.sendPushNotification(forBundleID: request.bundleID, jsonPayload: request.jsonPayload))
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct SetLocationMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_SetLocationRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SetLocationResponse {
    try await BridgeFuture.await(commandExecutor.set_location(request.location.latitude, longitude: request.location.longitude))
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct SettingMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_SettingRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SettingResponse {
    switch request.setting {
    case let .hardwareKeyboard(hardwareKeyboard):
      try await BridgeFuture.await(commandExecutor.set_hardware_keyboard_enabled(hardwareKeyboard.enabled))

    case let .stringSetting(stringSetting):
      switch stringSetting.setting {
      case .locale:
        try await BridgeFuture.await(commandExecutor.set_locale_with_identifier(stringSetting.value))

      case .any:
        let domain = stringSetting.domain.isEmpty ? nil : stringSetting.domain
        let type = stringSetting.valueType.isEmpty ? nil : stringSetting.valueType
        try await BridgeFuture.await(commandExecutor.set_preference(stringSetting.name, value: stringSetting.value, type: type, domain: domain))

      case .UNRECOGNIZED:
        throw GRPCStatus(code: .invalidArgument, message: "Unknown setting case")
      }
    case .none:
      throw GRPCStatus(code: .invalidArgument, message: "Unknown setting case")
    }

    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct SimulateMemoryWarningMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_SimulateMemoryWarningRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SimulateMemoryWarningResponse {
    try await BridgeFuture.await(commandExecutor.simulateMemoryWarning())
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import GRPC
import IDBCompanionUtilities
import IDBGRPCSwift

struct TailMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(requestStream: GRPCAsyncRequestStream<Idb_TailRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_TailResponse>, context: GRPCAsyncServerCallContext) async throws {
    @Atomic var finished = false

    guard case let .start(start) = try await requestStream.requiredNext.control
    else { throw GRPCStatus(code: .failedPrecondition, message: "Expected start control") }

    let responseWriter = FIFOStreamWriter(stream: responseStream)
    let consumer = FBBlockDataConsumer.asynchronousDataConsumer { data in
      guard !finished else { return }
      let response = Idb_TailResponse.with {
        $0.data = data
      }
      do {
        try responseWriter.send(response)
      } catch {
        _finished.set(true)
      }
    }

    let fileContainer = FileContainerValueTransformer.rawFileContainer(from: start.container)
    let tail = try await BridgeFuture.value(commandExecutor.tail(start.path, to_consumer: consumer, in_container: fileContainer))

    guard case .stop = try await requestStream.requiredNext.control
    else { throw GRPCStatus(code: .failedPrecondition, message: "Expected end control") }

    try await BridgeFuture.await(tail.cancel())
    _finished.set(true)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct TerminateMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_TerminateRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_TerminateResponse {
    try await BridgeFuture.await(commandExecutor.kill_application(request.bundleID))
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBGRPCSwift

struct UninstallMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_UninstallRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_UninstallResponse {
    try await BridgeFuture.await(commandExecutor.uninstall_application(request.bundleID))
    return .init()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import GRPC
import IDBCompanionUtilities
import IDBGRPCSwift

struct VideoStreamMethodHandler {

  let target: FBiOSTarget
  let targetLogger: FBControlCoreLogger
  let commandExecutor: FBIDBCommandExecutor

  func handle(requestStream: GRPCAsyncRequestStream<Idb_VideoStreamRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_VideoStreamResponse>, context: GRPCAsyncServerCallContext) async throws {
    @Atomic var finished = false

    guard case let .start(start) = try await requestStream.requiredNext.control
    else { throw GRPCStatus(code: .failedPrecondition, message: "Expected start control") }

    let videoStream = try await startVideoStream(request: start,
                                                 responseStream: responseStream,
                                                 finished: _finished)

    let observeClientCancelStreaming = Task<Void, Error> {
      for try await request in requestStream {
        switch request.control {
        case .start:
          throw GRPCStatus(code: .failedPrecondition, message: "Video streaming already started")
        case .stop:
          return
        case .none:
          throw GRPCStatus(code: .invalidArgument, message: "Client should not close request stream explicitly, send `stop` frame first")
        }
      }
    }

    let observeVideoStreamStop = Task<Void, Error> {
      try await BridgeFuture.await(videoStream.completed)
    }

    try await Task.select(observeClientCancelStreaming, observeVideoStreamStop).value

    try await BridgeFuture.await(videoStream.stopStreaming())
    targetLogger.log("The video stream is terminated")
  }

  private func startVideoStream(request start: Idb_VideoStreamRequest.Start, responseStream: GRPCAsyncResponseStreamWriter<Idb_VideoStreamResponse>, finished: Atomic<Bool>) async throws -> FBVideoStream {
    let consumer: FBDataConsumer

    if start.filePath.isEmpty {
      let responseWriter = FIFOStreamWriter(stream: responseStream)

      consumer = FBBlockDataConsumer.asynchronousDataConsumer { data in
        guard !finished.wrappedValue else { return }
        let response = Idb_VideoStreamResponse.with {
          $0.payload.data = data
        }
        do {
          try responseWriter.send(response)
        } catch {
          finished.set(true)
        }
      }
    } else {
      consumer = try FBFileWriter.syncWriter(forFilePath: start.filePath)
    }

    let framesPerSecond = start.fps > 0 ? NSNumber(value: start.fps) : nil
    let avgBitrate = start.avgBitrate > 0 ? NSNumber(value: start.avgBitrate) : nil
    let encoding = try streamEncoding(from: start.format)
    let config = FBVideoStreamConfiguration(encoding: encoding,
                                            framesPerSecond: framesPerSecond,
                                            compressionQuality: .init(value: start.compressionQuality),
                                            scaleFactor: .init(value: start.scaleFactor),
                                            avgBitrate: avgBitrate)

    let videoStream = try await BridgeFuture.value(target.createStream(with: config))

    try await BridgeFuture.await(videoStream.startStreaming(consumer))

    return videoStream
  }

  private func streamEncoding(from requestFormat: Idb_VideoStreamRequest.Format) throws -> FBVideoStreamEncoding {
    switch requestFormat {
    case .h264:
      return .H264
    case .rbga:
      return .BGRA
    case .mjpeg:
      return .MJPEG
    case .minicap:
      return .minicap
    case .i420, .UNRECOGNIZED:
      throw GRPCStatus(code: .invalidArgument, message: "Unrecognized video format")
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import Foundation
import GRPC
import IDBGRPCSwift
import XCTestBootstrap

struct XCTestListBundlesMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_XctestListBundlesRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_XctestListBundlesResponse {
    let descriptors: [FBXCTestDescriptor] = try await BridgeFuture.value(
      commandExecutor.list_test_bundles()
    )
    return .with {
      $0.bundles = descriptors.map(toBundle(descriptor:))
    }
  }
  private func toBundle(descriptor: FBXCTestDescriptor) -> Idb_XctestListBundlesResponse.Bundles {
    return .with {
      $0.name = descriptor.name
      $0.bundleID = descriptor.testBundleID
      $0.architectures = Array(descriptor.architectures)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import Foundation
import GRPC
import IDBGRPCSwift

struct XCTestListTestsMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_XctestListTestsRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_XctestListTestsResponse {
    let tests: [String] = try await BridgeFuture.value(
      commandExecutor.list_tests_(in_bundle: request.bundleName, with_app: request.appPath)
    )
    return .with {
      $0.names = tests
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import Foundation
import GRPC
import IDBGRPCSwift

struct XCTestRunMethodHandler {

  let target: FBiOSTarget
  let commandExecutor: FBIDBCommandExecutor
  let reporter: FBEventReporter
  let targetLogger: FBControlCoreLogger
  let logger: FBIDBLogger

  func handle(request: Idb_XctestRunRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_XctestRunResponse>, context: GRPCAsyncServerCallContext) async throws {
    guard let request = transform(value: request) else {
      throw GRPCStatus(code: .invalidArgument, message: "failed to create FBXCTestRunRequest")
    }

    let reporter = IDBXCTestReporter(responseStream: responseStream, queue: target.workQueue, logger: logger)

    let operationFuture = commandExecutor.xctest_run(request,
                                                     reporter: reporter,
                                                     logger: FBControlCoreLoggerFactory.logger(to: reporter))
    let operation = try await BridgeFuture.value(operationFuture)
    reporter.configuration = .init(legacy: operation.reporterConfiguration)

    do {
      try await BridgeFuture.await(operation.completed)
    } catch let error as NSError {
      // We should ignore errors that came from test binary. Like when exception is throwed or binary crashed.
      if error.domain != FBTestErrorDomain {
        throw error
      }
    }

    _ = try await BridgeFuture.value(reporter.reportingTerminated)
  }

  func transform(value request: Idb_XctestRunRequest) -> FBXCTestRunRequest? {
    let testsToRun = request.testsToRun.isEmpty ? nil : Set(request.testsToRun)
    switch request.mode.mode {
    case .logic:
      return FBXCTestRunRequest.logicTest(withTestBundleID: request.testBundleID,
                                          environment: request.environment,
                                          arguments: request.arguments,
                                          testsToRun: testsToRun,
                                          testsToSkip: Set(request.testsToSkip),
                                          testTimeout: request.timeout as NSNumber,
                                          reportActivities: request.reportActivities,
                                          reportAttachments: request.reportAttachments,
                                          coverageRequest: extractCodeCoverage(from: request),
                                          collectLogs: request.collectLogs,
                                          waitForDebugger: request.waitForDebugger,
                                          collectResultBundle: request.collectResultBundle)
    case let .application(app):
      return FBXCTestRunRequest.applicationTest(withTestBundleID: request.testBundleID,
                                                testHostAppBundleID: app.appBundleID,
                                                environment: request.environment,
                                                arguments: request.arguments,
                                                testsToRun: testsToRun,
                                                testsToSkip: Set(request.testsToSkip),
                                                testTimeout: request.timeout as NSNumber,
                                                reportActivities: request.reportActivities,
                                                reportAttachments: request.reportAttachments,
                                                coverageRequest: extractCodeCoverage(from: request),
                                                collectLogs: request.collectLogs,
                                                waitForDebugger: request.waitForDebugger,
                                                collectResultBundle: request.collectResultBundle)
    case let .ui(ui):
      return FBXCTestRunRequest.uiTest(withTestBundleID: request.testBundleID,
                                       testHostAppBundleID: ui.testHostAppBundleID,
                                       testTargetAppBundleID: ui.appBundleID,
                                       environment: request.environment,
                                       arguments: request.arguments,
                                       testsToRun: testsToRun,
                                       testsToSkip: Set(request.testsToSkip),
                                       testTimeout: request.timeout as NSNumber,
                                       reportActivities: request.reportActivities,
                                       reportAttachments: request.reportAttachments,
                                       coverageRequest: extractCodeCoverage(from: request),
                                       collectLogs: request.collectLogs,
                                       collectResultBundle: request.collectResultBundle)
    case .none:
      return nil
    }
  }

  private func extractCodeCoverage(from request: Idb_XctestRunRequest) -> FBCodeCoverageRequest {
    if request.hasCodeCoverage {
      switch request.codeCoverage.format {
      case .raw:
        return FBCodeCoverageRequest(collect: request.codeCoverage.collect, format: .raw)
      case .exported, .UNRECOGNIZED:
        return FBCodeCoverageRequest(collect: request.codeCoverage.collect, format: .exported)
      }
    }
    // fallback to deprecated request field for backwards compatibility
    return FBCodeCoverageRequest(collect: request.collectCoverage, format: .exported)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import GRPC
import IDBCompanionUtilities
import IDBGRPCSwift

struct XctraceRecordMethodHandler {

  let logger: FBControlCoreLogger
  let targetLogger: FBControlCoreLogger
  let target: FBiOSTarget

  func handle(requestStream: GRPCAsyncRequestStream<Idb_XctraceRecordRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_XctraceRecordResponse>, context: GRPCAsyncServerCallContext) async throws {

    @Atomic var finishedWriting = false
    defer { _finishedWriting.set(true) }

    guard case let .start(start) = try await requestStream.requiredNext.control
    else { throw GRPCStatus(code: .failedPrecondition, message: "Expected start control") }
    let operation = try await startXCTraceOperation(request: start, responseStream: responseStream, finishedWriting: _finishedWriting)

    guard case let .stop(stop) = try await requestStream.requiredNext.control
    else { throw GRPCStatus(code: .failedPrecondition, message: "Expected end control") }

    try await stopXCTrace(operation: operation, request: stop, responseStream: responseStream, finishedWriting: _finishedWriting)
  }

  private func startXCTraceOperation(request start: Idb_XctraceRecordRequest.Start, responseStream: GRPCAsyncResponseStreamWriter<Idb_XctraceRecordResponse>, finishedWriting: Atomic<Bool>) async throws -> FBXCTraceRecordOperation {
    let config = xcTraceRecordConfiguration(from: start)

    let responseWriter = FIFOStreamWriter(stream: responseStream)
    let consumer = FBBlockDataConsumer.asynchronousDataConsumer { data in
      guard !finishedWriting.wrappedValue else { return }

      let response = Idb_XctraceRecordResponse.with {
        $0.log = data
      }
      do {
        try responseWriter.send(response)
      } catch {
        finishedWriting.set(true)
      }
    }

    let logger = FBControlCoreLoggerFactory.compositeLogger(with: [
      FBControlCoreLoggerFactory.logger(to: consumer),
      targetLogger,
    ].compactMap({ $0 }))

    let operation = try await BridgeFuture.value(target.startXctraceRecord(config, logger: logger))
    let response = Idb_XctraceRecordResponse.with {
      $0.state = .running
    }
    try await responseStream.send(response)

    return operation
  }

  private func stopXCTrace(operation: FBXCTraceRecordOperation, request stop: Idb_XctraceRecordRequest.Stop, responseStream: GRPCAsyncResponseStreamWriter<Idb_XctraceRecordResponse>, finishedWriting: Atomic<Bool>) async throws {
    let stopTimeout = stop.timeout != 0 ? stop.timeout : DefaultXCTraceRecordStopTimeout
    _ = try await BridgeFuture.value(operation.stop(withTimeout: stopTimeout))
    let response = Idb_XctraceRecordResponse.with {
      $0.state = .processing
    }
    try await responseStream.send(response)

    let processed = try await BridgeFuture.value(
      FBInstrumentsOperation.postProcess(stop.args,
                                         traceDir: operation.traceDir,
                                         queue: BridgeQueues.miscEventReaderQueue,
                                         logger: logger)
    )
    finishedWriting.set(true)

    guard let path = processed.path else {
      throw GRPCStatus(code: .internalError, message: "Unable to get post process file path")
    }

    let data = try await BridgeFuture.value(FBArchiveOperations.createGzippedTarData(forPath: path, queue: BridgeQueues.futureSerialFullfillmentQueue, logger: targetLogger))
    let resp = Idb_XctraceRecordResponse.with {
      $0.payload = .with { $0.data = data as Data }
    }
    try await responseStream.send(resp)

    let createTarOperation = FBArchiveOperations.createGzippedTar(forPath: path, logger: logger)
    try await FileDrainWriter.performDrain(taskFuture: createTarOperation) { data in
      let response = Idb_XctraceRecordResponse.with {
        $0.payload = .with { $0.data = data }
      }
      try await responseStream.send(response)
    }
  }

  private func xcTraceRecordConfiguration(from request: Idb_XctraceRecordRequest.Start) -> FBXCTraceRecordConfiguration {
    let timeLimit = request.timeLimit != 0 ? request.timeLimit : DefaultXCTraceRecordOperationTimeLimit

    return .init(templateName: request.templateName,
                 timeLimit: timeLimit,
                 package: request.package,
                 allProcesses: request.target.allProcesses,
                 processToAttach: request.target.processToAttach,
                 processToLaunch: request.target.launchProcess.processToLaunch,
                 launchArgs: request.target.launchProcess.launchArgs,
                 targetStdin: request.target.launchProcess.targetStdin,
                 targetStdout: request.target.launchProcess.targetStdout,
                 processEnv: request.target.launchProcess.processEnv,
                 shim: nil)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import IDBGRPCSwift

enum CrashLogInfoValueTransformer {

  static func responseCrashLogInfo(from crash: FBCrashLogInfo) -> Idb_CrashLogInfo {
    return .with {
      $0.name = crash.name
      $0.processName = crash.processName
      $0.parentProcessName = crash.parentProcessName
      $0.processIdentifier = UInt64(crash.processIdentifier)
      $0.parentProcessIdentifier = UInt64(crash.parentProcessIdentifier)
      $0.timestamp = UInt64(crash.date.timeIntervalSince1970)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBControlCore
import IDBGRPCSwift

enum CrashLogQueryValueTransformer {

  static func predicate(from request: Idb_CrashLogQuery) -> NSPredicate {
    var subpredicates: [NSPredicate] = []
    if request.since != 0 {
      subpredicates.append(FBCrashLogInfo.predicateNewerThanDate(Date(timeIntervalSince1970: TimeInterval(request.since))))
    }
    if request.before != 0 {
      subpredicates.append(FBCrashLogInfo.predicateOlderThanDate(Date(timeIntervalSince1970: TimeInterval(request.before))))
    }
    if !request.bundleID.isEmpty {
      subpredicates.append(FBCrashLogInfo.predicate(forIdentifier: request.bundleID))
    }
    if !request.name.isEmpty {
      subpredicates.append(FBCrashLogInfo.predicate(forName: request.name))
    }
    if subpredicates.isEmpty {
      return NSPredicate(value: true)
    }
    return NSCompoundPredicate(andPredicateWithSubpredicates: subpredicates)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBControlCore
import Foundation
import IDBGRPCSwift

struct FileContainerValueTransformer {

  static func rawFileContainer(from proto: Idb_FileContainer) -> String {
    return fileContainer(from: proto.kind)?.rawValue ?? proto.bundleID
  }

  static func fileContainer(from proto: Idb_FileContainer.Kind) -> FBFileContainerKind? {
    switch proto {
    case .root:
      return .root
    case .media:
      return .media
    case .crashes:
      return .crashes
    case .provisioningProfiles:
      return .provisioningProfiles
    case .mdmProfiles:
      return .mdmProfiles
    case .springboardIcons:
      return .springboardIcons
    case .wallpaper:
      return .wallpaper
    case .diskImages:
      return .diskImages
    case .groupContainer:
      return .group
    case .applicationContainer:
      return .application
    case .auxillary:
      return .auxillary
    case .xctest:
      return .xctest
    case .dylib:
      return .dylib
    case .dsym:
      return .dsym
    case .framework:
      return .framework
    case .symbols:
      return .symbols
    case .application, .none, .UNRECOGNIZED:
      return nil
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation
import IDBGRPCSwift

// Convenient extractions of items from certain requests

protocol DataExtractable {
  func extractDataFrame() -> Data?
}

protocol PayloadExtractable: DataExtractable {
  func extractPayload() -> Idb_Payload?
}

extension Idb_InstallRequest: PayloadExtractable {
  func extractPayload() -> Idb_Payload? {
    switch value {
    case .payload(let payload):
      return payload
    default:
      return nil
    }
  }
}

extension Idb_PushRequest: PayloadExtractable {
  func extractPayload() -> Idb_Payload? {
    switch value {
    case .payload(let payload):
      return payload
    default:
      return nil
    }
  }
}

extension Idb_AddMediaRequest: PayloadExtractable {
  func extractPayload() -> Idb_Payload? {
    if hasPayload {
      return payload
    }
    return nil
  }
}

extension PayloadExtractable {
  func extractDataFrame() -> Data? {
    extractPayload()?.extractDataFrame()
  }
}

extension Idb_Payload: DataExtractable {
  func extractDataFrame() -> Data? {
    switch source {
    case let .data(data):
      return data
    default:
      return nil
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation
import GRPC

enum StreamReadError<Element>: Error, GRPCStatusTransformable {
  case nextElementNotProduced

  func makeGRPCStatus() -> GRPCStatus {
    switch self {
    case .nextElementNotProduced:
      return GRPCStatus(code: .failedPrecondition, message: "Expected next element of type \(Element.self)")
    }
  }
}

extension AsyncSequence {

  /// We have quite a lot of grpc request streams where we read request N constant number of times and do not need foreach loop. But pure next produces optinal by design.
  /// This small tweak just saves us from lots of boilerplate of unwrapping the optionals everywhere
  var requiredNext: Element {
    get async throws {
      guard let next = try await first(where: { _ in true }) else {
        throw StreamReadError<Element>.nextElementNotProduced
      }
      return next
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBControlCore
import Foundation
import IDBCompanionUtilities

enum FBFutureError: Error {

  /// This indicates an error in objc code where result callback was called but no error or result provided. In case of this, debug `FBFuture` implementation.
  case continuationFullfilledWithoutValues

  /// This indicates an error in `BridgeFuture.values` implementation. In case of this, debug `BridgeFuture.values` implementation.
  case taskGroupReceivedNilResultInternalError
}

/// Swift compiler does not allow usage of generic parameters of objc classes in extension
/// so we need to create a bridge for convenience.
enum BridgeFuture {

  /// Use this to receive results from multiple futures. The results are **ordered in the same order as passed futures**, so you can safely access them from
  /// array by indexes.
  /// - Note: We should *not* use @discardableResult, results should be dropped explicitly by the callee.
  static func values<T: AnyObject>(_ futures: FBFuture<T>...) async throws -> [T] {
    let futuresArr: [FBFuture<T>] = futures
    return try await values(futuresArr)
  }

  /// Use this to receive results from multiple futures. The results are **ordered in the same order as passed futures**, so you can safely access them from
  /// array by indexes.
  /// - Note: We should *not* use @discardableResult, results should be dropped explicitly by the callee.
  static func values<T: AnyObject>(_ futures: [FBFuture<T>]) async throws -> [T] {
    return try await withThrowingTaskGroup(of: (Int, T).self, returning: [T].self) { group in
      var results = [T?].init(repeating: nil, count: futures.count)

      for (index, future) in futures.enumerated() {
        group.addTask {
          return try await (index, BridgeFuture.value(future))
        }
      }

      for try await (index, value) in group {
        results[index] = value
      }

      return try results.map { value -> T in
        guard let shouldDefinitelyExist = value else {
          assertionFailure("This should never happen. We should fullfill all values at that moment")
          throw FBFutureError.taskGroupReceivedNilResultInternalError
        }
        return shouldDefinitelyExist
      }
    }
  }

  /// Awaitable value that waits for publishing from the wrapped future
  /// - Note: We should *not* use @discardableResult, results should be dropped explicitly by the callee.
  static func value<T: AnyObject>(_ future: FBFuture<T>) async throws -> T {
    try await withTaskCancellationHandler {
      try await withCheckedThrowingContinuation { continuation in
        future.onQueue(BridgeQueues.futureSerialFullfillmentQueue, notifyOfCompletion: { resultFuture in
          if let error = resultFuture.error {
            continuation.resume(throwing: error)
          } else if let value = resultFuture.result {
            continuation.resume(returning: value as! T)
          } else {
            continuation.resume(throwing: FBFutureError.continuationFullfilledWithoutValues)
          }
        })
      }
    } onCancel: {
      future.cancel()
    }
  }

  /// Awaitable value that waits for publishing from the wrapped future.
  /// This is convenient bridgeable overload for dealing with objc `NSArray`.
  /// - Warning: This operation not safe (as most of objc bridge). That means you should be sure that type bridging will succeed.
  /// Consider this method as
  ///
  /// ```
  /// // ------- command_executor.m
  ///  - (FBFuture<NSArray<NSNumer> *> *)doTheThing;
  ///
  /// // ------- swiftfile.swift
  /// let futureFromObjc: FBFuture<NSArray> = command_executor.doTheThing() // Note: NSNumber is lost
  /// let withoutBridge = BridgeFuture.value(futureFromObjc) // withoutBridge: NSArray
  /// let withBridge: [NSNumer] = BridgeFuture.value(futureFromObjc) // withBridge: [NSNumber]
  ///
  /// // But this starts to shine more when you have to pass results to methods/return results, e.g.
  /// func operation() -> [Int] {
  ///   return BridgeFuture.value(futureFromObjc)
  /// }
  ///
  /// // Or pass value to some oter method
  /// func someMethod(accepts: [NSNumber]) { ... }
  ///
  ///  self.someMethod(accepts: BridgeFuture.value(futureFromObjc)
  /// ```
  static func value<T>(_ future: FBFuture<NSArray>) async throws -> [T] {
    let objcValue = try await value(future)
    return objcValue as! [T]
  }

  /// Awaitable value that waits for publishing from the wrapped future.
  /// This is convenient bridgeable overload for dealing with objc `NSDictionary`.
  /// - Warning: This operation not safe (as most of objc bridge). That means you should be sure that type bridging will succeed.
  /// Consider this method as
  ///
  /// ```
  /// // ------- command_executor.m
  ///  - (FBFuture<NSDictionary<FBInstalledApplication *, id> *> *)doTheThing;
  ///
  /// // ------- swiftfile.swift
  /// let futureFromObjc: FBFuture<NSDictionary> = command_executor.doTheThing() // Note: types is lost
  /// let withoutBridge = BridgeFuture.value(futureFromObjc) // withoutBridge: NSDictionary
  /// let withBridge: [FBInstalledApplication: Any] = BridgeFuture.value(futureFromObjc) // withBridge: [FBInstalledApplication: Any]
  ///
  /// // But this starts to shine more when you have to pass results to methods/return results, e.g.
  /// func operation() -> [FBInstalledApplication: Any] {
  ///   return BridgeFuture.value(futureFromObjc)
  /// }
  ///
  /// // Or pass value to some oter method
  /// func someMethod(accepts: [FBInstalledApplication: Any]) { ... }
  ///
  ///  self.someMethod(accepts: BridgeFuture.value(futureFromObjc)
  /// ```
  static func value<T: Hashable, U>(_ future: FBFuture<NSDictionary>) async throws -> [T: U] {
    let objcValue = try await value(future)
    return objcValue as! [T: U]
  }

  /// NSNull is Void equivalent in objc reference world. So is is safe to ignore the result.
  static func await(_ future: FBFuture<NSNull>) async throws {
    _ = try await Self.value(future)
  }

  /// This overload exists because of `FBMutableFuture` does not convert its exact generic type automatically but it can be automatically converted to `FBFuture<AnyObject>`
  /// without any problems. This decision may be revisited in future.
  static func await(_ future: FBFuture<AnyObject>) async throws {
    _ = try await Self.value(future)
  }

  /// Interop between swift and objc generics are quite bad, so we have to write wrappers like this.
  /// By default swift bridge compiler could not convert generic type of `FBMutableFuture`. But this force cast is 100% valid and works in runtime
  /// so we just use this little helper.
  static func convertToFuture<T: AnyObject>(_ mutableFuture: FBMutableFuture<T>) -> FBFuture<T> {
    let future: FBFuture<AnyObject> = mutableFuture
    return future as! FBFuture<T>
  }

  /// Split FBFutureContext to two pieces: result and later cleanup closure
  /// - Parameter futureContext: source future context
  /// - Returns: Tuple of extracted result and cleanup closure that **should** be called later to perform all required cleanups
  static func value<T: AnyObject>(_ futureContext: FBFutureContext<T>) async throws -> T {
    try FBTeardownContext.current.addCleanup {
      let cleanupFuture = futureContext.onQueue(BridgeQueues.futureSerialFullfillmentQueue) { (result: Any, teardown: FBMutableFuture<NSNull>) -> NSNull in
        teardown.resolve(withResult: NSNull())
        return NSNull()
      }
      try await BridgeFuture.await(cleanupFuture)
    }

    return try await value(futureContext.future)
  }

  /// Awaitable value that waits for publishing from the wrapped futureContext.
  /// This is convenient bridgeable overload for dealing with objc `NSArray`.
  /// - Warning: This operation not safe (as most of objc bridge). That means you should be sure that type bridging will succeed.
  /// Consider this method as
  ///
  /// ```
  /// // ------- command_executor.m
  ///  - (FBFuture<NSArray<NSNumer> *> *)doTheThing;
  ///
  /// // ------- swiftfile.swift
  /// let futureFromObjc: FBFuture<NSArray> = command_executor.doTheThing() // Note: NSNumber is lost
  /// let withoutBridge = BridgeFuture.value(futureFromObjc) // withoutBridge: NSArray
  /// let withBridge: [NSNumer] = BridgeFuture.value(futureFromObjc) // withBridge: [NSNumber]
  ///
  /// // But this starts to shine more when you have to pass results to methods/return results, e.g.
  /// func operation() -> [Int] {
  ///   return BridgeFuture.value(futureFromObjc)
  /// }
  ///
  /// // Or pass value to some oter method
  /// func someMethod(accepts: [NSNumber]) { ... }
  ///
  ///  self.someMethod(accepts: BridgeFuture.value(futureFromObjc)
  /// ```
  static func values<T: AnyObject, U>(_ futureContext: FBFutureContext<T>) async throws -> [U] {
    let objcValue = try await value(futureContext)
    return objcValue as! [U]
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation

/// Queues collection to bring objc and async-swift worlds together
enum BridgeQueues {

  /// Plain serial queue that is primarily used to convert *all* FBFuture calls to swift awaitable values
  static let futureSerialFullfillmentQueue = DispatchQueue(label: "com.facebook.fbfuture.fullfilment")

  /// Some of *commandExecutor* operations requires DispatchQueue to send response.
  /// The only purpose of everything handled inside this queue is to passthrough call to swift async world via calling swift `Task` api
  /// ```
  /// commandExecutor.doSomething(onQueue: BridgeQueues.miscEventReaderQueue) { jobResult in
  ///   Task {
  ///     try? await responseStream.send(jobResult)
  ///   }
  /// }
  /// ```
  ///
  static let miscEventReaderQueue = DispatchQueue(label: "com.facebook.miscellaneous.reader", qos: .userInitiated, attributes: .concurrent)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl

extension FBControlCoreError: Error {
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import GRPC

struct FileDrainWriter {

  static func performDrain(taskFuture: FBFuture<FBProcess<NSNull, InputStream, AnyObject>>, sendResponse: (Data) async throws -> Void) async throws {
    let task = try await BridgeFuture.value(taskFuture)
    guard let inputStream = task.stdOut else {
      throw GRPCStatus(code: .internalError, message: "Unable to get stdOut to write")
    }

    inputStream.open()
    defer { inputStream.close() }

    // inputStream.hasBytesAvailable is unavailable due to custom implementation of NSInputStream
    while true {
      let sixteenKilobytes = 16384
      var buffer = [UInt8](repeating: 0, count: sixteenKilobytes)
      let readBytes = inputStream.read(&buffer, maxLength: sixteenKilobytes)

      guard readBytes >= 0 else {
        let message = "Draining operation failed with stream error: \(inputStream.streamError?.localizedDescription ?? "Unknown")"
        throw GRPCStatus(code: .internalError, message: message)
      }
      if readBytes == 0 {
        break
      }

      let data = Data(bytes: &buffer, count: readBytes)
      try await sendResponse(data)
    }

    let exitCode = try await BridgeFuture.value(task.exitCode).intValue
    if exitCode != 0 {
      throw GRPCStatus(code: .internalError, message: "Draining operation failed with exit code \(exitCode)")
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import GRPC
import IDBCompanionUtilities

extension GRPCAsyncResponseStreamWriter: AsyncStreamWriter {
  public typealias Value = Response

  @inlinable
  public func send(_ value: Value) async throws {
    try await send(value, compression: .deferToCallDefault)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBControlCore
import Foundation
import GRPC
import IDBGRPCSwift

enum MultisourceFileReader {

  static func filePathURLs<Request: PayloadExtractable>(from requestStream: GRPCAsyncRequestStream<Request>, temporaryDirectory: FBTemporaryDirectory, extractFromSubdir: Bool) async throws -> [URL] {
    func readNextPayload() async throws -> Idb_Payload {
      guard let p = try await requestStream.requiredNext.extractPayload()
      else { throw GRPCStatus(code: .failedPrecondition, message: "Incorrect request. Expected payload") }
      return p
    }

    var payload = try await readNextPayload()

    var compression = FBCompressionFormat.GZIP

    if case let .compression(payloadCompression) = payload.source {
      compression = compressionFormat(from: payloadCompression)
      payload = try await readNextPayload()
    }

    switch payload.source {
    case let .data(data):
      let (readTaskFromStreamTask, input) = pipeToInput(initialData: data, requestStream: requestStream)

      let result = try await filepathsFromTar(temporaryDirectory: temporaryDirectory, input: input, extractFromSubdir: extractFromSubdir, compression: compression)

      // We just check that read from request stream did not produce any errors
      _ = try await readTaskFromStreamTask.value

      return result

    case let .filePath(filePath):
      return try await filepathsFromStream(initial: .init(fileURLWithPath: filePath), requestStream: requestStream)

    case .url, .compression, .none:
      throw GRPCStatus(code: .invalidArgument, message: "Unrecogized initial payload type \(payload.source as Any)")
    }
  }

  private static func compressionFormat(from request: Idb_Payload.Compression) -> FBCompressionFormat {
    switch request {
    case .gzip:
      return .GZIP
    case .zstd:
      return .ZSTD
    case .UNRECOGNIZED:
      return .GZIP
    }
  }

  private static func filepathsFromStream<Request: PayloadExtractable>(initial: URL, requestStream: GRPCAsyncRequestStream<Request>) async throws -> [URL] {
    var filePaths = [initial]

    for try await request in requestStream {
      guard let payload = request.extractPayload()
      else { throw GRPCStatus(code: .invalidArgument, message: "Unrecogized buffer frame. Expect payload, got \(request)") }

      guard case .filePath(let filePath) = payload.source
      else { throw GRPCStatus(code: .invalidArgument, message: "Unrecogized buffer frame. Expect file path, got \(payload.source as Any)") }

      filePaths.append(URL(fileURLWithPath: filePath))
    }

    return filePaths
  }

  private static func filepathsFromTar(temporaryDirectory: FBTemporaryDirectory, input: FBProcessInput<OutputStream>, extractFromSubdir: Bool, compression: FBCompressionFormat) async throws -> [URL] {
    let mappedInput = input as! FBProcessInput<AnyObject>
    let tarContext = temporaryDirectory.withArchiveExtracted(fromStream: mappedInput, compression: compression)
    if extractFromSubdir {
      return try await BridgeFuture.values(temporaryDirectory.files(fromSubdirs: tarContext))
    } else {
      let extractionDir = try await BridgeFuture.value(tarContext)
      return try FileManager.default.contentsOfDirectory(at: extractionDir as URL, includingPropertiesForKeys: [.isDirectoryKey], options: [])
    }
  }

  // TODO: Do we really need multithreading here? Isnt we just fill the stream sequentially while read is blocked and only then read starts?
  private static func pipeToInput<Request: PayloadExtractable>(initialData: Data, requestStream: GRPCAsyncRequestStream<Request>) -> (Task<Void, Error>, FBProcessInput<OutputStream>) {
    let input = FBProcessInput<OutputStream>.fromStream()
    let stream = input.contents

    let readFromStreamTask = Task {
      stream.open()
      defer { stream.close() }

      var buffer = [UInt8](initialData)
      stream.write(&buffer, maxLength: buffer.count)

      for try await request in requestStream {
        guard let payload = request.extractPayload()
        else { throw GRPCStatus(code: .invalidArgument, message: "Unrecogized buffer frame. Expect payload, got \(request)") }

        guard case .data(let data) = payload.source
        else { throw GRPCStatus(code: .invalidArgument, message: "Unrecogized buffer frame. Expect file path, got \(payload.source as Any)") }

        var buffer = [UInt8](data)
        stream.write(&buffer, maxLength: buffer.count)
      }
    }

    return (readFromStreamTask, input)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBControlCore
import Foundation

extension Task where Failure == Error, Success: AnyObject & Sendable {

  /// Bridges swift concurrency back to FBFuture world.
  /// - Parameters:
  ///   - job: Asynchronous task
  /// - Returns: Job result
  /// - Note: Job starts its execution instantly and do not wait for FBFuture observation.
  /// - Note: FBFuture cancellation propagates to task.
  static func fbFuture(job: @escaping @Sendable () async throws -> Success) -> FBFuture<Success> {
    let mutableFuture = FBMutableFuture<Success>()

    let task = Task<Void, Error> {
      do {
        let result = try await job()
        mutableFuture.resolve(withResult: result)
      } catch {
        mutableFuture.resolveWithError(error)
      }
    }

    mutableFuture.onQueue(BridgeQueues.miscEventReaderQueue) {
      task.cancel()
      return FBFuture<NSNull>.empty()
    }

    return mutableFuture as! FBFuture<Success>
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import FBSimulatorControl
import Foundation
import GRPC
import IDBCompanionUtilities
import IDBGRPCSwift
import XCTestBootstrap

extension IDBXCTestReporter {

  struct Configuration {

    let resultBundlePath: String

    let coverageConfiguration: FBCodeCoverageConfiguration?

    let logDirectoryPath: String?

    let binariesPath: [String]

    let reportAttachments: Bool

    let reportResultBundle: Bool

    init(legacy: FBXCTestReporterConfiguration) {
      self.resultBundlePath = legacy.resultBundlePath ?? ""
      self.coverageConfiguration = legacy.coverageConfiguration
      self.logDirectoryPath = legacy.logDirectoryPath
      self.binariesPath = legacy.binariesPaths ?? []
      self.reportAttachments = legacy.reportAttachments
      self.reportResultBundle = legacy.reportResultBundle
    }
  }

  struct CurrentTestInfo {
    var bundleName = ""
    var testClass = ""
    var testMethod = ""
    var activityRecords: [FBActivityRecord] = []
    var failureInfo: Idb_XctestRunResponse.TestRunInfo.TestRunFailureInfo?
    var otherFailures: [Idb_XctestRunResponse.TestRunInfo.TestRunFailureInfo] = []
  }
}

@objc final class IDBXCTestReporter: NSObject, FBXCTestReporter, FBDataConsumer {

  let reportingTerminated = FBMutableFuture<NSNumber>()
  var configuration: Configuration!

  @Atomic private var responseStream: GRPCAsyncResponseStreamWriter<Idb_XctestRunResponse>?

  private let queue: DispatchQueue
  private let logger: FBControlCoreLogger

  private let processUnderTestExitedMutable = FBMutableFuture<NSNull>()

  @Atomic private var currentInfo = CurrentTestInfo()

  init(responseStream: GRPCAsyncResponseStreamWriter<Idb_XctestRunResponse>, queue: DispatchQueue, logger: FBControlCoreLogger) {
    self._responseStream = .init(wrappedValue: responseStream)
    self.queue = queue
    self.logger = logger
  }

  // MARK: - FBDataConsumer implementation

  @objc func consumeData(_ data: Data) {
    let logOutput = String(data: data, encoding: .utf8) ?? ""
    let response = createResponse(logOutput: logOutput)
    write(response: response)
  }

  @objc func consumeEndOfFile() {
    // Implementation not required
  }

  // MARK: - FBXCTestReporter implementation

  @objc func processWaitingForDebugger(withProcessIdentifier pid: pid_t) {
    logger.info().log("Tests waiting for debugger. To debug run: lldb -p \(pid)")
    let response = Idb_XctestRunResponse.with {
      $0.status = .running
      $0.debugger = .with {
        $0.pid = UInt64(pid)
      }
    }

    write(response: response)
  }

  @objc func didBeginExecutingTestPlan() {
    // Implementation not required
  }

  @objc func didFinishExecutingTestPlan() {
    let response = Idb_XctestRunResponse.with {
      $0.status = .terminatedNormally
    }
    write(response: response)
  }

  @objc func processUnderTestDidExit() {
    processUnderTestExitedMutable.resolve(withResult: NSNull())
  }

  @objc func testSuite(_ testSuite: String, didStartAt startTime: String) {
    _currentInfo.sync { $0.bundleName = testSuite }
  }

  @objc func testCaseDidFinish(forTestClass testClass: String, method: String, with status: FBTestReportStatus, duration: TimeInterval, logs: [String]?) {
    do {
      let info = try createRunInfo(testClass: testClass, method: method, status: status, duration: duration, logs: logs ?? [])
      write(testRunInfo: info)
    } catch {
      _responseStream.sync { $0 = nil }
      reportingTerminated.resolveWithError(error)
    }
  }

  @objc func testCaseDidFail(forTestClass testClass: String, method: String, exceptions: [FBExceptionInfo]) {
    let currentInfo = self.currentInfo
    if testClass == currentInfo.testClass && method != currentInfo.testMethod {
      logger.log("Got failure info for \(testClass)/\(method) but the current known executing test is \(currentInfo.testClass)\(currentInfo.testMethod). Ignoring it")
      return
    }
    self._currentInfo.sync {
      if let firstExceptionInfo = exceptions.first {
        $0.failureInfo = createFailureInfo(exceptionInfo: firstExceptionInfo)
        $0.otherFailures = exceptions.dropFirst().map { createFailureInfo(exceptionInfo: $0) }
      } else {
        logger.log("No exceptions were returned in the failure. This shouldn't happen.")
      }
    }
  }

  @objc func testCaseDidStart(forTestClass testClass: String, method: String) {
    _currentInfo.sync {
      $0.testClass = testClass
      $0.testMethod = method
    }
  }

  @objc func testPlanDidFail(withMessage message: String) {
    let response = responseFor(crashMessage: message)
    write(response: response)
  }

  @objc func testCase(_ testClass: String, method: String, didFinishActivity activity: FBActivityRecord) {
    _currentInfo.sync {
      $0.activityRecords.append(activity)
    }
  }

  @objc func finished(with summary: FBTestManagerResultSummary) {
    // didFinishExecutingTestPlan should be used to signify completion instead
  }

  @objc func testHadOutput(_ output: String) {
    let response = createResponseExtractingFailureInfo(from: output)
    write(response: response)
  }

  @objc func handleExternalEvent(_ event: String) {
    let response = createResponseExtractingFailureInfo(from: event)
    write(response: response)
  }

  @objc func printReport() throws {
    // Warning! This method is bridged to swift incorrectly and loses bool return type. Adapt and use with extra care
  }

  @objc func didCrashDuringTest(_ error: Error) {
    let response = responseFor(crashMessage: error.localizedDescription)
    write(response: response)
  }

  // MARK: - Privates

  private func createRunInfo(testClass: String, method: String, status: FBTestReportStatus, duration: TimeInterval, logs: [String]) throws -> Idb_XctestRunResponse.TestRunInfo {

    defer { resetCurrentTestState() }

    return try _currentInfo.sync { currentInfo in

      var stackedActivities: [FBActivityRecord] = []
      currentInfo.activityRecords.sort(by: { $0.start < $1.start })

      while let activity = currentInfo.activityRecords.first {
        currentInfo.activityRecords.remove(at: 0)
        populateSubactivities(root: activity, remaining: &currentInfo.activityRecords)
        stackedActivities.append(activity)
      }

      return try Idb_XctestRunResponse.TestRunInfo.with {
        $0.bundleName = currentInfo.bundleName
        $0.className = testClass
        $0.methodName = method
        $0.status = status == .failed ? .failed : .passed
        $0.duration = duration
        if let failureInfo = currentInfo.failureInfo {
          $0.failureInfo = failureInfo
        }
        $0.logs = logs
        $0.activityLogs = try stackedActivities.map(translate(activity:))
        $0.otherFailures = currentInfo.otherFailures
      }
    }
  }

  private func translate(activity: FBActivityRecord) throws -> Idb_XctestRunResponse.TestRunInfo.TestActivity {
    let subactivities = activity.subactivities as! [FBActivityRecord]
    return try Idb_XctestRunResponse.TestRunInfo.TestActivity.with {
      $0.title = activity.title
      $0.duration = activity.duration
      $0.uuid = activity.uuid.uuidString
      $0.activityType = activity.activityType
      $0.start = activity.start.timeIntervalSince1970
      $0.finish = activity.finish.timeIntervalSince1970
      $0.name = activity.name
      if configuration.reportAttachments {
        $0.attachments = try activity.attachments.map { attachment in
          try .with {
            $0.payload = attachment.payload ?? Data()
            $0.name = attachment.name
            $0.timestamp = attachment.timestamp.timeIntervalSince1970
            $0.uniformTypeIdentifier = attachment.uniformTypeIdentifier
            if let userInfo = attachment.userInfo {
              $0.userInfoJson = try translate(attachmentUserInfo: userInfo)
            }
          }
        }
      }
      $0.subActivities = try subactivities.map(translate(activity:))
    }
  }

  private func translate(attachmentUserInfo userInfo: [AnyHashable: Any]) throws -> Data {
    try JSONSerialization.data(withJSONObject: userInfo, options: [])
  }

  private func resetCurrentTestState() {
    _currentInfo.sync {
      $0.activityRecords.removeAll()
      $0.failureInfo = nil
      $0.testClass = ""
      $0.testMethod = ""
      $0.otherFailures.removeAll()
    }
  }

  private func populateSubactivities(root: FBActivityRecord, remaining: inout [FBActivityRecord]) {
    while let firstRemaining = remaining.first, root.start <= firstRemaining.start && firstRemaining.finish <= root.finish {
      remaining.remove(at: 0)
      populateSubactivities(root: firstRemaining, remaining: &remaining)
      root.subactivities.add(firstRemaining)
    }
  }

  private func write(testRunInfo: Idb_XctestRunResponse.TestRunInfo) {
    let response = Idb_XctestRunResponse.with {
      $0.status = .running
      $0.results = [testRunInfo]
    }
    write(response: response)
  }

  private func write(response: Idb_XctestRunResponse) {
    Task {
      do {
        switch response.status {
        case .terminatedNormally, .terminatedAbnormally:
          try await insertFinalDataThenWriteResponse(response: response)

        default:
          try await writeResponseFinal(response: response)
        }
      } catch {
        logger.log("Failed to write xctest run response \(error.localizedDescription)")
      }
    }
  }

  // TODO: Parallelism of execution was lost after rewriting this method to swift. Make this work in parallel again
  private func insertFinalDataThenWriteResponse(response: Idb_XctestRunResponse) async throws {
    var response = response

    if !configuration.resultBundlePath.isEmpty && configuration.reportResultBundle {
      do {
        let resultBundle = try await gzipFolder(at: configuration.resultBundlePath)
        response.resultBundle = .with {
          $0.data = resultBundle
        }
      } catch {
        logger.info().log("Failed to create result bundle \(error.localizedDescription)")
      }
    }

    if let coverageConfig = configuration.coverageConfiguration, !coverageConfig.coverageDirectory.isEmpty {
      do {
        let coverageData = try await getCoverageResponseData(config: coverageConfig)
        response.codeCoverageData = .with {
          $0.data = coverageData
        }
      } catch {
        logger.info().log("Failed to get coverage data: \(error.localizedDescription)")
      }
    }

    if let logDirectoryPath = configuration.logDirectoryPath {
      let data = try await gzipFolder(at: logDirectoryPath)
      response.logDirectory = .with {
        $0.data = data
      }
    }

    try await writeResponseFinal(response: response)
  }

  private func writeResponseFinal(response: Idb_XctestRunResponse) async throws {
    let shouldCloseStream = isLastMessage(responseStatus: response.status)

    let stream: GRPCAsyncResponseStreamWriter<Idb_XctestRunResponse>? = _responseStream.sync { storedStream in
      guard let responseStream = storedStream else { return nil }
      if shouldCloseStream {
        storedStream = nil
      }
      return responseStream
    }

    guard let responseStream = stream else {
      logger.error().log("writeResponse called, but the last response has already been written!")
      return
    }

    try await responseStream.send(response)

    if shouldCloseStream {
      logger.log("Test Reporting has finished with status \(response.status)")
      reportingTerminated.resolve(withResult: .init(value: response.status.rawValue))
    }
  }

  private func isLastMessage(responseStatus: Idb_XctestRunResponse.Status) -> Bool {
    return responseStatus == .terminatedNormally || responseStatus == .terminatedAbnormally
  }

  private func gzipFolder(at path: String) async throws -> Data {
    let task = FBArchiveOperations.createGzippedTarData(forPath: path,
                                                        queue: queue,
                                                        logger: logger)
    return try await BridgeFuture.value(task) as Data
  }

  private func getCoverageResponseData(config: FBCodeCoverageConfiguration) async throws -> Data {
    try await BridgeFuture.await(processUnderTestExitedMutable)
    switch config.format {
    case .exported:
      let data = try await getCoverageDataExported(config: config)
      return data as Data

    case .raw:
      return try await gzipFolder(at: config.coverageDirectory)

    default:
      throw FBControlCoreError.describe("Unsupported code coverage format")
    }
  }

  private func getCoverageDataExported(config: FBCodeCoverageConfiguration) async throws -> Data {
    let coverageDirectory = URL(fileURLWithPath: config.coverageDirectory)
    let profdataPath = coverageDirectory.appendingPathComponent("coverage.profdata")

    try await mergeRawCoverage(coverageDirectory: coverageDirectory, profdataPath: profdataPath)
    return try await exportCoverage(profdataPath: profdataPath, binariesPath: configuration.binariesPath)
  }

  private func mergeRawCoverage(coverageDirectory: URL, profdataPath: URL) async throws {
    let profraws = try FileManager.default
      .contentsOfDirectory(at: coverageDirectory, includingPropertiesForKeys: nil, options: [])
      .filter { $0.pathExtension == "profraw" }

    let mergeArgs: [String] = ["llvm-profdata", "merge", "-o", profdataPath.path]
      + profraws.map(\.path)

    let mergeProcessFuture = FBProcessBuilder<NSNull, NSData, NSString>
      .withLaunchPath("/usr/bin/xcrun", arguments: mergeArgs)
      .withStdOutInMemoryAsData()
      .withStdErrInMemoryAsString()
      .runUntilCompletion(withAcceptableExitCodes: nil)

    let mergeProcess = try await BridgeFuture.value(mergeProcessFuture)
    let exitCode = try await BridgeFuture.value(mergeProcess.exitCode)
    if exitCode != 0 {
      throw FBControlCoreError.describe("xcrun failed to export code coverage data \(exitCode.intValue) \(mergeProcess.stdErr ?? "")")
    }
  }

  private func exportCoverage(profdataPath: URL, binariesPath: [String]) async throws -> Data {
    let exportArgs: [String] = ["llvm-cov", "export", "-instr-profile", profdataPath.path]
      + binariesPath.reduce(into: []) {
        $0 += ["-object", $1]
      }
    let exportProcess = try await BridgeFuture.value(
      FBProcessBuilder<NSNull, NSData, NSString>
        .withLaunchPath("/usr/bin/xcrun", arguments: exportArgs)
        .withStdOutToInputStream()
        .withStdErrInMemoryAsString()
        .start()
    )

    let gzipProcessInput = FBProcessInput<OutputStream>.fromStream()
    let archiveFuture = FBArchiveOperations.createGzipData(from: gzipProcessInput as! FBProcessInput<AnyObject>, logger: self.logger)

    let oneMega = 1024 * 1024
    let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: oneMega)
    defer {
      buffer.deallocate()
    }

    guard let exportOutputStream = exportProcess.stdOut
    else {
      throw FBControlCoreError.describe("xcrun llvm-cov export misconfigured. stdOut stream is nil")
    }
    exportOutputStream.open()

    let gzipInputStream = gzipProcessInput.contents
    gzipInputStream.open()
    while case let bytesRead = exportOutputStream.read(buffer, maxLength: oneMega), bytesRead > 0 {
      gzipInputStream.write(buffer, maxLength: bytesRead)
    }
    exportOutputStream.close()
    gzipInputStream.close()

    let exitCode = try await BridgeFuture.value(exportProcess.exitCode)
    if exitCode != 0 {
      throw FBControlCoreError.describe("xcrun failed to export code coverage data \(exitCode.intValue) \(exportProcess.stdErr ?? "")")
    }

    let archiveProcess = try await BridgeFuture.value(archiveFuture)
    let stdOut = archiveProcess.stdOut ?? NSData()
    return stdOut as Data
  }

  private func createFailureInfo(message: String, file: String?, line: UInt) -> Idb_XctestRunResponse.TestRunInfo.TestRunFailureInfo {
    return Idb_XctestRunResponse.TestRunInfo.TestRunFailureInfo.with {
      $0.failureMessage = message
      $0.file = file ?? ""
      $0.line = UInt64(line)
    }
  }

  private func createFailureInfo(exceptionInfo: FBExceptionInfo) -> Idb_XctestRunResponse.TestRunInfo.TestRunFailureInfo {
    return Idb_XctestRunResponse.TestRunInfo.TestRunFailureInfo.with {
      $0.failureMessage = exceptionInfo.message
      $0.file = exceptionInfo.file ?? ""
      $0.line = UInt64(exceptionInfo.line)
    }
  }

  private func responseFor(crashMessage: String) -> Idb_XctestRunResponse {
    defer { resetCurrentTestState() }

    let currentInfo = self.currentInfo
    let info = Idb_XctestRunResponse.TestRunInfo.with {
      $0.bundleName = currentInfo.bundleName
      $0.className = currentInfo.testClass
      $0.methodName = currentInfo.testMethod
      $0.failureInfo = currentInfo.failureInfo ?? .init()
      $0.failureInfo.failureMessage = crashMessage
      $0.status = .crashed
      $0.otherFailures = currentInfo.otherFailures
    }

    return Idb_XctestRunResponse.with {
      $0.status = .terminatedAbnormally
      $0.results = [info]
    }
  }

  private func createResponseExtractingFailureInfo(from logOutput: String) -> Idb_XctestRunResponse {
    extractFailureInfo(from: logOutput)
    return createResponse(logOutput: logOutput)
  }

  private func extractFailureInfo(from logOutput: String) {
    do {
      let regexp = try NSRegularExpression(pattern: "Assertion failed: (.*), function (.*), file (.*), line (\\d+).", options: .caseInsensitive)
      let log = logOutput as NSString
      if let result = regexp.firstMatch(in: logOutput, options: [], range: .init(location: 0, length: log.length)) {
        _currentInfo.sync {
          $0.failureInfo = failureInfoWith(message: log.substring(with: result.range(at: 1)),
                                           file: log.substring(with: result.range(at: 3)),
                                           line: UInt(log.substring(with: result.range(at: 4))) ?? 0)
        }
      }
    } catch {
      assertionFailure(error.localizedDescription)
      logger.error().log("Incorrect regexp \(error.localizedDescription)")
    }
  }

  private func createResponse(logOutput: String) -> Idb_XctestRunResponse {
    return Idb_XctestRunResponse.with {
      $0.status = .running
      $0.logOutput = [logOutput]
    }
  }

  private func failureInfoWith(message: String, file: String, line: UInt) -> Idb_XctestRunResponse.TestRunInfo.TestRunFailureInfo {
    return .with {
      $0.failureMessage = message
      $0.file = file
      $0.line = UInt64(line)
    }
  }
}


// swift-tools-version: 5.6
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import PackageDescription

let package = Package(
  name: "Lexical",
  platforms: [.iOS(.v13)],
  products: [
    .library(
      name: "Lexical",
      targets: ["Lexical"]),
    .library(
      name: "LexicalListPlugin",
      targets: ["LexicalListPlugin"]),
    .library(
      name: "LexicalListHTMLSupport",
      targets: ["LexicalListHTMLSupport"]),
    .library(
      name: "LexicalHTML",
      targets: ["LexicalHTML"]),
  ],
  dependencies: [
    .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.6.0"),
  ],
  targets: [
    .target(
      name: "Lexical",
      dependencies: [],
      path: "./Lexical"),
    .testTarget(
      name: "LexicalTests",
      dependencies: ["Lexical"],
      path: "./LexicalTests"),

    .target(
      name: "LexicalListPlugin",
      dependencies: ["Lexical"],
      path: "./Plugins/LexicalListPlugin/LexicalListPlugin"),
    .target(
      name: "LexicalListHTMLSupport",
      dependencies: ["Lexical", "LexicalListPlugin", "LexicalHTML"],
      path: "./Plugins/LexicalListPlugin/LexicalListHTMLSupport"),

    .target(
      name: "LexicalHTML",
      dependencies: ["Lexical", "SwiftSoup"],
      path: "./Plugins/LexicalHTML/LexicalHTML"),
    .testTarget(
      name: "LexicalHTMLTests",
      dependencies: ["Lexical", "LexicalHTML", "SwiftSoup"],
      path: "./Plugins/LexicalHTML/LexicalHTMLTests"),
  ]
)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Foundation
import UIKit

public struct NodeType: Hashable, RawRepresentable {
  public init(rawValue: String) {
    self.rawValue = rawValue
  }
  public var rawValue: String

  public static let unknown = NodeType(rawValue: "unknown")
  public static let root = NodeType(rawValue: "root")
  public static let text = NodeType(rawValue: "text")
  public static let paragraph = NodeType(rawValue: "paragraph")
  public static let element = NodeType(rawValue: "element")
  public static let heading = NodeType(rawValue: "heading")
  public static let quote = NodeType(rawValue: "quote")
  public static let linebreak = NodeType(rawValue: "linebreak")
  public static let code = NodeType(rawValue: "code")
  public static let codeHighlight = NodeType(rawValue: "code-highlight")
}

public enum Mode: String, Codable {
  case normal
  case token
  case segmented
  case inert
}

enum LexicalConstants {
  // If we provide a systemFont as our default, it causes trouble for modifying font family.
  // Apple sets a private key NSCTFontUIUsageAttribute on the font descriptor, and that
  // key overrides any face or family key that we set. Hence we provide a default font of
  // Helvetica instead. Note that we need a fallback to something non-optional, hence
  // we do use system font if Helvetica cannot be found. This should never happen.
  static let defaultFont = UIFont(name: "Helvetica", size: 15.0) ?? UIFont.systemFont(ofSize: 15.0)

  static let defaultColor: UIColor = {
    if #available(iOS 13.0, *) {
      return UIColor.label
    } else {
      return UIColor.black
    }
  }()

  // Sigil value used during node initialization
  static let uninitializedNodeKey = "uninitializedNodeKey"

  static let pasteboardIdentifier = "x-lexical-nodes"
}

public typealias DirtyNodeMap = [NodeKey: DirtyStatusCause]

public typealias NodeTransform = (_ node: Node) throws -> Void

public enum DirtyStatusCause {
  case userInitiated
  case editorInitiated
}

public enum DirtyType {
  case noDirtyNodes
  case hasDirtyNodes
  case fullReconcile
}

public enum TextFormatType {
  case bold
  case italic
  case underline
  case strikethrough
  case code
  case subScript
  case superScript
}

enum Direction: String, Codable {
  case ltr
  case rtl
}

public enum Destination: Codable {
  case clone
  case html
}

enum TextStorageEditingMode {
  case none
  case controllerMode
}

public struct CommandType: RawRepresentable, Hashable {
  public var rawValue: String
  public init(rawValue: String) {
    self.rawValue = rawValue
  }

  public static let selectionChange = CommandType(rawValue: "selectionChange")
  public static let click = CommandType(rawValue: "click")
  public static let deleteCharacter = CommandType(rawValue: "deleteCharacter")
  public static let insertLineBreak = CommandType(rawValue: "insertLineBreak")
  public static let insertParagraph = CommandType(rawValue: "insertParagraph")
  public static let insertText = CommandType(rawValue: "insertText")
  public static let paste = CommandType(rawValue: "paste")
  public static let cut = CommandType(rawValue: "cut")
  public static let copy = CommandType(rawValue: "copy")
  public static let removeText = CommandType(rawValue: "removeText")
  public static let deleteWord = CommandType(rawValue: "deleteWord")
  public static let deleteLine = CommandType(rawValue: "deleteLine")
  public static let formatText = CommandType(rawValue: "formatText")
  public static let undo = CommandType(rawValue: "undo")
  public static let redo = CommandType(rawValue: "redo")
  public static let keyArrowRight = CommandType(rawValue: "keyArrowRight")
  public static let keyArrowLeft = CommandType(rawValue: "keyArrowLeft")
  public static let keyArrowUp = CommandType(rawValue: "keyArrowUp")
  public static let keyArrowDown = CommandType(rawValue: "keyArrowDown")
  public static let keyEnter = CommandType(rawValue: "keyEnter")
  public static let keyBackspace = CommandType(rawValue: "keyBackspace")
  public static let keyEscape = CommandType(rawValue: "keyEscape")
  public static let keyDelete = CommandType(rawValue: "keyDelete")
  public static let keyTab = CommandType(rawValue: "keyTab")
  public static let canUndo = CommandType(rawValue: "canUndo")
  public static let canRedo = CommandType(rawValue: "canRedo")
  public static let clearEditor = CommandType(rawValue: "clearEditor")
  public static let linkTapped = CommandType(rawValue: "linkTapped")
  public static let truncationIndicatorTapped = CommandType(rawValue: "truncationIndicatorTapped")
  public static let readOnlyViewTapped = CommandType(rawValue: "readOnlyViewTapped")
  public static let indentContent = CommandType(rawValue: "indentContent")
  public static let outdentContent = CommandType(rawValue: "outdentContent")
}

@objc public enum CommandPriority: Int {
  case Editor
  case Low
  case Normal
  case High
  case Critical
}

public typealias UpdateListener = (_ activeEditorState: EditorState, _ previousEditorState: EditorState, _ dirtyNodes: DirtyNodeMap) -> Void
public typealias TextContentListener = (_ text: String) -> Void
public typealias CommandListener = (_ payload: Any?) -> Bool
public typealias ErrorListener = (_ activeEditorState: EditorState, _ previousEditorState: EditorState, _ error: Error) -> Void

struct Listeners {
  var update: [UUID: UpdateListener] = [:]
  var textContent: [UUID: TextContentListener] = [:]
  var errors: [UUID: ErrorListener] = [:]
}

public typealias Commands = [CommandType: [CommandPriority: [UUID: CommandListener]]]

// enum is used in setting theme
public enum TextTransform: String {
  case lowercase = "lowercase"
  case uppercase = "uppercase"
  case none = "none"
}

public enum CustomDrawingLayer {
  case background
  case text
}

public enum CustomDrawingGranularity {
  case characterRuns
  case singleParagraph
  case contiguousParagraphs
}

/// See <doc:CustomDrawing> for description of the parameters
public typealias CustomDrawingHandler = (
  _ attributeKey: NSAttributedString.Key,
  _ attributeValue: Any,
  _ layoutManager: LayoutManager,
  _ attributeRunCharacterRange: NSRange,
  _ granularityExpandedCharacterRange: NSRange,
  _ glyphRange: NSRange,
  _ rect: CGRect,
  _ firstLineFragment: CGRect
) -> Void

@objc public class BlockLevelAttributes: NSObject {
  public init(marginTop: CGFloat, marginBottom: CGFloat, paddingTop: CGFloat, paddingBottom: CGFloat) {
    self.marginTop = marginTop
    self.marginBottom = marginBottom
    self.paddingTop = paddingTop
    self.paddingBottom = paddingBottom
  }

  let marginTop: CGFloat
  let marginBottom: CGFloat
  let paddingTop: CGFloat
  let paddingBottom: CGFloat

  override public func isEqual(_ object: Any?) -> Bool {
    if let object = object as? BlockLevelAttributes {
      return self.marginTop == object.marginTop && self.marginBottom == object.marginBottom && self.paddingTop == object.paddingTop && self.paddingBottom == object.paddingBottom
    }
    return false
  }

  override public var hash: Int {
    var hasher = Hasher()
    hasher.combine(marginTop)
    hasher.combine(marginBottom)
    hasher.combine(paddingTop)
    hasher.combine(paddingBottom)
    return hasher.finalize()
  }
}