#pragma once

#include <jsi/jsi.h>
#include <memory>
#include <utility>
#include <vector>

#include "JsiSkNativeObjects.h"
#include "JsiSkRect.h"

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

#include "include/core/SkVertices.h"

#pragma clang diagnostic pop

namespace RNSkia {

namespace jsi = facebook::jsi;

class JsiSkVertices
    : public JsiSkWrappingSkPtrNativeObject<JsiSkVertices, SkVertices> {
public:
  static constexpr const char *CLASS_NAME = "Vertices";

  JsiSkVertices(std::shared_ptr<RNSkPlatformContext> context,
                sk_sp<SkVertices> vertices)
      : JsiSkWrappingSkPtrNativeObject<JsiSkVertices, SkVertices>(
            std::move(context), std::move(vertices)) {}

  std::shared_ptr<JsiSkRect> bounds() {
    const auto &result = getObject()->bounds();
    return std::make_shared<JsiSkRect>(getContext(), result);
  }

  double uniqueID() { return static_cast<double>(getObject()->uniqueID()); }

  size_t getMemoryPressure() override {
    auto vertices = getObject();
    if (!vertices)
      return 0;

    // SkVertices provides approximateBytesUsed() to estimate memory usage
    return vertices->approximateSize();
  }

  static sk_sp<SkVertices> fromValue(jsi::Runtime &runtime,
                                     const jsi::Value &obj) {
    return objectFromValue(runtime, obj);
  }

  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
    installCommon(runtime, prototype);
    installMethod(runtime, prototype, "bounds", &JsiSkVertices::bounds);
    installMethod(runtime, prototype, "uniqueID", &JsiSkVertices::uniqueID);
  }

  /**
   * Creates the function for construction a new instance of the SkVertices
   * wrapper
   * @param context platform context
   * @return A function for creating a new host object wrapper for the
   * SkVertices class
   */
  static const jsi::HostFunctionType
  createCtor(std::shared_ptr<RNSkPlatformContext> context) {
    return JSI_HOST_FUNCTION_LAMBDA {
      auto mode = static_cast<SkVertices::VertexMode>(arguments[0].getNumber());
      std::vector<SkPoint> positions;
      std::vector<SkPoint> texs;
      std::vector<SkColor> colors;
      std::vector<uint16_t> indices;

      auto jsiPositions = arguments[1].asObject(runtime).asArray(runtime);
      auto positionsSize = static_cast<int>(jsiPositions.size(runtime));
      positions.reserve(positionsSize);
      for (int i = 0; i < positionsSize; i++) {
        std::shared_ptr<SkPoint> point = JsiSkPoint::fromValue(
            runtime,
            jsiPositions.getValueAtIndex(runtime, i).asObject(runtime));
        positions.push_back(*point.get());
      }

      if (count >= 3 && !arguments[2].isNull() && !arguments[2].isUndefined()) {
        auto jsiTexs = arguments[2].asObject(runtime).asArray(runtime);
        auto texsSize = jsiTexs.size(runtime);
        if (texsSize != positionsSize) {
          throw jsi::JSError(runtime, "The number of texture coordinates must "
                                      "match the number of positions");
        }
        texs.reserve(texsSize);
        for (int i = 0; i < texsSize; i++) {
          auto point = JsiSkPoint::fromValue(
              runtime, jsiTexs.getValueAtIndex(runtime, i).asObject(runtime));
          texs.push_back(*point.get());
        }
      }

      if (count >= 4 && !arguments[3].isNull() && !arguments[3].isUndefined()) {
        auto jsiColors = arguments[3].asObject(runtime).asArray(runtime);
        auto colorsSize = jsiColors.size(runtime);
        if (colorsSize != positionsSize) {
          throw jsi::JSError(
              runtime,
              "The number of colors must match the number of positions");
        }
        colors.reserve(colorsSize);
        for (int i = 0; i < colorsSize; i++) {
          SkColor color = JsiSkColor::fromValue(
              runtime, jsiColors.getValueAtIndex(runtime, i));
          colors.push_back(color);
        }
      }

      int indicesSize = 0;
      if (count >= 5 && !arguments[4].isNull() && !arguments[4].isUndefined()) {
        auto jsiIndices = arguments[4].asObject(runtime).asArray(runtime);
        indicesSize = static_cast<int>(jsiIndices.size(runtime));
        indices.reserve(indicesSize);
        for (int i = 0; i < indicesSize; i++) {
          uint16_t index = jsiIndices.getValueAtIndex(runtime, i).asNumber();
          indices.push_back(index);
        }
      }
      // TODO: this is the technic used in CanvasKit:
      // https://github.com/google/skia/blob/main/modules/canvaskit/interface.js#L1216
      // Note that the isVolatile parameter is unused when using MakeCopy()
      //                auto isVolatile = count >= 6 && !arguments[5].isNull()
      //                && !arguments[5].isUndefined() ? arguments[5].getBool()
      //                : false; auto flags = 0;
      //                // These flags are from SkVertices.h and should be kept
      //                in sync with those. if (texs.size() > 0) {
      //                    flags |= (1 << 0);
      //                }
      //                if (colors.size() > 0) {
      //                    flags |= (1 << 1);
      //                }
      //                if (!isVolatile) {
      //                    flags |= (1 << 2);
      //                }
      //                auto builder = SkVertices::Builder(mode,
      //                positionsSize/2, indicesSize, flags);
      //                std::copy(positions.data(), positions.data() +
      //                positionsSize, builder.positions()); if
      //                (builder.texCoords()) {
      //                    std::copy(std::begin(texs), std::end(texs),
      //                    builder.texCoords());
      //                }
      //                if (builder.colors()) {
      //                    std::copy(std::begin(colors), std::end(colors),
      //                    builder.colors());
      //                }
      //                if (builder.indices()) {
      //                    std::copy(std::begin(indices), std::end(indices),
      //                    builder.indices());
      //                }
      //                auto vertices = builder.detach();
      auto vertices =
          SkVertices::MakeCopy(mode, positionsSize, positions.data(),
                               texs.size() > 0 ? texs.data() : nullptr,
                               colors.size() > 0 ? colors.data() : nullptr,
                               indicesSize, indices.data());
      return makeJsiObject(runtime, std::make_shared<JsiSkVertices>(
                                        context, std::move(vertices)));
    };
  }
};
} // namespace RNSkia
