//! Lowering of `format_args!()`.

use base_db::FxIndexSet;
use hir_expand::name::Name;
use intern::{Symbol, sym};
use span::SyntaxContext;
use syntax::{AstPtr, AstToken as _, ast};

use crate::{
    builtin_type::BuiltinUint,
    expr_store::{HygieneId, lower::ExprCollector, path::Path},
    hir::{
        Array, BindingAnnotation, Expr, ExprId, Literal, Pat, RecordLitField, RecordSpread,
        Statement,
        format_args::{
            self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind,
            FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions,
            FormatPlaceholder, FormatSign, FormatTrait,
        },
    },
    lang_item::LangItemTarget,
    type_ref::{Mutability, Rawness},
};

impl<'db> ExprCollector<'db> {
    pub(super) fn collect_format_args(
        &mut self,
        f: ast::FormatArgsExpr,
        syntax_ptr: AstPtr<ast::Expr>,
    ) -> ExprId {
        let mut args = FormatArgumentsCollector::default();
        f.args().for_each(|arg| {
            let expr = arg.expr();
            args.add(FormatArgument {
                kind: match arg.arg_name() {
                    Some(name) => FormatArgumentKind::Named(Name::new_root(name.name().text())),
                    None => FormatArgumentKind::Normal,
                },
                syntax: expr.as_ref().map(AstPtr::new),
                expr: self.collect_expr_opt(expr),
            });
        });
        let template = f.template();
        let fmt_snippet = template.as_ref().and_then(|it| match it {
            ast::Expr::Literal(literal) => match literal.kind() {
                ast::LiteralKind::String(s) => Some(s.text().to_owned()),
                _ => None,
            },
            _ => None,
        });
        let mut mappings = vec![];
        let (fmt, hygiene) = match template.and_then(|template| {
            self.expand_macros_to_string(template.clone()).map(|it| (it, template))
        }) {
            Some(((s, is_direct_literal), template)) => {
                let call_ctx = SyntaxContext::root(self.def_map.edition());
                let hygiene = self.hygiene_id_for(s.syntax().text_range());
                let template_ptr = AstPtr::new(&template);
                let fmt = format_args::parse(
                    &s,
                    template_ptr,
                    fmt_snippet,
                    args,
                    is_direct_literal,
                    |name, range| {
                        let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name)));
                        if let Some(range) = range {
                            self.store
                                .template_map
                                .get_or_insert_with(Default::default)
                                .implicit_capture_to_source
                                .insert(expr_id, self.expander.in_file((template_ptr, range)));
                        }
                        if !hygiene.is_root() {
                            self.store.ident_hygiene.insert(expr_id.into(), hygiene);
                        }
                        expr_id
                    },
                    |name, span| {
                        if let Some(span) = span {
                            mappings.push((span, name))
                        }
                    },
                    call_ctx,
                );
                (fmt, hygiene)
            }
            None => (
                FormatArgs {
                    template: Default::default(),
                    arguments: args.finish(),
                    orphans: Default::default(),
                },
                HygieneId::ROOT,
            ),
        };

        let idx = if self.lang_items().FormatCount.is_none() {
            self.collect_format_args_after_1_93_0_impl(syntax_ptr, fmt)
        } else {
            self.collect_format_args_before_1_93_0_impl(syntax_ptr, fmt)
        };

        self.store
            .template_map
            .get_or_insert_with(Default::default)
            .format_args_to_captures
            .insert(idx, (hygiene, mappings));
        idx
    }

    fn collect_format_args_after_1_93_0_impl(
        &mut self,
        syntax_ptr: AstPtr<ast::Expr>,
        fmt: FormatArgs,
    ) -> ExprId {
        let lang_items = self.lang_items();

        // Create a list of all _unique_ (argument, format trait) combinations.
        // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]
        //
        // We use usize::MAX for arguments that don't exist, because that can never be a valid index
        // into the arguments array.
        let mut argmap = FxIndexSet::default();

        let mut incomplete_lit = String::new();

        let mut implicit_arg_index = 0;

        let mut bytecode = Vec::new();

        let template = if fmt.template.is_empty() {
            // Treat empty templates as a single literal piece (with an empty string),
            // so we produce `from_str("")` for those.
            &[FormatArgsPiece::Literal(sym::__empty)][..]
        } else {
            &fmt.template[..]
        };

        // See library/core/src/fmt/mod.rs for the format string encoding format.

        for (i, piece) in template.iter().enumerate() {
            match piece {
                FormatArgsPiece::Literal(sym) => {
                    // Coalesce adjacent literal pieces.
                    if let Some(FormatArgsPiece::Literal(_)) = template.get(i + 1) {
                        incomplete_lit.push_str(sym.as_str());
                        continue;
                    }
                    let mut s = if incomplete_lit.is_empty() {
                        sym.as_str()
                    } else {
                        incomplete_lit.push_str(sym.as_str());
                        &incomplete_lit
                    };

                    // If this is the last piece and was the only piece, that means
                    // there are no placeholders and the entire format string is just a literal.
                    //
                    // In that case, we can just use `from_str`.
                    if i + 1 == template.len() && bytecode.is_empty() {
                        // Generate:
                        //     <core::fmt::Arguments>::from_str("meow")
                        let from_str = self.ty_rel_lang_path_desugared_expr(
                            lang_items.FormatArguments,
                            sym::from_str,
                        );
                        let sym =
                            if incomplete_lit.is_empty() { sym.clone() } else { Symbol::intern(s) };
                        let s = self.alloc_expr_desugared(Expr::Literal(Literal::String(sym)));
                        let from_str = self.alloc_expr(
                            Expr::Call { callee: from_str, args: Box::new([s]) },
                            syntax_ptr,
                        );
                        return if !fmt.arguments.arguments.is_empty() {
                            // With an incomplete format string (e.g. only an opening `{`), it's possible for `arguments`
                            // to be non-empty when reaching this code path.
                            self.alloc_expr(
                                Expr::Block {
                                    id: None,
                                    statements: fmt
                                        .arguments
                                        .arguments
                                        .iter()
                                        .map(|arg| Statement::Expr {
                                            expr: arg.expr,
                                            has_semi: true,
                                        })
                                        .collect(),
                                    tail: Some(from_str),
                                    label: None,
                                },
                                syntax_ptr,
                            )
                        } else {
                            from_str
                        };
                    }

                    // Encode the literal in chunks of up to u16::MAX bytes, split at utf-8 boundaries.
                    while !s.is_empty() {
                        let len = s.floor_char_boundary(usize::from(u16::MAX));
                        if len < 0x80 {
                            bytecode.push(len as u8);
                        } else {
                            bytecode.push(0x80);
                            bytecode.extend_from_slice(&(len as u16).to_le_bytes());
                        }
                        bytecode.extend(&s.as_bytes()[..len]);
                        s = &s[len..];
                    }

                    incomplete_lit.clear();
                }
                FormatArgsPiece::Placeholder(p) => {
                    // Push the start byte and remember its index so we can set the option bits later.
                    let i = bytecode.len();
                    bytecode.push(0xC0);

                    let position = match &p.argument.index {
                        &Ok(it) => it,
                        Err(_) => usize::MAX,
                    };
                    let position = argmap
                        .insert_full((position, ArgumentType::Format(p.format_trait)))
                        .0 as u64;

                    // This needs to match the constants in library/core/src/fmt/mod.rs.
                    let o = &p.format_options;
                    let align = match o.alignment {
                        Some(FormatAlignment::Left) => 0,
                        Some(FormatAlignment::Right) => 1,
                        Some(FormatAlignment::Center) => 2,
                        None => 3,
                    };
                    let default_flags = 0x6000_0020;
                    let flags: u32 = o.fill.unwrap_or(' ') as u32
                        | ((o.sign == Some(FormatSign::Plus)) as u32) << 21
                        | ((o.sign == Some(FormatSign::Minus)) as u32) << 22
                        | (o.alternate as u32) << 23
                        | (o.zero_pad as u32) << 24
                        | ((o.debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25
                        | ((o.debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26
                        | (o.width.is_some() as u32) << 27
                        | (o.precision.is_some() as u32) << 28
                        | align << 29;
                    if flags != default_flags {
                        bytecode[i] |= 1;
                        bytecode.extend_from_slice(&flags.to_le_bytes());
                        if let Some(val) = &o.width {
                            let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap);
                            // Only encode if nonzero; zero is the default.
                            if indirect || val != 0 {
                                bytecode[i] |= 1 << 1 | (indirect as u8) << 4;
                                bytecode.extend_from_slice(&val.to_le_bytes());
                            }
                        }
                        if let Some(val) = &o.precision {
                            let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap);
                            // Only encode if nonzero; zero is the default.
                            if indirect || val != 0 {
                                bytecode[i] |= 1 << 2 | (indirect as u8) << 5;
                                bytecode.extend_from_slice(&val.to_le_bytes());
                            }
                        }
                    }
                    if implicit_arg_index != position {
                        bytecode[i] |= 1 << 3;
                        bytecode.extend_from_slice(&(position as u16).to_le_bytes());
                    }
                    implicit_arg_index = position + 1;
                }
            }
        }

        assert!(incomplete_lit.is_empty());

        // Zero terminator.
        bytecode.push(0);

        // Ensure all argument indexes actually fit in 16 bits, as we truncated them to 16 bits before.
        if argmap.len() > u16::MAX as usize {
            // FIXME: Emit an error.
            // ctx.dcx().span_err(macsp, "too many format arguments");
        }

        let arguments = &fmt.arguments.arguments[..];

        let (mut statements, args) = if arguments.is_empty() {
            // Generate:
            //     []
            (
                Vec::new(),
                self.alloc_expr_desugared(Expr::Array(Array::ElementList {
                    elements: Box::new([]),
                })),
            )
        } else {
            // Generate:
            //     super let args = (&arg0, &arg1, &…);
            let args_name = self.generate_new_name();
            let args_path = Path::from(args_name.clone());
            let args_binding = self.alloc_binding(
                args_name.clone(),
                BindingAnnotation::Unannotated,
                HygieneId::ROOT,
            );
            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
            self.add_definition_to_binding(args_binding, args_pat);
            let elements = arguments
                .iter()
                .map(|arg| {
                    self.alloc_expr_desugared(Expr::Ref {
                        expr: arg.expr,
                        rawness: Rawness::Ref,
                        mutability: Mutability::Shared,
                    })
                })
                .collect();
            let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements });
            // FIXME: Make this a `super let` when we have this statement.
            let let_statement_1 = Statement::Let {
                pat: args_pat,
                type_ref: None,
                initializer: Some(args_tuple),
                else_branch: None,
            };

            // Generate:
            //     super let args = [
            //         <core::fmt::Argument>::new_display(args.0),
            //         <core::fmt::Argument>::new_lower_hex(args.1),
            //         <core::fmt::Argument>::new_debug(args.0),
            //         …
            //     ];
            let args = argmap
                .iter()
                .map(|&(arg_index, ty)| {
                    let args_ident_expr = self.alloc_expr_desugared(Expr::Path(args_path.clone()));
                    let arg = self.alloc_expr_desugared(Expr::Field {
                        expr: args_ident_expr,
                        name: Name::new_tuple_field(arg_index),
                    });
                    let arg_ptr = arguments.get(arg_index).and_then(|it| it.syntax);
                    self.make_argument(arg_ptr, arg, ty)
                })
                .collect();
            let args =
                self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
            let args_binding =
                self.alloc_binding(args_name, BindingAnnotation::Unannotated, HygieneId::ROOT);
            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
            self.add_definition_to_binding(args_binding, args_pat);
            // FIXME: Make this a `super let` when we have this statement.
            let let_statement_2 = Statement::Let {
                pat: args_pat,
                type_ref: None,
                initializer: Some(args),
                else_branch: None,
            };
            (
                vec![let_statement_1, let_statement_2],
                self.alloc_expr_desugared(Expr::Path(args_path)),
            )
        };

        // Generate:
        //     unsafe {
        //         <core::fmt::Arguments>::new(b"…", &args)
        //     }
        let template = self
            .alloc_expr_desugared(Expr::Literal(Literal::ByteString(bytecode.into_boxed_slice())));
        let call = {
            let new = self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new);
            let args = self.alloc_expr_desugared(Expr::Ref {
                expr: args,
                rawness: Rawness::Ref,
                mutability: Mutability::Shared,
            });
            self.alloc_expr_desugared(Expr::Call { callee: new, args: Box::new([template, args]) })
        };
        let call = self.alloc_expr(
            Expr::Unsafe { id: None, statements: Box::new([]), tail: Some(call) },
            syntax_ptr,
        );

        // We collect the unused expressions here so that we still infer them instead of
        // dropping them out of the expression tree. We cannot store them in the `Unsafe`
        // block because then unsafe blocks within them will get a false "unused unsafe"
        // diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).
        statements
            .extend(fmt.orphans.into_iter().map(|expr| Statement::Expr { expr, has_semi: true }));

        if !statements.is_empty() {
            // Generate:
            //     {
            //         super let …
            //         super let …
            //         <core::fmt::Arguments>::new(…)
            //     }
            self.alloc_expr(
                Expr::Block {
                    id: None,
                    statements: statements.into_boxed_slice(),
                    tail: Some(call),
                    label: None,
                },
                syntax_ptr,
            )
        } else {
            call
        }
    }

    /// Get the value for a `width` or `precision` field.
    ///
    /// Returns the value and whether it is indirect (an indexed argument) or not.
    fn make_count_after_1_93_0(
        &self,
        count: &FormatCount,
        argmap: &mut FxIndexSet<(usize, ArgumentType)>,
    ) -> (bool, u16) {
        match count {
            FormatCount::Literal(n) => (false, *n),
            FormatCount::Argument(arg) => {
                let index = match &arg.index {
                    &Ok(it) => it,
                    Err(_) => usize::MAX,
                };
                (true, argmap.insert_full((index, ArgumentType::Usize)).0 as u16)
            }
        }
    }

    fn collect_format_args_before_1_93_0_impl(
        &mut self,
        syntax_ptr: AstPtr<ast::Expr>,
        fmt: FormatArgs,
    ) -> ExprId {
        // Create a list of all _unique_ (argument, format trait) combinations.
        // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]
        let mut argmap = FxIndexSet::default();
        for piece in fmt.template.iter() {
            let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
            if let Ok(index) = placeholder.argument.index {
                argmap.insert((index, ArgumentType::Format(placeholder.format_trait)));
            }
        }

        let lit_pieces = fmt
            .template
            .iter()
            .enumerate()
            .filter_map(|(i, piece)| {
                match piece {
                    FormatArgsPiece::Literal(s) => {
                        Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone()))))
                    }
                    &FormatArgsPiece::Placeholder(_) => {
                        // Inject empty string before placeholders when not already preceded by a literal piece.
                        if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_))
                        {
                            Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(
                                Symbol::empty(),
                            ))))
                        } else {
                            None
                        }
                    }
                }
            })
            .collect();
        let lit_pieces =
            self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: lit_pieces }));
        let lit_pieces = self.alloc_expr_desugared(Expr::Ref {
            expr: lit_pieces,
            rawness: Rawness::Ref,
            mutability: Mutability::Shared,
        });
        let format_options = {
            // Generate:
            //     &[format_spec_0, format_spec_1, format_spec_2]
            let elements = fmt
                .template
                .iter()
                .filter_map(|piece| {
                    let FormatArgsPiece::Placeholder(placeholder) = piece else { return None };
                    Some(self.make_format_spec(placeholder, &mut argmap))
                })
                .collect();
            let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements }));
            self.alloc_expr_desugared(Expr::Ref {
                expr: array,
                rawness: Rawness::Ref,
                mutability: Mutability::Shared,
            })
        };

        // Assume that rustc version >= 1.89.0 iff lang item `format_arguments` exists
        // but `format_unsafe_arg` does not
        let lang_items = self.lang_items();
        let fmt_args = lang_items.FormatArguments;
        let fmt_unsafe_arg = lang_items.FormatUnsafeArg;
        let use_format_args_since_1_89_0 = fmt_args.is_some() && fmt_unsafe_arg.is_none();

        if use_format_args_since_1_89_0 {
            self.collect_format_args_after_1_89_0_impl(
                syntax_ptr,
                fmt,
                argmap,
                lit_pieces,
                format_options,
            )
        } else {
            self.collect_format_args_before_1_89_0_impl(
                syntax_ptr,
                fmt,
                argmap,
                lit_pieces,
                format_options,
            )
        }
    }

    /// `format_args!` expansion implementation for rustc versions < `1.89.0`
    fn collect_format_args_before_1_89_0_impl(
        &mut self,
        syntax_ptr: AstPtr<ast::Expr>,
        fmt: FormatArgs,
        argmap: FxIndexSet<(usize, ArgumentType)>,
        lit_pieces: ExprId,
        format_options: ExprId,
    ) -> ExprId {
        let arguments = &*fmt.arguments.arguments;

        let args = if arguments.is_empty() {
            let expr = self
                .alloc_expr_desugared(Expr::Array(Array::ElementList { elements: Box::default() }));
            self.alloc_expr_desugared(Expr::Ref {
                expr,
                rawness: Rawness::Ref,
                mutability: Mutability::Shared,
            })
        } else {
            // Generate:
            //     &match (&arg0, &arg1, &…) {
            //         args => [
            //             <core::fmt::Argument>::new_display(args.0),
            //             <core::fmt::Argument>::new_lower_hex(args.1),
            //             <core::fmt::Argument>::new_debug(args.0),
            //             …
            //         ]
            //     }
            let args = argmap
                .iter()
                .map(|&(arg_index, ty)| {
                    let arg = self.alloc_expr_desugared(Expr::Ref {
                        expr: arguments[arg_index].expr,
                        rawness: Rawness::Ref,
                        mutability: Mutability::Shared,
                    });
                    let arg_ptr = arguments.get(arg_index).and_then(|it| it.syntax);
                    self.make_argument(arg_ptr, arg, ty)
                })
                .collect();
            let array =
                self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
            self.alloc_expr_desugared(Expr::Ref {
                expr: array,
                rawness: Rawness::Ref,
                mutability: Mutability::Shared,
            })
        };

        // Generate:
        //     <core::fmt::Arguments>::new_v1_formatted(
        //         lit_pieces,
        //         args,
        //         format_options,
        //         unsafe { ::core::fmt::UnsafeArg::new() }
        //     )

        let lang_items = self.lang_items();
        let new_v1_formatted =
            self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new_v1_formatted);
        let unsafe_arg_new =
            self.ty_rel_lang_path_desugared_expr(lang_items.FormatUnsafeArg, sym::new);
        let unsafe_arg_new =
            self.alloc_expr_desugared(Expr::Call { callee: unsafe_arg_new, args: Box::default() });
        let mut unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe {
            id: None,
            statements: Box::new([]),
            tail: Some(unsafe_arg_new),
        });
        if !fmt.orphans.is_empty() {
            unsafe_arg_new = self.alloc_expr_desugared(Expr::Block {
                id: None,
                // We collect the unused expressions here so that we still infer them instead of
                // dropping them out of the expression tree. We cannot store them in the `Unsafe`
                // block because then unsafe blocks within them will get a false "unused unsafe"
                // diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).
                statements: fmt
                    .orphans
                    .into_iter()
                    .map(|expr| Statement::Expr { expr, has_semi: true })
                    .collect(),
                tail: Some(unsafe_arg_new),
                label: None,
            });
        }

        self.alloc_expr(
            Expr::Call {
                callee: new_v1_formatted,
                args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]),
            },
            syntax_ptr,
        )
    }

    /// `format_args!` expansion implementation for rustc versions >= `1.89.0`,
    /// especially since [this PR](https://github.com/rust-lang/rust/pull/140748)
    fn collect_format_args_after_1_89_0_impl(
        &mut self,
        syntax_ptr: AstPtr<ast::Expr>,
        fmt: FormatArgs,
        argmap: FxIndexSet<(usize, ArgumentType)>,
        lit_pieces: ExprId,
        format_options: ExprId,
    ) -> ExprId {
        let arguments = &*fmt.arguments.arguments;

        let (let_stmts, args) = if arguments.is_empty() {
            (
                // Generate:
                //     []
                vec![],
                self.alloc_expr_desugared(Expr::Array(Array::ElementList {
                    elements: Box::default(),
                })),
            )
        } else if argmap.len() == 1 && arguments.len() == 1 {
            // Only one argument, so we don't need to make the `args` tuple.
            //
            // Generate:
            //     super let args = [<core::fmt::Arguments>::new_display(&arg)];
            let args = argmap
                .iter()
                .map(|&(arg_index, ty)| {
                    let ref_arg = self.alloc_expr_desugared(Expr::Ref {
                        expr: arguments[arg_index].expr,
                        rawness: Rawness::Ref,
                        mutability: Mutability::Shared,
                    });
                    let arg_ptr = arguments.get(arg_index).and_then(|it| it.syntax);
                    self.make_argument(arg_ptr, ref_arg, ty)
                })
                .collect();
            let args =
                self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
            let args_name = self.generate_new_name();
            let args_binding = self.alloc_binding(
                args_name.clone(),
                BindingAnnotation::Unannotated,
                HygieneId::ROOT,
            );
            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
            self.add_definition_to_binding(args_binding, args_pat);
            // TODO: We don't have `super let` yet.
            let let_stmt = Statement::Let {
                pat: args_pat,
                type_ref: None,
                initializer: Some(args),
                else_branch: None,
            };
            (vec![let_stmt], self.alloc_expr_desugared(Expr::Path(args_name.into())))
        } else {
            // Generate:
            //     super let args = (&arg0, &arg1, &...);
            let args_name = self.generate_new_name();
            let args_binding = self.alloc_binding(
                args_name.clone(),
                BindingAnnotation::Unannotated,
                HygieneId::ROOT,
            );
            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
            self.add_definition_to_binding(args_binding, args_pat);
            let elements = arguments
                .iter()
                .map(|arg| {
                    self.alloc_expr_desugared(Expr::Ref {
                        expr: arg.expr,
                        rawness: Rawness::Ref,
                        mutability: Mutability::Shared,
                    })
                })
                .collect();
            let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements });
            // TODO: We don't have `super let` yet
            let let_stmt1 = Statement::Let {
                pat: args_pat,
                type_ref: None,
                initializer: Some(args_tuple),
                else_branch: None,
            };

            // Generate:
            //     super let args = [
            //         <core::fmt::Argument>::new_display(args.0),
            //         <core::fmt::Argument>::new_lower_hex(args.1),
            //         <core::fmt::Argument>::new_debug(args.0),
            //         …
            //     ];
            let args = argmap
                .iter()
                .map(|&(arg_index, ty)| {
                    let args_ident_expr =
                        self.alloc_expr_desugared(Expr::Path(args_name.clone().into()));
                    let arg = self.alloc_expr_desugared(Expr::Field {
                        expr: args_ident_expr,
                        name: Name::new_tuple_field(arg_index),
                    });
                    let arg_ptr = arguments.get(arg_index).and_then(|it| it.syntax);
                    self.make_argument(arg_ptr, arg, ty)
                })
                .collect();
            let array =
                self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
            let args_binding = self.alloc_binding(
                args_name.clone(),
                BindingAnnotation::Unannotated,
                HygieneId::ROOT,
            );
            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
            self.add_definition_to_binding(args_binding, args_pat);
            let let_stmt2 = Statement::Let {
                pat: args_pat,
                type_ref: None,
                initializer: Some(array),
                else_branch: None,
            };
            (vec![let_stmt1, let_stmt2], self.alloc_expr_desugared(Expr::Path(args_name.into())))
        };

        // Generate:
        //     &args
        let args = self.alloc_expr_desugared(Expr::Ref {
            expr: args,
            rawness: Rawness::Ref,
            mutability: Mutability::Shared,
        });

        let call_block = {
            // Generate:
            //     unsafe {
            //         <core::fmt::Arguments>::new_v1_formatted(
            //             lit_pieces,
            //             args,
            //             format_options,
            //         )
            //     }

            let new_v1_formatted = self.ty_rel_lang_path_desugared_expr(
                self.lang_items().FormatArguments,
                sym::new_v1_formatted,
            );
            let args = [lit_pieces, args, format_options];
            let call = self
                .alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() });

            Expr::Unsafe { id: None, statements: Box::default(), tail: Some(call) }
        };

        if !let_stmts.is_empty() {
            // Generate:
            //     {
            //         super let …
            //         super let …
            //         <core::fmt::Arguments>::new_…(…)
            //     }
            let call = self.alloc_expr_desugared(call_block);
            self.alloc_expr(
                Expr::Block {
                    id: None,
                    statements: let_stmts.into(),
                    tail: Some(call),
                    label: None,
                },
                syntax_ptr,
            )
        } else {
            self.alloc_expr(call_block, syntax_ptr)
        }
    }

    /// Generate a hir expression for a format_args placeholder specification.
    ///
    /// Generates
    ///
    /// ```text
    ///     <core::fmt::rt::Placeholder::new(
    ///         …usize, // position
    ///         '…', // fill
    ///         <core::fmt::rt::Alignment>::…, // alignment
    ///         …u32, // flags
    ///         <core::fmt::rt::Count::…>, // width
    ///         <core::fmt::rt::Count::…>, // precision
    ///     )
    /// ```
    fn make_format_spec(
        &mut self,
        placeholder: &FormatPlaceholder,
        argmap: &mut FxIndexSet<(usize, ArgumentType)>,
    ) -> ExprId {
        let lang_items = self.lang_items();
        let position = match placeholder.argument.index {
            Ok(arg_index) => {
                let (i, _) =
                    argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait)));
                self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
                    i as u128,
                    Some(BuiltinUint::Usize),
                )))
            }
            Err(_) => self.missing_expr(),
        };
        let &FormatOptions {
            ref width,
            ref precision,
            alignment,
            fill,
            sign,
            alternate,
            zero_pad,
            debug_hex,
        } = &placeholder.format_options;

        let precision_expr = self.make_count_before_1_93_0(precision, argmap);
        let width_expr = self.make_count_before_1_93_0(width, argmap);

        if self.krate.workspace_data(self.db).is_atleast_187() {
            // These need to match the constants in library/core/src/fmt/rt.rs.
            let align = match alignment {
                Some(FormatAlignment::Left) => 0,
                Some(FormatAlignment::Right) => 1,
                Some(FormatAlignment::Center) => 2,
                None => 3,
            };
            // This needs to match `Flag` in library/core/src/fmt/rt.rs.
            let flags = fill.unwrap_or(' ') as u32
                | ((sign == Some(FormatSign::Plus)) as u32) << 21
                | ((sign == Some(FormatSign::Minus)) as u32) << 22
                | (alternate as u32) << 23
                | (zero_pad as u32) << 24
                | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25
                | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26
                | (width.is_some() as u32) << 27
                | (precision.is_some() as u32) << 28
                | align << 29
                | 1 << 31; // Highest bit always set.
            let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
                flags as u128,
                Some(BuiltinUint::U32),
            )));

            let position =
                RecordLitField { name: Name::new_symbol_root(sym::position), expr: position };
            let flags = RecordLitField { name: Name::new_symbol_root(sym::flags), expr: flags };
            let precision = RecordLitField {
                name: Name::new_symbol_root(sym::precision),
                expr: precision_expr,
            };
            let width =
                RecordLitField { name: Name::new_symbol_root(sym::width), expr: width_expr };
            match self.lang_path(lang_items.FormatPlaceholder) {
                Some(path) => self.alloc_expr_desugared(Expr::RecordLit {
                    path,
                    fields: Box::new([position, flags, precision, width]),
                    spread: RecordSpread::None,
                }),
                None => self.missing_expr(),
            }
        } else {
            let format_placeholder_new =
                self.ty_rel_lang_path_desugared_expr(lang_items.FormatPlaceholder, sym::new);
            // This needs to match `Flag` in library/core/src/fmt/rt.rs.
            let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32)
                | (((sign == Some(FormatSign::Minus)) as u32) << 1)
                | ((alternate as u32) << 2)
                | ((zero_pad as u32) << 3)
                | (((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4)
                | (((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5);
            let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
                flags as u128,
                Some(BuiltinUint::U32),
            )));
            let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' '))));
            let align = self.ty_rel_lang_path_desugared_expr(
                lang_items.FormatAlignment,
                match alignment {
                    Some(FormatAlignment::Left) => sym::Left,
                    Some(FormatAlignment::Right) => sym::Right,
                    Some(FormatAlignment::Center) => sym::Center,
                    None => sym::Unknown,
                },
            );
            self.alloc_expr_desugared(Expr::Call {
                callee: format_placeholder_new,
                args: Box::new([position, fill, align, flags, precision_expr, width_expr]),
            })
        }
    }

    /// Generate a hir expression for a format_args Count.
    ///
    /// Generates:
    ///
    /// ```text
    ///     <core::fmt::rt::Count>::Is(…)
    /// ```
    ///
    /// or
    ///
    /// ```text
    ///     <core::fmt::rt::Count>::Param(…)
    /// ```
    ///
    /// or
    ///
    /// ```text
    ///     <core::fmt::rt::Count>::Implied
    /// ```
    fn make_count_before_1_93_0(
        &mut self,
        count: &Option<FormatCount>,
        argmap: &mut FxIndexSet<(usize, ArgumentType)>,
    ) -> ExprId {
        let lang_items = self.lang_items();
        match count {
            Some(FormatCount::Literal(n)) => {
                let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
                    *n as u128,
                    // FIXME: Change this to Some(BuiltinUint::U16) once we drop support for toolchains < 1.88
                    None,
                )));
                let count_is =
                    self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Is);
                self.alloc_expr_desugared(Expr::Call { callee: count_is, args: Box::new([args]) })
            }
            Some(FormatCount::Argument(arg)) => {
                if let Ok(arg_index) = arg.index {
                    let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize));

                    let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
                        i as u128,
                        Some(BuiltinUint::Usize),
                    )));
                    let count_param =
                        self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Param);
                    self.alloc_expr_desugared(Expr::Call {
                        callee: count_param,
                        args: Box::new([args]),
                    })
                } else {
                    // FIXME: This drops arg causing it to potentially not be resolved/type checked
                    // when typing?
                    self.missing_expr()
                }
            }
            None => match self.ty_rel_lang_path(lang_items.FormatCount, sym::Implied) {
                Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)),
                None => self.missing_expr(),
            },
        }
    }

    /// Generate a hir expression representing an argument to a format_args invocation.
    ///
    /// Generates:
    ///
    /// ```text
    ///     <core::fmt::Argument>::new_…(arg)
    /// ```
    fn make_argument(
        &mut self,
        arg_ptr: Option<AstPtr<ast::Expr>>,
        arg: ExprId,
        ty: ArgumentType,
    ) -> ExprId {
        use ArgumentType::*;
        use FormatTrait::*;

        let new_fn = self.ty_rel_lang_path(
            self.lang_items().FormatArgument,
            match ty {
                Format(Display) => sym::new_display,
                Format(Debug) => sym::new_debug,
                Format(LowerExp) => sym::new_lower_exp,
                Format(UpperExp) => sym::new_upper_exp,
                Format(Octal) => sym::new_octal,
                Format(Pointer) => sym::new_pointer,
                Format(Binary) => sym::new_binary,
                Format(LowerHex) => sym::new_lower_hex,
                Format(UpperHex) => sym::new_upper_hex,
                Usize => sym::from_usize,
            },
        );
        let new_fn = match new_fn {
            Some(new_fn) => {
                let new_fn = self.store.exprs.alloc(Expr::Path(new_fn));
                if let Some(arg_ptr) = arg_ptr {
                    // Trait errors (the argument does not implement the expected fmt trait) will show
                    // on this path, so to not end up with synthetic syntax we insert this mapping. We
                    // don't want to insert the other way's mapping in order to not override the source
                    // for the argument.
                    self.store
                        .expr_map_back
                        .insert(new_fn, self.expander.in_file(arg_ptr.wrap_left()));
                }
                new_fn
            }
            None => self.missing_expr(),
        };
        self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) })
    }

    fn ty_rel_lang_path_desugared_expr(
        &mut self,
        lang: Option<impl Into<LangItemTarget>>,
        relative_name: Symbol,
    ) -> ExprId {
        self.alloc_expr_desugared(self.ty_rel_lang_path_expr(lang, relative_name))
    }
}

#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
enum ArgumentType {
    Format(FormatTrait),
    Usize,
}
