#ifndef STACKER_BACKGROUND_ADJUSTMENT_TPP
#define STACKER_BACKGROUND_ADJUSTMENT_TPP

#include <opencv2/opencv.hpp>
#include "../processing/backgroundextractor.h"
#include "../processing/nebulosity_detector.h"
#include "../io/timer.h"
#include "../io/logger.h"

template <typename T>
std::shared_ptr<ImageBuffer<T>> Stacker<T>::applyBackgroundAdjustment(const std::shared_ptr<ImageBuffer<T>> &frame,
                             float percentage)
{
    TIME_FUNCTION("applyBackgroundAdjustment");

    LOG_INFO("Background adjustment percentage: " + std::to_string(percentage));

    // Convert ImageBuffer to OpenCV Mat for nebulosity detection
    cv::Mat imageMat;
    if (frame->getChannels() == 1)
    {
        // Single channel image
        if (std::is_same<T, uint8_t>::value)
        {
            imageMat = cv::Mat(frame->getHeight(), frame->getWidth(), CV_8UC1, frame->getData());
        }
        else if (std::is_same<T, uint16_t>::value)
        {
            imageMat = cv::Mat(frame->getHeight(), frame->getWidth(), CV_16UC1, frame->getData());
        }
    }
    else if (frame->getChannels() == 3)
    {
        // RGB image
        if (std::is_same<T, uint8_t>::value)
        {
            imageMat = cv::Mat(frame->getHeight(), frame->getWidth(), CV_8UC3, frame->getData());
        }
        else if (std::is_same<T, uint16_t>::value)
        {
            imageMat = cv::Mat(frame->getHeight(), frame->getWidth(), CV_16UC3, frame->getData());
        }
    }
    imageMat = imageMat.clone(); // Create a deep copy

    // Check for extensive nebulosity
    bool hasExtensiveNebulosity = false;
    float nebulosityScore = 0.0f;

    // First check if we have cached nebulosity results
    if (m_settings->hasNebulosityBeenChecked())
    {
        // Use cached results
        hasExtensiveNebulosity = m_settings->hasExtensiveNebulosity();
        nebulosityScore = m_settings->getNebulosityScore();
        LOG_INFO("Using cached nebulosity detection results: " +
                 std::string(hasExtensiveNebulosity ? "nebulosity detected" : "no nebulosity") +
                 ", score: " + std::to_string(nebulosityScore));
    }
    // Otherwise, perform detection if auto-detection is enabled
    else if (m_settings->getAutoDetectNebulosity())
    {
        LOG_INFO("Checking for extensive nebulosity...");

        // Create nebulosity detector with the configured threshold
        NebulosityDetector detector;
        detector.setDetectionThreshold(m_settings->getNebulosityThreshold());

        // Detect nebulosity
        hasExtensiveNebulosity = detector.detectExtensiveNebulosity(imageMat, nebulosityScore);

        // Store the results in the settings for future use
        m_settings->setNebulosityResults(true, hasExtensiveNebulosity, nebulosityScore);

        // Save visualization in diagnostic mode
        if (m_settings->getDiagnosticMode() && !m_settings->getOutputPrefix().empty())
        {
            cv::Mat nebulosityViz = detector.generateVisualization(imageMat, nebulosityScore);
            std::string nebulosityFilename = m_settings->getOutputPrefix() + "nebulosity_detection.jpg";
            cv::imwrite(nebulosityFilename, nebulosityViz);
            LOG_INFO("Saved nebulosity detection visualization to " + nebulosityFilename);
        }
    }

    // Adjust background extraction based on nebulosity detection
    if (hasExtensiveNebulosity)
    {
        LOG_WARN("Extensive nebulosity detected! Adjusting background extraction to preserve details.");

        // Option 1: Disable background adjustment completely for very high nebulosity scores
        if (nebulosityScore > 0.8f)
        {
            LOG_WARN("Very high nebulosity score (" + std::to_string(nebulosityScore) +
                     "). Disabling background adjustment completely.");
            return frame->clone();
        }

        // Option 2: Reduce background adjustment based on nebulosity score
        percentage = percentage * (1.0f - nebulosityScore * 0.8f);
        LOG_INFO("Adjusted background percentage: " + std::to_string(percentage));
    }

    // Create a BackgroundExtractor
    BackgroundExtractor extractor;

    // Set parameters based on the percentage and nebulosity detection
    float normalizedPercentage = percentage / 100.0f;

    // Set polynomial degree (1-4)
    int polyDegree;
    if (hasExtensiveNebulosity)
    {
        // Use lower polynomial degree for nebulous images
        polyDegree = 1;
    }
    else
    {
        // Use higher degree for higher percentages
        polyDegree = 3;
        if (normalizedPercentage > 0.7f)
            polyDegree = 4;
    }
    extractor.setPolynomialDegree(polyDegree);

    // Set sample grid size (8-64)
    int gridSize;
    if (hasExtensiveNebulosity)
    {
        // Use larger grid size (less sampling points) for nebulous images
        gridSize = 32;
    }
    else
    {
        // Use finer grid for higher percentages
        gridSize = static_cast<int>(16 + normalizedPercentage * 16);
    }
    extractor.setSampleGridSize(gridSize);

    // Set protection threshold (0.0-1.0)
    float protectionThreshold;
    if (hasExtensiveNebulosity)
    {
        // Use higher protection threshold for nebulous images
        protectionThreshold = 0.8f;
    }
    else
    {
        // For higher percentages, we want a moderate protection level
        protectionThreshold = 0.5f;  // Use a constant value that works well
    }
    extractor.setProtectionThreshold(protectionThreshold);

    // Set smoothness factor (0.0-1.0)
    float smoothnessFactor;
    if (hasExtensiveNebulosity)
    {
        // Use higher smoothness factor for nebulous images
        smoothnessFactor = 0.8f;
    }
    else
    {
        // For higher percentages, use a moderate smoothness to preserve details
        smoothnessFactor = std::max(0.4f, 0.5f);  // Keep it in a safe range
    }
    extractor.setSmoothnessFactor(smoothnessFactor);

    // Process the image
    LOG_INFO("Using BackgroundExtractor with parameters:");
    LOG_INFO("  Polynomial degree: " + std::to_string(polyDegree));
    LOG_INFO("  Grid size: " + std::to_string(gridSize));
    LOG_INFO("  Protection threshold: " + std::to_string(protectionThreshold));
    LOG_INFO("  Smoothness factor: " + std::to_string(smoothnessFactor));
    if (hasExtensiveNebulosity)
    {
        LOG_INFO("  Using conservative settings due to detected nebulosity (score: " +
                 std::to_string(nebulosityScore) + ")");
    }

    // Generate the background model
    LOG_INFO("Generating background model...");
    auto backgroundModel = extractor.generateBackgroundModel(frame);
    LOG_INFO("Background model generated successfully");

    // Convert ImageBuffer to OpenCV Mat for the image (second time for subtraction)
    cv::Mat imageMat2;
    if (frame->getChannels() == 1)
    {
        // Single channel image
        if (std::is_same<T, uint8_t>::value)
        {
            imageMat2 = cv::Mat(frame->getHeight(), frame->getWidth(), CV_8UC1, frame->getData());
        }
        else if (std::is_same<T, uint16_t>::value)
        {
            imageMat2 = cv::Mat(frame->getHeight(), frame->getWidth(), CV_16UC1, frame->getData());
        }
    }
    else if (frame->getChannels() == 3)
    {
        // RGB image
        if (std::is_same<T, uint8_t>::value)
        {
            imageMat2 = cv::Mat(frame->getHeight(), frame->getWidth(), CV_8UC3, frame->getData());
        }
        else if (std::is_same<T, uint16_t>::value)
        {
            imageMat2 = cv::Mat(frame->getHeight(), frame->getWidth(), CV_16UC3, frame->getData());
        }
    }
    imageMat2 = imageMat2.clone(); // Create a deep copy

    // Convert background model to OpenCV Mat
    cv::Mat bgModelMat;
    if (backgroundModel->getChannels() == 1)
    {
        // Single channel image
        if (std::is_same<T, uint8_t>::value)
        {
            bgModelMat = cv::Mat(backgroundModel->getHeight(), backgroundModel->getWidth(), CV_8UC1, backgroundModel->getData());
        }
        else if (std::is_same<T, uint16_t>::value)
        {
            bgModelMat = cv::Mat(backgroundModel->getHeight(), backgroundModel->getWidth(), CV_16UC1, backgroundModel->getData());
        }
    }
    else if (backgroundModel->getChannels() == 3)
    {
        // RGB image
        if (std::is_same<T, uint8_t>::value)
        {
            bgModelMat = cv::Mat(backgroundModel->getHeight(), backgroundModel->getWidth(), CV_8UC3, backgroundModel->getData());
        }
        else if (std::is_same<T, uint16_t>::value)
        {
            bgModelMat = cv::Mat(backgroundModel->getHeight(), backgroundModel->getWidth(), CV_16UC3, backgroundModel->getData());
        }
    }
    bgModelMat = bgModelMat.clone(); // Create a deep copy

    // Apply pure background subtraction
    LOG_INFO("Applying pure background subtraction...");
    cv::Mat subtractedMat = pureBackgroundSubtraction(imageMat2, bgModelMat);

    // Create a new ImageBuffer from the processed Mat
    auto result = std::make_shared<ImageBuffer<T>>(subtractedMat.cols, subtractedMat.rows, subtractedMat.channels());

    // Copy data from Mat to ImageBuffer
    if (subtractedMat.isContinuous())
    {
        std::memcpy(result->getData(), subtractedMat.data, subtractedMat.total() * subtractedMat.channels() * sizeof(T));
    }
    else
    {
        // Handle non-continuous data
        for (int y = 0; y < subtractedMat.rows; ++y)
        {
            std::memcpy(result->getData() + y * subtractedMat.cols * subtractedMat.channels(),
                        subtractedMat.ptr<T>(y),
                        subtractedMat.cols * subtractedMat.channels() * sizeof(T));
        }
    }

    LOG_INFO("Pure background subtraction applied successfully");
    return result;
}

#endif // STACKER_BACKGROUND_ADJUSTMENT_TPP
