#include "mpv_core.h"

#include <dwmapi.h>

#include "mpv_container.h"
#include "utils.h"

namespace mpv {

MpvCore* MpvCore::GetInstance() { return instance_.get(); }

void MpvCore::SetInstance(std::unique_ptr<MpvCore> instance) { instance_ = std::move(instance); }

MpvCore::MpvCore(HWND flutter_window) : flutter_window_(flutter_window) {}

MpvCore::~MpvCore() {
  // Close all mpv views.
  for (const auto& [mpv_view, rect] : mpv_views_) {
    ::SendMessage(mpv_view, WM_CLOSE, 0, 0);
  }
  mpv_views_.clear();
}

void MpvCore::EnsureInitialized() {
  // Get container - composition will be enabled in SetVisible() to batch DwmFlush calls
  container_ = MpvContainer::GetInstance()->Get(flutter_window_);
}

void MpvCore::CreateMpvView(HWND mpv_hwnd, RECT rect, double device_pixel_ratio) {
  ::SetParent(mpv_hwnd, container_);
  ::ShowWindow(mpv_hwnd, SW_SHOW);

  // Remove window decorations.
  auto style = ::GetWindowLongPtr(mpv_hwnd, GWL_STYLE);
  style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
  ::SetWindowLongPtr(mpv_hwnd, GWL_STYLE, style);

  device_pixel_ratio_ = device_pixel_ratio;
  mpv_views_[mpv_hwnd] = rect;

  // Position the mpv view behind the Flutter window.
  auto global_rect = GetGlobalRect(rect.left, rect.top, rect.right, rect.bottom);
  ::SetWindowPos(
      mpv_hwnd, flutter_window_, global_rect.left, global_rect.top, global_rect.right - global_rect.left,
      global_rect.bottom - global_rect.top, SWP_NOACTIVATE);
}

void MpvCore::ResizeMpvView(HWND mpv_hwnd, RECT rect) {
  mpv_views_[mpv_hwnd] = rect;
  auto global_rect = GetGlobalRect(rect.left, rect.top, rect.right, rect.bottom);
  // Use MoveWindow to trigger redraw.
  ::MoveWindow(
      mpv_hwnd, global_rect.left, global_rect.top, global_rect.right - global_rect.left,
      global_rect.bottom - global_rect.top, TRUE);
}

void MpvCore::DisposeMpvView(HWND mpv_hwnd) {
  ::SendMessage(mpv_hwnd, WM_CLOSE, 0, 0);
  mpv_views_.erase(mpv_hwnd);
}

void MpvCore::SetVisible(bool visible) {
  visible_ = visible;
  if (container_) {
    if (visible) {
      EnableComposition();
    } else {
      DisableComposition();
    }
  }
}

std::optional<HRESULT> MpvCore::WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
  switch (message) {
    case WM_ACTIVATE: {
      RECT window_rect;
      ::GetWindowRect(flutter_window_, &window_rect);
      // Position container behind Flutter window.
      ::SetWindowPos(
          container_, flutter_window_, window_rect.left, window_rect.top, window_rect.right - window_rect.left,
          window_rect.bottom - window_rect.top, SWP_NOACTIVATE);
      break;
    }
    case WM_SIZE: {
      // Handle Windows's minimize & maximize animations properly.
      // During these transitions, we hide the container and make Flutter opaque,
      // then restore after the animation completes using a Windows timer.
      if (wparam != SIZE_RESTORED || last_wm_size_wparam_ == SIZE_MINIMIZED || last_wm_size_wparam_ == SIZE_MAXIMIZED ||
          was_window_hidden_due_to_minimize_) {
        was_window_hidden_due_to_minimize_ = false;
        DisableComposition();

        // Cancel any pending timer and set a new one.
        ::KillTimer(flutter_window_, kCompositionRestoreTimerId);
        ::SetTimer(flutter_window_, kCompositionRestoreTimerId, kPositionAndShowDelay, nullptr);
      }
      last_wm_size_wparam_ = wparam;
      break;
    }
    case WM_TIMER: {
      if (wparam == kCompositionRestoreTimerId) {
        ::KillTimer(flutter_window_, kCompositionRestoreTimerId);

        // Update container position to match current Flutter window bounds
        RECT window_rect;
        ::GetWindowRect(flutter_window_, &window_rect);
        ::SetWindowPos(
            container_, flutter_window_, window_rect.left, window_rect.top, window_rect.right - window_rect.left,
            window_rect.bottom - window_rect.top, SWP_NOACTIVATE);

        // Restore transparency if video is visible
        if (visible_) {
          EnableComposition();

          // Force a redraw to ensure Flutter's render surface is correctly sized
          ::RedrawWindow(flutter_window_, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
        }
      }
      break;
    }
    case WM_WINDOWPOSCHANGED: {
      RECT window_rect;
      ::GetWindowRect(flutter_window_, &window_rect);
      if (window_rect.right - window_rect.left > 0 && window_rect.bottom - window_rect.top > 0) {
        ::SetWindowPos(
            container_, flutter_window_, window_rect.left, window_rect.top, window_rect.right - window_rect.left,
            window_rect.bottom - window_rect.top, SWP_NOACTIVATE);
        // Window is minimized (negative coordinates).
        if (window_rect.left < 0 && window_rect.top < 0 && window_rect.right < 0 && window_rect.bottom < 0) {
          DisableComposition();
          was_window_hidden_due_to_minimize_ = true;
        }
      }
      break;
    }
    case WM_CLOSE: {
      ::SendMessage(container_, WM_CLOSE, 0, 0);
      for (const auto& [mpv_view, rect] : mpv_views_) {
        ::SendMessage(mpv_view, WM_CLOSE, 0, 0);
      }
      mpv_views_.clear();
      break;
    }
    default:
      break;
  }
  return std::nullopt;
}

RECT MpvCore::GetGlobalRect(int32_t left, int32_t top, int32_t right, int32_t bottom) {
  // Expand client area to prevent transparent gaps.
  left -= static_cast<int32_t>(ceil(device_pixel_ratio_));
  top -= static_cast<int32_t>(ceil(device_pixel_ratio_));
  right += static_cast<int32_t>(ceil(device_pixel_ratio_));
  bottom += static_cast<int32_t>(ceil(device_pixel_ratio_));

  RECT window_rect;
  ::GetClientRect(flutter_window_, &window_rect);
  RECT rect;
  rect.left = window_rect.left + left;
  rect.top = window_rect.top + top;
  rect.right = window_rect.left + right;
  rect.bottom = window_rect.top + bottom;
  return rect;
}

void MpvCore::EnableComposition() {
  ::SetWindowPos(
      flutter_window_, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
  if (!composition_enabled_) {
    SetWindowComposition(flutter_window_, 2, 0);
    composition_enabled_ = true;
  }
  ::ShowWindow(container_, SW_SHOWNOACTIVATE);
  ::DwmFlush();
}

void MpvCore::DisableComposition() {
  SetWindowComposition(flutter_window_, 0, 0);
  composition_enabled_ = false;
  ::ShowWindow(container_, SW_HIDE);
  ::DwmFlush();
}

std::unique_ptr<MpvCore> MpvCore::instance_ = nullptr;

}  // namespace mpv
