#include "flutter_window.h"

#include <commctrl.h>

#include <optional>

#include "flutter/generated_plugin_registrant.h"
#include "ocr_channel.h"
#include "pen_channel.h"

// SetWindowSubclass / DefSubclassProc / RemoveWindowSubclass live in comctl32.
#pragma comment(lib, "comctl32.lib")

namespace {

// Id for the pen observer subclass installed on the Flutter child view window.
constexpr UINT_PTR kPenSubclassId = 1;

// Subclass proc on the FLUTTER CHILD window. WM_POINTER messages are delivered
// to the window under the pointer — that is the Flutter view's child HWND, not
// the top-level window — so this is where the pen's barrel/eraser/tilt actually
// arrive. We observe (non-consuming) and pass every message straight through to
// Flutter's own handler via DefSubclassProc, so input behavior is unchanged.
LRESULT CALLBACK FlutterChildSubclassProc(HWND hwnd, UINT message,
                                          WPARAM wparam, LPARAM lparam,
                                          UINT_PTR /*subclass_id*/,
                                          DWORD_PTR /*ref_data*/) {
  ObservePenMessage(message, wparam, lparam);
  return DefSubclassProc(hwnd, message, wparam, lparam);
}

}  // namespace

FlutterWindow::FlutterWindow(const flutter::DartProject& project)
    : project_(project) {}

FlutterWindow::~FlutterWindow() {}

bool FlutterWindow::OnCreate() {
  if (!Win32Window::OnCreate()) {
    return false;
  }

  RECT frame = GetClientArea();

  // The size here must match the window dimensions to avoid unnecessary surface
  // creation / destruction in the startup path.
  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
      frame.right - frame.left, frame.bottom - frame.top, project_);
  // Ensure that basic setup of the controller was successful.
  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
    return false;
  }
  RegisterPlugins(flutter_controller_->engine());
  RegisterOcrChannel(flutter_controller_->engine());
  RegisterPenChannel(flutter_controller_->engine());

  HWND flutter_child = flutter_controller_->view()->GetNativeWindow();
  SetChildContent(flutter_child);

  // Observe WM_POINTER on the Flutter child window (where pen messages land) so
  // the pen barrel/eraser/inverted flags + tilt reach the badnote/pen channel.
  if (flutter_child) {
    SetWindowSubclass(flutter_child, FlutterChildSubclassProc, kPenSubclassId, 0);
  }

  flutter_controller_->engine()->SetNextFrameCallback([&]() {
    this->Show();
  });

  // Flutter can complete the first frame before the "show window" callback is
  // registered. The following call ensures a frame is pending to ensure the
  // window is shown. It is a no-op if the first frame hasn't completed yet.
  flutter_controller_->ForceRedraw();

  return true;
}

void FlutterWindow::OnDestroy() {
  if (flutter_controller_) {
    HWND flutter_child = flutter_controller_->view()->GetNativeWindow();
    if (flutter_child) {
      RemoveWindowSubclass(flutter_child, FlutterChildSubclassProc,
                           kPenSubclassId);
    }
    flutter_controller_ = nullptr;
  }

  Win32Window::OnDestroy();
}

LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
                              WPARAM const wparam,
                              LPARAM const lparam) noexcept {
  // Observe pen messages before Flutter consumes WM_POINTER events.
  ObservePenMessage(message, wparam, lparam);

  // Give Flutter, including plugins, an opportunity to handle window messages.
  if (flutter_controller_) {
    std::optional<LRESULT> result =
        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
                                                      lparam);
    if (result) {
      return *result;
    }
  }

  switch (message) {
    case WM_FONTCHANGE:
      flutter_controller_->engine()->ReloadSystemFonts();
      break;
  }

  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}
