#pragma once

#include <frostbite2D/2d/actor.h>

#include <memory>
#include <string>
#include <utility>

namespace qjs {
class Value;
}

namespace frostbite2D {

/**
 * @brief Basic bridge object between QuickJS and engine Vec2.
 */
class JsVec2 {
public:
  double x = 0.0;
  double y = 0.0;

  JsVec2() = default;
  JsVec2(double xValue, double yValue) : x(xValue), y(yValue) {}
  explicit JsVec2(const Vec2& value) : x(value.x), y(value.y) {}

  Vec2 toNative() const {
    return Vec2(static_cast<float>(x), static_cast<float>(y));
  }
};

/**
 * @brief Basic bridge base between QuickJS and engine Actor.
 *
 * This type only exposes the smallest stable surface that all script-side
 * Actor wrappers need, so engine bindings and Game-side bindings can share it.
 */
class JsActor {
public:
  JsActor() : actor_(MakePtr<Actor>()) {}
  explicit JsActor(Ptr<Actor> actor) : actor_(std::move(actor)) {
    if (!actor_) {
      actor_ = MakePtr<Actor>();
    }
  }

  std::string GetName() const { return actor_->GetName(); }
  void SetName(const std::string& name) { actor_->SetName(name); }

  std::shared_ptr<JsVec2> GetPosition() const {
    return std::make_shared<JsVec2>(actor_->GetPosition());
  }
  void SetPosition(const std::shared_ptr<JsVec2>& position) {
    if (position) {
      actor_->SetPosition(position->toNative());
    }
  }
  void SetPositionXY(double x, double y) {
    actor_->SetPosition(static_cast<float>(x), static_cast<float>(y));
  }

  double GetRotation() const { return actor_->GetRotation(); }
  void SetRotation(double rotation) {
    actor_->SetRotation(static_cast<float>(rotation));
  }

  std::shared_ptr<JsVec2> GetScale() const {
    return std::make_shared<JsVec2>(actor_->GetScale());
  }
  void SetScale(const std::shared_ptr<JsVec2>& scale) {
    if (scale) {
      actor_->SetScale(scale->toNative());
    }
  }
  void SetScaleValue(double scale) {
    actor_->SetScale(static_cast<float>(scale));
  }

  bool IsVisible() const { return actor_->IsVisible(); }
  void SetVisible(bool visible) { actor_->SetVisible(visible); }

  void AddChild(const std::shared_ptr<JsActor>& child) {
    if (!actor_ || !child) {
      return;
    }
    actor_->AddChild(child->native());
  }

  void RemoveAllChildren() {
    if (actor_) {
      actor_->RemoveAllChildren();
    }
  }

  std::shared_ptr<JsVec2> GetSize() const {
    return std::make_shared<JsVec2>(actor_->GetSize());
  }
  void SetSize(const std::shared_ptr<JsVec2>& size) {
    if (size) {
      actor_->SetSize(size->toNative());
    }
  }
  void SetSizeWH(double width, double height) {
    actor_->SetSize(static_cast<float>(width), static_cast<float>(height));
  }

  std::shared_ptr<JsVec2> GetAnchor() const {
    return std::make_shared<JsVec2>(actor_->GetAnchor());
  }
  void SetAnchor(const std::shared_ptr<JsVec2>& anchor) {
    if (anchor) {
      actor_->SetAnchor(anchor->toNative());
    }
  }
  void SetAnchorXY(double x, double y) {
    actor_->SetAnchor(static_cast<float>(x), static_cast<float>(y));
  }

  std::shared_ptr<JsActor> GetParent() const;
  std::shared_ptr<JsVec2> GetTopLeftPosition() const;
  void SetTopLeftPosition(const std::shared_ptr<JsVec2>& position);
  void SetTopLeftPositionXY(double x, double y);
  std::shared_ptr<JsVec2> LocalToWorld(const std::shared_ptr<JsVec2>& point) const;
  std::shared_ptr<JsVec2> WorldToLocal(const std::shared_ptr<JsVec2>& point) const;
  bool ContainsWorldPoint(double x, double y) const;

  bool IsEventReceiveEnabled() const {
    return actor_ && actor_->IsEventReceiveEnabled();
  }
  void EnableEventReceive() {
    if (actor_) {
      actor_->EnableEventReceive();
    }
  }
  void DisableEventReceive() {
    if (actor_) {
      actor_->DisableEventReceive();
    }
  }
  uint32 AddEventListener(const std::string& type,
                          std::function<bool(qjs::Value)> callback);
  bool RemoveEventListener(uint32 listenerId);
  void ClearEventListeners();

  const Ptr<Actor>& native() const { return actor_; }

protected:
  Ptr<Actor> actor_;
};

} // namespace frostbite2D
