use std::{collections::HashMap, ops::Deref};

use ecow::{EcoString, eco_format};
use num_bigint::BigInt;
use vec1::Vec1;

use crate::{
    cranelift::mir::{ast::*, visit::Visit},
    exhaustiveness, parse, type_,
};

pub mod ast;
pub mod visit;

struct FlattenBlockPass;

impl<'mir> Visit<'mir> for FlattenBlockPass {
    fn visit_expression(&mut self, expression: &'mir mut Expression) {
        visit::visit_expression(self, expression);

        if let Expression::Block(inner) = expression
            && inner.len() == 1
        {
            *expression = inner.pop().expect("block should've been poppable");
        }
    }

    fn visit_expression_block(&mut self, expressions: &'mir mut Vec<Expression>) {
        visit::visit_expression_block(self, expressions);

        *expressions = std::mem::take(expressions)
            .into_iter()
            .flat_map(|expr| match expr {
                Expression::Block(inner) => inner,
                _ => vec![expr],
            })
            .collect();
    }
}

struct RemoveUnusedPass;

impl<'mir> Visit<'mir> for RemoveUnusedPass {
    fn visit_expression_block(&mut self, expressions: &'mir mut Vec<Expression>) {
        visit::visit_expression_block(self, expressions);

        let last_index = expressions.len() - 1;

        *expressions = std::mem::take(expressions)
            .into_iter()
            .enumerate()
            .filter_map(|(index, expr)| match expr {
                Expression::Var { .. }
                | Expression::FunctionRef { .. }
                | Expression::Int { .. }
                | Expression::Float { .. }
                | Expression::Bool { .. }
                | Expression::String { .. }
                    if index != last_index =>
                {
                    None
                }

                _ => Some(expr),
            })
            .collect();
    }
}

#[derive(Default)]
struct RemoveUselessSetPass {
    replacements: HashMap<EcoString, EcoString>,
}

impl<'mir> Visit<'mir> for RemoveUselessSetPass {
    fn visit_expression(&mut self, expression: &'mir mut Expression) {
        visit::visit_expression(self, expression);

        if let Expression::Set { name, value } = expression {
            if let Expression::Var(var) = value.as_ref() {
                _ = self
                    .replacements
                    .insert(name.name.clone(), var.name.clone());

                _ = var;
                _ = name;
            }
        }
    }

    fn visit_expression_block(&mut self, expressions: &'mir mut Vec<Expression>) {
        visit::visit_expression_block(self, expressions);

        *expressions = std::mem::take(expressions)
            .into_iter()
            .filter_map(|expr| match expr {
                Expression::Set { name, value: _ }
                    if self.replacements.contains_key(&name.name) =>
                {
                    None
                }
                _ => Some(expr),
            })
            .collect();
    }

    fn visit_expression_var(&mut self, var: &'mir mut Var) {
        if let Some(replacement) = self.replacements.get(&var.name) {
            var.name = replacement.clone();
        }
    }
}

#[derive(Debug, Default, PartialEq, Eq)]
pub struct Translator {
    module: Module,
}

impl Translator {
    pub fn translate(mut self, module: &crate::ast::TypedModule) -> Module {
        for definition in &module.definitions {
            match definition {
                crate::ast::Definition::Function(function) => {
                    self.module
                        .functions
                        .push(Self::translate_function(function));
                }
                crate::ast::Definition::TypeAlias(_type_alias) => todo!(),
                crate::ast::Definition::CustomType(_custom_type) => {}
                crate::ast::Definition::Import(_import) => todo!(),
                crate::ast::Definition::ModuleConstant(_module_constant) => todo!(),
            }
        }

        FlattenBlockPass.visit_module(&mut self.module);
        RemoveUnusedPass.visit_module(&mut self.module);
        RemoveUselessSetPass::default().visit_module(&mut self.module);

        self.module
    }

    fn translate_function(function: &crate::ast::TypedFunction) -> Function {
        let (_, name) = function.name.clone().expect("function should have a name");

        let return_type = Self::translate_type(&function.return_type);

        let mut translator = BodyTranslator::default();
        let arguments: Vec<_> = Iterator::collect(function.arguments.iter().map(|argument| {
            argument
                .get_variable_name()
                .map(|name| translator.make_var(name.clone()))
        }));

        let parameters = Iterator::collect(function.arguments.iter().zip(arguments.iter()).map(
            |(argument, name)| {
                let type_ = Self::translate_type(&argument.type_);

                FunctionParameter {
                    name: name.clone().map(|name| name.name),
                    type_,
                }
            },
        ));

        let body = translator.translate(&function.body);

        Function {
            name,
            body,
            return_type,
            parameters,
        }
    }

    fn translate_type(type_: &type_::Type) -> Type {
        match type_ {
            type_::Type::Named {
                publicity: _,
                package: _,
                module,
                name,
                arguments,
                inferred_variant: _,
            } => match (module.as_str(), name.as_str()) {
                ("gleam", "Int") => Type::Int,
                ("gleam", "Float") => Type::Float,
                ("gleam", "Bool") => Type::Bool,
                ("gleam", "String") => Type::String,
                ("gleam", "List") => {
                    let list_type = Self::translate_type(&arguments[0]);

                    Type::List(Box::new(list_type))
                }
                (_, _) => Type::Struct {
                    elements: arguments
                        .iter()
                        .map(|type_| Self::translate_type(type_))
                        .collect(),
                },
            },
            type_::Type::Fn {
                arguments: _,
                return_: _,
            } => todo!(),
            type_::Type::Var { type_ } => match type_.borrow().deref() {
                type_::TypeVar::Link { type_ } => Self::translate_type(type_),
                type_::TypeVar::Unbound { id: _ } | type_::TypeVar::Generic { id: _ } => {
                    Type::Generic
                }
            },
            type_::Type::Tuple { elements: _ } => todo!(),
        }
    }
}

#[derive(Debug, Default)]
pub struct BodyTranslator {
    variables: HashMap<EcoString, EcoString>,
    variable_count: usize,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DecisionKind {
    Case,
    Let,
}

impl BodyTranslator {
    fn make_tmp_var(&mut self) -> Var {
        self.make_var("_tmp".into())
    }

    fn make_var(&mut self, name: EcoString) -> Var {
        self.variable_count += 1;

        let entry = eco_format!("{}${}", name.clone(), self.variable_count);
        _ = self.variables.insert(name.clone(), entry.clone());

        Var { name: entry }
    }

    fn get_var(&mut self, name: &EcoString) -> Var {
        let variable = self
            .variables
            .get(name)
            .cloned()
            .unwrap_or_else(|| panic!("variable '{name}' not found"));

        Var { name: variable }
    }

    fn in_var_scope<T>(&mut self, func: impl Fn(&mut Self) -> T) -> T {
        let snapshot = self.variables.clone();
        let result = func(self);
        self.variables = snapshot;
        result
    }

    fn translate(mut self, body: &Vec1<crate::ast::TypedStatement>) -> Expression {
        self.translate_statements(body)
    }

    fn translate_statements(&mut self, statements: &[crate::ast::TypedStatement]) -> Expression {
        Expression::Block(Iterator::collect(
            statements.iter().map(|stmt| self.translate_statement(stmt)),
        ))
    }

    fn translate_statement(&mut self, statement: &crate::ast::TypedStatement) -> Expression {
        match statement {
            crate::ast::Statement::Expression(expression) => self.translate_expression(expression),
            crate::ast::Statement::Assignment(assignment) => self.translate_assignment(assignment),
            crate::ast::Statement::Use(_) => todo!(),
            crate::ast::Statement::Assert(_) => todo!(),
        }
    }

    fn translate_expression(&mut self, expression: &crate::ast::TypedExpr) -> Expression {
        match expression {
            crate::ast::TypedExpr::Int {
                location: _,
                type_: _,
                value: _,
                int_value,
            } => Expression::Int {
                value: int_value.clone(),
            },
            crate::ast::TypedExpr::Float {
                location: _,
                type_: _,
                value,
            } => Expression::Float {
                value: value.clone(),
            },
            crate::ast::TypedExpr::String {
                location: _,
                type_: _,
                value,
            } => Expression::String {
                value: value.clone(),
            },
            crate::ast::TypedExpr::Block {
                location: _,
                statements,
            } => self.in_var_scope(|this| this.translate_statements(statements)),
            crate::ast::TypedExpr::Pipeline {
                location: _,
                first_value,
                assignments,
                finally,
                finally_kind: _,
            } => {
                let mut block = vec![];

                let value = self.translate_expression(&first_value.value);
                block.push(Expression::Set {
                    name: self.make_var(first_value.name.clone()),
                    value: Box::new(value),
                });

                for (assignment, _) in assignments {
                    let value = self.translate_expression(&assignment.value);
                    block.push(Expression::Set {
                        name: self.make_var(assignment.name.clone()),
                        value: Box::new(value),
                    });
                }

                block.push(self.translate_expression(finally));

                Expression::Block(block)
            }
            crate::ast::TypedExpr::Var {
                location: _,
                constructor,
                name,
            } => match &constructor.variant {
                type_::ValueConstructorVariant::LocalVariable {
                    location: _,
                    origin: _,
                } => Expression::Var(self.get_var(name)),
                type_::ValueConstructorVariant::ModuleConstant {
                    documentation: _,
                    location: _,
                    module: _,
                    name: _,
                    literal: _,
                    implementations: _,
                } => todo!(),
                type_::ValueConstructorVariant::LocalConstant { literal: _ } => todo!(),
                type_::ValueConstructorVariant::ModuleFn {
                    name,
                    field_map: _,
                    module,
                    arity,
                    location: _,
                    documentation: _,
                    implementations: _,
                    external_erlang: _,
                    external_javascript: _,
                    external_cranelift: _,
                    purity: _,
                } => Expression::FunctionRef {
                    module: module.clone(),
                    name: name.clone(),
                    arity: *arity,
                },
                type_::ValueConstructorVariant::Record {
                    name,
                    arity,
                    field_map: _,
                    location: _,
                    module,
                    variants_count: _,
                    variant_index: _,
                    documentation: _,
                } => match (module.as_str(), name.as_str()) {
                    ("gleam", "True") => Expression::Bool { value: true },
                    ("gleam", "False") => Expression::Bool { value: false },
                    (_, _) => Expression::FunctionRef {
                        module: module.clone(),
                        name: name.clone(),
                        arity: usize::from(*arity),
                    },
                },
            },
            crate::ast::TypedExpr::Fn {
                location: _,
                type_: _,
                kind: _,
                arguments: _,
                body: _,
                return_annotation: _,
                purity: _,
            } => todo!(),
            crate::ast::TypedExpr::List {
                location: _,
                type_: _,
                elements,
                tail,
            } => {
                let items = elements
                    .iter()
                    .map(|element| self.translate_expression(element))
                    .collect();

                let tail = tail
                    .as_ref()
                    .map(|tail| Box::new(self.translate_expression(tail)));

                Expression::List { items, tail }
            }
            crate::ast::TypedExpr::Call {
                location: _,
                type_: _,
                fun,
                arguments,
            } => {
                let target = self.translate_expression(fun);
                let args = arguments
                    .iter()
                    .map(|arg| self.translate_expression(&arg.value))
                    .collect();

                Expression::Call {
                    target: Box::new(target),
                    args,
                }
            }
            crate::ast::TypedExpr::BinOp {
                location: _,
                type_: _,
                name,
                name_location: _,
                left,
                right,
            } => {
                let lhs = Box::new(self.translate_expression(left));
                let rhs = Box::new(self.translate_expression(right));

                match name {
                    crate::ast::BinOp::And => Expression::If {
                        cond: lhs,
                        then: rhs,
                        else_: Box::new(Expression::Bool { value: false }),
                    },
                    crate::ast::BinOp::Or => Expression::If {
                        cond: lhs,
                        then: Box::new(Expression::Bool { value: true }),
                        else_: rhs,
                    },
                    crate::ast::BinOp::Eq => Expression::Equals { lhs, rhs },
                    crate::ast::BinOp::NotEq => Expression::NotEquals { lhs, rhs },
                    crate::ast::BinOp::LtInt => Expression::IntLt { lhs, rhs },
                    crate::ast::BinOp::LtEqInt => Expression::IntLtEq { lhs, rhs },
                    crate::ast::BinOp::LtFloat => Expression::FloatLt { lhs, rhs },
                    crate::ast::BinOp::LtEqFloat => Expression::FloatLtEq { lhs, rhs },
                    crate::ast::BinOp::GtEqInt => Expression::IntGtEq { lhs, rhs },
                    crate::ast::BinOp::GtInt => Expression::IntGt { lhs, rhs },
                    crate::ast::BinOp::GtEqFloat => Expression::FloatGtEq { lhs, rhs },
                    crate::ast::BinOp::GtFloat => Expression::FloatGt { lhs, rhs },
                    crate::ast::BinOp::AddInt => Expression::IntAdd { lhs, rhs },
                    crate::ast::BinOp::AddFloat => Expression::FloatAdd { lhs, rhs },
                    crate::ast::BinOp::SubInt => Expression::IntSub { lhs, rhs },
                    crate::ast::BinOp::SubFloat => Expression::FloatSub { lhs, rhs },
                    crate::ast::BinOp::MultInt => Expression::IntMul { lhs, rhs },
                    crate::ast::BinOp::MultFloat => Expression::FloatMul { lhs, rhs },
                    crate::ast::BinOp::DivInt => Expression::IntDiv { lhs, rhs },
                    crate::ast::BinOp::DivFloat => Expression::FloatDiv { lhs, rhs },
                    crate::ast::BinOp::RemainderInt => Expression::IntRem { lhs, rhs },
                    crate::ast::BinOp::Concatenate => Expression::StringConcat { lhs, rhs },
                }
            }
            crate::ast::TypedExpr::Case {
                location: _,
                type_: _,
                subjects,
                clauses,
                compiled_case,
            } => {
                let mut block = vec![];

                let subject_vars: Vec<_> = Iterator::collect(subjects.iter().map(|subject| {
                    let name = self.make_tmp_var();
                    let value = self.translate_expression(subject);

                    block.push(Expression::Set {
                        name: name.clone(),
                        value: Box::new(value),
                    });

                    return name;
                }));

                block.push(self.translate_decision(
                    DecisionKind::Case,
                    &subject_vars,
                    clauses,
                    &compiled_case.tree,
                ));

                Expression::Block(block)
            }
            crate::ast::TypedExpr::RecordAccess {
                location: _,
                field_start: _,
                type_: _,
                label: _,
                index,
                record,
            } => Expression::StructAccess {
                value: Box::new(self.translate_expression(record)),
                index: *index,
            },
            crate::ast::TypedExpr::ModuleSelect {
                location: _,
                field_start: _,
                type_: _,
                label: _,
                module_name: _,
                module_alias: _,
                constructor: _,
            } => todo!(),
            crate::ast::TypedExpr::Tuple {
                location: _,
                type_: _,
                elements,
            } => Expression::Struct {
                tag: None,
                items: elements
                    .iter()
                    .map(|element| self.translate_expression(element))
                    .collect(),
            },
            crate::ast::TypedExpr::TupleIndex {
                location: _,
                type_: _,
                index,
                tuple,
            } => Expression::StructAccess {
                value: Box::new(self.translate_expression(tuple)),
                index: *index,
            },
            crate::ast::TypedExpr::Todo {
                location: _,
                message: _,
                kind: _,
                type_: _,
            } => todo!(),
            crate::ast::TypedExpr::Panic {
                location: _,
                message: _,
                type_: _,
            } => todo!(),
            crate::ast::TypedExpr::Echo {
                location: _,
                type_: _,
                expression: _,
                message: _,
            } => todo!(),
            crate::ast::TypedExpr::BitArray {
                location: _,
                type_: _,
                segments: _,
            } => todo!(),
            crate::ast::TypedExpr::RecordUpdate {
                location: _,
                type_: _,
                record_assignment,
                constructor,
                arguments,
            } => {
                let mut block = vec![];

                if let Some(assignment) = record_assignment {
                    block.push(self.translate_assignment(assignment));
                }

                let constructor = self.translate_expression(constructor);
                let arguments = arguments
                    .iter()
                    .map(|argument| self.translate_expression(&argument.value))
                    .collect();

                block.push(Expression::Call {
                    target: Box::new(constructor),
                    args: arguments,
                });

                Expression::Block(block)
            }
            crate::ast::TypedExpr::NegateBool { location: _, value } => Expression::Equals {
                lhs: Box::new(self.translate_expression(value)),
                rhs: Box::new(Expression::Bool { value: false }),
            },
            crate::ast::TypedExpr::NegateInt { location: _, value } => Expression::IntSub {
                lhs: Box::new(Expression::Int {
                    value: BigInt::from(0),
                }),
                rhs: Box::new(self.translate_expression(value)),
            },
            crate::ast::TypedExpr::Invalid {
                location: _,
                type_: _,
            } => todo!(),
        }
    }

    fn translate_assignment(&mut self, assignment: &crate::ast::TypedAssignment) -> Expression {
        let mut block = vec![];

        let value_var = self.make_tmp_var();
        let value = self.translate_expression(&assignment.value);

        block.push(Expression::Set {
            name: value_var.clone(),
            value: Box::new(value),
        });

        block.push(self.translate_decision(
            DecisionKind::Let,
            &[value_var.clone()],
            &[],
            &assignment.compiled_case.tree,
        ));

        block.push(Expression::Var(value_var));

        Expression::Block(block)
    }

    fn translate_decision(
        &mut self,
        kind: DecisionKind,
        subjects: &[Var],
        clauses: &[crate::ast::TypedClause],
        decision: &exhaustiveness::Decision,
    ) -> Expression {
        match decision {
            exhaustiveness::Decision::Run { body } => {
                let mut block = vec![];

                for (binding, value) in &body.bindings {
                    let binding = self.make_var(binding.clone());

                    block.push(self.translate_binding(subjects, binding.clone(), value));
                }

                if kind == DecisionKind::Case {
                    block.push(self.translate_expression(&clauses[body.clause_index].then));
                }

                Expression::Block(block)
            }
            exhaustiveness::Decision::Guard {
                guard,
                if_true,
                if_false,
            } => {
                let mut block = vec![];

                let guard = clauses[*guard]
                    .guard
                    .as_ref()
                    .expect("expected clause to have a guard");

                let referenced_in_guard = guard.referenced_variables();

                let (guard_bindings, if_true_bindings): (Vec<_>, Vec<_>) = if_true
                    .bindings
                    .iter()
                    .partition(|(binding, _)| referenced_in_guard.contains(binding));

                for (binding, value) in guard_bindings {
                    let binding = self.make_var(binding.clone());

                    block.push(self.translate_binding(subjects, binding, value));
                }

                let cond = self.translate_guard(guard);

                let if_true = {
                    let mut block = vec![];

                    for (binding, value) in if_true_bindings {
                        let binding = self.make_var(binding.clone());

                        block.push(self.translate_binding(subjects, binding.clone(), value));
                    }

                    block.push(self.translate_expression(&clauses[if_true.clause_index].then));
                    block
                };

                let if_false = self.translate_decision(kind, subjects, clauses, if_false);

                block.push(Expression::If {
                    cond: Box::new(cond),
                    then: Box::new(Expression::Block(if_true)),
                    else_: Box::new(if_false),
                });

                Expression::Block(block)
            }
            exhaustiveness::Decision::Switch {
                var,
                choices,
                fallback,
                fallback_check: _,
            } => {
                let fallback = self.translate_decision(kind, subjects, clauses, fallback);

                DoubleEndedIterator::rfold(choices.iter(), fallback, |fallback, (check, then)| {
                    Expression::If {
                        cond: Box::new(self.translate_runtime_check(
                            check,
                            Expression::Var(subjects[var.id].clone()),
                        )),
                        then: Box::new(self.translate_decision(kind, subjects, clauses, then)),
                        else_: Box::new(fallback),
                    }
                })
            }
            exhaustiveness::Decision::Fail => todo!(),
        }
    }

    fn translate_guard(&mut self, guard: &crate::ast::TypedClauseGuard) -> Expression {
        match guard {
            crate::ast::ClauseGuard::Block { location: _, value } => self.translate_guard(value),
            crate::ast::ClauseGuard::Equals {
                location: _,
                left,
                right,
            } => {
                let lhs = self.translate_guard(left);
                let rhs = self.translate_guard(right);

                Expression::Equals {
                    lhs: Box::new(lhs),
                    rhs: Box::new(rhs),
                }
            }
            crate::ast::ClauseGuard::NotEquals {
                location: _,
                left,
                right,
            } => Expression::NotEquals {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::GtInt {
                location: _,
                left,
                right,
            } => Expression::IntGt {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::GtEqInt {
                location: _,
                left,
                right,
            } => Expression::IntGtEq {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::LtInt {
                location: _,
                left,
                right,
            } => Expression::IntLt {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::LtEqInt {
                location: _,
                left,
                right,
            } => Expression::IntLtEq {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::GtFloat {
                location: _,
                left,
                right,
            } => Expression::FloatGt {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::GtEqFloat {
                location: _,
                left,
                right,
            } => Expression::FloatGtEq {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::LtFloat {
                location: _,
                left,
                right,
            } => Expression::FloatLt {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::LtEqFloat {
                location: _,
                left,
                right,
            } => Expression::FloatLtEq {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::AddInt {
                location: _,
                left,
                right,
            } => Expression::IntAdd {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::AddFloat {
                location: _,
                left,
                right,
            } => Expression::FloatAdd {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::SubInt {
                location: _,
                left,
                right,
            } => Expression::IntSub {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::SubFloat {
                location: _,
                left,
                right,
            } => Expression::FloatSub {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::MultInt {
                location: _,
                left,
                right,
            } => Expression::IntMul {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::MultFloat {
                location: _,
                left,
                right,
            } => Expression::FloatMul {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::DivInt {
                location: _,
                left,
                right,
            } => Expression::IntDiv {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::DivFloat {
                location: _,
                left,
                right,
            } => Expression::FloatDiv {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::RemainderInt {
                location: _,
                left,
                right,
            } => Expression::IntRem {
                lhs: Box::new(self.translate_guard(left)),
                rhs: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::Or {
                location: _,
                left,
                right,
            } => Expression::If {
                cond: Box::new(self.translate_guard(left)),
                then: Box::new(Expression::Bool { value: true }),
                else_: Box::new(self.translate_guard(right)),
            },
            crate::ast::ClauseGuard::And {
                location: _,
                left,
                right,
            } => Expression::If {
                cond: Box::new(self.translate_guard(left)),
                then: Box::new(self.translate_guard(right)),
                else_: Box::new(Expression::Bool { value: false }),
            },
            crate::ast::ClauseGuard::Not {
                location: _,
                expression,
            } => Expression::Equals {
                lhs: Box::new(self.translate_guard(expression)),
                rhs: Box::new(Expression::Bool { value: false }),
            },
            crate::ast::ClauseGuard::Var {
                location: _,
                type_: _,
                name,
                definition_location: _,
            } => Expression::Var(self.get_var(name)),
            crate::ast::ClauseGuard::TupleIndex {
                location: _,
                index,
                type_: _,
                tuple,
            } => Expression::StructAccess {
                value: Box::new(self.translate_guard(tuple)),
                index: *index,
            },
            crate::ast::ClauseGuard::FieldAccess {
                label_location: _,
                index,
                label: _,
                type_: _,
                container,
            } => Expression::StructAccess {
                value: Box::new(self.translate_guard(container)),
                index: index.expect("field access expected an index"),
            },
            crate::ast::ClauseGuard::ModuleSelect {
                location: _,
                type_: _,
                label: _,
                module_name: _,
                module_alias: _,
                literal: _,
            } => todo!(),
            crate::ast::ClauseGuard::Constant(constant) => self.translate_constant(constant),
        }
    }

    fn translate_constant(&mut self, constant: &crate::ast::TypedConstant) -> Expression {
        match constant {
            crate::ast::Constant::Int {
                location: _,
                value: _,
                int_value,
            } => Expression::Int {
                value: int_value.clone(),
            },
            crate::ast::Constant::Float { location: _, value } => Expression::Float {
                value: value.clone(),
            },
            crate::ast::Constant::String { location: _, value } => Expression::String {
                value: value.clone(),
            },
            crate::ast::Constant::Tuple {
                location: _,
                elements,
            } => Expression::Struct {
                tag: None,
                items: elements
                    .iter()
                    .map(|element| self.translate_constant(element))
                    .collect(),
            },
            crate::ast::Constant::List {
                location: _,
                elements,
                type_: _,
            } => Expression::List {
                items: elements
                    .iter()
                    .map(|element| self.translate_constant(element))
                    .collect(),
                tail: None,
            },
            crate::ast::Constant::Record {
                location: _,
                module: _,
                name: _,
                arguments: _,
                tag: _,
                type_: _,
                field_map: _,
                record_constructor: _,
            } => todo!(),
            crate::ast::Constant::BitArray {
                location: _,
                segments: _,
            } => todo!(),
            crate::ast::Constant::Var {
                location: _,
                module: _,
                name: _,
                constructor: _,
                type_: _,
            } => todo!(),
            crate::ast::Constant::StringConcatenation {
                location: _,
                left,
                right,
            } => Expression::StringConcat {
                lhs: Box::new(self.translate_constant(left)),
                rhs: Box::new(self.translate_constant(right)),
            },
            crate::ast::Constant::Invalid {
                location: _,
                type_: _,
            } => todo!(),
        }
    }

    fn translate_binding(
        &mut self,
        subjects: &[Var],
        binding: Var,
        value: &exhaustiveness::BoundValue,
    ) -> Expression {
        let value = match value {
            exhaustiveness::BoundValue::Variable(variable) => {
                Expression::Var(subjects[variable.id].clone())
            }
            exhaustiveness::BoundValue::LiteralString(_eco_string) => todo!(),
            exhaustiveness::BoundValue::LiteralInt(big_int) => Expression::Int {
                value: big_int.clone(),
            },
            exhaustiveness::BoundValue::LiteralFloat(_eco_string) => todo!(),
            exhaustiveness::BoundValue::BitArraySlice {
                bit_array: _,
                read_action: _,
            } => todo!(),
        };

        Expression::Set {
            name: binding,
            value: Box::new(value),
        }
    }

    fn translate_runtime_check(
        &mut self,
        check: &exhaustiveness::RuntimeCheck,
        against: Expression,
    ) -> Expression {
        match check {
            exhaustiveness::RuntimeCheck::Int { value } => {
                let value = parse::parse_int_value(&value).expect("unable to parse integer value");

                Expression::Equals {
                    lhs: Box::new(against),
                    rhs: Box::new(Expression::Int { value }),
                }
            }
            exhaustiveness::RuntimeCheck::Float { value } => Expression::Equals {
                lhs: Box::new(against),
                rhs: Box::new(Expression::Float {
                    value: value.clone(),
                }),
            },
            exhaustiveness::RuntimeCheck::String { value } => Expression::Equals {
                lhs: Box::new(against),
                rhs: Box::new(Expression::String {
                    value: value.clone(),
                }),
            },
            exhaustiveness::RuntimeCheck::StringPrefix { prefix: _, rest: _ } => todo!(),
            exhaustiveness::RuntimeCheck::Tuple {
                size: _,
                elements: _,
            } => todo!(),
            exhaustiveness::RuntimeCheck::BitArray { test: _ } => todo!(),
            exhaustiveness::RuntimeCheck::Variant {
                match_: _,
                index,
                labels: _,
                fields: _,
            } => Expression::Equals {
                lhs: Box::new(Expression::Int {
                    value: BigInt::from(*index),
                }),
                rhs: Box::new(Expression::StructTag {
                    value: Box::new(against),
                }),
            },
            exhaustiveness::RuntimeCheck::NonEmptyList { first: _, rest: _ } => todo!(),
            exhaustiveness::RuntimeCheck::EmptyList => todo!(),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::collections::{HashMap, HashSet};

    use super::*;
    use crate::analyse::TargetSupport;
    use crate::build::{Origin, Target};
    use crate::config::PackageConfig;
    use crate::line_numbers::LineNumbers;
    use crate::parse::parse_module;
    use crate::type_::PRELUDE_MODULE_NAME;
    use crate::uid::UniqueIdGenerator;
    use crate::warning::{TypeWarningEmitter, WarningEmitter};
    use camino::Utf8PathBuf;

    fn compile_module(src: &str) -> crate::ast::TypedModule {
        use crate::type_::build_prelude;
        let parsed = parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null())
            .expect("syntax error");
        let ast = parsed.module;
        let ids = UniqueIdGenerator::new();
        let mut config = PackageConfig::default();
        config.name = "thepackage".into();
        let mut modules = im::HashMap::new();
        let _ = modules.insert(PRELUDE_MODULE_NAME.into(), build_prelude(&ids));
        let line_numbers = LineNumbers::new(src);

        crate::analyse::ModuleAnalyzerConstructor::<()> {
            target: Target::Erlang,
            ids: &ids,
            origin: Origin::Src,
            importable_modules: &modules,
            warnings: &TypeWarningEmitter::null(),
            direct_dependencies: &HashMap::new(),
            dev_dependencies: &HashSet::new(),
            target_support: TargetSupport::Enforced,
            package_config: &config,
        }
        .infer_module(ast, line_numbers, "".into())
        .expect("should successfully infer")
    }

    fn translate(src: &str) -> Module {
        let typed_module = compile_module(src);

        Translator::default().translate(&typed_module)
    }

    #[test]
    fn translates_samples() {
        insta::assert_debug_snapshot!(translate(
            r#"pub fn add(lhs: Int, rhs: Int) -> Int {
              lhs + rhs
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn some_case(n: Int) -> Int {
              case n {
                0 -> 2
                1 -> 4
                _ -> 6
              }
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn some_case(n: Int) -> Int {
              case n {
                y if y == 0 -> 2
                1 -> 4
                _ -> 6
              }
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn fib(n: Int) -> Int {
              case n {
                0 -> 0
                1 -> 1
                _ -> fib(n - 1) + fib(n - 2)
              }
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn maths() {
              let _ = 1 + 0
              let _ = 2 - 1
              let _ = 3 * 2
              let _ = 4 / 3
              let _ = 5 % 4

              let _ = 1.0 +. 0.0
              let _ = 2.0 -. 1.0
              let _ = 3.0 *. 2.0
              let _ = 4.0 /. 3.0

              let _ = 1 == 2 && 2 == 4
              let _ = 1 == 2 || 2 == 4
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn booleans() {
              let _ = True
              let _ = False

              let _ = True == False
              let _ = False != True

              let _ = 1 > 2
              let _ = 1 >= 2
              let _ = 1 < 2
              let _ = 1 <= 2

              let _ = 1.0 >. 2.0
              let _ = 1.0 >=. 2.0
              let _ = 1.0 <. 2.0
              let _ = 1.0 <=. 2.0
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn guard_operations(n: Int, f: Float) -> Int {
              case n, f {
                x, y if x == 0 -> 1
                x, y if x != 1 -> 2
                x, y if x > 2 -> 3
                x, y if x >= 3 -> 4
                x, y if x < 4 -> 5
                x, y if x <= 5 -> 6
                x, y if y >. 1.0 -> 7
                x, y if y >=. 2.0 -> 8
                x, y if y <. 3.0 -> 9
                x, y if y <=. 4.0 -> 10
                x, y if x > 0 && x < 10 -> 11
                x, y if x < 0 || x > 100 -> 12
                x, y if !{ x == 0 } -> 13
                x, y if x + 1 > 5 -> 14
                x, y if x - 1 < 0 -> 15
                x, y if x * 2 == 10 -> 16
                x, y if x / 2 == 1 -> 17
                x, y if y +. 1.0 >. 5.0 -> 18
                x, y if y -. 1.0 <. 0.0 -> 19
                x, y if y *. 2.0 == 4.0 -> 20
                x, y if y /. 2.0 >=. 1.0 -> 21
                x, y if x % 2 == 0 -> 22
                _, _ -> 0
              }
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn strings() {
              let _ = "hello"
              let _ = "hello, " <> "world!"
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn tuples() {
              let a = #(1, 2, 3)
              let _ = a.1
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub type Wibble {
              Wibble(a: Int, b: Int)
            }

            pub fn types(w: Wibble) {
              let x = Wibble(a: 10, b: 20)

              w.a + x.b
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn add(lhs: Int, rhs: Int) -> Int {
              lhs + rhs
            }

            pub fn pipe() {
              10 |> add(20) |> add(30)
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub fn pipe() {
                [1, 2, 3, ..[4, 5, 6]]
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub type Wibble {
              Wibble
              Wobble
            }

            pub fn check(wibble: Wibble) {
              case wibble {
                Wibble -> 0
                Wobble -> 1
              }
            }"#
        ));

        insta::assert_debug_snapshot!(translate(
            r#"pub type Wibble {
              Wibble(x: Int, y: Int, z: Int)
            }

            pub fn record_update(wibble: Wibble) {
              Wibble(..wibble, y: 20)
            }"#
        ));
    }
}
