// Aseprite UI Library
// Copyright (C) 2020-2025  Igara Studio S.A.
// Copyright (C) 2001-2017  David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif

#include "gfx/size.h"
#include "ui/graphics.h"
#include "ui/intern.h"
#include "ui/size_hint_event.h"
#include "ui/theme.h"
#include "ui/ui.h"

namespace ui {

using namespace gfx;

PopupWindow::PopupWindow(const std::string& text,
                         const ClickBehavior clickBehavior,
                         const EnterBehavior enterBehavior,
                         const bool withCloseButton)
  : Window(text.empty() ? WithoutTitleBar : WithTitleBar, text)
  , m_clickBehavior(clickBehavior)
  , m_enterBehavior(enterBehavior)
{
  setSizeable(false);
  setMoveable(false);
  setWantFocus(false);
  setAlign(LEFT | TOP);

  if (!withCloseButton) {
    // Remove close button
    for (auto child : children()) {
      if (child->type() == kWindowCloseButtonWidget) {
        delete child;
        break;
      }
    }
  }

  initTheme();
}

PopupWindow::~PopupWindow()
{
  stopFilteringMessages();
}

void PopupWindow::setHotRegion(const gfx::Region& screenRegion)
{
  startFilteringMessages();

  m_hotRegion = screenRegion;
}

void PopupWindow::setClickBehavior(ClickBehavior behavior)
{
  m_clickBehavior = behavior;
}

void PopupWindow::setEnterBehavior(EnterBehavior behavior)
{
  m_enterBehavior = behavior;
}

void PopupWindow::makeFloating()
{
  stopFilteringMessages();
  setMoveable(true);
  m_fixed = false;

  onMakeFloating();
}

void PopupWindow::makeFixed()
{
  startFilteringMessages();
  setMoveable(false);
  m_fixed = true;

  onMakeFixed();
}

bool PopupWindow::onProcessMessage(Message* msg)
{
  switch (msg->type()) {
    // There are cases where startFilteringMessages() is called when a
    // kCloseMessage for this same PopupWindow is enqueued. Processing
    // the kOpenMessage we ensure that the popup will be filtering
    // messages if it's needed when it's visible (as kCloseMessage and
    // kOpenMessage must be enqueued in the correct order).
    case kOpenMessage:
      if (!isMoveable())
        startFilteringMessages();
      break;

    case kCloseMessage: stopFilteringMessages(); break;

    case kCloseDisplayMessage:
      // Close the popup when the main native window (os::Window) is closed.
      closeWindow(nullptr);
      break;

    case kMouseLeaveMessage:
      if (m_hotRegion.isEmpty() && m_fixed)
        closeWindow(nullptr);
      break;

    case kKeyDownMessage:
      if (m_filtering) {
        KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
        KeyScancode scancode = keymsg->scancode();

        if (scancode == kKeyEsc)
          closeWindow(nullptr);

        if (m_enterBehavior == EnterBehavior::CloseOnEnter &&
            (scancode == kKeyEnter || scancode == kKeyEnterPad)) {
          closeWindow(this);
          return true;
        }

        // If the message came from a filter, we don't send it back to
        // the default Window processing (which will send the message
        // to the Manager). In this way, the focused children can
        // process the kKeyDownMessage.
        if (msg->fromFilter())
          return false;
      }
      break;

    case kMouseDownMessage:
      if (m_filtering && msg->display()) {
        auto mouseMsg = static_cast<const MouseMessage*>(msg);
        gfx::Point screenPos = mouseMsg->screenPosition();

        switch (m_clickBehavior) {
          // If the user click outside the window, we have to close
          // the tooltip window.
          case ClickBehavior::CloseOnClickInOtherWindow: {
            Widget* picked = pickFromScreenPos(screenPos);
            if (!picked || picked->window() != this) {
              closeWindow(nullptr);
            }
            break;
          }

          case ClickBehavior::CloseOnClickOutsideHotRegion: {
            // Convert the mousePos from display() coordinates to screen
            if (!m_hotRegion.contains(screenPos)) {
              closeWindow(nullptr);
            }
            break;
          }
        }
      }
      break;

    case kMouseMoveMessage:
      if (m_fixed && !m_hotRegion.isEmpty() && manager()->getCapture() == nullptr &&
          msg->display()) {
        gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
        gfx::Point screenPos = msg->display()->nativeWindow()->pointToScreen(mousePos);

        // If the mouse is outside the hot-region we have to close the
        // window.
        if (!m_hotRegion.contains(screenPos))
          closeWindow(nullptr);
      }
      break;
  }

  return Window::onProcessMessage(msg);
}

void PopupWindow::onHitTest(HitTestEvent& ev)
{
  Window::onHitTest(ev);

  Widget* picked = manager()->pick(ev.point());
  if (picked) {
    WidgetType type = picked->type();
    if (type == kWindowWidget && picked == this) {
      if (isSizeable() && (ev.hit() == HitTestBorderNW || ev.hit() == HitTestBorderN ||
                           ev.hit() == HitTestBorderNE || ev.hit() == HitTestBorderE ||
                           ev.hit() == HitTestBorderSE || ev.hit() == HitTestBorderS ||
                           ev.hit() == HitTestBorderSW || ev.hit() == HitTestBorderW)) {
        // Use the hit value from Window::onHitTest()
        return;
      }
      else {
        ev.setHit(isMoveable() ? HitTestCaption : HitTestClient);
      }
    }
    else if (type == kBoxWidget || type == kLabelWidget || type == kLinkLabelWidget ||
             type == kGridWidget || type == kSeparatorWidget) {
      ev.setHit(isMoveable() ? HitTestCaption : HitTestClient);
    }
  }
}

void PopupWindow::startFilteringMessages()
{
  if (!m_filtering) {
    m_filtering = true;

    Manager* manager = Manager::getDefault();
    manager->addMessageFilter(kCloseDisplayMessage, this);
    manager->addMessageFilter(kMouseMoveMessage, this);
    manager->addMessageFilter(kMouseDownMessage, this);
    manager->addMessageFilter(kKeyDownMessage, this);
  }
}

void PopupWindow::stopFilteringMessages()
{
  if (m_filtering) {
    m_filtering = false;

    Manager* manager = Manager::getDefault();
    manager->removeMessageFilter(kCloseDisplayMessage, this);
    manager->removeMessageFilter(kMouseMoveMessage, this);
    manager->removeMessageFilter(kMouseDownMessage, this);
    manager->removeMessageFilter(kKeyDownMessage, this);
  }
}

void PopupWindow::onMakeFloating()
{
  // Do nothing
}

void PopupWindow::onMakeFixed()
{
  // Do nothing
}

TransparentPopupWindow::TransparentPopupWindow(ClickBehavior clickBehavior)
  : PopupWindow("", clickBehavior)
{
  setTransparent(true);
  initTheme();
}

void TransparentPopupWindow::onInitTheme(InitThemeEvent& ev)
{
  PopupWindow::onInitTheme(ev);
  // TODO fix this, if we use alpha=0 (gfx::ColorNone), we get
  // "window_face" color as background the transparent popup window.
  // setBgColor(gfx::ColorNone);
  setBgColor(gfx::rgba(0, 0, 0, 1));
}

} // namespace ui
