"""
Core facade for the spafw37 application framework.

This module provides a high-level interface for interacting with the
spafw37 application framework, including configuration management,
command registration, and parameter handling.
"""
import sys

# Deprecation tracking
_deprecated_warnings_shown = set()
_suppress_deprecation_warnings = False


def _deprecated(message):
    """
    Decorator to mark functions as deprecated with one-time warning.
    
    Args:
        message: Deprecation message explaining the alternative.
    
    Returns:
        Decorator function that wraps the deprecated function.
    """
    def decorator(func):
        def wrapper(*args, **kwargs):
            if not _suppress_deprecation_warnings:
                func_name = func.__name__
                if func_name not in _deprecated_warnings_shown:
                    _deprecated_warnings_shown.add(func_name)
                    from spafw37 import logging as spafw37_logging
                    spafw37_logging.log_warning(_message=f"{func_name}() is deprecated. {message}")
            return func(*args, **kwargs)
        return wrapper
    return decorator


def suppress_deprecation(suppress=True):
    """
    Control deprecation warning display.
    
    Allows developers to suppress deprecation warnings during migration.
    Useful for large codebases migrating to new API gradually.
    
    Args:
        suppress: If True, suppress deprecation warnings. If False, enable them.
    
    Example:
        import spafw37
        spafw37.suppress_deprecation(True)  # Silence warnings during migration
        # ... migration code ...
        spafw37.suppress_deprecation(False)  # Re-enable warnings
    """
    global _suppress_deprecation_warnings
    _suppress_deprecation_warnings = suppress


def run_cli(args=sys.argv[1:], _embedded=False):
    """
    Run the command-line interface for the application.

    Import this function and call it in your main application 
    file to handle CLI arguments to set params and run commands.
    """
    import spafw37.configure  # Ensure configuration is set up
    import spafw37.cli as cli
    from spafw37.command import CommandParameterError
    from spafw37 import help
    # Pass user-provided command-line arguments (excluding program name)
    try:
        cli.handle_cli_args(args)
    except CommandParameterError as e:
        # On command parameter error, display help for that specific command
        print(f"Error: {e}")
        print()
        if e.command_name:
            help.display_command_help(e.command_name)
        else:
            help.display_all_help()
        if not _embedded:
            sys.exit(1)
    except (ValueError, KeyError) as e:
        print(f"Error: {e}")
        print()
        # display_all_help()
        if not _embedded:
            sys.exit(1)

def _default_output_handler(message):
    print(message)

_output_handler = _default_output_handler

def set_output_handler(_handler=_default_output_handler):
    """
    Set a custom output handler for framework output.
    
    Args:
        _handler: A callable that takes a message string. Defaults to built-in print().
    """
    global _output_handler
    _output_handler = _handler

def output(message="", verbose=False, output_handler=None):
    """
    Output a message respecting silent/verbose modes.
    
    Args:
        message: The message to output.
        verbose: If True, only outputs when verbose mode is enabled.
        output_handler: Optional custom handler for this call. If None, uses global handler.
    """
    from spafw37 import config
    if config.is_silent():
        return
    if verbose and not config.is_verbose():
        return
    handler = output_handler if output_handler is not None else _output_handler
    handler(message)


def set_config_file(file_path):
    """
    Set the configuration file.
    """
    from spafw37 import config_func
    config_func.set_config_file(file_path)


def set_app_name(name):
    """
    Set the application name.
    
    Args:
        name: Application name.
    """
    from spafw37 import config_func
    config_func.set_app_name(name)


def get_app_name():
    """
    Get the application name.
    
    Returns:
        Application name.
    """
    from spafw37 import config_func
    return config_func.get_app_name()


def add_params(params):
    """
    Add parameters.
    """
    from spafw37 import param
    param.add_params(params)

def add_param(_param):
    """
    Add a single parameter.
    """
    from spafw37 import param
    param.add_param(_param)

def add_commands(commands):
    """
    Add commands.
    """
    from spafw37 import command 
    command.add_commands(commands)

def add_command(_command):
    """
    Add a single command.
    """
    from spafw37 import command 
    command.add_command(_command)


def add_cycle(cycle_def):
    """Register a cycle definition for a command.
    
    Cycles define repeated command sequences with init, loop, and end functions.
    This function validates the cycle structure and stores it for later attachment
    to commands when they are registered.
    
    The CYCLE_COMMAND field can be:
    - String: Name of an existing or future command (command reference)
    - Dict: Inline command definition (will be registered immediately)
    
    Duplicate handling: If a cycle is already registered for the command:
    - Identical definition (deep equality): silently skip, first registration wins
    - Different definition: raise ValueError to prevent conflicting configurations
    
    Args:
        cycle_def: Cycle definition dict containing:
            - CYCLE_COMMAND: Command name (string) or inline definition (dict) (required)
            - CYCLE_NAME: Identifier for the cycle (required)
            - CYCLE_LOOP: Loop condition function (required)
            - CYCLE_INIT: Init function (optional)
            - CYCLE_LOOP_START: Loop start function (optional)
            - CYCLE_LOOP_END: Loop end function (optional)
            - CYCLE_END: End function (optional)
            - CYCLE_COMMANDS: List of commands in cycle (optional)
    
    Raises:
        ValueError: If required fields missing or conflicting cycle registered
    
    Example:
        >>> from spafw37 import core as spafw37
        >>> cycle = {
        ...     'cycle-command': 'my-command',
        ...     'cycle-name': 'my-cycle',
        ...     'cycle-loop-function': lambda: True
        ... }
        >>> spafw37.add_cycle(cycle)
    """
    from spafw37 import cycle
    cycle.add_cycle(cycle_def)


def add_cycles(cycle_defs):
    """Register multiple cycle definitions.
    
    Convenience function for registering multiple cycles at once.
    Each cycle is registered individually using add_cycle().
    
    Args:
        cycle_defs: List of cycle definition dicts
    
    Raises:
        ValueError: If any cycle validation fails
    
    Example:
        >>> from spafw37 import core as spafw37
        >>> cycles = [
        ...     {'cycle-command': 'cmd1', 'cycle-name': 'cycle1', 'cycle-loop-function': loop_fn1},
        ...     {'cycle-command': 'cmd2', 'cycle-name': 'cycle2', 'cycle-loop-function': loop_fn2}
        ... ]
        >>> spafw37.add_cycles(cycles)
    """
    from spafw37 import cycle
    cycle.add_cycles(cycle_defs)


def set_phases_order(phase_order):
    """
    Set the execution order for phases.
    
    Args:
        phase_order: List of phase names in execution order.
    """
    from spafw37 import config
    config.set_phases_order(phase_order)

def set_default_phase(default_phase):
    """
    Set the default phase for commands that don't specify a phase.
    
    Args:
        default_phase: The phase name to use as default.
    """
    from spafw37 import config
    config.set_default_phase(default_phase)

def get_max_cycle_nesting_depth():
    """Get the maximum allowed nesting depth for cycles.
    
    Returns:
        Maximum nesting depth (default: 5)
    """
    from spafw37 import config
    return config.get_max_cycle_nesting_depth()


def set_max_cycle_nesting_depth(depth):
    """Set the maximum allowed nesting depth for cycles.
    
    This controls how deeply cycles can be nested within each other.
    The default value of 5 is sufficient for most use cases. Increase
    this value if you need deeply nested cycle structures.
    
    Args:
        depth: Maximum nesting depth (must be positive integer)
        
    Raises:
        ValueError: If depth is not a positive integer
    """
    from spafw37 import config
    config.set_max_cycle_nesting_depth(depth)

def get_config_value(config_key):
    """
    Get a configuration value.
    """
    from spafw37 import param
    return param.get_param(param_name=config_key, strict=True)


def get_config_int(config_key, default=0):
    """
    Get a configuration value as integer.
    
    Args:
        config_key: Configuration key name.
        default: Default value if not found.
        
    Returns:
        Integer configuration value or default.
    """
    from spafw37 import param
    return param.get_param(param_name=config_key, default=default, strict=True)


def get_config_str(config_key, default=''):
    """
    Get a configuration value as string.
    
    Args:
        config_key: Configuration key name.
        default: Default value if not found.
        
    Returns:
        String configuration value or default.
    """
    from spafw37 import param
    return param.get_param(param_name=config_key, default=default, strict=True)


def get_config_bool(config_key, default=False):
    """
    Get a configuration value as boolean.
    
    Args:
        config_key: Configuration key name.
        default: Default value if not found.
        
    Returns:
        Boolean configuration value or default.
    """
    from spafw37 import param
    return param.get_param(param_name=config_key, default=default, strict=True)


def get_config_float(config_key, default=0.0):
    """
    Get a configuration value as float.
    
    Args:
        config_key: Configuration key name.
        default: Default value if not found.
        
    Returns:
        Float configuration value or default.
    """
    from spafw37 import param
    return param.get_param(param_name=config_key, default=default, strict=True)


def get_config_list(config_key, default=None):
    """
    Get a configuration value as list.
    
    Args:
        config_key: Configuration key name.
        default: Default value if not found.
        
    Returns:
        List configuration value or default (empty list if default is None).
    """
    from spafw37 import param
    return param.get_param(param_name=config_key, default=default, strict=True)


def get_config_dict(config_key, default=None):
    """
    Get a configuration value as dictionary.
    
    Args:
        config_key: Configuration key name.
        default: Default value if not found.
        
    Returns:
        Dictionary configuration value or default (empty dict if default is None).
    """
    from spafw37 import param
    return param.get_param(param_name=config_key, default=default, strict=True)


def set_config_value(config_key, value):
    """
    Set a configuration value.
    """
    from spafw37 import param
    try:
        param.set_param(param_name=config_key, value=value)
    except ValueError:
        # If param not registered, set directly in config (backward compatibility)
        from spafw37 import config
        config.set_config_value(config_key, value)


def is_verbose():
    """Check if verbose mode is enabled.
    
    Returns:
        True if verbose logging is enabled, False otherwise.
    """
    from spafw37 import config
    return config.is_verbose()


def is_silent():
    """Check if silent mode is enabled.
    
    Returns:
        True if silent mode is enabled, False otherwise.
    """
    from spafw37 import config
    return config.is_silent()


# Parameter API - Modern param-focused interface

def set_param(param_name=None, bind_name=None, alias=None, value=None):
    """
    Set a parameter value (replaces existing value).
    
    Sets the value of a parameter identified by param_name, bind_name, or alias.
    This replaces any existing value. Use join_param() to accumulate values instead.
    
    Args:
        param_name: Parameter name to set.
        bind_name: Config bind name to set.
        alias: Parameter alias to set.
        value: The value to set for the parameter.
    
    Raises:
        ValueError: If parameter not found or value validation fails.
    
    Example:
        set_param(param_name='host', value='localhost')
        set_param(bind_name='server_port', value=8080)
        set_param(alias='--verbose', value=True)
    """
    from spafw37 import param
    param.set_param(param_name=param_name, bind_name=bind_name, alias=alias, value=value)


def join_param(param_name=None, bind_name=None, alias=None, value=None):
    """
    Join/accumulate a parameter value (does not replace existing value).
    
    Joins a value to a parameter using type-specific logic:
    - Strings: Concatenates with separator (default: space)
    - Lists: Appends single values, extends with lists
    - Dicts: Merges (shallow or deep based on PARAM_DICT_MERGE_TYPE)
    
    Args:
        param_name: Parameter name to join.
        bind_name: Config bind name to join.
        alias: Parameter alias to join.
        value: The value to join to the parameter.
    
    Raises:
        ValueError: If parameter not found, unsupported type, or validation fails.
    
    Example:
        join_param(param_name='tags', value='tag1')
        join_param(bind_name='file_list', value=['item1', 'item2'])
        join_param(alias='--config', value={'key': 'value'})
    """
    from spafw37 import param
    param.join_param(param_name=param_name, bind_name=bind_name, alias=alias, value=value)


def get_param(param_name=None, bind_name=None, alias=None, default=None, strict=False):
    """
    Get a parameter value with automatic type coercion.
    
    This is the primary method for retrieving parameter values. It automatically
    determines the parameter type from the definition and returns a properly
    coerced value (string, int, bool, float, list, or dict).
    
    Args:
        param_name: Parameter name to get (use this in application code).
        bind_name: Config bind name to get (advanced use, for internal config key).
        alias: Parameter alias to get (advanced use).
        default: Default value if not found (default: None).
        strict: If True, raises ValueError when parameter not found (default: False).
    
    Returns:
        Parameter value coerced to the appropriate type based on PARAM_TYPE,
        or default if not found.
    
    Raises:
        ValueError: If strict=True and parameter not found.
    
    Example:
        host = get_param('database-host')  # Returns string
        port = get_param('server-port')  # Returns int (based on PARAM_TYPE)
                verbose = get_param('verbose')  # Returns bool
    """
    from spafw37 import param
    return param.get_param(param_name=param_name, bind_name=bind_name, alias=alias, default=default, strict=strict)


def unset_param(param_name=None, bind_name=None, alias=None):
    """Unset (remove) a parameter value.
    
    Completely removes the parameter value from configuration.
    Useful for clearing temporary state or runtime values.
    
    Args:
        param_name: Parameter name to unset.
        bind_name: Config bind name to unset.
        alias: Parameter alias to unset.
    
    Raises:
        ValueError: If parameter not found or parameter is immutable.
    
    Example:
        # Clear temporary processing state
        spafw37.unset_param('temp-counter')
        
        # Remove runtime flag
        spafw37.unset_param(bind_name='processing_active')
        
        # Clear debug mode
        spafw37.unset_param(alias='--debug')
    """
    from spafw37 import param
    param.unset_param(param_name=param_name, bind_name=bind_name, alias=alias)


def reset_param(param_name=None, bind_name=None, alias=None):
    """Reset a parameter to its default value, or unset it if no default exists.
    
    If the parameter has a default value defined, sets it to that default.
    If no default exists, removes the parameter value entirely.
    Useful for restoring initial state or clearing temporary values.
    
    Args:
        param_name: Parameter name to reset.
        bind_name: Config bind name to reset.
        alias: Parameter alias to reset.
    
    Raises:
        ValueError: If parameter not found or parameter is immutable.
    
    Example:
        # Reset counter to default value (or unset if no default)
        spafw37.reset_param('counter')
        
        # Reset log level to default
        spafw37.reset_param(bind_name='log_level')
        
        # Reset debug mode to default (likely False)
        spafw37.reset_param(alias='--debug')
    """
    from spafw37 import param
    param.reset_param(param_name=param_name, bind_name=bind_name, alias=alias)


# Logging delegates

def set_log_dir(log_dir):
    """
    Set the log directory.
    
    Args:
        log_dir: Directory path for log files.
    """
    from spafw37 import logging
    logging.set_log_dir(log_dir)


def log_trace(_scope=None, _message=''):
    """
    Log a message at TRACE level.
    
    Args:
        _scope: Optional scope for the log message.
        _message: The message to log.
    """
    from spafw37 import logging
    logging.log_trace(_scope=_scope, _message=_message)


def log_debug(_scope=None, _message=''):
    """
    Log a message at DEBUG level.
    
    Args:
        _scope: Optional scope for the log message.
        _message: The message to log.
    """
    from spafw37 import logging
    logging.log_debug(_scope=_scope, _message=_message)


def log_info(_scope=None, _message=''):
    """
    Log a message at INFO level.
    
    Args:
        _scope: Optional scope for the log message.
        _message: The message to log.
    """
    from spafw37 import logging
    logging.log_info(_scope=_scope, _message=_message)


def log_warning(_scope=None, _message=''):
    """
    Log a message at WARNING level.
    
    Args:
        _scope: Optional scope for the log message.
        _message: The message to log.
    """
    from spafw37 import logging
    logging.log_warning(_scope=_scope, _message=_message)


def log_error(_scope=None, _message=''):
    """
    Log a message at ERROR level.
    
    Args:
        _scope: Optional scope for the log message.
        _message: The message to log.
    """
    from spafw37 import logging
    logging.log_error(_scope=_scope, _message=_message)


def set_current_scope(scope):
    """
    Set the current logging scope.
    
    Args:
        scope: The scope name to use for subsequent log messages.
    """
    from spafw37 import logging
    logging.set_current_scope(scope)
