#pragma once

#include "MayaFlux/Kakshya/Utils/ContainerUtils.hpp"
#include "MayaFlux/Yantra/Data/DataIO.hpp"

#include "MayaFlux/Kakshya/Utils/DataUtils.hpp"

namespace MayaFlux::Yantra {

/**
 * @struct DataStructureInfo
 * @brief Metadata about data structure for reconstruction
 */
struct DataStructureInfo {
    Kakshya::DataModality modality = Kakshya::DataModality::UNKNOWN;
    std::vector<Kakshya::DataDimension> dimensions;
    std::type_index original_type = std::type_index(typeid(void));

    DataStructureInfo() = default;
    DataStructureInfo(Kakshya::DataModality mod,
        std::vector<Kakshya::DataDimension> dims,
        std::type_index type)
        : modality(mod)
        , dimensions(std::move(dims))
        , original_type(type)
    {
    }
};

/**
 * @class OperationHelper
 * @brief Universal data conversion helper for all Yantra operations
 *
 * Provides a unified interface for converting between ComputeData types and
 * processing formats. All operations (analyzers, sorters, extractors, transformers)
 * can use this helper to:
 *
 * 1. Convert any ComputeData → DataVariant → std::vector<double>
 * 2. Process data in double format (universal algorithms)
 * 3. Reconstruct results back to target ComputeData types
 *
 * Key Features:
 * - Universal conversion path for all ComputeData types
 * - Structure preservation via metadata
 * - Configurable complex number handling
 * - Lossless conversions (except complex → double)
 * - Thread-safe operation
 */
class MAYAFLUX_API OperationHelper {
public:
    /**
     * @brief Set global complex conversion strategy
     * @param strategy How to convert complex numbers to doubles
     */
    static inline void set_complex_conversion_strategy(Kakshya::ComplexConversionStrategy strategy) { s_complex_strategy = strategy; }

    /**
     * @brief Get current complex conversion strategy
     * @return Current conversion strategy
     */
    static inline Kakshya::ComplexConversionStrategy get_complex_conversion_strategy() { return s_complex_strategy; }

    /**
     * @brief extract numeric data from single-variant types
     * @tparam T ComputeData type
     * @param compute_data Input data
     * @return Span of double data
     */
    template <typename T>
        requires SingleVariant<T>
    static std::span<double> extract_numeric_data(const T& compute_data)
    {
        if constexpr (std::is_same_v<T, Kakshya::DataVariant>) {
            auto const_span = Kakshya::convert_variant<double>(compute_data);
            return std::span<double>(const_cast<double*>(const_span.data()), const_span.size());
        }
        if constexpr (is_eigen_matrix_v<T>) {
            Kakshya::DataVariant variant = create_data_variant_from_eigen(compute_data);
            return Kakshya::convert_variant<double>(variant, s_complex_strategy);
        }

        Kakshya::DataVariant variant { compute_data };
        return Kakshya::convert_variant_to_double(variant, s_complex_strategy);
    }

    /**
     * @brief extract numeric data from multi-variant types
     * @tparam T ComputeData type
     * @param compute_data Input data
     * @return Vector of spans of double data (one per channel/variant)
     */
    template <typename T>
        requires(MultiVariant<T> || EigenMatrixLike<T>)
    static std::vector<std::span<double>> extract_numeric_data(const T& compute_data, bool needs_processig = false)
    {
        if constexpr (std::is_same_v<T, std::vector<Kakshya::DataVariant>>) {
            return Kakshya::convert_variants<double>(compute_data, s_complex_strategy);
        }

        if constexpr (std::is_same_v<T, std::shared_ptr<Kakshya::SignalSourceContainer>>) {
            if (needs_processig) {
                if (compute_data->get_processing_state() != Kakshya::ProcessingState::PROCESSED) {
                    compute_data->process_default();
                    compute_data->update_processing_state(Kakshya::ProcessingState::PROCESSED);
                }
                std::vector<Kakshya::DataVariant> variant = compute_data->get_processed_data();
                return Kakshya::convert_variants<double>(variant, s_complex_strategy);
            }
            std::vector<Kakshya::DataVariant> variant = compute_data->get_data();
            return Kakshya::convert_variants<double>(variant, s_complex_strategy);
        }

        if constexpr (is_eigen_matrix_v<T>)
            return extract_from_eigen_matrix(compute_data);

        return std::vector<std::span<double>> {};
    }

    /**
     * @brief extract numeric data from region-like types
     * @tparam T ComputeData type
     * @param compute_data Input data
     * @param container Container to extract data from
     * @return Vector of spans of double data (one per region/segment)
     */
    template <typename T>
        requires RegionLike<T>
    static std::vector<std::span<double>> extract_numeric_data(
        const T& compute_data,
        const std::shared_ptr<Kakshya::SignalSourceContainer>& container)
    {
        if (!container) {
            error<std::invalid_argument>(Journal::Component::Yantra, Journal::Context::ContainerProcessing, std::source_location::current(), "Null container provided for region extraction");
        }

        if constexpr (std::is_same_v<T, Kakshya::Region>) {
            auto data = container->get_region_data(compute_data);
            return Kakshya::convert_variants<double>(data);

        } else if constexpr (std::is_same_v<T, Kakshya::RegionGroup>) {
            if (compute_data.regions.empty()) {
                error<std::runtime_error>(Journal::Component::Yantra, Journal::Context::ContainerProcessing, std::source_location::current(), "Empty RegionGroup cannot be extracted");
            }
            auto data = container->get_region_group_data(compute_data);
            return Kakshya::convert_variants<double>(data);

        } else if constexpr (std::is_same_v<T, std::vector<Kakshya::RegionSegment>>) {
            if (compute_data.empty()) {
                error<std::runtime_error>(Journal::Component::Yantra, Journal::Context::ContainerProcessing, std::source_location::current(), "Empty RegionSegment vector cannot be extracted");
            }
            auto data = container->get_segments_data(compute_data);
            return Kakshya::convert_variants<double>(data);
        }
    }

    // =========================================================================
    // Native extraction -- no double coercion
    //
    // extract_native_data returns data in the source's own scalar type.
    // Use this for GPU staging, Kinesis::Vision callers, and any path that
    // should not widen to double.
    //
    // For DataVariant / vector<DataVariant> the native type is runtime-
    // determined; these overloads return DataSpanVariant / vector<DataSpanVariant>
    // so the caller can branch on element_type() exactly as with get_frame().
    //
    // For SignalSourceContainer the native type is queried via value_element_type().
    // For Eigen the native type is T::Scalar.
    // Region types are not supported: region data comes out of containers as
    // DataVariant and is already accessible via the DataVariant overload below.
    // =========================================================================

    /**
     * @brief Extract a single DataVariant as a type-erased span without conversion.
     *
     * Returns a DataSpanVariant whose active alternative matches the variant's
     * native element type. No allocation, no coercion.
     * Call element_type() on the result or use as<E>() when E is known statically.
     *
     * @param variant Source variant.
     * @return DataSpanVariant aliasing the variant's internal storage.
     */
    static Kakshya::DataSpanVariant extract_native_data(const Kakshya::DataVariant& variant)
    {
        return std::visit([](const auto& vec) -> Kakshya::DataSpanVariant {
            return std::span<const typename std::decay_t<decltype(vec)>::value_type>(
                vec.data(), vec.size());
        },
            variant);
    }

    /**
     * @brief Extract a vector of DataVariants as type-erased spans without conversion.
     *
     * One DataSpanVariant per channel/variant in the input. Each span aliases
     * the variant's internal storage; no allocation or coercion.
     *
     * @param variants Source variants.
     * @return Per-channel DataSpanVariants.
     */
    static std::vector<Kakshya::DataSpanVariant> extract_native_data(
        const std::vector<Kakshya::DataVariant>& variants)
    {
        std::vector<Kakshya::DataSpanVariant> result;
        result.reserve(variants.size());
        for (const auto& v : variants)
            result.push_back(extract_native_data(v));
        return result;
    }

    /**
     * @brief Extract native-typed channel spans from a SignalSourceContainer.
     *
     * Queries value_element_type() to determine the native scalar, then
     * retrieves each channel's data as a FrameView (which already carries the
     * native span). Returns one DataSpanVariant per channel; no double coercion.
     *
     * @param container Source container. Must have data.
     * @param use_processed If true, reads processed_data; otherwise raw data.
     * @return Per-channel DataSpanVariants in native element type.
     */
    static std::vector<Kakshya::DataSpanVariant> extract_native_data(
        const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
        bool use_processed = false)
    {
        if (!container || !container->has_data())
            return {};

        const auto& variants = use_processed
            ? container->get_processed_data()
            : container->get_data();

        return extract_native_data(variants);
    }

    /**
     * @brief Extract native-typed column spans from any Eigen matrix.
     *
     * Returns one span per column in T::Scalar, aliasing the matrix's own
     * storage via Eigen::Map. The matrix must outlive the returned spans.
     *
     * @tparam EigenMatrix Any Eigen matrix type. T::Scalar is the native type.
     * @param matrix Source matrix.
     * @return Per-column spans in T::Scalar.
     */
    template <typename EigenMatrix>
        requires is_eigen_matrix_v<EigenMatrix>
    static auto extract_native_data(const EigenMatrix& matrix)
        -> std::vector<std::span<const typename EigenMatrix::Scalar>>
    {
        using Scalar = typename EigenMatrix::Scalar;
        std::vector<std::span<const Scalar>> result;
        result.reserve(static_cast<size_t>(matrix.cols()));

        for (int col = 0; col < matrix.cols(); ++col) {
            result.emplace_back(matrix.col(col).data(), static_cast<size_t>(matrix.rows()));
        }
        return result;
    }

    /**
     * @brief Convert ComputeData to DataVariant format
     * @tparam T ComputeData type
     * @param compute_data Input data
     * @return Vector of DataVariant (one per channel/variant)
     */
    template <typename T>
        requires MultiVariant<T>
    static std::vector<Kakshya::DataVariant> to_data_variant(const T& compute_data)
    {
        if constexpr (std::is_same_v<T, std::vector<Kakshya::DataVariant>>) {
            return compute_data;
        }

        if constexpr (std::is_same_v<T, std::shared_ptr<Kakshya::SignalSourceContainer>>) {
            if (compute_data->get_processing_state() == Kakshya::ProcessingState::PROCESSED) {
                return compute_data->get_processed_data();
            }
            return compute_data->get_data();
        }

        if constexpr (is_eigen_matrix_v<T>) {
            return convert_eigen_matrix_to_variant(compute_data);
        }
    }

    /**
     * @brief Convert region-like ComputeData to DataVariant format
     * @tparam T ComputeData type
     * @param compute_data Input data
     * @param container Container to extract data from
     * @return Vector of DataVariant (one per region/segment)
     */
    template <typename T>
        requires RegionLike<T>
    static std::vector<Kakshya::DataVariant> to_data_variant(
        const T& compute_data,
        const std::shared_ptr<Kakshya::SignalSourceContainer>& container)
    {
        if constexpr (std::is_same_v<T, Kakshya::Region>) {
            return container->get_region_data(compute_data);
        } else if constexpr (std::is_same_v<T, Kakshya::RegionGroup>) {
            return container->get_region_group_data(compute_data);
        } else if constexpr (std::is_same_v<T, std::vector<Kakshya::RegionSegment>>) {
            return container->get_segments_data(compute_data);
        }
    }

    /**
     * @brief Populate DataStructureInfo from a Datum without extracting spans.
     * @tparam T OperationReadyData type.
     * @param compute_data Source Datum.
     * @return DataStructureInfo with original_type, dimensions, modality populated.
     */
    template <OperationReadyData T>
    static DataStructureInfo get_structure_info(T& compute_data)
    {
        if constexpr (is_IO<T>::value) {
            DataStructureInfo info {};
            info.original_type = std::type_index(typeid(std::decay_t<decltype(compute_data.data)>));
            info.dimensions = compute_data.dimensions;
            info.modality = compute_data.modality;
            return info;
        } else {
            DataStructureInfo info {};
            info.original_type = std::type_index(typeid(T));
            auto [dims, mod] = infer_structure(compute_data);
            info.dimensions = std::move(dims);
            info.modality = mod;
            return info;
        }
    }

    /**
     * @brief Extract structured double data from Datum container or direct ComputeData with automatic container handling
     * @tparam T OperationReadyData type
     * @param compute_data  or Datum container with data and optional container
     * @return Tuple of [spans, structure_info]
     * @throws std::runtime_error if container required but not provided
     */
    template <OperationReadyData T>
    static std::tuple<std::vector<std::span<double>>, DataStructureInfo>
    extract_structured_double(T& compute_data)
    {
        if constexpr (is_IO<T>::value) {
            DataStructureInfo info {};
            info.original_type = std::type_index(typeid(std::decay_t<decltype(compute_data.data)>));
            info.dimensions = compute_data.dimensions;
            info.modality = compute_data.modality;

            if constexpr (RequiresContainer<std::decay_t<decltype(compute_data.data)>>) {
                if (!compute_data.has_container()) {
                    error<std::runtime_error>(Journal::Component::Yantra, Journal::Context::ContainerProcessing, std::source_location::current(), "Container is required for region-like data extraction but not provided");
                }
                std::vector<std::span<double>> double_data = extract_numeric_data(compute_data.data, compute_data.container.value());
                return std::make_tuple(double_data, info);
            } else {
                std::vector<std::span<double>> double_data = extract_numeric_data(compute_data.data, compute_data.needs_processig());
                return std::make_tuple(double_data, info);
            }
        } else {
            DataStructureInfo info {};
            info.original_type = std::type_index(typeid(T));
            std::vector<std::span<double>> double_data = extract_numeric_data(compute_data);
            auto [dimensions, modality] = infer_structure(compute_data);
            info.dimensions = dimensions;
            info.modality = modality;

            return std::make_tuple(double_data, info);
        }
    }

    /**
     * @brief Extract native-typed channel spans and structure metadata from
     *        a Datum or direct ComputeData, without double coercion.
     *
     * Mirrors extract_structured_double in structure: same DataStructureInfo
     * population, same Datum unwrapping, same container handling for
     * RegionLike types. The only difference is that channel data is returned
     * as DataSpanVariant (type-erased native span) rather than span<double>.
     *
     * Use this as the entry point for Kinesis::Vision operations and any
     * other pipeline that must preserve the source's native element type
     * (uint8_t pixel data, uint16_t depth, float HDR, etc.).
     *
     * For RegionLike data types the region variants are returned as
     * DataSpanVariant via extract_native_data(DataVariant), consistent with
     * the non-region overloads.
     *
     * Callers branch on DataSpanVariant::element_type() or use
     * FrameView::as<E>() / DataSpanVariant::get_if<span<const E>>() when
     * the native type is statically known.
     *
     * @tparam T OperationReadyData type.
     * @param compute_data Datum or direct ComputeData to extract from.
     * @return Tuple of [native channel spans, DataStructureInfo].
     * @throws std::runtime_error if a container is required but absent.
     */
    template <OperationReadyData T>
    static std::tuple<std::vector<Kakshya::DataSpanVariant>, DataStructureInfo>
    extract_structured_native(T& compute_data)
    {
        if constexpr (is_IO<T>::value) {
            DataStructureInfo info {};
            info.original_type = std::type_index(typeid(std::decay_t<decltype(compute_data.data)>));
            info.dimensions = compute_data.dimensions;
            info.modality = compute_data.modality;

            if constexpr (RequiresContainer<std::decay_t<decltype(compute_data.data)>>) {
                if (!compute_data.has_container()) {
                    error<std::runtime_error>(
                        Journal::Component::Yantra,
                        Journal::Context::ContainerProcessing,
                        std::source_location::current(),
                        "Container is required for region-like data extraction but not provided");
                }

                const auto region_variants = [&]() -> std::vector<Kakshya::DataVariant> {
                    if constexpr (std::is_same_v<std::decay_t<decltype(compute_data.data)>, Kakshya::Region>) {
                        return compute_data.container.value()->get_region_data(compute_data.data);
                    } else if constexpr (std::is_same_v<std::decay_t<decltype(compute_data.data)>, Kakshya::RegionGroup>) {
                        return compute_data.container.value()->get_region_group_data(compute_data.data);
                    } else {
                        return compute_data.container.value()->get_segments_data(compute_data.data);
                    }
                }();
                return { extract_native_data(region_variants), info };
            } else {
                auto spans = extract_native_data(compute_data.data);
                return { std::move(spans), info };
            }
        } else {
            DataStructureInfo info {};
            info.original_type = std::type_index(typeid(T));
            auto spans = extract_native_data(compute_data);
            auto [dimensions, modality] = infer_structure(compute_data);
            info.dimensions = dimensions;
            info.modality = modality;
            return { std::move(spans), info };
        }
    }

    /**
     * @brief Reconstruct ComputeData type from double vector and structure info
     * @tparam T Target ComputeData type
     * @param double_data Processed double vector
     * @param structure_info Original structure metadata
     * @return Reconstructed data of type T
     */
    template <ComputeData T>
        requires(!is_IO<T>::value)
    static T reconstruct_from_double(const std::vector<std::vector<double>>& double_data,
        const DataStructureInfo& structure_info)
    {
        if constexpr (std::is_same_v<T, std::vector<std::vector<double>>>) {
            return double_data;
        } else if constexpr (std::is_same_v<T, Eigen::MatrixXd>) {
            return recreate_eigen_matrix(double_data, structure_info);
        } else if constexpr (std::is_same_v<T, std::vector<Kakshya::DataVariant>>) {
            std::vector<Kakshya::DataVariant> variants;
            variants.reserve(double_data.size());
            for (const auto& vec : double_data) {
                variants.emplace_back(vec);
            }
            return variants;
        } else if constexpr (std::is_same_v<T, Kakshya::DataVariant>) {
            auto data = Kakshya::interleave_channels<double>(double_data);
            return reconstruct_data_variant_from_double(data, structure_info);
        } else {
            error<std::runtime_error>(Journal::Component::Yantra, Journal::Context::Runtime, std::source_location::current(), "Reconstruction not implemented for target type {}", structure_info.original_type.name());
            return T {};
        }
    }

    /**
     * @brief Reconstruct Datum type from double vector and structure info
     * @tparam T Target Datum type
     * @param double_data Processed double vector
     * @param structure_info Original structure metadata
     * @return Reconstructed data of type T
     */
    template <typename T>
        requires is_IO<T>::value
    static T reconstruct_from_double(const std::vector<std::vector<double>>& double_data,
        const DataStructureInfo& structure_info)
    {
        using UnderlyingType = std::decay_t<decltype(std::declval<T>().data)>;

        T io_data;
        io_data.dimensions = structure_info.dimensions;
        io_data.modality = structure_info.modality;

        io_data.data = reconstruct_from_double<UnderlyingType>(double_data, structure_info);

        return io_data;
    }

    /**
     * @brief Setup operation buffer from Datum or ComputeData type
     * @tparam T Datum or ComputeData type
     * @param input Datum container or direct ComputeData
     * @param working_buffer Buffer to setup (will be resized)
     * @return Tuple of [working_spans, structure_info]
     */
    template <OperationReadyData T>
    static auto setup_operation_buffer(T& input, std::vector<std::vector<double>>& working_buffer)
    {
        auto [data_spans, structure_info] = extract_structured_double(input);

        if (working_buffer.size() != data_spans.size()) {
            working_buffer.resize(data_spans.size());
        }

        std::vector<std::span<double>> working_spans(working_buffer.size());

        for (size_t i = 0; i < data_spans.size(); i++) {
            working_buffer[i].resize(data_spans[i].size());
            std::ranges::copy(data_spans[i], working_buffer[i].begin());
            working_spans[i] = std::span<double>(working_buffer[i].data(), working_buffer[i].size());
        }

        return std::make_tuple(working_spans, structure_info);
    }

private:
    static inline Kakshya::ComplexConversionStrategy s_complex_strategy = Kakshya::ComplexConversionStrategy::MAGNITUDE;

    /**
     * @brief Create DataVariant from Eigen matrix/vector
     */
    template <typename EigenType>
    static Kakshya::DataVariant create_data_variant_from_eigen(const EigenType& eigen_data)
    {
        std::vector<double> flat_data;

        if constexpr (EigenType::IsVectorAtCompileTime) {
            flat_data.resize(eigen_data.size());
            for (int i = 0; i < eigen_data.size(); ++i) {
                flat_data[i] = static_cast<double>(eigen_data(i));
            }
        } else {
            flat_data.resize(eigen_data.size());
            int idx = 0;
            for (int i = 0; i < eigen_data.rows(); ++i) {
                for (int j = 0; j < eigen_data.cols(); ++j) {
                    flat_data[idx++] = static_cast<double>(eigen_data(i, j));
                }
            }
        }

        return Kakshya::DataVariant { flat_data };
    }

    /**
     * @brief Infer data structure from ComputeData type
     * @tparam T ComputeData type
     * @param compute_data Input data
     * @return Pair of (dimensions, modality)
     */
    template <typename EigenMatrix>
    static std::vector<std::span<double>> extract_from_eigen_matrix(const EigenMatrix& matrix)
    {
        static thread_local std::vector<std::vector<double>> columns;
        columns.clear();
        columns.resize(matrix.cols());
        std::vector<std::span<double>> spans;
        spans.reserve(matrix.cols());

        for (int col = 0; col < matrix.cols(); ++col) {
            columns[col].resize(matrix.rows());
            for (int row = 0; row < matrix.rows(); ++row) {
                columns[col][row] = static_cast<double>(matrix(row, col));
            }
            spans.emplace_back(columns[col].data(), columns[col].size());
        }
        return spans;
    }

    /**
     * @brief Extract data from Eigen vector to double span
     */
    /* template <typename EigenVector>
    static std::span<double> extract_from_eigen_vector(const EigenVector& vec)
    {
        thread_local std::vector<double> buffer;
        buffer.clear();
        buffer.resize(vec.size());

        for (int i = 0; i < vec.size(); ++i) {
            buffer[i] = static_cast<double>(vec(i));
        }
        return { buffer.data(), buffer.size() };
    } */

    /**
     * @brief Convert Eigen matrix to DataVariant format
     */
    template <typename EigenMatrix>
    static std::vector<Kakshya::DataVariant> convert_eigen_matrix_to_variant(const EigenMatrix& matrix)
    {
        std::vector<Kakshya::DataVariant> columns(matrix.cols());

        for (int col = 0; col < matrix.cols(); ++col) {
            auto row_indices = std::views::iota(0, matrix.rows());
            auto col_data = row_indices
                | std::views::transform([&](int row) { return static_cast<double>(matrix(row, col)); });
            columns[col] = { std::vector<double>(col_data.begin(), col_data.end()) };
        }
        return columns;
    }

    /**
     * @brief Infer data structure from ComputeData type
     * @tparam T ComputeData type
     * @param compute_data Input data
     * @return Pair of (dimensions, modality)
     */
    template <typename T>
    static Eigen::MatrixXd create_eigen_matrix(const std::vector<std::vector<T>>& columns)
    {
        if (columns.empty()) {
            return {};
        }

        int rows = columns[0].size();
        int cols = columns.size();

        for (const auto& col : columns) {
            if (col.size() != rows) {
                error<std::invalid_argument>(Journal::Component::Yantra, Journal::Context::Runtime, std::source_location::current(), "All columns must have same size");
            }
        }

        Eigen::MatrixXd matrix(rows, cols);
        for (int col = 0; col < cols; ++col) {
            for (int row = 0; row < rows; ++row) {
                matrix(row, col) = static_cast<double>(columns[col][row]);
            }
        }
        return matrix;
    }

    /**
     * @brief Create Eigen matrix from spans
     */
    template <typename T>
    static Eigen::MatrixXd create_eigen_matrix(const std::vector<std::span<const T>>& spans)
    {
        if (spans.empty()) {
            return {};
        }

        int rows = spans[0].size();
        int cols = spans.size();

        for (const auto& span : spans) {
            if (span.size() != rows) {
                error<std::invalid_argument>(Journal::Component::Yantra, Journal::Context::Runtime, std::source_location::current(), "All spans must have same size");
            }
        }

        Eigen::MatrixXd matrix(rows, cols);
        for (int col = 0; col < cols; ++col) {
            for (int row = 0; row < rows; ++row) {
                matrix(row, col) = static_cast<double>(spans[col][row]);
            }
        }
        return matrix;
    }

    /**
     * @brief Infer data structure from ComputeData type
     * @tparam T ComputeData type
     * @param compute_data Input data
     * @param container Optional container for region-like types
     * @return Pair of (dimensions, modality)
     */
    static Eigen::MatrixXd recreate_eigen_matrix(
        const std::vector<std::vector<double>>& columns,
        const DataStructureInfo& structure_info);

    /**
     * @brief Infer data structure from ComputeData type
     * @tparam T ComputeData type
     * @param compute_data Input data
     * @param container Optional container for region-like types
     * @return Pair of (dimensions, modality)
     */
    static Eigen::MatrixXd recreate_eigen_matrix(
        const std::vector<std::span<const double>>& spans,
        const DataStructureInfo& structure_info);

    /**
     * @brief Reconstruct DataVariant from double data and structure info
     */
    static Kakshya::DataVariant reconstruct_data_variant_from_double(const std::vector<double>& double_data,
        const DataStructureInfo& structure_info);
};

} // namespace MayaFlux::Yantra
