#include "livestacker.h"
#include "io/imageloader.h"
#include "io/logger.h"
#include "processing/stacker.h"
#include "processing/backgroundextractor.h"
#include <algorithm>
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <unistd.h>

// Pure background subtraction function similar to the one in test_aggressive_background.cpp
cv::Mat pureBackgroundSubtraction(const cv::Mat &image, const cv::Mat &backgroundModel)
{
    // Convert both images to the same type (float) for processing
    cv::Mat floatImage, floatBgModel;
    image.convertTo(floatImage, CV_32F);
    backgroundModel.convertTo(floatBgModel, CV_32F);

    // Make sure both images have the same number of channels
    if (floatImage.channels() != floatBgModel.channels())
    {
        if (floatImage.channels() == 1 && floatBgModel.channels() == 3)
        {
            // Convert grayscale image to 3-channel
            cv::cvtColor(floatImage, floatImage, cv::COLOR_GRAY2BGR);
        }
        else if (floatImage.channels() == 3 && floatBgModel.channels() == 1)
        {
            // Convert grayscale background to 3-channel
            cv::cvtColor(floatBgModel, floatBgModel, cv::COLOR_GRAY2BGR);
        }
    }

    // Make sure both images have the same size
    if (floatImage.size() != floatBgModel.size())
    {
        cv::resize(floatBgModel, floatBgModel, floatImage.size());
    }

    // Pure subtraction without scaling factors
    cv::Mat result = floatImage - floatBgModel;

    // Ensure no negative values
    cv::threshold(result, result, 0, 0, cv::THRESH_TOZERO);

    // Convert back to original type
    cv::Mat output;
    if (image.type() == CV_8UC1 || image.type() == CV_8UC3)
    {
        result.convertTo(output, image.type());
    }
    else if (image.type() == CV_16UC1 || image.type() == CV_16UC3)
    {
        result.convertTo(output, image.type());
    }
    else
    {
        output = result;
    }

    return output;
}

class LiveStacker::Impl
{
    public:
        Impl() = default;

        // Stacking parameters
        LiveStacker::StackingMethod m_stackingMethod = LiveStacker::StackingMethod::Mean;
        bool m_applyDarkSubtraction = false;
        bool m_alignFrames = true;
        float m_backgroundAdjustment = 15.0f;  // Remove background noise
        float m_contrastAdjustment = 120.0f;   // Enhance details
        float m_saturationAdjustment = 120.0f; // Enhance colors
        float m_compressionPercentage = 20.0f; // Bring out faint details
        int m_medianFilterSize = 0;
        int m_maxAlignmentStars = 200;
        int m_sigmaClippingFrames = 5;       // Enable sigma clipping by default
        float m_sigmaClippingLower = 2.0f;     // Lower sigma threshold
        float m_sigmaClippingUpper = 2.0f;     // Upper sigma threshold
        bool m_diagnosticMode = false;
        std::string m_outputPrefix = "frame_";
        float m_qualityThreshold = 0.7f;       // Default quality threshold

        // Flags to track which parameters have been explicitly set
        bool m_backgroundAdjustmentSet = false;
        bool m_contrastAdjustmentSet = false;
        bool m_saturationAdjustmentSet = false;
        bool m_compressionPercentageSet = false;
        bool m_medianFilterSizeSet = false;
        bool m_localHistEqSet = false;

        // Background extraction parameters
        int m_backgroundPolynomialDegree = 2;
        int m_backgroundSampleGridSize = 16;
        float m_backgroundProtectionThreshold = 0.5f;
        float m_backgroundSmoothnessFactor = 0.5f;

        // Local histogram equalization parameters
        int m_localHistEqWindowSize = 0;     // Disabled by default
        float m_localHistEqClipLimit = 1.0f;   // Clip limit for astronomical images

        // Denoising parameters
        int m_denoisingMethod = 0;           // 0=None, 1=NonLocalMeans, 2=Bilateral
        int m_denoisingStrength = 10;        // Strength of denoising (1-30)
        bool m_denoisingSet = false;         // Whether denoising has been explicitly set

        // Output and performance settings
        bool m_verboseMode = false;          // Verbose mode disabled by default
        bool m_benchmarkMode = false;        // Benchmark mode disabled by default

        // Frames
        std::shared_ptr<ImageBuffer8bit> m_referenceFrame = nullptr;
        std::shared_ptr<ImageBuffer8bit> m_darkFrame = nullptr;
        std::vector<std::shared_ptr<ImageBuffer8bit>> m_frames;

        // State
        int m_frameCount = 0;
        std::shared_ptr<ImageBuffer8bit> m_currentStackedFrame = nullptr;
        float m_originalOrientation = 0.0f; // Store the original orientation in degrees

        // Nebulosity detection caching for performance optimization
        bool m_nebulosityDetected = false;
        float m_nebulosityScore = 0.0f;
        bool m_nebulosityChecked = false;

        // Error handling
        std::string m_lastError;
};

LiveStacker::LiveStacker()
    : m_impl(new Impl())
{
}

LiveStacker::~LiveStacker() = default;

void LiveStacker::setStackingMethod(StackingMethod method)
{
    m_impl->m_stackingMethod = method;
}

void LiveStacker::setApplyDarkSubtraction(bool apply)
{
    m_impl->m_applyDarkSubtraction = apply;
}

bool LiveStacker::setDarkFrame(const std::string &filename)
{
    try
    {
        m_impl->m_darkFrame = ImageLoader::loadImage(filename);
        return true;
    }
    catch (const std::exception &e)
    {
        m_impl->m_lastError = e.what();
        return false;
    }
}

void LiveStacker::setAlignFrames(bool align)
{
    m_impl->m_alignFrames = align;
}

void LiveStacker::setBackgroundAdjustment(float percentage)
{
    m_impl->m_backgroundAdjustment = std::max(0.0f, std::min(100.0f, percentage));
    m_impl->m_backgroundAdjustmentSet = true;
}

void LiveStacker::setContrastAdjustment(float percentage)
{
    // Map input range (0-100) to internal range (10-200)
    // 0 -> 10%, 50 -> 100%, 100 -> 200%
    float mappedValue = 10.0f + (percentage / 100.0f) * 190.0f;
    m_impl->m_contrastAdjustment = std::max(10.0f, std::min(200.0f, mappedValue));
    m_impl->m_contrastAdjustmentSet = true;
}

void LiveStacker::setSaturationAdjustment(float percentage)
{
    // Map input range (0-100) to internal range (0-200)
    // 0 -> 0%, 50 -> 100%, 100 -> 200%
    float mappedValue = (percentage / 100.0f) * 200.0f;
    m_impl->m_saturationAdjustment = std::max(0.0f, std::min(200.0f, mappedValue));
    m_impl->m_saturationAdjustmentSet = true;
}

void LiveStacker::setCompressionPercentage(float percentage)
{
    m_impl->m_compressionPercentage = std::max(0.0f, std::min(100.0f, percentage));
    m_impl->m_compressionPercentageSet = true;
}

void LiveStacker::setMedianFilterSize(int size)
{
    m_impl->m_medianFilterSize = std::max(0, size);
    m_impl->m_medianFilterSizeSet = true;
}

void LiveStacker::setLocalHistogramEqualization(bool enable, int windowSize, float clipLimit)
{
    m_impl->m_localHistEqWindowSize = enable ? std::max(3, windowSize) : 0;
    m_impl->m_localHistEqClipLimit = std::max(0.0f, std::min(4.0f, clipLimit));
    m_impl->m_localHistEqSet = true;
}

void LiveStacker::setDenoising(int method, int strength)
{
    m_impl->m_denoisingMethod = std::max(0, std::min(2, method)); // 0=None, 1=NonLocalMeans, 2=Bilateral
    m_impl->m_denoisingStrength = std::max(1, std::min(30, strength)); // Clamp to 1-30 range
    m_impl->m_denoisingSet = true;
}

void LiveStacker::setMaxAlignmentStars(int maxStars)
{
    m_impl->m_maxAlignmentStars = std::max(3, maxStars);
}

void LiveStacker::setSigmaClippingParams(int frames, float lower, float upper)
{
    m_impl->m_sigmaClippingFrames = std::max(3, frames);
    m_impl->m_sigmaClippingLower = std::max(1.0f, std::min(7.0f, lower));
    m_impl->m_sigmaClippingUpper = std::max(1.0f, std::min(7.0f, upper));
}

void LiveStacker::setDiagnosticMode(bool enable, const std::string &outputPrefix)
{
    m_impl->m_diagnosticMode = enable;
    m_impl->m_outputPrefix = outputPrefix;
}

void LiveStacker::setQualityThreshold(float threshold)
{
    m_impl->m_qualityThreshold = std::max(0.0f, std::min(1.0f, threshold));
}


void LiveStacker::setBackgroundPolynomialDegree(int degree)
{
    m_impl->m_backgroundPolynomialDegree = std::max(1, std::min(4, degree));
}

void LiveStacker::setBackgroundSampleGridSize(int gridSize)
{
    m_impl->m_backgroundSampleGridSize = std::max(8, std::min(64, gridSize));
}

void LiveStacker::setBackgroundProtectionThreshold(float threshold)
{
    m_impl->m_backgroundProtectionThreshold = std::max(0.0f, std::min(1.0f, threshold));
}

void LiveStacker::setBackgroundSmoothnessFactor(float smoothness)
{
    m_impl->m_backgroundSmoothnessFactor = std::max(0.0f, std::min(1.0f, smoothness));
}

void LiveStacker::setBenchmarkMode(bool enable)
{
    m_impl->m_benchmarkMode = enable;
}

std::string LiveStacker::getLastError() const
{
    return m_impl->m_lastError;
}

bool LiveStacker::processFrame(const std::string &filename, const std::string &outputFilename)
{
    try
    {
        LOG_INFO("Starting processFrame method...");

        // Load the new frame
        LOG_INFO("Loading frame: " + filename);
        auto newFrame = ImageLoader::loadImage(filename);
        LOG_INFO("Frame loaded successfully");

        // If this is the first frame, set it as the reference frame and perform nebulosity detection
        if (!m_impl->m_referenceFrame)
        {
            LOG_INFO("Setting reference frame");
            m_impl->m_referenceFrame = newFrame;

            // Perform nebulosity detection on the first frame if not already done
            if (!m_impl->m_nebulosityChecked)
            {
                LOG_INFO("Performing nebulosity detection on first frame");

                // Convert ImageBuffer to OpenCV Mat for nebulosity detection
                cv::Mat imageMat;
                if (newFrame->getChannels() == 1)
                {
                    imageMat = cv::Mat(newFrame->getHeight(), newFrame->getWidth(), CV_8UC1, newFrame->getData());
                }
                else if (newFrame->getChannels() == 3)
                {
                    imageMat = cv::Mat(newFrame->getHeight(), newFrame->getWidth(), CV_8UC3, newFrame->getData());
                }
                imageMat = imageMat.clone(); // Create a deep copy

                // Create nebulosity detector
                NebulosityDetector detector;

                // Detect nebulosity
                m_impl->m_nebulosityDetected = detector.detectExtensiveNebulosity(imageMat, m_impl->m_nebulosityScore);
                m_impl->m_nebulosityChecked = true;

                LOG_INFO("Nebulosity detection results: " +
                         std::string(m_impl->m_nebulosityDetected ? "nebulosity detected" : "no nebulosity") +
                         ", score: " + std::to_string(m_impl->m_nebulosityScore));
            }
        }

        // Apply dark subtraction if needed
        if (m_impl->m_applyDarkSubtraction && m_impl->m_darkFrame)
        {
            LOG_INFO("Applying dark subtraction");
            newFrame = applyDarkSubtraction(newFrame);
        }

        // Align frame if needed
        if (m_impl->m_alignFrames && m_impl->m_referenceFrame)
        {
            LOG_INFO("Aligning frame");
            newFrame = alignFrame(newFrame);
        }

        // Add the frame to the internal list
        LOG_INFO("Adding frame to internal list");
        m_impl->m_frames.push_back(newFrame);
        m_impl->m_frameCount++;
        LOG_INFO("Frame count: " + std::to_string(m_impl->m_frameCount));

        // If this is the first frame, just use it as the stacked frame
        if (m_impl->m_frameCount == 1)
        {
            LOG_INFO("First frame, cloning as stacked frame");
            m_impl->m_currentStackedFrame = newFrame->clone();
        }
        else
        {
            // Otherwise, update the stacked frame based on the stacking method
            LOG_INFO("Updating stacked frame with method: " + std::to_string(static_cast<int>(m_impl->m_stackingMethod)));

            // In stateful operation, only mean stacking is supported
            if (m_impl->m_stackingMethod == StackingMethod::Median ||
                    m_impl->m_stackingMethod == StackingMethod::SigmaClip)
            {
                LOG_WARN("Median and sigma clipping methods are not supported in stateful operation. Falling back to mean stacking.");
                // Fall back to mean stacking
                updateMeanStack(newFrame);
            }
            else
            {
                // Mean stacking
                LOG_INFO("Using mean stacking method");
                updateMeanStack(newFrame);
            }
        }

        // Apply post-processing only to the output image, not to the state
        LOG_INFO("Applying post-processing to output image only");
        auto result = applyPostProcessing(m_impl->m_currentStackedFrame);
        LOG_INFO("Saving result to " + outputFilename);
        ImageLoader::saveImage(outputFilename, result);
        LOG_INFO("Result saved successfully");

        return true;
    }
    catch (const std::exception &e)
    {
        LOG_ERROR("Exception in processFrame method: " + std::string(e.what()));
        m_impl->m_lastError = e.what();
        return false;
    }
}

bool LiveStacker::saveState(const std::string &stateFilename)
{
    try
    {
        // Save the current stacked frame without applying post-processing
        if (m_impl->m_currentStackedFrame)
        {
            // Save the raw stacked frame without post-processing
            LOG_INFO("Saving raw stacked frame without post-processing to state file");
            ImageLoader::saveImage(stateFilename, m_impl->m_currentStackedFrame);

            // Save the frame count, orientation, and nebulosity data to a separate metadata file
            std::string metadataFilename = stateFilename + ".meta";
            FILE *metadataFile = fopen(metadataFilename.c_str(), "w");
            if (metadataFile)
            {
                // Save the frame count, orientation, and nebulosity data
                // Format: frameCount orientation nebulosityChecked nebulosityDetected nebulosityScore
                fprintf(metadataFile, "%d\n%.2f\n%d\n%d\n%.4f",
                        m_impl->m_frameCount,
                        m_impl->m_originalOrientation,
                        m_impl->m_nebulosityChecked ? 1 : 0,
                        m_impl->m_nebulosityDetected ? 1 : 0,
                        m_impl->m_nebulosityScore);
                fclose(metadataFile);
                LOG_INFO("Saved metadata to " + metadataFilename + " with frame count: " +
                         std::to_string(m_impl->m_frameCount) + ", orientation: " +
                         std::to_string(m_impl->m_originalOrientation) + " degrees" +
                         (m_impl->m_nebulosityChecked ? ", nebulosity score: " +
                          std::to_string(m_impl->m_nebulosityScore) : ""));
            }
            else
            {
                LOG_ERROR("Warning: Could not save metadata file: " + metadataFilename);
            }
        }
        else
        {
            throw std::runtime_error("No stacked frame to save");
        }
        return true;
    }
    catch (const std::exception &e)
    {
        m_impl->m_lastError = e.what();
        return false;
    }
}

bool LiveStacker::loadState(const std::string &stateFilename)
{
    try
    {
        LOG_INFO("Loading state from " + stateFilename);

        // Check if the state file exists
        FILE *stateFile = fopen(stateFilename.c_str(), "r");
        if (!stateFile)
        {
            LOG_INFO("State file does not exist: " + stateFilename);
            return false;
        }
        fclose(stateFile);

        // Load the stacked frame
        m_impl->m_currentStackedFrame = ImageLoader::loadImage(stateFilename);
        LOG_INFO("Loaded stacked frame: " + std::to_string(m_impl->m_currentStackedFrame->getWidth()) + "x" +
                 std::to_string(m_impl->m_currentStackedFrame->getHeight()));

        // Try to load the frame count, orientation, and nebulosity data from the metadata file
        std::string metadataFilename = stateFilename + ".meta";
        FILE *metadataFile = fopen(metadataFilename.c_str(), "r");
        if (metadataFile)
        {
            int frameCount = 0;
            float orientation = 0.0f;
            int nebulosityChecked = 0;
            int nebulosityDetected = 0;
            float nebulosityScore = 0.0f;

            // Try to read all metadata values
            int readCount = fscanf(metadataFile, "%d\n%f\n%d\n%d\n%f",
                                   &frameCount, &orientation,
                                   &nebulosityChecked, &nebulosityDetected, &nebulosityScore);

            if (readCount >= 2 && frameCount > 0)
            {
                m_impl->m_frameCount = frameCount;
                m_impl->m_originalOrientation = orientation;
                LOG_INFO("Loaded frame count from metadata file: " + std::to_string(m_impl->m_frameCount));
                LOG_INFO("Loaded orientation from metadata file: " + std::to_string(m_impl->m_originalOrientation) + " degrees");

                // If we have nebulosity data (new format)
                if (readCount >= 5)
                {
                    m_impl->m_nebulosityChecked = (nebulosityChecked != 0);
                    m_impl->m_nebulosityDetected = (nebulosityDetected != 0);
                    m_impl->m_nebulosityScore = nebulosityScore;
                    LOG_INFO("Loaded nebulosity data: checked=" + std::string(m_impl->m_nebulosityChecked ? "true" : "false") +
                             ", detected=" + std::string(m_impl->m_nebulosityDetected ? "true" : "false") +
                             ", score=" + std::to_string(m_impl->m_nebulosityScore));
                }
                else
                {
                    // No nebulosity data in the metadata file (old format)
                    m_impl->m_nebulosityChecked = false;
                    m_impl->m_nebulosityDetected = false;
                    m_impl->m_nebulosityScore = 0.0f;
                    LOG_INFO("No nebulosity data found in metadata file");
                }
            }
            // Try to read just the frame count (for backward compatibility with very old format)
            else if (readCount == 1 && frameCount > 0)
            {
                m_impl->m_frameCount = frameCount;
                m_impl->m_originalOrientation = 0.0f; // Default to 0 degrees
                m_impl->m_nebulosityChecked = false;
                m_impl->m_nebulosityDetected = false;
                m_impl->m_nebulosityScore = 0.0f;
                LOG_INFO("Loaded frame count from metadata file: " + std::to_string(m_impl->m_frameCount));
                LOG_INFO("No orientation or nebulosity data found in metadata file, using defaults");
            }
            else
            {
                // If the frame count is invalid, assume it's a single frame
                m_impl->m_frameCount = 1;
                m_impl->m_originalOrientation = 0.0f; // Default to 0 degrees
                m_impl->m_nebulosityChecked = false;
                m_impl->m_nebulosityDetected = false;
                m_impl->m_nebulosityScore = 0.0f;
                LOG_WARN("Invalid frame count in metadata file, assuming frame count: " + std::to_string(m_impl->m_frameCount));
                LOG_WARN("Using default values for orientation and nebulosity data");
            }
            fclose(metadataFile);
        }
        else
        {
            // If no metadata file, assume it's a single frame
            m_impl->m_frameCount = 1;
            m_impl->m_originalOrientation = 0.0f; // Default to 0 degrees
            LOG_WARN("No metadata file found, assuming frame count: " + std::to_string(m_impl->m_frameCount));
            LOG_WARN("No orientation found, assuming 0 degrees");
        }

        // Set the reference frame to the loaded stacked frame
        m_impl->m_referenceFrame = m_impl->m_currentStackedFrame;
        LOG_INFO("Set reference frame to loaded stacked frame");

        // Clear the frames list and add the loaded frame
        // Note: In a more sophisticated implementation, we might want to save and restore
        // the entire frames list, but for now we'll just keep the stacked result
        m_impl->m_frames.clear();
        m_impl->m_frames.push_back(m_impl->m_currentStackedFrame);
        LOG_INFO("Added loaded frame to frames list");

        LOG_INFO("State loaded successfully");
        return true;
    }
    catch (const std::exception &e)
    {
        LOG_ERROR("Exception in loadState method: " + std::string(e.what()));
        m_impl->m_lastError = e.what();
        return false;
    }
}

void LiveStacker::reset()
{
    m_impl->m_frames.clear();
    m_impl->m_frameCount = 0;
    m_impl->m_currentStackedFrame = nullptr;
    m_impl->m_referenceFrame = nullptr;
}

// Private helper methods

std::shared_ptr<ImageBuffer8bit> LiveStacker::applyDarkSubtraction(const std::shared_ptr<ImageBuffer8bit> &frame)
{
    if (!m_impl->m_darkFrame)
    {
        return frame;
    }

    // Check if dimensions match
    if (frame->getWidth() != m_impl->m_darkFrame->getWidth() ||
            frame->getHeight() != m_impl->m_darkFrame->getHeight() ||
            frame->getChannels() != m_impl->m_darkFrame->getChannels())
    {
        throw std::runtime_error("Dark frame dimensions do not match frame dimensions");
    }

    // Create a copy of the frame
    auto result = frame->clone();

    // Subtract dark frame
    for (int y = 0; y < result->getHeight(); ++y)
    {
        for (int x = 0; x < result->getWidth(); ++x)
        {
            for (int c = 0; c < result->getChannels(); ++c)
            {
                uint8_t frameValue = result->getPixel(x, y, c);
                uint8_t darkValue = m_impl->m_darkFrame->getPixel(x, y, c);

                // Avoid underflow
                uint8_t newValue = (darkValue > frameValue) ? 0 : frameValue - darkValue;
                result->setPixel(x, y, newValue, c);
            }
        }
    }

    return result;
}

std::shared_ptr<ImageBuffer8bit> LiveStacker::alignFrame(const std::shared_ptr<ImageBuffer8bit> &frame)
{
    // Create a stacker for alignment
    Stacker8bit stacker;

    // Set the reference frame
    stacker.setReferenceFrame(m_impl->m_referenceFrame);

    // Set alignment parameters
    stacker.setAlignFrames(true);
    stacker.setMaxAlignmentStars(m_impl->m_maxAlignmentStars);

    // Set verbose and benchmark modes
    stacker.setBenchmarkMode(m_impl->m_benchmarkMode);

    // Add the frame to the stacker
    stacker.addFrame(frame);

    // Use stackMean to get the aligned frame
    // Since we only added one frame, this will effectively just return the aligned frame
    LOG_INFO("Performing frame alignment...");
    auto alignedFrame = stacker.stackMean();

    return alignedFrame;
}

void LiveStacker::updateMeanStack(const std::shared_ptr<ImageBuffer8bit> &newFrame)
{
    // Check if dimensions match
    if (m_impl->m_currentStackedFrame->getWidth() != newFrame->getWidth() ||
            m_impl->m_currentStackedFrame->getHeight() != newFrame->getHeight() ||
            m_impl->m_currentStackedFrame->getChannels() != newFrame->getChannels())
    {
        throw std::runtime_error("Frame dimensions do not match stacked frame dimensions");
    }

    // Update the stacked frame with the new frame
    for (int y = 0; y < m_impl->m_currentStackedFrame->getHeight(); ++y)
    {
        for (int x = 0; x < m_impl->m_currentStackedFrame->getWidth(); ++x)
        {
            for (int c = 0; c < m_impl->m_currentStackedFrame->getChannels(); ++c)
            {
                // Calculate the new value as a weighted average
                float currentValue = m_impl->m_currentStackedFrame->getPixel(x, y, c);
                float newValue = newFrame->getPixel(x, y, c);
                float weight = 1.0f / m_impl->m_frameCount;
                float updatedValue = currentValue * (1.0f - weight) + newValue * weight;

                m_impl->m_currentStackedFrame->setPixel(x, y, static_cast<uint8_t>(updatedValue), c);
            }
        }
    }
}

void LiveStacker::reprocessStack()
{
    // Create a stacker
    Stacker8bit stacker;

    // Set stacking parameters
    stacker.setReferenceFrame(m_impl->m_referenceFrame);

    if (m_impl->m_applyDarkSubtraction && m_impl->m_darkFrame)
    {
        stacker.setDarkFrame(m_impl->m_darkFrame);
    }

    stacker.setAlignFrames(m_impl->m_alignFrames);
    stacker.setMaxAlignmentStars(m_impl->m_maxAlignmentStars);

    // Set diagnostic mode and quality threshold
    if (m_impl->m_diagnosticMode)
    {
        stacker.setDiagnosticMode(true, m_impl->m_outputPrefix);
    }
    stacker.setQualityThreshold(m_impl->m_qualityThreshold);

    // Set verbose and benchmark modes
    stacker.setBenchmarkMode(m_impl->m_benchmarkMode);

    // Add frames to the stacker
    for (const auto &frame : m_impl->m_frames)
    {
        stacker.addFrame(frame);
    }

    // Process the stack based on the selected method without post-processing
    switch (m_impl->m_stackingMethod)
    {
        case StackingMethod::Mean:
            m_impl->m_currentStackedFrame = stacker.stackMean();
            break;
        case StackingMethod::Median:
            m_impl->m_currentStackedFrame = stacker.stackMedian();
            break;
        case StackingMethod::SigmaClip:
            m_impl->m_currentStackedFrame = stacker.stackSigmaClip();
            break;
    }
}

std::shared_ptr<ImageBuffer8bit> LiveStacker::applyPostProcessing(const std::shared_ptr<ImageBuffer8bit> &frame)
{
    std::shared_ptr<ImageBuffer8bit> result = frame->clone(); // Clone to avoid modifying the original

    // Create a stacker for post-processing
    Stacker8bit stacker;

    // Only set post-processing parameters that have been explicitly set
    if (m_impl->m_backgroundAdjustmentSet)
    {
        stacker.setBackgroundAdjustment(m_impl->m_backgroundAdjustment);
    }

    // Set cached nebulosity detection results if available
    if (m_impl->m_nebulosityChecked)
    {
        LOG_INFO("Using cached nebulosity detection results: " +
                 std::string(m_impl->m_nebulosityDetected ? "nebulosity detected" : "no nebulosity") +
                 ", score: " + std::to_string(m_impl->m_nebulosityScore));
        stacker.getSettings()->setNebulosityResults(
            m_impl->m_nebulosityChecked,
            m_impl->m_nebulosityDetected,
            m_impl->m_nebulosityScore
        );
    }

    if (m_impl->m_contrastAdjustmentSet)
    {
        stacker.setContrastAdjustment(m_impl->m_contrastAdjustment);
    }

    if (m_impl->m_saturationAdjustmentSet)
    {
        stacker.setSaturationAdjustment(m_impl->m_saturationAdjustment);
    }

    if (m_impl->m_compressionPercentageSet)
    {
        stacker.setCompressionPercentage(m_impl->m_compressionPercentage);
    }

    if (m_impl->m_medianFilterSizeSet)
    {
        stacker.setMedianFilterSize(m_impl->m_medianFilterSize);
    }

    // Set local histogram equalization parameters if enabled and explicitly set
    if (m_impl->m_localHistEqSet && m_impl->m_localHistEqWindowSize > 0)
    {
        stacker.setLocalHistogramEqualizationParams(m_impl->m_localHistEqWindowSize, m_impl->m_localHistEqClipLimit);
    }

    // Set denoising parameters if explicitly set
    if (m_impl->m_denoisingSet && m_impl->m_denoisingMethod > 0)
    {
        auto method = static_cast<StackerSettings::DenoisingMethod>(m_impl->m_denoisingMethod);
        stacker.setDenoising(method, m_impl->m_denoisingStrength);
    }

    // Set verbose and benchmark modes
    stacker.setBenchmarkMode(m_impl->m_benchmarkMode);

    // Apply post-processing
    return stacker.applyPostProcessing(result);
}
