#include "pen_channel.h"

#include <flutter/encodable_value.h>
#include <flutter/event_channel.h>
#include <flutter/event_stream_handler_functions.h>
#include <flutter/flutter_engine.h>
#include <flutter/standard_method_codec.h>

#include <memory>

namespace {

std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> g_pen_sink;

std::unique_ptr<flutter::EventChannel<flutter::EncodableValue>> g_pen_channel;

// Diagnostic counters so the Dart side can see WHAT the observer receives:
//  - g_ptr_msgs:   WM_POINTER* messages seen (is WM_POINTER reaching us at all?)
//  - g_pen_msgs:   of those, PT_PEN with a successful GetPointerPenInfo
//  - g_mouse_msgs: legacy mouse/touch input messages (Flutter on the old path?)
int g_ptr_msgs = 0;
int g_pen_msgs = 0;
int g_mouse_msgs = 0;
int g_last_msg = 0;

// OR-accumulated raw flag fields, so a momentary button press is CAPTURED and
// held (a live readout would miss it). These are the ground truth for "which
// field/bit does the side button / eraser set?".
int g_ptr_flags_or = 0;   // POINTER_INFO.pointerFlags  (POINTER_FLAG_SECONDBUTTON = barrel on many pens)
int g_pen_flags_or = 0;   // POINTER_PEN_INFO.penFlags   (PEN_FLAG_BARREL/INVERTED/ERASER)
int g_pen_mask_or = 0;    // POINTER_PEN_INFO.penMask
int g_btn_change_last = 0; // last non-zero POINTER_INFO.ButtonChangeType
int g_tilt_abs_max = 0;   // max |tiltX|,|tiltY| seen
float g_pressure_max_seen = 0.f;

}  // namespace

void RegisterPenChannel(flutter::FlutterEngine* engine) {
  g_pen_channel =
      std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(
          engine->messenger(), "badnote/pen",
          &flutter::StandardMethodCodec::GetInstance());

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

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

void ObservePenMessage(UINT message, WPARAM wparam, LPARAM lparam) {
  if (!g_pen_sink) {
    return;
  }

  const bool is_pointer =
      message == WM_POINTERENTER || message == WM_POINTERDOWN ||
      message == WM_POINTERUPDATE || message == WM_POINTERUP;
  // Legacy input path: if Flutter is feeding us mouse/touch instead of pointer
  // messages, these tell us so (so we know WM_POINTER never arrives here).
  const bool is_legacy_input =
      message == WM_LBUTTONDOWN || message == WM_LBUTTONUP ||
      message == WM_MOUSEMOVE || message == WM_TOUCH;

  if (!is_pointer && !is_legacy_input) {
    return;
  }
  g_last_msg = static_cast<int>(message);
  if (is_legacy_input) {
    ++g_mouse_msgs;
  }

  int flags = 0;
  double tilt_x = 0.0;
  double tilt_y = 0.0;
  double pressure = 0.0;
  int pressure_valid = 0;
  int raw_ptr_flags = 0;
  int raw_pen_flags = 0;
  int raw_pen_mask = 0;
  int btn_change = 0;
  int history_count = 0;

  if (is_pointer) {
    ++g_ptr_msgs;
    UINT32 pointerId = GET_POINTERID_WPARAM(wparam);
    POINTER_INPUT_TYPE type = PT_POINTER;
    if (GetPointerType(pointerId, &type) && type == PT_PEN) {
      POINTER_PEN_INFO ppi{};
      if (GetPointerPenInfo(pointerId, &ppi)) {
        ++g_pen_msgs;
        raw_pen_flags = static_cast<int>(ppi.penFlags);
        raw_pen_mask = static_cast<int>(ppi.penMask);
        raw_ptr_flags = static_cast<int>(ppi.pointerInfo.pointerFlags);
        btn_change = static_cast<int>(ppi.pointerInfo.ButtonChangeType);
        tilt_x = static_cast<double>(ppi.tiltX);
        tilt_y = static_cast<double>(ppi.tiltY);

        // Pressure: Win32 reports 0..1024 when PEN_MASK_PRESSURE is set.
        // Normalize to [0,1] for Dart. Also scan history for a non-zero sample
        // (some drivers zero the tip sample while history has the real value).
        if (ppi.penMask & PEN_MASK_PRESSURE) {
          pressure_valid = 1;
          pressure = static_cast<double>(ppi.pressure) / 1024.0;
          if (pressure < 0.0) pressure = 0.0;
          if (pressure > 1.0) pressure = 1.0;
        }

        const bool barrel = (ppi.penFlags & PEN_FLAG_BARREL) ||
                            (ppi.pointerInfo.pointerFlags & POINTER_FLAG_SECONDBUTTON);
        const bool inverted = (ppi.penFlags & PEN_FLAG_INVERTED) != 0;
        const bool eraser = (ppi.penFlags & PEN_FLAG_ERASER) != 0;
        if (barrel)   flags |= 1;
        if (inverted) flags |= 2;
        if (eraser)   flags |= 4;

        g_ptr_flags_or |= raw_ptr_flags;
        g_pen_flags_or |= raw_pen_flags;
        g_pen_mask_or |= raw_pen_mask;
        if (btn_change != 0) g_btn_change_last = btn_change;
        const int ax = ppi.tiltX < 0 ? -ppi.tiltX : ppi.tiltX;
        const int ay = ppi.tiltY < 0 ? -ppi.tiltY : ppi.tiltY;
        if (ax > g_tilt_abs_max) g_tilt_abs_max = ax;
        if (ay > g_tilt_abs_max) g_tilt_abs_max = ay;

        POINTER_PEN_INFO history[32];
        UINT32 hist_n = 32;
        if (GetPointerPenInfoHistory(pointerId, &hist_n, history)) {
          history_count = static_cast<int>(hist_n);
          // Prefer the max pressure in the history window (smoother + avoids
          // a zero tip sample when mask says pressure is present).
          for (UINT32 i = 0; i < hist_n; ++i) {
            if (!(history[i].penMask & PEN_MASK_PRESSURE)) continue;
            pressure_valid = 1;
            double p = static_cast<double>(history[i].pressure) / 1024.0;
            if (p > pressure) pressure = p;
          }
          if (pressure > 1.0) pressure = 1.0;
        }
        if (pressure > g_pressure_max_seen) {
          g_pressure_max_seen = static_cast<float>(pressure);
        }
      }
    }
    if (message == WM_POINTERUP) {
      flags = 0;
      pressure = 0.0;
      pressure_valid = 0;
    }
  }

  flutter::EncodableMap payload{
      {flutter::EncodableValue("flags"), flutter::EncodableValue(flags)},
      {flutter::EncodableValue("tiltX"), flutter::EncodableValue(tilt_x)},
      {flutter::EncodableValue("tiltY"), flutter::EncodableValue(tilt_y)},
      {flutter::EncodableValue("pressure"), flutter::EncodableValue(pressure)},
      {flutter::EncodableValue("pressureValid"), flutter::EncodableValue(pressure_valid)},
      {flutter::EncodableValue("diagPtr"), flutter::EncodableValue(g_ptr_msgs)},
      {flutter::EncodableValue("diagPen"), flutter::EncodableValue(g_pen_msgs)},
      {flutter::EncodableValue("diagMouse"), flutter::EncodableValue(g_mouse_msgs)},
      {flutter::EncodableValue("diagMsg"), flutter::EncodableValue(g_last_msg)},
      {flutter::EncodableValue("rawPtrFlags"), flutter::EncodableValue(raw_ptr_flags)},
      {flutter::EncodableValue("rawPenFlags"), flutter::EncodableValue(raw_pen_flags)},
      {flutter::EncodableValue("rawPenMask"), flutter::EncodableValue(raw_pen_mask)},
      {flutter::EncodableValue("btnChange"), flutter::EncodableValue(btn_change)},
      {flutter::EncodableValue("orPtrFlags"), flutter::EncodableValue(g_ptr_flags_or)},
      {flutter::EncodableValue("orPenFlags"), flutter::EncodableValue(g_pen_flags_or)},
      {flutter::EncodableValue("orPenMask"), flutter::EncodableValue(g_pen_mask_or)},
      {flutter::EncodableValue("btnChangeLast"), flutter::EncodableValue(g_btn_change_last)},
      {flutter::EncodableValue("tiltAbsMax"), flutter::EncodableValue(g_tilt_abs_max)},
      {flutter::EncodableValue("historyCount"), flutter::EncodableValue(history_count)},
      {flutter::EncodableValue("pressureMaxSeen"),
       flutter::EncodableValue(static_cast<double>(g_pressure_max_seen))},
  };
  g_pen_sink->Success(flutter::EncodableValue(payload));
}
