#[cfg(test)]
mod tests {
    use crate::{
        assert_format_with_config,
        config::{
            EndOfLine, ExpandStrategy, IndentConfig, IndentKind, LayoutConfig, LuaFormatConfig,
            LuaSyntaxLevel, OutputConfig, QuoteStyle, SimpleLambdaSingleLine, SingleArgCallParens,
            SpacingConfig, TrailingComma, TrailingTableSeparator,
        },
    };

    // ========== spacing options ==========

    #[test]
    fn test_space_before_func_paren() {
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_before_func_paren: true,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"
function foo(a, b)
return a
end
"#,
            r#"
function foo (a, b)
    return a
end
"#,
            config
        );
    }

    #[test]
    fn test_space_before_call_paren() {
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_before_call_paren: true,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"print(1)
"#,
            r#"print (1)
"#,
            config
        );
    }

    #[test]
    fn test_space_inside_parens() {
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_inside_parens: true,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local a = (1 + 2)
"#,
            r#"local a = ( 1 + 2 )
"#,
            config
        );
    }

    #[test]
    fn test_space_inside_braces() {
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_inside_braces: true,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local t = {1, 2, 3}
"#,
            r#"local t = { 1, 2, 3 }
"#,
            config
        );
    }

    #[test]
    fn test_no_space_inside_braces() {
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_inside_braces: false,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local t = { 1, 2, 3 }
"#,
            r#"local t = {1, 2, 3}
"#,
            config
        );
    }

    // ========== table expand strategy ==========

    #[test]
    fn test_table_expand_always() {
        let config = LuaFormatConfig {
            layout: LayoutConfig {
                table_expand: ExpandStrategy::Always,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local t = {a = 1, b = 2}
"#,
            r#"
local t = {
    a = 1,
    b = 2
}
"#,
            config
        );
    }

    #[test]
    fn test_table_expand_never() {
        let config = LuaFormatConfig {
            layout: LayoutConfig {
                table_expand: ExpandStrategy::Never,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"
local t = {
a = 1,
b = 2
}
"#,
            r#"local t = { a = 1, b = 2 }
"#,
            config
        );
    }

    #[test]
    fn test_prefer_table_layout_from_source_option() {
        let config = LuaFormatConfig {
            layout: LayoutConfig {
                prefer_table_layout_from_source: true,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"
local t = {
    1,
    2,
    3,
}
"#,
            r#"
local t = {
    1,
    2,
    3
}
"#,
            config
        );
    }

    #[test]
    fn test_prefer_call_args_layout_from_source_option() {
        let config = LuaFormatConfig {
            layout: LayoutConfig {
                prefer_call_args_layout_from_source: true,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"
local value = call(
    a,
    b,
    c
)
"#,
            r#"
local value = call(
    a,
    b,
    c
)
"#,
            config
        );
    }

    // ========== trailing comma ==========

    #[test]
    fn test_trailing_comma_always_table() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                trailing_comma: TrailingComma::Always,
                ..Default::default()
            },
            layout: LayoutConfig {
                table_expand: ExpandStrategy::Always,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"
local t = {
a = 1,
b = 2
}
"#,
            r#"
local t = {
    a = 1,
    b = 2,
}
"#,
            config
        );
    }

    #[test]
    fn test_trailing_comma_never() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                trailing_comma: TrailingComma::Never,
                ..Default::default()
            },
            layout: LayoutConfig {
                table_expand: ExpandStrategy::Always,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"
local t = {
a = 1,
b = 2,
}
"#,
            r#"
local t = {
    a = 1,
    b = 2
}
"#,
            config
        );
    }

    #[test]
    fn test_table_trailing_separator_can_override_global_trailing_comma() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                trailing_comma: TrailingComma::Never,
                trailing_table_separator: TrailingTableSeparator::Multiline,
                ..Default::default()
            },
            layout: LayoutConfig {
                table_expand: ExpandStrategy::Always,
                call_args_expand: ExpandStrategy::Always,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"local t = { a = 1, b = 2 }
"#,
            r#"local t = {
    a = 1,
    b = 2,
}
"#,
            config.clone()
        );

        assert_format_with_config!(
            r#"foo(a, b)
"#,
            r#"foo(
    a,
    b
)
"#,
            config
        );
    }

    // ========== quote style ===========

    #[test]
    fn test_quote_style_double_rewrites_short_strings() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                quote_style: QuoteStyle::Double,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"local s = 'hello'
"#,
            r#"local s = "hello"
"#,
            config
        );
    }

    #[test]
    fn test_quote_style_double_allows_escaped_target_quotes_in_raw_text() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                quote_style: QuoteStyle::Double,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"local s = 'hello \"lua\"'
"#,
            r#"local s = "hello \"lua\""
"#,
            config
        );
    }

    #[test]
    fn test_quote_style_single_preserves_when_target_quote_exists_in_value() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                quote_style: QuoteStyle::Single,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"local s = "it's \"ok\""
"#,
            r#"local s = "it's \"ok\""
"#,
            config
        );
    }

    #[test]
    fn test_quote_style_single_allows_escaped_target_quotes_in_raw_text() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                quote_style: QuoteStyle::Single,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"local s = "it\'s fine"
"#,
            r#"local s = 'it\'s fine'
"#,
            config
        );
    }

    #[test]
    fn test_quote_style_single_rewrites_when_value_has_no_target_quote() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                quote_style: QuoteStyle::Single,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"local s = "hello \"lua\""
"#,
            r#"local s = 'hello "lua"'
"#,
            config
        );
    }

    #[test]
    fn test_quote_style_preserves_long_strings() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                quote_style: QuoteStyle::Single,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"local s = [[a
"b"
]]
"#,
            r#"local s = [[a
"b"
]]
"#,
            config
        );
    }

    // ========== single arg call parens ===========

    #[test]
    fn test_single_arg_call_parens_always_wraps_string_and_table_calls() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                single_arg_call_parens: SingleArgCallParens::Always,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"require "module"
"#,
            r#"require("module")
"#,
            config.clone()
        );
        assert_format_with_config!(
            r#"foo {1, 2, 3}
"#,
            r#"foo({ 1, 2, 3 })
"#,
            config
        );
    }

    #[test]
    fn test_single_arg_call_parens_omit_removes_parens_for_string_and_table_calls() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                single_arg_call_parens: SingleArgCallParens::Omit,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"require("module")
"#,
            r#"require "module"
"#,
            config.clone()
        );
        assert_format_with_config!(
            r#"foo({1, 2, 3})
"#,
            r#"foo { 1, 2, 3 }
"#,
            config
        );
    }

    #[test]
    fn test_simple_lambda_single_line_always_collapses_eligible_multiline_lambda() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                simple_lambda_single_line: SimpleLambdaSingleLine::Always,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"local f = function(x)
    return x + 1
end
"#,
            r#"local f = function (x) return x + 1 end
"#,
            config
        );
    }

    #[test]
    fn test_simple_lambda_single_line_never_keeps_simple_lambda_multiline() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                simple_lambda_single_line: SimpleLambdaSingleLine::Never,
                ..Default::default()
            },
            ..Default::default()
        };

        assert_format_with_config!(
            r#"local f = function() return x + 1 end
"#,
            r#"local f = function ()
    return x + 1
end
"#,
            config
        );
    }

    // ========== indentation ==========

    #[test]
    fn test_tab_indent() {
        let config = LuaFormatConfig {
            indent: IndentConfig {
                kind: IndentKind::Tab,
                ..Default::default()
            },
            ..Default::default()
        };
        // Keep escaped strings: raw strings can't represent \t visually
        assert_format_with_config!(
            r#"if true then
print(1)
end
"#,
            "if true then\n\tprint(1)\nend\n",
            config
        );
    }

    // ========== blank lines ==========

    #[test]
    fn test_max_blank_lines() {
        let config = LuaFormatConfig {
            layout: LayoutConfig {
                max_blank_lines: 1,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"
local a = 1




local b = 2
"#,
            r#"
local a = 1

local b = 2
"#,
            config
        );
    }

    // ========== end of line ==========

    #[test]
    fn test_crlf_end_of_line() {
        let config = LuaFormatConfig {
            output: OutputConfig {
                end_of_line: EndOfLine::CRLF,
                ..Default::default()
            },
            ..Default::default()
        };
        // Keep escaped strings: raw strings can't represent \r\n distinctly
        assert_format_with_config!(
            r#"if true then
print(1)
end
"#,
            "if true then\r\n    print(1)\r\nend\r\n",
            config
        );
    }

    // ========== operator spacing options ==========

    #[test]
    fn test_no_space_around_math_operator() {
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_around_math_operator: false,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local a = 1 + 2 * 3 - 4 / 5
"#,
            r#"local a = 1+2*3-4/5
"#,
            config
        );
    }

    #[test]
    fn test_space_around_math_operator_default() {
        // Default: spaces around math operators
        assert_format_with_config!(
            r#"local a = 1+2*3
"#,
            r#"local a = 1 + 2 * 3
"#,
            LuaFormatConfig::default()
        );
    }

    #[test]
    fn test_no_space_around_concat_operator() {
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_around_concat_operator: false,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local s = a .. b .. c
"#,
            r#"local s = a..b..c
"#,
            config
        );
    }

    #[test]
    fn test_space_around_concat_operator_default() {
        assert_format_with_config!(
            r#"local s = a..b
"#,
            r#"local s = a .. b
"#,
            LuaFormatConfig::default()
        );
    }

    #[test]
    fn test_float_concat_no_space_keeps_space() {
        // When no-space concat is enabled, `1. .. x` must keep the space to
        // avoid producing the invalid token `1...`
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_around_concat_operator: false,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local s = 1. .. "str"
"#,
            r#"local s = 1. .."str"
"#,
            config
        );
    }

    #[test]
    fn test_no_math_space_keeps_comparison_space() {
        // Disabling math operator spaces should NOT affect comparison operators
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_around_math_operator: false,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local x = a+b == c*d
"#,
            r#"local x = a+b == c*d
"#,
            config
        );
    }

    #[test]
    fn test_no_math_space_keeps_logical_space() {
        // Disabling math operator spaces should NOT affect logical operators
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_around_math_operator: false,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local a = b and c or d
"#,
            r#"local a = b and c or d
"#,
            config
        );
    }

    // ========== space around assign operator ==========

    #[test]
    fn test_no_space_around_assign() {
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_around_assign_operator: false,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local a = 1
"#,
            r#"local a=1
"#,
            config
        );
    }

    #[test]
    fn test_no_space_around_assign_table() {
        let config = LuaFormatConfig {
            spacing: SpacingConfig {
                space_around_assign_operator: false,
                ..Default::default()
            },
            ..Default::default()
        };
        assert_format_with_config!(
            r#"local t = { a = 1 }
"#,
            r#"local t={ a=1 }
"#,
            config
        );
    }

    #[test]
    fn test_space_around_assign_default() {
        assert_format_with_config!(
            r#"local a=1
"#,
            r#"local a = 1
"#,
            LuaFormatConfig::default()
        );
    }

    #[test]
    fn test_structured_toml_deserialize() {
        let config: LuaFormatConfig = toml_edit::de::from_str(
            r#"
[syntax]
level = "Lua54"

[indent]
kind = "Space"
width = 2

[layout]
max_line_width = 88
table_expand = "Always"
prefer_call_args_layout_from_source = true

[output]
quote_style = "Single"
trailing_table_separator = "Multiline"
single_arg_call_parens = "Always"
simple_lambda_single_line = "Always"

[spacing]
space_before_call_paren = true

[comments]
align_line_comments = false
space_after_comment_dash = false

[emmy_doc]
align_multiline_alias_descriptions = false
space_between_tag_columns = false
space_after_description_dash = false
compact_type_or = true

[align]
table_field = false
"#,
        )
        .expect("structured toml config should deserialize");

        assert_eq!(config.syntax.level, LuaSyntaxLevel::Lua54);
        assert_eq!(config.indent.kind, IndentKind::Space);
        assert_eq!(config.indent.width, 2);
        assert_eq!(config.layout.max_line_width, 88);
        assert_eq!(config.layout.table_expand, ExpandStrategy::Always);
        assert!(config.layout.prefer_call_args_layout_from_source);
        assert_eq!(config.output.quote_style, QuoteStyle::Single);
        assert_eq!(
            config.output.trailing_table_separator,
            TrailingTableSeparator::Multiline
        );
        assert_eq!(
            config.output.single_arg_call_parens,
            SingleArgCallParens::Always
        );
        assert_eq!(
            config.output.simple_lambda_single_line,
            SimpleLambdaSingleLine::Always
        );
        assert!(config.spacing.space_before_call_paren);
        assert!(!config.comments.align_line_comments);
        assert!(!config.comments.space_after_comment_dash);
        assert!(!config.emmy_doc.align_multiline_alias_descriptions);
        assert!(!config.emmy_doc.space_between_tag_columns);
        assert!(!config.emmy_doc.space_after_description_dash);
        assert!(config.emmy_doc.compact_type_or);
        assert!(!config.align.table_field);
    }
}
