use std::collections::HashMap;

use ty_ree::declaration as ty;
use ty_ree::expression as ty_expression;
use ty_ree::types as ty_types;

use t_ree::declaration as t;
use t_ree::expression as t_expression;
use t_ree::types as t_types;

use crate::{is_field_of, is_struct_backed};

fn has_duplicate_tuple_fields(inner_type: &ty_types::Type) -> bool {
    if let ty_types::Type::Tuple(fields) = inner_type {
        let mut seen = std::collections::HashSet::new();
        for field in fields {
            if let ty_types::Type::Named(name, _) = field
                && !seen.insert(name)
            {
                return true;
            }
        }
    }
    false
}

pub fn lower_declaration(
    declaration: ty::Declaration,
    output: &mut Vec<t::Declaration>,
    newtypes: &HashMap<String, ty_types::Type>,
    public_types: &std::collections::HashSet<String>,
) -> Result<(), String> {
    match declaration {
        ty::Declaration::Type(newtype) => {
            reject_generics(&newtype.generic_parameters, &newtype.name)?;
            if newtype.name == "main" {
                return Ok(());
            }
            if has_duplicate_tuple_fields(&newtype.inner_type) {
                return Ok(());
            }
            output.push(t::Declaration::Type(lower_newtype(newtype)?));
        }
        ty::Declaration::Impl(impl_block) => {
            reject_generics(&impl_block.generic_parameters, "<impl>")?;
            let is_public = if let ty_types::Type::Named(name, _) = &impl_block.target_type {
                public_types.contains(name)
            } else {
                false
            };
            output.push(t::Declaration::Function(lower_callable_impl(
                &impl_block.target_type,
                impl_block.callable,
                newtypes,
                is_public,
            )?));
        }
        ty::Declaration::Extern(extern_function) => {
            output.push(t::Declaration::Extern(lower_extern(extern_function)?));
        }
        ty::Declaration::Constant(constant) => {
            output.push(t::Declaration::Constant(lower_constant(
                constant, newtypes,
            )?));
        }
        ty::Declaration::Import(path) => {
            output.push(t::Declaration::Import(path));
        }
    }
    Ok(())
}

fn extract_fields(
    target_name: &str,
    newtypes: &HashMap<String, ty_types::Type>,
) -> Result<Vec<(String, ty_types::Type)>, String> {
    let inner = newtypes
        .get(target_name)
        .ok_or_else(|| format!("unknown type '{target_name}' in impl"))?;

    match inner {
        ty_types::Type::Named(field_name, _) => Ok(vec![(field_name.clone(), inner.clone())]),
        ty_types::Type::Tuple(fields) => {
            let mut result = Vec::new();
            for field in fields {
                if let ty_types::Type::Named(field_name, _) = field {
                    result.push((field_name.clone(), field.clone()));
                } else {
                    return Err(format!(
                        "impl on '{target_name}': tuple fields must be named types"
                    ));
                }
            }
            Ok(result)
        }
        ty_types::Type::Array(element_type, ty_types::ConstExpression::Integer(size)) => {
            let mut result = Vec::new();
            for index in 0..*size {
                result.push((format!("_{index}"), *element_type.clone()));
            }
            Ok(result)
        }
        other => Ok(vec![("it".into(), other.clone())]),
    }
}

fn replace_it_in_expression(
    expression: ty_expression::Expression,
    it_name: Option<&str>,
) -> ty_expression::Expression {
    let kind = replace_it_in_kind(expression.kind, it_name);
    ty_expression::Expression::new(kind, expression.resolved_type)
}

fn rit(expression: ty_expression::Expression, it_name: Option<&str>) -> ty_expression::Expression {
    replace_it_in_expression(expression, it_name)
}

fn replace_it_in_kind(
    kind: ty_expression::ExpressionKind,
    it_name: Option<&str>,
) -> ty_expression::ExpressionKind {
    use ty_expression::ExpressionKind as E;

    match kind {
        E::Variable(ref name) if name == "it" && it_name.is_some() => {
            E::Variable(it_name.unwrap().into())
        }
        E::Index(inner, index)
            if it_name.is_none()
                && matches!(inner.kind, E::Variable(ref name) if name == "it")
                && matches!(
                    index.kind,
                    E::Literal(t_ree::expression::Literal::Integer(_))
                ) =>
        {
            if let E::Literal(t_ree::expression::Literal::Integer(value)) = index.kind {
                E::Variable(format!("_{value}"))
            } else {
                unreachable!()
            }
        }
        E::Field(inner, field_name) if matches!(inner.kind, E::Variable(ref name) if name == "it") => {
            E::Variable(field_name)
        }
        E::Field(inner, field_name) => E::Field(Box::new(rit(*inner, it_name)), field_name),
        E::BinaryOperation(operator, left, right) => E::BinaryOperation(
            operator,
            Box::new(rit(*left, it_name)),
            Box::new(rit(*right, it_name)),
        ),
        E::UnaryOperation(operator, operand) => {
            E::UnaryOperation(operator, Box::new(rit(*operand, it_name)))
        }
        E::Call(callee, arguments) => E::Call(
            Box::new(rit(*callee, it_name)),
            arguments.into_iter().map(|e| rit(e, it_name)).collect(),
        ),
        E::Index(object, index) => E::Index(
            Box::new(rit(*object, it_name)),
            Box::new(rit(*index, it_name)),
        ),
        E::Dereference(inner) => E::Dereference(Box::new(rit(*inner, it_name))),
        E::Convert(inner, target) => E::Convert(Box::new(rit(*inner, it_name)), target),
        E::TypeConstruction(name, generic_arguments, fields) => E::TypeConstruction(
            name,
            generic_arguments,
            fields
                .into_iter()
                .map(|(name, expression)| (name, rit(expression, it_name)))
                .collect(),
        ),
        E::ArrayLiteral(elements) => {
            E::ArrayLiteral(elements.into_iter().map(|e| rit(e, it_name)).collect())
        }
        E::TupleLiteral(elements) => {
            E::TupleLiteral(elements.into_iter().map(|e| rit(e, it_name)).collect())
        }
        E::Block(block) => E::Block(replace_it_in_block(block, it_name)),
        E::If {
            condition,
            then_branch,
            else_branch,
        } => E::If {
            condition: Box::new(rit(*condition, it_name)),
            then_branch: replace_it_in_block(then_branch, it_name),
            else_branch: else_branch.map(|b| replace_it_in_block(b, it_name)),
        },
        E::Match { value, arms } => E::Match {
            value: Box::new(rit(*value, it_name)),
            arms: arms
                .into_iter()
                .map(|arm| ty_expression::MatchArm {
                    pattern: arm.pattern,
                    body: replace_it_in_block(arm.body, it_name),
                })
                .collect(),
        },
        E::Replace(target, value) => E::Replace(
            Box::new(rit(*target, it_name)),
            Box::new(rit(*value, it_name)),
        ),
        E::Print(arguments) => E::Print(arguments.into_iter().map(|e| rit(e, it_name)).collect()),
        other => other,
    }
}

fn replace_it_in_block(block: ty_expression::Block, it_name: Option<&str>) -> ty_expression::Block {
    ty_expression::Block {
        statements: block
            .statements
            .into_iter()
            .map(|s| replace_it_in_statement(s, it_name))
            .collect(),
        result: block
            .result
            .map(|expression| Box::new(rit(*expression, it_name))),
    }
}

fn replace_it_in_statement(
    statement: ty_expression::Statement,
    it_name: Option<&str>,
) -> ty_expression::Statement {
    use ty_expression::Statement as S;
    match statement {
        S::Expression(expression) => S::Expression(rit(expression, it_name)),
        S::Let {
            name,
            binding,
            declared_type,
            value,
            line,
        } => S::Let {
            name,
            binding,
            declared_type,
            value: rit(value, it_name),
            line,
        },
        S::Assign(target, value) => S::Assign(rit(target, it_name), rit(value, it_name)),
        S::Return(expression) => S::Return(expression.map(|e| rit(e, it_name))),
        S::Label {
            name,
            parameters,
            initial_arguments,
        } => S::Label {
            name,
            parameters,
            initial_arguments: initial_arguments
                .into_iter()
                .map(|e| rit(e, it_name))
                .collect(),
        },
        S::Jump { label, arguments } => S::Jump {
            label,
            arguments: arguments.into_iter().map(|e| rit(e, it_name)).collect(),
        },
        S::While { condition, body } => S::While {
            condition: rit(condition, it_name),
            body: replace_it_in_block(body, it_name),
        },
        S::Defer(inner) => S::Defer(Box::new(replace_it_in_statement(*inner, it_name))),
    }
}

fn lower_callable_impl(
    target_type: &ty_types::Type,
    callable: ty::Callable,
    newtypes: &HashMap<String, ty_types::Type>,
    is_public: bool,
) -> Result<t::FunctionDefinition, String> {
    let ty_types::Type::Named(target_name, _) = target_type else {
        return Err("impl target must be a named type".into());
    };

    let is_main = target_name == "main";
    let function_name = if is_main {
        "main".into()
    } else {
        format!("impl_{target_name}")
    };
    let is_public = if is_main { true } else { is_public };

    if let Some(original_parameters) = callable.parameters {
        let body = lower_block(callable.body, newtypes)?;
        return Ok(t::FunctionDefinition {
            name: function_name,
            parameters: original_parameters
                .into_iter()
                .map(lower_parameter)
                .collect::<Result<_, _>>()?,
            return_type: lower_type(callable.return_type)?,
            body,
            public: is_public,
            #[allow(clippy::default_trait_access)]
            span: Default::default(),
        });
    }

    let fields = extract_fields(target_name, newtypes)?;

    let mut renames: Vec<(String, String)> = Vec::new();
    for (name, field_type) in &fields {
        if let ty_types::Type::Named(type_name, _) = field_type
            && name == type_name
        {
            renames.push((name.clone(), format!("_{name}")));
        }
    }

    let parameters: Vec<t::Parameter> = fields
        .iter()
        .map(|(name, field_type)| {
            let param_name = renames
                .iter()
                .find(|(old, _)| old == name)
                .map_or_else(|| name.clone(), |(_, new)| new.clone());
            Ok(t::Parameter {
                name: param_name,
                parameter_type: Some(lower_type(field_type.clone())?),
            })
        })
        .collect::<Result<_, String>>()?;

    let it_replacement = if fields.len() == 1 && fields[0].0 != "it" {
        Some(fields[0].0.as_str())
    } else {
        None
    };
    let mut transformed_body = replace_it_in_block(callable.body, it_replacement);
    for (old_name, new_name) in &renames {
        rename_variable_in_block(&mut transformed_body, old_name, new_name);
    }
    let body = lower_block(transformed_body, newtypes)?;

    Ok(t::FunctionDefinition {
        name: function_name,
        parameters,
        return_type: lower_type(callable.return_type)?,
        body,
        public: is_public,
        #[allow(clippy::default_trait_access)]
        span: Default::default(),
    })
}

fn reject_generics(parameters: &[ty_types::GenericParameter], name: &str) -> Result<(), String> {
    if !parameters.is_empty() {
        return Err(format!("generics not yet supported (on '{name}')"));
    }
    Ok(())
}

fn rename_variable_in_block(block: &mut ty_expression::Block, old: &str, new: &str) {
    for statement in &mut block.statements {
        rename_variable_in_statement(statement, old, new);
    }
    if let Some(result) = &mut block.result {
        rename_variable_in_expression(result, old, new);
    }
}

fn rename_variable_in_statement(statement: &mut ty_expression::Statement, old: &str, new: &str) {
    use ty_expression::Statement as S;
    match statement {
        S::Expression(expression) | S::Return(Some(expression)) => {
            rename_variable_in_expression(expression, old, new);
        }
        S::Let { value, .. } | S::Assign(_, value) => {
            rename_variable_in_expression(value, old, new);
        }
        S::While { condition, body } => {
            rename_variable_in_expression(condition, old, new);
            rename_variable_in_block(body, old, new);
        }
        S::Label {
            initial_arguments, ..
        } => {
            for argument in initial_arguments {
                rename_variable_in_expression(argument, old, new);
            }
        }
        S::Jump { arguments, .. } => {
            for argument in arguments {
                rename_variable_in_expression(argument, old, new);
            }
        }
        S::Defer(inner) => rename_variable_in_statement(inner, old, new),
        S::Return(None) => {}
    }
}

fn rename_variable_in_expression(expression: &mut ty_expression::Expression, old: &str, new: &str) {
    use ty_expression::ExpressionKind as E;
    match &mut expression.kind {
        E::Variable(name) if name == old => *name = new.into(),
        E::Call(callee, arguments) => {
            rename_variable_in_expression(callee, old, new);
            for argument in arguments {
                rename_variable_in_expression(argument, old, new);
            }
        }
        E::BinaryOperation(_, left, right) | E::Replace(left, right) => {
            rename_variable_in_expression(left, old, new);
            rename_variable_in_expression(right, old, new);
        }
        E::UnaryOperation(_, operand) | E::Dereference(operand) | E::Convert(operand, _) => {
            rename_variable_in_expression(operand, old, new);
        }
        E::Field(object, _) => rename_variable_in_expression(object, old, new),
        E::Index(object, index) => {
            rename_variable_in_expression(object, old, new);
            rename_variable_in_expression(index, old, new);
        }
        E::Block(block) => rename_variable_in_block(block, old, new),
        E::If {
            condition,
            then_branch,
            else_branch,
        } => {
            rename_variable_in_expression(condition, old, new);
            rename_variable_in_block(then_branch, old, new);
            if let Some(else_branch) = else_branch {
                rename_variable_in_block(else_branch, old, new);
            }
        }
        E::ArrayLiteral(elements) | E::TupleLiteral(elements) | E::Print(elements) => {
            for element in elements {
                rename_variable_in_expression(element, old, new);
            }
        }
        E::TypeConstruction(_, _, fields) => {
            for (_, expression) in fields {
                rename_variable_in_expression(expression, old, new);
            }
        }
        _ => {}
    }
}

fn lower_newtype(newtype: ty::Newtype) -> Result<t::Newtype, String> {
    Ok(t::Newtype {
        name: newtype.name,
        inner_type: lower_type(newtype.inner_type)?,
        public: newtype.public,
    })
}

fn lower_extern(extern_function: ty::ExternFunction) -> Result<t::ExternFunction, String> {
    Ok(t::ExternFunction {
        name: extern_function.name,
        parameters: lower_parameter_list(extern_function.parameters)?,
        return_type: lower_type(extern_function.return_type)?,
    })
}

fn lower_constant(
    constant: ty::Constant,
    newtypes: &HashMap<String, ty_types::Type>,
) -> Result<t::Constant, String> {
    Ok(t::Constant {
        name: constant.name,
        constant_type: lower_type(constant.constant_type)?,
        value: lower_expression(constant.value, newtypes)?,
        public: constant.public,
    })
}

fn lower_parameter(parameter: ty::Parameter) -> Result<t::Parameter, String> {
    Ok(t::Parameter {
        name: parameter.name,
        parameter_type: Some(lower_type(parameter.parameter_type)?),
    })
}

fn lower_parameter_list(parameter_list: ty::ParameterList) -> Result<t::ParameterList, String> {
    match parameter_list {
        ty::ParameterList::Fixed(parameters) => Ok(t::ParameterList::Fixed(
            parameters
                .into_iter()
                .map(lower_parameter)
                .collect::<Result<_, _>>()?,
        )),
        ty::ParameterList::Variadic(parameters) => Ok(t::ParameterList::Variadic(
            parameters
                .into_iter()
                .map(lower_parameter)
                .collect::<Result<_, _>>()?,
        )),
    }
}

fn lower_type(source: ty_types::Type) -> Result<t_types::Type, String> {
    match source {
        ty_types::Type::Never => Ok(t_types::Type::Never),
        ty_types::Type::Bool => Ok(t_types::Type::Bool),
        ty_types::Type::Int(width, signedness) => Ok(t_types::Type::Int(width, signedness)),
        ty_types::Type::Float(width) => Ok(t_types::Type::Float(width)),

        ty_types::Type::Pointer(mutability, _lifetime, inner) => Ok(t_types::Type::Pointer(
            mutability,
            Box::new(lower_type(*inner)?),
        )),
        ty_types::Type::Reference(mutability, _lifetime, inner) => Ok(t_types::Type::Pointer(
            mutability,
            Box::new(lower_type(*inner)?),
        )),

        ty_types::Type::Array(element, size) => {
            let size = lower_const_to_usize(size)?;
            Ok(t_types::Type::Array(Box::new(lower_type(*element)?), size))
        }
        ty_types::Type::Vector(element, size) => {
            let size = lower_const_to_usize(size)?;
            Ok(t_types::Type::Vector(Box::new(lower_type(*element)?), size))
        }

        ty_types::Type::Slice(mutability, element) => Ok(t_types::Type::Slice(
            mutability,
            Box::new(lower_type(*element)?),
        )),
        ty_types::Type::Tuple(fields) => Ok(t_types::Type::Tuple(
            fields
                .into_iter()
                .map(lower_type)
                .collect::<Result<_, _>>()?,
        )),
        ty_types::Type::Enum(variants) => Ok(t_types::Type::Enum(
            variants
                .into_iter()
                .map(lower_type)
                .collect::<Result<_, _>>()?,
        )),

        ty_types::Type::Named(name, generic_arguments) => {
            if !generic_arguments.is_empty() {
                return Err(format!("generic arguments on '{name}' not yet supported"));
            }
            Ok(t_types::Type::Named(name))
        }

        ty_types::Type::Function(signature) => {
            Ok(t_types::Type::Function(t_types::FunctionSignature {
                parameters: signature
                    .parameters
                    .into_iter()
                    .map(lower_type)
                    .collect::<Result<_, _>>()?,
                return_type: Box::new(lower_type(*signature.return_type)?),
            }))
        }

        ty_types::Type::Generic(name) => Err(format!("unresolved generic type '{name}'")),
        ty_types::Type::Inferred => Err("unresolved inferred type".into()),
    }
}

fn lower_type_optional(source: ty_types::Type) -> Result<Option<t_types::Type>, String> {
    if contains_inferred(&source) {
        return Ok(None);
    }
    lower_type(source).map(Some)
}

fn contains_inferred(source: &ty_types::Type) -> bool {
    match source {
        ty_types::Type::Inferred => true,
        ty_types::Type::Array(element, size) | ty_types::Type::Vector(element, size) => {
            contains_inferred(element) || contains_unresolved_const(size)
        }
        ty_types::Type::Pointer(_, _, inner) | ty_types::Type::Reference(_, _, inner) => {
            contains_inferred(inner)
        }
        _ => false,
    }
}

fn contains_unresolved_const(constant: &ty_types::ConstExpression) -> bool {
    use ty_types::ConstExpression as C;
    match constant {
        C::Integer(_) => false,
        C::Named(_) | C::Call(..) | C::Field(..) | C::Construction(..) => true,
        C::Add(l, r)
        | C::Subtract(l, r)
        | C::Multiply(l, r)
        | C::Divide(l, r)
        | C::Modulo(l, r)
        | C::BitwiseAnd(l, r)
        | C::BitwiseOr(l, r)
        | C::BitwiseXor(l, r)
        | C::ShiftLeft(l, r)
        | C::ShiftRight(l, r) => contains_unresolved_const(l) || contains_unresolved_const(r),
    }
}

fn lower_const_to_usize(constant: ty_types::ConstExpression) -> Result<usize, String> {
    crate::const_expression::eval_to_usize(constant)
}

pub fn lower_block(
    block: ty_expression::Block,
    newtypes: &HashMap<String, ty_types::Type>,
) -> Result<t_expression::Block, String> {
    let mut statements = Vec::new();
    for statement in block.statements {
        if let ty_expression::Statement::While { condition, body } = statement {
            lower_while(condition, body, newtypes, &mut statements)?;
        } else {
            statements.push(lower_statement(statement, newtypes)?);
        }
    }
    Ok(t_expression::Block {
        statements,
        result: block
            .result
            .map(|expression| lower_expression(*expression, newtypes).map(Box::new))
            .transpose()?,
    })
}

static WHILE_COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);

fn lower_while(
    condition: ty_expression::Expression,
    body: ty_expression::Block,
    newtypes: &HashMap<String, ty_types::Type>,
    output: &mut Vec<t_expression::Statement>,
) -> Result<(), String> {
    use t_expression::{ExpressionKind as T, Statement as S};

    let label_name = format!(
        "_while_{}",
        WHILE_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
    );
    let lowered_condition = lower_expression(condition, newtypes)?;
    let mut lowered_body = lower_block(body, newtypes)?;
    lowered_body.statements.push(S::Jump {
        label: label_name.clone(),
        arguments: vec![],
    });

    output.push(S::Label {
        name: label_name,
        parameters: vec![],
        initial_arguments: vec![],
    });
    output.push(S::Expression(t_expression::Expression::new(
        T::If {
            condition: Box::new(lowered_condition),
            then_branch: lowered_body,
            else_branch: None,
        },
        Some(t_types::Type::unit()),
    )));
    Ok(())
}

fn lower_expression(
    expression: ty_expression::Expression,
    newtypes: &HashMap<String, ty_types::Type>,
) -> Result<t_expression::Expression, String> {
    Ok(t_expression::Expression::new(
        lower_expression_kind(expression.kind, newtypes)?,
        lower_type_optional(expression.resolved_type)?,
    ))
}

fn lower_expression_kind(
    kind: ty_expression::ExpressionKind,
    newtypes: &HashMap<String, ty_types::Type>,
) -> Result<t_expression::ExpressionKind, String> {
    use t_expression::ExpressionKind as T;
    use ty_expression::ExpressionKind as Ty;

    match kind {
        Ty::Literal(literal) => Ok(T::Literal(literal)),
        Ty::Variable(name) => Ok(T::Variable(name)),

        Ty::BinaryOperation(operator, left, right) => Ok(T::BinaryOperation(
            operator,
            Box::new(lower_expression(*left, newtypes)?),
            Box::new(lower_expression(*right, newtypes)?),
        )),
        Ty::UnaryOperation(operator, operand) => Ok(T::UnaryOperation(
            operator,
            Box::new(lower_expression(*operand, newtypes)?),
        )),

        Ty::Call(callee, arguments) => Ok(T::Call(
            Box::new(lower_expression(*callee, newtypes)?),
            arguments
                .into_iter()
                .map(|argument| lower_expression(argument, newtypes))
                .collect::<Result<_, _>>()?,
        )),
        Ty::Field(object, field) => Ok(T::Field(
            Box::new(lower_expression(*object, newtypes)?),
            field,
        )),
        Ty::Index(object, index) => Ok(T::Index(
            Box::new(lower_expression(*object, newtypes)?),
            Box::new(lower_expression(*index, newtypes)?),
        )),

        Ty::Specialize(_, _) => {
            Err("generic specialization should be resolved before lowering".into())
        }

        Ty::Dereference(inner) => Ok(T::Dereference(Box::new(lower_expression(
            *inner, newtypes,
        )?))),
        Ty::Eval(inner) => lower_expression_kind(inner.kind, newtypes),

        Ty::Convert(expression, target_type) => {
            let source_struct_name = match &expression.resolved_type {
                ty_types::Type::Named(name, _) if is_struct_backed(name, newtypes) => {
                    Some(name.clone())
                }
                _ => None,
            };
            let target_is_struct = match &target_type {
                ty_types::Type::Named(name, _) => is_struct_backed(name, newtypes),
                _ => false,
            };
            if let Some(ref source_name) = source_struct_name {
                if target_is_struct {
                    Ok(T::Convert(
                        Box::new(lower_expression(*expression, newtypes)?),
                        lower_type(target_type)?,
                    ))
                } else if let ty_types::Type::Named(target_name, _) = &target_type {
                    if is_field_of(source_name, target_name, newtypes) {
                        Ok(T::Field(
                            Box::new(lower_expression(*expression, newtypes)?),
                            target_name.clone(),
                        ))
                    } else {
                        Ok(T::Convert(
                            Box::new(lower_expression(*expression, newtypes)?),
                            lower_type(target_type)?,
                        ))
                    }
                } else {
                    Ok(T::Convert(
                        Box::new(lower_expression(*expression, newtypes)?),
                        lower_type(target_type)?,
                    ))
                }
            } else {
                Ok(T::Convert(
                    Box::new(lower_expression(*expression, newtypes)?),
                    lower_type(target_type)?,
                ))
            }
        }
        Ty::SizeOf(target_type) => Ok(T::SizeOf(lower_type(target_type)?)),

        Ty::TypeConstruction(name, generic_arguments, fields) => {
            if !generic_arguments.is_empty() {
                return Err(format!(
                    "generic arguments on make '{name}' not yet supported"
                ));
            }
            Ok(T::TypeConstruction(
                name,
                fields
                    .into_iter()
                    .map(|(field_name, expression)| {
                        Ok((field_name, lower_expression(expression, newtypes)?))
                    })
                    .collect::<Result<_, String>>()?,
            ))
        }
        Ty::ArrayLiteral(elements) => Ok(T::ArrayLiteral(
            elements
                .into_iter()
                .map(|element| lower_expression(element, newtypes))
                .collect::<Result<_, _>>()?,
        )),
        Ty::ArrayRepeat(value, count) => {
            let count = lower_const_to_usize(count)?;
            let lowered = lower_expression(*value, newtypes)?;
            Ok(T::ArrayLiteral(vec![lowered; count]))
        }
        Ty::TupleLiteral(elements) => Ok(T::TupleLiteral(
            elements
                .into_iter()
                .map(|element| lower_expression(element, newtypes))
                .collect::<Result<_, _>>()?,
        )),

        Ty::Block(block) => Ok(T::Block(lower_block(block, newtypes)?)),
        Ty::If {
            condition,
            then_branch,
            else_branch,
        } => Ok(T::If {
            condition: Box::new(lower_expression(*condition, newtypes)?),
            then_branch: lower_block(then_branch, newtypes)?,
            else_branch: else_branch
                .map(|block| lower_block(block, newtypes))
                .transpose()?,
        }),
        Ty::Match { value, arms } => Ok(T::Match {
            value: Box::new(lower_expression(*value, newtypes)?),
            arms: arms
                .into_iter()
                .map(|arm| {
                    Ok(t_expression::MatchArm {
                        pattern: arm.pattern,
                        body: lower_block(arm.body, newtypes)?,
                    })
                })
                .collect::<Result<_, String>>()?,
        }),

        Ty::Replace(target, value) => {
            if let Ty::BinaryOperation(
                ty_ree::expression::operator::BinaryOperator::Arithmetic(op),
                ref left,
                _,
            ) = value.kind
            {
                let same_target = match (&target.kind, &left.kind) {
                    (Ty::Variable(a), Ty::Variable(b)) => a == b,
                    (Ty::Variable(a), Ty::Dereference(inner)) => {
                        matches!(&inner.kind, Ty::Variable(b) if a == b)
                    }
                    _ => false,
                };
                if same_target && let Ty::BinaryOperation(_, _, right) = value.kind {
                    let t_op = match op {
                        ty_ree::expression::operator::ArithmeticOperator::Add => {
                            t_ree::operator::ArithmeticOperator::Add
                        }
                        ty_ree::expression::operator::ArithmeticOperator::Subtract => {
                            t_ree::operator::ArithmeticOperator::Subtract
                        }
                        ty_ree::expression::operator::ArithmeticOperator::Multiply => {
                            t_ree::operator::ArithmeticOperator::Multiply
                        }
                        ty_ree::expression::operator::ArithmeticOperator::Divide => {
                            t_ree::operator::ArithmeticOperator::Divide
                        }
                        ty_ree::expression::operator::ArithmeticOperator::Remainder => {
                            t_ree::operator::ArithmeticOperator::Remainder
                        }
                    };
                    return Ok(T::OpAssign(
                        t_op,
                        Box::new(lower_expression(*target, newtypes)?),
                        Box::new(lower_expression(*right, newtypes)?),
                    ));
                }
            }
            Ok(T::Replace(
                Box::new(lower_expression(*target, newtypes)?),
                Box::new(lower_expression(*value, newtypes)?),
            ))
        }
        Ty::Print(arguments) => Ok(T::Print(
            arguments
                .into_iter()
                .map(|argument| lower_expression(argument, newtypes))
                .collect::<Result<_, _>>()?,
        )),
    }
}

fn lower_statement(
    statement: ty_expression::Statement,
    newtypes: &HashMap<String, ty_types::Type>,
) -> Result<t_expression::Statement, String> {
    use t_expression::Statement as T;
    use ty_expression::Statement as Ty;

    match statement {
        Ty::Expression(expression) => Ok(T::Expression(lower_expression(expression, newtypes)?)),
        Ty::Let {
            name,
            binding,
            declared_type,
            value,
            line: _,
        } => Ok(T::Let {
            name,
            binding,
            declared_type: declared_type.map(lower_type).transpose()?,
            value: lower_expression(value, newtypes)?,
        }),
        Ty::Assign(target, value) => Ok(T::Assign(
            lower_expression(target, newtypes)?,
            lower_expression(value, newtypes)?,
        )),
        Ty::Return(expression) => Ok(T::Return(
            expression
                .map(|expression| lower_expression(expression, newtypes))
                .transpose()?,
        )),
        Ty::Label {
            name,
            parameters,
            initial_arguments,
        } => Ok(T::Label {
            name,
            parameters: parameters
                .into_iter()
                .map(lower_parameter)
                .collect::<Result<_, _>>()?,
            initial_arguments: initial_arguments
                .into_iter()
                .map(|argument| lower_expression(argument, newtypes))
                .collect::<Result<_, _>>()?,
        }),
        Ty::Jump { label, arguments } => Ok(T::Jump {
            label,
            arguments: arguments
                .into_iter()
                .map(|argument| lower_expression(argument, newtypes))
                .collect::<Result<_, _>>()?,
        }),
        Ty::While { .. } => unreachable!("while loops are expanded in lower_block"),
        Ty::Defer(inner) => Ok(T::Defer(Box::new(lower_statement(*inner, newtypes)?))),
    }
}
