/*!
 * Native module created for Expo Share Intent (https://github.com/achorein/expo-share-intent)
 * author: achorein (https://github.com/achorein)
 * inspired by :
 *  - https://ajith-ab.github.io/react-native-receive-sharing-intent/docs/ios#create-share-extension
 */
import MobileCoreServices
import Photos
import Social
import UIKit

class ShareViewController: UIViewController {
  let hostAppGroupIdentifier: String = "<GROUPIDENTIFIER>"
  let shareProtocol: String = "<SCHEME>"
  let sharedKey: String = "<SCHEME>ShareKey"
  let hideView: Bool = <HIDEVIEW>
  var sharedMedia: [SharedMediaFile] = []
  var sharedWebUrl: [WebUrl] = []
  var sharedText: [String] = []
  // Race condition handling
  var totalAttachments: Int = 0
  var processedAttachments: Int = 0
  var expectedFinalType: RedirectType? = nil
  let imageContentType: String = UTType.image.identifier
  let videoContentType: String = UTType.movie.identifier
  let textContentType: String = UTType.text.identifier
  let urlContentType: String = UTType.url.identifier
  let propertyListType: String = UTType.propertyList.identifier
  let fileURLType: String = UTType.fileURL.identifier
  let pkpassContentType: String = "com.apple.pkpass"
  let pdfContentType: String = UTType.pdf.identifier
  let vcardContentType: String = "public.vcard"

  override func viewDidLoad() {
    super.viewDidLoad()
    if hideView {
      view.backgroundColor = .clear
      view.isOpaque = false
      handleViewLoad()
    }
  }

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if !hideView {
      handleViewLoad()
    }
  }

  private func handleViewLoad() {
    Task {
      guard let extensionContext = self.extensionContext,
        let content = extensionContext.inputItems.first as? NSExtensionItem,
        let attachments = content.attachments
      else {
        dismissWithError(message: "No content found")
        return
      }
      self.totalAttachments = attachments.count
      let lastIndex = attachments.count - 1
      for (index, attachment) in (attachments).enumerated() {
        var thisType: RedirectType? = nil
        if attachment.hasItemConformingToTypeIdentifier(imageContentType) {
          thisType = .media
          await handleImages(content: content, attachment: attachment, index: index)
        } else if attachment.hasItemConformingToTypeIdentifier(videoContentType) {
          thisType = .media
          await handleVideos(content: content, attachment: attachment, index: index)
        } else if attachment.hasItemConformingToTypeIdentifier(vcardContentType) {
          await handleVCard(content: content, attachment: attachment, index: index) 
        } else if attachment.hasItemConformingToTypeIdentifier(fileURLType) {
          thisType = .file
          await handleFiles(content: content, attachment: attachment, index: index)
        } else if attachment.hasItemConformingToTypeIdentifier(pkpassContentType) {
          thisType = .file
          await handlePkPass(content: content, attachment: attachment, index: index)
        } else if attachment.hasItemConformingToTypeIdentifier(pdfContentType) {
          thisType = .file
          await handlePdf(content: content, attachment: attachment, index: index)
        } else if attachment.hasItemConformingToTypeIdentifier(propertyListType) {
          thisType = .weburl
          await handlePrepocessing(content: content, attachment: attachment, index: index)
        } else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
          thisType = .weburl
          await handleUrl(content: content, attachment: attachment, index: index)
        } else if attachment.hasItemConformingToTypeIdentifier(textContentType) {
          thisType = .text
          await handleText(content: content, attachment: attachment, index: index)
        } else {
          NSLog("[ERROR] content type not handle !\(String(describing: content))")
          dismissWithError(message: "content type not handle \(String(describing: content)))")
        }
        if index == lastIndex { self.expectedFinalType = thisType }
      }
    }
  }

  private func handleVCard(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async {
    Task.detached {
      do {
        if let url = try? await attachment.loadItem(forTypeIdentifier: self.vcardContentType) as? URL {
          // ensure a .vcf file extension so mime resolves properly
          let tmp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".vcf")
          _ = Self.copyFile(at: url, to: tmp)
          Task { @MainActor in
            await self.handleFileURL(content: content, url: tmp, index: index)
          }
        } else if let data = try? await attachment.loadItem(forTypeIdentifier: self.vcardContentType) as? Data {
          let tmp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".vcf")
          try data.write(to: tmp)
          Task { @MainActor in
            await self.handleFileURL(content: content, url: tmp, index: index)
          }
        } else {
          NSLog("[ERROR] Cannot load vcard content !\(String(describing: content))")
          await self.dismissWithError(message: "Cannot load vCard content \(String(describing: content))")
        }
      } catch {
        NSLog("[ERROR] handleVCard exception: \(error.localizedDescription)")
        await self.dismissWithError(message: "vCard error: \(error.localizedDescription)")
      }
    }
  }

  private func handleText(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async {
    Task.detached {
      if let item = try! await attachment.loadItem(forTypeIdentifier: self.textContentType)
        as? String
      {
        Task { @MainActor in

          self.sharedText.append(item)
          self.markProcessed()

        }
      } else {
        NSLog("[ERROR] Cannot load text content !\(String(describing: content))")
        await self.dismissWithError(
          message: "Cannot load text content \(String(describing: content))")
      }
    }
  }

  private func handleUrl(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async {
    Task.detached {
      if let item = try! await attachment.loadItem(forTypeIdentifier: self.urlContentType) as? URL {
        Task { @MainActor in

          self.sharedWebUrl.append(WebUrl(url: item.absoluteString, meta: ""))
          self.markProcessed()

        }
      } else {
        NSLog("[ERROR] Cannot load url content !\(String(describing: content))")
        await self.dismissWithError(
          message: "Cannot load url content \(String(describing: content))")
      }
    }
  }

  private func handlePrepocessing(content: NSExtensionItem, attachment: NSItemProvider, index: Int)
    async
  {
    Task.detached {
      if let item = try! await attachment.loadItem(
        forTypeIdentifier: self.propertyListType, options: nil)
        as? NSDictionary
      {
        Task { @MainActor in

          if let results = item[NSExtensionJavaScriptPreprocessingResultsKey]
            as? NSDictionary
          {
            NSLog(
              "[DEBUG] NSExtensionJavaScriptPreprocessingResultsKey \(String(describing: results))"
            )
            self.sharedWebUrl.append(
              WebUrl(url: results["baseURI"] as! String, meta: results["meta"] as! String))
            self.markProcessed()
          } else {
            NSLog("[ERROR] Cannot load preprocessing results !\(String(describing: content))")
            self.dismissWithError(
              message: "Cannot load preprocessing results \(String(describing: content))")
          }

        }
      } else {
        NSLog("[ERROR] Cannot load preprocessing content !\(String(describing: content))")
        await self.dismissWithError(
          message: "Cannot load preprocessing content \(String(describing: content))")
      }
    }
  }

  private func handlePkPass(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async {
      Task.detached {
          NSLog("[DEBUG] Attempting to handle pkpass file for item \(index)")
          NSLog("[DEBUG] Available type identifiers: \(attachment.registeredTypeIdentifiers)")
  
          do {
              if let url = try await attachment.loadItem(forTypeIdentifier: self.pkpassContentType) as? URL {
                  NSLog("[DEBUG] Successfully loaded pkpass as URL: \(url.absoluteString)")
                  NSLog("[DEBUG] URL path: \(url.path), isFileURL: \(url.isFileURL)")
                  await self.handleFileURL(content: content, url: url, index: index)
  
              } else if let data = try await attachment.loadItem(forTypeIdentifier: self.pkpassContentType) as? Data {
                  NSLog("[DEBUG] Successfully loaded pkpass as Data, size: \(data.count) bytes")
                  let tempFileName = UUID().uuidString + ".pkpass"
                  let tempFileURL = FileManager.default.temporaryDirectory.appendingPathComponent(tempFileName)
  
                  // Writing data to a file is I/O, keep it off the main thread.
                  try data.write(to: tempFileURL)
                  NSLog("[DEBUG] Saved pkpass data to temporary file: \(tempFileURL.path)")
  
                  // Handle the newly created temporary file URL.
                  await self.handleFileURL(content: content, url: tempFileURL, index: index)
  
              } else {
                  // If it's neither URL nor Data, it's unexpected for pkpassContentType.
                  NSLog("[ERROR] Cannot load pkpass content: Item was neither URL nor Data for type \(self.pkpassContentType). Attachment: \(attachment)")
                  // Ensure dismissWithError runs on the main thread if it interacts with UI
                  Task { @MainActor in
                      self.dismissWithError(message: "Cannot load pkpass content (unexpected data type).")
                  }
              }
          } catch {
              // Catch errors from loadItem or data.write
              NSLog("[ERROR] Exception when handling pkpass: \(error.localizedDescription)")
              // Ensure dismissWithError runs on the main thread if it interacts with UI
              Task { @MainActor in
                  self.dismissWithError(message: "Error processing pkpass: \(error.localizedDescription)")
              }
          }
      }
  }


  private func handleImages(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async {
    Task.detached {
      do {
        let item = try await attachment.loadItem(forTypeIdentifier: self.imageContentType)
        
        Task { @MainActor in
          var url: URL? = nil
          
          if let dataURL = item as? URL {
            url = dataURL
          } else if let imageData = item as? UIImage {
            url = self.saveScreenshot(imageData)
            if url == nil {
              NSLog("[ERROR] handleImages: saveScreenshot returned nil")
            }
          } else if let data = item as? Data {
            if let image = UIImage(data: data) {
              url = self.saveScreenshot(image)
            } else {
              NSLog("[ERROR] handleImages: Failed to create UIImage from Data")
            }
          } else {
            NSLog("[ERROR] handleImages: Item is unexpected type: \(type(of: item))")
          }

          guard let safeURL = url else {
            NSLog("[ERROR] handleImages: Failed to get URL for image item")
            self.dismissWithError(message: "Failed to process image")
            return
          }

          var pixelWidth: Int? = nil
          var pixelHeight: Int? = nil
          if let imageSource = CGImageSourceCreateWithURL(safeURL as CFURL, nil) {
            if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
              as Dictionary?
            {
              pixelWidth = imageProperties[kCGImagePropertyPixelWidth] as? Int
              pixelHeight = imageProperties[kCGImagePropertyPixelHeight] as? Int
              // Check orientation and flip size if required
              if let orientationNumber = imageProperties[kCGImagePropertyOrientation] as! CFNumber?
              {
                var orientation: Int = 0
                CFNumberGetValue(orientationNumber, .intType, &orientation)
                if orientation > 4 {
                  let temp: Int? = pixelWidth
                  pixelWidth = pixelHeight
                  pixelHeight = temp
                }
              }
            }
          }

          // Always copy
          let fileName = self.getFileName(from: safeURL, type: .image)
          let fileExtension = self.getExtension(from: safeURL, type: .image)
          let fileSize = self.getFileSize(from: safeURL)
          let mimeType = safeURL.mimeType(ext: fileExtension)
          let newName = "\(UUID().uuidString).\(fileExtension)"
          let newPath = FileManager.default
            .containerURL(
              forSecurityApplicationGroupIdentifier: self.hostAppGroupIdentifier)!
            .appendingPathComponent(newName)
          
          let copied = Self.copyFile(at: safeURL, to: newPath)
          
          if copied {
            self.sharedMedia.append(
              SharedMediaFile(
                path: newPath.absoluteString, thumbnail: nil, fileName: fileName,
                fileSize: fileSize, width: pixelWidth, height: pixelHeight, duration: nil,
                mimeType: mimeType, type: .image))
          }

          self.markProcessed()

        }
      } catch {
        NSLog("[ERROR] handleImages: Exception loading image item: \(error)")
        await self.dismissWithError(message: "Cannot load image content: \(error.localizedDescription)")
      }
    }
  }

  private func documentDirectoryPath() -> URL? {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    
    if let firstPath = paths.first {
      _ = FileManager.default.fileExists(atPath: firstPath.path)
      return firstPath
    } else {
      return nil
    }
  }

  private func saveScreenshot(_ image: UIImage) -> URL? {
    guard let screenshotData = image.pngData() else {
      return nil
    }
    
    // Try using the app group container instead of documents directory
    guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: self.hostAppGroupIdentifier) else {
      return nil
    }
    
    let fileName = "screenshot_\(UUID().uuidString).png"
    let screenshotPath = containerURL.appendingPathComponent(fileName)
    
    do {
      try screenshotData.write(to: screenshotPath)

      let fileExists = FileManager.default.fileExists(atPath: screenshotPath.path)
      
      if fileExists {
        let attributes = try? FileManager.default.attributesOfItem(atPath: screenshotPath.path)
        _ = attributes?[.size] as? Int ?? 0
      }
      
      return screenshotPath
    } catch {
      NSLog("[ERROR] saveScreenshot: Failed to write screenshot: \(error)")
      NSLog("[ERROR] saveScreenshot: Error details: \(error.localizedDescription)")
      return nil
    }
  }

  private func handleVideos(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async
  {
    Task.detached {
      if let url = try? await attachment.loadItem(forTypeIdentifier: self.videoContentType) as? URL
      {
        Task { @MainActor in

          // Always copy
          let fileName = self.getFileName(from: url, type: .video)
          let fileExtension = self.getExtension(from: url, type: .video)
          let fileSize = self.getFileSize(from: url)
          let mimeType = url.mimeType(ext: fileExtension)
          let newName = "\(UUID().uuidString).\(fileExtension)"
          let newPath = FileManager.default
            .containerURL(
              forSecurityApplicationGroupIdentifier: self.hostAppGroupIdentifier)!
            .appendingPathComponent(newName)
          let copied = Self.copyFile(at: url, to: newPath)
          if copied {
            guard
              let sharedFile = self.getSharedMediaFile(
                forVideo: newPath, fileName: fileName, fileSize: fileSize, mimeType: mimeType)
            else {
              return
            }
            self.sharedMedia.append(sharedFile)
          }

          self.markProcessed()

        }
      } else {
        NSLog("[ERROR] Cannot load video content !\(String(describing: content))")
        await self.dismissWithError(
          message: "Cannot load video content \(String(describing: content))")
      }
    }
  }

  private func handlePdf(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async {
    Task.detached {
      if let url = try? await attachment.loadItem(forTypeIdentifier: self.pdfContentType) as? URL {
        Task { @MainActor in

          await self.handleFileURL(content: content, url: url, index: index)

        }
      } else {
        NSLog("[ERROR] Cannot load pdf content !\(String(describing: content))")
        await self.dismissWithError(
          message: "Cannot load pdf content \(String(describing: content))")
      }
    }
  }

  private func handleFiles(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async {
    Task.detached {
      if let url = try? await attachment.loadItem(forTypeIdentifier: self.fileURLType) as? URL {
        Task { @MainActor in

          await self.handleFileURL(content: content, url: url, index: index)

        }
      } else {
        NSLog("[ERROR] Cannot load file content !\(String(describing: content))")
        await self.dismissWithError(
          message: "Cannot load file content \(String(describing: content))")
      }
    }
  }

  private func handleFileURL(content: NSExtensionItem, url: URL, index: Int) async {
    // Always copy
    let fileName = self.getFileName(from: url, type: .file)
    let fileExtension = self.getExtension(from: url, type: .file)
    let fileSize = self.getFileSize(from: url)
    let mimeType = url.mimeType(ext: fileExtension)
    let newName = "\(UUID().uuidString).\(fileExtension)"
    let newPath = FileManager.default
      .containerURL(
        forSecurityApplicationGroupIdentifier: self.hostAppGroupIdentifier)!
      .appendingPathComponent(newName)
    let copied = Self.copyFile(at: url, to: newPath)
    if copied {
      self.sharedMedia.append(
        SharedMediaFile(
          path: newPath.absoluteString, thumbnail: nil, fileName: fileName,
          fileSize: fileSize, width: nil, height: nil, duration: nil, mimeType: mimeType,
          type: .file))
    }

  self.markProcessed()
  }

  private func dismissWithError(message: String? = nil) {
    DispatchQueue.main.async {
      NSLog("[ERROR] Error loading application ! \(message!)")
      let alert = UIAlertController(
        title: "Error", message: "Error loading application: \(message!)", preferredStyle: .alert)

      let action = UIAlertAction(title: "OK", style: .cancel) { _ in
        self.dismiss(animated: true, completion: nil)
        self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
      }

      alert.addAction(action)
      self.present(alert, animated: true, completion: nil)
    }
  }

  private func redirectToHostApp(type: RedirectType) {
    let nonce = UUID().uuidString
    let url = URL(string: "\(shareProtocol)://dataUrl=\(sharedKey)?nonce=\(nonce)#\(type)")!
    var responder = self as UIResponder?

    while responder != nil {
      if let application = responder as? UIApplication {
        if application.canOpenURL(url) {
          application.open(url)
        } else {
          NSLog("redirectToHostApp canOpenURL KO: \(shareProtocol)")
          self.dismissWithError(
            message: "Application not found, invalid url scheme \(shareProtocol)")
          return
        }
      }
      responder = responder!.next
    }
    extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
  }

  enum RedirectType {
    case media
    case text
    case weburl
    case file
  }

  func getExtension(from url: URL, type: SharedMediaType) -> String {
    let parts = url.lastPathComponent.components(separatedBy: ".")
    var ex: String? = nil
    if parts.count > 1 {
      ex = parts.last
    }
    if ex == nil {
      switch type {
      case .image:
        ex = "PNG"
      case .video:
        ex = "MP4"
      case .file:
        ex = "TXT"
        if url.lastPathComponent.lowercased().contains("pkpass") { ex = "pkpass" }
      }
    }
    return ex ?? "Unknown"
  }

  func getFileName(from url: URL, type: SharedMediaType) -> String {
    var name = url.lastPathComponent
    if name == "" {
      name = UUID().uuidString + "." + getExtension(from: url, type: type)
    }
    return name
  }

  func getFileSize(from url: URL) -> Int? {
    do {
      let resources = try url.resourceValues(forKeys: [.fileSizeKey])
      return resources.fileSize
    } catch {
      NSLog("Error: \(error)")
      return nil
    }
  }

  nonisolated static func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
    do {
      if FileManager.default.fileExists(atPath: dstURL.path) {
        try FileManager.default.removeItem(at: dstURL)
      }
      try FileManager.default.copyItem(at: srcURL, to: dstURL)
    } catch (let error) {
      NSLog("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
      return false
    }
    return true
  }

  private func getSharedMediaFile(forVideo: URL, fileName: String, fileSize: Int?, mimeType: String)
    -> SharedMediaFile?
  {
    let asset = AVAsset(url: forVideo)
    let thumbnailPath = getThumbnailPath(for: forVideo)
    let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
    var trackWidth: Int? = nil
    var trackHeight: Int? = nil

    // get video info
    let track = asset.tracks(withMediaType: AVMediaType.video).first ?? nil
    if track != nil {
      let size = track!.naturalSize.applying(track!.preferredTransform)
      trackWidth = abs(Int(size.width))
      trackHeight = abs(Int(size.height))
    }

    if FileManager.default.fileExists(atPath: thumbnailPath.path) {
      return SharedMediaFile(
        path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, fileName: fileName,
        fileSize: fileSize, width: trackWidth, height: trackHeight, duration: duration,
        mimeType: mimeType, type: .video)
    }

    var saved = false
    let assetImgGenerate = AVAssetImageGenerator(asset: asset)
    assetImgGenerate.appliesPreferredTrackTransform = true
    assetImgGenerate.maximumSize = CGSize(width: 360, height: 360)
    do {
      let img = try assetImgGenerate.copyCGImage(
        at: CMTimeMakeWithSeconds(600, preferredTimescale: Int32(1.0)), actualTime: nil)
      try UIImage.pngData(UIImage(cgImage: img))()?.write(to: thumbnailPath)
      saved = true
    } catch {
      saved = false
    }

    return saved
      ? SharedMediaFile(
        path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, fileName: fileName,
        fileSize: fileSize, width: trackWidth, height: trackHeight, duration: duration,
        mimeType: mimeType, type: .video) : nil
  }

  private func getThumbnailPath(for url: URL) -> URL {
    let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(
      of: "==", with: "")
    let path = FileManager.default
      .containerURL(forSecurityApplicationGroupIdentifier: self.hostAppGroupIdentifier)!
      .appendingPathComponent("\(fileName).jpg")
    return path
  }

  class WebUrl: Codable {
    var url: String
    var meta: String

    init(url: String, meta: String) {
      self.url = url
      self.meta = meta
    }
  }

  class SharedMediaFile: Codable {
    var path: String  // can be image, video or url path
    var thumbnail: String?  // video thumbnail
    var fileName: String  // uuid + extension
    var fileSize: Int?
    var width: Int?  // for image
    var height: Int?  // for image
    var duration: Double?  // video duration in milliseconds
    var mimeType: String
    var type: SharedMediaType
    var extra: String?  // caption / extra text

    init(
      path: String, thumbnail: String?, fileName: String, fileSize: Int?, width: Int?, height: Int?,
      duration: Double?, mimeType: String, type: SharedMediaType, extra: String? = nil
    ) {
      self.path = path
      self.thumbnail = thumbnail
      self.fileName = fileName
      self.fileSize = fileSize
      self.width = width
      self.height = height
      self.duration = duration
      self.mimeType = mimeType
      self.type = type
      self.extra = extra
    }
  }

  enum SharedMediaType: Int, Codable {
    case image
    case video
    case file
  }

  func toData(data: [WebUrl]) -> Data? {
    let encodedData = try? JSONEncoder().encode(data)
    return encodedData
  }
  func toData(data: [SharedMediaFile]) -> Data? {
    let encodedData = try? JSONEncoder().encode(data)
    return encodedData
  }

  // MARK: - Finalization helpers (race condition fix)
  private func markProcessed() {
    processedAttachments += 1
    if processedAttachments == totalAttachments { finalizeAndRedirect() }
  }

  private func finalizeAndRedirect() {
    // Prefer media if any media files were collected, regardless of last attachment type.
    var chosenType = expectedFinalType
    if !sharedMedia.isEmpty { chosenType = .media }
    guard let finalType = chosenType else {
      dismissWithError(message: "Unknown final type")
      return
    }
    let userDefaults = UserDefaults(suiteName: self.hostAppGroupIdentifier)

    switch finalType {
    case .media, .file:
      // Attach extra caption if we have text and media
      let caption = sharedText.first
      if let caption = caption, !caption.isEmpty {
        for i in 0..<sharedMedia.count { if sharedMedia[i].extra == nil { sharedMedia[i].extra = caption } }
      }
      userDefaults?.set(self.toData(data: self.sharedMedia), forKey: self.sharedKey)
    case .text:
      userDefaults?.set(self.sharedText, forKey: self.sharedKey)
    case .weburl:
      userDefaults?.set(self.toData(data: self.sharedWebUrl), forKey: self.sharedKey)
    }
    userDefaults?.synchronize()
    self.redirectToHostApp(type: finalType)
  }
}

internal let mimeTypes = [
  "html": "text/html",
  "htm": "text/html",
  "shtml": "text/html",
  "css": "text/css",
  "xml": "text/xml",
  "gif": "image/gif",
  "jpeg": "image/jpeg",
  "jpg": "image/jpeg",
  "js": "application/javascript",
  "atom": "application/atom+xml",
  "rss": "application/rss+xml",
  "mml": "text/mathml",
  "txt": "text/plain",
  "jad": "text/vnd.sun.j2me.app-descriptor",
  "wml": "text/vnd.wap.wml",
  "htc": "text/x-component",
  "png": "image/png",
  "tif": "image/tiff",
  "tiff": "image/tiff",
  "wbmp": "image/vnd.wap.wbmp",
  "ico": "image/x-icon",
  "jng": "image/x-jng",
  "bmp": "image/x-ms-bmp",
  "svg": "image/svg+xml",
  "svgz": "image/svg+xml",
  "webp": "image/webp",
  "woff": "application/font-woff",
  "jar": "application/java-archive",
  "war": "application/java-archive",
  "ear": "application/java-archive",
  "json": "application/json",
  "hqx": "application/mac-binhex40",
  "doc": "application/msword",
  "pdf": "application/pdf",
  "ps": "application/postscript",
  "eps": "application/postscript",
  "ai": "application/postscript",
  "rtf": "application/rtf",
  "m3u8": "application/vnd.apple.mpegurl",
  "xls": "application/vnd.ms-excel",
  "eot": "application/vnd.ms-fontobject",
  "ppt": "application/vnd.ms-powerpoint",
  "wmlc": "application/vnd.wap.wmlc",
  "kml": "application/vnd.google-earth.kml+xml",
  "kmz": "application/vnd.google-earth.kmz",
  "7z": "application/x-7z-compressed",
  "cco": "application/x-cocoa",
  "jardiff": "application/x-java-archive-diff",
  "jnlp": "application/x-java-jnlp-file",
  "pkpass": "application/vnd.apple.pkpass",
  "run": "application/x-makeself",
  "pl": "application/x-perl",
  "pm": "application/x-perl",
  "prc": "application/x-pilot",
  "pdb": "application/x-pilot",
  "rar": "application/x-rar-compressed",
  "rpm": "application/x-redhat-package-manager",
  "sea": "application/x-sea",
  "swf": "application/x-shockwave-flash",
  "sit": "application/x-stuffit",
  "tcl": "application/x-tcl",
  "tk": "application/x-tcl",
  "der": "application/x-x509-ca-cert",
  "pem": "application/x-x509-ca-cert",
  "crt": "application/x-x509-ca-cert",
  "xpi": "application/x-xpinstall",
  "xhtml": "application/xhtml+xml",
  "xspf": "application/xspf+xml",
  "zip": "application/zip",
  "epub": "application/epub+zip",
  "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
  "mid": "audio/midi",
  "midi": "audio/midi",
  "kar": "audio/midi",
  "mp3": "audio/mpeg",
  "ogg": "audio/ogg",
  "m4a": "audio/x-m4a",
  "ra": "audio/x-realaudio",
  "3gpp": "video/3gpp",
  "3gp": "video/3gpp",
  "ts": "video/mp2t",
  "mp4": "video/mp4",
  "mpeg": "video/mpeg",
  "mpg": "video/mpeg",
  "mov": "video/quicktime",
  "webm": "video/webm",
  "flv": "video/x-flv",
  "m4v": "video/x-m4v",
  "mng": "video/x-mng",
  "asx": "video/x-ms-asf",
  "asf": "video/x-ms-asf",
  "wmv": "video/x-ms-wmv",
  "avi": "video/x-msvideo",
  "vcf": "text/vcard",
]

extension URL {
  func mimeType(ext: String?) -> String {
    if #available(iOSApplicationExtension 14.0, *) {
      if let pathExt = ext,
        let mimeType = UTType(filenameExtension: pathExt)?.preferredMIMEType
      {
        return mimeType
      } else {
        return "application/octet-stream"
      }
    } else {
      return mimeTypes[ext?.lowercased() ?? ""] ?? "application/octet-stream"
    }
  }
}

extension Array {
  subscript(safe index: UInt) -> Element? {
    return Int(index) < count ? self[Int(index)] : nil
  }
}