// generated by bun run generate from the SwiftUI SDK and codegen/catalog.ts.
// edit the generator or catalog, then regenerate.
import SwiftUI
import UIKit
import UniformTypeIdentifiers
import Foundation

private final class FileImporterModel: ObservableObject {
  @Published var controlled = OneNativeControlled<Bool>(false)
  @Published var allowedContentTypes: [String] = []
  @Published var allowsMultipleSelection: Bool = false
  @Published var accessibility = OneNativeAccessibility()
  @Published var swiftStyle = OneNativeStyle()
  var active = false
  var onSDKEvent: ((String, String) -> Void)?
  func emitSDKEvent(_ name: String, _ value: String) { if active { onSDKEvent?(name, value) } }

  var onChange: ((Bool, Int, Int) -> Void)?
  func change(_ value: Bool) {
    guard active, controlled.value != value else { return }
    controlled.change(value)
    onChange?(value, controlled.eventCount, controlled.revision)
  }
  var onCompletion: ((String, String, Double, Double, String, Int) -> Void)?
  private var completionCount = 0
  func completion(_ type: String, _ url: String, _ index: Double, _ count: Double, _ message: String) {
    guard active else { return }
    completionCount += 1
    onCompletion?(type, url, index, count, message, completionCount)
  }
}
@objcMembers public final class OneNativeFileImporterView: UIView, OneNativeComposable {
  public var onSDKEvent: ((String, String) -> Void)?

  public var onChange: ((Bool, Int, Int) -> Void)?
  public var onCompletion: ((String, String, Double, Double, String, Int) -> Void)?
  private var model = FileImporterModel()
  private var controller: OneNativeHostingController<OneNativeStandalone<FileImporterContent>>?
  public override init(frame: CGRect) { super.init(frame: frame) }
  required init?(coder: NSCoder) { fatalError("init(coder:) is unavailable") }
  public func configureAccessibility(_ label: String, hint: String, value: String, identifier: String) {
    let next = OneNativeAccessibility(label: label, hint: hint, value: value, identifier: identifier)
    if model.accessibility != next { model.accessibility = next }
  }
  public func configureStyle(_ style: [String: Any]) {
    let next = OneNativeStyle(dictionary: style)
    if model.swiftStyle != next {
      let previousRows = model.swiftStyle.sdkModifiers.filter { $0.first?.hasPrefix("listRow") == true }
      let nextRows = next.sdkModifiers.filter { $0.first?.hasPrefix("listRow") == true }
      model.swiftStyle = next
      if previousRows != nextRows { compositionParent?.refreshRow(for: self) }
    }
  }
  public func configure(_ value: Bool, acknowledgedEvent: Int, revision: Int, allowsMultipleSelection: Bool) {
    if let next = model.controlled.applying(value, acknowledged: acknowledgedEvent, revision: revision) { model.controlled = next }
    if model.allowsMultipleSelection != allowsMultipleSelection { model.allowsMultipleSelection = allowsMultipleSelection }
  }
  public func setAllowedContentTypes(_ items: [String]) {
    let resolved = items.compactMap(UTType.init)
    if !items.isEmpty && resolved.isEmpty {
      model.completion("failed", "", 0, 0, "unknown content type identifiers: \(items.joined(separator: ", "))")
      return
    }
    if model.allowedContentTypes != items { model.allowedContentTypes = items }
  }

  private weak var compositionParent: OneNativeCompositionParent?
  public func compositionContent() -> AnyView { AnyView(FileImporterContent(model: model)) }
  // composed, activation follows the parent: a subtree mounted before root attachment
  // stays silent until the root attaches.
  public func composeInto(_ parent: OneNativeCompositionParent) {
    controller?.detach(); controller = nil
    compositionParent = parent
    bindCallbacks()
    model.active = parent.compositionActive
  }
  public func decompose() { compositionParent = nil; model.active = false }
  public func propagateActive(_ active: Bool) { model.active = active }
  public override func didMoveToWindow() { super.didMoveToWindow(); updateHost() }
  public override func layoutSubviews() { super.layoutSubviews(); updateHost() }
  private func bindCallbacks() {
    model.onSDKEvent = { [weak self] name, value in self?.onSDKEvent?(name, value) }

    model.onChange = { [weak self] value, count, revision in self?.onChange?(value, count, revision) }
    model.onCompletion = { [weak self] type, url, index, count, message, completionCount in self?.onCompletion?(type, url, index, count, message, completionCount) }
  }
  private func updateHost() {
    guard compositionParent == nil else { return }
    model.active = false
    guard window != nil else { controller?.detach(); return }
    if controller == nil {
      bindCallbacks()
      controller = OneNativeHostingController(rootView: OneNativeStandalone(content: FileImporterContent(model: model)))
    }
    controller?.attach(to: self)
    model.active = controller?.isAttached == true
  }
  public func reset() {
    compositionParent = nil
    model.active = false; model.onSDKEvent = nil; model.onChange = nil; model.onCompletion = nil
    controller?.presentedViewController?.dismiss(animated: false)
    controller?.detach(); controller = nil; model = FileImporterModel()
  }
}
private struct FileImporterContent: View {
  @ObservedObject var model: FileImporterModel
  var body: some View {
    FileImporterSurface(model: model)
      .oneNativeAccessibility(model.accessibility)
      .oneNativeStyle(model.swiftStyle, emit: model.emitSDKEvent)
  }
}
// UTType identifiers are an open set, so they travel as strings and resolve here.
// only an empty list means the unrestricted import, UTType.item, the base of
// every pickable type. a non-empty list that resolves to nothing never reaches
// this fallback: the setter rejects it with a failed completion instead.
private func oneNativeContentTypes(_ identifiers: [String]) -> [UTType] {
  let resolved = identifiers.compactMap { UTType($0) }
  return resolved.isEmpty ? [.item] : resolved
}

// the picker hands out security-scoped urls, which stop working once the picker goes
// away and were never readable from JS. each pick is copied under a fresh folder in
// Caches, keeping its filename, and the copy is what the event reports. Caches is the
// system's to purge; nothing here deletes the file. runs on a utility queue; the
// security-scoped access opens and closes on that same thread.
func oneNativeCopyToCaches(_ url: URL) throws -> URL {
  let accessing = url.startAccessingSecurityScopedResource()
  defer { if accessing { url.stopAccessingSecurityScopedResource() } }
  let folder = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
    .appendingPathComponent("one-native-file-importer", isDirectory: true)
    .appendingPathComponent(UUID().uuidString, isDirectory: true)
  try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
  let name = url.lastPathComponent.isEmpty ? "file" : url.lastPathComponent
  let destination = folder.appendingPathComponent(name)
  try FileManager.default.copyItem(at: url, to: destination)
  return destination
}

private struct FileImporterSurface: View {
  @ObservedObject var model: FileImporterModel
  var body: some View {
    Color.clear
      .fileImporter(
        isPresented: Binding(
          get: { model.controlled.value },
          set: { value in model.change(value) }
        ),
        allowedContentTypes: oneNativeContentTypes(model.allowedContentTypes),
        allowsMultipleSelection: model.allowsMultipleSelection
      ) { result in
        switch result {
        case .success(let urls):
          // backing out routes to onCancellation, never here, so an empty
          // success cannot arrive; the guard only satisfies the type.
          guard !urls.isEmpty else { return }
          let count = urls.count
          DispatchQueue.global(qos: .utility).async {
            var outcomes: [(Int, String, String)] = []
            outcomes.reserveCapacity(urls.count)
            for (index, url) in urls.enumerated() {
              do {
                let copy = try oneNativeCopyToCaches(url)
                outcomes.append((index, copy.absoluteString, ""))
              } catch {
                outcomes.append((index, "", error.localizedDescription))
              }
            }
            DispatchQueue.main.async {
              for (index, urlString, message) in outcomes {
                if message.isEmpty {
                  model.completion("success", urlString, Double(index), Double(count), "")
                } else {
                  model.completion("failed", "", Double(index), Double(count), message)
                }
              }
            }
          }
        case .failure(let error):
          model.completion("failed", "", 0, 0, error.localizedDescription)
        }
      } onCancellation: {
        model.completion("cancelled", "", 0, 0, "")
      }
  }
}
