#include "ANSMotionDetector.h"
namespace ANSCENTER {

	ANSMOTIONDETECTOR::~ANSMOTIONDETECTOR()
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		for (auto& [key, cameraData] : cameras)
		{
			cameraData.clear();
		}
		cameras.clear();
	}

	bool ANSMOTIONDETECTOR::hasCameraData(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		return cameras.find(camera_id) != cameras.end();
	}

	void ANSMOTIONDETECTOR::removeCamera(const std::string& camera_id)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.clear();
			cameras.erase(it);
		}
	}

	std::vector<std::string> ANSMOTIONDETECTOR::getCameraIds() const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		std::vector<std::string> ids;
		ids.reserve(cameras.size());
		for (const auto& [id, _] : cameras)
		{
			ids.push_back(id);
		}
		return ids;
	}

	bool ANSMOTIONDETECTOR::empty(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		return it == cameras.end() || it->second.control_frames.empty();
	}

	void ANSMOTIONDETECTOR::clear(const std::string& camera_id)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.clear();
		}
	}

	void ANSMOTIONDETECTOR::clearAll()
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		for (auto& [key, cameraData] : cameras)
		{
			cameraData.clear();
		}
		cameras.clear();
	}

	void ANSMOTIONDETECTOR::setThreshold(const std::string& camera_id, double threshold)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.psnr_threshold = std::clamp(threshold, 0.0, 100.0);
		}
	}

	void ANSMOTIONDETECTOR::setKeyFrameFrequency(const std::string& camera_id, size_t frequency)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.key_frame_frequency = std::max(size_t(1), frequency);
		}
	}

	void ANSMOTIONDETECTOR::setNumberOfControlFrames(const std::string& camera_id, size_t count)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.number_of_control_frames = std::clamp(count, size_t(1), size_t(100));
		}
	}

	void ANSMOTIONDETECTOR::setThumbnailRatio(const std::string& camera_id, double ratio)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.thumbnail_ratio = std::clamp(ratio, 0.01, 1.0);
			it->second.thumbnail_size = cv::Size(0, 0); // Reset to recalculate
		}
	}

	void ANSMOTIONDETECTOR::setMinObjectArea(const std::string& camera_id, double area)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.min_object_area = std::max(0.0, area);
		}
	}

	void ANSMOTIONDETECTOR::setMinObjectSize(const std::string& camera_id, int size)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.min_object_dimension = std::max(0, size);
		}
	}

	void ANSMOTIONDETECTOR::setMorphologyIterations(const std::string& camera_id, int iterations)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.morphology_iterations = std::clamp(iterations, 1, 50);
		}
	}

	double ANSMOTIONDETECTOR::getThreshold(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.psnr_threshold;
		}
		return 0.0;
	}

	size_t ANSMOTIONDETECTOR::getKeyFrameFrequency(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.key_frame_frequency;
		}
		return 0;
	}

	size_t ANSMOTIONDETECTOR::getNumberOfControlFrames(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.number_of_control_frames;
		}
		return 0;
	}

	double ANSMOTIONDETECTOR::getThumbnailRatio(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.thumbnail_ratio;
		}
		return 0.0;
	}

	bool ANSMOTIONDETECTOR::isMovementDetected(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		return it != cameras.end() && it->second.movement_detected;
	}

	bool ANSMOTIONDETECTOR::wasTransitionDetected(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		return it != cameras.end() && it->second.transition_detected;
	}

	double ANSMOTIONDETECTOR::getPSNRScore(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.most_recent_psnr_score;
		}
		return 0.0;
	}

	size_t ANSMOTIONDETECTOR::getFrameIndexWithMovement(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.frame_index_with_movement;
		}
		return 0;
	}

	std::chrono::milliseconds ANSMOTIONDETECTOR::getTimeSinceLastMovement(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);

		if (it == cameras.end() || it->second.frame_index_with_movement == 0)
		{
			return std::chrono::milliseconds::max();
		}

		auto now = std::chrono::high_resolution_clock::now();
		return std::chrono::duration_cast<std::chrono::milliseconds>(
			now - it->second.movement_last_detected
		);
	}

	size_t ANSMOTIONDETECTOR::getControlFrameCount(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.control_frames.size();
		}
		return 0;
	}

	size_t ANSMOTIONDETECTOR::getNextFrameIndex(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.next_frame_index;
		}
		return 0;
	}

	//cv::Mat ANSMOTIONDETECTOR::getOutput(const std::string& camera_id) const
	//{
	//	std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
	//	auto it = cameras.find(camera_id);
	//	if (it != cameras.end())
	//	{
	//		return it->second.output.clone();
	//	}
	//	return cv::Mat();
	//}
	const cv::Mat& ANSMOTIONDETECTOR::getOutput(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.output;  // Return reference - zero copy!
		}

		static const cv::Mat empty_mat;  // Static empty Mat to return reference to
		return empty_mat;
	}

	const cv::Mat ANSMOTIONDETECTOR::getMask(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.mask;
		}
		return cv::Mat();
	}

	std::vector<std::vector<cv::Point>> ANSMOTIONDETECTOR::getContours(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.contours;
		}
		return std::vector<std::vector<cv::Point>>();
	}

	ANSMOTIONDETECTOR::CameraStats ANSMOTIONDETECTOR::getStats(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.stats;
		}
		return CameraStats();
	}

	void ANSMOTIONDETECTOR::resetStats(const std::string& camera_id)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.stats.reset();
		}
	}

	bool ANSMOTIONDETECTOR::cameraExists(const std::string& camera_id) const
	{
		// Note: cameras_mutex should already be locked by caller
		return cameras.find(camera_id) != cameras.end();
	}

	ANSMOTIONDETECTOR::CameraData& ANSMOTIONDETECTOR::getCameraData(const std::string& camera_id)
	{
		// Note: cameras_mutex should already be locked by caller
		return cameras.try_emplace(camera_id, CameraData{}).first->second;
	}

	const ANSMOTIONDETECTOR::CameraData* ANSMOTIONDETECTOR::getCameraDataConst(const std::string& camera_id) const
	{
		auto it = cameras.find(camera_id);
		if (it == cameras.end())
		{
			return nullptr;
		}
		return &it->second;
	}

	double ANSMOTIONDETECTOR::psnr(const cv::Mat& src, const cv::Mat& dst)
	{
		if (src.empty() || dst.empty())
		{
			std::cerr << "PSNR: Empty image(s)" << std::endl;
			return 0.0;
		}

		if (src.type() != dst.type())
		{
			std::cerr << "PSNR: Type mismatch - src: " << src.type()
				<< " dst: " << dst.type() << std::endl;
			return 0.0;
		}

		if (src.size() != dst.size())
		{
			std::cerr << "PSNR: Size mismatch - src: " << src.size()
				<< " dst: " << dst.size() << std::endl;
			return 0.0;
		}

		cv::Mat s1;
		cv::absdiff(src, dst, s1);
		s1.convertTo(s1, CV_32F);
		s1 = s1.mul(s1);

		cv::Scalar s = cv::sum(s1);
		double sse = s.val[0] + s.val[1] + s.val[2];

		if (sse <= 1e-10)
		{
			// Identical images - return very high PSNR
			return 100.0; // Use a high but finite value instead of infinity
		}

		const double mse = sse / static_cast<double>(src.channels() * src.total());
		const double psnr_value = 10.0 * std::log10((255.0 * 255.0) / mse);

		// Sanity check
		if (std::isnan(psnr_value) || std::isinf(psnr_value))
		{
			std::cerr << "PSNR: Invalid result (NaN or Inf), MSE=" << mse << std::endl;
			return 0.0;
		}

		return psnr_value;
	}

	/*cv::Mat ANSMOTIONDETECTOR::simple_colour_balance(const cv::Mat& src)
	{
		if (src.empty() || src.channels() != 3)
		{
			return src.clone();
		}

		const float full_percent = 1.0f;
		const float half_percent = full_percent / 200.0f;

		std::vector<cv::Mat> tmpsplit;
		cv::split(src, tmpsplit);

		for (size_t idx = 0; idx < 3; idx++)
		{
			cv::Mat flat;
			tmpsplit[idx].reshape(1, 1).copyTo(flat);
			cv::sort(flat, flat, cv::SORT_EVERY_ROW + cv::SORT_ASCENDING);

			const int lo = flat.at<uchar>(cvFloor(static_cast<float>(flat.cols) * half_percent));
			const int hi = flat.at<uchar>(cvCeil(static_cast<float>(flat.cols) * (1.0f - half_percent)));

			tmpsplit[idx].setTo(lo, tmpsplit[idx] < lo);
			tmpsplit[idx].setTo(hi, tmpsplit[idx] > hi);

			cv::normalize(tmpsplit[idx], tmpsplit[idx], 0, 255, cv::NORM_MINMAX);
		}

		cv::Mat output;
		cv::merge(tmpsplit, output);

		return output;
	}*/
	cv::Mat ANSMOTIONDETECTOR::simple_colour_balance(const cv::Mat& src)
	{
		if (src.empty() || src.channels() != 3)
		{
			return cv::Mat();  // Return empty instead of cloning invalid input
		}

		const float full_percent = 1.0f;
		const float half_percent = full_percent / 200.0f;

		std::vector<cv::Mat> channels;
		cv::split(src, channels);

		cv::Mat output;
		output.create(src.size(), src.type());

		for (int idx = 0; idx < 3; idx++)
		{
			// Use histogram instead of sorting - much faster!
			int histSize = 256;
			float range[] = { 0, 256 };
			const float* histRange = { range };

			cv::Mat hist;
			cv::calcHist(&channels[idx], 1, 0, cv::Mat(), hist, 1, &histSize, &histRange);

			// Calculate cumulative histogram
			for (int i = 1; i < 256; i++)
			{
				hist.at<float>(i) += hist.at<float>(i - 1);
			}

			// Find low and high percentile values
			const float total_pixels = hist.at<float>(255);
			const float low_threshold = total_pixels * half_percent;
			const float high_threshold = total_pixels * (1.0f - half_percent);

			int lo = 0, hi = 255;
			for (int i = 0; i < 256; i++)
			{
				if (hist.at<float>(i) >= low_threshold)
				{
					lo = i;
					break;
				}
			}

			for (int i = 255; i >= 0; i--)
			{
				if (hist.at<float>(i) <= high_threshold)
				{
					hi = i;
					break;
				}
			}

			// Apply stretch without creating temp Mats
			if (hi > lo)
			{
				const float scale = 255.0f / (hi - lo);
				channels[idx].convertTo(channels[idx], CV_32F);
				channels[idx] = (channels[idx] - lo) * scale;
				channels[idx].setTo(0, channels[idx] < 0);
				channels[idx].setTo(255, channels[idx] > 255);
				channels[idx].convertTo(channels[idx], CV_8U);
			}
		}

		cv::merge(channels, output);
		return output;
	}
	cv::Rect ANSMOTIONDETECTOR::BoundingBoxFromContour(const std::vector<cv::Point>& contour)
	{
		if (contour.empty())
		{
			return cv::Rect();
		}

		if (cv::contourArea(contour) < 1000)
		{
			return cv::Rect();
		}

		return cv::boundingRect(contour);
	}
	void ANSMOTIONDETECTOR::setDynamicPSNRThreshold(CameraData& camera, const cv::Mat& working_image)
	{
		int min_dimension = std::min(working_image.cols, working_image.rows);

		double base_threshold;

		if (min_dimension >= 1080)
		{
			base_threshold = 45.0;
			camera.max_control_frame_age = 30;
			camera.thumbnail_ratio = 0.1;
		}
		else if (min_dimension >= 720)
		{
			base_threshold = 40.0;
			camera.max_control_frame_age = 25;
			camera.thumbnail_ratio = 0.1;
		}
		else if (min_dimension >= 480)
		{
			base_threshold = 35.0;
			camera.max_control_frame_age = 20;
			camera.thumbnail_ratio = 0.15;
		}
		else
		{
			base_threshold = 30.0;
			camera.max_control_frame_age = 15;
			camera.thumbnail_ratio = 0.2;
		}

		// Apply sensitivity multiplier
		if (_modelConfig.detectionScoreThreshold == 0.0) {
			camera.psnr_threshold = base_threshold * camera.sensitivity_multiplier;
		}
		else {
			camera.sensitivity_multiplier = (1.5 - _modelConfig.detectionScoreThreshold);
			if (camera.sensitivity_multiplier < 0)camera.sensitivity_multiplier = 0.0;
			else if (camera.sensitivity_multiplier > 2.0)camera.sensitivity_multiplier = 2.0;

			camera.psnr_threshold = base_threshold * camera.sensitivity_multiplier;
		}

		std::cout << "Base threshold: " << base_threshold
			<< ", Sensitivity: " << camera.sensitivity_multiplier
			<< ", Final threshold: " << camera.psnr_threshold << std::endl;

	}
	cv::Mat ANSMOTIONDETECTOR::computeMovementMask(const cv::Mat& control_frame,
		const cv::Mat& current_frame,
		const cv::Size& output_size,
		int morphology_iterations,
		const CameraData& camera)
	{
		// Convert to grayscale
		cv::Mat control_gray, current_gray;
		cv::cvtColor(control_frame, control_gray, cv::COLOR_BGR2GRAY);
		cv::cvtColor(current_frame, current_gray, cv::COLOR_BGR2GRAY);

		// Apply Gaussian blur to reduce noise sensitivity
		cv::GaussianBlur(control_gray, control_gray, cv::Size(5, 5), 0);
		cv::GaussianBlur(current_gray, current_gray, cv::Size(5, 5), 0);

		// Compute absolute difference
		cv::Mat diff;
		cv::absdiff(control_gray, current_gray, diff);

		cv::Mat mask;
		cv::threshold(diff, mask, camera.mask_diff_threshold, 255, cv::THRESH_BINARY);

		double frame_percentage = (cv::countNonZero(mask) * 100.0) / mask.total();
		std::cout << "Fixed threshold " << camera.mask_diff_threshold
			<< ": " << std::fixed << std::setprecision(2)
			<< frame_percentage << "% of frame" << std::endl;

		// Reject if too much of frame is flagged
		if (frame_percentage > camera.mask_max_percentage)
		{
			std::cout << "Mask too large (" << frame_percentage
				<< "% > " << camera.mask_max_percentage << "%), rejecting" << std::endl;
			mask = cv::Mat::zeros(mask.size(), CV_8UC1);
			std::cout << "Final mask: 0.00% of frame" << std::endl;
			return mask;
		}

		// CHANGED: Don't reject small masks here - let morphology clean them up first
		// Small movements on thumbnails will appear tiny

		// ADAPTIVE MORPHOLOGY: Only apply if mask is large enough to survive it
		if (morphology_iterations > 0 && frame_percentage >= 0.5)
		{
			cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));

			// MORPH_OPEN: Remove small noise
			cv::morphologyEx(mask, mask, cv::MORPH_OPEN, kernel,
				cv::Point(-1, -1), morphology_iterations);

			// MORPH_CLOSE: Fill small holes
			cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel,
				cv::Point(-1, -1), morphology_iterations / 2);

			std::cout << "Applied morphology (iterations: " << morphology_iterations << ")" << std::endl;
		}
		else if (frame_percentage < 0.5)
		{
			std::cout << "Skipping morphology (mask too small: " << frame_percentage << "%)" << std::endl;
		}

		// Check final mask percentage AFTER morphology
		double final_percentage = (cv::countNonZero(mask) * 100.0) / mask.total();
		std::cout << "Final mask: " << std::fixed << std::setprecision(2)
			<< final_percentage << "% of frame" << std::endl;

		// Reject if final mask is too small (but use lower threshold)
		if (final_percentage < camera.mask_min_percentage)
		{
			std::cout << "Final mask too small (" << final_percentage
				<< "% < " << camera.mask_min_percentage << "%), rejecting" << std::endl;
			mask = cv::Mat::zeros(mask.size(), CV_8UC1);
			std::cout << "Final mask: 0.00% of frame" << std::endl;
			return mask;
		}

		// Resize to output size
		cv::resize(mask, mask, output_size, 0, 0, cv::INTER_NEAREST);

		return mask;
	}
	//std::vector<Object> ANSMOTIONDETECTOR::extractObjectsFromMask(const cv::Mat& mask,
	//	const cv::Mat& image,
	//	CameraData& camera,
	//	const std::string& camera_id)
	//{
	//	std::vector<Object> outputObjects;

	//	if (mask.empty())
	//	{
	//		return outputObjects;
	//	}

	//	std::vector<cv::Vec4i> hierarchy;
	//	cv::findContours(mask, camera.contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

	//	for (const auto& contour : camera.contours)
	//	{
	//		double area = cv::contourArea(contour);
	//		if (area < camera.min_object_area)
	//		{
	//			continue;
	//		}

	//		cv::Rect boundingRect = cv::boundingRect(contour);

	//		// Clamp to image bounds
	//		boundingRect &= cv::Rect(0, 0, image.cols, image.rows);

	//		if (boundingRect.width < camera.min_object_dimension ||
	//			boundingRect.height < camera.min_object_dimension ||
	//			boundingRect.area() < camera.min_object_total_size)
	//		{
	//			continue;
	//		}

	//		// Draw visualizations
	//		//if (camera.contours_enabled)
	//		//{
	//		//	cv::polylines(camera.output, contour, true, cv::Scalar(0, 0, 255),
	//		//		camera.contours_size, camera.line_type);
	//		//}

	//		//if (camera.bbox_enabled)
	//		//{
	//		//	cv::rectangle(camera.output, boundingRect, cv::Scalar(0, 255, 0),
	//		//		camera.bbox_size, camera.line_type);
	//		//}

	//		Object obj;
	//		obj.classId = 0;
	//		obj.trackId = 0;
	//		obj.className = "movement";
	//		obj.confidence = 1.0f;
	//		obj.box = boundingRect;
	//		obj.mask = image(boundingRect).clone();
	//		obj.cameraId = camera_id;

	//		// Create polygon from contour (simplified to bounding box corners)
	//		obj.polygon = {
	//			cv::Point2f(static_cast<float>(boundingRect.x), static_cast<float>(boundingRect.y)),
	//			cv::Point2f(static_cast<float>(boundingRect.x + boundingRect.width), static_cast<float>(boundingRect.y)),
	//			cv::Point2f(static_cast<float>(boundingRect.x + boundingRect.width), static_cast<float>(boundingRect.y + boundingRect.height)),
	//			cv::Point2f(static_cast<float>(boundingRect.x), static_cast<float>(boundingRect.y + boundingRect.height))
	//		};
	//		if ((boundingRect.width >= 25) &&
	//			(boundingRect.height >= 25))outputObjects.push_back(obj);
	//	}

	//	return outputObjects;
	//}
	std::vector<Object> ANSMOTIONDETECTOR::extractObjectsFromMask(
		const cv::Mat& mask,
		const cv::Mat& image,
		CameraData& camera,
		const std::string& camera_id)
	{
		std::vector<Object> outputObjects;

		if (mask.empty())
		{
			return outputObjects;
		}

		// Use local contours to avoid overwriting camera.contours
		std::vector<std::vector<cv::Point>> contours;
		std::vector<cv::Vec4i> hierarchy;
		cv::findContours(mask, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

		// Pre-allocate based on expected objects (prevents reallocations)
		outputObjects.reserve(contours.size());

		// Pre-calculate image bounds rectangle
		const cv::Rect imageBounds(0, 0, image.cols, image.rows);

		// Consolidated minimum size (use configuration or hardcoded value)
		const int min_width = std::max(camera.min_object_dimension, 25);
		const int min_height = std::max(camera.min_object_dimension, 25);
		const int min_area = camera.min_object_area;

		for (const auto& contour : contours)
		{
			// Early exit: check area first (cheapest check)
			const double area = cv::contourArea(contour);
			if (area < min_area)
			{
				continue;
			}

			// Calculate bounding rect
			cv::Rect boundingRect = cv::boundingRect(contour);

			// Clamp to image bounds
			boundingRect &= imageBounds;

			// Consolidated dimension checks
			if (boundingRect.width < min_width ||
				boundingRect.height < min_height ||
				boundingRect.area() < camera.min_object_total_size)
			{
				continue;
			}

			// Construct object in-place using emplace_back
			outputObjects.emplace_back();
			Object& obj = outputObjects.back();

			obj.classId = 0;
			obj.trackId = 0;
			obj.className = "movement";
			obj.confidence = 1.0f;
			obj.box = boundingRect;
			obj.cameraId = camera_id;

			// CRITICAL FIX: Use ROI reference instead of clone
			// Only clone if caller will modify the mask
			obj.mask = image(boundingRect);  // Shallow copy - shares data!

			// Optional: Create polygon only if needed
			// If you need actual contour, use: obj.polygon = contour;
			// Otherwise, skip this allocation entirely
			obj.polygon.reserve(4);
			obj.polygon = {
				cv::Point2f(static_cast<float>(boundingRect.x),
						   static_cast<float>(boundingRect.y)),
				cv::Point2f(static_cast<float>(boundingRect.x + boundingRect.width),
						   static_cast<float>(boundingRect.y)),
				cv::Point2f(static_cast<float>(boundingRect.x + boundingRect.width),
						   static_cast<float>(boundingRect.y + boundingRect.height)),
				cv::Point2f(static_cast<float>(boundingRect.x),
						   static_cast<float>(boundingRect.y + boundingRect.height))
			};
		}

		return outputObjects;
	}
	//void ANSMOTIONDETECTOR::updateControlFrames(CameraData& camera, size_t frame_index,
	//	const cv::Mat& thumbnail)
	//{
	//	if (thumbnail.empty())
	//	{
	//		std::cerr << "updateControlFrames: Empty thumbnail!" << std::endl;
	//		return;
	//	}

	//	if (frame_index >= camera.next_key_frame ||
	//		camera.control_frames.size() < camera.number_of_control_frames)
	//	{
	//		// Store a deep copy
	//		cv::Mat thumbnail_copy = thumbnail.clone();

	//		// Ensure continuous memory
	//		if (!thumbnail_copy.isContinuous())
	//		{
	//			thumbnail_copy = thumbnail_copy.clone();
	//		}

	//		camera.control_frames[frame_index] = thumbnail_copy;

	//		// Remove oldest frames if exceeding limit
	//		while (camera.control_frames.size() > camera.number_of_control_frames)
	//		{
	//			auto oldest = camera.control_frames.begin();
	//			oldest->second.release(); // Explicitly release memory
	//			camera.control_frames.erase(oldest);
	//		}

	//		camera.next_key_frame = frame_index + camera.key_frame_frequency;
	//	}
	//}
	void ANSMOTIONDETECTOR::updateControlFrames(CameraData& camera,
		size_t frame_index,
		const cv::Mat& thumbnail)
	{
		if (thumbnail.empty())
		{
			// Use logger instead of cerr for consistency
			// std::cerr << "updateControlFrames: Empty thumbnail!" << std::endl;
			return;
		}

		// Only update at key frame intervals or when buffer not full
		if (frame_index < camera.next_key_frame &&
			camera.control_frames.size() >= camera.number_of_control_frames)
		{
			return;  // Early exit - nothing to do
		}

		// Remove oldest frames BEFORE insertion to maintain exact size limit
		while (camera.control_frames.size() >= camera.number_of_control_frames)
		{
			auto oldest = camera.control_frames.begin();
			camera.control_frames.erase(oldest);  // .release() unnecessary
		}

		// Clone once, ensuring continuous memory in single operation
		cv::Mat thumbnail_copy;
		if (thumbnail.isContinuous())
		{
			thumbnail_copy = thumbnail.clone();
		}
		else
		{
			// Force continuous memory during clone
			thumbnail_copy = thumbnail.clone();
			if (!thumbnail_copy.isContinuous())
			{
				thumbnail_copy = thumbnail_copy.clone();  // Rare case
			}
		}

		// Use emplace for efficient insertion
		camera.control_frames.emplace(frame_index, std::move(thumbnail_copy));

		// Update next key frame
		camera.next_key_frame = frame_index + camera.key_frame_frequency;
	}

	void ANSMOTIONDETECTOR::updateStatistics(CameraData& camera, double psnr_value,
		bool movement_detected,
		std::chrono::milliseconds processing_time)
	{
		camera.stats.total_frames_processed++;

		if (movement_detected)
		{
			camera.stats.frames_with_movement++;
			camera.stats.last_movement_time = std::chrono::high_resolution_clock::now();
		}

		camera.stats.control_frames_count = camera.control_frames.size();

		// Update PSNR statistics (skip infinity values from identical frames)
		if (!std::isinf(psnr_value))
		{
			camera.stats.min_psnr = std::min(camera.stats.min_psnr, psnr_value);
			camera.stats.max_psnr = std::max(camera.stats.max_psnr, psnr_value);

			// Running average
			double n = static_cast<double>(camera.stats.total_frames_processed);
			camera.stats.average_psnr =
				(camera.stats.average_psnr * (n - 1.0) + psnr_value) / n;
		}

		camera.stats.total_processing_time += processing_time;
	}

	// ============================================================================
	// Main Detection Methods
	// ============================================================================

	std::vector<Object> ANSMOTIONDETECTOR::MovementDetect(const std::string& camera_id, const cv::Mat& next_image)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		CameraData& camera = getCameraData(camera_id);
		return MovementDetect(camera_id, camera.next_frame_index, next_image);
	}

	//std::vector<Object> ANSMOTIONDETECTOR::MovementDetect(const std::string& camera_id, const size_t frame_index, const cv::Mat& image)
	//{
	//	std::lock_guard<std::recursive_mutex> lock(cameras_mutex);

	//	if (image.empty())
	//	{
	//		return std::vector<Object>();
	//	}

	//	// CRITICAL: Ensure consistent format
	//	cv::Mat processed_image;

	//	// Convert to BGR if needed
	//	if (image.channels() == 1)
	//	{
	//		cv::cvtColor(image, processed_image, cv::COLOR_GRAY2BGR);
	//	}
	//	else if (image.channels() == 4)
	//	{
	//		cv::cvtColor(image, processed_image, cv::COLOR_BGRA2BGR);
	//	}
	//	else if (image.channels() == 3)
	//	{
	//		// Already BGR, but ensure it's a continuous clone
	//		processed_image = image.clone();
	//	}
	//	else
	//	{
	//		// Unsupported format
	//		return std::vector<Object>();
	//	}

	//	// Ensure continuous memory layout
	//	if (!processed_image.isContinuous())
	//	{
	//		processed_image = processed_image.clone();
	//	}

	//	CameraData& camera = getCameraData(camera_id);
	//	return MovementDetectInternal(camera_id, frame_index, processed_image, camera);
	//}
	std::vector<Object> ANSMOTIONDETECTOR::MovementDetect(const std::string& camera_id,
		const size_t frame_index,
		const cv::Mat& image)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);

		if (image.empty())
		{
			return {};
		}

		cv::Mat processed_image;

		// Convert to BGR if needed
		if (image.channels() == 1)
		{
			cv::cvtColor(image, processed_image, cv::COLOR_GRAY2BGR);
		}
		else if (image.channels() == 4)
		{
			cv::cvtColor(image, processed_image, cv::COLOR_BGRA2BGR);
		}
		else if (image.channels() == 3)
		{
			// Use shallow copy for continuous BGR, clone only if needed
			processed_image = image.isContinuous() ? image : image.clone();
		}
		else
		{
			return {};
		}

		CameraData& camera = getCameraData(camera_id);
		return MovementDetectInternal(camera_id, frame_index, processed_image, camera);
	}

	void ANSMOTIONDETECTOR::setTemporalConsistency(const std::string& camera_id, bool enabled)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.temporal_consistency_enabled = enabled;
		}
	}

	void ANSMOTIONDETECTOR::setMaskOverlapThreshold(const std::string& camera_id, double threshold)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.mask_overlap_threshold = std::clamp(threshold, 0.0, 1.0);
		}
	}

	void ANSMOTIONDETECTOR::setTemporalHistorySize(const std::string& camera_id, size_t size)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.temporal_history_size = std::clamp(size, size_t(1), size_t(20));
		}
	}

	void ANSMOTIONDETECTOR::setMinConsistentFrames(const std::string& camera_id, size_t frames)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.min_consistent_frames = std::max(size_t(1), frames);
		}
	}

	void ANSMOTIONDETECTOR::setLocationStabilityEnabled(const std::string& camera_id, bool enabled)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.location_stability_enabled = enabled;
		}
	}

	void ANSMOTIONDETECTOR::setMaxLocationJitter(const std::string& camera_id, double pixels)
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			it->second.max_location_jitter = std::max(0.0, pixels);
		}
	}

	bool ANSMOTIONDETECTOR::isTemporalConsistencyEnabled(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.temporal_consistency_enabled;
		}
		return false;
	}

	double ANSMOTIONDETECTOR::getTemporalConsistencyScore(const std::string& camera_id) const
	{
		std::lock_guard<std::recursive_mutex> lock(cameras_mutex);
		auto it = cameras.find(camera_id);
		if (it != cameras.end())
		{
			return it->second.last_temporal_consistency_score;
		}
		return 0.0;
	}

	// ============================================================================
	// Temporal Consistency Helper Functions
	// ============================================================================

	double ANSMOTIONDETECTOR::calculateMaskOverlap(const cv::Mat& mask1, const cv::Mat& mask2)
	{
		if (mask1.empty() || mask2.empty())
		{
			return 0.0;
		}

		if (mask1.size() != mask2.size())
		{
			return 0.0;
		}

		// Calculate intersection
		cv::Mat intersection;
		cv::bitwise_and(mask1, mask2, intersection);
		int intersection_pixels = cv::countNonZero(intersection);

		// Calculate union
		cv::Mat union_mask;
		cv::bitwise_or(mask1, mask2, union_mask);
		int union_pixels = cv::countNonZero(union_mask);

		if (union_pixels == 0)
		{
			return 0.0;
		}

		// IoU (Intersection over Union)
		double overlap = static_cast<double>(intersection_pixels) / static_cast<double>(union_pixels);

		return overlap;
	}

	cv::Point ANSMOTIONDETECTOR::calculateMaskCentroid(const cv::Mat& mask)
	{
		if (mask.empty())
		{
			return cv::Point(-1, -1);
		}

		cv::Moments m = cv::moments(mask, true);

		if (m.m00 == 0)
		{
			return cv::Point(-1, -1);
		}

		cv::Point centroid;
		centroid.x = static_cast<int>(m.m10 / m.m00);
		centroid.y = static_cast<int>(m.m01 / m.m00);

		return centroid;
	}

	double ANSMOTIONDETECTOR::calculateLocationStability(const std::deque<cv::Point>& centroids)
	{
		if (centroids.size() < 2)
		{
			return 0.0;
		}

		// Calculate total movement distance
		double total_distance = 0.0;

		for (size_t i = 1; i < centroids.size(); i++)
		{
			if (centroids[i].x < 0 || centroids[i - 1].x < 0)
			{
				continue; // Skip invalid centroids
			}

			double dx = centroids[i].x - centroids[i - 1].x;
			double dy = centroids[i].y - centroids[i - 1].y;
			double distance = std::sqrt(dx * dx + dy * dy);

			total_distance += distance;
		}

		// Average movement per frame
		double avg_movement = total_distance / (centroids.size() - 1);

		return avg_movement;
	}

	//void ANSMOTIONDETECTOR::updateTemporalHistory(CameraData& camera, const cv::Mat& mask)
	//{
	//	if (mask.empty())
	//	{
	//		return;
	//	}

	//	// Add mask to history
	//	camera.mask_history.push_back(mask.clone());

	//	// Maintain history size limit
	//	while (camera.mask_history.size() > camera.temporal_history_size)
	//	{
	//		camera.mask_history.front().release();
	//		camera.mask_history.pop_front();
	//	}

	//	// Calculate and store centroid
	//	cv::Point centroid = calculateMaskCentroid(mask);
	//	camera.centroid_history.push_back(centroid);

	//	// Maintain centroid history size
	//	while (camera.centroid_history.size() > camera.temporal_history_size)
	//	{
	//		camera.centroid_history.pop_front();
	//	}
	//}
	void ANSMOTIONDETECTOR::updateTemporalHistory(CameraData& camera, const cv::Mat& mask)
	{
		if (mask.empty())
		{
			return;
		}

		// Enforce size limit BEFORE insertion
		if (camera.mask_history.size() >= camera.temporal_history_size)
		{
			camera.mask_history.pop_front();  // Remove oldest
			camera.centroid_history.pop_front();
		}

		// Add new mask (single clone)
		camera.mask_history.push_back(mask.clone());

		// Calculate and add centroid
		camera.centroid_history.push_back(calculateMaskCentroid(mask));
	}

	bool ANSMOTIONDETECTOR::checkTemporalConsistency(CameraData& camera, const cv::Mat& current_mask)
	{
		if (!camera.temporal_consistency_enabled)
		{
			camera.last_temporal_consistency_score = 1.0;
			return true; // Always pass if disabled
		}

		if (current_mask.empty())
		{
			camera.last_temporal_consistency_score = 0.0;
			return false;
		}

		// If no history yet, accept the first detection
		if (camera.mask_history.empty())
		{
			camera.last_temporal_consistency_score = 1.0;
			return true;
		}

		// Calculate overlap with recent masks
		double total_overlap = 0.0;
		int valid_comparisons = 0;

		for (const auto& historical_mask : camera.mask_history)
		{
			if (!historical_mask.empty())
			{
				double overlap = calculateMaskOverlap(current_mask, historical_mask);
				total_overlap += overlap;
				valid_comparisons++;
			}
		}

		double avg_overlap = (valid_comparisons > 0) ? (total_overlap / valid_comparisons) : 0.0;
		camera.last_temporal_consistency_score = avg_overlap;

		std::cout << "Temporal consistency check:" << std::endl;
		std::cout << "  Average overlap with history: " << (avg_overlap * 100) << "%" << std::endl;
		std::cout << "  Required threshold: " << (camera.mask_overlap_threshold * 100) << "%" << std::endl;

		// Check mask overlap criterion
		bool overlap_passed = avg_overlap >= camera.mask_overlap_threshold;

		// Check location stability if enabled
		bool location_stable = true;
		if (camera.location_stability_enabled && camera.centroid_history.size() >= 2)
		{
			cv::Point current_centroid = calculateMaskCentroid(current_mask);

			if (current_centroid.x >= 0)
			{
				// Check jitter with most recent centroid
				const cv::Point& last_centroid = camera.centroid_history.back();

				if (last_centroid.x >= 0)
				{
					double dx = current_centroid.x - last_centroid.x;
					double dy = current_centroid.y - last_centroid.y;
					double distance = std::sqrt(dx * dx + dy * dy);

					location_stable = distance <= camera.max_location_jitter;

					std::cout << "  Location jitter: " << distance << " pixels (max: "
						<< camera.max_location_jitter << ")" << std::endl;
					std::cout << "  Location stable: " << (location_stable ? "YES" : "NO") << std::endl;
				}
			}
		}

		// Both criteria must pass
		bool passed = overlap_passed && location_stable;

		// Update consistent frame counter
		if (passed)
		{
			camera.consistent_frame_count++;
			std::cout << "  Consistent frames: " << camera.consistent_frame_count
				<< " / " << camera.min_consistent_frames << std::endl;
		}
		else
		{
			camera.consistent_frame_count = 0;
			std::cout << "  Consistency check FAILED - resetting counter" << std::endl;
		}

		// Movement is only confirmed after min consecutive consistent frames
		bool confirmed = camera.consistent_frame_count >= camera.min_consistent_frames;

		std::cout << "  Movement confirmed: " << (confirmed ? "YES" : "NO") << std::endl;

		return confirmed;
	}

	//std::vector<Object> ANSMOTIONDETECTOR::MovementDetectInternal(
	//	const std::string& camera_id,
	//	const size_t frame_index,
	//	const cv::Mat& image,
	//	CameraData& camera)
	//{
	//	auto start_time = std::chrono::high_resolution_clock::now();

	//	std::vector<Object> outputObjects;

	//	// DEBUG: Log input
	//	std::cout << "\n=== MovementDetect Debug ===" << std::endl;
	//	std::cout << "Camera ID: " << camera_id << std::endl;
	//	std::cout << "Frame Index: " << frame_index << std::endl;
	//	std::cout << "Original image size: " << image.cols << "x" << image.rows << std::endl;
	//	std::cout << "Image type: " << image.type() << " (CV_8UC3=" << CV_8UC3 << ")" << std::endl;
	//	std::cout << "Image channels: " << image.channels() << std::endl;
	//	std::cout << "Image empty: " << (image.empty() ? "YES" : "NO") << std::endl;

	//	if (image.empty())
	//	{
	//		std::cout << "ERROR: Empty image!" << std::endl;
	//		return outputObjects;
	//	}

	//	// Store original dimensions for scaling back
	//	const int original_width = image.cols;
	//	const int original_height = image.rows;
	//	const int max_dimension = std::max(original_width, original_height);

	//	// Maintain aspect ratio while resizing to max dimension of 1920
	//	const int target_size = 1920;
	//	double scale_x = 1.0;
	//	double scale_y = 1.0;
	//	int working_width = original_width;
	//	int working_height = original_height;

	//	cv::Mat working_image;

	//	if (max_dimension > target_size)
	//	{
	//		// Calculate new dimensions maintaining aspect ratio
	//		double aspect_ratio = static_cast<double>(original_width) / static_cast<double>(original_height);

	//		if (original_width > original_height)
	//		{
	//			working_width = target_size;
	//			working_height = static_cast<int>(target_size / aspect_ratio);
	//		}
	//		else
	//		{
	//			working_height = target_size;
	//			working_width = static_cast<int>(target_size * aspect_ratio);
	//		}

	//		// Ensure dimensions are at least 1
	//		working_width = std::max(1, working_width);
	//		working_height = std::max(1, working_height);

	//		// Calculate scale factors AFTER determining final dimensions
	//		scale_x = static_cast<double>(original_width) / static_cast<double>(working_width);
	//		scale_y = static_cast<double>(original_height) / static_cast<double>(working_height);

	//		std::cout << "Working size: " << working_width << "x" << working_height
	//			<< " (maintains " << std::fixed << std::setprecision(2) << aspect_ratio << ":1 aspect ratio)" << std::endl;

	//		cv::resize(image, working_image, cv::Size(working_width, working_height), 0, 0, cv::INTER_AREA);
	//	}
	//	else
	//	{
	//		working_image = image.clone();
	//		std::cout << "Image size within limits, no resize needed" << std::endl;
	//	}

	//	std::cout << "Scale factors - X: " << scale_x << ", Y: " << scale_y << std::endl;
	//	std::cout << "Working image size: " << working_image.cols << "x" << working_image.rows << std::endl;

	//	// Ensure image is continuous in memory
	//	if (!working_image.isContinuous())
	//	{
	//		std::cout << "WARNING: Working image is not continuous, cloning..." << std::endl;
	//		working_image = working_image.clone();
	//	}

	//	camera.contours.clear();
	//	if (camera.thumbnail_size.area() == 0)
	//	{
	//		setDynamicPSNRThreshold(camera, working_image);  // Use working image!
	//	}

	//	// Initialize thumbnail size if needed (based on working image dimensions)
	//	if (camera.thumbnail_size.area() == 0)
	//	{
	//		camera.thumbnail_ratio = std::clamp(camera.thumbnail_ratio, 0.01, 1.0);
	//		camera.thumbnail_size.width = static_cast<int>(working_width * camera.thumbnail_ratio);
	//		camera.thumbnail_size.height = static_cast<int>(working_height * camera.thumbnail_ratio);

	//		camera.thumbnail_size.width = std::max(1, camera.thumbnail_size.width);
	//		camera.thumbnail_size.height = std::max(1, camera.thumbnail_size.height);

	//		std::cout << "Initialized thumbnail size: " << camera.thumbnail_size.width
	//			<< "x" << camera.thumbnail_size.height << std::endl;
	//	}

	//	// Create thumbnail for comparison
	//	cv::Mat thumbnail;
	//	cv::resize(working_image, thumbnail, camera.thumbnail_size, 0, 0, cv::INTER_AREA);

	//	if (thumbnail.empty())
	//	{
	//		std::cout << "ERROR: Failed to create thumbnail!" << std::endl;
	//		return outputObjects;
	//	}

	//	std::cout << "Thumbnail created: " << thumbnail.cols << "x" << thumbnail.rows
	//		<< " type: " << thumbnail.type() << std::endl;

	//	// DEBUG: Control frames info
	//	std::cout << "Control frames count: " << camera.control_frames.size() << std::endl;
	//	std::cout << "Control frame indices: ";
	//	for (const auto& [idx, frame] : camera.control_frames)
	//	{
	//		std::cout << idx << " ";
	//	}
	//	std::cout << std::endl;

	//	if (camera.control_frames.empty())
	//	{
	//		std::cout << "First frame - initializing control frame" << std::endl;
	//		camera.control_frames[frame_index] = thumbnail.clone();
	//		camera.next_key_frame = frame_index + camera.key_frame_frequency;
	//		camera.next_frame_index = frame_index + 1;
	//		camera.output = working_image.clone();
	//		camera.mask = cv::Mat(working_image.size(), CV_8UC1, cv::Scalar(0));

	//		auto end_time = std::chrono::high_resolution_clock::now();
	//		auto processing_time = std::chrono::duration_cast<std::chrono::milliseconds>(
	//			end_time - start_time
	//		);
	//		updateStatistics(camera, 0.0, false, processing_time);

	//		return outputObjects;
	//	}

	//	bool previous_movement_detected = camera.movement_detected;
	//	camera.movement_detected = false;
	//	double min_psnr = std::numeric_limits<double>::infinity();
	//	cv::Mat best_control_frame;
	//	size_t best_control_index = 0;

	//	std::cout << "Previous movement: " << (previous_movement_detected ? "YES" : "NO") << std::endl;
	//	std::cout << "PSNR Threshold: " << camera.psnr_threshold << std::endl;
	//	std::cout << "Max control frame age: " << camera.max_control_frame_age << " frames" << std::endl;

	//	// Compare with ALL control frames within max age and find the one with minimum PSNR
	//	int compared_frames_count = 0;
	//	for (auto iter = camera.control_frames.rbegin(); iter != camera.control_frames.rend(); ++iter)
	//	{
	//		const cv::Mat& control_image = iter->second;
	//		size_t control_frame_index = iter->first;

	//		// ADDED: Calculate frame age and skip if too old
	//		size_t frame_age = frame_index - control_frame_index;
	//		if (frame_age > camera.max_control_frame_age)
	//		{
	//			std::cout << "Skipping control frame " << control_frame_index
	//				<< " (age: " << frame_age << " > max: " << camera.max_control_frame_age << ")" << std::endl;
	//			continue;
	//		}

	//		// DEBUG: Verify control frame
	//		std::cout << "Comparing with control frame " << control_frame_index
	//			<< " (age: " << frame_age << " frames)"
	//			<< " size: " << control_image.cols << "x" << control_image.rows
	//			<< " type: " << control_image.type() << std::endl;

	//		if (control_image.empty())
	//		{
	//			std::cout << "ERROR: Control frame is empty!" << std::endl;
	//			continue;
	//		}

	//		if (control_image.size() != thumbnail.size())
	//		{
	//			std::cout << "ERROR: Size mismatch! Control: " << control_image.size()
	//				<< " Thumbnail: " << thumbnail.size() << std::endl;
	//			continue;
	//		}

	//		if (control_image.type() != thumbnail.type())
	//		{
	//			std::cout << "ERROR: Type mismatch! Control: " << control_image.type()
	//				<< " Thumbnail: " << thumbnail.type() << std::endl;
	//			continue;
	//		}

	//		double current_psnr = psnr(control_image, thumbnail);

	//		std::cout << "PSNR Score: " << std::fixed << std::setprecision(2)
	//			<< current_psnr << std::endl;

	//		if (std::isinf(current_psnr))
	//		{
	//			std::cout << "PSNR is infinity (identical frames), skipping..." << std::endl;
	//			continue;
	//		}

	//		// Track the minimum PSNR and corresponding control frame
	//		if (current_psnr < min_psnr)
	//		{
	//			min_psnr = current_psnr;
	//			best_control_frame = control_image;
	//			best_control_index = control_frame_index;
	//		}

	//		compared_frames_count++;
	//	}

	//	std::cout << "Compared " << compared_frames_count << " control frames within age limit" << std::endl;

	//	// ADDED: If no valid control frames were compared, treat as no movement
	//	if (compared_frames_count == 0)
	//	{
	//		std::cout << "WARNING: No valid control frames within age limit to compare!" << std::endl;
	//		camera.most_recent_psnr_score = 0.0;
	//		camera.movement_detected = false;
	//		camera.mask = cv::Mat(working_image.size(), CV_8UC1, cv::Scalar(0));
	//		camera.output = working_image.clone();

	//		auto end_time = std::chrono::high_resolution_clock::now();
	//		auto processing_time = std::chrono::duration_cast<std::chrono::milliseconds>(
	//			end_time - start_time
	//		);
	//		updateStatistics(camera, 0.0, false, processing_time);

	//		std::cout << "Processing time: " << processing_time.count() << "ms" << std::endl;
	//		std::cout << "=== End Debug ===" << std::endl;

	//		return outputObjects;
	//	}

	//	camera.most_recent_psnr_score = min_psnr;

	//	// Check if minimum PSNR is below threshold
	//	if (!std::isinf(min_psnr) && min_psnr < camera.psnr_threshold)
	//	{
	//		std::cout << "*** POTENTIAL MOVEMENT *** (Min PSNR: " << min_psnr << " < threshold: "
	//			<< camera.psnr_threshold << ")" << std::endl;
	//		std::cout << "Best matching control frame: " << best_control_index
	//			<< " (age: " << (frame_index - best_control_index) << " frames)" << std::endl;

	//		// Compute movement mask using the best control frame
	//		if (!best_control_frame.empty())
	//		{

	//			camera.mask = computeMovementMask(best_control_frame, thumbnail, working_image.size(),
	//				camera.morphology_iterations, camera);

	//			int non_zero_pixels = cv::countNonZero(camera.mask);
	//			double mask_percentage = (non_zero_pixels * 100.0) / (camera.mask.cols * camera.mask.rows);

	//			std::cout << "Mask created: " << camera.mask.cols << "x" << camera.mask.rows
	//				<< " non-zero pixels: " << non_zero_pixels
	//				<< " (" << std::fixed << std::setprecision(2) << mask_percentage << "%)" << std::endl;
	//			int min_mask_pixels = static_cast<int>(
	//				working_image.cols * working_image.rows * camera.min_mask_pixels_percentage / 100.0
	//				);

	//			std::cout << "Minimum required pixels: " << min_mask_pixels << std::endl;

	//			if (non_zero_pixels >= min_mask_pixels && mask_percentage < camera.mask_max_percentage)
	//			{
	//				std::cout << "*** MOVEMENT CONFIRMED *** (mask has " << non_zero_pixels
	//					<< " pixels, minimum: " << min_mask_pixels << ")" << std::endl;
	//				camera.movement_detected = true;
	//				camera.movement_last_detected = std::chrono::high_resolution_clock::now();
	//				camera.frame_index_with_movement = frame_index;
	//			}
	//			else
	//			{
	//				std::cout << "Movement REJECTED - mask too small (" << non_zero_pixels
	//					<< " < " << min_mask_pixels << " pixels)" << std::endl;
	//				std::cout << "This was likely noise or compression artifacts" << std::endl;
	//				camera.mask = cv::Mat(working_image.size(), CV_8UC1, cv::Scalar(0));
	//			}
	//		}
	//	}
	//	else
	//	{
	//		std::cout << "No movement (Min PSNR: " << min_psnr
	//			<< " >= threshold: " << camera.psnr_threshold << ")" << std::endl;
	//	}

	//	camera.transition_detected = (previous_movement_detected != camera.movement_detected);

	//	std::cout << "Current movement: " << (camera.movement_detected ? "YES" : "NO") << std::endl;
	//	std::cout << "Transition detected: " << (camera.transition_detected ? "YES" : "NO") << std::endl;

	//	if (camera.mask.empty() || (camera.transition_detected && !camera.movement_detected))
	//	{
	//		std::cout << "Resetting mask to zeros" << std::endl;
	//		camera.mask = cv::Mat(working_image.size(), CV_8UC1, cv::Scalar(0));
	//	}

	//	camera.output = working_image.clone();

	//	if (!camera.mask.empty() && camera.movement_detected && cv::countNonZero(camera.mask) > 0)
	//	{
	//		// Extract objects from working image
	//		outputObjects = extractObjectsFromMask(camera.mask, working_image, camera, camera_id);
	//		std::cout << "Objects extracted (at working resolution): " << outputObjects.size() << std::endl;

	//		// Scale objects back to original image size
	//		for (auto& obj : outputObjects)
	//		{
	//			// Scale bounding box coordinates
	//			obj.box.x = static_cast<int>(obj.box.x * scale_x);
	//			obj.box.y = static_cast<int>(obj.box.y * scale_y);
	//			obj.box.width = static_cast<int>(obj.box.width * scale_x);
	//			obj.box.height = static_cast<int>(obj.box.height * scale_y);

	//			// Clamp to original image boundaries
	//			obj.box.x = std::max(0, std::min(obj.box.x, original_width - 1));
	//			obj.box.y = std::max(0, std::min(obj.box.y, original_height - 1));
	//			obj.box.width = std::min(obj.box.width, original_width - obj.box.x);
	//			obj.box.height = std::min(obj.box.height, original_height - obj.box.y);

	//			// Scale polygon points if they exist
	//			if (!obj.polygon.empty())
	//			{
	//				for (auto& point : obj.polygon)
	//				{
	//					point.x = static_cast<int>(point.x * scale_x);
	//					point.y = static_cast<int>(point.y * scale_y);

	//					// Clamp points to image boundaries
	//					point.x = MAX(0, MIN(point.x, original_width - 1));
	//					point.y = MAX(0, MIN(point.y, original_height - 1));
	//				}
	//			}
	//		}

	//		std::cout << "Objects scaled back to original size (" << original_width
	//			<< "x" << original_height << ")" << std::endl;
	//	}

	//	// Update control frames
	//	if (frame_index >= camera.next_key_frame ||
	//		camera.control_frames.size() < camera.number_of_control_frames)
	//	{
	//		std::cout << "Adding new control frame at index " << frame_index << std::endl;
	//		updateControlFrames(camera, frame_index, thumbnail);
	//		std::cout << "Next key frame will be at: " << camera.next_key_frame << std::endl;
	//	}

	//	camera.next_frame_index = frame_index + 1;

	//	auto end_time = std::chrono::high_resolution_clock::now();
	//	auto processing_time = std::chrono::duration_cast<std::chrono::milliseconds>(
	//		end_time - start_time
	//	);
	//	updateStatistics(camera, camera.most_recent_psnr_score, camera.movement_detected,
	//		processing_time);

	//	std::cout << "Processing time: " << processing_time.count() << "ms" << std::endl;
	//	std::cout << "=== End Debug ===" << std::endl;

	//	return outputObjects;
	//}

	std::vector<Object> ANSMOTIONDETECTOR::MovementDetectInternal(
		const std::string& camera_id,
		const size_t frame_index,
		const cv::Mat& image,
		CameraData& camera)
	{
		auto start_time = std::chrono::high_resolution_clock::now();
		std::vector<Object> outputObjects;

		if (image.empty())
		{
			return outputObjects;
		}

		// Store original dimensions for scaling back
		const int original_width = image.cols;
		const int original_height = image.rows;
		const int max_dimension = std::max(original_width, original_height);

		// Calculate scaling parameters
		constexpr int target_size = 1920;
		double scale_x = 1.0;
		double scale_y = 1.0;
		int working_width = original_width;
		int working_height = original_height;

		cv::Mat working_image;

		if (max_dimension > target_size)
		{
			// Calculate new dimensions maintaining aspect ratio
			const double aspect_ratio = static_cast<double>(original_width) / original_height;

			if (original_width > original_height)
			{
				working_width = target_size;
				working_height = static_cast<int>(target_size / aspect_ratio);
			}
			else
			{
				working_height = target_size;
				working_width = static_cast<int>(target_size * aspect_ratio);
			}

			// Ensure minimum dimensions
			working_width = std::max(1, working_width);
			working_height = std::max(1, working_height);

			// Calculate scale factors
			scale_x = static_cast<double>(original_width) / working_width;
			scale_y = static_cast<double>(original_height) / working_height;

			// Resize once
			cv::resize(image, working_image, cv::Size(working_width, working_height),
				0, 0, cv::INTER_AREA);
		}
		else
		{
			// Use shallow copy if continuous, clone only if needed
			working_image = image.isContinuous() ? image : image.clone();
		}

		// Clear previous contours
		camera.contours.clear();

		// Initialize thumbnail size if needed
		if (camera.thumbnail_size.area() == 0)
		{
			setDynamicPSNRThreshold(camera, working_image);

			camera.thumbnail_ratio = std::clamp(camera.thumbnail_ratio, 0.01, 1.0);
			camera.thumbnail_size.width = static_cast<int>(working_width * camera.thumbnail_ratio);
			camera.thumbnail_size.height = static_cast<int>(working_height * camera.thumbnail_ratio);
			camera.thumbnail_size.width = std::max(1, camera.thumbnail_size.width);
			camera.thumbnail_size.height = std::max(1, camera.thumbnail_size.height);
		}

		// Create thumbnail
		cv::Mat thumbnail;
		cv::resize(working_image, thumbnail, camera.thumbnail_size, 0, 0, cv::INTER_AREA);

		if (thumbnail.empty())
		{
			return outputObjects;
		}

		// First frame initialization
		if (camera.control_frames.empty())
		{
			camera.control_frames[frame_index] = thumbnail.clone();
			camera.next_key_frame = frame_index + camera.key_frame_frequency;
			camera.next_frame_index = frame_index + 1;
			camera.output = working_image.isContinuous() ? working_image : working_image.clone();
			camera.mask = cv::Mat(working_image.size(), CV_8UC1, cv::Scalar(0));

			auto processing_time = std::chrono::duration_cast<std::chrono::milliseconds>(
				std::chrono::high_resolution_clock::now() - start_time
			);
			updateStatistics(camera, 0.0, false, processing_time);

			return outputObjects;
		}

		// Motion detection
		const bool previous_movement_detected = camera.movement_detected;
		camera.movement_detected = false;
		double min_psnr = std::numeric_limits<double>::infinity();
		cv::Mat best_control_frame;
		size_t best_control_index = 0;
		int compared_frames_count = 0;

		// Compare with control frames within age limit
		for (auto iter = camera.control_frames.rbegin();
			iter != camera.control_frames.rend(); ++iter)
		{
			const size_t control_frame_index = iter->first;
			const size_t frame_age = frame_index - control_frame_index;

			// Skip frames exceeding max age
			if (frame_age > camera.max_control_frame_age)
			{
				continue;
			}

			const cv::Mat& control_image = iter->second;

			// Validate control frame
			if (control_image.empty() ||
				control_image.size() != thumbnail.size() ||
				control_image.type() != thumbnail.type())
			{
				continue;
			}

			const double current_psnr = psnr(control_image, thumbnail);

			// Skip infinite PSNR (identical frames)
			if (std::isinf(current_psnr))
			{
				continue;
			}

			// Track minimum PSNR
			if (current_psnr < min_psnr)
			{
				min_psnr = current_psnr;
				best_control_frame = control_image;
				best_control_index = control_frame_index;
			}

			compared_frames_count++;
		}

		// Handle no valid control frames
		if (compared_frames_count == 0)
		{
			camera.most_recent_psnr_score = 0.0;
			camera.movement_detected = false;
			camera.mask = cv::Mat(working_image.size(), CV_8UC1, cv::Scalar(0));
			camera.output = working_image.isContinuous() ? working_image : working_image.clone();

			auto processing_time = std::chrono::duration_cast<std::chrono::milliseconds>(
				std::chrono::high_resolution_clock::now() - start_time
			);
			updateStatistics(camera, 0.0, false, processing_time);

			return outputObjects;
		}

		camera.most_recent_psnr_score = min_psnr;

		// Check for movement
		if (!std::isinf(min_psnr) && min_psnr < camera.psnr_threshold)
		{
			if (!best_control_frame.empty())
			{
				camera.mask = computeMovementMask(best_control_frame, thumbnail,
					working_image.size(),
					camera.morphology_iterations, camera);

				const int non_zero_pixels = cv::countNonZero(camera.mask);
				const double mask_percentage = (non_zero_pixels * 100.0) / camera.mask.total();

				const int min_mask_pixels = static_cast<int>(
					working_image.total() * camera.min_mask_pixels_percentage / 100.0
					);

				if (non_zero_pixels >= min_mask_pixels &&
					mask_percentage < camera.mask_max_percentage)
				{
					camera.movement_detected = true;
					camera.movement_last_detected = std::chrono::high_resolution_clock::now();
					camera.frame_index_with_movement = frame_index;
				}
				else
				{
					// Noise - reset mask
					camera.mask = cv::Mat(working_image.size(), CV_8UC1, cv::Scalar(0));
				}
			}
		}

		camera.transition_detected = (previous_movement_detected != camera.movement_detected);

		// Reset mask on transition to no movement
		if (camera.mask.empty() || (camera.transition_detected && !camera.movement_detected))
		{
			camera.mask = cv::Mat(working_image.size(), CV_8UC1, cv::Scalar(0));
		}

		// Store output
		camera.output = working_image.isContinuous() ? working_image : working_image.clone();

		// Extract and scale objects
		if (!camera.mask.empty() && camera.movement_detected && cv::countNonZero(camera.mask) > 0)
		{
			outputObjects = extractObjectsFromMask(camera.mask, working_image, camera, camera_id);

			// Scale objects back to original size (only if resized)
			if (scale_x != 1.0 || scale_y != 1.0)
			{
				const cv::Rect imageBounds(0, 0, original_width, original_height);

				for (auto& obj : outputObjects)
				{
					// Scale bounding box
					obj.box.x = static_cast<int>(obj.box.x * scale_x);
					obj.box.y = static_cast<int>(obj.box.y * scale_y);
					obj.box.width = static_cast<int>(obj.box.width * scale_x);
					obj.box.height = static_cast<int>(obj.box.height * scale_y);

					// Clamp to image boundaries
					obj.box &= imageBounds;

					// Scale polygon points
					if (!obj.polygon.empty())
					{
						for (auto& point : obj.polygon)
						{
							point.x = std::clamp(point.x * static_cast<float>(scale_x),
								0.0f, static_cast<float>(original_width - 1));
							point.y = std::clamp(point.y * static_cast<float>(scale_y),
								0.0f, static_cast<float>(original_height - 1));
						}
					}
				}
			}
		}

		// Update control frames
		if (frame_index >= camera.next_key_frame ||
			camera.control_frames.size() < camera.number_of_control_frames)
		{
			updateControlFrames(camera, frame_index, thumbnail);
		}

		camera.next_frame_index = frame_index + 1;

		auto processing_time = std::chrono::duration_cast<std::chrono::milliseconds>(
			std::chrono::high_resolution_clock::now() - start_time
		);
		updateStatistics(camera, camera.most_recent_psnr_score, camera.movement_detected,
			processing_time);

		return outputObjects;
	}
	// Override functions
	bool ANSMOTIONDETECTOR::Initialize(std::string licenseKey, ModelConfig modelConfig, const std::string& modelZipFilePath, const std::string& modelZipPassword, std::string& labelMap) {

		bool result = ANSODBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword, labelMap);
		if (!result) return false;
		_isInitialized = true;
		//_modelConfig.detectionScoreThreshold = 0.5;
		labelMap = "Movement";
		return true;
	}

	bool ANSMOTIONDETECTOR::LoadModel(const std::string& modelZipFilePath, const std::string& modelZipPassword) {
		bool result = ANSODBase::LoadModel(modelZipFilePath, modelZipPassword);
		_isInitialized = true;
		return true;
	}

	bool ANSMOTIONDETECTOR::LoadModelFromFolder(std::string licenseKey, ModelConfig modelConfig, std::string modelName, std::string className, const std::string& modelFolder, std::string& labelMap) {
		bool result = ANSODBase::LoadModelFromFolder(licenseKey, modelConfig, modelName, className, modelFolder, labelMap);
		if (!result) return false;
		_isInitialized = true;
		labelMap = "Movement";
		return true;

	}

	bool ANSMOTIONDETECTOR::OptimizeModel(bool fp16, std::string& optimizedModelFolder) {
		if (!ANSODBase::OptimizeModel(fp16, optimizedModelFolder)) {
			return false;
		}
		return true;
	}
	std::vector<Object> ANSMOTIONDETECTOR::RunInference(const cv::Mat& input) {
		return RunInference(input, "MotionCam");
	}

	
	std::vector<Object> ANSMOTIONDETECTOR::RunInference(const cv::Mat& input,const std::string& camera_id)
	{
		std::lock_guard<std::recursive_mutex> lock(_mutex);

		try {
			// Consolidated validation
			if (!_licenseValid || input.empty() || input.cols < 5 || input.rows < 5)
			{
				return {};
			}

			// Direct call - no clone needed!
			std::vector<Object> result = MovementDetect(camera_id, input);
			if (_trackerEnabled) {
				result = ApplyTracking(result, camera_id);
				if (_stabilizationEnabled) result = StabilizeDetections(result, camera_id);
			}
			return result;
		}
		catch (const std::exception& e) {
			_logger.LogFatal("ANSMOTIONDETECTOR::RunInference",
				e.what(), __FILE__, __LINE__);
			return {};
		}
	}
	bool ANSMOTIONDETECTOR::Destroy() {
		return true;
	}
}

/*std::vector<Object> ANSMOTIONDETECTOR::RunInference(const cv::Mat& input, const std::string& camera_id) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		std::vector<Object> objects;
		objects.clear();
		try {
			if (!_licenseValid) return objects;
			if (input.empty() || !input.data || !input.u) {
				return objects;
			}
			if ((input.cols < 5) || (input.rows < 5)) return objects;
			cv::Mat frame = input.clone();
			objects = MovementDetect(camera_id, frame);
			frame.release();
			return objects;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSMOTIONDETECTOR::RunInference", e.what(), __FILE__, __LINE__);
			return objects;
		}
	}*//*std::vector<Object> ANSMOTIONDETECTOR::RunInference(const cv::Mat& input, const std::string& camera_id) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		std::vector<Object> objects;
		objects.clear();
		try {
			if (!_licenseValid) return objects;
			if (input.empty() || !input.data || !input.u) {
				return objects;
			}
			if ((input.cols < 5) || (input.rows < 5)) return objects;
			cv::Mat frame = input.clone();
			objects = MovementDetect(camera_id, frame);
			frame.release();
			return objects;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSMOTIONDETECTOR::RunInference", e.what(), __FILE__, __LINE__);
			return objects;
		}
	}*/