#include "mpv_plugin.h"

#include "mpv_container.h"
#include "mpv_core.h"

static flutter::EncodableMap DisplayModeToMap(const mpv::DisplayMode& mode) {
  flutter::EncodableMap m;
  m[flutter::EncodableValue("width")] = flutter::EncodableValue(static_cast<int32_t>(mode.width));
  m[flutter::EncodableValue("height")] = flutter::EncodableValue(static_cast<int32_t>(mode.height));
  m[flutter::EncodableValue("refreshRate")] = flutter::EncodableValue(static_cast<int32_t>(mode.refresh_rate));
  return m;
}

void MpvPlayerPluginRegisterWithRegistrar(FlutterDesktopPluginRegistrarRef registrar) {
  mpv::MpvPlayerPlugin::RegisterWithRegistrar(
      flutter::PluginRegistrarManager::GetInstance()->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
}

namespace mpv {

namespace {
constexpr UINT kPlatformTaskMessage = WM_APP + 0x4D50;
}

void MpvPlayerPlugin::RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar) {
  auto plugin = std::make_unique<MpvPlayerPlugin>(registrar);
  registrar->AddPlugin(std::move(plugin));
}

MpvPlayerPlugin::MpvPlayerPlugin(flutter::PluginRegistrarWindows* registrar)
    : registrar_(registrar), platform_thread_id_(::GetCurrentThreadId()) {
  // Create method channel.
  method_channel_ = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
      registrar->messenger(), "com.plezy/mpv_player", &flutter::StandardMethodCodec::GetInstance());

  method_channel_->SetMethodCallHandler(
      [this](const auto& call, auto result) { HandleMethodCall(call, std::move(result)); });

  // Create event channel.
  event_channel_ = std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(
      registrar->messenger(), "com.plezy/mpv_player/events", &flutter::StandardMethodCodec::GetInstance());

  auto handler = std::make_unique<flutter::StreamHandlerFunctions<flutter::EncodableValue>>(
      [this](
          const flutter::EncodableValue* arguments,
          std::unique_ptr<flutter::EventSink<flutter::EncodableValue>>&& events)
          -> std::unique_ptr<flutter::StreamHandlerError<flutter::EncodableValue>> {
        event_sink_ = std::move(events);
        return nullptr;
      },
      [this](const flutter::EncodableValue* arguments)
          -> std::unique_ptr<flutter::StreamHandlerError<flutter::EncodableValue>> {
        event_sink_ = nullptr;
        return nullptr;
      });

  event_channel_->SetStreamHandler(std::move(handler));
}

MpvPlayerPlugin::~MpvPlayerPlugin() {
  DrainPlatformTasks();

  // Unregister window proc delegate.
  if (proc_id_) {
    registrar_->UnregisterTopLevelWindowProcDelegate(proc_id_.value());
    proc_id_ = std::nullopt;
  }
}

HWND MpvPlayerPlugin::GetChildWindow() { return registrar_->GetView()->GetNativeWindow(); }

HWND MpvPlayerPlugin::GetWindow() { return ::GetAncestor(GetChildWindow(), GA_ROOT); }

void MpvPlayerPlugin::PostToPlatformThread(std::function<void()> task) {
  if (::GetCurrentThreadId() == platform_thread_id_) {
    task();
    return;
  }

  {
    std::lock_guard<std::mutex> lock(platform_tasks_mutex_);
    platform_tasks_.push(std::move(task));
  }

  if (flutter_window_) {
    ::PostMessage(flutter_window_, kPlatformTaskMessage, 0, 0);
  }
}

void MpvPlayerPlugin::DrainPlatformTasks() {
  std::queue<std::function<void()>> tasks;
  {
    std::lock_guard<std::mutex> lock(platform_tasks_mutex_);
    tasks.swap(platform_tasks_);
  }

  while (!tasks.empty()) {
    tasks.front()();
    tasks.pop();
  }
}

void MpvPlayerPlugin::HandleMethodCall(
    const flutter::MethodCall<flutter::EncodableValue>& method_call,
    std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
  const auto& method = method_call.method_name();

  if (method == "initialize") {
    // Set up MpvCore for z-order management.
    if (proc_id_) {
      registrar_->UnregisterTopLevelWindowProcDelegate(proc_id_.value());
      proc_id_ = std::nullopt;
    }

    HWND flutter_window = GetWindow();
    flutter_window_ = flutter_window;

    MpvCore::SetInstance(std::make_unique<MpvCore>(flutter_window));

    proc_id_ =
        registrar_->RegisterTopLevelWindowProcDelegate([this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
          if (message == kPlatformTaskMessage) {
            DrainPlatformTasks();
            return std::optional<HRESULT>(0);
          }

          auto* core = MpvCore::GetInstance();
          if (core) {
            return core->WindowProc(hwnd, message, wparam, lparam);
          }
          return std::optional<HRESULT>(std::nullopt);
        });

    MpvCore::GetInstance()->EnsureInitialized();

    // Create player - use the container from MpvCore which was set up by EnsureInitialized
    player_ = std::make_unique<MpvPlayer>();
    HWND container = MpvContainer::GetInstance()->handle();

    if (!container) {
      result->Error("INIT_FAILED", "Failed to create container window");
      return;
    }

    bool success = player_->Initialize(container, flutter_window);

    if (success) {
      // Set up event callback.
      player_->SetEventCallback([this](const flutter::EncodableValue& event) { SendEvent(event); });

      // Register the mpv window with core for z-order management.
      RECT rect = {0, 0, 100, 100};
      MpvCore::GetInstance()->CreateMpvView(player_->GetHwnd(), rect, 1.0);

      // Start hidden.
      MpvCore::GetInstance()->SetVisible(false);
      result->Success(flutter::EncodableValue(true));
    } else {
      player_.reset();  // Clear the player so we don't have a half-initialized state
      result->Error("INIT_FAILED", "Failed to initialize MPV player");
    }
  } else if (method == "dispose") {
    if (player_) {
      auto hwnd = player_->GetHwnd();
      player_->Dispose();
      player_.reset();

      if (MpvCore::GetInstance() && hwnd) {
        MpvCore::GetInstance()->DisposeMpvView(hwnd);
      }
    }
    result->Success();
  } else if (method == "command") {
    if (!player_ || !player_->IsInitialized()) {
      result->Error("NOT_INITIALIZED", "Player not initialized");
      return;
    }

    const auto* args = method_call.arguments();
    if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
      result->Error("INVALID_ARGS", "Expected map argument");
      return;
    }

    const auto& map = std::get<flutter::EncodableMap>(*args);
    auto it = map.find(flutter::EncodableValue("args"));
    if (it == map.end() || !std::holds_alternative<flutter::EncodableList>(it->second)) {
      result->Error("INVALID_ARGS", "Missing 'args' list");
      return;
    }

    const auto& list = std::get<flutter::EncodableList>(it->second);
    std::vector<std::string> command_args;
    for (const auto& item : list) {
      if (std::holds_alternative<std::string>(item)) {
        command_args.push_back(std::get<std::string>(item));
      }
    }

    // Use async command to prevent UI blocking during network operations
    // Move result into shared_ptr for safe capture in callback
    auto result_ptr =
        std::make_shared<std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>>(std::move(result));
    std::string cmd_name = command_args.empty() ? "unknown" : command_args[0];
    player_->CommandAsync(command_args, [this, result_ptr, cmd_name](int error) {
      PostToPlatformThread([result_ptr, cmd_name, error]() {
        if (error < 0) {
          (*result_ptr)
              ->Error("COMMAND_FAILED", "MPV command failed: " + cmd_name + " (error " + std::to_string(error) + ")");
        } else {
          (*result_ptr)->Success();
        }
      });
    });
    return;  // Response will be sent asynchronously
  } else if (method == "setProperty") {
    if (!player_ || !player_->IsInitialized()) {
      result->Error("NOT_INITIALIZED", "Player not initialized");
      return;
    }

    const auto* args = method_call.arguments();
    if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
      result->Error("INVALID_ARGS", "Expected map argument");
      return;
    }

    const auto& map = std::get<flutter::EncodableMap>(*args);
    auto name_it = map.find(flutter::EncodableValue("name"));
    auto value_it = map.find(flutter::EncodableValue("value"));

    if (name_it == map.end() || !std::holds_alternative<std::string>(name_it->second)) {
      result->Error("INVALID_ARGS", "Missing 'name'");
      return;
    }
    if (value_it == map.end() || !std::holds_alternative<std::string>(value_it->second)) {
      result->Error("INVALID_ARGS", "Missing 'value'");
      return;
    }

    auto result_ptr =
        std::make_shared<std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>>(std::move(result));
    player_->SetPropertyAsync(
        std::get<std::string>(name_it->second), std::get<std::string>(value_it->second),
        [this, result_ptr](int error) { PostToPlatformThread([result_ptr]() { (*result_ptr)->Success(); }); });
    return;
  } else if (method == "setLogLevel") {
    if (!player_ || !player_->IsInitialized()) {
      result->Error("NOT_INITIALIZED", "Player not initialized");
      return;
    }

    const auto* args = method_call.arguments();
    if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
      result->Error("INVALID_ARGS", "Expected map argument");
      return;
    }

    const auto& map = std::get<flutter::EncodableMap>(*args);
    auto level_it = map.find(flutter::EncodableValue("level"));

    if (level_it == map.end() || !std::holds_alternative<std::string>(level_it->second)) {
      result->Error("INVALID_ARGS", "Missing 'level'");
      return;
    }

    player_->SetLogLevel(std::get<std::string>(level_it->second));
    result->Success();
  } else if (method == "getProperty") {
    if (!player_ || !player_->IsInitialized()) {
      result->Error("NOT_INITIALIZED", "Player not initialized");
      return;
    }

    const auto* args = method_call.arguments();
    if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
      result->Error("INVALID_ARGS", "Expected map argument");
      return;
    }

    const auto& map = std::get<flutter::EncodableMap>(*args);
    auto name_it = map.find(flutter::EncodableValue("name"));

    if (name_it == map.end() || !std::holds_alternative<std::string>(name_it->second)) {
      result->Error("INVALID_ARGS", "Missing 'name'");
      return;
    }

    auto result_ptr =
        std::make_shared<std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>>(std::move(result));
    player_->GetPropertyAsync(
        std::get<std::string>(name_it->second), [this, result_ptr](int error, const std::string& value) {
          PostToPlatformThread([result_ptr, error, value]() {
            if (error < 0 || value.empty()) {
              (*result_ptr)->Success();
            } else {
              (*result_ptr)->Success(flutter::EncodableValue(value));
            }
          });
        });
    return;
  } else if (method == "observeProperty") {
    if (!player_ || !player_->IsInitialized()) {
      result->Error("NOT_INITIALIZED", "Player not initialized");
      return;
    }

    const auto* args = method_call.arguments();
    if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
      result->Error("INVALID_ARGS", "Expected map argument");
      return;
    }

    const auto& map = std::get<flutter::EncodableMap>(*args);
    auto name_it = map.find(flutter::EncodableValue("name"));
    auto format_it = map.find(flutter::EncodableValue("format"));
    auto id_it = map.find(flutter::EncodableValue("id"));

    if (name_it == map.end() || !std::holds_alternative<std::string>(name_it->second)) {
      result->Error("INVALID_ARGS", "Missing 'name'");
      return;
    }
    if (format_it == map.end() || !std::holds_alternative<std::string>(format_it->second)) {
      result->Error("INVALID_ARGS", "Missing 'format'");
      return;
    }
    if (id_it == map.end() || !std::holds_alternative<int32_t>(id_it->second)) {
      result->Error("INVALID_ARGS", "Missing 'id'");
      return;
    }

    player_->ObserveProperty(
        std::get<std::string>(name_it->second), std::get<std::string>(format_it->second),
        std::get<int32_t>(id_it->second));
    result->Success();
  } else if (method == "setVisible") {
    const auto* args = method_call.arguments();
    if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
      result->Error("INVALID_ARGS", "Expected map argument");
      return;
    }

    const auto& map = std::get<flutter::EncodableMap>(*args);
    auto visible_it = map.find(flutter::EncodableValue("visible"));

    if (visible_it == map.end() || !std::holds_alternative<bool>(visible_it->second)) {
      result->Error("INVALID_ARGS", "Missing 'visible'");
      return;
    }

    bool visible = std::get<bool>(visible_it->second);

    if (player_) {
      player_->SetVisible(visible);
    }
    if (MpvCore::GetInstance()) {
      MpvCore::GetInstance()->SetVisible(visible);
    }

    result->Success();
  } else if (method == "setVideoRect") {
    const auto* args = method_call.arguments();
    if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
      result->Error("INVALID_ARGS", "Expected map argument");
      return;
    }

    const auto& map = std::get<flutter::EncodableMap>(*args);

    auto get_int = [&map](const char* key) -> int {
      auto it = map.find(flutter::EncodableValue(key));
      if (it != map.end()) {
        if (std::holds_alternative<int32_t>(it->second)) {
          return std::get<int32_t>(it->second);
        } else if (std::holds_alternative<int64_t>(it->second)) {
          return static_cast<int>(std::get<int64_t>(it->second));
        }
      }
      return 0;
    };

    auto get_double = [&map](const char* key) -> double {
      auto it = map.find(flutter::EncodableValue(key));
      if (it != map.end() && std::holds_alternative<double>(it->second)) {
        return std::get<double>(it->second);
      }
      return 1.0;
    };

    RECT rect;
    rect.left = get_int("left");
    rect.top = get_int("top");
    rect.right = get_int("right");
    rect.bottom = get_int("bottom");
    double dpr = get_double("devicePixelRatio");

    if (player_ && MpvCore::GetInstance()) {
      MpvCore::GetInstance()->ResizeMpvView(player_->GetHwnd(), rect);
      player_->SetRect(rect, dpr);
    }

    result->Success();
  } else if (method == "isInitialized") {
    bool initialized = player_ && player_->IsInitialized();
    result->Success(flutter::EncodableValue(initialized));

    // --- Display mode matching ---
  } else if (method == "getDisplayModes") {
    HWND hwnd = GetWindow();
    auto modes = display_mode_manager_.EnumerateDisplayModes(hwnd);
    flutter::EncodableList list;
    for (const auto& mode : modes) {
      list.push_back(flutter::EncodableValue(DisplayModeToMap(mode)));
    }
    result->Success(flutter::EncodableValue(list));
  } else if (method == "getCurrentDisplayMode") {
    HWND hwnd = GetWindow();
    auto mode = display_mode_manager_.GetCurrentMode(hwnd);
    result->Success(flutter::EncodableValue(DisplayModeToMap(mode)));
  } else if (method == "setDisplayMode") {
    const auto* args = method_call.arguments();
    if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
      result->Error("INVALID_ARGS", "Expected map argument");
      return;
    }
    const auto& map = std::get<flutter::EncodableMap>(*args);
    auto get_int = [&map](const char* key) -> int {
      auto it = map.find(flutter::EncodableValue(key));
      if (it != map.end() && std::holds_alternative<int32_t>(it->second)) return std::get<int32_t>(it->second);
      return 0;
    };
    HWND hwnd = GetWindow();
    bool success =
        display_mode_manager_.SetDisplayMode(hwnd, get_int("width"), get_int("height"), get_int("refreshRate"));
    result->Success(flutter::EncodableValue(success));
  } else if (method == "restoreDisplayMode") {
    HWND hwnd = GetWindow();
    bool success = display_mode_manager_.RestoreOriginalMode(hwnd);
    result->Success(flutter::EncodableValue(success));
  } else if (method == "isHDRSupported") {
    HWND hwnd = GetWindow();
    result->Success(flutter::EncodableValue(display_mode_manager_.IsHDRSupported(hwnd)));
  } else if (method == "isHDREnabled") {
    HWND hwnd = GetWindow();
    result->Success(flutter::EncodableValue(display_mode_manager_.IsHDREnabled(hwnd)));
  } else if (method == "setSystemHDR") {
    const auto* args = method_call.arguments();
    if (!args || !std::holds_alternative<flutter::EncodableMap>(*args)) {
      result->Error("INVALID_ARGS", "Expected map argument");
      return;
    }
    const auto& map = std::get<flutter::EncodableMap>(*args);
    auto it = map.find(flutter::EncodableValue("enabled"));
    if (it == map.end() || !std::holds_alternative<bool>(it->second)) {
      result->Error("INVALID_ARGS", "Missing 'enabled'");
      return;
    }
    bool enabled = std::get<bool>(it->second);
    HWND hwnd = GetWindow();
    bool success = display_mode_manager_.SetHDREnabled(hwnd, enabled);
    result->Success(flutter::EncodableValue(success));
  } else if (method == "restoreSystemHDR") {
    HWND hwnd = GetWindow();
    bool success = display_mode_manager_.RestoreOriginalHDRState(hwnd);
    result->Success(flutter::EncodableValue(success));
  } else if (method == "isModeChanged") {
    result->Success(flutter::EncodableValue(display_mode_manager_.IsModeChanged()));
  } else if (method == "isHDRChanged") {
    result->Success(flutter::EncodableValue(display_mode_manager_.IsHDRChanged()));
  } else {
    result->NotImplemented();
  }
}

void MpvPlayerPlugin::SendEvent(const flutter::EncodableValue& event) {
  if (event_sink_) {
    event_sink_->Success(event);
  }
}

}  // namespace mpv
