"""Main module for datamodel-code-generator CLI."""

from __future__ import annotations

import sys

# Fast path for --version (avoid importing heavy modules)
if len(sys.argv) == 2 and sys.argv[1] in {"--version", "-V"}:  # pragma: no cover  # noqa: PLR2004
    from importlib.metadata import version

    sys.stdout.write(f"datamodel-codegen {version('datamodel-code-generator')}\n")
    sys.exit(0)

# Fast path for --help (avoid importing heavy modules)
if len(sys.argv) == 2 and sys.argv[1] in {"--help", "-h"}:  # pragma: no cover  # noqa: PLR2004
    from datamodel_code_generator.arguments import arg_parser

    arg_parser.print_help()
    sys.exit(0)

match sys.argv:
    case [_, "--output-format-json-schema", schema_output_name] if schema_output_name in {
        "config",
        "generation",
        "model-metadata",
        "structured-output",
    }:
        pass
    case [_, schema_output_option] if schema_output_option.startswith("--output-format-json-schema=") and (
        schema_output_name := schema_output_option.partition("=")[2]
    ) in {"config", "generation", "model-metadata", "structured-output"}:
        pass
    case _:
        schema_output_name = None

match schema_output_name:
    case "config":
        from datamodel_code_generator.json_config import json_config_json_schema

        sys.stdout.write(f"{json_config_json_schema()}\n")
        sys.exit(0)
    case "generation":  # pragma: no cover
        from datamodel_code_generator._structured_output import generation_output_json_schema

        sys.stdout.write(f"{generation_output_json_schema()}\n")
        sys.exit(0)
    case "model-metadata":
        from datamodel_code_generator.model_metadata import model_metadata_json_schema

        sys.stdout.write(f"{model_metadata_json_schema()}\n")
        sys.exit(0)
    case "structured-output":  # pragma: no cover
        from datamodel_code_generator._structured_output import structured_output_json_schema

        sys.stdout.write(f"{structured_output_json_schema()}\n")
        sys.exit(0)

# Fast path for prompt helper outputs
if any(
    arg.startswith(("--generate-prompt", "--output-format-json-schema=")) or arg == "--output-format-json-schema"
    for arg in sys.argv[1:]
):  # pragma: no cover
    from datamodel_code_generator.arguments import arg_parser, namespace

    vars(namespace).clear()
    namespace.no_color = False
    arg_parser.parse_args(namespace=namespace)
    if namespace.output_format_json_schema == "generate-prompt":
        from datamodel_code_generator.prompt import generate_prompt_json_schema

        sys.stdout.write(f"{generate_prompt_json_schema()}\n")
        sys.exit(0)
    if namespace.output_format_json_schema == "config":
        from datamodel_code_generator.json_config import json_config_json_schema

        sys.stdout.write(f"{json_config_json_schema()}\n")
        sys.exit(0)
    if namespace.output_format_json_schema == "generation":
        from datamodel_code_generator._structured_output import generation_output_json_schema

        sys.stdout.write(f"{generation_output_json_schema()}\n")
        sys.exit(0)
    if namespace.output_format_json_schema == "model-metadata":
        from datamodel_code_generator.model_metadata import model_metadata_json_schema

        sys.stdout.write(f"{model_metadata_json_schema()}\n")
        sys.exit(0)
    if namespace.output_format_json_schema == "structured-output":
        from datamodel_code_generator._structured_output import structured_output_json_schema

        sys.stdout.write(f"{structured_output_json_schema()}\n")
        sys.exit(0)
    if namespace.output_format_json_schema is None and namespace.generate_prompt is not None:
        from datamodel_code_generator.prompt import generate_prompt

        help_text = arg_parser.format_help()
        prompt_output = generate_prompt(namespace, help_text, arg_parser)
        sys.stdout.write(f"{prompt_output}\n")
        sys.exit(0)

import difflib
import json
import os
import shlex
import shutil
import signal
import tempfile
import warnings
from collections.abc import Mapping, Sequence
from enum import Enum, IntEnum
from functools import lru_cache
from keyword import iskeyword
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Literal, NamedTuple, Optional, TypeAlias, Union, cast
from urllib.parse import ParseResult, urlparse

from datamodel_code_generator import (
    AllExportsScope,
    ClassNameAffixScope,
    DataModelType,
    Error,
    InputFileType,
    InputModelRefStrategy,
    InvalidClassNameError,
    NamingStrategy,
    OpenAPIScope,
    ReuseScope,
    _validate_alias_generator,
    _validate_output_datetime_class,
    enable_debug_message,
    generate,
)
from datamodel_code_generator._format_types import Formatter, PythonVersion
from datamodel_code_generator.arguments import arg_parser, namespace
from datamodel_code_generator.deprecations import render_deprecations, warn_deprecated
from datamodel_code_generator.enums import StrictTypes
from datamodel_code_generator.util import load_toml

if TYPE_CHECKING:
    from argparse import Namespace
    from collections import defaultdict

    from datamodel_code_generator._structured_output import (
        CheckDifferencePayload,
        CommandOutputKind,
        GeneratedFilePayload,
    )
    from datamodel_code_generator.json_config import JsonConfigFieldName, JsonConfigSource
    from datamodel_code_generator.validators import ModelValidators

    Config = cast("Any", object)
    ValidatorsConfigValue: TypeAlias = Mapping[str, ModelValidators]
else:
    ValidatorsConfigValue: TypeAlias = Mapping[str, Any]

# Options that should be excluded from pyproject.toml config generation
EXCLUDED_CONFIG_OPTIONS: frozenset[str] = frozenset({
    "check",
    "generate_pyproject_config",
    "generate_cli_command",
    "generate_prompt",
    "ignore_pyproject",
    "profile",
    "version",
    "help",
    "debug",
    "no_color",
    "output_format",
    "output_format_json_schema",
    "disable_warnings",
    "list_deprecations",
    "list_experimental",
    "watch",
    "watch_delay",
})

BOOLEAN_OPTIONAL_OPTIONS: frozenset[str] = frozenset({
    "allow_population_by_field_name",
    "collapse_root_models",
    "snake_case_field",
    "use_frozen_field",
    "use_type_checking_imports",
    "use_specialized_enum",
    "use_standard_collections",
    "use_standard_primitive_types",
})

ORIGINAL_FIELD_NAME_DELIMITER_ERROR = "`--original-field-name-delimiter` can not be used without `--snake-case-field`."
SENSITIVE_COMMAND_OPTIONS: frozenset[str] = frozenset({"--http-headers", "--http-query-parameters"})
REDACTED_COMMAND_ARGUMENT = "<redacted>"


class Exit(IntEnum):
    """Exit reasons."""

    OK = 0
    DIFF = 1
    ERROR = 2
    KeyboardInterrupt = 3


def sig_int_handler(_: int, __: Any) -> None:  # pragma: no cover
    """Handle SIGINT signal gracefully."""
    sys.exit(Exit.OK)


def _redact_command_args(args: Sequence[str]) -> list[str]:
    redacted: list[str] = []
    redact_values = False
    for arg in args:
        match arg:
            case option if option in SENSITIVE_COMMAND_OPTIONS:
                redacted.append(option)
                redact_values = True
            case option_value if (option := option_value.partition("="))[1] and option[0] in SENSITIVE_COMMAND_OPTIONS:
                redacted.append(f"{option[0]}={REDACTED_COMMAND_ARGUMENT}")
                redact_values = False
            case option if redact_values and option.startswith("-"):
                redacted.append(option)
                redact_values = False
            case _ if redact_values:
                redacted.append(REDACTED_COMMAND_ARGUMENT)
            case _:
                redacted.append(arg)
    return redacted


def _command_header(args: Sequence[str]) -> str:
    return shlex.join(["datamodel-codegen", *_redact_command_args(args)])


signal.signal(signal.SIGINT, sig_int_handler)


def _get_black() -> Any:
    from datamodel_code_generator.format import _get_black as get_black  # noqa: PLC0415

    return get_black()


def is_supported_in_black(python_version: PythonVersion) -> bool:
    """Return whether the installed black supports the target Python version."""
    from datamodel_code_generator.format import is_supported_in_black as supported  # noqa: PLC0415

    return supported(python_version)


def is_url(ref: str) -> bool:
    """Check if a reference string is a URL (HTTP, HTTPS, or file scheme)."""
    # Keep this local so importing the CLI does not import reference.py and its model stack.
    return ref.startswith(("https://", "http://", "file://"))


_HttpKeyValuePair: TypeAlias = tuple[str, str]
_HttpKeyValueInput: TypeAlias = str | _HttpKeyValuePair
_HttpSeparator: TypeAlias = Literal[":", "="]
_HttpItemErrorName: TypeAlias = Literal["http header", "http query parameter"]
_HttpValueErrorName: TypeAlias = Literal["http_headers", "http_query_parameters"]
_RawConfigValue: TypeAlias = (
    str
    | bool
    | int
    | float
    | Path
    | ParseResult
    | Enum
    | Sequence[str]
    | Sequence[StrictTypes]
    | Sequence[OpenAPIScope]
    | Sequence[tuple[str, str]]
    | Mapping[str, Any]
    | Mapping[str, str]
    | Mapping[str, str | list[str]]
)


def _validate_http_key_value_options(
    value: Any,
    *,
    separator: _HttpSeparator,
    item_error_name: _HttpItemErrorName,
    value_error_name: _HttpValueErrorName,
) -> list[_HttpKeyValuePair] | None:
    if value is None:  # pragma: no cover
        return None

    def validate_each_item(each_item: _HttpKeyValueInput) -> _HttpKeyValuePair:
        if isinstance(each_item, str):  # pragma: no cover
            try:
                field_name, field_value = each_item.split(separator, maxsplit=1)
                return field_name, field_value.lstrip()
            except ValueError as exc:
                msg = f"Invalid {item_error_name}: {each_item!r}"
                raise Error(msg) from exc
        return each_item  # pragma: no cover

    if isinstance(value, list):
        return [validate_each_item(cast("_HttpKeyValueInput", each_item)) for each_item in value]
    msg = f"Invalid {value_error_name} value: {value!r}"  # pragma: no cover
    raise Error(msg)  # pragma: no cover


@lru_cache(maxsize=1)
def _get_config_class() -> type[Config]:
    from pydantic import ConfigDict, Field, ValidationInfo, field_validator, model_validator  # noqa: PLC0415
    from typing_extensions import Self  # noqa: PLC0415

    from datamodel_code_generator.base_config import BaseGenerateConfig  # noqa: PLC0415

    class Config(BaseGenerateConfig):  # noqa: PLR0904
        """Configuration model for code generation."""

        model_config = ConfigDict(
            extra="ignore",
            arbitrary_types_allowed=True,
            protected_namespaces=(),
            defer_build=True,
        )

        def get(self, item: str) -> Any:  # pragma: no cover
            """Get attribute value by name."""
            return getattr(self, item)

        def __getitem__(self, item: str) -> Any:  # pragma: no cover
            """Get item by key."""
            return self.get(item)

        @classmethod
        def get_fields(cls) -> dict[str, Any]:
            """Get model fields."""
            return cls.model_fields

        @field_validator(
            "aliases",
            "serialization_aliases",
            "extra_template_data",
            "custom_formatters_kwargs",
            "default_values",
            "base_class_map",
            "model_name_map",
            "enum_field_as_literal_map",
            "duplicate_name_suffix",
            "type_overrides",
            mode="before",
        )
        @classmethod
        def validate_json_config(cls, value: Any, info: ValidationInfo) -> Any:
            """Load and validate JSON configuration values from inline JSON or file paths."""
            if value is None:  # pragma: no cover
                return value
            from datamodel_code_generator.json_config import JsonConfigError, load_json_config_field  # noqa: PLC0415

            field_name: JsonConfigFieldName = cast("JsonConfigFieldName", info.field_name)
            try:
                json_config_source: JsonConfigSource = cast("JsonConfigSource", value)
                return load_json_config_field(field_name, json_config_source)
            except JsonConfigError as e:
                raise Error(str(e)) from e

        @field_validator("validators", mode="before")
        @classmethod
        def validate_validators_config(cls, value: Any) -> Any:
            """Validate validators lazily only when the option is present."""
            if value is None:
                return None

            from datamodel_code_generator.json_config import JsonConfigError, load_json_config_field  # noqa: PLC0415

            try:
                return load_json_config_field("validators", cast("JsonConfigSource", value))
            except JsonConfigError as e:
                raise Error(str(e)) from e

        @field_validator(
            "input",
            "output",
            "custom_template_dir",
            "custom_file_header_path",
            "http_local_ref_path",
            mode="before",
        )
        @classmethod
        def validate_path(cls, value: Any) -> Path | None:
            """Validate and resolve path."""
            if value is None or isinstance(value, Path):
                return value  # pragma: no cover
            return Path(value).expanduser().resolve()

        @field_validator("url", mode="before")
        @classmethod
        def validate_url(cls, value: Any) -> ParseResult | None:
            """Validate and parse URL."""
            if isinstance(value, str) and is_url(value):  # pragma: no cover
                return urlparse(value)
            if value is None:  # pragma: no cover
                return None
            msg = f"Unsupported URL scheme. Supported: http, https, file. --input={value}"  # pragma: no cover
            raise Error(msg)  # pragma: no cover

        # Pydantic 1.5.1 doesn't support each_item=True correctly
        @field_validator("http_headers", mode="before")
        @classmethod
        def validate_http_headers(cls, value: Any) -> list[tuple[str, str]] | None:
            """Validate HTTP headers."""
            return _validate_http_key_value_options(
                value,
                separator=":",
                item_error_name="http header",
                value_error_name="http_headers",
            )

        @field_validator("http_query_parameters", mode="before")
        @classmethod
        def validate_http_query_parameters(cls, value: Any) -> list[tuple[str, str]] | None:
            """Validate HTTP query parameters."""
            return _validate_http_key_value_options(
                value,
                separator="=",
                item_error_name="http query parameter",
                value_error_name="http_query_parameters",
            )

        @model_validator(mode="before")
        @classmethod
        def validate_additional_imports(cls, values: dict[str, Any]) -> dict[str, Any]:
            """Validate and split additional imports."""
            additional_imports = values.get("additional_imports")
            if additional_imports is not None:
                values["additional_imports"] = additional_imports.split(",")
            return values

        @model_validator(mode="before")
        @classmethod
        def validate_custom_formatters(cls, values: dict[str, Any]) -> dict[str, Any]:
            """Validate and split custom formatters."""
            custom_formatters = values.get("custom_formatters")
            if custom_formatters is not None:
                values["custom_formatters"] = custom_formatters.split(",")
            return values

        @model_validator(mode="before")
        @classmethod
        def validate_naming_strategy_migration(cls, values: dict[str, Any]) -> dict[str, Any]:
            """Migrate deprecated --parent-scoped-naming to --naming-strategy."""
            if values.get("parent_scoped_naming") and not values.get("naming_strategy"):
                values["naming_strategy"] = NamingStrategy.ParentPrefixed
                warn_deprecated("cli.parent-scoped-naming", stacklevel=2)
            return values

        @model_validator(mode="before")
        @classmethod
        def validate_allow_extra_fields_migration(cls, values: dict[str, Any]) -> dict[str, Any]:
            """Migrate deprecated --allow-extra-fields to --extra-fields."""
            if values.get("allow_extra_fields") and not values.get("extra_fields"):
                values["extra_fields"] = "allow"
                warn_deprecated("cli.allow-extra-fields", stacklevel=2)
            return values

        @model_validator(mode="before")
        @classmethod
        def validate_class_decorators(cls, values: dict[str, Any]) -> dict[str, Any]:
            """Validate and split class decorators, adding @ prefix if missing."""
            class_decorators = values.get("class_decorators")
            if class_decorators is not None:
                decorators = []
                for raw_decorator in class_decorators.split(","):
                    stripped = raw_decorator.strip()
                    if stripped:
                        if not stripped.startswith("@"):
                            stripped = f"@{stripped}"
                        decorators.append(stripped)
                values["class_decorators"] = decorators
            return values

        @model_validator(mode="before")
        @classmethod
        def validate_external_ref_mapping(cls, values: dict[str, Any]) -> dict[str, Any]:
            """Parse external_ref_mapping from list of KEY=VALUE strings to dict."""
            raw = values.get("external_ref_mapping")
            if raw is not None and isinstance(raw, list):
                mapping: dict[str, str] = {}
                for item in raw:
                    if not isinstance(item, str) or "=" not in item:
                        msg = (
                            f"Invalid --external-ref-mapping format: {item!r}. "
                            "Expected FILE_PATH=PYTHON_PACKAGE (e.g., '../common/schema.yaml=mypackage.models')"
                        )
                        raise Error(msg)
                    file_path, python_package = item.split("=", maxsplit=1)
                    file_path = file_path.strip()
                    python_package = python_package.strip()
                    if not file_path or not python_package:
                        msg = (
                            f"Invalid --external-ref-mapping format: {item!r}. "
                            "Both FILE_PATH and PYTHON_PACKAGE must be non-empty."
                        )
                        raise Error(msg)
                    mapping[file_path] = python_package
                values["external_ref_mapping"] = mapping
            return values

        __validate_custom_file_header_err: ClassVar[str] = (
            "`--custom_file_header_path` can not be used with `--custom_file_header`."
        )
        __validate_keyword_only_err: ClassVar[str] = (
            f"`--keyword-only` requires `--target-python-version` {PythonVersion.PY_310.value} or higher."
        )

        __validate_all_exports_collision_strategy_err: ClassVar[str] = (
            "`--all-exports-collision-strategy` can only be used with `--all-exports-scope=recursive`."
        )

        @model_validator(mode="after")
        def validate_output_datetime_class(self: Self) -> Self:
            """Validate output datetime class compatibility."""
            _validate_output_datetime_class(self.output_model_type, self.output_datetime_class)
            return self

        __validate_original_field_name_delimiter_err: ClassVar[str] = ORIGINAL_FIELD_NAME_DELIMITER_ERROR

        @model_validator(mode="after")
        def validate_alias_generator(self: Self) -> Self:
            """Validate alias generator compatibility."""
            _validate_alias_generator(self.output_model_type, self.alias_generator)
            return self

        def validate_original_field_name_delimiter(self) -> None:
            """Validate original field name delimiter requires snake case after preset merging."""
            if self.original_field_name_delimiter is not None and not self.snake_case_field:
                raise Error(self.__validate_original_field_name_delimiter_err)

        @model_validator(mode="after")
        def validate_custom_file_header(self: Self) -> Self:
            """Validate custom file header options are mutually exclusive."""
            if self.custom_file_header and self.custom_file_header_path:
                raise Error(self.__validate_custom_file_header_err)
            return self

        @model_validator(mode="after")
        def validate_keyword_only(self: Self) -> Self:
            """Validate keyword-only compatibility with target Python version."""
            output_model_type: DataModelType = self.output_model_type
            python_target: PythonVersion = self.target_python_version
            if (
                self.keyword_only
                and output_model_type == DataModelType.DataclassesDataclass
                and not python_target.has_kw_only_dataclass
            ):
                raise Error(self.__validate_keyword_only_err)  # pragma: no cover
            return self

        @model_validator(mode="after")
        def validate_root(self: Self) -> Self:
            """Validate root model configuration."""
            if self.use_annotated:
                self.field_constraints = True
            return self

        @model_validator(mode="after")
        def validate_all_exports_collision_strategy(self: Self) -> Self:
            """Validate all_exports_collision_strategy requires recursive scope."""
            if self.all_exports_collision_strategy is not None and self.all_exports_scope != AllExportsScope.Recursive:
                raise Error(self.__validate_all_exports_collision_strategy_err)
            return self

        @field_validator("input_model", mode="before")
        @classmethod
        def coerce_input_model_to_list(cls, v: str | list[str] | None) -> list[str] | None:
            """Convert string input_model to list for backwards compatibility."""
            if isinstance(v, str):
                return [v]
            return v

        @field_validator("class_name_affix_scope", mode="before")
        @classmethod
        def validate_class_name_affix_scope(cls, v: str | ClassNameAffixScope | None) -> ClassNameAffixScope:
            """Convert string to ClassNameAffixScope enum."""
            if v is None:  # pragma: no cover
                return ClassNameAffixScope.All
            if isinstance(v, str):
                return ClassNameAffixScope(v)
            return v  # pragma: no cover

        @field_validator("schema_validator_base_class_name")
        @classmethod
        def validate_schema_validator_base_class_name(cls, v: str | None) -> str | None:
            """Validate schema validator base class name."""
            if v is None:  # pragma: no cover
                return v
            if not v.isidentifier() or iskeyword(v):
                msg = f"--schema-validator-base-class-name '{v}' is not a valid Python identifier"
                raise Error(msg)
            return v

        input: Optional[Union[Path, str]] = None  # noqa: UP007, UP045
        input_model: Optional[list[str]] = None  # noqa: UP045
        input_model_ref_strategy: Optional[InputModelRefStrategy] = None  # noqa: UP045
        input_file_type: InputFileType = InputFileType.Auto
        output_model_type: DataModelType = DataModelType.PydanticV2BaseModel
        output: Optional[Path] = None  # noqa: UP045
        check: bool = False
        debug: bool = False
        disable_warnings: bool = False
        extra_template_data: Mapping[str, dict[str, Any]] | None = None
        validators: Optional[ValidatorsConfigValue] = None  # noqa: UP045
        aliases: Optional[Mapping[str, str | list[str]]] = None  # noqa: UP045
        serialization_aliases: Optional[Mapping[str, str]] = None  # noqa: UP045
        default_values: Optional[Mapping[str, Any]] = None  # noqa: UP045
        use_default: bool = False
        force_optional: bool = False
        url: Optional[ParseResult] = None  # noqa: UP045
        strict_types: list[StrictTypes] = Field(default_factory=list)
        openapi_scopes: Optional[list[OpenAPIScope]] = Field(default_factory=lambda: [OpenAPIScope.Schemas])  # noqa: UP045
        custom_formatters_kwargs: Optional[dict[str, str]] = None  # noqa: UP045
        watch: bool = False
        watch_delay: float = 0.5
        list_deprecations: Optional[str] = None  # noqa: UP045
        list_experimental: Optional[str] = None  # noqa: UP045

        def merge_args(self, args: Namespace) -> None:
            """Merge command-line arguments into config."""
            set_args = _prepare_cli_config_args(_explicit_config_args(args))
            explicit_input_sources = {
                field_name for field_name in ("input", "url", "input_model") if field_name in set_args
            }

            if explicit_input_sources:
                for field_name in {"input", "url", "input_model"} - explicit_input_sources:
                    setattr(self, field_name, None)

            parsed_args = Config.model_validate(set_args)
            for field_name in set_args:
                setattr(self, field_name, getattr(parsed_args, field_name))

    Config.__qualname__ = "Config"
    globals()["Config"] = Config
    return Config


def __getattr__(name: str) -> Any:
    if name == "Config":
        return _get_config_class()
    msg = f"module {__name__!r} has no attribute {name!r}"
    raise AttributeError(msg)


def _explicit_config_args(args: Namespace) -> dict[str, _RawConfigValue]:
    """Return command-line values that explicitly target Config fields."""
    config_class = _get_config_class()
    return {field: value for field in config_class.get_fields() if (value := getattr(args, field, None)) is not None}


def _prepare_cli_config_args(set_args: Mapping[str, _RawConfigValue]) -> dict[str, _RawConfigValue]:
    """Apply CLI-only derived config values before validation."""
    if not set_args:
        return {}

    prepared_args = dict(set_args)
    if prepared_args.get("output_model_type") == DataModelType.MsgspecStruct.value:
        prepared_args["use_annotated"] = True

    if prepared_args.get("use_annotated"):
        prepared_args["field_constraints"] = True

    return prepared_args


def _create_config(
    pyproject_config: Mapping[str, Any],
    cli_config_args: Mapping[str, _RawConfigValue],
) -> Config:
    """Create the final CLI config while preserving pyproject/CLI validation order."""
    config_class = _get_config_class()
    if not pyproject_config:
        return config_class.model_validate(_prepare_cli_config_args(cli_config_args))

    from argparse import Namespace as ArgNamespace  # noqa: PLC0415

    config = config_class.model_validate(pyproject_config)
    cli_namespace = ArgNamespace(**cli_config_args)
    config.merge_args(cli_namespace)
    return config


def _apply_preset(
    config: Config,
    pyproject_config: Mapping[str, _RawConfigValue],
    cli_config_args: Mapping[str, _RawConfigValue],
) -> None:
    """Apply the selected preset to the final CLI/pyproject config."""
    preset_from_cli = "preset" in cli_config_args
    if preset_from_cli:
        preset_value = cli_config_args["preset"]
        if not isinstance(preset_value, str):  # pragma: no cover
            msg = f"--preset must be a string, got {preset_value!r}"
            raise Error(msg)
        preset_name = preset_value
    else:
        preset_name = config.preset
    if preset_name is None:
        return

    explicit_fields = set(cli_config_args) if preset_from_cli else set(pyproject_config) | set(cli_config_args)
    explicit_fields.discard("preset")

    from datamodel_code_generator.preset import (  # noqa: PLC0415
        PresetContext,
        PresetError,
        resolve_preset_config_updates,
    )

    try:
        preset_config = resolve_preset_config_updates(
            preset_name,
            context=PresetContext(
                input_file_type=config.input_file_type,
                output_model_type=config.output_model_type,
                target_python_version=config.target_python_version,
            ),
            use_annotated=config.use_annotated,
            explicit_fields=explicit_fields,
        )
    except PresetError as e:
        raise Error(str(e)) from e

    if preset_config.target_python_version is not None:
        config.target_python_version = preset_config.target_python_version

    for item in preset_config.items:
        setattr(config, item.field_name, item.applied_value)

    if preset_config.force_field_constraints:
        config.field_constraints = True


def _validate_final_config(config: Config) -> None:
    """Validate invariants that depend on CLI, pyproject, and preset merging."""
    config.validate_original_field_name_delimiter()


def _extract_additional_imports(extra_template_data: defaultdict[str, dict[str, Any]]) -> list[str]:
    """Extract additional_imports from extra_template_data entries."""
    additional_imports: list[str] = []
    for type_data in extra_template_data.values():
        if "additional_imports" in type_data:
            imports = type_data.pop("additional_imports")
            if isinstance(imports, str):
                if imports.strip():  # pragma: no branch
                    additional_imports.append(imports.strip())
            elif isinstance(imports, list):  # pragma: no branch
                additional_imports.extend(item.strip() for item in imports if isinstance(item, str) and item.strip())
    return additional_imports


def _resolve_profile_extends(
    profiles: dict[str, Any],
    profile_name: str,
    visited: set[str] | None = None,
) -> dict[str, Any]:
    """Resolve profile inheritance via extends key."""
    if visited is None:
        visited = set()

    if profile_name in visited:
        chain = " -> ".join(visited) + f" -> {profile_name}"
        msg = f"Circular extends detected: {chain}"
        raise Error(msg)

    if profile_name not in profiles:
        available = list(profiles.keys()) if profiles else "none"
        msg = f"Extended profile '{profile_name}' not found in pyproject.toml. Available profiles: {available}"
        raise Error(msg)

    visited.add(profile_name)
    profile = profiles[profile_name]
    extends = profile.get("extends")

    if not extends:
        return dict(profile.items())

    parents = [extends] if isinstance(extends, str) else extends
    result: dict[str, Any] = {}

    for parent in parents:
        if parent == profile_name:
            msg = f"Profile '{profile_name}' cannot extend itself"
            raise Error(msg)
        parent_config = _resolve_profile_extends(profiles, parent, visited.copy())
        result.update(parent_config)

    result.update({k: v for k, v in profile.items() if k != "extends"})
    return result


def _get_pyproject_toml_config(source: Path, profile: str | None = None) -> dict[str, Any]:
    """Find and return the [tool.datamodel-codegen] section of the closest pyproject.toml if it exists."""
    current_path = source
    while current_path != current_path.parent:
        if (current_path / "pyproject.toml").is_file():
            pyproject_toml = load_toml(current_path / "pyproject.toml")
            if "datamodel-codegen" in pyproject_toml.get("tool", {}):
                tool_config = pyproject_toml["tool"]["datamodel-codegen"]

                base_config: dict[str, Any] = {k: v for k, v in tool_config.items() if k != "profiles"}

                if profile:
                    profiles = tool_config.get("profiles", {})
                    if profile not in profiles:
                        available = list(profiles.keys()) if profiles else "none"
                        msg = f"Profile '{profile}' not found in pyproject.toml. Available profiles: {available}"
                        raise Error(msg)
                    resolved_profile = _resolve_profile_extends(profiles, profile)
                    base_config.update(resolved_profile)

                pyproject_config = {k.replace("-", "_"): v for k, v in base_config.items()}
                if (
                    "capitalize_enum_members" in pyproject_config and "capitalise_enum_members" not in pyproject_config
                ):  # pragma: no cover
                    pyproject_config["capitalise_enum_members"] = pyproject_config.pop("capitalize_enum_members")
                return pyproject_config

        if (current_path / ".git").exists():  # pragma: no cover
            break

        current_path = current_path.parent

    if profile:
        msg = f"Profile '{profile}' requested but no [tool.datamodel-codegen] section found in pyproject.toml"
        raise Error(msg)

    return {}


TomlValue: TypeAlias = str | bool | list["TomlValue"] | tuple["TomlValue", ...]


def _json_ready(value: Any) -> Any:
    from pydantic import BaseModel  # noqa: PLC0415

    if isinstance(value, Enum):
        return value.value
    if isinstance(value, Path):
        return value.as_posix()
    if isinstance(value, BaseModel):
        return value.model_dump(mode="json", exclude_none=True)
    if isinstance(value, tuple | list):
        return [_json_ready(item) for item in value]
    if isinstance(value, Mapping):
        return {str(key): _json_ready(item) for key, item in value.items()}
    return value


def _pyproject_toml_value(key: str, value: object) -> TomlValue:
    from datamodel_code_generator.json_config import JsonConfigSpecs  # noqa: PLC0415

    if key not in JsonConfigSpecs.by_field_name or not isinstance(value, Mapping):
        return cast("TomlValue", value)
    return json.dumps(_json_ready(value), ensure_ascii=False, separators=(",", ":"))


def _format_toml_value(value: TomlValue) -> str:
    """Format a Python value as a TOML value string."""
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, str):
        return json.dumps(value, ensure_ascii=False)
    formatted_items = [_format_toml_value(item) for item in value]
    return f"[{', '.join(formatted_items)}]"


def _pyproject_config_data(args: Namespace) -> dict[str, TomlValue]:
    """Return pyproject.toml configuration data from CLI arguments."""
    config_data: dict[str, TomlValue] = {}
    args_dict: dict[str, object] = vars(args)
    for key, value in sorted(args_dict.items()):
        if value is None:
            continue
        if key in EXCLUDED_CONFIG_OPTIONS:
            continue

        config_data[key.replace("_", "-")] = _pyproject_toml_value(key, value)
    return config_data


def _format_pyproject_config(config_data: Mapping[str, TomlValue]) -> str:
    lines: list[str] = ["[tool.datamodel-codegen]"]
    for key, value in config_data.items():
        lines.append(f"{key} = {_format_toml_value(value)}")
    return "\n".join(lines) + "\n"


def generate_pyproject_config(args: Namespace) -> str:
    """Generate pyproject.toml [tool.datamodel-codegen] section from CLI arguments."""
    return _format_pyproject_config(_pyproject_config_data(args))


def _normalize_line_endings(text: str) -> str:
    """Normalize line endings to LF for cross-platform comparison."""
    return text.replace("\r\n", "\n")


def _compare_single_file(
    generated_path: Path,
    actual_path: Path,
    encoding: str,
) -> tuple[bool, list[str]]:
    """Compare generated file content with existing file.

    Returns:
        Tuple of (has_differences, diff_lines)
        - has_differences: True if files differ or actual file doesn't exist
        - diff_lines: List of diff lines for output
    """
    generated_content = _normalize_line_endings(generated_path.read_text(encoding=encoding))

    display_path = actual_path.as_posix()
    if not actual_path.exists():
        return True, [f"MISSING: {display_path} (file does not exist but should be generated)"]

    actual_content = _normalize_line_endings(actual_path.read_text(encoding=encoding))

    if generated_content == actual_content:
        return False, []

    diff_lines = list(
        difflib.unified_diff(
            actual_content.splitlines(keepends=True),
            generated_content.splitlines(keepends=True),
            fromfile=display_path,
            tofile=f"{display_path} (expected)",
        )
    )
    return True, diff_lines


class DirectoryChangedFile(NamedTuple):
    """One changed file found while comparing generated and existing directories."""

    path: str
    diff_lines: list[str]


def _compare_directories(
    generated_dir: Path,
    actual_dir: Path,
    encoding: str,
) -> tuple[list[DirectoryChangedFile], list[str], list[str]]:
    """Compare generated directory with existing directory."""
    changed_files: list[DirectoryChangedFile] = []

    generated_files = {path.relative_to(generated_dir) for path in generated_dir.rglob("*.py")}

    actual_files: set[Path] = set()
    if actual_dir.exists():
        for path in actual_dir.rglob("*.py"):
            if "__pycache__" not in path.parts:
                actual_files.add(path.relative_to(actual_dir))

    missing_files = [rel_path.as_posix() for rel_path in sorted(generated_files - actual_files)]
    extra_files = [rel_path.as_posix() for rel_path in sorted(actual_files - generated_files)]

    for rel_path in sorted(generated_files & actual_files):
        generated_content = _normalize_line_endings((generated_dir / rel_path).read_text(encoding=encoding))
        actual_content = _normalize_line_endings((actual_dir / rel_path).read_text(encoding=encoding))
        if generated_content != actual_content:
            changed_files.append(
                DirectoryChangedFile(
                    path=rel_path.as_posix(),
                    diff_lines=list(
                        difflib.unified_diff(
                            actual_content.splitlines(keepends=True),
                            generated_content.splitlines(keepends=True),
                            fromfile=rel_path.as_posix(),
                            tofile=f"{rel_path.as_posix()} (expected)",
                        )
                    ),
                )
            )

    return changed_files, missing_files, extra_files


def _format_cli_value(value: str | list[str]) -> str:
    """Format a value for CLI argument."""
    if isinstance(value, list):
        return " ".join(f'"{v}"' if " " in v else v for v in value)
    return f'"{value}"' if " " in value else value


def generate_cli_command(config: dict[str, TomlValue]) -> str:
    """Generate CLI command from pyproject.toml configuration."""
    parts: list[str] = ["datamodel-codegen"]

    for key, value in sorted(config.items()):
        if key in EXCLUDED_CONFIG_OPTIONS:
            continue

        cli_key = key.replace("_", "-")

        if isinstance(value, bool):
            if value:
                parts.append(f"--{cli_key}")
            elif key in BOOLEAN_OPTIONAL_OPTIONS:
                parts.append(f"--no-{cli_key}")
        elif isinstance(value, list):
            parts.extend((f"--{cli_key}", _format_cli_value(cast("list[str]", value))))
        else:
            parts.extend((f"--{cli_key}", _format_cli_value(str(value))))

    return " ".join(parts)


def _hyphenated_config_data(config: Mapping[str, Any]) -> dict[str, Any]:
    return {key.replace("_", "-"): _json_ready(value) for key, value in sorted(config.items())}


def _generated_module_path(module: tuple[str, ...]) -> str:
    return Path(*module).as_posix()


def _generated_files_from_result(result: str | Mapping[tuple[str, ...], str]) -> list[GeneratedFilePayload]:
    from datamodel_code_generator._structured_output import GeneratedFilePayload  # noqa: PLC0415

    if isinstance(result, str):
        return [GeneratedFilePayload(path=None, content=result)]
    return [
        GeneratedFilePayload(path=_generated_module_path(module), content=content)
        for module, content in sorted(result.items())
    ]


def _generated_files_from_output(
    output: Path, encoding: str, *, display_output: Path | None = None
) -> list[GeneratedFilePayload]:
    from datamodel_code_generator._structured_output import GeneratedFilePayload  # noqa: PLC0415

    if output.is_file():
        return [
            GeneratedFilePayload(
                path=(display_output or output).name,
                content=output.read_text(encoding=encoding),
            )
        ]
    return [
        GeneratedFilePayload(path=path.relative_to(output).as_posix(), content=path.read_text(encoding=encoding))
        for path in sorted(output.rglob("*.py"))
        if path.is_file()
    ]


def _structured_output_path(output: Path | str | None) -> str | None:
    if isinstance(output, Path):
        return output.as_posix()
    return output


def _generation_output_json(files: list[GeneratedFilePayload], output: Path | str | None = None) -> str:
    from datamodel_code_generator._structured_output import generation_output_json  # noqa: PLC0415

    return generation_output_json(files, output=_structured_output_path(output))


def _command_output_json(
    kind: CommandOutputKind,
    content: str,
    *,
    config: dict[str, Any] | None = None,
    items: list[dict[str, Any]] | None = None,
    arguments: list[str] | None = None,
) -> str:
    from datamodel_code_generator._structured_output import command_output_json  # noqa: PLC0415

    return command_output_json(kind, content, config=config, items=items, arguments=arguments)


def _check_output_json(
    *,
    success: bool,
    content: str,
    differences: list[CheckDifferencePayload],
) -> str:
    from datamodel_code_generator._structured_output import check_output_json  # noqa: PLC0415

    return check_output_json(success=success, content=content, differences=differences)


def _generation_output_json_schema() -> str:
    from datamodel_code_generator._structured_output import generation_output_json_schema  # noqa: PLC0415

    return generation_output_json_schema()


def _model_metadata_json_schema() -> str:
    from datamodel_code_generator.model_metadata import model_metadata_json_schema  # noqa: PLC0415

    return model_metadata_json_schema()


def _structured_output_json_schema() -> str:
    from datamodel_code_generator._structured_output import structured_output_json_schema  # noqa: PLC0415

    return structured_output_json_schema()


def _copy_generated_output(generated_output: Path, actual_output: Path, *, is_directory_output: bool) -> None:
    if is_directory_output:
        for generated_file in sorted(generated_output.rglob("*")):
            if not generated_file.is_file():
                continue
            target = actual_output / generated_file.relative_to(generated_output)
            target.parent.mkdir(parents=True, exist_ok=True)
            shutil.copyfile(generated_file, target)
        return

    actual_output.parent.mkdir(parents=True, exist_ok=True)
    shutil.copyfile(generated_output, actual_output)


def _write_generated_result(result: str | Mapping[tuple[str, ...], str], output_format: str | None) -> None:
    if output_format == "json":
        sys.stdout.write(_generation_output_json(_generated_files_from_result(result)) + "\n")
    elif isinstance(result, str):
        sys.stdout.write(result + "\n")
    else:
        for content in result.values():
            sys.stdout.write(content + "\n")


def run_generate_from_config(  # noqa: PLR0913, PLR0917
    config: Config,
    input_: Path | str | ParseResult,
    output: Path | None,
    extra_template_data: defaultdict[str, dict[str, Any]] | None,
    aliases: Mapping[str, str | list[str]] | None,
    serialization_aliases: Mapping[str, str] | None,
    command_line: str | None,
    custom_formatters_kwargs: dict[str, str] | None,
    settings_path: Path | None = None,
    validators: Mapping[str, ModelValidators] | None = None,
    default_value_overrides: Mapping[str, Any] | None = None,
) -> str | Mapping[tuple[str, ...], str] | None:
    """Run code generation with the given config and parameters."""
    generation_config = config.model_copy(
        update={
            "input_filename": None,
            "output": output,
            "preset": None,
            "extra_template_data": extra_template_data,
            "aliases": aliases,
            "serialization_aliases": serialization_aliases,
            "command_line": command_line,
            "apply_default_values_for_required_fields": config.use_default,
            "force_optional_for_required_fields": config.force_optional,
            "custom_formatters_kwargs": custom_formatters_kwargs,
            "settings_path": settings_path,
            "validators": validators,
            "default_value_overrides": default_value_overrides,
        }
    )
    return generate(
        input_=input_,
        config=cast("Any", generation_config),  # ty: ignore[redundant-cast]
    )


def main(args: Sequence[str] | None = None) -> Exit:  # noqa: PLR0911, PLR0912, PLR0914, PLR0915
    """Execute datamodel code generation from command-line arguments."""
    vars(namespace).clear()
    namespace.no_color = False

    if "_ARGCOMPLETE" in os.environ:  # pragma: no cover
        import argcomplete  # noqa: PLC0415

        argcomplete.autocomplete(arg_parser)

    if args is None:  # pragma: no cover
        args = sys.argv[1:]

    arg_parser.parse_args(args, namespace=namespace)

    if namespace.version:
        from datamodel_code_generator import get_version  # noqa: PLC0415

        print(get_version())  # noqa: T201
        sys.exit(0)

    if namespace.output_format_json_schema == "generate-prompt":
        from datamodel_code_generator.prompt import generate_prompt_json_schema  # noqa: PLC0415

        print(generate_prompt_json_schema())  # noqa: T201
        return Exit.OK
    if namespace.output_format_json_schema == "config":
        from datamodel_code_generator.json_config import json_config_json_schema  # noqa: PLC0415

        print(json_config_json_schema())  # noqa: T201
        return Exit.OK
    if namespace.output_format_json_schema == "generation":
        print(_generation_output_json_schema())  # noqa: T201
        return Exit.OK
    if namespace.output_format_json_schema == "model-metadata":
        print(_model_metadata_json_schema())  # noqa: T201
        return Exit.OK
    if namespace.output_format_json_schema == "structured-output":
        print(_structured_output_json_schema())  # noqa: T201
        return Exit.OK

    if namespace.generate_pyproject_config:
        config_data = _pyproject_config_data(namespace)
        config_output = _format_pyproject_config(config_data)
        if namespace.output_format == "json":
            print(  # noqa: T201
                _command_output_json(
                    "pyproject-config",
                    config_output,
                    config={"tool": {"datamodel-codegen": _json_ready(config_data)}},
                )
            )
        else:
            print(config_output)  # noqa: T201
        return Exit.OK

    if namespace.generate_prompt is not None:
        from datamodel_code_generator.prompt import generate_prompt  # noqa: PLC0415

        help_text = arg_parser.format_help()
        prompt_output = generate_prompt(namespace, help_text, arg_parser)
        print(prompt_output)  # noqa: T201
        return Exit.OK

    # Handle --ignore-pyproject and --profile options
    if namespace.ignore_pyproject:
        pyproject_config: dict[str, Any] = {}
    else:
        try:
            pyproject_config = _get_pyproject_toml_config(Path.cwd(), profile=namespace.profile)
        except Error as e:
            print(e.message, file=sys.stderr)  # noqa: T201
            return Exit.ERROR

    if namespace.generate_cli_command:
        if not pyproject_config:
            print(  # noqa: T201
                "No [tool.datamodel-codegen] section found in pyproject.toml",
                file=sys.stderr,
            )
            return Exit.ERROR
        command_output = generate_cli_command(pyproject_config)
        if namespace.output_format == "json":
            print(  # noqa: T201
                _command_output_json(
                    "cli-command",
                    command_output,
                    config=_hyphenated_config_data(pyproject_config),
                    arguments=shlex.split(command_output),
                )
            )
        else:
            print(command_output)  # noqa: T201
        return Exit.OK

    try:
        cli_config_args = _explicit_config_args(namespace)
        config = _create_config(pyproject_config, cli_config_args)
        _apply_preset(config, pyproject_config, cli_config_args)
        _validate_final_config(config)
    except Error as e:
        print(str(e), file=sys.stderr)  # noqa: T201
        return Exit.ERROR

    if config.list_deprecations:
        content = render_deprecations(cast("Any", config.list_deprecations))
        if namespace.output_format == "json":
            print(  # noqa: T201
                _command_output_json(
                    "deprecations",
                    content,
                    items=json.loads(render_deprecations("json")),
                )
            )
        else:
            print(content, end="")  # noqa: T201
        return Exit.OK

    if config.list_experimental:
        from datamodel_code_generator.experimental import render_experimental_features  # noqa: PLC0415

        content = render_experimental_features(cast("Any", config.list_experimental))
        if namespace.output_format == "json":
            print(  # noqa: T201
                _command_output_json(
                    "experimental",
                    content,
                    items=json.loads(render_experimental_features("json")),
                )
            )
        else:
            print(content, end="")  # noqa: T201
        return Exit.OK

    if not config.input and not config.url and not config.input_model and sys.stdin.isatty():
        print(  # noqa: T201
            "Not Found Input: require `stdin` or arguments `--input`, `--url`, or `--input-model`",
            file=sys.stderr,
        )
        arg_parser.print_help()
        return Exit.ERROR

    if config.input_model and (config.input or config.url):
        print(  # noqa: T201
            "Error: --input-model cannot be used with --input or --url",
            file=sys.stderr,
        )
        return Exit.ERROR

    if config.url and config.allow_remote_refs is None:
        config.allow_remote_refs = True

    if config.check and config.output is None:
        print(  # noqa: T201
            "Error: --check cannot be used with stdout output (no --output specified)",
            file=sys.stderr,
        )
        return Exit.ERROR
    if config.check and config.emit_model_metadata is not None:
        print(  # noqa: T201
            "Error: --check cannot be used with --emit-model-metadata",
            file=sys.stderr,
        )
        return Exit.ERROR

    if config.watch and config.check:
        print(  # noqa: T201
            "Error: --watch and --check cannot be used together",
            file=sys.stderr,
        )
        return Exit.ERROR

    if config.watch and namespace.output_format == "json":
        print(  # noqa: T201
            "Error: --output-format json cannot be used with --watch",
            file=sys.stderr,
        )
        return Exit.ERROR

    if config.watch and config.input_model:
        print(  # noqa: T201
            "Error: --watch cannot be used with --input-model",
            file=sys.stderr,
        )
        return Exit.ERROR

    if config.watch and (config.input is None or is_url(str(config.input))):
        print(  # noqa: T201
            "Error: --watch requires --input file path (not URL or stdin)",
            file=sys.stderr,
        )
        return Exit.ERROR

    uses_black_formatter = config.formatters is None or Formatter.BLACK in config.formatters
    if uses_black_formatter and not is_supported_in_black(config.target_python_version):  # pragma: no cover
        print(  # noqa: T201
            f"Installed black doesn't support Python version {config.target_python_version.value}.\n"
            f"You have to install a newer black.\n"
            f"Installed black version: {_get_black().__version__}",
            file=sys.stderr,
        )
        return Exit.ERROR

    if config.debug:  # pragma: no cover
        enable_debug_message()

    if config.disable_warnings:
        warnings.simplefilter("ignore")

    if (
        config.output_model_type in {DataModelType.PydanticV2BaseModel, DataModelType.PydanticV2Dataclass}
        and not config.use_annotated
        and namespace.use_annotated is None
        and pyproject_config.get("use_annotated") is None
    ):
        warn_deprecated(
            "behavior.pydantic-v2-use-annotated-default",
            details=(
                "The current default (use_annotated=False) generates constrained types like "
                "'conint(ge=1, le=365)' which are discouraged in Pydantic v2."
            ),
            stacklevel=1,
        )

    if config.reuse_scope == ReuseScope.Tree and not config.reuse_model:
        print(  # noqa: T201
            "Warning: --reuse-scope=tree has no effect without --reuse-model",
            file=sys.stderr,
        )

    if config.collapse_root_models_name_strategy and not config.collapse_root_models:
        print(  # noqa: T201
            "Error: --collapse-root-models-name-strategy requires --collapse-root-models",
            file=sys.stderr,
        )
        return Exit.ERROR

    if (
        config.use_specialized_enum
        and namespace.use_specialized_enum is not False  # CLI didn't disable it
        and (namespace.use_specialized_enum is True or pyproject_config.get("use_specialized_enum") is True)
        and not config.target_python_version.has_strenum
    ):
        print(  # noqa: T201
            f"Error: --use-specialized-enum requires --target-python-version 3.11 or later.\n"
            f"Current target version: {config.target_python_version.value}\n"
            f"StrEnum is only available in Python 3.11+.",
            file=sys.stderr,
        )
        return Exit.ERROR

    extra_template_data: defaultdict[str, dict[str, Any]] | None
    if config.extra_template_data is None:
        extra_template_data = None
    else:
        extra_template_data = cast("defaultdict[str, dict[str, Any]]", config.extra_template_data)
        # Extract additional_imports from extra_template_data entries and merge with config
        additional_imports_from_template_data = _extract_additional_imports(extra_template_data)
        if additional_imports_from_template_data:
            if config.additional_imports is None:
                config.additional_imports = additional_imports_from_template_data
            else:
                config.additional_imports = list(config.additional_imports) + additional_imports_from_template_data

    aliases = config.aliases
    serialization_aliases = config.serialization_aliases
    default_value_overrides = config.default_values
    custom_formatters_kwargs = config.custom_formatters_kwargs
    validators_config = config.validators

    writes_json_output_file = namespace.output_format == "json" and config.output is not None and not config.check
    if config.check or writes_json_output_file:
        config_output = cast("Path", config.output)
        is_directory_output = not config_output.suffix
        temp_context: tempfile.TemporaryDirectory[str] | None = tempfile.TemporaryDirectory()
        temp_dir = Path(temp_context.name)
        if is_directory_output:
            generate_output: Path | None = temp_dir / config_output.name
        else:
            generate_output = temp_dir / "output.py"
    else:
        temp_context = None
        generate_output = config.output
        is_directory_output = False

    def cleanup_and_return(exit_code: Exit) -> Exit:
        if temp_context is not None:
            temp_context.cleanup()
        return exit_code

    try:
        input_: Path | str | ParseResult
        if config.input_model:
            from datamodel_code_generator.input_model import Error as InputModelError  # noqa: PLC0415
            from datamodel_code_generator.input_model import load_model_schema  # noqa: PLC0415

            try:
                schema = load_model_schema(
                    config.input_model,
                    config.input_file_type,
                    config.input_model_ref_strategy,
                    config.output_model_type,
                )
            except InputModelError as e:
                raise Error(str(e)) from e
            input_ = json.dumps(schema)
            if config.input_file_type == InputFileType.Auto:
                config.input_file_type = InputFileType.JsonSchema
        else:
            input_ = config.url or config.input or sys.stdin.read()

        result = run_generate_from_config(
            config=config,
            input_=input_,
            output=generate_output,
            extra_template_data=extra_template_data,
            aliases=aliases,
            serialization_aliases=serialization_aliases,
            command_line=_command_header(args) if config.enable_command_header else None,
            custom_formatters_kwargs=custom_formatters_kwargs,
            settings_path=config.output,
            validators=validators_config,
            default_value_overrides=default_value_overrides,
        )
    except InvalidClassNameError as e:
        print(f"{e} You have to set `--class-name` option", file=sys.stderr)  # noqa: T201
        return cleanup_and_return(Exit.ERROR)
    except Error as e:
        print(str(e), file=sys.stderr)  # noqa: T201
        return cleanup_and_return(Exit.ERROR)
    except Exception:  # noqa: BLE001
        import traceback  # noqa: PLC0415

        print(traceback.format_exc(), file=sys.stderr)  # noqa: T201
        return cleanup_and_return(Exit.ERROR)

    if writes_json_output_file and generate_output is not None and config.output is not None:
        _copy_generated_output(generate_output, config.output, is_directory_output=is_directory_output)

    if generate_output is None and result is not None:
        _write_generated_result(result, namespace.output_format)
    elif namespace.output_format == "json" and generate_output is not None and not config.check:
        display_output = config.output if writes_json_output_file else None
        sys.stdout.write(
            _generation_output_json(
                _generated_files_from_output(generate_output, config.encoding, display_output=display_output),
                output=config.output,
            )
            + "\n"
        )
    if config.check and config.output is not None and generate_output is not None:
        from datamodel_code_generator._structured_output import CheckDifferencePayload  # noqa: PLC0415

        has_differences = False
        single_file_diff_lines: list[str] = []
        changed_files: list[DirectoryChangedFile] = []
        missing_files: list[str] = []
        extra_files: list[str] = []
        difference_items: list[CheckDifferencePayload] = []

        if is_directory_output:
            changed_files, missing_files, extra_files = _compare_directories(
                generate_output,
                config.output,
                config.encoding,
            )
            for changed_file in changed_files:
                diff_text = "".join(changed_file.diff_lines)
                difference_items.append(
                    CheckDifferencePayload(
                        kind="changed",
                        path=changed_file.path,
                        diff=diff_text,
                    )
                )
                if namespace.output_format != "json":
                    print(diff_text, end="")  # noqa: T201
                has_differences = True
            for missing in missing_files:
                difference_items.append(
                    CheckDifferencePayload(
                        kind="missing",
                        path=missing,
                        message=f"MISSING: {missing} (should be generated)",
                    )
                )
                if namespace.output_format != "json":
                    print(f"MISSING: {missing} (should be generated)")  # noqa: T201
                has_differences = True
            for extra in extra_files:
                difference_items.append(
                    CheckDifferencePayload(
                        kind="extra",
                        path=extra,
                        message=f"EXTRA: {extra} (no longer generated)",
                    )
                )
                if namespace.output_format != "json":
                    print(f"EXTRA: {extra} (no longer generated)")  # noqa: T201
                has_differences = True
        else:
            diff_found, single_file_diff_lines = _compare_single_file(generate_output, config.output, config.encoding)
            if diff_found:
                if config.output.exists():
                    difference_items.append(
                        CheckDifferencePayload(
                            kind="changed",
                            path=config.output.as_posix(),
                            diff="".join(single_file_diff_lines),
                        )
                    )
                else:
                    difference_items.append(
                        CheckDifferencePayload(
                            kind="missing",
                            path=config.output.as_posix(),
                            message="".join(single_file_diff_lines),
                        )
                    )
                if namespace.output_format != "json":
                    print("".join(single_file_diff_lines), end="")  # noqa: T201
                has_differences = True

        if namespace.output_format == "json":
            content = "".join(single_file_diff_lines)
            content += "".join("".join(changed_file.diff_lines) for changed_file in changed_files)
            content += "".join(f"MISSING: {missing} (should be generated)\n" for missing in missing_files)
            content += "".join(f"EXTRA: {extra} (no longer generated)\n" for extra in extra_files)
            sys.stdout.write(
                _check_output_json(
                    success=not has_differences,
                    content=content,
                    differences=difference_items,
                )
                + "\n"
            )

        return cleanup_and_return(Exit.DIFF if has_differences else Exit.OK)

    if config.watch:
        try:
            from datamodel_code_generator.watch import watch_and_regenerate  # noqa: PLC0415

            return cleanup_and_return(
                watch_and_regenerate(
                    config,
                    extra_template_data,
                    aliases,
                    serialization_aliases,
                    custom_formatters_kwargs,
                    default_value_overrides,
                )
            )
        except Exception as e:  # noqa: BLE001
            print(str(e), file=sys.stderr)  # noqa: T201
            return cleanup_and_return(Exit.ERROR)

    return cleanup_and_return(Exit.OK)


if __name__ == "__main__":
    sys.exit(main())
