#include "webview/webview_bridge.h"

#include <flutter/event_stream_handler_functions.h>
#include <flutter/method_result_functions.h>

#include <objbase.h>
#include <windows.h>
#include <winrt/Windows.Foundation.h>

#include <cctype>
#include <map>
#include <memory>
#include <optional>
#include <string>

#include "rendering/texture_bridge_gpu.h"
#include "util/string_converter.h"

namespace coreframe_flutter_windows {
namespace {
constexpr auto kEventType = "type";
constexpr auto kEventValue = "value";

constexpr auto kChannelPrefix = "com.coreframe.webview_windows.webview";

const flutter::EncodableMap *AsMap(const flutter::EncodableValue *value) {
  return value == nullptr ? nullptr
                          : std::get_if<flutter::EncodableMap>(value);
}

const std::string *MapString(const flutter::EncodableMap *map,
                             const char *key) {
  if (map == nullptr) return nullptr;
  const auto it = map->find(flutter::EncodableValue(key));
  return it == map->end() ? nullptr : std::get_if<std::string>(&it->second);
}

std::optional<int64_t> MapInt(const flutter::EncodableMap *map,
                              const char *key) {
  if (map == nullptr) return std::nullopt;
  const auto it = map->find(flutter::EncodableValue(key));
  if (it == map->end()) return std::nullopt;
  if (const auto value = std::get_if<int64_t>(&it->second)) return *value;
  if (const auto value = std::get_if<int32_t>(&it->second)) return *value;
  return std::nullopt;
}

std::optional<bool> MapBool(const flutter::EncodableMap *map,
                            const char *key) {
  if (map == nullptr) return std::nullopt;
  const auto it = map->find(flutter::EncodableValue(key));
  if (it == map->end()) return std::nullopt;
  const auto value = std::get_if<bool>(&it->second);
  return value == nullptr ? std::nullopt : std::optional<bool>(*value);
}

std::string NewHexId() {
  GUID guid{};
  if (FAILED(CoCreateGuid(&guid))) return std::string();
  wchar_t text[40]{};
  if (StringFromGUID2(guid, text, 40) == 0) return std::string();
  std::string result;
  for (const char value : util::Utf8FromUtf16(text)) {
    if (std::isxdigit(static_cast<unsigned char>(value))) {
      result.push_back(static_cast<char>(std::tolower(value)));
    }
  }
  return result;
}

std::string JsonQuote(const std::string &value) {
  std::string result = "\"";
  for (const unsigned char byte : value) {
    switch (byte) {
    case '\\': result += "\\\\"; break;
    case '"': result += "\\\""; break;
    case '\n': result += "\\n"; break;
    case '\r': result += "\\r"; break;
    case '\t': result += "\\t"; break;
    default:
      if (byte >= 0x20) result.push_back(static_cast<char>(byte));
      break;
    }
  }
  result.push_back('"');
  return result;
}

bool SupportedWebView2Runtime() {
  wil::unique_cotaskmem_string version;
  if (FAILED(GetAvailableCoreWebView2BrowserVersionString(nullptr, &version)) ||
      version == nullptr) {
    return false;
  }
  const std::string text = util::Utf8FromUtf16(version.get());
  const size_t separator = text.find('.');
  if (separator == std::string::npos) return false;
  try {
    return std::stoi(text.substr(0, separator)) >= 109;
  } catch (...) {
    return false;
  }
}

std::optional<std::string> LoopbackOrigin(const std::string &value) {
  try {
    const winrt::Windows::Foundation::Uri uri(winrt::to_hstring(value));
    const int32_t port = uri.Port();
    if (winrt::to_string(uri.SchemeName()) != "http" ||
        winrt::to_string(uri.Host()) != "localhost" ||
        !winrt::to_string(uri.UserName()).empty() ||
        port < 1024 || port > 65535) {
      return std::nullopt;
    }
    const std::string origin = "http://localhost:" + std::to_string(port);
    if (value.rfind(origin, 0) != 0 || value.size() <= origin.size()) {
      return std::nullopt;
    }
    const char boundary = value[origin.size()];
    return boundary == '/' || boundary == '?' || boundary == '#'
               ? std::optional<std::string>(origin)
               : std::nullopt;
  } catch (...) {
    return std::nullopt;
  }
}

static const std::string &GetCursorName(const HCURSOR cursor) {
  // The cursor names correspond to the Flutter Engine names:
  // in shell/platform/windows/flutter_window_win32.cc
  static const std::string kDefaultCursorName = "basic";
  static const std::pair<std::string, const wchar_t *> mappings[] = {
      {"allScroll", IDC_SIZEALL},
      {kDefaultCursorName, IDC_ARROW},
      {"click", IDC_HAND},
      {"forbidden", IDC_NO},
      {"help", IDC_HELP},
      {"move", IDC_SIZEALL},
      {"none", nullptr},
      {"noDrop", IDC_NO},
      {"precise", IDC_CROSS},
      {"progress", IDC_APPSTARTING},
      {"text", IDC_IBEAM},
      {"resizeColumn", IDC_SIZEWE},
      {"resizeDown", IDC_SIZENS},
      {"resizeDownLeft", IDC_SIZENESW},
      {"resizeDownRight", IDC_SIZENWSE},
      {"resizeLeft", IDC_SIZEWE},
      {"resizeLeftRight", IDC_SIZEWE},
      {"resizeRight", IDC_SIZEWE},
      {"resizeRow", IDC_SIZENS},
      {"resizeUp", IDC_SIZENS},
      {"resizeUpDown", IDC_SIZENS},
      {"resizeUpLeft", IDC_SIZENWSE},
      {"resizeUpRight", IDC_SIZENESW},
      {"resizeUpLeftDownRight", IDC_SIZENWSE},
      {"resizeUpRightDownLeft", IDC_SIZENESW},
      {"wait", IDC_WAIT},
  };

  static std::map<HCURSOR, std::string> cursors;
  static bool initialized = false;

  if (!initialized) {
    initialized = true;
    for (const auto &pair : mappings) {
      HCURSOR cursor_handle = LoadCursor(nullptr, pair.second);
      if (cursor_handle) {
        cursors[cursor_handle] = pair.first;
      }
    }
  }

  const auto it = cursors.find(cursor);
  if (it != cursors.end()) {
    return it->second;
  }
  return kDefaultCursorName;
}

static std::string JavaScriptDialogKindName(WebviewJavaScriptDialogKind kind) {
  switch (kind) {
  case WebviewJavaScriptDialogKind::Alert:
    return "alert";
  case WebviewJavaScriptDialogKind::Confirm:
    return "confirm";
  case WebviewJavaScriptDialogKind::Prompt:
    return "prompt";
  case WebviewJavaScriptDialogKind::BeforeUnload:
    return "beforeUnload";
  }
  return "alert";
}

flutter::EncodableMap
HeadersToEncodableMap(const std::map<std::string, std::string> &headers) {
  flutter::EncodableMap result;
  for (const auto &[name, value] : headers) {
    result[flutter::EncodableValue(name)] = flutter::EncodableValue(value);
  }
  return result;
}

} // namespace

WebviewBridge::WebviewBridge(flutter::BinaryMessenger *messenger,
                             flutter::TextureRegistrar *texture_registrar,
                             GraphicsContext *graphics_context,
                             std::unique_ptr<Webview> webview)
    : webview_(std::move(webview)), texture_registrar_(texture_registrar) {
  texture_bridge_ =
      std::make_unique<TextureBridgeGpu>(graphics_context, webview_->surface());
  if (!texture_bridge_->IsValid()) {
    return;
  }

  flutter_texture_ =
      std::make_unique<flutter::TextureVariant>(flutter::GpuSurfaceTexture(
          kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle,
          [bridge = static_cast<TextureBridgeGpu *>(texture_bridge_.get())](
              size_t width,
              size_t height) -> const FlutterDesktopGpuSurfaceDescriptor * {
            return bridge->GetSurfaceDescriptor(width, height);
          }));

  texture_id_ = texture_registrar->RegisterTexture(flutter_texture_.get());
  if (texture_id_ < 0) {
    flutter_texture_.reset();
    texture_bridge_.reset();
    return;
  }
  texture_bridge_->SetOnFrameAvailable(
      [this]() { texture_registrar_->MarkTextureFrameAvailable(texture_id_); });
  // texture_bridge_->SetOnSurfaceSizeChanged([this](Size size) {
  //  webview_->SetSurfaceSize(size.width, size.height);
  //});

  const std::string channel_name =
      std::string(kChannelPrefix) + "/" + std::to_string(texture_id_);
  method_channel_ =
      std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
          messenger, channel_name,
          &flutter::StandardMethodCodec::GetInstance());
  method_channel_->SetMethodCallHandler(
      [this](const flutter::MethodCall<flutter::EncodableValue> &call,
             std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>
                 result) {
        HandleTrustedMethod(call, std::move(result));
      });

  const std::string event_channel_name = channel_name + "/events";
  event_channel_ =
      std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(
          messenger, event_channel_name,
          &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) {
        event_sink_ = std::move(events);
        RegisterEventHandlers();
        return nullptr;
      },
      [this](const flutter::EncodableValue *arguments) {
        event_sink_ = nullptr;
        return nullptr;
      });

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

WebviewBridge::~WebviewBridge() {
  if (texture_id_ >= 0) {
    texture_registrar_->UnregisterTexture(texture_id_);
  }
}

void WebviewBridge::RegisterEventHandlers() {
  webview_->OnUrlChanged([this](const std::string &url) {
    const auto event = flutter::EncodableValue(flutter::EncodableMap{
        {flutter::EncodableValue(kEventType),
         flutter::EncodableValue("urlChanged")},
        {flutter::EncodableValue(kEventValue), flutter::EncodableValue(url)},
    });
    EmitEvent(event);
  });

  webview_->OnLoadError([this](COREWEBVIEW2_WEB_ERROR_STATUS web_status) {
    const auto event = flutter::EncodableValue(flutter::EncodableMap{
        {flutter::EncodableValue(kEventType),
         flutter::EncodableValue("onLoadError")},
        {flutter::EncodableValue(kEventValue),
         flutter::EncodableValue(static_cast<int>(web_status))},
    });
    EmitEvent(event);
  });

  webview_->OnHttpResponseError(
      [this](const WebviewHttpResponseError &http_error) {
        flutter::EncodableMap value = {
            {flutter::EncodableValue("url"),
             flutter::EncodableValue(http_error.url)},
            {flutter::EncodableValue("method"),
             flutter::EncodableValue(http_error.method)},
            {flutter::EncodableValue("requestHeaders"),
             flutter::EncodableValue(
                 HeadersToEncodableMap(http_error.request_headers))},
            {flutter::EncodableValue("statusCode"),
             flutter::EncodableValue(http_error.status_code)},
            {flutter::EncodableValue("responseHeaders"),
             flutter::EncodableValue(
                 HeadersToEncodableMap(http_error.response_headers))},
        };
        if (http_error.reason_phrase.has_value()) {
          value[flutter::EncodableValue("reasonPhrase")] =
              flutter::EncodableValue(http_error.reason_phrase.value());
        }

        const auto event = flutter::EncodableValue(flutter::EncodableMap{
            {flutter::EncodableValue(kEventType),
             flutter::EncodableValue("httpError")},
            {flutter::EncodableValue(kEventValue),
             flutter::EncodableValue(value)},
        });
        EmitEvent(event);
      });

  webview_->OnLoadingStateChanged([this](WebviewLoadingState state) {
    const auto event = flutter::EncodableValue(flutter::EncodableMap{
        {flutter::EncodableValue(kEventType),
         flutter::EncodableValue("loadingStateChanged")},
        {flutter::EncodableValue(kEventValue),
         flutter::EncodableValue(static_cast<int>(state))},
    });
    EmitEvent(event);
  });

  webview_->OnDownloadEvent([this](WebviewDownloadEvent webviewDownloadEvent) {
    const auto event = flutter::EncodableValue(flutter::EncodableMap{
        {flutter::EncodableValue(kEventType),
         flutter::EncodableValue("downloadEvent")},
        {flutter::EncodableValue(kEventValue),
         flutter::EncodableValue(flutter::EncodableMap{
             {flutter::EncodableValue("kind"),
              flutter::EncodableValue(
                  static_cast<int>(webviewDownloadEvent.kind))},
             {flutter::EncodableValue("url"),
              flutter::EncodableValue(webviewDownloadEvent.url)},
             {flutter::EncodableValue("resultFilePath"),
              flutter::EncodableValue(webviewDownloadEvent.resultFilePath)},
             {flutter::EncodableValue("bytesReceived"),
              flutter::EncodableValue(webviewDownloadEvent.bytesReceived)},
             {flutter::EncodableValue("totalBytesToReceive"),
              flutter::EncodableValue(
                  webviewDownloadEvent.totalBytesToReceive)},
         })}});
    EmitEvent(event);
  });

  webview_->OnHistoryChanged([this](WebviewHistoryChanged historyChanged) {
    const auto event = flutter::EncodableValue(flutter::EncodableMap{
        {flutter::EncodableValue(kEventType),
         flutter::EncodableValue("historyChanged")},
        {flutter::EncodableValue(kEventValue),
         flutter::EncodableValue(flutter::EncodableMap{
             {flutter::EncodableValue("canGoBack"),
              flutter::EncodableValue(
                  static_cast<bool>(historyChanged.can_go_back))},
             {flutter::EncodableValue("canGoForward"),
              flutter::EncodableValue(
                  static_cast<bool>(historyChanged.can_go_forward))},
         })},
    });
    EmitEvent(event);
  });

  webview_->OnDevtoolsProtocolEvent([this](const std::string &json) {
    const auto event = flutter::EncodableValue(flutter::EncodableMap{
        {flutter::EncodableValue(kEventType),
         flutter::EncodableValue("securityStateChanged")},
        {flutter::EncodableValue(kEventValue), flutter::EncodableValue(json)}});
    EmitEvent(event);
  });

  webview_->OnDocumentTitleChanged([this](const std::string &title) {
    const auto event = flutter::EncodableValue(flutter::EncodableMap{
        {flutter::EncodableValue(kEventType),
         flutter::EncodableValue("titleChanged")},
        {flutter::EncodableValue(kEventValue), flutter::EncodableValue(title)},
    });
    EmitEvent(event);
  });

  webview_->OnSurfaceSizeChanged([this](size_t width, size_t height) {
    texture_bridge_->NotifySurfaceSizeChanged();
  });

  webview_->OnCursorChanged([this](const HCURSOR cursor) {
    const auto &name = GetCursorName(cursor);
    const auto event = flutter::EncodableValue(
        flutter::EncodableMap{{flutter::EncodableValue(kEventType),
                               flutter::EncodableValue("cursorChanged")},
                              {flutter::EncodableValue(kEventValue), name}});
    EmitEvent(event);
  });

  webview_->OnWebMessageReceived([this](const std::string &source,
                                        const std::string &message) {
    if (webview_->IsTrusted()) {
      EmitTrustedSecurityEvent("message", source, message,
                                webview_->trusted_epoch());
      return;
    }
    const auto event = flutter::EncodableValue(
        flutter::EncodableMap{{flutter::EncodableValue(kEventType),
                               flutter::EncodableValue("webMessageReceived")},
                              {flutter::EncodableValue(kEventValue), message}});
    EmitEvent(event);
  });

  webview_->OnTrustedSecurityEvent(
      [this](const std::string &kind, const std::string &url,
             const std::string &detail, uint64_t epoch) {
        EmitTrustedSecurityEvent(kind, url, detail, epoch);
      });

  webview_->OnTrustedNavigationRequest(
      [this](const std::string &request_id, const std::string &url,
             const std::string &frame, const std::string &navigation_kind,
             bool is_user_initiated, uint64_t epoch) {
        EmitTrustedNavigationRequest(request_id, url, frame, navigation_kind,
                                      is_user_initiated, epoch);
      });

  webview_->OnPermissionRequested(
      [this](const std::string &url, WebviewPermissionKind kind,
             bool is_user_initiated,
             Webview::WebviewPermissionRequestedCompleter completer) {
        OnPermissionRequested(url, kind, is_user_initiated, completer);
      });

  webview_->OnNavigationRequested(
      [this](const std::string &url, bool is_user_initiated, bool is_redirected,
             Webview::WebviewNavigationRequestedCompleter completer) {
        OnNavigationRequested(url, is_user_initiated, is_redirected,
                              std::move(completer));
      });

  webview_->OnHttpAuthRequested(
      [this](const WebviewHttpAuthRequest &request,
             Webview::WebviewHttpAuthRequestedCompleter completer) {
        OnHttpAuthRequested(request, completer);
      });

  webview_->OnSslAuthError(
      [this](const WebviewSslAuthError &error,
             Webview::WebviewSslAuthErrorCompleter completer) {
        OnSslAuthError(error, completer);
      });

  webview_->OnJavaScriptDialogRequested(
      [this](const WebviewJavaScriptDialogRequest &request,
             Webview::WebviewJavaScriptDialogCompleter completer) {
        OnJavaScriptDialogRequested(request, completer);
      });

  webview_->OnContainsFullScreenElementChanged(
      [this](bool contains_fullscreen_element) {
        const auto event = flutter::EncodableValue(flutter::EncodableMap{
            {flutter::EncodableValue(kEventType),
             flutter::EncodableValue("containsFullScreenElementChanged")},
            {flutter::EncodableValue(kEventValue),
             contains_fullscreen_element}});
        EmitEvent(event);
      });
}

void WebviewBridge::EmitTrustedSecurityEvent(const std::string &kind,
                                              const std::string &url,
                                              const std::string &detail,
                                              uint64_t epoch) {
  flutter::EncodableMap value{
      {flutter::EncodableValue("kind"), flutter::EncodableValue(kind)},
      {flutter::EncodableValue("controllerId"),
       flutter::EncodableValue(webview_->trusted_controller_id())},
      {flutter::EncodableValue("origin"),
       flutter::EncodableValue(webview_->trusted_origin())},
      {flutter::EncodableValue("epoch"),
       flutter::EncodableValue(static_cast<int64_t>(epoch))},
      {flutter::EncodableValue("isMainFrame"),
       flutter::EncodableValue(true)},
  };
  if (!url.empty()) {
    value[flutter::EncodableValue("url")] = flutter::EncodableValue(url);
  }
  if (!detail.empty()) {
    const char *name = kind == "message"       ? "message"
                       : kind == "loadError"   ? "description"
                                                : "reason";
    value[flutter::EncodableValue(name)] = flutter::EncodableValue(detail);
  }
  EmitEvent(flutter::EncodableValue(flutter::EncodableMap{
      {flutter::EncodableValue(kEventType),
       flutter::EncodableValue("trustedSecurityEvent")},
      {flutter::EncodableValue(kEventValue),
       flutter::EncodableValue(std::move(value))},
  }));
}

void WebviewBridge::EmitTrustedNavigationRequest(
    const std::string &request_id, const std::string &url,
    const std::string &frame, const std::string &navigation_kind,
    bool is_user_initiated, uint64_t epoch) {
  EmitEvent(flutter::EncodableValue(flutter::EncodableMap{
      {flutter::EncodableValue(kEventType),
       flutter::EncodableValue("trustedSecurityEvent")},
      {flutter::EncodableValue(kEventValue),
       flutter::EncodableValue(flutter::EncodableMap{
           {flutter::EncodableValue("kind"),
            flutter::EncodableValue("navigationRequest")},
           {flutter::EncodableValue("requestId"),
            flutter::EncodableValue(request_id)},
           {flutter::EncodableValue("controllerId"),
            flutter::EncodableValue(webview_->trusted_controller_id())},
           {flutter::EncodableValue("origin"),
            flutter::EncodableValue(webview_->trusted_origin())},
           {flutter::EncodableValue("epoch"),
            flutter::EncodableValue(static_cast<int64_t>(epoch))},
           {flutter::EncodableValue("url"), flutter::EncodableValue(url)},
           {flutter::EncodableValue("frame"),
            flutter::EncodableValue(frame)},
           {flutter::EncodableValue("isMainFrame"),
            flutter::EncodableValue(frame == "main")},
           {flutter::EncodableValue("navigationKind"),
            flutter::EncodableValue(navigation_kind)},
           {flutter::EncodableValue("isUserInitiated"),
            flutter::EncodableValue(is_user_initiated)},
       })},
  }));
}

void WebviewBridge::HandleTrustedMethod(
    const flutter::MethodCall<flutter::EncodableValue> &call,
    std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
  const flutter::EncodableMap *arguments = AsMap(call.arguments());
  if (call.method_name() == "configureTrusted") {
    const std::string *initial_location =
        MapString(arguments, "initialLocation");
    const auto diagnostics_enabled = MapBool(arguments, "diagnosticsEnabled");
    const auto show_scrollbars = MapBool(arguments, "showScrollbars");
    const auto origin = initial_location == nullptr
                            ? std::nullopt
                            : LoopbackOrigin(*initial_location);
    if (initial_location == nullptr || !origin || !diagnostics_enabled ||
        !show_scrollbars) {
      result->Error("invalid_configuration",
                    "The loopback bootstrap request is invalid.");
      return;
    }
    if (!SupportedWebView2Runtime()) {
      result->Error("unsupported_runtime",
                    "Coreframe WebViews require WebView2 Runtime 109 or newer.");
      return;
    }
    const std::string controller_id = NewHexId();
    const std::string bridge_token = NewHexId();
    std::string failure_reason;
    bool configured = false;
    if (controller_id.empty() || bridge_token.empty()) {
      failure_reason = "The WebView security identifiers could not be created.";
    } else {
      configured = webview_->ConfigureTrusted(
          *origin, controller_id, bridge_token, *initial_location,
          *diagnostics_enabled, *show_scrollbars, &failure_reason);
    }
    if (!configured) {
      result->Error("trusted_initialization_failed",
                    failure_reason.empty()
                        ? "The secure WebView2 host could not be initialized."
                        : failure_reason);
      return;
    }
    result->Success(flutter::EncodableValue(flutter::EncodableMap{
        {flutter::EncodableValue("origin"),
         flutter::EncodableValue(*origin)},
        {flutter::EncodableValue("controllerId"),
         flutter::EncodableValue(controller_id)},
    }));
    return;
  }
  if (call.method_name() == "capturePresentationArtifact") {
    const std::string *entry_key = MapString(arguments, "entryKey");
    const std::string *root_web_runtime_id =
        MapString(arguments, "rootWebRuntimeId");
    const std::string *application_scope_id =
        MapString(arguments, "applicationScopeId");
    const std::optional<int64_t> presentation_revision =
        MapInt(arguments, "presentationRevision");
    if (entry_key == nullptr || root_web_runtime_id == nullptr ||
        application_scope_id == nullptr || !presentation_revision ||
        entry_key->empty() || root_web_runtime_id->empty() ||
        application_scope_id->empty() || *presentation_revision < 1) {
      result->Success();
      return;
    }
    PresentationIdentity expected{
        *entry_key,
        *presentation_revision,
        *root_web_runtime_id,
        *application_scope_id,
    };
    auto shared_result =
        std::shared_ptr<flutter::MethodResult<flutter::EncodableValue>>(
            std::move(result));
    webview_->CapturePresentationArtifact(
        expected,
        [result = std::move(shared_result)](
            std::optional<PresentationIdentity> identity,
            std::vector<uint8_t> png) mutable {
          if (!identity || png.empty()) {
            result->Success();
            return;
          }
          const flutter::EncodableMap captured_identity{
              {flutter::EncodableValue("entryKey"),
               flutter::EncodableValue(identity->entry_key)},
              {flutter::EncodableValue("presentationRevision"),
               flutter::EncodableValue(identity->presentation_revision)},
              {flutter::EncodableValue("rootWebRuntimeId"),
               flutter::EncodableValue(identity->root_web_runtime_id)},
              {flutter::EncodableValue("applicationScopeId"),
               flutter::EncodableValue(identity->application_scope_id)},
          };
          result->Success(flutter::EncodableValue(flutter::EncodableMap{
              {flutter::EncodableValue("identity"),
               flutter::EncodableValue(captured_identity)},
              {flutter::EncodableValue("png"), flutter::EncodableValue(png)},
          }));
        });
    return;
  }
  if (call.method_name() == "releaseFocus") {
    auto shared_result =
        std::shared_ptr<flutter::MethodResult<flutter::EncodableValue>>(
            std::move(result));
    webview_->ReleaseFocus(
        [result = std::move(shared_result)](bool success,
                                            const std::string &) mutable {
          if (success) {
            result->Success();
          } else {
            result->Error("focus_release_failed",
                          "The WebView could not release focus.");
          }
        });
    return;
  }
  if (call.method_name() == "loadTrusted") {
    const std::string *url = MapString(arguments, "url");
    if (url == nullptr || !webview_->LoadTrusted(*url)) {
      result->Error("navigation_rejected",
                    "The trusted navigation was rejected.");
      return;
    }
    result->Success();
    return;
  }
  if (call.method_name() == "startTrusted") {
    if (!webview_->StartTrusted()) {
      result->Error("navigation_rejected",
                    "The initial trusted navigation could not start.");
      return;
    }
    result->Success();
    return;
  }
  if (call.method_name() == "completeTrustedNavigation") {
    const std::string *request_id = MapString(arguments, "requestId");
    const std::string *controller = MapString(arguments, "controllerId");
    const std::optional<int64_t> epoch = MapInt(arguments, "epoch");
    const std::string *disposition = MapString(arguments, "disposition");
    if (request_id == nullptr || controller == nullptr || !epoch ||
        *epoch < 1 || disposition == nullptr ||
        !webview_->CompleteTrustedNavigation(
            *request_id, *controller, static_cast<uint64_t>(*epoch),
            *disposition)) {
      result->Error("navigation_rejected",
                    "The trusted navigation decision was stale or invalid.");
      return;
    }
    result->Success();
    return;
  }
  if (call.method_name() == "markTrustedReady") {
    const std::string *controller = MapString(arguments, "controllerId");
    const std::string *origin = MapString(arguments, "origin");
    const std::optional<int64_t> epoch = MapInt(arguments, "epoch");
    if (controller == nullptr || origin == nullptr || !epoch || *epoch < 1 ||
        !webview_->MarkTrustedReady(*controller, *origin,
                                     static_cast<uint64_t>(*epoch))) {
      result->Error("untrusted_document",
                    "Readiness did not match the active document.");
      return;
    }
    result->Success();
    return;
  }
  if (call.method_name() == "goBackTrusted" ||
      call.method_name() == "goForwardTrusted" ||
      call.method_name() == "reloadTrusted") {
    const bool accepted =
        call.method_name() == "goBackTrusted"
            ? webview_->GoBackTrusted()
            : call.method_name() == "goForwardTrusted"
                  ? webview_->GoForwardTrusted()
                  : webview_->ReloadTrusted();
    if (!accepted) {
      result->Error("navigation_rejected",
                    "The engine navigation was unavailable.");
    } else {
      result->Success();
    }
    return;
  }
  if (call.method_name() == "dispatchTrusted") {
    const std::string *controller = MapString(arguments, "controllerId");
    const std::string *origin = MapString(arguments, "origin");
    const std::optional<int64_t> epoch = MapInt(arguments, "epoch");
    const std::string *event_name = MapString(arguments, "eventName");
    const std::string *message = MapString(arguments, "message");
    if (controller == nullptr || origin == nullptr || !epoch || *epoch < 1 ||
        event_name == nullptr || message == nullptr) {
      result->Error("invalid_message", "The dispatch identity is malformed.");
      return;
    }
    const std::string script =
        "window.dispatchEvent(new CustomEvent(" + JsonQuote(*event_name) +
        ",{detail:JSON.parse(" + JsonQuote(*message) + ")}));";
    auto shared_result =
        std::shared_ptr<flutter::MethodResult<flutter::EncodableValue>>(
            std::move(result));
    webview_->DispatchTrusted(
        *controller, *origin, static_cast<uint64_t>(*epoch), script,
        [result = std::move(shared_result)](bool success) mutable {
          if (success) {
            result->Success();
          } else {
            result->Error("untrusted_document",
                          "The trusted document changed before dispatch.");
          }
        });
    return;
  }
  result->NotImplemented();
}

void WebviewBridge::OnNavigationRequested(
    const std::string &url, bool is_user_initiated, bool is_redirected,
    Webview::WebviewNavigationRequestedCompleter completer) {
  auto args = std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
      {"url", url},
      {"isUserInitiated", is_user_initiated},
      {"isRedirected", is_redirected},
  });

  method_channel_->InvokeMethod(
      "navigationRequested", std::move(args),
      std::make_unique<flutter::MethodResultFunctions<flutter::EncodableValue>>(
          [completer](const flutter::EncodableValue *result) {
            const bool *allow =
                result == nullptr ? nullptr : std::get_if<bool>(result);
            completer(allow != nullptr && *allow);
          },
          [completer](const std::string &error_code,
                      const std::string &error_message,
                      const flutter::EncodableValue *error_details) {
            completer(false);
          },
          [completer]() { completer(false); }));
}

void WebviewBridge::OnPermissionRequested(
    const std::string &url, WebviewPermissionKind permissionKind,
    bool isUserInitiated,
    Webview::WebviewPermissionRequestedCompleter completer) {
  auto args = std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
      {"url", url},
      {"isUserInitiated", isUserInitiated},
      {"permissionKind", static_cast<int>(permissionKind)}});

  method_channel_->InvokeMethod(
      "permissionRequested", std::move(args),
      std::make_unique<flutter::MethodResultFunctions<flutter::EncodableValue>>(
          [completer](const flutter::EncodableValue *result) {
            auto allow = std::get_if<bool>(result);
            if (allow != nullptr) {
              return completer(*allow ? WebviewPermissionState::Allow
                                      : WebviewPermissionState::Deny);
            }
            completer(WebviewPermissionState::Default);
          },
          [completer](const std::string &error_code,
                      const std::string &error_message,
                      const flutter::EncodableValue *error_details) {
            completer(WebviewPermissionState::Default);
          },
          [completer]() { completer(WebviewPermissionState::Default); }));
}

void WebviewBridge::OnHttpAuthRequested(
    const WebviewHttpAuthRequest &request,
    Webview::WebviewHttpAuthRequestedCompleter completer) {
  auto args = std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
      {"url", request.url},
      {"challenge", request.challenge},
  });

  method_channel_->InvokeMethod(
      "httpAuthRequested", std::move(args),
      std::make_unique<flutter::MethodResultFunctions<flutter::EncodableValue>>(
          [completer](const flutter::EncodableValue *result) {
            const auto response =
                result == nullptr ? nullptr
                                  : std::get_if<flutter::EncodableMap>(result);
            if (response == nullptr) {
              completer(false, "", "");
              return;
            }

            bool accepted = false;
            auto action_it = response->find(flutter::EncodableValue("action"));
            if (action_it != response->end()) {
              const auto action = std::get_if<std::string>(&action_it->second);
              accepted = action != nullptr && *action == "proceed";
            }

            std::string user;
            auto user_it = response->find(flutter::EncodableValue("user"));
            if (user_it != response->end()) {
              if (const auto response_user =
                      std::get_if<std::string>(&user_it->second)) {
                user = *response_user;
              }
            }

            std::string password;
            auto password_it =
                response->find(flutter::EncodableValue("password"));
            if (password_it != response->end()) {
              if (const auto response_password =
                      std::get_if<std::string>(&password_it->second)) {
                password = *response_password;
              }
            }

            completer(accepted, user, password);
          },
          [completer](const std::string &error_code,
                      const std::string &error_message,
                      const flutter::EncodableValue *error_details) {
            completer(false, "", "");
          },
          [completer]() { completer(false, "", ""); }));
}

void WebviewBridge::OnSslAuthError(
    const WebviewSslAuthError &error,
    Webview::WebviewSslAuthErrorCompleter completer) {
  auto args = std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
      {"url", error.url},
      {"errorStatus", static_cast<int>(error.status)},
  });

  method_channel_->InvokeMethod(
      "sslAuthError", std::move(args),
      std::make_unique<flutter::MethodResultFunctions<flutter::EncodableValue>>(
          [completer](const flutter::EncodableValue *result) {
            const auto response =
                result == nullptr ? nullptr
                                  : std::get_if<flutter::EncodableMap>(result);
            if (response == nullptr) {
              completer(false);
              return;
            }

            bool proceed = false;
            auto action_it = response->find(flutter::EncodableValue("action"));
            if (action_it != response->end()) {
              const auto action = std::get_if<std::string>(&action_it->second);
              proceed = action != nullptr && *action == "proceed";
            }
            completer(proceed);
          },
          [completer](const std::string &error_code,
                      const std::string &error_message,
                      const flutter::EncodableValue *error_details) {
            completer(false);
          },
          [completer]() { completer(false); }));
}

void WebviewBridge::OnJavaScriptDialogRequested(
    const WebviewJavaScriptDialogRequest &request,
    Webview::WebviewJavaScriptDialogCompleter completer) {
  flutter::EncodableMap dialog_request{
      {"dialogType", JavaScriptDialogKindName(request.kind)},
      {"url", request.url},
      {"message", request.message},
  };
  if (request.default_text) {
    dialog_request[flutter::EncodableValue("defaultText")] =
        flutter::EncodableValue(*request.default_text);
  }

  auto args =
      std::make_unique<flutter::EncodableValue>(std::move(dialog_request));
  method_channel_->InvokeMethod(
      "javaScriptDialogRequested", std::move(args),
      std::make_unique<flutter::MethodResultFunctions<flutter::EncodableValue>>(
          [completer](const flutter::EncodableValue *result) {
            const auto response =
                result == nullptr ? nullptr
                                  : std::get_if<flutter::EncodableMap>(result);
            if (response == nullptr) {
              completer(false, std::nullopt);
              return;
            }

            bool accepted = false;
            auto action_it = response->find(flutter::EncodableValue("action"));
            if (action_it != response->end()) {
              const auto action = std::get_if<std::string>(&action_it->second);
              accepted = action != nullptr &&
                         (*action == "accept" || *action == "confirm");
            }

            std::optional<std::string> text;
            auto text_it = response->find(flutter::EncodableValue("text"));
            if (text_it != response->end()) {
              const auto response_text =
                  std::get_if<std::string>(&text_it->second);
              if (response_text != nullptr) {
                text = *response_text;
              }
            }

            completer(accepted, text);
          },
          [completer](const std::string &error_code,
                      const std::string &error_message,
                      const flutter::EncodableValue *error_details) {
            completer(false, std::nullopt);
          },
          [completer]() { completer(false, std::nullopt); }));
}

void WebviewBridge::SetCursorPos(double x, double y) {
  webview_->SetCursorPos(x, y);
}

void WebviewBridge::SetPointerUpdate(int64_t pointer, int64_t event, double x,
                                     double y, double size, double pressure) {
  webview_->SetPointerUpdate(static_cast<int32_t>(pointer),
                             static_cast<WebviewPointerEventKind>(event), x, y,
                             size, pressure);
}

void WebviewBridge::SetScrollDelta(double dx, double dy) {
  webview_->SetScrollDelta(dx, dy);
}

void WebviewBridge::SetPointerButtonState(int64_t button, bool is_down) {
  webview_->SetPointerButtonState(static_cast<WebviewPointerButton>(button),
                                  is_down);
}

bool WebviewBridge::SetSize(double width, double height, double scale_factor) {
  webview_->SetSurfaceSize(static_cast<size_t>(width),
                           static_cast<size_t>(height),
                           static_cast<float>(scale_factor));
  return texture_bridge_->Start();
}

void WebviewBridge::LoadUrl(const std::string &url) { webview_->LoadUrl(url); }

bool WebviewBridge::LoadRequest(const std::string &url,
                                const std::string &method,
                                const std::string &headers,
                                const std::vector<uint8_t> *body) {
  return webview_->LoadRequest(url, method, headers, body);
}

void WebviewBridge::LoadStringContent(const std::string &content) {
  webview_->LoadStringContent(content);
}

bool WebviewBridge::Reload() { return webview_->Reload(); }

bool WebviewBridge::Stop() { return webview_->Stop(); }

bool WebviewBridge::GoBack() { return webview_->GoBack(); }

bool WebviewBridge::GoForward() { return webview_->GoForward(); }

void WebviewBridge::Suspend() {
  texture_bridge_->Stop();
  webview_->Suspend();
}

bool WebviewBridge::Resume() {
  webview_->Resume();
  return texture_bridge_->Start();
}

void WebviewBridge::SetVirtualHostNameMapping(const std::string &host_name,
                                              const std::string &path,
                                              int64_t access_kind) {
  webview_->SetVirtualHostNameMapping(
      host_name, path, static_cast<WebviewHostResourceAccessKind>(access_kind));
}

bool WebviewBridge::ClearVirtualHostNameMapping(const std::string &host_name) {
  return webview_->ClearVirtualHostNameMapping(host_name);
}

void WebviewBridge::AddScriptToExecuteOnDocumentCreated(
    const std::string &script,
    std::function<void(bool success, const std::string &script_id)> result) {
  webview_->AddScriptToExecuteOnDocumentCreated(script, std::move(result));
}

void WebviewBridge::RemoveScriptToExecuteOnDocumentCreated(
    const std::string &script_id) {
  webview_->RemoveScriptToExecuteOnDocumentCreated(script_id);
}

void WebviewBridge::ExecuteScript(
    const std::string &script,
    std::function<void(bool success, const std::string &json_result)> result) {
  webview_->ExecuteScript(script, std::move(result));
}

bool WebviewBridge::PostWebMessage(const std::string &message) {
  return webview_->PostWebMessage(message);
}

bool WebviewBridge::SetUserAgent(const std::string *user_agent) {
  return webview_->SetUserAgent(user_agent);
}

std::optional<std::string> WebviewBridge::GetUserAgent() {
  return webview_->GetUserAgent();
}

bool WebviewBridge::SetJavaScriptEnabled(bool enabled) {
  return webview_->SetJavaScriptEnabled(enabled);
}

bool WebviewBridge::SetZoomControlEnabled(bool enabled) {
  return webview_->SetZoomControlEnabled(enabled);
}

bool WebviewBridge::SetBackgroundColor(int64_t color) {
  return webview_->SetBackgroundColor(static_cast<int32_t>(color));
}

bool WebviewBridge::SetZoomFactor(double zoom_factor) {
  return webview_->SetZoomFactor(zoom_factor);
}

bool WebviewBridge::OpenDevTools() { return webview_->OpenDevTools(); }

void WebviewBridge::SetJavaScriptDialogCallbacksEnabled(bool alert,
                                                        bool confirm,
                                                        bool prompt) {
  webview_->SetJavaScriptDialogCallbacksEnabled(alert, confirm, prompt);
}

void WebviewBridge::ClearCookies(
    std::function<void(bool success, bool had_cookies)> result) {
  webview_->ClearCookies(std::move(result));
}

bool WebviewBridge::SetCookie(const WebviewCookie &cookie) {
  return webview_->SetCookie(cookie);
}

void WebviewBridge::GetCookies(
    const std::string &url,
    std::function<void(bool success, std::vector<WebviewCookie> cookies)>
        result) {
  webview_->GetCookies(url, std::move(result));
}

bool WebviewBridge::DeleteCookie(const WebviewCookie &cookie) {
  return webview_->DeleteCookie(cookie);
}

bool WebviewBridge::DeleteCookiesWithNameAndUrl(const std::string &name,
                                                const std::string &url) {
  return webview_->DeleteCookiesWithNameAndUrl(name, url);
}

bool WebviewBridge::DeleteCookiesWithNameDomainAndPath(
    const std::string &name, const std::string &domain,
    const std::string &path) {
  return webview_->DeleteCookiesWithNameDomainAndPath(name, domain, path);
}

bool WebviewBridge::ClearCache() { return webview_->ClearCache(); }

void WebviewBridge::ClearLocalStorage(
    Webview::OperationCompletedCallback callback) {
  webview_->ClearLocalStorage(std::move(callback));
}

bool WebviewBridge::SetCacheDisabled(bool disabled) {
  return webview_->SetCacheDisabled(disabled);
}

void WebviewBridge::SetPopupWindowPolicy(int64_t policy) {
  switch (policy) {
  case 1:
    webview_->SetPopupWindowPolicy(WebviewPopupWindowPolicy::Deny);
    break;
  case 2:
    webview_->SetPopupWindowPolicy(WebviewPopupWindowPolicy::ShowInSameWindow);
    break;
  default:
    webview_->SetPopupWindowPolicy(WebviewPopupWindowPolicy::Allow);
    break;
  }
}

void WebviewBridge::SetNavigationRequestCallbacksEnabled(bool enabled) {
  webview_->SetNavigationRequestCallbacksEnabled(enabled);
}

void WebviewBridge::SetFpsLimit(int64_t max_fps) {
  if (max_fps == 0) {
    texture_bridge_->SetFpsLimit(std::nullopt);
    return;
  }
  texture_bridge_->SetFpsLimit(
      std::make_optional<int>(static_cast<int>(max_fps)));
}

} // namespace coreframe_flutter_windows
