// dllmain.cpp : Defines the entry point for the DLL application.
#pragma once
#include "pch.h"
#include "ANSLPR.h"
#include "ANSLPR_CPU.h"
#include "ANSLPR_OD.h"
#include "ANSLPR_OCR.h"
#include "ANSLibsLoader.h"
#include "ANSGpuFrameRegistry.h"    // gpu_frame_lookup(cv::Mat*)
#include <unordered_set>
#include <unordered_map>
#include <condition_variable>
#include <cstdint>

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

// Write a message to Windows Event Log (Application log, source "ANSLogger").
// Works even when spdlog is not initialized — useful for crash diagnostics.
static void WriteWindowsEventLog(const char* message, WORD eventType = EVENTLOG_ERROR_TYPE) {
	static HANDLE hEventLog = RegisterEventSourceA(NULL, "ANSLogger");
	if (hEventLog) {
		const char* msgs[1] = { message };
		ReportEventA(hEventLog, eventType, 0, 0, NULL, 1, 0, msgs, NULL);
	}
	OutputDebugStringA(message);
	OutputDebugStringA("\n");
}

// Handle registry with refcount — prevents use-after-free when
// ReleaseANSALPRHandle is called while inference is still running.
// refcount: starts at 1 on Register. AcquireALPRHandle increments,
// ReleaseALPRHandleRef decrements. Object is destroyed when refcount hits 0.
// 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 ALPREntry { int refcount; bool destructionStarted; };
static std::unordered_map<ANSCENTER::ANSALPR*, ALPREntry>& ALPRHandleRegistry() {
	static std::unordered_map<ANSCENTER::ANSALPR*, ALPREntry> s;
	return s;
}
static std::mutex& ALPRHandleRegistryMutex() {
	static std::mutex m;
	return m;
}
static std::condition_variable& ALPRHandleRegistryCV() {
	static std::condition_variable cv;
	return cv;
}

static void RegisterALPRHandle(ANSCENTER::ANSALPR* h) {
	std::lock_guard<std::mutex> lk(ALPRHandleRegistryMutex());
	ALPRHandleRegistry()[h] = { 1, false };
	ANS_DBG("ANSLPR","Register: handle=%p (uint=%llu) registrySize=%zu",
	       (void*)h, (unsigned long long)(uintptr_t)h, ALPRHandleRegistry().size());
}

// Acquire a handle for use (increment refcount). Returns the handle
// if valid, nullptr if already released or being destroyed.
static ANSCENTER::ANSALPR* AcquireALPRHandle(ANSCENTER::ANSALPR* h) {
	std::lock_guard<std::mutex> lk(ALPRHandleRegistryMutex());
	auto it = ALPRHandleRegistry().find(h);
	if (it == ALPRHandleRegistry().end()) {
		ANS_DBG("ANSLPR","Acquire FAIL: handle=%p (uint=%llu) NOT in registry. registrySize=%zu",
		       (void*)h, (unsigned long long)(uintptr_t)h, ALPRHandleRegistry().size());
		size_t i = 0;
		for (auto& kv : ALPRHandleRegistry()) {
			ANS_DBG("ANSLPR","  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("ANSLPR","Acquire FAIL: handle=%p is being destroyed (destructionStarted=true)", (void*)h);
		return nullptr;
	}
	it->second.refcount++;
	ANS_DBG("ANSLPR","Acquire OK:   handle=%p refcount=%d", (void*)h, it->second.refcount);
	return h;
}

// Release a use of the handle (decrement refcount). Only signals the CV;
// the object is always deleted by Unregister, never by a stray ref-drop.
static bool ReleaseALPRHandleRef(ANSCENTER::ANSALPR* h) {
	std::lock_guard<std::mutex> lk(ALPRHandleRegistryMutex());
	auto it = ALPRHandleRegistry().find(h);
	if (it == ALPRHandleRegistry().end()) return false;
	it->second.refcount--;
	if (it->second.refcount <= 0) {
		ALPRHandleRegistryCV().notify_all();
	}
	return false;
}

// Unregister and wait for all in-flight uses to finish.
// First caller takes ownership of destruction; subsequent calls return false.
static bool UnregisterALPRHandle(ANSCENTER::ANSALPR* h) {
	std::unique_lock<std::mutex> lk(ALPRHandleRegistryMutex());
	auto it = ALPRHandleRegistry().find(h);
	if (it == ALPRHandleRegistry().end()) {
		ANS_DBG("ANSLPR","Unregister: handle=%p NOT in registry (already gone)", (void*)h);
		return false;
	}
	if (it->second.destructionStarted) {
		// Another thread already owns the delete — back off.
		ANS_DBG("ANSLPR","Unregister: handle=%p already being destroyed by another thread, returning false", (void*)h);
		return false;
	}
	ANS_DBG("ANSLPR","Unregister: handle=%p starting (refcount before=%d)", (void*)h, it->second.refcount);
	it->second.destructionStarted = true;
	it->second.refcount--;  // Remove creation ref
	bool ok = ALPRHandleRegistryCV().wait_for(lk, std::chrono::seconds(30), [&]() {
		auto it2 = ALPRHandleRegistry().find(h);
		return it2 == ALPRHandleRegistry().end() || it2->second.refcount <= 0;
	});
	if (!ok) {
		ANS_DBG("ANSLPR","WARNING: Unregister timed out waiting for in-flight operations on handle=%p", (void*)h);
		OutputDebugStringA("WARNING: UnregisterALPRHandle timed out waiting for in-flight inference\n");
	}
	ALPRHandleRegistry().erase(h);
	return true;  // Safe to destroy now
}

// RAII guard — ensures ReleaseALPRHandleRef is always called, preventing
// refcount leaks that would cause UnregisterALPRHandle to deadlock.
class ALPRHandleGuard {
	ANSCENTER::ANSALPR* engine;
public:
	explicit ALPRHandleGuard(ANSCENTER::ANSALPR* e) : engine(e) {}
	~ALPRHandleGuard() { if (engine) ReleaseALPRHandleRef(engine); }
	ANSCENTER::ANSALPR* get() const { return engine; }
	explicit operator bool() const { return engine != nullptr; }
	ALPRHandleGuard(const ALPRHandleGuard&) = delete;
	ALPRHandleGuard& operator=(const ALPRHandleGuard&) = 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:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}


static int CreateANSALPRHandle_Impl(ANSCENTER::ANSALPR** Handle, const char* licenseKey, const char* modelZipFilePath, const char* modelZipPassword,
	int engineType, double detectorThreshold, double ocrThreshold, double colourThreshold) {
	ANS_DBG("ANSLPR","Create called: HandlePtr=%p, *Handle(in)=%p, engineType=%d, detTh=%.3f, ocrTh=%.3f, colTh=%.3f, modelZip=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), engineType, detectorThreshold, ocrThreshold, colourThreshold,
	       modelZipFilePath ? modelZipFilePath : "(null)");
	if (Handle == nullptr) { ANS_DBG("ANSLPR","Create FAIL: Handle is null"); return 0; }
	try {
		// Ensure all shared DLLs (OpenCV, OpenVINO, TRT, ORT) are pre-loaded
		ANSCENTER::ANSLibsLoader::Initialize();

		// 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;

		if (engineType == 0) {
			(*Handle) = new ANSCENTER::ANSALPR_CPU();// built-in paddle OCR
		}
		else if (engineType == 1) {
			(*Handle) = new ANSCENTER::ANSALPR_OD();
		}
		else if (engineType == 2) {
			(*Handle) = new ANSCENTER::ANSALPR_OCR();// ONNX OCR (PaddleOCR v5)
		}
		else {
			ANS_DBG("ANSLPR","Create FAIL: unknown engineType=%d", engineType);
			return 0;
		}
		if (*Handle == nullptr) { ANS_DBG("ANSLPR","Create FAIL: new returned null for engineType=%d", engineType); return 0; }
		else {
			ANS_DBG("ANSLPR","Create: allocated handle=%p (uint=%llu) engineType=%d, calling Initialize...",
			       (void*)*Handle, (unsigned long long)(uintptr_t)*Handle, engineType);
			RegisterALPRHandle(*Handle);
			int result = (*Handle)->Initialize(licenseKey, modelZipFilePath, modelZipPassword, detectorThreshold, ocrThreshold,colourThreshold);
			ANS_DBG("ANSLPR","Create: Initialize returned %d for handle=%p (uint=%llu)",
			       result, (void*)*Handle, (unsigned long long)(uintptr_t)*Handle);
			return result;
		}
	}
	catch (std::exception& e) {
		ANS_DBG("ANSLPR","Create EXCEPTION (std::exception): %s", e.what());
		return 0;
	}
	catch (...) {
		ANS_DBG("ANSLPR","Create EXCEPTION (unknown)");
		return 0;
	}
}

extern "C" ANSLPR_API int		   CreateANSALPRHandle(ANSCENTER::ANSALPR * *Handle, const char* licenseKey, const char* modelZipFilePath, const char* modelZipPassword,
									int engineType, double detectorThreshold, double ocrThreshold, double colourThreshold) {
	ANS_DBG("ANSLPR","CreateANSALPRHandle: HandlePtr=%p, *Handle=%p, engineType=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), engineType);
	__try {
		return CreateANSALPRHandle_Impl(Handle, licenseKey, modelZipFilePath, modelZipPassword, engineType, detectorThreshold, ocrThreshold, colourThreshold);
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		char buf[256];
		snprintf(buf, sizeof(buf), "ANSLPR CreateANSALPRHandle: SEH exception 0x%08X caught during initialization", GetExceptionCode());
		ANS_DBG("ANSLPR","CreateANSALPRHandle: SEH exception caught");
		WriteWindowsEventLog(buf);
		if (Handle) *Handle = nullptr;
		return 0;
	}
}
static int LoadANSALPREngineHandle_Impl(ANSCENTER::ANSALPR** Handle) {
	try {
		if (*Handle != nullptr) {
			int result = (*Handle)->LoadEngine();
			return result;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSLPR_API int		   LoadANSALPREngineHandle(ANSCENTER::ANSALPR** Handle) {
	ANS_DBG("ANSLPR","LoadANSALPREngineHandle: HandlePtr=%p, *Handle=%p",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr));
	__try {
		return LoadANSALPREngineHandle_Impl(Handle);
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		char buf[256];
		snprintf(buf, sizeof(buf), "ANSLPR LoadANSALPREngineHandle: SEH exception 0x%08X caught during engine load", GetExceptionCode());
		ANS_DBG("ANSLPR","LoadANSALPREngineHandle: SEH exception caught");
		WriteWindowsEventLog(buf);
		return 0;
	}
}
extern "C" ANSLPR_API std::string  ANSALPR_RunInference(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_string, unsigned int bufferLength) {
	ANS_DBG("ANSLPR","ANSALPR_RunInference: HandlePtr=%p, *Handle=%p, bufferLength=%u",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), bufferLength);
	if (!Handle || !*Handle) return "";
	ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_UNCHANGED);
		if (frame.empty()) return "";
		std::string lprResult;
		bool result = engine->Inference(frame, lprResult);
		frame.release();
		return lprResult;
	}
	catch (...) { return ""; }
}

extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceWithCamID(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* cameraId) {
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%u, cameraId=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), bufferLength, cameraId ? cameraId : "(null)");
	if (!Handle || !*Handle) return "";
	ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_UNCHANGED);
		if (frame.empty()) return "";
		std::string lprResult;
		bool result = engine->Inference(frame, lprResult, cameraId);
		frame.release();
		return lprResult;
	}
	catch (...) { return ""; }
}

extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceInCroppedImages(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes) {
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceInCroppedImages: HandlePtr=%p, *Handle=%p, bufferLength=%u, strBboxes=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), bufferLength, strBboxes ? strBboxes : "(null)");
	if (!Handle || !*Handle) return "";
	ALPRHandleGuard guard(AcquireALPRHandle(*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::string lprResult;
		std::vector<cv::Rect> Bboxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
		bool result = engine->Inference(frame, Bboxes, lprResult);
		frame.release();
		Bboxes.clear();
		return lprResult;
	}
	catch (...) { return ""; }
}

extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceInCroppedImagesWithCamID(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, const char* cameraId) {
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceInCroppedImagesWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%u, strBboxes=%s, cameraId=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), bufferLength, strBboxes ? strBboxes : "(null)", cameraId ? cameraId : "(null)");
	if (!Handle || !*Handle) return "";
	ALPRHandleGuard guard(AcquireALPRHandle(*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::string lprResult;
		std::vector<cv::Rect> Bboxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
		bool result = engine->Inference(frame, Bboxes, lprResult, cameraId);
		frame.release();
		Bboxes.clear();
		return lprResult;
	}
	catch (...) { return ""; }
}

extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceBinary(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height) {
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceBinary: HandlePtr=%p, *Handle=%p, width=%u, height=%u",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), width, height);
	if (!Handle || !*Handle || !jpeg_bytes || width == 0 || height == 0) return "";
	ALPRHandleGuard guard(AcquireALPRHandle(*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::string lprResult;
		bool result = engine->Inference(frame, lprResult);
		frame.release();
		return lprResult;
	}
	catch (...) { return ""; }
}


extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceBinaryInCroppedImages(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, const char* strBboxes) {
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceBinaryInCroppedImages: HandlePtr=%p, *Handle=%p, width=%u, height=%u, strBboxes=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), width, height, strBboxes ? strBboxes : "(null)");
	if (!Handle || !*Handle) return "";
	ALPRHandleGuard guard(AcquireALPRHandle(*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::string lprResult;
		std::vector<cv::Rect> Bboxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
		bool result = engine->Inference(frame, Bboxes, lprResult);
		frame.release();
		return lprResult;
	}
	catch (...) { return ""; }
}


static int ReleaseANSALPRHandle_Impl(ANSCENTER::ANSALPR** Handle) {
	try {
		if (!Handle || !*Handle) {
			ANS_DBG("ANSLPR","Release: HandlePtr or *Handle is null, no-op");
			return 1;
		}
		ANSCENTER::ANSALPR* h = *Handle;
		ANS_DBG("ANSLPR","Release called: handle=%p (uint=%llu)", (void*)h, (unsigned long long)(uintptr_t)h);
		if (!UnregisterALPRHandle(h)) {
			ANS_DBG("ANSLPR","Release: Unregister returned false (already gone or being destroyed by another thread), handle=%p", (void*)h);
			*Handle = nullptr;
			return 1;  // Not in registry — already freed
		}
		(*Handle)->Destroy();
		delete *Handle;
		*Handle = nullptr;
		ANS_DBG("ANSLPR","Release OK: handle=%p deleted, registry now has %zu entries",
		       (void*)h, ALPRHandleRegistry().size());
		return 1;
	}
	catch (...) {
		ANS_DBG("ANSLPR","Release EXCEPTION (unknown)");
		if (Handle) *Handle = nullptr;
		return 0;
	}
}

extern "C" ANSLPR_API int ReleaseANSALPRHandle(ANSCENTER::ANSALPR** Handle) {
	ANS_DBG("ANSLPR","ReleaseANSALPRHandle: HandlePtr=%p, *Handle=%p",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr));
	__try {
		return ReleaseANSALPRHandle_Impl(Handle);
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		ANS_DBG("ANSLPR","ReleaseANSALPRHandle: SEH exception caught");
		if (Handle) *Handle = nullptr;
		return 0;
	}
}
//// For LabVIEW API
extern "C" ANSLPR_API int    ANSALPR_RunInference_LV(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_string, unsigned int bufferLength, LStrHandle detectionResult) {
	ANS_DBG("ANSLPR","ANSALPR_RunInference_LV: HandlePtr=%p, *Handle=%p, bufferLength=%u",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), bufferLength);
	try {
		std::string st = ANSALPR_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" ANSLPR_API int    ANSALPR_RunInferenceWithCamID_LV(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* cameraId, LStrHandle detectionResult)
{
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceWithCamID_LV: HandlePtr=%p, *Handle=%p, bufferLength=%u, cameraId=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), bufferLength, cameraId ? cameraId : "(null)");
	try {
		std::string st = ANSALPR_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" ANSLPR_API int  ANSALPR_RunInferenceBinary_LV(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) {
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceBinary_LV: HandlePtr=%p, *Handle=%p, width=%u, height=%u",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), width, height);
	try {
		std::string st = ANSALPR_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" ANSLPR_API int    ANSALPR_RunInferenceInCroppedImages_LV(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, LStrHandle detectionResult) {
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceInCroppedImages_LV: HandlePtr=%p, *Handle=%p, bufferLength=%u, strBboxes=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), bufferLength, strBboxes ? strBboxes : "(null)");
	try {
		std::string st = ANSALPR_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" ANSLPR_API int		   ANSALPR_RunInferenceInCroppedImagesWithCamID_LV(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) {
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceInCroppedImagesWithCamID_LV: HandlePtr=%p, *Handle=%p, bufferLength=%u, strBboxes=%s, cameraId=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), bufferLength, strBboxes ? strBboxes : "(null)", cameraId ? cameraId : "(null)");
	try {
		std::string st = ANSALPR_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" ANSLPR_API int ANSALPR_RunInferenceComplete_LV(
	ANSCENTER::ANSALPR** Handle,
	cv::Mat** cvImage,
	const char* cameraId,
	int getJpegString,
	int jpegImageSize,
	LStrHandle detectionResult,
	LStrHandle imageStr)
{
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceComplete_LV: HandlePtr=%p, *Handle=%p, cvImage=%p, cameraId=%s, getJpeg=%d, jpegImageSize=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (void*)(cvImage ? *cvImage : nullptr),
	       cameraId ? cameraId : "(null)", getJpegString, jpegImageSize);
	if (!Handle || !*Handle) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

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

	try {
		const cv::Mat& localImage = **cvImage;  // No clone — RunInference takes const ref

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

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

		std::vector<ANSCENTER::Object> outputs;
		{
			// Scoped NV12 fast-path pointer; cleared on any exit path (normal or
			// throw) so the next call on this thread cannot see a stale frame.
			GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
			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));
			}
		}

		// Convert to JPEG only if requested (avoid expensive encode when not needed)
		if (getJpeg) {
			cv::Mat processedImage;
			if (resizeNeeded) {
				cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
			}
			else {
				processedImage = localImage;  // shallow copy (header only, no pixel copy)
			}

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

		std::string stDetectionResult = engine->VectorDetectionToJsonString(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 (const std::exception& ex) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}


extern "C" ANSLPR_API int		   ANSALPR_RunInferenceComplete_CPP(ANSCENTER::ANSALPR** Handle, cv::Mat** cvImage, const char* cameraId, int getJpegString, int jpegImageSize, std::string& detectionResult, std::string& imageStr) {
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceComplete_CPP: HandlePtr=%p, *Handle=%p, cvImage=%p, cameraId=%s, getJpeg=%d, jpegImageSize=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (void*)(cvImage ? *cvImage : nullptr),
	       cameraId ? cameraId : "(null)", getJpegString, jpegImageSize);
	if (!Handle || !*Handle) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

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

	try {
		const cv::Mat& localImage = **cvImage;  // No clone — RunInference takes const ref

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

		std::vector<ANSCENTER::Object> outputs;
		{
			// Scoped NV12 fast-path pointer; cleared on any exit path (normal or throw).
			GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
			outputs = engine->RunInference(localImage, cameraId);
		}
		bool getJpeg = (getJpegString == 1);
		std::string stImage;

		if ((jpegImageSize > 0) && (jpegImageSize < maxImageSize)) {
			int newWidth = jpegImageSize;
			int newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
			float 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;
			if ((jpegImageSize > 0) && (jpegImageSize < maxImageSize)) {
				int newWidth = jpegImageSize;
				int newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
				cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
			}
			else {
				processedImage = localImage;  // shallow copy (header only)
			}
			std::vector<uchar> buf;
			if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) {
				stImage.assign(buf.begin(), buf.end());
			}
		}
		detectionResult = engine->VectorDetectionToJsonString(outputs);

		if (detectionResult.empty()) return 0;
		if (getJpeg) {
			if (stImage.empty()) return 0;
			imageStr = stImage;
		}
		return 1;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSLPR_API int ANSALPR_RunInferencesComplete_LV(
	ANSCENTER::ANSALPR** Handle,
	cv::Mat** cvImage,
	const char* cameraId,
	int maxImageSize,
	const char* strBboxes,
	LStrHandle detectionResult)
{
	ANS_DBG("ANSLPR","ANSALPR_RunInferencesComplete_LV: HandlePtr=%p, *Handle=%p, cvImage=%p, cameraId=%s, maxImageSize=%d, strBboxes=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (void*)(cvImage ? *cvImage : nullptr),
	       cameraId ? cameraId : "(null)", maxImageSize, strBboxes ? strBboxes : "(null)");
	if (!Handle || !*Handle) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

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

	try {
		const cv::Mat& localImage = **cvImage;  // No clone — RunInference takes const ref

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

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

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

		if (bBox.empty()) {
			// Full-frame path: NV12 fast-path is only safe for the full-frame
			// inference call. Scope the TL pointer so it is cleared on any exit
			// path (normal or thrown) and is NOT seen by any subsequent
			// cropped-image inference (which would mismatch the NV12 cache).
			GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
			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::Object> 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 = engine->VectorDetectionToJsonString(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 (const std::exception& ex) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

// Dedicated pipeline-mode export. Unlike ANSALPR_RunInferencesComplete_LV
// this function:
//   1. Always runs with tracker OFF, voting OFF, dedup OFF — it targets
//      callers that already have precise per-vehicle bboxes and want raw,
//      stateless results.
//   2. Issues ONE batched LP-detect call and ONE batched recognizer call
//      per frame via ANSALPR::RunInferencesBatch, instead of looping
//      engine->RunInference once per crop. This eliminates the per-shape
//      ORT/TRT allocator churn that causes ANSALPR_OCR memory growth when
//      the legacy pipeline-mode loop is used under LabVIEW worker threads.
//   3. Does NOT touch tl_currentGpuFrame — the caller is working with
//      cropped regions, not the NV12-keyed source Mat, so the fast-path
//      pointer is meaningless here. Eliminates a class of UAF risk.
// Output coordinates are rescaled back into the caller's resized space,
// matching the convention used by ANSALPR_RunInferencesComplete_LV.
extern "C" ANSLPR_API int ANSALPR_RunInferencesBatch_LV(
	ANSCENTER::ANSALPR** Handle,
	cv::Mat** cvImage,
	const char* cameraId,
	int maxImageSize,
	const char* strBboxes,
	LStrHandle detectionResult)
{
	ANS_DBG("ANSLPR","ANSALPR_RunInferencesBatch_LV: HandlePtr=%p, *Handle=%p, cvImage=%p, cameraId=%s, maxImageSize=%d, strBboxes=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), (void*)(cvImage ? *cvImage : nullptr),
	       cameraId ? cameraId : "(null)", maxImageSize, strBboxes ? strBboxes : "(null)");
	if (!Handle || !*Handle) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

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

	try {
		const cv::Mat& frame = **cvImage;
		const int frameW = frame.cols;
		const int frameH = frame.rows;
		if (frameW <= 0 || frameH <= 0) return -2;

		// Same scaling convention as ANSALPR_RunInferencesComplete_LV:
		// the bboxes in `strBboxes` are in a resized coordinate space;
		// scale them up to the full frame for the crop, then rescale the
		// plate outputs back to the caller's space.
		const double scale =
			(maxImageSize > 0) ? static_cast<double>(frameW) / maxImageSize : 1.0;

		std::vector<cv::Rect> rawBoxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
		std::vector<cv::Rect> scaledBoxes;
		scaledBoxes.reserve(rawBoxes.size());
		const cv::Rect frameRect(0, 0, frameW, frameH);
		for (const auto& r : rawBoxes) {
			cv::Rect s(
				static_cast<int>(r.x * scale),
				static_cast<int>(r.y * scale),
				static_cast<int>(r.width  * scale),
				static_cast<int>(r.height * scale));
			s &= frameRect;
			if (s.width > 0 && s.height > 0) scaledBoxes.push_back(s);
		}

		// Empty bbox list → fall through to full-frame RunInference so
		// existing LabVIEW code that passes "" still works, matching the
		// behaviour of ANSALPR_RunInferencesComplete_LV.
		std::vector<ANSCENTER::Object> results;
		if (scaledBoxes.empty()) {
			results = engine->RunInference(frame, cameraId);
		} else {
			results = engine->RunInferencesBatch(frame, scaledBoxes, cameraId);
		}

		// Rescale plate boxes back into the caller's resized space.
		if (scale != 1.0) {
			const double inv = 1.0 / scale;
			for (auto& o : results) {
				o.box.x      = static_cast<int>(o.box.x      * inv);
				o.box.y      = static_cast<int>(o.box.y      * inv);
				o.box.width  = static_cast<int>(o.box.width  * inv);
				o.box.height = static_cast<int>(o.box.height * inv);
			}
		}

		std::string json = engine->VectorDetectionToJsonString(results);
		if (json.empty()) return 0;

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

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, json.c_str(), size);
		return 1;
	}
	catch (const std::exception& /*ex*/) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}


extern "C" ANSLPR_API int		   ANSALPR_SetFormat(ANSCENTER::ANSALPR** Handle, const char* format) {
	ANS_DBG("ANSLPR","ANSALPR_SetFormat: HandlePtr=%p, *Handle=%p, format=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), format ? format : "(null)");
	if (!Handle || !*Handle) return -1;
	if (!format) return -1;
	try {
		std::string strFormat(format);
		(*Handle)->SetPlateFormat(strFormat);
		return 1;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSLPR_API int		   ANSALPR_SetFormats(ANSCENTER::ANSALPR** Handle, const char* formats){// semi separated formats
	ANS_DBG("ANSLPR","ANSALPR_SetFormats: HandlePtr=%p, *Handle=%p, formats=%s",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), formats ? formats : "(null)");
	if (!Handle || !*Handle) return -1;
	if (!formats) return -1;
	try {
		std::vector<std::string> formatList;
		std::string token;
		std::istringstream tokenStream(formats);
		while (getline(tokenStream, token, ';')) {
			formatList.push_back(token);
		}
		(*Handle)->SetPlateFormats(formatList);
		return 1;
	}
	catch (std::exception& e) {
		return 0;
	}	
	catch (...) {
		return 0;
	}
}


// ALPRChecker: 1 = enabled (full-frame auto-detected), 0 = disabled (raw OCR)
extern "C" ANSLPR_API int		   ANSALPR_SetALPRCheckerEnabled(ANSCENTER::ANSALPR** Handle, int enable) {
	ANS_DBG("ANSLPR","ANSALPR_SetALPRCheckerEnabled: HandlePtr=%p, *Handle=%p, enable=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), enable);
	if (!Handle || !*Handle) return -1;
	try {
		(*Handle)->SetALPRCheckerEnabled(enable != 0);
		ANS_DBG("ALPR_Checker", "SetALPRCheckerEnabled=%d (%s)",
			enable, enable ? "ON" : "OFF");
		return 1;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSLPR_API int		   ANSALPR_SetCountry(ANSCENTER::ANSALPR** Handle, int country) {
	ANS_DBG("ANSLPR","ANSALPR_SetCountry: HandlePtr=%p, *Handle=%p, country=%d",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr), country);
	if (!Handle || !*Handle) return -1;
	try {
		(*Handle)->SetCountry(static_cast<ANSCENTER::Country>(country));
		return 1;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSLPR_API int		   ANSALPR_GetFormats(ANSCENTER::ANSALPR** Handle, LStrHandle Lstrformats)// semi separated formats
{
	ANS_DBG("ANSLPR","ANSALPR_GetFormats: HandlePtr=%p, *Handle=%p",
	       (void*)Handle, (void*)(Handle ? *Handle : nullptr));
	if (!Handle || !*Handle) return -1;
	if (!Lstrformats) return -1;
	try {
		std::vector<std::string> formatList = (*Handle)->GetPlateFormats();
		std::string formats;
		for (const auto& format : formatList) {
			formats += format + ";";
		}
		if (!formats.empty()) {
			formats.pop_back(); // Remove the last semicolon
		}
		int size = static_cast<int>(formats.length());
		MgErr error = DSSetHandleSize(Lstrformats, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;
		(*Lstrformats)->cnt = size;
		memcpy((*Lstrformats)->str, formats.c_str(), size);
		return 1;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}

}

// Unicode conversion utilities for LabVIEW wrapper classes
extern "C" ANSLPR_API int ANSLPR_ConvertUTF8ToUTF16LE(const char* utf8Str, LStrHandle result, int includeBOM) {
	ANS_DBG("ANSLPR","ANSLPR_ConvertUTF8ToUTF16LE: utf8Str=%p, result=%p, includeBOM=%d",
	       (void*)utf8Str, (void*)result, includeBOM);
	try {
		if (!utf8Str || !result) return -1;
		int len = (int)strlen(utf8Str);
		if (len == 0) return 0;
		const char bom[2] = { '\xFF', '\xFE' };
		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" ANSLPR_API int ANSLPR_ConvertUTF16LEToUTF8(const unsigned char* utf16leBytes, int byteLen, LStrHandle result) {
	ANS_DBG("ANSLPR","ANSLPR_ConvertUTF16LEToUTF8: utf16leBytes=%p, byteLen=%d, result=%p",
	       (void*)utf16leBytes, byteLen, (void*)result);
	try {
		if (!utf16leBytes || byteLen <= 0 || !result) return -1;
		bool isUtf16le = (byteLen >= 2 && byteLen % 2 == 0);
		if (isUtf16le) {
			bool isAscii = true;
			for (int i = 1; i < byteLen; i += 2) {
				if (utf16leBytes[i] != 0x00) { isAscii = false; break; }
			}
			if (isAscii) {
				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; }
}

// ============================================================================
// V2 API — accepts uint64_t handle by value (eliminates LabVIEW buffer reuse bug)
// ============================================================================
#define ALPR_V2_HANDLE_SETUP(handleVal) \
	ANSCENTER::ANSALPR* _v2Direct = reinterpret_cast<ANSCENTER::ANSALPR*>(handleVal); \
	if (_v2Direct == nullptr) return 0; \
	ANSCENTER::ANSALPR* _v2Arr[1] = { _v2Direct }; \
	ANSCENTER::ANSALPR** Handle = &_v2Arr[0];

extern "C" ANSLPR_API int ANSALPR_RunInference_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, unsigned int bufferLength, LStrHandle detectionResult) {
	ALPR_V2_HANDLE_SETUP(handleVal);
	ANS_DBG("ANSLPR","ANSALPR_RunInference_LV_V2: handleVal=%llu handle=%p, bufferLength=%u",
	       (unsigned long long)handleVal, (void*)_v2Direct, bufferLength);
	try {
		std::string st = ANSALPR_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" ANSLPR_API int ANSALPR_RunInferenceWithCamID_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, unsigned int bufferLength, const char* cameraId, LStrHandle detectionResult) {
	ALPR_V2_HANDLE_SETUP(handleVal);
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceWithCamID_LV_V2: handleVal=%llu handle=%p, bufferLength=%u, cameraId=%s",
	       (unsigned long long)handleVal, (void*)_v2Direct, bufferLength, cameraId ? cameraId : "(null)");
	try {
		std::string st = ANSALPR_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" ANSLPR_API int ANSALPR_RunInferenceBinary_LV_V2(uint64_t handleVal, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) {
	ALPR_V2_HANDLE_SETUP(handleVal);
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceBinary_LV_V2: handleVal=%llu handle=%p, width=%u, height=%u",
	       (unsigned long long)handleVal, (void*)_v2Direct, width, height);
	try {
		std::string st = ANSALPR_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" ANSLPR_API int ANSALPR_RunInferenceInCroppedImages_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, LStrHandle detectionResult) {
	ALPR_V2_HANDLE_SETUP(handleVal);
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceInCroppedImages_LV_V2: handleVal=%llu handle=%p, bufferLength=%u, strBboxes=%s",
	       (unsigned long long)handleVal, (void*)_v2Direct, bufferLength, strBboxes ? strBboxes : "(null)");
	try {
		std::string st = ANSALPR_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" ANSLPR_API int ANSALPR_RunInferenceInCroppedImagesWithCamID_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) {
	ALPR_V2_HANDLE_SETUP(handleVal);
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceInCroppedImagesWithCamID_LV_V2: handleVal=%llu handle=%p, bufferLength=%u, strBboxes=%s, cameraId=%s",
	       (unsigned long long)handleVal, (void*)_v2Direct, bufferLength, strBboxes ? strBboxes : "(null)", cameraId ? cameraId : "(null)");
	try {
		std::string st = ANSALPR_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" ANSLPR_API int ANSALPR_RunInferenceComplete_LV_V2(
	uint64_t handleVal,
	cv::Mat** cvImage,
	const char* cameraId,
	int getJpegString,
	int jpegImageSize,
	LStrHandle detectionResult,
	LStrHandle imageStr)
{
	ANSCENTER::ANSALPR* _v2Direct = reinterpret_cast<ANSCENTER::ANSALPR*>(handleVal);
	ANS_DBG("ANSLPR","ANSALPR_RunInferenceComplete_LV_V2: handleVal=%llu handle=%p, cvImage=%p, cameraId=%s, getJpeg=%d, jpegImageSize=%d",
	       (unsigned long long)handleVal, (void*)_v2Direct, (void*)(cvImage ? *cvImage : nullptr),
	       cameraId ? cameraId : "(null)", getJpegString, jpegImageSize);
	if (_v2Direct == nullptr) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	ALPRHandleGuard guard(AcquireALPRHandle(_v2Direct));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		const cv::Mat& localImage = **cvImage;  // No clone — RunInference takes const ref

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

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

		std::vector<ANSCENTER::Object> outputs;
		{
			// Scoped NV12 fast-path pointer; cleared on any exit path (normal or throw).
			GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
			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;
			if (resizeNeeded) {
				cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
			}
			else {
				processedImage = localImage;  // shallow copy (header only)
			}

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

		std::string stDetectionResult = engine->VectorDetectionToJsonString(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 (const std::exception& ex) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

// V2 uint64_t handle variant of ANSALPR_RunInferencesBatch_LV.
// See the non-V2 version for semantics — identical behaviour, differs only
// in how the caller passes the ALPR engine handle (by value instead of via
// a LabVIEW Handle** pointer-to-pointer).
extern "C" ANSLPR_API int ANSALPR_RunInferencesBatch_LV_V2(
	uint64_t handleVal,
	cv::Mat** cvImage,
	const char* cameraId,
	int maxImageSize,
	const char* strBboxes,
	LStrHandle detectionResult)
{
	ANSCENTER::ANSALPR* _v2Direct = reinterpret_cast<ANSCENTER::ANSALPR*>(handleVal);
	ANS_DBG("ANSLPR","ANSALPR_RunInferencesBatch_LV_V2: handleVal=%llu handle=%p, cvImage=%p, cameraId=%s, maxImageSize=%d, strBboxes=%s",
	       (unsigned long long)handleVal, (void*)_v2Direct, (void*)(cvImage ? *cvImage : nullptr),
	       cameraId ? cameraId : "(null)", maxImageSize, strBboxes ? strBboxes : "(null)");
	if (_v2Direct == nullptr) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	ALPRHandleGuard guard(AcquireALPRHandle(_v2Direct));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		const cv::Mat& frame = **cvImage;
		const int frameW = frame.cols;
		const int frameH = frame.rows;
		if (frameW <= 0 || frameH <= 0) return -2;

		const double scale =
			(maxImageSize > 0) ? static_cast<double>(frameW) / maxImageSize : 1.0;

		std::vector<cv::Rect> rawBoxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
		std::vector<cv::Rect> scaledBoxes;
		scaledBoxes.reserve(rawBoxes.size());
		const cv::Rect frameRect(0, 0, frameW, frameH);
		for (const auto& r : rawBoxes) {
			cv::Rect s(
				static_cast<int>(r.x * scale),
				static_cast<int>(r.y * scale),
				static_cast<int>(r.width  * scale),
				static_cast<int>(r.height * scale));
			s &= frameRect;
			if (s.width > 0 && s.height > 0) scaledBoxes.push_back(s);
		}

		std::vector<ANSCENTER::Object> results;
		if (scaledBoxes.empty()) {
			results = engine->RunInference(frame, cameraId);
		} else {
			results = engine->RunInferencesBatch(frame, scaledBoxes, cameraId);
		}

		if (scale != 1.0) {
			const double inv = 1.0 / scale;
			for (auto& o : results) {
				o.box.x      = static_cast<int>(o.box.x      * inv);
				o.box.y      = static_cast<int>(o.box.y      * inv);
				o.box.width  = static_cast<int>(o.box.width  * inv);
				o.box.height = static_cast<int>(o.box.height * inv);
			}
		}

		std::string json = engine->VectorDetectionToJsonString(results);
		if (json.empty()) return 0;

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

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, json.c_str(), size);
		return 1;
	}
	catch (const std::exception& /*ex*/) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

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

	ALPRHandleGuard guard(AcquireALPRHandle(_v2Direct));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		const cv::Mat& localImage = **cvImage;  // No clone — RunInference takes const ref

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

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

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

		if (bBox.empty()) {
			// Full-frame path: NV12 fast-path is only safe for the full-frame
			// inference call. Scope the TL pointer so it is cleared on any exit
			// path (normal or thrown) and is NOT seen by any subsequent
			// cropped-image inference (which would mismatch the NV12 cache).
			GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
			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::Object> 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 = engine->VectorDetectionToJsonString(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 (const std::exception& ex) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}
