"""Tests for JSON Schema input file code generation."""

from __future__ import annotations

import importlib.util
import itertools
import json
import os
import sys
import tempfile
from collections import defaultdict
from collections.abc import Sequence
from pathlib import Path

import black
import pytest
from packaging import version
from pydantic import ValidationError

from datamodel_code_generator import (
    MIN_VERSION,
    DataModelType,
    Error,
    InputFileType,
    PythonVersion,
    PythonVersionMin,
    SchemaValidatorType,
    TargetPydanticVersion,
    _clear_parser_source_data_cache,
    chdir,
    generate,
    load_data_from_path,
)
from datamodel_code_generator.__main__ import Exit
from datamodel_code_generator.format import is_supported_in_black
from datamodel_code_generator.model import base as model_base
from datamodel_code_generator.model.pydantic_v2.version import PYDANTIC_V2_DATACLASS_ALIAS_NEEDS_FALLBACK
from tests.conftest import (
    HttpxGetMockFactory,
    MockHttpxResponse,
    assert_directory_content,
    assert_httpx_get_kwargs,
    assert_output,
    create_assert_file_content,
    freeze_time,
    validate_generated_code,
)
from tests.main.conftest import (
    ALIASES_DATA_PATH,
    BLACK_PY313_SKIP,
    BLACK_PY314_SKIP,
    DATA_PATH,
    DEFAULT_VALUES_DATA_PATH,
    EXPECTED_MAIN_PATH,
    JSON_SCHEMA_DATA_PATH,
    LEGACY_BLACK_SKIP,
    MSGSPEC_LEGACY_BLACK_SKIP,
    TIMESTAMP,
    assert_generated_model_json_invalid,
    assert_generated_model_json_validation,
    assert_path_cache_invalidates_after_write,
    assert_path_cache_reuses_value,
    run_generate_and_assert,
    run_generate_file_and_assert,
    run_main_and_assert,
    run_main_url_and_assert,
    run_main_with_args,
    run_main_with_system_exit,
)
from tests.main.jsonschema.conftest import EXPECTED_JSON_SCHEMA_PATH, assert_file_content

FixtureRequest = pytest.FixtureRequest


def assert_run_main_with_args_error(args: list[str], capsys: pytest.CaptureFixture[str], expected_error: str) -> None:
    """Assert that running the CLI exits with code 2 and emits the expected error."""
    run_main_with_system_exit(args, expected_code=2, capsys=capsys, expected_stderr_contains=expected_error)


def assert_schema_required_group_validator_not_generated(
    output_file: Path,
    expected_name: str | Path | None = None,  # noqa: ARG001
    encoding: str = "utf-8",
    transform: object | None = None,  # noqa: ARG001
) -> None:
    """Assert constrained oneOf/anyOf branches are not lowered to required-group validators."""
    generated = output_file.read_text(encoding=encoding)
    forbidden_fragments = (
        "__json_schema_one_of_required_groups__",
        "_validate_json_schema_required_groups",
    )
    generated_fragments = [fragment for fragment in forbidden_fragments if fragment in generated]
    if generated_fragments:  # pragma: no cover
        pytest.fail(
            "Constrained oneOf/anyOf branches generated required-group validators: " + ", ".join(generated_fragments),
            pytrace=False,
        )


def _keep_model_order_field_references_expected_file(
    target_version: PythonVersion,
    *,
    keep_model_order: bool,
    disable_future_imports: bool,
) -> str:
    if target_version.has_native_deferred_annotations:
        return "keep_model_order_field_references_native_deferred.py"
    if disable_future_imports:
        return "keep_model_order_field_references.py"
    if keep_model_order:
        return "keep_model_order_field_references_deferred.py"
    return "keep_model_order_field_references_default.py"


def _install_test_my_app(base_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    package_dir = base_dir / "my_app"
    package_dir.mkdir()
    (package_dir / "__init__.py").write_text(
        """from typing import Literal

from pydantic import BaseModel


class AliasA(BaseModel):
    type: Literal["a"] = "a"


class B(BaseModel):
    type: Literal["b"] = "b"
""",
        encoding="utf-8",
    )
    monkeypatch.syspath_prepend(str(base_dir))


def _run_jsonschema_dict(
    schema: dict[str, object],
    output_file: Path,
    *,
    expected_exit: Exit = Exit.OK,
    expected_output: str | None = None,
    extra_args: list[str] | None = None,
    force_exec_validation: bool = False,
) -> None:
    input_file = output_file.with_suffix(".json")
    input_file.write_text(json.dumps(schema), encoding="utf-8")
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=expected_exit,
        expected_output=expected_output,
        extra_args=["--disable-timestamp", *(extra_args or [])],
        ignore_whitespace=expected_output is not None,
        force_exec_validation=force_exec_validation,
    )


@pytest.mark.benchmark
def test_main_inheritance_forward_ref(output_file: Path, tmp_path: Path) -> None:
    """Test inheritance with forward references."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "inheritance_forward_ref.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        copy_files=[(DATA_PATH / "pyproject.toml", tmp_path / "pyproject.toml")],
    )


def test_main_external_ref_slash_containing_key(output_dir: Path) -> None:
    """Generate importable modular output for external refs targeting slash-containing keys."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "external_ref_slash_key" / "schema.json",
        output_path=output_dir,
        input_file_type="jsonschema",
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "external_ref_slash_key",
        extra_args=["--disable-timestamp", "--target-python-version", "3.10"],
    )


def test_main_root_ref(output_file: Path) -> None:
    """Generate root models referenced through a JSON Pointer root ref."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_ref.py",
        extra_args=["--disable-timestamp", "--skip-root-model", "--target-python-version", "3.10"],
    )


def test_main_string_min_max_items_compat(output_file: Path) -> None:
    """A string field carrying minItems/maxItems generates a usable constr().

    Compatibility case: minItems/maxItems are inapplicable to strings in JSON
    Schema, but when present they map to constr(min_length=, max_length=).
    Mapping them to min_items/max_items produced constr(min_items=...), which
    raises TypeError the moment the generated pydantic v2 model is built.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "string_min_max_items_compat.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="string_min_max_items_compat.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.10",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )


def test_main_array_uri_items_preserves_min_items(output_file: Path) -> None:
    """An array of URI-formatted strings keeps array length constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "array_uri_items_min_items.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="array_uri_items_min_items.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.10",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )


@pytest.mark.cli_doc(
    options=["--emit-model-metadata"],
    option_description=(
        "Write a separate JSON map from source schema references to the final generated models, modules, fields, "
        "and type hints."
    ),
    input_schema="jsonschema/model_metadata.json",
    cli_args=["--emit-model-metadata", "model-map.json", "--module-split-mode", "single", "--disable-timestamp"],
    golden_output="jsonschema/model_metadata_module_split/root_title.py",
    extra_outputs=[
        {
            "title": "Generated metadata (`model-map.json`)",
            "path": "main/jsonschema/model_metadata_map.txt",
            "language": "json",
        }
    ],
)
def test_main_emit_model_metadata(output_file: Path) -> None:
    """Write generated model metadata to a separate JSON file."""
    output_dir = output_file.with_suffix("")
    metadata_path = output_file.parent / "metadata" / "model-map.json"

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_metadata.json",
        output_path=output_dir,
        input_file_type="jsonschema",
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "model_metadata_module_split",
        extra_args=[
            "--emit-model-metadata",
            str(metadata_path),
            "--module-split-mode",
            "single",
            "--disable-timestamp",
        ],
    )
    assert_output(metadata_path.read_text(encoding="utf-8"), EXPECTED_JSON_SCHEMA_PATH / "model_metadata_map.txt")


def test_main_emit_model_metadata_rejects_check(output_file: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Reject metadata output in no-write check mode."""
    metadata_path = output_file.parent / "metadata" / "model-map.json"

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_metadata.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        file_should_not_exist=(metadata_path, output_file),
        capsys=capsys,
        expected_stderr_contains="Error: --check cannot be used with --emit-model-metadata",
        extra_args=["--check", "--emit-model-metadata", str(metadata_path)],
    )


def test_main_emit_model_metadata_preserves_source_ref_for_internal_modules(output_dir: Path) -> None:
    """Keep source refs stable when circular import resolution relocates models."""
    metadata_path = output_dir.parent / "metadata" / "model-map.json"

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_circular.json",
        output_path=output_dir,
        input_file_type="jsonschema",
        expected_directory=EXPECTED_MAIN_PATH / "jsonschema" / "use_generic_base_class_circular",
        extra_args=[
            "--emit-model-metadata",
            str(metadata_path),
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--module-split-mode",
            "single",
            "--use-generic-base-class",
        ],
    )
    assert_output(
        metadata_path.read_text(encoding="utf-8"),
        EXPECTED_JSON_SCHEMA_PATH / "use_generic_base_class_circular_metadata_map.txt",
    )


def test_main_emit_model_metadata_keeps_source_model_named_base_model(output_file: Path) -> None:
    """Keep a legitimate source model named BaseModel in metadata."""
    metadata_path = output_file.parent / "metadata" / "model-map.json"

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_metadata_base_model_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_file=EXPECTED_JSON_SCHEMA_PATH / "model_metadata_base_model_name.py",
        extra_args=[
            "--emit-model-metadata",
            str(metadata_path),
            "--disable-timestamp",
        ],
    )
    assert_output(
        metadata_path.read_text(encoding="utf-8"),
        EXPECTED_JSON_SCHEMA_PATH / "model_metadata_base_model_name_map.txt",
    )


@pytest.mark.benchmark
@pytest.mark.cli_doc(
    options=["--keep-model-order"],
    option_description="""Keep generated model order deterministic while respecting dependency constraints.

The `--keep-model-order` flag produces a stable, deterministic output order. The
generator starts from class-name order and only moves models when required to
satisfy dependency or runtime constraints (for example, a base class must appear
before its subclass, and cyclic groups must stay together).

This option is not equivalent to preserving the raw definition order from the
input schema, and it does not guarantee a strict unconditional alphabetical
order either. Inheritance and other runtime ordering requirements can force a
non-alphabetical arrangement. The value of the flag is that repeated runs on the
same schema produce the same ordering, which keeps diffs stable.""",
    input_schema="jsonschema/inheritance_forward_ref.json",
    cli_args=["--keep-model-order"],
    golden_output="jsonschema/inheritance_forward_ref_keep_model_order.py",
    related_options=["--collapse-root-models"],
)
def test_main_inheritance_forward_ref_keep_model_order(output_file: Path, tmp_path: Path) -> None:
    """Keep generated model order deterministic while respecting dependency constraints.

    The `--keep-model-order` flag produces a stable, deterministic output order.
    The generator starts from class-name order and only moves models when required
    to satisfy dependency or runtime constraints. It is not equivalent to
    preserving the raw schema/spec definition order, and it does not guarantee a
    strict unconditional alphabetical order either — inheritance and other
    runtime ordering requirements can force a non-alphabetical arrangement.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "inheritance_forward_ref.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--keep-model-order"],
        copy_files=[(DATA_PATH / "pyproject.toml", tmp_path / "pyproject.toml")],
    )


@pytest.mark.benchmark
def test_main_type_alias_forward_ref_keep_model_order(output_file: Path) -> None:
    """Test TypeAliasType with forward references keeping model order."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_forward_ref.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=[
            "--keep-model-order",
            "--output-model-type",
            "typing.TypedDict",
            "--use-standard-collections",
            "--use-union-operator",
            "--use-type-alias",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.benchmark
def test_main_type_alias_cycle_keep_model_order(output_file: Path) -> None:
    """Test TypeAlias cycle ordering with keep_model_order."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_cycle.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=[
            "--keep-model-order",
            "--output-model-type",
            "typing.TypedDict",
            "--use-standard-collections",
            "--use-union-operator",
            "--use-type-alias",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.cli_doc(
    options=["--disable-future-imports"],
    option_description="""Prevent automatic addition of __future__ imports in generated code.

The --disable-future-imports option stops the generator from adding
'from __future__ import annotations' to the output. This is useful when
you need compatibility with tools or environments that don't support
postponed evaluation of annotations (PEP 563).""",
    input_schema="jsonschema/keep_model_order_field_references.json",
    cli_args=["--disable-future-imports", "--target-python-version", "3.10"],
    golden_output="main/jsonschema/keep_model_order_field_references.py",
)
@pytest.mark.benchmark
def test_main_keep_model_order_field_references(output_file: Path) -> None:
    """Prevent automatic addition of __future__ imports in generated code.

    The --disable-future-imports option stops the generator from adding
    'from __future__ import annotations' to the output. This is useful when
    you need compatibility with tools or environments that don't support
    postponed evaluation of annotations (PEP 563).
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "keep_model_order_field_references.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=[
            "--keep-model-order",
            "--disable-future-imports",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.parametrize(
    ("target_python_version", "keep_model_order", "disable_future_imports"),
    [
        ("3.10", False, False),
        ("3.10", False, True),
        ("3.10", True, False),
        ("3.10", True, True),
        ("3.11", True, False),
        ("3.11", True, True),
        ("3.12", True, False),
        ("3.12", True, True),
        ("3.13", True, False),
        ("3.13", True, True),
        ("3.14", True, False),
        ("3.14", True, True),
    ],
)
def test_main_keep_model_order_matrix_keep_model_order_field_references(
    output_file: Path,
    target_python_version: str,
    keep_model_order: bool,
    disable_future_imports: bool,
) -> None:
    """E2E matrix for keep_model_order vs deferred annotations.

    When deferred annotations are enabled (default), field references should not
    force reordering (to avoid meaningless churn). When disabled, ordering must
    satisfy runtime dependency requirements.
    """
    target_version = PythonVersion(target_python_version)
    if not is_supported_in_black(target_version):
        pytest.skip(f"Installed black ({black.__version__}) doesn't support Python {target_python_version}")

    extra_args = [
        "--target-python-version",
        target_python_version,
        "--formatters",
        "isort",
    ]
    if keep_model_order:
        extra_args.append("--keep-model-order")
    if disable_future_imports:
        extra_args.append("--disable-future-imports")

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "keep_model_order_field_references.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=_keep_model_order_field_references_expected_file(
            target_version,
            keep_model_order=keep_model_order,
            disable_future_imports=disable_future_imports,
        ),
        extra_args=extra_args,
    )
    code = output_file.read_text(encoding="utf-8")

    # For targets without native deferred annotations, validate runtime safety
    # under the current interpreter by executing the generated module.
    if not target_version.has_native_deferred_annotations:
        exec(compile(code, str(output_file), "exec"), {})


@pytest.mark.cli_doc(
    options=["--target-python-version"],
    option_description="""Target Python version for generated code syntax and imports.

The `--target-python-version` flag configures the code generation behavior.""",
    input_schema="jsonschema/pydantic_v2_model_rebuild_inheritance.json",
    cli_args=["--output-model-type", "pydantic_v2.BaseModel", "--keep-model-order", "--target-python-version", "3.10"],
    golden_output="jsonschema/pydantic_v2_model_rebuild_inheritance.py",
)
@pytest.mark.benchmark
def test_main_pydantic_v2_model_rebuild_inheritance(output_file: Path) -> None:
    """Target Python version for generated code syntax and imports.

    The `--target-python-version` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pydantic_v2_model_rebuild_inheritance.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--keep-model-order",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.benchmark
def test_main_autodetect(output_file: Path) -> None:
    """Test automatic input file type detection."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type="auto",
        assert_func=assert_file_content,
    )


def test_main_autodetect_failed(tmp_path: Path) -> None:
    """Test autodetect failure with invalid input."""
    input_file: Path = tmp_path / "input.yaml"
    output_file: Path = tmp_path / "output.py"
    input_file.write_text(":", encoding="utf-8")
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="auto",
        expected_exit=Exit.ERROR,
    )


def test_main_jsonschema(output_file: Path) -> None:
    """Test JSON Schema file code generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="general.py",
    )


@pytest.mark.cli_doc(
    options=["--keyword-only"],
    option_description="""Generate dataclass fields as keyword-only arguments.

The `--keyword-only` flag generates all dataclass fields as keyword-only,
requiring explicit parameter names when instantiating models. Combined with
`--frozen-dataclasses`, creates immutable models with keyword-only constructors.""",
    input_schema="jsonschema/person.json",
    cli_args=["--output-model-type", "dataclasses.dataclass", "--frozen-dataclasses", "--keyword-only"],
    golden_output="main/jsonschema/general_dataclass_frozen_kw_only.py",
    related_options=["--frozen-dataclasses", "--output-model-type"],
)
def test_main_jsonschema_dataclass_frozen_keyword_only(output_file: Path) -> None:
    """Generate dataclass fields as keyword-only arguments.

    The `--keyword-only` flag generates all dataclass fields as keyword-only,
    requiring explicit parameter names when instantiating models. Combined with
    `--frozen-dataclasses`, creates immutable models with keyword-only constructors.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="general_dataclass_frozen_kw_only.py",
        extra_args=[
            "--output-model-type",
            "dataclasses.dataclass",
            "--frozen-dataclasses",
            "--keyword-only",
            "--target-python-version",
            "3.10",
        ],
    )


def test_main_jsonschema_dataclass_use_annotated(output_file: Path) -> None:
    """Generate dataclasses with use-annotated enabled.

    Dataclass fields have no Annotated representation, so the output matches
    the plain dataclass form and no Annotated import is emitted.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="general_dataclass_use_annotated.py",
        extra_args=[
            "--output-model-type",
            "dataclasses.dataclass",
            "--use-annotated",
            "--target-python-version",
            "3.10",
        ],
    )


def test_main_jsonschema_constrained_types_keyword_order(output_file: Path) -> None:
    """Constrained type arguments keep the JsonSchemaObject field-definition order.

    Guards the iteration-order contract of transform_kwargs: conint/confloat/constr
    keyword order follows the schema field-definition (model_dump) order regardless
    of which side of the kwargs/filter pair is iterated.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "constrained_types_keyword_order.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="constrained_types_keyword_order.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.benchmark
def test_main_jsonschema_nested_deep(tmp_path: Path) -> None:
    """Test deeply nested JSON Schema generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nested_person.json",
        output_path=tmp_path,
        output_to_expected=[
            ("__init__.py", EXPECTED_JSON_SCHEMA_PATH / "nested_deep" / "__init__.py"),
            ("nested/deep.py", EXPECTED_JSON_SCHEMA_PATH / "nested_deep" / "nested" / "deep.py"),
            (
                "empty_parent/nested/deep.py",
                EXPECTED_JSON_SCHEMA_PATH / "nested_deep" / "empty_parent" / "nested" / "deep.py",
            ),
        ],
        assert_func=assert_file_content,
        input_file_type="jsonschema",
    )


def test_main_jsonschema_nested_skip(output_dir: Path) -> None:
    """Test nested JSON Schema with skipped items."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nested_skip.json",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "nested_skip",
        input_file_type="jsonschema",
    )


@pytest.mark.benchmark
def test_main_jsonschema_external_files(output_file: Path) -> None:
    """Test JSON Schema with external file references."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "external_parent_root.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="external_files.py",
    )


@pytest.mark.benchmark
def test_main_jsonschema_collapsed_external_references(tmp_path: Path) -> None:
    """Test collapsed external references in JSON Schema."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "external_reference",
        output_path=tmp_path,
        output_to_expected=[
            ("ref0.py", "external_ref0.py"),
            ("other/ref2.py", EXPECTED_JSON_SCHEMA_PATH / "external_other_ref2.py"),
        ],
        assert_func=assert_file_content,
        input_file_type="jsonschema",
        extra_args=["--collapse-root-models"],
    )


@pytest.mark.benchmark
def test_main_jsonschema_multiple_files(output_dir: Path) -> None:
    """Test JSON Schema generation from multiple files."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "multiple_files",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "multiple_files",
        input_file_type="jsonschema",
    )


@pytest.mark.benchmark
def test_main_jsonschema_no_empty_collapsed_external_model(tmp_path: Path) -> None:
    """Test no empty files with collapsed external models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "external_collapse",
        output_path=tmp_path,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "no_empty_collapsed_external_model",
        input_file_type="jsonschema",
        extra_args=["--collapse-root-models"],
    )


@pytest.mark.cli_doc(
    options=["--output-model-type"],
    option_description="""Select the output model type (Pydantic v2, Pydantic v2 dataclass,
dataclasses, TypedDict, msgspec).

The `--output-model-type` flag specifies which Python data model framework to use
for the generated code. Supported values include `pydantic_v2.BaseModel`,
`pydantic_v2.dataclass`, `dataclasses.dataclass`, `typing.TypedDict`, and `msgspec.Struct`.""",
    input_schema="jsonschema/null_and_array.json",
    cli_args=["--output-model-type", "pydantic_v2.BaseModel"],
    model_outputs={
        "pydantic_v2": "main/jsonschema/null_and_array_v2.py",
    },
    primary=True,
)
def test_main_null_and_array(output_file: Path) -> None:
    """Select the output model type.

    The `--output-model-type` flag specifies which Python data model framework to use
    for the generated code. Supported values include `pydantic_v2.BaseModel`,
    `pydantic_v2.dataclass`, `dataclasses.dataclass`, `typing.TypedDict`, and `msgspec.Struct`.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "null_and_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="null_and_array_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


@pytest.mark.cli_doc(
    options=["--use-default"],
    option_description="""Use default values from schema in generated models.

The `--use-default` flag allows required fields with default values to be generated
with their defaults, making them optional to provide when instantiating the model.

When `--strict-nullable` is enabled, the field type still follows the schema's
nullability. For example, a required string field with a default is generated
as `str = 'value'`, not `str | None = 'value'`, unless the schema allows null.""",
    input_schema="jsonschema/use_default_with_const.json",
    cli_args=["--output-model-type", "pydantic_v2.BaseModel", "--use-default"],
    golden_output="jsonschema/use_default_with_const.py",
    related_options=["--strict-nullable"],
)
def test_use_default_pydantic_v2_with_json_schema_const(output_file: Path) -> None:
    """Use default values from schema in generated models.

    The `--use-default` flag allows required fields with default values to be generated
    with their defaults, making them optional to provide when instantiating the model.

    When `--strict-nullable` is enabled, the field type still follows the schema's
    nullability. For example, a required string field with a default is generated
    as `str = 'value'`, not `str | None = 'value'`, unless the schema allows null.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_default_with_const.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="use_default_with_const.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--use-default"],
    )


@pytest.mark.cli_doc(
    options=["--use-missing-sentinel"],
    option_description="""Use Pydantic's MISSING sentinel for optional fields without defaults.

The `--use-missing-sentinel` flag generates `MISSING` as the default for optional
Pydantic v2 fields that do not define a schema default. This preserves the
difference between an omitted field and a nullable field set to `None`.""",
    input_schema="jsonschema/missing_sentinel.json",
    cli_args=["--output-model-type", "pydantic_v2.BaseModel", "--use-missing-sentinel"],
    golden_output="jsonschema/missing_sentinel.py",
    related_options=["--target-pydantic-version", "--strict-nullable"],
)
def test_main_jsonschema_use_missing_sentinel(output_file: Path) -> None:
    """Use Pydantic's MISSING sentinel for optional fields without defaults.

    The `--use-missing-sentinel` flag generates `MISSING` as the default for optional
    Pydantic v2 fields that do not define a schema default. This preserves the
    difference between an omitted field and a nullable field set to `None`.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "missing_sentinel.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="missing_sentinel.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--use-missing-sentinel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="missing_sentinel",
        model_name="MissingSentinel",
        valid_json='{"required": 1, "requiredNullable": null, "nullableUnrequired": null}',
        invalid_json='{"required": 1, "requiredNullable": null, "unrequired": null}',
        expected_error_type="int_type",
        expected_attribute_path=("nullableUnrequired",),
        expected_attribute_value=None,
    )


def test_main_jsonschema_use_missing_sentinel_no_union_operator(output_file: Path) -> None:
    """Use MISSING sentinel when generated unions use typing.Union instead of the | operator."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "missing_sentinel.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="missing_sentinel_no_union_operator.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-missing-sentinel",
            "--no-use-union-operator",
        ],
        force_exec_validation=True,
    )


def test_main_jsonschema_use_missing_sentinel_explicit_pydantic_v2_12(output_file: Path) -> None:
    """Use MISSING sentinel when the required Pydantic target version is explicit."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "missing_sentinel.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="missing_sentinel.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.12",
            "--use-missing-sentinel",
        ],
        force_exec_validation=True,
    )


@pytest.mark.parametrize(
    ("extra_args", "expected_stderr_contains"),
    [
        (
            [
                "--output-model-type",
                "pydantic_v2.BaseModel",
                "--target-pydantic-version",
                "2.11",
                "--use-missing-sentinel",
            ],
            "`--use-missing-sentinel` requires `--target-pydantic-version 2.12`",
        ),
        (
            ["--output-model-type", "dataclasses.dataclass", "--use-missing-sentinel"],
            "`--use-missing-sentinel` is only supported for `--output-model-type pydantic_v2.BaseModel`",
        ),
    ],
)
def test_main_jsonschema_use_missing_sentinel_requires_pydantic_v2_12(
    extra_args: list[str],
    expected_stderr_contains: str,
    output_file: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Reject MISSING sentinel generation when the requested output cannot support it."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "missing_sentinel.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=extra_args,
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains=expected_stderr_contains,
    )


@pytest.mark.parametrize(
    ("output_model", "expected_output", "option"),
    [
        (
            "dataclasses.dataclass",
            "complicated_enum_default_member_dataclass.py",
            "--set-default-enum-member",
        ),
        (
            "dataclasses.dataclass",
            "complicated_enum_default_member_dataclass.py",
            None,
        ),
    ],
)
def test_main_complicated_enum_default_member(
    output_model: str, expected_output: str, option: str | None, output_file: Path
) -> None:
    """Test complicated enum with default member."""
    extra_args = [a for a in [option, "--output-model-type", output_model] if a]
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "complicated_enum.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=extra_args,
    )


@pytest.mark.cli_doc(
    options=["--set-default-enum-member"],
    option_description="""Set the first enum member as the default value for enum fields.

The `--set-default-enum-member` flag configures the code generation behavior.""",
    input_schema="jsonschema/duplicate_enum.json",
    cli_args=["--reuse-model", "--set-default-enum-member"],
    golden_output="jsonschema/json_reuse_enum_default_member.py",
)
@pytest.mark.benchmark
def test_main_json_reuse_enum_default_member(output_file: Path) -> None:
    """Set the first enum member as the default value for enum fields.

    The `--set-default-enum-member` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "duplicate_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--reuse-model", "--set-default-enum-member"],
    )


def test_main_invalid_model_name_failed(capsys: pytest.CaptureFixture[str], output_file: Path) -> None:
    """Test invalid model name error handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "invalid_model_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--class-name", "with"],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="title='with' is invalid class name. You have to set `--class-name` option",
    )


def test_main_invalid_model_name_converted(capsys: pytest.CaptureFixture[str], output_file: Path) -> None:
    """Test invalid model name conversion error."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "invalid_model_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="title='1Xyz' is invalid class name. You have to set `--class-name` option",
    )


@pytest.mark.cli_doc(
    options=["--class-name"],
    option_description="""Override the auto-generated class name with a custom name.

The --class-name option allows you to specify a custom class name for the
generated model. This is useful when the schema title is invalid as a Python
class name (e.g., starts with a number) or when you want to use a different
naming convention than what's in the schema.""",
    input_schema="jsonschema/invalid_model_name.json",
    cli_args=["--class-name", "ValidModelName"],
    golden_output="main/jsonschema/invalid_model_name.py",
)
def test_main_invalid_model_name(output_file: Path) -> None:
    """Override the auto-generated class name with a custom name.

    The --class-name option allows you to specify a custom class name for the
    generated model. This is useful when the schema title is invalid as a Python
    class name (e.g., starts with a number) or when you want to use a different
    naming convention than what's in the schema.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "invalid_model_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--class-name", "ValidModelName"],
    )


@pytest.mark.cli_doc(
    options=["--class-name-prefix"],
    option_description="""Add a prefix to all generated class names.

The --class-name-prefix option allows you to add a prefix to all generated class
names, including both models and enums. This is useful for namespacing generated
code or avoiding conflicts with existing classes.""",
    input_schema="jsonschema/class_name_affix.json",
    cli_args=["--class-name-prefix", "Api"],
    golden_output="main/jsonschema/class_name_prefix/output.py",
    related_options=["--class-name-suffix", "--class-name-affix-scope"],
)
@freeze_time("2019-07-26")
def test_main_class_name_prefix(output_file: Path) -> None:
    """Add a prefix to all generated class names.

    The --class-name-prefix option allows you to add a prefix to all generated class
    names, including both models and enums. This is useful for namespacing generated
    code or avoiding conflicts with existing classes.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "class_name_affix.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="class_name_prefix/output.py",
        extra_args=["--class-name-prefix", "Api"],
    )


@pytest.mark.cli_doc(
    options=["--class-name-suffix"],
    option_description="""Add a suffix to all generated class names.

The --class-name-suffix option allows you to add a suffix to all generated class
names, including both models and enums. This is useful for distinguishing generated
classes (e.g., adding 'Schema' or 'Model' suffix).""",
    input_schema="jsonschema/class_name_affix.json",
    cli_args=["--class-name-suffix", "Schema"],
    golden_output="main/jsonschema/class_name_suffix/output.py",
    related_options=["--class-name-prefix", "--class-name-affix-scope"],
)
@freeze_time("2019-07-26")
def test_main_class_name_suffix(output_file: Path) -> None:
    """Add a suffix to all generated class names.

    The --class-name-suffix option allows you to add a suffix to all generated class
    names, including both models and enums. This is useful for distinguishing generated
    classes (e.g., adding 'Schema' or 'Model' suffix).
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "class_name_affix.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="class_name_suffix/output.py",
        extra_args=["--class-name-suffix", "Schema"],
    )


@pytest.mark.cli_doc(
    options=["--class-name-affix-scope"],
    option_description="""Control which classes receive the prefix/suffix.

The --class-name-affix-scope option controls which types of classes receive the
prefix or suffix specified by --class-name-prefix or --class-name-suffix:
- 'all': Apply to all classes (models and enums) - this is the default
- 'models': Apply only to model classes (BaseModel, dataclass, TypedDict, etc.)
- 'enums': Apply only to enum classes""",
    input_schema="jsonschema/class_name_affix.json",
    cli_args=["--class-name-suffix", "Schema", "--class-name-affix-scope", "models"],
    golden_output="main/jsonschema/class_name_affix_scope_models/output.py",
    related_options=["--class-name-prefix", "--class-name-suffix"],
)
@freeze_time("2019-07-26")
def test_main_class_name_affix_scope_models(output_file: Path) -> None:
    """Control which classes receive the prefix/suffix.

    The --class-name-affix-scope option controls which types of classes receive the
    prefix or suffix specified by --class-name-prefix or --class-name-suffix:
    - 'all': Apply to all classes (models and enums) - this is the default
    - 'models': Apply only to model classes (BaseModel, dataclass, TypedDict, etc.)
    - 'enums': Apply only to enum classes
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "class_name_affix.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="class_name_affix_scope_models/output.py",
        extra_args=["--class-name-suffix", "Schema", "--class-name-affix-scope", "models"],
    )


@freeze_time("2019-07-26")
def test_main_class_name_suffix_with_class_name(output_file: Path) -> None:
    """Test that --class-name-suffix does not apply to root when --class-name is specified.

    When --class-name is explicitly set, the suffix should not be applied to the root model
    because the user has explicitly chosen the root model name.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "class_name_affix.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="class_name_suffix_with_class_name/output.py",
        extra_args=["--class-name-suffix", "Schema", "--class-name", "MyRootModel"],
    )


def test_main_leading_underscore_class_name_default_unchanged(output_file: Path) -> None:
    """Leading underscore class names are still normalized unless explicitly allowed."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "class_name_affix.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="leading_underscore_class_name_default_unchanged.py",
        extra_args=["--class-name", "__ParsedModel", "--disable-timestamp"],
    )


@pytest.mark.cli_doc(
    options=["--allow-leading-underscore-class-name"],
    option_description="""Allow an explicitly specified root class name to start with an underscore.

By default, leading underscores in class names are normalized to avoid private
model names. Use --allow-leading-underscore-class-name together with an explicit
--class-name when you need the generated root model to preserve that name
exactly.""",
    input_schema="jsonschema/class_name_affix.json",
    cli_args=["--class-name", "__ParsedModel", "--allow-leading-underscore-class-name", "--disable-timestamp"],
    golden_output="main/jsonschema/allow_leading_underscore_class_name.py",
    related_options=["--class-name"],
)
def test_main_allow_leading_underscore_class_name_cli(output_file: Path) -> None:
    """Allow leading underscore root class names through the CLI option."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "class_name_affix.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allow_leading_underscore_class_name.py",
        extra_args=["--class-name", "__ParsedModel", "--allow-leading-underscore-class-name", "--disable-timestamp"],
    )


def test_main_class_name_prefix_invalid(output_file: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test that invalid --class-name-prefix is rejected."""
    run_main_with_args(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "class_name_affix.json"),
            "--output",
            str(output_file),
            "--class-name-prefix",
            "123Invalid",
        ],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="--class-name-prefix '123Invalid' is not a valid Python identifier",
    )


def test_main_class_name_suffix_invalid(output_file: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test that invalid --class-name-suffix is rejected."""
    run_main_with_args(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "class_name_affix.json"),
            "--output",
            str(output_file),
            "--class-name-suffix",
            "Schema!",
        ],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="--class-name-suffix 'Schema!' is not a valid Python identifier component",
    )


def test_main_jsonschema_reserved_field_names(output_file: Path) -> None:
    """Test reserved names are safely suffixed and aliased."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reserved_property.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="reserved_property.py",
    )


def test_main_jsonschema_with_local_anchor(output_file: Path) -> None:
    """Test $id anchor lookup resolves without error and reuses definitions."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "with_anchor.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="with_anchor.py",
    )


def test_main_jsonschema_anchor_ref(output_file: Path) -> None:
    """Test JSON Schema $anchor references generate referenced models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "anchor_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="anchor_ref.py",
        extra_args=["--disable-timestamp"],
    )


def test_main_jsonschema_missing_anchor_reports_error(capsys: pytest.CaptureFixture[str], output_file: Path) -> None:
    """Test missing $id anchor produces a clear error instead of KeyError trace."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "missing_anchor.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="Unresolved $id reference '#address'",
    )


def test_main_root_id_jsonschema_with_local_file(mock_httpx_get: HttpxGetMockFactory, output_file: Path) -> None:
    """Test root ID JSON Schema with local file reference."""
    httpx_get_mock = mock_httpx_get()
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_id.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_id.py",
    )
    assert_httpx_get_kwargs(httpx_get_mock, called=False)


@pytest.mark.cli_doc(
    options=["--allow-remote-refs"],
    option_description="""Enable fetching of `$ref` targets over HTTP/HTTPS.

When enabled, the generator will resolve `$ref` references that point to remote URLs,
including relative refs resolved against a schema's `$id` base URL. This is required
for schemas that reference definitions hosted on external servers.

Automatically enabled when using `--url` input.""",
    input_schema="jsonschema/root_id.json",
    cli_args=["--allow-remote-refs"],
    golden_output="main/jsonschema/root_id.py",
)
def test_main_root_id_jsonschema_with_remote_file(mock_httpx_get: HttpxGetMockFactory, tmp_path: Path) -> None:
    """Enable fetching of `$ref` targets over HTTP/HTTPS.

    When enabled, the generator will resolve `$ref` references that point to remote URLs,
    including relative refs resolved against a schema's `$id` base URL. This is required
    for schemas that reference definitions hosted on external servers.

    Automatically enabled when using `--url` input.
    """
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse("https://example.com/person.json", JSON_SCHEMA_DATA_PATH / "person.json")
    )
    input_file = tmp_path / "root_id.json"
    output_file: Path = tmp_path / "output.py"
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--allow-remote-refs"],
        assert_func=assert_file_content,
        expected_file="root_id.py",
        copy_files=[(JSON_SCHEMA_DATA_PATH / "root_id.json", input_file)],
    )
    assert_httpx_get_kwargs(httpx_get_mock, expected_url="https://example.com/person.json", call_count=1)


@pytest.mark.benchmark
def test_main_root_id_jsonschema_self_refs_with_local_file(
    mock_httpx_get: HttpxGetMockFactory, output_file: Path
) -> None:
    """Test root ID JSON Schema self-references with local file."""
    httpx_get_mock = mock_httpx_get()
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_id_self_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_id.py",
        transform=lambda s: s.replace("filename:  root_id_self_ref.json", "filename:  root_id.json"),
    )
    assert_httpx_get_kwargs(httpx_get_mock, called=False)


@pytest.mark.benchmark
def test_main_root_id_jsonschema_self_refs_with_remote_file(
    mock_httpx_get: HttpxGetMockFactory, tmp_path: Path
) -> None:
    """Test root ID JSON Schema self-references with remote file."""
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse("https://example.com/person.json", JSON_SCHEMA_DATA_PATH / "person.json")
    )
    input_file = tmp_path / "root_id_self_ref.json"
    output_file: Path = tmp_path / "output.py"
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--allow-remote-refs"],
        assert_func=assert_file_content,
        expected_file="root_id.py",
        transform=lambda s: s.replace("filename:  root_id_self_ref.json", "filename:  root_id.json"),
        copy_files=[(JSON_SCHEMA_DATA_PATH / "root_id_self_ref.json", input_file)],
    )
    assert_httpx_get_kwargs(httpx_get_mock, expected_url="https://example.com/person.json", call_count=1)


def test_main_root_id_jsonschema_with_absolute_remote_file(mock_httpx_get: HttpxGetMockFactory, tmp_path: Path) -> None:
    """Test root ID JSON Schema with absolute remote file URL."""
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse("https://example.com/person.json", JSON_SCHEMA_DATA_PATH / "person.json")
    )
    input_file = tmp_path / "root_id_absolute_url.json"
    output_file: Path = tmp_path / "output.py"
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--allow-remote-refs"],
        assert_func=assert_file_content,
        expected_file="root_id_absolute_url.py",
        copy_files=[(JSON_SCHEMA_DATA_PATH / "root_id_absolute_url.json", input_file)],
    )
    assert_httpx_get_kwargs(httpx_get_mock, expected_url="https://example.com/person.json", call_count=1)


def test_main_root_id_jsonschema_with_absolute_local_file(output_file: Path) -> None:
    """Test root ID JSON Schema with absolute local file path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_id_absolute_url.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_id_absolute_url.py",
    )


def test_main_url_with_relative_root_id_resolves_relative_refs(
    mock_httpx_get: HttpxGetMockFactory, tmp_path: Path
) -> None:
    """Test --url input keeps resolving relative refs remotely when root $id is path-only."""
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse(
            "https://example.com/schemas/v1/main.schema.json",
            JSON_SCHEMA_DATA_PATH / "url_relative_root_id" / "main.schema.json",
        ),
        MockHttpxResponse(
            "https://example.com/schemas/v1/sub.schema.json",
            JSON_SCHEMA_DATA_PATH / "url_relative_root_id" / "sub.schema.json",
        ),
    )
    output_dir = tmp_path / "output"

    run_main_with_args([
        "--url",
        "https://example.com/schemas/v1/main.schema.json",
        "--output",
        str(output_dir),
        "--input-file-type",
        "jsonschema",
        "--output-model-type",
        "pydantic_v2.BaseModel",
        "--use-annotated",
        "--disable-timestamp",
    ])

    assert_directory_content(
        output_dir,
        EXPECTED_MAIN_PATH / "jsonschema" / "url_relative_root_id_resolves_relative_refs",
    )
    assert_httpx_get_kwargs(
        httpx_get_mock,
        expected_urls=[
            "https://example.com/schemas/v1/main.schema.json",
            "https://example.com/schemas/v1/sub.schema.json",
        ],
        call_count=2,
    )


def test_main_local_directory_path_absolute_root_id_refs(output_dir: Path) -> None:
    """Test local directory input maps path-absolute $id refs to the input root."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "path_absolute_root_id_refs",
        output_path=output_dir,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--disable-timestamp"],
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "path_absolute_root_id_refs",
    )


def test_main_local_directory_path_absolute_root_id_refs_rejects_parent_traversal(
    output_dir: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test path-absolute $id refs cannot escape the input root."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "path_absolute_root_id_refs_parent_traversal" / "schema-root",
        output_path=output_dir,
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
            "--no-allow-remote-refs",
        ],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="Blocked unsafe local $ref",
    )


def test_main_jsonschema_unresolved_local_id_ref_reports_known_ids(
    output_file: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test unresolved local ID refs report a helpful CLI error."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unresolved_local_id.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--disable-timestamp"],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="Unresolved $id reference '#MissingId'",
    )


def test_main_url_jsonschema_relative_ref_without_fragment(
    mock_httpx_get: HttpxGetMockFactory, output_file: Path
) -> None:
    """Test URL input with a relative remote reference that has no fragment."""
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse(
            "https://example.com/schemas/root.json",
            json.dumps({
                "$schema": "http://json-schema.org/draft-07/schema#",
                "title": "RemoteRoot",
                "type": "object",
                "properties": {"person": {"$ref": "person.json"}},
            }),
        ),
        MockHttpxResponse(
            "https://example.com/schemas/person.json",
            json.dumps({
                "$schema": "http://json-schema.org/draft-07/schema#",
                "title": "Person",
                "type": "object",
                "properties": {"name": {"type": "string"}},
            }),
        ),
    )

    run_main_url_and_assert(
        url="https://example.com/schemas/root.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="remote_relative_ref.py",
    )
    assert_httpx_get_kwargs(
        httpx_get_mock,
        expected_urls=[
            "https://example.com/schemas/root.json",
            "https://example.com/schemas/person.json",
        ],
        call_count=2,
    )


def test_main_url_jsonschema_remote_ref_respects_explicit_disable(
    mock_httpx_get: HttpxGetMockFactory,
    output_file: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Respect explicit --no-allow-remote-refs with --url input."""
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse(
            "https://example.com/schemas/root.json",
            json.dumps({
                "$schema": "http://json-schema.org/draft-07/schema#",
                "title": "RemoteRoot",
                "type": "object",
                "properties": {"person": {"$ref": "person.json"}},
            }),
        )
    )

    run_main_with_args(
        [
            "--url",
            "https://example.com/schemas/root.json",
            "--output",
            str(output_file),
            "--input-file-type",
            "jsonschema",
            "--no-allow-remote-refs",
        ],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="Fetching remote $ref is disabled",
    )
    assert_httpx_get_kwargs(httpx_get_mock, expected_url="https://example.com/schemas/root.json")


def test_main_jsonschema_remote_relative_ref_without_fragment(
    mock_httpx_get: HttpxGetMockFactory, output_file: Path
) -> None:
    """Test remote file parsing with a relative reference that has no fragment."""
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse(
            "https://example.com/schemas/person.json",
            json.dumps({
                "$schema": "http://json-schema.org/draft-07/schema#",
                "title": "Person",
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "friend": {"$ref": "friend.json"},
                },
            }),
        ),
        MockHttpxResponse(
            "https://example.com/schemas/friend.json",
            json.dumps({
                "$schema": "http://json-schema.org/draft-07/schema#",
                "title": "Friend",
                "type": "object",
                "properties": {"nickname": {"type": "string"}},
            }),
        ),
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "remote_relative_ref_root.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="remote_relative_ref_nested.py",
        extra_args=["--allow-remote-refs"],
    )
    assert_httpx_get_kwargs(
        httpx_get_mock,
        expected_urls=[
            "https://example.com/schemas/person.json",
            "https://example.com/schemas/friend.json",
        ],
        call_count=2,
    )


def test_main_remote_ref_warns_without_explicit_allow(
    mock_httpx_get: HttpxGetMockFactory,
    output_file: Path,
) -> None:
    """Keep implicit public remote $ref fetching compatible, but warn."""
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse(
            "https://example.com/schema/../other/schema.json",
            JSON_SCHEMA_DATA_PATH / "remote_ref" / "other.schema.json",
        )
    )

    with pytest.warns(FutureWarning, match="--allow-remote-refs"):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "remote_ref" / "main.json",
            output_path=output_file,
            input_file_type="jsonschema",
        )
    assert_httpx_get_kwargs(
        httpx_get_mock,
        expected_url="https://example.com/schema/../other/schema.json",
        call_count=1,
    )


def test_main_remote_ref_blocked_when_explicitly_disabled(mock_httpx_get: HttpxGetMockFactory) -> None:
    """Test that remote $ref fetching is blocked when allow_remote_refs=False."""
    httpx_get_mock = mock_httpx_get()

    with pytest.raises(Error, match=r"--allow-remote-refs.*--allow-private-network"):
        generate(JSON_SCHEMA_DATA_PATH / "remote_ref" / "main.json", allow_remote_refs=False)
    assert_httpx_get_kwargs(httpx_get_mock, called=False)


def test_main_remote_ref_blocks_private_network_without_explicit_allow(
    tmp_path: Path,
    output_file: Path,
    mock_httpx_get: HttpxGetMockFactory,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Block private network HTTP $ref targets before fetching."""
    schema_path = tmp_path / "schema.json"
    schema_path.write_text(
        json.dumps({
            "$schema": "http://json-schema.org/draft-07/schema#",
            "title": "Root",
            "type": "object",
            "properties": {"local": {"$ref": "http://127.0.0.1/schema.json#/definitions/Local"}},
        }),
        encoding="utf-8",
    )
    httpx_get_mock = mock_httpx_get()

    run_main_and_assert(
        input_path=schema_path,
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--allow-remote-refs"],
        expected_exit=Exit.ERROR,
        output_should_not_exist=True,
        capsys=capsys,
        expected_stderr_contains="--allow-private-network",
    )
    assert_httpx_get_kwargs(httpx_get_mock, called=False)


def test_main_remote_ref_allows_private_network_with_explicit_allow(
    tmp_path: Path,
    output_file: Path,
    mock_httpx_get: HttpxGetMockFactory,
) -> None:
    """Allow trusted private network HTTP $ref targets only when explicitly requested."""
    schema_path = tmp_path / "schema.json"
    schema_path.write_text(
        json.dumps({
            "$schema": "http://json-schema.org/draft-07/schema#",
            "title": "Root",
            "type": "object",
            "properties": {"local": {"$ref": "http://127.0.0.1/schema.json#/definitions/Local"}},
        }),
        encoding="utf-8",
    )
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse(
            "http://127.0.0.1/schema.json",
            json.dumps({
                "$schema": "http://json-schema.org/draft-07/schema#",
                "definitions": {
                    "Local": {
                        "type": "object",
                        "properties": {"name": {"type": "string"}},
                    }
                },
            }),
        )
    )

    run_main_and_assert(
        input_path=schema_path,
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--allow-remote-refs", "--allow-private-network"],
    )
    assert_httpx_get_kwargs(httpx_get_mock, expected_url="http://127.0.0.1/schema.json")


def test_main_missing_local_ref_error_message(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test that missing local $ref files produce a clear error message."""
    schema = {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "Test",
        "type": "object",
        "properties": {
            "ref_field": {"$ref": "nonexistent.json#/definitions/Thing"},
        },
    }
    input_file = tmp_path / "schema.json"
    input_file.write_text(json.dumps(schema))
    output_file = tmp_path / "output.py"

    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="$ref file not found: " + str(tmp_path / "nonexistent.json"),
    )


@pytest.mark.benchmark
def test_main_jsonschema_id(output_file: Path) -> None:
    """Test JSON Schema with ID field."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "id.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="id.py",
    )


def test_main_jsonschema_id_as_stdin(monkeypatch: pytest.MonkeyPatch, output_file: Path) -> None:
    """Test JSON Schema ID handling from stdin."""
    run_main_and_assert(
        stdin_path=JSON_SCHEMA_DATA_PATH / "id.json",
        output_path=output_file,
        monkeypatch=monkeypatch,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="id_stdin.py",
    )


def test_main_jsonschema_stdin_oneof_ref(monkeypatch: pytest.MonkeyPatch, output_file: Path) -> None:
    """Test JSON Schema with oneOf $ref from stdin."""
    run_main_and_assert(
        stdin_path=JSON_SCHEMA_DATA_PATH / "stdin_oneof_ref.json",
        output_path=output_file,
        monkeypatch=monkeypatch,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="stdin_oneof_ref.py",
    )


def test_main_jsonschema_ids(output_dir: Path) -> None:
    """Test JSON Schema with multiple IDs."""
    with freeze_time(TIMESTAMP):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "ids" / "Organization.schema.json",
            output_path=output_dir,
            expected_directory=EXPECTED_JSON_SCHEMA_PATH / "ids",
            input_file_type="jsonschema",
        )


@pytest.mark.benchmark
def test_main_external_definitions(output_file: Path) -> None:
    """Test external definitions in JSON Schema."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "external_definitions_root.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_external_files_in_directory(output_file: Path) -> None:
    """Test external files in directory structure."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "external_files_in_directory" / "person.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_nested_directory(output_dir: Path) -> None:
    """Test nested directory structure generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "external_files_in_directory",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "nested_directory",
        input_file_type="jsonschema",
    )


def test_main_circular_reference(output_file: Path) -> None:
    """Test circular reference handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "circular_reference.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_invalid_enum_name(output_file: Path) -> None:
    """Test invalid enum name handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "invalid_enum_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.cli_doc(
    options=["--snake-case-field"],
    option_description="""Convert field names to snake_case format.

The `--snake-case-field` flag converts camelCase or PascalCase field names
to snake_case format in the generated Python code, following Python naming
conventions (PEP 8).""",
    input_schema="jsonschema/invalid_enum_name.json",
    cli_args=["--snake-case-field"],
    golden_output="jsonschema/invalid_enum_name_snake_case_field.py",
    related_options=["--capitalize-enum-members"],
)
def test_main_invalid_enum_name_snake_case_field(output_file: Path) -> None:
    """Convert field names to snake_case format.

    The `--snake-case-field` flag converts camelCase or PascalCase field names
    to snake_case format in the generated Python code, following Python naming
    conventions (PEP 8).
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "invalid_enum_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--snake-case-field"],
    )


@pytest.mark.cli_doc(
    options=["--alias-generator"],
    option_description="""Use a Pydantic v2 alias generator in model_config.

The `--alias-generator` option emits a per-model ConfigDict alias generator for
Pydantic v2 BaseModel output and omits matching per-field aliases.""",
    input_schema="jsonschema/alias_generator.json",
    cli_args=["--snake-case-field", "--alias-generator", "to_camel", "--output-model-type", "pydantic_v2.BaseModel"],
    golden_output="jsonschema/alias_generator_pydantic_v2.py",
    related_options=["--snake-case-field", "--output-model-type"],
)
def test_main_alias_generator_pydantic_v2(output_file: Path) -> None:
    """Use a Pydantic v2 alias generator in model_config."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "alias_generator.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="alias_generator_pydantic_v2.py",
        extra_args=[
            "--snake-case-field",
            "--alias-generator",
            "to_camel",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_alias_generator_requires_pydantic_v2(output_file: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Reject --alias-generator for non-Pydantic v2 output models."""
    run_main_with_args(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "alias_generator.json"),
            "--output",
            str(output_file),
            "--input-file-type",
            "jsonschema",
            "--output-model-type",
            "dataclasses.dataclass",
            "--alias-generator",
            "to_camel",
        ],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains=(
            "`--alias-generator` is only supported for `--output-model-type pydantic_v2.BaseModel`"
        ),
    )


def test_main_alias_generator_no_alias(output_file: Path) -> None:
    """Keep --no-alias behavior when --alias-generator is enabled."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "alias_generator.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="alias_generator_no_alias.py",
        extra_args=[
            "--snake-case-field",
            "--alias-generator",
            "to_camel",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--no-alias",
        ],
    )


@pytest.mark.cli_doc(
    options=["--reuse-model"],
    option_description="""Reuse identical model definitions instead of generating duplicates.

The `--reuse-model` flag detects identical enum or model definitions
across the schema and generates a single shared definition, reducing
code duplication in the output.""",
    input_schema="jsonschema/duplicate_enum.json",
    cli_args=["--reuse-model"],
    golden_output="jsonschema/json_reuse_enum.py",
    related_options=["--collapse-root-models"],
)
def test_main_json_reuse_enum(output_file: Path) -> None:
    """Reuse identical model definitions instead of generating duplicates.

    The `--reuse-model` flag detects identical enum or model definitions
    across the schema and generates a single shared definition, reducing
    code duplication in the output.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "duplicate_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--reuse-model"],
    )


def test_main_reuse_model_collapse_inline_definitions(output_file: Path) -> None:
    """Test --reuse-model --collapse-reuse-models deduplicates identical inline definitions."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_model_inline_definitions.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--reuse-model",
            "--collapse-reuse-models",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_reuse_model_discriminator_literal(output_file: Path) -> None:
    """Reuse inherited discriminator literals instead of injecting the reuse path segment."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_model_discriminator_literal.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--reuse-model",
            "--use-union-operator",
            "--use-standard-collections",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.10",
        ],
        force_exec_validation=True,
    )


def test_main_reuse_model_collapse_with_root(output_file: Path) -> None:
    """Test --reuse-model --collapse-reuse-models skips RootModel deduplication."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_model_collapse_with_root.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--reuse-model",
            "--collapse-reuse-models",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_reuse_model_collapse_nested(output_file: Path) -> None:
    """Test --reuse-model --collapse-reuse-models with deeply nested identical structures."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_model_collapse_nested.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--reuse-model",
            "--collapse-reuse-models",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.parametrize(
    "collapse_reuse_model_args",
    [[], ["--collapse-reuse-models"]],
)
def test_main_reuse_model_collapse_root_models_preserves_constraints(
    output_file: Path, collapse_reuse_model_args: list[str]
) -> None:
    """Test root-model collapse keeps distinct field constraints when titles collide."""
    extra_args = [
        "--reuse-model",
        "--collapse-root-models",
        "--use-title-as-name",
        "--field-constraints",
        "--snake-case-field",
        "--target-python-version",
        "3.10",
        "--output-model-type",
        "pydantic_v2.BaseModel",
        *collapse_reuse_model_args,
    ]

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_model_collapse_root_models_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="reuse_model_collapse_root_models_constraints.py",
        extra_args=extra_args,
    )


@pytest.mark.cli_doc(
    options=["--capitalize-enum-members"],
    option_description="""Capitalize enum member names to UPPER_CASE format.

The `--capitalize-enum-members` flag converts enum member names to
UPPER_CASE format (e.g., `active` becomes `ACTIVE`), following Python
naming conventions for constants.""",
    input_schema="jsonschema/many_case_enum.json",
    cli_args=["--capitalize-enum-members"],
    golden_output="jsonschema/json_capitalise_enum_members.py",
    related_options=["--snake-case-field"],
    aliases=["--capitalise-enum-members"],
)
def test_main_json_capitalise_enum_members(output_file: Path) -> None:
    """Capitalize enum member names to UPPER_CASE format.

    The `--capitalize-enum-members` flag converts enum member names to
    UPPER_CASE format (e.g., `active` becomes `ACTIVE`), following Python
    naming conventions for constants.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "many_case_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--capitalise-enum-members"],
    )


def test_main_json_capitalise_enum_members_without_enum(output_file: Path) -> None:
    """Test enum member capitalization without enum flag."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="autodetect.py",
    )


def test_main_similar_nested_array(output_file: Path) -> None:
    """Test similar nested array structures."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "similar_nested_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_require_referenced_field(tmp_path: Path) -> None:
    """Test required referenced fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "require_referenced_field/",
        output_path=tmp_path,
        output_to_expected=[
            ("referenced.py", "require_referenced_field_pydantic_v2/referenced.py"),
            ("required.py", "require_referenced_field_pydantic_v2/required.py"),
        ],
        assert_func=assert_file_content,
        input_file_type="jsonschema",
        extra_args=["--output-datetime-class", "AwareDatetime", "--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_require_referenced_field_naive_datetime(tmp_path: Path) -> None:
    """Test required referenced field with naive datetime."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "require_referenced_field/",
        output_path=tmp_path,
        output_to_expected=[
            ("referenced.py", "require_referenced_field_naivedatetime/referenced.py"),
            ("required.py", "require_referenced_field_naivedatetime/required.py"),
        ],
        assert_func=assert_file_content,
        input_file_type="jsonschema",
        extra_args=["--output-datetime-class", "NaiveDatetime", "--output-model-type", "pydantic_v2.BaseModel"],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        (
            "pydantic_v2.BaseModel",
            "require_referenced_field_pydantic_v2",
        ),
        (
            "msgspec.Struct",
            "require_referenced_field_msgspec",
        ),
    ],
)
def test_main_require_referenced_field_datetime(output_model: str, expected_output: str, tmp_path: Path) -> None:
    """Test required referenced field with datetime."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "require_referenced_field/",
        output_path=tmp_path,
        output_to_expected=[
            ("referenced.py", f"{expected_output}/referenced.py"),
            ("required.py", f"{expected_output}/required.py"),
        ],
        assert_func=assert_file_content,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", output_model],
    )


def test_main_json_pointer(output_file: Path) -> None:
    """Test JSON pointer references."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "json_pointer.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_nested_json_pointer(output_file: Path) -> None:
    """Test nested JSON pointer references."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nested_json_pointer.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_jsonschema_definitions_namespace(output_file: Path) -> None:
    """Test nested namespace-like definitions are parsed without wrapper models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "definitions_v2_namespace.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="definitions_v2_namespace.py",
        extra_args=["--disable-timestamp"],
    )


def test_main_jsonschema_nested_defs_namespace_ref(output_file: Path) -> None:
    """Test nested $defs/definitions namespace references skip wrapper models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nested_defs_namespace_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="nested_defs_namespace_ref.py",
        extra_args=["--disable-timestamp"],
    )


def test_main_jsonschema_root_ref_self_with_keywords(output_file: Path) -> None:
    """Test root $ref '#' with local schema keywords parses the local schema."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_ref_self_with_keywords.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_ref_self_with_keywords.py",
        extra_args=["--disable-timestamp"],
    )


def test_main_jsonschema_multiple_files_json_pointer(output_dir: Path) -> None:
    """Test JSON pointer with multiple files."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "multiple_files_json_pointer",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "multiple_files_json_pointer",
        input_file_type="jsonschema",
    )


def test_main_root_model_with_additional_properties(output_file: Path) -> None:
    """Test root model with additional properties."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_with_additional_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.cli_doc(
    options=["--use-generic-container-types"],
    option_description="""Use generic container types (Sequence, Mapping) for type hinting.

The `--use-generic-container-types` flag generates abstract container types
(Sequence, Mapping, FrozenSet) instead of concrete types (list, dict, set).
If `--use-standard-collections` is set, imports from `collections.abc`;
otherwise imports from `typing`.""",
    input_schema="jsonschema/root_model_with_additional_properties.json",
    cli_args=["--use-generic-container-types"],
    golden_output="jsonschema/root_model_with_additional_properties_use_generic_container_types.py",
    related_options=["--use-standard-collections"],
)
def test_main_root_model_with_additional_properties_use_generic_container_types(output_file: Path) -> None:
    """Use generic container types (Sequence, Mapping) for type hinting.

    The `--use-generic-container-types` flag generates abstract container types
    (Sequence, Mapping, FrozenSet) instead of concrete types (list, dict, set).
    If `--use-standard-collections` is set, imports from `collections.abc`;
    otherwise imports from `typing`.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_with_additional_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--use-generic-container-types"],
    )


@pytest.mark.cli_doc(
    options=["--use-standard-collections"],
    option_description="""Use built-in dict/list instead of typing.Dict/List.

The `--use-standard-collections` flag generates built-in container types
(dict, list) instead of typing module equivalents. This produces cleaner
code for Python 3.10+ where built-in types support subscripting.""",
    input_schema="jsonschema/root_model_with_additional_properties.json",
    cli_args=["--use-standard-collections"],
    golden_output="jsonschema/root_model_with_additional_properties_use_standard_collections.py",
    related_options=["--use-generic-container-types"],
)
def test_main_root_model_with_additional_properties_use_standard_collections(output_file: Path) -> None:
    """Use built-in dict/list instead of typing.Dict/List.

    The `--use-standard-collections` flag generates built-in container types
    (dict, list) instead of typing module equivalents. This produces cleaner
    code for Python 3.10+ where built-in types support subscripting.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_with_additional_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--use-standard-collections"],
    )


@pytest.mark.cli_doc(
    options=["--no-use-standard-collections"],
    option_description="""Use typing.Dict/List instead of built-in dict/list for container types.

The `--no-use-standard-collections` flag generates typing module containers
(Dict, List) instead of built-in types. This is useful for older Python
versions or when explicit typing imports are preferred.""",
    input_schema="jsonschema/root_model_with_additional_properties.json",
    cli_args=["--no-use-standard-collections"],
    golden_output="jsonschema/root_model_with_additional_properties_no_use_standard_collections.py",
    related_options=["--use-standard-collections"],
)
def test_main_root_model_with_additional_properties_no_use_standard_collections(output_file: Path) -> None:
    """Use typing.Dict/List instead of built-in dict/list for container types.

    The `--no-use-standard-collections` flag generates typing module containers
    (Dict, List) instead of built-in types. This is useful for older Python
    versions or when explicit typing imports are preferred.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_with_additional_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--no-use-standard-collections"],
    )


@pytest.mark.cli_doc(
    options=["--no-use-union-operator"],
    option_description="""Use Union[X, Y] / Optional[X] instead of X | Y union operator.

The `--no-use-union-operator` flag generates union types using typing.Union
and typing.Optional instead of the | operator (PEP 604). This is useful
for older Python versions or when explicit typing imports are preferred.""",
    input_schema="jsonschema/root_model_with_additional_properties.json",
    cli_args=["--no-use-union-operator"],
    golden_output="jsonschema/root_model_with_additional_properties_no_use_union_operator.py",
    related_options=["--use-union-operator"],
)
def test_main_root_model_with_additional_properties_no_use_union_operator(output_file: Path) -> None:
    """Use Union[X, Y] / Optional[X] instead of X | Y union operator.

    The `--no-use-union-operator` flag generates union types using typing.Union
    and typing.Optional instead of the | operator (PEP 604). This is useful
    for older Python versions or when explicit typing imports are preferred.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_with_additional_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--no-use-union-operator"],
    )


def test_main_import_fast_path_primitives(output_file: Path) -> None:
    """Test primitive fields keep Optional imports with no union operator."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "import_fast_path_primitives.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--no-use-union-operator"],
    )


def test_main_ref_nullable_import_fast_path(output_file: Path) -> None:
    """Test referenced nullable fields keep Optional imports with no union operator."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_nullable_import_fast_path.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--no-use-union-operator"],
    )


def test_main_serialize_as_any_import_fast_path(output_file: Path) -> None:
    """Test SerializeAsAny reference fields keep generated imports."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "serialize_as_any_import_fast_path.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-serialize-as-any",
        ],
        force_exec_validation=True,
    )


def test_main_structured_imports(output_file: Path) -> None:
    """Test structured import detection keeps generated Union and Optional output."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "structured_imports.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--no-use-union-operator"],
        force_exec_validation=True,
        importable_module_name="generated_structured_imports",
    )


def test_main_msgspec_structured_imports(output_file: Path) -> None:
    """Test msgspec structured imports preserve nullable container fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "structured_imports.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="msgspec_structured_imports.py",
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--target-python-version",
            "3.10",
            "--class-name",
            "Payload",
        ],
        force_exec_validation=True,
        importable_module_name="generated_msgspec_structured_imports",
        importable_module_attribute="Payload",
    )


def test_main_root_model_with_additional_properties_literal(min_version: str, output_file: Path) -> None:
    """Test root model additional properties with literal types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_with_additional_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--enum-field-as-literal", "all", "--target-python-version", min_version],
    )


def test_main_jsonschema_multiple_files_ref(output_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test multiple files with references."""
    monkeypatch.setattr(
        "datamodel_code_generator.parser.jsonschema.load_data_from_path",
        lambda *_args, **_kwargs: pytest.fail("local refs should reuse loaded directory sources"),
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "multiple_files_self_ref",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "multiple_files_self_ref",
        input_file_type="jsonschema",
    )


def test_main_jsonschema_multiple_files_ref_test_json(output_file: Path) -> None:
    """Test main jsonschema multiple files ref json."""
    with chdir(JSON_SCHEMA_DATA_PATH / "multiple_files_self_ref"):
        run_main_and_assert(
            input_path=Path("test.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="multiple_files_self_ref_single.py",
        )


@pytest.mark.cli_doc(
    options=["--original-field-name-delimiter"],
    option_description="""Specify delimiter for original field names when using snake-case conversion.

The `--original-field-name-delimiter` option works with `--snake-case-field` to specify
the delimiter used in original field names. This is useful when field names contain
delimiters like spaces or hyphens that should be treated as word boundaries during
snake_case conversion.""",
    input_schema="jsonschema/space_field_enum.json",
    cli_args=["--snake-case-field", "--original-field-name-delimiter", " "],
    golden_output="main/jsonschema/space_field_enum_snake_case_field.py",
)
def test_main_space_field_enum_snake_case_field(output_file: Path) -> None:
    """Specify delimiter for original field names when using snake-case conversion.

    The `--original-field-name-delimiter` option works with `--snake-case-field` to specify
    the delimiter used in original field names. This is useful when field names contain
    delimiters like spaces or hyphens that should be treated as word boundaries during
    snake_case conversion.
    """
    with chdir(JSON_SCHEMA_DATA_PATH / "space_field_enum.json"):
        run_main_and_assert(
            input_path=Path("space_field_enum.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            extra_args=["--snake-case-field", "--original-field-name-delimiter", " "],
        )


@pytest.mark.benchmark
def test_main_all_of_ref(output_file: Path) -> None:
    """Test allOf with references."""
    with chdir(JSON_SCHEMA_DATA_PATH / "all_of_ref"):
        run_main_and_assert(
            input_path=Path("test.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            extra_args=["--class-name", "Test"],
        )


def test_main_all_of_with_object(output_file: Path) -> None:
    """Test allOf with object types."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("all_of_with_object.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
        )


def test_main_all_of_merge_same_property(output_file: Path) -> None:
    """Test allOf merging when duplicate property names exist across refs."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("all_of_merge_same_property.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="all_of_merge_same_property.py",
            extra_args=["--class-name", "Model"],
        )


def test_main_all_of_merge_boolean_property(output_file: Path) -> None:
    """Test allOf merging when a property has a boolean schema (false)."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("all_of_merge_boolean_property.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="all_of_merge_boolean_property.py",
            extra_args=["--class-name", "Model"],
        )


def test_main_all_of_ref_with_property_override(output_file: Path) -> None:
    """Test allOf with $ref preserves inheritance when properties are overridden."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("all_of_ref_with_property_override.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="all_of_ref_with_property_override.py",
        )


def test_main_all_of_multi_ref_with_property_override(output_file: Path) -> None:
    """Test allOf with multiple $refs preserves multi-inheritance when properties are overridden."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("all_of_multi_ref_with_property_override.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="all_of_multi_ref_with_property_override.py",
        )


def test_main_all_of_deep_hierarchy_property_override(output_file: Path) -> None:
    """Test allOf with deep hierarchy inherits types from grandparent when parent has partial override."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("all_of_deep_hierarchy_property_override.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="all_of_deep_hierarchy_property_override.py",
        )


def test_main_all_of_very_deep_hierarchy_property_override(output_file: Path) -> None:
    """Test allOf with 4+ levels of hierarchy inherits types from great-grandparent."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("all_of_very_deep_hierarchy_property_override.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="all_of_very_deep_hierarchy_property_override.py",
        )


def test_main_all_of_hierarchy_property_not_in_ancestor(output_file: Path) -> None:
    """Test allOf hierarchy when property override is not found in any ancestor."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("all_of_hierarchy_property_not_in_ancestor.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="all_of_hierarchy_property_not_in_ancestor.py",
        )


def test_main_all_of_hierarchy_inline_allof(output_file: Path) -> None:
    """Test allOf hierarchy when parent has inline allOf without $ref."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("all_of_hierarchy_inline_allof.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="all_of_hierarchy_inline_allof.py",
        )


@pytest.mark.skipif(
    black.__version__.split(".")[0] >= "24",
    reason="Installed black doesn't support the old style",
)
def test_main_combined_array(output_file: Path) -> None:
    """Test combined array types."""
    with chdir(JSON_SCHEMA_DATA_PATH):
        run_main_and_assert(
            input_path=Path("combined_array.json"),
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
        )


@LEGACY_BLACK_SKIP
@pytest.mark.cli_doc(
    options=["--disable-timestamp"],
    option_description="""Disable timestamp in generated file header for reproducible output.

The `--disable-timestamp` flag configures the code generation behavior.""",
    input_schema="jsonschema/pattern.json",
    cli_args=["--disable-timestamp"],
    golden_output="jsonschema/pattern.py",
)
def test_main_jsonschema_pattern(output_file: Path) -> None:
    """Disable timestamp in generated file header for reproducible output.

    The `--disable-timestamp` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern.py",
        extra_args=["--disable-timestamp"],
    )


def test_main_generate(output_file: Path) -> None:
    """Test code generation function."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="general.py",
    )


def test_main_generate_with_parsed_source_cache(output_file: Path) -> None:
    """Test code generation function with process-local parsed source cache enabled."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="general.py",
    )


def test_load_data_from_path_caches_json_source(tmp_path: Path) -> None:
    """Reuse parsed JSON file data by path and content hash for local reference loading."""
    schema_path = tmp_path / "schema.json"
    schema_path.write_text((JSON_SCHEMA_DATA_PATH / "person.json").read_text(encoding="utf-8"), encoding="utf-8")
    _clear_parser_source_data_cache()

    assert_path_cache_reuses_value(load_data_from_path, schema_path, warmups=1)


def test_load_data_from_path_invalidates_updated_json_source(tmp_path: Path) -> None:
    """Reload parsed JSON data when a cached local reference file changes."""
    schema_path = tmp_path / "schema.json"
    schema_path.write_text((JSON_SCHEMA_DATA_PATH / "person.json").read_text(encoding="utf-8"), encoding="utf-8")
    _clear_parser_source_data_cache()

    assert_path_cache_invalidates_after_write(
        load_data_from_path,
        schema_path,
        (JSON_SCHEMA_DATA_PATH / "simple_string.json").read_text(encoding="utf-8"),
        ["s"],
        expected_value_path=("required",),
    )


def test_main_generate_non_pydantic_output(output_file: Path) -> None:
    """Test generation with non-Pydantic output models (see issue #1452)."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "simple_string.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        output_model_type=DataModelType.DataclassesDataclass,
    )


def test_main_generate_without_input_file_type(output_file: Path) -> None:
    """Test helper preserves generate() input_file_type default behavior."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        assert_func=assert_file_content,
        expected_file="general.py",
    )


def test_generate_keeps_cached_yaml_root_original_unchanged(tmp_path: Path) -> None:
    """Keep cached parsed YAML root unchanged while ignoring root self metadata."""
    schema_path = tmp_path / "schema.yaml"
    output_path = tmp_path / "output.py"
    expected_path = tmp_path / "expected.py"
    schema_path.write_text(
        """
self:
  version: metadata
title: Root
type: object
properties:
  name:
    type: string
""".lstrip(),
        encoding="utf-8",
    )
    cached_schema = load_data_from_path(schema_path.resolve(), "utf-8")
    expected_path.write_text(
        """# generated by datamodel-codegen:
#   filename:  schema.yaml

from __future__ import annotations

from pydantic import BaseModel


class Root(BaseModel):
    name: str | None = None
""",
        encoding="utf-8",
    )

    run_generate_file_and_assert(
        input_path=schema_path,
        output_path=output_path,
        input_file_type=InputFileType.JsonSchema,
        assert_func=create_assert_file_content(tmp_path),
        expected_file=expected_path.name,
        disable_timestamp=True,
        unchanged_inputs={"cached schema.yaml": cached_schema},
    )


def test_generate_keeps_cached_yaml_ref_original_unchanged(tmp_path: Path) -> None:
    """Keep cached parsed YAML references unchanged while resolving JSON Schema refs."""
    schema_path = tmp_path / "schema.yaml"
    child_path = tmp_path / "child.yaml"
    output_path = tmp_path / "output.py"
    expected_path = tmp_path / "expected.py"
    schema_path.write_text(
        """
title: Root
type: object
properties:
  child:
    $ref: child.yaml#
""".lstrip(),
        encoding="utf-8",
    )
    child_path.write_text(
        """
self:
  version: metadata
title: Child
type: object
properties:
  name:
    type: string
""".lstrip(),
        encoding="utf-8",
    )
    cached_child_schema = load_data_from_path(child_path.resolve(), "utf-8")
    expected_path.write_text(
        """# generated by datamodel-codegen:
#   filename:  schema.yaml

from __future__ import annotations

from pydantic import BaseModel


class Child(BaseModel):
    name: str | None = None


class Root(BaseModel):
    child: Child | None = None
""",
        encoding="utf-8",
    )

    run_generate_file_and_assert(
        input_path=schema_path,
        output_path=output_path,
        input_file_type=InputFileType.JsonSchema,
        assert_func=create_assert_file_content(tmp_path),
        expected_file=expected_path.name,
        disable_timestamp=True,
        unchanged_inputs={"cached child.yaml": cached_child_schema},
    )


def test_main_generate_relative_input_path(output_file: Path) -> None:
    """Test helper with a relative input path."""
    run_generate_file_and_assert(
        input_path=Path(os.path.relpath(JSON_SCHEMA_DATA_PATH / "person.json", Path.cwd())),
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="general.py",
    )


@pytest.mark.allow_direct_assert
def test_main_generate_external_absolute_input_path(tmp_path: Path) -> None:
    """Test helper keeps absolute input paths that are outside the repository root."""
    with tempfile.TemporaryDirectory() as temp_dir:
        input_path = Path(temp_dir) / "person.json"
        assert Path.cwd() not in input_path.resolve().parents
        input_path.write_text((JSON_SCHEMA_DATA_PATH / "person.json").read_text(encoding="utf-8"), encoding="utf-8")

        run_generate_file_and_assert(
            input_path=input_path,
            output_path=tmp_path / "output.py",
            input_file_type=InputFileType.JsonSchema,
            assert_func=assert_file_content,
            expected_file="general.py",
        )


def test_main_generate_pydantic_v2_dataclass(output_file: Path) -> None:
    """Test generation with pydantic_v2.dataclass output model."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "simple_string.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="generate_pydantic_v2_dataclass.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
    )


def test_main_generate_pydantic_v2_dataclass_with_config(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with ConfigDict from additionalProperties."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pydantic_v2_dataclass_config.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_config.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
    )


def test_main_generate_pydantic_v2_dataclass_additional_props_true(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with additionalProperties: true."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pydantic_v2_dataclass_additional_props_true.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_additional_props_true.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
    )


def test_main_generate_pydantic_v2_dataclass_unevaluated_props_true(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with unevaluatedProperties: true."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_properties_true.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="unevaluated_properties_true_dataclass.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
    )


def test_main_generate_pydantic_v2_base_model_unevaluated_props(output_file: Path) -> None:
    """Test pydantic_v2.BaseModel with unevaluatedProperties: false."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_properties.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="unevaluated_properties_pydantic_v2.py",
        output_model_type=DataModelType.PydanticV2BaseModel,
    )


def test_main_generate_pydantic_v2_base_model_unevaluated_props_true(output_file: Path) -> None:
    """Test pydantic_v2.BaseModel with unevaluatedProperties: true."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_properties_true.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="unevaluated_properties_true_pydantic_v2.py",
        output_model_type=DataModelType.PydanticV2BaseModel,
    )


def test_main_generate_pydantic_v2_dataclass_unevaluated_props_false(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with unevaluatedProperties: false."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_properties.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="unevaluated_properties_dataclass.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
    )


def test_main_generate_pydantic_v2_dataclass_use_attribute_docstrings(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with use_attribute_docstrings."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "simple_string.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_use_attribute_docstrings.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
        use_attribute_docstrings=True,
    )


def test_main_generate_pydantic_v2_dataclass_allow_population_by_field_name(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with allow_population_by_field_name."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "simple_string.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_populate_by_name.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
        allow_population_by_field_name=True,
    )


def test_main_generate_pydantic_v2_dataclass_allow_population_by_field_name_v2_11(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with allow_population_by_field_name and target v2.11."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "simple_string.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_validate_by_name.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
        allow_population_by_field_name=True,
        target_pydantic_version=TargetPydanticVersion.V2_11,
    )


def test_main_generate_pydantic_v2_dataclass_extra_allow(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with extra='allow'."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "simple_string.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_extra_allow.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
        extra_fields="allow",
    )


def test_main_generate_pydantic_v2_dataclass_extra_forbid(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with extra='forbid'."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "simple_string.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_extra_forbid.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
        extra_fields="forbid",
    )


def test_main_generate_pydantic_v2_dataclass_extra_ignore(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with extra='ignore'."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "simple_string.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_extra_ignore.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
        extra_fields="ignore",
    )


def test_main_generate_pydantic_v2_dataclass_nested(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with nested models."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pydantic_v2_dataclass_nested.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_nested.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
    )


def test_main_generate_pydantic_v2_dataclass_constraints(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with field constraints."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pydantic_v2_dataclass_constraints.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_constraints.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
    )


def test_main_generate_pydantic_v2_dataclass_nested_frozen(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with nested models and frozen=True."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pydantic_v2_dataclass_nested.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_nested_frozen.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
        frozen_dataclasses=True,
    )


def test_main_generate_pydantic_v2_dataclass_field(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with Field constraints and defaults."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pydantic_v2_dataclass_field.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_field.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
    )


def test_main_generate_pydantic_v2_dataclass_required_field_order(output_file: Path) -> None:
    """Test pydantic_v2.dataclass keeps required fields before defaulted fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pydantic_v2_dataclass_required_field_order.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_required_field_order.py",
        extra_args=[
            "--disable-timestamp",
            "--snake-case-field",
            "--output-model-type",
            "pydantic_v2.dataclass",
            "--formatters",
            "ruff-format",
            "ruff-check",
            "isort",
            "--no-use-type-checking-imports",
            "--use-annotated",
            "--use-union-operator",
            "--use-standard-collections",
            "--use-default",
            "--target-python-version",
            "3.10",
        ],
        force_exec_validation=True,
    )


@pytest.mark.skipif(
    not PYDANTIC_V2_DATACLASS_ALIAS_NEEDS_FALLBACK,
    reason="Pydantic 2.4+ accepts non-identifier dataclass aliases without generator fallback",
)
def test_main_generate_pydantic_v2_dataclass_required_alias_field_pydantic20(output_file: Path) -> None:
    """Test pydantic_v2.dataclass keeps non-identifier aliases importable."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_required_alias_field.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=[
            "--disable-timestamp",
            "--snake-case-field",
            "--output-model-type",
            "pydantic_v2.dataclass",
            "--formatters",
            "ruff-format",
            "ruff-check",
            "isort",
            "--no-use-type-checking-imports",
            "--use-annotated",
            "--use-union-operator",
            "--use-standard-collections",
            "--use-default",
            "--target-python-version",
            "3.10",
        ],
        force_exec_validation=True,
    )


def test_main_generate_pydantic_v2_dataclass_enum(output_file: Path) -> None:
    """Test pydantic_v2.dataclass with enum types."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pydantic_v2_dataclass_enum.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="pydantic_v2_dataclass_enum.py",
        output_model_type=DataModelType.PydanticV2Dataclass,
    )


@pytest.mark.parametrize(
    ("input_file", "expected_file"),
    [
        ("pydantic_v2_model_default_dict_empty.json", "pydantic_v2_model_default_dict_empty.py"),
        ("pydantic_v2_model_default_dict_non_empty.json", "pydantic_v2_model_default_dict_non_empty.py"),
        ("pydantic_v2_model_default_nullable_dict_empty.json", "pydantic_v2_model_default_nullable_dict_empty.py"),
        (
            "pydantic_v2_model_default_nullable_dict_non_empty.json",
            "pydantic_v2_model_default_nullable_dict_non_empty.py",
        ),
    ],
)
def test_main_generate_pydantic_v2_model_default_dict(input_file: str, expected_file: str, output_file: Path) -> None:
    """Test pydantic_v2.BaseModel with dict defaults."""
    input_ = (JSON_SCHEMA_DATA_PATH / input_file).relative_to(Path.cwd())
    generate(
        input_=input_,
        input_file_type=InputFileType.JsonSchema,
        output=output_file,
        output_model_type=DataModelType.PydanticV2BaseModel,
    )
    assert_file_content(output_file, expected_file)


def test_main_generate_from_directory(tmp_path: Path) -> None:
    """Test generation from directory input."""
    input_ = (JSON_SCHEMA_DATA_PATH / "external_files_in_directory").relative_to(Path.cwd())
    generate(
        input_=input_,
        input_file_type=InputFileType.JsonSchema,
        output=tmp_path,
    )

    main_nested_directory = EXPECTED_JSON_SCHEMA_PATH / "nested_directory"
    assert_directory_content(tmp_path, main_nested_directory)


def test_main_generate_custom_class_name_generator(tmp_path: Path) -> None:
    """Test custom class name generator."""

    def custom_class_name_generator(title: str) -> str:
        return f"Custom{title}"

    output_file: Path = tmp_path / "output.py"
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="general.py",
        transform=lambda s: s.replace("CustomPerson", "Person"),
        custom_class_name_generator=custom_class_name_generator,
    )


def test_main_generate_custom_class_name_generator_additional_properties(tmp_path: Path) -> None:
    """Test custom class name generator with additional properties."""
    output_file = tmp_path / "models.py"

    def custom_class_name_generator(name: str) -> str:
        return f"Custom{name[0].upper() + name[1:]}"

    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_with_additional_properties.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="root_model_with_additional_properties_custom_class_name.py",
        custom_class_name_generator=custom_class_name_generator,
    )


def test_main_generate_custom_class_name_generator_keep_underscores(output_file: Path) -> None:
    """Test custom_class_name_generator preserves underscores in class names (Issue #1315)."""

    def keep_underscores(name: str) -> str:
        return name

    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "underscore_title.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="underscore_title.py",
        custom_class_name_generator=keep_underscores,
    )


def test_main_http_jsonschema(mock_httpx_get: HttpxGetMockFactory, output_file: Path) -> None:
    """Test HTTP JSON Schema fetching."""
    external_directory = JSON_SCHEMA_DATA_PATH / "external_files_in_directory"
    base_url = "https://example.com/external_files_in_directory/"

    url_to_path = {
        f"{base_url}person.json": "person.json",
        f"{base_url}definitions/relative/animal/pet/pet.json": "definitions/relative/animal/pet/pet.json",
        f"{base_url}definitions/relative/animal/fur.json": "definitions/relative/animal/fur.json",
        f"{base_url}definitions/friends.json": "definitions/friends.json",
        f"{base_url}definitions/food.json": "definitions/food.json",
        f"{base_url}definitions/machine/robot.json": "definitions/machine/robot.json",
        f"{base_url}definitions/drink/coffee.json": "definitions/drink/coffee.json",
        f"{base_url}definitions/drink/tea.json": "definitions/drink/tea.json",
    }

    httpx_get_mock = mock_httpx_get(
        *(MockHttpxResponse(url, external_directory / path) for url, path in url_to_path.items())
    )
    run_main_url_and_assert(
        url="https://example.com/external_files_in_directory/person.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="external_files_in_directory.py",
        transform=lambda s: s.replace(
            "#   filename:  https://example.com/external_files_in_directory/person.json",
            "#   filename:  person.json",
        ),
    )
    assert_httpx_get_kwargs(httpx_get_mock, expected_urls=list(url_to_path), any_order=True, call_count=8)


@pytest.mark.parametrize(
    (
        "headers_arguments",
        "headers_requests",
        "query_parameters_arguments",
        "query_parameters_requests",
        "http_ignore_tls",
    ),
    [
        (
            ("Authorization: Basic dXNlcjpwYXNz",),
            [("Authorization", "Basic dXNlcjpwYXNz")],
            ("key=value",),
            [("key", "value")],
            False,
        ),
        (
            ("Authorization: Basic dXNlcjpwYXNz", "X-API-key: abcefg"),
            [("Authorization", "Basic dXNlcjpwYXNz"), ("X-API-key", "abcefg")],
            ("key=value", "newkey=newvalue"),
            [("key", "value"), ("newkey", "newvalue")],
            True,
        ),
    ],
)
def test_main_http_jsonschema_with_http_headers_and_http_query_parameters_and_ignore_tls(
    mock_httpx_get: HttpxGetMockFactory,
    headers_arguments: tuple[str, ...],
    headers_requests: list[tuple[str, str]],
    query_parameters_arguments: tuple[str, ...],
    query_parameters_requests: list[tuple[str, str]],
    http_ignore_tls: bool,
    tmp_path: Path,
) -> None:
    """Test HTTP JSON Schema with headers, query params, and TLS ignore."""
    external_directory = JSON_SCHEMA_DATA_PATH / "external_files_in_directory"
    base_url = "https://example.com/external_files_in_directory/"

    url_to_path = {
        f"{base_url}person.json": "person.json",
        f"{base_url}definitions/relative/animal/pet/pet.json": "definitions/relative/animal/pet/pet.json",
        f"{base_url}definitions/relative/animal/fur.json": "definitions/relative/animal/fur.json",
        f"{base_url}definitions/friends.json": "definitions/friends.json",
        f"{base_url}definitions/food.json": "definitions/food.json",
        f"{base_url}definitions/machine/robot.json": "definitions/machine/robot.json",
        f"{base_url}definitions/drink/coffee.json": "definitions/drink/coffee.json",
        f"{base_url}definitions/drink/tea.json": "definitions/drink/tea.json",
    }

    httpx_get_mock = mock_httpx_get(
        *(MockHttpxResponse(url, external_directory / path) for url, path in url_to_path.items())
    )
    output_file: Path = tmp_path / "output.py"
    extra_args = [
        "--http-headers",
        *headers_arguments,
        "--http-query-parameters",
        *query_parameters_arguments,
    ]
    if http_ignore_tls:
        extra_args.append("--http-ignore-tls")

    run_main_url_and_assert(
        url="https://example.com/external_files_in_directory/person.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="external_files_in_directory.py",
        extra_args=extra_args,
        transform=lambda s: s.replace(
            "#   filename:  https://example.com/external_files_in_directory/person.json",
            "#   filename:  person.json",
        ),
    )
    assert_httpx_get_kwargs(
        httpx_get_mock,
        expected_urls=list(url_to_path),
        any_order=True,
        headers=headers_requests,
        params=query_parameters_requests,
        verify=bool(not http_ignore_tls),
        call_count=8,
    )


def test_main_self_reference(output_file: Path) -> None:
    """Test self-referencing schemas."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "self_reference.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.benchmark
def test_main_strict_types(output_file: Path) -> None:
    """Test strict type generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "strict_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.cli_doc(
    options=["--strict-types"],
    option_description="""Enable strict type validation for specified Python types.

The --strict-types option enforces stricter type checking by preventing implicit
type coercion for the specified types (str, bytes, int, float, bool). This
generates StrictStr, StrictBytes, StrictInt, StrictFloat, and StrictBool types
in Pydantic models, ensuring values match exactly without automatic conversion.""",
    input_schema="jsonschema/strict_types.json",
    cli_args=["--strict-types", "str", "bytes", "int", "float", "bool"],
    golden_output="main/jsonschema/strict_types_all.py",
)
@pytest.mark.skipif(
    black.__version__.split(".")[0] >= "24",
    reason="Installed black doesn't support the old style",
)
def test_main_strict_types_all(output_file: Path) -> None:
    """Enable strict type validation for specified Python types.

    The --strict-types option enforces stricter type checking by preventing implicit
    type coercion for the specified types (str, bytes, int, float, bool). This
    generates StrictStr, StrictBytes, StrictInt, StrictFloat, and StrictBool types
    in Pydantic models, ensuring values match exactly without automatic conversion.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "strict_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--strict-types", "str", "bytes", "int", "float", "bool"],
    )


@pytest.mark.cli_doc(
    options=["--field-constraints"],
    option_description="""Generate Field() with validation constraints from schema.

The `--field-constraints` flag generates Pydantic Field() declarations with
validation constraints (min/max length, pattern, minimum/maximum values, etc.)
extracted from the JSON Schema, enabling runtime validation.""",
    input_schema="jsonschema/strict_types.json",
    cli_args=["--strict-types", "str", "bytes", "int", "float", "bool", "--field-constraints"],
    golden_output="jsonschema/strict_types_all_field_constraints.py",
    related_options=["--strict-types"],
)
def test_main_strict_types_all_with_field_constraints(output_file: Path) -> None:
    """Generate Field() with validation constraints from schema.

    The `--field-constraints` flag generates Pydantic Field() declarations with
    validation constraints (min/max length, pattern, minimum/maximum values, etc.)
    extracted from the JSON Schema, enabling runtime validation.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "strict_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="strict_types_all_field_constraints.py",
        extra_args=["--strict-types", "str", "bytes", "int", "float", "bool", "--field-constraints"],
    )


@LEGACY_BLACK_SKIP
def test_main_strict_types_with_constraints(output_file: Path) -> None:
    """Test strict int/float with constraints generates conint/confloat with strict=True, and decimal format."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "strict_types_matrix.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="strict_types_with_constraints.py",
        extra_args=["--strict-types", "int", "float", "str"],
    )


def test_main_selective_constraint_kwargs_pydantic_v2(output_file: Path) -> None:
    """Test primitive constraints are preserved without dumping the full schema object."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "selective_constraint_kwargs.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="selective_constraint_kwargs.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.10",
            "--disable-timestamp",
        ],
    )


@LEGACY_BLACK_SKIP
def test_main_hostname_strict_field_constraints(output_file: Path) -> None:
    """Test hostname with --strict-types str and --field-constraints returns StrictStr."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "strict_types_matrix.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="hostname_strict_field_constraints.py",
        extra_args=["--strict-types", "str", "--field-constraints"],
    )


def test_main_hostname_field_constraints_pydantic_v2(output_file: Path) -> None:
    """Test hostname format uses Field(pattern=) instead of constr with --field-constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "hostname_field_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="hostname_field_constraints_pydantic_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--field-constraints"],
    )


def test_main_hostname_root_type_pydantic_v2(output_file: Path) -> None:
    """Test hostname format in root type uses Field(pattern=) with --field-constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "hostname_root_type.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="hostname_root_type_pydantic_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--field-constraints"],
    )


def test_main_hostname_multiple_types_pydantic_v2(output_file: Path) -> None:
    """Test hostname format with multiple types constrains only the string branch."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "hostname_multiple_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="hostname_multiple_types_pydantic_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--field-constraints"],
    )


def test_main_root_multiple_primitive_constraints_pydantic_v2(output_file: Path) -> None:
    """Test root constraints are skipped when multiple primitive types are allowed."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_multiple_primitive_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_multiple_primitive_constraints.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--field-constraints"],
    )


def test_main_jsonschema_special_enum(output_file: Path) -> None:
    """Test special enum handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "special_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="special_enum.py",
    )


@pytest.mark.cli_doc(
    options=["--special-field-name-prefix"],
    option_description="""Prefix to add to special field names (like reserved keywords).

The `--special-field-name-prefix` flag configures the code generation behavior.""",
    input_schema="jsonschema/special_enum.json",
    cli_args=["--special-field-name-prefix", "special"],
    golden_output="jsonschema/special_enum_special_field_name_prefix.py",
)
def test_main_jsonschema_special_enum_special_field_name_prefix(output_file: Path) -> None:
    """Prefix to add to special field names (like reserved keywords).

    The `--special-field-name-prefix` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "special_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="special_enum_special_field_name_prefix.py",
        extra_args=["--special-field-name-prefix", "special"],
    )


def test_main_jsonschema_special_enum_special_field_name_prefix_keep_private(output_file: Path) -> None:
    """Test special enum with prefix keeping private fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "special_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="special_enum_special_field_name_prefix_keep_private.py",
        extra_args=["--special-field-name-prefix", ""],
    )


@pytest.mark.cli_doc(
    options=["--remove-special-field-name-prefix"],
    option_description="""Remove the special prefix from field names.

The `--remove-special-field-name-prefix` flag configures the code generation behavior.""",
    input_schema="jsonschema/special_prefix_model.json",
    cli_args=["--remove-special-field-name-prefix"],
    golden_output="jsonschema/special_model_remove_special_field_name_prefix.py",
)
def test_main_jsonschema_special_model_remove_special_field_name_prefix(output_file: Path) -> None:
    """Remove the special prefix from field names.

    The `--remove-special-field-name-prefix` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "special_prefix_model.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="special_model_remove_special_field_name_prefix.py",
        extra_args=["--remove-special-field-name-prefix"],
    )


def test_main_jsonschema_subclass_enum(output_file: Path) -> None:
    """Test enum subclassing."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "subclass_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="subclass_enum.py",
        extra_args=["--use-subclass-enum"],
    )


def test_main_jsonschema_allof_enum_ref(output_file: Path) -> None:
    """Test allOf referencing enum from another schema."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_enum_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_jsonschema_allof_enum_no_external_ref(output_file: Path) -> None:
    """Test allOf referencing enum without external $ref.

    This covers the case where existing_ref is None in parse_all_of,
    so the schema is optimized to directly return the enum reference.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_enum_no_external_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support the old style",
)
@pytest.mark.cli_doc(
    options=["--use-specialized-enum"],
    option_description="""Generate StrEnum/IntEnum for string/integer enums (Python 3.11+).

The `--use-specialized-enum` flag generates specialized enum types:
- `StrEnum` for string enums
- `IntEnum` for integer enums

This is the default behavior for Python 3.11+ targets.""",
    input_schema="jsonschema/subclass_enum.json",
    cli_args=["--target-python-version", "3.11", "--use-specialized-enum"],
    golden_output="jsonschema/enum_specialized.py",
    related_options=["--no-use-specialized-enum", "--use-subclass-enum"],
)
def test_main_jsonschema_specialized_enums(output_file: Path) -> None:
    """Generate StrEnum/IntEnum for string/integer enums (Python 3.11+).

    The `--use-specialized-enum` flag generates specialized enum types:
    - `StrEnum` for string enums
    - `IntEnum` for integer enums

    This is the default behavior for Python 3.11+ targets.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "subclass_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="enum_specialized.py",
        extra_args=["--target-python-version", "3.11"],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support the old style",
)
@pytest.mark.cli_doc(
    options=["--no-use-specialized-enum"],
    option_description="""Disable specialized enum generation (StrEnum/IntEnum).

The `--no-use-specialized-enum` flag disables specialized enum types,
generating standard `Enum` classes instead of `StrEnum`/`IntEnum`.""",
    input_schema="jsonschema/subclass_enum.json",
    cli_args=["--target-python-version", "3.11", "--no-use-specialized-enum"],
    golden_output="jsonschema/enum_specialized_disable.py",
    related_options=["--use-specialized-enum", "--use-subclass-enum"],
)
def test_main_jsonschema_specialized_enums_disabled(output_file: Path) -> None:
    """Disable specialized enum generation (StrEnum/IntEnum).

    The `--no-use-specialized-enum` flag disables specialized enum types,
    generating standard `Enum` classes instead of `StrEnum`/`IntEnum`.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "subclass_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="enum_specialized_disable.py",
        extra_args=["--target-python-version", "3.11", "--no-use-specialized-enum"],
    )


@pytest.mark.cli_doc(
    options=["--empty-enum-field-name"],
    option_description="""Name for empty string enum field values.

The `--empty-enum-field-name` flag configures the code generation behavior.""",
    input_schema="jsonschema/special_enum.json",
    cli_args=["--empty-enum-field-name", "empty"],
    golden_output="jsonschema/special_enum_empty_enum_field_name.py",
)
def test_main_jsonschema_special_enum_empty_enum_field_name(output_file: Path) -> None:
    """Name for empty string enum field values.

    The `--empty-enum-field-name` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "special_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="special_enum_empty_enum_field_name.py",
        extra_args=["--empty-enum-field-name", "empty"],
    )


@pytest.mark.benchmark
def test_main_jsonschema_special_field_name(output_file: Path) -> None:
    """Test special field name handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "special_field_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="special_field_name.py",
    )


def test_main_jsonschema_complex_one_of(output_file: Path) -> None:
    """Test complex oneOf schemas."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "complex_one_of.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="complex_one_of.py",
    )


def test_main_jsonschema_complex_any_of(output_file: Path) -> None:
    """Test complex anyOf schemas."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "complex_any_of.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="complex_any_of.py",
    )


def test_main_jsonschema_combine_one_of_object(output_file: Path) -> None:
    """Test combining oneOf with objects."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "combine_one_of_object.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="combine_one_of_object.py",
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "19",
    reason="Installed black doesn't support the old style",
)
@pytest.mark.parametrize(
    ("union_mode", "output_model", "expected_output"),
    [
        (None, "pydantic_v2.BaseModel", "combine_any_of_object_v2.py"),
        (
            "left_to_right",
            "pydantic_v2.BaseModel",
            "combine_any_of_object_left_to_right.py",
        ),
    ],
)
@pytest.mark.cli_doc(
    options=["--union-mode"],
    option_description="""Union mode for combining anyOf/oneOf schemas (smart or left_to_right).

The `--union-mode` flag configures the code generation behavior.""",
    input_schema="jsonschema/combine_any_of_object.json",
    cli_args=["--union-mode", "left_to_right", "--output-model-type", "pydantic_v2.BaseModel"],
    golden_output="jsonschema/combine_any_of_object_left_to_right.py",
)
def test_main_jsonschema_combine_any_of_object(
    union_mode: str | None, output_model: str, expected_output: str, output_file: Path
) -> None:
    """Union mode for combining anyOf/oneOf schemas (smart or left_to_right).

    The `--union-mode` flag configures the code generation behavior.
    """
    extra_args = ["--output-model-type", output_model]
    if union_mode is not None:
        extra_args.extend(["--union-mode", union_mode])
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "combine_any_of_object.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=extra_args,
    )


@pytest.mark.benchmark
@pytest.mark.parametrize(
    ("extra_args", "expected_file"),
    [
        (["--output-model-type", "pydantic_v2.BaseModel"], "jsonschema_root_model_ordering.py"),
        (
            ["--output-model-type", "pydantic_v2.BaseModel", "--keep-model-order"],
            "jsonschema_root_model_ordering_keep_model_order.py",
        ),
    ],
)
def test_main_jsonschema_root_model_ordering(output_file: Path, extra_args: list[str], expected_file: str) -> None:
    """Test RootModel is ordered after the types it references."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_ordering.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_file,
        extra_args=extra_args,
    )


@pytest.mark.cli_doc(
    options=["--use-root-model-sequence-interface"],
    option_description="""Make non-null sequence-like Pydantic v2 RootModel classes implement collections.abc.Sequence.

When enabled, a RootModel whose root is list[T] or Sequence[T] inherits from Sequence[T]
and includes __iter__, __getitem__, and __len__ methods that delegate to the wrapped root value.
Scalar, optional, nullable, and RootModel type-alias outputs are unchanged.

This makes generated RootModel wrappers convenient to use anywhere a typed sequence is expected,
without unpacking `.root` at every call site:

```python
from collections.abc import Sequence

pets = Pets(root=[Pet(name="dog")])

first_pet: Pet = pets[0]
selected_pets: list[Pet] = pets[:1]
pet_names = [pet.name for pet in pets]

def render_pet_names(pets: Sequence[Pet]) -> list[str]:
    return [pet.name for pet in pets]

render_pet_names(pets)
```

When used with `--custom-template-dir`, a custom `pydantic_v2/RootModel.jinja2` template must render both
`sequence_base_class` and the helper methods using `sequence_item_type` and `sequence_slice_type`. If the custom
RootModel template does not render the sequence base class and helper methods, generation fails with an error
instead of silently ignoring this option.""",
    input_schema="jsonschema/root_model_sequence_interface.json",
    cli_args=[
        "--output-model-type",
        "pydantic_v2.BaseModel",
        "--use-root-model-sequence-interface",
        "--class-name",
        "Pets",
    ],
    golden_output="jsonschema/root_model_sequence_interface.py",
)
def test_main_jsonschema_root_model_sequence_interface(output_file: Path) -> None:
    """Generate the Sequence interface for list RootModel classes."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_sequence_interface.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_model_sequence_interface.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-root-model-sequence-interface",
            "--class-name",
            "Pets",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )

    module_name = "generated_root_model_sequence_interface"
    spec = importlib.util.spec_from_file_location(module_name, output_file)
    if spec is None:  # pragma: no cover
        pytest.fail(f"Unable to load generated module from {output_file}", pytrace=False)
    if spec.loader is None:  # pragma: no cover
        pytest.fail(f"Unable to load generated module loader from {output_file}", pytrace=False)
    module = importlib.util.module_from_spec(spec)
    sys.modules[module_name] = module
    try:
        spec.loader.exec_module(module)
        pet = module.Pet(name="dog")
        pets = module.Pets(root=[pet])
        if not isinstance(pets, Sequence):  # pragma: no cover
            pytest.fail("Generated RootModel does not implement collections.abc.Sequence")
        if pets.count(pet) != 1:  # pragma: no cover
            pytest.fail("Generated RootModel Sequence.count does not delegate to root")
        if pets.index(pet) != 0:  # pragma: no cover
            pytest.fail("Generated RootModel Sequence.index does not delegate to root")
        if list(reversed(pets)) != [pet]:  # pragma: no cover
            pytest.fail("Generated RootModel Sequence.__reversed__ does not delegate to root")
    finally:
        sys.modules.pop(module_name, None)


def test_main_jsonschema_root_model_sequence_interface_type_alias(output_file: Path) -> None:
    """Sequence interface is skipped for RootModel type aliases."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_sequence_interface.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_model_sequence_interface_type_alias.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-root-model-sequence-interface",
            "--use-root-model-type-alias",
            "--class-name",
            "Pets",
            "--disable-timestamp",
        ],
    )


@pytest.mark.cli_doc(
    options=["--field-include-all-keys"],
    option_description="""Include all schema keys in Field() json_schema_extra.

The `--field-include-all-keys` flag configures the code generation behavior.""",
    input_schema="jsonschema/person.json",
    cli_args=["--field-include-all-keys"],
    golden_output="jsonschema/general.py",
)
@pytest.mark.benchmark
def test_main_jsonschema_field_include_all_keys(output_file: Path) -> None:
    """Include all schema keys in Field() json_schema_extra.

    The `--field-include-all-keys` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="general.py",
        extra_args=["--field-include-all-keys"],
    )


@pytest.mark.cli_doc(
    options=["--field-extra-keys-without-x-prefix"],
    option_description="""Include schema extension keys in Field() without requiring 'x-' prefix.

The --field-extra-keys-without-x-prefix option allows you to specify custom
schema extension keys that should be included in Pydantic Field() extras without
the 'x-' prefix requirement. For example, 'x-repr' in the schema becomes 'repr'
in Field(). This is useful for custom schema extensions and vendor-specific metadata.""",
    input_schema="jsonschema/extras.json",
    cli_args=["--field-include-all-keys", "--field-extra-keys-without-x-prefix", "x-repr"],
    model_outputs={
        "pydantic_v2": "main/jsonschema/field_extras_field_include_all_keys_v2.py",
    },
)
def test_main_jsonschema_field_extras_field_include_all_keys(output_file: Path) -> None:
    """Include schema extension keys in Field() without requiring 'x-' prefix.

    The --field-extra-keys-without-x-prefix option allows you to specify custom
    schema extension keys that should be included in Pydantic Field() extras without
    the 'x-' prefix requirement. For example, 'x-repr' in the schema becomes 'repr'
    in Field(). This is useful for custom schema extensions and vendor-specific metadata.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "extras.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_extras_field_include_all_keys_v2.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-include-all-keys",
            "--field-extra-keys-without-x-prefix",
            "x-repr",
        ],
    )


@pytest.mark.cli_doc(
    options=["--field-extra-keys"],
    option_description="""Include specific extra keys in Field() definitions.

The `--field-extra-keys` flag configures the code generation behavior.""",
    input_schema="jsonschema/extras.json",
    cli_args=["--field-extra-keys", "key2", "--field-extra-keys-without-x-prefix", "x-repr"],
    model_outputs={
        "pydantic_v2": "main/jsonschema/field_extras_field_extra_keys_v2.py",
    },
)
def test_main_jsonschema_field_extras_field_extra_keys(output_file: Path) -> None:
    """Include specific extra keys in Field() definitions.

    The `--field-extra-keys` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "extras.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_extras_field_extra_keys_v2.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-extra-keys",
            "key2",
            "invalid-key-1",
            "--field-extra-keys-without-x-prefix",
            "x-repr",
        ],
    )


def test_main_jsonschema_field_extras(output_file: Path) -> None:
    """Test field extras generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "extras.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_extras_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


@pytest.mark.parametrize(
    "default_factory",
    [
        "__import__('builtins').print('DF_EXEC') or str",
        "lambda: []",
        "datetime.utcnow",
        None,
    ],
)
@pytest.mark.parametrize(
    "output_model",
    [
        "pydantic_v2.BaseModel",
        "pydantic_v2.dataclass",
        "dataclasses.dataclass",
        "msgspec.Struct",
    ],
)
def test_main_jsonschema_default_factory_rejects_unsafe_value(
    default_factory: object,
    output_model: str,
    output_file: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Reject unsafe schema-supplied default_factory values."""
    input_file = output_file.with_suffix(".json")
    input_file.write_text(
        json.dumps({
            "type": "object",
            "properties": {
                "payload": {
                    "type": "string",
                    "default_factory": default_factory,
                }
            },
        }),
        encoding="utf-8",
    )
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        output_should_not_exist=True,
        capsys=capsys,
        expected_stderr_contains="default_factory must be one of: dict, list, set",
        extra_args=["--output-model-type", output_model],
    )


@pytest.mark.parametrize(
    ("schema", "expected_error"),
    [
        (
            {
                "type": "object",
                "properties": {"payload": {"$ref": "#/$defs/Payload"}},
                "$defs": {
                    "Payload": {
                        "type": "object",
                        "x-python-import": {
                            "module": "os",
                            "name": "getcwd\nprint('DMCG_EXEC')",
                        },
                    }
                },
            },
            "x-python-import must be a dotted Python identifier path",
        ),
        (
            {
                "type": "object",
                "properties": {
                    "payload": {
                        "customTypePath": "os.getcwd\nprint('DMCG_EXEC')",
                    }
                },
            },
            "customTypePath must be a dotted Python identifier path",
        ),
        (
            {
                "type": "object",
                "properties": {"payload": {"$ref": "#/$defs/Payload"}},
                "$defs": {
                    "Payload": {
                        "type": "object",
                        "x-python-import": {
                            "module": "os",
                        },
                    }
                },
            },
            "x-python-import requires both module and name",
        ),
    ],
)
def test_main_jsonschema_rejects_unsafe_python_import_extensions(
    schema: dict[str, object],
    expected_error: str,
    output_file: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Reject schema-controlled import paths that would break generated import statements."""
    input_file = output_file.with_suffix(".json")
    input_file.write_text(json.dumps(schema), encoding="utf-8")

    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        output_should_not_exist=True,
        capsys=capsys,
        expected_stderr_contains=expected_error,
    )


@pytest.mark.parametrize("ref_template", ["../secret/leak.json", "{file_uri}"])
def test_main_jsonschema_warns_local_ref_outside_base_path(
    ref_template: str,
    output_file: Path,
) -> None:
    """Keep local refs outside the input base path compatible, but warn."""
    project_dir = output_file.parent / "project"
    secret_dir = output_file.parent / "secret"
    project_dir.mkdir()
    secret_dir.mkdir()
    secret_schema = secret_dir / "leak.json"
    secret_schema.write_text(
        json.dumps({
            "type": "object",
            "properties": {
                "token": {
                    "type": "string",
                    "default": "SECRET_TOKEN",
                }
            },
        }),
        encoding="utf-8",
    )
    ref = ref_template.format(file_uri=secret_schema.resolve().as_uri())
    input_file = project_dir / "schema.json"
    input_file.write_text(
        json.dumps({
            "type": "object",
            "properties": {
                "payload": {
                    "$ref": ref,
                }
            },
        }),
        encoding="utf-8",
    )

    with pytest.warns(FutureWarning, match=r"outside the input base path"):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file,
            input_file_type="jsonschema",
        )


def test_main_jsonschema_no_allow_remote_refs_blocks_local_ref_outside_base_path(
    output_file: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Reject local JSON Schema refs outside the input base path when explicitly disabled."""
    project_dir = output_file.parent / "project"
    secret_dir = output_file.parent / "secret"
    project_dir.mkdir()
    secret_dir.mkdir()
    (secret_dir / "leak.json").write_text(json.dumps({"type": "object"}), encoding="utf-8")
    input_file = project_dir / "schema.json"
    input_file.write_text(
        json.dumps({
            "type": "object",
            "properties": {
                "payload": {
                    "$ref": "../secret/leak.json",
                }
            },
        }),
        encoding="utf-8",
    )

    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        output_should_not_exist=True,
        capsys=capsys,
        expected_stderr_contains="Blocked unsafe local $ref",
        extra_args=["--no-allow-remote-refs"],
    )


def test_main_jsonschema_no_allow_remote_refs_blocks_file_url(
    output_file: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Apply --no-allow-remote-refs to file:// refs as external refs."""
    project_dir = output_file.parent / "project"
    project_dir.mkdir()
    referenced_schema = project_dir / "referenced.json"
    referenced_schema.write_text(json.dumps({"type": "object"}), encoding="utf-8")
    input_file = project_dir / "schema.json"
    input_file.write_text(
        json.dumps({
            "type": "object",
            "properties": {
                "payload": {
                    "$ref": referenced_schema.resolve().as_uri(),
                }
            },
        }),
        encoding="utf-8",
    )

    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        output_should_not_exist=True,
        capsys=capsys,
        expected_stderr_contains="Fetching remote $ref is disabled",
        extra_args=["--no-allow-remote-refs"],
    )


def test_main_jsonschema_custom_base_path(output_file: Path) -> None:
    """Test custom base path configuration."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "custom_base_path.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="custom_base_path.py",
    )


@pytest.mark.cli_doc(
    options=["--base-class-map"],
    option_description="""Specify different base classes for specific models via JSON mapping.

The `--base-class-map` option allows you to assign different base classes
to specific models. This is useful when you want selective base class inheritance,
for example, applying custom base classes only to specific models while leaving
others with the default `BaseModel`.

Priority: `--base-class-map` > `customBasePath` (schema extension) > `--base-class`

You can specify either a single base class as a string, or multiple base classes
(mixins) as a list:

- Single: `{"Person": "custom.bases.PersonBase"}`
- Multiple: `{"User": ["mixins.AuditMixin", "mixins.TimestampMixin"]}`

You can pass the mapping either inline as JSON or as a path to a JSON file.

When using multiple base classes, the specified classes are used directly without
adding `BaseModel`. Ensure your mixins inherit from `BaseModel` if needed.""",
    input_schema="jsonschema/base_class_map.json",
    cli_args=[
        "--base-class-map",
        '{"Person": "custom.bases.PersonBase", "Animal": "custom.bases.AnimalBase"}',
    ],
    golden_output="base_class_map.py",
    related_options=["--base-class"],
)
def test_main_jsonschema_base_class_map(output_file: Path) -> None:
    """Specify different base classes for specific models via JSON mapping.

    The `--base-class-map` option allows you to assign different base classes
    to specific models. Priority: base-class-map > customBasePath > base-class.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "base_class_map.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="base_class_map.py",
        extra_args=[
            "--base-class-map",
            '{"Person": "custom.bases.PersonBase", "Animal": "custom.bases.AnimalBase"}',
        ],
    )


def test_main_jsonschema_base_class_map_from_file(output_file: Path, tmp_path: Path) -> None:
    """Test base_class_map loaded from a JSON file."""
    mapping_path = tmp_path / "base_class_map.json"
    mapping_path.write_text(
        json.dumps({"Person": "custom.bases.PersonBase", "Animal": "custom.bases.AnimalBase"}),
        encoding="utf-8",
    )
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "base_class_map.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="base_class_map.py",
        extra_args=["--base-class-map", str(mapping_path)],
    )


@pytest.mark.cli_doc(
    options=["--model-name-map"],
    option_description="""Rename generated model classes from a JSON mapping.

The `--model-name-map` option applies explicit class names to generated models.
Mapping keys can be canonical schema refs such as `#/definitions/Foo` or the
current generated class name such as `Foo1`. Values are final Python class names.

This is useful when a schema cannot be edited but generated model names must be
stable for public APIs or downstream code. Colliding mapped names fail instead
of being silently suffixed.""",
    input_schema="jsonschema/model_name_map.json",
    cli_args=[
        "--model-name-map",
        '{"#/definitions/Foo": "RenamedFoo", "Bar": "RenamedBar", "model-name": "OriginalMapped"}',
    ],
    golden_output="jsonschema/model_name_map.py",
    related_options=["--use-title-as-name", "--naming-strategy"],
)
def test_main_jsonschema_model_name_map(output_file: Path) -> None:
    """Test class renames by schema ref, generated class name, and original schema name."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_name_map.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="model_name_map.py",
        extra_args=[
            "--model-name-map",
            '{"#/definitions/Foo": "RenamedFoo", "Bar": "RenamedBar", "model-name": "OriginalMapped"}',
        ],
    )


def test_main_jsonschema_model_name_map_from_file(output_file: Path, tmp_path: Path) -> None:
    """Test model_name_map loaded from a JSON file."""
    mapping_path = tmp_path / "model_name_map.json"
    mapping_path.write_text(
        json.dumps({
            "#/definitions/Foo": "RenamedFoo",
            "Bar": "RenamedBar",
            "model-name": "OriginalMapped",
        }),
        encoding="utf-8",
    )
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_name_map.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="model_name_map.py",
        extra_args=["--model-name-map", str(mapping_path)],
    )


def test_main_jsonschema_model_name_map_rejects_collision(
    output_file: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test mapped class name collisions fail instead of adding a suffix."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_name_map.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        file_should_not_exist=output_file,
        capsys=capsys,
        expected_stderr_contains="but that model name is already used",
        extra_args=[
            "--model-name-map",
            '{"#/definitions/Foo": "Renamed", "#/definitions/Bar": "Renamed"}',
        ],
    )


def test_main_jsonschema_model_name_map_rejects_invalid_name(
    output_file: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test mapped class names must be valid Python identifiers."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_name_map.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        file_should_not_exist=output_file,
        capsys=capsys,
        expected_stderr_contains="to invalid class name",
        extra_args=[
            "--model-name-map",
            '{"#/definitions/Foo": "not valid"}',
        ],
    )


def test_main_jsonschema_base_class_map_from_file_invalid_json(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test invalid JSON file passed to --base-class-map."""
    mapping_path = tmp_path / "base_class_map.json"
    mapping_path.write_text("{invalid json}", encoding="utf-8")
    assert_run_main_with_args_error(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"),
            "--output",
            str(tmp_path / "output.py"),
            "--input-file-type",
            "jsonschema",
            "--base-class-map",
            str(mapping_path),
        ],
        capsys,
        "Invalid JSON:",
    )


def test_main_jsonschema_base_class_map_from_file_invalid_encoding(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test invalid-encoding JSON file passed to --base-class-map."""
    mapping_path = tmp_path / "base_class_map.json"
    mapping_path.write_bytes(b"\x80")
    assert_run_main_with_args_error(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"),
            "--output",
            str(tmp_path / "output.py"),
            "--input-file-type",
            "jsonschema",
            "--base-class-map",
            str(mapping_path),
        ],
        capsys,
        "Unable to read JSON file",
    )


def test_main_jsonschema_base_class_map_inline_requires_json_object(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test non-object JSON passed to --base-class-map."""
    assert_run_main_with_args_error(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"),
            "--output",
            str(tmp_path / "output.py"),
            "--input-file-type",
            "jsonschema",
            "--base-class-map",
            '["custom.bases.PersonBase"]',
        ],
        capsys,
        "Expected a JSON object",
    )


def test_main_jsonschema_base_class_map_rejects_invalid_mapping_value(
    output_file: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test invalid base-class-map values fail with the option name."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "base_class_map.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        file_should_not_exist=output_file,
        capsys=capsys,
        expected_stderr_contains="Invalid --base-class-map",
        extra_args=[
            "--base-class-map",
            '{"Person": 1}',
        ],
    )


def test_main_jsonschema_custom_base_paths_list(output_file: Path) -> None:
    """Test customBasePath with list of base classes."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "custom_base_paths_list.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="custom_base_paths_list.py",
    )


def test_main_jsonschema_base_class_map_list(output_file: Path) -> None:
    """Test base_class_map with list values for multiple inheritance."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "base_class_map_list.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="base_class_map_list.py",
        extra_args=[
            "--base-class-map",
            '{"User": ["mixins.AuditMixin", "mixins.TimestampMixin"], "Admin": "admin.AdminBase"}',
        ],
    )


def test_main_jsonschema_base_class_map_empty_list(output_file: Path) -> None:
    """Test base_class_map with empty strings list (falls back to default)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "base_class_map_empty_list.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="base_class_map_empty_list.py",
        extra_args=[
            "--base-class-map",
            '{"User": ["", ""]}',
        ],
    )


def test_main_jsonschema_base_class_map_json_when_path_check_raises(
    output_file: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test base_class_map JSON parsing when path probing raises OSError."""
    original_is_file = Path.is_file

    def raise_for_mapping_value(self: Path) -> bool:
        if str(self).startswith('{"User"'):
            raise OSError
        return original_is_file(self)

    monkeypatch.setattr(Path, "is_file", raise_for_mapping_value)

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "base_class_map_empty_list.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="base_class_map_empty_list.py",
        extra_args=[
            "--base-class-map",
            '{"User": ["", ""]}',
        ],
    )


def test_main_jsonschema_base_class_map_long_json(output_file: Path) -> None:
    """Test base_class_map with very long json string."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "base_class_map_empty_list.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="base_class_map_empty_list.py",
        extra_args=[
            "--base-class-map",
            '{"User": [' + ",".join(itertools.repeat('""', 100)) + "]}",
        ],
    )


def test_long_description(output_file: Path) -> None:
    """Test long description handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "long_description.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.cli_doc(
    options=["--wrap-string-literal"],
    option_description="""Wrap long string literals across multiple lines.

The `--wrap-string-literal` flag breaks long string literals (like descriptions)
across multiple lines for better readability, instead of having very long
single-line strings in the generated code.""",
    input_schema="jsonschema/long_description.json",
    cli_args=["--wrap-string-literal"],
    golden_output="jsonschema/long_description_wrap_string_literal.py",
)
def test_long_description_wrap_string_literal(output_file: Path) -> None:
    """Wrap long string literals across multiple lines.

    The `--wrap-string-literal` flag breaks long string literals (like descriptions)
    across multiple lines for better readability, instead of having very long
    single-line strings in the generated code.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "long_description.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--wrap-string-literal"],
    )


@pytest.mark.allow_direct_assert
def test_version(capsys: pytest.CaptureFixture) -> None:
    """Test version output."""
    with pytest.raises(SystemExit) as e:
        run_main_with_args(["--version"])
    assert e.value.code == Exit.OK
    captured = capsys.readouterr()
    assert captured.out != "0.0.0\n"
    assert not captured.err


def test_jsonschema_pattern_properties(output_file: Path) -> None:
    """Test JSON Schema pattern properties."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern_properties.py",
    )


def test_jsonschema_pattern_properties_field_constraints(output_file: Path) -> None:
    """Test pattern properties with field constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern_properties_field_constraints.py",
        extra_args=["--field-constraints"],
    )


def test_jsonschema_pattern_properties_use_annotated(output_file: Path) -> None:
    """Test pattern properties with --use-annotated preserves pattern constraint on dict keys."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern_properties_use_annotated.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--use-annotated"],
    )


@LEGACY_BLACK_SKIP
def test_jsonschema_titles(output_file: Path) -> None:
    """Test JSON Schema title handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "titles.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="titles.py",
    )


@LEGACY_BLACK_SKIP
@pytest.mark.cli_doc(
    options=["--use-title-as-name"],
    option_description="""Use schema title as the generated class name.

The `--use-title-as-name` flag uses the `title` property from the schema
as the class name instead of deriving it from the property name or path.
This is useful when schemas have descriptive titles that should be preserved.""",
    input_schema="jsonschema/titles.json",
    cli_args=["--use-title-as-name"],
    golden_output="jsonschema/titles_use_title_as_name.py",
    related_options=["--class-name"],
)
def test_jsonschema_titles_use_title_as_name(output_file: Path) -> None:
    """Use schema title as the generated class name.

    The `--use-title-as-name` flag uses the `title` property from the schema
    as the class name instead of deriving it from the property name or path.
    This is useful when schemas have descriptive titles that should be preserved.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "titles.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="titles_use_title_as_name.py",
        extra_args=["--use-title-as-name"],
    )


@pytest.mark.cli_doc(
    options=["--infer-union-variant-names"],
    option_description="""Infer names for inline oneOf/anyOf object variants from literal fields.

The `--infer-union-variant-names` flag uses branch-local `const` values or
single-value enums on a shared discriminator-style property to name generated
inline union models. This is useful when inline union branches would otherwise
receive position-based names such as `Event` and `Event1`.

The literal value can also come from a single-value enum or an internal `$ref`.
Existing generated output is preserved unless this option is enabled.""",
    input_schema="jsonschema/infer_union_variant_names.json",
    cli_args=["--infer-union-variant-names"],
    golden_output="jsonschema/infer_union_variant_names.py",
    comparison_output="jsonschema/infer_union_variant_names_baseline.py",
    related_options=["--use-title-as-name"],
)
def test_jsonschema_infer_union_variant_names(output_file: Path) -> None:
    """Infer inline union variant model names from literal branch fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "infer_union_variant_names.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="infer_union_variant_names.py",
        extra_args=["--infer-union-variant-names"],
    )


def test_jsonschema_infer_union_variant_names_default_names(output_file: Path) -> None:
    """Keep default inline union model names unless variant name inference is enabled."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "infer_union_variant_names.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="infer_union_variant_names_baseline.py",
    )


@LEGACY_BLACK_SKIP
def test_jsonschema_infer_union_variant_names_edges(output_file: Path) -> None:
    """Infer variant names for discriminator, scalar literal, ref, and fallback cases."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "infer_union_variant_names_edges.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="infer_union_variant_names_edges.py",
        extra_args=[
            "--infer-union-variant-names",
            "--external-ref-mapping",
            "external_kind.json=external.models",
            "--disable-timestamp",
        ],
    )


@LEGACY_BLACK_SKIP
def test_jsonschema_without_titles_use_title_as_name(output_file: Path) -> None:
    """Test title as name without titles present."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "without_titles.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="without_titles_use_title_as_name.py",
        extra_args=["--use-title-as-name"],
    )


def test_jsonschema_title_with_dots(output_file: Path) -> None:
    """Test using title as name when title contains dots (e.g., version numbers)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "title_with_dots.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="title_with_dots.py",
        extra_args=["--use-title-as-name"],
    )


@BLACK_PY313_SKIP
def test_jsonschema_use_title_as_name_inline_types(output_file: Path) -> None:
    """Test use-title-as-name creates type aliases for inline types.

    When use_title_as_name is enabled and inline types (array, dict, oneOf, anyOf, enum)
    have a title, type aliases should be created instead of using inline types directly.

    Fixes: https://github.com/koxudaxi/datamodel-code-generator/issues/2887
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_title_as_name_inline_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_title_as_name_inline_types.py",
        extra_args=[
            "--use-title-as-name",
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.13",
            "--use-union-operator",
            "--use-standard-collections",
            "--skip-root-model",
        ],
    )


@BLACK_PY313_SKIP
def test_jsonschema_use_title_as_name_inline_types_pydantic(output_file: Path) -> None:
    """Test use-title-as-name with Pydantic v2 creates named types for inline types.

    This test covers the case where should_parse_enum_as_literal returns False
    (for oneOf with const values), exercising the False branch in
    _should_create_type_alias_for_title.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_title_as_name_inline_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_title_as_name_inline_types_pydantic.py",
        extra_args=[
            "--use-title-as-name",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.13",
            "--use-union-operator",
            "--use-standard-collections",
        ],
    )


@BLACK_PY313_SKIP
def test_jsonschema_use_title_as_name_nested_titles(output_file: Path) -> None:
    """Test use-title-as-name creates type aliases for nested elements with titles.

    When use_title_as_name is enabled, nested elements like array items,
    additionalProperties values, and oneOf/anyOf branches that have their own
    titles should also create type aliases.

    Fixes: https://github.com/koxudaxi/datamodel-code-generator/issues/2887
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_title_as_name_nested_titles.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_title_as_name_nested_titles.py",
        extra_args=[
            "--use-title-as-name",
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.13",
            "--use-union-operator",
            "--use-standard-collections",
            "--skip-root-model",
        ],
    )


@BLACK_PY313_SKIP
def test_jsonschema_use_title_as_name_nested_titles_pydantic(output_file: Path) -> None:
    """Test use-title-as-name with Pydantic v2 creates named types for nested elements."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_title_as_name_nested_titles.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_title_as_name_nested_titles_pydantic.py",
        extra_args=[
            "--use-title-as-name",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.13",
            "--use-union-operator",
            "--use-standard-collections",
        ],
    )


@pytest.mark.cli_doc(
    options=["--use-object-type"],
    option_description="""Use object instead of Any for unspecified object and array values.

The `--use-object-type` flag configures the code generation to use `object`
instead of `Any` when JSON Schema does not define object properties or array
item schemas. This helps generate stricter type hints for unstructured values.""",
    input_schema="jsonschema/use_object_type.json",
    cli_args=[
        "--output-model-type",
        "typing.TypedDict",
        "--target-python-version",
        "3.10",
        "--use-object-type",
    ],
    golden_output="jsonschema/use_object_type_typed_dict.py",
    related_options=["--output-model-type", "--target-python-version"],
)
def test_jsonschema_use_object_type_typed_dict(output_file: Path) -> None:
    """Test using object instead of Any for unspecified object and array values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_object_type.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_object_type_typed_dict.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.10",
            "--use-object-type",
        ],
    )


def test_main_jsonschema_has_default_value(output_file: Path) -> None:
    """Test default value handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "has_default_value.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="has_default_value_pydantic_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_boolean_property(output_file: Path) -> None:
    """Test boolean property generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "boolean_property.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="boolean_property.py",
    )


def test_main_jsonschema_modular_default_enum_member(output_dir: Path) -> None:
    """Test modular enum with default member."""
    with freeze_time(TIMESTAMP):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "modular_default_enum_member",
            output_path=output_dir,
            expected_directory=EXPECTED_JSON_SCHEMA_PATH / "modular_default_enum_member",
            extra_args=["--set-default-enum-member"],
        )


def test_main_jsonschema_falsy_default_enum_member(output_file: Path) -> None:
    """Test enum member mapping for falsy default values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "falsy_default_enum_member.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="falsy_default_enum_member.py",
        extra_args=["--set-default-enum-member"],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] < "22",
    reason="Installed black doesn't support Python version 3.10",
)
def test_main_use_union_operator(output_dir: Path) -> None:
    """Test union operator usage."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "external_files_in_directory",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "use_union_operator",
        input_file_type="jsonschema",
        extra_args=["--use-union-operator"],
    )


@pytest.mark.parametrize(
    ("extra_args", "expected_suffix"),
    [
        (["--treat-dot-as-module"], "treat_dot_as_module"),
        (None, "treat_dot_not_as_module"),
        (["--no-treat-dot-as-module"], "treat_dot_not_as_module"),
    ],
)
def test_treat_dot_as_module(extra_args: list[str] | None, expected_suffix: str, output_dir: Path) -> None:
    """Test dot notation as module separator."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "treat_dot_as_module",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / expected_suffix,
        extra_args=extra_args,
    )


@pytest.mark.cli_doc(
    options=["--treat-dot-as-module"],
    option_description="""Treat dots in schema names as module separators.

The `--treat-dot-as-module` flag configures the code generation behavior.""",
    input_schema="jsonschema/treat_dot_as_module_single",
    cli_args=["--treat-dot-as-module"],
    golden_output="jsonschema/treat_dot_as_module_single/",
)
def test_treat_dot_as_module_single_file(output_dir: Path) -> None:
    """Treat dots in schema names as module separators.

    The `--treat-dot-as-module` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "treat_dot_as_module_single",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "treat_dot_as_module_single",
        extra_args=["--treat-dot-as-module"],
    )


@pytest.mark.cli_doc(
    options=["--no-treat-dot-as-module"],
    option_description="""Keep dots in schema names as underscores for flat output.

The `--no-treat-dot-as-module` flag prevents splitting dotted schema names.""",
    input_schema="jsonschema/treat_dot_as_module_single",
    cli_args=["--no-treat-dot-as-module"],
    golden_output="jsonschema/treat_dot_as_module_single_no_treat/",
    primary=True,
)
def test_no_treat_dot_as_module_single_file(output_dir: Path) -> None:
    """Keep dots in schema names as underscores for flat output.

    The `--no-treat-dot-as-module` flag prevents splitting dotted schema names.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "treat_dot_as_module_single",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "treat_dot_as_module_single_no_treat",
        extra_args=["--no-treat-dot-as-module"],
    )


@pytest.mark.parametrize(
    ("extra_args", "expected_suffix"),
    [
        (["--treat-dot-as-module"], "treat_dot_single"),
        (None, "no_treat_dot_single"),
        (["--no-treat-dot-as-module"], "no_treat_dot_single"),
    ],
)
def test_treat_dot_as_module_version_style(
    extra_args: list[str] | None, expected_suffix: str, output_dir: Path
) -> None:
    """Test dotted version-style schema names (e.g., v0.0.39.job.json)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "no_treat_dot_single",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / expected_suffix,
        extra_args=extra_args,
    )


@pytest.mark.parametrize(
    ("extra_args", "expected_suffix"),
    [
        (["--treat-dot-as-module"], "treat_dot_complex_treat"),
        (None, "treat_dot_complex_no_treat"),
        (["--no-treat-dot-as-module"], "treat_dot_complex_no_treat"),
    ],
)
def test_treat_dot_as_module_complex_refs(extra_args: list[str] | None, expected_suffix: str, output_dir: Path) -> None:
    """Test dotted schema names with cross-file references."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "treat_dot_complex",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / expected_suffix,
        extra_args=extra_args,
    )


def test_main_jsonschema_duplicate_name(output_dir: Path) -> None:
    """Test duplicate name handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "duplicate_name",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "duplicate_name",
        input_file_type="jsonschema",
    )


def test_main_jsonschema_items_boolean(output_file: Path) -> None:
    """Test items with boolean values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "items_boolean.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="items_boolean.py",
    )


def test_main_jsonschema_array_in_additional_properites(output_file: Path) -> None:
    """Test array in additional properties."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "array_in_additional_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="array_in_additional_properties.py",
    )


def test_main_jsonschema_object_with_only_additional_properties(output_file: Path) -> None:
    """Test object with only additional properties."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "string_dict.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="string_dict.py",
    )


def test_main_jsonschema_unevaluated_properties(output_file: Path) -> None:
    """Test unevaluatedProperties: false generates extra='forbid'."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="unevaluated_properties.py",
    )


def test_main_jsonschema_unevaluated_properties_true(output_file: Path) -> None:
    """Test unevaluatedProperties: true generates extra='allow'."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_properties_true.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="unevaluated_properties_true.py",
    )


def test_main_jsonschema_unevaluated_properties_schema(output_file: Path) -> None:
    """Test unevaluatedProperties schema allows extra keys without typed runtime validation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_properties_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="unevaluated_properties_schema.py",
        force_exec_validation=True,
    )


def test_main_jsonschema_unevaluated_properties_multiple_types(output_file: Path) -> None:
    """Test unevaluatedProperties with multiple types triggers _set_schema_metadata."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_properties_multiple_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="unevaluated_properties_multiple_types.py",
    )


def test_main_jsonschema_nullable_object(output_file: Path) -> None:
    """Test nullable object handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nullable_object.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="nullable_object.py",
    )


def test_main_jsonschema_ref_type_has_null(output_file: Path) -> None:
    """Test that type: [type, null] from $ref schema is propagated."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_type_has_null.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_type_has_null.py",
        extra_args=["--use-union-operator"],
    )


def test_main_jsonschema_object_has_one_of(output_file: Path) -> None:
    """Test object with oneOf constraint."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "object_has_one_of.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="object_has_one_of.py",
    )


def test_main_jsonschema_oneof_const_enum(output_file: Path) -> None:
    """Test oneOf with const values generates enum (issue #1925)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum.py",
    )


def test_main_jsonschema_oneof_const_enum_nullable(output_file: Path) -> None:
    """Test nullable oneOf with const values generates optional enum."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_nullable.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_nullable.py",
    )


def test_main_jsonschema_oneof_const_enum_int_nullable(output_file: Path) -> None:
    """Test nullable integer oneOf with const values does not emit a None enum member."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_int_nullable.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_int_nullable.py",
    )


def test_main_jsonschema_oneof_const_enum_nested(output_file: Path) -> None:
    """Test nested oneOf with const values in properties and array items."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_nested.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_nested.py",
    )


@pytest.mark.cli_doc(
    options=["--enum-field-as-literal"],
    option_description="""Generate Literal types instead of Enums for fields with enumerated values.

The --enum-field-as-literal option replaces Enum classes with Literal types for
fields that have a fixed set of allowed values. Use 'all' to convert all enum
fields, or 'one' to only convert enums with a single value. This produces more
concise type hints and avoids creating Enum classes when not needed.""",
    input_schema="jsonschema/oneof_const_enum_nested.yaml",
    cli_args=["--enum-field-as-literal", "all"],
    golden_output="main/jsonschema/oneof_const_enum_nested_literal.py",
)
def test_main_jsonschema_oneof_const_enum_nested_literal(output_file: Path) -> None:
    """Generate Literal types instead of Enums for fields with enumerated values.

    The --enum-field-as-literal option replaces Enum classes with Literal types for
    fields that have a fixed set of allowed values. Use 'all' to convert all enum
    fields, or 'one' to only convert enums with a single value. This produces more
    concise type hints and avoids creating Enum classes when not needed.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_nested.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_nested_literal.py",
        extra_args=["--enum-field-as-literal", "all"],
    )


def test_main_jsonschema_oneof_const_enum_int(output_file: Path) -> None:
    """Test oneOf with integer const values generates IntEnum."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_int.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_int.py",
    )


def test_main_jsonschema_oneof_const_enum_type_list(output_file: Path) -> None:
    """Test oneOf with const values and type list (nullable)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_type_list.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_type_list.py",
    )


def test_main_jsonschema_oneof_const_enum_literal(output_file: Path) -> None:
    """Test oneOf with const values as Literal type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_literal.py",
        extra_args=["--enum-field-as-literal", "all"],
    )


def test_main_jsonschema_enum_complex_values_literal(output_file: Path) -> None:
    """Test complex enum values with --enum-field-as-literal all use type-safe fallbacks."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_complex_values_literal.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="enum_complex_values_literal.py",
        extra_args=["--enum-field-as-literal", "all"],
    )


def test_main_jsonschema_anyof_with_false_schema(output_file: Path) -> None:
    """Test false schemas inside anyOf are accepted and ignored as unreachable branches."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "anyof_with_false_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="anyof_with_false_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_oneof_with_false_schema(output_file: Path) -> None:
    """Test false schemas inside oneOf are accepted and ignored as unreachable branches."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_with_false_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_with_false_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


@pytest.mark.parametrize(
    "schema",
    [
        pytest.param({"title": "Payload", "anyOf": [False, False]}, id="anyof-only-false"),
        pytest.param(
            {
                "title": "Payload",
                "allOf": [False, {"type": "object", "properties": {"name": {"type": "string"}}}],
            },
            id="allof-direct-false",
        ),
        pytest.param(
            {
                "title": "Payload",
                "$defs": {
                    "A": {"type": "object", "properties": {"id": {"type": "string"}}},
                    "B": {"type": "object", "properties": {"id": {"type": "integer"}}},
                },
                "allOf": [{"$ref": "#/$defs/A"}, {"$ref": "#/$defs/B"}, False],
            },
            id="merged-allof-direct-false",
        ),
        pytest.param(
            {
                "title": "Payload",
                "$defs": {
                    "A": {"type": "object", "properties": {"id": {"type": "string"}}},
                    "Never": False,
                },
                "allOf": [{"$ref": "#/$defs/A"}, {"$ref": "#/$defs/Never"}],
            },
            id="merged-allof-ref-false",
        ),
        pytest.param(
            {"title": "Payload", "$defs": {"Never": False}, "allOf": [{"$ref": "#/$defs/Never"}]},
            id="single-allof-ref-false",
        ),
        pytest.param(
            {
                "title": "Payload",
                "$defs": {"Never": False},
                "allOf": [{"$ref": "#/$defs/Never"}, {"type": "object", "properties": {"name": {"type": "string"}}}],
            },
            id="allof-item-ref-false",
        ),
        pytest.param({"title": "Payload", "allOf": [{"allOf": [False]}]}, id="nested-allof-direct-false"),
        pytest.param(
            {"title": "Payload", "type": "object", "properties": {"value": {"allOf": [False]}}},
            id="property-allof-direct-false",
        ),
    ],
)
def test_main_jsonschema_false_schema_errors(schema: dict[str, object], output_file: Path) -> None:
    """Test boolean false branches are rejected whenever they make a schema unsatisfiable."""
    _run_jsonschema_dict(
        schema,
        output_file,
        expected_exit=Exit.ERROR,
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_anyof_ref_false_schema(output_file: Path) -> None:
    """Test anyOf skips local $ref branches that resolve to boolean false schemas."""
    expected = (
        "# generated by datamodel-codegen:\n"
        "#   filename:  output.json\n\n"
        "from __future__ import annotations\n\n"
        "from typing import Any\n\n"
        "from pydantic import Field, RootModel\n\n\n"
        "class Payload(RootModel[str]):\n"
        "    root: str = Field(..., title='Payload')\n\n\n"
        "class Never(RootModel[Any]):\n"
        "    root: Any\n"
    )
    _run_jsonschema_dict(
        {
            "title": "Payload",
            "$defs": {"Never": False},
            "anyOf": [{"$ref": "#/$defs/Never"}, {"type": "string"}],
        },
        output_file,
        expected_output=expected,
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_empty_anyof_uses_any(output_file: Path) -> None:
    """Test empty anyOf remains an unconstrained root type."""
    expected = (
        "# generated by datamodel-codegen:\n"
        "#   filename:  output.json\n\n"
        "from __future__ import annotations\n\n"
        "from typing import Any\n\n"
        "from pydantic import Field, RootModel\n\n\n"
        "class Payload(RootModel[Any]):\n"
        "    root: Any = Field(..., title='Payload')\n"
    )
    _run_jsonschema_dict(
        {"title": "Payload", "anyOf": []},
        output_file,
        expected_output=expected,
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_discriminated_oneof_allof_cycle_use_union_operator(output_file: Path) -> None:
    """Test forward refs in RootModel unions fall back to Union[] when PEP 604 would fail."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "jsonschema_discriminated_oneof_allof_cycle.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="jsonschema_discriminated_oneof_allof_cycle.py",
        extra_args=["--use-union-operator"],
    )


def test_main_jsonschema_oneof_with_true_schema(output_file: Path) -> None:
    """Test a single true schema inside oneOf is accepted as an unconstrained branch."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_with_true_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_with_true_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_allof_with_true_schema(output_file: Path) -> None:
    """Test true schemas inside allOf are accepted as neutral branches."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_with_true_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allof_with_true_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_ref_to_true_schema(output_file: Path) -> None:
    """Test refs to boolean true schemas are accepted as unconstrained models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_to_true_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_to_true_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )


def test_main_jsonschema_allof_ref_true_schema(output_file: Path) -> None:
    """Test allOf refs to boolean true schemas are accepted as unconstrained models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_ref_true_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allof_ref_true_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )


def test_main_jsonschema_oneof_const_enum_infer_type(output_file: Path) -> None:
    """Test oneOf with const values and inferred type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_infer_type.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_infer_type.py",
    )


def test_main_jsonschema_oneof_const_enum_bool(output_file: Path) -> None:
    """Test oneOf with boolean const values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_bool.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_bool.py",
    )


def test_main_jsonschema_oneof_const_enum_float(output_file: Path) -> None:
    """Test oneOf with float const values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_float.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_float.py",
    )


def test_main_jsonschema_anyof_const_enum_nested(output_file: Path) -> None:
    """Test nested anyOf with const values in properties and array items."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "anyof_const_enum_nested.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="anyof_const_enum_nested.py",
    )


def test_main_jsonschema_anyof_const_enum_descriptions(output_file: Path) -> None:
    """Test anyOf const enum member descriptions are preserved."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "anyof_const_enum_descriptions.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="anyof_const_enum_descriptions.py",
        extra_args=["--use-field-description"],
    )


def test_main_jsonschema_anyof_const_enum_nested_literal(output_file: Path) -> None:
    """Test nested anyOf const with --enum-field-as-literal all."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "anyof_const_enum_nested.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="anyof_const_enum_nested_literal.py",
        extra_args=["--enum-field-as-literal", "all"],
    )


def test_main_jsonschema_oneof_const_mixed_with_ref(output_file: Path) -> None:
    """Test oneOf with mixed const and $ref falls back to Union."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_mixed_with_ref.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_mixed_with_ref.py",
    )


def test_main_jsonschema_oneof_const_with_properties(output_file: Path) -> None:
    """Test oneOf with const and properties falls back to Union."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_with_properties.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_with_properties.py",
    )


def test_main_jsonschema_oneof_const_enum_type_list_no_null(output_file: Path) -> None:
    """Test oneOf const with type list without null."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_type_list_no_null.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_type_list_no_null.py",
    )


def test_main_jsonschema_oneof_const_enum_object(output_file: Path) -> None:
    """Test oneOf with object const values for type inference coverage."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "oneof_const_enum_object.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="oneof_const_enum_object.py",
    )


def test_main_jsonschema_json_pointer_array(output_file: Path) -> None:
    """Test JSON pointer with arrays."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "json_pointer_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="json_pointer_array.py",
    )


@pytest.mark.filterwarnings("error")
def test_main_disable_warnings_config(capsys: pytest.CaptureFixture[str], output_file: Path) -> None:
    """Test disable warnings configuration."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        capsys=capsys,
        assert_no_stderr=True,
        input_file_type="jsonschema",
        extra_args=[
            "--use-union-operator",
            "--target-python-version",
            f"3.{MIN_VERSION}",
            "--disable-warnings",
        ],
    )


@pytest.mark.cli_doc(
    options=["--disable-warnings"],
    option_description="""Suppress warning messages during code generation.

The --disable-warnings option silences all warning messages that the generator
might emit during processing (e.g., about unsupported features, ambiguous schemas,
or potential issues). Useful for clean output in CI/CD pipelines.""",
    input_schema="jsonschema/all_of_with_object.json",
    cli_args=["--disable-warnings"],
    golden_output="main/jsonschema/all_of_with_object.py",
)
@pytest.mark.filterwarnings("error")
def test_main_disable_warnings(capsys: pytest.CaptureFixture[str], output_file: Path) -> None:
    """Suppress warning messages during code generation.

    The --disable-warnings option silences all warning messages that the generator
    might emit during processing (e.g., about unsupported features, ambiguous schemas,
    or potential issues). Useful for clean output in CI/CD pipelines.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "all_of_with_object.json",
        output_path=output_file,
        capsys=capsys,
        assert_no_stderr=True,
        input_file_type="jsonschema",
        extra_args=["--disable-warnings"],
    )


@LEGACY_BLACK_SKIP
def test_main_jsonschema_pattern_properties_by_reference(output_file: Path) -> None:
    """Test pattern properties by reference."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern_properties_by_reference.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern_properties_by_reference.py",
    )


def test_main_jsonschema_copy_deep_pattern_properties(output_file: Path) -> None:
    """Test copy_deep properly preserves dict_key from patternProperties during allOf inheritance."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "copy_deep_pattern_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="copy_deep_pattern_properties.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--read-only-write-only-model-type",
            "all",
        ],
    )


def test_main_jsonschema_pattern_properties_boolean(output_file: Path) -> None:
    """Test patternProperties with boolean values (true/false) as allowed in JSON Schema 2020-12."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern_properties_boolean.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern_properties_boolean.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )


def test_main_jsonschema_pattern_properties_merge(output_file: Path) -> None:
    """Test merging multiple patternProperties with same value type into single regex pattern."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern_properties_merge.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern_properties_merge.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )


def test_main_jsonschema_pattern_properties_all_false(output_file: Path) -> None:
    """Test patternProperties with all false values fall back to object type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern_properties_all_false.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern_properties_all_false.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_pattern_properties_all_false_closed(output_file: Path) -> None:
    """Test all false patternProperties with additionalProperties false generates empty dict constraint."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern_properties_all_false_closed.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern_properties_all_false_closed.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_pattern(output_file: Path) -> None:
    """Test propertyNames with pattern constraint generates dict with constr key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_pattern.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_pattern.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_pattern_field_constraints(output_file: Path) -> None:
    """Test propertyNames pattern with field_constraints preserves constr key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_pattern.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_pattern_field_constraints.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-constraints",
        ],
    )


def test_main_jsonschema_property_names_pattern_properties_intersection(output_file: Path) -> None:
    """Test propertyNames constraints also apply to patternProperties keys."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_pattern_properties_intersection.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_pattern_properties_intersection.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="property_names_pattern_properties_intersection",
        model_name="PropertyNamesPatternPropertiesIntersection",
        valid_json='{"scores":{"x_ok":1}}',
        invalid_json='{"scores":{"x_":1}}',
        expected_error_type="string_too_short",
        expected_attribute_path=("scores",),
        expected_attribute_value={"x_ok": 1},
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="property_names_pattern_properties_intersection",
        model_name="PropertyNamesPatternPropertiesIntersection",
        valid_json='{"scores":{"x_ok":1}}',
        invalid_json='{"scores":{"y_ok":1}}',
        expected_error_type="string_pattern_mismatch",
    )


def test_main_jsonschema_property_names_ref_pattern_properties_intersection(output_file: Path) -> None:
    """Test referenced propertyNames constraints also apply to patternProperties keys."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_ref_pattern_properties_intersection.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_ref_pattern_properties_intersection.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="property_names_ref_pattern_properties_intersection",
        model_name="PropertyNamesRefPatternPropertiesIntersection",
        valid_json='{"scores":{"x_ok":1}}',
        invalid_json='{"scores":{"x_":1}}',
        expected_error_type="string_too_short",
        expected_attribute_path=("scores",),
        expected_attribute_value={"x_ok": 1},
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="property_names_ref_pattern_properties_intersection",
        model_name="PropertyNamesRefPatternPropertiesIntersection",
        valid_json='{"scores":{"x_ok":1}}',
        invalid_json='{"scores":{"y_ok":1}}',
        expected_error_type="string_pattern_mismatch",
    )


def test_main_jsonschema_property_names_enum(output_file: Path) -> None:
    """Test propertyNames with enum constraint generates dict with Literal key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_enum.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_enum_mixed(output_file: Path) -> None:
    """Test propertyNames enum ignores non-string values for JSON object keys."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_enum_mixed.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_enum_mixed.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="property_names_enum_mixed",
        model_name="PropertyNamesEnumMixed",
        valid_json='{"ok":"yes"}',
        invalid_json='{"1":"bad"}',
        expected_error_type="literal_error",
        expected_attribute_path=("root",),
        expected_attribute_value={"ok": "yes"},
    )


def test_main_jsonschema_property_names_const(output_file: Path) -> None:
    """Test propertyNames with const constraint generates dict with Literal key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_const.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_const.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_const_non_string(output_file: Path) -> None:
    """Test non-string propertyNames const rejects every JSON object key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_const_non_string.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_const_non_string.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_true(output_file: Path) -> None:
    """Test boolean true propertyNames is accepted as unconstrained keys."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_true.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_true.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_false(output_file: Path) -> None:
    """Test boolean false propertyNames constrains dicts to empty keys."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_false.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_false.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_false_without_type(output_file: Path) -> None:
    """Test boolean false propertyNames is not skipped when type is omitted."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_false_without_type.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_false_without_type.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_closed_object(output_file: Path) -> None:
    """Test propertyNames with additionalProperties false rejects every undeclared key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_closed_object.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_closed_object.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_additional_properties_schema_with_properties(output_file: Path) -> None:
    """Test additionalProperties schema validates typed extra values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_schema_with_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_schema_with_properties.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_schema_with_properties",
        model_name="KnownAndExtra",
        valid_json='{"name":"known","size":1}',
        invalid_json='{"name":"known","size":[]}',
        expected_error_type="int_type",
        expected_attribute_path=("__pydantic_extra__",),
        expected_attribute_value={"size": 1},
    )


@BLACK_PY314_SKIP
def test_main_jsonschema_additional_properties_schema_with_properties_target_python_314(output_file: Path) -> None:
    """Test Python 3.14 target keeps typed extras as native deferred annotations."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_schema_with_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_schema_with_properties_py314.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.14",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_schema_with_properties_py314",
        model_name="KnownAndExtra",
        valid_json='{"name":"known","size":1}',
        invalid_json='{"name":"known","size":[]}',
        expected_error_type="int_type",
        expected_attribute_path=("__pydantic_extra__",),
        expected_attribute_value={"size": 1},
    )


def test_main_jsonschema_additional_properties_schema_with_allof_properties(output_file: Path) -> None:
    """Test allOf object schemas validate typed extra values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_schema_with_allof_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_schema_with_allof_properties.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_schema_with_allof_properties",
        model_name="AllOfKnownAndExtra",
        valid_json='{"name":"known","size":1}',
        invalid_json='{"name":"known","size":[]}',
        expected_error_type="int_type",
        expected_attribute_path=("__pydantic_extra__",),
        expected_attribute_value={"size": 1},
    )


def test_main_jsonschema_additional_properties_schema_with_allof_ref(output_file: Path) -> None:
    """Test allOf inherited schemas validate typed extra values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_schema_with_allof_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_schema_with_allof_ref.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_schema_with_allof_ref",
        model_name="AllOfInheritedAndExtra",
        valid_json='{"name":"known","size":1}',
        invalid_json='{"name":"known","size":[]}',
        expected_error_type="int_type",
        expected_attribute_path=("__pydantic_extra__",),
        expected_attribute_value={"size": 1},
    )


def test_main_jsonschema_additional_properties_enum_schema_with_properties(output_file: Path) -> None:
    """Test additionalProperties enum schema validates typed extra values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_enum_schema_with_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_enum_schema_with_properties.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_enum_schema_with_properties",
        model_name="KnownAndEnumExtra",
        valid_json='{"name":"known","color":"red"}',
        invalid_json='{"name":"known","color":"green"}',
        expected_error_type="enum",
        expected_attribute_path=("__pydantic_extra__", "color", "value"),
        expected_attribute_value="red",
    )


def test_main_jsonschema_additional_properties_const_schema_with_properties(output_file: Path) -> None:
    """Test additionalProperties const schema validates typed extra values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_const_schema_with_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_const_schema_with_properties.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_const_schema_with_properties",
        model_name="KnownAndConstExtra",
        valid_json='{"name":"known","color":"red"}',
        invalid_json='{"name":"known","color":"blue"}',
        expected_error_type="literal_error",
        expected_attribute_path=("__pydantic_extra__",),
        expected_attribute_value={"color": "red"},
    )


def test_main_jsonschema_additional_properties_object_schema_with_properties(output_file: Path) -> None:
    """Test additionalProperties object schema validates typed extra values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_object_schema_with_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_object_schema_with_properties.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_object_schema_with_properties",
        model_name="KnownAndObjectExtra",
        valid_json='{"name":"known","payload":{"count":1}}',
        invalid_json='{"name":"known","payload":{"count":[]}}',
        expected_error_type="int_type",
        expected_attribute_path=("__pydantic_extra__", "payload", "count"),
        expected_attribute_value=1,
    )


def test_main_jsonschema_additional_properties_array_schema_with_properties(output_file: Path) -> None:
    """Test additionalProperties array schema validates typed extra values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_array_schema_with_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_array_schema_with_properties.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_array_schema_with_properties",
        model_name="KnownAndArrayExtra",
        valid_json='{"name":"known","items":["a",1]}',
        invalid_json='{"name":"known","items":{}}',
        expected_error_type="list_type",
        expected_attribute_path=("__pydantic_extra__",),
        expected_attribute_value={"items": ["a", 1]},
    )


def test_main_jsonschema_additional_properties_ref_schema_with_properties(output_file: Path) -> None:
    """Test additionalProperties ref schema validates typed extra values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_ref_schema_with_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_ref_schema_with_properties.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_ref_schema_with_properties",
        model_name="KnownAndRefExtra",
        valid_json='{"name":"known","payload":{"count":1}}',
        invalid_json='{"name":"known","payload":{"count":[]}}',
        expected_error_type="int_type",
        expected_attribute_path=("__pydantic_extra__", "payload", "count"),
        expected_attribute_value=1,
    )


def test_main_jsonschema_additional_properties_ref_schema_with_keywords_and_properties(output_file: Path) -> None:
    """Test additionalProperties ref schema with sibling keywords validates typed extra values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_ref_schema_with_keywords_and_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_ref_schema_with_keywords_and_properties.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_ref_schema_with_keywords_and_properties",
        model_name="KnownAndRefExtra",
        valid_json='{"name":"known","payload":{"count":1,"enabled":true}}',
        invalid_json='{"name":"known","payload":{"count":1,"enabled":[]}}',
        expected_error_type="bool_type",
        expected_attribute_path=("__pydantic_extra__", "payload", "enabled"),
        expected_attribute_value=True,
    )


def test_main_jsonschema_additional_properties_oneof_schema_with_required_property(output_file: Path) -> None:
    """Test typed extras work with required declared properties."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_oneof_schema_with_required_property.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_oneof_schema_with_required_property.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_oneof_schema_with_required_property",
        model_name="RCloneConfig",
        valid_json='{"type":"s3","size":1}',
        invalid_json='{"type":"s3","items":[]}',
        expected_error_type="float_type",
        expected_attribute_path=("__pydantic_extra__",),
        expected_attribute_value={"size": 1.0},
    )


def test_main_jsonschema_additional_properties_anyof_schema_with_required_property(output_file: Path) -> None:
    """Test typed extras include nullable anyOf branches."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_anyof_schema_with_required_property.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_anyof_schema_with_required_property.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_anyof_schema_with_required_property",
        model_name="AnyOfConfig",
        valid_json='{"type":"s3","size":1}',
        invalid_json='{"type":"s3","items":[]}',
        expected_error_type="int_type",
        expected_attribute_path=("__pydantic_extra__",),
        expected_attribute_value={"size": 1},
    )


def test_main_jsonschema_additional_properties_anyof_schema_without_nullable_property(output_file: Path) -> None:
    """Test typed extras keep non-nullable anyOf branches unchanged."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_anyof_schema_without_nullable_property.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_anyof_schema_without_nullable_property.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_anyof_schema_without_nullable_property",
        model_name="AnyOfNonNullableConfig",
        valid_json='{"type":"s3","size":1}',
        invalid_json='{"type":"s3","value":null}',
        expected_error_type="int_type",
        expected_attribute_path=("__pydantic_extra__",),
        expected_attribute_value={"size": 1},
    )


def test_main_jsonschema_additional_properties_self_ref(output_file: Path) -> None:
    """Test typed extras can reference their containing model."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_properties_self_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_properties_self_ref.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="additional_properties_self_ref",
        model_name="Node",
        valid_json='{"name":"root","child":{"name":"leaf"}}',
        invalid_json='{"name":"root","child":{"name":"leaf","bad":1}}',
        expected_error_type="model_type",
        expected_attribute_path=("__pydantic_extra__", "child", "name"),
        expected_attribute_value="leaf",
    )


def test_main_jsonschema_property_names_type_non_string(output_file: Path) -> None:
    """Test non-string propertyNames type rejects every JSON object key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_type_non_string.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_type_non_string.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_count_constraints(output_file: Path) -> None:
    """Test minProperties/maxProperties generate dict length constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_count_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_count_constraints.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_object_max_properties_zero(output_file: Path) -> None:
    """Test empty object schemas with maxProperties generate dict length constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "object_max_properties_zero.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="object_max_properties_zero.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="object_max_properties_zero",
        model_name="ObjectMaxPropertiesZero",
        valid_json="{}",
        invalid_json='{"x":1}',
        expected_error_type="too_long",
        expected_attribute_path=("root",),
        expected_attribute_value={},
    )


def test_main_jsonschema_object_property_count_additional_true(output_file: Path) -> None:
    """Test object property counts with additionalProperties true generate dict length constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "object_property_count_additional_true.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="object_property_count_additional_true.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="object_property_count_additional_true",
        model_name="ObjectPropertyCountAdditionalTrue",
        valid_json='{"a":1}',
        invalid_json="{}",
        expected_error_type="too_short",
        expected_attribute_path=("root",),
        expected_attribute_value={"a": 1},
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="object_property_count_additional_true",
        model_name="ObjectPropertyCountAdditionalTrue",
        valid_json='{"a":1,"b":2}',
        invalid_json='{"a":1,"b":2,"c":3}',
        expected_error_type="too_long",
        expected_attribute_path=("root",),
        expected_attribute_value={"a": 1, "b": 2},
    )


def test_main_jsonschema_object_impossible_property_count(output_file: Path) -> None:
    """Test impossible object property count constraints reject both empty and populated objects."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "object_impossible_property_count.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="object_impossible_property_count.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_invalid(
        output_file,
        module_name="object_impossible_property_count",
        model_name="ObjectImpossiblePropertyCount",
        invalid_json="{}",
        expected_error_type="too_short",
    )
    assert_generated_model_json_invalid(
        output_file,
        module_name="object_impossible_property_count",
        model_name="ObjectImpossiblePropertyCount",
        invalid_json='{"a":1}',
        expected_error_type="too_long",
    )


def test_main_jsonschema_property_names_min_max_length(output_file: Path) -> None:
    """Test propertyNames with minLength/maxLength constraints generates dict with constr key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_min_max_length.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_min_max_length.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_no_additional(output_file: Path) -> None:
    """Test propertyNames without additionalProperties generates dict with Any value type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_no_additional.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_no_additional.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_nested(output_file: Path) -> None:
    """Test propertyNames in nested object property."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_nested.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_nested.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_enum_integers(output_file: Path) -> None:
    """Test propertyNames with only non-string enum values rejects every JSON object key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_enum_integers.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_enum_integers.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_allof_ref(output_file: Path) -> None:
    """Test propertyNames in allOf with $ref."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_allof_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_allof_ref.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_property_names_ref_enum(output_file: Path) -> None:
    """Test propertyNames with $ref to enum definition uses enum type as dict key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_ref_enum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_ref_enum.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )


def test_main_jsonschema_property_names_ref_with_constraints(output_file: Path) -> None:
    """Test propertyNames $ref with sibling constraints merges both key constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_ref_with_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_ref_with_constraints.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="property_names_ref_with_constraints",
        model_name="PropertyNamesRefWithConstraints",
        valid_json='{"x_ok":1}',
        invalid_json='{"x_":1}',
        expected_error_type="string_too_short",
        expected_attribute_path=("root",),
        expected_attribute_value={"x_ok": 1},
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="property_names_ref_with_constraints",
        model_name="PropertyNamesRefWithConstraints",
        valid_json='{"x_ok":1}',
        invalid_json='{"y_ok":1}',
        expected_error_type="string_pattern_mismatch",
    )


def test_main_jsonschema_property_names_anyof_ref(output_file: Path) -> None:
    """Test propertyNames with anyOf containing $refs uses union of enum types as dict key."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "property_names_anyof_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="property_names_anyof_ref.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        force_exec_validation=True,
    )


def test_main_dataclass_field(output_file: Path) -> None:
    """Test dataclass field generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "user.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "dataclasses.dataclass"],
    )


def test_main_dataclass_deprecated_model(output_file: Path) -> None:
    """Test dataclass generation with deprecated schema metadata."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "deprecated_dataclass.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="deprecated_dataclass.py",
        extra_args=["--output-model-type", "dataclasses.dataclass"],
    )


def test_main_dataclass_deprecated_model_preserves_existing_decorator(output_file: Path) -> None:
    """Test deprecated dataclass generation keeps the import with an existing decorator."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "deprecated_dataclass.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="deprecated_dataclass.py",
        extra_args=[
            "--output-model-type",
            "dataclasses.dataclass",
            "--class-decorators",
            "@deprecated('LegacyUser is deprecated.')",
        ],
    )


def test_main_dataclass_deprecated_model_with_other_decorator(output_file: Path) -> None:
    """Test deprecated dataclass generation adds deprecation alongside other decorators."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "deprecated_dataclass.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="deprecated_dataclass_with_other_decorator.py",
        extra_args=[
            "--output-model-type",
            "dataclasses.dataclass",
            "--class-decorators",
            "@some_decorator",
            "--additional-imports",
            "some_module.some_decorator",
        ],
    )


def test_main_pydantic_v2_dataclass_deprecated_model(output_file: Path) -> None:
    """Test pydantic v2 dataclass generation with deprecated schema metadata."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "deprecated_dataclass.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="deprecated_pydantic_v2_dataclass.py",
        extra_args=["--output-model-type", "pydantic_v2.dataclass"],
    )


def test_main_pydantic_v2_dataclass_deprecated_model_with_other_decorator(output_file: Path) -> None:
    """Test pydantic v2 dataclass generation adds deprecation alongside other decorators."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "deprecated_dataclass.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="deprecated_pydantic_v2_dataclass_with_other_decorator.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.dataclass",
            "--class-decorators",
            "@some_decorator",
            "--additional-imports",
            "some_module.some_decorator",
        ],
    )


@pytest.mark.skipif(
    not is_supported_in_black(PythonVersion.PY_312),
    reason="Black does not support Python 3.12",
)
def test_main_dataclass_field_py312(output_file: Path) -> None:
    """Test dataclass field generation with Python 3.12 type statement."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "user.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--output-model-type",
            "dataclasses.dataclass",
            "--target-python-version",
            "3.12",
        ],
    )


def test_main_jsonschema_enum_root_literal(output_file: Path) -> None:
    """Test enum root with literal type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_in_root" / "enum_in_root.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_in_enum.py",
        extra_args=[
            "--use-schema-description",
            "--use-title-as-name",
            "--field-constraints",
            "--target-python-version",
            "3.10",
            "--allow-population-by-field-name",
            "--strip-default-none",
            "--use-default",
            "--enum-field-as-literal",
            "all",
            "--snake-case-field",
            "--collapse-root-models",
        ],
    )


def test_main_nullable_any_of(output_file: Path) -> None:
    """Test nullable anyOf schemas."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nullable_any_of.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--field-constraints"],
    )


def test_main_nullable_any_of_use_union_operator(output_file: Path) -> None:
    """Test nullable anyOf with union operator."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nullable_any_of.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--field-constraints", "--use-union-operator"],
    )


def test_main_nested_all_of(output_file: Path) -> None:
    """Test nested allOf schemas."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nested_all_of.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_all_of_any_of(output_dir: Path) -> None:
    """Test combination of allOf and anyOf."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "all_of_any_of",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "all_of_any_of",
        input_file_type="jsonschema",
    )


@pytest.mark.cli_doc(
    options=["--use-double-quotes"],
    option_description="""Use double quotes for string literals in generated code.

The --use-double-quotes option formats all string literals in the generated
Python code with double quotes instead of the default single quotes. This
helps maintain consistency with codebases that prefer double-quote formatting.""",
    input_schema="jsonschema/all_of_any_of_base_class_ref.json",
    cli_args=["--use-double-quotes"],
    golden_output="main/jsonschema/all_of_any_of_base_class_ref.py",
)
def test_main_all_of_any_of_base_class_ref(output_file: Path) -> None:
    """Use double quotes for string literals in generated code.

    The --use-double-quotes option formats all string literals in the generated
    Python code with double quotes instead of the default single quotes. This
    helps maintain consistency with codebases that prefer double-quote formatting.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "all_of_any_of_base_class_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--snake-case-field", "--use-double-quotes", "--reuse-model"],
    )


def test_main_all_of_one_of(output_dir: Path) -> None:
    """Test combination of allOf and oneOf."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "all_of_one_of",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "all_of_one_of",
        input_file_type="jsonschema",
    )


def test_main_top_level_allof_oneof(output_dir: Path) -> None:
    """Test top-level oneOf is preserved when sibling allOf is present."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "top_level_allof_oneof",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "top_level_allof_oneof",
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.10",
        ],
        force_exec_validation=True,
    )


def test_main_null(output_file: Path) -> None:
    """Test null type handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "null.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_type_array_only_null(output_file: Path) -> None:
    """Test a JSON Schema type array containing only null."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_array_only_null.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_const_null(output_file: Path) -> None:
    """Test null const handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "const_null.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_const_complex_values(output_file: Path) -> None:
    """Test const values beyond string, integer, and boolean literals."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "const_complex_values.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_const_optional_values(output_file: Path) -> None:
    """Do not treat optional non-literal const values as schema defaults."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "const_optional_values.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_const_optional_primitive(output_file: Path) -> None:
    """Optional primitive const fields must not inject the const as a default.

    A single-value ``const`` on a bool/int/str property that is not ``required`` and
    has no schema-defined default follows the normal optional path
    (``Literal[...] | None = None``) so an omitted field stays unset instead of being
    silently filled with the const value. A required const keeps its no-default literal.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "const_optional_primitive.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


@pytest.mark.skipif(
    version.parse(black.__version__) < version.parse("23.3.0"),
    reason="Require Black version 23.3.0 or later ",
)
def test_main_typed_dict_special_field_name_with_inheritance_model(output_file: Path) -> None:
    """Test TypedDict special field names with inheritance."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "special_field_name_with_inheritance_model.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict", "--target-python-version", "3.11"],
    )


@pytest.mark.skipif(
    version.parse(black.__version__) < version.parse("23.3.0"),
    reason="Require Black version 23.3.0 or later ",
)
def test_main_typed_dict_not_required_nullable(output_file: Path) -> None:
    """Test main function writing to TypedDict, with combos of Optional/NotRequired."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "not_required_nullable.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict", "--target-python-version", "3.11"],
    )


def test_main_typed_dict_const(output_file: Path) -> None:
    """Test main function writing to TypedDict with const fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "const.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict", "--target-python-version", "3.10"],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] < "24",
    reason="Installed black doesn't support the new style",
)
def test_main_typed_dict_additional_properties(output_file: Path) -> None:
    """Test main function writing to TypedDict with additional properties, and no other fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "string_dict.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_with_only_additional_properties.py",
        extra_args=["--output-model-type", "typing.TypedDict", "--target-python-version", "3.11"],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.11",
)
def test_main_typed_dict_enum_field_as_literal_none(output_file: Path) -> None:
    """Test TypedDict with enum_field_as_literal=none."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_literal_typed_dict.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_enum_literal_none.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--enum-field-as-literal",
            "none",
            "--target-python-version",
            "3.11",
        ],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.11",
)
def test_main_typed_dict_enum_field_as_literal_one(output_file: Path) -> None:
    """Test TypedDict with enum_field_as_literal=one."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_literal_typed_dict.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_enum_literal_one.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--enum-field-as-literal",
            "one",
            "--target-python-version",
            "3.11",
        ],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.11",
)
def test_main_typed_dict_enum_field_as_literal_all(output_file: Path) -> None:
    """Test TypedDict with enum_field_as_literal=all."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_literal_typed_dict.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_enum_literal_all.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--enum-field-as-literal",
            "all",
            "--target-python-version",
            "3.11",
        ],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.11",
)
def test_main_typed_dict_nullable_enum_literal(output_file: Path) -> None:
    """Test TypedDict with nullable enum literals generates | None correctly."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nullable_enum_literal_typed_dict.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_nullable_enum_literal.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.11",
        ],
    )


@pytest.mark.cli_doc(
    options=["--use-closed-typed-dict"],
    option_description="""Generate TypedDict with PEP 728 closed/extra_items (default: enabled).

When enabled (default), generates TypedDict with PEP 728 `closed=True` or `extra_items`
parameters based on `additionalProperties` constraints in the schema.""",
    input_schema="jsonschema/typed_dict_closed.json",
    cli_args=["--output-model-type", "typing.TypedDict", "--use-closed-typed-dict"],
    golden_output="jsonschema/typed_dict_closed.py",
)
@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.10",
)
def test_main_typed_dict_closed(output_file: Path) -> None:
    """Test TypedDict with additionalProperties: false generates closed=True (PEP 728)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_closed.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_closed.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.10",
)
def test_main_typed_dict_extra_items(output_file: Path) -> None:
    """Test TypedDict with additionalProperties type generates extra_items (PEP 728)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_extra_items.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_extra_items.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.10",
)
def test_main_typed_dict_allof_extra_items(output_file: Path) -> None:
    """Test TypedDict extra_items can infer an allOf reference type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_allof_extra_items.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_allof_extra_items.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.10",
)
def test_main_typed_dict_allof_constraint_extra_items(output_file: Path) -> None:
    """Test TypedDict extra_items warns when allOf constraints are ignored."""
    with pytest.warns(UserWarning, match=r"allOf combines \$ref with 1 constraint"):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_allof_constraint_extra_items.json",
            output_path=output_file,
            input_file_type=None,
            assert_func=assert_file_content,
            expected_file="typed_dict_allof_constraint_extra_items.py",
            extra_args=[
                "--output-model-type",
                "typing.TypedDict",
                "--target-python-version",
                "3.10",
            ],
        )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.10",
)
def test_main_typed_dict_nested_allof_extra_items(output_file: Path) -> None:
    """Test TypedDict extra_items keeps dependencies from nested allOf refs."""
    expected = (
        "# generated by datamodel-codegen:\n"
        "#   filename:  output.json\n\n"
        "from __future__ import annotations\n\n"
        "from typing_extensions import NotRequired, TypedDict\n\n\n"
        "class BaseExtra(TypedDict):\n"
        "    id: NotRequired[int]\n\n\n"
        "class Payload(TypedDict, extra_items='BaseExtra'):\n"
        "    name: NotRequired[str]\n"
    )
    _run_jsonschema_dict(
        {
            "title": "Payload",
            "type": "object",
            "$defs": {
                "BaseExtra": {
                    "type": "object",
                    "properties": {"id": {"type": "integer"}},
                },
            },
            "properties": {"name": {"type": "string"}},
            "additionalProperties": {
                "allOf": [
                    {
                        "allOf": [
                            {"$ref": "#/$defs/BaseExtra"},
                        ],
                    },
                    {
                        "allOf": [
                            {"$ref": "#/$defs/BaseExtra"},
                        ],
                    },
                ],
            },
        },
        output_file,
        expected_output=expected,
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.10",
)
def test_main_typed_dict_false_allof_extra_items(output_file: Path) -> None:
    """Test TypedDict extra_items ignores unsatisfiable allOf schemas."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_false_allof_extra_items.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_false_allof_extra_items.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.10",
        ],
    )


def test_main_typed_dict_datetime_default_uses_str(output_file: Path) -> None:
    """Test that TypedDict output uses str for datetime fields by default."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "datetime.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_datetime_default_uses_str.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
        ],
    )


def test_main_typed_dict_with_datetime_class_uses_datetime(output_file: Path) -> None:
    """Test that TypedDict output uses datetime.datetime when requested."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "datetime.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_datetime_with_datetime_class.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--output-datetime-class",
            "datetime",
        ],
    )


def test_main_typed_dict_with_non_datetime_class_raises_error(capsys: pytest.CaptureFixture, output_file: Path) -> None:
    """Test that CLI TypedDict output rejects pydantic-specific datetime types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "datetime.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--output-datetime-class",
            "AwareDatetime",
        ],
        capsys=capsys,
        expected_stderr_contains=(
            '`--output-datetime-class` only allows "datetime" for `--output-model-type` typing.TypedDict'
        ),
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.10",
)
def test_main_typed_dict_mixed_closed_no_duplicate_imports(output_file: Path) -> None:
    """Test that mixing closed and open TypedDicts in the same file does not produce duplicate TypedDict imports."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_mixed_closed.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_mixed_closed.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.cli_doc(
    options=["--no-use-closed-typed-dict"],
    option_description="""Disable PEP 728 TypedDict closed/extra_items generation.

Use this option for compatibility with type checkers that don't yet support PEP 728
(e.g., mypy). TypedDict will be generated without `closed=True` or `extra_items`.""",
    input_schema="jsonschema/typed_dict_closed.json",
    cli_args=["--output-model-type", "typing.TypedDict", "--no-use-closed-typed-dict"],
    golden_output="jsonschema/typed_dict_no_closed.py",
)
@pytest.mark.skipif(
    black.__version__.split(".")[0] == "22",
    reason="Installed black doesn't support Python version 3.10",
)
def test_main_typed_dict_no_closed(output_file: Path) -> None:
    """Test --no-use-closed-typed-dict disables PEP 728 closed/extra_items generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_closed.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="typed_dict_no_closed.py",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--target-python-version",
            "3.10",
            "--no-use-closed-typed-dict",
        ],
    )


@pytest.mark.cli_doc(
    options=["--enum-field-as-literal-map"],
    option_description="""Override enum/literal generation per-field via JSON mapping.

The `--enum-field-as-literal-map` option allows per-field control over whether
to generate Literal types or Enum classes. Overrides `--enum-field-as-literal`.

You can pass the mapping either inline as JSON or as a path to a JSON file.""",
    input_schema="jsonschema/enum_field_as_literal_map.json",
    cli_args=["--enum-field-as-literal-map", '{"status": "literal"}'],
    golden_output="jsonschema/enum_field_as_literal_map.py",
)
def test_main_enum_field_as_literal_map(output_file: Path) -> None:
    """Override enum/literal generation per-field via JSON mapping.

    The `--enum-field-as-literal-map` option allows per-field control over whether
    to generate Literal types or Enum classes. Overrides `--enum-field-as-literal`.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="enum_field_as_literal_map.py",
        extra_args=[
            "--enum-field-as-literal-map",
            '{"status": "literal"}',
        ],
    )


def test_main_enum_field_as_literal_map_from_file(output_file: Path, tmp_path: Path) -> None:
    """Test enum_field_as_literal_map loaded from a JSON file."""
    mapping_path = tmp_path / "enum_field_as_literal_map.json"
    mapping_path.write_text(json.dumps({"status": "literal"}), encoding="utf-8")
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="enum_field_as_literal_map.py",
        extra_args=["--enum-field-as-literal-map", str(mapping_path)],
    )


def test_main_enum_field_as_literal_map_long_json(output_file: Path) -> None:
    """Test enum_field_as_literal_map with very long json string."""
    long_inline_mapping = '{"status": "literal",' + ",".join(f'"unused_field_{i}": "enum"' for i in range(100)) + "}"
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="enum_field_as_literal_map.py",
        extra_args=[
            "--enum-field-as-literal-map",
            long_inline_mapping,
        ],
    )


def test_main_enum_field_as_literal_map_override_global(output_file: Path) -> None:
    """Test --enum-field-as-literal-map overrides global --enum-field-as-literal."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="enum_field_as_literal_map_override.py",
        extra_args=[
            "--enum-field-as-literal",
            "all",
            "--enum-field-as-literal-map",
            '{"priority": "enum"}',
        ],
    )


def test_main_enum_field_as_literal_map_invalid_json_file(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test invalid JSON file passed to --enum-field-as-literal-map."""
    mapping_path = tmp_path / "enum_field_as_literal_map.json"
    mapping_path.write_text("{invalid json}", encoding="utf-8")
    assert_run_main_with_args_error(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"),
            "--output",
            str(tmp_path / "output.py"),
            "--enum-field-as-literal-map",
            str(mapping_path),
        ],
        capsys,
        "Invalid JSON:",
    )


def test_main_enum_field_as_literal_map_invalid_encoding_json_file(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test invalid-encoding JSON file passed to --enum-field-as-literal-map."""
    mapping_path = tmp_path / "enum_field_as_literal_map.json"
    mapping_path.write_bytes(b"\x80")
    assert_run_main_with_args_error(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"),
            "--output",
            str(tmp_path / "output.py"),
            "--enum-field-as-literal-map",
            str(mapping_path),
        ],
        capsys,
        "Unable to read JSON file",
    )


def test_main_enum_field_as_literal_map_inline_requires_json_object(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test non-object JSON passed to --enum-field-as-literal-map."""
    assert_run_main_with_args_error(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"),
            "--output",
            str(tmp_path / "output.py"),
            "--enum-field-as-literal-map",
            '["literal"]',
        ],
        capsys,
        "Expected a JSON object",
    )


@pytest.mark.filterwarnings(
    "ignore:JSON configuration values that do not match the documented schema are deprecated:FutureWarning"
)
def test_main_enum_field_as_literal_map_legacy_value_remains_compatible(output_file: Path) -> None:
    """Test legacy enum-field map values remain compatible."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="enum_field_as_literal_map_legacy.py",
        extra_args=[
            "--enum-field-as-literal-map",
            '{"status": "legacy"}',
            "--disable-timestamp",
        ],
    )


def test_main_enum_field_as_literal_map_legacy_value_rejects_invalid_type(
    output_file: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test legacy enum-field map fallback still rejects invalid value types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json",
        output_path=output_file,
        input_file_type=None,
        expected_exit=Exit.ERROR,
        file_should_not_exist=output_file,
        capsys=capsys,
        expected_stderr_contains="Invalid --enum-field-as-literal-map",
        extra_args=[
            "--enum-field-as-literal-map",
            '{"status": 1}',
        ],
    )


def test_main_x_enum_field_as_literal(output_file: Path) -> None:
    """Test x-enum-field-as-literal schema extension for per-field control."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_enum_field_as_literal.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="x_enum_field_as_literal.py",
    )


def test_main_dataclass_const(output_file: Path) -> None:
    """Test main function writing to dataclass with const fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "const.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "dataclasses.dataclass", "--target-python-version", "3.10"],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        (
            "pydantic_v2.BaseModel",
            "discriminator_literals.py",
        ),
        (
            "msgspec.Struct",
            "discriminator_literals_msgspec.py",
        ),
    ],
)
@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 24,
    reason="Installed black doesn't support the new style",
)
def test_main_jsonschema_discriminator_literals(
    output_model: str, expected_output: str, min_version: str, output_file: Path
) -> None:
    """Test discriminator with literal types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_literals.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=["--output-model-type", output_model, "--target-python-version", min_version],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        (
            "pydantic_v2.BaseModel",
            "prefix_items.py",
        ),
        (
            "msgspec.Struct",
            "prefix_items_msgspec.py",
        ),
    ],
)
@freeze_time("2019-07-26")
@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 24,
    reason="Installed black doesn't support the new style",
)
def test_main_jsonschema_prefix_items(
    output_model: str, expected_output: str, min_version: str, output_file: Path
) -> None:
    """Test prefix items handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "prefix_items.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=["--output-model-type", output_model, "--target-python-version", min_version],
    )


@freeze_time("2019-07-26")
@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 24,
    reason="Installed black doesn't support the new style",
)
def test_main_jsonschema_prefix_items_no_tuple(min_version: str, output_file: Path) -> None:
    """Test prefix items with minItems != maxItems falls back to list."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "prefix_items_no_tuple.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="prefix_items_no_tuple.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--target-python-version", min_version],
    )


@freeze_time("2019-07-26")
@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 24,
    reason="Installed black doesn't support the new style",
)
@pytest.mark.cli_doc(
    options=["--use-tuple-for-fixed-items"],
    option_description="""Generate tuple types for arrays with items array syntax.

When `--use-tuple-for-fixed-items` is enabled and an array has `items` as an array
with `minItems == maxItems == len(items)`, generate a tuple type instead of a list.""",
    input_schema="jsonschema/items_array_tuple.json",
    cli_args=["--use-tuple-for-fixed-items"],
    golden_output="jsonschema/items_array_tuple.py",
)
def test_main_jsonschema_items_array_tuple(min_version: str, output_file: Path) -> None:
    """Generate tuple types for arrays with items array syntax.

    When `--use-tuple-for-fixed-items` is enabled and an array has `items` as an array
    with `minItems == maxItems == len(items)`, generate a tuple type instead of a list.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "items_array_tuple.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="items_array_tuple.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            min_version,
            "--use-tuple-for-fixed-items",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 24,
    reason="Installed black doesn't support the new style",
)
def test_main_jsonschema_discriminator_literals_with_no_mapping(min_version: str, output_file: Path) -> None:
    """Test discriminator literals without mapping."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_no_mapping.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="discriminator_no_mapping.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--target-python-version", min_version],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        (
            "pydantic_v2.BaseModel",
            "discriminator_with_external_reference.py",
        ),
        pytest.param(
            "msgspec.Struct",
            "discriminator_with_external_reference_msgspec.py",
            marks=MSGSPEC_LEGACY_BLACK_SKIP,
        ),
    ],
)
def test_main_jsonschema_external_discriminator(
    output_model: str, expected_output: str, min_version: str, output_file: Path
) -> None:
    """Test external discriminator references."""
    with pytest.warns(FutureWarning, match=r"outside the input base path"):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "discriminator_with_external_reference" / "inner_folder" / "schema.json",
            output_path=output_file,
            input_file_type=None,
            assert_func=assert_file_content,
            expected_file=expected_output,
            extra_args=["--output-model-type", output_model, "--target-python-version", min_version],
        )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        pytest.param(
            "msgspec.Struct",
            "discriminator_with_external_references_folder_msgspec",
            marks=MSGSPEC_LEGACY_BLACK_SKIP,
        ),
    ],
)
def test_main_jsonschema_external_discriminator_folder(
    output_model: str, expected_output: str, min_version: str, output_dir: Path
) -> None:
    """Test external discriminator in folder structure."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_with_external_reference",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / expected_output,
        extra_args=[
            "--output-model-type",
            output_model,
            "--target-python-version",
            min_version,
        ],
    )


def test_main_duplicate_field_constraints(output_dir: Path) -> None:
    """Test duplicate field constraint handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "duplicate_field_constraints",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "duplicate_field_constraints",
        input_file_type="jsonschema",
        extra_args=[
            "--collapse-root-models",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "19",
    reason="Installed black doesn't support the old style",
)
def test_main_duplicate_field_constraints_msgspec(min_version: str, output_dir: Path) -> None:
    """Test duplicate field constraints with msgspec."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "duplicate_field_constraints",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "duplicate_field_constraints_msgspec",
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--target-python-version",
            min_version,
        ],
    )


def test_main_dataclass_field_defs(output_file: Path) -> None:
    """Test dataclass field definitions."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "user_defs.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="dataclass_field.py",
        extra_args=["--output-model-type", "dataclasses.dataclass"],
        transform=lambda s: s.replace("filename:  user_defs.json", "filename:  user.json"),
    )


def test_main_dataclass_default(output_file: Path) -> None:
    """Test dataclass default values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "user_default.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="dataclass_field_default.py",
        extra_args=["--output-model-type", "dataclasses.dataclass"],
    )


def test_main_all_of_ref_self(output_file: Path) -> None:
    """Test allOf with self-reference."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "all_of_ref_self.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "19",
    reason="Installed black doesn't support the old style",
)
def test_main_array_field_constraints(output_file: Path) -> None:
    """Test array field constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "array_field_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--field-constraints"],
    )


def test_all_of_use_default(output_file: Path) -> None:
    """Test allOf with use-default option."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "all_of_default.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--use-default"],
    )


def test_allof_required_use_default(output_file: Path) -> None:
    """Test allOf with required fields and --use-default renders defaults without nullable types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_required_use_default.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--use-default"],
    )


def test_allof_inherited_required_use_default(output_file: Path) -> None:
    """Test allOf required override preserves inherited defaults with --use-default."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_inherited_required_use_default.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--use-default"],
    )


def test_force_optional_required(output_file: Path) -> None:
    """Test --force-optional makes required fields optional."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "force_optional_required.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--force-optional"],
    )


def test_use_default_msgspec_field_ordering(output_file: Path) -> None:
    """Required fields with defaults must sort after required fields without for msgspec.Struct."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_required_use_default.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--use-default", "--output-model-type", "msgspec.Struct"],
    )


def test_use_default_required_null_default(output_file: Path) -> None:
    """Required nullable field with default null must not break dataclass field ordering."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_default_required_null_default.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--use-default", "--output-model-type", "dataclasses.dataclass"],
    )


def test_use_default_strict_nullable_required(output_file: Path) -> None:
    """Required nullable field with default must render the default, not Field(...), under --strict-nullable."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_default_strict_nullable_required.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--use-default", "--strict-nullable", "--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_root_one_of(output_dir: Path) -> None:
    """Test root-level oneOf schemas."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_one_of",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "root_one_of",
        input_file_type="jsonschema",
    )


def test_one_of_with_sub_schema_array_item(output_file: Path) -> None:
    """Test oneOf with sub-schema array items."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "one_of_with_sub_schema_array_item.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_with_custom_formatters(output_file: Path, tmp_path: Path) -> None:
    """Test custom formatter integration."""
    formatter_config = {
        "license_file": str(Path(__file__).parent.parent.parent / "data/python/custom_formatters/license_example.txt")
    }
    formatter_config_path = tmp_path / "formatter_config"
    formatter_config_path.write_text(json.dumps(formatter_config))
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="custom_formatters.py",
        extra_args=[
            "--custom-formatters",
            "tests.data.python.custom_formatters.add_license",
            "--custom-formatters-kwargs",
            str(formatter_config_path),
        ],
    )


def test_main_imports_correct(output_dir: Path) -> None:
    """Test correct import generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "imports_correct",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "imports_correct",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        (
            "pydantic_v2.BaseModel",
            "duration_pydantic_v2.py",
        ),
        (
            "msgspec.Struct",
            "duration_msgspec.py",
        ),
    ],
)
def test_main_jsonschema_duration(output_model: str, expected_output: str, min_version: str, output_file: Path) -> None:
    """Test duration type handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "duration.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=["--output-model-type", output_model, "--target-python-version", min_version],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        (
            "pydantic_v2.BaseModel",
            "time_delta_pydantic_v2.py",
        ),
        (
            "msgspec.Struct",
            "time_delta_msgspec.py",
        ),
    ],
)
def test_main_jsonschema_time_delta(
    output_model: str, expected_output: str, min_version: str, output_file: Path
) -> None:
    """Test time-delta type handling for number format."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "time_delta.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=["--output-model-type", output_model, "--target-python-version", min_version],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 24,
    reason="Installed black doesn't support the new style",
)
def test_main_jsonschema_keyword_only_msgspec(min_version: str, output_file: Path) -> None:
    """Test msgspec keyword-only arguments."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_literals.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="discriminator_literals_msgspec_keyword_only.py",
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--keyword-only",
            "--target-python-version",
            min_version,
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 24,
    reason="Installed black doesn't support the new style",
)
def test_main_jsonschema_keyword_only_msgspec_with_extra_data(min_version: str, output_file: Path) -> None:
    """Test msgspec keyword-only with extra data."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_literals.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="discriminator_literals_msgspec_keyword_only_omit_defaults.py",
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--keyword-only",
            "--target-python-version",
            min_version,
            "--extra-template-data",
            str(JSON_SCHEMA_DATA_PATH / "extra_data_msgspec.json"),
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 24,
    reason="Installed black doesn't support the new style",
)
def test_main_jsonschema_openapi_keyword_only_msgspec_with_extra_data(tmp_path: Path) -> None:
    """Test OpenAPI msgspec keyword-only with extra data."""
    extra_data = json.loads((JSON_SCHEMA_DATA_PATH / "extra_data_msgspec.json").read_text())
    output_file: Path = tmp_path / "output.py"
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_literals.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="discriminator_literals_msgspec_keyword_only_omit_defaults.py",
        output_model_type=DataModelType.MsgspecStruct,
        keyword_only=True,
        target_python_version=PythonVersionMin,
        extra_template_data=defaultdict(dict, extra_data),
        # Following values are implied by `msgspec.Struct` in the CLI
        use_annotated=True,
        field_constraints=True,
    )


@MSGSPEC_LEGACY_BLACK_SKIP
def test_main_msgspec_discriminator_with_type_string(output_file: Path) -> None:
    """Test msgspec Struct generation with discriminator using type: string + const."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_with_type_string.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="discriminator_with_type_string_msgspec.py",
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--target-python-version",
            "3.10",
        ],
    )


@MSGSPEC_LEGACY_BLACK_SKIP
def test_main_msgspec_discriminator_with_meta(output_file: Path) -> None:
    """Test msgspec Struct generation with discriminator ClassVar having Meta constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_with_meta_msgspec.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="discriminator_with_meta_msgspec.py",
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--target-python-version",
            "3.10",
        ],
    )


@MSGSPEC_LEGACY_BLACK_SKIP
def test_main_msgspec_discriminator_without_annotated(output_file: Path) -> None:
    """Test msgspec Struct discriminator generates ClassVar even without use_annotated."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_with_type_string.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="discriminator_with_type_string_msgspec_no_annotated.py",
        output_model_type=DataModelType.MsgspecStruct,
        target_python_version=PythonVersion.PY_310,
        use_annotated=False,
    )


@MSGSPEC_LEGACY_BLACK_SKIP
def test_main_msgspec_null_field(output_file: Path) -> None:
    """Test msgspec Struct generation with null type fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_null_field.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--use-union-operator",
            "--target-python-version",
            "3.10",
        ],
    )


@MSGSPEC_LEGACY_BLACK_SKIP
def test_main_msgspec_falsy_defaults(output_file: Path) -> None:
    """Test msgspec Struct generation preserves falsy default values (0, '', False)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_falsy_defaults.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--use-union-operator",
            "--target-python-version",
            "3.10",
        ],
    )


def test_main_invalid_import_name(output_dir: Path) -> None:
    """Test invalid import name handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "invalid_import_name",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "invalid_import_name",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_alias_import_alias(output_dir: Path) -> None:
    """Ensure imports with aliases are retained after cleanup."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "alias_import_alias",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "alias_import_alias",
    )


def test_main_jsonschema_field_has_same_name(output_file: Path) -> None:
    """Test field with same name as parent."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_has_same_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_has_same_name_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_field_has_same_name_rename_type(output_file: Path) -> None:
    """Test field type collision with rename-type strategy."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_has_same_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_has_same_name_rename_type.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-type-collision-strategy",
            "rename-type",
        ],
    )


@pytest.mark.cli_doc(
    options=["--field-type-collision-strategy"],
    option_description="""Rename type class instead of field when names collide (Pydantic v2 only).

The `--field-type-collision-strategy` flag controls how field name and type name
collisions are resolved. With `rename-type`, the type class is renamed with a suffix
to preserve the original field name, instead of renaming the field and adding an alias.""",
    input_schema="jsonschema/field_has_same_name.json",
    cli_args=[
        "--output-model-type",
        "pydantic_v2.BaseModel",
        "--field-type-collision-strategy",
        "rename-type",
    ],
    golden_output="jsonschema/field_has_same_name_rename_type.py",
)
def test_main_jsonschema_field_has_same_name_rename_type_cli_doc(output_file: Path) -> None:
    """Rename type class instead of field when names collide (Pydantic v2 only).

    The `--field-type-collision-strategy` flag controls how field name and type name
    collisions are resolved. With `rename-type`, the type class is renamed with a suffix
    to preserve the original field name, instead of renaming the field and adding an alias.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_has_same_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_has_same_name_rename_type.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-type-collision-strategy",
            "rename-type",
        ],
    )


def test_main_jsonschema_field_type_collision_rename_type_double(output_file: Path) -> None:
    """Test field type collision with rename-type strategy when schema has existing _1 suffix."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_type_collision_rename_type_double.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_type_collision_rename_type_double.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-type-collision-strategy",
            "rename-type",
        ],
    )


@pytest.mark.benchmark
def test_main_jsonschema_required_and_any_of_required(output_file: Path) -> None:
    """Test required field with anyOf required."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "required_and_any_of_required.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="required_and_any_of_required.py",
    )


def test_main_json_pointer_escaped_segments(tmp_path: Path) -> None:
    """Test JSON pointer with escaped segments."""
    schema = {
        "definitions": {
            "foo/bar": {"type": "object", "properties": {"value": {"type": "string"}}},
            "baz~qux": {"type": "object", "properties": {"value": {"type": "integer"}}},
        },
        "properties": {
            "foo_bar": {"$ref": "#/definitions/foo~1bar"},
            "baz_qux": {"$ref": "#/definitions/baz~0qux"},
        },
        "type": "object",
    }
    expected = (
        "# generated by datamodel-codegen:\n"
        "#   filename: input.json\n"
        "#   timestamp: 2019-07-26T00:00:00+00:00\n\n"
        "from __future__ import annotations\n\n"
        "from pydantic import BaseModel\n\n"
        "class FooBar(BaseModel):\n    value: str | None = None\n\n"
        "class BazQux(BaseModel):\n    value: int | None = None\n\n"
        "class Baz0qux(BaseModel):\n    value: int | None = None\n\n"
        "class Foo1bar(BaseModel):\n    value: str | None = None\n\n"
        "class Model(BaseModel):\n    foo_bar: Foo1bar | None = None\n    baz_qux: Baz0qux | None = None\n"
    )

    input_file = tmp_path / "input.json"
    output_file = tmp_path / "output.py"
    input_file.write_text(json.dumps(schema))
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        expected_output=expected,
        input_file_type="jsonschema",
        ignore_whitespace=True,
    )


def test_main_json_pointer_percent_encoded_segments(tmp_path: Path) -> None:
    """Test JSON pointer with percent-encoded segments."""
    schema = {
        "definitions": {
            "foo/bar": {"type": "object", "properties": {"value": {"type": "string"}}},
            "baz~qux": {"type": "object", "properties": {"value": {"type": "integer"}}},
            "space key": {"type": "object", "properties": {"value": {"type": "boolean"}}},
        },
        "properties": {
            "foo_bar": {"$ref": "#/definitions/foo%2Fbar"},
            "baz_qux": {"$ref": "#/definitions/baz%7Equx"},
            "space_key": {"$ref": "#/definitions/space%20key"},
        },
        "type": "object",
    }
    expected = (
        "# generated by datamodel-codegen:\n"
        "#   filename: input.json\n"
        "#   timestamp: 2019-07-26T00:00:00+00:00\n\n"
        "from __future__ import annotations\n\n"
        "from pydantic import BaseModel\n\n"
        "class FooBar(BaseModel):\n    value: str | None = None\n\n"
        "class BazQux(BaseModel):\n    value: int | None = None\n\n"
        "class SpaceKey(BaseModel):\n    value: bool | None = None\n\n"
        "class Baz7Equx(BaseModel):\n    value: int | None = None\n\n"
        "class Foo2Fbar(BaseModel):\n    value: str | None = None\n\n"
        "class Space20key(BaseModel):\n    value: bool | None = None\n\n"
        "class Model(BaseModel):\n    foo_bar: Foo2Fbar | None = None\n"
        "    baz_qux: Baz7Equx | None = None\n"
        "    space_key: Space20key | None = None\n"
    )

    input_file = tmp_path / "input.json"
    output_file = tmp_path / "output.py"
    input_file.write_text(json.dumps(schema))
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        expected_output=expected,
        input_file_type="jsonschema",
        ignore_whitespace=True,
    )


@pytest.mark.parametrize(
    ("extra_fields", "output_model", "expected_output"),
    [
        (
            "allow",
            "pydantic_v2.BaseModel",
            "extra_fields_v2_allow.py",
        ),
        (
            "forbid",
            "pydantic_v2.BaseModel",
            "extra_fields_v2_forbid.py",
        ),
        (
            "ignore",
            "pydantic_v2.BaseModel",
            "extra_fields_v2_ignore.py",
        ),
    ],
)
def test_main_extra_fields(extra_fields: str, output_model: str, expected_output: str, output_file: Path) -> None:
    """Test extra fields configuration."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "extra_fields.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=["--extra-fields", extra_fields, "--output-model-type", output_model],
    )


@pytest.mark.cli_doc(
    options=["--use-generic-base-class"],
    option_description="""Generate a shared base class with model configuration to avoid repetition (DRY).""",
    input_schema="jsonschema/extra_fields.json",
    cli_args=["--extra-fields", "forbid", "--output-model-type", "pydantic_v2.BaseModel", "--use-generic-base-class"],
    golden_output="jsonschema/use_generic_base_class.py",
)
def test_main_use_generic_base_class(output_file: Path) -> None:
    """Generate a shared base class with model configuration to avoid repetition (DRY)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "extra_fields.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class.py",
        extra_args=[
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
        ],
    )


def test_main_use_generic_base_class_populate_by_name(output_file: Path) -> None:
    """Test --use-generic-base-class with --allow-population-by-field-name."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_populate_by_name.py",
        extra_args=[
            "--allow-population-by-field-name",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
        ],
    )


def test_main_use_generic_base_class_alias_generator(output_file: Path) -> None:
    """Test --use-generic-base-class with --alias-generator."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "alias_generator.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="alias_generator_generic_base.py",
        extra_args=[
            "--snake-case-field",
            "--alias-generator",
            "to_camel",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
        ],
    )


@pytest.mark.parametrize("target_pydantic_version", ["2.11", "2.12"])
def test_main_use_generic_base_class_target_pydantic_v2_11_or_later(
    target_pydantic_version: str, output_file: Path
) -> None:
    """Test --use-generic-base-class with target Pydantic versions that use validate_by_name."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_target_pydantic_v2_11.py",
        extra_args=[
            "--allow-population-by-field-name",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
            "--target-pydantic-version",
            target_pydantic_version,
        ],
    )


def test_main_allof_list_any_inheritance(output_file: Path) -> None:
    """Test allOf with List[Any] type inheritance from parent."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_list_any_inheritance.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allof_list_any_inheritance.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_use_generic_base_class_allow_extra(output_file: Path) -> None:
    """Test --use-generic-base-class with --allow-extra-fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_allow_extra.py",
        extra_args=[
            "--allow-extra-fields",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
        ],
    )


def test_main_use_generic_base_class_frozen(output_file: Path) -> None:
    """Test --use-generic-base-class with --enable-faux-immutability."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_frozen.py",
        extra_args=[
            "--enable-faux-immutability",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
        ],
    )


def test_main_use_generic_base_class_attr_docstrings(output_file: Path) -> None:
    """Test --use-generic-base-class with --use-attribute-docstrings."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_attr_docstrings.py",
        extra_args=[
            "--use-attribute-docstrings",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
        ],
    )


def test_main_use_generic_base_class_dataclass(output_file: Path) -> None:
    """Test --use-generic-base-class with dataclasses (no effect)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_dataclass.py",
        extra_args=[
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "dataclasses.dataclass",
            "--use-generic-base-class",
        ],
    )


def test_main_use_generic_base_class_enum_only(output_file: Path) -> None:
    """Test --use-generic-base-class with enum-only schema (no ProjectBaseModel)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_enum_only.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_enum_only.py",
        extra_args=[
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
        ],
    )


def test_main_use_generic_base_class_with_inheritance(output_file: Path) -> None:
    """Test --use-generic-base-class preserves schema inheritance (allOf)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_with_inheritance.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_with_inheritance.py",
        extra_args=[
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
        ],
    )


def test_main_use_generic_base_class_module_split(output_dir: Path) -> None:
    """Test --use-generic-base-class with module split mode (cross-module inheritance)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_with_inheritance.json",
        output_path=output_dir,
        input_file_type="jsonschema",
        extra_args=[
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
            "--module-split-mode",
            "single",
        ],
        expected_directory=EXPECTED_MAIN_PATH / "jsonschema" / "use_generic_base_class_module_split",
    )


def test_main_use_generic_base_class_deep_inheritance(output_dir: Path) -> None:
    """Test --use-generic-base-class with deep inheritance chain across modules."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_deep_inheritance.json",
        output_path=output_dir,
        input_file_type="jsonschema",
        extra_args=[
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
            "--module-split-mode",
            "single",
        ],
        expected_directory=EXPECTED_MAIN_PATH / "jsonschema" / "use_generic_base_class_deep_inheritance",
    )


def test_main_use_generic_base_class_multi_root(output_dir: Path) -> None:
    """Test --use-generic-base-class with multiple independent inheritance trees."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_multi_root.json",
        output_path=output_dir,
        input_file_type="jsonschema",
        extra_args=[
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
            "--module-split-mode",
            "single",
        ],
        expected_directory=EXPECTED_MAIN_PATH / "jsonschema" / "use_generic_base_class_multi_root",
    )


def test_main_use_generic_base_class_circular(output_dir: Path) -> None:
    """Test --use-generic-base-class with circular references (uses _internal.py pattern)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_circular.json",
        output_path=output_dir,
        input_file_type="jsonschema",
        extra_args=[
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-base-class",
            "--module-split-mode",
            "single",
        ],
        expected_directory=EXPECTED_MAIN_PATH / "jsonschema" / "use_generic_base_class_circular",
    )


def test_main_use_generic_base_class_msgspec(output_file: Path) -> None:
    """Test --use-generic-base-class with msgspec.Struct."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_msgspec.py",
        extra_args=[
            "--allow-population-by-field-name",
            "--enable-faux-immutability",
            "--output-model-type",
            "msgspec.Struct",
            "--use-generic-base-class",
        ],
    )


def test_main_use_generic_base_class_msgspec_forbid(output_file: Path) -> None:
    """Test --use-generic-base-class with msgspec.Struct and --extra-fields forbid."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_generic_base_class_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_generic_base_class_msgspec_forbid.py",
        extra_args=[
            "--enable-faux-immutability",
            "--extra-fields",
            "forbid",
            "--output-model-type",
            "msgspec.Struct",
            "--use-generic-base-class",
        ],
    )


def test_main_jsonschema_same_name_objects(output_file: Path) -> None:
    """Test objects with same name (see issue #2460)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "same_name_objects.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="same_name_objects.py",
    )


def test_main_jsonschema_forwarding_reference_collapse_root(output_dir: Path) -> None:
    """Test forwarding reference with collapsed root (see issue #1466)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "forwarding_reference",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "forwarding_reference",
        input_file_type="jsonschema",
        extra_args=["--collapse-root-models"],
    )


@pytest.mark.cli_doc(
    options=["--use-type-alias"],
    option_description="""Generate TypeAlias for root models instead of wrapper classes.

The `--use-type-alias` flag configures the code generation behavior.""",
    input_schema="jsonschema/type_alias.json",
    cli_args=["--use-type-alias"],
    golden_output="jsonschema/type_alias.py",
)
def test_main_jsonschema_type_alias(output_file: Path) -> None:
    """Generate TypeAlias for root models instead of wrapper classes.

    The `--use-type-alias` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias.py",
        extra_args=["--use-type-alias"],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_py312(output_file: Path) -> None:
    """Test that type statement syntax is generated for Python 3.12+ with Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_py312.py",
        extra_args=[
            "--use-type-alias",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.cli_doc(
    options=["--use-field-description"],
    option_description="""Include schema descriptions as Field docstrings.

The `--use-field-description` flag extracts the `description` property from
schema fields and includes them as docstrings or Field descriptions in the
generated models, preserving documentation from the original schema.""",
    input_schema="jsonschema/type_alias.json",
    cli_args=["--use-type-alias", "--use-field-description"],
    golden_output="jsonschema/type_alias_with_field_description.py",
)
def test_main_jsonschema_type_alias_with_field_description(output_file: Path) -> None:
    """Include schema descriptions as Field docstrings.

    The `--use-field-description` flag extracts the `description` property from
    schema fields and includes them as docstrings or Field descriptions in the
    generated models, preserving documentation from the original schema.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_with_field_description.py",
        extra_args=["--use-type-alias", "--use-field-description"],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_with_field_description_py312(output_file: Path) -> None:
    """Test that type statement syntax is generated with field descriptions for Python 3.12+ and Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_with_field_description_py312.py",
        extra_args=[
            "--use-type-alias",
            "--use-field-description",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_enum_literal_type_alias_default(output_file: Path) -> None:
    """Don't wrap type alias defaults in default_factory (TypeAliasType is not callable)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_literal_type_alias_default.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="enum_literal_type_alias_default.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--enum-field-as-literal",
            "all",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_union_default_object_ref(output_file: Path) -> None:
    """Validate $ref object-union defaults through the type alias."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_union_default_object_ref.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_union_default_object_ref.py",
        extra_args=[
            "--use-type-alias",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_model_default_object_ref(output_file: Path) -> None:
    """Validate model-backed type alias defaults through the alias."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_model_default_object_ref.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_model_default_object_ref.py",
        extra_args=[
            "--use-type-alias",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_chain_model_default_object_ref(output_file: Path) -> None:
    """Validate recursively chained model-backed aliases for structured defaults."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_chain_model_default_object_ref.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_chain_model_default_object_ref.py",
        extra_args=[
            "--use-type-alias",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_list_model_default_object_ref(output_file: Path) -> None:
    """Validate list-of-model type alias defaults through the alias."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_list_model_default_object_ref.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_list_model_default_object_ref.py",
        extra_args=[
            "--use-type-alias",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_nullable_model_default_object_ref(output_file: Path) -> None:
    """Validate nullable model-backed type alias defaults through the alias."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_nullable_model_default_object_ref.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_nullable_model_default_object_ref.py",
        extra_args=[
            "--use-type-alias",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_nullable_list_model_default_object_ref(output_file: Path) -> None:
    """Validate nullable list-of-model type alias defaults through the alias."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_nullable_list_model_default_object_ref.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_nullable_list_model_default_object_ref.py",
        extra_args=[
            "--use-type-alias",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_recursive_default_list(output_file: Path) -> None:
    """Avoid recursive alias traversal loops when checking structured defaults."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_recursive_default_list.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_recursive_default_list.py",
        extra_args=[
            "--use-type-alias",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_inline_union_default_object(output_file: Path) -> None:
    """Validate inline object-union defaults through the union type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_inline_union_default_object.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="type_alias_inline_union_default_object.py",
        extra_args=[
            "--use-type-alias",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_union_default_object_ref_any_of_relevant_flags(
    output_file: Path,
) -> None:
    """Validate anyOf alias union defaults with the relevant type-alias flags."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_union_default_object_ref_any_of.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_union_default_object_ref_any_of.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_chain_union_default_object_ref_relevant_flags(
    output_file: Path,
) -> None:
    """Validate chained alias union defaults with the relevant type-alias flags."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_chain_union_default_object_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_chain_union_default_object_ref.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_union_default_object_ref_one_of_relevant_flags(
    output_file: Path,
) -> None:
    """Validate oneOf alias union defaults with the relevant type-alias flags."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_union_default_object_ref_one_of.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_union_default_object_ref_one_of.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_inline_union_default_object_one_of_relevant_flags(
    output_file: Path,
) -> None:
    """Validate oneOf inline union defaults with the relevant type-alias flags."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_inline_union_default_object_one_of.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_inline_union_default_object_one_of.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_inline_union_default_object_import_collision_relevant_flags(
    output_file: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Keep TypeAdapter targets aligned after imported-name collision renames local models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_inline_union_default_object_import_collision.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_inline_union_default_object_import_collision.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
            "--type-overrides",
            '{"Other.a": "my_app.B"}',
        ],
        skip_code_validation=True,
    )
    _install_test_my_app(output_file.parent, monkeypatch)
    validate_generated_code(output_file.read_text(encoding="utf-8"), str(output_file), do_exec=True)


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_inline_union_default_object_type_override_relevant_flags(
    output_file: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Validate TypeAdapter targets after late type overrides change field types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_inline_union_default_object_type_override.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_inline_union_default_object_type_override.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
            "--type-overrides",
            '{"A": "my_app.AliasA"}',
        ],
        skip_code_validation=True,
    )
    _install_test_my_app(output_file.parent, monkeypatch)
    validate_generated_code(output_file.read_text(encoding="utf-8"), str(output_file), do_exec=True)


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_inline_union_default_object_silent_wrong_branch_relevant_flags(
    output_file: Path,
) -> None:
    """Validate inline unions that would silently pick the wrong branch."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_inline_union_default_object_silent_wrong_branch.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_inline_union_default_object_silent_wrong_branch.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_inline_list_union_default_object_relevant_flags(
    output_file: Path,
) -> None:
    """Validate list-of-union defaults with the relevant type-alias flags."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_inline_list_union_default_object.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_inline_list_union_default_object.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_list_union_default_object_ref_relevant_flags(
    output_file: Path,
) -> None:
    """Validate aliased list-of-union defaults with the relevant type-alias flags."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_list_union_default_object_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_list_union_default_object_ref.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_inline_dict_union_default_object_relevant_flags(
    output_file: Path,
) -> None:
    """Validate dict-of-union defaults with the relevant type-alias flags."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_inline_dict_union_default_object.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_inline_dict_union_default_object.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_dict_union_default_object_ref_relevant_flags(
    output_file: Path,
) -> None:
    """Validate aliased dict-of-union defaults with the relevant type-alias flags."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_dict_union_default_object_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_dict_union_default_object_ref.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_union_default_object_ref_dict_alias_branch_relevant_flags(
    output_file: Path,
) -> None:
    """Keep raw defaults when a plain dict union branch is hidden behind a type alias."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_union_default_object_ref_dict_alias_branch.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_union_default_object_ref_dict_alias_branch.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.skipif(
    int(black.__version__.split(".")[0]) < 23,
    reason="Installed black doesn't support the new 'type' statement",
)
def test_main_jsonschema_type_alias_union_default_object_ref_mixed_scalar_relevant_flags(
    output_file: Path,
) -> None:
    """Validate aliased mixed scalar/model union defaults."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_union_default_object_ref_mixed_scalar.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_union_default_object_ref_mixed_scalar.py",
        extra_args=[
            "--use-type-alias",
            "--use-annotated",
            "--target-python-version",
            "3.12",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-pydantic-version",
            "2.11",
        ],
    )


@pytest.mark.cli_doc(
    options=["--type-mappings"],
    option_description="""Override default type mappings for schema formats.

The `--type-mappings` flag configures the code generation behavior.""",
    input_schema="jsonschema/type_mappings.json",
    cli_args=["--output-model-type", "pydantic_v2.BaseModel", "--type-mappings", "binary=string"],
    golden_output="jsonschema/type_mappings.py",
)
def test_main_jsonschema_type_mappings(output_file: Path) -> None:
    """Override default type mappings for schema formats.

    The `--type-mappings` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_mappings.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_mappings.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--type-mappings",
            "binary=string",
        ],
    )


def test_main_jsonschema_type_mappings_with_type_prefix(output_file: Path) -> None:
    """Test --type-mappings option with type+format syntax."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_mappings.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_mappings.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--type-mappings",
            "string+binary=string",
        ],
    )


def test_main_jsonschema_type_mappings_to_type_default(output_file: Path) -> None:
    """Test --type-mappings option mapping to a type's default (e.g., binary=integer)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_mappings.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_mappings_to_integer.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--type-mappings",
            "binary=integer",
        ],
    )


def test_main_jsonschema_type_mappings_to_boolean(output_file: Path) -> None:
    """Test --type-mappings option mapping to a top-level type (e.g., binary=boolean)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_mappings.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_mappings_to_boolean.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--type-mappings",
            "binary=boolean",
        ],
    )


def test_main_jsonschema_type_mappings_invalid_format(output_file: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --type-mappings option with invalid format raises error."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_mappings.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--type-mappings",
            "invalid_without_equals",
        ],
        expected_stderr_contains="Invalid type mapping format",
        capsys=capsys,
    )


@pytest.mark.cli_doc(
    options=["--reuse-scope"],
    option_description="""Scope for model reuse detection (root or tree).

The `--reuse-scope` flag configures the code generation behavior.""",
    input_schema="jsonschema/reuse_scope_tree",
    cli_args=["--reuse-model", "--reuse-scope", "tree"],
    golden_output="jsonschema/reuse_scope_tree",
)
def test_main_jsonschema_reuse_scope_tree(output_dir: Path) -> None:
    """Scope for model reuse detection (root or tree).

    The `--reuse-scope` flag configures the code generation behavior.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree",
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree"],
    )


def test_main_jsonschema_reuse_scope_tree_exact_imports(output_dir: Path) -> None:
    """Test --reuse-scope=tree with --use-exact-imports keeps local subclass references."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree_exact_imports",
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--reuse-model",
            "--reuse-scope",
            "tree",
            "--use-exact-imports",
            "--disable-timestamp",
        ],
    )


def test_main_jsonschema_reuse_scope_tree_enum(output_dir: Path) -> None:
    """Test --reuse-scope=tree to deduplicate enum models across multiple files."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_enum",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree_enum",
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree"],
    )


def test_main_jsonschema_reuse_scope_tree_warning(capsys: pytest.CaptureFixture[str], output_dir: Path) -> None:
    """Test warning when --reuse-scope=tree is used without --reuse-model."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree",
        output_path=output_dir,
        input_file_type="jsonschema",
        extra_args=["--reuse-scope", "tree"],
        capsys=capsys,
        expected_stderr_contains="Warning: --reuse-scope=tree has no effect without --reuse-model",
    )


def test_main_jsonschema_reuse_scope_tree_no_dup(output_dir: Path) -> None:
    """Test --reuse-scope=tree when there are no duplicate models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_no_dup",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree_no_dup",
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree"],
    )


def test_main_jsonschema_reuse_scope_tree_self_ref(output_dir: Path) -> None:
    """Test --reuse-scope=tree with self-referencing models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_self_ref",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree_self_ref",
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree"],
    )


def test_main_jsonschema_reuse_scope_tree_conflict(capsys: pytest.CaptureFixture[str], output_dir: Path) -> None:
    """Test --reuse-scope=tree error when schema file name conflicts with shared module."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_conflict",
        output_path=output_dir,
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree"],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="Schema file or directory 'shared' conflicts with the shared module name",
    )


def test_main_jsonschema_reuse_scope_tree_conflict_dir(capsys: pytest.CaptureFixture[str], output_dir: Path) -> None:
    """Test --reuse-scope=tree error when schema directory name conflicts with shared module."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_conflict_dir",
        output_path=output_dir,
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree"],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="Schema file or directory 'shared' conflicts with the shared module name",
    )


def test_main_jsonschema_reuse_scope_tree_no_conflict_dir(output_dir: Path) -> None:
    """Test --reuse-scope=tree does not error when shared/ dir exists but no duplicates."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_no_conflict_dir",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree_no_conflict_dir",
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree"],
    )


def test_main_jsonschema_reuse_scope_tree_multi(output_dir: Path) -> None:
    """Test --reuse-scope=tree with multiple files where canonical is not in first module."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_multi",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree_multi",
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree"],
    )


def test_main_jsonschema_reuse_scope_tree_branch(output_dir: Path) -> None:
    """Test --reuse-scope=tree branch coverage with duplicate in later modules."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_branch",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree_branch",
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree"],
    )


def test_main_jsonschema_reuse_scope_tree_dataclass(output_dir: Path) -> None:
    """Test --reuse-scope=tree with dataclasses output type (supports inheritance)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_dataclass",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree_dataclass",
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree", "--output-model-type", "dataclasses.dataclass"],
    )


def test_main_jsonschema_reuse_scope_tree_dataclass_frozen(output_dir: Path) -> None:
    """Test --reuse-scope=tree preserves dataclass arguments in inherited models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_dataclass",
        output_path=output_dir,
        output_to_expected=[
            ("__init__.py", "reuse_scope_tree_dataclass_frozen/__init__.py"),
            ("schema_a.py", "reuse_scope_tree_dataclass_frozen/schema_a.py"),
            ("schema_b.py", "reuse_scope_tree_dataclass_frozen/schema_b.py"),
            ("shared.py", "reuse_scope_tree_dataclass_frozen/shared.py"),
        ],
        assert_func=assert_file_content,
        input_file_type="jsonschema",
        extra_args=[
            "--reuse-model",
            "--reuse-scope",
            "tree",
            "--output-model-type",
            "dataclasses.dataclass",
            "--frozen-dataclasses",
            "--keyword-only",
        ],
    )


def test_main_jsonschema_reuse_scope_tree_typeddict(output_dir: Path) -> None:
    """Test --reuse-scope=tree with TypedDict output type (no inheritance, direct reference)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reuse_scope_tree_typeddict",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "reuse_scope_tree_typeddict",
        input_file_type="jsonschema",
        extra_args=["--reuse-model", "--reuse-scope", "tree", "--output-model-type", "typing.TypedDict"],
    )


def test_main_jsonschema_empty_items_array(output_file: Path) -> None:
    """Test that arrays with empty items ({}) generate List[Any] instead of bare List."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "empty_items_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_jsonschema_items_false_array(output_file: Path) -> None:
    """Test items false constrains arrays to zero elements."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "items_false_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="items_false_array.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_items_array_with_false_schema(output_file: Path) -> None:
    """Test false schemas inside tuple-style items are accepted and constrain array length."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "items_array_with_false_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="items_array_with_false_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_items_array_with_true_schema(output_file: Path) -> None:
    """Test true schemas inside tuple-style items are accepted as unconstrained item types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "items_array_with_true_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="items_array_with_true_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_additional_items_false_empty_tuple(output_file: Path) -> None:
    """Test additionalItems false with empty tuple items constrains arrays to zero elements."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_items_false_empty_tuple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_items_false_empty_tuple.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_additional_items_schema_tuple(output_file: Path) -> None:
    """Test additionalItems schema is included for tuple-style array item unions."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "additional_items_schema_tuple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="additional_items_schema_tuple.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_prefix_items_with_true_schema(output_file: Path) -> None:
    """Test true schemas inside prefixItems are accepted as unconstrained item types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "prefix_items_with_true_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="prefix_items_with_true_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_prefix_items_with_false_schema(output_file: Path) -> None:
    """Test false schemas inside prefixItems are accepted and constrain array length."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "prefix_items_with_false_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="prefix_items_with_false_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_prefix_items_items_false(output_file: Path) -> None:
    """Test prefixItems with items false constrains arrays to declared prefix length."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "prefix_items_items_false.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="prefix_items_items_false.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_unevaluated_items_false_prefix(output_file: Path) -> None:
    """Test prefixItems with unevaluatedItems false constrains arrays to declared prefix length."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_items_false_prefix.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="unevaluated_items_false_prefix.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="unevaluated_items_false_prefix",
        model_name="UnevaluatedItemsFalsePrefix",
        valid_json='{"values":[1,"ok"]}',
        invalid_json='{"values":[1,"ok",false]}',
        expected_error_type="too_long",
        expected_attribute_path=("values",),
        expected_attribute_value=[1, "ok"],
    )


def test_main_jsonschema_unevaluated_items_false_array(output_file: Path) -> None:
    """Test unevaluatedItems false without item schemas constrains arrays to empty."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_items_false_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="unevaluated_items_false_array.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="unevaluated_items_false_array",
        model_name="UnevaluatedItemsFalseArray",
        valid_json='{"values":[]}',
        invalid_json='{"values":[1]}',
        expected_error_type="too_long",
        expected_attribute_path=("values",),
        expected_attribute_value=[],
    )


def test_main_jsonschema_unevaluated_items_schema_array(output_file: Path) -> None:
    """Test unevaluatedItems schema without item schemas applies to array items."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unevaluated_items_schema_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="unevaluated_items_schema_array.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="unevaluated_items_schema_array",
        model_name="UnevaluatedItemsSchemaArray",
        valid_json='{"values":["a","b"]}',
        invalid_json='{"values":["a",1]}',
        expected_error_type="string_type",
        expected_attribute_path=("values",),
        expected_attribute_value=["a", "b"],
    )


def test_main_jsonschema_prefix_items_items_schema(output_file: Path) -> None:
    """Test prefixItems with items schema generates prefix and tail item union."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "prefix_items_items_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="prefix_items_items_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )


def test_main_jsonschema_prefix_items_fixed_tail_schema(output_file: Path) -> None:
    """Test fixed-length prefixItems with items schema preserves positional types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "prefix_items_fixed_tail_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="prefix_items_fixed_tail_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="prefix_items_fixed_tail_schema",
        model_name="PrefixItemsFixedTailSchema",
        valid_json='{"values":[1,"a","b"]}',
        invalid_json='{"values":["x","a","b"]}',
        expected_error_type="int_parsing",
        expected_attribute_path=("values",),
        expected_attribute_value=(1, "a", "b"),
    )


def test_main_jsonschema_prefix_items_fixed_any_tail_schema(output_file: Path) -> None:
    """Test fixed-length prefixItems without tail schema fills missing tuple slots with Any."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "prefix_items_fixed_any_tail_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="prefix_items_fixed_any_tail_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )


def test_main_jsonschema_prefix_items_fixed_unevaluated_tail_schema(output_file: Path) -> None:
    """Test fixed-length prefixItems uses unevaluatedItems schema for missing tuple slots."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "prefix_items_fixed_unevaluated_tail_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="prefix_items_fixed_unevaluated_tail_schema.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="prefix_items_fixed_unevaluated_tail_schema",
        model_name="PrefixItemsFixedUnevaluatedTailSchema",
        valid_json='{"values":[1,"a","b"]}',
        invalid_json='{"values":[1,2,"b"]}',
        expected_error_type="string_type",
        expected_attribute_path=("values",),
        expected_attribute_value=(1, "a", "b"),
    )


def test_main_jsonschema_contains_true_max_contains_zero(output_file: Path) -> None:
    """Test contains true with maxContains zero constrains arrays to empty."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "contains_true_max_contains_zero.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="contains_true_max_contains_zero.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="contains_true_max_contains_zero",
        model_name="ContainsTrueMaxContainsZero",
        valid_json='{"values":[]}',
        invalid_json='{"values":["x"]}',
        expected_error_type="too_long",
        expected_attribute_path=("values",),
        expected_attribute_value=[],
    )


def test_main_jsonschema_contains_true_default_min_contains(output_file: Path) -> None:
    """Test contains true defaults minContains to one."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "contains_true_default_min_contains.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="contains_true_default_min_contains.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="contains_true_default_min_contains",
        model_name="ContainsTrueDefaultMinContains",
        valid_json='{"values":["x"]}',
        invalid_json='{"values":[]}',
        expected_error_type="too_short",
        expected_attribute_path=("values",),
        expected_attribute_value=["x"],
    )


def test_main_jsonschema_contains_true_count_constraints(output_file: Path) -> None:
    """Test contains true count constraints are generated as array length constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "contains_true_count_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="contains_true_count_constraints.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="contains_true_count_constraints",
        model_name="ContainsTrueCountConstraints",
        valid_json='{"values":[1,2]}',
        invalid_json='{"values":[1]}',
        expected_error_type="too_short",
        expected_attribute_path=("values",),
        expected_attribute_value=[1, 2],
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="contains_true_count_constraints",
        model_name="ContainsTrueCountConstraints",
        valid_json='{"values":[1,2,3]}',
        invalid_json='{"values":[1,2,3,4]}',
        expected_error_type="too_long",
        expected_attribute_path=("values",),
        expected_attribute_value=[1, 2, 3],
    )


def test_main_jsonschema_contains_empty_schema_count_constraints(output_file: Path) -> None:
    """Test contains empty schema count constraints are generated as array length constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "contains_empty_schema_count_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="contains_empty_schema_count_constraints.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="contains_empty_schema_count_constraints",
        model_name="ContainsEmptySchemaCountConstraints",
        valid_json='{"values":[1,2]}',
        invalid_json='{"values":[1]}',
        expected_error_type="too_short",
        expected_attribute_path=("values",),
        expected_attribute_value=[1, 2],
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="contains_empty_schema_count_constraints",
        model_name="ContainsEmptySchemaCountConstraints",
        valid_json='{"values":[1,2,3]}',
        invalid_json='{"values":[1,2,3,4]}',
        expected_error_type="too_long",
        expected_attribute_path=("values",),
        expected_attribute_value=[1, 2, 3],
    )


def test_main_jsonschema_contains_false_default(output_file: Path) -> None:
    """Test contains false with default minContains rejects every array."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "contains_false_default.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="contains_false_default.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_invalid(
        output_file,
        module_name="contains_false_default",
        model_name="ContainsFalseDefault",
        invalid_json='{"values":[]}',
        expected_error_type="too_short",
    )
    assert_generated_model_json_invalid(
        output_file,
        module_name="contains_false_default",
        model_name="ContainsFalseDefault",
        invalid_json='{"values":[1]}',
        expected_error_type="too_long",
    )


def test_main_jsonschema_contains_false_min_contains_zero(output_file: Path) -> None:
    """Test contains false with minContains zero remains unconstrained."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "contains_false_min_contains_zero.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="contains_false_min_contains_zero.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="contains_false_min_contains_zero",
        model_name="ContainsFalseMinContainsZero",
        valid_json='{"values":[1]}',
        invalid_json='{"values":null}',
        expected_error_type="list_type",
        expected_attribute_path=("values",),
        expected_attribute_value=[1],
    )


@pytest.mark.cli_doc(
    options=["--aliases"],
    option_description="""Apply custom field and class name aliases via inline JSON or a JSON file path.

The `--aliases` option allows renaming fields and classes via an inline JSON object or JSON mapping file,
providing fine-grained control over generated names independent of schema definitions.
Supports scoped format (ClassName.field) for hierarchical aliasing.""",
    input_schema="jsonschema/hierarchical_aliases.json",
    cli_args=["--aliases", "aliases/hierarchical_aliases_scoped.json"],
    golden_output="jsonschema/jsonschema_hierarchical_aliases_scoped.py",
)
def test_main_jsonschema_hierarchical_aliases_scoped(output_file: Path) -> None:
    """Test hierarchical aliases with scoped format (ClassName.field)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "hierarchical_aliases.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--aliases",
            str(ALIASES_DATA_PATH / "hierarchical_aliases_scoped.json"),
        ],
    )


def test_main_jsonschema_hierarchical_aliases_inline_json(output_file: Path) -> None:
    """Test aliases mapping loaded from inline JSON."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "hierarchical_aliases.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="jsonschema_hierarchical_aliases_scoped.py",
        extra_args=[
            "--aliases",
            '{"Root.name": "root_name", "User.name": "user_name", "Address.name": "address_name"}',
        ],
    )


@pytest.mark.cli_doc(
    options=["--aliases"],
    option_description="""Test multiple aliases with AliasChoices for Pydantic v2.""",
    input_schema="jsonschema/multiple_aliases.json",
    cli_args=["--aliases", "aliases/multiple_aliases.json", "--output-model-type", "pydantic_v2.BaseModel"],
    golden_output="jsonschema/multiple_aliases_pydantic_v2.py",
)
def test_main_jsonschema_multiple_aliases_pydantic_v2(output_file: Path) -> None:
    """Test multiple aliases with AliasChoices for Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "multiple_aliases.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--aliases",
            str(ALIASES_DATA_PATH / "multiple_aliases.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_multiple_aliases_serialization_alias_pydantic_v2(output_file: Path) -> None:
    """Test multiple aliases with serialization_alias for Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "multiple_aliases.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_file="jsonschema_multiple_aliases_serialization_alias_pydantic_v2.py",
        assert_func=assert_file_content,
        extra_args=[
            "--aliases",
            str(ALIASES_DATA_PATH / "multiple_aliases.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-serialization-alias",
        ],
    )


def test_main_jsonschema_array_combined_types(output_file: Path) -> None:
    """Test array schemas combined with allOf, object fields, and enum values."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "array_combined.py.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="array_combined.py",
    )


def test_main_jsonschema_recursive_array(output_file: Path) -> None:
    """Test a root array schema that recursively references itself."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "recursive_array.py.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="recursive_array.py",
    )


def test_main_jsonschema_serialization_alias_same_name_pydantic_v2(output_file: Path) -> None:
    """Test use_serialization_alias skips aliases that match the field name."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "serialization_alias_same_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="serialization_alias_same_name.py",
        extra_args=[
            "--aliases",
            str(ALIASES_DATA_PATH / "same_alias.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-serialization-alias",
        ],
    )


@pytest.mark.cli_doc(
    options=["--serialization-aliases"],
    option_description="""Apply custom Pydantic v2 serialization aliases via inline JSON or a JSON file path.

The `--serialization-aliases` option lets Pydantic v2 models keep input aliases
or validation aliases while using separate output-only names for serialization.""",
    input_schema="jsonschema/serialization_aliases.json",
    cli_args=[
        "--aliases",
        "aliases/serialization_aliases.json",
        "--serialization-aliases",
        "aliases/serialization_aliases_output.json",
        "--output-model-type",
        "pydantic_v2.BaseModel",
    ],
    golden_output="jsonschema/serialization_aliases.py",
)
def test_main_jsonschema_serialization_aliases_pydantic_v2(output_file: Path) -> None:
    """Test explicit serialization aliases with independent validation aliases."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "serialization_aliases.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="serialization_aliases.py",
        extra_args=[
            "--aliases",
            str(ALIASES_DATA_PATH / "serialization_aliases.json"),
            "--serialization-aliases",
            str(ALIASES_DATA_PATH / "serialization_aliases_output.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_serialization_aliases_inline_json_pydantic_v2(output_file: Path) -> None:
    """Test explicit serialization aliases from inline JSON."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "serialization_aliases.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="serialization_aliases.py",
        extra_args=[
            "--aliases",
            (ALIASES_DATA_PATH / "serialization_aliases.json").read_text(encoding="utf-8"),
            "--serialization-aliases",
            (ALIASES_DATA_PATH / "serialization_aliases_output.json").read_text(encoding="utf-8"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_serialization_aliases_with_use_serialization_alias_pydantic_v2(output_file: Path) -> None:
    """Test explicit serialization aliases override alias removal mode."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "serialization_aliases.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="serialization_aliases_with_use_serialization_alias.py",
        extra_args=[
            "--aliases",
            str(ALIASES_DATA_PATH / "serialization_aliases.json"),
            "--serialization-aliases",
            str(ALIASES_DATA_PATH / "serialization_aliases_output.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-serialization-alias",
        ],
    )


def test_main_jsonschema_serialization_aliases_invalid(output_file: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test invalid serialization_aliases mapping exits with an error."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "serialization_aliases.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="Unable to load serialization alias mapping",
        extra_args=[
            "--serialization-aliases",
            str(ALIASES_DATA_PATH / "serialization_aliases.json"),
        ],
    )


def test_main_jsonschema_discriminator_multiple_aliases_pydantic_v2(output_file: Path) -> None:
    """Test discriminator with multiple aliases using AliasChoices for Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_multiple_aliases.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--aliases",
            str(ALIASES_DATA_PATH / "discriminator_multiple_aliases.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_discriminator_no_literal_multiple_aliases_pydantic_v2(output_file: Path) -> None:
    """Test discriminator without literal in child models using multiple aliases for Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "discriminator_no_literal.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--aliases",
            str(ALIASES_DATA_PATH / "discriminator_no_literal.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_multiple_types_with_object(output_file: Path) -> None:
    """Test multiple types in array including object with properties generates Union type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "multiple_types_with_object.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@MSGSPEC_LEGACY_BLACK_SKIP
def test_main_jsonschema_type_alias_with_circular_ref_to_class_msgspec(min_version: str, output_file: Path) -> None:
    """Test TypeAlias with circular reference to class generates quoted forward refs."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "type_alias_with_circular_ref_to_class.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="type_alias_with_circular_ref_to_class_msgspec.py",
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--target-python-version",
            min_version,
        ],
    )


def test_main_jsonschema_enum_object_values(output_file: Path) -> None:
    """Test that enum with object values uses title/name/const for member names (issue #1620)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_object_values.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.cli_doc(
    options=["--collapse-root-models"],
    option_description="""Inline root model definitions instead of creating separate wrapper classes.

The --collapse-root-models option simplifies generated code by collapsing
root-level models (top-level type aliases) directly into their usage sites.
This eliminates unnecessary wrapper classes and produces more concise output,
especially useful when schemas define simple root types or type aliases.""",
    input_schema="jsonschema/collapse_root_models_empty_union.json",
    cli_args=["--collapse-root-models"],
    golden_output="main/jsonschema/jsonschema_collapse_root_models_empty_union.py",
)
def test_main_jsonschema_collapse_root_models_empty_union(output_file: Path) -> None:
    """Inline root model definitions instead of creating separate wrapper classes.

    The --collapse-root-models option simplifies generated code by collapsing
    root-level models (top-level type aliases) directly into their usage sites.
    This eliminates unnecessary wrapper classes and produces more concise output,
    especially useful when schemas define simple root types or type aliases.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_empty_union.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--collapse-root-models"],
    )


def test_main_jsonschema_collapse_root_models_with_optional(output_file: Path) -> None:
    """Test that collapse-root-models correctly preserves Optional import when needed."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_with_optional.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--collapse-root-models"],
    )


def test_main_jsonschema_collapse_root_models_nested_reference(output_file: Path) -> None:
    """Ensure nested references inside root models still get imported when collapsing."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_nested_reference.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--collapse-root-models"],
    )


@pytest.mark.cli_doc(
    options=["--collapse-root-models-name-strategy"],
    option_description="""Select which name to keep when collapsing root models with object references.

The --collapse-root-models-name-strategy option controls naming when collapsing
root models. 'child' keeps the inner model's name, 'parent' uses the wrapper's name.""",
    input_schema="jsonschema/collapse_root_models_name_strategy_child.json",
    cli_args=["--collapse-root-models", "--collapse-root-models-name-strategy", "child"],
    golden_output="main/jsonschema/jsonschema_collapse_root_models_name_strategy_child.py",
    related_options=["--collapse-root-models"],
)
def test_main_jsonschema_collapse_root_models_name_strategy_child(output_file: Path) -> None:
    """Select which name to keep when collapsing root models with object references.

    The --collapse-root-models-name-strategy option controls naming when collapsing
    root models. 'child' keeps the inner model's name, 'parent' uses the wrapper's name.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_child.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--collapse-root-models", "--collapse-root-models-name-strategy", "child"],
    )


def test_main_jsonschema_collapse_root_models_name_strategy_parent(output_file: Path) -> None:
    """Test collapse-root-models with parent name strategy uses wrapper's name for inner model."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_parent.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--collapse-root-models", "--collapse-root-models-name-strategy", "parent"],
    )


def test_main_jsonschema_collapse_root_models_name_strategy_requires_collapse_root_models(
    output_file: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test that --collapse-root-models-name-strategy requires --collapse-root-models."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_parent.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--collapse-root-models-name-strategy", "parent"],
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="--collapse-root-models-name-strategy requires --collapse-root-models",
    )


def test_main_jsonschema_collapse_root_models_name_strategy_multiple_wrappers(output_file: Path) -> None:
    """Test that parent strategy warns and skips when inner model has multiple wrappers."""
    with pytest.warns(UserWarning, match="Cannot apply 'parent' strategy.*multiple root models"):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_multiple_wrappers.json",
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            extra_args=["--collapse-root-models", "--collapse-root-models-name-strategy", "parent"],
        )


def test_main_jsonschema_collapse_root_models_name_strategy_direct_refs(output_file: Path) -> None:
    """Test that parent strategy warns and skips when inner model has direct references."""
    with pytest.warns(UserWarning, match="Cannot apply 'parent' strategy.*directly referenced"):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_direct_refs.json",
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            extra_args=["--collapse-root-models", "--collapse-root-models-name-strategy", "parent"],
        )


def test_main_jsonschema_collapse_root_models_name_strategy_with_inheritance(output_file: Path) -> None:
    """Test collapse-root-models with parent strategy when inner model has derived classes."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_with_inheritance.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=["--collapse-root-models", "--collapse-root-models-name-strategy", "parent"],
    )


def test_main_jsonschema_collapse_root_models_name_strategy_nested_wrappers_child(output_file: Path) -> None:
    """Test nested wrappers with child strategy - all wrappers collapsed."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_nested_wrappers.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="jsonschema_collapse_root_models_name_strategy_nested_wrappers_child.py",
        extra_args=[
            "--collapse-root-models",
            "--collapse-root-models-name-strategy",
            "child",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_collapse_root_models_name_strategy_nested_wrappers_parent(output_file: Path) -> None:
    """Test nested wrappers with parent strategy - partial collapse due to multiple refs."""
    with pytest.warns(UserWarning, match="Cannot apply 'parent' strategy.*multiple root models"):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_nested_wrappers.json",
            output_path=output_file,
            input_file_type="jsonschema",
            assert_func=assert_file_content,
            expected_file="jsonschema_collapse_root_models_name_strategy_nested_wrappers_parent_v2.py",
            extra_args=[
                "--collapse-root-models",
                "--collapse-root-models-name-strategy",
                "parent",
                "--output-model-type",
                "pydantic_v2.BaseModel",
            ],
        )


def test_main_jsonschema_collapse_root_models_name_strategy_complex_child(output_file: Path) -> None:
    """Test complex schema with multiple wrappers and inheritance using child strategy."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_complex.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="jsonschema_collapse_root_models_name_strategy_complex_child.py",
        extra_args=[
            "--collapse-root-models",
            "--collapse-root-models-name-strategy",
            "child",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_collapse_root_models_name_strategy_complex_parent(output_file: Path) -> None:
    """Test complex schema with multiple wrappers and inheritance using parent strategy."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "collapse_root_models_name_strategy_complex.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="jsonschema_collapse_root_models_name_strategy_complex_parent.py",
        extra_args=[
            "--collapse-root-models",
            "--collapse-root-models-name-strategy",
            "parent",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_main_jsonschema_file_url_ref(tmp_path: Path) -> None:
    """Test that file:// URL $ref is resolved correctly."""
    pet_schema = {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
        "required": ["name"],
    }
    pet_file = tmp_path / "pet.json"
    pet_file.write_text(json.dumps(pet_schema))

    main_schema = {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {"pet": {"$ref": pet_file.as_uri()}},
    }
    main_file = tmp_path / "main.json"
    main_file.write_text(json.dumps(main_schema))

    expected = (
        "# generated by datamodel-codegen:\n"
        "#   filename:  main.json\n\n"
        "from __future__ import annotations\n\n"
        "from pydantic import BaseModel\n\n\n"
        "class Pet(BaseModel):\n"
        "    name: str\n"
        "    age: int | None = None\n\n\n"
        "class Model(BaseModel):\n"
        "    pet: Pet | None = None\n"
    )
    run_main_and_assert(
        input_path=main_file,
        output_path=tmp_path / "output.py",
        input_file_type="jsonschema",
        expected_output=expected,
        ignore_whitespace=True,
        extra_args=["--disable-timestamp"],
    )


def test_main_jsonschema_file_url_ref_percent_encoded(tmp_path: Path) -> None:
    """Test that file:// URL with percent-encoded path is resolved correctly."""
    dir_with_space = tmp_path / "my schemas"
    dir_with_space.mkdir()

    pet_schema = {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {"name": {"type": "string"}},
    }
    pet_file = dir_with_space / "pet.json"
    pet_file.write_text(json.dumps(pet_schema))

    main_schema = {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {"pet": {"$ref": pet_file.as_uri()}},
    }
    main_file = tmp_path / "main.json"
    main_file.write_text(json.dumps(main_schema))

    expected = (
        "# generated by datamodel-codegen:\n"
        "#   filename:  main.json\n\n"
        "from __future__ import annotations\n\n"
        "from pydantic import BaseModel\n\n\n"
        "class Pet(BaseModel):\n"
        "    name: str | None = None\n\n\n"
        "class Model(BaseModel):\n"
        "    pet: Pet | None = None\n"
    )
    run_main_and_assert(
        input_path=main_file,
        output_path=tmp_path / "output.py",
        input_file_type="jsonschema",
        expected_output=expected,
        ignore_whitespace=True,
        extra_args=["--disable-timestamp"],
    )


@pytest.mark.benchmark
def test_main_jsonschema_root_model_default_value(output_file: Path) -> None:
    """Test RootModel default values are wrapped with type constructors."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_default_value.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_model_default_value.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-annotated",
            "--set-default-enum-member",
        ],
    )


@pytest.mark.benchmark
def test_main_jsonschema_root_model_default_value_no_annotated(output_file: Path) -> None:
    """Test RootModel default values without --use-annotated flag."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_default_value.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_model_default_value_no_annotated.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--set-default-enum-member",
        ],
    )


@pytest.mark.benchmark
def test_main_jsonschema_root_model_default_value_branches(output_file: Path) -> None:
    """Test RootModel default value branches."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_default_value_branches.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_model_default_value_branches.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-annotated",
        ],
    )


@pytest.mark.benchmark
def test_main_jsonschema_root_model_default_value_non_root(output_file: Path) -> None:
    """Test that non-RootModel references are not wrapped."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_default_value_non_root.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_model_default_value_non_root.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-annotated",
        ],
    )


@pytest.mark.benchmark
def test_main_jsonschema_extras_in_oneof(output_file: Path) -> None:
    """Test that extras are preserved in oneOf/anyOf structures (Issue #2403)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "extras_in_oneof.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="extras_in_oneof.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-include-all-keys",
        ],
    )


def test_main_jsonschema_ref_with_additional_keywords(output_dir: Path) -> None:
    """Test that $ref combined with additional keywords merges properties (Issue #2330)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_with_additional_keywords",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "ref_with_additional_keywords",
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_file"),
    [
        ("typing.TypedDict", "reserved_field_name_schema_typed_dict.py"),
        ("dataclasses.dataclass", "reserved_field_name_schema_dataclass.py"),
        ("pydantic_v2.BaseModel", "reserved_field_name_schema_pydantic.py"),
    ],
)
@pytest.mark.cli_doc(
    options=["--output-model-type"],
    option_description="""Select the output model type (Pydantic v2, Pydantic v2 dataclass,
dataclasses, TypedDict, msgspec).

The `--output-model-type` flag specifies which Python data model framework to use.
Each model type has different handling for reserved field names like 'schema':
TypedDict and dataclass preserve the name, while Pydantic renames with alias.""",
    input_schema="jsonschema/reserved_field_name_schema.json",
    cli_args=["--target-python-version", "3.11"],
    model_outputs={
        "typeddict": "main/jsonschema/reserved_field_name_schema_typed_dict.py",
        "dataclass": "main/jsonschema/reserved_field_name_schema_dataclass.py",
        "pydantic_v2": "main/jsonschema/reserved_field_name_schema_pydantic.py",
    },
)
@pytest.mark.benchmark
@LEGACY_BLACK_SKIP
def test_main_jsonschema_reserved_field_name(output_model: str, expected_file: str, output_file: Path) -> None:
    """Test reserved field name handling across model types (Issue #1833).

    This demonstrates how 'schema' field is handled:
    - TypedDict: not renamed (schema is not reserved)
    - dataclass: not renamed (schema is not reserved)
    - Pydantic: renamed to 'schema_' with alias (BaseModel.schema conflicts)
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reserved_field_name_schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_file,
        extra_args=[
            "--output-model-type",
            output_model,
            "--target-python-version",
            "3.11",
        ],
    )


@pytest.mark.benchmark
@LEGACY_BLACK_SKIP
def test_main_bundled_schema_with_id_local_file(output_file: Path) -> None:
    """Test bundled schema with $id using local file input (Issue #1798)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "bundled_schema_with_id.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="bundled_schema_with_id.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
@LEGACY_BLACK_SKIP
def test_main_bundled_schema_with_id_url(mock_httpx_get: HttpxGetMockFactory, output_file: Path) -> None:
    """Test bundled schema with $id using URL input produces same output as local file."""
    schema_path = JSON_SCHEMA_DATA_PATH / "bundled_schema_with_id.json"
    httpx_get_mock = mock_httpx_get(
        MockHttpxResponse("https://cdn.example.com/schemas/bundled_schema_with_id.json", schema_path)
    )

    run_main_url_and_assert(
        url="https://cdn.example.com/schemas/bundled_schema_with_id.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="bundled_schema_with_id.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        transform=lambda s: s.replace(
            "#   filename:  https://cdn.example.com/schemas/bundled_schema_with_id.json",
            "#   filename:  bundled_schema_with_id.json",
        ),
    )

    assert_httpx_get_kwargs(
        httpx_get_mock,
        expected_url="https://cdn.example.com/schemas/bundled_schema_with_id.json",
        call_count=1,
    )


@pytest.mark.parametrize(
    ("output_model", "expected_file"),
    [
        ("pydantic_v2.BaseModel", "use_frozen_field_v2.py"),
        ("dataclasses.dataclass", "use_frozen_field_dataclass.py"),
    ],
)
@pytest.mark.cli_doc(
    options=["--use-frozen-field"],
    option_description="""Generate frozen (immutable) field definitions for readOnly properties.

The `--use-frozen-field` flag generates frozen field definitions:
- Pydantic v2: `Field(frozen=True)`
- Dataclasses: silently ignored (no frozen fields generated)""",
    input_schema="jsonschema/use_frozen_field.json",
    cli_args=["--use-frozen-field"],
    model_outputs={
        "pydantic_v2": "main/jsonschema/use_frozen_field_v2.py",
        "dataclass": "main/jsonschema/use_frozen_field_dataclass.py",
    },
)
@pytest.mark.benchmark
@LEGACY_BLACK_SKIP
def test_main_use_frozen_field(output_model: str, expected_file: str, output_file: Path) -> None:
    """Generate frozen (immutable) field definitions for readOnly properties.

    The `--use-frozen-field` flag generates frozen field definitions:
    - Pydantic v2: `Field(frozen=True)`
    - Dataclasses: silently ignored (no frozen fields generated)
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_frozen_field.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_file,
        extra_args=["--output-model-type", output_model, "--use-frozen-field"],
    )


@pytest.mark.benchmark
@LEGACY_BLACK_SKIP
def test_main_use_frozen_field_no_readonly(output_file: Path) -> None:
    """Test --use-frozen-field with no readOnly fields produces no frozen fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "user.json",  # Has no readOnly fields
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="use_frozen_field_no_readonly.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--use-frozen-field"],
    )


@pytest.mark.parametrize(
    ("target_python_version", "expected_file"),
    [
        pytest.param("3.13", "use_frozen_field_typed_dict.py", marks=BLACK_PY313_SKIP),
        ("3.11", "use_frozen_field_typed_dict_py311.py"),
        ("3.10", "use_frozen_field_typed_dict_py310.py"),
    ],
)
@pytest.mark.cli_doc(
    options=["--use-frozen-field"],
    option_description="""Generate ReadOnly type hints for readOnly properties in TypedDict.

The `--use-frozen-field` flag generates ReadOnly type hints for TypedDict:
- Python 3.13+: uses `typing.ReadOnly`
- Python 3.11-3.12: uses `typing_extensions.ReadOnly`
- Python 3.10: uses `typing_extensions.ReadOnly` and `typing_extensions.NotRequired`""",
    input_schema="jsonschema/use_frozen_field.json",
    cli_args=["--output-model-type", "typing.TypedDict", "--use-frozen-field"],
    model_outputs={
        "typeddict": "main/jsonschema/use_frozen_field_typed_dict.py",
    },
)
@pytest.mark.benchmark
@LEGACY_BLACK_SKIP
def test_main_use_frozen_field_typed_dict(target_python_version: str, expected_file: str, output_file: Path) -> None:
    """Generate ReadOnly type hints for readOnly properties in TypedDict.

    The `--use-frozen-field` flag generates ReadOnly type hints for TypedDict:
    - Python 3.13+: uses `typing.ReadOnly`
    - Python 3.11-3.12: uses `typing_extensions.ReadOnly`
    - Python 3.10: uses `typing_extensions.ReadOnly` and `typing_extensions.NotRequired`
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_frozen_field.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_file,
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--use-frozen-field",
            "--target-python-version",
            target_python_version,
        ],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_file"),
    [
        ("dataclasses.dataclass", "default_factory_nested_model_dataclass.py"),
        ("pydantic_v2.BaseModel", "default_factory_nested_model_pydantic_v2.py"),
        ("msgspec.Struct", "default_factory_nested_model_msgspec.py"),
    ],
)
@pytest.mark.cli_doc(
    options=["--use-default-factory-for-optional-nested-models"],
    option_description="""Generate default_factory for optional nested model fields.

The `--use-default-factory-for-optional-nested-models` flag generates default_factory
for optional nested model fields instead of None default:
- Dataclasses: `field: Model | None = field(default_factory=Model)`
- Pydantic: `field: Model | None = Field(default_factory=Model)`
- msgspec: `field: Model | UnsetType = field(default_factory=Model)`""",
    input_schema="jsonschema/default_factory_nested_model.json",
    cli_args=["--use-default-factory-for-optional-nested-models"],
    model_outputs={
        "dataclass": "main/jsonschema/default_factory_nested_model_dataclass.py",
        "pydantic_v2": "main/jsonschema/default_factory_nested_model_pydantic_v2.py",
        "msgspec": "main/jsonschema/default_factory_nested_model_msgspec.py",
    },
)
@pytest.mark.benchmark
@LEGACY_BLACK_SKIP
def test_main_use_default_factory_for_optional_nested_models(
    output_model: str, expected_file: str, output_file: Path
) -> None:
    """Generate default_factory for optional nested model fields.

    The `--use-default-factory-for-optional-nested-models` flag generates default_factory
    for optional nested model fields instead of None default:
    - Dataclasses: `field: Model | None = field(default_factory=Model)`
    - Pydantic: `field: Model | None = Field(default_factory=Model)`
    - msgspec: `field: Model | UnsetType = field(default_factory=Model)`
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "default_factory_nested_model.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_file,
        extra_args=[
            "--output-model-type",
            output_model,
            "--use-default-factory-for-optional-nested-models",
        ],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_file"),
    [
        ("dataclasses.dataclass", "default_factory_nested_model_with_dict_dataclass.py"),
        ("pydantic_v2.BaseModel", "default_factory_nested_model_with_dict_pydantic_v2.py"),
        ("msgspec.Struct", "default_factory_nested_model_with_dict_msgspec.py"),
    ],
)
@pytest.mark.benchmark
@LEGACY_BLACK_SKIP
def test_main_use_default_factory_for_optional_nested_models_with_dict(
    output_model: str, expected_file: str, output_file: Path
) -> None:
    """Test --use-default-factory-for-optional-nested-models with dict union skips dict types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "default_factory_nested_model_with_dict.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_file,
        extra_args=[
            "--output-model-type",
            output_model,
            "--use-default-factory-for-optional-nested-models",
        ],
    )


@pytest.mark.benchmark
def test_main_field_name_shadows_class_name(output_file: Path) -> None:
    """Test field name shadowing class name is renamed with alias for Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_name_shadows_class_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.cli_doc(
    options=["--allof-merge-mode"],
    option_description="""Merge constraints from root model references in allOf schemas.

The `--allof-merge-mode constraints` merges only constraint properties
(minLength, maximum, etc.) from parent schemas referenced in allOf.
This ensures child schemas inherit validation constraints while keeping
other properties separate.""",
    input_schema="jsonschema/allof_root_model_constraints.json",
    cli_args=["--allof-merge-mode", "constraints"],
    golden_output="main/jsonschema/allof_root_model_constraints_merge.py",
    comparison_output="main/jsonschema/allof_root_model_constraints.py",
)
@pytest.mark.benchmark
def test_main_allof_root_model_constraints_merge(output_file: Path) -> None:
    """Merge constraints from root model references in allOf schemas.

    The `--allof-merge-mode constraints` merges only constraint properties
    (minLength, maximum, etc.) from parent schemas referenced in allOf.
    This ensures child schemas inherit validation constraints while keeping
    other properties separate.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_root_model_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allof_root_model_constraints_merge.py",
        extra_args=["--allof-merge-mode", "constraints"],
    )


@pytest.mark.cli_doc(
    options=["--allof-class-hierarchy"],
    option_description="""Controls how allOf schemas are represented in the generated class hierarchy.
`--allof-class-hierarchy if-no-conflict` (default) creates parent classes for allOf schemas
only when there are no property conflicts between parent schemas. Otherwise, properties are merged into the child class
which is then decoupled from the parent classes and no longer inherits from them.
`--allof-class-hierarchy always` keeps class hierarchy for allOf schemas,
even in multiple inheritance scenarios where two parent schemas define the same property.""",
    input_schema="jsonschema/allof_root_model_constraints.json",
    cli_args=["--allof-class-hierarchy", "always"],
    golden_output="main/jsonschema/allof_class_hierarchy.py",
    comparison_output="main/jsonschema/allof_class_hierarchy_ref.py",
)
@pytest.mark.benchmark
def test_main_allof_class_hierarchy(output_file: Path) -> None:
    """Control how allOf schemas are represented in the generated class hierarchy.

    The `--allof-class-hierarchy` option configures whether the generator preserves
    parent classes for allOf schemas (even when there are property conflicts) or
    falls back to merging properties into a single class.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_class_hierarchy.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allof_class_hierarchy.py",
        extra_args=["--allof-class-hierarchy", "always"],
    )


@pytest.mark.benchmark
def test_main_allof_root_model_constraints_none(output_file: Path) -> None:
    """Test allOf with root model reference without merging (issue #1901)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_root_model_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allof_root_model_constraints.py",
        extra_args=["--allof-merge-mode", "none"],
    )


@pytest.mark.benchmark
def test_main_allof_root_model_constraints_merge_pydantic_v2(output_file: Path) -> None:
    """Test allOf with root model constraints in Pydantic v2 (issue #2232).

    When merging pattern constraints that use lookaround assertions,
    the generated RootModel should use the base type in the generic
    (e.g., RootModel[str]) rather than the constrained type
    (e.g., RootModel[constr(pattern=...)]) to avoid regex evaluation
    before model_config with regex_engine='python-re' is processed.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_root_model_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allof_root_model_constraints_merge_pydantic_v2.py",
        extra_args=[
            "--allof-merge-mode",
            "constraints",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
def test_main_nested_lookaround_array_pydantic_v2(output_file: Path) -> None:
    """Test nested lookaround pattern detection in array items (issue #2232).

    When array items have patterns with lookaround assertions, the lookaround
    should be detected in nested types and regex_engine='python-re' should be
    added. The RootModel generic should use the base type (list[str]) rather
    than the constrained type (list[constr(pattern=...)]).
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nested_lookaround_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="nested_lookaround_array_pydantic_v2.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
def test_main_lookaround_anyof_nullable_pydantic_v2(output_file: Path) -> None:
    """Test lookaround pattern with anyOf null for union/optional path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "lookaround_anyof_nullable.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="lookaround_anyof_nullable_pydantic_v2.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
def test_main_lookaround_anyof_nullable_pydantic_v2_dataclass(output_file: Path) -> None:
    """Lookaround reachable only through a referenced alias sets regex_engine on the dataclass.

    The alias becomes a bare ``TypeAliasType`` that carries no config, so the consuming
    pydantic v2 dataclass must emit ``ConfigDict(regex_engine="python-re")`` or fail to
    import under pydantic's default rust regex engine. ``force_exec_validation`` proves the
    generated module actually constructs.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "lookaround_anyof_nullable.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="lookaround_anyof_nullable_pydantic_v2_dataclass.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.dataclass",
        ],
        force_exec_validation=True,
    )


@pytest.mark.benchmark
def test_main_lookaround_anyof_nullable_pydantic_v2_dataclass_json_validation(output_file: Path) -> None:
    """Generated pydantic v2 dataclasses validate lookaround patterns through the shared JSON helper."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "lookaround_anyof_nullable.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.dataclass",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="lookaround_anyof_nullable_dataclass_json_validation",
        model_name="Model",
        valid_json='{"value": "ABC"}',
        invalid_json='{"value": "abc"}',
        expected_error_type="string_pattern_mismatch",
        expected_attribute_path=("value",),
        expected_attribute_value="ABC",
    )


@LEGACY_BLACK_SKIP
@pytest.mark.benchmark
def test_main_lookaround_mixed_constraints_pydantic_v2(output_file: Path) -> None:
    """Test lookaround pattern with union of constr and conint to test base_type_hint fallback for non-constr types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "lookaround_mixed_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="lookaround_mixed_constraints_pydantic_v2.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
def test_main_lookaround_dict_pydantic_v2(output_file: Path) -> None:
    """Test lookaround pattern in dict values for base_type_hint dict path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "lookaround_dict.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="lookaround_dict_pydantic_v2.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
def test_main_lookaround_union_types_pydantic_v2(output_file: Path) -> None:
    """Test lookaround pattern in union for base_type_hint union path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "lookaround_union_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="lookaround_union_types_pydantic_v2.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
def test_main_nested_lookaround_array_generic_container(output_file: Path) -> None:
    """Test lookaround pattern with --use-generic-container-types for Sequence path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nested_lookaround_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="nested_lookaround_array_generic_container.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-container-types",
        ],
    )


@pytest.mark.benchmark
def test_main_lookaround_dict_generic_container(output_file: Path) -> None:
    """Test lookaround dict pattern with --use-generic-container-types for Mapping path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "lookaround_dict.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="lookaround_dict_generic_container.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-generic-container-types",
        ],
    )


@pytest.mark.benchmark
def test_main_nested_lookaround_array_standard_collections(output_file: Path) -> None:
    """Test lookaround pattern with --use-standard-collections for list path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nested_lookaround_array.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="nested_lookaround_array_standard_collections.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-standard-collections",
        ],
    )


@pytest.mark.benchmark
def test_main_lookaround_dict_standard_collections(output_file: Path) -> None:
    """Test lookaround dict pattern with --use-standard-collections for dict path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "lookaround_dict.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="lookaround_dict_standard_collections.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-standard-collections",
        ],
    )


@pytest.mark.benchmark
def test_main_lookaround_dict_key_pydantic_v2(output_file: Path) -> None:
    """Test lookaround pattern on dict key for dict_key.all_data_types path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "lookaround_dict_key.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="lookaround_dict_key_pydantic_v2.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
def test_main_nullable_array_items_strict_nullable(output_file: Path) -> None:
    """Test nullable array items with strict-nullable flag (issue #1815)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "nullable_array_items.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="nullable_array_items_strict_nullable.py",
        extra_args=[
            "--strict-nullable",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
def test_main_builtin_field_names(output_file: Path) -> None:
    """Test that builtin type names as field names don't break code generation (issue #2431).

    When a field has a name that matches a Python builtin (int, float, bool, str),
    the generated code should still use the builtin type directly without aliasing,
    since builtin types don't require imports.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "builtin_field_names.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="builtin_field_names.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.benchmark
def test_main_builtin_field_names_snake_case(output_file: Path) -> None:
    """Test builtin field rename keeps original alias after snake_case normalization."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "builtin_field_names_snake_case.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="builtin_field_names_snake_case.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--snake-case-field",
        ],
    )


@pytest.mark.benchmark
def test_main_builtin_field_names_container_types(output_file: Path) -> None:
    """Test builtin container names are renamed for set/frozenset/tuple/list/dict collisions."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "builtin_field_names_container_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="builtin_field_names_container_types.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-constraints",
            "--use-unique-items-as-set",
        ],
    )


@pytest.mark.benchmark
def test_main_builtin_field_names_container_types_no_use_standard_collections(output_file: Path) -> None:
    """Test builtin container renaming when container types are imported from typing."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "builtin_field_names_container_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="builtin_field_names_container_types_no_use_standard_collections.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-constraints",
            "--use-unique-items-as-set",
            "--no-use-standard-collections",
        ],
    )


@pytest.mark.benchmark
@pytest.mark.parametrize(
    ("target_python_version", "expected_file"),
    [
        ("3.10", "builtin_field_names_target_python_version_310.py"),
        pytest.param("3.13", "builtin_field_names_target_python_version_313.py", marks=BLACK_PY313_SKIP),
    ],
)
def test_main_builtin_field_names_target_python_version(
    output_file: Path, target_python_version: str, expected_file: str
) -> None:
    """Test builtin field renaming follows target Python version instead of runtime builtins."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "builtin_field_names_target_python_version.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_file,
        skip_code_validation=True,
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            target_python_version,
        ],
    )


@pytest.mark.benchmark
def test_main_root_model_config_populate_by_name(output_file: Path) -> None:
    """Test that RootModel subclasses don't get populate_by_name config (issue #2483).

    The populate_by_name config is meaningless for RootModel because it only has
    a single 'root' field. Only BaseModel subclasses should have this config.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_config.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_model_config_populate_by_name.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--allow-population-by-field-name",
        ],
    )


@pytest.mark.benchmark
def test_main_root_model_config_frozen(output_file: Path) -> None:
    """Test that RootModel subclasses DO get frozen config (issue #2483).

    Unlike populate_by_name, the frozen config is meaningful for RootModel
    because it prevents mutation of the root value. Both BaseModel and RootModel
    subclasses should have this config when --enable-faux-immutability is used.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_config.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_model_config_frozen.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--enable-faux-immutability",
        ],
    )


@pytest.mark.cli_doc(
    options=["--naming-strategy"],
    option_description="""Use parent-prefixed naming strategy for duplicate model names.

The `--naming-strategy parent-prefixed` flag prefixes model names with their
parent model name when duplicates occur. For example, if both `Order` and
`Cart` have an inline `Item` definition, they become `OrderItem` and `CartItem`.""",
    input_schema="jsonschema/naming_strategy/input.json",
    cli_args=["--naming-strategy", "parent-prefixed"],
    golden_output="main/jsonschema/naming_strategy/parent_prefixed/output.py",
    related_options=["--duplicate-name-suffix", "--parent-scoped-naming"],
)
@freeze_time("2019-07-26")
def test_main_naming_strategy_parent_prefixed(output_file: Path) -> None:
    """Use parent-prefixed naming strategy for duplicate model names.

    The `--naming-strategy parent-prefixed` flag prefixes model names with their
    parent model name when duplicates occur. For example, if both `Order` and
    `Cart` have an inline `Item` definition, they become `OrderItem` and `CartItem`.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "naming_strategy" / "input.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="naming_strategy/parent_prefixed/output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--naming-strategy",
            "parent-prefixed",
        ],
    )


@freeze_time("2019-07-26")
def test_main_naming_strategy_full_path(output_file: Path) -> None:
    """Use full-path naming strategy for duplicate model names.

    The `--naming-strategy full-path` flag uses the full schema path
    to generate unique names by concatenating ancestor names.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "naming_strategy" / "input.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="naming_strategy/full_path/output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--naming-strategy",
            "full-path",
        ],
    )


@pytest.mark.cli_doc(
    options=["--duplicate-name-suffix"],
    option_description="""Customize suffix for duplicate model names.

The `--duplicate-name-suffix` flag allows specifying custom suffixes for
resolving duplicate names by type. The value is a JSON mapping where keys
are type names ('model', 'enum', 'default') and values are suffix strings.
For example, `{"model": "Schema"}` changes `Item1` to `ItemSchema`.""",
    input_schema="jsonschema/naming_strategy/input.json",
    cli_args=["--duplicate-name-suffix", '{"model": "Schema"}'],
    golden_output="main/jsonschema/naming_strategy/duplicate_name_suffix/output.py",
    related_options=["--naming-strategy"],
)
@freeze_time("2019-07-26")
def test_main_duplicate_name_suffix(output_file: Path) -> None:
    """Customize suffix for duplicate model names.

    The `--duplicate-name-suffix` flag allows specifying custom suffixes for
    resolving duplicate names by type. The value is a JSON mapping where keys
    are type names ('model', 'enum', 'default') and values are suffix strings.
    For example, `{"model": "Schema"}` changes `Item1` to `ItemSchema`.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "naming_strategy" / "input.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="naming_strategy/duplicate_name_suffix/output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--duplicate-name-suffix",
            '{"model": "Schema"}',
        ],
    )


@freeze_time("2019-07-26")
def test_main_naming_strategy_complex_numbered(output_file: Path) -> None:
    """Test numbered strategy with complex nested schema and multiple duplicates.

    Tests deeply nested structures (Company > employees > employee > address)
    and multiple models with same name (4 different Address definitions).
    Expected: Address, Address1, Address2, Address3.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "naming_strategy" / "complex_input.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="naming_strategy/complex_numbered/output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@freeze_time("2019-07-26")
def test_main_naming_strategy_complex_parent_prefixed(output_file: Path) -> None:
    """Test parent-prefixed strategy with complex nested schema.

    Tests deeply nested structures where each Address gets a unique name
    based on its parent hierarchy.
    Expected: ModelCompanyAddress, ModelCompanyEmployeeAddress,
    ModelCustomerAddress, ModelWarehouseAddress.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "naming_strategy" / "complex_input.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="naming_strategy/complex_parent_prefixed/output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--naming-strategy",
            "parent-prefixed",
        ],
    )


@freeze_time("2019-07-26")
def test_main_naming_strategy_complex_duplicate_suffix(output_file: Path) -> None:
    """Test duplicate-name-suffix with complex schema having multiple duplicates.

    Tests that custom suffix is applied consistently across multiple duplicates.
    Expected: Address, AddressSchema, AddressSchema1, AddressSchema2.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "naming_strategy" / "complex_input.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="naming_strategy/complex_duplicate_suffix/output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--duplicate-name-suffix",
            '{"model": "Schema"}',
        ],
    )


@freeze_time("2019-07-26")
@pytest.mark.cli_doc(
    options=["--naming-strategy"],
    option_description="""Use primary-first strategy to keep clean names for top-level definitions.

The `--naming-strategy primary-first` keeps clean names for primary definitions
(directly under #/definitions/, #/components/schemas/, #/$defs/).
Inline/nested definitions get numeric suffixes.""",
    input_schema="jsonschema/naming_strategy/primary_first_input.json",
    cli_args=["--naming-strategy", "primary-first"],
    golden_output="jsonschema/naming_strategy/complex_primary_first/output.py",
)
def test_main_naming_strategy_primary_first(output_file: Path) -> None:
    """Use primary-first strategy to keep clean names for top-level definitions.

    The `--naming-strategy primary-first` keeps clean names for primary definitions
    (directly under #/definitions/, #/components/schemas/, #/$defs/).
    Inline/nested definitions get numeric suffixes.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "naming_strategy" / "primary_first_input.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="naming_strategy/complex_primary_first/output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--naming-strategy",
            "primary-first",
        ],
    )


@freeze_time("2019-07-26")
def test_main_naming_strategy_primary_first_multi_file(output_file: Path) -> None:
    """Test primary-first strategy with multiple files having same-named definitions.

    When multiple JSON schema files have definitions with the same name,
    the primary input file's definition should keep the clean name,
    while external references get numeric suffixes.

    This fixes GitHub issue #1300.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "naming_strategy" / "primary_first_multi_file" / "main.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="naming_strategy/primary_first_multi_file/output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--naming-strategy",
            "primary-first",
        ],
    )


def test_main_duplicate_name_suffix_invalid_json(output_file: Path) -> None:
    """Test that invalid JSON in --duplicate-name-suffix raises an error."""
    run_main_with_args(
        [
            "--input",
            str(JSON_SCHEMA_DATA_PATH / "naming_strategy" / "input.json"),
            "--output",
            str(output_file),
            "--duplicate-name-suffix",
            "invalid json",
        ],
        expected_exit=Exit.ERROR,
    )


@freeze_time("2019-07-26")
def test_main_parent_scoped_naming_backward_compat(output_file: Path) -> None:
    """Test --parent-scoped-naming backward compatibility (deprecated flag)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "naming_strategy" / "input.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="naming_strategy/parent_prefixed/output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--parent-scoped-naming",
        ],
    )


@pytest.mark.cli_doc(
    options=["--use-root-model-type-alias"],
    option_description="""Generate RootModel as type alias format for better mypy support.

When enabled, root models with simple types are generated as type aliases
instead of class definitions, improving mypy type inference.""",
    input_schema="jsonschema/root_model_type_alias.json",
    cli_args=["--use-root-model-type-alias", "--output-model-type", "pydantic_v2.BaseModel"],
    golden_output="jsonschema/root_model_type_alias.py",
)
def test_main_use_root_model_type_alias(output_file: Path) -> None:
    """Generate RootModel as type alias format for better mypy support (issue #1903)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "root_model_type_alias.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="root_model_type_alias.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-root-model-type-alias",
            "--target-python-version",
            "3.10",
        ],
    )


@pytest.mark.isolate_builtin_formatter_config
def test_main_jsonschema_schema_id(
    capsys: pytest.CaptureFixture, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test that $id is exposed as schema_id in custom templates (issue #2098)."""
    model_base._get_environment.cache_clear()
    model_base._get_template_with_custom_dir.cache_clear()
    monkeypatch.chdir(tmp_path)
    with freeze_time(TIMESTAMP):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "schema_id.json",
            output_path=None,
            expected_stdout_path=EXPECTED_JSON_SCHEMA_PATH / "schema_id.py",
            capsys=capsys,
            input_file_type=None,
            extra_args=[
                "--custom-template-dir",
                str(DATA_PATH / "templates_schema_id"),
                "--output-model-type",
                "pydantic_v2.BaseModel",
            ],
        )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        (
            "pydantic_v2.BaseModel",
            "model_extras_v2.py",
        ),
    ],
)
@pytest.mark.cli_doc(
    options=["--model-extra-keys"],
    option_description="""Add model-level schema extensions to ConfigDict json_schema_extra.

The `--model-extra-keys` flag adds specified x-* extensions from the schema
to the model's ConfigDict json_schema_extra.""",
    input_schema="jsonschema/model_extras.json",
    cli_args=["--model-extra-keys", "x-custom-metadata"],
    model_outputs={
        "pydantic_v2": "main/jsonschema/model_extras_v2.py",
    },
)
def test_main_jsonschema_model_extras(output_model: str, expected_output: str, output_file: Path) -> None:
    """Add model-level schema extensions to ConfigDict json_schema_extra.

    The `--model-extra-keys` flag adds specified x-* extensions from the schema
    to the model's ConfigDict json_schema_extra.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_extras.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=[
            "--output-model-type",
            output_model,
            "--model-extra-keys",
            "x-custom-metadata",
        ],
    )


def test_main_jsonschema_standard_schema_metadata(output_file: Path) -> None:
    """Preserve standard schema metadata in pydantic v2 json_schema_extra."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_metadata.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="schema_metadata_v2.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        (
            "pydantic_v2.BaseModel",
            "model_extras_without_x_prefix_v2.py",
        ),
    ],
)
@pytest.mark.cli_doc(
    options=["--model-extra-keys-without-x-prefix"],
    option_description="""Strip x- prefix from model-level schema extensions and add to ConfigDict json_schema_extra.

The `--model-extra-keys-without-x-prefix` flag adds specified x-* extensions
from the schema to the model's ConfigDict json_schema_extra with the x- prefix stripped.""",
    input_schema="jsonschema/model_extras.json",
    cli_args=["--model-extra-keys-without-x-prefix", "x-custom-metadata", "x-version"],
    model_outputs={
        "pydantic_v2": "main/jsonschema/model_extras_without_x_prefix_v2.py",
    },
)
def test_main_jsonschema_model_extras_without_x_prefix(
    output_model: str, expected_output: str, output_file: Path
) -> None:
    """Strip x- prefix from model-level schema extensions and add to ConfigDict json_schema_extra.

    The `--model-extra-keys-without-x-prefix` flag adds specified x-* extensions
    from the schema to the model's ConfigDict json_schema_extra with the x- prefix stripped.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_extras.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=[
            "--output-model-type",
            output_model,
            "--model-extra-keys-without-x-prefix",
            "x-custom-metadata",
            "x-version",
        ],
    )


@pytest.mark.parametrize(
    ("output_model", "expected_output"),
    [
        (
            "pydantic_v2.BaseModel",
            "model_extras_no_match_v2.py",
        ),
    ],
)
def test_main_jsonschema_model_extras_no_match(output_model: str, expected_output: str, output_file: Path) -> None:
    """No json_schema_extra when specified model-extra-keys don't match schema extensions.

    When the specified key doesn't exist in the schema's x-* extensions,
    no json_schema_extra is added to ConfigDict.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_extras.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_output,
        extra_args=[
            "--output-model-type",
            output_model,
            "--model-extra-keys",
            "x-nonexistent",
        ],
    )


def test_main_jsonschema_non_dict_files_in_directory(output_dir: Path) -> None:
    """Test that non-dict files (lists, empty files) are skipped with warnings."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "non_dict_files",
        output_path=output_dir,
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "non_dict_files",
        input_file_type="jsonschema",
    )


def test_main_jsonschema_ref_to_json_list_file() -> None:
    """Test that $ref to a JSON file containing a list raises TypeError."""
    with pytest.raises(TypeError, match="Expected dict, got list"):
        generate(
            input_=JSON_SCHEMA_DATA_PATH / "ref_to_json_list" / "main.json",
            input_file_type=InputFileType.JsonSchema,
        )


def test_main_jsonschema_x_python_import_unused(output_file: Path) -> None:
    """Test x-python-import entries in $defs are handled without model generation."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_import_unused.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="x_python_import_unused.py",
    )


@pytest.mark.parametrize(
    "x_python_type",
    [
        "X[1]; __import__('builtins').print('XPT_EXEC') #",
        "__import__('builtins').print('XPT_EXEC')",
        "Callable[[str], __import__('builtins').print('XPT_EXEC')]",
        "str\nprint('XPT_EXEC')\n#",
    ],
)
@pytest.mark.parametrize(
    "output_model",
    [
        "pydantic_v2.BaseModel",
        "pydantic_v2.dataclass",
        "dataclasses.dataclass",
        "msgspec.Struct",
        "typing.TypedDict",
    ],
)
def test_x_python_type_rejects_unsafe_value(
    x_python_type: str,
    output_model: str,
    output_file: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Reject unsafe x-python-type values."""
    input_file = output_file.with_suffix(".json")
    input_file.write_text(
        json.dumps({
            "type": "object",
            "properties": {
                "payload": {
                    "type": "string",
                    "x-python-type": x_python_type,
                }
            },
        }),
        encoding="utf-8",
    )
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        output_should_not_exist=True,
        capsys=capsys,
        expected_stderr_contains="x-python-type must be a valid Python type annotation",
        extra_args=["--output-model-type", output_model],
    )


def test_x_python_type_callable(output_file: Path) -> None:
    """Test x-python-type with Callable preserves the Callable type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_callable.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_callable_anyof(output_file: Path) -> None:
    """Test x-python-type in anyOf preserves the Callable type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_callable_anyof.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_compatible_set(output_file: Path) -> None:
    """Test x-python-type with compatible type (Set) uses existing flag logic."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_compatible_set.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_builtin_dict_collision(output_file: Path) -> None:
    """Test x-python-type Dict keeps builtin dict collision handling with typing containers."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_builtin_dict_collision.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-constraints",
            "--no-use-standard-collections",
        ],
    )


def test_x_python_type_fqpath(output_file: Path) -> None:
    """Test x-python-type with fully qualified path (e.g., collections.abc.Callable)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_fqpath.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_no_schema_type(output_file: Path) -> None:
    """Test x-python-type when schema type is not specified."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_no_schema_type.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_custom_fqpath(output_file: Path) -> None:
    """Test x-python-type with custom fully qualified path not in predefined imports."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_custom_fqpath.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_nested_imports(output_file: Path) -> None:
    """Test x-python-type with nested types that require imports (e.g., Callable[[Iterable[str]], str])."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_nested_imports.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_qualified_spans(output_file: Path) -> None:
    """Test x-python-type qualified-name shortening uses annotation spans."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_qualified_spans.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_nested_unknown_type(output_file: Path) -> None:
    """Test x-python-type with nested types not in PYTHON_TYPE_IMPORTS (e.g., MyCustomType)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_nested_unknown_type.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_dynamic_resolve(output_file: Path) -> None:
    """Test x-python-type with types resolved dynamically (not in static PYTHON_TYPE_IMPORTS)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_dynamic_resolve.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        extra_args=["--output-model-type", "typing.TypedDict"],
    )


def test_x_python_type_union_anyof(output_file: Path) -> None:
    """Test x-python-type with union type in anyOf schema."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "x_python_type_union_anyof.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
    )


@pytest.mark.cli_doc(
    options=["--default-values"],
    option_description="""Override field default values via inline JSON or a JSON file path.

The `--default-values` option allows specifying default values for fields via an inline JSON object or JSON file.
Supports scoped format (ClassName.field) for hierarchical overrides.""",
    input_schema="jsonschema/default_values_override.json",
    cli_args=["--default-values", "default_values/scoped_defaults.json"],
    golden_output="jsonschema/jsonschema_default_values_override.py",
)
def test_main_jsonschema_default_values_override(output_file: Path) -> None:
    """Test default value overrides from a JSON file path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "default_values_override.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="jsonschema_default_values_override.py",
        extra_args=[
            "--default-values",
            str(DEFAULT_VALUES_DATA_PATH / "scoped_defaults.json"),
        ],
    )


def test_main_jsonschema_default_values_override_inline_json(output_file: Path) -> None:
    """Test default value overrides from inline JSON."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "default_values_override.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="jsonschema_default_values_override.py",
        extra_args=[
            "--default-values",
            (DEFAULT_VALUES_DATA_PATH / "scoped_defaults.json").read_text(encoding="utf-8"),
        ],
    )


def test_main_jsonschema_default_values_allof(output_file: Path) -> None:
    """Test default value overrides with allOf inheritance."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "default_values_allof.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="jsonschema_default_values_allof.py",
        extra_args=[
            "--use-default",
            "--default-values",
            str(DEFAULT_VALUES_DATA_PATH / "allof_defaults.json"),
        ],
    )


def test_ref_nullable_only_no_duplicate_model(output_file: Path) -> None:
    """Test that $ref + nullable: true does not create duplicate models.

    When a property has $ref with nullable: true, it should use the referenced
    type directly (e.g., User) with Optional type annotation, not create a new
    model with a derived name (e.g., UserA, UserB).

    See: https://github.com/koxudaxi/datamodel-code-generator/discussions/1792
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_nullable_only.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_nullable_only.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_ref_nullable_only_strict_nullable(output_file: Path) -> None:
    """Test $ref + nullable: true with --strict-nullable flag.

    The output should be the same as without strict-nullable for this case,
    using the referenced type directly with Optional annotation.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_nullable_only.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_nullable_only_strict.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--strict-nullable"],
    )


def test_ref_nullable_with_metadata_no_duplicate_model(output_file: Path) -> None:
    """Test $ref + nullable: true + metadata (title/description) does not merge.

    When a property has $ref with nullable: true and metadata-only fields like
    title or description, it should not create a new model. The metadata should
    be applied to the field, and the reference should be used directly.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_nullable_with_metadata.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_nullable_with_metadata.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--strict-nullable"],
    )


def test_ref_nullable_with_constraint_creates_model(output_file: Path) -> None:
    """Test $ref + nullable: true + constraint DOES create a merged model.

    When a property has $ref with nullable: true AND a schema constraint like
    minLength, it should merge the schemas and create a new model because the
    constraint affects the schema structure.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_nullable_with_constraint.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_nullable_with_constraint.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--strict-nullable"],
    )


def test_ref_nullable_with_extra_uses_reference_directly(output_file: Path) -> None:
    """Test $ref + nullable: true + non-schema-affecting extras uses reference directly.

    When a property has $ref with nullable: true AND extras that the tool cannot
    structurally process (like 'if'), it should use the reference directly
    instead of creating a merged model.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_nullable_with_extra.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_nullable_with_extra.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--strict-nullable"],
    )


def test_reduce_duplicate_field_types(output_file: Path) -> None:
    """Test reducing duplicate field types using $ref and --use-type-alias.

    When multiple classes share the same field type defined in $defs,
    using --use-type-alias creates a single TypeAlias that's reused across classes.
    This is the recommended pattern to avoid duplicate Annotated field definitions.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "reduce_duplicate_field_types.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="reduce_duplicate_field_types.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--use-type-alias"],
    )


@pytest.mark.cli_doc(
    options=["--validators"],
    option_description="""Add custom field validators to generated Pydantic v2 models.

The `--validators` option takes an inline JSON object or JSON file defining validators per model.
Each validator specifies the field(s) to validate, the validation function
to import, and optionally the mode (before/after/wrap/plain).
This allows injecting custom validation logic into generated models.""",
    input_schema="jsonschema/field_validators.json",
    cli_args=[
        "--validators",
        "tests/data/jsonschema/field_validators_config.json",
        "--output-model-type",
        "pydantic_v2.BaseModel",
        "--use-annotated",
        "--disable-timestamp",
    ],
    golden_output="jsonschema/field_validators.py",
)
def test_field_validators(output_file: Path) -> None:
    """Add custom field validators to generated Pydantic v2 models.

    The `--validators` option takes an inline JSON object or JSON file defining validators per model.
    Each validator specifies the field(s) to validate, the validation function
    to import, and optionally the mode (before/after/wrap/plain).
    This allows injecting custom validation logic into generated models.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_validators.py",
        extra_args=[
            "--validators",
            str(JSON_SCHEMA_DATA_PATH / "field_validators_config.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-annotated",
            "--disable-timestamp",
        ],
        skip_code_validation=True,
    )


def test_field_validators_inline_json(output_file: Path) -> None:
    """Test validators configuration from inline JSON."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_validators.py",
        extra_args=[
            "--validators",
            (JSON_SCHEMA_DATA_PATH / "field_validators_config.json").read_text(encoding="utf-8"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-annotated",
            "--disable-timestamp",
        ],
        skip_code_validation=True,
    )


@pytest.mark.cli_doc(
    options=["--generate-schema-validators"],
    option_description="""Generate experimental Pydantic v2 model validators for JSON Schema runtime rules.

The `--generate-schema-validators` option emits schema-derived model validators
for object constraints that cannot be represented as type hints alone, including
patternProperties on composed object models, required-only oneOf/anyOf groups,
and simple if/then/else required-property conditions. This feature is
experimental and may change as JSON Schema coverage is expanded.""",
    input_schema="jsonschema/schema_validators.json",
    cli_args=[
        "--generate-schema-validators",
        "--output-model-type",
        "pydantic_v2.BaseModel",
        "--disable-timestamp",
    ],
    golden_output="jsonschema/schema_validators.py",
)
def test_main_jsonschema_generate_schema_validators(output_file: Path) -> None:
    """Generate Pydantic v2 model validators for JSON Schema runtime rules."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="schema_validators.py",
        extra_args=[
            "--generate-schema-validators",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-annotated",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output",
        model_name="PatternTarget",
        valid_json='{"first":"x","Alpha":{"second":"y"}}',
        invalid_json='{"first":"x","1bad":{"second":"y"}}',
        expected_error_type="value_error",
        expected_attribute_path=("Alpha", "second"),
        expected_attribute_value="y",
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output",
        model_name="DirectPatternBag",
        valid_json='{"item_1":7}',
        invalid_json='{"bad":7}',
        expected_error_type="value_error",
        expected_attribute_path=("item_1",),
        expected_attribute_value=7,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output",
        model_name="ValidatorOnlyChild",
        valid_json='{"first":"x","extra_count":3}',
        invalid_json='{"first":"x","bad":3}',
        expected_error_type="value_error",
        expected_attribute_path=("extra_count",),
        expected_attribute_value=3,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output",
        model_name="ValidatorOnlyPatternRef",
        valid_json='{"first":"x","ref_extra_count":3}',
        invalid_json='{"first":"x","bad":3}',
        expected_error_type="value_error",
        expected_attribute_path=("ref_extra_count",),
        expected_attribute_value=3,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output",
        model_name="OneOfContact",
        valid_json='{"email":"a@example.com"}',
        invalid_json='{"email":"a@example.com","phone":"123"}',
        expected_error_type="value_error",
        expected_attribute_path=("email",),
        expected_attribute_value="a@example.com",
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output",
        model_name="AnyOfContact",
        valid_json='{"email":"a@example.com","phone":"123"}',
        invalid_json="{}",
        expected_error_type="value_error",
        expected_attribute_path=("phone",),
        expected_attribute_value="123",
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output",
        model_name="ConditionalPayload",
        valid_json='{"kind":"metric","metric":7}',
        invalid_json='{"kind":"metric"}',
        expected_error_type="value_error",
        expected_attribute_path=("metric",),
        expected_attribute_value=7,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output",
        model_name="ConditionalPayload",
        valid_json='{"kind":"note","note":"ok"}',
        invalid_json='{"kind":"note"}',
        expected_error_type="value_error",
        expected_attribute_path=("note",),
        expected_attribute_value="ok",
    )


@pytest.mark.cli_doc(
    options=["--schema-validator-type"],
    option_description="""Select the schema-derived runtime validator backend.

The `--schema-validator-type pydantic-v2` option enables the current generated
Pydantic v2 model-validator backend for JSON Schema rules that cannot be
represented as type hints alone. The explicit type keeps the CLI ready for
additional validator backends without adding them in this release.""",
    input_schema="jsonschema/schema_validators.json",
    cli_args=[
        "--schema-validator-type",
        "pydantic-v2",
        "--output-model-type",
        "pydantic_v2.BaseModel",
        "--disable-timestamp",
    ],
    golden_output="jsonschema/schema_validators.py",
    related_options=["--generate-schema-validators"],
)
def test_main_jsonschema_schema_validator_type_pydantic_v2(output_file: Path) -> None:
    """Generate schema validators through the explicit backend selector."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="schema_validators.py",
        extra_args=[
            "--schema-validator-type",
            "pydantic-v2",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-annotated",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output_schema_validator_type_pydantic_v2",
        model_name="DirectPatternBag",
        valid_json='{"item_1":7}',
        invalid_json='{"bad":7}',
        expected_error_type="value_error",
        expected_attribute_path=("item_1",),
        expected_attribute_value=7,
    )


def test_main_jsonschema_generate_schema_validators_extra_template_collision(
    output_file: Path,
    tmp_path: Path,
) -> None:
    """Generate schema validators when user extra template data uses the reserved runtime key."""
    extra_template_data = tmp_path / "extra-template-data.json"
    extra_template_data.write_text(
        json.dumps({"#/$defs/DirectPatternBag": {"schema_runtime_validation": {"ignored": True}}}),
        encoding="utf-8",
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="schema_validators.py",
        extra_args=[
            "--generate-schema-validators",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
            "--extra-template-data",
            str(extra_template_data),
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output_extra_template_collision",
        model_name="DirectPatternBag",
        valid_json='{"item_1":7}',
        invalid_json='{"bad":7}',
        expected_error_type="value_error",
        expected_attribute_path=("item_1",),
        expected_attribute_value=7,
    )


@pytest.mark.cli_doc(
    options=["--schema-validator-base-class-name"],
    option_description="""Set the generated shared Pydantic v2 schema runtime validator base class name.

The `--schema-validator-base-class-name` option changes the name of the generated
shared base class that owns schema-derived runtime validators. It is only used when
`--generate-schema-validators` emits shared validator code.""",
    input_schema="jsonschema/schema_validators_custom_base_class_name.json",
    cli_args=[
        "--generate-schema-validators",
        "--schema-validator-base-class-name",
        "SharedSchemaValidatorBase",
        "--output-model-type",
        "pydantic_v2.BaseModel",
        "--disable-timestamp",
    ],
    golden_output="jsonschema/schema_validators_custom_base_class_name.py",
    related_options=["--generate-schema-validators"],
)
def test_main_jsonschema_generate_schema_validators_custom_base_class_name(output_file: Path) -> None:
    """Generate schema validators with a custom shared base class name."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators_custom_base_class_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="schema_validators_custom_base_class_name.py",
        extra_args=[
            "--generate-schema-validators",
            "--schema-validator-base-class-name",
            "SharedSchemaValidatorBase",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output_custom_base_class_name",
        model_name="CustomBaseClassName",
        valid_json='{"item_1":7}',
        invalid_json='{"bad":7}',
        expected_error_type="value_error",
        expected_attribute_path=("item_1",),
        expected_attribute_value=7,
    )


def test_main_jsonschema_generate_schema_validators_parser_branch_runtime(
    output_file: Path,
) -> None:
    """Generate and execute a model that covers runtime-validator parser integration branches."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators_parser_branch_runtime.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=[
            "--generate-schema-validators",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--read-only-write-only-model-type",
            "all",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output_parser_branch_runtime",
        model_name="RuntimeBranchCoverage",
        valid_json='{"child":{"value":"x"},"listOverride":{"items":[1]}}',
        invalid_json='{"child":{"value":1}}',
        expected_error_type="string_type",
        expected_attribute_path=("child", "value"),
        expected_attribute_value="x",
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="output_parser_branch_runtime_inline_pattern",
        model_name="RuntimeBranchCoverage",
        valid_json='{"inlinePattern":{"item_1":7}}',
        invalid_json='{"inlinePattern":{"bad":7}}',
        expected_error_type="value_error",
        expected_attribute_path=("inlinePattern", "item_1"),
        expected_attribute_value=7,
    )

    required_ref_output_file = output_file.with_name("required_ref_coverage.py")
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators_required_ref_branch_runtime.json",
        output_path=required_ref_output_file,
        input_file_type="jsonschema",
        extra_args=[
            "--generate-schema-validators",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )
    assert_generated_model_json_validation(
        required_ref_output_file,
        module_name="output_required_ref_branch_runtime",
        model_name="RequiredRefCoverage",
        valid_json='{"first":"x"}',
        invalid_json="[]",
        expected_error_type="model_type",
        expected_attribute_path=("root", "first"),
        expected_attribute_value="x",
    )


def test_main_jsonschema_generate_schema_validators_required_branch_constraints(
    output_file: Path,
) -> None:
    """Generate from a file and avoid collapsing constrained oneOf branches to presence checks."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators_required_branch_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_schema_required_group_validator_not_generated,
        extra_args=[
            "--generate-schema-validators",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )


@pytest.mark.parametrize(
    "schema_validator_args",
    [
        pytest.param(["--generate-schema-validators"]),
        pytest.param(["--schema-validator-type", "pydantic-v2"]),
    ],
)
def test_main_jsonschema_generate_schema_validators_requires_pydantic_v2(
    output_file: Path,
    capsys: pytest.CaptureFixture[str],
    schema_validator_args: list[str],
) -> None:
    """Reject schema-derived validators for output models without Pydantic v2 runtime hooks."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="schema_validator_type='pydantic-v2' is only supported for pydantic_v2.BaseModel",
        extra_args=[
            *schema_validator_args,
            "--output-model-type",
            "dataclasses.dataclass",
        ],
    )


def test_main_jsonschema_generate_schema_validators_invalid_base_class_name(
    output_file: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Reject invalid custom schema validator base class names."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        capsys=capsys,
        expected_stderr_contains="--schema-validator-base-class-name '123Invalid' is not a valid Python identifier",
        extra_args=[
            "--generate-schema-validators",
            "--schema-validator-base-class-name",
            "123Invalid",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_generate_schema_validators_invalid_base_class_name_public_api(output_file: Path) -> None:
    """Reject invalid schema validator base class names through generate()."""
    from datamodel_code_generator.config import GenerateConfig

    for value in (None, "SharedSchemaValidatorBase"):
        config = GenerateConfig.model_validate({"schema_validator_base_class_name": value})
        if config.schema_validator_base_class_name != value:  # pragma: no cover
            pytest.fail(
                "Expected schema_validator_base_class_name to be "
                f"{value!r}, got {config.schema_validator_base_class_name!r}",
                pytrace=False,
            )
    with pytest.raises(
        ValidationError,
        match="--schema-validator-base-class-name '123Invalid' is not a valid Python identifier",
    ):
        generate(
            input_={"type": "object"},
            input_file_type=InputFileType.JsonSchema,
            output=output_file,
            output_model_type=DataModelType.PydanticV2BaseModel,
            generate_schema_validators=True,
            schema_validator_base_class_name="123Invalid",
        )


def test_generate_schema_validator_type_public_api(output_file: Path) -> None:
    """Generate schema validators through the public API backend selector."""
    run_generate_file_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "schema_validators.json",
        output_path=output_file,
        input_file_type=InputFileType.JsonSchema,
        assert_func=assert_file_content,
        expected_file="schema_validators.py",
        output_model_type=DataModelType.PydanticV2BaseModel,
        schema_validator_type=SchemaValidatorType.PydanticV2,
        disable_timestamp=True,
    )


def test_field_validators_multi_fields(output_file: Path) -> None:
    """Test validators with multiple fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_validators_multi_fields.py",
        extra_args=[
            "--validators",
            str(JSON_SCHEMA_DATA_PATH / "field_validators_multi_fields_config.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
        skip_code_validation=True,
    )


def test_extra_template_data_field_validators(output_file: Path, tmp_path: Path) -> None:
    """Test validators supplied through extra template data."""
    extra_template_data = tmp_path / "extra_template_data_validators.json"
    extra_template_data.write_text(
        json.dumps({
            "User": {
                "validators": [
                    {
                        "field": "name",
                        "function": "myapp.validators.validate_name",
                        "mode": "before",
                    },
                    {
                        "field": "email",
                        "function": "myapp.validators.validate_email",
                        "mode": "after",
                    },
                ]
            }
        })
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_validators.py",
        extra_args=[
            "--extra-template-data",
            str(extra_template_data),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-annotated",
            "--disable-timestamp",
        ],
        skip_code_validation=True,
    )


@pytest.mark.parametrize("extra_template_data_key", ["User", "#all#"])
def test_extra_template_data_prepared_validators_ignored(
    output_file: Path,
    tmp_path: Path,
    extra_template_data_key: str,
) -> None:
    """Ignore prepared validator data supplied through extra template data."""
    extra_template_data = tmp_path / "extra_template_data_prepared_validators.json"
    extra_template_data.write_text(
        json.dumps({
            extra_template_data_key: {
                "prepared_validators": [
                    {
                        "fields_str": "'name'); __import__('os').system('touch pwned') #",
                        "mode_str": "mode='after'",
                        "method_name": "validate_name_validator",
                        "function_name": "validate_name",
                        "mode": "after",
                    }
                ]
            }
        })
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_validators_all_skipped.py",
        extra_args=[
            "--extra-template-data",
            str(extra_template_data),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
        skip_code_validation=True,
    )


@pytest.mark.parametrize(
    ("validator", "expected_message"),
    [
        (
            {
                "field": "name', __import__('os').system('touch pwned'), 'email",
                "function": "myapp.validators.validate_name",
            },
            (
                "validators.0.field: must be a valid Python identifier: "
                "\"name', __import__('os').system('touch pwned'), 'email\""
            ),
        ),
        (
            {
                "fields": ["name", "email', __import__('os').system('touch pwned'), 'age"],
                "function": "myapp.validators.validate_name",
            },
            (
                "validators.0.fields: must be a valid Python identifier: "
                "\"email', __import__('os').system('touch pwned'), 'age\""
            ),
        ),
        (
            {
                "field": "name",
                "function": "myapp.validators.validate_name;__import__('os').system('touch pwned')",
            },
            (
                "validators.0.function: must be a dotted Python identifier path: "
                "\"myapp.validators.validate_name;__import__('os').system('touch pwned')\""
            ),
        ),
        (
            {
                "field": "name",
                "function": "myapp.validators.validate_name",
                "mode": "after', __import__('os').system('touch pwned'), mode='after",
            },
            "validators.0.mode: must be one of: 'before', 'after', 'wrap', 'plain'",
        ),
    ],
)
def test_extra_template_data_field_validators_reject_unsafe_values(
    output_file: Path,
    tmp_path: Path,
    capsys: pytest.CaptureFixture[str],
    validator: dict[str, str | list[str]],
    expected_message: str,
) -> None:
    """Test unsafe extra template data validators fail before code generation."""
    extra_template_data = tmp_path / "extra_template_data_invalid_validators.json"
    extra_template_data.write_text(json.dumps({"User": {"validators": [validator]}}))

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        extra_args=[
            "--extra-template-data",
            str(extra_template_data),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        capsys=capsys,
        expected_stderr=f"Invalid validators configuration: {expected_message}\n",
        output_should_not_exist=True,
    )


def test_validators_option_rejects_unsafe_values(
    output_file: Path,
    tmp_path: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Test unsafe validators file values fail before code generation."""
    validators_config = tmp_path / "invalid_validators_config.json"
    validators_config.write_text(
        json.dumps({
            "User": {
                "validators": [
                    {
                        "field": "name', __import__('os').system('touch pwned'), 'email",
                        "function": "myapp.validators.validate_name",
                    }
                ]
            }
        })
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        extra_args=[
            "--validators",
            str(validators_config),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        capsys=capsys,
        expected_stderr_contains="must be a valid Python identifier",
        output_should_not_exist=True,
    )


def test_field_validators_wrap_mode(output_file: Path, tmp_path: Path) -> None:
    """Test validators with wrap mode."""
    config_file = tmp_path / "wrap_mode_config.json"
    config_file.write_text(
        """{
        "User": {
            "validators": [
                {"field": "name", "function": "myapp.validators.validate_name_wrap", "mode": "wrap"}
            ]
        }
    }"""
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_validators_wrap_mode.py",
        extra_args=[
            "--validators",
            str(config_file),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
        skip_code_validation=True,
    )


def test_field_validators_with_no_field_skipped(output_file: Path, tmp_path: Path) -> None:
    """Test that validators without fields are skipped gracefully."""
    config_file = tmp_path / "no_field_validators_config.json"
    config_file.write_text(
        """{
        "User": {
            "validators": [
                {"function": "myapp.validators.validate_something"},
                {"field": "name", "function": "myapp.validators.validate_name"}
            ]
        }
    }"""
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_validators_with_no_field_skipped.py",
        extra_args=[
            "--validators",
            str(config_file),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
        skip_code_validation=True,
    )


def test_field_validators_plain_mode(output_file: Path, tmp_path: Path) -> None:
    """Test validators with plain mode (no ValidationInfo import)."""
    config_file = tmp_path / "plain_mode_config.json"
    config_file.write_text(
        """{
        "User": {
            "validators": [
                {"field": "name", "function": "myapp.validators.validate_name_plain", "mode": "plain"}
            ]
        }
    }"""
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_validators_plain_mode.py",
        extra_args=[
            "--validators",
            str(config_file),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
        skip_code_validation=True,
    )


def test_field_validators_all_skipped(output_file: Path, tmp_path: Path) -> None:
    """Test that when all validators have no fields, output has no validators."""
    config_file = tmp_path / "all_skipped_config.json"
    config_file.write_text(
        """{
        "User": {
            "validators": [
                {"function": "myapp.validators.validate_something"}
            ]
        }
    }"""
    )

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="field_validators_all_skipped.py",
        extra_args=[
            "--validators",
            str(config_file),
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
        skip_code_validation=True,
    )


def test_validators_invalid_json(output_file: Path, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test error handling for invalid validators JSON file."""
    invalid_json = tmp_path / "invalid.json"
    invalid_json.write_text("not valid json{")

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        extra_args=[
            "--validators",
            str(invalid_json),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        capsys=capsys,
        expected_stderr_contains="Unable to load validators configuration",
    )


def test_validators_invalid_structure(output_file: Path, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test error handling for validators JSON with invalid structure (not an object)."""
    invalid_structure = tmp_path / "invalid_structure.json"
    invalid_structure.write_text('["not", "an", "object"]')

    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "field_validators.json",
        output_path=output_file,
        input_file_type="jsonschema",
        expected_exit=Exit.ERROR,
        extra_args=[
            "--validators",
            str(invalid_structure),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
        capsys=capsys,
        expected_stderr_contains="Invalid validators configuration",
    )


def test_jsonschema_classvar_extra_pydantic_v2(output_file: Path) -> None:
    """Test default value handling."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "has_classvar_extra.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="has_classvar_extra.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--field-include-all-keys"],
    )


@pytest.mark.isolate_builtin_formatter_config
def test_jsonschema_classvar_field_str_custom_template(
    capsys: pytest.CaptureFixture, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test that custom templates can render a ClassVar field via str(field)."""
    model_base._get_environment.cache_clear()
    model_base._get_template_with_custom_dir.cache_clear()
    monkeypatch.chdir(tmp_path)
    with freeze_time(TIMESTAMP):
        run_main_and_assert(
            input_path=JSON_SCHEMA_DATA_PATH / "has_classvar_extra.json",
            output_path=None,
            expected_stdout_path=EXPECTED_JSON_SCHEMA_PATH / "has_classvar_extra_custom_template_field.py",
            capsys=capsys,
            input_file_type=None,
            extra_args=[
                "--custom-template-dir",
                str(DATA_PATH / "templates_class_var_field"),
                "--output-model-type",
                "pydantic_v2.BaseModel",
                "--field-include-all-keys",
            ],
        )


def test_jsonschema_classvar_extra_set_pydantic_v2(output_file: Path) -> None:
    """Test ClassVar with set default value."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "has_classvar_extra_set.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="has_classvar_extra_set.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-include-all-keys",
            "--use-unique-items-as-set",
        ],
    )


def test_jsonschema_classvar_extra_annotated_pydantic_v2(output_file: Path) -> None:
    """Test ClassVar with use_annotated option."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "has_classvar_extra_annotated.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="has_classvar_extra_annotated.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-include-all-keys",
            "--use-annotated",
        ],
    )


def test_unique_items_enum_set(output_file: Path) -> None:
    """Test set with enum items does not add __hash__ to enum (already hashable)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "unique_items_enum_set.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="unique_items_enum_set.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-unique-items-as-set",
            "--use-standard-collections",
        ],
    )


@pytest.mark.benchmark
def test_main_allof_mro(output_file: Path) -> None:
    """Test allOf with diamond inheritance pattern produces valid Python MRO."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "allof_mro.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allof_mro.py",
        extra_args=[
            "--use-schema-description",
        ],
    )


@pytest.mark.benchmark
def test_main_circular_ref_with_schema_keywords(output_file: Path) -> None:
    """Test circular $ref with additional schema keywords does not cause RecursionError."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "circular_ref_with_schema_keywords.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.benchmark
def test_main_circular_ref_indirect(output_file: Path) -> None:
    """Test indirect circular $ref (A->B->A) does not cause RecursionError."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "circular_ref_indirect.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.benchmark
def test_main_circular_ref_root_with_type(output_file: Path) -> None:
    """Test circular $ref at root level with type does not cause RecursionError."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "circular_ref_root_with_type.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.benchmark
def test_main_circular_ref_external_relative_keywords(output_file: Path) -> None:
    """Test circular external refs with relative paths and schema keywords."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "circular_ref_external_relative_keywords" / "root.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


@pytest.mark.benchmark
def test_main_circular_ref_external_url_keywords(mock_httpx_get: HttpxGetMockFactory, output_file: Path) -> None:
    """Test circular external refs with relative paths and schema keywords via URL input."""
    external_directory = JSON_SCHEMA_DATA_PATH / "circular_ref_external_relative_keywords"
    base_url = "https://example.com/circular_ref_external_relative_keywords/"

    url_to_path = {
        f"{base_url}root.json": "root.json",
        f"{base_url}defs/context.json": "defs/context.json",
        f"{base_url}defs/nested/child.json": "defs/nested/child.json",
    }

    httpx_get_mock = mock_httpx_get(
        *(MockHttpxResponse(url, external_directory / path) for url, path in url_to_path.items())
    )

    run_main_url_and_assert(
        url=f"{base_url}root.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="circular_ref_external_relative_keywords.py",
        transform=lambda s: s.replace(
            f"#   filename:  {base_url}root.json",
            "#   filename:  root.json",
        ),
    )

    assert_httpx_get_kwargs(httpx_get_mock, expected_urls=list(url_to_path), any_order=True, call_count=3)


@pytest.mark.benchmark
def test_main_circular_ref_ref_with_schema_keywords(output_file: Path) -> None:
    """Test named schema with circular $ref and schema keywords still generates alias model."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "circular_ref_ref_with_schema_keywords.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_jsonschema_recursive_ref(output_file: Path) -> None:
    """Test JSON Schema 2019-09 $recursiveRef with $recursiveAnchor."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "recursive_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="recursive_ref.py",
    )


def test_main_jsonschema_recursive_ref_pydantic_v2(output_file: Path) -> None:
    """Test JSON Schema 2019-09 $recursiveRef with Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "recursive_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="recursive_ref_pydantic_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_dynamic_ref(output_file: Path) -> None:
    """Test JSON Schema 2020-12 $dynamicRef with $dynamicAnchor."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "dynamic_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="dynamic_ref.py",
    )


def test_main_jsonschema_dynamic_ref_pydantic_v2(output_file: Path) -> None:
    """Test JSON Schema 2020-12 $dynamicRef with Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "dynamic_ref.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="dynamic_ref_pydantic_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_recursive_ref_no_anchor(output_file: Path) -> None:
    """Test JSON Schema 2019-09 $recursiveRef without $recursiveAnchor."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "recursive_ref_no_anchor.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="recursive_ref_no_anchor.py",
    )


def test_main_jsonschema_recursive_ref_no_anchor_pydantic_v2(output_file: Path) -> None:
    """Test JSON Schema 2019-09 $recursiveRef without $recursiveAnchor for Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "recursive_ref_no_anchor.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="recursive_ref_no_anchor_pydantic_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_recursive_ref_in_defs(output_file: Path) -> None:
    """Test JSON Schema 2019-09 $recursiveRef with anchor in $defs."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "recursive_ref_in_defs.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="recursive_ref_in_defs.py",
    )


def test_main_jsonschema_recursive_ref_in_defs_pydantic_v2(output_file: Path) -> None:
    """Test JSON Schema 2019-09 $recursiveRef with anchor in $defs for Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "recursive_ref_in_defs.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="recursive_ref_in_defs_pydantic_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_recursive_ref_in_defs_pydantic_v2_no_future_imports(
    output_file: Path,
) -> None:
    """Self-referencing fields must be quoted when --disable-future-imports is used.

    Without ``from __future__ import annotations``, a self-reference such as
    ``children: list[TreeNode]`` inside ``class TreeNode`` is an undefined name
    at class-body evaluation time (Python < 3.14).  The generator must emit a
    quoted forward reference (``list["TreeNode"]``) in that case so that both
    static analysers (Ruff F821) and runtimes without PEP 649 are satisfied.

    Regression test for https://github.com/koxudaxi/datamodel-code-generator/issues/2973
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "recursive_ref_in_defs.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="recursive_ref_in_defs_pydantic_v2_no_future_imports.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--disable-future-imports"],
    )


def test_main_jsonschema_dynamic_ref_in_defs(output_file: Path) -> None:
    """Test JSON Schema 2020-12 $dynamicRef with anchor in $defs."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "dynamic_ref_in_defs.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="dynamic_ref_in_defs.py",
    )


def test_main_jsonschema_dynamic_ref_in_defs_pydantic_v2(output_file: Path) -> None:
    """Test JSON Schema 2020-12 $dynamicRef with anchor in $defs for Pydantic v2."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "dynamic_ref_in_defs.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="dynamic_ref_in_defs_pydantic_v2.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )


def test_main_jsonschema_multiple_aliases_required_pydantic_v2(output_file: Path) -> None:
    """Test multiple aliases with AliasChoices on required fields for Pydantic v2. (#2989)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "multiple_aliases_required.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--aliases",
            str(ALIASES_DATA_PATH / "multiple_aliases_required.json"),
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_ref_with_nonstandard_metadata(output_file: Path) -> None:
    """Test $ref with non-standard metadata fields preserves type information.

    When $ref is combined with non-standard metadata like 'markdownDescription',
    the reference type should be preserved instead of being replaced by the
    underlying type. Non-standard fields are annotation-only and should not
    trigger schema merging.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_with_nonstandard_metadata.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_with_nonstandard_metadata.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--use-annotated"],
    )


def test_ref_nullable_with_nonstandard_metadata(output_file: Path) -> None:
    """Test $ref + nullable: true with non-standard metadata uses reference directly.

    When $ref is combined with nullable: true and non-standard metadata like
    'markdownDescription', the reference should be used directly with Optional
    type annotation instead of creating a merged model.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_nullable_with_nonstandard_metadata.yaml",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_nullable_with_nonstandard_metadata.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--strict-nullable"],
    )


def test_ref_with_const(output_file: Path) -> None:
    """Test $ref + const triggers schema merging as const is schema-affecting.

    When $ref is combined with 'const', the const keyword structurally affects
    the generated type (producing Literal), so schema merging should occur.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_with_const.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_with_const.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--strict-nullable"],
    )


def test_ref_merge_field_metadata(output_file: Path) -> None:
    """Test $ref + const merges ref metadata."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_merge_field_metadata.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="ref_merge_field_metadata.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-frozen-field",
            "--field-constraints",
            "--use-annotated",
            "--strict-nullable",
        ],
    )


def test_ref_merge_additional_properties(output_file: Path) -> None:
    """Test $ref + const merge in additionalProperties root model path."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "ref_merge_additional_properties.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--field-constraints",
            "--use-annotated",
        ],
    )


def test_main_exact_imports_collapse_root_models_module_class_collision(output_dir: Path) -> None:
    """Test --use-exact-imports with --collapse-root-models when module and class names collide."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "exact_imports_collapse_root_models",
        output_path=output_dir,
        input_file_type="jsonschema",
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "exact_imports_collapse_root_models",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.10",
            "--use-exact-imports",
            "--collapse-root-models",
            "--use-title-as-name",
            "--snake-case-field",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )


def test_main_exact_imports_collapse_root_models_title_array(output_dir: Path) -> None:
    """Test --use-exact-imports with --collapse-root-models when array field has title.

    Regression test for https://github.com/koxudaxi/datamodel-code-generator/issues/3001
    When an allOf $ref and an array items $ref point to the same type, and the array
    field has a title (causing a root model to be created and collapsed), both fields
    should use the same import alias.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "exact_imports_collapse_root_models_title",
        output_path=output_dir,
        input_file_type="jsonschema",
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "exact_imports_collapse_root_models_title",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.10",
            "--use-exact-imports",
            "--collapse-root-models",
            "--use-title-as-name",
            "--snake-case-field",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
    )


def test_main_exact_imports_collapse_root_models_module_split_oneof_imports(output_dir: Path) -> None:
    """Regression test for https://github.com/koxudaxi/datamodel-code-generator/issues/3487."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "exact_imports_collapse_root_models_module_split_oneof.json",
        output_path=output_dir,
        input_file_type="jsonschema",
        expected_directory=EXPECTED_JSON_SCHEMA_PATH / "exact_imports_collapse_root_models_module_split_oneof",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.10",
            "--use-exact-imports",
            "--collapse-root-models",
            "--module-split-mode",
            "single",
            "--disable-timestamp",
        ],
        force_exec_validation=True,
        runtime_validation_module="animal_home",
        runtime_validation_model_name="AnimalHome",
        runtime_validation_data={"pets": [{"bark": True}]},
    )


@pytest.mark.timeout(30)
def test_main_jsonschema_discriminated_oneof_allof_cycle(output_file: Path) -> None:
    """Discriminated oneOf with variants that allOf the parent (circular graph).

    Covers `sort_data_models` ordering for cyclic base dependencies and discriminator
    handling (mapping + RootModel) on a minimal JSON Schema spec. See the
    [Pull Request](https://github.com/koxudaxi/datamodel-code-generator/pull/3078) for
    more details.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "jsonschema_discriminated_oneof_allof_cycle.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
    )


def test_main_jsonschema_pattern_escaped_quote(output_file: Path) -> None:
    """Test pattern with an escaped quote renders importable code that still matches."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pattern_escaped_quote.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="pattern_escaped_quote.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="generated_pattern_escaped_quote",
        model_name="Model",
        valid_json='{"x": "don\'t"}',
        invalid_json='{"x": "dont"}',
        expected_error_type="string_pattern_mismatch",
        expected_attribute_path=("x",),
        expected_attribute_value="don't",
    )


@pytest.mark.parametrize(
    ("constraint_args", "module_name", "expected_file"),
    [
        ([], "generated_integer_fractional_constraints", "integer_fractional_constraints.py"),
        (
            ["--field-constraints"],
            "generated_integer_fractional_field_constraints",
            "integer_fractional_constraints_field_constraints.py",
        ),
    ],
)
def test_main_jsonschema_integer_fractional_constraints(
    constraint_args: list[str], module_name: str, expected_file: str, output_file: Path
) -> None:
    """Test fractional integer constraints normalize to integer-safe bounds."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "integer_fractional_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=expected_file,
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", *constraint_args],
    )
    assert_generated_model_json_validation(
        output_file,
        module_name=module_name,
        model_name="Model",
        valid_json=(
            '{"ge_field": 1, "any_multiple": 7, "gt_field": -1, "fraction_multiple": 3,'
            ' "le_field": 2, "lt_field": 2, "combined_min": 3, "combined_max": 2}'
        ),
        invalid_json='{"ge_field": 0}',
        expected_error_type="greater_than_equal",
        expected_attribute_path=("fraction_multiple",),
        expected_attribute_value=3,
    )
    assert_generated_model_json_invalid(
        output_file,
        module_name=module_name,
        model_name="Model",
        invalid_json='{"combined_min": 2}',
        expected_error_type="greater_than_equal",
    )
    assert_generated_model_json_invalid(
        output_file,
        module_name=module_name,
        model_name="Model",
        invalid_json='{"combined_max": 3}',
        expected_error_type="less_than_equal",
    )


def test_main_jsonschema_non_finite_number_values(output_file: Path) -> None:
    """Test non-finite numeric defaults and bounds render as valid Python expressions."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "non_finite_number_values.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="non_finite_number_values.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="generated_non_finite_number_values",
        model_name="Model",
        valid_json="{}",
        invalid_json='{"big_min": 1.0}',
        expected_error_type="greater_than_equal",
        expected_attribute_path=("big_default",),
        expected_attribute_value=float("inf"),
    )


def test_main_jsonschema_decimal_fractional_constraints(output_file: Path) -> None:
    """Test decimal fields keep fractional bounds and multipleOf with field constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "decimal_fractional_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="decimal_fractional_constraints.py",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--field-constraints"],
    )
    assert_generated_model_json_validation(
        output_file,
        module_name="generated_decimal_fractional_constraints",
        model_name="Model",
        valid_json='{"price": "0.51"}',
        invalid_json='{"price": "0.4"}',
        expected_error_type="greater_than_equal",
    )


def test_main_msgspec_inherited_optional_default_uses_kw_only(output_file: Path) -> None:
    """Test msgspec allOf child with required fields stays importable via kw_only."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_inherited_optional_default.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "msgspec.Struct"],
        assert_func=assert_file_content,
        expected_file="msgspec_inherited_optional_default.py",
        importable_module_name="generated_msgspec_inherited_optional_default",
        importable_module_attribute="Child",
    )


def test_main_msgspec_required_alias_field_sorts_first(output_file: Path) -> None:
    """Test msgspec required field with alias sorts before optional fields."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_required_alias_field.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "msgspec.Struct"],
        assert_func=assert_file_content,
        expected_file="msgspec_required_alias_field.py",
        importable_module_name="generated_msgspec_required_alias_field",
        importable_module_attribute="Record",
    )


def test_main_msgspec_required_nullable_field_stays_required(output_file: Path) -> None:
    """Test msgspec required nullable field renders without a default."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_required_nullable_field.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "msgspec.Struct"],
        assert_func=assert_file_content,
        expected_file="msgspec_required_nullable_field.py",
        importable_module_name="generated_msgspec_required_nullable_field",
        importable_module_attribute="Record",
    )


def test_main_payload_runtime_compatibility(output_file: Path) -> None:
    """Test JSON Schema payload runtime compatibility fixture with default output."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_payload_runtime_compatibility.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=[
            "--target-python-version",
            "3.10",
            "--class-name",
            "Payload",
        ],
        assert_func=assert_file_content,
        expected_file="payload_runtime_compatibility.py",
        force_exec_validation=True,
        importable_module_name="generated_payload_runtime_compatibility",
        importable_module_attribute="Payload",
    )


def test_main_msgspec_payload_runtime_compatibility(output_file: Path) -> None:
    """Test msgspec avoids unsupported bool Literal and tags supported Struct unions."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_payload_runtime_compatibility.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--target-python-version",
            "3.10",
            "--class-name",
            "Payload",
        ],
        assert_func=assert_file_content,
        expected_file="msgspec_payload_runtime_compatibility.py",
        force_exec_validation=True,
        importable_module_name="generated_msgspec_payload_runtime_compatibility",
        importable_module_attribute="Payload",
    )


def test_main_msgspec_array_length_constraints_use_annotated(output_file: Path) -> None:
    """Test msgspec renders array length constraints as Meta annotations."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_array_length_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "msgspec.Struct", "--use-annotated"],
        assert_func=assert_file_content,
        expected_file="msgspec_array_length_constraints_use_annotated.py",
        importable_module_name="generated_msgspec_array_length_constraints",
    )


def test_main_msgspec_annotated_optional_imports(output_file: Path) -> None:
    """Test msgspec keeps Optional imports when Annotated wraps optional types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_annotated_optional_imports.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "msgspec.Struct",
            "--use-annotated",
            "--no-use-union-operator",
        ],
        assert_func=assert_file_content,
        expected_file="msgspec_annotated_optional_imports.py",
        force_exec_validation=True,
        importable_module_name="generated_msgspec_annotated_optional_imports",
    )


def test_main_msgspec_array_length_constraints_without_annotated() -> None:
    """Test msgspec generate API without use_annotated keeps imports minimal."""
    run_generate_and_assert(
        input_=JSON_SCHEMA_DATA_PATH / "msgspec_array_length_constraints.json",
        expected_file=EXPECTED_JSON_SCHEMA_PATH / "msgspec_array_length_constraints_without_annotated.py",
        input_file_type=InputFileType.JsonSchema,
        output_model_type=DataModelType.MsgspecStruct,
    )


def test_main_msgspec_integer_fractional_constraints(output_file: Path) -> None:
    """Test msgspec normalizes fractional integer constraints to integer-safe bounds."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "integer_fractional_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "msgspec.Struct", "--use-annotated"],
        assert_func=assert_file_content,
        expected_file="msgspec_integer_fractional_constraints.py",
        importable_module_name="generated_msgspec_integer_fractional_constraints",
    )


def test_main_msgspec_non_finite_number_values(output_file: Path) -> None:
    """Test msgspec renders non-finite defaults as expressions and drops non-finite bounds."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "non_finite_number_values.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "msgspec.Struct", "--use-annotated"],
        assert_func=assert_file_content,
        expected_file="msgspec_non_finite_number_values.py",
        importable_module_name="generated_msgspec_non_finite_number_values",
    )


def test_main_dataclass_non_finite_number_values(output_file: Path) -> None:
    """Test dataclass renders non-finite defaults as valid Python expressions."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "non_finite_number_values.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "dataclasses.dataclass"],
        assert_func=assert_file_content,
        expected_file="dataclass_non_finite_number_values.py",
        importable_module_name="generated_dataclass_non_finite_number_values",
    )


@pytest.mark.parametrize(
    ("output_model", "module_name", "expected_file"),
    [
        (
            "dataclasses.dataclass",
            "generated_dataclass_non_finite_container_defaults",
            "dataclass_non_finite_container_defaults.py",
        ),
        (
            "msgspec.Struct",
            "generated_msgspec_non_finite_container_defaults",
            "msgspec_non_finite_container_defaults.py",
        ),
    ],
)
def test_main_non_finite_container_defaults(
    output_model: str, module_name: str, expected_file: str, output_file: Path
) -> None:
    """Test container defaults containing non-finite numbers render as expressions."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "non_finite_container_defaults.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", output_model],
        assert_func=assert_file_content,
        expected_file=expected_file,
        importable_module_name=module_name,
    )


def test_main_msgspec_decimal_constraints(output_file: Path) -> None:
    """Test msgspec keeps fractional decimal constraints and integer-valued bounds."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "msgspec_decimal_constraints.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "msgspec.Struct", "--use-annotated"],
        assert_func=assert_file_content,
        expected_file="msgspec_decimal_constraints.py",
        importable_module_name="generated_msgspec_decimal_constraints",
    )


def test_main_jsonschema_uuid_format_versions(output_file: Path) -> None:
    """Test uuid format versions map to importable pydantic types."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "uuid_format_versions.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel"],
        assert_func=assert_file_content,
        expected_file="uuid_format_versions.py",
        importable_module_name="generated_uuid_format_versions",
    )


def test_main_dataclass_enum_member_special_defaults(output_file: Path) -> None:
    """Test enum defaults containing quotes resolve to members for scalars and lists."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_member_special_defaults.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "dataclasses.dataclass"],
        assert_func=assert_file_content,
        expected_file="dataclass_enum_member_special_defaults.py",
        importable_module_name="generated_dataclass_enum_member_special_defaults",
    )


def test_main_jsonschema_enum_member_typed_defaults(output_file: Path) -> None:
    """Test enum defaults resolve to the member with a matching JSON type."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_member_typed_defaults.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--set-default-enum-member"],
        assert_func=assert_file_content,
        expected_file="enum_member_typed_defaults.py",
        importable_module_name="generated_enum_member_typed_defaults",
    )


def test_main_typed_dict_functional_descriptions(output_file: Path) -> None:
    """Test functional TypedDict renders descriptions outside the fields dict."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_functional_descriptions.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "typing.TypedDict",
            "--use-schema-description",
            "--use-field-description",
        ],
        assert_func=assert_file_content,
        expected_file="typed_dict_functional_descriptions.py",
        importable_module_name="generated_typed_dict_functional_descriptions",
        importable_module_attribute="FuncDesc",
    )


def test_main_typed_dict_self_referencing_extra_items(output_file: Path) -> None:
    """Test self-referencing additionalProperties renders extra_items as a forward reference."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_self_referencing_extra_items.json",
        output_path=output_file,
        input_file_type="jsonschema",
        extra_args=["--output-model-type", "typing.TypedDict"],
        assert_func=assert_file_content,
        expected_file="typed_dict_self_referencing_extra_items.py",
        importable_module_name="generated_typed_dict_self_referencing_extra_items",
        importable_module_attribute="SelfMap",
    )
