/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "clipboard_proxy_impl.h"

#include <atomic>
#include <map>
#include <unordered_set>

#include "common/constant.h"
#include "image_mime_type.h"
#include "log.h"
#include "pasteboard_error.h"
#include "pixel_map.h"

#import <MobileCoreServices/UTCoreTypes.h>
#import <UIKit/UIKit.h>
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
#import <UniformTypeIdentifiers/UTCoreTypes.h>
#endif

namespace {
static UIPasteboard *GetGlobalPasteboard() {
    static UIPasteboard *pasteboard = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        pasteboard = [UIPasteboard generalPasteboard];
    });
    return pasteboard;
}
}

namespace OHOS {
namespace Plugin {

static NSString *const TYPE_RECORD = @"com.arkui-x.pasteboard.record";
static NSString *const TYPE_PROPS = @"com.arkui-x.pasteboard.props";
static NSString *const TYPE_WANT = @"com.arkui-x.pasteboard.want";
static NSString *const TYPE_IMAGE = @"image/image";
static NSString *const TYPE_IMAGE_PNG = @"image/png";
static NSString *const TYPE_IMAGE_JPEG = @"image/jpeg";
static NSString *const TYPE_TEXT_PLAIN = @"text/plain";
static NSString *const TYPE_TEXT_HTML = @"text/html";
static NSString *const TYPE_WEB_URL = @"web/url";
static NSString *const TYPE_FILE_URL = @"file/url";

struct ObserverEntry {
    id token;
    std::shared_ptr<std::atomic_bool> alive;
};
static std::map<sptr<PasteboardObserverStub>, ObserverEntry> g_observerTokenMap;

ClipboardProxyImpl::~ClipboardProxyImpl()
{
    std::lock_guard<std::mutex> lock(mutex_);
    for (auto& it : g_observerTokenMap) {
        if (it.second.alive) {
            it.second.alive->store(false, std::memory_order_relaxed);
        }
        if (it.second.token) {
            [[NSNotificationCenter defaultCenter] removeObserver:it.second.token];
        }
    }
    g_observerTokenMap.clear();
}

std::string TranslateMimeType(NSString *type)
{
    if (@available(iOS 15.0, *)) {
        if ([type isEqualToString:UTTypeUTF8PlainText.identifier]) {
            return MIMETYPE_TEXT_PLAIN;
        }
        if ([type isEqualToString:UTTypePNG.identifier] ||
            [type isEqualToString:UTTypeJPEG.identifier] ||
            [type isEqualToString:UTTypeImage.identifier]) {
            return MIMETYPE_PIXELMAP;
        }
        if ([type isEqualToString:UTTypeHTML.identifier]) {
            return MIMETYPE_TEXT_HTML;
        }
        if ([type isEqualToString:UTTypeURL.identifier] ||
            [type isEqualToString:UTTypeFileURL.identifier]) {
            return MIMETYPE_TEXT_URI;
        }
        if ([type isEqualToString:TYPE_WANT]) {
            return MIMETYPE_TEXT_WANT;
        }
    } else {
        if ([type isEqualToString:(NSString *)kUTTypeUTF8PlainText]) {
            return MIMETYPE_TEXT_PLAIN;
        }
        if ([type isEqualToString:(NSString *)kUTTypePNG] ||
            [type isEqualToString:(NSString *)kUTTypeJPEG] ||
            [type isEqualToString:(NSString *)kUTTypeImage]) {
            return MIMETYPE_PIXELMAP;
        }
        if ([type isEqualToString:(NSString *)kUTTypeHTML]) {
            return MIMETYPE_TEXT_HTML;
        }
        if ([type isEqualToString:(NSString *)kUTTypeURL] ||
            [type isEqualToString:(NSString *)kUTTypeFileURL]) {
            return MIMETYPE_TEXT_URI;
        }
        if ([type isEqualToString:TYPE_WANT]) {
            return MIMETYPE_TEXT_WANT;
        }
    }
    return "";
}

NSString *TranslateMimeType(const std::string& mimeType)
{
    if (@available(iOS 15.0, *)) {
        if (mimeType == MIMETYPE_TEXT_PLAIN) {
            return UTTypeUTF8PlainText.identifier;
        }
        if (mimeType == MIMETYPE_PIXELMAP) {
            return UTTypeImage.identifier;
        }
        if (mimeType == MIMETYPE_TEXT_HTML) {
            return UTTypeHTML.identifier;
        }
        if (mimeType == MIMETYPE_TEXT_URI) {
            return UTTypeURL.identifier;
        }
        if (mimeType == MIMETYPE_TEXT_WANT) {
            return TYPE_WANT;
        }
    } else {
        if (mimeType == MIMETYPE_TEXT_PLAIN) {
            return (NSString *)kUTTypeUTF8PlainText;
        }
        if (mimeType == MIMETYPE_PIXELMAP) {
            return (NSString *)kUTTypeImage;
        }
        if (mimeType == MIMETYPE_TEXT_HTML) {
            return (NSString *)kUTTypeHTML;
        }
        if (mimeType == MIMETYPE_TEXT_URI) {
            return (NSString *)kUTTypeURL;
        }
        if (mimeType == MIMETYPE_TEXT_WANT) {
            return TYPE_WANT;
        }
    }
    return @"";
}

NSString *TranslateImageUTType(const std::string& imageMimeType)
{
    if (@available(iOS 15.0, *)) {
        if (imageMimeType == Media::IMAGE_JPEG_FORMAT) {
            return UTTypeJPEG.identifier;
        }
        if (imageMimeType == Media::IMAGE_PNG_FORMAT) {
            return UTTypePNG.identifier;
        }
        return UTTypeImage.identifier;
    } else {
        if (imageMimeType == Media::IMAGE_JPEG_FORMAT) {
            return (NSString *)kUTTypeJPEG;
        }
        if (imageMimeType == Media::IMAGE_PNG_FORMAT) {
            return (NSString *)kUTTypePNG;
        }
        return (NSString *)kUTTypeImage;
    }
}

NSString *TranslateUTType(NSString *type)
{
    if (@available(iOS 15.0, *)) {
        if ([type isEqualToString:TYPE_IMAGE_PNG]) {
            return UTTypePNG.identifier;
        }
        if ([type isEqualToString:TYPE_IMAGE_JPEG]) {
            return UTTypeJPEG.identifier;
        }
        if ([type isEqualToString:TYPE_IMAGE]) {
            return UTTypeImage.identifier;
        }
        if ([type isEqualToString:TYPE_TEXT_PLAIN]) {
            return UTTypeUTF8PlainText.identifier;
        }
        if ([type isEqualToString:TYPE_TEXT_HTML]) {
            return UTTypeHTML.identifier;
        }
        if ([type isEqualToString:TYPE_WEB_URL]) {
            return UTTypeURL.identifier;
        }
        if ([type isEqualToString:TYPE_FILE_URL]) {
            return UTTypeFileURL.identifier;
        }
    } else {
        if ([type isEqualToString:TYPE_IMAGE_PNG]) {
            return (NSString *)kUTTypePNG;
        }
        if ([type isEqualToString:TYPE_IMAGE_JPEG]) {
            return (NSString *)kUTTypeJPEG;
        }
        if ([type isEqualToString:TYPE_IMAGE]) {
            return (NSString *)kUTTypeImage;
        }
        if ([type isEqualToString:TYPE_TEXT_PLAIN]) {
            return (NSString *)kUTTypeUTF8PlainText;
        }
        if ([type isEqualToString:TYPE_TEXT_HTML]) {
            return (NSString *)kUTTypeHTML;
        }
        if ([type isEqualToString:TYPE_WEB_URL]) {
            return (NSString *)kUTTypeURL;
        }
        if ([type isEqualToString:TYPE_FILE_URL]) {
            return (NSString *)kUTTypeFileURL;
        }
    }
    return @"";
}

static CGImageRef CreateCGImageFromData(
    const std::vector<uint8_t>& bgra, int32_t width, int32_t height, uint32_t bytesPerRow)
{
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    if (!colorSpace) {
        return nullptr;
    }
    CFDataRef cfData = CFDataCreate(kCFAllocatorDefault, bgra.data(), (CFIndex)bgra.size());
    if (!cfData) {
        CGColorSpaceRelease(colorSpace);
        return nullptr;
    }
    CGDataProviderRef provider = CGDataProviderCreateWithCFData(cfData);
    CFRelease(cfData);
    if (!provider) {
        CGColorSpaceRelease(colorSpace);
        return nullptr;
    }
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst;
    CGImageRef imageRef = CGImageCreate(width, height, 8, 32, bytesPerRow, colorSpace, bitmapInfo, provider,
                                        NULL, true, kCGRenderingIntentDefault);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);
    return imageRef;
}

static UIImage *PixelMapToUIImage(const std::shared_ptr<Media::PixelMap>& pixelMap)
{
    if (pixelMap == nullptr) {
        return nil;
    }
    const int32_t width = pixelMap->GetWidth();
    const int32_t height = pixelMap->GetHeight();
    if (width <= 0 || height <= 0) {
        return nil;
    }
    if (static_cast<uint32_t>(width) > (UINT32_MAX / 4)) {
        return nil;
    }
    const uint32_t bytesPerRow = static_cast<uint32_t>(width) * 4;
    if (static_cast<uint64_t>(bytesPerRow) > (UINT64_MAX / static_cast<uint64_t>(height))) {
        return nil;
    }
    const uint64_t bufferSize = static_cast<uint64_t>(bytesPerRow) * static_cast<uint64_t>(height);
    if (bufferSize == 0) {
        return nil;
    }
    std::vector<uint8_t> bgra(static_cast<size_t>(bufferSize));
    Media::RWPixelsOptions opts;
    opts.pixels = bgra.data();
    opts.bufferSize = bufferSize;
    opts.stride = bytesPerRow;
    opts.region = Media::Rect { 0, 0, width, height };
    opts.pixelFormat = Media::PixelFormat::BGRA_8888;
    if (pixelMap->ReadPixels(opts) != 0) {
        return nil;
    }
    CGImageRef imageRef = CreateCGImageFromData(bgra, width, height, bytesPerRow);
    UIImage *image = imageRef ? [UIImage imageWithCGImage:imageRef] : nil;
    if (imageRef) {
        CGImageRelease(imageRef);
    }
    return image;
}

static std::shared_ptr<Media::PixelMap> UIImageToPixelMap(UIImage *image)
{
    if (!image) {
        return nullptr;
    }
    CGImageRef cgImage = image.CGImage;
    if (!cgImage) {
        return nullptr;
    }
    const size_t width = CGImageGetWidth(cgImage);
    const size_t height = CGImageGetHeight(cgImage);
    if (width == 0 || height == 0) {
        return nullptr;
    }
    const size_t bytesPerRow = width * 4;
    std::vector<uint8_t> pixels(bytesPerRow * height);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    if (!colorSpace) {
        return nullptr;
    }
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst;
    CGContextRef context = CGBitmapContextCreate(pixels.data(), width, height, 8, bytesPerRow, colorSpace, bitmapInfo);
    CGColorSpaceRelease(colorSpace);
    if (!context) {
        return nullptr;
    }
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
    CGContextRelease(context);
    Media::InitializationOptions opts;
    opts.size.width = static_cast<int32_t>(width);
    opts.size.height = static_cast<int32_t>(height);
    opts.pixelFormat = Media::PixelFormat::BGRA_8888;
    opts.alphaType = Media::AlphaType::IMAGE_ALPHA_TYPE_PREMUL;
    opts.editable = true;
    std::unique_ptr<Media::PixelMap> pixelMap = Media::PixelMap::Create(opts);
    if (!pixelMap) {
        return nullptr;
    }
    const uint64_t bufferSize = static_cast<uint64_t>(pixels.size());
    uint32_t ret = pixelMap->WritePixels(pixels.data(), bufferSize);
    if (ret != 0) {
        return nullptr;
    }
    return std::shared_ptr<Media::PixelMap>(pixelMap.release());
}

NSDictionary *MakePropsItem(const PasteDataProperty& props)
{
    const size_t capacity = props.CountTLV();
    if (capacity == 0) {
        return nil;
    }
    WriteOnlyBuffer buffer(capacity);
    if (props.EncodeTLV(buffer)) {
        NSData *propsData = [NSData dataWithBytes:buffer.Data() length:buffer.Size()];
        if (!propsData || propsData.length == 0) {
            return nil;
        }
        return @{ TYPE_PROPS : propsData };
    }
    return nil;
}

NSDictionary *MakePixelMapItem(std::shared_ptr<Media::PixelMap> pixelMap)
{
    if (pixelMap == nullptr) {
        return nil;
    }
    Media::ImageInfo imageInfo;
    pixelMap->GetImageInfo(imageInfo);
    const std::string format = imageInfo.encodedFormat;
    if (format == Media::IMAGE_JPEG_FORMAT || format == Media::IMAGE_PNG_FORMAT) {
        UIImage *image = PixelMapToUIImage(pixelMap);
        if (!image) {
            return nil;
        }
        return @{ TranslateImageUTType(format) : image };
    }
    std::vector<uint8_t> buffer = TLVUtils::PixelMap2Vector(pixelMap);
    NSData *imageData = [NSData dataWithBytes:buffer.data() length:buffer.size()];
    if (!imageData || imageData.length == 0) {
        return nil;
    }
    return @{ TranslateImageUTType(format) : imageData };
}

NSDictionary *MakeTextHtmlItem(std::shared_ptr<std::string> plain, std::shared_ptr<std::string> html)
{
    if (html == nullptr) {
        return nil;
    }
    NSString *htmlString = [NSString stringWithCString:html->c_str() encoding:NSUTF8StringEncoding];
    if (plain != nullptr) {
        NSString *plainString = [NSString stringWithCString:plain->c_str() encoding:NSUTF8StringEncoding];
        return @{ TranslateMimeType(MIMETYPE_TEXT_PLAIN) : plainString,
                  TranslateMimeType(MIMETYPE_TEXT_HTML) : htmlString };
    }
    return @{ TranslateMimeType(MIMETYPE_TEXT_PLAIN) : @"",
              TranslateMimeType(MIMETYPE_TEXT_HTML) : htmlString };
}

NSDictionary *MakePlainTextItem(std::shared_ptr<std::string> plain)
{
    if (plain == nullptr) {
        return nil;
    }
    NSString *plainString = [NSString stringWithCString:plain->c_str() encoding:NSUTF8StringEncoding];
    return @{ TranslateMimeType(MIMETYPE_TEXT_PLAIN) : plainString };
}

NSDictionary *MakeUriItem(std::shared_ptr<Uri> uri)
{
    if (uri == nullptr) {
        return nil;
    }
    NSString *uriString = [NSString stringWithCString:uri->ToString().c_str() encoding:NSUTF8StringEncoding];
    NSURL *url = nil;
    if ([uriString hasPrefix:@"file://"]) {
        url = [NSURL URLWithString:uriString];
    } else if ([uriString hasPrefix:@"/"]) {
        url = [NSURL fileURLWithPath:uriString];
    } else {
        url = [NSURL URLWithString:uriString];
    }
    if (!url) {
        return nil;
    }
    return @{ TranslateMimeType(MIMETYPE_TEXT_URI) : url };
}

NSDictionary *MakeWantItem(std::shared_ptr<AAFwk::Want> want)
{
    if (want == nullptr) {
        return nil;
    }
    const std::string wantJson = TLVUtils::Want2Json(*want);
    if (wantJson.empty()) {
        return nil;
    }
    NSString *jsonString = [NSString stringWithUTF8String:wantJson.c_str()];
    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
    if (!jsonData || jsonData.length == 0) {
        return nil;
    }
    return @{ TYPE_WANT : jsonData };
}

NSDictionary *MakeCustomItem(std::shared_ptr<MineCustomData> custom)
{
    if (custom == nullptr) {
        return nil;
    }
    auto itemData = custom->GetItemData();
    if (itemData.empty()) {
        return nil;
    }
    std::string key = itemData.begin()->first;
    if (key.empty()) {
        return nil;
    }
    const size_t capacity = custom->CountTLV();
    if (capacity == 0) {
        return nil;
    }
    WriteOnlyBuffer buffer(capacity);
    if (custom->EncodeTLV(buffer)) {
        NSData *customData = [NSData dataWithBytes:buffer.Data() length:buffer.Size()];
        if (!customData || customData.length == 0) {
            return nil;
        }
        return @{ [NSString stringWithUTF8String:key.c_str()] : customData };
    }
    return nil;
}

NSDictionary *MakeRecordItem(std::shared_ptr<PasteDataRecord> record)
{
    if (record == nullptr) {
        return nil;
    }
    const size_t capacity = record->CountTLV();
    if (capacity == 0) {
        return nil;
    }
    WriteOnlyBuffer buffer(capacity);
    if (record->EncodeTLV(buffer)) {
        NSData *recordData = [NSData dataWithBytes:buffer.Data() length:buffer.Size()];
        if (!recordData || recordData.length == 0) {
            return nil;
        }
        return @{ TYPE_RECORD : recordData };
    }
    return nil;
}

NSDictionary *MakeItem(std::shared_ptr<PasteDataRecord> record)
{
    if (record == nullptr) {
        return nil;
    }
    NSMutableDictionary *item = [NSMutableDictionary dictionary];
    auto recordItem = MakeRecordItem(record);
    if (recordItem) {
        [item addEntriesFromDictionary:recordItem];
    }
    auto plainText = MakePlainTextItem(record->GetPlainText());
    if (plainText) {
        [item addEntriesFromDictionary:plainText];
    }
    auto uri = MakeUriItem(record->GetUri());
    if (uri) {
        [item addEntriesFromDictionary:uri];
    }
    auto htmlText = MakeTextHtmlItem(record->GetPlainText(), record->GetHtmlText());
    if (htmlText) {
        [item addEntriesFromDictionary:htmlText];
    }
    auto pixelMap = MakePixelMapItem(record->GetPixelMap());
    if (pixelMap) {
        [item addEntriesFromDictionary:pixelMap];
    }
    auto want = MakeWantItem(record->GetWant());
    if (want) {
        [item addEntriesFromDictionary:want];
    }
    auto custom = MakeCustomItem(record->GetCustomData());
    if (custom) {
        [item addEntriesFromDictionary:custom];
    }
    return item;
}

static void AppendRecordItem(NSMutableArray<NSDictionary*> *items, const std::shared_ptr<PasteDataRecord>& record)
{
    if (items == nullptr || record == nullptr) {
        return;
    }
    @autoreleasepool {
        NSDictionary *item = MakeItem(record);
        if (item.count > 0) {
            [items addObject:item];
        }
    }
}

int32_t ClipboardProxyImpl::SetPasteData(const PasteData& pasteData)
{
    @autoreleasepool {
        auto records = pasteData.AllRecords();
        if (records.empty()) {
            Clear();
            return ERR_OK;
        }
        UIPasteboard *pasteboard = GetGlobalPasteboard();
        if (!pasteboard) {
            return ERR_NO_INIT;
        }
        NSMutableArray<NSDictionary*> *items = [NSMutableArray arrayWithCapacity:records.size() + 1];
        for (auto record = records.rbegin(); record != records.rend(); ++record) {
            AppendRecordItem(items, *record);
        }
        auto propsItem = MakePropsItem(pasteData.GetProperty());
        if (propsItem) {
            [items addObject:propsItem];
        }
        pasteboard.items = items;
        return ERR_OK;
    }
}

static std::shared_ptr<Media::PixelMap> PixelMapFromPasteboardValue(id value)
{
    if (!value) {
        return nullptr;
    }
    if ([value isKindOfClass:[UIImage class]]) {
        return UIImageToPixelMap((UIImage *)value);
    }
    if ([value isKindOfClass:[NSData class]]) {
        NSData *data = (NSData *)value;
        if (!data || data.length == 0) {
            return nullptr;
        }
        std::vector<uint8_t> buffer((const uint8_t *)data.bytes,
                                    (const uint8_t *)data.bytes + data.length);
        auto pixelMap = TLVUtils::Vector2PixelMap(buffer);
        if (pixelMap) {
            return pixelMap;
        }
        UIImage *image = [UIImage imageWithData:data];
        return UIImageToPixelMap(image);
    }
    return nullptr;
}

static std::shared_ptr<Media::PixelMap> ProcessImageItem(NSDictionary<NSString *, id> *item)
{
    id pngVal = item[TranslateUTType(TYPE_IMAGE_PNG)];
    id jpegVal = item[TranslateUTType(TYPE_IMAGE_JPEG)];
    id imageVal = item[TranslateUTType(TYPE_IMAGE)];
    std::shared_ptr<Media::PixelMap> pixelMap = nullptr;
    if (pngVal) {
        pixelMap = PixelMapFromPasteboardValue(pngVal);
    }
    if (!pixelMap && jpegVal) {
        pixelMap = PixelMapFromPasteboardValue(jpegVal);
    }
    if (!pixelMap && imageVal) {
        pixelMap = PixelMapFromPasteboardValue(imageVal);
    }
    return pixelMap;
}

static std::shared_ptr<std::string> ProcessPlainTextItem(NSDictionary<NSString *, id> *item)
{
    NSString *textStr = nil;
    id text = item[TranslateUTType(TYPE_TEXT_PLAIN)];
    if (text && [text isKindOfClass:[NSString class]]) {
        textStr = [NSString stringWithString:text];
    }
    if (!textStr || textStr.length == 0) {
        return nullptr;
    }
    return std::make_shared<std::string>(textStr.UTF8String);
}

static std::shared_ptr<std::string> ProcessHtmlItem(NSDictionary<NSString *, id> *item)
{
    NSString *htmlStr = nil;
    id html = item[TranslateUTType(TYPE_TEXT_HTML)];
    if (html && [html isKindOfClass:[NSString class]]) {
        htmlStr = [NSString stringWithString:html];
    }
    std::shared_ptr<std::string> htmlText = nullptr;
    if (!htmlStr || htmlStr.length == 0) {
        return nullptr;
    }
    return std::make_shared<std::string>(htmlStr.UTF8String);
}

static std::shared_ptr<Uri> ProcessUriItem(NSDictionary<NSString *, id> *item)
{
    NSURL *webUrl = item[TranslateUTType(TYPE_WEB_URL)];
    NSURL *fileUrl = item[TranslateUTType(TYPE_FILE_URL)];
    if (!webUrl && !fileUrl) {
        return nullptr;
    }
    NSURL *url = webUrl ? webUrl : fileUrl;
    std::string path = url.absoluteString ? url.absoluteString.UTF8String : "";
    if (path.empty()) {
        return nullptr;
    }
    return std::make_shared<Uri>(path);
}

static std::shared_ptr<AAFwk::Want> ProcessWantItem(NSDictionary<NSString *, id> *item)
{
    id data = item[TYPE_WANT];
    if (!data || ![data isKindOfClass:[NSData class]] || [(NSData *)data length] == 0) {
        return nullptr;
    }
    NSData *wantData = [NSData dataWithData:data];
    NSString *wantJsonString = [[NSString alloc] initWithData:wantData encoding:NSUTF8StringEncoding];
    if (!wantJsonString || wantJsonString.length == 0) {
        return nullptr;
    }
    return TLVUtils::Json2Want(std::string([wantJsonString UTF8String]));
}

static bool IsInternalPasteboardType(NSString *type)
{
    if (!type || type.length == 0) {
        return true;
    }
    return [type isEqualToString:TYPE_PROPS] || [type isEqualToString:TYPE_RECORD];
}

static std::vector<std::shared_ptr<MineCustomData>> ProcessCustomDataItem(NSDictionary<NSString *, id> *item)
{
    std::vector<std::shared_ptr<MineCustomData>> customs;
    for (NSString *key in item.allKeys) {
        if (IsInternalPasteboardType(key)) {
            continue;
        }
        std::string mime = TranslateMimeType(key);
        if (!mime.empty()) {
            continue;
        }
        id data = item[key];
        if (!data || ![data isKindOfClass:[NSData class]] || [(NSData *)data length] == 0) {
            continue;
        }
        NSData *customData = [NSData dataWithData:data];
        std::vector<uint8_t> buffer((const uint8_t *)customData.bytes,
                                    (const uint8_t *)customData.bytes + customData.length);
        ReadOnlyBuffer readBuffer(buffer);
        auto custom = std::make_shared<MineCustomData>();
        if (custom->DecodeTLV(readBuffer)) {
            customs.push_back(custom);
        }
    }
    return customs;
}

static void ProcessPasteboardItem(NSDictionary<NSString *, id> *item, PasteData& pasteData)
{
    std::string mimeType = "";
    for (NSString *key in item.allKeys) {
        std::string mime = TranslateMimeType(key);
        if (!mime.empty()) {
            mimeType = mime;
            break;
        }
    }
    if (mimeType.empty() && item.allKeys.count > 0) {
        mimeType = [NSString stringWithString:item.allKeys.firstObject].UTF8String;
    }
    PasteDataRecord::Builder builder(mimeType);
    auto pixelMap = ProcessImageItem(item);
    if (pixelMap) {
        builder.SetPixelMap(pixelMap);
    }
    auto plainText = ProcessPlainTextItem(item);
    if (plainText) {
        builder.SetPlainText(plainText);
    }
    auto htmlText = ProcessHtmlItem(item);
    if (htmlText) {
        builder.SetHtmlText(htmlText);
    }
    auto uri = ProcessUriItem(item);
    if (uri) {
        builder.SetUri(uri);
    }
    auto want = ProcessWantItem(item);
    if (want) {
        builder.SetWant(want);
    }
    auto customs = ProcessCustomDataItem(item);
    for (auto& custom : customs) {
        builder.SetCustomData(custom);
    }
    pasteData.AddRecord(builder.Build());
}

static bool TryDecodePropsItem(NSDictionary<NSString *, id> *item, PasteData& pasteData)
{
    id data = item[TYPE_PROPS];
    if (!data || ![data isKindOfClass:[NSData class]] || [(NSData *)data length] == 0) {
        return false;
    }
    NSData *propsData = [NSData dataWithData:data];
    std::vector<uint8_t> buffer((const uint8_t *)propsData.bytes,
                                (const uint8_t *)propsData.bytes + propsData.length);
    ReadOnlyBuffer readBuffer(buffer);
    PasteDataProperty props;
    if (!props.DecodeTLV(readBuffer)) {
        return false;
    }
    pasteData.SetProperty(props);
    return true;
}

static bool TryDecodeRecordItem(NSDictionary<NSString *, id> *item, PasteData& pasteData)
{
    id data = item[TYPE_RECORD];
    if (!data || ![data isKindOfClass:[NSData class]] || [(NSData *)data length] == 0) {
        return false;
    }
    NSData *recordData = [NSData dataWithData:data];
    std::vector<uint8_t> buffer((const uint8_t *)recordData.bytes,
                                (const uint8_t *)recordData.bytes + recordData.length);
    ReadOnlyBuffer readBuffer(buffer);
    auto record = std::make_shared<PasteDataRecord>();
    if (!record) {
        return false;
    }
    if (!record->DecodeTLV(readBuffer)) {
        return false;
    }
    pasteData.AddRecord(record);
    return true;
}

int32_t ClipboardProxyImpl::GetPasteData(PasteData& pasteData, int32_t& realErrorCode)
{
    @autoreleasepool {
        UIPasteboard *pasteboard = GetGlobalPasteboard();
        if (!pasteboard) {
            realErrorCode = ERR_NO_INIT;
            return ERR_NO_INIT;
        }
        for (NSDictionary<NSString *, id> *item in pasteboard.items) {
            if (TryDecodePropsItem(item, pasteData)) {
                continue;
            }
            if (TryDecodeRecordItem(item, pasteData)) {
                continue;
            }
            ProcessPasteboardItem(item, pasteData);
        }
        realErrorCode = ERR_OK;
        return ERR_OK;
    }
}

int ClipboardProxyImpl::Clear()
{
    @autoreleasepool {
        UIPasteboard *pasteboard = GetGlobalPasteboard();
        if (!pasteboard) {
            return ERR_NO_INIT;
        }
        pasteboard.items = @[];
        return ERR_OK;
    }
}

bool ClipboardProxyImpl::Subscribe(PasteboardObserverType type, sptr<PasteboardObserverStub> callback)
{
    if (!callback) {
        return false;
    }
    UIPasteboard *pasteboard = GetGlobalPasteboard();
    if (!pasteboard) {
        return false;
    }
    std::lock_guard<std::mutex> lock(mutex_);
    if (g_observerTokenMap.find(callback) != g_observerTokenMap.end()) {
        return true;
    }
    auto alive = std::make_shared<std::atomic_bool>(true);
    id token = [[NSNotificationCenter defaultCenter] addObserverForName:UIPasteboardChangedNotification
        object:pasteboard queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull notification) {
        if (!alive || !alive->load(std::memory_order_relaxed)) {
            return;
        }
        if (callback && !notification.userInfo) {
            callback->OnPasteboardChanged();
        }
    }];
    if (!token) {
        return false;
    }
    g_observerTokenMap[callback] = { token, alive };
    return true;
}

void ClipboardProxyImpl::Unsubscribe(PasteboardObserverType type, sptr<PasteboardObserverStub> callback)
{
    if (!callback) {
        return;
    }
    std::lock_guard<std::mutex> lock(mutex_);
    auto it = g_observerTokenMap.find(callback);
    if (it != g_observerTokenMap.end()) {
        if (it->second.alive) {
            it->second.alive->store(false, std::memory_order_relaxed);
        }
        if (it->second.token) {
            [[NSNotificationCenter defaultCenter] removeObserver:it->second.token];
        }
        g_observerTokenMap.erase(it);
    }
}

void ClipboardProxyImpl::NotifyObservers() {}

int ClipboardProxyImpl::DetectPatterns(
    const std::vector<Pattern>& patternsToCheck,
    std::vector<Pattern>& funcResult)
{
    return -1;
}

ErrCode ClipboardProxyImpl::HasPasteData(bool& funcResult)
{
    UIPasteboard *pasteboard = GetGlobalPasteboard();
    if (!pasteboard) {
        return ERR_NO_INIT;
    }
    funcResult = (pasteboard.items.count > 0);
    return ERR_OK;
}

ErrCode ClipboardProxyImpl::HasDataType(const std::string& mimeType, bool& funcResult)
{
    if (mimeType.empty()) {
        return static_cast<int>(PasteboardError::PARAM_ERROR);
    }
    UIPasteboard *pasteboard = GetGlobalPasteboard();
    if (!pasteboard) {
        return ERR_NO_INIT;
    }
    NSString *type = TranslateMimeType(mimeType);
    if (!type || type.length == 0) {
        type = [NSString stringWithUTF8String:mimeType.c_str()];
    }
    funcResult = [pasteboard containsPasteboardTypes:@[type] inItemSet:nil];
    return ERR_OK;
}

NSSet<NSString *> *GetAllTypesFromPasteboard(UIPasteboard *pasteboard)
{
    if (!pasteboard) {
        return [NSSet set];
    }
    NSArray<NSDictionary<NSString *, id> *> *items = pasteboard.items;
    if (!items || items.count == 0) {
        return [NSSet set];
    }
    NSMutableSet<NSString *> *typeSet = [NSMutableSet set];
    for (NSDictionary<NSString *, id> *item in items) {
        for (NSString *key in item) {
            if (key.length > 0) {
                [typeSet addObject:key];
            }
        }
    }
    return typeSet;
}

static bool TryGetMimeTypeFromPasteboardType(NSString *type, std::string& mimeType)
{
    mimeType = TranslateMimeType(type);
    if (!mimeType.empty()) {
        return true;
    }
    const char* cstr = type.UTF8String;
    if (!cstr || cstr[0] == '\0') {
        return false;
    }
    mimeType = cstr;
    return true;
}

ErrCode ClipboardProxyImpl::GetMimeTypes(std::vector<std::string>& funcResult)
{
    @autoreleasepool {
        funcResult.clear();
        UIPasteboard *pasteboard = GetGlobalPasteboard();
        if (!pasteboard) {
            return ERR_NO_INIT;
        }
        NSSet<NSString *> *typeSet = GetAllTypesFromPasteboard(pasteboard);
        if (!typeSet || typeSet.count == 0) {
            return ERR_OK;
        }
        funcResult.reserve(typeSet.count);
        std::unordered_set<std::string> set;
        set.reserve(typeSet.count);
        for (NSString *type in typeSet) {
            if (IsInternalPasteboardType(type)) {
                continue;
            }
            std::string mimeType;
            if (!TryGetMimeTypeFromPasteboardType(type, mimeType)) {
                continue;
            }
            if (set.insert(mimeType).second) {
                funcResult.emplace_back(std::move(mimeType));
            }
        }
        return ERR_OK;
    }
}

ErrCode ClipboardProxyImpl::GetChangeCount(uint32_t& changeCount)
{
    UIPasteboard *pasteboard = GetGlobalPasteboard();
    if (!pasteboard) {
        return ERR_NO_INIT;
    }
    changeCount = (uint32_t)pasteboard.changeCount;
    return ERR_OK;
}
} // namespace Plugin
} // namespace OHOS