#pragma once

#include <algorithm>
#include <memory>
#include <utility>

#include <jsi/jsi.h>

#include "JsiSkNativeObjects.h"
#include "api/third_party/CSSColorParser.h"

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

#include "include/core/SkColor.h"

#pragma clang diagnostic pop

namespace RNSkia {

namespace jsi = facebook::jsi;

/**
 * JsiSkColor is a pure utility class — colors are represented as
 * Float32Arrays on the JS side, so there is no wrapper object.
 */
class JsiSkColor {
public:
  JsiSkColor() = default;

  static jsi::Object toValue(jsi::Runtime &runtime, SkColor color) {
    auto result = runtime.global()
                      .getPropertyAsFunction(runtime, "Float32Array")
                      .callAsConstructor(runtime, 4)
                      .getObject(runtime);
    jsi::ArrayBuffer buffer =
        result
            .getProperty(runtime, jsi::PropNameID::forAscii(runtime, "buffer"))
            .asObject(runtime)
            .getArrayBuffer(runtime);
    auto bfrPtr = reinterpret_cast<float *>(buffer.data(runtime));
    auto color4f = SkColor4f::FromColor(color).array();
    std::copy(color4f.begin(), color4f.end(), bfrPtr);
    return result;
  }

  static SkColor fromValue(jsi::Runtime &runtime, const jsi::Value &obj) {
    const auto &object = obj.asObject(runtime);

    // Handle regular JavaScript arrays
    if (object.isArray(runtime)) {
      auto array = object.asArray(runtime);
      if (array.size(runtime) != 4) {
        throw jsi::JSError(runtime,
                           "Expected array of length 4 for color, got " +
                               std::to_string(array.size(runtime)));
      }
      auto r = array.getValueAtIndex(runtime, 0).asNumber();
      auto g = array.getValueAtIndex(runtime, 1).asNumber();
      auto b = array.getValueAtIndex(runtime, 2).asNumber();
      auto a = array.getValueAtIndex(runtime, 3).asNumber();
      return SkColorSetARGB(a * 255, r * 255, g * 255, b * 255);
    }

    // Handle Float32Array (has buffer property)
    jsi::ArrayBuffer buffer =
        object
            .getProperty(runtime, jsi::PropNameID::forAscii(runtime, "buffer"))
            .asObject(runtime)
            .getArrayBuffer(runtime);
    auto bfrPtr = reinterpret_cast<float *>(buffer.data(runtime));
    if (bfrPtr[0] > 1 || bfrPtr[1] > 1 || bfrPtr[2] > 1 || bfrPtr[3] > 1) {
      return SK_ColorBLACK;
    }
    return SkColorSetARGB(bfrPtr[3] * 255, bfrPtr[0] * 255, bfrPtr[1] * 255,
                          bfrPtr[2] * 255);
  }

  /**
   * Creates the function for construction a new instance of the SkColor
   * wrapper
   * @return A function for creating a new host object wrapper for the SkColor
   * class
   */
  static jsi::HostFunctionType createCtor() {
    return JSI_HOST_FUNCTION_LAMBDA {
      if (arguments[0].isNumber()) {
        return JsiSkColor::toValue(runtime, arguments[0].getNumber());
      } else if (arguments[0].isString()) {
        auto text = arguments[0].asString(runtime).utf8(runtime);
        auto color = CSSColorParser::parse(text);
        if (color.a == -1.0f) {
          return JsiSkColor::toValue(runtime, SK_ColorBLACK);
        }
        return JsiSkColor::toValue(
            runtime, SkColorSetARGB(color.a * 255, color.r, color.g, color.b));
      } else if (arguments[0].isObject()) {
        auto obj = arguments[0].getObject(runtime);

        // Check if it's a regular array - convert to Float32Array
        if (obj.isArray(runtime)) {
          auto arr = obj.getArray(runtime);
          if (arr.size(runtime) != 4) {
            throw jsi::JSError(runtime,
                               "Expected array of length 4 for color, got " +
                                   std::to_string(arr.size(runtime)));
          }
          auto r =
              static_cast<float>(arr.getValueAtIndex(runtime, 0).asNumber());
          auto g =
              static_cast<float>(arr.getValueAtIndex(runtime, 1).asNumber());
          auto b =
              static_cast<float>(arr.getValueAtIndex(runtime, 2).asNumber());
          auto a =
              static_cast<float>(arr.getValueAtIndex(runtime, 3).asNumber());

          // Create Float32Array and populate
          auto result = runtime.global()
                            .getPropertyAsFunction(runtime, "Float32Array")
                            .callAsConstructor(runtime, 4)
                            .getObject(runtime);
          jsi::ArrayBuffer buffer =
              result
                  .getProperty(runtime,
                               jsi::PropNameID::forAscii(runtime, "buffer"))
                  .asObject(runtime)
                  .getArrayBuffer(runtime);
          auto bfrPtr = reinterpret_cast<float *>(buffer.data(runtime));
          bfrPtr[0] = r;
          bfrPtr[1] = g;
          bfrPtr[2] = b;
          bfrPtr[3] = a;
          return result;
        }

        // Already a Float32Array or similar - return as-is
        return obj;
      }
      return jsi::Value::undefined();
    };
  }
};
} // namespace RNSkia
