import Foundation

/// Stable machine-readable codes — MUST match `BridgeErrorCode` in
/// `src/MapmapNav.types.ts`. Every rejected promise carries one.
enum BridgeErrorCode: String {
  case notInitialised = "E_NOT_INITIALISED"
  case initFailed = "E_INIT_FAILED"
  case network = "E_NETWORK"
  case auth = "E_AUTH"
  case territoryAllowance = "E_TERRITORY_ALLOWANCE"
  case territoryVerify = "E_TERRITORY_VERIFY"
  case territoryNotFound = "E_TERRITORY_NOT_FOUND"
  case noTerritory = "E_NO_TERRITORY"
  case noRoute = "E_NO_ROUTE"
  case guidanceActive = "E_GUIDANCE_ACTIVE"
  case noGuidance = "E_NO_GUIDANCE"
  case replayCorpus = "E_REPLAY_CORPUS"
  case internalError = "E_INTERNAL"
}

/// A bridge error carrying a contract code + message. Thrown across every
/// async boundary; the Expo module converts it into a rejected JS promise
/// whose `code` is the raw string above.
struct BridgeError: Error, CustomNSError {
  let code: BridgeErrorCode
  let message: String

  init(_ code: BridgeErrorCode, _ message: String) {
    self.code = code
    self.message = message
  }

  static var errorDomain: String { "MapmapNav" }
  var errorCode: Int { 0 }
  var errorUserInfo: [String: Any] {
    // Expo surfaces `code` (string) + `message` to JS; we also stash the
    // contract code under NSLocalizedDescription for native logs.
    ["code": code.rawValue, "message": message,
     NSLocalizedDescriptionKey: "\(code.rawValue): \(message)"]
  }

  /// JSON-serialisable shape for the `bridgeError` event / `error` field
  /// of a failed territoryProgress event.
  var asDictionary: [String: Any] {
    ["code": code.rawValue, "message": message]
  }
}

extension Error {
  /// Best-effort classification of an arbitrary error (SDK or transport)
  /// into a `BridgeError`. Known `BridgeError`s pass through unchanged.
  func asBridgeError(default fallback: BridgeErrorCode = .internalError) -> BridgeError {
    if let already = self as? BridgeError { return already }

    // URLSession / transport failures -> E_NETWORK, except explicit HTTP
    // status classification which the fetcher does up front.
    if let urlError = self as? URLError {
      return BridgeError(.network, "network: \(urlError.localizedDescription)")
    }

    // The SDK's verification failures are surfaced as NavCoreError cases
    // whose description mentions verification/signature. We can only match
    // on the human text here because the concrete NavCoreError type ships
    // in the (currently missing) generated bindings — see FINDINGS.
    let text = String(describing: self).lowercased()
    if text.contains("verif") || text.contains("signature") || text.contains("hash") {
      return BridgeError(.territoryVerify, String(describing: self))
    }
    if text.contains("no route") || text.contains("no path") || text.contains("noroute") {
      return BridgeError(.noRoute, String(describing: self))
    }
    return BridgeError(fallback, String(describing: self))
  }
}
