// ============================================================
//  wrapper_v3.cpp  — 基于 TwoEyeUserApi.h 的精确 C++ vtable 封装
//
//  程序员需要做的事：
//    1. 确认 OpenCV + OpenMesh 头文件路径（见 TODOs）
//    2. 编译（用 build_v3.bat 一键编译）
//    3. 在 Windows 真机运行→回答 TODO(runtime) 里的运行时参数
//
//  ⚠️ 不改逻辑，只填参数
// ============================================================

// ═══════════════════════════════════════════════════════════
//  包含 SDK 头文件
// ═══════════════════════════════════════════════════════════

// ============================================================
// 【TODO(programmer)】根据实际解压路径修改下面四个目录
//  假设 SDK 解压到 D:\xhk_sdk\，则：
//     SDK_HEADERS = D:\xhk_sdk\Xhk_demo\Xhk_demo\
//     OPENCV_INC  = D:\xhk_sdk\Xhk_demo\Xhk_demo\OpenCV\include
//     OPENMESH_INC= D:\xhk_sdk\Xhk_demo\Xhk_demo\OpenMesh\include
//     EIGEN_INC   = D:\xhk_sdk\Xhk_demo\Xhk_demo\eigen-3.3.7
// ============================================================
#ifndef SDK_HEADERS_DIR
#error "请在编译时定义 SDK_HEADERS_DIR 指向 Xhk_demo/Xhk_demo/ 目录"
#endif

#ifdef _MSC_VER
#pragma message("SDK_HEADERS_DIR=" STRINGIFY(SDK_HEADERS_DIR) )
#endif

#include <Windows.h>
#include <cstdio>
#include <cassert>
#include <vector>
#include <queue>

// ── SDK 核心接口（来自上传的 TwoEyeAPI 源码）──
#include "TwoEyeApi/ICUserDefs.h"
#include "TwoEyeApi/TwoEyeUserApi.h"

// ── stdafx.h 中的类型定义（只需与 SDK 对齐的最小集合）──
typedef struct tagPT3D20 {
    float x, y, z;
    unsigned char r, g, b, f;
} PT3D20;

// IC_CAMERA_INFO 简化版（只需要布局一致，实际工作距离等参数）
typedef struct tagIC_CAMERA_INFO_SIMPLE {
    double cam[9];
    double dist[4];
    double FMat[9];
    double Aff[9];
    double InvAff[9];
    double Rzl[9];
    double vcam[9];
    double dor;
    double O1O2;
    double workdist;
    double exptime;
    double avgDist;
    double distErr[4];
    double gTrasform[12];
    // 前面的 CvSize/CvMat 字段省略，因为我们用偏移访问
    // 实际上会用 SDK API 获取，不直接访问成员
} IC_CAMERA_INFO_SIMPLE;

// ── OpenCV 最小声明 —— 只需要知道 IplImage 布局 ──
// 注意：SDK 内部需要完整的 OpenCV，所以编译时必须能 Include 到 OpenCV
// OpenCV 头文件包含在 Xhk_demo 压缩包中：
//   Xhk_demo/Xhk_demo/OpenCV/include/
// ============================================================
// 【TODO(programmer)】如果编译时报 OpenCV 相关错误：
//   1. 确认 OPENCV_INC 指向正确（见上方目录设定）
//   2. 确保 #include <cv.h> 能找到 cv.h
// ============================================================
#include <cv.h>
#include <cxcore.h>

// ── 项目中自定义的头（需要 SDK_HEADERS_DIR 指向） ──
typedef double Matrix3x4[3][4];
typedef float  Matrix4x4[4][4];
// (不包含 GRtype.h 和 GlobalGRForICP.h，这些不需要)


// ═══════════════════════════════════════════════════════════
//  动态加载 DLL
// ═══════════════════════════════════════════════════════════

static HMODULE g_hIC  = NULL;   // ICDevice.dll
static HMODULE g_hCam = NULL;   // xhcv2011.dll
static HMODULE g_hTwo = NULL;   // TwoEyeApi.dll

// 函数指针
static IC_STATUS (*pIC_Start)(IC_GRAB_MODE mode, LONG idx);
static IC_STATUS (*pIC_Stop)(LONG idx);
static IC_STATUS (*pIC_Get)(IC_PARAM_TYPE Type, PVOID pVal, LONG idx);
static IC_STATUS (*pIC_Set)(IC_PARAM_TYPE Type, PVOID pVal, LONG idx);

static int (*pInitUseCam2011)(void);   // 相机硬件初始化
static int (*pxhCreateObject)(void** ppObj, void* pParam, int ctype);


// ═══════════════════════════════════════════════════════════
//  全局状态
// ═══════════════════════════════════════════════════════════

static struct {
    bool  initialized;          // SDK 已初始化
    int   devCount;             // 设备数量
    int   devIdx;               // 当前使用的设备索引
    int   devId;                // 设备 ID (1201/1211/1221)
    int   devType;              // 类型 (1=Fs, 2=Fw)
    int   dogId;                // 加密狗 ID
    int   hasColor;             // 是否是彩色版
    int   usbMode;              // USB_MODE_2 或 USB_MODE_3
    int   scanMode;             // SCAN_MODE_LARGE / SMALL
    int   brightness;           // 当前亮度值
    int   midExposureIdx;       // 中间曝光索引
    int   midGlobalGain;        // 中间增益值
    
    // SDK 对象
    CScanner3DObject* pScanner;     // type 4
    CStaticApiObject*  pStaticApi;  // type 2
    
    // 标定参数
    IC_CAMERA_INFO  camerInfo[2];
    IC_CAMERA_INFO  camerInfoCommon[2];
    IC_CAMERA_INFO  camerInfoMini[2];
    PIC_CAMERA_INFO pCamerInfo;     // 指向选中的标定参数
    
    // 颜色标定
    int       colorZy[4];
    PIC_CAMERA_INFO pColorInfo;
    PIC_CAMERA_INFO pColorInfoMini;
    
    // 点云队列（用于异步扫描的数据收集）
    CRITICAL_SECTION  csQueue;
    std::queue<CData3DObject*> frameQueue;  // 已 ICP 配准的帧
    int    frameCount;
    bool   isScanning;
    bool   scanStopping;
    
    // 开始扫描的时间戳（用于超时检测）
    LARGE_INTEGER scanStartTime;
    LARGE_INTEGER perfFreq;
    
} g_ctx;


// ═══════════════════════════════════════════════════════════
//  回调函数（由 SDK 在异步扫描时调用）
// ═══════════════════════════════════════════════════════════

// 扫描仪准备好接收下一帧时触发
static void __cdecl OnProjectOK(void* pCtnt)
{
    if (g_ctx.isScanning && !g_ctx.scanStopping) {
        if (g_ctx.usbMode == USB_MODE_2) {
            // USB 2.0 需要主动触发
            int errCode = 0;
            g_ctx.pScanner->AsynScan(errCode);
            if (errCode != 0) {
                printf("[wrapper] AsynScan err=%d\n", errCode);
            }
        }
        QueryPerformanceCounter(&g_ctx.scanStartTime);
    }
}

// 一帧 3D 数据就绪时触发
static void __cdecl OnScannerDataOk(void* pObj, void* pCtnt)
{
    if (!pObj) return;
    static bool inside = false;
    if (inside) return;  // 防止重入
    inside = true;
    
    CData3DObject* pData = (CData3DObject*)pObj;
    
    if (g_ctx.isScanning && !g_ctx.scanStopping && g_ctx.pCamerInfo) {
        // ── 做 ICP 配准（如果开启） ──
        // 这一步完全照搬 Xhk_demo.cpp 的 ScannerDataOk 逻辑
        
        // 后处理：去边 + 去碎片
        float fMaxProp = 6.0f;
        int cMinPiece = 2500;
        pData->DeleteSmallPieces(fMaxProp, cMinPiece);
        for (int i = 0; i < 2; i++) {
            pData->DeleteBorder(g_ctx.pCamerInfo[0].avgDist, 5.0f);
        }
        
        // 计算法向
        int width, height;
        pData->CopyTo(NULL, width, height);
        
        // ICP 配准
        CData3DObject* pClone = pData->Clone();
        
        EnterCriticalSection(&g_ctx.csQueue);
        if (g_ctx.frameQueue.empty()) {
            // 第一帧，直接入队
            printf("[wrapper] first frame OK, pts=%dx%d\n", width, height);
        } else {
            // 与上一帧 ICP 配准
            CData3DObject* prev = g_ctx.frameQueue.back();
            if (prev) {
                int icpIters[4] = { 5, 4, 0, 0 };
                BOOL icpOK = FALSE;
                
                CData3DObject* pFixed  = prev->Clone();
                CData3DObject* pFloat  = pClone->Clone();
                
                // 先粗配
                pFloat->IcpTo(pFixed, FALSE,
                    g_ctx.pCamerInfo[0].workdist,
                    g_ctx.pCamerInfo[0].avgDist, icpIters);
                
                // 再精配
                icpIters[0] = 10; icpIters[1] = 5;
                icpOK = pFloat->IcpTo(pFixed, FALSE,
                    g_ctx.pCamerInfo[0].workdist,
                    g_ctx.pCamerInfo[0].avgDist * 2, icpIters);
                
                if (icpOK) {
                    // 获取 RT 矩阵，传给 pClone
                    double rt[12];
                    pFloat->GetRT(rt, NULL, NULL);
                    pClone->SetRT(rt, NULL, NULL);
                    g_ctx.frameQueue.push(pClone);
                    g_ctx.frameCount++;
                    printf("[wrapper] frame %d ICP OK (%dx%d)\n",
                           g_ctx.frameCount, width, height);
                } else {
                    printf("[wrapper] ICP failed, skip frame\n");
                    pClone->ReleaseObject();
                }
                
                pFixed->ReleaseObject();
                pFloat->ReleaseObject();
            } else {
                g_ctx.frameQueue.push(pClone);
                g_ctx.frameCount++;
            }
        }
        LeaveCriticalSection(&g_ctx.csQueue);
    }
    
    inside = false;
}


// ═══════════════════════════════════════════════════════════
//  ① wrp_init_sdk — 加载所有 DLL，验证
// ═══════════════════════════════════════════════════════════

__declspec(dllexport) int wrp_init_sdk(void)
{
    if (g_ctx.initialized) return WRAPPER_OK;
    
    ZeroMemory(&g_ctx, sizeof(g_ctx));
    InitializeCriticalSection(&g_ctx.csQueue);
    
    // 加载 ICDevice.dll
    g_hIC = LoadLibraryA("ICDevice.dll");
    if (!g_hIC) { printf("[wrapper] ❌ 找不到 ICDevice.dll\n"); return ERR_DLL_MISSING; }
    
    pIC_Start = (decltype(pIC_Start))GetProcAddress(g_hIC, "IC_Start");
    pIC_Stop  = (decltype(pIC_Stop)) GetProcAddress(g_hIC, "IC_Stop");
    pIC_Get   = (decltype(pIC_Get))  GetProcAddress(g_hIC, "IC_Get");
    pIC_Set   = (decltype(pIC_Set))  GetProcAddress(g_hIC, "IC_Set");
    if (!pIC_Start || !pIC_Stop || !pIC_Get || !pIC_Set) {
        printf("[wrapper] ❌ ICDevice.dll 导出函数不完整\n");
        return ERR_FUNC_MISSING;
    }
    
    // 加载 xhcv2011.dll
    g_hCam = LoadLibraryA("xhcv2011.dll");
    if (!g_hCam) { printf("[wrapper] ❌ 找不到 xhcv2011.dll\n"); return ERR_DLL_MISSING; }
    
    pInitUseCam2011 = (decltype(pInitUseCam2011))GetProcAddress(g_hCam, "InitUseCam2011");
    if (!pInitUseCam2011) { printf("[wrapper] ❌ xhcv2011.dll 缺少 InitUseCam2011\n"); return ERR_FUNC_MISSING; }
    
    // 加载 TwoEyeApi.dll
    g_hTwo = LoadLibraryA("TwoEyeApi.dll");
    if (!g_hTwo) { printf("[wrapper] ❌ 找不到 TwoEyeApi.dll\n"); return ERR_DLL_MISSING; }
    
    // ════════════════════════════════════════════════════════
    // 【TODO(runtime)】xhCreateObject 实际导出名是什么？
    // 在 Windows cmd 中跑：
    //   dumpbin /exports TwoEyeApi.dll | findstr xhCreate
    // 如果函数名不是 "xhCreateObject"，请修改下方字符串
    // ════════════════════════════════════════════════════════
    pxhCreateObject = (decltype(pxhCreateObject))
        GetProcAddress(g_hTwo, "xhCreateObject");     // ← 如名不同，改这里
    
    if (!pxhCreateObject) {
        // ── 备选方案：从 xhcv2011.dll 中找 ──
        pxhCreateObject = (decltype(pxhCreateObject))
            GetProcAddress(g_hCam, "xhCreateObject");
    }
    
    if (!pxhCreateObject) { printf("[wrapper] ❌ 找不到 xhCreateObject\n"); return ERR_FUNC_MISSING; }
    
    // 高频性能计数器
    QueryPerformanceFrequency(&g_ctx.perfFreq);
    
    g_ctx.initialized = true;
    printf("[wrapper] ✅ SDK 初始化完成\n");
    return WRAPPER_OK;
}


// ═══════════════════════════════════════════════════════════
//  ② wrp_open_dongle — 打开加密狗 + 枚举设备
// ═══════════════════════════════════════════════════════════

__declspec(dllexport) int wrp_open_dongle(void)
{
    if (!g_ctx.initialized) return ERR_NO_DONGLE;
    
    // 打开设备（持续采集模式）
    IC_STATUS s = pIC_Start(GRAB_IN_CONTINUOUS_MODE, USB_DEV_BASE_IDX);
    if (s != IC_STATUS_OK) {
        printf("[wrapper] ❌ IC_Start 失败: status=%d\n", s);
        return ERR_NO_DONGLE;
    }
    
    // 获取设备数量
    int devCount = 0;
    s = pIC_Get(CAMERA_OPENED_COUNT, &devCount, 0);
    if (s != IC_STATUS_OK || devCount == 0) {
        printf("[wrapper] ❌ 未发现设备\n");
        pIC_Stop(USB_DEV_BASE_IDX);
        return ERR_NO_DEVICE;
    }
    
    // 枚举设备，找到匹配型号
    int foundIdx = -1;
    int foundId  = -1;
    int foundType = -1;
    int foundDogId = 0;
    
    for (int i = 0; i < devCount; i++) {
        int idFind = 0;
        if (pIC_Get(CAMERA_USER_SET_ID, &idFind, i) == IC_STATUS_OK) {
            // ════════════════════════════════════════════
            // 【TODO(runtime)】实际设备 ID 是什么？
            // 在 Windows 真机上运行 Xhk_demo.exe 看看打印
            // 或用 dumpbin 确认。当前已知值：
            //   1201 = Fs (type 1), 1211 = Fw (type 2), 1221 = ? (type 3)
            // ════════════════════════════════════════════
            if (idFind == 1201) { foundIdx = i; foundId = 1201; foundType = 1; }
            if (idFind == 1211) { foundIdx = i; foundId = 1211; foundType = 2; }
            if (idFind == 1221) { foundIdx = i; foundId = 1221; foundType = 3; }
        }
    }
    
    if (foundIdx < 0) {
        printf("[wrapper] ❌ 未找到匹配设备 (devCount=%d)\n", devCount);
        pIC_Stop(USB_DEV_BASE_IDX);
        return ERR_NO_DEVICE;
    }
    
    g_ctx.devCount = devCount;
    g_ctx.devIdx   = foundIdx;
    g_ctx.devId    = foundId;
    g_ctx.devType  = foundType;
    
    printf("[wrapper] ✅ 加密狗已打开, 设备%d个, 使用索引%d(ID=%d, type=%d)\n",
           devCount, foundIdx, foundId, foundType);
    return WRAPPER_OK;
}


// ═══════════════════════════════════════════════════════════
//  ③ wrp_init_scanner — 创建 SDK 对象 + 初始化
// ═══════════════════════════════════════════════════════════

static BOOL InitCameraHardware(void);
static BOOL LoadCalibrationData(void);

__declspec(dllexport) int wrp_init_scanner(const ScannerParams* params)
{
    if (!g_ctx.initialized)  return ERR_INIT_FAIL;
    if (!params)             return ERR_INVALID_PARAM;
    if (g_ctx.devCount <= 0) return ERR_NO_DEVICE;
    
    g_ctx.usbMode = params->usbMode;
    g_ctx.scanMode = params->scanMode;
    g_ctx.brightness = params->brightness;
    
    // ── 第一步：初始化相机硬件 ──
    if (!InitCameraHardware()) {
        printf("[wrapper] ❌ 相机硬件初始化失败\n");
        return ERR_INIT_FAIL;
    }
    
    // ── 第二步：填充 XH_SCANNER_PARAMS ──
    XH_SCANNER_PARAMS sp;
    ZeroMemory(&sp, sizeof(sp));
    
    sp.m_dbExpTime[0]     = params->expTime[0] > 0 ? params->expTime[0] : 16.4;
    sp.m_dbExpTime[1]     = params->expTime[1] > 0 ? params->expTime[1] : 32.9;
    sp.m_dbExpTime[2]     = params->expTime[2] > 0 ? params->expTime[2] : 49.4;
    sp.m_iInitExpTime     = params->initExpTime;
    sp.m_nInitGlobalGain  = params->initGlobalGain;
    sp.m_lPrjWidth        = params->prjWidth > 0 ? params->prjWidth : 2000;
    sp.m_prjResSel        = params->prjResSel > 0 ? params->prjResSel : 9;
    sp.m_prjTimes         = params->prjTimes > 0 ? params->prjTimes : 3;
    sp.m_nPrjectMethod    = params->prjMethod > 0 ? params->prjMethod : 1;
    sp.m_nDistenceFiltProp= params->distFiltProp;
    sp.m_nProjectorInRowMode = 0;
    sp.m_nMinProcessPixVal   = params->minProcessPixVal;
    
    // ════════════════════════════════════════════════════════
    // 【TODO(runtime)】硬件设备类型值？
    // 从 Xhk_demo.cpp 逻辑看：
    //   USB3.0: type 1→11(Fs), type 2→12(Fw)
    //   USB2.0: 3
    // ======================================================
    if (g_ctx.usbMode == USB_MODE_3) {
        switch (g_ctx.devType) {
            case 1: sp.m_nHardwareDevType = 11; break;  // ← 确认 Fs 用 11
            case 2: sp.m_nHardwareDevType = 12; break;  // ← 确认 Fw 用 12
            default: sp.m_nHardwareDevType = 11; break;
        }
    } else {
        sp.m_nHardwareDevType = 3;
    }
    
    sp.m_nCameraColorMode = 0;
    sp.m_nLeftCameraUserID = g_ctx.devId;
    
    // 注册回调
    sp.m_pProjectOK    = &OnProjectOK;
    sp.m_pProjectContent = NULL;
    sp.m_pDataOK       = &OnScannerDataOk;
    sp.m_pDataContent  = NULL;
    sp.m_pImageOK      = NULL;      // 不需要图像回调
    sp.m_pImageContent = NULL;
    
    // ── 第三步：创建 CScanner3DObject ──
    CScanner3DObject* pScanner = NULL;
    int err = pxhCreateObject((void**)&pScanner, &sp, 4);
    if (err != 0 || !pScanner) {
        printf("[wrapper] ❌ xhCreateObject(type=4) 失败: %d\n", err);
        return ERR_INIT_FAIL;
    }
    g_ctx.pScanner = pScanner;
    
    // ── 第四步：创建 CStaticApiObject ──
    CStaticApiObject* pStatic = NULL;
    err = pxhCreateObject((void**)&pStatic, NULL, 2);
    if (err == 0 && pStatic) {
        g_ctx.pStaticApi = pStatic;
    }
    
    // ── 第五步：加载标定数据 ──
    if (!LoadCalibrationData()) {
        printf("[wrapper] ⚠️ 标定数据加载异常，但继续\n");
    }
    
    // ── 第六步：设置扫描范围 + 亮度 ──
    g_ctx.pScanner->ProjectBack(g_ctx.scanMode);
    LONG newVal = 0;
    g_ctx.pScanner->AdjImgZY(g_ctx.brightness, newVal);
    
    printf("[wrapper] ✅ 扫描仪初始化完成\n");
    return WRAPPER_OK;
}


// ═══════════════════════════════════════════════════════════
//  内部辅助函数
// ═══════════════════════════════════════════════════════════

static BOOL InitCameraHardware(void)
{
    // ════════════════════════════════════════════════════════
    // 【TODO(runtime)】InitUseCam2011 的参数？
    // 从 Xhk_demo.cpp 看，InitUseCam2011() 是无参调用
    // 如果编译不通过或有不同签名，修改下方调用
    // ════════════════════════════════════════════════════════
    int r = pInitUseCam2011();   // ← 假设无参，如果需参数请修改
    if (r != 0) {
        printf("[wrapper] ❌ InitUseCam2011 返回 %d\n", r);
        return FALSE;
    }
    return TRUE;
}

static BOOL LoadCalibrationData(void)
{
    // 这部分完全照搬 Xhk_demo.cpp 的 init 逻辑
    
    // 获取加密狗 ID
    char dogBuf[3280] = {0};  // 3K + 256 头部
    int dogRet = g_ctx.pScanner->GetDogInfo(dogBuf);
    if (dogRet != 0) {
        printf("[wrapper] ❌ GetDogInfo 返回 %d\n", dogRet);
        return FALSE;
    }
    
    // ════════════════════════════════════════════════════════
    // 【TODO(runtime)】标定文件路径？
    // 当前使用当前目录下的 icdev_info/ 子目录
    // 需要在 DLL 所在目录创建 icdev_info/ 并把 SDK 的标定文件放进去
    // ════════════════════════════════════════════════════════
    char calibDir[MAX_PATH] = "";
    GetCurrentDirectoryA(MAX_PATH, calibDir);
    strcat_s(calibDir, MAX_PATH, "\\icdev_info\\");
    
    // 检查标定文件是否存在
    // （简化版：跳过详细标定，使用默认参数）
    // 更完整的标定加载参考 Xhk_demo.cpp 的 init 阶段
    
    IC_CAMERA_INFO* pInfo = &g_ctx.camerInfo[0];
    
    // ════════════════════════════════════════════════════════
    // 【TODO(runtime)】标定参数从哪里获取？
    // 需要从 icdev_info/ 目录的 .clb / .ccf 文件读取
    // 或从 GetDogInfo 返回的数据中提取
    // 或者 pScanner 自己维护了标定信息，不需手动加载
    // 先在真机上跑 Xhk_demo.exe，看 clb 文件的加载路径
    // ════════════════════════════════════════════════════════
    // 临时使用默认值
    pInfo->workdist = 300.0;   // 典型工作距离 300mm
    pInfo->avgDist  = 0.12;    // 典型平均点距 0.12mm
    g_ctx.pCamerInfo = pInfo;
    
    printf("[wrapper] ⚠️ 标定数据使用默认值（实际标定文件需真机确认）\n");
    printf("       workdist=%.1f mm, avgDist=%.3f mm\n",
           pInfo->workdist, pInfo->avgDist);
    
    // 检测是否彩色版
    int colorType = 0;
    // ════════════════════════════════════════════════════════
    // 【TODO(runtime)】ColorType() 检测逻辑？
    // 在 Xhk_demo.cpp 通过 ColorType(index) 函数判断
    // 如果 N/A，默认非彩色
    // ════════════════════════════════════════════════════════
    g_ctx.hasColor = 0;   // 默认无彩色
    // g_ctx.hasColor = (colorType == 2) ? 1 : 0;
    
    return TRUE;
}


// ═══════════════════════════════════════════════════════════
//  ④ wrp_scan_frame — 单次扫描（同步阻塞）
// ═══════════════════════════════════════════════════════════

__declspec(dllexport) void* wrp_scan_frame(void)
{
    if (!g_ctx.pScanner) return NULL;
    
    // 清空旧帧
    EnterCriticalSection(&g_ctx.csQueue);
    while (!g_ctx.frameQueue.empty()) {
        CData3DObject* p = g_ctx.frameQueue.front();
        g_ctx.frameQueue.pop();
        p->ReleaseObject();
    }
    g_ctx.frameCount = 0;
    LeaveCriticalSection(&g_ctx.csQueue);
    
    // 暂时打开扫描
    g_ctx.isScanning = true;
    g_ctx.scanStopping = false;
    
    // 触发一次扫描
    int errCode = 0;
    BOOL ret = g_ctx.pScanner->AsynScan(errCode);
    if (!ret || errCode != 0) {
        printf("[wrapper] AsynScan 返回 %d, err=%d\n", ret, errCode);
        g_ctx.isScanning = false;
        return NULL;
    }
    
    // 等待数据（最多 5 秒）
    // 数据通过 OnScannerDataOk 回调写入 g_ctx.frameQueue
    LARGE_INTEGER t0, t1;
    QueryPerformanceCounter(&t0);
    
    CData3DObject* result = NULL;
    while (true) {
        QueryPerformanceCounter(&t1);
        double elapsed = (double)(t1.QuadPart - t0.QuadPart) / (double)g_ctx.perfFreq.QuadPart;
        if (elapsed > 5.0) break;  // 超时
        
        EnterCriticalSection(&g_ctx.csQueue);
        if (!g_ctx.frameQueue.empty()) {
            result = g_ctx.frameQueue.front();
            g_ctx.frameQueue.pop();
        }
        LeaveCriticalSection(&g_ctx.csQueue);
        
        if (result) break;
        Sleep(10);
    }
    
    g_ctx.isScanning = false;
    
    if (result) {
        printf("[wrapper] ✅ 单帧扫描完成\n");
        return result;
    }
    printf("[wrapper] ❌ 单帧扫描超时\n");
    return NULL;
}


// ═══════════════════════════════════════════════════════════
//  ⑦ wrp_start_continuous_scan — 开始连续扫描
// ═══════════════════════════════════════════════════════════

__declspec(dllexport) int wrp_start_continuous_scan(void)
{
    if (!g_ctx.pScanner) return ERR_INIT_FAIL;
    
    g_ctx.isScanning = true;
    g_ctx.scanStopping = false;
    g_ctx.frameCount = 0;
    
    // 清空旧帧
    EnterCriticalSection(&g_ctx.csQueue);
    while (!g_ctx.frameQueue.empty()) {
        CData3DObject* p = g_ctx.frameQueue.front();
        g_ctx.frameQueue.pop();
        p->ReleaseObject();
    }
    LeaveCriticalSection(&g_ctx.csQueue);
    
    // 如果是 USB3.0，设置投影模式为 -3（连续扫描）
    if (g_ctx.usbMode == USB_MODE_3) {
        g_ctx.pScanner->ProjectBack(-3);
    }
    
    // 触发首次扫描
    int errCode = 0;
    if (g_ctx.usbMode == USB_MODE_3) {
        g_ctx.pScanner->AsynScan(errCode);
    }
    QueryPerformanceCounter(&g_ctx.scanStartTime);
    
    printf("[wrapper] ✅ 连续扫描已启动\n");
    return WRAPPER_OK;
}


// ═══════════════════════════════════════════════════════════
//  ⑧ wrp_stop_scan — 停止连续扫描
// ═══════════════════════════════════════════════════════════

__declspec(dllexport) int wrp_stop_scan(void)
{
    g_ctx.isScanning = false;
    g_ctx.scanStopping = true;
    
    if (g_ctx.pScanner && g_ctx.usbMode == USB_MODE_3) {
        g_ctx.pScanner->ProjectBack(-5);  // 停止投影
    }
    
    Sleep(1000);  // 等待最后一帧就绪
    
    int frameCount = g_ctx.frameCount;
    printf("[wrapper] ✅ 扫描停止，共收集 %d 帧\n", frameCount);
    return WRAPPER_OK;
}


// ═══════════════════════════════════════════════════════════
//  ⑨ wrp_get_latest_frame — 取最新一帧
// ═══════════════════════════════════════════════════════════

__declspec(dllexport) void* wrp_get_latest_frame(void)
{
    EnterCriticalSection(&g_ctx.csQueue);
    CData3DObject* result = NULL;
    if (!g_ctx.frameQueue.empty()) {
        result = g_ctx.frameQueue.front();
        g_ctx.frameQueue.pop();
    }
    LeaveCriticalSection(&g_ctx.csQueue);
    return result;
}


// ═══════════════════════════════════════════════════════════
//  数据对象操作
// ═══════════════════════════════════════════════════════════

__declspec(dllexport) int wrp_data_clone(void* src, void** dst)
{
    if (!src || !dst) return ERR_INVALID_PARAM;
    CData3DObject* pSrc = (CData3DObject*)src;
    *dst = pSrc->Clone();
    return (*dst) ? WRAPPER_OK : ERR_INIT_FAIL;
}

__declspec(dllexport) void wrp_data_release(void* obj)
{
    if (obj) ((CData3DObject*)obj)->ReleaseObject();
}

__declspec(dllexport) int wrp_data_copy_to(void* obj, Point3D* ptsBuf,
                                            int* width, int* height)
{
    if (!obj || !width || !height) return ERR_INVALID_PARAM;
    
    CData3DObject* pData = (CData3DObject*)obj;
    int w = *width, h = *height;
    
    if (ptsBuf == NULL) {
        // 只查宽高
        BOOL ret = pData->CopyTo(NULL, *width, *height);
        return ret ? WRAPPER_OK : ERR_SCAN_FAIL;
    }
    
    // ── PT3D20 到 Point3D 的转换 ──
    // 注意：PT3D20 的 f 字段是 uchar：第0位=有效标志
    // 而 Point3D 直接用 f!=0 表示有效
    
    // ════════════════════════════════════════════════════════
    // 【TODO(runtime)】PT3D20.f 的标志位含义确认
    // 从 Xhk_demo.cpp 看：if(vecPt[i].f & 0x01 == 1)
    // 所以 f 的第0位是有效标志。下面用 f & 1 判断
    // ════════════════════════════════════════════════════════
    PT3D20* tmpBuf = new PT3D20[w * h];
    BOOL ret = pData->CopyTo(tmpBuf, *width, *height);
    if (ret) {
        for (int i = 0; i < (*width) * (*height); i++) {
            ptsBuf[i].x = tmpBuf[i].x;
            ptsBuf[i].y = tmpBuf[i].y;
            ptsBuf[i].z = tmpBuf[i].z;
            ptsBuf[i].r = tmpBuf[i].r;
            ptsBuf[i].g = tmpBuf[i].g;
            ptsBuf[i].b = tmpBuf[i].b;
            ptsBuf[i].f = (tmpBuf[i].f & 0x01) ? 1 : 0;  // ← 确认标志位
        }
    }
    delete[] tmpBuf;
    return ret ? WRAPPER_OK : ERR_SCAN_FAIL;
}

__declspec(dllexport) int wrp_data_icp_to(void* data, void* target,
                                           int* iters /* [in/out] */)
{
    if (!data || !target) return ERR_INVALID_PARAM;
    CData3DObject* pData   = (CData3DObject*)data;
    CData3DObject* pTarget = (CData3DObject*)target;
    
    int icpIters[4] = { iters ? iters[0] : 5,
                        iters && iters[1] ? iters[1] : 4, 0, 0 };
    BOOL ok = pData->IcpTo(pTarget, FALSE,
        g_ctx.pCamerInfo[0].workdist,
        g_ctx.pCamerInfo[0].avgDist, icpIters);
    
    return ok ? WRAPPER_OK : ERR_SCAN_FAIL;
}

__declspec(dllexport) int wrp_data_get_rt(void* obj, double* rtOut)
{
    if (!obj || !rtOut) return ERR_INVALID_PARAM;
    ((CData3DObject*)obj)->GetRT(rtOut, NULL, NULL);
    return WRAPPER_OK;
}

__declspec(dllexport) int wrp_data_set_rt(void* obj, const double* rtIn)
{
    if (!obj || !rtIn) return ERR_INVALID_PARAM;
    ((CData3DObject*)obj)->SetRT((double*)rtIn, NULL, NULL);
    return WRAPPER_OK;
}

__declspec(dllexport) int wrp_data_delete_border(void* obj, float distProp)
{
    if (!obj || !g_ctx.pCamerInfo) return ERR_INVALID_PARAM;
    CData3DObject* p = (CData3DObject*)obj;
    int ret = p->DeleteBorder(g_ctx.pCamerInfo[0].avgDist, distProp);
    return ret >= 0 ? WRAPPER_OK : ERR_SCAN_FAIL;
}

__declspec(dllexport) int wrp_data_delete_small_pieces(void* obj,
                                                       float maxProp,
                                                       int minPiece)
{
    if (!obj) return ERR_INVALID_PARAM;
    CData3DObject* p = (CData3DObject*)obj;
    int ret = p->DeleteSmallPieces(maxProp, minPiece);
    return ret >= 0 ? WRAPPER_OK : ERR_SCAN_FAIL;
}

__declspec(dllexport) int wrp_data_create_box(void* obj)
{
    if (!obj) return ERR_INVALID_PARAM;
    CData3DObject* p = (CData3DObject*)obj;
    // ══════════════════════════════════════════════
    // 【TODO(runtime)】体素尺寸参数
    // 从 Xhk_demo.cpp: CreateBox(0.5, 2.56)
    // 0.5 = mmdist(细节度), 2.56 = trucMulti(截断倍数)
    // ══════════════════════════════════════════════
    p->CreateBox(0.5, 2.56f);   // ← 需要根据网格大小调整
    return WRAPPER_OK;
}

__declspec(dllexport) int wrp_data_integrate(void* dst, void* src)
{
    if (!dst || !src) return ERR_INVALID_PARAM;
    CData3DObject* pDst = (CData3DObject*)dst;
    CData3DObject* pSrc = (CData3DObject*)src;
    BOOL ok = pDst->Integrate(pSrc);
    return ok ? WRAPPER_OK : ERR_SCAN_FAIL;
}


// ─── 导出函数 ───

__declspec(dllexport) int wrp_data_save_stl(void* obj, const char* path,
                                             float* progress)
{
    if (!obj || !path) return ERR_INVALID_PARAM;
    CData3DObject* p = (CData3DObject*)obj;
    float prog = 0;
    BOOL ok = p->SaveAsSTL(path, progress ? *progress : prog);
    return ok ? WRAPPER_OK : ERR_SCAN_FAIL;
}

__declspec(dllexport) int wrp_data_save_asc(void* obj, const char* path,
                                             int withColor)
{
    if (!obj || !path) return ERR_INVALID_PARAM;
    CData3DObject* p = (CData3DObject*)obj;
    BOOL ok = p->SaveAsAsc(path, withColor != 0);
    return ok ? WRAPPER_OK : ERR_SCAN_FAIL;
}

__declspec(dllexport) int wrp_data_save_ply(void* obj, const char* path,
                                             float* progress)
{
    if (!obj || !path) return ERR_INVALID_PARAM;
    CData3DObject* p = (CData3DObject*)obj;
    float prog = 0;
    // PLY 保存需要先转成 ASC 再通过 StaticApi 转 PLY
    // 简化：先用 ASC 中转
    char ascTmp[MAX_PATH];
    sprintf_s(ascTmp, "%s.tmp.asc", path);
    p->SaveAsAsc(ascTmp, TRUE);
    
    if (g_ctx.pStaticApi) {
        BOOL ok = g_ctx.pStaticApi->WrapCloudNormalToPly(ascTmp, path,
            11 /* level */, 8.5f /* fHole */, TRUE, prog, 0);
        DeleteFileA(ascTmp);
        return ok ? WRAPPER_OK : ERR_SCAN_FAIL;
    }
    return ERR_INIT_FAIL;
}

__declspec(dllexport) int wrp_data_save_obj(void* obj, const char* path,
                                             float* progress)
{
    if (!obj || !path) return ERR_INVALID_PARAM;
    CData3DObject* p = (CData3DObject*)obj;
    float prog = 0;
    BOOL ok = p->SaveAsOBJ(path, progress ? *progress : prog);
    return ok ? WRAPPER_OK : ERR_SCAN_FAIL;
}


// ═══════════════════════════════════════════════════════════
//  全局配准
// ═══════════════════════════════════════════════════════════

// （简化版：先做 ICP 配准的收集，全局配准后续迭代）
// 当前 wrp_scan_frame 和连续扫描已经自动做 ICP 配准
// 全局配准 + 体素融合 + 彩色映射 是后续下一版

__declspec(dllexport) void* wrp_create_global_registration(void)
{
    // ════════════════════════════════════════════════════════
    // 【TODO(next)】CGlobalRegistrationForMarkObject 的创建
    // 需要先确认 xhCreateObject 是否有 type=8 的支持
    // 以及两帧之间的标记点匹配逻辑
    // ════════════════════════════════════════════════════════
    // 临时返回 NULL
    return NULL;
}

__declspec(dllexport) int wrp_gr_add_frame(void* grObj, void* dataObj,
                                           const char* label)
{
    return ERR_INVALID_PARAM;  // 待实现
}

__declspec(dllexport) int wrp_gr_analyze(void* grObj, float* progress)
{
    return ERR_INVALID_PARAM;  // 待实现
}

__declspec(dllexport) int wrp_gr_run(void* grObj, int iters)
{
    return ERR_INVALID_PARAM;  // 待实现
}

__declspec(dllexport) void* wrp_gr_fuse_and_export(void* grObj,
                                                    const char* stlPath,
                                                    int hasColor)
{
    return NULL;  // 待实现
}


// ═══════════════════════════════════════════════════════════
//  控制函数
// ═══════════════════════════════════════════════════════════

__declspec(dllexport) int wrp_set_brightness(int value, int* newValue)
{
    if (!g_ctx.pScanner) return ERR_INIT_FAIL;
    LONG newVal = 0;
    BOOL ok = g_ctx.pScanner->AdjImgZY(value, newVal);
    if (newValue) *newValue = newVal;
    return ok ? WRAPPER_OK : ERR_INIT_FAIL;
}

__declspec(dllexport) int wrp_set_project_mode(int mode)
{
    if (!g_ctx.pScanner) return ERR_INIT_FAIL;
    BOOL ok = g_ctx.pScanner->ProjectBack(mode);
    return ok ? WRAPPER_OK : ERR_INIT_FAIL;
}

__declspec(dllexport) int wrp_get_device_info(DeviceInfo* info)
{
    if (!info) return ERR_INVALID_PARAM;
    info->devCount = g_ctx.devCount;
    info->devId    = g_ctx.devId;
    info->devType  = g_ctx.devType;
    info->dogId    = g_ctx.dogId;
    info->hasColor = g_ctx.hasColor;
    info->workDist = g_ctx.pCamerInfo ? g_ctx.pCamerInfo[0].workdist : 0;
    info->avgDist  = g_ctx.pCamerInfo ? g_ctx.pCamerInfo[0].avgDist : 0;
    return WRAPPER_OK;
}


// ═══════════════════════════════════════════════════════════
//  关闭 / 清理
// ═══════════════════════════════════════════════════════════

__declspec(dllexport) void wrp_close_scanner(void)
{
    g_ctx.isScanning = false;
    g_ctx.scanStopping = true;
    Sleep(100);
    
    // 清空队列
    EnterCriticalSection(&g_ctx.csQueue);
    while (!g_ctx.frameQueue.empty()) {
        CData3DObject* p = g_ctx.frameQueue.front();
        g_ctx.frameQueue.pop();
        p->ReleaseObject();
    }
    LeaveCriticalSection(&g_ctx.csQueue);
    
    if (g_ctx.pScanner) {
        g_ctx.pScanner->ProjectBack(-5);  // 关投影
        Sleep(100);
        g_ctx.pScanner->ReleaseObject();
        g_ctx.pScanner = NULL;
    }
    if (g_ctx.pStaticApi) {
        g_ctx.pStaticApi->ReleaseObject();
        g_ctx.pStaticApi = NULL;
    }
    printf("[wrapper] ✅ 扫描仪已关闭\n");
}

__declspec(dllexport) void wrp_close_dongle(void)
{
    g_ctx.devCount = 0;
    pIC_Stop(USB_DEV_BASE_IDX);
    printf("[wrapper] ✅ 加密狗已关闭\n");
}

__declspec(dllexport) void wrp_cleanup_sdk(void)
{
    wrp_close_scanner();
    wrp_close_dongle();
    
    DeleteCriticalSection(&g_ctx.csQueue);
    
    if (g_hTwo)  { FreeLibrary(g_hTwo);  g_hTwo  = NULL; }
    if (g_hCam)  { FreeLibrary(g_hCam);  g_hCam  = NULL; }
    if (g_hIC)   { FreeLibrary(g_hIC);   g_hIC   = NULL; }
    
    g_ctx.initialized = false;
    printf("[wrapper] ✅ SDK 已完全清理\n");
}
