// 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 AVKit

private final class VideoPlayerModel: ObservableObject {
  @Published var url: String = ""
  @Published var autoplay: Bool = false
  @Published var command: String = ""
  @Published var commandRevision: Double = 0
  @Published var seekToMs: Double = 0
  @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 onPlaybackStatus: ((String, Double, Double, Int) -> Void)?
  private var playbackStatusCount = 0
  func playbackStatus(_ state: String, _ positionMs: Double, _ durationMs: Double) {
    guard active else { return }
    playbackStatusCount += 1
    onPlaybackStatus?(state, positionMs, durationMs, playbackStatusCount)
  }
}
@objcMembers public final class OneNativeVideoPlayerView: UIView, OneNativeComposable {
  public var onSDKEvent: ((String, String) -> Void)?

  public var onPlaybackStatus: ((String, Double, Double, Int) -> Void)?
  private var model = VideoPlayerModel()
  private var controller: OneNativeHostingController<OneNativeStandalone<VideoPlayerContent>>?
  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(_ url: String, autoplay: Bool, command: String, commandRevision: Double, seekToMs: Double) {
    if model.url != url { model.url = url }
    if model.autoplay != autoplay { model.autoplay = autoplay }
    if model.command != command { model.command = command }
    if model.commandRevision != commandRevision { model.commandRevision = commandRevision }
    if model.seekToMs != seekToMs { model.seekToMs = seekToMs }
  }


  private weak var compositionParent: OneNativeCompositionParent?
  public func compositionContent() -> AnyView { AnyView(VideoPlayerContent(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.onPlaybackStatus = { [weak self] state, positionMs, durationMs, playbackStatusCount in self?.onPlaybackStatus?(state, positionMs, durationMs, playbackStatusCount) }
  }
  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: VideoPlayerContent(model: model)))
    }
    controller?.attach(to: self)
    model.active = controller?.isAttached == true
  }
  public func reset() {
    compositionParent = nil
    model.active = false; model.onSDKEvent = nil; model.onPlaybackStatus = nil
    controller?.detach(); controller = nil; model = VideoPlayerModel()
  }
}
private struct VideoPlayerContent: View {
  @ObservedObject var model: VideoPlayerModel
  var body: some View {
    VideoPlayerSurface(model: model)
      .oneNativeAccessibility(model.accessibility)
      .oneNativeStyle(model.swiftStyle, emit: model.emitSDKEvent)
  }
}
private final class OneNativeVideoSession: ObservableObject {
  @Published private(set) var player: AVPlayer?
  private var loaded: String?
  private var timeObserver: Any?
  private var controlObservation: NSKeyValueObservation?
  private var itemObservation: NSKeyValueObservation?
  private var endObserver: NSObjectProtocol?
  private var ended = false
  private var appliedRevision: Double = 0
  var report: ((String, Double, Double) -> Void)?

  deinit { clear() }

  // a player owns its position. changing unrelated React props must not recreate it.
  func load(_ url: String, autoplay: Bool) {
    guard loaded != url else { return }
    clear()
    loaded = url
    guard let parsed = URL(string: url) else { return }
    let next = AVPlayer(url: parsed)
    player = next
    controlObservation = next.observe(\.timeControlStatus, options: [.new]) { [weak self] _, _ in
      DispatchQueue.main.async { self?.emit() }
    }
    if let item = next.currentItem {
      itemObservation = item.observe(\.status, options: [.new]) { [weak self] _, _ in
        DispatchQueue.main.async { self?.emit() }
      }
      endObserver = NotificationCenter.default.addObserver(
        forName: .AVPlayerItemDidPlayToEndTime, object: item, queue: .main
      ) { [weak self] _ in
        self?.ended = true
        self?.emit()
      }
    }
    timeObserver = next.addPeriodicTimeObserver(
      forInterval: CMTime(seconds: 0.5, preferredTimescale: 600), queue: .main
    ) { [weak self] _ in
      guard let self, let player = self.player else { return }
      if self.ended, player.currentTime().seconds + 0.5 < (player.currentItem?.duration.seconds ?? 0) {
        self.ended = false
      }
      self.emit()
    }
    if autoplay { next.play() }
    emit()
  }

  func apply(_ command: String, revision: Double, seekToMs: Double) {
    guard revision > appliedRevision else { return }
    guard let player else { return }
    appliedRevision = revision
    switch command {
    case "play":
      if ended { player.seek(to: .zero); ended = false }
      player.play()
    case "pause": player.pause()
    case "seek":
      ended = false
      player.seek(to: CMTime(seconds: seekToMs / 1000, preferredTimescale: 600)) { [weak self] _ in
        DispatchQueue.main.async { self?.emit() }
      }
    default: return
    }
    emit()
  }

  private func emit() {
    guard let player else { return }
    let seconds = player.currentTime().seconds
    let duration = player.currentItem?.duration.seconds ?? 0
    let state: String
    if player.currentItem?.status == .failed { state = "failed" }
    else if ended { state = "ended" }
    else if player.timeControlStatus == .playing { state = "playing" }
    else if player.timeControlStatus == .waitingToPlayAtSpecifiedRate { state = "loading" }
    else { state = "paused" }
    report?(state, seconds.isFinite ? max(0, seconds * 1000) : 0,
            duration.isFinite ? max(0, duration * 1000) : 0)
  }

  private func clear() {
    controlObservation = nil
    itemObservation = nil
    if let endObserver { NotificationCenter.default.removeObserver(endObserver) }
    endObserver = nil
    if let player, let timeObserver { player.removeTimeObserver(timeObserver) }
    timeObserver = nil
    player?.pause()
    player = nil
    ended = false
  }
}

private struct VideoPlayerSurface: View {
  @ObservedObject var model: VideoPlayerModel
  @StateObject private var session = OneNativeVideoSession()
  var body: some View {
    VideoPlayer(player: session.player)
      .onAppear {
        session.report = { [weak model = model] state, position, duration in
          model?.playbackStatus(state, position, duration)
        }
        session.load(model.url, autoplay: model.autoplay)
        if model.commandRevision > 0 {
          session.apply(model.command, revision: model.commandRevision, seekToMs: model.seekToMs)
        }
      }
      .onChange(of: model.url) { session.load(model.url, autoplay: model.autoplay) }
      .onChange(of: model.commandRevision) {
        if model.commandRevision > 0 {
          session.apply(model.command, revision: model.commandRevision, seekToMs: model.seekToMs)
        }
      }
  }
}
