use std::collections::HashMap;

use crate::frontend::ast::*;
use crate::frontend::sema::Ty;
use crate::middle::mir::*;

/// Lowers a fully-typed AST into a linear Control Flow Graph (MIR).
pub struct MirBuilder;

impl MirBuilder {
    /// Builds a `MirModule` from a `TypedModule`.
    pub fn build(module: &TypedModule) -> MirModule {
        let mut extern_functions = Vec::new();
        let mut functions = Vec::new();
        let mut structs = HashMap::new();

        // Collect struct layouts so the backend knows their sizes
        for decl in &module.decls {
            if let TypedDeclKind::Struct { name, fields, .. } = &decl.kind {
                structs.insert(
                    name.clone(),
                    fields
                        .iter()
                        .map(|f| (f.name.clone(), Ty::from(&f.ty.kind)))
                        .collect(),
                );
            }
        }

        for decl in &module.decls {
            match &decl.kind {
                TypedDeclKind::Function {
                    name,
                    name_span: _,
                    params,
                    return_type,
                    body,
                } => {
                    let is_sret = matches!(return_type, Ty::Struct(_) | Ty::Array(_, _));
                    let mir_return_type = if is_sret {
                        Ty::Unit
                    } else {
                        return_type.clone()
                    };
                    let mut builder = FuncBuilder::new(name.clone(), mir_return_type);

                    // Implement implicit pass-by-reference for struct return values (sret)
                    if is_sret {
                        let sret_id = builder.new_local(
                            "$sret".to_string(),
                            Ty::Pointer(Box::new(return_type.clone())),
                        );
                        builder.params.push(sret_id);
                        builder.sret_local = Some(sret_id);
                    }

                    // Register parameters as local variables
                    for (param_name, ty) in params {
                        let local_id = builder.new_local(param_name.clone(), ty.clone());
                        builder.params.push(local_id);
                    }

                    let entry = builder.new_block();
                    builder.switch_to_block(entry);

                    builder.lower_stmt(body);

                    // If the final block was never terminated (e.g. missing an explicit return),
                    // we insert an implicit return for Unit, or an Unreachable trap otherwise.
                    if builder.current_block.is_some() {
                        let span = body.span;
                        if *return_type == Ty::Unit {
                            builder.terminate(Terminator {
                                kind: TerminatorKind::Return { value: None },
                                span,
                            });
                        } else {
                            builder.terminate(Terminator {
                                kind: TerminatorKind::Unreachable,
                                span,
                            });
                        }
                    }

                    functions.push(builder.finish());
                }
                TypedDeclKind::ForeignFunction {
                    name,
                    params,
                    return_type,
                    ..
                } => {
                    extern_functions.push(MirExternFunction {
                        name: name.clone(),
                        params: params.iter().map(|(_, ty)| ty.clone()).collect(),
                        return_type: return_type.clone(),
                    });
                }
                TypedDeclKind::Struct { .. } => {}
            }
        }

        MirModule {
            structs,
            extern_functions,
            functions,
        }
    }
}

/// A helper struct that manages the state required to construct a single `MirFunction`.
///
/// It keeps track of variable declarations, basic blocks, and handles the flattening
/// of nested AST nodes into a sequential Control Flow Graph.
struct FuncBuilder {
    /// The name of the function being built.
    name: String,
    /// The expected return type of the function.
    return_type: Ty,
    /// `LocalId`s corresponding to the function's parameters.
    params: Vec<LocalId>,
    /// All local variables and compiler-generated temporaries used in the function.
    locals: Vec<LocalDecl>,

    /// The basic blocks that make up the function's control flow graph.
    blocks: Vec<BasicBlock>,
    /// The block currently being populated with statements. If `None`, the builder is in "dead code" territory.
    current_block: Option<BlockId>,
    /// Statements buffered for the current block before a terminator is reached.
    current_statements: Vec<Statement>,
    /// Counter for generating unique `BlockId`s.
    next_block_id: usize,

    /// Scoped mapping from user-defined variable names to their corresponding `LocalId`.
    scopes: Vec<HashMap<String, LocalId>>,

    /// Stack of `(continue_target, break_target)` for nested loops
    loop_stack: Vec<(BlockId, BlockId)>,
    /// Local ID mapped to the hidden `$sret` return pointer (if applicable)
    sret_local: Option<LocalId>,
}

impl FuncBuilder {
    /// Creates a new `FuncBuilder` for a function with the given name and return type.
    fn new(name: String, return_type: Ty) -> Self {
        Self {
            name,
            return_type,
            params: Vec::new(),
            locals: Vec::new(),
            blocks: Vec::new(),
            current_block: None,
            current_statements: Vec::new(),
            next_block_id: 0,
            scopes: vec![HashMap::new()],
            loop_stack: Vec::new(),
            sret_local: None,
        }
    }

    fn enter_scope(&mut self) {
        self.scopes.push(HashMap::new());
    }

    fn leave_scope(&mut self) {
        self.scopes.pop();
    }

    /// Registers a new user-defined local variable and returns its `LocalId`.
    fn new_local(&mut self, name: String, ty: Ty) -> LocalId {
        let id = LocalId(self.locals.len());
        self.locals.push(LocalDecl {
            id,
            ty,
            mutable: false,
            name: Some(name.clone()),
            address_taken: false,
        });
        self.scopes.last_mut().unwrap().insert(name, id);
        id
    }

    /// Creates a new compiler-generated temporary variable and returns its `LocalId`.
    fn new_temp(&mut self, ty: Ty) -> LocalId {
        let id = LocalId(self.locals.len());
        self.locals.push(LocalDecl {
            id,
            ty,
            mutable: false,
            name: None,
            address_taken: false,
        });
        id
    }

    fn lookup(&self, name: &str) -> LocalId {
        for scope in self.scopes.iter().rev() {
            if let Some(id) = scope.get(name) {
                return *id;
            }
        }
        panic!("undeclared variable `{}` in MIR lowering", name);
    }

    /// Allocates a new, empty basic block and returns its `BlockId`.
    fn new_block(&mut self) -> BlockId {
        let id = BlockId(self.next_block_id);
        self.next_block_id += 1;
        id
    }

    /// Sets the given block as the active insertion point for new statements.
    /// Note: The previous block should be terminated before switching.
    fn switch_to_block(&mut self, id: BlockId) {
        assert!(self.current_statements.is_empty());
        self.current_block = Some(id);
    }

    /// Appends a statement to the current basic block.
    /// If the current block has already been terminated, the statement is ignored (dead code elimination).
    fn emit_stmt(&mut self, stmt: Statement) {
        if self.current_block.is_some() {
            self.current_statements.push(stmt);
        }
    }

    /// Terminates the current basic block, sealing it and finalizing its instructions.
    /// Subsequent statements will be ignored until `switch_to_block` is called.
    fn terminate(&mut self, terminator: Terminator) {
        if let Some(id) = self.current_block.take() {
            self.blocks.push(BasicBlock {
                id,
                statements: std::mem::take(&mut self.current_statements),
                terminator,
            });
        }
    }

    /// Finalizes the function construction, returning the complete `MirFunction`.
    fn finish(mut self) -> MirFunction {
        // Ensure basic blocks are strictly ordered by their numerical IDs
        self.blocks.sort_by_key(|b| b.id.0);

        MirFunction {
            name: self.name,
            params: self.params,
            return_type: self.return_type,
            locals: self.locals,
            blocks: self.blocks,
        }
    }

    /// Recursively lowers a typed statement into MIR instructions and basic blocks.
    fn lower_stmt(&mut self, stmt: &TypedStmt) {
        match &stmt.kind {
            TypedStmtKind::Compound { inner } => {
                self.enter_scope();
                for s in inner {
                    self.lower_stmt(s);
                }
                self.leave_scope();
            }
            TypedStmtKind::If {
                condition,
                then,
                elze,
            } => {
                let cond_op = self.lower_expr(condition);

                let then_block = self.new_block();
                let merge_block = self.new_block();
                let else_block = if elze.is_some() {
                    self.new_block()
                } else {
                    merge_block
                };

                self.terminate(Terminator {
                    kind: TerminatorKind::CondBranch {
                        cond: cond_op,
                        target_true: then_block,
                        target_false: else_block,
                    },
                    span: stmt.span,
                });

                self.switch_to_block(then_block);
                self.lower_stmt(then);
                self.terminate(Terminator {
                    kind: TerminatorKind::Goto {
                        target: merge_block,
                    },
                    span: then.span,
                });

                if let Some(e) = elze {
                    self.switch_to_block(else_block);
                    self.lower_stmt(e);
                    self.terminate(Terminator {
                        kind: TerminatorKind::Goto {
                            target: merge_block,
                        },
                        span: e.span,
                    });
                }

                self.switch_to_block(merge_block);
            }
            TypedStmtKind::While { condition, body } => {
                let cond_block = self.new_block();
                let body_block = self.new_block();
                let merge_block = self.new_block();

                self.terminate(Terminator {
                    kind: TerminatorKind::Goto { target: cond_block },
                    span: stmt.span,
                });

                self.switch_to_block(cond_block);
                let cond_op = self.lower_expr(condition);
                self.terminate(Terminator {
                    kind: TerminatorKind::CondBranch {
                        cond: cond_op,
                        target_true: body_block,
                        target_false: merge_block,
                    },
                    span: condition.span,
                });

                self.switch_to_block(body_block);
                self.loop_stack.push((cond_block, merge_block));
                self.lower_stmt(body);
                self.loop_stack.pop();

                self.terminate(Terminator {
                    kind: TerminatorKind::Goto { target: cond_block },
                    span: body.span,
                });

                self.switch_to_block(merge_block);
            }
            TypedStmtKind::Break => {
                if let Some(&(_, merge_block)) = self.loop_stack.last() {
                    self.terminate(Terminator {
                        kind: TerminatorKind::Goto {
                            target: merge_block,
                        },
                        span: stmt.span,
                    });
                }
            }
            TypedStmtKind::Continue => {
                if let Some(&(cond_block, _)) = self.loop_stack.last() {
                    self.terminate(Terminator {
                        kind: TerminatorKind::Goto { target: cond_block },
                        span: stmt.span,
                    });
                }
            }
            TypedStmtKind::Return { value } => {
                if let Some(sret_id) = self.sret_local {
                    let val_op = self.lower_expr(value.as_ref().unwrap());
                    self.emit_stmt(Statement {
                        kind: StatementKind::Store {
                            ptr: Operand::Copy(sret_id),
                            val: Rvalue::Use(val_op),
                        },
                        span: stmt.span,
                    });
                    self.terminate(Terminator {
                        kind: TerminatorKind::Return { value: None },
                        span: stmt.span,
                    });
                } else {
                    let val_op = value.as_ref().map(|v| self.lower_expr(v));
                    self.terminate(Terminator {
                        kind: TerminatorKind::Return { value: val_op },
                        span: stmt.span,
                    });
                }
            }
            TypedStmtKind::Let {
                name,
                name_span: _,
                ty,
                initializer,
            } => {
                let local_id = self.new_local(name.clone(), ty.clone());

                if let Some(init_expr) = initializer {
                    let val_op = self.lower_expr(init_expr);
                    self.emit_stmt(Statement {
                        kind: StatementKind::Assign(local_id, Rvalue::Use(val_op)),
                        span: stmt.span,
                    });
                }
            }
            TypedStmtKind::Expression { expr } => {
                self.lower_expr(expr);
            }
        }
    }

    /// Recursively lowers a typed expression into MIR instructions, returning an `Operand` representing its result.
    fn lower_expr(&mut self, expr: &TypedExpr) -> Operand {
        match &expr.kind {
            TypedExprKind::Identifier { name } => {
                let local = self.lookup(name);
                Operand::Copy(local)
            }
            TypedExprKind::Integer { value } => {
                Operand::Constant(ConstantValue::Integer(*value, expr.ty.clone()))
            }
            TypedExprKind::Float { value } => {
                Operand::Constant(ConstantValue::Float(*value, expr.ty.clone()))
            }
            TypedExprKind::Boolean { value } => Operand::Constant(ConstantValue::Boolean(*value)),
            TypedExprKind::Unary { op, expr: inner } => match op {
                UnaryOp::AddressOf => self.lower_address_of(inner),
                UnaryOp::Deref => {
                    let inner_op = self.lower_expr(inner);
                    let temp = self.new_temp(expr.ty.clone());
                    self.emit_stmt(Statement {
                        kind: StatementKind::Assign(temp, Rvalue::ReadPointer(inner_op)),
                        span: expr.span,
                    });
                    Operand::Copy(temp)
                }
                _ => {
                    let inner_op = self.lower_expr(inner);
                    let temp = self.new_temp(expr.ty.clone());
                    self.emit_stmt(Statement {
                        kind: StatementKind::Assign(temp, Rvalue::UnaryOp(*op, inner_op)),
                        span: expr.span,
                    });
                    Operand::Copy(temp)
                }
            },
            TypedExprKind::Binary { op, lhs, rhs } => {
                let lhs_op = self.lower_expr(lhs);
                let rhs_op = self.lower_expr(rhs);
                let temp = self.new_temp(expr.ty.clone());

                self.emit_stmt(Statement {
                    kind: StatementKind::Assign(temp, Rvalue::BinaryOp(*op, lhs_op, rhs_op)),
                    span: expr.span,
                });

                Operand::Copy(temp)
            }
            TypedExprKind::Assign { lval, rval } => {
                let rval_op = self.lower_expr(rval);

                match &lval.kind {
                    TypedExprKind::Identifier { name } => {
                        let local_id = self.lookup(name);
                        self.emit_stmt(Statement {
                            kind: StatementKind::Assign(local_id, Rvalue::Use(rval_op.clone())),
                            span: expr.span,
                        });
                    }
                    _ => {
                        let ptr_op = self.lower_address_of(lval);
                        self.emit_stmt(Statement {
                            kind: StatementKind::Store {
                                ptr: ptr_op,
                                val: Rvalue::Use(rval_op.clone()),
                            },
                            span: expr.span,
                        });
                    }
                }

                rval_op
            }
            TypedExprKind::Cast {
                expr: inner,
                ty: target_ty,
            } => {
                let inner_op = self.lower_expr(inner);
                let temp = self.new_temp(target_ty.clone());

                self.emit_stmt(Statement {
                    kind: StatementKind::Assign(temp, Rvalue::Cast(target_ty.clone(), inner_op)),
                    span: expr.span,
                });

                Operand::Copy(temp)
            }
            TypedExprKind::Call { callee, args } => {
                let callee_name = match &callee.kind {
                    TypedExprKind::Identifier { name } => name.clone(),
                    _ => unimplemented!("indirect function calls are not yet supported"),
                };

                let mut arg_ops = Vec::new();
                let is_sret = matches!(expr.ty, Ty::Struct(_) | Ty::Array(_, _));
                let mut sret_temp = None;

                // Implement implicit sret struct passing to the callee
                if is_sret {
                    let temp = self.new_temp(expr.ty.clone());
                    self.locals[temp.0].address_taken = true;

                    let ptr_temp = self.new_temp(Ty::Pointer(Box::new(expr.ty.clone())));
                    self.emit_stmt(Statement {
                        kind: StatementKind::Assign(ptr_temp, Rvalue::AddressOf(temp)),
                        span: expr.span,
                    });

                    arg_ops.push(Operand::Copy(ptr_temp));
                    sret_temp = Some(temp);
                }

                for arg in args {
                    arg_ops.push(self.lower_expr(arg));
                }

                let mir_ret_ty = if is_sret { Ty::Unit } else { expr.ty.clone() };
                let rval = Rvalue::Call(callee_name, arg_ops, mir_ret_ty.clone());

                if mir_ret_ty == Ty::Unit {
                    self.emit_stmt(Statement {
                        kind: StatementKind::SideEffect(rval),
                        span: expr.span,
                    });
                    if let Some(temp) = sret_temp {
                        Operand::Copy(temp)
                    } else {
                        Operand::Constant(ConstantValue::Boolean(false)) // Dummy value for Unit
                    }
                } else {
                    let temp = self.new_temp(mir_ret_ty);
                    self.emit_stmt(Statement {
                        kind: StatementKind::Assign(temp, rval),
                        span: expr.span,
                    });
                    Operand::Copy(temp)
                }
            }
            TypedExprKind::Struct {
                name,
                name_span: _,
                fields,
            } => {
                let local_id = self.new_temp(expr.ty.clone());
                self.locals[local_id.0].address_taken = true;

                let base_ptr_temp = self.new_temp(Ty::Pointer(Box::new(expr.ty.clone())));
                self.emit_stmt(Statement {
                    kind: StatementKind::Assign(base_ptr_temp, Rvalue::AddressOf(local_id)),
                    span: expr.span,
                });

                for field in fields {
                    let val_op = self.lower_expr(&field.value);
                    let field_ptr_temp =
                        self.new_temp(Ty::Pointer(Box::new(field.value.ty.clone())));

                    self.emit_stmt(Statement {
                        kind: StatementKind::Assign(
                            field_ptr_temp,
                            Rvalue::GetFieldPtr {
                                base_ptr: Operand::Copy(base_ptr_temp),
                                struct_name: name.clone(),
                                field_name: field.name.clone(),
                            },
                        ),
                        span: field.name_span,
                    });

                    self.emit_stmt(Statement {
                        kind: StatementKind::Store {
                            ptr: Operand::Copy(field_ptr_temp),
                            val: Rvalue::Use(val_op),
                        },
                        span: field.value.span,
                    });
                }

                Operand::Copy(local_id)
            }
            TypedExprKind::FieldAccess { .. } => {
                let ptr_op = self.lower_address_of(expr);
                let temp = self.new_temp(expr.ty.clone());
                self.emit_stmt(Statement {
                    kind: StatementKind::Assign(temp, Rvalue::ReadPointer(ptr_op)),
                    span: expr.span,
                });
                Operand::Copy(temp)
            }
            TypedExprKind::Array { elements } => {
                let local_id = self.new_temp(expr.ty.clone());
                self.locals[local_id.0].address_taken = true;

                let base_ptr_temp = self.new_temp(Ty::Pointer(Box::new(expr.ty.clone())));
                self.emit_stmt(Statement {
                    kind: StatementKind::Assign(base_ptr_temp, Rvalue::AddressOf(local_id)),
                    span: expr.span,
                });

                let element_ty = match &expr.ty {
                    Ty::Array(inner, _) => *inner.clone(),
                    _ => unreachable!(),
                };

                for (i, el) in elements.iter().enumerate() {
                    let val_op = self.lower_expr(el);
                    let index_op = Operand::Constant(ConstantValue::Integer(i as u64, Ty::I32));
                    let element_ptr_temp = self.new_temp(Ty::Pointer(Box::new(element_ty.clone())));

                    self.emit_stmt(Statement {
                        kind: StatementKind::Assign(
                            element_ptr_temp,
                            Rvalue::GetElementPtr {
                                base_ptr: Operand::Copy(base_ptr_temp),
                                index: index_op,
                                element_ty: element_ty.clone(),
                            },
                        ),
                        span: el.span,
                    });

                    self.emit_stmt(Statement {
                        kind: StatementKind::Store {
                            ptr: Operand::Copy(element_ptr_temp),
                            val: Rvalue::Use(val_op),
                        },
                        span: el.span,
                    });
                }
                Operand::Copy(local_id)
            }
            TypedExprKind::Index { .. } => {
                let ptr_op = self.lower_address_of(expr);
                let temp = self.new_temp(expr.ty.clone());
                self.emit_stmt(Statement {
                    kind: StatementKind::Assign(temp, Rvalue::ReadPointer(ptr_op)),
                    span: expr.span,
                });
                Operand::Copy(temp)
            }
        }
    }

    /// Safely computes the memory address of an expression without extracting or dereferencing
    /// its underlying evaluated contents explicitly.
    fn lower_address_of(&mut self, expr: &TypedExpr) -> Operand {
        match &expr.kind {
            TypedExprKind::Identifier { name } => {
                let id = self.lookup(name);
                self.locals[id.0].address_taken = true;
                let temp = self.new_temp(Ty::Pointer(Box::new(expr.ty.clone())));
                self.emit_stmt(Statement {
                    kind: StatementKind::Assign(temp, Rvalue::AddressOf(id)),
                    span: expr.span,
                });
                Operand::Copy(temp)
            }
            TypedExprKind::Unary {
                op: UnaryOp::Deref,
                expr: ptr_expr,
            } => self.lower_expr(ptr_expr), // `&*ptr` is optimized right back to `ptr`!
            TypedExprKind::FieldAccess {
                expr: base,
                field,
                field_span: _,
            } => {
                let base_ptr = self.lower_address_of(base);
                let struct_name = match &base.ty {
                    Ty::Struct(name) => name.clone(),
                    _ => unreachable!("field access on non-struct"),
                };
                let temp = self.new_temp(Ty::Pointer(Box::new(expr.ty.clone())));
                self.emit_stmt(Statement {
                    kind: StatementKind::Assign(
                        temp,
                        Rvalue::GetFieldPtr {
                            base_ptr,
                            struct_name,
                            field_name: field.clone(),
                        },
                    ),
                    span: expr.span,
                });
                Operand::Copy(temp)
            }
            TypedExprKind::Index { array, index } => {
                let base_ptr = match &array.ty {
                    Ty::Array(_, _) => self.lower_address_of(array),
                    Ty::Pointer(_) => self.lower_expr(array),
                    _ => unreachable!(),
                };
                let index_op = self.lower_expr(index);
                let temp = self.new_temp(Ty::Pointer(Box::new(expr.ty.clone())));
                self.emit_stmt(Statement {
                    kind: StatementKind::Assign(
                        temp,
                        Rvalue::GetElementPtr {
                            base_ptr,
                            index: index_op,
                            element_ty: expr.ty.clone(),
                        },
                    ),
                    span: expr.span,
                });
                Operand::Copy(temp)
            }
            _ => {
                let val_op = self.lower_expr(expr);
                if let Operand::Copy(id) = val_op {
                    self.locals[id.0].address_taken = true;
                    let temp = self.new_temp(Ty::Pointer(Box::new(expr.ty.clone())));
                    self.emit_stmt(Statement {
                        kind: StatementKind::Assign(temp, Rvalue::AddressOf(id)),
                        span: expr.span,
                    });
                    Operand::Copy(temp)
                } else {
                    unreachable!("cannot safely take address of constant rvalue")
                }
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::frontend::parser::Parser;
    use crate::frontend::sema::Sema;

    /// Helper function to parse, analyze, and build a MIR module from source code.
    fn build_mir(source: &str) -> MirModule {
        let mut parser = Parser::new(source);
        let module = parser.parse_module();
        if let Some(errors) = parser.errors() {
            panic!("Parse errors: {:?}", errors);
        }

        let mut sema = Sema::new();
        let typed_module = sema.analyze_module(&module);
        if let Some(errors) = sema.errors() {
            panic!("Semantic errors: {:?}", errors);
        }

        MirBuilder::build(&typed_module)
    }

    #[test]
    fn test_lower_while_loop() {
        let mir = build_mir("fn main() { while true { } }");
        let func = &mir.functions[0];

        // Ensure exactly 4 basic blocks are generated
        assert_eq!(func.blocks.len(), 4);

        // Block 0: Entry -> Goto(1)
        assert!(matches!(
            func.blocks[0].terminator.kind,
            TerminatorKind::Goto { target: BlockId(1) }
        ));
        // Block 1: Cond -> CondBranch(true -> 2, false -> 3)
        assert!(matches!(
            func.blocks[1].terminator.kind,
            TerminatorKind::CondBranch {
                target_true: BlockId(2),
                target_false: BlockId(3),
                ..
            }
        ));
        // Block 2: Body -> Goto(1)
        assert!(matches!(
            func.blocks[2].terminator.kind,
            TerminatorKind::Goto { target: BlockId(1) }
        ));
        // Block 3: Merge -> Return
        assert!(matches!(
            func.blocks[3].terminator.kind,
            TerminatorKind::Return { value: None }
        ));
    }

    #[test]
    fn test_lower_break_and_continue() {
        let mir = build_mir("fn main() { while true { continue; break; } }");
        let func = &mir.functions[0];

        // The body block (BlockId(2)) hits `continue` first, meaning it terminates immediately with a Goto back to the condition block.
        // The trailing `break` is correctly skipped by the builder as automatic dead code!
        assert!(matches!(
            func.blocks[2].terminator.kind,
            TerminatorKind::Goto { target: BlockId(1) }
        ));
    }

    #[test]
    fn test_lower_let_and_assign() {
        let mir = build_mir("fn main() { let a = 5; a = 10; }");
        let func = &mir.functions[0];

        assert_eq!(func.locals.len(), 1); // Only 1 variable 'a' (No temporaries generated for literals)
        assert_eq!(func.blocks.len(), 1); // No branches, so 1 block

        let block = &func.blocks[0];
        assert_eq!(block.statements.len(), 2); // Includes both the let initialization and the later assignment
        assert!(matches!(
            block.statements[0].kind,
            StatementKind::Assign(LocalId(0), _)
        ));
        assert!(matches!(
            block.statements[1].kind,
            StatementKind::Assign(LocalId(0), _)
        ));
    }
}
