use crate::{
    core::{
        actually_used_field::ActuallyUsedField,
        bounds::Bounds,
        compile::Compiler,
        compile_passes::{circuit_builder::CircuitBuilder, Gate},
        expressions::{
            bit_expr::BitExpr,
            conversion_expr::ConversionExpr,
            curve_expr::CurveExpr,
            domain::DomainElement,
            expr::Expr,
            field_expr::FieldExpr,
            other_expr::OtherExpr,
        },
        instruction::{ArcisInput, ArcisMetadata},
        ir::IntermediateRepresentation,
        mxe_input::ArxInput,
        profile_circuit::profile_circuit,
    },
    profile_summary::ProfileSummary,
    AsyncMPCCircuit,
};
use core_utils::circuit::{
    AlgebraicType,
    Batched,
    BitPlaintext,
    BitShareBinaryOp,
    BitShareUnaryOp,
    FieldPlaintextBinaryOp,
    FieldPlaintextUnaryOp,
    FieldShareBinaryOp,
    FieldShareUnaryOp,
    FieldType,
    Input,
    Plaintext,
    PointPlaintext,
    PointPlaintextBinaryOp,
    PointPlaintextUnaryOp,
    PointShareBinaryOp,
    PointShareUnaryOp,
    ShareOrPlaintext,
};
use primitives::algebra::BoxedUint;

type Label = core_utils::circuit::GateIndex;

fn constant_gate<F: ActuallyUsedField>(circuit: &mut CircuitBuilder, c: F) -> Gate {
    if c.is_ge_zero() {
        F::field_to_constant_gate(c)
    } else {
        let e_c = circuit.add_gate(F::field_to_constant_gate(-c));
        Gate::FieldPlaintextUnaryOp {
            x: e_c,
            op: FieldPlaintextUnaryOp::Neg,
            field_type: F::field_type(),
        }
    }
}

fn bool_gate(b: bool) -> Gate {
    Gate::Input {
        input_type: Input::Bit(BitPlaintext::Fixed(b.into())),
    }
}

fn has_negatives<F: ActuallyUsedField>(ir: &IntermediateRepresentation, e: usize) -> bool {
    F::bounds_to_field_bounds(ir.get_bounds()[e]).has_negatives()
}

fn field_expr_to_gate<F: ActuallyUsedField>(
    res: &mut CircuitBuilder,
    expr: &FieldExpr<F, usize>,
    is_plaintext: &[bool],
    old_id_to_new_id: &[Label],
    ir: &IntermediateRepresentation,
    already_opened: &mut bool,
    input_order: &mut Vec<ArcisInput>,
) -> Gate {
    let field_type = F::field_type();
    let algebraic_type = F::algebraic_type();

    match expr {
        FieldExpr::Add(e1, e2) => match (is_plaintext[*e1], is_plaintext[*e2]) {
            (true, true) => Gate::FieldPlaintextBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                op: FieldPlaintextBinaryOp::Add,
                field_type,
            },
            (true, false) => Gate::FieldShareBinaryOp {
                x: old_id_to_new_id[*e2],
                y: old_id_to_new_id[*e1],
                y_form: ShareOrPlaintext::Plaintext,
                op: FieldShareBinaryOp::Add,
                field_type,
            },
            (false, true) => Gate::FieldShareBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                y_form: ShareOrPlaintext::Plaintext,
                op: FieldShareBinaryOp::Add,
                field_type,
            },
            (false, false) => Gate::FieldShareBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                y_form: ShareOrPlaintext::Share,
                op: FieldShareBinaryOp::Add,
                field_type,
            },
        },
        FieldExpr::Mul(e1, e2) => match (is_plaintext[*e1], is_plaintext[*e2]) {
            (true, true) => Gate::FieldPlaintextBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                op: FieldPlaintextBinaryOp::Mul,
                field_type,
            },
            (true, false) => Gate::FieldShareBinaryOp {
                x: old_id_to_new_id[*e2],
                y: old_id_to_new_id[*e1],
                y_form: ShareOrPlaintext::Plaintext,
                op: FieldShareBinaryOp::Mul,
                field_type,
            },
            (false, true) => Gate::FieldShareBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                y_form: ShareOrPlaintext::Plaintext,
                op: FieldShareBinaryOp::Mul,
                field_type,
            },
            (false, false) => Gate::FieldShareBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                y_form: ShareOrPlaintext::Share,
                op: FieldShareBinaryOp::Mul,
                field_type,
            },
        },
        FieldExpr::Val(c) => constant_gate(res, *c),
        FieldExpr::Equal(e1, e2) => {
            if is_plaintext[*e1] && is_plaintext[*e2] {
                Gate::FieldPlaintextBinaryOp {
                    x: old_id_to_new_id[*e1],
                    y: old_id_to_new_id[*e2],
                    op: FieldPlaintextBinaryOp::Eq,
                    field_type,
                }
            } else {
                assert_eq!(
                    ir.get_exprs()[*e2].clone(),
                    F::field_expr_to_expr(FieldExpr::Val(0u64.into()))
                );
                *already_opened = true;
                Gate::FieldShareUnaryOp {
                    x: old_id_to_new_id[*e1],
                    op: FieldShareUnaryOp::IsZero,
                    field_type,
                }
            }
        }
        FieldExpr::Neg(e) => match is_plaintext[*e] {
            true => Gate::FieldPlaintextUnaryOp {
                x: old_id_to_new_id[*e],
                op: FieldPlaintextUnaryOp::Neg,
                field_type,
            },
            false => Gate::FieldShareUnaryOp {
                x: old_id_to_new_id[*e],
                op: FieldShareUnaryOp::Neg,
                field_type,
            },
        },
        FieldExpr::FieldInverse(e) => match is_plaintext[*e] {
            true => Gate::FieldPlaintextUnaryOp {
                x: old_id_to_new_id[*e],
                op: FieldPlaintextUnaryOp::MulInverse,
                field_type,
            },
            false => Gate::FieldShareUnaryOp {
                x: old_id_to_new_id[*e],
                op: FieldShareUnaryOp::MulInverse,
                field_type,
            },
        },
        FieldExpr::Div(e1, e2) if is_plaintext[*e1] && is_plaintext[*e2] => {
            Gate::FieldPlaintextBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                op: FieldPlaintextBinaryOp::EuclDiv,
                field_type,
            }
        }
        FieldExpr::Rem(e1, e2) if is_plaintext[*e1] && is_plaintext[*e2] => {
            Gate::FieldPlaintextBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                op: FieldPlaintextBinaryOp::Mod,
                field_type,
            }
        }
        FieldExpr::Ge(e1, e2, signed) if is_plaintext[*e1] && is_plaintext[*e2] => {
            if *signed && (has_negatives::<F>(ir, *e1) || has_negatives::<F>(ir, *e2)) {
                let offset_gate = constant_gate(res, -F::TWO_INV);
                let offset_label = res.add_gate(offset_gate);
                let x = res.add_gate(Gate::FieldPlaintextBinaryOp {
                    x: old_id_to_new_id[*e1],
                    y: offset_label,
                    op: FieldPlaintextBinaryOp::Add,
                    field_type,
                });
                let y = res.add_gate(Gate::FieldPlaintextBinaryOp {
                    x: old_id_to_new_id[*e2],
                    y: offset_label,
                    op: FieldPlaintextBinaryOp::Add,
                    field_type,
                });
                Gate::FieldPlaintextBinaryOp {
                    x,
                    y,
                    op: FieldPlaintextBinaryOp::Ge,
                    field_type,
                }
            } else {
                Gate::FieldPlaintextBinaryOp {
                    x: old_id_to_new_id[*e1],
                    y: old_id_to_new_id[*e2],
                    op: FieldPlaintextBinaryOp::Ge,
                    field_type,
                }
            }
        }
        FieldExpr::Gt(e1, e2, signed) if is_plaintext[*e1] && is_plaintext[*e2] => {
            if *signed && (has_negatives::<F>(ir, *e1) || has_negatives::<F>(ir, *e2)) {
                let offset_gate = constant_gate(res, -F::TWO_INV);
                let offset_label = res.add_gate(offset_gate);
                let x = res.add_gate(Gate::FieldPlaintextBinaryOp {
                    x: old_id_to_new_id[*e1],
                    y: offset_label,
                    op: FieldPlaintextBinaryOp::Add,
                    field_type,
                });
                let y = res.add_gate(Gate::FieldPlaintextBinaryOp {
                    x: old_id_to_new_id[*e2],
                    y: offset_label,
                    op: FieldPlaintextBinaryOp::Add,
                    field_type,
                });
                Gate::FieldPlaintextBinaryOp {
                    x,
                    y,
                    op: FieldPlaintextBinaryOp::Gt,
                    field_type,
                }
            } else {
                Gate::FieldPlaintextBinaryOp {
                    x: old_id_to_new_id[*e1],
                    y: old_id_to_new_id[*e2],
                    op: FieldPlaintextBinaryOp::Gt,
                    field_type,
                }
            }
        }
        FieldExpr::RandomVal(_) => Gate::Input {
            input_type: Input::RandomShare {
                algebraic_type,
                batched: Batched::No,
            },
        },
        FieldExpr::Sqrt(e) if is_plaintext[*e] => Gate::FieldPlaintextUnaryOp {
            x: old_id_to_new_id[*e],
            op: FieldPlaintextUnaryOp::Sqrt,
            field_type,
        },
        FieldExpr::Pow(v, e, _) if is_plaintext[*v] => {
            let mut exp_u64 = [0u64; 4];
            for (i, chunk) in <[u8; 32]>::from(e.clone()).chunks_exact(8).enumerate() {
                exp_u64[i] = u64::from_le_bytes(chunk.try_into().unwrap());
            }
            Gate::FieldPlaintextUnaryOp {
                x: old_id_to_new_id[*v],
                op: FieldPlaintextUnaryOp::Pow {
                    exp: BoxedUint::from(exp_u64.to_vec()),
                },
                field_type,
            }
        }
        FieldExpr::Input(input_id, i) => {
            input_order.push(ArcisInput::InputId(*input_id));
            Gate::Input {
                input_type: F::input(i.kind),
            }
        }
        FieldExpr::KeyRecoveryComputeErrors(d_minus_one, syndromes, i)
            if is_plaintext[*d_minus_one] && syndromes.iter().all(|s| is_plaintext[*s]) =>
        {
            let batch_gate = Gate::CollectToBatch {
                wires: syndromes
                    .iter()
                    .map(|id| old_id_to_new_id[*id])
                    .collect::<Vec<Label>>(),
                x_type: AlgebraicType::BaseField,
                x_form: ShareOrPlaintext::Plaintext,
            };
            let batched_label = res.add_gate(batch_gate);
            let gate = Gate::KeyRecoveryPlaintextComputeErrors {
                d_minus_one: old_id_to_new_id[*d_minus_one],
                syndromes: batched_label,
            };
            let label = res.add_gate(gate);
            Gate::BatchGetIndex {
                x: label,
                x_type: AlgebraicType::BaseField,
                x_form: ShareOrPlaintext::Plaintext,
                index: *i,
            }
        }
        _ => {
            panic!("Expr {:?} should not be there anymore", expr);
        }
    }
}

fn conversion_expr_to_gate<F: ActuallyUsedField>(
    res: &mut CircuitBuilder,
    expr: &ConversionExpr<F, usize>,
    old_id_to_new_id: &[Label],
) -> Gate {
    let field_type = F::field_type();

    match expr {
        ConversionExpr::BitNumToBit(e, idx, signed) => {
            let arith_bit = res.add_gate(Gate::FieldPlaintextUnaryOp {
                x: old_id_to_new_id[*e],
                op: FieldPlaintextUnaryOp::BitExtract {
                    little_endian_bit_idx: *idx as u16,
                    signed: *signed,
                },
                field_type,
            });
            Gate::FieldPlaintextToBit {
                x: arith_bit,
                field_type,
            }
        }
        ConversionExpr::EdaBit(_, 1, _) => Gate::DaBit {
            field_type,
            batched: Batched::No,
        },
        ConversionExpr::ScalarFromEdaBit(e) => Gate::GetDaBitFieldShare {
            x: old_id_to_new_id[*e],
            field_type,
        },
        ConversionExpr::BitFromEdaBit(e, 0) => Gate::GetDaBitSharedBit {
            x: old_id_to_new_id[*e],
            field_type,
        },
        ConversionExpr::ScalarFromPlaintextBit(e) => Gate::BitPlaintextToField {
            x: old_id_to_new_id[*e],
            field_type,
        },
        _ => {
            panic!("{:?} should have been deleted already.", expr);
        }
    }
}

fn bit_expr_to_gate(
    res: &mut CircuitBuilder,
    expr: &BitExpr<usize>,
    is_plaintext: &[bool],
    old_id_to_new_id: &[Label],
    true_gate: Label,
    input_order: &mut Vec<ArcisInput>,
) -> Gate {
    match expr {
        BitExpr::And(e1, e2) => match (is_plaintext[*e1], is_plaintext[*e2]) {
            (false, false) => Gate::BitShareBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                y_form: ShareOrPlaintext::Share,
                op: BitShareBinaryOp::And,
            },
            (true, false) => Gate::BitShareBinaryOp {
                x: old_id_to_new_id[*e2],
                y: old_id_to_new_id[*e1],
                y_form: ShareOrPlaintext::Plaintext,
                op: BitShareBinaryOp::And,
            },
            (false, true) => Gate::BitShareBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                y_form: ShareOrPlaintext::Plaintext,
                op: BitShareBinaryOp::And,
            },
            (true, true) => Gate::BitPlaintextBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                op: FieldPlaintextBinaryOp::Mul,
            },
        },
        BitExpr::Not(e) => {
            if is_plaintext[*e] {
                Gate::BitPlaintextBinaryOp {
                    x: old_id_to_new_id[*e],
                    y: true_gate,
                    op: FieldPlaintextBinaryOp::Xor,
                }
            } else {
                Gate::BitShareUnaryOp {
                    x: old_id_to_new_id[*e],
                    op: BitShareUnaryOp::Not,
                }
            }
        }
        BitExpr::Xor(e1, e2) => match (is_plaintext[*e1], is_plaintext[*e2]) {
            (false, false) => Gate::BitShareBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                y_form: ShareOrPlaintext::Share,
                op: BitShareBinaryOp::Xor,
            },
            (true, false) => Gate::BitShareBinaryOp {
                x: old_id_to_new_id[*e2],
                y: old_id_to_new_id[*e1],
                y_form: ShareOrPlaintext::Plaintext,
                op: BitShareBinaryOp::Xor,
            },
            (false, true) => Gate::BitShareBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                y_form: ShareOrPlaintext::Plaintext,
                op: BitShareBinaryOp::Xor,
            },
            (true, true) => Gate::BitPlaintextBinaryOp {
                x: old_id_to_new_id[*e1],
                y: old_id_to_new_id[*e2],
                op: FieldPlaintextBinaryOp::Xor,
            },
        },
        BitExpr::Val(b) => bool_gate(*b),
        BitExpr::Random(_) => Gate::Input {
            input_type: Input::RandomShare {
                algebraic_type: AlgebraicType::Bit,
                batched: Batched::No,
            },
        },
        BitExpr::KeccakF1600(bits, i) if bits.iter().all(|b| is_plaintext[*b]) => {
            let gate = Gate::PlaintextKeccakF1600 {
                wires: bits
                    .iter()
                    .map(|id| old_id_to_new_id[*id])
                    .collect::<Vec<Label>>(),
            };
            let label = res.add_gate(gate);
            Gate::BatchGetIndex {
                x: label,
                x_type: AlgebraicType::Bit,
                x_form: ShareOrPlaintext::Plaintext,
                index: *i,
            }
        }
        BitExpr::Input(input_id, i) => {
            input_order.push(ArcisInput::InputId(*input_id));
            Gate::Input {
                input_type: i.kind.to_input(AlgebraicType::Bit),
            }
        }
        _ => {
            panic!("{:?} should have been deleted already.", expr);
        }
    }
}

fn curve_expr_to_gate(
    expr: &CurveExpr<usize>,
    is_plaintext: &[bool],
    old_id_to_new_id: &[Label],
    input_order: &mut Vec<ArcisInput>,
) -> Gate {
    match expr {
        CurveExpr::Input(input_id, i) => {
            input_order.push(ArcisInput::InputId(*input_id));
            Gate::Input {
                input_type: i.to_async_mpc_input(),
            }
        }
        CurveExpr::Add(e1, e2) => {
            if is_plaintext[*e1] && is_plaintext[*e2] {
                Gate::PointPlaintextBinaryOp {
                    p: old_id_to_new_id[*e1],
                    y: old_id_to_new_id[*e2],
                    op: PointPlaintextBinaryOp::Add,
                }
            } else {
                Gate::PointShareBinaryOp {
                    p: old_id_to_new_id[*e1],
                    y: old_id_to_new_id[*e2],
                    p_form: if is_plaintext[*e1] {
                        ShareOrPlaintext::Plaintext
                    } else {
                        ShareOrPlaintext::Share
                    },
                    y_form: if is_plaintext[*e2] {
                        ShareOrPlaintext::Plaintext
                    } else {
                        ShareOrPlaintext::Share
                    },
                    op: PointShareBinaryOp::Add,
                }
            }
        }
        CurveExpr::Neg(e) => {
            if is_plaintext[*e] {
                Gate::PointPlaintextUnaryOp {
                    p: old_id_to_new_id[*e],
                    op: PointPlaintextUnaryOp::Neg,
                }
            } else {
                Gate::PointShareUnaryOp {
                    p: old_id_to_new_id[*e],
                    op: PointShareUnaryOp::Neg,
                }
            }
        }
        CurveExpr::Mul(e1, e2) => {
            if is_plaintext[*e1] && is_plaintext[*e2] {
                // e1 is the id of the scalar, e2 is the id of the point
                Gate::PointPlaintextBinaryOp {
                    p: old_id_to_new_id[*e2],
                    y: old_id_to_new_id[*e1],
                    op: PointPlaintextBinaryOp::ScalarMul,
                }
            } else {
                // e1 is the id of the scalar, e2 is the id of the point
                Gate::PointShareBinaryOp {
                    p: old_id_to_new_id[*e2],
                    y: old_id_to_new_id[*e1],
                    p_form: if is_plaintext[*e2] {
                        ShareOrPlaintext::Plaintext
                    } else {
                        ShareOrPlaintext::Share
                    },
                    y_form: if is_plaintext[*e1] {
                        ShareOrPlaintext::Plaintext
                    } else {
                        ShareOrPlaintext::Share
                    },
                    op: PointShareBinaryOp::ScalarMul,
                }
            }
        }
        CurveExpr::Val(p) => Gate::Input {
            input_type: Input::Point(PointPlaintext::Fixed(*p)),
        },
        _ => {
            panic!("{:?} should have been deleted already.", expr);
        }
    }
}

#[allow(non_snake_case)]
fn other_expr_to_gate(
    res: &mut CircuitBuilder,
    expr: &OtherExpr<usize>,
    is_plaintext: &[bool],
    old_id_to_new_id: &[Label],
    current_id: usize,
    input_order: &mut Vec<ArcisInput>,
) -> Gate {
    match expr {
        OtherExpr::PlaintextCurveToExtendedEdwards(c, i) if is_plaintext[*c] => {
            let gate = Gate::PlaintextPointToExtendedEdwards {
                point: old_id_to_new_id[*c],
            };
            let label = res.add_gate(gate);
            Gate::BatchGetIndex {
                x: label,
                x_type: AlgebraicType::BaseField,
                x_form: ShareOrPlaintext::Plaintext,
                index: *i,
            }
        }
        OtherExpr::CurveFromPlaintextExtendedEdwards(X, Y, Z, T)
            if is_plaintext[*X] && is_plaintext[*Y] && is_plaintext[*Z] && is_plaintext[*T] =>
        {
            Gate::PointFromPlaintextExtendedEdwards {
                wires: vec![
                    old_id_to_new_id[*X],
                    old_id_to_new_id[*Y],
                    old_id_to_new_id[*Z],
                    old_id_to_new_id[*T],
                ],
            }
        }
        OtherExpr::CompressPlaintextPoint(c, i) if is_plaintext[*c] => {
            let gate = Gate::CompressPlaintextPoint {
                point: old_id_to_new_id[*c],
            };
            let label = res.add_gate(gate);
            Gate::BatchGetIndex {
                x: label,
                x_type: AlgebraicType::Bit,
                x_form: ShareOrPlaintext::Plaintext,
                index: *i,
            }
        }
        OtherExpr::MxeKey(k) => {
            input_order.push(ArcisInput::Mxe(*k));
            let input_type = if k.is_plaintext() {
                match k.bounds() {
                    Bounds::Bit(_) => Input::Bit(Plaintext::Input(1)),
                    Bounds::Scalar(_) => Input::Scalar(Plaintext::Input(1)),
                    Bounds::Base(_) => Input::BaseField(Plaintext::Input(1)),
                    Bounds::Curve(_) => Input::Point(Plaintext::Input(1)),
                }
            } else {
                if is_plaintext[current_id] {
                    panic!("revealing input {k:?}")
                }
                let algebraic_type = match k.bounds() {
                    Bounds::Bit(_) => AlgebraicType::Bit,
                    Bounds::Scalar(_) => AlgebraicType::ScalarField,
                    Bounds::Base(_) => AlgebraicType::BaseField,
                    Bounds::Curve(_) => AlgebraicType::Point,
                };
                Input::Share {
                    algebraic_type,
                    batched: Batched::No,
                }
            };
            Gate::Input { input_type }
        }
        _ => {
            panic!("{:?} should have been deleted already.", expr);
        }
    }
}

impl Compiler {
    pub fn ir_to_async_mpc_circuit(
        ir: &IntermediateRepresentation,
    ) -> (AsyncMPCCircuit, ArcisMetadata, Vec<ProfileSummary>) {
        let (is_plaintext, do_open) = {
            let mut is_plaintext = ir.get_is_plaintext().to_vec();
            let mut do_open = vec![false; is_plaintext.len()];
            for (i, expr) in ir.get_exprs().iter().enumerate() {
                let is_naturally_plaintext = expr.clone().apply(|j| is_plaintext[j]).is_plaintext();
                is_plaintext[i] = is_plaintext[i] || is_naturally_plaintext;
                if is_plaintext[i] && !is_naturally_plaintext {
                    do_open[i] = true;
                }
            }

            (is_plaintext, do_open)
        };

        let mut circuit_builder = CircuitBuilder::default();
        let mut old_id_to_new_id: Vec<Label> = vec![0; ir.get_exprs().len()];

        let mut input_order: Vec<ArcisInput> = Vec::new();

        let true_gate = circuit_builder.add_gate(bool_gate(true));
        for (i, expr) in ir.get_exprs().iter().enumerate() {
            let mut already_opened = false;
            let gate = match expr {
                Expr::Scalar(expr) => field_expr_to_gate(
                    &mut circuit_builder,
                    expr,
                    &is_plaintext,
                    &old_id_to_new_id,
                    ir,
                    &mut already_opened,
                    &mut input_order,
                ),
                Expr::Bit(expr) => bit_expr_to_gate(
                    &mut circuit_builder,
                    expr,
                    &is_plaintext,
                    &old_id_to_new_id,
                    true_gate,
                    &mut input_order,
                ),
                Expr::ScalarConversion(expr) => {
                    conversion_expr_to_gate(&mut circuit_builder, expr, &old_id_to_new_id)
                }
                Expr::Base(expr) => field_expr_to_gate(
                    &mut circuit_builder,
                    expr,
                    &is_plaintext,
                    &old_id_to_new_id,
                    ir,
                    &mut already_opened,
                    &mut input_order,
                ),
                Expr::BaseConversion(expr) => {
                    conversion_expr_to_gate(&mut circuit_builder, expr, &old_id_to_new_id)
                }
                Expr::Curve(expr) => {
                    curve_expr_to_gate(expr, &is_plaintext, &old_id_to_new_id, &mut input_order)
                }
                Expr::Other(expr) => other_expr_to_gate(
                    &mut circuit_builder,
                    expr,
                    &is_plaintext,
                    &old_id_to_new_id,
                    i,
                    &mut input_order,
                ),
            };

            let label = circuit_builder.add_gate(gate);
            let label = if do_open[i] && !already_opened {
                let x = label;
                let gate = match expr.result_domain() {
                    DomainElement::Bit(_) => Gate::BitShareUnaryOp {
                        x,
                        op: BitShareUnaryOp::Open,
                    },
                    DomainElement::Scalar(_) => Gate::FieldShareUnaryOp {
                        x,
                        op: FieldShareUnaryOp::Open,
                        field_type: FieldType::ScalarField,
                    },
                    DomainElement::Base(_) => Gate::FieldShareUnaryOp {
                        x,
                        op: FieldShareUnaryOp::Open,
                        field_type: FieldType::BaseField,
                    },
                    DomainElement::Curve(_) => Gate::PointShareUnaryOp {
                        p: x,
                        op: PointShareUnaryOp::Open,
                    },
                };
                circuit_builder.add_gate(gate)
            } else {
                label
            };
            old_id_to_new_id[i] = label;
        }
        let outputs = ir
            .get_outputs()
            .iter()
            .map(|&idx| old_id_to_new_id[idx])
            .collect();
        let res = circuit_builder.into_circuit(outputs);
        let mut tracking = ir.get_tracking().clone();
        tracking.combine(
            old_id_to_new_id
                .into_iter()
                .map(|label| label as usize)
                .collect(),
            res.ops_count() as usize,
        );
        let profile = profile_circuit(&res, tracking.get_sizes());
        let metadata = ArcisMetadata { input_order };
        (res, metadata, profile)
    }
}
