"""
CSSL Runtime Environment
Executes CSSL scripts by interpreting the AST
"""

import os
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Callable, Union

from .cssl_parser import ASTNode, parse_cssl, parse_cssl_program, CSSLSyntaxError
from .cssl_events import CSSLEventManager, EventType, EventData, get_event_manager
from .cssl_builtins import CSSLBuiltins
from .cssl_modules import get_module_registry, get_standard_module


class CSSLRuntimeError(Exception):
    """Runtime error during CSSL execution"""
    def __init__(self, message: str, line: int = 0):
        self.line = line
        super().__init__(f"{message} (line {line})" if line else message)


class CSSLBreak(Exception):
    """Break statement"""
    pass


class CSSLContinue(Exception):
    """Continue statement"""
    pass


class CSSLReturn(Exception):
    """Return statement"""
    def __init__(self, value: Any = None):
        self.value = value
        super().__init__()


@dataclass
class Scope:
    """Variable scope"""
    variables: Dict[str, Any] = field(default_factory=dict)
    parent: Optional['Scope'] = None

    def get(self, name: str) -> Any:
        if name in self.variables:
            return self.variables[name]
        if self.parent:
            return self.parent.get(name)
        return None

    def set(self, name: str, value: Any):
        self.variables[name] = value

    def update(self, name: str, value: Any) -> bool:
        if name in self.variables:
            self.variables[name] = value
            return True
        if self.parent:
            return self.parent.update(name, value)
        return False

    def has(self, name: str) -> bool:
        if name in self.variables:
            return True
        if self.parent:
            return self.parent.has(name)
        return False


@dataclass
class ServiceDefinition:
    """Parsed service definition"""
    name: str = ""
    version: str = "1.0"
    author: str = ""
    description: str = ""
    dependencies: List[str] = field(default_factory=list)
    autostart: bool = False
    priority: int = 0
    structs: Dict[str, ASTNode] = field(default_factory=dict)
    functions: Dict[str, ASTNode] = field(default_factory=dict)
    event_handlers: Dict[str, List[ASTNode]] = field(default_factory=dict)


class CSSLRuntime:
    """
    CSSL Script Runtime
    Interprets and executes CSSL Abstract Syntax Trees
    """

    def __init__(self, service_engine=None, output_callback: Callable[[str, str], None] = None):
        self.service_engine = service_engine
        self.scope = Scope()
        self.global_scope = self.scope
        self.builtins = CSSLBuiltins(self)
        self.event_manager = get_event_manager()
        self.output_buffer: List[str] = []
        self.services: Dict[str, ServiceDefinition] = {}
        self._modules: Dict[str, Any] = {}
        self._global_structs: Dict[str, Any] = {}  # Global structs for s@<name> references
        self._function_injections: Dict[str, List[ASTNode]] = {}  # NEW: Permanent function injections
        self._promoted_globals: Dict[str, Any] = {}  # NEW: Variables promoted via global()
        self._running = False
        self._exit_code = 0
        self._output_callback = output_callback  # Callback for console output (text, level)

        self._setup_modules()
        self._setup_builtins()

    def _setup_modules(self):
        """Setup module references for @KernelClient, @VSRAM, etc."""
        if self.service_engine:
            self._modules['KernelClient'] = self.service_engine.KernelClient
            self._modules['Kernel'] = self.service_engine.KernelClient

            if hasattr(self.service_engine.KernelClient, 'VSRam'):
                self._modules['VSRAM'] = self.service_engine.KernelClient.VSRam
                self._modules['VSRam'] = self.service_engine.KernelClient.VSRam

            if hasattr(self.service_engine.KernelClient, 'WheelKernel'):
                self._modules['Wheel'] = self.service_engine.KernelClient.WheelKernel
                self._modules['WheelKernel'] = self.service_engine.KernelClient.WheelKernel

            if hasattr(self.service_engine.KernelClient, 'CSnI'):
                self._modules['Network'] = self.service_engine.KernelClient.CSnI
                self._modules['CSnI'] = self.service_engine.KernelClient.CSnI

            if hasattr(self.service_engine, 'ServiceOperation'):
                self._modules['Service'] = self.service_engine.ServiceOperation
                self._modules['ServiceOperation'] = self.service_engine.ServiceOperation

            self._modules['ServiceEngine'] = self.service_engine
            self._modules['Boot'] = self.service_engine

        self._modules['event'] = self.event_manager
        self._modules['Events'] = self.event_manager

        # Register CSSL Standard Modules
        module_registry = get_module_registry()
        for module_name in module_registry.list_modules():
            module = module_registry.get_module(module_name)
            if module:
                module.runtime = self
                self._modules[module_name] = module

    def _setup_builtins(self):
        """Register built-in functions in global scope"""
        for name in self.builtins.list_functions():
            self.global_scope.set(name, self.builtins.get_function(name))

    def get_module(self, path: str) -> Any:
        """Get a module by path like 'KernelClient.VSRam'"""
        parts = path.split('.')
        obj = self._modules.get(parts[0])

        if obj is None:
            return None

        for part in parts[1:]:
            if hasattr(obj, part):
                obj = getattr(obj, part)
            elif isinstance(obj, dict) and part in obj:
                obj = obj[part]
            else:
                return None

        return obj

    def register_global_struct(self, name: str, struct_data: Any):
        """Register a struct as globally accessible via s@<name>"""
        self._global_structs[name] = struct_data

    def get_global_struct(self, path: str) -> Any:
        """Get a global struct by path like 'Backend.Loop.timer'"""
        parts = path.split('.')
        obj = self._global_structs.get(parts[0])

        if obj is None:
            return None

        for part in parts[1:]:
            if hasattr(obj, part):
                obj = getattr(obj, part)
            elif isinstance(obj, dict) and part in obj:
                obj = obj[part]
            else:
                return None

        return obj

    def execute(self, source: str) -> Any:
        """Execute CSSL service source code"""
        try:
            ast = parse_cssl(source)
            return self._execute_node(ast)
        except CSSLSyntaxError as e:
            raise CSSLRuntimeError(str(e), e.line)
        except SyntaxError as e:
            raise CSSLRuntimeError(f"Syntax error: {e}")

    def execute_program(self, source: str) -> Any:
        """Execute standalone CSSL program (no service wrapper)"""
        try:
            ast = parse_cssl_program(source)
            return self._exec_program(ast)
        except CSSLSyntaxError as e:
            raise CSSLRuntimeError(str(e), e.line)
        except SyntaxError as e:
            raise CSSLRuntimeError(f"Syntax error: {e}")

    def execute_ast(self, ast: ASTNode) -> Any:
        """Execute a pre-parsed AST"""
        return self._execute_node(ast)

    def execute_file(self, filepath: str) -> Any:
        """Execute a CSSL service file"""
        with open(filepath, 'r', encoding='utf-8') as f:
            source = f.read()
        return self.execute(source)

    def execute_program_file(self, filepath: str) -> Any:
        """Execute a standalone CSSL program file"""
        with open(filepath, 'r', encoding='utf-8') as f:
            source = f.read()
        return self.execute_program(source)

    def _execute_node(self, node: ASTNode) -> Any:
        """Execute an AST node"""
        if node is None:
            return None

        method_name = f'_exec_{node.type.replace("-", "_")}'
        method = getattr(self, method_name, None)

        if method:
            return method(node)
        else:
            raise CSSLRuntimeError(f"Unknown node type: {node.type}", node.line)

    def _exec_service(self, node: ASTNode) -> ServiceDefinition:
        """Execute service root node"""
        service = ServiceDefinition()

        for child in node.children:
            if child.type == 'service-init':
                self._exec_service_init(child, service)
            elif child.type == 'service-include':
                self._exec_service_include(child, service)
            elif child.type == 'service-run':
                self._exec_service_run(child, service)
            # NEW: package block support
            elif child.type == 'package':
                self._exec_package(child, service)
            # NEW: package-includes block support
            elif child.type == 'package-includes':
                self._exec_package_includes(child, service)
            # NEW: struct at top level
            elif child.type == 'struct':
                struct_info = child.value
                if isinstance(struct_info, dict):
                    struct_name = struct_info.get('name', '')
                else:
                    struct_name = struct_info
                service.structs[struct_name] = child
                self._exec_struct(child)
            # NEW: define at top level
            elif child.type == 'function':
                func_info = child.value
                func_name = func_info.get('name')
                service.functions[func_name] = child
                self.scope.set(func_name, child)

        return service

    def _exec_program(self, node: ASTNode) -> Any:
        """Execute standalone program (no service wrapper)

        A program can contain:
        - struct definitions
        - function definitions (define)
        - top-level statements (assignments, function calls, control flow)
        """
        result = None

        for child in node.children:
            if child.type == 'struct':
                self._exec_struct(child)
            elif child.type == 'function':
                self._exec_function(child)
            elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
                               'if', 'while', 'for', 'foreach', 'switch', 'try'):
                result = self._execute_node(child)
            elif child.type == 'call':
                result = self._eval_call(child)
            else:
                # Try to execute as statement
                try:
                    result = self._execute_node(child)
                except CSSLRuntimeError:
                    pass  # Ignore unknown nodes in program mode

        # Look for and execute main() if defined
        main_func = self.scope.get('main')
        if main_func and isinstance(main_func, ASTNode) and main_func.type == 'function':
            try:
                result = self._call_function(main_func, [])
            except CSSLReturn as ret:
                result = ret.value

        return result

    def _exec_service_init(self, node: ASTNode, service: ServiceDefinition):
        """Execute service-init block"""
        # Property aliases mapping
        key_aliases = {
            'service-name': 'name',
            'service-version': 'version',
            'service-author': 'author',
            'service-description': 'description',
            'executation': 'execution',
            'execution': 'execution',
        }

        for child in node.children:
            if child.type == 'property' and child.value:
                key = child.value.get('key')
                value = child.value.get('value')

                # Apply alias mapping
                key = key_aliases.get(key, key)

                if key == 'name':
                    service.name = value
                elif key == 'version':
                    service.version = value
                elif key == 'author':
                    service.author = value
                elif key == 'description':
                    service.description = value
                elif key == 'dependencies':
                    if isinstance(value, list):
                        service.dependencies = value
                    elif isinstance(value, str):
                        service.dependencies = [value]
                elif key == 'autostart':
                    service.autostart = bool(value)
                elif key == 'priority':
                    service.priority = int(value)
                elif key == 'execution':
                    # Handle execution type
                    if value == 'Persistent':
                        service.autostart = True
                    elif value == 'Only-Once':
                        service.autostart = False

    def _exec_service_include(self, node: ASTNode, service: ServiceDefinition):
        """Execute service-include block for importing modules and files"""
        for child in node.children:
            if child.type == 'expression':
                # Evaluate the expression which may be:
                # - @ModuleName (standard module reference)
                # - include(cso_root('/path/to/file.cssl'))
                # - Variable assignment
                result = self._evaluate(child.value)

                # If result is a module, register it
                if result is not None:
                    # Check if it's a call result (like include())
                    if hasattr(result, 'name') and result.name:
                        # It's a ServiceDefinition from include()
                        # Merge its functions and structs into current service
                        if hasattr(result, 'functions'):
                            for fname, fnode in result.functions.items():
                                service.functions[fname] = fnode
                                self.scope.set(fname, fnode)
                        if hasattr(result, 'structs'):
                            for sname, snode in result.structs.items():
                                service.structs[sname] = snode

            elif child.type == 'assignment' or child.type == 'injection':
                # Handle: utils <== include(cso_root('/services/utils.cssl'));
                info = child.value
                name = info.get('name') or info.get('target')
                if isinstance(name, ASTNode):
                    name = name.value if name.type == 'identifier' else str(name)
                source = info.get('source') or info.get('value')
                value = self._evaluate(source)
                self.scope.set(name, value)

            elif child.type == 'call':
                # Direct function call like include(...)
                self._eval_call(child)

            elif child.type == 'module_ref':
                # @ModuleName reference - just evaluate to register
                self._evaluate(child)

    def _exec_service_run(self, node: ASTNode, service: ServiceDefinition):
        """Execute service-run block"""
        for child in node.children:
            if child.type == 'struct':
                # Handle new dict format: {'name': name, 'global': is_global}
                struct_info = child.value
                if isinstance(struct_info, dict):
                    struct_name = struct_info.get('name', '')
                else:
                    struct_name = struct_info
                service.structs[struct_name] = child
                self._exec_struct(child)
            elif child.type == 'function':
                func_info = child.value
                func_name = func_info.get('name')
                service.functions[func_name] = child
                self.scope.set(func_name, child)

    def _exec_package(self, node: ASTNode, service: ServiceDefinition):
        """Execute package {} block for service metadata - NEW

        Syntax:
        package {
            service = "ServiceName";
            exec = @Start();
            version = "1.0.0";
            description = "Beschreibung";
        }
        """
        exec_func = None

        for child in node.children:
            if child.type == 'package_property' and child.value:
                key = child.value.get('key')
                value_node = child.value.get('value')

                # Evaluate the value
                value = self._evaluate(value_node)

                if key == 'service':
                    service.name = value
                elif key == 'version':
                    service.version = value
                elif key == 'description':
                    service.description = value
                elif key == 'author':
                    service.author = value
                elif key == 'exec':
                    # Store exec function for later execution
                    exec_func = value_node

        # Store exec function reference for later
        if exec_func:
            service.functions['__exec__'] = exec_func

    def _exec_package_includes(self, node: ASTNode, service: ServiceDefinition):
        """Execute package-includes {} block for imports - NEW

        Syntax:
        package-includes {
            @Lists = get('list');
            @OS = get('os');
            @Time = get('time');
            @VSRam = get('vsramsdk');
        }
        """
        for child in node.children:
            if child.type == 'assignment':
                info = child.value
                target = info.get('target')
                value_node = info.get('value')

                # Evaluate the value (e.g., get('list'))
                value = self._evaluate(value_node)

                # Get target name
                if isinstance(target, ASTNode):
                    if target.type == 'module_ref':
                        # @ModuleName = get(...)
                        module_name = target.value
                        self._modules[module_name] = value
                    elif target.type == 'identifier':
                        # varName = get(...)
                        self.scope.set(target.value, value)
                elif isinstance(target, str):
                    self.scope.set(target, value)

            elif child.type == 'expression':
                # Evaluate expression statements
                self._evaluate(child.value)

            elif child.type == 'inject':
                # Handle: @Module <== get(...);
                target = child.value.get('target')
                source = self._evaluate(child.value.get('source'))

                if isinstance(target, ASTNode) and target.type == 'module_ref':
                    self._modules[target.value] = source
                elif isinstance(target, ASTNode) and target.type == 'identifier':
                    self.scope.set(target.value, source)

    def _exec_struct(self, node: ASTNode) -> Dict[str, Any]:
        """Execute struct block"""
        struct_data = {}

        # Get struct name and global flag
        struct_info = node.value
        if isinstance(struct_info, dict):
            struct_name = struct_info.get('name', '')
            is_global = struct_info.get('global', False)
        else:
            # Backwards compatibility: value is just the name
            struct_name = struct_info
            is_global = False

        for child in node.children:
            if child.type == 'injection':
                info = child.value
                name = info.get('name')
                source = info.get('source')
                value = self._evaluate(source)
                struct_data[name] = value
                self.scope.set(name, value)

            elif child.type == 'assignment':
                info = child.value
                name = info.get('name')
                value = self._evaluate(info.get('value'))
                struct_data[name] = value
                self.scope.set(name, value)

            elif child.type == 'function':
                func_info = child.value
                func_name = func_info.get('name')
                struct_data[func_name] = child
                self.scope.set(func_name, child)

            elif child.type == 'expression':
                # Execute expression statements like print()
                self._evaluate(child.value)

            elif child.type == 'call':
                # Direct function calls
                self._eval_call(child)

            else:
                # Try to execute other statement types
                try:
                    self._execute_node(child)
                except Exception:
                    pass

        # Register as global struct if decorated with (@)
        if is_global and struct_name:
            self.register_global_struct(struct_name, struct_data)

        return struct_data

    def _exec_function(self, node: ASTNode) -> Any:
        """Execute function definition - just registers it"""
        func_info = node.value
        func_name = func_info.get('name')
        self.scope.set(func_name, node)
        return None

    def _call_function(self, func_node: ASTNode, args: List[Any]) -> Any:
        """Call a function node with arguments"""
        func_info = func_node.value
        params = func_info.get('params', [])

        # Create new scope
        new_scope = Scope(parent=self.scope)

        # Bind parameters
        for i, param in enumerate(params):
            if i < len(args):
                new_scope.set(param, args[i])
            else:
                new_scope.set(param, None)

        # Execute body
        old_scope = self.scope
        self.scope = new_scope

        try:
            for child in func_node.children:
                self._execute_node(child)
        except CSSLReturn as ret:
            return ret.value
        finally:
            self.scope = old_scope

        return None

    def _exec_if(self, node: ASTNode) -> Any:
        """Execute if statement"""
        condition = self._evaluate(node.value.get('condition'))

        if condition:
            for child in node.children:
                if child.type == 'then':
                    for stmt in child.children:
                        self._execute_node(stmt)
                    return None

        # Execute else block
        for child in node.children:
            if child.type == 'else':
                for stmt in child.children:
                    self._execute_node(stmt)
                return None

        return None

    def _exec_while(self, node: ASTNode) -> Any:
        """Execute while loop"""
        while self._evaluate(node.value.get('condition')):
            try:
                for child in node.children:
                    self._execute_node(child)
            except CSSLBreak:
                break
            except CSSLContinue:
                continue

        return None

    def _exec_for(self, node: ASTNode) -> Any:
        """Execute for loop"""
        var_name = node.value.get('var')
        start = int(self._evaluate(node.value.get('start')))
        end = int(self._evaluate(node.value.get('end')))

        for i in range(start, end):
            self.scope.set(var_name, i)
            try:
                for child in node.children:
                    self._execute_node(child)
            except CSSLBreak:
                break
            except CSSLContinue:
                continue

        return None

    def _exec_foreach(self, node: ASTNode) -> Any:
        """Execute foreach loop"""
        var_name = node.value.get('var')
        iterable = self._evaluate(node.value.get('iterable'))

        if iterable is None:
            return None

        for item in iterable:
            self.scope.set(var_name, item)
            try:
                for child in node.children:
                    self._execute_node(child)
            except CSSLBreak:
                break
            except CSSLContinue:
                continue

        return None

    def _exec_switch(self, node: ASTNode) -> Any:
        """Execute switch statement"""
        value = self._evaluate(node.value.get('value'))
        matched = False

        for child in node.children:
            if child.type == 'case':
                case_value = self._evaluate(child.value.get('value'))
                if value == case_value:
                    matched = True
                    try:
                        for stmt in child.children:
                            self._execute_node(stmt)
                    except CSSLBreak:
                        return None
            elif child.type == 'default' and not matched:
                try:
                    for stmt in child.children:
                        self._execute_node(stmt)
                except CSSLBreak:
                    return None

        return None

    def _exec_return(self, node: ASTNode) -> Any:
        """Execute return statement"""
        value = self._evaluate(node.value) if node.value else None
        raise CSSLReturn(value)

    def _exec_break(self, node: ASTNode) -> Any:
        """Execute break statement"""
        raise CSSLBreak()

    def _exec_continue(self, node: ASTNode) -> Any:
        """Execute continue statement"""
        raise CSSLContinue()

    def _exec_try(self, node: ASTNode) -> Any:
        """Execute try/catch block"""
        try:
            for child in node.children:
                if child.type == 'try-block':
                    for stmt in child.children:
                        self._execute_node(stmt)
        except CSSLRuntimeError as e:
            for child in node.children:
                if child.type == 'catch-block':
                    error_var = child.value.get('error_var') if child.value else None
                    if error_var:
                        self.scope.set(error_var, str(e))
                    for stmt in child.children:
                        self._execute_node(stmt)

        return None

    def _exec_createcmd_inject(self, node: ASTNode) -> Any:
        """Execute createcmd injection: createcmd('cmd') <== { action }"""
        command_call = node.value.get('command_call')
        action_block = node.value.get('action')

        # Get command name from the createcmd call arguments
        args = command_call.value.get('args', [])
        if args:
            command_name = self._evaluate(args[0])
        else:
            raise CSSLRuntimeError("createcmd requires a command name argument")

        # Create the command handler function
        def command_handler(*cmd_args):
            # Create a scope for the command execution
            cmd_scope = Scope(parent=self.scope)

            # Set cmd_args in scope
            cmd_scope.set('args', list(cmd_args))
            cmd_scope.set('argc', len(cmd_args))

            old_scope = self.scope
            self.scope = cmd_scope

            try:
                # Execute action block statements
                for child in action_block.children:
                    if child.type == 'function':
                        # If there's a define action { } inside, call it
                        func_name = child.value.get('name')
                        if func_name == 'action':
                            return self._call_function(child, list(cmd_args))
                    else:
                        self._execute_node(child)
            except CSSLReturn as ret:
                return ret.value
            finally:
                self.scope = old_scope

            return None

        # Register the command using the builtin
        self.builtins.builtin_createcmd(command_name, command_handler)

        return command_name

    def _apply_injection_filter(self, source: Any, filter_info: dict) -> Any:
        """Apply injection filter to extract specific data from source.

        Filters:
        - string::where=VALUE - Filter strings containing VALUE
        - string::length=LENGTH - Filter strings of specific length
        - integer::where=VALUE - Filter integers matching VALUE
        - json::key=KEY - Extract values with specific key from JSON/dict
        - json::value=VALUE - Filter by value in JSON/dict
        - array::index=INDEX - Get specific index from array
        - array::length=LENGTH - Filter arrays of specific length
        - vector::where=VALUE - Filter vectors containing VALUE
        - combo::filterdb - Get filter database from combo
        - combo::blocked - Get blocked items from combo
        - dynamic::VarName=VALUE - Filter by dynamic variable
        """
        if not filter_info:
            return source

        result = source

        for filter_key, filter_value in filter_info.items():
            if '::' in filter_key:
                filter_type, helper = filter_key.split('::', 1)
                filter_val = self._evaluate(filter_value) if isinstance(filter_value, ASTNode) else filter_value

                if filter_type == 'string':
                    if helper == 'where':
                        if isinstance(result, str) and filter_val in result:
                            pass  # Keep result
                        elif isinstance(result, list):
                            result = [item for item in result if isinstance(item, str) and filter_val in item]
                    elif helper == 'length':
                        if isinstance(result, str):
                            result = result if len(result) == filter_val else None
                        elif isinstance(result, list):
                            result = [item for item in result if isinstance(item, str) and len(item) == filter_val]

                elif filter_type == 'integer':
                    if helper == 'where':
                        if isinstance(result, int) and result == filter_val:
                            pass  # Keep result
                        elif isinstance(result, list):
                            result = [item for item in result if isinstance(item, int) and item == filter_val]

                elif filter_type == 'json':
                    if helper == 'key':
                        if isinstance(result, dict):
                            result = result.get(filter_val)
                        elif isinstance(result, list):
                            result = [item.get(filter_val) for item in result if isinstance(item, dict) and filter_val in item]
                    elif helper == 'value':
                        if isinstance(result, dict):
                            result = {k: v for k, v in result.items() if v == filter_val}
                        elif isinstance(result, list):
                            result = [item for item in result if (isinstance(item, dict) and filter_val in item.values())]

                elif filter_type == 'array' or filter_type == 'vector':
                    if helper == 'index':
                        if isinstance(result, list) and 0 <= filter_val < len(result):
                            result = result[filter_val]
                    elif helper == 'length':
                        if isinstance(result, list):
                            result = result if len(result) == filter_val else []
                    elif helper == 'where':
                        if isinstance(result, list):
                            result = [item for item in result if item == filter_val]

                elif filter_type == 'combo':
                    if helper == 'filterdb':
                        if hasattr(result, '_filterdb'):
                            result = result._filterdb
                    elif helper == 'blocked':
                        if hasattr(result, '_blocked'):
                            result = result._blocked

                elif filter_type == 'sql':
                    if helper == 'data':
                        # Return only SQL-compatible data types
                        if isinstance(result, (int, str, bool, float, list, dict)):
                            pass  # Keep result
                        else:
                            result = str(result)  # Convert to string

        return result

    def _exec_inject(self, node: ASTNode) -> Any:
        """Execute inject operation (<==, +<==, -<==)

        Modes:
        - replace: target <== source (replace target with source)
        - add: target +<== source (copy & add to target)
        - move: target -<== source (move from source, remove from source)
        """
        target = node.value.get('target')
        source_node = node.value.get('source')
        mode = node.value.get('mode', 'replace')
        filter_info = node.value.get('filter')

        # Check if target is a function call (for permanent injection)
        if isinstance(target, ASTNode) and target.type == 'call':
            callee = target.value.get('callee')
            if isinstance(callee, ASTNode) and callee.type == 'identifier':
                func_name = callee.value
                self.register_function_injection(func_name, source_node)
                return None

        # Evaluate source
        source = self._evaluate(source_node)

        # Apply filter if present
        if filter_info:
            source = self._apply_injection_filter(source, filter_info)

        # Get current target value for add/move modes
        current_value = None
        if mode in ('add', 'move'):
            current_value = self._evaluate(target)

        # Determine final value based on mode
        if mode == 'replace':
            final_value = source
        elif mode == 'add':
            # Copy & add - preserve target and add source
            if isinstance(current_value, list):
                if isinstance(source, list):
                    final_value = current_value + source
                else:
                    final_value = current_value + [source]
            elif isinstance(current_value, dict) and isinstance(source, dict):
                final_value = {**current_value, **source}
            elif isinstance(current_value, str) and isinstance(source, str):
                final_value = current_value + source
            elif current_value is None:
                final_value = [source] if not isinstance(source, list) else source
            else:
                final_value = [current_value, source]
        elif mode == 'move':
            # Move & remove from source
            final_value = source
            # Clear the source
            if isinstance(source_node, ASTNode) and source_node.type == 'identifier':
                self.scope.set(source_node.value, None)
        else:
            final_value = source

        # Set the target
        if target.type == 'identifier':
            self.scope.set(target.value, final_value)
        elif target.type == 'module_ref':
            self._set_module_value(target.value, final_value)
        elif target.type == 'member_access':
            self._set_member(target, final_value)
        elif target.type == 'call':
            callee = target.value.get('callee')
            if isinstance(callee, ASTNode) and callee.type == 'member_access':
                obj = self._evaluate(callee.value.get('object'))
                method_name = callee.value.get('member')
                if method_name == 'add' and isinstance(obj, list):
                    obj.append(final_value)
                    return final_value

        return final_value

    def _exec_receive(self, node: ASTNode) -> Any:
        """Execute receive operation (==>, ==>+, -==>)

        Modes:
        - replace: source ==> target (move source to target, replace)
        - add: source ==>+ target (copy source to target, add)
        - move: source -==> target (move from source, remove)
        """
        source_node = node.value.get('source')
        target = node.value.get('target')
        mode = node.value.get('mode', 'replace')
        filter_info = node.value.get('filter')

        # Evaluate source
        source = self._evaluate(source_node)

        # Apply filter if present
        if filter_info:
            source = self._apply_injection_filter(source, filter_info)

        # Get current target value for add mode
        current_value = None
        if mode == 'add':
            current_value = self._evaluate(target)

        # Determine final value based on mode
        if mode == 'replace':
            final_value = source
        elif mode == 'add':
            if isinstance(current_value, list):
                if isinstance(source, list):
                    final_value = current_value + source
                else:
                    final_value = current_value + [source]
            elif isinstance(current_value, dict) and isinstance(source, dict):
                final_value = {**current_value, **source}
            elif isinstance(current_value, str) and isinstance(source, str):
                final_value = current_value + source
            elif current_value is None:
                final_value = [source] if not isinstance(source, list) else source
            else:
                final_value = [current_value, source]
        elif mode == 'move':
            final_value = source
            # Clear the source
            if isinstance(source_node, ASTNode) and source_node.type == 'identifier':
                self.scope.set(source_node.value, None)
        else:
            final_value = source

        # Set the target
        if target.type == 'identifier':
            self.scope.set(target.value, final_value)
        elif target.type == 'module_ref':
            self._set_module_value(target.value, final_value)
        elif target.type == 'member_access':
            self._set_member(target, final_value)

        return final_value

    def _exec_infuse(self, node: ASTNode) -> Any:
        """Execute code infusion (<<==, +<<==, -<<==)

        Modes:
        - replace: func <<== { code } (inject code into function, replaces)
        - add: func +<<== { code } (add code to function)
        - remove: func -<<== { code } (remove matching code from function)
        """
        target = node.value.get('target')
        code_block = node.value.get('code')
        mode = node.value.get('mode', 'add')

        # Get function name from target
        func_name = None
        if isinstance(target, ASTNode):
            if target.type == 'identifier':
                func_name = target.value
            elif target.type == 'call':
                callee = target.value.get('callee')
                if isinstance(callee, ASTNode) and callee.type == 'identifier':
                    func_name = callee.value

        if not func_name:
            return None

        if mode == 'add':
            # Add code to function (permanent injection)
            self.register_function_injection(func_name, code_block)
        elif mode == 'replace':
            # Replace - clear existing and add new
            self._function_injections[func_name] = [code_block]
        elif mode == 'remove':
            # Remove matching code patterns from function
            if func_name in self._function_injections:
                # For simplicity, clear all injections for this function
                # A more sophisticated implementation would compare code blocks
                self._function_injections[func_name] = []

        return None

    def _exec_infuse_right(self, node: ASTNode) -> Any:
        """Execute right-side code infusion (==>>)"""
        source = node.value.get('source')
        target = node.value.get('target')
        mode = node.value.get('mode', 'replace')

        # Similar to infuse but direction is reversed
        func_name = None
        if isinstance(target, ASTNode):
            if target.type == 'identifier':
                func_name = target.value
            elif target.type == 'call':
                callee = target.value.get('callee')
                if isinstance(callee, ASTNode) and callee.type == 'identifier':
                    func_name = callee.value

        if func_name and isinstance(source, ASTNode):
            self.register_function_injection(func_name, source)

        return None

    def _exec_flow(self, node: ASTNode) -> Any:
        """Execute flow operation (-> or <-)"""
        source = self._evaluate(node.value.get('source'))
        target = node.value.get('target')

        # Flow sends data to a target (could be function call or variable)
        if target.type == 'call':
            callee = self._evaluate(target.value.get('callee'))
            args = [source] + [self._evaluate(a) for a in target.value.get('args', [])]

            if callable(callee):
                return callee(*args)
            elif isinstance(callee, ASTNode) and callee.type == 'function':
                return self._call_function(callee, args)
        elif target.type == 'identifier':
            self.scope.set(target.value, source)
        elif target.type == 'module_ref':
            self._set_module_value(target.value, source)

        return source

    def _exec_assignment(self, node: ASTNode) -> Any:
        """Execute assignment"""
        target = node.value.get('target')
        value = self._evaluate(node.value.get('value'))

        if isinstance(target, ASTNode):
            if target.type == 'identifier':
                self.scope.set(target.value, value)
            elif target.type == 'member_access':
                self._set_member(target, value)
            elif target.type == 'index_access':
                self._set_index(target, value)
        elif isinstance(target, str):
            self.scope.set(target, value)

        return value

    def _exec_expression(self, node: ASTNode) -> Any:
        """Execute expression statement"""
        return self._evaluate(node.value)

    def _exec_await(self, node: ASTNode) -> Any:
        """Execute await statement - waits for expression to complete"""
        # Evaluate the awaited expression
        # The expression is typically a call like wait_for_booted()
        result = self._evaluate(node.value)

        # If result is a callable (like a coroutine or future), wait for it
        if hasattr(result, '__await__'):
            import asyncio
            loop = asyncio.get_event_loop()
            return loop.run_until_complete(result)

        # If result is a boolean condition waiting function, it already handled the waiting
        return result

    def _exec_then(self, node: ASTNode) -> Any:
        """Execute then block"""
        for child in node.children:
            self._execute_node(child)
        return None

    def _exec_else(self, node: ASTNode) -> Any:
        """Execute else block"""
        for child in node.children:
            self._execute_node(child)
        return None

    def _exec_try_block(self, node: ASTNode) -> Any:
        """Execute try block"""
        for child in node.children:
            self._execute_node(child)
        return None

    def _exec_catch_block(self, node: ASTNode) -> Any:
        """Execute catch block"""
        for child in node.children:
            self._execute_node(child)
        return None

    def _evaluate(self, node: Any) -> Any:
        """Evaluate an expression node to get its value"""
        if node is None:
            return None

        if not isinstance(node, ASTNode):
            return node

        if node.type == 'literal':
            value = node.value
            # NEW: String interpolation - replace <variable> with scope values
            if isinstance(value, str) and '<' in value and '>' in value:
                value = self._interpolate_string(value)
            return value

        # NEW: Type literals (list, dict) - create empty instances
        if node.type == 'type_literal':
            type_name = node.value
            if type_name == 'list':
                return []
            elif type_name == 'dict':
                return {}
            return None

        if node.type == 'identifier':
            value = self.scope.get(node.value)
            if value is None and self.builtins.has_function(node.value):
                return self.builtins.get_function(node.value)
            return value

        if node.type == 'module_ref':
            return self.get_module(node.value)

        if node.type == 'self_ref':
            # s@<name> reference to global struct
            return self.get_global_struct(node.value)

        if node.type == 'binary':
            return self._eval_binary(node)

        if node.type == 'unary':
            return self._eval_unary(node)

        if node.type == 'call':
            return self._eval_call(node)

        if node.type == 'member_access':
            return self._eval_member_access(node)

        if node.type == 'index_access':
            return self._eval_index_access(node)

        if node.type == 'array':
            return [self._evaluate(elem) for elem in node.value]

        if node.type == 'object':
            return {k: self._evaluate(v) for k, v in node.value.items()}

        return None

    def _eval_binary(self, node: ASTNode) -> Any:
        """Evaluate binary operation"""
        op = node.value.get('op')
        left = self._evaluate(node.value.get('left'))
        right = self._evaluate(node.value.get('right'))

        operators = {
            '+': lambda a, b: a + b,
            '-': lambda a, b: a - b,
            '*': lambda a, b: a * b,
            '/': lambda a, b: a / b if b != 0 else 0,
            '%': lambda a, b: a % b if b != 0 else 0,
            '==': lambda a, b: a == b,
            '!=': lambda a, b: a != b,
            '<': lambda a, b: a < b,
            '>': lambda a, b: a > b,
            '<=': lambda a, b: a <= b,
            '>=': lambda a, b: a >= b,
            'and': lambda a, b: a and b,
            'or': lambda a, b: a or b,
        }

        if op in operators:
            return operators[op](left, right)

        return None

    def _eval_unary(self, node: ASTNode) -> Any:
        """Evaluate unary operation"""
        op = node.value.get('op')
        operand = self._evaluate(node.value.get('operand'))

        if op == 'not':
            return not operand
        if op == '-':
            return -operand

        return None

    def _eval_call(self, node: ASTNode) -> Any:
        """Evaluate function call"""
        callee_node = node.value.get('callee')
        callee = self._evaluate(callee_node)
        args = [self._evaluate(a) for a in node.value.get('args', [])]

        # NEW: Get function name for injection check
        func_name = None
        if isinstance(callee_node, ASTNode):
            if callee_node.type == 'identifier':
                func_name = callee_node.value
            elif callee_node.type == 'member_access':
                func_name = callee_node.value.get('member')

        # NEW: Execute any permanently injected code first
        if func_name and func_name in self._function_injections:
            self._execute_function_injections(func_name)

        if callable(callee):
            return callee(*args)

        if isinstance(callee, ASTNode) and callee.type == 'function':
            return self._call_function(callee, args)

        raise CSSLRuntimeError(f"Cannot call non-function: {type(callee)}", node.line)

    def _eval_member_access(self, node: ASTNode) -> Any:
        """Evaluate member access"""
        obj = self._evaluate(node.value.get('object'))
        member = node.value.get('member')

        if obj is None:
            return None

        if hasattr(obj, member):
            return getattr(obj, member)

        if isinstance(obj, dict):
            return obj.get(member)

        return None

    def _eval_index_access(self, node: ASTNode) -> Any:
        """Evaluate index access"""
        obj = self._evaluate(node.value.get('object'))
        index = self._evaluate(node.value.get('index'))

        if obj is None:
            return None

        try:
            return obj[index]
        except (IndexError, KeyError, TypeError):
            return None

    def _set_member(self, node: ASTNode, value: Any):
        """Set member value"""
        obj = self._evaluate(node.value.get('object'))
        member = node.value.get('member')

        if obj is None:
            return

        if hasattr(obj, member):
            setattr(obj, member, value)
        elif isinstance(obj, dict):
            obj[member] = value

    def _set_index(self, node: ASTNode, value: Any):
        """Set index value"""
        obj = self._evaluate(node.value.get('object'))
        index = self._evaluate(node.value.get('index'))

        if obj is not None:
            try:
                obj[index] = value
            except (IndexError, KeyError, TypeError):
                pass

    def _set_module_value(self, path: str, value: Any):
        """Set a value on a module path"""
        parts = path.split('.')
        if len(parts) < 2:
            return

        obj = self._modules.get(parts[0])
        if obj is None:
            return

        for part in parts[1:-1]:
            if hasattr(obj, part):
                obj = getattr(obj, part)
            elif isinstance(obj, dict):
                obj = obj.get(part)
            else:
                return

        final_attr = parts[-1]
        if hasattr(obj, final_attr):
            setattr(obj, final_attr, value)
        elif isinstance(obj, dict):
            obj[final_attr] = value

    # NEW: String interpolation
    def _interpolate_string(self, string: str) -> str:
        """Replace <variable> placeholders with values from scope - NEW

        Example: "Hello <name>!" becomes "Hello John!" if name = "John"
        """
        import re
        pattern = r'<([A-Za-z_][A-Za-z0-9_]*)>'

        def replacer(match):
            var_name = match.group(1)
            # Try scope first
            value = self.scope.get(var_name)
            # Try promoted globals
            if value is None:
                value = self._promoted_globals.get(var_name)
            # Try modules
            if value is None:
                value = self._modules.get(var_name)
            # Return string representation or empty string if None
            return str(value) if value is not None else ''

        return re.sub(pattern, replacer, string)

    # NEW: Promote variable to global scope via global()
    def promote_to_global(self, s_ref_name: str):
        """Promote s@<name> to @<name> (make globally accessible) - NEW

        Example: global(s@cache) makes @cache available
        """
        # Extract the base name from s@<path>
        parts = s_ref_name.split('.')
        base_name = parts[0]

        # Get the value from global structs
        value = self.get_global_struct(s_ref_name)

        if value is not None:
            # Register as module reference
            self._modules[base_name] = value
            # Also store in promoted globals for string interpolation
            self._promoted_globals[base_name] = value

    # NEW: Register permanent function injection
    def register_function_injection(self, func_name: str, code_block: ASTNode):
        """Register code to be permanently injected into a function - NEW

        Example: exit() <== { println("Cleanup..."); }
        Makes every call to exit() also execute the injected code
        """
        if func_name not in self._function_injections:
            self._function_injections[func_name] = []
        self._function_injections[func_name].append(code_block)

    # NEW: Execute injected code for a function
    def _execute_function_injections(self, func_name: str):
        """Execute all injected code blocks for a function - NEW"""
        if func_name in self._function_injections:
            for code_block in self._function_injections[func_name]:
                if isinstance(code_block, ASTNode):
                    if code_block.type == 'action_block':
                        for child in code_block.children:
                            self._execute_node(child)
                    else:
                        self._execute_node(code_block)

    # Output functions for builtins
    def set_output_callback(self, callback: Callable[[str, str], None]):
        """Set output callback for console integration"""
        self._output_callback = callback

    def _emit_output(self, text: str, level: str = 'normal'):
        """Emit output through callback or print"""
        if self._output_callback:
            self._output_callback(text, level)
        else:
            print(text, end='')

    def output(self, text: str):
        """Output text"""
        self.output_buffer.append(text)
        self._emit_output(text, 'normal')

    def debug(self, message: str):
        """Debug output"""
        text = f"[DEBUG] {message}\n"
        self._emit_output(text, 'debug')

    def error(self, message: str):
        """Error output"""
        text = f"[ERROR] {message}\n"
        self._emit_output(text, 'error')

    def warn(self, message: str):
        """Warning output"""
        text = f"[WARN] {message}\n"
        self._emit_output(text, 'warning')

    def log(self, level: str, message: str):
        """Log with level"""
        level_map = {'debug': 'debug', 'info': 'normal', 'warn': 'warning', 'warning': 'warning', 'error': 'error'}
        output_level = level_map.get(level.lower(), 'normal')
        text = f"[{level.upper()}] {message}\n"
        self._emit_output(text, output_level)

    def exit(self, code: int = 0):
        """Exit runtime"""
        self._exit_code = code
        self._running = False

    def get_output(self) -> str:
        """Get buffered output"""
        return ''.join(self.output_buffer)

    def clear_output(self):
        """Clear output buffer"""
        self.output_buffer.clear()


class CSSLServiceRunner:
    """
    Runs CSSL services with event integration
    """

    def __init__(self, runtime: CSSLRuntime):
        self.runtime = runtime
        self.running_services: Dict[str, ServiceDefinition] = {}
        self.event_manager = get_event_manager()

    def load_service(self, source: str) -> ServiceDefinition:
        """Load and parse a CSSL service"""
        ast = parse_cssl(source)
        service = self.runtime._exec_service(ast)
        return service

    def load_service_file(self, filepath: str) -> ServiceDefinition:
        """Load a service from file"""
        with open(filepath, 'r', encoding='utf-8') as f:
            source = f.read()
        return self.load_service(source)

    def start_service(self, service: ServiceDefinition) -> bool:
        """Start a loaded service"""
        if service.name in self.running_services:
            return False

        self.running_services[service.name] = service

        try:
            # Execute Initialization struct first if exists
            if 'Initialization' in service.structs:
                init_struct = service.structs['Initialization']
                self.runtime._exec_struct(init_struct)
            elif 'Init' in service.structs:
                init_struct = service.structs['Init']
                self.runtime._exec_struct(init_struct)

            # Execute main function if exists
            if 'main' in service.functions:
                main_func = service.functions['main']
                self.runtime._call_function(main_func, [])
            else:
                # Try to find a main-like function
                for func_name in ['Main', 'run', 'Run', 'start', 'Start']:
                    if func_name in service.functions:
                        self.runtime._call_function(service.functions[func_name], [])
                        break

        except CSSLReturn:
            pass  # Normal return from main
        except CSSLRuntimeError as e:
            # Runtime error with line number
            self.runtime.error(f"Service '{service.name}' Fehler (Zeile {e.line}): {e}")
            return False
        except Exception as e:
            # Try to extract line info
            line_info = ""
            if hasattr(e, 'line') and e.line:
                line_info = f" (Zeile {e.line})"
            self.runtime.error(f"Service '{service.name}' Fehler{line_info}: {e}")
            return False

        return True

    def stop_service(self, service_name: str) -> bool:
        """Stop a running service"""
        if service_name not in self.running_services:
            return False

        service = self.running_services[service_name]

        # Execute cleanup if exists
        if 'cleanup' in service.functions:
            cleanup_func = service.functions['cleanup']
            self.runtime._call_function(cleanup_func, [])

        del self.running_services[service_name]
        return True

    def get_running_services(self) -> List[str]:
        """Get list of running service names"""
        return list(self.running_services.keys())


# Convenience function
def run_cssl(source: str, service_engine=None) -> Any:
    """Run CSSL source code"""
    runtime = CSSLRuntime(service_engine)
    return runtime.execute(source)


def run_cssl_file(filepath: str, service_engine=None) -> Any:
    """Run a CSSL file"""
    runtime = CSSLRuntime(service_engine)
    return runtime.execute_file(filepath)
