# PythonGenerator.py

import logging
import math
import re
from typing import Any, Dict, List, Set, Tuple, Union

logger = logging.getLogger(__name__)


class PythonGenerator:
    def __init__(self, default_source: Dict[str, Any], strategy_id: str):
        """
        Initializes the PythonGenerator.

        :param default_source: The default source for undefined sources in the strategy.
        :param strategy_id: The ID of the strategy.
        """
        self.indicators_used: List[Dict[str, Any]] = []
        self.data_sources_used: Set[Tuple[str, str, str]] = set()
        self.flags_used: Set[str] = set()
        self.default_source: Dict[str, Any] = default_source
        self.strategy_id: str = strategy_id

        # Initialize scheduled action tracking
        self.scheduled_action_count: int = 0
        self.scheduled_actions: List[str] = []

    def generate(self, strategy_json: dict) -> dict:
        code_lines = ["def next():"]
        indent_level = 1  # Starting indentation inside the function

        # Extract statements from the strategy
        statements = strategy_json.get('statements', [])
        if not isinstance(statements, list):
            logger.error("'statements' should be a list in strategy_json.")
            # Handle as needed, possibly returning an error or skipping code generation
            return {
                "generated_code": '\n'.join(code_lines),
                "indicators": list(self.indicators_used),
                "data_sources": list(self.data_sources_used),
                "flags_used": list(self.flags_used)
            }

        # Process each statement
        code_lines.extend(self.generate_code_from_json(statements, indent_level))

        # Handle exit logic at the end of 'next()'
        indent = '    ' * indent_level
        exit_indent = '    ' * (indent_level + 1)
        code_lines.append(f"{indent}exit = flags.get('exit', False)")
        code_lines.append(f"{indent}if exit:")
        code_lines.append(f"{exit_indent}exit_strategy()")
        code_lines.append(f"{exit_indent}set_paused(True)  # Pause the strategy while exiting.")

        return {
            "generated_code": '\n'.join(code_lines),
            "indicators": list(self.indicators_used),
            "data_sources": list(self.data_sources_used),
            "flags_used": list(self.flags_used)
        }

    # ==============================
    # Helper Methods
    # ==============================

    def process_numeric_input(self, value, option_name, default_value=1, min_value=None, max_value=None):
        """
        Processes a numeric input, applying limits if necessary.

        :param value: The value to validate.
        :param option_name: The name of the option for logging.
        :param default_value: Default value if input is invalid.
        :param min_value: Minimum allowable value.
        :param max_value: Maximum allowable value.
        :return: Validated numeric value.
        """
        if isinstance(value, (int, float)):
            if min_value is not None and value < min_value:
                logger.warning(f"{option_name} value too low. Clamping to {min_value}.")
                return min_value
            if max_value is not None and value > max_value:
                logger.warning(f"{option_name} value too high. Clamping to {max_value}.")
                return max_value
            return value
        elif isinstance(value, dict):
            return self.generate_code_from_json(value, 0)
        else:
            logger.warning(f"Invalid {option_name} value. Using default: {default_value}.")
            return default_value

    def process_numeric_list(self, value, indent_level: int, allow_single_value: bool = True) -> str:
        """
        Processes a numeric input or list of inputs, handling dynamic values and lists.

        :param value: The input value, which may be a single value, list, or nested expression.
        :param indent_level: Current indentation level.
        :param allow_single_value: Whether to return a single value as-is instead of wrapped in a list.
        :return: A string representing the processed numeric expression(s).
        """
        if isinstance(value, list):
            # Process each item in the list individually
            processed_values = [
                self.generate_condition_code(v, indent_level) if isinstance(v, dict) else str(v)
                for v in value
            ]
            return f"[{', '.join(processed_values)}]"

        elif isinstance(value, dict):
            # Handle nested expressions
            return self.generate_condition_code(value, indent_level)

        elif allow_single_value:
            # Single numeric value case
            return str(value)

        else:
            # Forcing single value into a list format if allow_single_value is False
            return f"[{str(value)}]"

    def generate_code_from_json(self, json_nodes: Any, indent_level: int) -> List[str]:
        """
        Recursively generates code lines from JSON nodes.

        :param json_nodes: The current JSON node(s) to process.
        :param indent_level: Current indentation level.
        :return: A list of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level

        if isinstance(json_nodes, dict):
            json_nodes = [json_nodes]

        for node in json_nodes:
            node_type = node.get('type')
            if not node_type:
                logger.warning("Node missing 'type'. Skipping.")
                continue  # Skip nodes without a type

            logger.debug(f"Handling node of type: {node_type}")

            # Route indicator_* types to the generic indicator handler
            if node_type.startswith('indicator_'):
                handler_method = self.handle_indicator
            elif node_type.startswith('signal_'):
                handler_method = self.handle_signal
            elif node_type.startswith('formation_') or node_type == 'formation':
                handler_method = self.handle_formation
            else:
                handler_method = getattr(self, f'handle_{node_type}', self.handle_default)
            handler_code = handler_method(node, indent_level)

            if isinstance(handler_code, list):
                code_lines.extend(handler_code)
            elif isinstance(handler_code, str):
                code_lines.append(handler_code)

            # Process 'next' recursively if present
            next_node = node.get('next')
            if next_node:
                code_lines.extend(self.generate_code_from_json(next_node, indent_level))

        return code_lines

    def handle_default(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Default handler for unhandled condition types.

        :param node: The node with an unhandled type.
        :param indent_level: Current indentation level.
        :return: A string representing the default condition.
        """
        node_type = node.get('type')
        logger.warning(f"Unhandled condition node type: {node_type}. Defaulting to 'False'.")
        return 'False'

    def generate_condition_code(self, condition_node: Dict[str, Any], indent_level: int) -> str:
        """
        Generates the condition code string based on the condition node.

        :param condition_node: The condition node dictionary.
        :param indent_level: Current indentation level.
        :return: A string representing the condition in Python syntax.
        """
        # Check if the condition_node is a direct value or a nested condition
        if not isinstance(condition_node, dict):
            # If condition_node is not a dict, return its string representation
            return str(condition_node)
        node_type = condition_node.get('type')
        if not node_type:
            return 'False'  # Default to False if node type is missing

        # Retrieve the handler method based on node_type
        # Route indicator_* types to the generic indicator handler
        if node_type.startswith('indicator_'):
            handler_method = self.handle_indicator
        elif node_type.startswith('signal_'):
            handler_method = self.handle_signal
        elif node_type.startswith('formation_') or node_type == 'formation':
            handler_method = self.handle_formation
        else:
            handler_method = getattr(self, f'handle_{node_type}', self.handle_default)
        condition_code = handler_method(condition_node, indent_level=indent_level)
        return condition_code

    # Add more helper methods here

    # ==============================
    # Indicators Handlers
    # ==============================

    def handle_indicator(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles indicator nodes by generating a function call to retrieve indicator values.
        Supports both:
        - Generic 'indicator' type with NAME field
        - Custom 'indicator_<name>' types where name is extracted from the type

        :param node: The indicator node.
        :param indent_level: Current indentation level.
        :return: A string representing the indicator value retrieval.
        """
        fields = node.get('fields', {})
        node_type = node.get('type', '')

        # Try to get indicator name from fields first, then from type
        indicator_name = fields.get('NAME')
        if not indicator_name and node_type.startswith('indicator_'):
            # Extract name from type, e.g., 'indicator_ema2' -> 'ema2'
            indicator_name = node_type[len('indicator_'):]

        output_field = fields.get('OUTPUT')

        if not indicator_name or not output_field:
            logger.error(f"indicator node missing name or OUTPUT. type={node_type}, fields={fields}")
            return 'None'

        # Collect the indicator information
        self.indicators_used.append({
            'name': indicator_name,
            'output': output_field
        })

        # Generate code that calls process_indicator
        expr = f"process_indicator('{indicator_name}', '{output_field}')"
        logger.debug(f"Generated indicator condition: {expr}")
        return expr

    # ==============================
    # Signals Handlers
    # ==============================

    def handle_signal(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles signal nodes by generating a function call to check signal state.
        Supports both:
        - Generic 'signal' type with NAME field
        - Custom 'signal_<name>' types where name is extracted from the type

        :param node: The signal node.
        :param indent_level: Current indentation level.
        :return: A string representing the signal check.
        """
        fields = node.get('fields', {})
        node_type = node.get('type', '')

        # Try to get signal name from fields first, then from type
        signal_name = fields.get('NAME')
        if not signal_name and node_type.startswith('signal_'):
            # Extract name from type, e.g., 'signal_my_signal' -> 'my_signal'
            # Replace underscores back to spaces for display names
            signal_name = node_type[len('signal_'):].replace('_', ' ')

        output_field = fields.get('OUTPUT', 'triggered')

        if not signal_name:
            logger.error(f"signal node missing name. type={node_type}, fields={fields}")
            return 'False'

        # Collect the signal information
        if not hasattr(self, 'signals_used'):
            self.signals_used = []
        self.signals_used.append({
            'name': signal_name,
            'output': output_field
        })

        # Generate code that calls process_signal
        if output_field == 'triggered':
            expr = f"process_signal('{signal_name}', 'triggered')"
        else:
            expr = f"process_signal('{signal_name}', 'value')"

        logger.debug(f"Generated signal condition: {expr}")
        return expr

    # ==============================
    # Formations Handlers
    # ==============================

    def handle_formation(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles formation nodes by generating a function call to get formation value.
        Uses tbl_key (stable UUID) for referencing formations.

        :param node: The formation node.
        :param indent_level: Current indentation level.
        :return: A string representing the formation value lookup.
        """
        fields = node.get('fields', {})

        # Get formation reference by tbl_key (stable) not name
        tbl_key = fields.get('TBL_KEY')
        property_name = fields.get('PROPERTY', 'line')
        formation_name = fields.get('NAME', 'unknown')

        if not tbl_key:
            logger.error(f"formation node missing TBL_KEY. fields={fields}")
            return 'None'

        # Track formation usage for dependency resolution
        if not hasattr(self, 'formations_used'):
            self.formations_used = []
        self.formations_used.append({
            'tbl_key': tbl_key,
            'name': formation_name,
            'property': property_name
        })

        # Generate code that calls process_formation
        # Uses current candle time by default (timestamp=None)
        expr = f"process_formation('{tbl_key}', '{property_name}')"

        logger.debug(f"Generated formation lookup: {expr}")
        return expr

    # ==============================
    # Balances Handlers
    # ==============================

    def handle_starting_balance(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'starting_balance' condition type.

        :param node: The starting_balance node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_starting_balance()"

    def handle_current_balance(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'current_balance' condition type.

        :param node: The current_balance node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_current_balance()"

    def handle_available_balance(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'available_balance' condition type.

        :param node: The available_balance node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_available_balance()"

    def handle_available_strategy_balance(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'available_strategy_balance' condition type.

        :param node: The available_strategy_balance node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_available_strategy_balance()"

    def handle_set_available_strategy_balance(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'set_available_strategy_balance' node type.

        :param node: The set_available_strategy_balance node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level

        # Extract the 'BALANCE' input
        balance = node.get('inputs', {}).get('BALANCE', 0)

        # If 'balance' is a dict, generate nested code
        if isinstance(balance, dict):
            balance = self.generate_condition_code(balance, indent_level)

        # Generate the code line to set the available strategy balance
        code_lines.append(f"{indent}set_available_strategy_balance({balance})")

        logger.debug(f"Generated set_available_strategy_balance action with balance: {balance}")
        return code_lines

    # ==============================
    # Order Metrics Handlers
    # ==============================

    def handle_order_volume(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'order_volume' condition type.

        :param node: The order_volume node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        order_type = node.get('inputs', {}).get('order_type', 'filled').lower()
        if order_type not in ['filled', 'unfilled']:
            logger.error(f"Invalid order type '{order_type}' in 'order_volume'. Defaulting to 'filled'.")
            order_type = 'filled'

        expr = f"get_order_volume(order_type='{order_type}')"
        logger.debug(f"Generated order_volume condition: {expr}")
        return expr

    def handle_filled_orders(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'filled_orders' condition type.

        :param node: The filled_orders node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_filled_orders()"

    def handle_unfilled_orders(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'unfilled_orders' condition type.

        :param node: The unfilled_orders node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_unfilled_orders()"

    def handle_order_status(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'order_status' condition type.

        :param node: The order_status node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        order_name = node.get('inputs', {}).get('order_name', 'last_order').strip()
        order_status = node.get('inputs', {}).get('status', 'filled').lower()
        valid_statuses = ['filled', 'unfilled', 'partial']

        if not order_name:
            logger.warning("Empty 'order_name' in 'order_status'. Using 'last_order'.")
            order_name = 'last_order'

        if order_status not in valid_statuses:
            logger.error(f"Invalid order status '{order_status}' in 'order_status'. Defaulting to 'filled'.")
            order_status = 'filled'

        expr = f"is_order_status(order_name='{order_name}', status='{order_status}')"
        logger.debug(f"Generated order_status condition: {expr}")
        return expr

    # Add other Order Metrics handlers here...

    # ==============================
    # Trade Metrics Handlers
    # ==============================

    def handle_active_trades(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'active_trades' condition type.

        :param node: The active_trades node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_active_trades()"

    def handle_total_trades(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'total_trades' condition type.

        :param node: The total_trades node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_total_trades()"

    def handle_last_trade_details(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'last_trade_details' condition type.

        :param node: The last_trade_details node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        details = node.get('inputs', {}).get('details', 'price').lower()
        valid_details = ['price', 'volume', 'direction']
        if details not in valid_details:
            logger.error(f"Invalid details '{details}' in 'last_trade_details'. Defaulting to 'price'.")
            details = 'price'

        expr = f"get_last_trade_details(detail='{details}')"
        logger.debug(f"Generated last_trade_details condition: {expr}")
        return expr

    def handle_average_entry_price(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'average_entry_price' condition type.

        :param node: The average_entry_price node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_average_entry_price()"

    def handle_unrealized_profit_loss(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'unrealized_profit_loss' condition type.

        :param node: The unrealized_profit_loss node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_unrealized_profit_loss()"

    def handle_user_active_trades(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'user_active_trades' condition type.

        :param node: The user_active_trades node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        return "get_user_active_trades()"

    # Add other Trade Metrics handlers here...

    # ==============================
    # Time Metrics Handlers
    # ==============================

    def handle_time_since_start(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'time_since_start' condition type.

        :param node: The time_since_start node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        # Extract the 'unit' from inputs
        unit = node.get('inputs', {}).get('unit', 'seconds').lower()
        valid_units = ['seconds', 'minutes', 'hours']

        if unit not in valid_units:
            logger.error(f"Invalid time unit '{unit}' in 'time_since_start'. Defaulting to 'seconds'.")
            unit = 'seconds'

        expr = f"get_time_since_start(unit='{unit}')"
        logger.debug(f"Generated time_since_start condition: {expr}")
        return expr

    # Add other Time Metrics handlers here...

    # ==============================
    # Market Data Handlers
    # ==============================

    def handle_current_price(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'current_price' condition type.

        :param node: The current_price node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        # Process source input
        inputs = node.get('inputs', {})
        source_node = inputs.get('source', {})
        timeframe = source_node.get('timeframe', self.default_source.get('timeframe', '1m'))
        exchange = source_node.get('exchange', self.default_source.get('exchange', 'Binance'))
        # Support both 'symbol' and 'market' keys (default_source uses 'market')
        symbol = source_node.get('symbol') or source_node.get('market') or self.default_source.get('symbol') or self.default_source.get('market', 'BTC/USDT')

        # Track data sources
        self.data_sources_used.add((exchange, symbol, timeframe))

        # Correctly format the function call with separate parameters
        return f"get_current_price(timeframe='{timeframe}', exchange='{exchange}', symbol='{symbol}')"

    def handle_bid_price(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'bid_price' condition type.

        :param node: The bid_price node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        # Process source input
        inputs = node.get('inputs', {})
        source_node = inputs.get('source', {})
        timeframe = source_node.get('timeframe', self.default_source.get('timeframe', '1m'))
        exchange = source_node.get('exchange', self.default_source.get('exchange', 'Binance'))
        # Support both 'symbol' and 'market' keys (default_source uses 'market')
        symbol = source_node.get('symbol') or source_node.get('market') or self.default_source.get('symbol') or self.default_source.get('market', 'BTC/USDT')

        # Track data sources
        self.data_sources_used.add((exchange, symbol, timeframe))

        # Correctly format the function call with separate parameters
        return f"get_bid_price(timeframe='{timeframe}', exchange='{exchange}', symbol='{symbol}')"

    def handle_ask_price(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'ask_price' condition type.

        :param node: The ask_price node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        # Process source input
        inputs = node.get('inputs', {})
        source_node = inputs.get('source', {})
        timeframe = source_node.get('timeframe', self.default_source.get('timeframe', '1m'))
        exchange = source_node.get('exchange', self.default_source.get('exchange', 'Binance'))
        # Support both 'symbol' and 'market' keys (default_source uses 'market')
        symbol = source_node.get('symbol') or source_node.get('market') or self.default_source.get('symbol') or self.default_source.get('market', 'BTC/USDT')

        # Track data sources
        self.data_sources_used.add((exchange, symbol, timeframe))

        # Correctly format the function call with separate parameters
        return f"get_ask_price(timeframe='{timeframe}', exchange='{exchange}', symbol='{symbol}')"

    def handle_last_candle_value(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'last_candle_value' condition type by generating a function call to get candle data.

        :param node: The last_candle_value node.
        :param indent_level: Current indentation level.
        :return: A string representing the candle data retrieval.
        """
        inputs = node.get('inputs', {})
        candle_part = inputs.get('candle_part', 'close').lower()
        valid_candle_parts = ['open', 'high', 'low', 'close']
        if candle_part not in valid_candle_parts:
            logger.error(f"Invalid candle_part '{candle_part}' in 'last_candle_value'. Defaulting to 'close'.")
            candle_part = 'close'

        # Process source input
        source_node = node.get('source', {})
        timeframe = source_node.get('timeframe', self.default_source.get('timeframe', '1m'))
        exchange = source_node.get('exchange', self.default_source.get('exchange', 'Binance'))
        # Support both 'symbol' and 'market' keys (default_source uses 'market')
        symbol = source_node.get('symbol') or source_node.get('market') or self.default_source.get('symbol') or self.default_source.get('market', 'BTC/USDT')

        # Track data sources
        self.data_sources_used.add((exchange, symbol, timeframe))

        expr = f"get_last_candle(candle_part='{candle_part}', timeframe='{timeframe}', exchange='{exchange}', symbol='{symbol}')"
        logger.debug(f"Generated last_candle_value condition: {expr}")
        return expr

    def handle_source(self, node: Dict[str, Any], indent_level: int) -> Dict[str, Any]:
        """
        Handles the 'source' condition type.

        :param node: The source node.
        :param indent_level: Current indentation level.
        :return: A dictionary representing the source configuration.
        """
        timeframe = node.get('time_frame', '5m')
        exchange = node.get('exchange', 'Binance')
        # Support both 'symbol' and 'market' keys
        symbol = node.get('symbol') or node.get('market', 'BTC/USDT')

        # Track data sources
        self.data_sources_used.add((exchange, symbol, timeframe))

        expr = {
            'timeframe': timeframe,
            'exchange': exchange,
            'symbol': symbol
        }
        logger.debug(f"Generated source configuration: {expr}")
        return expr

    # Add other Market Data handlers here...

    # ==============================
    # Logical Handlers
    # ==============================

    def handle_comparison(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'comparison' condition type.

        :param node: The comparison node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        operator = node.get('operator')
        inputs = node.get('inputs', {})
        # Support both uppercase (LEFT/RIGHT) and lowercase (left/right) keys
        left_node = inputs.get('LEFT') or inputs.get('left')
        right_node = inputs.get('RIGHT') or inputs.get('right')

        if not operator or not left_node or not right_node:
            logger.error(f"comparison node missing 'operator', 'LEFT', or 'RIGHT'. inputs={inputs}")
            return 'False'

        operator_map = {
            '>': '>',
            '<': '<',
            '>=': '>=',
            '<=': '<=',
            '==': '==',
            '!=': '!='
        }
        python_operator = operator_map.get(operator)
        if not python_operator:
            logger.error(f"Unsupported operator '{operator}'. Defaulting to '=='.")
            python_operator = '=='

        left_expr = self.generate_condition_code(left_node, indent_level)
        right_expr = self.generate_condition_code(right_node, indent_level)
        condition = f"{left_expr} {python_operator} {right_expr}"
        logger.debug(f"Generated comparison condition: {condition}")
        return condition

    def handle_logical_and(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'logical_and' condition type.

        :param node: The logical_and node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        inputs = node.get('inputs', {})
        # Support both uppercase (LEFT/RIGHT) and lowercase (left/right) keys
        left_node = inputs.get('LEFT') or inputs.get('left')
        right_node = inputs.get('RIGHT') or inputs.get('right')

        if not left_node or not right_node:
            logger.warning(f"logical_and node missing 'LEFT' or 'RIGHT'. inputs={inputs}. Defaulting to 'False'.")
            return 'False'

        left_expr = self.generate_condition_code(left_node, indent_level)
        right_expr = self.generate_condition_code(right_node, indent_level)
        condition = f"{left_expr} and {right_expr}"
        logger.debug(f"Generated logical AND condition: {condition}")
        return condition

    def handle_logical_or(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'logical_or' condition type.

        :param node: The logical_or node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        inputs = node.get('inputs', {})
        # Support both uppercase (LEFT/RIGHT) and lowercase (left/right) keys
        left_node = inputs.get('LEFT') or inputs.get('left')
        right_node = inputs.get('RIGHT') or inputs.get('right')

        if not left_node or not right_node:
            logger.warning(f"logical_or node missing 'LEFT' or 'RIGHT'. inputs={inputs}. Defaulting to 'False'.")
            return 'False'

        left_expr = self.generate_condition_code(left_node, indent_level)
        right_expr = self.generate_condition_code(right_node, indent_level)
        condition = f"{left_expr} or {right_expr}"
        logger.debug(f"Generated logical OR condition: {condition}")
        return condition

    def handle_is_false(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'is_false' condition type.

        :param node: The is_false node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition.
        """
        condition_node = node.get('condition')
        if not condition_node:
            logger.error("is_false node missing 'condition'.")
            return 'True'  # Negation of 'False' is 'True'

        condition_expr = self.generate_condition_code(condition_node, indent_level)
        condition = f"not ({condition_expr})"
        logger.debug(f"Generated is_false condition: {condition}")
        return condition

    # Add other Logical handlers here...

    # ==============================
    # Trade Order Handlers
    # ==============================

    def handle_trade_action(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'trade_action' node type by passing trade options as arguments to a single trade_order function.

        :param node: The trade_action node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level

        # Accessing fields and inputs
        trade_type = node.get('trade_type', 'buy').lower()  # 'buy' or 'sell'
        inputs = node.get('inputs', {})

        # Initialize defaults
        tif = node.get('tif', 'GTC')  # Time in Force

        # Set initial default values for optional parameters
        price_type = node.get('price_type', 'market')
        # price_value is not used in current context; can be removed or utilized as needed
        # price_value = node.get('price_value', None)

        size = inputs.get('size', 1)
        if isinstance(size, dict):
            size = self.generate_condition_code(size, indent_level)  # Handle nested json

        # Handle Trade Options if present
        trade_options = {}
        trade_options_nodes = node.get('trade_options', [])
        for option in trade_options_nodes:
            option_type = option.get('type')
            option_inputs = option.get('inputs', {})
            handler_method = getattr(self, f'handle_{option_type}', self.handle_default_trade_option)
            option_value = handler_method(option_inputs, indent_level)
            if option_value is not None:
                trade_options[option_type] = option_value

        # Collect data sources
        source = trade_options.get('source', self.default_source)
        exchange = source.get('exchange', 'binance')
        # Support both 'symbol' and 'market' keys (default_source uses 'market')
        symbol = source.get('symbol') or source.get('market', 'BTC/USDT')
        timeframe = source.get('timeframe', '5m')
        self.data_sources_used.add((exchange, symbol, timeframe))

        # Prepare arguments for trade_order function
        trade_order_args = {
            'trade_type': f"'{trade_options.get('trade_type', trade_type)}'",
            'size': size,
            'order_type': f"'{price_type}'",
            'source': self.format_trade_option(source),
            'tif': f"'{trade_options.get('tif', tif)}'",
            'stop_loss': self.format_trade_option(trade_options.get('stop_loss', {})),
            'trailing_stop': self.format_trade_option(trade_options.get('trailing_stop', {})),
            'take_profit': self.format_trade_option(trade_options.get('take_profit', {})),
            'limit': self.format_trade_option(trade_options.get('limit', {})),
            'trailing_limit': self.format_trade_option(trade_options.get('trailing_limit', {})),
            'target_market': self.format_trade_option(trade_options.get('target_market', {})),
            'name_order': self.format_trade_option(trade_options.get('name_order', {})),
        }

        # Remove keys with empty dictionaries or None
        trade_order_args = {k: v for k, v in trade_order_args.items() if v not in [{}, None]}

        # Convert trade_order_args dictionary to a comma-separated string
        args_str = ', '.join([f"{key}={value}" for key, value in trade_order_args.items()])

        # Generate the trade_order function call
        code_lines.append(f"{indent}trade_order({args_str})")

        return code_lines

    def format_trade_option(self, option: Dict[str, Any]) -> str:
        """
        Formats the trade option dictionary into a Python dictionary string.

        :param option: The trade option dictionary.
        :return: A string representing the trade option or 'None' if empty.
        """
        if not option:
            return 'None'

        # Precompile the regex pattern for market symbols (e.g., 'BTC/USD', 'BTC/USDT', '0G/USDT', '1INCH/USDT')
        # Matches 1-10 alphanumeric characters (allowing symbols like 0G, 1INCH), a slash, and 2-5 uppercase letters
        market_symbol_pattern = re.compile(r'^[A-Z0-9]{1,10}/[A-Z]{2,5}$')

        def is_market_symbol(value: str) -> bool:
            """
            Determines if a string is a market symbol following the pattern 'XXX/YYY'.
            Supports symbols starting with numbers like '0G/USDT' or '1INCH/USDT'.

            :param value: The string to check.
            :return: True if it matches the market symbol pattern, False otherwise.
            """
            return bool(market_symbol_pattern.match(value))

        def format_value(value: Any) -> str:
            if isinstance(value, str):
                if is_market_symbol(value):
                    return f"'{value}'"  # Quote market symbols like 'BTC/USD'
                # Check if the string represents an expression (contains operators or function calls)
                elif any(op in value for op in ['(', ')', '+', '-', '*', '/', '.']):
                    return value  # Assume it's an expression and return as-is
                else:
                    return f"'{value}'"
            elif isinstance(value, dict):
                # Recursively format nested dictionaries
                nested_items = [f"'{k}': {format_value(v)}" for k, v in value.items()]
                return f"{{{', '.join(nested_items)}}}"
            elif isinstance(value, (int, float)):
                return str(value)
            else:
                logger.error(f"Unsupported value type: {type(value)}. Using 'None'.")
                return 'None'

        items = [f"'{key}': {format_value(value)}" for key, value in option.items()]
        return f"{{{', '.join(items)}}}"

    def handle_default_trade_option(self, inputs: Dict[str, Any], indent_level: int) -> Any:
        """
        Default handler for unimplemented trade options.

        :param inputs: The inputs for the trade option.
        :param indent_level: Current indentation level.
        :return: None
        """
        logger.warning(f"Unhandled trade option with inputs: {inputs}")
        return None

    def handle_tpsl_options(self, option_name: str, inputs: Dict[str, Any], indent_level: int) -> Any:
        """
        Generalized handler for trade options like 'stop_loss' and 'take_profit'.
        Supports:
            - Single value: Standard Stop Loss / Take Profit
            - Two values: Stop Loss / Take Profit with a Limit
        The values can be int, float, string, dict (node), or a mixed list.

        :param option_name: The name of the trade option ('stop_loss' or 'take_profit').
        :param inputs: The inputs for the trade option.
        :param indent_level: Current indentation level.
        :return: A dictionary representing the trade option configuration.
        """
        option_input = inputs.get(option_name)
        if not option_input:
            logger.error(f"{option_name} option missing '{option_name}' input.")
            return None

        option_config = {}

        def process_value(value):
            if isinstance(value, dict):
                return self.generate_condition_code(value, indent_level)
            elif isinstance(value, (int, float, str)):
                return value
            else:
                logger.error(f"Unsupported type for {option_name}: {type(value)}")
                return 'None'

        if isinstance(option_input, list):
            if len(option_input) >= 1:
                option_config['value'] = process_value(option_input[0])
            if len(option_input) >= 2:
                option_config['limit'] = process_value(option_input[1])
            if len(option_input) > 2:
                logger.warning(f"{option_name} input list has more than two elements; extras will be ignored.")
        else:
            option_config['value'] = process_value(option_input)

        logger.debug(f"Generated {option_name} configuration: {option_config}")
        return option_config

    def handle_take_profit(self, inputs: Dict[str, Any], indent_level: int) -> Any:
        """Handles the 'take_profit' trade option by delegating to the generalized handler."""
        return self.handle_tpsl_options('take_profit', inputs, indent_level)

    def handle_stop_loss(self, inputs: Dict[str, Any], indent_level: int) -> Any:
        """Handles the 'stop_loss' trade option by delegating to the generalized handler."""
        return self.handle_tpsl_options('stop_loss', inputs, indent_level)

    def handle_time_in_force(self, inputs: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'time_in_force' trade option by generating the corresponding value.

        :param inputs: The inputs for time_in_force.
        :param indent_level: Current indentation level.
        :return: A string representing the time_in_force value.
        """
        tif = inputs.get('time_in_force', 'gtc').lower()
        valid_tif = {'gtc', 'fok', 'ioc'}
        if tif not in valid_tif:
            logger.warning(f"Invalid Time in Force '{tif}', defaulting to 'gtc'")
            tif = 'gtc'
        return f"'{tif}'"

    def handle_limit(self, inputs: Dict[str, Any], indent_level: int) -> Any:
        """
        Handles the 'limit' trade option by processing the limit input.

        :param inputs: The inputs for limit.
        :param indent_level: Current indentation level.
        :return: The limit value or None if invalid.
        """
        limit = inputs.get('limit')
        processed_limit = self.process_numeric_input(limit, 'limit')
        if processed_limit is not None:
            return {'limit': processed_limit}
        return None

    def handle_trailing_stop(self, inputs: Dict[str, Any], indent_level: int) -> Any:
        """
        Handles the 'trailing_stop' trade option by processing the trailing distance.

        :param inputs: The inputs for trailing_stop.
        :param indent_level: Current indentation level.
        :return: A dictionary representing the trailing_stop configuration or None if invalid.
        """
        trail_distance = inputs.get('trail_distance')
        processed_trail = self.process_numeric_input(trail_distance, 'trailing_stop')
        if processed_trail is not None:
            return {'trail_distance': processed_trail}
        return None

    def handle_trailing_limit(self, inputs: Dict[str, Any], indent_level: int) -> Any:
        """
        Handles the 'trailing_limit' trade option by processing the trail limit distance.

        :param inputs: The inputs for trailing_limit.
        :param indent_level: Current indentation level.
        :return: A dictionary representing the trailing_limit configuration or None if invalid.
        """
        trail_limit_distance = inputs.get('trail_limit_distance')
        processed_trail_limit = self.process_numeric_input(trail_limit_distance, 'trailing_limit')
        if processed_trail_limit is not None:
            return {'trail_limit_distance': processed_trail_limit}
        return None

    def handle_target_market(self, inputs: Dict[str, Any], indent_level: int) -> Dict[str, Any]:
        """
        Handles the 'target_market' trade option by retrieving time frame, exchange, and symbol.

        :param inputs: The inputs for target_market.
        :param indent_level: Current indentation level.
        :return: A dictionary representing the target market configuration.
        """
        time_frame = inputs.get('time_frame', '1m')
        exchange = inputs.get('exchange', 'Binance')
        # Support both 'symbol' and 'market' keys
        symbol = inputs.get('symbol') or inputs.get('market', 'BTC/USDT')

        target_market = {
            'time_frame': time_frame,
            'exchange': exchange,
            'symbol': symbol
        }
        return target_market

    def handle_name_order(self, inputs: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'name_order' trade option by processing the order name.

        :param inputs: The inputs for name_order.
        :param indent_level: Current indentation level.
        :return: A string representing the name_order value.
        """
        order_name = inputs.get('order_name', 'Unnamed Order').strip()
        return f"'{order_name}'" if order_name else "'Unnamed Order'"

    # Add other Trade Order handlers here...

    # ==============================
    # Control Handlers
    # ==============================

    def handle_pause_strategy(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'pause_strategy' node type.

        :param node: The pause_strategy node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level
        code_lines.append(f"{indent}set_paused(True)")
        code_lines.append(f"{indent}notify_user('Strategy paused.')")

        return code_lines

    def handle_strategy_resume(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'strategy_resume' node type.

        :param node: The strategy_resume node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level
        code_lines.append(f"{indent}self.resume_strategy()")
        code_lines.append(f"{indent}notify_user('Strategy resumed.')")

        return code_lines

    def handle_strategy_exit(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'strategy_exit' node type.

        :param node: The strategy_exit node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level
        exit_option = node.get('condition', 'all')  # 'all', 'in_profit', 'in_loss'
        code_lines.append(f"{indent}set_exit(True, '{exit_option}')  # Initiate exit")
        code_lines.append(f"{indent}set_paused(True)  # Pause the strategy while exiting")

        return code_lines

    def handle_max_drawdown(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'max_drawdown' condition type by generating the corresponding Python code.

        :param node: The max_drawdown node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level

        inputs = node.get('inputs', {})
        if not inputs:
            logger.error("max_drawdown node missing 'inputs'. Skipping.")
            return code_lines  # Return empty list

        drawdown = inputs.get('drawdown')
        action = inputs.get('action', 'PAUSE')  # 'PAUSE' or 'EXIT'

        processed_drawdown = self.process_numeric_input(drawdown, 'drawdown')
        if processed_drawdown is None:
            logger.error("Invalid or missing 'drawdown' value. Skipping 'max_drawdown' configuration.")
            return code_lines  # Return empty list

        # Assuming 'set_max_drawdown' is a method in the strategy class
        expr = f"set_max_drawdown({processed_drawdown}, '{action}')"
        code_lines.append(f"{indent}{expr}")
        logger.debug(f"Generated max_drawdown condition: {expr}")

        return code_lines

    def handle_schedule_action(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'schedule_action' node type by generating the corresponding Python code.

        :param node: The schedule_action node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level

        # Extract and validate inputs
        inputs = node.get('inputs', {})
        time_interval = inputs.get('time_interval', '5m')
        repeat = inputs.get('repeat', False)
        action = inputs.get('action', [])

        if not isinstance(time_interval, str):
            logger.warning(f"Invalid type for time_interval: {type(time_interval)}. Defaulting to '1m'.")
            time_interval = '1m'
        if not isinstance(repeat, bool):
            logger.warning(f"Invalid type for repeat: {type(repeat)}. Defaulting to False.")
            repeat = False

        # Generate unique function name and a timestamp variable name to track last execution
        self.scheduled_action_count += 1
        function_name = f"scheduled_action_{self.scheduled_action_count}"
        last_execution_var = f"last_execution_{self.scheduled_action_count}"

        # Set up the time parsing and interval conversion based on standard time suffixes
        time_multipliers = {'m': 60, 'h': 3600, 'd': 86400}
        try:
            interval_value = int(time_interval[:-1])
            interval_unit = time_interval[-1]
            interval_seconds = interval_value * time_multipliers.get(interval_unit, 60)
        except ValueError:
            logger.error(f"Invalid time interval format: '{time_interval}'. Using default 5 minutes.")
            interval_seconds = 5 * 60

        # Define the action function with time-checking logic
        code_lines.append(f"{indent}def {function_name}(current_time):")
        code_lines.append(f"{indent}    from datetime import timedelta")
        code_lines.append(f"{indent}    global {last_execution_var}")  # Track last execution time globally
        code_lines.append(f"{indent}    if {last_execution_var} is None:")
        code_lines.append(
            f"{indent}        {last_execution_var} = current_time - timedelta(seconds={interval_seconds})")

        # Check if enough time has passed since the last execution
        code_lines.append(f"{indent}    time_since_last = (current_time - {last_execution_var}).total_seconds()")
        code_lines.append(f"{indent}    if time_since_last >= {interval_seconds}:")
        code_lines.append(f"{indent}        {last_execution_var} = current_time  # Update last execution time")

        # Generate action code within the time condition
        if not action:
            code_lines.append(f"{indent}        pass  # No actions defined")
        else:
            action_code = self.generate_code_from_json(action, indent_level + 2)
            if not action_code:
                code_lines.append(f"{indent}        pass  # No valid actions defined")
            else:
                code_lines.extend(action_code)

        # If repeat is False, remove the function from the schedule after first execution
        if not repeat:
            code_lines.append(f"{indent}        self.scheduled_actions.remove({function_name})")

        # Register this function in the schedule
        code_lines.append(f"{indent}self.schedule_action({function_name})")

        logger.debug(
            f"Generated schedule_action with function '{function_name}', time_interval='{time_interval}', repeat={repeat}")
        return code_lines

    def handle_execute_if(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'execute_if' node type by generating an if statement with the given condition and do actions.

        :param node: The execute_if node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level

        inputs = node.get('inputs', {})
        condition = inputs.get('CONDITION', {})
        do_statements = node.get('statements', {}).get('DO', [])

        condition_code = self.generate_condition_code(condition, indent_level)
        code_lines.append(f"{indent}if {condition_code}:")

        if not do_statements:
            code_lines.append(f"{indent}    pass  # No actions defined")
        else:
            action_code = self.generate_code_from_json(do_statements, indent_level + 1)
            if not action_code:
                code_lines.append(f"{indent}    pass  # No valid actions defined")
            else:
                code_lines.extend(action_code)

        logger.debug(f"Generated execute_if with condition '{condition_code}'")
        return code_lines

    # Add other Control handlers here...

    # ==============================
    # Values and Flags Handlers
    # ==============================

    def handle_dynamic_value(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'dynamic_value' node type.

        :param node: The dynamic_value node.
        :param indent_level: Current indentation level.
        :return: A string representing the value.
        """
        values = node.get('values', [])
        if not values:
            logger.error("dynamic_value node has no 'values'.")
            return 'None'
        # Assuming the first value is the primary value
        first_value = values[0]
        if isinstance(first_value, dict):
            return self.generate_condition_code(first_value, indent_level)
        else:
            return str(first_value)

    def handle_notify_user(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'notify_user' node type.
        Supports both static text messages and dynamic values (variables, calculations, etc.).

        :param node: The notify_user node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        indent = '    ' * indent_level
        message = node.get('message', '').replace("'", "\\'")
        value_node = node.get('value')

        # Generate the dynamic value expression if present
        value_expr = None
        if value_node:
            if isinstance(value_node, dict):
                result = self.generate_code_from_json(value_node, indent_level)
                # Handle case where result is a list (from dynamic_value or value_input)
                if isinstance(result, list):
                    # Use first element if single, otherwise join them
                    value_expr = result[0] if len(result) == 1 else ', '.join(str(v) for v in result)
                else:
                    value_expr = result
            else:
                value_expr = str(value_node)

        # Build the notify_user call based on what's provided
        if message and value_expr:
            # Both static message and dynamic value: concatenate with str()
            return [f"{indent}notify_user('{message}' + str({value_expr}))"]
        elif message:
            # Only static message
            return [f"{indent}notify_user('{message}')"]
        elif value_expr:
            # Only dynamic value
            return [f"{indent}notify_user(str({value_expr}))"]
        else:
            # Neither provided - fallback
            return [f"{indent}notify_user('No message provided.')"]

    def handle_value_input(self, node: Dict[str, Any], indent_level: int) -> Union[str, List[str]]:
        """
        Handles the 'value_input' node type, managing sequential or nested values.

        :param node: The value_input node.
        :param indent_level: Current indentation level.
        :return: A single string if there is only one value, or a list of strings if multiple values.
        """
        values = node.get('values', [])
        if not values:
            return ''  # Return an empty string if no values are present

        # If there's only one value, handle it directly
        if len(values) == 1:
            value = values[0]
            return self.generate_code_from_json(value, indent_level) if isinstance(value, dict) else str(value)

        # For multiple values, create a list of processed values
        return [
            self.generate_code_from_json(v, indent_level) if isinstance(v, dict) else str(v)
            for v in values
        ]

    def handle_get_variable(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'get_variable' node type, managing variable retrieval with chaining.

        :param node: The get_variable node.
        :param indent_level: Current indentation level.
        :return: A string representing the variable retrieval.
        """
        variable_name = node.get('variable_name', '').strip()
        if not variable_name:
            logger.error("get_variable node missing 'variable_name'.")
            return 'None'
        return f"variables.get('{variable_name}', None)"

    def handle_set_variable(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'set_variable' node type, supporting dynamic value assignment.

        :param node: The set_variable node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level
        variable_name = node.get('variable_name', '').strip()
        value_node = node.get('values')

        if not variable_name:
            logger.error("set_variable node missing 'variable_name'. Skipping.")
            return []

        # Process 'values' list as either a single or aggregated expression
        if isinstance(value_node, list):
            processed_values = [
                self.generate_condition_code(v, indent_level) if isinstance(v, dict) else str(v)
                for v in value_node
            ]
            value_code = processed_values[0] if len(processed_values) == 1 else f"[{', '.join(processed_values)}]"
        else:
            value_code = self.generate_condition_code(value_node, indent_level) if value_node else '0'

        code_lines.append(f"{indent}variables['{variable_name}'] = {value_code}")
        return code_lines

    def handle_set_flag(self, node: Dict[str, Any], indent_level: int) -> List[str]:
        """
        Handles the 'set_flag' node type, setting a flag conditionally.

        :param node: The set_flag node.
        :param indent_level: Current indentation level.
        :return: List of generated code lines.
        """
        code_lines = []
        indent = '    ' * indent_level
        flag_name = node.get('flag_name')

        if not flag_name:
            logger.error("set_flag node missing 'flag_name'. Skipping.")
            return []

        flag_value_input = node.get('flag_value', 'True')
        flag_value = 'True' if str(flag_value_input).strip().lower() == 'true' else 'False'
        code_lines.append(f"{indent}flags['{flag_name}'] = {flag_value}")
        self.flags_used.add(flag_name)

        # # Process 'next' field if present
        # next_node = node.get('next')
        # if next_node:
        #     next_code = self.generate_code_from_json(next_node, indent_level)
        #     if isinstance(next_code, list):
        #         code_lines.extend(next_code)
        #     elif isinstance(next_code, str):
        #         code_lines.append(next_code)

        return code_lines

    def handle_flag_is_set(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'flag_is_set' condition type, checking if a flag is set to a specific value.

        :param node: The flag_is_set node.
        :param indent_level: Current indentation level.
        :return: A string representing the flag condition.
        """
        flag_name = node.get('flag_name')
        flag_value = node.get('flag_value', True)  # Default to True if not specified

        if not flag_name:
            logger.error("flag_is_set node missing 'flag_name'.")
            return 'False'

        # Generate condition based on flag_value
        if isinstance(flag_value, bool):
            condition = f"flags.get('{flag_name}', False) == {flag_value}"
        else:
            logger.error(f"Unsupported flag_value type: {type(flag_value)}. Defaulting to 'False'.")
            condition = 'False'

        logger.debug(f"Generated flag_is_set condition: {condition}")
        return condition

    # Add other Values and Flags handlers here...

    # ==============================
    # Risk Management Handlers
    # ==============================

    def handle_set_leverage(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'set_leverage' node type.

        :param node: The set_leverage node.
        :param indent_level: Current indentation level.
        :return: A string representing the leverage setting action.
        """
        leverage = node.get('inputs', {}).get('LEVERAGE', 1)
        leverage = self.process_numeric_input(leverage, 'leverage', 1, min_value=1, max_value=100)
        return f"set_leverage({leverage})"

    def handle_current_margin(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'current_margin' node type.

        :param node: The current_margin node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition to check current margin.
        """
        return "get_current_margin()"

    def handle_risk_ratio(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'risk_ratio' node type.

        :param node: The risk_ratio node.
        :param indent_level: Current indentation level.
        :return: A string representing the risk ratio condition.
        """
        operator = node.get('operator')
        value = node.get('value')
        if not operator or value is None:
            logger.error("risk_ratio node missing 'operator' or 'value'.")
            return 'False'

        operator_map = {
            '>': '>',
            '<': '<',
            '>=': '>=',
            '<=': '<=',
            '==': '==',
            '!=': '!='
        }
        python_operator = operator_map.get(operator)
        if not python_operator:
            logger.error(f"Invalid operator for risk_ratio: {operator}.")
            return 'False'

        return f"calculate_risk_ratio() {python_operator} {value}"

    def handle_max_position_size(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'max_position_size' node type.

        :param node: The max_position_size node.
        :param indent_level: Current indentation level.
        :return: A string representing the condition to check the max position size.
        """
        max_size = node.get('inputs', {}).get('MAX_SIZE', 1)
        max_size = self.process_numeric_input(max_size, 'max_position_size', 1, min_value=1)
        return f"get_open_positions_count() < {max_size}"

    # ==============================
    # Math Handlers
    # ==============================

    def handle_math_operation(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'math_operation' node type.

        :param node: The math_operation node.
        :param indent_level: Current indentation level.
        :return: A string representing the math operation.
        """
        # Extract from 'inputs' instead of top-level
        inputs = node.get('inputs', {})
        operator = inputs.get('operator', 'ADD').upper()
        left_operand = inputs.get('left_operand')
        right_operand = inputs.get('right_operand')

        operator_map = {
            'ADD': '+',
            'SUBTRACT': '-',
            'MULTIPLY': '*',
            'DIVIDE': '/'
        }
        python_operator = operator_map.get(operator, '+')
        if operator not in operator_map:
            logger.error(f"Invalid operator for math_operation: {operator}. Defaulting to 'ADD'.")

        left_expr = self.generate_condition_code(left_operand, indent_level)
        right_expr = self.generate_condition_code(right_operand, indent_level)
        expr = f"({left_expr} {python_operator} {right_expr})"
        logger.debug(f"Generated math_operation expression: {expr}")
        return expr

    def handle_power(self, node: Dict[str, Any], indent_level: int) -> str:
        base = self.process_numeric_list(node.get('base', 2), indent_level)
        exponent = self.process_numeric_list(node.get('exponent', 3), indent_level)
        expr = f"{base} ** {exponent}"
        logger.debug(f"Generated power expression: {expr}")
        return expr

    def handle_modulo(self, node: Dict[str, Any], indent_level: int) -> str:
        dividend = self.process_numeric_list(node.get('dividend', 10), indent_level)
        divisor = self.process_numeric_list(node.get('divisor', 3), indent_level)
        expr = f"{dividend} % {divisor}"
        logger.debug(f"Generated modulo expression: {expr}")
        return expr

    def handle_sqrt(self, node: Dict[str, Any], indent_level: int) -> str:
        number = self.process_numeric_list(node.get('number', 16), indent_level)
        expr = f"math.sqrt({number})"
        logger.debug(f"Generated sqrt expression: {expr}")
        return expr

    def handle_abs(self, node: Dict[str, Any], indent_level: int) -> str:
        number = self.process_numeric_list(node.get('number', -5), indent_level)
        expr = f"abs({number})"
        logger.debug(f"Generated abs expression: {expr}")
        return expr

    def handle_max(self, node: Dict[str, Any], indent_level: int) -> str:
        numbers = self.process_numeric_list(node.get('numbers', [1, 2, 3]), indent_level)
        expr = f"max({numbers})"
        logger.debug(f"Generated max expression: {expr}")
        return expr

    def handle_min(self, node: Dict[str, Any], indent_level: int) -> str:
        numbers = self.process_numeric_list(node.get('numbers', [1, 2, 3]), indent_level)
        expr = f"min({numbers})"
        logger.debug(f"Generated min expression: {expr}")
        return expr

    def handle_factorial(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'factorial' node type.

        :param node: The factorial node.
        :param indent_level: Current indentation level.
        :return: A string representing the factorial operation.
        """
        number = self.process_numeric_list(node.get('number', 1), indent_level)
        expr = f"math.factorial({number})"
        logger.debug(f"Generated factorial expression: {expr}")
        return expr

    def handle_log(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'log' node type.

        :param node: The log node.
        :param indent_level: Current indentation level.
        :return: A string representing the logarithm operation.
        """
        number = self.process_numeric_list(node.get('number', 1), indent_level)
        base = self.process_numeric_list(node.get('base', 10), indent_level)
        expr = f"math.log({number}, {base})"
        logger.debug(f"Generated log expression: {expr}")
        return expr

    def handle_ln(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'ln' node type.

        :param node: The ln node.
        :param indent_level: Current indentation level.
        :return: A string representing the natural logarithm operation.
        """
        number = self.process_numeric_list(node.get('number', 1), indent_level)
        expr = f"math.log({number})"
        logger.debug(f"Generated ln expression: {expr}")
        return expr

    def handle_trig(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'trig' node type.

        :param node: The trig node.
        :param indent_level: Current indentation level.
        :return: A string representing the trigonometric function.
        """
        operator = node.get('operator', 'sin').lower()  # 'sin', 'cos', 'tan'
        angle = self.process_numeric_list(node.get('angle', 0), indent_level)

        valid_operators = {'sin', 'cos', 'tan'}
        if operator not in valid_operators:
            logger.error(f"Invalid trig operator '{operator}'. Defaulting to 'sin'.")
            operator = 'sin'

        expr = f"math.{operator}({angle})"
        logger.debug(f"Generated trig expression: {expr}")
        return expr

    def handle_mean(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'mean' node type.

        :param node: The mean node.
        :param indent_level: Current indentation level.
        :return: A string representing the mean calculation.
        """
        numbers = self.process_numeric_list(node.get('numbers', []), indent_level)
        if not numbers.strip('[]'):
            logger.error("Mean operation received an empty list.")
            return "0"  # or another default value or error handling
        expr = f"statistics.mean({numbers})"
        logger.debug(f"Generated mean expression: {expr}")
        return expr

    def handle_median(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'median' node type.

        :param node: The median node.
        :param indent_level: Current indentation level.
        :return: A string representing the median calculation.
        """
        numbers = self.process_numeric_list(node.get('numbers', []), indent_level)
        if not numbers.strip('[]'):
            logger.error("Median operation received an empty list.")
            return "0"  # or another default value or error handling
        expr = f"statistics.median({numbers})"
        logger.debug(f"Generated median expression: {expr}")
        return expr

    def handle_std_dev(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'std_dev' node type.

        :param node: The std_dev node.
        :param indent_level: Current indentation level.
        :return: A string representing the standard deviation calculation.
        """
        numbers = self.process_numeric_list(node.get('numbers', []), indent_level)
        if not numbers.strip('[]'):
            logger.error("Standard deviation operation received an empty list.")
            return "0"  # or another default value or error handling
        expr = f"statistics.stdev({numbers})"
        logger.debug(f"Generated std_dev expression: {expr}")
        return expr

    def handle_round(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'round' node type.

        :param node: The round node.
        :param indent_level: Current indentation level.
        :return: A string representing the rounding operation.
        """
        number = self.process_numeric_list(node.get('number', 0), indent_level)
        decimals = self.process_numeric_list(node.get('decimals', 0), indent_level)
        expr = f"round({number}, {decimals})"
        logger.debug(f"Generated round expression: {expr}")
        return expr

    def handle_floor(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'floor' node type.

        :param node: The floor node.
        :param indent_level: Current indentation level.
        :return: A string representing the floor operation.
        """
        number = self.process_numeric_list(node.get('number', 0), indent_level)
        expr = f"math.floor({number})"
        logger.debug(f"Generated floor expression: {expr}")
        return expr

    def handle_ceil(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'ceil' node type.

        :param node: The ceil node.
        :param indent_level: Current indentation level.
        :return: A string representing the ceiling operation.
        """
        number = self.process_numeric_list(node.get('number', 0), indent_level)
        expr = f"math.ceil({number})"
        logger.debug(f"Generated ceil expression: {expr}")
        return expr

    def handle_random(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'random' node type.

        :param node: The random node.
        :param indent_level: Current indentation level.
        :return: A string representing the random integer generation.
        """
        min_val = self.process_numeric_list(node.get('min', 0), indent_level)
        max_val = self.process_numeric_list(node.get('max', 1), indent_level)
        expr = f"random.randint({min_val}, {max_val})"
        logger.debug(f"Generated random expression: {expr}")
        return expr

    def handle_clamp(self, node: Dict[str, Any], indent_level: int) -> str:
        """
        Handles the 'clamp' node type.

        :param node: The clamp node.
        :param indent_level: Current indentation level.
        :return: A string representing the clamp operation.
        """
        number = self.process_numeric_list(node.get('number', 0), indent_level)
        min_val = node.get('min')  # Optional
        max_val = node.get('max')  # Optional

        if min_val is not None:
            min_val = self.process_numeric_input(min_val, 'min', default_value=number, min_value=-math.inf)
        else:
            min_val = 'None'

        if max_val is not None:
            max_val = self.process_numeric_input(max_val, 'max', default_value=number, max_value=math.inf)
        else:
            max_val = 'None'

        # Generate the clamp expression
        if min_val != 'None' and max_val != 'None':
            expr = f"max({min_val}, min({number}, {max_val}))"
        elif min_val != 'None':
            expr = f"max({min_val}, {number})"
        elif max_val != 'None':
            expr = f"min({number}, {max_val})"
        else:
            expr = str(number)  # No clamping needed

        logger.debug(f"Generated clamp expression: {expr}")
        return expr

    # add other math handlers here...


