#import "RNSkApplePlatformContext.h"

#import <CoreMedia/CMSampleBuffer.h>
#include <Metal/Metal.h>
#import <React/RCTUtils.h>
#include <algorithm>
#include <set>
#include <thread>
#include <utility>

#if defined(SK_GRAPHITE)
#include "RNDawnContext.h"
#else
#include "MetalContext.h"
#endif
#include "RNSkAppleVideo.h"

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

#include "include/core/SkBlendMode.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#import "include/core/SkColorSpace.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkPaint.h"
#include "include/core/SkSamplingOptions.h"
#include "include/core/SkSurface.h"

#include "include/ports/SkFontMgr_mac_ct.h"

#pragma clang diagnostic pop

namespace RNSkia {

void RNSkApplePlatformContext::performStreamOperation(
    const std::string &sourceUri,
    const std::function<void(std::unique_ptr<SkStreamAsset>)> &op) {

  auto loader = [=]() {
    NSURL *url = [[NSURL alloc]
        initWithString:[NSString stringWithUTF8String:sourceUri.c_str()]];

    NSData *data = nullptr;
    auto scheme = url.scheme;
    auto extension = url.pathExtension;

    if (scheme == nullptr &&
        (extension == nullptr || [extension isEqualToString:@""])) {
      // If the extension and scheme is nil, we assume that we're trying to
      // load from the embedded iOS app bundle and will try to load image
      // and get data from the image directly. imageNamed will return the
      // best version of the requested image:
#if !TARGET_OS_OSX
      auto image = [UIImage imageNamed:[url absoluteString]];
#else
      auto image = [NSImage imageNamed:[url absoluteString]];
#endif // !TARGET_OS_OSX
      // We don't know the image format (png, jpg, etc) but
      // UIImagePNGRepresentation will support all of them
      data = UIImagePNGRepresentation(image);
    } else {
      // Load from metro / node
      data = [NSData dataWithContentsOfURL:url];
    }

    auto bytes = [data bytes];
    auto skData = SkData::MakeWithCopy(bytes, [data length]);
    auto stream = SkMemoryStream::Make(skData);

    op(std::move(stream));
  };

  // Fire and forget the thread - will be resolved on completion
  std::thread(loader).detach();
}

void RNSkApplePlatformContext::releaseNativeBuffer(uint64_t pointer) {
  CVPixelBufferRef pixelBuffer = reinterpret_cast<CVPixelBufferRef>(pointer);
  if (pixelBuffer) {
    CFRelease(pixelBuffer);
  }
}

uint64_t RNSkApplePlatformContext::makeNativeBuffer(sk_sp<SkImage> image) {
#if defined(SK_GRAPHITE)
  // A Graphite GPU texture can't be read with readPixels(nullptr) (and can't be
  // drawn onto a raster surface) — both yield uninitialized/black pixels. Read
  // it back to a raster image first. (JsiNativeBuffer calls the Ganesh-only
  // SkImage::makeNonTextureImage(), which is a no-op on Graphite.)
  if (image && image->isTextureBacked()) {
    image = DawnContext::getInstance().MakeRasterImage(image);
  }
#endif
  // 0. If Image is not in BGRA, convert to BGRA as only BGRA is supported.
  if (image->colorType() != kBGRA_8888_SkColorType) {
    const SkImageInfo bgraInfo =
        SkImageInfo::Make(image->dimensions(), kBGRA_8888_SkColorType,
                          kPremul_SkAlphaType, SkColorSpace::MakeSRGB());
    auto surface = SkSurfaces::Raster(bgraInfo);
    if (!surface) {
      throw std::runtime_error(
          "Failed to allocate raster surface for BGRA conversion");
    }
    SkCanvas *canvas = surface->getCanvas();
    canvas->clear(SK_ColorTRANSPARENT);
    SkPaint paint;
    paint.setBlendMode(SkBlendMode::kSrc);
    canvas->drawImage(image.get(), 0.0f, 0.0f, SkSamplingOptions(), &paint);
    auto bgraImage = surface->makeImageSnapshot();
    if (bgraImage == nullptr) {
      throw std::runtime_error(
          "Failed to convert image to BGRA_8888 colortype! Only BGRA_8888 "
          "NativeBuffers are supported.");
    }
    image = std::move(bgraImage);
  }

  // 1. Get image info
  auto bytesPerPixel = image->imageInfo().bytesPerPixel();
  int bytesPerRow = image->width() * bytesPerPixel;
  auto buf = SkData::MakeUninitialized(image->width() * image->height() *
                                       bytesPerPixel);
  SkImageInfo info = SkImageInfo::Make(image->width(), image->height(),
                                       image->colorType(), image->alphaType());
  // 2. Copy pixels into our buffer
  image->readPixels(nullptr, info, const_cast<void *>(buf->data()), bytesPerRow,
                    0, 0);

  // 3. Create an IOSurface (GPU + CPU memory)
  CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
      kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
      &kCFTypeDictionaryValueCallBacks);
  int width = image->width();
  int height = image->height();
  int pitch = width * bytesPerPixel;
  int size = width * height * bytesPerPixel;
  OSType pixelFormat = kCVPixelFormatType_32BGRA;
  CFDictionarySetValue(
      dict, kIOSurfaceBytesPerRow,
      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pitch));
  CFDictionarySetValue(
      dict, kIOSurfaceBytesPerElement,
      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bytesPerPixel));
  CFDictionarySetValue(
      dict, kIOSurfaceWidth,
      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &width));
  CFDictionarySetValue(
      dict, kIOSurfaceHeight,
      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &height));
  CFDictionarySetValue(
      dict, kIOSurfacePixelFormat,
      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pixelFormat));
  CFDictionarySetValue(
      dict, kIOSurfaceAllocSize,
      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &size));
  IOSurfaceRef surface = IOSurfaceCreate(dict);
  if (surface == nil) {
    throw std::runtime_error("Failed to create " + std::to_string(width) + "x" +
                             std::to_string(height) + " IOSurface!");
  }

  // 4. Copy over the memory from the pixels into the IOSurface
  IOSurfaceLock(surface, 0, nil);
  void *base = IOSurfaceGetBaseAddress(surface);
  memcpy(base, buf->data(), buf->size());
  IOSurfaceUnlock(surface, 0, nil);

  // 5. Create a CVPixelBuffer from the IOSurface
  CVPixelBufferRef pixelBuffer = nullptr;
  CVReturn result =
      CVPixelBufferCreateWithIOSurface(nil, surface, nil, &pixelBuffer);
  if (result != kCVReturnSuccess) {
    throw std::runtime_error(
        "Failed to create CVPixelBuffer from SkImage! Return value: " +
        std::to_string(result));
  }

  // 8. Return CVPixelBuffer casted to uint64_t
  return reinterpret_cast<uint64_t>(pixelBuffer);
}

uint64_t RNSkApplePlatformContext::makeTestNativeBuffer(int width, int height) {
  // Allocate a BGRA IOSurface and fill it with a procedural test pattern (RGB
  // gradient + diagonal stripes), entirely on the CPU. No GPU / SkImage round
  // trip, so this works the same on every backend.
  const int bytesPerElement = 4;
  const int pitch = width * bytesPerElement;
  const int allocSize = width * height * bytesPerElement;
  OSType pixelFormat = kCVPixelFormatType_32BGRA;
  CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
      kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
      &kCFTypeDictionaryValueCallBacks);
  auto setInt = [&](CFStringRef key, int value) {
    CFNumberRef num =
        CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
    CFDictionarySetValue(dict, key, num);
    CFRelease(num);
  };
  setInt(kIOSurfaceWidth, width);
  setInt(kIOSurfaceHeight, height);
  setInt(kIOSurfaceBytesPerRow, pitch);
  setInt(kIOSurfaceBytesPerElement, bytesPerElement);
  setInt(kIOSurfacePixelFormat, static_cast<int>(pixelFormat));
  setInt(kIOSurfaceAllocSize, allocSize);
  IOSurfaceRef surface = IOSurfaceCreate(dict);
  CFRelease(dict);
  if (surface == nil) {
    throw std::runtime_error("Failed to create " + std::to_string(width) + "x" +
                             std::to_string(height) + " test IOSurface!");
  }

  IOSurfaceLock(surface, 0, nil);
  auto *base = static_cast<uint8_t *>(IOSurfaceGetBaseAddress(surface));
  const size_t rowBytes = IOSurfaceGetBytesPerRow(surface);
  for (int y = 0; y < height; ++y) {
    uint8_t *row = base + y * rowBytes;
    for (int x = 0; x < width; ++x) {
      uint8_t r = static_cast<uint8_t>((x * 255) / std::max(width - 1, 1));
      uint8_t g = static_cast<uint8_t>((y * 255) / std::max(height - 1, 1));
      uint8_t b = static_cast<uint8_t>(((x + y) & 0x20) ? 220 : 30);
      row[x * 4 + 0] = b; // BGRA byte order
      row[x * 4 + 1] = g;
      row[x * 4 + 2] = r;
      row[x * 4 + 3] = 0xFF;
    }
  }
  IOSurfaceUnlock(surface, 0, nil);

  CVPixelBufferRef pixelBuffer = nullptr;
  CVReturn result =
      CVPixelBufferCreateWithIOSurface(nil, surface, nil, &pixelBuffer);
  // The CVPixelBuffer retains the IOSurface; drop our reference so the
  // CVPixelBuffer is its sole owner (freed by releaseNativeBuffer).
  CFRelease(surface);
  if (result != kCVReturnSuccess) {
    throw std::runtime_error("Failed to create CVPixelBuffer for test native "
                             "buffer! Return value: " +
                             std::to_string(result));
  }
  return reinterpret_cast<uint64_t>(pixelBuffer);
}

#if !defined(SK_GRAPHITE)
GrDirectContext *RNSkApplePlatformContext::getDirectContext() {
  return MetalContext::getInstance().getDirectContext();
}

const TextureInfo RNSkApplePlatformContext::getTexture(sk_sp<SkImage> image) {
  TextureInfo result;
  GrBackendTexture texture;
  if (!SkImages::GetBackendTextureFromImage(image, &texture, true)) {
    throw std::runtime_error("Couldn't get backend texture");
  }
  if (!texture.isValid()) {
    throw std::runtime_error("Invalid backend texture");
  }
  GrMtlTextureInfo textureInfo;
  if (!GrBackendTextures::GetMtlTextureInfo(texture, &textureInfo)) {
    throw std::runtime_error("Couldn't get Metal texture info");
  }
  result.mtlTexture = textureInfo.fTexture.get();
  return result;
}

const TextureInfo
RNSkApplePlatformContext::getTexture(sk_sp<SkSurface> surface) {
  TextureInfo result;
  GrBackendTexture texture = SkSurfaces::GetBackendTexture(
      surface.get(), SkSurfaces::BackendHandleAccess::kFlushRead);
  if (!texture.isValid()) {
    throw std::runtime_error("Invalid backend texture");
  }
  GrMtlTextureInfo textureInfo;
  if (!GrBackendTextures::GetMtlTextureInfo(texture, &textureInfo)) {
    throw std::runtime_error("Couldn't get Metal texture info");
  }
  result.mtlTexture = textureInfo.fTexture.get();
  return result;
}

sk_sp<SkImage> RNSkApplePlatformContext::makeImageFromNativeTexture(
    const TextureInfo &texInfo, int width, int height, bool mipMapped) {
  id<MTLTexture> mtlTexture = (__bridge id<MTLTexture>)(texInfo.mtlTexture);

  SkColorType colorType = mtlPixelFormatToSkColorType(mtlTexture.pixelFormat);
  if (colorType == SkColorType::kUnknown_SkColorType) {
    throw std::runtime_error("Unsupported pixelFormat");
  }

  GrMtlTextureInfo textureInfo;
  textureInfo.fTexture.retain((__bridge const void *)mtlTexture);

  GrBackendTexture texture = GrBackendTextures::MakeMtl(
      width, height, mipMapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo,
      textureInfo);

  return SkImages::BorrowTextureFrom(getDirectContext(), texture,
                                     kTopLeft_GrSurfaceOrigin, colorType,
                                     kPremul_SkAlphaType, nullptr);
  return nullptr;
}
#endif

std::shared_ptr<RNSkVideo>
RNSkApplePlatformContext::createVideo(const std::string &url) {
  return std::make_shared<RNSkAppleVideo>(url, this);
}

void RNSkApplePlatformContext::raiseError(const std::exception &err) {
  RCTFatal(RCTErrorWithMessage([NSString stringWithUTF8String:err.what()]));
}

sk_sp<SkSurface>
RNSkApplePlatformContext::makeOffscreenSurface(int width, int height,
                                               bool useP3ColorSpace) {
#if defined(SK_GRAPHITE)
  return DawnContext::getInstance().MakeOffscreen(width, height,
                                                  useP3ColorSpace);
#else
  return MetalContext::getInstance().MakeOffscreen(width, height,
                                                   useP3ColorSpace);
#endif
}

sk_sp<SkImage>
RNSkApplePlatformContext::makeImageFromNativeBuffer(void *buffer) {
#if defined(SK_GRAPHITE)
  return DawnContext::getInstance().MakeImageFromBuffer(buffer);
#else
  return MetalContext::getInstance().MakeImageFromBuffer(buffer);
#endif
}

SkColorType RNSkApplePlatformContext::mtlPixelFormatToSkColorType(
    MTLPixelFormat pixelFormat) {
  switch (pixelFormat) {
  case MTLPixelFormatRGBA8Unorm:
    return kRGBA_8888_SkColorType;
  case MTLPixelFormatBGRA8Unorm:
    return kBGRA_8888_SkColorType;
  case MTLPixelFormatRGB10A2Unorm:
    return kRGBA_1010102_SkColorType;
  case MTLPixelFormatR8Unorm:
    return kGray_8_SkColorType;
  case MTLPixelFormatRGBA16Float:
    return kRGBA_F16_SkColorType;
  case MTLPixelFormatRG8Unorm:
    return kR8G8_unorm_SkColorType;
  case MTLPixelFormatR16Float:
    return kA16_float_SkColorType;
  case MTLPixelFormatRG16Float:
    return kR16G16_float_SkColorType;
  case MTLPixelFormatR16Unorm:
    return kA16_unorm_SkColorType;
  case MTLPixelFormatRG16Unorm:
    return kR16G16_unorm_SkColorType;
  case MTLPixelFormatRGBA16Unorm:
    return kR16G16B16A16_unorm_SkColorType;
  case MTLPixelFormatRGBA8Unorm_sRGB:
    return kSRGBA_8888_SkColorType;
  default:
    return kUnknown_SkColorType;
  }
}

sk_sp<SkFontMgr> RNSkApplePlatformContext::createFontMgr() {
  return SkFontMgr_New_CoreText(nullptr);
}

std::vector<std::string> RNSkApplePlatformContext::getSystemFontFamilies() {
  std::vector<std::string> families;

  // System UI fonts (e.g., .AppleSystemUIFont) are not enumerated by Skia's
  // font manager. We retrieve them via Core Text's CTFontUIFontType constants.
  // This list covers common system font types as of iOS 17 / macOS 14.
  // Apple may add new CTFontUIFontType values in future OS versions,
  // so this list may need to be updated periodically.
  CTFontUIFontType fontTypes[] = {
      kCTFontUIFontUser,        kCTFontUIFontUserFixedPitch,
      kCTFontUIFontSystem,      kCTFontUIFontEmphasizedSystem,
      kCTFontUIFontSmallSystem, kCTFontUIFontSmallEmphasizedSystem,
      kCTFontUIFontMiniSystem,  kCTFontUIFontMiniEmphasizedSystem,
      kCTFontUIFontLabel,       kCTFontUIFontMessage,
      kCTFontUIFontToolTip,
  };

  std::set<std::string> uniqueFamilies;

  for (CTFontUIFontType fontType : fontTypes) {
    CTFontRef font = CTFontCreateUIFontForLanguage(fontType, 12.0, nullptr);
    if (font) {
      CFStringRef familyName = CTFontCopyFamilyName(font);
      if (familyName) {
        const char *cstr =
            CFStringGetCStringPtr(familyName, kCFStringEncodingUTF8);
        if (cstr) {
          uniqueFamilies.insert(std::string(cstr));
        } else {
          char buffer[256];
          if (CFStringGetCString(familyName, buffer, sizeof(buffer),
                                 kCFStringEncodingUTF8)) {
            uniqueFamilies.insert(std::string(buffer));
          }
        }
        CFRelease(familyName);
      }
      CFRelease(font);
    }
  }

  families.assign(uniqueFamilies.begin(), uniqueFamilies.end());
  return families;
}

std::string
RNSkApplePlatformContext::resolveFontFamily(const std::string &familyName) {
  // Handle special font family names like React Native does
  // See: RCTFont.mm in React Native
  if (familyName == "System" || familyName == "system" ||
      familyName == "sans-serif") {
    return ".AppleSystemUIFont";
  }
  if (familyName == "SystemCondensed" || familyName == "system-condensed") {
    // Return system font - condensed trait is handled via font style
    return ".AppleSystemUIFont";
  }
  // CSS generic font families
  if (familyName == "serif") {
    return "Times New Roman";
  }
  if (familyName == "monospace") {
    return "Courier New";
  }
  // Return as-is if no mapping exists
  return familyName;
}

void RNSkApplePlatformContext::runOnMainThread(std::function<void()> func) {
  dispatch_async(dispatch_get_main_queue(), ^{
    func();
  });
}

sk_sp<SkImage>
RNSkApplePlatformContext::takeScreenshotFromViewTag(size_t viewTag) {
  return [_screenshotService
      screenshotOfViewWithTag:[NSNumber numberWithLong:viewTag]];
}

} // namespace RNSkia
