class Interpreter:
    def __init__(self, ast):
        self.ast = ast
        self.scope_stack = [{}]
        self.functions = {
            "ask": self._ask_builtin,
            "len": self._len_builtin,
            "type": self._type_builtin
        }

    def _ask_builtin(self, prompt):
        return input(prompt)

    @property
    def current_scope(self):
        return self.scope_stack[-1]

    def interpret(self):
        for node in self.ast:
            self.visit(node)

    def visit(self, node):
        method_name = f'visit_{type(node).__name__}'
        method = getattr(self, method_name, self.no_visit_method)
        return method(node)

    def no_visit_method(self, node):
        raise Exception(f'No visit_{type(node).__name__} method defined')

    def visit_Number(self, node):
        return node.value

    def visit_String(self, node):
        return node.value

    def visit_Boolean(self, node):
        return node.value

    def visit_LogicalOp(self, node):
        if node.op.type == 'AND':
            return self.visit(node.left) and self.visit(node.right)
        elif node.op.type == 'OR':
            return self.visit(node.left) or self.visit(node.right)

    def visit_UnaryOp(self, node):
        if node.op.type == 'NOT':
            return not self.visit(node.right)

    def visit_BinOp(self, node):
        left_val = self.visit(node.left)
        right_val = self.visit(node.right)
        if node.op.type == 'PLUS':
            return left_val + right_val
        elif node.op.type == 'MINUS':
            return left_val - right_val
        elif node.op.type == 'MUL':
            return left_val * right_val
        elif node.op.type == 'DIV':
            return left_val / right_val

    def visit_Comparison(self, node):
        left_val = self.visit(node.left)
        right_val = self.visit(node.right)
        if node.op.type == 'EQ':
            return left_val == right_val
        elif node.op.type == 'NEQ':
            return left_val != right_val
        elif node.op.type == 'GT':
            return left_val > right_val
        elif node.op.type == 'GTE':
            return left_val >= right_val
        elif node.op.type == 'LT':
            return left_val < right_val
        elif node.op.type == 'LTE':
            return left_val <= right_val

    def visit_Print(self, node):
        value_to_print = self.visit(node.value)
        print(value_to_print)

    def visit_Assign(self, node):
        var_name = node.left.value
        value = self.visit(node.right)
        self.current_scope[var_name] = value

    def visit_Variable(self, node):
        var_name = node.value
        # Search up the scope stack for the variable
        for scope in reversed(self.scope_stack):
            if var_name in scope:
                return scope[var_name]
        raise NameError(f"name '{var_name}' is not defined")

    def visit_Block(self, node):
        for statement in node.statements:
            self.visit(statement)

    def visit_IfStatement(self, node):
        condition_result = self.visit(node.condition)
        if condition_result:
            self.visit(node.if_block)
        elif node.else_block:
            self.visit(node.else_block)

    def visit_WhileStatement(self, node):
        while self.visit(node.condition):
            self.visit(node.body)

    def visit_List(self, node):
        elements = [self.visit(element) for element in node.elements]
        return elements

    def visit_IndexAccess(self, node):
        target = self.visit(node.target)
        index = self.visit(node.index)
        if isinstance(target, list):
            return target[index]
        else:
            raise TypeError(f"Cannot index type {type(target).__name__}")

    def visit_Dictionary(self, node):
        dictionary = {}
        for key_expr, value_expr in node.pairs:
            key = self.visit(key_expr)
            value = self.visit(value_expr)
            dictionary[key] = value
        return dictionary

    def visit_PropertyAccess(self, node):
        target = self.visit(node.target)
        property_name = node.property_name
        if isinstance(target, dict):
            return target.get(property_name)
        else:
            raise TypeError(f"Cannot access property '{property_name}' on type {type(target).__name__}")

    def visit_FunctionDefinition(self, node):
        self.functions[node.name] = {
            'parameters': node.parameters,
            'body': node.body
        }

    def visit_FunctionCall(self, node):
        func_name = node.name
        if func_name not in self.functions:
            raise NameError(f"Function '{func_name}' is not defined")

        func_info = self.functions[func_name]

        # Evaluate arguments
        evaluated_arguments = [self.visit(arg) for arg in node.arguments]

        # Check if it's a built-in function
        if callable(func_info):
            return func_info(*evaluated_arguments)

        # Existing logic for user-defined functions
        # Create a new scope for the function call
        new_scope = {}
        for i, param_name in enumerate(func_info['parameters']):
            new_scope[param_name] = evaluated_arguments[i]

        self.scope_stack.append(new_scope)

        # Execute function body
        try:
            self.visit(func_info['body'])
        except ReturnValue as e:
            self.scope_stack.pop() # Pop the function scope
            return e.value
        finally:
            # Ensure scope is popped even if no return or an error occurs
            if len(self.scope_stack) > 1: # Don't pop global scope
                self.scope_stack.pop()

    def visit_ReturnStatement(self, node):
        raise ReturnValue(self.visit(node.value))

    @staticmethod
    def _len_builtin(obj):
        return len(obj)

    @staticmethod
    def _type_builtin(obj):
        return str(type(obj).__name__)

class ReturnValue(Exception):
    def __init__(self, value):
        self.value = value