#pragma once

#include "JsiSkRSXform.h"
#include "NodeProp.h"
#include "PointProp.h"

#include <memory>
#include <utility>
#include <vector>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation"

#include "include/core/SkRect.h"

#pragma clang diagnostic pop

namespace RNSkia {

static PropId PropNameRect = JsiPropId::get("rect");
static PropId PropNameWidth = JsiPropId::get("width");
static PropId PropNameHeight = JsiPropId::get("height");

/**
 Reads a rect from a given propety in the node. The name of the property is
 provided on the constructor. The property can either be a Javascript property
 or a host object representing an SkRect.
 */
class RectProp : public DerivedProp<SkRect> {
public:
  explicit RectProp(PropId name,
                    const std::function<void(BaseNodeProp *)> &onChange)
      : DerivedProp(onChange) {
    _prop = defineProperty<NodeProp>(name);
  }

  static std::shared_ptr<SkRect> processRect(const JsiValue &value) {
    if (value.getType() == PropType::HostObject) {
      auto rectPtr =
          std::dynamic_pointer_cast<JsiSkRect>(value.getAsHostObject());
      if (rectPtr != nullptr) {
        return std::make_shared<SkRect>(SkRect::MakeXYWH(
            rectPtr->getObject()->x(), rectPtr->getObject()->y(),
            rectPtr->getObject()->width(), rectPtr->getObject()->height()));
      }
    } else if (value.getType() == PropType::Object &&
               value.hasValue(PropNameX) && value.hasValue(PropNameY) &&
               value.hasValue(PropNameWidth) &&
               value.hasValue(PropNameHeight)) {
      // Save props for fast access
      auto x = value.getValue(PropNameX);
      auto y = value.getValue(PropNameY);
      auto width = value.getValue(PropNameWidth);
      auto height = value.getValue(PropNameHeight);
      // Update cache from js object value
      return std::make_shared<SkRect>(
          SkRect::MakeXYWH(x.getAsNumber(), y.getAsNumber(),
                           width.getAsNumber(), height.getAsNumber()));
    }
    return nullptr;
  }

  void updateDerivedValue() override {
    if (_prop->isSet()) {
      setDerivedValue(RectProp::processRect(_prop->value()));
    }
  }

private:
  NodeProp *_prop;
};

/**
 Reads rect properties from a node's properties
 */
class RectPropFromProps : public DerivedProp<SkRect> {
public:
  explicit RectPropFromProps(
      const std::function<void(BaseNodeProp *)> &onChange)
      : DerivedProp<SkRect>(onChange) {
    _x = defineProperty<NodeProp>(PropNameX);
    _y = defineProperty<NodeProp>(PropNameY);
    _width = defineProperty<NodeProp>(PropNameWidth);
    _height = defineProperty<NodeProp>(PropNameHeight);
  }

  void updateDerivedValue() override {
    if (_width->isSet() && _height->isSet()) {
      auto x = 0.0;
      auto y = 0.0;
      if (_x->isSet()) {
        x = _x->value().getAsNumber();
      }
      if (_y->isSet()) {
        y = _y->value().getAsNumber();
      }
      setDerivedValue(SkRect::MakeXYWH(x, y, _width->value().getAsNumber(),
                                       _height->value().getAsNumber()));
    }
  }

private:
  NodeProp *_x;
  NodeProp *_y;
  NodeProp *_width;
  NodeProp *_height;
};

/**
 Reads rect props from either a given property or from the property object
 itself.
 */
class RectProps : public DerivedProp<SkRect> {
public:
  explicit RectProps(PropId name,
                     const std::function<void(BaseNodeProp *)> &onChange)
      : DerivedProp<SkRect>(onChange) {
    _rectProp = defineProperty<RectProp>(name);
    _rectPropFromProps = defineProperty<RectPropFromProps>();
  }

  void updateDerivedValue() override {
    if (_rectProp->isSet()) {
      setDerivedValue(_rectProp->getUnsafeDerivedValue());
    } else if (_rectPropFromProps->isSet()) {
      setDerivedValue(_rectPropFromProps->getUnsafeDerivedValue());
    } else {
      setDerivedValue(nullptr);
    }
  }

private:
  RectProp *_rectProp;
  RectPropFromProps *_rectPropFromProps;
};

class RectsProp : public DerivedProp<std::vector<SkRect>> {
public:
  explicit RectsProp(PropId name,
                     const std::function<void(BaseNodeProp *)> &onChange)
      : DerivedProp<std::vector<SkRect>>(onChange) {
    _rectsProp = defineProperty<NodeProp>(name);
  }

  void updateDerivedValue() override {
    if (_rectsProp->isSet()) {
      auto rects = _rectsProp->value().getAsArray();
      std::vector<SkRect> derivedRects;
      derivedRects.reserve(rects.size());

      for (size_t i = 0; i < rects.size(); ++i) {
        derivedRects.push_back(*RectProp::processRect(rects[i]));
      }
      setDerivedValue(std::move(derivedRects));
    } else {
      setDerivedValue(nullptr);
    }
  }

private:
  NodeProp *_rectsProp;
};

} // namespace RNSkia
