import Cocoa
import FlutterMacOS

/// Flutter plugin that bridges MPV player to Dart via method and event channels
class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPluginShared {

  // MARK: - Properties

  private var playerCore: MpvPlayerCore?
  var eventSink: FlutterEventSink?
  private weak var registrar: FlutterPluginRegistrar?
  var nameToId: [String: Int] = [:]

  // MpvPluginShared conformance
  var coreBase: MpvPlayerCoreBase? { playerCore }
  func setPlayerVisible(_ visible: Bool, restoreOnWindowVisible: Bool) {
    playerCore?.setVisible(visible, restoreOnWindowVisible: restoreOnWindowVisible)
  }
  func updatePlayerFrame() { playerCore?.updateFrame() }

  // PiP
  private var pipController: MpvPipController?
  private var pipChannel: FlutterMethodChannel?
  private var autoPipEnabled = false
  private var enteredPipViaAuto = false

  // MARK: - FlutterPlugin Registration

  static func register(with registrar: FlutterPluginRegistrar) {
    // Method channel for commands
    let methodChannel = FlutterMethodChannel(
      name: "com.plezy/mpv_player",
      binaryMessenger: registrar.messenger
    )

    // Event channel for state updates
    let eventChannel = FlutterEventChannel(
      name: "com.plezy/mpv_player/events",
      binaryMessenger: registrar.messenger
    )

    let pipChannel = FlutterMethodChannel(
      name: "com.plezy/pip",
      binaryMessenger: registrar.messenger
    )

    let instance = MpvPlayerPlugin()
    instance.registrar = registrar
    instance.pipChannel = pipChannel

    registrar.addMethodCallDelegate(instance, channel: methodChannel)
    eventChannel.setStreamHandler(instance)
    pipChannel.setMethodCallHandler(instance.handlePipCall)

    print("[MpvPlayerPlugin] Registered with Flutter")
  }

  // MARK: - FlutterStreamHandler

  func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink)
    -> FlutterError?
  {
    self.eventSink = events
    print("[MpvPlayerPlugin] Event stream connected")
    return nil
  }

  func onCancel(withArguments arguments: Any?) -> FlutterError? {
    self.eventSink = nil
    print("[MpvPlayerPlugin] Event stream disconnected")
    return nil
  }

  // MARK: - FlutterPlugin Method Handler

  func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    case "initialize":
      handleInitialize(result: result)

    case "dispose":
      handleDispose(result: result)

    case "setProperty":
      handleSetProperty(call: call, result: result)

    case "getProperty":
      handleGetProperty(call: call, result: result)

    case "observeProperty":
      handleObserveProperty(call: call, result: result)

    case "command":
      handleCommand(call: call, result: result)

    case "setVisible":
      handleSetVisible(call: call, result: result)

    case "isInitialized":
      result(playerCore?.isInitialized ?? false)

    case "updateFrame":
      handleUpdateFrame(result: result)

    case "setLogLevel":
      handleSetLogLevel(call: call, result: result)

    default:
      result(FlutterMethodNotImplemented)
    }
  }

  // MARK: - PiP

  private func ensurePipController() -> MpvPipController {
    if let existing = pipController { return existing }
    let controller = MpvPipController()
    controller.delegate = self
    pipController = controller
    return controller
  }

  private func handlePipCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    case "isSupported":
      result(MpvPipController.isSupported)
    case "enter":
      enterPip(manual: true, result: result)
    case "exit":
      pipController?.stopPip()
      result(nil)
    case "setAutoPipReady":
      if let args = call.arguments as? [String: Any], let ready = args["ready"] as? Bool {
        autoPipEnabled = ready
        let pip = ensurePipController()
        pip.setAutoStart(ready)
        if ready {
          // Observe app resigning active to auto-enter PiP
          NotificationCenter.default.removeObserver(
            self, name: NSApplication.didResignActiveNotification, object: nil)
          NotificationCenter.default.addObserver(
            self, selector: #selector(appDidResignActive),
            name: NSApplication.didResignActiveNotification, object: nil)
          // Observe app becoming active to auto-exit PiP
          NotificationCenter.default.removeObserver(
            self, name: NSApplication.didBecomeActiveNotification, object: nil)
          NotificationCenter.default.addObserver(
            self, selector: #selector(appDidBecomeActive),
            name: NSApplication.didBecomeActiveNotification, object: nil)
        } else {
          NotificationCenter.default.removeObserver(
            self, name: NSApplication.didResignActiveNotification, object: nil)
          NotificationCenter.default.removeObserver(
            self, name: NSApplication.didBecomeActiveNotification, object: nil)
        }
      }
      result(nil)
    default:
      result(FlutterMethodNotImplemented)
    }
  }

  /// Enter PiP by moving the Metal rendering layer to a PiP window.
  /// No VO switching — mpv keeps rendering to the same layer.
  private func enterPip(manual: Bool, result: FlutterResult? = nil) {
    guard let playerCore = playerCore else {
      result?([
        "success": false, "errorCode": "failed", "errorMessage": "Player not initialized",
      ])
      return
    }
    guard let metalLayer = playerCore.metalLayer else {
      result?(["success": false, "errorCode": "failed", "errorMessage": "No Metal layer"])
      return
    }
    guard let window = findFlutterWindow()?.0 else {
      result?(["success": false, "errorCode": "failed", "errorMessage": "No window"])
      return
    }

    let pip = ensurePipController()
    guard !pip.isActive else {
      result?(["success": false, "errorCode": "failed", "errorMessage": "PiP already active"])
      return
    }

    // Get video dimensions for aspect ratio
    var aspectRatio = NSSize(width: 16, height: 9)  // default
    if let videoSize = playerCore.videoSize {
      aspectRatio = NSSize(width: videoSize.width, height: videoSize.height)
    }

    enteredPipViaAuto = !manual
    playerCore.isPipActive = true

    pip.startPip(metalLayer: metalLayer, window: window, aspectRatio: aspectRatio)
    pipChannel?.invokeMethod("onPipChanged", arguments: true)
    result?(["success": true])
  }

  /// App resigned active — auto-enter PiP if enabled and playing
  @objc private func appDidResignActive() {
    guard autoPipEnabled,
      let pc = playerCore,
      !pc.isPipActive,
      !pc.isPaused,
      pipController?.autoPipEnabled == true
    else { return }
    print("[MpvPlayerPlugin] Auto-PiP: app resigned active, entering PiP")
    enterPip(manual: false)
  }

  /// App became active — auto-exit PiP if it was entered automatically
  @objc private func appDidBecomeActive() {
    guard enteredPipViaAuto, let pip = pipController, pip.isActive else { return }
    print("[MpvPlayerPlugin] Auto-PiP: app became active, exiting PiP")
    pip.stopPip()
  }

  // MARK: - Platform-Specific Method Handlers

  private func handleInitialize(result: @escaping FlutterResult) {
    DispatchQueue.main.async { [weak self] in
      guard let self = self else {
        result(FlutterError(code: "ERROR", message: "Plugin deallocated", details: nil))
        return
      }

      // Check if already initialized
      if self.playerCore?.isInitialized == true {
        print("[MpvPlayerPlugin] Already initialized")
        result(true)
        return
      }

      // Find the Flutter window
      guard let (window, _, _) = self.findFlutterWindow() else {
        print("[MpvPlayerPlugin] Failed to find Flutter window")
        result(
          FlutterError(
            code: "NO_WINDOW", message: "Could not find Flutter window", details: nil))
        return
      }

      // Create and initialize player core
      let core = MpvPlayerCore()
      core.delegate = self

      guard core.initialize(in: window) else {
        print("[MpvPlayerPlugin] Failed to initialize MPV")
        result(
          FlutterError(
            code: "MPV_INIT_FAILED", message: "Failed to initialize MPV", details: nil))
        return
      }

      self.playerCore = core

      // Start hidden
      core.setVisible(false)

      print("[MpvPlayerPlugin] Initialized successfully")
      result(true)
    }
  }

  private func handleDispose(result: @escaping FlutterResult) {
    DispatchQueue.main.async { [weak self] in
      guard let self = self else { result(nil); return }
      if let pip = self.pipController, pip.isActive {
        pip.stopPip()
        pip.detachLayer()
      }
      self.pipController = nil
      self.autoPipEnabled = false
      NotificationCenter.default.removeObserver(
        self, name: NSApplication.didResignActiveNotification, object: nil)
      NotificationCenter.default.removeObserver(
        self, name: NSApplication.didBecomeActiveNotification, object: nil)
      self.playerCore?.dispose()
      self.playerCore = nil
      print("[MpvPlayerPlugin] Disposed")
      result(nil)
    }
  }

  private func handleSetProperty(call: FlutterMethodCall, result: @escaping FlutterResult) {
    guard let args = call.arguments as? [String: Any],
      let name = args["name"] as? String,
      let value = args["value"] as? String
    else {
      result(
        FlutterError(
          code: "INVALID_ARGS", message: "Missing 'name' or 'value' argument",
          details: nil))
      return
    }

    guard let core = playerCore else {
      result(nil)
      return
    }

    core.setPropertyAsync(name, value: value) { [weak self] _ in
      if name == "pause" {
        let isPlaying = value == "no"
        self?.pipController?.setPlaying(isPlaying)
        core.setPaused(!isPlaying)
      }
      result(nil)
    }
  }

  // MARK: - Helpers

  private func findFlutterWindow() -> (NSWindow, NSView, NSView)? {
    for window in NSApplication.shared.windows {
      if window is MainFlutterWindow,
        let contentView = window.contentView,
        let contentVC = window.contentViewController
      {
        let flutterView = contentVC.view
        return (window, contentView, flutterView)
      }
    }

    // Fallback
    for window in NSApplication.shared.windows {
      if let contentView = window.contentView,
        let contentVC = window.contentViewController
      {
        let flutterView = contentVC.view
        return (window, contentView, flutterView)
      }
    }

    return nil
  }
}

// MARK: - MpvPipDelegate

extension MpvPlayerPlugin: MpvPipDelegate {

  func pipWillStart() {
    print("[MpvPlayerPlugin] PiP will start")
  }

  func pipDidStart() {
    print("[MpvPlayerPlugin] PiP did start")
  }

  func pipDidStop(restored: Bool) {
    print("[MpvPlayerPlugin] PiP did stop (restored: \(restored))")
    playerCore?.isPipActive = false
    enteredPipViaAuto = false

    // Detach the Metal layer from the PiP wrapper view
    pipController?.detachLayer()

    // Re-attach the Metal layer to the main window
    playerCore?.reattachMetalLayer()

    // Force a redraw if paused (prevents black frame after PiP exit)
    if playerCore?.isPaused == true {
      playerCore?.forceDraw()
    }

    pipChannel?.invokeMethod("onPipChanged", arguments: false)
  }

  func pipSetPlaying(_ playing: Bool) {
    guard let playerCore else { return }
    playerCore.setPropertyAsync("pause", value: playing ? "no" : "yes") { [weak self] _ in
      self?.pipController?.setPlaying(playing)
      playerCore.setPaused(!playing)
    }
  }

  var isPipPlaying: Bool { !(playerCore?.isPaused ?? true) }
}
