import Combine
@preconcurrency import QuickLook
import SwiftUI

@MainActor
@Observable public class QuickLook {
  public var url: URL? {
    didSet {
      if url == nil {
        cleanup(urls: urls)
      }
    }
  }

  public private(set) var urls: [URL] = []
  public private(set) var isPreparing: Bool = false
  public private(set) var latestError: Error?

  public init() {}

  public func prepareFor(urls: [URL], selectedURL: URL) async {
    var transaction = Transaction(animation: .default)
    transaction.disablesAnimations = true
    withTransaction(transaction) {
      isPreparing = true
    }
    do {
      var order = 0
      let pathOrderMap = urls.reduce(into: [String: Int]()) { result, url in
        result[url.lastPathComponent] = order
        order += 1
      }
      let paths: [URL] = try await withThrowingTaskGroup(of: URL.self, body: { group in
        var paths: [URL] = []
        for url in urls {
          group.addTask {
            try await self.localPathFor(url: url)
          }
        }
        for try await path in group {
          paths.append(path)
        }
        return paths.sorted { url1, url2 in
          pathOrderMap[url1.lastPathComponent] ?? 0 < pathOrderMap[url2.lastPathComponent] ?? 0
        }
      })
      withTransaction(transaction) {
        self.urls = paths
        url = paths.first(where: { $0.lastPathComponent == selectedURL.lastPathComponent })
        isPreparing = false
      }
    } catch {
      withTransaction(transaction) {
        isPreparing = false
        self.urls = []
        url = nil
        latestError = error
      }
    }
  }

  private var quickLookDir: URL {
    try! FileManager.default.url(for: .cachesDirectory,
                                 in: .userDomainMask,
                                 appropriateFor: nil,
                                 create: false)
      .appending(component: "quicklook")
  }

  private func localPathFor(url: URL) async throws -> URL {
    try? FileManager.default.createDirectory(at: quickLookDir, withIntermediateDirectories: true)
    let path = quickLookDir.appendingPathComponent(url.lastPathComponent)

    // Warning: Non-sendable type '(any URLSessionTaskDelegate)?' exiting main actor-isolated
    // context in call to non-isolated instance method 'data(for:delegate:)' cannot cross actor
    // boundary.
    // This is on the defaulted-to-nil second parameter of `.data(from:delegate:)`.
    // There is a Radar tracking this & others like it.
    let data = try await URLSession.shared.data(from: url).0
    try data.write(to: path)
    return path
  }

  private func cleanup(urls _: [URL]) {
    try? FileManager.default.removeItem(at: quickLookDir)
  }
}
