#include <boostAsioLinkageFix.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <map>
#include <functional>
#include <algorithm>
#include <dlfcn.h>
#include <opts.h>
#include <user/senseApiDesc.h>
#include <user/deviceAttachmentSpec.h>
#include <spinscale/callback.h>
#include <livoxProto1/livoxProto1.h>
#include <livoxProto1/device.h>
#include <livoxProto1/protocol.h>
#include <spinscale/asynchronousContinuation.h>
#include <boost/asio/deadline_timer.hpp>
#include "pcloudStimulusProducer.h"
#include "livoxGen1.h"


namespace smo {
namespace stim_buff {

// Salmanoff hooks, obtained from SMO_GET_STIM_BUFF_API_DESC_FN_NAME().
const SmoCallbacks* smoHooksPtr = nullptr;
static SmoThreadingModelDesc smoThreadingModelDesc;

// Local collection of stimulus producers
static std::vector<std::shared_ptr<StimulusProducer>> attachedStimulusProducers;

// Get stimulus producer by device attachment spec
static std::shared_ptr<StimulusProducer>
getStimulusProducer(
	const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec
	)
{
	for (const auto& stimProducer : attachedStimulusProducers)
	{
		// Compare device selectors to find matching buffer
		if (livoxProto1::comms::deviceIdentifiersEqual(
				stimProducer->deviceAttachmentSpec->deviceSelector,
				spec->deviceSelector)
			&& stimProducer->exportsQualeIfaceApi(spec->qualeIfaceApi))
		{
			return stimProducer;
		}
	}

	return nullptr;
}

// Helper function to parse n-dgrams-per-frame from stim-buff-api params
static size_t parseNDgramsPerFrame(
	const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec)
{
	const std::vector<std::string> nDgramsPerFrameParamNames = {
		"n-dgrams-per-frame",
		"num-dgrams-per-frame"
	};

	return static_cast<size_t>(
		smo::device::DeviceAttachmentSpec::parseOptionalParamAsIntWithSynonyms(
			spec->stimBuffApiParams, nDgramsPerFrameParamNames, 84));
}

// LivoxProto1DllState constructor implementation
LivoxProto1DllState::LivoxProto1DllState()
: dlopenHandle(nullptr, DlCloser),
  livoxProto1_main(nullptr),
  livoxProto1_exit(nullptr),
  livoxProto1_getOrCreateDeviceReq(nullptr),
  livoxProto1_destroyDeviceReq(nullptr),
  livoxProto1_device_enablePcloudDataReq(nullptr),
  livoxProto1_device_disablePcloudDataReq(nullptr),
  livoxProto1_device_getReturnModeReq(nullptr),
  livoxProto1_getPcloudDataFdDesc(nullptr)
{}

// LivoxProto1DllState DlCloser implementation
void LivoxProto1DllState::DlCloser(void* handle)
{
	if (handle) {
		dlclose(handle);
	}
}

LivoxProto1DllState livoxProto1;

// Continuation classes for async operations
class AttachDeviceReq
:	public sscl::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>
{
public:
	AttachDeviceReq(
		const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
		sscl::Callback<sal_mlo_attachDeviceReqCbFn> cb)
	:	sscl::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>(
			std::move(cb)),
	spec(spec)
	{}

public:
	const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
	std::shared_ptr<PcloudStimulusProducer> stimProducer;
	std::shared_ptr<livoxProto1::Device> deviceTmp;

private:
	std::unique_ptr<boost::asio::deadline_timer> delayTimer;

	// Helper method to ensure StimBuffer is attached
	// Returns true if successful, false on error
	bool ensureStimBufferAttached(std::shared_ptr<AttachDeviceReq> context)
	{
		if (!context->stimProducer)
		{
			std::cerr << __func__ << ": stimProducer is null" << std::endl;
			return false;
		}

		// Check for duplicate qualeIfaceApi
		const std::string& qualeIfaceApi = context->spec->qualeIfaceApi;
		if (context->stimProducer->hasBufferWithQualeIfaceApi(qualeIfaceApi))
		{
			std::cerr << __func__ << ": Buffer with qualeIfaceApi '"
				<< qualeIfaceApi << "' already exists for this producer. "
				"Each producer can only have one buffer per qualeIfaceApi."
				<< std::endl;
			return false;
		}

		// Call getOrCreateAttachedStimulusBuffer (may throw, catch and return failure)
		try {
			context->stimProducer->getOrCreateAttachedStimulusBuffer(
				context->spec);
		} catch (const std::exception& e) {
			std::cerr << __func__ << ": Failed to create StimBuffer: "
				<< e.what() << ". Producer is committed, DeviceReattacher will retry."
				<< std::endl;
			// Return false so caller can handle error callback
			return false;
		}

		return true;
	}

public:
	void attachDeviceReq1(
		std::shared_ptr<AttachDeviceReq> context,
		bool success, std::shared_ptr<livoxProto1::Device> dev)
	{
		if (!success || !dev)
		{
			std::cerr << __func__ << ": Failed to create/find Livox device: "
				<< context->spec->deviceSelector << std::endl;
			context->callOriginalCb(false, context->spec);
			return;
		}

		// Stash device pointer until after getReturnMode succeeds
		context->deviceTmp = dev;

		if (1 || smoHooksPtr->OptionParser_getOptions().verbose)
		{
			std::cout << __func__ << ": Successfully attached/found Livox "
				"device: " << context->spec->deviceSelector << " (ID: "
				<< context->spec->deviceIdentifier << ")\n";
		}

		/* Delay here because getOrCreate just sent HandshakeReq, so device
		 * may not yet be ready for another command.
		 */
		context->delayedGetReturnMode(context);
	}

	void delayedGetReturnMode(
		std::shared_ptr<AttachDeviceReq> context)
	{
		// Initialize timer with LivoxGen1 metadata io_service
		delayTimer = std::make_unique<boost::asio::deadline_timer>(
			smoThreadingModelDesc.componentThread->getIoService());

		delayTimer->expires_from_now(boost::posix_time::milliseconds(5));
		delayTimer->async_wait(
			std::bind(
				&AttachDeviceReq::attachDeviceReq2,
				context.get(), context,
				std::placeholders::_1));
	}

	void attachDeviceReq2(
		std::shared_ptr<AttachDeviceReq> context,
		const boost::system::error_code& error)
	{
		if (error)
		{
			std::cerr << __func__ << ": Timer error: " << error.message()
				<< std::endl;
			context->callOriginalCb(false, context->spec);
			return;
		}

		(*livoxProto1.livoxProto1_device_getReturnModeReq)(
			context->deviceTmp,
			{context, std::bind(
				&AttachDeviceReq::attachDeviceReq3_doCreateStimProducer,
				context.get(), context,
				std::placeholders::_1, std::placeholders::_2)});
	}

	void attachDeviceReq3_doCreateStimProducer(
		std::shared_ptr<AttachDeviceReq> context,
		bool success, uint8_t mode)
	{
		if (!success)
		{
			std::cerr << __func__ << ": Failed to get return mode for dev "
				<< context->spec->deviceSelector << std::endl;

			context->callOriginalCb(false, context->spec);
			return;
		}

		if (1 || smoHooksPtr->OptionParser_getOptions().verbose)
		{
			std::cout << __func__ << ": Got return mode (" << (int)mode
				<< ") for device: " << context->spec->deviceSelector
				<< std::endl;
		}

		/* Check if PcloudStimulusProducer already exists
		 * (race condition or double-add)
		 */
		auto existingProducer = getStimulusProducer(context->spec);
		if (existingProducer)
		{
			throw std::runtime_error(
				std::string(__func__) + ": PcloudStimulusProducer already "
				"exists for device " + context->spec->deviceSelector + " "
				"(race condition or double-add)");
		}

		// Create & add PcloudStimulusProducer to collection since dev now ready
		PcloudStimulusProducer::PcloudFormatDesc formatDesc;
		formatDesc.format = PcloudStimulusProducer::PcloudFormatDesc::Format
			::XYZI;

		// Parse n-dgrams-per-frame from stim-buff-api params (default: 84)
		size_t nDgramsPerFrame = parseNDgramsPerFrame(context->spec);

		auto pcloudDataProducer = std::make_shared<PcloudStimulusProducer>(
			context->spec, context->deviceTmp, formatDesc, nDgramsPerFrame);

		context->stimProducer = pcloudDataProducer;
		context->deviceTmp->nAttachedStimulusProducers++;
		if (context->deviceTmp->nAttachedStimulusProducers > 2)
		{
			throw std::runtime_error(
				std::string(__func__) + ": Each LivoxGen1 device can only have "
					"at most two StimulusProducers attached to it. Found "
					+ std::to_string(
						context->deviceTmp->nAttachedStimulusProducers) + ".");
		}

		attachedStimulusProducers.push_back(pcloudDataProducer);
		if (false
			/*attachedStimulusProducers.size() >= 2*nDevicesKnownToGen1Lib */)
		{
			/**	TODO:
			 * It would be nice to add an nDevicesKnownToGen1Lib counter, and
			 * then add a check here to ensure that
			 * attachedStimulusProducers.size() is always less than or equal to
			 * 2*nDevicesKnownToGen1Lib.
			 *
			 * (2 stim producers per device).
			 */
#if 0
			throw std::runtime_error(
				std::string(__func__) + ": Number of StimulusProducers attached "
				"to LivoxGen1 devices known to the library ("
				+ std::to_string(attachedStimulusProducers.size())
				+ ") is greater than "
				"expected. Lib knows about "
				+ std::to_string(nDevicesKnownToGen1Lib) + " devices, "
				"so there should be at most "
				+ std::to_string(2*nDevicesKnownToGen1Lib)
				+ " StimulusProducers attached to devices.");
#endif
		}

		pcloudDataProducer->start();

		// Ensure StimBuffer is attached
		attachDeviceReq4_doCreateStimBuff_maybeDirectlyCalled(context);
	}

	// Ensure StimBuffer is attached
	void attachDeviceReq4_doCreateStimBuff_maybeDirectlyCalled(
		std::shared_ptr<AttachDeviceReq> context
		)
	{
		// Ensure StimBuffer is attached
		if (!ensureStimBufferAttached(context))
		{
			context->callOriginalCb(false, context->spec);
			return;
		}

		// Continue to enable pcloud data if needed
		attachDeviceReq5_doEnablePcloudData_maybeDirectlyCalled(context);
	}

	// Enable pcloud data if needed
	void attachDeviceReq5_doEnablePcloudData_maybeDirectlyCalled(
		std::shared_ptr<AttachDeviceReq> context
		)
	{
		if (!context->stimProducer || !context->stimProducer->device)
		{
			std::cerr << __func__ << ": stimProducer or device is null"
				<< std::endl;
			context->callOriginalCb(false, context->spec);
			return;
		}

		/* Enable pcloud data. Don't need delay since no commands were
		 * sent to device prior to us reaching here (or delay already handled).
		 */
		(*livoxProto1.livoxProto1_device_enablePcloudDataReq)(
			context->stimProducer->device,
			{context, std::bind(
				&AttachDeviceReq::attachDeviceReq6,
				context.get(), context,
				std::placeholders::_1)});
	}

	void attachDeviceReq6(
		std::shared_ptr<AttachDeviceReq> context,
		bool success)
	{
		if (!success)
		{
			std::cerr << __func__ << ": Failed to enable pcloud data for dev "
				<< context->spec->deviceSelector << std::endl;

			context->callOriginalCb(false, context->spec);
			return;
		}

		if (1 || smoHooksPtr->OptionParser_getOptions().verbose)
		{
			std::cout << __func__ << ": Enabled pcloud data for device: "
				<< context->spec->deviceSelector << std::endl;
		}

		context->callOriginalCb(success, context->spec);
	}
};

class DetachDeviceReq
:	public sscl::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>
{
public:
	DetachDeviceReq(
		const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
		const std::shared_ptr<StimulusBuffer>& stimBuffer,
		sscl::Callback<sal_mlo_detachDeviceReqCbFn> cb)
	:	sscl::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>(
		std::move(cb)),
	spec(spec), stimBuffer(stimBuffer)
	{}

public:
	const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
	std::shared_ptr<StimulusBuffer> stimBuffer;
private:
	std::unique_ptr<boost::asio::deadline_timer> delayTimer;

public:
	void detachDeviceReq1(
		std::shared_ptr<DetachDeviceReq> context,
		bool success)
	{
		if (!success)
		{
			std::cerr << __func__ << ": Failed to disable pcloud data for "
				"stim producer " << context->spec->deviceSelector << std::endl;
			// Fallthrough.
		}

		// Add 5ms delay before destroying device
		context->delayedDestroyDevice(context);
	}

	// Helper method to delay and then call destroyDeviceReq
	void delayedDestroyDevice(
		std::shared_ptr<DetachDeviceReq> context)
	{
		// Initialize timer with LivoxGen1 metadata io_service
		delayTimer = std::make_unique<boost::asio::deadline_timer>(
			smoThreadingModelDesc.componentThread->getIoService());

		delayTimer->expires_from_now(boost::posix_time::milliseconds(5));
		delayTimer->async_wait(
			std::bind(
				&DetachDeviceReq::detachDeviceReq1_delayed,
				context.get(), context,
				std::placeholders::_1));
	}

	// Callback for the delayed destroyDeviceReq
	void detachDeviceReq1_delayed(
		std::shared_ptr<DetachDeviceReq> context,
		const boost::system::error_code& error)
	{
		if (error)
		{
			std::cerr << __func__ << ": Timer error: " << error.message()
				<< std::endl;
			// Fallthrough.
		}

		// Remove StimBuffer from collection if it exists
		if (!context->stimBuffer)
		{
			throw std::runtime_error(std::string(__func__)
				+ ": stimBuffer (API: " + context->spec->stimBuffApi + ") "
				+ "is missing in detachDeviceReq1_delayed "
				+ "for device " + context->spec->deviceSelector);
		}

		// Get the producer from the buffer's parent
		auto& stimProducer = dynamic_cast<PcloudStimulusProducer&>(
			context->stimBuffer->parent);

		stimProducer.destroyAttachedStimulusBuffer(context->stimBuffer);

		// Check if StimProducer has other buffers
		if (!stimProducer.attachedStimulusBuffers.empty())
		{
			// Other buffers exist - just remove this buffer, done
			context->callOriginalCb(true, context->spec);
			return;
		}

		// No other buffers - stop and remove StimProducer
		stimProducer.stop();
		// Remove stimulus producer from collection before destroying device
		stimProducer.device->nAttachedStimulusProducers--;
		// Find and remove the producer from the collection by comparing device
		auto it2 = std::find_if(
			attachedStimulusProducers.begin(), attachedStimulusProducers.end(),
			[&stimProducer](const std::shared_ptr<StimulusProducer>& p)
			{
				/**	FIXME:
				 * When we implement the ImuStimulusProducer, we need to make
				 * sure we handle that properly here.
				 */
				auto pcloudProd = std::dynamic_pointer_cast<PcloudStimulusProducer>(p);
				return pcloudProd && pcloudProd->device == stimProducer.device;
			});
		if (it2 != attachedStimulusProducers.end())
			{ attachedStimulusProducers.erase(it2); }

		(*livoxProto1.livoxProto1_destroyDeviceReq)(
			stimProducer.device,
			{context, std::bind(
				&DetachDeviceReq::detachDeviceReq2,
				context.get(), context,
				std::placeholders::_1)});
	}

	void detachDeviceReq2(
		std::shared_ptr<DetachDeviceReq> context,
		bool success)
	{
		if (!success)
		{
			std::cerr << __func__ << ": Failed to destroy dev "
				"device " << context->spec->deviceSelector << " for stim "
				"producer.\n";

			/**	NOTE:
			 * There's a decent argument for falling through here and still
			 * removing the stimulus producer from attachedStimulusProducers.
			 */
			context->callOriginalCb(false, context->spec);
			return;
		}

		if (1 || smoHooksPtr->OptionParser_getOptions().verbose)
		{
			std::cout << __func__ << ": Successfully detached pcloud stim "
				"producer for device " << context->spec->deviceSelector
				<< " and possibly also destroyed device.\n";
		}

		context->callOriginalCb(success, context->spec);
	}
};

// Callback function declarations
extern "C" sal_mlo_initializeIndFn livoxGen1_initializeInd;
extern "C" sal_mlo_finalizeIndFn livoxGen1_finalizeInd;
extern "C" sal_mlo_attachDeviceReqFn livoxGen1_attachDeviceReq;
extern "C" sal_mlo_detachDeviceReqFn livoxGen1_detachDeviceReq;

// Stim Buff API descriptor
static const StimBuffApiDesc livoxGen1ApiDesc = {
	.name = "livoxGen1",
	.exportedQualeIfaceApis = {
		{.name = "mesh"},
		{.name = "pcloudIntensity"},
		{.name = "pcloudAmbience"},
		{.name = "gyro"},
		{.name = "accel"}
	},
	.sal_mgmt_libOps = {
		.initializeInd = livoxGen1_initializeInd,
		.finalizeInd = livoxGen1_finalizeInd,
		.attachDeviceReq = livoxGen1_attachDeviceReq,
		.detachDeviceReq = livoxGen1_detachDeviceReq
	}
};

// Callback function implementations
extern "C" int livoxGen1_initializeInd(void)
{
	if (!smoHooksPtr)
	{
		throw std::runtime_error(std::string(__func__) + ": SMO hooks "
			"pointers not filled in.");
	}

	// Load LivoxProto1 library
	auto libPath = smoHooksPtr->searchForLibInSmoSearchPaths(
		"liblivoxProto1.so");

	livoxProto1.dlopenHandle.reset(dlopen(
		libPath.value_or("liblivoxProto1.so").c_str(), RTLD_LAZY));

	if (!livoxProto1.dlopenHandle)
	{
		throw std::runtime_error(
			std::string(__func__) +
			": Failed to load LivoxProto1 library: " +
			(dlerror() ? dlerror() : "unknown error"));
	}

	// Get LivoxProto1 library functions
	livoxProto1.livoxProto1_main = reinterpret_cast<livoxProto1_mainFn *>(
		dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_main"));
	livoxProto1.livoxProto1_exit = reinterpret_cast<livoxProto1_exitFn *>(
		dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_exit"));
	livoxProto1.livoxProto1_getOrCreateDeviceReq = reinterpret_cast<
		livoxProto1_getOrCreateDeviceReqFn *>(
			dlsym(
				livoxProto1.dlopenHandle.get(),
				"livoxProto1_getOrCreateDeviceReq"));
	livoxProto1.livoxProto1_destroyDeviceReq = reinterpret_cast<
		livoxProto1_destroyDeviceReqFn *>(
			dlsym(
				livoxProto1.dlopenHandle.get(),
				"livoxProto1_destroyDeviceReq"));
	livoxProto1.livoxProto1_device_enablePcloudDataReq = reinterpret_cast<
		livoxProto1_device_enablePcloudDataReqFn *>(
			dlsym(
				livoxProto1.dlopenHandle.get(),
				"livoxProto1_device_enablePcloudDataReq"));
	livoxProto1.livoxProto1_device_disablePcloudDataReq = reinterpret_cast<
		livoxProto1_device_disablePcloudDataReqFn *>(
			dlsym(
				livoxProto1.dlopenHandle.get(),
				"livoxProto1_device_disablePcloudDataReq"));
	livoxProto1.livoxProto1_device_getReturnModeReq = reinterpret_cast<
		livoxProto1_device_getReturnModeReqFn *>(
			dlsym(
				livoxProto1.dlopenHandle.get(),
				"livoxProto1_device_getReturnModeReq"));
	livoxProto1.livoxProto1_getPcloudDataFdDesc = reinterpret_cast<
		livoxProto1_getPcloudDataFdDescFn *>(
			dlsym(
				livoxProto1.dlopenHandle.get(),
				"livoxProto1_getPcloudDataFdDesc"));

	if (!livoxProto1.livoxProto1_main || !livoxProto1.livoxProto1_exit
		|| !livoxProto1.livoxProto1_getOrCreateDeviceReq
		|| !livoxProto1.livoxProto1_destroyDeviceReq
		|| !livoxProto1.livoxProto1_device_enablePcloudDataReq
		|| !livoxProto1.livoxProto1_device_disablePcloudDataReq
		|| !livoxProto1.livoxProto1_device_getReturnModeReq
		|| !livoxProto1.livoxProto1_getPcloudDataFdDesc)
	{
		throw std::runtime_error(
			std::string(__func__) +
			": Failed to get LivoxProto1 library functions");
	}

	// Call LivoxProto1 library main function
	(*livoxProto1.livoxProto1_main)(
		smoThreadingModelDesc.componentThread, *smoHooksPtr);

	return 0; // Success
}

extern "C" int livoxGen1_finalizeInd(void)
{

	attachedStimulusProducers.clear();

	// Call LivoxProto1 library exit function
	if (livoxProto1.livoxProto1_exit) {
		(*livoxProto1.livoxProto1_exit)();
	}

	livoxProto1.dlopenHandle.reset(nullptr);

	livoxProto1 = LivoxProto1DllState();
	return 0; // Success
}

extern "C" void livoxGen1_attachDeviceReq(
	const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
	const std::shared_ptr<sscl::ComponentThread>& componentThread,
	sscl::Callback<smo::stim_buff::sal_mlo_attachDeviceReqCbFn> cb
	)
{
	if (!livoxProto1.livoxProto1_getOrCreateDeviceReq)
	{
		throw std::runtime_error(
			std::string(__func__) + ": LivoxProto1 getOrCreateDevice function "
			"not available");
	}

	// Validate qualeIfaceApi
	const std::string& qualeIfaceApi = desc->qualeIfaceApi;
	if (qualeIfaceApi == "gyro" || qualeIfaceApi == "accel")
	{
		// These are for ImuStimulusProducer (not yet implemented)
		std::cerr << __func__ << ": qualeIfaceApi '" << qualeIfaceApi
			<< "' requires ImuStimulusProducer which is not yet implemented"
			<< std::endl;
		cb.callbackFn(false, desc);
		return;
	}

	if (!PcloudStimulusProducer::supportsQualeIfaceApi(qualeIfaceApi))
	{
		// Unknown qualeIfaceApi
		std::cerr << __func__ << ": Unsupported qualeIfaceApi '"
			<< qualeIfaceApi << "' for LivoxGen1. "
			"Supported values: mesh, pcloudIntensity, pcloudAmbience"
			<< std::endl;
		cb.callbackFn(false, desc);
		return;
	}

	auto request = std::make_shared<AttachDeviceReq>(desc, cb);

	// Case 1: Check if StimBuffer already exists
	auto stimProducer = std::dynamic_pointer_cast<PcloudStimulusProducer>(
		getStimulusProducer(desc));
	if (stimProducer)
	{
		auto existingBuffer = stimProducer->getAttachedStimulusBuffer(desc);
		if (existingBuffer)
		{
			// StimBuffer exists, check if pcloud data is active
			if (stimProducer->device && stimProducer->device->pcloudDataActive)
			{
				// Both StimBuffer and pcloud data are active, early return with success
				request->callOriginalCb(true, request->spec);
				return;
			}

			// StimBuffer exists but pcloud data is not active, enable it
			request->stimProducer = stimProducer;
			request->attachDeviceReq5_doEnablePcloudData_maybeDirectlyCalled(
				request);

			return;
		}
		else
		{
			/**	EXPLANATION:
			 * StimProducer exists, StimBuffer doesn't (DASpec doesn't match)
			 * Check if producer already has a buffer with the requested
			 * qualeIfaceApi but different DASpec - this is not allowed.
			 */
			if (stimProducer->hasBufferWithQualeIfaceApi(desc->qualeIfaceApi))
			{
				std::cerr << __func__ << ": Producer already has a buffer with "
					"qualeIfaceApi '" << desc->qualeIfaceApi
					<< "' but with a different DeviceAttachmentSpec. "
					"A single LivoxGen1 device cannot support multiple DASpecs "
					"with the same qualeIfaceApi." << std::endl;

				cb.callbackFn(false, desc);
				return;
			}

			request->stimProducer = stimProducer;
			// Ensure StimBuffer is attached and enable pcloud data if needed
			request->attachDeviceReq4_doCreateStimBuff_maybeDirectlyCalled(
				request);

			return;
		}
	}

	// StimProducer doesn't exist - need to create device first

	// Parse integer parameters from provider params with defaults
	/**	EXPLANATION:
	 * We may want to add a new param here called "command-delay-ms" to control
	 * the delay we insert between commands sent to the device. 5ms has been
	 * shown to be sufficient for the Livox Avia.
	 */

	/* The Livox Avia will generally respond to a handshake request within
	 * 5ms.
	 */
	int commandTimeoutMs = 5;		// Default: 5ms
	/* Based on testing on a Livox Avia, the device will generally resume
	 * sending broadcast advertisement dgrams after about 5 seconds at most.
	 * Generally, it will resume sending them within 1-2 seconds.
	 */
	int retryDelayMs = 5250;		// Default: 5250ms
	uint8_t smoSubnetNbits = 24;	// Default: /24 subnet
	uint16_t dataPort = 56000;		// Default data port
	uint16_t cmdPort = 56001;		// Default command port
	uint16_t imuPort = 56002;		// Default IMU port
	// Default: empty string (will trigger IP auto-detection)
	std::string smoIp = "";

	// Parse optional integer parameters from provider params
	for (const auto& param : desc->providerParams)
	{
		if (param.first == "cmd-timeout-ms")
		{
			commandTimeoutMs = smo::device::DeviceAttachmentSpec
				::parseRequiredParamAsInt(
					desc->providerParams, "cmd-timeout-ms");
		} else if (param.first == "command-timeout-ms")
		{
			commandTimeoutMs = smo::device::DeviceAttachmentSpec
				::parseRequiredParamAsInt(
					desc->providerParams, "command-timeout-ms");
		} else if (param.first == "retry-delay-ms")
		{
			retryDelayMs = smo::device::DeviceAttachmentSpec
				::parseRequiredParamAsInt(
					desc->providerParams, "retry-delay-ms");
		} else if (param.first == "smo-subnet-nbits")
		{
			smoSubnetNbits = static_cast<uint8_t>(
				smo::device::DeviceAttachmentSpec
				::parseRequiredParamAsInt(
					desc->providerParams, "smo-subnet-nbits"));
		} else if (param.first == "data-port")
		{
			dataPort = static_cast<uint16_t>(
				smo::device::DeviceAttachmentSpec
				::parseRequiredParamAsInt(desc->providerParams, "data-port"));
		} else if (param.first == "cmd-port")
		{
			cmdPort = static_cast<uint16_t>(
				smo::device::DeviceAttachmentSpec
				::parseRequiredParamAsInt(desc->providerParams, "cmd-port"));
		} else if (param.first == "imu-port")
		{
			imuPort = static_cast<uint16_t>(
				smo::device::DeviceAttachmentSpec
				::parseRequiredParamAsInt(desc->providerParams, "imu-port"));
		} else if (param.first == "smo-ip")
		{
			if (param.second.empty())
			{
				throw std::runtime_error(
					std::string(__func__) + ": smo-ip parameter is empty");
			}
			if (param.second.find('.') == std::string::npos ||
			    std::count(param.second.begin(), param.second.end(), '.') != 3)
			{
				throw std::runtime_error(
					std::string(__func__) + ": smo-ip parameter is not an "
					"IPv4 address");
			}
			smoIp = param.second;
		}
		else
		{
			throw std::runtime_error(
				std::string(__func__) + ": Unknown provider parameter: "
				+ param.first);
		}
	}

	(*livoxProto1.livoxProto1_getOrCreateDeviceReq)(
		desc->deviceSelector,  // deviceIdentifier (broadcast code)
		componentThread,
		commandTimeoutMs, retryDelayMs,
		smoIp, smoSubnetNbits,
		dataPort, cmdPort, imuPort,
		{request, std::bind(
			&AttachDeviceReq::attachDeviceReq1,
			request.get(), request,
			std::placeholders::_1, std::placeholders::_2)});
}

extern "C" void livoxGen1_detachDeviceReq(
	const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
	sscl::Callback<smo::stim_buff::sal_mlo_detachDeviceReqCbFn> cb
	)
{
	// Case 1: Check if StimBuffer doesn't exist (early return)
	auto stimProducerBase = getStimulusProducer(desc);
	if (!stimProducerBase)
	{
		// StimProducer doesn't exist, nothing to detach - success
		cb.callbackFn(true, desc);
		return;
	}

	auto stimProducer = std::dynamic_pointer_cast<PcloudStimulusProducer>(
		stimProducerBase);

	if (!stimProducer)
	{
		throw std::runtime_error(std::string(__func__) +
			": Failed to cast StimulusProducer to PcloudStimulusProducer "
			"for device " + desc->deviceSelector);
	}

	// Check if StimBuffer exists
	auto stimBuffer = stimProducer->getAttachedStimulusBuffer(desc);
	if (!stimBuffer)
	{
		// StimBuffer doesn't exist, nothing to detach - success
		cb.callbackFn(true, desc);
		return;
	}

	// Case 2: StimBuffer exists - proceed with detach
	auto request = std::make_shared<DetachDeviceReq>(
		desc, stimBuffer, cb);

	// Disable point cloud data first
	(*livoxProto1.livoxProto1_device_disablePcloudDataReq)(
		stimProducer->device,
		{request, std::bind(
			&DetachDeviceReq::detachDeviceReq1,
			request.get(), request,
			std::placeholders::_1)});
}

// Exported function
extern "C" smo::stim_buff::SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF
	SMO_GET_STIM_BUFF_API_DESC_FN_NAME;

const smo::stim_buff::StimBuffApiDesc& SMO_GET_STIM_BUFF_API_DESC_FN_NAME(
	const smo::stim_buff::SmoCallbacks& callbacks,
	const smo::stim_buff::SmoThreadingModelDesc& threadingModel)
{
	smoHooksPtr = &callbacks;
	smoThreadingModelDesc = threadingModel;

	return livoxGen1ApiDesc;
}

} // namespace stim_buff
} // namespace smo
