//! Completes identifiers in format string literals.

use ide_db::helpers::format_string::is_format_string;
use itertools::Itertools;
use syntax::{ast, AstToken, TextRange, TextSize};

use crate::{context::CompletionContext, CompletionItem, CompletionItemKind, Completions};

/// Complete identifiers in format strings.
pub(crate) fn format_string(acc: &mut Completions, ctx: &CompletionContext) {
    let string = match ast::String::cast(ctx.token.clone())
        .zip(ast::String::cast(ctx.original_token.clone()))
    {
        Some((expanded, original)) if is_format_string(&expanded) => original,
        _ => return,
    };
    let cursor = ctx.position.offset;
    let lit_start = ctx.original_token.text_range().start();
    let cursor_in_lit = cursor - lit_start;

    let prefix = &string.text()[..cursor_in_lit.into()];
    let braces = prefix.char_indices().rev().skip_while(|&(_, c)| c.is_alphanumeric()).next_tuple();
    let brace_offset = match braces {
        // escaped brace
        Some(((_, '{'), (_, '{'))) => return,
        Some(((idx, '{'), _)) => lit_start + TextSize::from(idx as u32 + 1),
        _ => return,
    };

    let source_range = TextRange::new(brace_offset, cursor);
    ctx.locals.iter().for_each(|(name, _)| {
        CompletionItem::new(CompletionItemKind::Binding, source_range, name.to_smol_str())
            .add_to(acc);
    })
}

#[cfg(test)]
mod tests {
    use expect_test::{expect, Expect};

    use crate::tests::{check_edit, completion_list_no_kw};

    fn check(ra_fixture: &str, expect: Expect) {
        let actual = completion_list_no_kw(ra_fixture);
        expect.assert_eq(&actual);
    }

    #[test]
    fn works_when_wrapped() {
        check(
            r#"
macro_rules! format_args {
    ($lit:literal $(tt:tt)*) => { 0 },
}
macro_rules! print {
    ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
}
fn main() {
    let foobar = 1;
    print!("f$0");
}
"#,
            expect![[]],
        );
    }

    #[test]
    fn no_completion_without_brace() {
        check(
            r#"
macro_rules! format_args {
    ($lit:literal $(tt:tt)*) => { 0 },
}
fn main() {
    let foobar = 1;
    format_args!("f$0");
}
"#,
            expect![[]],
        );
    }

    #[test]
    fn completes_locals() {
        check_edit(
            "foobar",
            r#"
macro_rules! format_args {
    ($lit:literal $(tt:tt)*) => { 0 },
}
fn main() {
    let foobar = 1;
    format_args!("{f$0");
}
"#,
            r#"
macro_rules! format_args {
    ($lit:literal $(tt:tt)*) => { 0 },
}
fn main() {
    let foobar = 1;
    format_args!("{foobar");
}
"#,
        );
        check_edit(
            "foobar",
            r#"
macro_rules! format_args {
    ($lit:literal $(tt:tt)*) => { 0 },
}
fn main() {
    let foobar = 1;
    format_args!("{$0");
}
"#,
            r#"
macro_rules! format_args {
    ($lit:literal $(tt:tt)*) => { 0 },
}
fn main() {
    let foobar = 1;
    format_args!("{foobar");
}
"#,
        );
    }
}
