// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "ANSOCRBase.h"
#include "ANSCpuOCR.h"
#include "ANSOnnxOCR.h"
#include "ANSRtOCR.h"
#include "ANSLibsLoader.h"
#include "ANSGpuFrameRegistry.h"
#include <json.hpp>
#include "NV12PreprocessHelper.h"
#include "engine/TRTEngineCache.h"
#include "engine/EnginePoolManager.h"
#include <unordered_map>
#include <condition_variable>
#include <cstdint>

// DebugView: filter on "[ANSOCR]" — gated by ANSCORE_DEBUGVIEW in ANSLicense.h.

// Handle registry with refcount — prevents use-after-free when
// ReleaseANSOCRHandle is called while inference is still running.
// destructionStarted: set by the first Unregister caller; blocks new Acquires
// and makes subsequent Unregister calls return false without deleting.
// Prevents double-free when Release is raced on the same handle.
struct OCREntry { int refcount; bool destructionStarted; };
static std::unordered_map<ANSCENTER::ANSOCRBase*, OCREntry>& OCRHandleRegistry() {
	static std::unordered_map<ANSCENTER::ANSOCRBase*, OCREntry> s;
	return s;
}
static std::mutex& OCRHandleRegistryMutex() {
	static std::mutex m;
	return m;
}
static std::condition_variable& OCRHandleRegistryCV() {
	static std::condition_variable cv;
	return cv;
}

static void RegisterOCRHandle(ANSCENTER::ANSOCRBase* h) {
	std::lock_guard<std::mutex> lk(OCRHandleRegistryMutex());
	OCRHandleRegistry()[h] = { 1, false };
	ANS_DBG("ANSOCR","Register: handle=%p (uint=%llu) registrySize=%zu",
	       (void*)h, (unsigned long long)(uintptr_t)h, OCRHandleRegistry().size());
}

static ANSCENTER::ANSOCRBase* AcquireOCRHandle(ANSCENTER::ANSOCRBase* h) {
	std::lock_guard<std::mutex> lk(OCRHandleRegistryMutex());
	auto it = OCRHandleRegistry().find(h);
	if (it == OCRHandleRegistry().end()) {
		ANS_DBG("ANSOCR","Acquire FAIL: handle=%p (uint=%llu) NOT in registry. registrySize=%zu",
		       (void*)h, (unsigned long long)(uintptr_t)h, OCRHandleRegistry().size());
		size_t i = 0;
		for (auto& kv : OCRHandleRegistry()) {
			ANS_DBG("ANSOCR","  registry[%zu] = %p (uint=%llu) refcount=%d destructionStarted=%d",
			       i++, (void*)kv.first, (unsigned long long)(uintptr_t)kv.first,
			       kv.second.refcount, kv.second.destructionStarted ? 1 : 0);
		}
		return nullptr;
	}
	if (it->second.destructionStarted) {
		ANS_DBG("ANSOCR","Acquire FAIL: handle=%p is being destroyed (destructionStarted=true)", (void*)h);
		return nullptr;
	}
	it->second.refcount++;
	ANS_DBG("ANSOCR","Acquire OK:   handle=%p refcount=%d", (void*)h, it->second.refcount);
	return h;
}

static bool ReleaseOCRHandleRef(ANSCENTER::ANSOCRBase* h) {
	std::lock_guard<std::mutex> lk(OCRHandleRegistryMutex());
	auto it = OCRHandleRegistry().find(h);
	if (it == OCRHandleRegistry().end()) return false;
	it->second.refcount--;
	if (it->second.refcount <= 0) {
		OCRHandleRegistryCV().notify_all();
	}
	return false;  // Only Unregister deletes.
}

static bool UnregisterOCRHandle(ANSCENTER::ANSOCRBase* h) {
	std::unique_lock<std::mutex> lk(OCRHandleRegistryMutex());
	auto it = OCRHandleRegistry().find(h);
	if (it == OCRHandleRegistry().end()) {
		ANS_DBG("ANSOCR","Unregister: handle=%p NOT in registry (already gone)", (void*)h);
		return false;
	}
	if (it->second.destructionStarted) {
		ANS_DBG("ANSOCR","Unregister: handle=%p already being destroyed by another thread, returning false", (void*)h);
		return false;  // Another thread already owns the delete.
	}
	ANS_DBG("ANSOCR","Unregister: handle=%p starting (refcount before=%d)", (void*)h, it->second.refcount);
	it->second.destructionStarted = true;
	it->second.refcount--;
	bool ok = OCRHandleRegistryCV().wait_for(lk, std::chrono::seconds(5), [&]() {
		auto it2 = OCRHandleRegistry().find(h);
		return it2 == OCRHandleRegistry().end() || it2->second.refcount <= 0;
	});
	if (!ok) {
		ANS_DBG("ANSOCR","WARNING: Unregister timed out waiting for in-flight inference on handle=%p", (void*)h);
		OutputDebugStringA("WARNING: UnregisterOCRHandle timed out waiting for in-flight inference\n");
	}
	OCRHandleRegistry().erase(h);
	return true;
}

// RAII guard — ensures ReleaseOCRHandleRef is always called, preventing
// refcount leaks that would cause UnregisterOCRHandle to deadlock.
class OCRHandleGuard {
	ANSCENTER::ANSOCRBase* engine;
public:
	explicit OCRHandleGuard(ANSCENTER::ANSOCRBase* e) : engine(e) {}
	~OCRHandleGuard() { if (engine) ReleaseOCRHandleRef(engine); }
	ANSCENTER::ANSOCRBase* get() const { return engine; }
	explicit operator bool() const { return engine != nullptr; }
	OCRHandleGuard(const OCRHandleGuard&) = delete;
	OCRHandleGuard& operator=(const OCRHandleGuard&) = delete;
};

// RAII guard — sets the per-thread current GPU frame pointer and always
// clears it on scope exit, even if the wrapped inference call throws.
// Without this, a throwing RunInference leaves tl_currentGpuFrame pointing
// at a GpuFrameData that may be freed before the next call on this thread,
// causing use-after-free or stale NV12 data on subsequent frames.
class GpuFrameScope {
public:
	explicit GpuFrameScope(GpuFrameData* f) { tl_currentGpuFrame() = f; }
	~GpuFrameScope()                        { tl_currentGpuFrame() = nullptr; }
	GpuFrameScope(const GpuFrameScope&) = delete;
	GpuFrameScope& operator=(const GpuFrameScope&) = delete;
};

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		// When lpReserved != NULL, the process is terminating via ExitProcess.
		// The OS has already killed all worker threads (idle timers, CUDA
		// threads, etc.).  Set the global flag so atexit destructors skip
		// thread joins and CUDA/TRT cleanup that would fail on a dead context.
		if (lpReserved != nullptr) {
			g_processExiting().store(true, std::memory_order_relaxed);
			break;
		}

		// Dynamic FreeLibrary — threads are still alive, safe to clean up.
		try {
			std::vector<ANSCENTER::ANSOCRBase*> leakedHandles;
			{
				std::lock_guard<std::mutex> lk(OCRHandleRegistryMutex());
				for (auto& [h, _] : OCRHandleRegistry())
					leakedHandles.push_back(h);
				OCRHandleRegistry().clear();
			}
			for (auto* h : leakedHandles) {
				try { h->Destroy(); delete h; } catch (...) {}
			}
			try { EnginePoolManager<float>::instance().clearAll(); } catch (...) {}
			try { TRTEngineCache::instance().clearAll(); } catch (...) {}
		} catch (...) {}
		break;
	}
	return TRUE;
}

// Extended version with limitSideLen parameter — new callers should use this.
extern "C" ANSOCR_API int		CreateANSOCRHandleEx(ANSCENTER::ANSOCRBase** Handle, const char* licenseKey,
	const char* modelFilePath, const char* modelFileZipPassword,
	int language, int engineMode, // 0: autodetect, 1 gpu, 2 cpu
	int gpuId,
	double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio,
	double classifierThreshold, int useDilation, int limitSideLen) {
	ANS_DBG("ANSOCR","CreateEx called: HandlePtr=%p, *Handle(in)=%p, language=%d, engineMode=%d, gpuId=%d, limitSideLen=%d, modelPath=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), language, engineMode, gpuId, limitSideLen,
	       modelFilePath ? modelFilePath : "(null)");
	if (Handle == nullptr) { ANS_DBG("ANSOCR","CreateEx FAIL: Handle is null"); return 0; }
	try {
		// Ensure all shared DLLs (OpenCV, OpenVINO, TRT, ORT) are pre-loaded
		ANSCENTER::ANSLibsLoader::Initialize();
		ANSCENTER::EngineType engineType = ANSCENTER::ANSLicenseHelper::CheckHardwareInformation();
		{
			// Describe the backend the engine-selector below will actually choose
			// for this (hardware, engineMode) combination. Previous versions of
			// this log claimed "TensorRT OCR enabled" based on hardware alone,
			// which was misleading because engineMode=0 (auto) unconditionally
			// picked ONNX — users saw the log and assumed TRT was running.
			const bool isNvidia = (engineType == ANSCENTER::EngineType::NVIDIA_GPU);
			const bool willUseTRT =
				isNvidia && (engineMode == 0 /* auto → TRT on NVIDIA */ ||
				             engineMode == 1 /* GPU  → TRT on NVIDIA */);
			const char* vendorTag =
				engineType == ANSCENTER::EngineType::NVIDIA_GPU
					? (willUseTRT ? "NVIDIA_GPU (TensorRT OCR active)"
					              : "NVIDIA_GPU (TensorRT available, but engineMode forces ONNX)")
				: engineType == ANSCENTER::EngineType::AMD_GPU      ? "AMD_GPU (ONNX Runtime / DirectML, TensorRT OCR unavailable)"
				: engineType == ANSCENTER::EngineType::OPENVINO_GPU ? "OPENVINO_GPU (ONNX Runtime / OpenVINO, TensorRT OCR unavailable)"
				:                                                     "CPU (ONNX Runtime, TensorRT OCR unavailable)";
			char buf[192];
			snprintf(buf, sizeof(buf),
				"[ANSOCR] CreateANSOCRHandleEx: detected engineType=%d [%s], engineMode=%d\n",
				static_cast<int>(engineType), vendorTag, engineMode);
			OutputDebugStringA(buf);
		}

		// Pure constructor: ignore *Handle(in). LabVIEW's CLF Node marshalling
		// reuses the same temp buffer per call site, so *Handle(in) often holds
		// leftover bytes from the previous Create's output even when the actual
		// LabVIEW wire is a different, freshly-allocated instance. Inspecting
		// *Handle(in) and destroying what we "see" tears down legitimate
		// parallel instances. (Same reasoning as CreateANSAWSHandle.)
		// Trade-off: a true double-Create on the same wire leaks the prior
		// handle -- caller's bug; the alternative is far worse.
		*Handle = nullptr;

		// Validate limitSideLen: must be in (0, 20000], default to 960
		if (limitSideLen <= 0 || limitSideLen > 20000)
			limitSideLen = 960;

		// Backward compatibility: legacy PaddleOCR model uses CPU OCR engine
		std::string modelPath(modelFilePath ? modelFilePath : "");
		bool isLegacyModel = (modelPath.find("ANS_GenericOCR_v1.0") != std::string::npos);

		if (isLegacyModel) {
			(*Handle) = new ANSCENTER::ANSCPUOCR();
			ANS_DBG("ANSOCR","CreateEx: legacy model detected, allocated ANSCPUOCR=%p", (void*)*Handle);
		}
		else {
			// ANSRTOCR wraps PaddleOCRV5RTEngine which is strictly NVIDIA/CUDA:
			// RTOCRDetector/Classifier/Recognizer use cv::cuda::GpuMat, cudaMalloc,
			// cudaMemcpy and link against cudart.  Instantiating it on AMD, Intel
			// or pure-CPU machines either crashes in the CUDA runtime loader or
			// hangs in amdkmdag when DirectML and TRT coexist.  We therefore
			// hard-gate the TRT path on NVIDIA_GPU and fall back to ANSONNXOCR
			// (which uses CPU-side NV12 conversion and ONNX Runtime's EP auto-
			// select, including DirectML for AMD).
			const bool isNvidia = (engineType == ANSCENTER::EngineType::NVIDIA_GPU);
			switch (engineMode) {
			case 0: // Auto-detect — prefer TensorRT on NVIDIA, ONNX elsewhere.
				// Previous policy was "always ONNX" for cross-platform safety,
				// but on NVIDIA that defeated the point: each ANSONNXOCR handle
				// allocates its own cls/dec/rec OrtSessions (no dedupe), which
				// wasted ~300–600 MB VRAM per extra instance and ran ~2× slower
				// than ANSRTOCR's shared-engine path via EnginePoolManager.
				if (isNvidia) {
					limitSideLen = 960;
					(*Handle) = new ANSCENTER::ANSRTOCR();
				} else {
					// AMD / Intel / CPU — ANSRTOCR hard-requires CUDA and would
					// crash. ANSONNXOCR auto-picks the correct ORT EP
					// (DirectML on AMD, OpenVINO on Intel, CPU otherwise).
					(*Handle) = new ANSCENTER::ANSONNXOCR();
				}
				break;
			case 1: // GPU — use TensorRT engine ONLY on NVIDIA hardware.
				if (isNvidia) {
					limitSideLen = 960;
					(*Handle) = new ANSCENTER::ANSRTOCR();
				} else {
					// AMD / Intel / CPU requested GPU mode — ANSRTOCR would crash.
					// Fall back to ANSONNXOCR which picks the right ORT provider
					// (DirectML on AMD, OpenVINO/CPU on Intel, CPU otherwise).
					(*Handle) = new ANSCENTER::ANSONNXOCR();
				}
				break;
			case 2: // CPU
				(*Handle) = new ANSCENTER::ANSONNXOCR();
				break;
			default:
				(*Handle) = new ANSCENTER::ANSONNXOCR();
				break;
			}
		}
		if (*Handle == nullptr) { ANS_DBG("ANSOCR","CreateEx FAIL: new returned null"); return 0; }
		else {
			ANS_DBG("ANSOCR","CreateEx: allocated handle=%p (uint=%llu), calling Initialize...",
			       (void*)*Handle, (unsigned long long)(uintptr_t)*Handle);
			RegisterOCRHandle(*Handle);
			ANSCENTER::OCRModelConfig modelConfig;
			switch (language) {
				case 0: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::ENGLISH;
					break;
				}

				case 1: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::CHINESE;
					break;
				}

				case 2: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::FRENCH;
					break;
				}

				case 3: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::GERMANY;
					break;
				}

				case 4: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::JAPANESE;
					break;
				}

				case 5: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::KOREAN;
					break;
				}

				case 6: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::CUSTOM;
					break;
				}

				default: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::ENGLISH;
					break;
				}
			}
			modelConfig.useDetector = true;
			modelConfig.useRecognizer = true;
			modelConfig.useCLS = true;
			modelConfig.useLayout = false;
			modelConfig.useTable = false;
			modelConfig.useTensorRT = false;
			modelConfig.enableMKLDNN = false;
			modelConfig.useDilation = false;
			modelConfig.useAngleCLS = false;
			modelConfig.gpuId = gpuId;
			modelConfig.detectionDBThreshold = detectorDBThreshold;
			modelConfig.detectionBoxThreshold = detectorDBBoxThreshold;
			modelConfig.detectionDBUnclipRatio = detectorDBUnclipRatio;
			modelConfig.clsThreshold = classifierThreshold;
			if (useDilation == 1)modelConfig.useDilation = true;
			modelConfig.limitSideLen = limitSideLen;
			int result = (*Handle)->Initialize(licenseKey, modelConfig, modelFilePath, modelFileZipPassword, engineMode);
			if (!result) {
				ANS_DBG("ANSOCR","CreateEx FAIL: Initialize returned false, tearing down handle=%p", (void*)*Handle);
				// Initialize failed — tear down the engine we just registered so
				// the caller, who sees a 0 return and (typically) does not call
				// ReleaseANSOCRHandle, does not leak the engine + registry entry.
				if (UnregisterOCRHandle(*Handle)) {
					try { (*Handle)->Destroy(); } catch (...) {}
					delete *Handle;
				}
				*Handle = nullptr;
				return 0;
			}
			ANS_DBG("ANSOCR","CreateEx OK: handle=%p (uint=%llu)", (void*)*Handle, (unsigned long long)(uintptr_t)*Handle);
			return result;
		}
	}
	catch (std::exception& e) {
		ANS_DBG("ANSOCR","CreateEx EXCEPTION (std::exception): %s", e.what());
		// Partially-constructed engine may already be registered — unwind it
		// so the leak does not accumulate across repeated failed Create calls.
		if (Handle && *Handle) {
			if (UnregisterOCRHandle(*Handle)) {
				try { (*Handle)->Destroy(); } catch (...) {}
				delete *Handle;
			}
			*Handle = nullptr;
		}
		return 0;
	}
	catch (...) {
		ANS_DBG("ANSOCR","CreateEx EXCEPTION (unknown)");
		if (Handle && *Handle) {
			if (UnregisterOCRHandle(*Handle)) {
				try { (*Handle)->Destroy(); } catch (...) {}
				delete *Handle;
			}
			*Handle = nullptr;
		}
		return 0;
	}
}

// Original signature (without limitSideLen) — kept for backward compatibility
// with third-party apps already built against the old DLL.
extern "C" ANSOCR_API int		CreateANSOCRHandle(ANSCENTER::ANSOCRBase** Handle, const char* licenseKey,
	const char* modelFilePath, const char* modelFileZipPassword,
	int language, int engineMode, // 0: autodetect, 1 gpu, 2 cpu
	int gpuId,
	double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio,
	double classifierThreshold, int useDilation) {
	ANS_DBG("ANSOCR","CreateANSOCRHandle: HandlePtr=%p, language=%d, engineMode=%d, gpuId=%d (delegating to CreateEx, limitSideLen=960)",
	       (void*)Handle, language, engineMode, gpuId);
	return CreateANSOCRHandleEx(Handle, licenseKey, modelFilePath, modelFileZipPassword,
		language, engineMode, gpuId,
		detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio,
		classifierThreshold, useDilation, 960);
}

// Helper: serialize OCR results with optional ALPR post-processing
static std::string SerializeOCRResults(ANSCENTER::ANSOCRBase* engine,
	const std::vector<ANSCENTER::OCRObject>& outputs, int imageWidth, int imageHeight,
	const cv::Mat& originalImage = cv::Mat()) {
	if (engine->GetOCRMode() == ANSCENTER::OCR_ALPR && !engine->GetALPRFormats().empty()) {
		auto alprResults = ANSCENTER::ANSOCRUtility::ALPRPostProcessing(
			outputs, engine->GetALPRFormats(), imageWidth, imageHeight,
			engine, originalImage);
		return ANSCENTER::ANSOCRUtility::ALPRResultToJsonString(alprResults);
	}
	return ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
}

extern "C" ANSOCR_API std::string  RunInference(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength) {
	ANS_DBG("ANSOCR","RunInference: HandlePtr=%p, *Handle=%p, bufferLength=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength);
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame);
		std::string stResult = SerializeOCRResults(engine, outputs, frame.cols, frame.rows, frame);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}

extern "C" ANSOCR_API std::string  RunInferenceWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* cameraId) {
	ANS_DBG("ANSOCR","RunInferenceWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, cameraId=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, cameraId ? cameraId : "(null)");
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame, cameraId);
		std::string stResult = SerializeOCRResults(engine, outputs, frame.cols, frame.rows, frame);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}

extern "C" ANSOCR_API int RunInferenceCV(ANSCENTER::ANSOCRBase** Handle, const cv::Mat& image, std::string& ocrResult) {
	ANS_DBG("ANSOCR","RunInferenceCV: HandlePtr=%p, *Handle=%p, image=%dx%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), image.cols, image.rows);
	if (!Handle || !*Handle) return -1;
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return -3;
	auto* engine = guard.get();
	try {
		if (image.empty()) return 0;
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(image, "cameraId");
		ocrResult = SerializeOCRResults(engine, outputs, image.cols, image.rows, image);
		return 1;
	}
	catch (...) { return -2; }
}

extern "C" ANSOCR_API std::string  RunInferenceBinary(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height) {
	ANS_DBG("ANSOCR","RunInferenceBinary: HandlePtr=%p, *Handle=%p, %ux%u",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), width, height);
	if (!Handle || !*Handle || !jpeg_bytes || width == 0 || height == 0) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::Mat(height, width, CV_8UC3, jpeg_bytes).clone();
		if (frame.empty()) return "";
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame);
		std::string stResult = SerializeOCRResults(engine, outputs, width, height, frame);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}
static int ReleaseANSOCRHandle_Impl(ANSCENTER::ANSOCRBase** Handle) {
	try {
		if (!Handle || !*Handle) {
			ANS_DBG("ANSOCR","Release: HandlePtr or *Handle is null, no-op");
			return 0;
		}
		ANSCENTER::ANSOCRBase* h = *Handle;
		ANS_DBG("ANSOCR","Release called: handle=%p (uint=%llu)", (void*)h, (unsigned long long)(uintptr_t)h);
		if (!UnregisterOCRHandle(h)) {
			ANS_DBG("ANSOCR","Release: Unregister returned false (already gone or being destroyed by another thread), handle=%p", (void*)h);
			*Handle = nullptr;
			return 0;  // Not in registry — already freed
		}
		(*Handle)->Destroy();
		delete *Handle;
		*Handle = nullptr;
		ANS_DBG("ANSOCR","Release OK: handle=%p deleted, registry now has %zu entries",
		       (void*)h, OCRHandleRegistry().size());
		return 0;
	}
	catch (...) {
		ANS_DBG("ANSOCR","Release EXCEPTION (unknown)");
		if (Handle) *Handle = nullptr;
		return 1;
	}
}

extern "C" ANSOCR_API int ReleaseANSOCRHandle(ANSCENTER::ANSOCRBase** Handle) {
	__try {
		return ReleaseANSOCRHandle_Impl(Handle);
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		ANS_DBG("ANSOCR","ReleaseANSOCRHandle: SEH exception caught");
		return 1;
	}
}

// ── ALPR Configuration API ──────────────────────────────────────────

extern "C" ANSOCR_API int SetANSOCRMode(ANSCENTER::ANSOCRBase** Handle, int ocrMode) {
	ANS_DBG("ANSOCR","SetANSOCRMode: HandlePtr=%p, *Handle=%p, ocrMode=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), ocrMode);
	if (!Handle || !*Handle) return -1;
	(*Handle)->SetOCRMode(static_cast<ANSCENTER::OCRMode>(ocrMode));
	return 0;
}

extern "C" ANSOCR_API int SetANSOCRCountry(ANSCENTER::ANSOCRBase** Handle, int country) {
	ANS_DBG("ANSOCR","SetANSOCRCountry: HandlePtr=%p, *Handle=%p, country=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), country);
	if (!Handle || !*Handle) return -1;
	(*Handle)->SetCountry(static_cast<ANSCENTER::Country>(country));
	return 0;
}

extern "C" ANSOCR_API int SetANSOCRALPRFormat(ANSCENTER::ANSOCRBase** Handle, const char* formatJson) {
	ANS_DBG("ANSOCR","SetANSOCRALPRFormat: HandlePtr=%p, *Handle=%p, formatJson=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), formatJson ? "(set)" : "(null)");
	if (!Handle || !*Handle || !formatJson) return -1;
	try {
		nlohmann::json j = nlohmann::json::parse(formatJson);
		ANSCENTER::ALPRPlateFormat fmt;
		fmt.name = j.value("name", "CUSTOM");
		fmt.country = static_cast<ANSCENTER::Country>(j.value("country", 99));
		fmt.numRows = j.value("num_rows", 2);
		fmt.rowSplitThreshold = j.value("row_split_threshold", 0.3f);

		static const std::map<std::string, ANSCENTER::ALPRCharClass> classMap = {
			{"digit", ANSCENTER::CHAR_DIGIT}, {"latin_alpha", ANSCENTER::CHAR_LATIN_ALPHA},
			{"alphanumeric", ANSCENTER::CHAR_ALPHANUMERIC}, {"hiragana", ANSCENTER::CHAR_HIRAGANA},
			{"katakana", ANSCENTER::CHAR_KATAKANA}, {"kanji", ANSCENTER::CHAR_KANJI},
			{"cjk_any", ANSCENTER::CHAR_CJK_ANY}, {"any", ANSCENTER::CHAR_ANY}
		};

		for (const auto& zj : j["zones"]) {
			ANSCENTER::ALPRZone zone;
			zone.name = zj.value("name", "");
			zone.row = zj.value("row", 0);
			zone.col = zj.value("col", 0);
			std::string ccStr = zj.value("char_class", "any");
			auto it = classMap.find(ccStr);
			zone.charClass = (it != classMap.end()) ? it->second : ANSCENTER::CHAR_ANY;
			zone.minLength = zj.value("min_length", 1);
			zone.maxLength = zj.value("max_length", 10);
			zone.validationRegex = zj.value("regex", "");
			if (zj.contains("corrections")) {
				for (auto& [key, val] : zj["corrections"].items()) {
					zone.corrections[key] = val.get<std::string>();
				}
			}
			fmt.zones.push_back(zone);
		}
		(*Handle)->SetALPRFormat(fmt);
		return 0;
	} catch (...) {
		return -2;
	}
}

// Unicode conversion utilities for LabVIEW wrapper classes
// Converts input string to UTF-16LE. Handles both:
// - JSON Unicode escapes (\uXXXX) from ensure_ascii=true output
// - Raw UTF-8 encoded strings
// Pure ASCII input is passed through directly (no conversion overhead).
extern "C" ANSOCR_API int ANSOCR_ConvertUTF8ToUTF16LE(const char* utf8Str, LStrHandle result, int includeBOM) {
	ANS_DBG("ANSOCR","ANSOCR_ConvertUTF8ToUTF16LE: utf8Str=%s, includeBOM=%d",
	       utf8Str ? "(set)" : "(null)", includeBOM);
	try {
		if (!utf8Str || !result) return -1;
		int len = (int)strlen(utf8Str);
		if (len == 0) return 0;

		// Always output UTF-16LE (required for LabVIEW "Force Unicode Text" indicators)
		const char bom[2] = { '\xFF', '\xFE' };

		// Check if input contains \uXXXX escapes
		bool hasUnicodeEscapes = false;
		for (int i = 0; i + 1 < len; i++) {
			if (utf8Str[i] == '\\' && utf8Str[i + 1] == 'u') { hasUnicodeEscapes = true; break; }
		}

		if (hasUnicodeEscapes) {
			std::string utf16le;
			if (includeBOM) utf16le.assign(bom, 2);
			utf16le.reserve(len * 2 + 2);
			for (int i = 0; i < len; ) {
				if (i + 5 < len && utf8Str[i] == '\\' && utf8Str[i + 1] == 'u') {
					char hex[5] = { utf8Str[i + 2], utf8Str[i + 3], utf8Str[i + 4], utf8Str[i + 5], 0 };
					uint16_t cp = (uint16_t)strtoul(hex, nullptr, 16);
					utf16le += static_cast<char>(cp & 0xFF);
					utf16le += static_cast<char>((cp >> 8) & 0xFF);
					i += 6;
				} else {
					utf16le += utf8Str[i];
					utf16le += '\0';
					i++;
				}
			}
			int size = (int)utf16le.size();
			MgErr error = DSSetHandleSize(result, sizeof(int32) + size * sizeof(uChar));
			if (error != noErr) return -2;
			(*result)->cnt = size;
			memcpy((*result)->str, utf16le.data(), size);
			return 1;
		}

#ifdef _WIN32
		int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8Str, len, nullptr, 0);
		if (wideLen <= 0) return 0;
		std::wstring wideStr(wideLen, 0);
		MultiByteToWideChar(CP_UTF8, 0, utf8Str, len, &wideStr[0], wideLen);
		int dataSize = wideLen * (int)sizeof(wchar_t);
		int bomSize = includeBOM ? 2 : 0;
		int totalSize = bomSize + dataSize;
		MgErr error = DSSetHandleSize(result, sizeof(int32) + totalSize * sizeof(uChar));
		if (error != noErr) return -2;
		(*result)->cnt = totalSize;
		if (includeBOM) memcpy((*result)->str, bom, 2);
		memcpy((*result)->str + bomSize, wideStr.data(), dataSize);
		return 1;
#else
		return 0;
#endif
	}
	catch (...) { return -1; }
}

extern "C" ANSOCR_API int ANSOCR_ConvertUTF16LEToUTF8(const unsigned char* utf16leBytes, int byteLen, LStrHandle result) {
	ANS_DBG("ANSOCR","ANSOCR_ConvertUTF16LEToUTF8: utf16leBytes=%s, byteLen=%d",
	       utf16leBytes ? "(set)" : "(null)", byteLen);
	try {
		if (!utf16leBytes || byteLen <= 0 || !result) return -1;
		// Check if input is already pure ASCII (no high bytes, or not valid UTF-16LE)
		// If all bytes < 0x80 and byteLen has no null bytes in odd positions pattern,
		// treat as already-UTF8 ASCII and pass through
		bool isAlreadyAscii = true;
		bool isUtf16le = (byteLen >= 2 && byteLen % 2 == 0);
		if (isUtf16le) {
			// Check if all high bytes (odd indices) are 0x00 — means pure ASCII in UTF-16LE
			for (int i = 1; i < byteLen; i += 2) {
				if (utf16leBytes[i] != 0x00) { isAlreadyAscii = false; break; }
			}
			if (isAlreadyAscii) {
				// Extract just the low bytes (ASCII characters)
				int asciiLen = byteLen / 2;
				MgErr error = DSSetHandleSize(result, sizeof(int32) + asciiLen * sizeof(uChar));
				if (error != noErr) return -2;
				(*result)->cnt = asciiLen;
				for (int i = 0; i < asciiLen; i++) {
					(*result)->str[i] = utf16leBytes[i * 2];
				}
				return 1;
			}
		}
#ifdef _WIN32
		int wideLen = byteLen / (int)sizeof(wchar_t);
		const wchar_t* wideStr = reinterpret_cast<const wchar_t*>(utf16leBytes);
		int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wideStr, wideLen, nullptr, 0, nullptr, nullptr);
		if (utf8Len <= 0) return 0;
		std::string utf8Str(utf8Len, 0);
		WideCharToMultiByte(CP_UTF8, 0, wideStr, wideLen, &utf8Str[0], utf8Len, nullptr, nullptr);
		MgErr error = DSSetHandleSize(result, sizeof(int32) + utf8Len * sizeof(uChar));
		if (error != noErr) return -2;
		(*result)->cnt = utf8Len;
		memcpy((*result)->str, utf8Str.data(), utf8Len);
		return 1;
#else
		return 0;
#endif
	}
	catch (...) { return -1; }
}

extern "C" ANSOCR_API std::string  RunInferenceImagePath(ANSCENTER::ANSOCRBase** Handle, const char* imageFilePath) {
	ANS_DBG("ANSOCR","RunInferenceImagePath: HandlePtr=%p, *Handle=%p, imageFilePath=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), imageFilePath ? imageFilePath : "(null)");
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		std::string stImageFileName(imageFilePath);
		cv::Mat frame = cv::imread(stImageFileName, cv::ImreadModes::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame);
		std::string stResult = SerializeOCRResults(engine, outputs, frame.cols, frame.rows, frame);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}


extern "C" ANSOCR_API std::string  RunInferenceInCroppedImages(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes) {
	ANS_DBG("ANSOCR","RunInferenceInCroppedImages: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, strBboxes ? "(set)" : "(null)");
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<cv::Rect> bBoxes = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame, bBoxes);
		std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}
extern "C" ANSOCR_API std::string  RunInferenceInCroppedImagesWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, const char* cameraId) {
	ANS_DBG("ANSOCR","RunInferenceInCroppedImagesWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s, cameraId=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength,
	       strBboxes ? "(set)" : "(null)", cameraId ? cameraId : "(null)");
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<cv::Rect> bBoxes = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame, bBoxes, cameraId);
		std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}
//// For LabVIEW API
extern "C" ANSOCR_API int          RunInference_LV(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInference_LV: HandlePtr=%p, *Handle=%p, bufferLength=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength);
	try {
		std::string st = RunInference(Handle, jpeg_string, bufferLength);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSOCR_API int          RunInference_LVWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* cameraId, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInference_LVWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, cameraId=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, cameraId ? cameraId : "(null)");
	try {
		std::string st = RunInferenceWithCamID(Handle, jpeg_string, bufferLength, cameraId);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}
extern "C" ANSOCR_API int          RunInferenceBinary_LV(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferenceBinary_LV: HandlePtr=%p, *Handle=%p, %ux%u",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), width, height);
	try {
		std::string st = RunInferenceBinary(Handle, jpeg_bytes, width, height);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}
extern "C" ANSOCR_API int		   RunInferenceImagePath_LV(ANSCENTER::ANSOCRBase** Handle, const char* imageFilePath, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferenceImagePath_LV: HandlePtr=%p, *Handle=%p, imageFilePath=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), imageFilePath ? imageFilePath : "(null)");
	try {
		std::string st = RunInferenceImagePath(Handle, imageFilePath);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}
extern "C" ANSOCR_API int		   ANSOCRUnitTest(const char* modelFilePath, const char* imageFilePath, LStrHandle detectionResult)
{
	ANS_DBG("ANSOCR","ANSOCRUnitTest: modelFilePath=%s, imageFilePath=%s",
	       modelFilePath ? modelFilePath : "(null)", imageFilePath ? imageFilePath : "(null)");
	try {
		ANSCENTER::ANSOCRBase* infHandle;
		std::string licenseKey = "";
		int language = 0;// English
		int engine = 0;
		int createResult = CreateANSOCRHandle(&infHandle, licenseKey.c_str(), modelFilePath, "", language, engine);
		std::string st = RunInferenceImagePath(&infHandle, imageFilePath);
		ReleaseANSOCRHandle(&infHandle);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSOCR_API int          RunInferenceInCroppedImages_LV(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferenceInCroppedImages_LV: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, strBboxes ? "(set)" : "(null)");
	try {
		std::string st = RunInferenceInCroppedImages(Handle, jpeg_string, bufferLength, strBboxes);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}
extern "C" ANSOCR_API int		   RunInferenceInCroppedImages_LVWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferenceInCroppedImages_LVWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s, cameraId=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength,
	       strBboxes ? "(set)" : "(null)", cameraId ? cameraId : "(null)");
	try {
		std::string st = RunInferenceInCroppedImagesWithCamID(Handle, jpeg_string, bufferLength, strBboxes, cameraId);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSOCR_API int RunInferenceComplete_LV(
	ANSCENTER::ANSOCRBase** Handle,
	cv::Mat** cvImage,
	const char* cameraId,
	int getJpegString,
	int jpegImageSize,
	LStrHandle detectionResult,
	LStrHandle imageStr)
{
	ANS_DBG("ANSOCR","RunInferenceComplete_LV: HandlePtr=%p, *Handle=%p, cameraId=%s, getJpegString=%d, jpegImageSize=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), cameraId ? cameraId : "(null)", getJpegString, jpegImageSize);
	if (!Handle || !*Handle) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		// Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*)
		GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage);
		cv::Mat localImage = (**cvImage).clone();

		int originalWidth = localImage.cols;
		int originalHeight = localImage.rows;

		if (originalWidth == 0 || originalHeight == 0) return -2;

		std::vector<ANSCENTER::OCRObject> outputs;
		{
			GpuFrameScope _gfs(gpuFrame);
			outputs = engine->RunInference(localImage, cameraId);
		}

		bool getJpeg = (getJpegString == 1);
		std::string stImage;

		int maxImageSize = originalWidth;
		bool resizeNeeded = (jpegImageSize > 0) && (jpegImageSize < maxImageSize);

		float ratio = 1.0f;
		int newWidth = originalWidth;
		int newHeight = originalHeight;

		if (resizeNeeded) {
			newWidth = jpegImageSize;
			newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
			ratio = static_cast<float>(newWidth) / originalWidth;

			for (auto& obj : outputs) {
				obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x * ratio), newWidth - 1));
				obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y * ratio), newHeight - 1));
				obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width * ratio), newWidth - obj.box.x));
				obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height * ratio), newHeight - obj.box.y));
			}
		}
		else {
			for (auto& obj : outputs) {
				obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x), originalWidth - 1));
				obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y), originalHeight - 1));
				obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width), originalWidth - obj.box.x));
				obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height), originalHeight - obj.box.y));
			}
		}

		if (getJpeg) {
			cv::Mat processedImage = localImage;
			if (resizeNeeded) {
				cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
			}

			std::vector<uchar> buf;
			if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) {
				stImage.assign(buf.begin(), buf.end());
			}
		}

		std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		if (stDetectionResult.empty()) return 0;

		int size = static_cast<int>(stDetectionResult.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);

		if (getJpeg) {
			if (stImage.empty()) return 0;
			size = static_cast<int>(stImage.length());
			error = DSSetHandleSize(imageStr, sizeof(int32) + size * sizeof(uChar));
			if (error != noErr) return 0;

			(*imageStr)->cnt = size;
			memcpy((*imageStr)->str, stImage.c_str(), size);
		}

		return 1;
	}
	catch (...) { return 0; }
}
extern "C" ANSOCR_API int RunInferencesComplete_LV(ANSCENTER::ANSOCRBase** Handle, cv::Mat** cvImage, const char* cameraId, int maxImageSize, const char* strBboxes, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferencesComplete_LV: HandlePtr=%p, *Handle=%p, cameraId=%s, maxImageSize=%d, strBboxes=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), cameraId ? cameraId : "(null)", maxImageSize, strBboxes ? "(set)" : "(null)");
	if (!Handle || !*Handle) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		// Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*)
		GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage);
		cv::Mat localImage = (**cvImage).clone();

		std::vector<ANSCENTER::OCRObject> objectDetectionResults;
		std::vector<cv::Rect> bBox = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);

		const int originalWidth = localImage.cols;
		const int originalHeight = localImage.rows;

		const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(originalWidth) / maxImageSize : 1.0;

		{
			GpuFrameScope _gfs(gpuFrame);
			if (bBox.empty()) {
				objectDetectionResults = engine->RunInference(localImage, cameraId);
			}
			else {
				for (const auto& rect : bBox) {
					cv::Rect scaledRect;
					scaledRect.x = static_cast<int>(rect.x * scaleFactor);
					scaledRect.y = static_cast<int>(rect.y * scaleFactor);
					scaledRect.width = static_cast<int>(rect.width * scaleFactor);
					scaledRect.height = static_cast<int>(rect.height * scaleFactor);

					scaledRect &= cv::Rect(0, 0, originalWidth, originalHeight);
					if (scaledRect.width <= 0 || scaledRect.height <= 0)
						continue;

					const cv::Mat croppedImage = localImage(scaledRect);
					std::vector<ANSCENTER::OCRObject> croppedDetectionResults = engine->RunInference(croppedImage, cameraId);

					for (auto& obj : croppedDetectionResults) {
						obj.box.x = (obj.box.x + scaledRect.x) / scaleFactor;
						obj.box.y = (obj.box.y + scaledRect.y) / scaleFactor;
						obj.box.width /= scaleFactor;
						obj.box.height /= scaleFactor;

						objectDetectionResults.push_back(std::move(obj));
					}
				}
			}
		}

		std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(objectDetectionResults);
		if (stDetectionResult.empty()) return 0;

		const int size = static_cast<int>(stDetectionResult.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);

		return 1;
	}
	catch (...) { return 0; }

}

// ============================================================================
// V2 LabVIEW API — Accept handle as uint64_t by value.
// Eliminates Handle** pointer-to-pointer instability when LabVIEW calls
// concurrently from multiple tasks.
// LabVIEW CLFN: set Handle parameter to Numeric / Unsigned Pointer-sized Integer / Pass: Value
// ============================================================================

// Helper: cast uint64_t handle to ANSOCRBase** for delegation to existing functions
#define OCR_V2_HANDLE_SETUP(handleVal) \
	ANSCENTER::ANSOCRBase* _v2Direct = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal); \
	if (_v2Direct == nullptr) return 0; \
	ANSCENTER::ANSOCRBase* _v2Arr[1] = { _v2Direct }; \
	ANSCENTER::ANSOCRBase** Handle = &_v2Arr[0];

extern "C" ANSOCR_API int RunInference_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInference_LV_V2: handleVal=%llu (handle=%p), bufferLength=%d",
	       (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, (int)bufferLength);
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInference(Handle, jpeg_string, bufferLength);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInference_LVWithCamID_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, const char* cameraId, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInference_LVWithCamID_V2: handleVal=%llu (handle=%p), bufferLength=%d, cameraId=%s",
	       (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, (int)bufferLength, cameraId ? cameraId : "(null)");
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceWithCamID(Handle, jpeg_string, bufferLength, cameraId);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceBinary_LV_V2(uint64_t handleVal, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferenceBinary_LV_V2: handleVal=%llu (handle=%p), %ux%u",
	       (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, width, height);
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceBinary(Handle, jpeg_bytes, width, height);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceImagePath_LV_V2(uint64_t handleVal, const char* imageFilePath, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferenceImagePath_LV_V2: handleVal=%llu (handle=%p), imageFilePath=%s",
	       (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, imageFilePath ? imageFilePath : "(null)");
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceImagePath(Handle, imageFilePath);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceInCroppedImages_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferenceInCroppedImages_LV_V2: handleVal=%llu (handle=%p), bufferLength=%d, strBboxes=%s",
	       (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, (int)bufferLength, strBboxes ? "(set)" : "(null)");
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceInCroppedImages(Handle, jpeg_string, bufferLength, strBboxes);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceInCroppedImages_LVWithCamID_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferenceInCroppedImages_LVWithCamID_V2: handleVal=%llu (handle=%p), bufferLength=%d, strBboxes=%s, cameraId=%s",
	       (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, (int)bufferLength,
	       strBboxes ? "(set)" : "(null)", cameraId ? cameraId : "(null)");
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceInCroppedImagesWithCamID(Handle, jpeg_string, bufferLength, strBboxes, cameraId);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceComplete_LV_V2(
	uint64_t handleVal,
	cv::Mat** cvImage,
	const char* cameraId,
	int getJpegString,
	int jpegImageSize,
	LStrHandle detectionResult,
	LStrHandle imageStr)
{
	ANS_DBG("ANSOCR","RunInferenceComplete_LV_V2: handleVal=%llu (handle=%p), cameraId=%s, getJpegString=%d, jpegImageSize=%d",
	       (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, cameraId ? cameraId : "(null)", getJpegString, jpegImageSize);
	ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal);
	if (directHandle == nullptr) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	OCRHandleGuard guard(AcquireOCRHandle(directHandle));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		// Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*)
		GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage);
		cv::Mat localImage = (**cvImage).clone();

		int originalWidth = localImage.cols;
		int originalHeight = localImage.rows;

		if (originalWidth == 0 || originalHeight == 0) return -2;

		std::vector<ANSCENTER::OCRObject> outputs;
		{
			GpuFrameScope _gfs(gpuFrame);
			outputs = engine->RunInference(localImage, cameraId);
		}

		bool getJpeg = (getJpegString == 1);
		std::string stImage;

		int maxImageSize = originalWidth;
		bool resizeNeeded = (jpegImageSize > 0) && (jpegImageSize < maxImageSize);

		float ratio = 1.0f;
		int newWidth = originalWidth;
		int newHeight = originalHeight;

		if (resizeNeeded) {
			newWidth = jpegImageSize;
			newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
			ratio = static_cast<float>(newWidth) / originalWidth;

			for (auto& obj : outputs) {
				obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x * ratio), newWidth - 1));
				obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y * ratio), newHeight - 1));
				obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width * ratio), newWidth - obj.box.x));
				obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height * ratio), newHeight - obj.box.y));
			}
		}
		else {
			for (auto& obj : outputs) {
				obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x), originalWidth - 1));
				obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y), originalHeight - 1));
				obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width), originalWidth - obj.box.x));
				obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height), originalHeight - obj.box.y));
			}
		}

		if (getJpeg) {
			cv::Mat processedImage = localImage;
			if (resizeNeeded) {
				cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
			}

			std::vector<uchar> buf;
			if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) {
				stImage.assign(buf.begin(), buf.end());
			}
		}

		std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		if (stDetectionResult.empty()) return 0;

		int size = static_cast<int>(stDetectionResult.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);

		if (getJpeg) {
			if (stImage.empty()) return 0;
			size = static_cast<int>(stImage.length());
			error = DSSetHandleSize(imageStr, sizeof(int32) + size * sizeof(uChar));
			if (error != noErr) return 0;

			(*imageStr)->cnt = size;
			memcpy((*imageStr)->str, stImage.c_str(), size);
		}

		return 1;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferencesComplete_LV_V2(uint64_t handleVal, cv::Mat** cvImage, const char* cameraId, int maxImageSize, const char* strBboxes, LStrHandle detectionResult) {
	ANS_DBG("ANSOCR","RunInferencesComplete_LV_V2: handleVal=%llu (handle=%p), cameraId=%s, maxImageSize=%d, strBboxes=%s",
	       (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, cameraId ? cameraId : "(null)", maxImageSize, strBboxes ? "(set)" : "(null)");
	ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal);
	if (directHandle == nullptr) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	OCRHandleGuard guard(AcquireOCRHandle(directHandle));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		// Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*)
		GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage);
		cv::Mat localImage = (**cvImage).clone();

		std::vector<ANSCENTER::OCRObject> objectDetectionResults;
		std::vector<cv::Rect> bBox = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);

		const int originalWidth = localImage.cols;
		const int originalHeight = localImage.rows;

		const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(originalWidth) / maxImageSize : 1.0;

		{
			GpuFrameScope _gfs(gpuFrame);
			if (bBox.empty()) {
				objectDetectionResults = engine->RunInference(localImage, cameraId);
			}
			else {
				for (const auto& rect : bBox) {
					cv::Rect scaledRect;
					scaledRect.x = static_cast<int>(rect.x * scaleFactor);
					scaledRect.y = static_cast<int>(rect.y * scaleFactor);
					scaledRect.width = static_cast<int>(rect.width * scaleFactor);
					scaledRect.height = static_cast<int>(rect.height * scaleFactor);

					scaledRect &= cv::Rect(0, 0, originalWidth, originalHeight);
					if (scaledRect.width <= 0 || scaledRect.height <= 0)
						continue;

					const cv::Mat croppedImage = localImage(scaledRect);
					std::vector<ANSCENTER::OCRObject> croppedDetectionResults = engine->RunInference(croppedImage, cameraId);

					for (auto& obj : croppedDetectionResults) {
						obj.box.x = (obj.box.x + scaledRect.x) / scaleFactor;
						obj.box.y = (obj.box.y + scaledRect.y) / scaleFactor;
						obj.box.width /= scaleFactor;
						obj.box.height /= scaleFactor;

						objectDetectionResults.push_back(std::move(obj));
					}
				}
			}
		}

		std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(objectDetectionResults);
		if (stDetectionResult.empty()) return 0;

		const int size = static_cast<int>(stDetectionResult.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);

		return 1;
	}
	catch (...) { return 0; }
}

// ============================================================================
// V2 Create / Release — handle returned/passed as uint64_t by value.
// ============================================================================

extern "C" ANSOCR_API uint64_t CreateANSOCRHandleEx_V2(const char* licenseKey,
	const char* modelFilePath, const char* modelFileZipPassword,
	int language, int engineMode,
	int gpuId,
	double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio,
	double classifierThreshold, int useDilation, int limitSideLen) {
	ANS_DBG("ANSOCR","CreateANSOCRHandleEx_V2: language=%d, engineMode=%d, gpuId=%d, limitSideLen=%d, modelPath=%s",
	       language, engineMode, gpuId, limitSideLen, modelFilePath ? modelFilePath : "(null)");
	try {
		ANSCENTER::ANSOCRBase* handle = nullptr;
		int result = CreateANSOCRHandleEx(&handle, licenseKey, modelFilePath, modelFileZipPassword,
			language, engineMode, gpuId,
			detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio,
			classifierThreshold, useDilation, limitSideLen);
		if (result == 0 || handle == nullptr) return 0;
		return reinterpret_cast<uint64_t>(handle);
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API uint64_t CreateANSOCRHandle_V2(const char* licenseKey,
	const char* modelFilePath, const char* modelFileZipPassword,
	int language, int engineMode,
	int gpuId,
	double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio,
	double classifierThreshold, int useDilation) {
	ANS_DBG("ANSOCR","CreateANSOCRHandle_V2: language=%d, engineMode=%d, gpuId=%d (delegating to CreateEx_V2, limitSideLen=960)",
	       language, engineMode, gpuId);
	return CreateANSOCRHandleEx_V2(licenseKey, modelFilePath, modelFileZipPassword,
		language, engineMode, gpuId,
		detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio,
		classifierThreshold, useDilation, 960);
}

extern "C" ANSOCR_API int ReleaseANSOCRHandle_V2(uint64_t handleVal) {
	ANS_DBG("ANSOCR","ReleaseANSOCRHandle_V2: handleVal=%llu (handle=%p)",
	       (unsigned long long)handleVal, (void*)(uintptr_t)handleVal);
	try {
		ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal);
		if (directHandle == nullptr) {
			ANS_DBG("ANSOCR","Release_V2: handleVal is 0/null, no-op");
			return 0;
		}
		if (!UnregisterOCRHandle(directHandle)) {
			ANS_DBG("ANSOCR","Release_V2: Unregister returned false (already gone or being destroyed by another thread), handle=%p", (void*)directHandle);
			return 0;  // Not in registry — already freed
		}
		directHandle->Destroy();
		delete directHandle;
		ANS_DBG("ANSOCR","Release_V2 OK: handle=%p deleted, registry now has %zu entries",
		       (void*)directHandle, OCRHandleRegistry().size());
		return 0;
	}
	catch (...) {
		ANS_DBG("ANSOCR","Release_V2 EXCEPTION (unknown)");
		return 1;
	}
}

