// Copyright 2023-present 650 Industries. All rights reserved.

import AVKit
import ExpoModulesCore

public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
  lazy var playerViewController = OrientationAVPlayerViewController(delegate: self)

  weak var player: VideoPlayer? {
    didSet {
      playerViewController.player = player?.ref
      // Pass through PIP properties to the VideoPlayer
      if let player = player {
        player.allowsPictureInPicture = self.allowPictureInPicture
        player.startsPictureInPictureAutomatically = self.startPictureInPictureAutomatically
      }
      updatePictureInPictureConfiguration()
    }
  }

  #if os(tvOS)
    var wasPlaying: Bool = false
    let startPictureInPictureAutomatically = false
    var isFullscreen: Bool = false
  #else
    var startPictureInPictureAutomatically = false {
      didSet {
        // Pass through to VideoPlayer
        if let player = player {
          player.startsPictureInPictureAutomatically = startPictureInPictureAutomatically
        }
        updatePictureInPictureConfiguration()
      }
    }
  #endif

  var allowPictureInPicture: Bool = false {
    didSet {
      // PiP requires `.playback` audio session category in `.moviePlayback` mode
      VideoManager.shared.setAppropriateAudioSessionOrWarn()
      // Pass through to VideoPlayer
      if let player = player {
        player.allowsPictureInPicture = allowPictureInPicture
      }
      updatePictureInPictureConfiguration()
    }
  }

  let onPictureInPictureStart = EventDispatcher()
  let onPictureInPictureStop = EventDispatcher()
  let onFullscreenEnter = EventDispatcher()
  let onFullscreenExit = EventDispatcher()
  let onFirstFrameRender = EventDispatcher()

  var firstFrameObserver: NSKeyValueObservation?

  public override var bounds: CGRect {
    didSet {
      playerViewController.view.frame = self.bounds
    }
  }

  public required init(appContext: AppContext? = nil) {
    super.init(appContext: appContext)

    VideoManager.shared.register(videoView: self)

    clipsToBounds = true
    playerViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    playerViewController.view.backgroundColor = .clear
    // Now playing is managed by the `NowPlayingManager`
    #if !os(tvOS)
      playerViewController.updatesNowPlayingInfoCenter = false
    #endif

    addFirstFrameObserver()
    addSubview(playerViewController.view)
  }

  deinit {
    VideoManager.shared.unregister(videoView: self)
    removeFirstFrameObserver()
  }

  func updatePictureInPictureConfiguration() {
    #if !os(tvOS)
    print("🔧 [VideoView] updatePictureInPictureConfiguration called")
    
    // If player has PIP callbacks, disable automatic PIP on the traditional controller
    // The persistent controller will handle all PIP activities
    if let player = self.player {
      let callbacks = player.getPipRestoreCallbacks()
      print("🔧 [VideoView] Player callbacks check: \(callbacks != nil ? "FOUND" : "NOT_FOUND")")
      player.emit(event: "pipDebug", arguments: ["message": "VideoView updateConfig called - callbacks: \(callbacks != nil ? "FOUND" : "NOT_FOUND")"])
      
      if callbacks != nil {
        print("🔧 [VideoView] PIP callbacks detected - configuring for persistent controller")
        player.emit(event: "pipDebug", arguments: ["message": "VideoView PIP callbacks detected - configuring for persistent controller"])
        
        // Keep PIP enabled but disable automatic activation on traditional controller
        playerViewController.allowsPictureInPicturePlayback = allowPictureInPicture
        playerViewController.canStartPictureInPictureAutomaticallyFromInline = false
        
        print("🔧 [VideoView] Traditional controller: allowsPictureInPicturePlayback=\(allowPictureInPicture), canStartAutomatically=false")
        player.emit(event: "pipDebug", arguments: ["message": "VideoView traditional controller configured: allowsPIP=\(allowPictureInPicture), autoStart=false"])
      } else {
        // No callbacks set, use traditional configuration
        print("🔧 [VideoView] No PIP callbacks - using traditional controller configuration")
        player.emit(event: "pipDebug", arguments: ["message": "VideoView using traditional controller configuration"])
        
        playerViewController.allowsPictureInPicturePlayback = allowPictureInPicture
        playerViewController.canStartPictureInPictureAutomaticallyFromInline = startPictureInPictureAutomatically
        
        print("🔧 [VideoView] Traditional controller: allowsPictureInPicturePlayback=\(allowPictureInPicture), canStartAutomatically=\(startPictureInPictureAutomatically)")
      }
    } else {
      print("🔧 [VideoView] No player found - skipping configuration")
    }
    #endif
  }

  func enterFullscreen() {
    let tvOSFallback = {
      #if os(tvOS)
        // For TV, save the currently playing state,
        // remove the view controller from its superview,
        // and present the view controller normally
        self.wasPlaying = self.player?.isPlaying == true
        self.playerViewController.view.removeFromSuperview()
        self.reactViewController().present(self.playerViewController, animated: true)
        self.onFullscreenEnter()
        self.isFullscreen = true
      #endif
    }
    playerViewController.enterFullscreen(selectorUnsupportedFallback: tvOSFallback)
  }

  func exitFullscreen() {
    playerViewController.exitFullscreen()
    #if os(tvOS)
      self.isFullscreen = false
    #endif
  }

  func startPictureInPicture() throws {
    print("🟡 [VideoView] startPictureInPicture called")
    if let player = self.player {
      player.emit(event: "pipDebug", arguments: ["message": "VideoView startPictureInPicture - checking for persistent controller"])
      
      // If player has PIP callbacks set, use the persistent controller
      if player.getPipRestoreCallbacks() != nil {
        player.emit(event: "pipDebug", arguments: ["message": "VideoView using persistent PIP controller"])
        try player.startPersistentPictureInPicture()
      } else {
        // No callbacks set, use the traditional VideoView controller
        player.emit(event: "pipDebug", arguments: ["message": "VideoView using traditional playerViewController PIP"])
        try playerViewController.startPictureInPicture()
      }
    } else {
      // Fallback to the old method if no player
      try playerViewController.startPictureInPicture()
    }
  }

  func stopPictureInPicture() {
    print("🟡 [VideoView] stopPictureInPicture called")
    if let player = self.player {
      player.emit(event: "pipDebug", arguments: ["message": "VideoView stopPictureInPicture - checking for persistent controller"])
      
      // If player has PIP callbacks set, use the persistent controller
      if player.getPipRestoreCallbacks() != nil {
        player.emit(event: "pipDebug", arguments: ["message": "VideoView using persistent PIP controller to stop"])
        player.stopPersistentPictureInPicture()
      } else {
        // No callbacks set, use the traditional VideoView controller
        player.emit(event: "pipDebug", arguments: ["message": "VideoView using traditional playerViewController to stop"])
        playerViewController.stopPictureInPicture()
      }
    } else {
      // Fallback to the old method if no player
      playerViewController.stopPictureInPicture()
    }
  }

  func refreshPipConfiguration() {
    print("🔄 [VideoView] refreshPipConfiguration called")
    updatePictureInPictureConfiguration()
  }

  // MARK: - AVPlayerViewControllerDelegate

  #if os(tvOS)
    // TV actually presents the playerViewController, so it implements the view controller
    // dismissal delegate methods
    public func playerViewControllerWillBeginDismissalTransition(
      _ playerViewController: AVPlayerViewController
    ) {
      // Start an appearance transition
      self.playerViewController.beginAppearanceTransition(true, animated: true)
    }

    public func playerViewControllerDidEndDismissalTransition(
      _ playerViewController: AVPlayerViewController
    ) {
      self.onFullscreenExit()
      self.isFullscreen = false
      // Reset the bounds of the view controller and add it back to our view
      self.playerViewController.view.frame = self.bounds
      addSubview(self.playerViewController.view)
      // End the appearance transition
      self.playerViewController.endAppearanceTransition()
      // Ensure playing state is preserved
      if wasPlaying {
        self.player?.ref.play()
      } else {
        self.player?.ref.pause()
      }
    }
  #endif

  #if !os(tvOS)
    public func playerViewController(
      _ playerViewController: AVPlayerViewController,
      willBeginFullScreenPresentationWithAnimationCoordinator coordinator:
        UIViewControllerTransitionCoordinator
    ) {
      onFullscreenEnter()
    }

    public func playerViewController(
      _ playerViewController: AVPlayerViewController,
      willEndFullScreenPresentationWithAnimationCoordinator coordinator:
        UIViewControllerTransitionCoordinator
    ) {
      // Platform's behavior is to pause the player when exiting the fullscreen mode.
      // It seems better to continue playing, so we resume the player once the dismissing animation finishes.
      let wasPlaying = player?.isPlaying ?? false

      coordinator.animate(alongsideTransition: nil) { context in
        if !context.isCancelled && wasPlaying {
          DispatchQueue.main.async {
            self.player?.ref.play()
          }
        }

        if !context.isCancelled {
          self.onFullscreenExit()
        }
      }
    }
  #endif

  public func playerViewControllerDidStartPictureInPicture(
    _ playerViewController: AVPlayerViewController
  ) {
    print("🟢 [VideoView] PIP did start")
    if let player = self.player {
      // Only emit events if NOT using persistent controller (to avoid duplicates)
      if player.getPipRestoreCallbacks() == nil {
        player.emit(event: "pipDebug", arguments: ["message": "VideoView PIP did start - traditional controller"])
      }
    }
    onPictureInPictureStart()
  }

  public func playerViewControllerDidStopPictureInPicture(
    _ playerViewController: AVPlayerViewController
  ) {
    print("🔴 [VideoView] PIP did stop")
    if let player = self.player {
      // Only emit events if NOT using persistent controller (to avoid duplicates)
      if player.getPipRestoreCallbacks() == nil {
        player.emit(event: "pipDebug", arguments: ["message": "VideoView PIP did stop - traditional controller"])
      }
    }
    onPictureInPictureStop()
  }

  public func playerViewController(
    _ playerViewController: AVPlayerViewController,
    restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (
      Bool
    ) -> Void
  ) {
    // This is the key method for PIP restoration!
    // Called when user taps the PIP window to restore the full interface
    
    print("🔴 [VideoView] PIP restoration delegate called!")
    
    guard let player = self.player else {
      print("❌ [VideoView] No player found, failing restoration")
      completionHandler(false)
      return
    }
    
    // Check if using persistent controller - if so, don't handle restoration here
    if player.getPipRestoreCallbacks() != nil {
      print("⚠️ [VideoView] Player has PIP callbacks - persistent controller should handle restoration, skipping VideoView delegate")
      player.emit(event: "pipDebug", arguments: ["message": "VideoView restoration delegate skipped - persistent controller active"])
      // Don't call completionHandler here - let the persistent controller handle it
      return
    }
    
    // Traditional restoration flow (no callbacks set)
    print("✅ [VideoView] Using traditional restoration flow")
    player.emit(event: "pipDebug", arguments: ["message": "VideoView using traditional restoration flow"])
    
    // For traditional flow, just allow restoration immediately
    completionHandler(true)
  }

  public override func didMoveToWindow() {
    // TV is doing a normal view controller present, so we should not execute
    // this code
    #if !os(tvOS)
      playerViewController.beginAppearanceTransition(self.window != nil, animated: true)
    #endif
  }

  public override func safeAreaInsetsDidChange() {
    super.safeAreaInsetsDidChange()
    // This is the only way that I (@behenate) found to force re-calculation of the safe-area insets for native controls
    playerViewController.view.removeFromSuperview()
    addSubview(playerViewController.view)
  }

  private func addFirstFrameObserver() {
    firstFrameObserver = playerViewController.observe(
      \.isReadyForDisplay,
      changeHandler: { [weak self] playerViewController, _ in
        if playerViewController.isReadyForDisplay {
          self?.onFirstFrameRender()
        }
      })
  }
  private func removeFirstFrameObserver() {
    firstFrameObserver?.invalidate()
    firstFrameObserver = nil
  }
}
