"""Integration tests that compare literalize_yaml output against golden
files.

Each subdirectory contains an ``input.yaml`` and one expected-output file
per supported language, using the real file extension for that language.

Golden files contain syntactically valid programs so that pre-commit hooks
can syntax-check them directly without additional wrapping.

To regenerate all golden files after changing output::

    uv run pytest tests/integration/ --regen-all
"""

import dataclasses
import enum
from collections.abc import Callable, Iterable
from pathlib import Path
from typing import Any

import pytest
from beartype import beartype
from pytest_regressions.file_regression import FileRegressionFixture

import literalizer
import literalizer.languages
from literalizer._language import DeclarationStyleConfig
from literalizer.exceptions import NullInCollectionError
from literalizer.languages import ALL_LANGUAGES


@pytest.fixture(name="cases_dir")
def fixture_cases_dir(request: pytest.FixtureRequest) -> Path:
    """Return the absolute path to the integration test cases
    directory.
    """
    return request.config.rootpath / "tests" / "integration" / "cases"


@beartype
def _wrap_identity(content: str, _variable_name: str) -> str:
    """Return content unchanged."""
    return content


def _find_redefinition_style(
    spec: literalizer.Language,
) -> enum.Enum | None:
    """Return a declaration style that supports redefinition, or None."""
    for style in spec.declaration_styles:
        style_value = style.value
        if isinstance(style_value, DeclarationStyleConfig):
            if style_value.supports_redefinition:
                return style
        else:
            # String-valued enum — dynamic languages always allow redefinition.
            return style
    return None


def _newline_combined(
    wrap: Callable[[str, str], str],
) -> Callable[[str, str, str], str]:
    """Build a combined_wrap that joins declaration and assignment with a
    newline and passes through *wrap*.
    """
    return lambda d, a, v: wrap(d + "\n" + a, v)


@beartype
def _wrap_ocaml(content: str, _variable_name: str) -> str:
    """Wrap an OCaml ``let`` declaration in a module."""
    return "module Check = struct\n\n" + content + "\n\nend"


@beartype
def _wrap_occam(content: str, _variable_name: str) -> str:
    """Wrap an occam-pi ``VAL`` declaration in a PROC."""
    indented = "  " + content.replace("\n", "\n  ")
    return (
        "\nPROC check ()\n" + indented + "\n" + "  SEQ\n" + "    SKIP\n" + ":"
    )


@beartype
def _wrap_fsharp(content: str, _variable_name: str) -> str:
    """Wrap an F# ``let`` declaration in a module."""
    return "module Check\n\n" + content


@beartype
def _wrap_go(content: str, variable_name: str) -> str:
    """Wrap a Go short variable declaration in a main function."""
    return f"\nfunc main() {{\n{content}\n_ = {variable_name}\n}}"


@beartype
def _wrap_java(content: str, _variable_name: str) -> str:
    """Wrap a Java var declaration in a static method."""
    return (
        "class Check {\n"
        "    public static void check() {\n"
        f"{content}\n"
        "    }\n"
        "}"
    )


@beartype
def _wrap_ts(content: str, _variable_name: str) -> str:
    """Wrap a TypeScript variable declaration as a module.

    Adding ``export {}`` turns the file into a module so that ``const``
    declarations are module-scoped rather than global, preventing
    duplicate-declaration errors when ``tsc`` checks all ``.ts`` files
    together.
    """
    return f"{content}\nexport {{}};"


@beartype
def _wrap_cpp(content: str, _variable_name: str) -> str:
    """Wrap a C++ variable declaration in a function body."""
    return f"void _check() {{\n{content}\n}}"


@beartype
def _wrap_scala(content: str, _variable_name: str) -> str:
    """Wrap a Scala variable declaration in an object."""
    return f"object Check {{\n{content}\n}}"


@beartype
def _wrap_elixir(content: str, variable_name: str) -> str:
    """Wrap an Elixir variable assignment in a module function."""
    return (
        f"defmodule Check do\n"
        f"  def x do\n"
        f"    {content}\n"
        f"    _ = {variable_name}\n"
        f"  end\n"
        f"end"
    )


@beartype
def _wrap_erlang(content: str, variable_name: str) -> str:
    """Wrap an Erlang variable binding in a module function.

    The variable is referenced at the end of the function body so that
    Erlang does not warn about an unused variable.
    """
    erlang_varname = variable_name[0].upper() + variable_name[1:]
    return (
        f"-module(check).\n"
        f"-export([x/0]).\n"
        f"x() ->\n"
        f"    {content},\n"
        f"    {erlang_varname}."
    )


@beartype
def _wrap_ada(content: str, _variable_name: str) -> str:
    """Wrap an Ada object declaration inside a procedure."""
    indented = "   " + content.replace("\n", "\n   ")
    return f"procedure Check is\n{indented}\nbegin\n   null;\nend Check;"


@beartype
def _wrap_ada_combined(
    declaration: str,
    assignment: str,
    _variable_name: str,
) -> str:
    """Ada: declaration in one nested procedure, assignment in another."""
    decl_indented = "      " + declaration.replace("\n", "\n      ")
    assign_indented = "      " + assignment.replace("\n", "\n      ")
    return (
        "procedure Check is\n"
        "   procedure Check_Declaration is\n"
        f"{decl_indented}\n"
        "   begin\n"
        "      null;\n"
        "   end Check_Declaration;\n"
        "   procedure Check_Assignment is\n"
        "   begin\n"
        f"{assign_indented}\n"
        "   end Check_Assignment;\n"
        "begin\n"
        "   Check_Declaration;\n"
        "   Check_Assignment;\n"
        "end Check;"
    )


@beartype
def _wrap_d(content: str, _variable_name: str) -> str:
    """Wrap a D ``auto`` declaration in a function."""
    return f"void _check() {{\n{content}\n}}"


@beartype
def _wrap_c(content: str, variable_name: str) -> str:
    """Wrap a C _CVal declaration in a function."""
    return f"void _check(void) {{\n{content}\n    (void){variable_name};\n}}"


@beartype
def _wrap_objc(content: str, variable_name: str) -> str:
    """Wrap an Objective-C variable declaration in a function."""
    return f"void _check(void) {{\n{content}\n    (void){variable_name};\n}}"


@beartype
def _in_mojo_main(content: str, _variable_name: str) -> str:
    """Indent content and wrap in a Mojo ``def main():`` function.

    Mojo does not support top-level code.  No statements, expressions,
    or variable declarations are allowed at module scope, so generated
    output must be placed inside a function body.

    Inside a function, ``var`` is optional: both ``var x = [...]`` and
    ``x = [...]`` are valid.  Declarations (``new_variable=True``)
    include ``var`` for explicit variable binding; assignments
    (``new_variable=False``) omit it.  The distinction is stylistic
    since Mojo does not require ``var`` inside functions.
    """
    indented = "\n".join(f"    {line}" for line in content.splitlines())
    return f"def main():\n{indented}"


@beartype
def _wrap_mojo(content: str, variable_name: str) -> str:
    """Wrap a Mojo variable declaration in a main function."""
    # Consume the variable so ``--Werror`` does not flag the
    # "assignment was never used" warning.
    return _in_mojo_main(
        content=content + f"\n_ = {variable_name}",
        _variable_name=variable_name,
    )


@beartype
def _wrap_mojo_combined(
    declaration: str,
    assignment: str,
    variable_name: str,
) -> str:
    """Wrap Mojo declaration and assignment in a main function.

    This cannot use ``_newline_combined(wrap=_wrap_mojo)`` because the
    Mojo ``--Werror`` flag treats an assignment that is immediately
    overwritten without being read as an error
    (``assignment to 'x' was never used``).  A bare ``_ = variable``
    must appear *between* the declaration and the reassignment, not
    only at the end.
    """
    use = f"_ = {variable_name}"
    return _in_mojo_main(
        content=declaration + f"\n{use}\n" + assignment + f"\n{use}",
        _variable_name=variable_name,
    )


@beartype
def _wrap_rust(content: str, variable_name: str) -> str:
    """Wrap a Rust let binding in a main function."""
    indented = content.replace("\n", "\n    ")
    return f"fn main() {{\n    {indented}\n    let _ = {variable_name};\n}}"


@beartype
def _wrap_haskell(content: str, variable_name: str) -> str:
    """Wrap a Haskell variable binding in a module."""
    lines = content.split(sep="\n")
    preamble_prefixes = ("import ", "instance ", "data ")
    # Find the first line that is NOT body-preamble.  A line counts as
    # preamble when it starts with a known prefix or is an indented
    # continuation of a preceding block.
    expr_start = next(
        (
            idx
            for idx, line in enumerate(iterable=lines)
            if not (
                any(line.startswith(p) for p in preamble_prefixes)
                or (line.startswith("    ") and idx > 0)
            )
        ),
        len(lines),
    )

    preamble = "\n".join(lines[:expr_start])
    expression = "\n".join(lines[expr_start:])

    header = "module Check where\n" + preamble + "\n"
    # Tuples are not Val-typed, so skip the type annotation for them.
    eq_pos = expression.find("= ")
    rhs = expression[eq_pos + 2 :].lstrip() if eq_pos >= 0 else ""
    if rhs.startswith("("):
        return header + expression
    return header + f"{variable_name} :: Val\n" + expression


@beartype
def _wrap_zig(content: str, variable_name: str) -> str:
    """Wrap a Zig declaration in a main function.

    For ``var`` declarations the wrapper mutates the variable so the Zig
    compiler does not warn about a ``var`` that is never mutated.
    """
    indented = "    " + content.replace("\n", "\n    ")
    if content.startswith("var "):
        use = f"    {variable_name} = .nil;"
    else:
        use = f"    _ = {variable_name};"
    return f"pub fn main() void {{\n{indented}\n{use}\n}}"


@beartype
def _wrap_rust_combined(
    declaration: str,
    assignment: str,
    variable_name: str,
) -> str:
    """Rust: let declaration in an inner block, then a deferred-init
    let + assignment in the outer scope.
    """
    decl_indented = "        " + declaration.replace("\n", "\n        ")
    assign_indented = "    " + assignment.replace("\n", "\n    ")
    return (
        "fn main() {\n"
        "    {\n"
        f"{decl_indented}\n"
        f"        let _ = {variable_name};\n"
        "    }\n"
        f"    let {variable_name};\n"
        f"{assign_indented}\n"
        f"    let _ = {variable_name};\n"
        "}"
    )


@beartype
def _wrap_fortran(content: str, _variable_name: str) -> str:
    """Wrap a Fortran variable declaration in a program."""
    indented = "  " + content.replace("\n", "\n  ")
    return (
        "program check\n"
        "  use fval_m\n"
        "  implicit none\n"
        f"{indented}\n"
        "end program check"
    )


@beartype
def _wrap_fortran_combined(
    declaration: str,
    assignment: str,
    variable_name: str,
) -> str:
    """Fortran: declaration in one subroutine, assignment in another."""
    decl_indented = "  " + declaration.replace("\n", "\n  ")
    assign_indented = "  " + assignment.replace("\n", "\n  ")
    return (
        "subroutine check_declaration()\n"
        "  use fval_m\n"
        "  implicit none\n"
        f"{decl_indented}\n"
        "end subroutine check_declaration\n"
        "\n"
        "subroutine check_assignment()\n"
        "  use fval_m\n"
        "  implicit none\n"
        f"  type(fval_t) :: {variable_name}\n"
        f"{assign_indented}\n"
        "end subroutine check_assignment\n"
        "\n"
        "program main\n"
        "  call check_declaration()\n"
        "  call check_assignment()\n"
        "end program main"
    )


@beartype
def _wrap_vb(content: str, _variable_name: str) -> str:
    """Wrap a VB.NET Dim declaration inside a Module."""
    indented = "    " + content.replace("\n", "\n    ")
    return f"Module Check\n{indented}\nEnd Module"


@beartype
def _wrap_vb_combined(
    declaration: str,
    assignment: str,
    variable_name: str,
) -> str:
    """VB.NET: Dim declaration in one Sub, assignment in another."""
    decl_indented = "        " + declaration.replace("\n", "\n        ")
    assign_indented = "        " + assignment.replace("\n", "\n        ")
    return (
        "Module Check\n"
        "    Sub _declaration()\n"
        f"{decl_indented}\n"
        "    End Sub\n"
        "    Sub _assignment()\n"
        f"        Dim {variable_name} As Object\n"
        f"{assign_indented}\n"
        "    End Sub\n"
        "End Module"
    )


@beartype
def _prepend_preamble(
    wrapped: str,
    preamble: tuple[str, ...],
) -> str:
    """Prepend *preamble* lines before *wrapped*."""
    if not preamble:
        return wrapped
    return "\n".join(preamble) + "\n" + wrapped


@dataclasses.dataclass
class _Variant:
    """A formatting variant for a language (date, sequence, set, etc.)."""

    name: str
    spec: literalizer.Language
    wrap: Callable[[str, str], str]
    wrap_variable_name: str | None = None


@dataclasses.dataclass
class _LanguageConfig:
    """Language configuration with class, file extension, and wrapper."""

    lang_cls: literalizer.LanguageCls
    wrap: Callable[[str, str], str]
    combined_wrap: Callable[[str, str, str], str]
    wrap_variable_name: str | None = None


_COBOL_PROGRAM_PREFIX = (
    "IDENTIFICATION DIVISION.\n"
    "PROGRAM-ID. CHECK.\n"
    "DATA DIVISION.\n"
    "WORKING-STORAGE SECTION.\n"
)

_COBOL_PROGRAM_SUFFIX = "PROCEDURE DIVISION.\n    STOP RUN."


@beartype
def _wrap_cobol(content: str, _variable_name: str) -> str:
    """Wrap a COBOL variable declaration in a complete program."""
    return _COBOL_PROGRAM_PREFIX + f"{content}\n" + _COBOL_PROGRAM_SUFFIX


@beartype
def _wrap_cobol_combined(
    declaration: str,
    assignment: str,
    _variable_name: str,
) -> str:
    """Wrap COBOL declaration (DATA DIVISION) and assignment (PROCEDURE
    DIVISION).
    """
    return (
        _COBOL_PROGRAM_PREFIX
        + f"{declaration}\n"
        + "PROCEDURE DIVISION.\n"
        + f"    {assignment}\n"
        + "    STOP RUN."
    )


_LANGUAGES: dict[str, _LanguageConfig] = {
    literalizer.languages.Ada.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Ada,
        wrap=_wrap_ada,
        combined_wrap=_wrap_ada_combined,
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Bash.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Bash,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.C.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.C,
        wrap=_wrap_c,
        combined_wrap=_newline_combined(wrap=_wrap_c),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Cobol.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Cobol,
        wrap=_wrap_cobol,
        combined_wrap=_wrap_cobol_combined,
        wrap_variable_name="my_data",
    ),
    literalizer.languages.D.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.D,
        wrap=_wrap_d,
        combined_wrap=_newline_combined(wrap=_wrap_d),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.CommonLisp.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.CommonLisp,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
    ),
    literalizer.languages.Clojure.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Clojure,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
    ),
    literalizer.languages.Python.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Python,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
    ),
    literalizer.languages.JavaScript.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.JavaScript,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.TypeScript.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.TypeScript,
        wrap=_wrap_ts,
        combined_wrap=_newline_combined(wrap=_wrap_ts),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Kotlin.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Kotlin,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Ruby.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Ruby,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
    ),
    literalizer.languages.Go.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Go,
        wrap=_wrap_go,
        combined_wrap=_newline_combined(wrap=_wrap_go),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Java.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Java,
        wrap=_wrap_java,
        combined_wrap=_newline_combined(wrap=_wrap_java),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.CSharp.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.CSharp,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Dart.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Dart,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Swift.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Swift,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Cpp.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Cpp,
        wrap=_wrap_cpp,
        combined_wrap=_newline_combined(wrap=_wrap_cpp),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Rust.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Rust,
        wrap=_wrap_rust,
        combined_wrap=_wrap_rust_combined,
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Haskell.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Haskell,
        wrap=_wrap_haskell,
        combined_wrap=lambda d, _a, v: _wrap_haskell(
            content=d,
            variable_name=v,
        ),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Hcl.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Hcl,
        wrap=_wrap_identity,
        combined_wrap=lambda d, _a, _v: d,
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Julia.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Julia,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
    ),
    literalizer.languages.Lua.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Lua,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Perl.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Perl,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Php.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Php,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Elixir.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Elixir,
        wrap=_wrap_elixir,
        combined_wrap=lambda d, _a, v: _wrap_elixir(
            content=d,
            variable_name=v,
        ),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Erlang.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Erlang,
        wrap=_wrap_erlang,
        combined_wrap=lambda d, _a, v: _wrap_erlang(
            content=d,
            variable_name=v,
        ),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.FSharp.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.FSharp,
        wrap=_wrap_fsharp,
        combined_wrap=lambda d, _a, _v: _wrap_fsharp(
            content=d,
            _variable_name=_v,
        ),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.OCaml.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.OCaml,
        wrap=_wrap_ocaml,
        combined_wrap=lambda d, _a, _v: _wrap_ocaml(
            content=d,
            _variable_name=_v,
        ),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Occam.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Occam,
        wrap=_wrap_occam,
        combined_wrap=lambda d, _a, _v: _wrap_occam(
            content=d,
            _variable_name=_v,
        ),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Groovy.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Groovy,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Scala.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Scala,
        wrap=_wrap_scala,
        combined_wrap=_newline_combined(wrap=_wrap_scala),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.R.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.R,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Racket.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Racket,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
    ),
    literalizer.languages.Crystal.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Crystal,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Matlab.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Matlab,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Mojo.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Mojo,
        wrap=_wrap_mojo,
        combined_wrap=_wrap_mojo_combined,
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Nim.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Nim,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Norg.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Norg,
        wrap=_wrap_identity,
        combined_wrap=lambda d, _a, _v: d,
        wrap_variable_name="my_data",
    ),
    literalizer.languages.VisualBasic.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.VisualBasic,
        wrap=_wrap_vb,
        combined_wrap=_wrap_vb_combined,
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Zig.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Zig,
        wrap=_wrap_zig,
        combined_wrap=_newline_combined(wrap=_wrap_zig),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.PowerShell.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.PowerShell,
        wrap=_wrap_identity,
        combined_wrap=_newline_combined(wrap=_wrap_identity),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Toml.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Toml,
        wrap=_wrap_identity,
        combined_wrap=lambda d, _a, _v: d,
        wrap_variable_name="my_data",
    ),
    literalizer.languages.ObjectiveC.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.ObjectiveC,
        wrap=_wrap_objc,
        combined_wrap=_newline_combined(wrap=_wrap_objc),
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Fortran.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Fortran,
        wrap=_wrap_fortran,
        combined_wrap=_wrap_fortran_combined,
        wrap_variable_name="my_data",
    ),
    literalizer.languages.Yaml.__name__: _LanguageConfig(
        lang_cls=literalizer.languages.Yaml,
        wrap=_wrap_identity,
        combined_wrap=lambda d, _a, _v: d,
    ),
}

_COVERED_LANGUAGES = frozenset(cfg.lang_cls for cfg in _LANGUAGES.values())
assert _COVERED_LANGUAGES == ALL_LANGUAGES, (
    f"Missing from integration tests: {ALL_LANGUAGES - _COVERED_LANGUAGES}"
)


@dataclasses.dataclass
class _VariantCase:
    """A format-variant golden-file test case."""

    variant_name: str
    variant: _Variant
    case_dir_name: str
    variable_name: str | None


@beartype
def _build_date_variants() -> Iterable[_Variant]:
    """Build date-format variants for scalar dates.

    For each language, create a variant for every non-default date format,
    using ``wrap``.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.format_date
        for fmt in list(spec.date_formats):
            if fmt is default_format:
                continue
            # Date and datetime formats can share enum member names
            # within the same language (e.g. Python has both
            # DateFormats.ISO and DatetimeFormats.ISO), so we include
            # a "_date_" infix to keep keys unique.
            variants.append(
                _Variant(
                    name=f"{lang_name}_date_{fmt.name.lower()}",
                    spec=lang_config.lang_cls(date_format=fmt),
                    wrap=lang_config.wrap,
                    wrap_variable_name=lang_config.wrap_variable_name,
                )
            )
    return variants


@beartype
def _build_datetime_variants() -> Iterable[_Variant]:
    """Build datetime-format variants for scalar datetimes.

    For each language, create a variant for every non-default datetime format,
    using ``wrap``.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.format_datetime
        for fmt in list(spec.datetime_formats):
            if fmt is default_format:
                continue
            # See _build_date_variants for why "_datetime_" is needed.
            variants.append(
                _Variant(
                    name=f"{lang_name}_datetime_{fmt.name.lower()}",
                    spec=lang_config.lang_cls(datetime_format=fmt),
                    wrap=lang_config.wrap,
                    wrap_variable_name=lang_config.wrap_variable_name,
                )
            )
    return variants


@beartype
def _build_sequence_variants() -> Iterable[_Variant]:
    """Build sequence-format variants for all languages with multiple
    formats.

    For each language that has more than one sequence format, create a variant
    for every non-default format.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format: Any = spec.sequence_format
        for fmt in list(spec.sequence_formats):
            if fmt is default_format:
                continue
            variants.append(
                _Variant(
                    name=f"{lang_name}_sequence_{fmt.name.lower()}",
                    spec=lang_config.lang_cls(sequence_format=fmt),
                    wrap=lang_config.wrap,
                    wrap_variable_name=lang_config.wrap_variable_name,
                )
            )
    return variants


@beartype
def _build_sequence_varname_variants() -> Iterable[_Variant]:
    """Build sequence-format variants with variable declarations.

    Like :func:`_build_sequence_variants` but exercises
    ``_format_variable_declaration`` for each non-default sequence format.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format: Any = spec.sequence_format
        for fmt in list(spec.sequence_formats):
            if fmt is default_format:
                continue
            variants.append(
                _Variant(
                    name=f"{lang_name}_sequence_{fmt.name.lower()}",
                    spec=lang_config.lang_cls(sequence_format=fmt),
                    wrap=lang_config.wrap,
                    wrap_variable_name=lang_config.wrap_variable_name,
                )
            )
    return variants


@beartype
def _build_set_variants() -> Iterable[_Variant]:
    """Build set-format variants for all languages with multiple formats.

    For each language that has more than one set format, create a variant
    for every non-default set format.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.set_format
        for fmt in list(spec.set_formats):
            if fmt is default_format:
                continue
            variants.append(
                _Variant(
                    name=f"{lang_name}_set_{fmt.name.lower()}",
                    spec=lang_config.lang_cls(set_format=fmt),
                    wrap=lang_config.wrap,
                    wrap_variable_name=lang_config.wrap_variable_name,
                )
            )
    return variants


@beartype
def _build_default_set_element_type_variants() -> Iterable[_Variant]:
    """Build default-set-type variants for languages that support it.

    For each language that advertises ``supports_default_set_element_type``,
    create a variant with a non-default value.
    """
    # The test value must differ from the language's own default *and* be
    # a valid type name for that language's linter / compiler.
    type_overrides: dict[str, str] = {
        "Crystal": "Int32",
        "Go": "string",
        "CSharp": "string",
        "Mojo": "Int",
        "Rust": "i32",
    }
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        if not lang_config.lang_cls.supports_default_set_element_type:
            continue
        string_type = type_overrides.get(lang_name, "String")
        variants.append(
            _Variant(
                name=f"{lang_name}_default_set_element_type_string",
                spec=lang_config.lang_cls(
                    default_set_element_type=string_type
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
        )
    return variants


@beartype
def _build_default_sequence_element_type_variants() -> Iterable[_Variant]:
    """Build default-sequence-type variants for languages that support it.

    For each language that advertises
    ``supports_default_sequence_element_type``, create a variant with a
    non-default value.
    """
    type_overrides: dict[str, str] = {
        "Go": "interface{}",
        "CSharp": "string",
        "Mojo": "Int",
    }
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        if not lang_config.lang_cls.supports_default_sequence_element_type:
            continue
        string_type = type_overrides.get(lang_name, "String")
        variants.append(
            _Variant(
                name=f"{lang_name}_default_sequence_element_type_string",
                spec=lang_config.lang_cls(
                    default_sequence_element_type=string_type,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
        )
    return variants


@beartype
def _build_default_dict_value_type_variants() -> Iterable[_Variant]:
    """Build default-dict-type variants for languages that support it.

    For each language that advertises ``supports_default_dict_value_type``,
    create a variant with a non-default value.
    """
    type_overrides: dict[str, str] = {
        "Crystal": "Int32",
        "Go": "interface{}",
        "CSharp": "object?",
        "Dart": "Object?",
        "Kotlin": "Comparable<*>?",
        "Mojo": "Int",
        "Rust": "i32",
    }
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        if not lang_config.lang_cls.supports_default_dict_value_type:
            continue
        string_type = type_overrides.get(lang_name, "String")
        variants.append(
            _Variant(
                name=f"{lang_name}_default_dict_value_type_string",
                spec=lang_config.lang_cls(default_dict_value_type=string_type),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
        )
    return variants


@beartype
def _build_default_dict_key_type_variants() -> Iterable[_Variant]:
    """Build default-dict-key-type variants for languages that support it.

    For each language that advertises ``supports_default_dict_key_type``,
    create a variant with a non-default key type.
    """
    type_overrides: dict[str, str] = {
        "Crystal": "Int32",
        "Go": "any",
        "CSharp": "object",
        "Dart": "Object",
        "Kotlin": "Any",
        "Rust": "&str",
        "Swift": "AnyHashable",
        "VisualBasic": "Object",
    }
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        if not lang_config.lang_cls.supports_default_dict_key_type:
            continue
        key_type = type_overrides.get(lang_name, "String")
        variants.append(
            _Variant(
                name=f"{lang_name}_default_dict_key_type",
                spec=lang_config.lang_cls(default_dict_key_type=key_type),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
        )
    return variants


@beartype
def _build_comment_variants() -> Iterable[_Variant]:
    """Build comment-format variants for all languages with multiple
    formats.

    For each language that has more than one comment format, create a variant
    for every non-default format.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.comment_format
        for fmt in list(spec.comment_formats):
            if fmt is default_format:
                continue
            variants.append(
                _Variant(
                    name=f"{lang_name}_comment_{fmt.name.lower()}",
                    spec=lang_config.lang_cls(comment_format=fmt),
                    wrap=lang_config.wrap,
                    wrap_variable_name=lang_config.wrap_variable_name,
                )
            )
    return variants


@beartype
def _build_type_hint_variants() -> Iterable[_Variant]:
    """Build variable-type-hint variants for all languages with multiple
    formats.

    For each language that has more than one variable type-hint format,
    create a variant for every non-default type-hint style.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.variable_type_hints
        for fmt in list(spec.variable_type_hints_formats):
            if fmt is default_format:
                continue
            variants.append(
                _Variant(
                    name=f"{lang_name}_type_hints_{fmt.name.lower()}",
                    spec=lang_config.lang_cls(variable_type_hints=fmt),
                    wrap=lang_config.wrap,
                    wrap_variable_name="my_data",
                )
            )
    return variants


@beartype
def _build_declaration_style_variants() -> Iterable[_Variant]:
    """Build declaration-style variants for all languages with multiple
    styles.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.declaration_style
        non_defaults = [
            fmt for fmt in spec.declaration_styles if fmt is not default_format
        ]
        variants.extend(
            _Variant(
                name=f"{lang_name}_declaration_style_{fmt.name.lower()}",
                spec=lang_config.lang_cls(
                    declaration_style=fmt,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
            for fmt in non_defaults
        )
    return variants


@beartype
def _build_dict_format_variants() -> Iterable[_Variant]:
    """Build dict/map-format variants for all languages with multiple
    formats.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.dict_format
        non_defaults = [
            fmt for fmt in spec.dict_formats if fmt is not default_format
        ]
        variants.extend(
            _Variant(
                name=f"{lang_name}_dict_format_{fmt.name.lower()}",
                spec=lang_config.lang_cls(
                    dict_format=fmt,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
            for fmt in non_defaults
        )
    return variants


@beartype
def _build_integer_format_variants() -> Iterable[_Variant]:
    """Build integer-format variants for all languages with multiple
    formats.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.integer_format
        non_defaults = [
            fmt for fmt in spec.integer_formats if fmt is not default_format
        ]
        variants.extend(
            _Variant(
                name=f"{lang_name}_integer_format_{fmt.name.lower()}",
                spec=lang_config.lang_cls(
                    integer_format=fmt,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
            for fmt in non_defaults
        )
    return variants


@beartype
def _build_numeric_separator_variants() -> Iterable[_Variant]:
    """Build numeric-separator variants for all languages with multiple
    options.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.numeric_separator
        non_defaults = [
            fmt for fmt in spec.numeric_separators if fmt is not default_format
        ]
        variants.extend(
            _Variant(
                name=f"{lang_name}_numeric_separator_{fmt.name.lower()}",
                spec=lang_config.lang_cls(
                    numeric_separator=fmt,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
            for fmt in non_defaults
        )
    return variants


@beartype
def _build_string_format_variants() -> Iterable[_Variant]:
    """Build string-format variants for all languages with multiple
    formats.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.string_format
        non_defaults = [
            fmt for fmt in spec.string_formats if fmt is not default_format
        ]
        variants.extend(
            _Variant(
                name=f"{lang_name}_string_format_{fmt.name.lower()}",
                spec=lang_config.lang_cls(
                    string_format=fmt,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
            for fmt in non_defaults
        )
    return variants


@beartype
def _build_bytes_format_variants() -> Iterable[_Variant]:
    """Build bytes-format variants for all languages with multiple
    formats.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.format_bytes
        non_defaults = [
            fmt for fmt in spec.bytes_formats if fmt is not default_format
        ]
        variants.extend(
            _Variant(
                name=f"{lang_name}_bytes_format_{fmt.name.lower()}",
                spec=lang_config.lang_cls(
                    bytes_format=fmt,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
            for fmt in non_defaults
        )
    return variants


@beartype
def _build_trailing_comma_variants() -> Iterable[_Variant]:
    """Build trailing-comma variants for all languages with multiple
    options.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.trailing_comma
        non_defaults = [
            fmt for fmt in spec.trailing_commas if fmt is not default_format
        ]
        variants.extend(
            _Variant(
                name=f"{lang_name}_trailing_comma_{fmt.name.lower()}",
                spec=lang_config.lang_cls(
                    trailing_comma=fmt,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
            for fmt in non_defaults
        )
    return variants


@beartype
def _build_line_ending_variants() -> Iterable[_Variant]:
    """Build line-ending variants for all languages with multiple
    options.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_format = spec.line_ending
        non_defaults = [
            fmt for fmt in spec.line_endings if fmt is not default_format
        ]
        variants.extend(
            _Variant(
                name=f"{lang_name}_line_ending_{fmt.name.lower()}",
                spec=lang_config.lang_cls(
                    line_ending=fmt,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
            for fmt in non_defaults
        )
    return variants


@beartype
def _build_line_ending_decl_variants() -> Iterable[_Variant]:
    """Build line-ending + declaration-style cross-option variants.

    For each language with multiple line endings *and* multiple
    declaration styles, create a variant for every non-default
    line ending paired with every non-default declaration style.
    """
    variants: list[_Variant] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        default_le = spec.line_ending
        default_ds = spec.declaration_style
        non_default_le = [
            le for le in spec.line_endings if le is not default_le
        ]
        non_default_ds = [
            ds for ds in spec.declaration_styles if ds is not default_ds
        ]
        variants.extend(
            _Variant(
                name=(
                    f"{lang_name}_line_ending_{le.name.lower()}"
                    f"_decl_{ds.name.lower()}"
                ),
                spec=lang_config.lang_cls(
                    line_ending=le,
                    declaration_style=ds,
                ),
                wrap=lang_config.wrap,
                wrap_variable_name=lang_config.wrap_variable_name,
            )
            for le in non_default_le
            for ds in non_default_ds
        )
    return variants


@beartype
def _discover_cases() -> list[tuple[str, str]]:
    """Return ``(case_name, language)`` tuples."""
    cases_dir = Path(__file__).parent / "cases"
    cases: list[tuple[str, str]] = []
    for case_dir in sorted(cases_dir.iterdir()):
        cases.extend((case_dir.name, lang_name) for lang_name in _LANGUAGES)
    return cases


_CASES = _discover_cases()


@pytest.mark.parametrize(
    argnames=("_case_name", "language"),
    argvalues=_CASES,
    ids=[f"{c[0]}/{c[1]}" for c in _CASES],
)
def test_golden_file(
    _case_name: str,
    language: str,
    cases_dir: Path,
    file_regression: FileRegressionFixture,
) -> None:
    """Test that literalize_yaml output matches expected golden file."""
    input_path = cases_dir / _case_name / "input.yaml"
    lang_config = _LANGUAGES[language]
    yaml_string = input_path.read_text()
    result = literalizer.literalize_yaml(
        yaml_string=yaml_string,
        language=lang_config.lang_cls(),
        pre_indent_level=0,
        include_delimiters=True,
        variable_name=lang_config.wrap_variable_name,
        new_variable=True,
        error_on_coercion=False,
    )
    variable_name = lang_config.wrap_variable_name or ""
    wrapped = lang_config.wrap(result.code, variable_name)

    wrapped = _prepend_preamble(wrapped=wrapped, preamble=result.preamble)
    file_regression.check(
        contents=wrapped + "\n",
        extension=lang_config.lang_cls.extension,
        fullpath=input_path.parent
        / (language + lang_config.lang_cls.extension),
    )


@pytest.mark.parametrize(
    argnames=("_case_name", "language"),
    argvalues=_CASES,
    ids=[f"{c[0]}/{c[1]}" for c in _CASES],
)
def test_golden_file_combined_variable_forms(
    _case_name: str,
    language: str,
    cases_dir: Path,
    file_regression: FileRegressionFixture,
) -> None:
    """Test that literalize_yaml with new_variable=True (declaration) and
    new_variable=False (assignment to existing variable) produce expected
    golden output, combined in one file to show the difference in syntax.
    """
    input_path = cases_dir / _case_name / "input.yaml"
    lang_config = _LANGUAGES[language]
    spec = lang_config.lang_cls()
    redef_style = _find_redefinition_style(spec=spec)
    if redef_style is None:
        pytest.skip(
            f"{language} has no declaration style that supports redefinition"
        )
    spec = lang_config.lang_cls(declaration_style=redef_style)
    yaml_string = input_path.read_text()
    declaration = literalizer.literalize_yaml(
        yaml_string=yaml_string,
        language=spec,
        pre_indent_level=0,
        include_delimiters=True,
        variable_name="my_data",
        new_variable=True,
        error_on_coercion=False,
    )
    assignment = literalizer.literalize_yaml(
        yaml_string=yaml_string,
        language=spec,
        pre_indent_level=0,
        include_delimiters=True,
        variable_name="my_data",
        new_variable=False,
        error_on_coercion=False,
    )
    variable_name = lang_config.wrap_variable_name or ""
    combined = lang_config.combined_wrap(
        declaration.code,
        assignment.code,
        variable_name,
    )
    combined = _prepend_preamble(
        wrapped=combined, preamble=declaration.preamble
    )
    file_regression.check(
        contents=combined + "\n",
        extension=lang_config.lang_cls.extension,
        fullpath=input_path.parent
        / (language + "_combined" + lang_config.lang_cls.extension),
    )


@beartype
def _build_variant_cases() -> list[_VariantCase]:
    """Collect all format-variant golden-file test cases."""
    cases: list[_VariantCase] = []
    variant_sources: list[tuple[Iterable[_Variant], str, str]] = [
        (_build_date_variants(), "scalar_date", ""),
        (_build_date_variants(), "date_list", ""),
        (_build_date_variants(), "date_set", ""),
        (_build_datetime_variants(), "scalar_datetime", ""),
        (_build_datetime_variants(), "scalar_datetime_naive", "_naive"),
        (_build_datetime_variants(), "scalar_datetime_non_utc", "_non_utc"),
        (_build_sequence_variants(), "simple_sequence", ""),
        (_build_sequence_variants(), "pair_sequence", "_pair"),
        (_build_sequence_variants(), "triple_sequence", "_triple"),
        (_build_sequence_varname_variants(), "simple_sequence", "_varname"),
        (_build_set_variants(), "set", ""),
        (_build_default_set_element_type_variants(), "empty_set", ""),
        (_build_default_set_element_type_variants(), "set", ""),
        (
            _build_default_sequence_element_type_variants(),
            "empty_sequence",
            "",
        ),
        (
            _build_default_sequence_element_type_variants(),
            "simple_sequence",
            "",
        ),
        (_build_default_dict_value_type_variants(), "empty_dict", ""),
        (_build_default_dict_value_type_variants(), "simple_dict", ""),
        (_build_default_dict_key_type_variants(), "empty_dict", ""),
        (_build_default_dict_key_type_variants(), "simple_dict", ""),
        (_build_comment_variants(), "comments", ""),
        (_build_type_hint_variants(), "type_hints", ""),
        (_build_type_hint_variants(), "scalar_date", ""),
        (_build_type_hint_variants(), "scalar_datetime", ""),
        (_build_type_hint_variants(), "binary", ""),
        (_build_type_hint_variants(), "mixed_type_dicts_in_sequence", ""),
        (_build_type_hint_variants(), "empty_dicts_in_sequence", ""),
        (_build_declaration_style_variants(), "simple_sequence", ""),
        (_build_declaration_style_variants(), "simple_dict", ""),
        (_build_declaration_style_variants(), "empty_list", ""),
        (_build_dict_format_variants(), "simple_dict", ""),
        (_build_dict_format_variants(), "dict_with_list_value", "_list_val"),
        (_build_integer_format_variants(), "int_list", ""),
        (_build_integer_format_variants(), "int_list_large", "_large"),
        (_build_numeric_separator_variants(), "int_list", ""),
        (_build_numeric_separator_variants(), "int_list_large", "_large"),
        (_build_string_format_variants(), "string_list", ""),
        (_build_string_format_variants(), "string_with_backslash", ""),
        (_build_bytes_format_variants(), "binary", ""),
        (_build_trailing_comma_variants(), "simple_sequence", ""),
        (_build_line_ending_variants(), "simple_sequence", ""),
        (_build_line_ending_variants(), "simple_dict", "_dict"),
        (_build_line_ending_decl_variants(), "simple_sequence", ""),
    ]
    for variants, case_dir_name, suffix in variant_sources:
        cases.extend(
            _VariantCase(
                variant_name=f"{variant.name}{suffix}",
                variant=variant,
                case_dir_name=case_dir_name,
                variable_name=variant.wrap_variable_name,
            )
            for variant in variants
        )
    return cases


_FORMAT_VARIANT_CASES = _build_variant_cases()


@pytest.mark.parametrize(
    argnames="variant_case",
    argvalues=_FORMAT_VARIANT_CASES,
    ids=[c.variant_name for c in _FORMAT_VARIANT_CASES],
)
def test_format_variant_golden_file(
    variant_case: _VariantCase,
    cases_dir: Path,
    file_regression: FileRegressionFixture,
) -> None:
    """Test format-variant options (dates, sequences, sets, type hints)
    against golden files.
    """
    case_dir = cases_dir / variant_case.case_dir_name
    variant = variant_case.variant
    yaml_string = (case_dir / "input.yaml").read_text()
    try:
        result = literalizer.literalize_yaml(
            yaml_string=yaml_string,
            language=variant.spec,
            pre_indent_level=0,
            include_delimiters=True,
            variable_name=variant_case.variable_name,
            new_variable=True,
            error_on_coercion=False,
        )
    except NullInCollectionError:
        pytest.skip("Format rejects null elements in this input")
    variable_name = variant.wrap_variable_name or ""
    wrapped = variant.wrap(result.code, variable_name)
    wrapped = _prepend_preamble(wrapped=wrapped, preamble=result.preamble)
    file_regression.check(
        contents=wrapped + "\n",
        extension=variant.spec.extension,
        fullpath=case_dir
        / (variant_case.variant_name + variant.spec.extension),
    )


@dataclasses.dataclass
class _LineEndingCombinedCase:
    """A combined-variable-forms test case with a non-default line
    ending.
    """

    name: str
    lang_config: _LanguageConfig
    line_ending: enum.Enum
    case_dir_name: str


@beartype
def _build_line_ending_combined_cases() -> list[_LineEndingCombinedCase]:
    """Collect combined (declaration + assignment) test cases for
    non-default line endings.
    """
    cases: list[_LineEndingCombinedCase] = []
    for lang_name, lang_config in _LANGUAGES.items():
        spec = lang_config.lang_cls()
        if _find_redefinition_style(spec=spec) is None:
            continue
        default_le = spec.line_ending
        for le in spec.line_endings:
            if le is default_le:
                continue
            for case_dir_name in ("simple_sequence", "simple_dict"):
                name = (
                    f"{lang_name}_line_ending"
                    f"_{le.name.lower()}_{case_dir_name}"
                )
                cases.append(
                    _LineEndingCombinedCase(
                        name=name,
                        lang_config=lang_config,
                        line_ending=le,
                        case_dir_name=case_dir_name,
                    )
                )
    return cases


_LINE_ENDING_COMBINED_CASES = _build_line_ending_combined_cases()


@pytest.mark.parametrize(
    argnames="case",
    argvalues=_LINE_ENDING_COMBINED_CASES,
    ids=[c.name for c in _LINE_ENDING_COMBINED_CASES],
)
def test_line_ending_combined_variable_forms(
    case: _LineEndingCombinedCase,
    cases_dir: Path,
    file_regression: FileRegressionFixture,
) -> None:
    """Test that combined (declaration + assignment) output with a
    non-default line ending matches the golden file.
    """
    input_path = cases_dir / case.case_dir_name / "input.yaml"
    yaml_string = input_path.read_text()
    base_spec = case.lang_config.lang_cls()
    redef_style = _find_redefinition_style(spec=base_spec)
    assert redef_style is not None
    spec = case.lang_config.lang_cls(
        line_ending=case.line_ending,
        declaration_style=redef_style,
    )
    declaration = literalizer.literalize_yaml(
        yaml_string=yaml_string,
        language=spec,
        pre_indent_level=0,
        include_delimiters=True,
        variable_name="my_data",
        new_variable=True,
        error_on_coercion=False,
    )
    assignment = literalizer.literalize_yaml(
        yaml_string=yaml_string,
        language=spec,
        pre_indent_level=0,
        include_delimiters=True,
        variable_name="my_data",
        new_variable=False,
        error_on_coercion=False,
    )
    combined = case.lang_config.combined_wrap(
        declaration.code,
        assignment.code,
        case.lang_config.wrap_variable_name or "",
    )
    combined = _prepend_preamble(
        wrapped=combined, preamble=declaration.preamble
    )
    file_regression.check(
        contents=combined + "\n",
        extension=spec.extension,
        fullpath=input_path.parent / (case.name + spec.extension),
    )


_SORTED_LANGUAGES: list[literalizer.LanguageCls] = sorted(
    ALL_LANGUAGES,
    key=lambda c: c.__name__,
)


@pytest.mark.parametrize(
    argnames="language_cls",
    argvalues=_SORTED_LANGUAGES,
    ids=[c.__name__ for c in _SORTED_LANGUAGES],
)
def test_format_enumeration_properties(
    language_cls: literalizer.LanguageCls,
) -> None:
    """Every language exposes iterable format-enumeration properties."""
    spec = language_cls()
    assert issubclass(spec.bytes_formats, enum.Enum)
    assert len(spec.bytes_formats) >= 1
    assert issubclass(spec.sequence_formats, enum.Enum)
    assert len(spec.sequence_formats) >= 1
    assert issubclass(spec.set_formats, enum.Enum)
    assert len(spec.set_formats) >= 1
    assert issubclass(spec.date_formats, enum.Enum)
    assert len(spec.date_formats) >= 1
    assert issubclass(spec.datetime_formats, enum.Enum)
    assert len(spec.datetime_formats) >= 1
    assert issubclass(spec.comment_formats, enum.Enum)
    assert len(spec.comment_formats) >= 1
    assert issubclass(spec.declaration_styles, enum.Enum)
    assert len(spec.declaration_styles) >= 1
    assert issubclass(spec.dict_formats, enum.Enum)
    assert len(spec.dict_formats) >= 1
    assert issubclass(spec.integer_formats, enum.Enum)
    assert len(spec.integer_formats) >= 1
    assert issubclass(spec.numeric_separators, enum.Enum)
    assert len(spec.numeric_separators) >= 1
    assert issubclass(spec.string_formats, enum.Enum)
    assert len(spec.string_formats) >= 1
    assert issubclass(spec.trailing_commas, enum.Enum)
    assert len(spec.trailing_commas) >= 1
    assert issubclass(spec.line_endings, enum.Enum)
    assert len(spec.line_endings) >= 1
