use syntax::{AstToken, ast};

use crate::{AssistContext, AssistId, Assists, GroupLabel};

// Assist: convert_char_literal
//
// Converts character literals between different representations. Currently supports normal character -> ASCII / Unicode escape.
// ```
// const _: char = 'a'$0;
// ```
// ->
// ```
// const _: char = '\x61';
// ```
pub(crate) fn convert_char_literal(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
    if !ctx.has_empty_selection() {
        return None;
    }

    let literal = ctx.find_node_at_offset::<ast::Literal>()?;
    let literal = match literal.kind() {
        ast::LiteralKind::Char(it) => it,
        _ => return None,
    };

    let value = literal.value().ok()?;
    let text = literal.syntax().text().to_owned();
    let range = literal.syntax().text_range();
    let group_id = GroupLabel("Convert char representation".into());

    let mut add_assist = |converted: String| {
        // Skip no-op assists (e.g. `'const C: char = '\\x61';'` already matches the ASCII form).
        if converted == text {
            return;
        }
        let label = format!("Convert {text} to {converted}");
        acc.add_group(
            &group_id,
            AssistId::refactor_rewrite("convert_char_literal"),
            label,
            range,
            |builder| builder.replace(range, converted),
        );
    };

    if value.is_ascii() {
        add_assist(format!("'\\x{:02x}'", value as u32));
    }

    add_assist(format!("'\\u{{{:x}}}'", value as u32));

    Some(())
}

#[cfg(test)]
mod tests {
    use crate::tests::check_assist_by_label;

    use super::convert_char_literal;

    #[test]
    fn ascii_char_to_ascii_and_unicode() {
        let before = "const _: char = 'a'$0;";
        check_assist_by_label(
            convert_char_literal,
            before,
            "const _: char = '\\x61';",
            "Convert 'a' to '\\x61'",
        );
        check_assist_by_label(
            convert_char_literal,
            before,
            "const _: char = '\\u{61}';",
            "Convert 'a' to '\\u{61}'",
        );
    }

    #[test]
    fn non_ascii_char_only_unicode() {
        check_assist_by_label(
            convert_char_literal,
            "const _: char = '😀'$0;",
            "const _: char = '\\u{1f600}';",
            "Convert '😀' to '\\u{1f600}'",
        );
    }

    #[test]
    fn ascii_escape_can_convert_to_unicode() {
        check_assist_by_label(
            convert_char_literal,
            "const _: char = '\\x61'$0;",
            "const _: char = '\\u{61}';",
            "Convert '\\x61' to '\\u{61}'",
        );
    }
}
