"""Tests for literalizer converter."""

import ast
import base64
import dataclasses
import datetime
import json
import textwrap
from collections.abc import Callable

import pytest
from beartype import beartype
from hypothesis import HealthCheck, given, settings
from hypothesis import strategies as st

from literalizer import (
    Language,
    LanguageSpec,
    literalize_json,
    literalize_yaml,
)
from literalizer._formatters import (
    dict_entry_with_separator,
    fixed_sequence_open,
    format_bytes_hex,
    format_bytes_python,
    format_date_cpp,
    format_date_csharp,
    format_date_go,
    format_date_iso,
    format_date_java,
    format_date_js,
    format_date_kotlin,
    format_date_python,
    format_date_ruby,
    format_date_rust,
    format_datetime_cpp,
    format_datetime_csharp,
    format_datetime_epoch,
    format_datetime_go,
    format_datetime_iso,
    format_datetime_java_instant,
    format_datetime_java_zoned,
    format_datetime_js,
    format_datetime_kotlin,
    format_datetime_python,
    format_datetime_ruby,
    format_datetime_rust,
    format_string_backslash,
    passthrough_sequence_entry,
    passthrough_set_entry,
)
from literalizer.exceptions import JSONParseError, ParseError, YAMLParseError
from literalizer.languages import (
    Clojure,
    Cpp,
    CSharp,
    Dart,
    Elixir,
    FSharp,
    Go,
    Haskell,
    Java,
    JavaScript,
    Kotlin,
    Matlab,
    Php,
    Python,
    R,
    Ruby,
    Rust,
    Scala,
    Swift,
    TypeScript,
)

CLOJURE = Clojure()
CPP = Cpp()
CSHARP = CSharp()
DART = Dart()
ELIXIR = Elixir()
FSHARP = FSharp()
GO = Go()
HASKELL = Haskell()
JAVA = Java()
JAVASCRIPT = JavaScript()
KOTLIN = Kotlin()
PHP = Php()
PYTHON = Python()
RUBY = Ruby()
RUST = Rust()
SCALA = Scala()
SWIFT = Swift()
TYPESCRIPT = TypeScript()


@beartype
def _format_test_omap_entry(key: str, value: str) -> str:
    """Format an omap entry for use in custom Language test
    fixtures.
    """
    return f"{key}: {value}"


@pytest.mark.parametrize(
    argnames=("language", "expected"),
    argvalues=[
        (PYTHON, '(True, None, "hi", (1, 2)),'),
        (JAVASCRIPT, '[true, null, "hi", [1, 2]],'),
        (TYPESCRIPT, '[true, null, "hi", [1, 2]],'),
        (GO, '[]any{true, nil, "hi", []int{1, 2}},'),
        (CPP, '{true, nullptr, "hi", std::vector<int>{1, 2}},'),
        (JAVA, 'new Object[]{true, null, "hi", new int[]{1, 2}}'),
        (CSHARP, 'new object[] {true, (object?)null, "hi", new int[] {1, 2}}'),
        (RUBY, '[true, nil, "hi", [1, 2]],'),
        (KOTLIN, 'listOf<Any?>(true, null, "hi", intArrayOf(1, 2)),'),
        (RUST, 'vec![true, None, "hi", vec![1, 2]],'),
    ],
)
def test_language_sequence(*, language: Language, expected: str) -> None:
    """Each language produces the correct sequence literal."""
    result = literalize_json(
        json_string=json.dumps(obj=[[True, None, "hi", [1, 2]]]),
        language=language,
        line_prefix="",
        wrap=False,
    )
    assert result == expected


def test_ruby_dict() -> None:
    """Ruby dicts use => syntax and nil."""
    result = literalize_json(
        json_string=json.dumps(obj=[{"key": None}]),
        language=RUBY,
        line_prefix="",
        wrap=False,
    )
    assert result == '{"key" => nil},'


def test_dict_python() -> None:
    """Python dict renders key-value pairs with a prefix."""
    data = {"user_1": "team_alpha", "user_2": "team_alpha"}
    result = literalize_json(
        json_string=json.dumps(obj=data),
        language=PYTHON,
        line_prefix="    ",
        wrap=False,
    )
    assert result == '    "user_1": "team_alpha",\n    "user_2": "team_alpha",'


def test_dict_ruby() -> None:
    """Ruby dict renders with => syntax."""
    result = literalize_json(
        json_string=json.dumps(obj={"user_1": "team_alpha"}),
        language=RUBY,
        line_prefix="  ",
        wrap=False,
    )
    assert result == '  "user_1" => "team_alpha",'


def test_dict_wrap() -> None:
    """Wrapping a dict adds braces and indentation."""
    result = literalize_json(
        json_string=json.dumps(obj={"a": 1, "b": 2}),
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        {
            "a": 1,
            "b": 2,
        }"""
    )
    assert result == expected


def test_java_dict_wrap_no_multiline_trailing_comma() -> None:
    """Java Map.ofEntries() must not have a trailing comma before the
    closing paren.
    """
    result = literalize_json(
        json_string=json.dumps(obj={"name": "Alice", "age": 30}),
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        Map.ofEntries(
            Map.entry("name", "Alice"),
            Map.entry("age", 30)
        )"""
    )
    assert result == expected


def test_java_dict_skips_null_values() -> None:
    """Java Map.ofEntries() omits entries with null values."""
    data = {"name": "Alice", "score": None, "age": 30}
    result = literalize_json(
        json_string=json.dumps(obj=data),
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        Map.ofEntries(
            Map.entry("name", "Alice"),
            Map.entry("age", 30)
        )"""
    )
    assert result == expected


def test_java_dict_skips_null_values_no_wrap() -> None:
    """Java dict omits null entries even without wrap."""
    result = literalize_json(
        json_string=json.dumps(obj={"a": None, "b": 1}),
        language=JAVA,
        line_prefix="",
        wrap=False,
    )
    expected = 'Map.entry("b", 1)'
    assert result == expected


def test_java_dict_all_null_values_wrap() -> None:
    """Java dict where all values are null produces empty
    Map.ofEntries().
    """
    result = literalize_json(
        json_string=json.dumps(obj={"a": None, "b": None}),
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    assert result == "Map.ofEntries()"


def test_java_yaml_dict_null_values_with_comments() -> None:
    """Java YAML dict with null values and comments does not crash."""
    yaml_string = "# comment\nname: Alice\nscore: null\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        Map.ofEntries(
            // comment
            Map.entry("name", "Alice")
        )"""
    )
    assert result == expected


def test_java_yaml_dict_null_value_inline_comment_preserved() -> None:
    """Inline comment on a null-valued dict entry is preserved as a before
    comment on the next non-null entry when skip_null_dict_values=True.
    """
    yaml_string = textwrap.dedent(
        text="""\
        host: localhost
        port: null  # not configured yet
        debug: true
        """,
    )
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        Map.ofEntries(
            Map.entry("host", "localhost"),
            // not configured yet
            Map.entry("debug", true)
        )"""
    )
    assert result == expected


def test_java_yaml_null_value_inline_comment_as_trailing() -> None:
    """Inline comment on a null-valued dict entry at the end becomes a
    trailing comment when skip_null_dict_values=True.
    """
    yaml_string = textwrap.dedent(
        text="""\
        host: localhost
        port: null  # not configured yet
        """,
    )
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        Map.ofEntries(
            Map.entry("host", "localhost")
            // not configured yet
        )"""
    )
    assert result == expected


def test_java_yaml_all_null_dict_with_trailing_comments() -> None:
    """All-null Java YAML dict with trailing comments does not duplicate
    delimiters.
    """
    yaml_string = "a: null\nb: null\n# trailing\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    expected = "Map.ofEntries()\n    // trailing"
    assert result == expected


def test_java_yaml_omap_skips_null_values() -> None:
    """Java YAML omap nested in a list filters null values."""
    yaml_string = textwrap.dedent(
        text="""\
        ---
        - !!omap
          - name: Alice
          - score: null
          - age: 30
        """,
    )
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    omap_inline = (
        "new java.util.ArrayList<>(java.util.Arrays.asList("
        'Map.entry("name", "Alice"), Map.entry("age", 30)))'
    )
    expected = f"new Object[]{{\n    {omap_inline}\n}}"
    assert result == expected


def test_java_sequence_wrap_uses_braces() -> None:
    """Java wrapped sequences use ``new Object[]{…}``."""
    result = literalize_json(
        json_string=json.dumps(obj=[1, "hello", True]),
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        new Object[]{
            1,
            "hello",
            true
        }"""
    )
    assert result == expected


@pytest.mark.parametrize(
    argnames=("json_input", "expected_open"),
    argvalues=[
        ([1, 2, 3], "new int[]{"),
        (["hello", "world"], "new String[]{"),
        ([1, "hello", True], "new Object[]{"),
    ],
    ids=["all_int", "all_string", "mixed"],
)
def test_java_typed_array_opener(
    *, json_input: list[object], expected_open: str
) -> None:
    """Java uses typed array openers inferred from element types."""
    result = literalize_json(
        json_string=json.dumps(obj=json_input),
        language=JAVA,
        line_prefix="",
        wrap=True,
    )
    assert result.startswith(expected_open)


@pytest.mark.parametrize(argnames="wrap", argvalues=[False, True])
def test_dict_empty(*, wrap: bool) -> None:
    """An empty dict produces an empty string regardless of wrap."""
    result = literalize_json(
        json_string=json.dumps(obj={}),
        language=PYTHON,
        line_prefix="",
        wrap=wrap,
    )
    assert result == ""


def test_integers() -> None:
    """Integer values are rendered literally."""
    result = literalize_json(
        json_string=json.dumps(obj=[42, 0, -7]),
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    expected = textwrap.dedent(
        text="""\
        42,
        0,
        -7,"""
    )
    assert result == expected


def test_floats() -> None:
    """Float values are rendered literally."""
    result = literalize_json(
        json_string=json.dumps(obj=[1000.0, 3.14]),
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    expected = textwrap.dedent(
        text="""\
        1000.0,
        3.14,"""
    )
    assert result == expected


def test_string_escaping() -> None:
    """Special characters in strings are properly escaped."""
    result = literalize_json(
        json_string=json.dumps(obj=['say "hi"', "a\\b", "line1\nline2"]),
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    lines = result.split(sep="\n")
    assert lines[0] == '"say \\"hi\\"",'
    assert lines[1] == '"a\\\\b",'
    assert lines[2] == '"line1\\nline2",'


def test_nested_arrays() -> None:
    """Nested arrays are rendered recursively."""
    result = literalize_json(
        json_string=json.dumps(obj=[[[1, 2], [3, 4]]]),
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert result == "((1, 2), (3, 4)),"


def test_dicts() -> None:
    """Dicts inside a list are rendered inline."""
    result = literalize_json(
        json_string=json.dumps(obj=[{"name": "alice", "age": 30}]),
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert result == '{"name": "alice", "age": 30},'


def test_nested_dict_in_sequence() -> None:
    """A dict nested inside a sequence is rendered inline."""
    result = literalize_json(
        json_string=json.dumps(obj=[["a", {"x": 1}]]),
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert result == '("a", {"x": 1}),'


def test_nested_sequence_in_dict() -> None:
    """A sequence nested inside a dict is rendered inline."""
    result = literalize_json(
        json_string=json.dumps(obj=[{"items": [1, 2]}]),
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert result == '{"items": (1, 2)},'


def test_indent_spaces() -> None:
    """Space-based prefix is prepended to each line."""
    result = literalize_json(
        json_string=json.dumps(obj=[True, False]),
        language=PYTHON,
        line_prefix="        ",
        wrap=False,
    )
    assert result == "        True,\n        False,"


def test_indent_tabs() -> None:
    """Tab-based prefix is prepended to each line."""
    result = literalize_json(
        json_string=json.dumps(obj=[True, False]),
        language=GO,
        line_prefix="\t\t",
        wrap=False,
    )
    assert result == "\t\ttrue,\n\t\tfalse,"


def test_wrap() -> None:
    """Wrapping adds brackets and indentation."""
    result = literalize_json(
        json_string=json.dumps(obj=[True, False]),
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            True,
            False,
        )"""
    )
    assert result == expected


def test_wrap_with_line_prefix() -> None:
    """Wrapping respects the given line_prefix."""
    result = literalize_json(
        json_string=json.dumps(obj=[["a", 1.0]]),
        language=PYTHON,
        line_prefix="    ",
        wrap=True,
    )
    expected = '    (\n        ("a", 1.0),\n    )'
    assert result == expected


@pytest.mark.parametrize(argnames="wrap", argvalues=[False, True])
def test_empty_data(*, wrap: bool) -> None:
    """An empty list produces an empty string regardless of wrap."""
    result = literalize_json(
        json_string=json.dumps(obj=[]),
        language=PYTHON,
        line_prefix="",
        wrap=wrap,
    )
    assert result == ""


@pytest.mark.parametrize(
    argnames=("json_string", "language", "expected"),
    argvalues=[
        ("42", PYTHON, "42"),
        ("3.14", PYTHON, "3.14"),
        ('"hello"', PYTHON, '"hello"'),
        ("true", PYTHON, "True"),
        ("false", PYTHON, "False"),
        ("null", PYTHON, "None"),
        ("true", JAVASCRIPT, "true"),
        ("null", CSHARP, "(object?)null"),
        ("null", GO, "nil"),
        ("null", RUBY, "nil"),
        ("null", CPP, "nullptr"),
    ],
)
def test_scalar(
    *, json_string: str, language: Language, expected: str
) -> None:
    """Scalar values are formatted as native literals."""
    result = literalize_json(
        json_string=json_string, language=language, line_prefix="", wrap=False
    )
    assert result == expected


def test_scalar_with_indent() -> None:
    """Scalar values respect the prefix parameter."""
    result = literalize_json(
        json_string="42", language=PYTHON, line_prefix="    ", wrap=False
    )
    assert result == "    42"


def test_scalar_wrap_ignored() -> None:
    """Wrap is ignored for scalar values."""
    result = literalize_json(
        json_string="42", language=PYTHON, line_prefix="", wrap=True
    )
    assert result == "42"


def test_custom_language() -> None:
    """A custom Language works as a language."""
    custom = LanguageSpec(
        null_literal="NIL",
        true_literal="YES",
        false_literal="NO",
        sequence_open=fixed_sequence_open(open_str="<"),
        sequence_close=">",
        dict_open="{",
        dict_close="}",
        format_dict_entry=dict_entry_with_separator(separator=" -> "),
        multiline_trailing_comma=True,
        single_element_trailing_comma=False,
        format_string=format_string_backslash,
        format_bytes=format_bytes_hex,
        format_date=format_date_iso,
        format_datetime=format_datetime_iso,
        empty_sequence=None,
        empty_dict=None,
        set_open="<",
        set_close=">",
        empty_set=None,
        format_sequence_entry=passthrough_sequence_entry,
        format_set_entry=passthrough_set_entry,
        comment_prefix="//",
        comment_suffix="",
        omap_open="{",
        omap_close="}",
        format_omap_entry=_format_test_omap_entry,
        multiline_close_indent="",
        element_separator=", ",
        skip_null_dict_values=False,
        format_variable_declaration=PYTHON.format_variable_declaration,
        format_variable_assignment=PYTHON.format_variable_assignment,
    )
    result = literalize_json(
        json_string=json.dumps(obj=[True, None, "hi"]),
        language=custom,
        line_prefix="",
        wrap=False,
    )
    assert result == 'YES,\nNIL,\n"hi",'


def test_part1_sample_python() -> None:
    """Realistic test matching part1_sample_input.json structure."""
    data = [
        ["user_1", 1000.0],
        ["user_1", 1001.0],
        ["user_1", 1002.0],
        ["user_1", 1003.0],
    ]
    result = literalize_json(
        json_string=json.dumps(obj=data),
        language=PYTHON,
        line_prefix="        ",
        wrap=False,
    )
    expected_lines = [
        '        ("user_1", 1000.0),',
        '        ("user_1", 1001.0),',
        '        ("user_1", 1002.0),',
        '        ("user_1", 1003.0),',
    ]
    assert result.split(sep="\n") == expected_lines


def test_part2_sample_go() -> None:
    """Realistic test matching part2_sample_input.json structure."""
    data = [["user_1", 49, 1000.0], ["user_9", 10, 1003.0]]
    result = literalize_json(
        json_string=json.dumps(obj=data),
        language=GO,
        line_prefix="        ",
        wrap=False,
    )
    lines = result.split(sep="\n")
    assert lines[0] == '        []any{"user_1", 49, 1000.0},'
    assert lines[1] == '        []any{"user_9", 10, 1003.0},'


type _JSONScalar = str | int | float | bool | None

type _JSONValue = _JSONScalar | list[_JSONValue] | dict[str, _JSONValue]


@beartype
def _lists_to_tuples(*, value: _JSONValue) -> object:
    """Recursively convert lists to tuples to match Python converter
    output.
    """
    if isinstance(value, list):
        return tuple(_lists_to_tuples(value=v) for v in value)
    if isinstance(value, dict):
        return {k: _lists_to_tuples(value=v) for k, v in value.items()}
    return value


# Characters valid in JSON strings: Unicode letters (L), marks (M), numbers
# (N), punctuation (P), symbols (S), and separators (Z). Control characters
# (category C) are excluded because JSON forbids raw control characters,
# and ``\x00`` is excluded explicitly because json.dumps refuses null bytes.
json_text = st.text(
    alphabet=st.characters(
        categories=("L", "M", "N", "P", "S", "Z"), exclude_characters="\x00"
    )
)
json_scalars = (
    st.none()
    | st.booleans()
    | st.integers()
    # ``nan`` and ``inf`` are excluded because JSON does not support them.
    | st.floats(allow_nan=False, allow_infinity=False)
    | json_text
)
json_values: st.SearchStrategy[_JSONValue] = st.recursive(
    base=json_scalars,
    extend=lambda children: (
        # ``max_size`` prevents unbounded nesting that causes test timeouts.
        st.lists(elements=children, max_size=5)
        | st.dictionaries(keys=json_text, values=children, max_size=5)
    ),
)
# ``max_size`` prevents very large inputs that would slow down tests.
json_arrays = st.lists(elements=json_values, max_size=10)
json_objects = st.dictionaries(keys=json_text, values=json_values, max_size=10)


def test_literalize_json_array() -> None:
    """``literalize_json`` parses a JSON array string."""
    json_string = '[["user_1", 1000.0], ["user_2", 2000.0]]'
    result = literalize_json(
        json_string=json_string,
        language=PYTHON,
        line_prefix="    ",
        wrap=False,
    )
    expected = '    ("user_1", 1000.0),\n    ("user_2", 2000.0),'
    assert result == expected


def test_literalize_json_object() -> None:
    """``literalize_json`` parses a JSON object string."""
    json_string = '{"a": 1, "b": true}'
    result = literalize_json(
        json_string=json_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = '{\n    "a": 1,\n    "b": True,\n}'
    assert result == expected


def test_literalize_json_invalid() -> None:
    """``literalize_json`` raises on invalid JSON."""
    with pytest.raises(expected_exception=JSONParseError):
        literalize_json(
            json_string="not json",
            language=PYTHON,
            line_prefix="",
            wrap=False,
        )


def test_literalize_json_invalid_is_parse_error() -> None:
    """``JSONParseError`` is a subclass of ``ParseError``."""
    with pytest.raises(expected_exception=ParseError):
        literalize_json(
            json_string="not json",
            language=PYTHON,
            line_prefix="",
            wrap=False,
        )


@given(data=json_arrays)
def test_roundtrip_array(data: list[_JSONValue]) -> None:
    """JSON array -> Python literal -> ast.literal_eval round-trips."""
    result = literalize_json(
        json_string=json.dumps(obj=data),
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    if not data:
        assert result == ""
        return
    parsed = ast.literal_eval(node_or_string=result)
    assert parsed == tuple(_lists_to_tuples(value=v) for v in data)


@given(data=json_scalars)
def test_roundtrip_scalar(data: _JSONScalar) -> None:
    """Scalar -> Python literal -> ast.literal_eval round-trips."""
    result = literalize_json(
        json_string=json.dumps(obj=data),
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    parsed = ast.literal_eval(node_or_string=result)
    assert parsed == data


# ``st.dictionaries`` internally filters draws to ensure unique keys, which
# can accumulate enough filtered examples to trigger the ``filter_too_much``
# health check on unlucky seeds.  The filtering is expected behavior here,
# so we suppress the check rather than change the strategy.
@given(data=json_objects)
@settings(suppress_health_check=[HealthCheck.filter_too_much])
def test_roundtrip_dict(data: dict[str, _JSONValue]) -> None:
    """JSON object -> Python literal -> ast.literal_eval round-trips."""
    result = literalize_json(
        json_string=json.dumps(obj=data),
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    if not data:
        assert result == ""
        return
    parsed = ast.literal_eval(node_or_string=result)
    assert parsed == _lists_to_tuples(value=data)


@given(data=st.binary())
def test_roundtrip_bytes_python(data: bytes) -> None:
    """Format_bytes_python -> ast.literal_eval round-trips."""
    result = format_bytes_python(value=data)
    assert ast.literal_eval(node_or_string=result) == data


@given(data=st.binary())
def test_roundtrip_bytes_hex(data: bytes) -> None:
    """Format_bytes_hex -> bytes.fromhex round-trips."""
    result = format_bytes_hex(value=data)
    assert bytes.fromhex(result.strip('"')) == data


@given(data=st.binary())
def test_roundtrip_yaml_binary_python(data: bytes) -> None:
    """YAML !!binary -> literalize_yaml (Python) -> ast.literal_eval round-
    trips.
    """
    encoded = base64.b64encode(s=data).decode()
    yaml_string = f"- !!binary |\n  {encoded}\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert ast.literal_eval(node_or_string=result.rstrip(",")) == data.hex()


def test_literalize_yaml_empty_sequence() -> None:
    """An empty YAML sequence produces an empty string."""
    result = literalize_yaml(
        yaml_string="[]\n",
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    assert result == ""


def test_literalize_yaml_sequence() -> None:
    """``literalize_yaml`` parses a YAML sequence string."""
    yaml_string = "- [user_1, 1000.0]\n- [user_2, 2000.0]\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="    ",
        wrap=False,
    )
    expected = '    ("user_1", 1000.0),\n    ("user_2", 2000.0),'
    assert result == expected


def test_literalize_yaml_mapping() -> None:
    """``literalize_yaml`` parses a YAML mapping string."""
    yaml_string = "a: 1\nb: true\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = '{\n    "a": 1,\n    "b": True,\n}'
    assert result == expected


def test_literalize_yaml_invalid() -> None:
    """``literalize_yaml`` raises on invalid YAML."""
    with pytest.raises(expected_exception=YAMLParseError):
        literalize_yaml(
            yaml_string=":\n  :\n    - ][",
            language=PYTHON,
            line_prefix="",
            wrap=False,
        )


def test_literalize_yaml_invalid_is_parse_error() -> None:
    """``YAMLParseError`` is a subclass of ``ParseError``."""
    with pytest.raises(expected_exception=ParseError):
        literalize_yaml(
            yaml_string=":\n  :\n    - ][",
            language=PYTHON,
            line_prefix="",
            wrap=False,
        )


@pytest.mark.parametrize(
    argnames=("yaml_string", "language", "expected"),
    argvalues=[
        ("42", PYTHON, "42"),
        ("3.14", PYTHON, "3.14"),
        ("hello", PYTHON, '"hello"'),
        ("true", PYTHON, "True"),
        ("false", PYTHON, "False"),
        ("null", PYTHON, "None"),
        ("true", JAVASCRIPT, "true"),
        ("null", GO, "nil"),
    ],
)
def test_literalize_yaml_scalar(
    *,
    yaml_string: str,
    language: Language,
    expected: str,
) -> None:
    """``literalize_yaml`` handles scalar YAML values."""
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=language,
        line_prefix="",
        wrap=False,
    )
    assert result == expected


def test_literalize_yaml_date() -> None:
    """``literalize_yaml`` formats date values as ISO string literals."""
    yaml_string = "- 2024-01-15\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert result == '"2024-01-15",'


def test_literalize_yaml_datetime() -> None:
    """``literalize_yaml`` formats datetime values as ISO string
    literals.
    """
    yaml_string = "- 2024-01-15T12:30:00\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert result == '"2024-01-15T12:30:00",'


_SAMPLE_DATE = datetime.date(year=2024, month=1, day=15)
_SAMPLE_DATETIME = datetime.datetime.fromisoformat("2024-01-15T12:30:00")
_SAMPLE_DATETIME_MICRO = datetime.datetime.fromisoformat(
    "2024-01-15T12:30:00.123456"
)


@pytest.mark.parametrize(
    argnames=("func", "value", "expected"),
    argvalues=[
        pytest.param(
            format_date_iso,
            _SAMPLE_DATE,
            '"2024-01-15"',
            id="format_date_iso",
        ),
        pytest.param(
            format_datetime_iso,
            _SAMPLE_DATETIME,
            '"2024-01-15T12:30:00"',
            id="format_datetime_iso",
        ),
        pytest.param(
            format_date_python,
            _SAMPLE_DATE,
            "datetime.date(2024, 1, 15)",
            id="format_date_python",
        ),
        pytest.param(
            format_datetime_python,
            _SAMPLE_DATETIME,
            "datetime.datetime(2024, 1, 15, 12, 30, 0)",
            id="format_datetime_python",
        ),
        pytest.param(
            format_datetime_python,
            _SAMPLE_DATETIME_MICRO,
            "datetime.datetime(2024, 1, 15, 12, 30, 0, 123456)",
            id="format_datetime_python_microsecond",
        ),
        pytest.param(
            format_date_java,
            _SAMPLE_DATE,
            "LocalDate.of(2024, 1, 15)",
            id="format_date_java",
        ),
        pytest.param(
            format_datetime_java_instant,
            _SAMPLE_DATETIME,
            'Instant.parse("2024-01-15T12:30:00")',
            id="format_datetime_java_instant",
        ),
        pytest.param(
            format_datetime_java_zoned,
            _SAMPLE_DATETIME,
            'ZonedDateTime.of(2024, 1, 15, 12, 30, 0, 0, ZoneId.of("UTC"))',
            id="format_datetime_java_zoned",
        ),
        pytest.param(
            format_date_ruby,
            _SAMPLE_DATE,
            "Date.new(2024, 1, 15)",
            id="format_date_ruby",
        ),
        pytest.param(
            format_datetime_ruby,
            _SAMPLE_DATETIME,
            "Time.new(2024, 1, 15, 12, 30, 0)",
            id="format_datetime_ruby",
        ),
        pytest.param(
            format_date_js,
            _SAMPLE_DATE,
            'new Date("2024-01-15")',
            id="format_date_js",
        ),
        pytest.param(
            format_datetime_js,
            _SAMPLE_DATETIME,
            'new Date("2024-01-15T12:30:00")',
            id="format_datetime_js",
        ),
        pytest.param(
            format_date_csharp,
            _SAMPLE_DATE,
            "new DateOnly(2024, 1, 15)",
            id="format_date_csharp",
        ),
        pytest.param(
            format_datetime_csharp,
            _SAMPLE_DATETIME,
            "new DateTime(2024, 1, 15, 12, 30, 0)",
            id="format_datetime_csharp",
        ),
        pytest.param(
            format_date_go,
            _SAMPLE_DATE,
            "time.Date(2024, time.January, 15, 0, 0, 0, 0, time.UTC)",
            id="format_date_go",
        ),
        pytest.param(
            format_datetime_go,
            _SAMPLE_DATETIME,
            "time.Date(2024, time.January, 15, 12, 30, 0, 0, time.UTC)",
            id="format_datetime_go",
        ),
        pytest.param(
            format_date_kotlin,
            _SAMPLE_DATE,
            "LocalDate.of(2024, 1, 15)",
            id="format_date_kotlin",
        ),
        pytest.param(
            format_datetime_kotlin,
            _SAMPLE_DATETIME,
            "LocalDateTime.of(2024, 1, 15, 12, 30, 0)",
            id="format_datetime_kotlin",
        ),
        pytest.param(
            format_date_rust,
            _SAMPLE_DATE,
            "NaiveDate::from_ymd_opt(2024, 1, 15).unwrap()",
            id="format_date_rust",
        ),
        pytest.param(
            format_datetime_rust,
            _SAMPLE_DATETIME,
            "NaiveDateTime::new("
            "NaiveDate::from_ymd_opt(2024, 1, 15).unwrap(), "
            "NaiveTime::from_hms_opt(12, 30, 0).unwrap())",
            id="format_datetime_rust",
        ),
        pytest.param(
            format_datetime_rust,
            datetime.datetime.fromisoformat("2024-01-15T12:30:00.123456"),
            "NaiveDateTime::new("
            "NaiveDate::from_ymd_opt(2024, 1, 15).unwrap(), "
            "NaiveTime::from_hms_micro_opt(12, 30, 0, 123456).unwrap())",
            id="format_datetime_rust_microsecond",
        ),
    ],
)
def test_format_date_datetime(
    func: Callable[..., str],
    value: datetime.date | datetime.datetime,
    expected: str,
) -> None:
    """Each format function returns the expected string."""
    assert func(value=value) == expected


def test_format_datetime_epoch() -> None:
    """``format_datetime_epoch`` returns a numeric timestamp."""
    result = format_datetime_epoch(value=_SAMPLE_DATETIME)
    # The exact value depends on local timezone for naive datetimes,
    # so just check it parses as a float.
    float(result)


def test_format_date_cpp() -> None:
    """``format_date_cpp`` returns a year_month_day literal."""
    result = format_date_cpp(value=_SAMPLE_DATE)
    assert "std::chrono::year{2024}" in result
    assert "std::chrono::month{1}" in result
    assert "std::chrono::day{15}" in result


def test_format_datetime_cpp() -> None:
    """``format_datetime_cpp`` returns a sys_days expression."""
    result = format_datetime_cpp(value=_SAMPLE_DATETIME)
    assert "std::chrono::sys_days" in result
    assert "std::chrono::hours{12}" in result
    assert "std::chrono::minutes{30}" in result


def test_format_datetime_cpp_midnight() -> None:
    """``format_datetime_cpp`` at midnight omits zero time components."""
    midnight = datetime.datetime.fromisoformat("2024-01-15T00:00:00")
    result = format_datetime_cpp(value=midnight)
    assert "std::chrono::sys_days" in result
    assert "hours" not in result
    assert "minutes" not in result
    assert "seconds" not in result
    assert "microseconds" not in result


def test_format_datetime_cpp_seconds_and_microseconds() -> None:
    """``format_datetime_cpp`` includes seconds and microseconds."""
    dt = datetime.datetime.fromisoformat("2024-01-15T12:30:45.123456")
    result = format_datetime_cpp(value=dt)
    assert "std::chrono::seconds{45}" in result
    assert "std::chrono::microseconds{123456}" in result


def test_custom_format_date() -> None:
    """A custom format_date callable is used for date values."""
    spec = Python(date_format="python")
    result = literalize_yaml(
        yaml_string="- 2024-01-15\n",
        language=spec,
        line_prefix="",
        wrap=False,
    )
    assert result == "datetime.date(2024, 1, 15),"


def test_custom_format_datetime() -> None:
    """A custom format_datetime callable is used for datetime values."""
    spec = Python(datetime_format="python")
    result = literalize_yaml(
        yaml_string="- 2024-01-15T12:30:00\n",
        language=spec,
        line_prefix="",
        wrap=False,
    )
    assert result == "datetime.datetime(2024, 1, 15, 12, 30, 0),"


def test_java_native_dates() -> None:
    """Java language spec with native date formatting."""
    spec = Java(date_format="java", datetime_format="instant")
    result = literalize_yaml(
        yaml_string="- 2024-01-15\n- 2024-01-15T12:30:00\n",
        language=spec,
        line_prefix="",
        wrap=False,
    )
    lines = result.split(sep="\n")
    assert lines[0] == "LocalDate.of(2024, 1, 15),"
    assert lines[1] == 'Instant.parse("2024-01-15T12:30:00")'


def test_ruby_native_dates() -> None:
    """Ruby language spec with native date formatting."""
    spec = Ruby(date_format="ruby", datetime_format="ruby")
    result = literalize_yaml(
        yaml_string="- 2024-01-15T12:30:00\n",
        language=spec,
        line_prefix="",
        wrap=False,
    )
    assert result == "Time.new(2024, 1, 15, 12, 30, 0),"


def test_yaml_set_inline_in_sequence() -> None:
    """A !!set nested in a sequence is formatted inline using set
    delimiters.
    """
    result = literalize_yaml(
        yaml_string="- !!set\n  ? a\n  ? b\n",
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert result == '{"a", "b"},'


def test_yaml_set_inline_with_format_set_entry() -> None:
    """A !!set nested in a list uses format_set_entry when provided."""
    result = literalize_yaml(
        yaml_string="- !!set\n  ? a\n",
        language=GO,
        line_prefix="",
        wrap=False,
    )
    assert result == 'map[any]struct{}{"a": struct{}{}},'


def test_yaml_empty_set_inline() -> None:
    """An empty !!set nested in a list uses empty_set override."""
    result = literalize_yaml(
        yaml_string="- !!set {}\n",
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert result == "set(),"


def test_default_format_date_is_iso() -> None:
    """The default format_date is ISO format."""
    assert PYTHON.format_date is format_date_iso
    assert JAVA.format_date is format_date_iso


def test_default_format_datetime_is_iso() -> None:
    """The default format_datetime is ISO format."""
    assert PYTHON.format_datetime is format_datetime_iso
    assert JAVA.format_datetime is format_datetime_iso


@pytest.mark.parametrize(
    argnames=("func", "value", "expected"),
    argvalues=[
        pytest.param(
            format_bytes_hex,
            b"Hello",
            '"48656c6c6f"',
            id="format_bytes_hex",
        ),
        pytest.param(
            format_bytes_hex,
            b"",
            '""',
            id="format_bytes_hex_empty",
        ),
        pytest.param(
            format_bytes_python,
            b"Hello",
            "b'Hello'",
            id="format_bytes_python",
        ),
        pytest.param(
            format_bytes_python,
            b"\x00\x01\x02",
            "b'\\x00\\x01\\x02'",
            id="format_bytes_python_non_printable",
        ),
    ],
)
def test_format_bytes(
    func: Callable[..., str],
    value: bytes,
    expected: str,
) -> None:
    """Each bytes format function returns the expected string."""
    assert func(value=value) == expected


def test_literalize_yaml_binary() -> None:
    """``literalize_yaml`` formats !!binary values as hex strings."""
    yaml_string = "- !!binary |\n    SGVsbG8=\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    assert result == '"48656c6c6f",'


def test_custom_format_bytes() -> None:
    """A custom format_bytes callable is used for bytes values."""
    spec = Python(bytes_format="python")
    result = literalize_yaml(
        yaml_string="- !!binary |\n    SGVsbG8=\n",
        language=spec,
        line_prefix="",
        wrap=False,
    )
    assert result == "b'Hello',"


def test_yaml_comment_sequence_before() -> None:
    """Full-line comments before sequence items are preserved."""
    yaml_string = "# first\n- a\n# second\n- b\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            # first
            "a",
            # second
            "b",
        )"""
    )
    assert result == expected


def test_yaml_comment_sequence_inline() -> None:
    """Inline comments on sequence items are preserved."""
    yaml_string = "- a  # note a\n- b  # note b\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            "a",  # note a
            "b",  # note b
        )"""
    )
    assert result == expected


def test_yaml_comment_mapping() -> None:
    """Comments in mappings are preserved."""
    yaml_string = "# comment for a\na: 1  # inline a\n# comment for b\nb: 2\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        {
            # comment for a
            "a": 1,  # inline a
            # comment for b
            "b": 2,
        }"""
    )
    assert result == expected


def test_yaml_comment_javascript_prefix() -> None:
    """Comments use the target language's comment prefix."""
    yaml_string = "# comment\n- a\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=JAVASCRIPT,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        [
            // comment
            "a",
        ]"""
    )
    assert result == expected


def test_yaml_comment_trailing() -> None:
    """Trailing comments after the last element are preserved."""
    yaml_string = "- a\n# trailing\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            "a",
            # trailing
        )"""
    )
    assert result == expected


def test_yaml_comment_no_wrap() -> None:
    """Comments work with wrap=False."""
    yaml_string = "# comment\n- a\n- b\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="    ",
        wrap=False,
    )
    expected = '    # comment\n    "a",\n    "b",'
    assert result == expected


def test_yaml_comment_scalar() -> None:
    """Comments on scalar YAML values are preserved."""
    yaml_string = "# note\n42\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    expected = "# note\n42"
    assert result == expected


def test_yaml_comment_scalar_inline() -> None:
    """Inline comments on scalar YAML values are preserved."""
    yaml_string = "42  # note\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    expected = "42  # note"
    assert result == expected


def test_yaml_no_comments_unchanged() -> None:
    """YAML without comments produces the same output as before."""
    yaml_string = "- a\n- b\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            "a",
            "b",
        )"""
    )
    assert result == expected


def test_yaml_comment_multiple_before_lines() -> None:
    """Multiple comment lines before an element are all preserved."""
    yaml_string = "# line 1\n# line 2\n- a\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            # line 1
            # line 2
            "a",
        )"""
    )
    assert result == expected


@pytest.mark.parametrize(
    argnames=("language", "expected"),
    argvalues=[
        (PYTHON, "#"),
        (RUBY, "#"),
        (JAVASCRIPT, "//"),
        (GO, "//"),
    ],
)
def test_comment_prefix(language: Language, expected: str) -> None:
    """Each language has the expected comment prefix."""
    assert language.comment_prefix == expected


@pytest.mark.parametrize(
    argnames="language",
    argvalues=[
        PYTHON,
        RUBY,
        JAVASCRIPT,
        GO,
    ],
)
def test_comment_suffix(language: Language) -> None:
    """Each language has an empty comment suffix."""
    assert language.comment_suffix == ""


def test_yaml_comment_escaped_quote_in_value() -> None:
    """Escaped quotes do not end the quoted context."""
    yaml_string = 'key: "value \\" # not a comment" # real\n'
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        {
            "key": "value \\" # not a comment",  # real
        }"""
    )
    assert result == expected


def test_yaml_comment_scalar_quoted_with_hash() -> None:
    """Inline comment after a quoted scalar containing ``#``."""
    yaml_string = '"hello # world"  # note\n'
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=False,
    )
    expected = '"hello # world"  # note'
    assert result == expected


def test_yaml_comment_double_hash() -> None:
    """Double-hash comments preserve the extra ``#``."""
    yaml_string = "## section\n- a\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            # # section
            "a",
        )"""
    )
    assert result == expected


def test_yaml_comment_block_scalar_not_extracted() -> None:
    """Text inside a block scalar is not mistaken for a comment."""
    yaml_string = "description: |\n  # not a comment\nname: foo\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        {
            "description": "# not a comment\\n",
            "name": "foo",
        }"""
    )
    assert result == expected


def test_yaml_comment_scalar_with_document_markers() -> None:
    """Document markers ``---`` and ``...`` are ignored in scalars."""
    yaml_string = "---\n# note\n42\n...\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = "# note\n42"
    assert result == expected


def test_yaml_comment_empty_comment_line() -> None:
    """A bare ``#`` with no text produces a prefix-only comment."""
    yaml_string = "- a\n#\n- b\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            "a",
            #
            "b",
        )"""
    )
    assert result == expected


def test_yaml_comment_more_body_lines_than_comments() -> None:
    """Body lines beyond the number of comments are emitted plain."""
    yaml_string = "- a\n- b\n- c\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            "a",
            "b",
            "c",
        )"""
    )
    assert result == expected


def test_yaml_comment_scalar_only_comments() -> None:
    """Scalar YAML with only markers and comments, no value line."""
    yaml_string = "---\n# just a comment\n...\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = "# just a comment\nNone"
    assert result == expected


def test_omap_nested_in_sequence() -> None:
    """An omap nested inside a sequence exercises _format_value's omap
    branch.
    """
    yaml_string = textwrap.dedent(
        text="""\
        ---
        - !!omap
          - name: Alice
          - age: 30
        """,
    )
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        (
            OrderedDict([("name", "Alice"), ("age", 30)]),
        )""",
    )
    assert result == expected


def test_omap_custom_language_spec() -> None:
    """An omap with a custom Language calls format_omap_entry."""
    yaml_string = textwrap.dedent(
        text="""\
        !!omap
        - name: Alice
        - age: 30
        """,
    )
    custom = LanguageSpec(
        null_literal="null",
        true_literal="true",
        false_literal="false",
        sequence_open=fixed_sequence_open(open_str="["),
        sequence_close="]",
        dict_open="{",
        dict_close="}",
        format_dict_entry=dict_entry_with_separator(separator=": "),
        multiline_trailing_comma=True,
        single_element_trailing_comma=False,
        format_string=format_string_backslash,
        format_bytes=format_bytes_hex,
        format_date=format_date_iso,
        format_datetime=format_datetime_iso,
        empty_sequence=None,
        empty_dict=None,
        set_open="[",
        set_close="]",
        empty_set=None,
        format_sequence_entry=passthrough_sequence_entry,
        format_set_entry=passthrough_set_entry,
        comment_prefix="//",
        comment_suffix="",
        omap_open="{",
        omap_close="}",
        format_omap_entry=_format_test_omap_entry,
        multiline_close_indent="",
        element_separator=", ",
        skip_null_dict_values=False,
        format_variable_declaration=PYTHON.format_variable_declaration,
        format_variable_assignment=PYTHON.format_variable_assignment,
    )
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=custom,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        {
            "name": "Alice",
            "age": 30,
        }""",
    )
    assert result == expected


def test_yaml_comment_mapping_nested_value_none_token() -> None:
    """Mapping key with nested comment has None at token index 2."""
    yaml_string = "a:\n  # indented\n  x: 1\nb: 2\n"
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=PYTHON,
        line_prefix="",
        wrap=True,
    )
    expected = textwrap.dedent(
        text="""\
        {
            "a": {"x": 1},
            "b": 2,
        }"""
    )
    assert result == expected


@dataclasses.dataclass(frozen=True)
class _VariableSyntax:
    """Expected output for variable declaration and assignment."""

    declaration: str
    assignment: str


# Maps each language to its declaration and assignment expected output for a
# scalar integer. Both _DECLARATION_PARAMS and _ASSIGNMENT_PARAMS are derived
# from this single source of truth so that adding a language to one
# automatically adds it to the other.
_VARIABLE_SYNTAX: dict[Language, _VariableSyntax] = {
    PYTHON: _VariableSyntax(
        declaration="my_var = 42", assignment="my_var = 42"
    ),
    JAVASCRIPT: _VariableSyntax(
        declaration="const my_var = 42;", assignment="my_var = 42;"
    ),
    TYPESCRIPT: _VariableSyntax(
        declaration="const my_var = 42;", assignment="my_var = 42;"
    ),
    GO: _VariableSyntax(declaration="my_var := 42", assignment="my_var = 42"),
    RUBY: _VariableSyntax(declaration="my_var = 42", assignment="my_var = 42"),
    CSHARP: _VariableSyntax(
        declaration="var my_var = 42;", assignment="my_var = 42;"
    ),
    CPP: _VariableSyntax(
        declaration="auto my_var = 42;", assignment="my_var = 42;"
    ),
    JAVA: _VariableSyntax(
        declaration="var my_var = 42;", assignment="my_var = 42;"
    ),
    KOTLIN: _VariableSyntax(
        declaration="val my_var = 42", assignment="my_var = 42"
    ),
    SWIFT: _VariableSyntax(
        declaration="let my_var = 42", assignment="my_var = 42"
    ),
    RUST: _VariableSyntax(
        declaration="let my_var = 42;", assignment="my_var = 42;"
    ),
    PHP: _VariableSyntax(
        declaration="$my_var = 42;", assignment="$my_var = 42;"
    ),
    HASKELL: _VariableSyntax(
        declaration="my_var = 42", assignment="my_var = 42"
    ),
    DART: _VariableSyntax(
        declaration="final my_var = 42;", assignment="my_var = 42;"
    ),
    ELIXIR: _VariableSyntax(
        declaration="my_var = 42", assignment="my_var = 42"
    ),
    FSHARP: _VariableSyntax(
        declaration="let my_var: Val = FInt 42L",
        assignment="let my_var: Val = FInt 42L",
    ),
    CLOJURE: _VariableSyntax(
        declaration="(def my_var 42)", assignment="(def my_var 42)"
    ),
    SCALA: _VariableSyntax(
        declaration="val my_var = 42", assignment="my_var = 42"
    ),
    R(): _VariableSyntax(
        declaration="my_var <- 42", assignment="my_var <- 42"
    ),
}

_DECLARATION_PARAMS = [
    (lang, syntax.declaration) for lang, syntax in _VARIABLE_SYNTAX.items()
]
_ASSIGNMENT_PARAMS = [
    (lang, syntax.assignment) for lang, syntax in _VARIABLE_SYNTAX.items()
]


@pytest.mark.parametrize(
    argnames=("language", "expected"), argvalues=_DECLARATION_PARAMS
)
def test_variable_declaration_json(
    *, language: Language, expected: str
) -> None:
    """Each language produces correct variable declaration syntax."""
    result = literalize_json(
        json_string="42",
        language=language,
        line_prefix="",
        wrap=False,
        variable_name="my_var",
    )
    assert result == expected


@pytest.mark.parametrize(
    argnames=("language", "expected"), argvalues=_DECLARATION_PARAMS
)
def test_variable_declaration_yaml(
    *, language: Language, expected: str
) -> None:
    """Each language produces correct variable declaration syntax for YAML."""
    result = literalize_yaml(
        yaml_string="42\n",
        language=language,
        line_prefix="",
        wrap=False,
        variable_name="my_var",
    )
    assert result == expected


def test_variable_declaration_none_no_wrap() -> None:
    """Omitting variable_name leaves output unchanged."""
    result = literalize_json(
        json_string="[1, 2]",
        language=PYTHON,
        line_prefix="",
        wrap=True,
        variable_name=None,
    )
    assert result == "(\n    1,\n    2,\n)"


@pytest.mark.parametrize(
    argnames=("language", "expected"), argvalues=_ASSIGNMENT_PARAMS
)
def test_existing_variable_assignment_json(
    *, language: Language, expected: str
) -> None:
    """Each language produces correct existing-variable assignment
    syntax.
    """
    result = literalize_json(
        json_string="42",
        language=language,
        line_prefix="",
        wrap=False,
        variable_name="my_var",
        new_variable=False,
    )
    assert result == expected


@pytest.mark.parametrize(
    argnames=("language", "expected"), argvalues=_ASSIGNMENT_PARAMS
)
def test_existing_variable_assignment_yaml(
    *, language: Language, expected: str
) -> None:
    """Each language produces correct existing-variable assignment syntax
    for YAML.
    """
    result = literalize_yaml(
        yaml_string="42\n",
        language=language,
        line_prefix="",
        wrap=False,
        variable_name="my_var",
        new_variable=False,
    )
    assert result == expected


@pytest.mark.parametrize(
    argnames=("yaml_string", "expected"),
    argvalues=[
        ("hello\n", '"hello"'),
        ('"hello\\nworld"\n', '"hello" + char(10) + "world"'),
        ('"hello\\tworld"\n', '"hello" + char(9) + "world"'),
        ('"back\\\\slash"\n', '"back\\\\slash"'),
    ],
)
def test_matlab_string_escaping(*, yaml_string: str, expected: str) -> None:
    r"""MATLAB string values escape backslashes and use char() for control
    characters.

    Backslashes are doubled (``\\`` -> ``\\\\``) because MATLAB
    double-quoted strings interpret backslash escape sequences; control
    characters (newlines, tabs, etc.) are represented as ``char(N)``
    expressions joined with ``+``.
    """
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=Matlab(),
        line_prefix="",
        wrap=False,
    )
    assert result == expected


def test_matlab_dict_key_with_quote() -> None:
    """MATLAB struct keys containing double quotes are decoded correctly.

    The ``_decode_matlab_string_expr`` helper must handle ``""`` inside a
    double-quoted string, which represents a literal ``"`` character.
    """
    yaml_string = '{"hello \\"world\\"": 1}\n'
    result = literalize_yaml(
        yaml_string=yaml_string,
        language=Matlab(),
        line_prefix="",
        wrap=False,
    )
    assert result == "'hello \"world\"', 1"
