import UIKit
import UniformTypeIdentifiers
import AVFoundation

// Datenmodell muss byte-für-byte zu dem passen, was
// ReceiveSharingIntentPlugin auf der Host-App-Seite decodiert.
private enum SharedMediaType: String, Codable {
  case image, video, text, file, url
}

private struct SharedMediaFile: Codable {
  let path: String
  let mimeType: String?
  let thumbnail: String?
  let duration: Double?
  let message: String?
  let type: SharedMediaType
}

final class ShareViewController: UIViewController {
  // Schlüssel sind die, die das Plugin liest.
  private let userDefaultsKey = "ShareKey"
  private let userDefaultsMessageKey = "ShareMessageKey"
  private let urlSchemePrefix = "ShareMedia"

  private var appGroupId = ""
  private var hostBundleId = ""

  override func viewDidLoad() {
    super.viewDidLoad()
    resolveIds()
    setupPlaceholderUI()
  }

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    processAttachments()
  }

  private func resolveIds() {
    let extBundleId = Bundle.main.bundleIdentifier ?? ""
    if let lastDot = extBundleId.lastIndex(of: ".") {
      hostBundleId = String(extBundleId[..<lastDot])
    }
    let custom = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String
    if let custom, !custom.isEmpty, !custom.contains("$(") {
      appGroupId = custom
    } else {
      appGroupId = "group.\(hostBundleId)"
    }
  }

  private func setupPlaceholderUI() {
    view.backgroundColor = .systemBackground
    let spinner = UIActivityIndicatorView(style: .medium)
    spinner.startAnimating()
    let label = UILabel()
    label.text = "Wird geteilt …"
    label.font = .preferredFont(forTextStyle: .footnote)
    label.textColor = .secondaryLabel
    let stack = UIStackView(arrangedSubviews: [spinner, label])
    stack.axis = .vertical
    stack.alignment = .center
    stack.spacing = 8
    stack.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(stack)
    NSLayoutConstraint.activate([
      stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    ])
  }

  private func processAttachments() {
    let attachments: [NSItemProvider] = (extensionContext?.inputItems ?? [])
      .compactMap { $0 as? NSExtensionItem }
      .flatMap { $0.attachments ?? [] }
    guard !attachments.isEmpty else {
      finish()
      return
    }
    let group = DispatchGroup()
    var collected: [SharedMediaFile] = []
    let lock = NSLock()
    for provider in attachments {
      group.enter()
      handle(provider: provider) { file in
        if let f = file {
          lock.lock(); collected.append(f); lock.unlock()
        }
        group.leave()
      }
    }
    group.notify(queue: .main) { [weak self] in
      self?.saveAndRedirect(items: collected)
    }
  }

  // Reihenfolge entspricht SharedMediaType.allCases im Plugin —
  // spezifische Typen (image, video) vor generischen (file, url).
  private func handle(provider: NSItemProvider,
                      completion: @escaping (SharedMediaFile?) -> Void) {
    let order: [(String, SharedMediaType)] = [
      (UTType.image.identifier, .image),
      (UTType.movie.identifier, .video),
      (UTType.text.identifier, .text),
      (UTType.fileURL.identifier, .file),
      (UTType.url.identifier, .url),
      (UTType.data.identifier, .file),
    ]
    for (typeId, kind) in order {
      if provider.hasItemConformingToTypeIdentifier(typeId) {
        provider.loadItem(forTypeIdentifier: typeId) { [weak self] data, error in
          guard let self else { completion(nil); return }
          if error != nil { completion(nil); return }
          completion(self.toSharedFile(data: data, kind: kind))
        }
        return
      }
    }
    completion(nil)
  }

  private func toSharedFile(data: Any?, kind: SharedMediaType) -> SharedMediaFile? {
    switch kind {
    case .text:
      guard let s = data as? String else { return nil }
      return SharedMediaFile(path: s, mimeType: "text/plain",
                             thumbnail: nil, duration: nil, message: nil, type: .text)
    case .url:
      guard let u = data as? URL else { return nil }
      return SharedMediaFile(path: u.absoluteString, mimeType: nil,
                             thumbnail: nil, duration: nil, message: nil, type: .url)
    case .image:
      if let u = data as? URL, let dst = copyIntoAppGroup(src: u) {
        return SharedMediaFile(path: pathString(for: dst), mimeType: mime(for: u),
                               thumbnail: nil, duration: nil, message: nil, type: .image)
      }
      if let img = data as? UIImage, let dst = writePng(image: img) {
        return SharedMediaFile(path: pathString(for: dst), mimeType: "image/png",
                               thumbnail: nil, duration: nil, message: nil, type: .image)
      }
      return nil
    case .video:
      guard let u = data as? URL, let dst = copyIntoAppGroup(src: u) else { return nil }
      return SharedMediaFile(path: pathString(for: dst), mimeType: mime(for: u),
                             thumbnail: nil, duration: videoDurationMs(url: u),
                             message: nil, type: .video)
    case .file:
      guard let u = data as? URL, let dst = copyIntoAppGroup(src: u) else { return nil }
      return SharedMediaFile(path: pathString(for: dst), mimeType: mime(for: u),
                             thumbnail: nil, duration: nil, message: nil, type: .file)
    }
  }

  private func copyIntoAppGroup(src: URL) -> URL? {
    guard let container = FileManager.default
      .containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
      return nil
    }
    let name = src.lastPathComponent.isEmpty ? UUID().uuidString : src.lastPathComponent
    let dst = container.appendingPathComponent(name)
    do {
      if FileManager.default.fileExists(atPath: dst.path) {
        try FileManager.default.removeItem(at: dst)
      }
      try FileManager.default.copyItem(at: src, to: dst)
      return dst
    } catch {
      return nil
    }
  }

  private func writePng(image: UIImage) -> URL? {
    guard let container = FileManager.default
            .containerURL(forSecurityApplicationGroupIdentifier: appGroupId),
          let data = image.pngData() else { return nil }
    let dst = container.appendingPathComponent("\(UUID().uuidString).png")
    return (try? data.write(to: dst)) != nil ? dst : nil
  }

  // Das Plugin macht `path.replacingOccurrences(of: "file://", with: "")`,
  // also liefern wir absoluteString mit Prozent-Decoding — selbes Format wie
  // im Original-RSIShareViewController.
  private func pathString(for url: URL) -> String {
    url.absoluteString.removingPercentEncoding ?? url.absoluteString
  }

  private func mime(for url: URL) -> String? {
    UTType(filenameExtension: url.pathExtension)?.preferredMIMEType
  }

  private func videoDurationMs(url: URL) -> Double {
    (CMTimeGetSeconds(AVURLAsset(url: url).duration) * 1000).rounded()
  }

  private func saveAndRedirect(items: [SharedMediaFile]) {
    guard !items.isEmpty else {
      finish()
      return
    }
    let defaults = UserDefaults(suiteName: appGroupId)
    guard let encoded = try? JSONEncoder().encode(items) else {
      finish()
      return
    }
    defaults?.set(encoded, forKey: userDefaultsKey)
    defaults?.removeObject(forKey: userDefaultsMessageKey)

    let urlStr = "\(urlSchemePrefix)-\(hostBundleId):share"
    guard let url = URL(string: urlStr) else {
      finish()
      return
    }

    // Apple-DTS says Share Extensions are not officially allowed to open
    // URLs. Both `extensionContext.open` and the responder-chain trick
    // succeed in some iOS versions and fail silently in others, so we fire
    // both in parallel and let whichever Apple is honouring this release
    // win the race.
    extensionContext?.open(url, completionHandler: nil)
    var responder: UIResponder? = self
    while responder != nil {
      if let app = responder as? UIApplication {
        app.open(url, options: [:], completionHandler: nil)
        break
      }
      responder = responder?.next
    }

    // Brief window so the open request reaches the system before we tear
    // the extension down.
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
      self?.finish()
    }
  }

  private func finish() {
    extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
  }
}
