//
// LDASTools AL - Debug Level Management Utility
//
// Copyright (C) 2025 California Institute of Technology
//
// LDASTools AL 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 AL 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.
//

#ifndef LDASTOOLSAL_DEBUG_LEVEL_MANAGER_HH
#define LDASTOOLSAL_DEBUG_LEVEL_MANAGER_HH

#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <memory>
#include <functional>
#include <mutex>
#include <boost/program_options.hpp>

// Forward declaration
class SimpleLogger;

namespace LDASTools
{
    namespace AL
    {

        /**
         * @brief Debug Level Management System for LDAS Tools Utilities
         *
         * This class provides a centralized, reusable system for managing debug
         * output levels across LDAS Tools utilities. It integrates with the
         * ldastoolsal logging infrastructure while providing enhanced
         * command-line and runtime configuration capabilities.
         *
         * Features:
         * - Flexible debug levels (any integer, not restricted to multiples of
         * 10)
         * - Channel pattern matching with wildcards (e.g.,
         * "project.component.*")
         * - Command-line option integration via boost::program_options
         * - Configurable environment variable support (LDAS_DEBUG_LEVEL by
         * default)
         * - Runtime level adjustment without restart
         * - Configuration file support for complex setups
         *
         * @example Basic Usage
         * @code
         * // In main() of utility
         * DebugLevelManager::initialize(argc, argv, "my_utility");
         * DebugLevelManager::set_global_level(30);
         * DebugLevelManager::set_channel_level("project.component.*", 20);
         *
         * // Library code remains unchanged
         * logger_log_debug(logger(), 25, "Debug message at level 25");
         * @endcode
         */
        class DebugLevelManager
        {
        public:
            /// @brief Channel level configuration entry
            struct ChannelConfig
            {
                std::string pattern; ///< Channel pattern (supports wildcards)
                int         level; ///< Debug level for this channel
                bool        is_wildcard; ///< Whether pattern contains wildcards
            };

            /// @brief Callback types for library-specific customization
            using help_callback_type = std::function<std::string()>;

            /// @brief Debug level bounds for validation
            static constexpr int MIN_DEBUG_LEVEL = 0;
            static constexpr int MAX_DEBUG_LEVEL = 100;
            static constexpr int DEFAULT_DEBUG_LEVEL = 0;

            //=================================================================
            // Library Customization Interface (DRY Support)
            //=================================================================

            /**
             * @brief Register library-specific help content callback
             * @param callback Function that returns library-specific help text
             * 
             * This allows libraries to extend the debug help with their own
             * available channels and examples without overriding methods.
             */
            static void set_help_callback(help_callback_type callback);

            /**
             * @brief Set library-specific name for help display
             * @param name Library name (e.g., "framecpp", "frameutils")
             */
            static void set_library_name(const std::string& name);

            //=================================================================
            // Core Interface
            //=================================================================

            /**
             * @brief Initialize the debug level manager with command-line
             * arguments
             * @param argc Argument count from main()
             * @param argv Argument vector from main()
             * @param program_name Optional program name for help text
             * @param env_prefix Optional environment variable prefix (default:
             * "LDAS_DEBUG")
             * @return true if initialization successful, false if help was
             * requested
             */
            static bool
            initialize( int                argc,
                        char*              argv[],
                        const std::string& program_name = "ldas_utility",
                        const std::string& env_prefix = "LDAS_DEBUG" );

            /**
             * @brief Add debug-related options to boost::program_options
             * @param desc Options description to add debug options to
             * @param variables_map Variables map to store parsed values
             */
            static void add_debug_options(
                boost::program_options::options_description& desc,
                boost::program_options::variables_map&       vm );

            /**
             * @brief Configure debug levels from parsed command-line options
             * @param vm Parsed variables map from boost::program_options
             */
            static void configure_from_options(
                const boost::program_options::variables_map& vm );

            /**
             * @brief Set global debug level (affects all channels without
             * specific config)
             * @param level Global debug level (MIN_DEBUG_LEVEL to
             * MAX_DEBUG_LEVEL)
             * @throws std::invalid_argument if level is out of bounds
             */
            static void set_global_level( int level );

            /**
             * @brief Set debug level for a specific channel or pattern
             * @param channel_pattern Channel name or pattern (e.g.,
             * "project.component.*")
             * @param level Debug level for this channel
             * @throws std::invalid_argument if level is out of bounds
             */
            static void set_channel_level( const std::string& channel_pattern,
                                           int                level );

            /**
             * @brief Parse and set multiple channel levels from string
             * @param channel_spec Channel specification string (e.g.,
             * "chan1:20,chan2:30")
             */
            static void parse_channel_spec( const std::string& channel_spec );

            /**
             * @brief Get effective debug level for a specific channel
             * @param channel Channel name (e.g., "project.component.data")
             * @return Effective debug level for the channel
             */
            static int get_channel_level( const std::string& channel );

            /**
             * @brief Check if a debug message should be logged
             * @param channel Channel name
             * @param level Message debug level
             * @return true if message should be logged, false otherwise
             */
            static bool should_log( const std::string& channel, int level );

            /**
             * @brief Get current global debug level
             * @return Current global debug level
             */
            static int get_global_level( );

            /**
             * @brief Load configuration from environment variables
             *
             * Uses the configured environment variable prefix (e.g.,
             * LDAS_DEBUG_LEVEL)
             */
            static void load_from_environment( );

            /**
             * @brief Load configuration from file
             * @param config_file Path to configuration file
             * @throws std::runtime_error if file cannot be read or parsed
             */
            static void load_from_file( const std::string& config_file );

            /**
             * @brief Clear all channel-specific configurations
             */
            static void clear_channel_configs( );

            /**
             * @brief Get all current channel configurations
             * @return Vector of channel configurations
             */
            static std::vector< ChannelConfig > get_channel_configs( );

            /**
             * @brief Print current debug configuration to stream
             * @param os Output stream
             */
            static void print_configuration( std::ostream& os = std::cout );

            /**
             * @brief Validate debug level is within acceptable bounds
             * @param level Debug level to validate
             * @throws std::invalid_argument if level is out of bounds
             */
            static void validate_level( int level );

            /**
             * @brief Set environment variable prefix for this instance
             * @param prefix Environment variable prefix (e.g.,
             * "FRAMECPP_DEBUG", "LDASGEN_DEBUG")
             */
            static void set_env_prefix( const std::string& prefix );

            /**
             * @brief Set example channels for help text
             * @param examples Vector of example channel patterns
             */
            static void
            set_example_channels( const std::vector< std::string >& examples );

            /**
             * @brief Get environment variable prefix
             * @return Current environment variable prefix
             */
            static std::string get_env_prefix( );

            /**
             * @brief Generate help text for debug options
             * @return Help text string with project-specific examples
             */
            static std::string get_help_text( );

            /**
             * @brief Add debug options to ldastoolsal CommandLineOptions system
             * @param options_description Options description object to add options to
             * @param start_option_id Starting option ID for debug options (must reserve 5 consecutive IDs)
             * @return Next available option ID after debug options
             */
            static int add_ldas_debug_options( void* options_description, int start_option_id );

            /**
             * @brief Configure debug manager from parsed ldastoolsal command line options
             * @param option_id Option ID that was parsed
             * @param arg_value Argument value (if any)
             * @param program_name Program name for help display
             * @return true if option was handled, false if not a debug option
             */
            static bool configure_from_ldas_option( int option_id, const std::string& arg_value, 
                                                   const std::string& program_name, int base_option_id );

        private:
            /// @brief Private constructor - singleton pattern
            DebugLevelManager( ) = default;

            /// @brief Get singleton instance
            static DebugLevelManager& instance( );

            /// @brief Check if channel matches pattern (supports wildcards)
            static bool match_pattern( const std::string& pattern,
                                       const std::string& channel );

            /// @brief Parse channel specification entry (format:
            /// "channel:level")
            static std::pair< std::string, int >
            parse_channel_entry( const std::string& entry );

            // Member variables
            int                          m_global_level = DEFAULT_DEBUG_LEVEL;
            std::vector< ChannelConfig > m_channel_configs;
            bool                         m_initialized = false;
            std::string                  m_env_prefix = "LDAS_DEBUG";
            std::vector< std::string >   m_example_channels = {
                "project.component.*", "project.module.*"
            };
        };

    } // namespace AL
} // namespace LDASTools

#endif // LDASTOOLSAL_DEBUG_LEVEL_MANAGER_HH
