//
// Copyright (c) Double Symmetry GmbH
// Commercial use requires a license. See https://rntp.dev/pricing
//

import Foundation

/// A media URL passed from the JS layer in canonical form.
///
/// JS normalizes every input so native only sees:
///   - `http(s)://...`             — remote stream
///   - `file:///absolute/path`     — local file (URL-encoded)
///   - `asset://<name>`            — RN-bundled asset; we look it up in `Bundle.main`
///   - any other scheme            — passed through to AVFoundation as-is
///
/// We also tolerate raw absolute paths (`"/var/mobile/..."`) and bare names
/// as a safety net for callers that bypass the JS resolver.
struct MediaURL {
  let value: URL
  let isLocal: Bool
  let headers: [String: String]?

  init?(object: Any?) {
    guard let object = object else { return nil }

    let urlString: String
    let hdrs: [String: String]?

    if let dict = object as? [String: Any] {
      guard let raw = (dict["uri"] as? String) ?? (dict["url"] as? String),
            !raw.isEmpty
      else { return nil }
      urlString = raw
      hdrs = dict["headers"] as? [String: String]
    } else if let raw = object as? String, !raw.isEmpty {
      urlString = raw
      hdrs = nil
    } else {
      return nil
    }

    let lower = urlString.lowercased()
    if lower.hasPrefix("http://") || lower.hasPrefix("https://") {
      guard let u = URL(string: urlString) else { return nil }
      value = u
      isLocal = false
    } else if lower.hasPrefix("file://") {
      value = Self.fileURL(fromFileScheme: urlString)
      isLocal = true
    } else if lower.hasPrefix("asset://") {
      let name = String(urlString.dropFirst("asset://".count))
      guard let u = Self.resolveBundleAsset(name) else { return nil }
      value = u
      isLocal = true
    } else if urlString.hasPrefix("/") {
      // Bare absolute filesystem path — defensive fallback for callers that
      // skipped the JS normalizer.
      value = URL(fileURLWithPath: urlString)
      isLocal = true
    } else {
      // Unknown scheme (e.g. ipod-library://, data:) — let AVFoundation deal.
      guard let u = URL(string: urlString) else { return nil }
      value = u
      isLocal = u.isFileURL
    }

    headers = hdrs
  }

  /// Build a file URL from a `file://`-prefixed string, tolerating both
  /// percent-encoded URLs (`file:///foo%20bar.mp3`) and raw paths embedded
  /// in the scheme (`file:///foo bar.mp3`).
  private static func fileURL(fromFileScheme s: String) -> URL {
    if let u = URL(string: s) { return u }
    let path = String(s.dropFirst("file://".count))
    return URL(fileURLWithPath: path)
  }

  /// Resolve an `asset://<name>` reference by looking up `name` in the main
  /// app bundle. Supports both `Foo.mp3` (treated as `Foo`, extension `mp3`)
  /// and `Sounds.bundle/Foo.mp3` (treated as subdirectory + filename).
  /// Returns `nil` if no matching resource exists.
  private static func resolveBundleAsset(_ name: String) -> URL? {
    let bundle = Bundle.main
    let trimmed = name.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
    if trimmed.isEmpty { return nil }

    let lastSlash = trimmed.lastIndex(of: "/")
    let subdirectory = lastSlash.map { String(trimmed[..<$0]) }
    let filename = lastSlash
      .map { String(trimmed[trimmed.index(after: $0)...]) }
      ?? trimmed

    let (base, ext) = splitExtension(filename)
    if let url = bundle.url(
      forResource: base,
      withExtension: ext,
      subdirectory: subdirectory
    ) {
      return url
    }

    // Fallback: try the URL as a literal path inside the bundle.
    let candidate = bundle.bundleURL.appendingPathComponent(trimmed)
    if FileManager.default.fileExists(atPath: candidate.path) {
      return candidate
    }
    return nil
  }

  private static func splitExtension(_ filename: String) -> (base: String, ext: String?) {
    guard let dot = filename.lastIndex(of: ".") else { return (filename, nil) }
    let ext = String(filename[filename.index(after: dot)...])
    let base = String(filename[..<dot])
    return (base, ext.isEmpty ? nil : ext)
  }
}
