#include "shell/hot_corners/hot_corners.h"

#include "app/application.h"
#include "config/config_service.h"
#include "render/scene/input_area.h"
#include "wayland/wayland_connection.h"

HotCorners::HotCorners(Application* app) : m_app(app) {}

HotCorners::~HotCorners() { destroySurfaces(); }

void HotCorners::initialize(WaylandConnection& wayland, ConfigService* config, RenderContext* renderContext) {
  m_wayland = &wayland;
  m_config = config;
  m_renderContext = renderContext;

  onOutputChange();
}

void HotCorners::onConfigReload() {
  const auto& config = m_config->config().hotCorners;
  if (config.enabled != m_lastEnabled) {
    onOutputChange();
  }
}

void HotCorners::onOutputChange() {
  if (m_config == nullptr || m_wayland == nullptr) {
    return;
  }
  const auto& config = m_config->config().hotCorners;
  m_lastEnabled = config.enabled;

  destroySurfaces();

  if (!config.enabled) {
    return;
  }

  ensureSurfaces();
}

void HotCorners::ensureSurfaces() {
  for (const auto& out : m_wayland->outputs()) {
    if (!out.done || out.connectorName.empty()) {
      continue;
    }

    auto instance = std::make_unique<OutputInstance>();
    instance->output = out.output;

    buildCorner(instance->topLeft, 0, out.output);
    buildCorner(instance->topRight, 1, out.output);
    buildCorner(instance->bottomLeft, 2, out.output);
    buildCorner(instance->bottomRight, 3, out.output);

    m_instances.push_back(std::move(instance));
  }
}

void HotCorners::destroySurfaces() { m_instances.clear(); }

void HotCorners::triggerAction(const std::string& action, const std::string& command, wl_output* output) {
  if (action == "command") {
    if (!command.empty()) {
      m_app->runUserCommand(command);
    }
  } else if (action != "none" && !action.empty()) {
    m_app->triggerShellAction(action, output);
  }
}

void HotCorners::buildCorner(Corner& corner, int position, wl_output* output) {
  LayerShellLayer layer = LayerShellLayer::Bottom;

  std::uint32_t anchor = 0;
  std::string cornerKey;
  if (position == 0) {
    anchor = LayerShellAnchor::Top | LayerShellAnchor::Left;
    cornerKey = "top_left";
  } else if (position == 1) {
    anchor = LayerShellAnchor::Top | LayerShellAnchor::Right;
    cornerKey = "top_right";
  } else if (position == 2) {
    anchor = LayerShellAnchor::Bottom | LayerShellAnchor::Left;
    cornerKey = "bottom_left";
  } else {
    anchor = LayerShellAnchor::Bottom | LayerShellAnchor::Right;
    cornerKey = "bottom_right";
  }

  LayerSurfaceConfig surfaceConfig{
      .nameSpace = "hot_corner_" + cornerKey,
      .layer = layer,
      .anchor = anchor,
      .width = 1,
      .height = 1,
      .exclusiveZone = 0,
  };

  corner.surface = std::make_unique<LayerSurface>(*m_wayland, surfaceConfig);
  corner.surface->initialize(output);

  auto inputArea = std::make_unique<InputArea>();
  // 1x1 region
  inputArea->setPosition(0, 0);
  inputArea->setSize(1, 1);
  inputArea->setOnEnter([this, position, output](const InputArea::PointerData&) {
    const auto& config = m_config->config().hotCorners;
    std::string action;
    std::string command;
    if (position == 0) {
      action = config.topLeft.action;
      command = config.topLeft.command;
    } else if (position == 1) {
      action = config.topRight.action;
      command = config.topRight.command;
    } else if (position == 2) {
      action = config.bottomLeft.action;
      command = config.bottomLeft.command;
    } else {
      action = config.bottomRight.action;
      command = config.bottomRight.command;
    }
    triggerAction(action, command, output);
  });

  corner.sceneRoot = std::move(inputArea);
  corner.inputDispatcher.setSceneRoot(corner.sceneRoot.get());

  corner.surface->setRenderContext(m_renderContext);
  corner.surface->setSceneRoot(corner.sceneRoot.get());
  corner.surface->requestRedraw();
}

bool HotCorners::onPointerEvent(const PointerEvent& event) {
  if (!m_lastEnabled || event.surface == nullptr) {
    return false;
  }

  for (const auto& instance : m_instances) {
    Corner* corners[] = {&instance->topLeft, &instance->topRight, &instance->bottomLeft, &instance->bottomRight};
    for (auto* corner : corners) {
      if (corner->surface && event.surface == corner->surface->wlSurface()) {
        switch (event.type) {
        case PointerEvent::Type::Enter:
          corner->inputDispatcher.pointerEnter(
              static_cast<float>(event.sx), static_cast<float>(event.sy), event.serial
          );
          return true;
        case PointerEvent::Type::Leave:
          corner->inputDispatcher.pointerLeave();
          return true;
        case PointerEvent::Type::Motion:
          corner->inputDispatcher.pointerMotion(
              static_cast<float>(event.sx), static_cast<float>(event.sy), event.serial
          );
          return true;
        case PointerEvent::Type::Button:
          return corner->inputDispatcher.pointerButton(
              static_cast<float>(event.sx), static_cast<float>(event.sy), event.button, event.state == 1
          );
        case PointerEvent::Type::Axis:
          return corner->inputDispatcher.pointerAxis(
              static_cast<float>(event.sx), static_cast<float>(event.sy), event.axis, event.axisSource, event.axisValue,
              event.axisDiscrete, event.axisValue120, event.axisLines
          );
        }
        return false;
      }
    }
  }
  return false;
}
