//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2025 California Institute of Technology
//
// LDASTools frameCPP is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools frameCPP is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

/**
 * @file FrVectCompressionMapping.hh
 * @brief Adjacent-Version Compression Mode Conversion Mappings for FrVect
 *
 * This file contains data-driven mapping tables for converting FrVect compression
 * modes between ADJACENT frame specification versions. The tables use an
 * ITERATIVE CONVERSION APPROACH where conversion between non-adjacent versions
 * is done by chaining adjacent conversions.
 *
 * =============================================================================
 * DESIGN PHILOSOPHY: ADJACENT-VERSION MAPPINGS
 * =============================================================================
 *
 * Instead of having direct mappings like v9→v3, we use ADJACENT-VERSION mappings:
 *   - v9 ↔ v8 (one step)
 *   - v8 ↔ v5 (one step, covers v5/v6/v7)
 *   - v5 ↔ v4 (one step)
 *   - v4 ↔ v3 (one step)
 *
 * **Benefits**:
 *   1. Easier to verify against frame specification documents
 *   2. Smaller, simpler tables - each table only handles ONE version transition
 *   3. Self-documenting - comments can reference specific spec sections
 *   4. More maintainable - changes localized to specific version differences
 *
 * **Example Conversion Path (v9 → v3)**:
 *   v9 data → [v9→v8 table] → v8 mode → [v8→v5 table] → v5 mode →
 *   [v5→v4 table] → v4 mode → [v4→v3 table] → v3 mode
 *
 * =============================================================================
 * HOW TO MODIFY COMPRESSION MAPPINGS:
 * =============================================================================
 *
 * 1. **Find the appropriate ADJACENT mapping table**:
 *    - V9_TO_V8_MAPPINGS  : v9 → v8 (down one version)
 *    - V8_TO_V5_MAPPINGS  : v8 → v5 (down one version)
 *    - V5_TO_V4_MAPPINGS  : v5 → v4 (down one version)
 *    - V4_TO_V3_MAPPINGS  : v4 → v3 (down one version)
 *    - V3_TO_V4_MAPPINGS  : v3 → v4 (up one version)
 *    - V4_TO_V5_MAPPINGS  : v4 → v5 (up one version)
 *    - V5_TO_V8_MAPPINGS  : v5 → v8 (up one version)
 *    - V8_TO_V9_MAPPINGS  : v8 → v9 (up one version)
 *
 * 2. **Verify against frame specification document**:
 *    Each table should correspond to documented differences between those two versions.
 *
 * 3. **Add or modify mapping entries** using this structure:
 *    {source_mode, data_type, target_mode, requires_recompression}
 *
 * 4. **Example - Target version doesn't support a compression mode**:
 *    ```cpp
 *    // v8 has ZERO_SUPPRESS_WORD_8 but v5 doesn't support it - recompress to GZIP
 *    {Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_8, FR_VECT_ANY,
 *     Data::v5::BIGENDIAN_GZIP, true},
 *    ```
 *
 * 5. **Test your changes**:
 *    ```bash
 *    cd build
 *    ctest -R downconvert
 *    ```
 *
 * =============================================================================
 * MAPPING STRUCTURE EXPLANATION:
 * =============================================================================
 *
 * - source_mode           : The compression mode in the source version
 * - data_type             : The FrVect data type (FR_VECT_ANY means all types)
 * - target_mode           : The compression mode in the target version
 * - requires_recompression: If true, data must be uncompressed and recompressed
 *                          (e.g., WORD_8 → GZIP when target doesn't support WORD_8)
 */

#ifndef FrameCPP_UNIVERSAL_IMPL_FrVectCompressionMapping_HH
#define FrameCPP_UNIVERSAL_IMPL_FrVectCompressionMapping_HH

#include "framecpp/Universal/impl/FrVectData.hh"

namespace FrameCPP
{
    namespace Universal
    {
        namespace FrVectImpl
        {
            namespace CompressionMapping
            {
                // Bring Data class into scope for accessing nested types and enums
                using FrVectImpl::Data;

                //=============================================================
                // Special Data Type Constants
                //=============================================================

                /**
                 * @brief Special value indicating mapping applies to all data types
                 */
                const Data::data_types_type FR_VECT_ANY = static_cast< Data::data_types_type >( static_cast< INT_2U >( -1 ) );

                //=============================================================
                // Compression Mode Mapping Structure
                //=============================================================

                /**
                 * @brief Structure defining a compression mode conversion mapping
                 */
                struct CompressionModeMapping
                {
                    INT_2U               source_mode;             ///< Source compression mode
                    Data::data_types_type data_type;               ///< Data type (FR_VECT_ANY = all)
                    INT_2U               target_mode;             ///< Target compression mode
                    bool                 requires_recompression;  ///< Needs uncompress/recompress cycle
                };

                //=============================================================
                // V9 ↔ V8 Adjacent-Version Mappings
                //=============================================================

                /**
                 * @brief Compression mode mappings from v9 to v8 (ONE VERSION STEP)
                 *
                 * V8 differences from V9:
                 * - Uses ZERO_SUPPRESS_WORD_2/4/8 instead of unified ZERO_SUPPRESS
                 * - No ZSTD support (must fall back to GZIP with recompression)
                 * - No DIFF_ZSTD support (must fall back to DIFF_GZIP with recompression)
                 */
                constexpr CompressionModeMapping V9_TO_V8_MAPPINGS[] = {
                    // RAW modes (direct mapping)
                    { Data::v9::BIGENDIAN_RAW, FR_VECT_ANY, Data::v8::BIGENDIAN_RAW, false },
                    { Data::v9::LITTLEENDIAN_RAW, FR_VECT_ANY, Data::v8::LITTLEENDIAN_RAW, false },

                    // GZIP modes (direct mapping)
                    { Data::v9::BIGENDIAN_GZIP, FR_VECT_ANY, Data::v8::BIGENDIAN_GZIP, false },
                    { Data::v9::LITTLEENDIAN_GZIP, FR_VECT_ANY, Data::v8::LITTLEENDIAN_GZIP, false },

                    // DIFF_GZIP modes (direct mapping)
                    { Data::v9::BIGENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v8::BIGENDIAN_DIFF_GZIP, false },
                    { Data::v9::LITTLEENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v8::LITTLEENDIAN_DIFF_GZIP, false },

                    // ZERO_SUPPRESS → ZERO_SUPPRESS_WORD_2 (2-byte types)
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_2S, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_2, false },
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_2U, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_2, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_2S, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_2, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_2U, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_2, false },

                    // ZERO_SUPPRESS → ZERO_SUPPRESS_WORD_4 (4-byte types + FR_VECT_8C)
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_4S, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_4, false },
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_4U, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_4, false },
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_4R, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_4, false },
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_8C, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_4, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_4S, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_4, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_4U, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_4, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_4R, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_4, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_8C, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_4, false },

                    // ZERO_SUPPRESS → ZERO_SUPPRESS_WORD_8 (8-byte types + FR_VECT_16C)
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_8S, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_8, false },
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_8U, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_8, false },
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_8R, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_8, false },
                    { Data::v9::BIGENDIAN_ZERO_SUPPRESS, Data::FR_VECT_16C, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_8, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_8S, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_8, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_8U, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_8, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_8R, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_8, false },
                    { Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, Data::FR_VECT_16C, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_8, false },

                    // ZSTD → GZIP (v8 doesn't support ZSTD, REQUIRES RECOMPRESSION)
                    { Data::v9::BIGENDIAN_ZSTD, FR_VECT_ANY, Data::v8::BIGENDIAN_GZIP, true },
                    { Data::v9::LITTLEENDIAN_ZSTD, FR_VECT_ANY, Data::v8::LITTLEENDIAN_GZIP, true },

                    // DIFF_ZSTD → DIFF_GZIP (v8 doesn't support DIFF_ZSTD, REQUIRES RECOMPRESSION)
                    { Data::v9::BIGENDIAN_DIFF_ZSTD, FR_VECT_ANY, Data::v8::BIGENDIAN_DIFF_GZIP, true },
                    { Data::v9::LITTLEENDIAN_DIFF_ZSTD, FR_VECT_ANY, Data::v8::LITTLEENDIAN_DIFF_GZIP, true },
                };

                /**
                 * @brief Compression mode mappings from v8 to v9 (ONE VERSION STEP)
                 *
                 * V8 to V9 is straightforward:
                 * - All ZERO_SUPPRESS_WORD_* variants map to unified ZERO_SUPPRESS
                 */
                constexpr CompressionModeMapping V8_TO_V9_MAPPINGS[] = {
                    // RAW modes (direct mapping)
                    { Data::v8::BIGENDIAN_RAW, FR_VECT_ANY, Data::v9::BIGENDIAN_RAW, false },
                    { Data::v8::LITTLEENDIAN_RAW, FR_VECT_ANY, Data::v9::LITTLEENDIAN_RAW, false },

                    // GZIP modes (direct mapping)
                    { Data::v8::BIGENDIAN_GZIP, FR_VECT_ANY, Data::v9::BIGENDIAN_GZIP, false },
                    { Data::v8::LITTLEENDIAN_GZIP, FR_VECT_ANY, Data::v9::LITTLEENDIAN_GZIP, false },

                    // DIFF_GZIP modes (direct mapping)
                    { Data::v8::BIGENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v9::BIGENDIAN_DIFF_GZIP, false },
                    { Data::v8::LITTLEENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v9::LITTLEENDIAN_DIFF_GZIP, false },

                    // ZERO_SUPPRESS_WORD_2 → ZERO_SUPPRESS
                    { Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_2, FR_VECT_ANY, Data::v9::BIGENDIAN_ZERO_SUPPRESS, false },
                    { Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_2, FR_VECT_ANY, Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, false },

                    // ZERO_SUPPRESS_WORD_4 → ZERO_SUPPRESS
                    { Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_4, FR_VECT_ANY, Data::v9::BIGENDIAN_ZERO_SUPPRESS, false },
                    { Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_4, FR_VECT_ANY, Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, false },

                    // ZERO_SUPPRESS_WORD_8 → ZERO_SUPPRESS
                    { Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_8, FR_VECT_ANY, Data::v9::BIGENDIAN_ZERO_SUPPRESS, false },
                    { Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_8, FR_VECT_ANY, Data::v9::LITTLEENDIAN_ZERO_SUPPRESS, false },
                };

                //=============================================================
                // V8 ↔ V5 Adjacent-Version Mappings
                //=============================================================

                /**
                 * @brief Compression mode mappings from v8 to v5 (ONE VERSION STEP)
                 *
                 * V5 differences from V8:
                 * - V8 ZERO_SUPPRESS_WORD_2 (0x05) = V5 ZERO_SUPPRESS_SHORT (0x05) - SAME VALUE
                 * - V8 ZERO_SUPPRESS_WORD_4 (0x08) = V5 ZERO_SUPPRESS_INT_FLOAT (0x08) - SAME VALUE
                 * - V8 ZERO_SUPPRESS_WORD_8 (0x0A) not supported in v5, REQUIRES RECOMPRESSION to GZIP
                 */
                constexpr CompressionModeMapping V8_TO_V5_MAPPINGS[] = {
                    // RAW modes (direct mapping)
                    { Data::v8::BIGENDIAN_RAW, FR_VECT_ANY, Data::v5::BIGENDIAN_RAW, false },
                    { Data::v8::LITTLEENDIAN_RAW, FR_VECT_ANY, Data::v5::LITTLEENDIAN_RAW, false },

                    // GZIP modes (direct mapping)
                    { Data::v8::BIGENDIAN_GZIP, FR_VECT_ANY, Data::v5::BIGENDIAN_GZIP, false },
                    { Data::v8::LITTLEENDIAN_GZIP, FR_VECT_ANY, Data::v5::LITTLEENDIAN_GZIP, false },

                    // DIFF_GZIP modes (direct mapping)
                    { Data::v8::BIGENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v5::BIGENDIAN_DIFF_GZIP, false },
                    { Data::v8::LITTLEENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v5::LITTLEENDIAN_DIFF_GZIP, false },

                    // ZERO_SUPPRESS_WORD_2 → ZERO_SUPPRESS_SHORT (same value 0x05, no recompression)
                    { Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_2, FR_VECT_ANY, Data::v5::BIGENDIAN_ZERO_SUPPRESS_SHORT, false },
                    { Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_2, FR_VECT_ANY, Data::v5::LITTLEENDIAN_ZERO_SUPPRESS_SHORT, false },

                    // ZERO_SUPPRESS_WORD_4 → ZERO_SUPPRESS_INT_FLOAT (same value 0x08, no recompression)
                    { Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_4, FR_VECT_ANY, Data::v5::BIGENDIAN_ZERO_SUPPRESS_INT_FLOAT, false },
                    { Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_4, FR_VECT_ANY, Data::v5::LITTLEENDIAN_ZERO_SUPPRESS_INT_FLOAT, false },

                    // ZERO_SUPPRESS_WORD_8 → GZIP (v5 doesn't support 8-byte ZERO_SUPPRESS, REQUIRES RECOMPRESSION)
                    { Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_8, FR_VECT_ANY, Data::v5::BIGENDIAN_GZIP, true },
                    { Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_8, FR_VECT_ANY, Data::v5::LITTLEENDIAN_GZIP, true },
                };

                /**
                 * @brief Compression mode mappings from v5 to v8 (ONE VERSION STEP)
                 *
                 * V5 to V8 is straightforward:
                 * - V5 ZERO_SUPPRESS_SHORT (0x05) = V8 ZERO_SUPPRESS_WORD_2 (0x05) - SAME VALUE
                 * - V5 ZERO_SUPPRESS_INT_FLOAT (0x08) = V8 ZERO_SUPPRESS_WORD_4 (0x08) - SAME VALUE
                 */
                constexpr CompressionModeMapping V5_TO_V8_MAPPINGS[] = {
                    // RAW modes (direct mapping)
                    { Data::v5::BIGENDIAN_RAW, FR_VECT_ANY, Data::v8::BIGENDIAN_RAW, false },
                    { Data::v5::LITTLEENDIAN_RAW, FR_VECT_ANY, Data::v8::LITTLEENDIAN_RAW, false },

                    // GZIP modes (direct mapping)
                    { Data::v5::BIGENDIAN_GZIP, FR_VECT_ANY, Data::v8::BIGENDIAN_GZIP, false },
                    { Data::v5::LITTLEENDIAN_GZIP, FR_VECT_ANY, Data::v8::LITTLEENDIAN_GZIP, false },

                    // DIFF_GZIP modes (direct mapping)
                    { Data::v5::BIGENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v8::BIGENDIAN_DIFF_GZIP, false },
                    { Data::v5::LITTLEENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v8::LITTLEENDIAN_DIFF_GZIP, false },

                    // ZERO_SUPPRESS_SHORT → ZERO_SUPPRESS_WORD_2 (same value 0x05, no recompression)
                    { Data::v5::BIGENDIAN_ZERO_SUPPRESS_SHORT, FR_VECT_ANY, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_2, false },
                    { Data::v5::LITTLEENDIAN_ZERO_SUPPRESS_SHORT, FR_VECT_ANY, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_2, false },

                    // ZERO_SUPPRESS_INT_FLOAT → ZERO_SUPPRESS_WORD_4 (same value 0x08, no recompression)
                    { Data::v5::BIGENDIAN_ZERO_SUPPRESS_INT_FLOAT, FR_VECT_ANY, Data::v8::BIGENDIAN_ZERO_SUPPRESS_WORD_4, false },
                    { Data::v5::LITTLEENDIAN_ZERO_SUPPRESS_INT_FLOAT, FR_VECT_ANY, Data::v8::LITTLEENDIAN_ZERO_SUPPRESS_WORD_4, false },
                };

                //=============================================================
                // V5 ↔ V4 Adjacent-Version Mappings
                //=============================================================

                /**
                 * @brief Compression mode mappings from v5 to v4 (ONE VERSION STEP)
                 *
                 * V4 differences from V5:
                 * - V5 ZERO_SUPPRESS_SHORT (0x05) = V4 ZERO_SUPPRESS_SHORT (0x05) - SAME VALUE
                 * - V5 ZERO_SUPPRESS_INT_FLOAT (0x08) not supported in v4, REQUIRES RECOMPRESSION to GZIP
                 */
                constexpr CompressionModeMapping V5_TO_V4_MAPPINGS[] = {
                    // RAW modes (direct mapping)
                    { Data::v5::BIGENDIAN_RAW, FR_VECT_ANY, Data::v4::BIGENDIAN_RAW, false },
                    { Data::v5::LITTLEENDIAN_RAW, FR_VECT_ANY, Data::v4::LITTLEENDIAN_RAW, false },

                    // GZIP modes (direct mapping)
                    { Data::v5::BIGENDIAN_GZIP, FR_VECT_ANY, Data::v4::BIGENDIAN_GZIP, false },
                    { Data::v5::LITTLEENDIAN_GZIP, FR_VECT_ANY, Data::v4::LITTLEENDIAN_GZIP, false },

                    // DIFF_GZIP modes (direct mapping)
                    { Data::v5::BIGENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v4::BIGENDIAN_DIFF_GZIP, false },
                    { Data::v5::LITTLEENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v4::LITTLEENDIAN_DIFF_GZIP, false },

                    // ZERO_SUPPRESS_SHORT → ZERO_SUPPRESS_SHORT (same value 0x05, no recompression)
                    { Data::v5::BIGENDIAN_ZERO_SUPPRESS_SHORT, FR_VECT_ANY, Data::v4::BIGENDIAN_ZERO_SUPPRESS_SHORT, false },
                    { Data::v5::LITTLEENDIAN_ZERO_SUPPRESS_SHORT, FR_VECT_ANY, Data::v4::LITTLEENDIAN_ZERO_SUPPRESS_SHORT, false },

                    // ZERO_SUPPRESS_INT_FLOAT → GZIP (v4 doesn't support 4-byte ZERO_SUPPRESS, REQUIRES RECOMPRESSION)
                    { Data::v5::BIGENDIAN_ZERO_SUPPRESS_INT_FLOAT, FR_VECT_ANY, Data::v4::BIGENDIAN_GZIP, true },
                    { Data::v5::LITTLEENDIAN_ZERO_SUPPRESS_INT_FLOAT, FR_VECT_ANY, Data::v4::LITTLEENDIAN_GZIP, true },
                };

                /**
                 * @brief Compression mode mappings from v4 to v5 (ONE VERSION STEP)
                 *
                 * V4 to V5 is straightforward:
                 * - V4 ZERO_SUPPRESS_SHORT (0x05) = V5 ZERO_SUPPRESS_SHORT (0x05) - SAME VALUE
                 */
                constexpr CompressionModeMapping V4_TO_V5_MAPPINGS[] = {
                    // RAW modes (direct mapping)
                    { Data::v4::BIGENDIAN_RAW, FR_VECT_ANY, Data::v5::BIGENDIAN_RAW, false },
                    { Data::v4::LITTLEENDIAN_RAW, FR_VECT_ANY, Data::v5::LITTLEENDIAN_RAW, false },

                    // GZIP modes (direct mapping)
                    { Data::v4::BIGENDIAN_GZIP, FR_VECT_ANY, Data::v5::BIGENDIAN_GZIP, false },
                    { Data::v4::LITTLEENDIAN_GZIP, FR_VECT_ANY, Data::v5::LITTLEENDIAN_GZIP, false },

                    // DIFF_GZIP modes (direct mapping)
                    { Data::v4::BIGENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v5::BIGENDIAN_DIFF_GZIP, false },
                    { Data::v4::LITTLEENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v5::LITTLEENDIAN_DIFF_GZIP, false },

                    // ZERO_SUPPRESS_SHORT → ZERO_SUPPRESS_SHORT (same value 0x05, no recompression)
                    { Data::v4::BIGENDIAN_ZERO_SUPPRESS_SHORT, FR_VECT_ANY, Data::v5::BIGENDIAN_ZERO_SUPPRESS_SHORT, false },
                    { Data::v4::LITTLEENDIAN_ZERO_SUPPRESS_SHORT, FR_VECT_ANY, Data::v5::LITTLEENDIAN_ZERO_SUPPRESS_SHORT, false },
                };

                //=============================================================
                // V4 ↔ V3 Adjacent-Version Mappings
                //=============================================================

                /**
                 * @brief Compression mode mappings from v4 to v3 (ONE VERSION STEP)
                 *
                 * V3 differences from V4:
                 * - V4 ZERO_SUPPRESS_SHORT not supported in v3, REQUIRES RECOMPRESSION to GZIP
                 */
                constexpr CompressionModeMapping V4_TO_V3_MAPPINGS[] = {
                    // RAW modes (direct mapping)
                    { Data::v4::BIGENDIAN_RAW, FR_VECT_ANY, Data::v3::BIGENDIAN_RAW, false },
                    { Data::v4::LITTLEENDIAN_RAW, FR_VECT_ANY, Data::v3::LITTLEENDIAN_RAW, false },

                    // GZIP modes (direct mapping)
                    { Data::v4::BIGENDIAN_GZIP, FR_VECT_ANY, Data::v3::BIGENDIAN_GZIP, false },
                    { Data::v4::LITTLEENDIAN_GZIP, FR_VECT_ANY, Data::v3::LITTLEENDIAN_GZIP, false },

                    // DIFF_GZIP modes (direct mapping)
                    { Data::v4::BIGENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v3::BIGENDIAN_DIFF_GZIP, false },
                    { Data::v4::LITTLEENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v3::LITTLEENDIAN_DIFF_GZIP, false },

                    // ZERO_SUPPRESS_SHORT → GZIP (v3 doesn't support ZERO_SUPPRESS at all, REQUIRES RECOMPRESSION)
                    { Data::v4::BIGENDIAN_ZERO_SUPPRESS_SHORT, FR_VECT_ANY, Data::v3::BIGENDIAN_GZIP, true },
                    { Data::v4::LITTLEENDIAN_ZERO_SUPPRESS_SHORT, FR_VECT_ANY, Data::v3::LITTLEENDIAN_GZIP, true },
                };

                /**
                 * @brief Compression mode mappings from v3 to v4 (ONE VERSION STEP)
                 *
                 * V3 to V4 is straightforward - all v3 modes exist in v4 with same values
                 */
                constexpr CompressionModeMapping V3_TO_V4_MAPPINGS[] = {
                    // RAW modes (direct mapping)
                    { Data::v3::BIGENDIAN_RAW, FR_VECT_ANY, Data::v4::BIGENDIAN_RAW, false },
                    { Data::v3::LITTLEENDIAN_RAW, FR_VECT_ANY, Data::v4::LITTLEENDIAN_RAW, false },

                    // GZIP modes (direct mapping)
                    { Data::v3::BIGENDIAN_GZIP, FR_VECT_ANY, Data::v4::BIGENDIAN_GZIP, false },
                    { Data::v3::LITTLEENDIAN_GZIP, FR_VECT_ANY, Data::v4::LITTLEENDIAN_GZIP, false },

                    // DIFF_GZIP modes (direct mapping)
                    { Data::v3::BIGENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v4::BIGENDIAN_DIFF_GZIP, false },
                    { Data::v3::LITTLEENDIAN_DIFF_GZIP, FR_VECT_ANY, Data::v4::LITTLEENDIAN_DIFF_GZIP, false },
                };

                //=============================================================
                // Helper Functions
                //=============================================================

                /**
                 * @brief Find compression mode mapping for given parameters
                 *
                 * @param table Mapping table to search
                 * @param table_size Size of the mapping table
                 * @param source_mode Source compression mode
                 * @param data_type FrVect data type
                 *
                 * @return Pointer to matching mapping or nullptr if not found
                 *
                 * The function searches for a matching entry first by exact data type,
                 * then falls back to FR_VECT_ANY if no exact match is found.
                 */
                inline const CompressionModeMapping*
                find_mapping( const CompressionModeMapping* table,
                              size_t                        table_size,
                              INT_2U                        source_mode,
                              Data::data_types_type         data_type )
                {
                    // First pass: Look for exact data type match
                    for ( size_t i = 0; i < table_size; ++i )
                    {
                        if ( table[ i ].source_mode == source_mode &&
                             table[ i ].data_type == data_type )
                        {
                            return &table[ i ];
                        }
                    }

                    // Second pass: Look for FR_VECT_ANY (applies to all types)
                    for ( size_t i = 0; i < table_size; ++i )
                    {
                        if ( table[ i ].source_mode == source_mode &&
                             table[ i ].data_type == FR_VECT_ANY )
                        {
                            return &table[ i ];
                        }
                    }

                    return nullptr;
                }

                /**
                 * @brief Get size of a mapping table
                 */
                template < size_t N >
                constexpr size_t
                table_size( const CompressionModeMapping ( &)[ N ] )
                {
                    return N;
                }

                //=============================================================
                // Adjacent-Version Table Selector Templates
                //=============================================================
                // These templates select mapping tables for ADJACENT versions only.
                // For non-adjacent versions, use iterative conversion in compressDemote()/compressPromote().
                //=============================================================

                /**
                 * @brief Template for selecting appropriate ADJACENT-VERSION mapping table
                 *
                 * This template is specialized ONLY for ADJACENT version combinations.
                 * Non-adjacent conversions must use iterative conversion.
                 *
                 * @tparam SourceVersion Source frame specification version
                 * @tparam TargetVersion Target frame specification version (ADJACENT TO SOURCE)
                 *
                 * When v10 is added, add new specializations:
                 * - MappingTableSelector<Data::v10, Data::v9>
                 * - MappingTableSelector<Data::v9, Data::v10>
                 *
                 * NOTE: Base template provides default implementation with nullptr table.
                 * For non-adjacent versions, use VersionConverter which chains adjacent conversions.
                 */
                template < typename SourceVersion, typename TargetVersion >
                struct MappingTableSelector
                {
                    static constexpr const CompressionModeMapping* table = nullptr;
                    static constexpr size_t size = 0;
                };

                // Downgrade specializations (one version step down)
                template <>
                struct MappingTableSelector< Data::v9, Data::v8 >
                {
                    static constexpr const CompressionModeMapping* table = V9_TO_V8_MAPPINGS;
                    static constexpr size_t size = sizeof( V9_TO_V8_MAPPINGS ) / sizeof( CompressionModeMapping );
                };

                template <>
                struct MappingTableSelector< Data::v8, Data::v5 >
                {
                    static constexpr const CompressionModeMapping* table = V8_TO_V5_MAPPINGS;
                    static constexpr size_t size = sizeof( V8_TO_V5_MAPPINGS ) / sizeof( CompressionModeMapping );
                };

                template <>
                struct MappingTableSelector< Data::v5, Data::v4 >
                {
                    static constexpr const CompressionModeMapping* table = V5_TO_V4_MAPPINGS;
                    static constexpr size_t size = sizeof( V5_TO_V4_MAPPINGS ) / sizeof( CompressionModeMapping );
                };

                template <>
                struct MappingTableSelector< Data::v4, Data::v3 >
                {
                    static constexpr const CompressionModeMapping* table = V4_TO_V3_MAPPINGS;
                    static constexpr size_t size = sizeof( V4_TO_V3_MAPPINGS ) / sizeof( CompressionModeMapping );
                };

                // Upgrade specializations (one version step up)
                template <>
                struct MappingTableSelector< Data::v3, Data::v4 >
                {
                    static constexpr const CompressionModeMapping* table = V3_TO_V4_MAPPINGS;
                    static constexpr size_t size = sizeof( V3_TO_V4_MAPPINGS ) / sizeof( CompressionModeMapping );
                };

                template <>
                struct MappingTableSelector< Data::v4, Data::v5 >
                {
                    static constexpr const CompressionModeMapping* table = V4_TO_V5_MAPPINGS;
                    static constexpr size_t size = sizeof( V4_TO_V5_MAPPINGS ) / sizeof( CompressionModeMapping );
                };

                template <>
                struct MappingTableSelector< Data::v5, Data::v8 >
                {
                    static constexpr const CompressionModeMapping* table = V5_TO_V8_MAPPINGS;
                    static constexpr size_t size = sizeof( V5_TO_V8_MAPPINGS ) / sizeof( CompressionModeMapping );
                };

                template <>
                struct MappingTableSelector< Data::v8, Data::v9 >
                {
                    static constexpr const CompressionModeMapping* table = V8_TO_V9_MAPPINGS;
                    static constexpr size_t size = sizeof( V8_TO_V9_MAPPINGS ) / sizeof( CompressionModeMapping );
                };

                //=================================================================
                // VersionConverter - Iterative Conversion for Non-Adjacent Versions
                //=================================================================
                /**
                 * @brief Helper template for converting compression schemes between versions
                 *
                 * This template handles both direct (adjacent) and chained (non-adjacent)
                 * version conversions by automatically detecting whether a direct mapping
                 * exists and falling back to iterative chaining when needed.
                 *
                 * Base template: Tries direct conversion using MappingTableSelector.
                 * Specializations: Implement chained conversion for non-adjacent version pairs.
                 *
                 * @tparam SourceVersion Source frame specification version
                 * @tparam TargetVersion Target frame specification version
                 */
                template < typename SourceVersion, typename TargetVersion >
                struct VersionConverter
                {
                    /**
                     * @brief Convert compression scheme from source to target version
                     *
                     * @param source Source compression mode (including endianness bit)
                     * @param data_type Data type for mapping lookup
                     * @return Target compression mode (with endianness preserved)
                     */
                    static INT_2U convert( INT_2U source, Data::data_types_type data_type )
                    {
                        // Extract endianness bit
                        INT_2U endianness = source & Data::COMPRESSION_SCHEME_ENDIANNESS;
                        INT_2U source_mode = source;

                        // Try direct conversion using MappingTableSelector
                        const CompressionModeMapping* mapping_table =
                            MappingTableSelector< SourceVersion, TargetVersion >::table;
                        size_t array_size =
                            MappingTableSelector< SourceVersion, TargetVersion >::size;

                        INT_2U target_mode = 0x00; // Default to RAW

                        if ( mapping_table != nullptr )
                        {
                            // Direct mapping exists
                            const CompressionModeMapping* mapping =
                                find_mapping( mapping_table, array_size, source_mode, data_type );

                            if ( mapping != nullptr )
                            {
                                target_mode = mapping->target_mode;
                            }
                        }

                        // Apply endianness to target mode
                        target_mode = ( target_mode & 0xFF ) | endianness;
                        return target_mode;
                    }
                };

                //=================================================================
                // VersionConverter Specializations for Non-Adjacent Version Pairs
                //=================================================================

                /**
                 * @brief v9 → v3 conversion (chains v9→v8→v5→v4→v3)
                 */
                template <>
                struct VersionConverter< Data::v9, Data::v3 >
                {
                    static INT_2U convert( INT_2U source, Data::data_types_type data_type )
                    {
                        // Chain through adjacent versions
                        INT_2U temp = VersionConverter< Data::v9, Data::v8 >::convert( source, data_type );
                        temp = VersionConverter< Data::v8, Data::v5 >::convert( temp, data_type );
                        temp = VersionConverter< Data::v5, Data::v4 >::convert( temp, data_type );
                        temp = VersionConverter< Data::v4, Data::v3 >::convert( temp, data_type );
                        return temp;
                    }
                };

                /**
                 * @brief v9 → v4 conversion (chains v9→v8→v5→v4)
                 */
                template <>
                struct VersionConverter< Data::v9, Data::v4 >
                {
                    static INT_2U convert( INT_2U source, Data::data_types_type data_type )
                    {
                        // Chain through adjacent versions
                        INT_2U temp = VersionConverter< Data::v9, Data::v8 >::convert( source, data_type );
                        temp = VersionConverter< Data::v8, Data::v5 >::convert( temp, data_type );
                        temp = VersionConverter< Data::v5, Data::v4 >::convert( temp, data_type );
                        return temp;
                    }
                };

                /**
                 * @brief v3 → v9 conversion (chains v3→v4→v5→v8→v9)
                 */
                template <>
                struct VersionConverter< Data::v3, Data::v9 >
                {
                    static INT_2U convert( INT_2U source, Data::data_types_type data_type )
                    {
                        // Chain through adjacent versions
                        INT_2U temp = VersionConverter< Data::v3, Data::v4 >::convert( source, data_type );
                        temp = VersionConverter< Data::v4, Data::v5 >::convert( temp, data_type );
                        temp = VersionConverter< Data::v5, Data::v8 >::convert( temp, data_type );
                        temp = VersionConverter< Data::v8, Data::v9 >::convert( temp, data_type );
                        return temp;
                    }
                };

                /**
                 * @brief v4 → v9 conversion (chains v4→v5→v8→v9)
                 */
                template <>
                struct VersionConverter< Data::v4, Data::v9 >
                {
                    static INT_2U convert( INT_2U source, Data::data_types_type data_type )
                    {
                        // Chain through adjacent versions
                        INT_2U temp = VersionConverter< Data::v4, Data::v5 >::convert( source, data_type );
                        temp = VersionConverter< Data::v5, Data::v8 >::convert( temp, data_type );
                        temp = VersionConverter< Data::v8, Data::v9 >::convert( temp, data_type );
                        return temp;
                    }
                };

            }  // namespace CompressionMapping
        }      // namespace FrVectImpl
    }          // namespace Universal
}  // namespace FrameCPP

#endif  // FrameCPP_UNIVERSAL_IMPL_FrVectCompressionMapping_HH
