"""Abstract base parser and utilities for schema parsing.

Provides the Parser abstract base class that defines the parsing algorithm,
along with helper functions for model sorting, import resolution, and
code generation.
"""

from __future__ import annotations

import builtins
import operator
import os.path
import re
import sys
from abc import ABC, abstractmethod
from collections import Counter, OrderedDict, defaultdict
from copy import deepcopy
from functools import cache
from itertools import groupby
from pathlib import Path
from typing import (
    TYPE_CHECKING,
    Any,
    ClassVar,
    Final,
    Generic,
    NamedTuple,
    Optional,
    TypeAlias,
    TypeVar,
    cast,
)
from urllib.parse import ParseResult
from warnings import warn

from pydantic import BaseModel, ConfigDict
from typing_extensions import Unpack

from datamodel_code_generator import (
    AllExportsCollisionStrategy,
    AllExportsScope,
    AllOfClassHierarchy,
    AllOfMergeMode,
    CollapseRootModelsNameStrategy,
    Error,
    FieldTypeCollisionStrategy,
    ModuleSplitMode,
    ReadOnlyWriteOnlyModelType,
    ReuseScope,
    YamlValue,
    _internal_utils,
    _is_parsed_source_cache_enabled,
    _read_parser_source_data_from_path,
)
from datamodel_code_generator._format_types import Formatter, PythonVersion
from datamodel_code_generator.enums import StrictTypes
from datamodel_code_generator.imports import (
    IMPORT_ANNOTATIONS,
    IMPORT_LITERAL,
    IMPORT_OPTIONAL,
    IMPORT_UNION,
    Import,
    Imports,
)
from datamodel_code_generator.model.base import (
    ALL_MODEL,
    GENERIC_BASE_CLASS_NAME,
    GENERIC_BASE_CLASS_PATH,
    UNDEFINED,
    BaseClassDataType,
    ConstraintsBase,
    DataModel,
    DataModelFieldBase,
)
from datamodel_code_generator.model.enum import Enum, Member, evaluate_member_value
from datamodel_code_generator.model.imports import IMPORT_TYPED_DICT, IMPORT_TYPED_DICT_BACKPORT
from datamodel_code_generator.model.type_alias import TypeAliasBase, TypeStatement
from datamodel_code_generator.parser import DefaultPutDict, LiteralType
from datamodel_code_generator.parser._graph import stable_toposort
from datamodel_code_generator.parser._scc import find_circular_sccs, strongly_connected_components
from datamodel_code_generator.parser.generation import GenerationIndex, GenerationStore, set_model_base_classes
from datamodel_code_generator.parser.schema_version import SchemaFeaturesT
from datamodel_code_generator.reference import ModelResolver, ModelType, Reference
from datamodel_code_generator.types import ANY, DataType, DataTypeManager
from datamodel_code_generator.util import camel_to_snake

if TYPE_CHECKING:
    from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence

    from datamodel_code_generator._types import ParserConfigDict
    from datamodel_code_generator.config import ParserConfig
    from datamodel_code_generator.format import CodeFormatter
    from datamodel_code_generator.model_metadata import GeneratedModelMetadata, ModelFieldMetadata, ModelMetadata
ParserConfigT = TypeVar("ParserConfigT", bound="ParserConfig")

HashableComparable = _internal_utils.HashableComparable
to_hashable = _internal_utils.to_hashable

# Keep these as module-name checks so non-pydantic-v2 outputs do not import the
# pydantic_v2 generator package and its runtime feature gates.
_DATACLASS_MODULE: Final = "datamodel_code_generator.model.dataclass"
_MSGSPEC_MODULE: Final = "datamodel_code_generator.model.msgspec"
_PYDANTIC_V2_BASE_MODEL_MODULE: Final = "datamodel_code_generator.model.pydantic_v2.base_model"
_PYDANTIC_V2_DATACLASS_MODULE: Final = "datamodel_code_generator.model.pydantic_v2.dataclass"
_PYDANTIC_V2_MODULE: Final = "datamodel_code_generator.model.pydantic_v2"
_PYDANTIC_V2_ROOT_MODEL_MODULE: Final = "datamodel_code_generator.model.pydantic_v2.root_model"
_TYPED_DICT_MODULE: Final = "datamodel_code_generator.model.typed_dict"
_CLASS_NAME_SEPARATOR_PATTERN: Final = re.compile(r"[^A-Za-z0-9]+")


@cache
def _type_mro_contains_type(model_type: type[object], *, module: str, name: str) -> bool:
    return any(base.__module__ == module and base.__name__ == name for base in model_type.__mro__)


def _model_type(value: object | type[object]) -> type[object]:
    return value if isinstance(value, type) else value.__class__


def _is_pydantic_v2_base_model(value: object | type[object]) -> bool:
    return _type_mro_contains_type(_model_type(value), module=_PYDANTIC_V2_BASE_MODEL_MODULE, name="BaseModel")


def _is_dataclass_data_model(value: object | type[object]) -> bool:
    return _type_mro_contains_type(_model_type(value), module=_DATACLASS_MODULE, name="DataClass")


def _is_msgspec_struct(value: object | type[object]) -> bool:
    return _type_mro_contains_type(_model_type(value), module=_MSGSPEC_MODULE, name="Struct")


def _is_pydantic_v2_data_model_field(value: object) -> bool:
    return _type_mro_contains_type(_model_type(value), module=_PYDANTIC_V2_BASE_MODEL_MODULE, name="DataModelField")


def _is_pydantic_v2_dataclass(value: object | type[object]) -> bool:
    return _type_mro_contains_type(_model_type(value), module=_PYDANTIC_V2_DATACLASS_MODULE, name="DataClass")


def _get_pydantic_v2_root_model_type(model_type: type[DataModel]) -> type[DataModel] | None:
    if _type_mro_contains_type(model_type, module=_PYDANTIC_V2_ROOT_MODEL_MODULE, name="RootModel"):
        return model_type
    return None


def _is_pydantic_v2_root_model(model: DataModel, root_model_type: type[DataModel] | None) -> bool:
    return root_model_type is not None and isinstance(model, root_model_type)


def _is_pydantic_v2_dump_resolve_reference_action(value: object) -> bool:
    return (
        getattr(value, "__module__", None) == _PYDANTIC_V2_MODULE
        and getattr(value, "__name__", None) == "dump_resolve_reference_action"
    )


def _is_typed_dict_data_model(value: object | type[object]) -> bool:
    return _type_mro_contains_type(_model_type(value), module=_TYPED_DICT_MODULE, name="TypedDict")


def _add_msgspec_base_class_kwarg(model: DataModel, name: str, value: str) -> None:
    cast("Any", model).add_base_class_kwarg(name, value)


def __getattr__(name: str) -> Any:
    """Return compatibility model modules without importing them on parser load."""
    match name:
        case "dataclass_model":
            from datamodel_code_generator.model import dataclass as dataclass_model  # noqa: PLC0415

            return dataclass_model
        case "msgspec_model":
            from datamodel_code_generator.model import msgspec as msgspec_model  # noqa: PLC0415

            return msgspec_model
    raise AttributeError(name)


Child = _internal_utils.Child
T = _internal_utils.T
get_most_of_parent = _internal_utils.get_most_of_parent


ModelName: TypeAlias = str
ModelNames: TypeAlias = set[ModelName]
ModelDeps: TypeAlias = dict[ModelName, set[ModelName]]
OrderIndex: TypeAlias = dict[ModelName, int]
DiscriminatorValue: TypeAlias = str | int | bool

_BUILTIN_NAMES: frozenset[str] = frozenset(name for name in builtins.__dict__ if not name.startswith("_"))
_BUILTIN_NAMES_INTRODUCED_IN: dict[PythonVersion, frozenset[str]] = {
    PythonVersion.PY_311: frozenset({"BaseExceptionGroup", "ExceptionGroup"}),
    PythonVersion.PY_313: frozenset({"PythonFinalizationError"}),
}
_BUILTIN_CONTAINER_COLLISION_FLAGS: dict[str, str] = {
    "list": "is_list",
    "dict": "is_dict",
    "set": "is_set",
    "frozenset": "is_frozen_set",
    "tuple": "is_tuple",
}


def _get_builtin_names_for_target(target_python_version: PythonVersion) -> frozenset[str]:
    builtin_names = set(_BUILTIN_NAMES)
    target_key = target_python_version.version_key

    for introduced_version, names in _BUILTIN_NAMES_INTRODUCED_IN.items():
        if target_key >= introduced_version.version_key:
            builtin_names.update(names)
        else:
            builtin_names.difference_update(names)

    return frozenset(builtin_names)


def _is_builtin_type_collision(current_name: str, data_type: DataType) -> bool:
    if data_type.type == current_name and not data_type.import_:
        return True

    if flag := _BUILTIN_CONTAINER_COLLISION_FLAGS.get(current_name):
        return bool(getattr(data_type, flag))

    return False


ComponentId: TypeAlias = int
Components: TypeAlias = list[list[ModelName]]
ComponentOf: TypeAlias = dict[ModelName, ComponentId]
ComponentEdges: TypeAlias = dict[ComponentId, set[ComponentId]]

ClassNode: TypeAlias = tuple[ModelName, ...]
ClassGraph: TypeAlias = dict[ClassNode, set[ClassNode]]

ModulePath: TypeAlias = tuple[str, ...]
ModuleModels: TypeAlias = list[tuple[ModulePath, list[DataModel]]]
ForwarderMap: TypeAlias = dict[ModulePath, tuple[ModulePath, list[tuple[str, str]]]]


class ModuleContext(NamedTuple):
    """Context for processing a single module during code generation."""

    module: ModulePath
    module_key: ModulePath
    models: list[DataModel]
    is_init: bool
    imports: Imports
    scoped_model_resolver: ModelResolver


class ParseConfig(NamedTuple):
    """Configuration for the parse operation."""

    with_import: bool
    use_deferred_annotations: bool
    code_formatter: CodeFormatter | None
    module_split_mode: ModuleSplitMode | None
    all_exports_scope: AllExportsScope | None
    all_exports_collision_strategy: AllExportsCollisionStrategy | None


def _decode_json_pointer_part(value: str) -> str:
    return value.replace("~1", "/").replace("~0", "~")


def _source_path_from_reference_path(reference_path: str) -> list[str] | None:
    _, separator, fragment = reference_path.partition("#")
    return (
        None
        if not separator or (fragment and not fragment.startswith("/"))
        else []
        if not fragment
        else [_decode_json_pointer_part(part) for part in fragment[1:].split("/")]
    )


def _module_name_from_module_path(module: ModulePath) -> str:
    if module == ("__init__.py",):
        return ""

    parts = [*module]
    if parts and parts[-1].endswith(".py"):  # pragma: no branch - Module paths are Python files.
        parts[-1] = parts[-1][:-3]
    return ".".join(parts[:-1] if parts[-1:] == ["__init__"] else parts)


class _KeepModelOrderDeps(NamedTuple):
    strong: ModelDeps
    all: ModelDeps


class _KeepModelOrderComponents(NamedTuple):
    components: Components
    comp_of: ComponentOf


def _collect_keep_model_order_deps(
    model: DataModel,
    *,
    model_names: ModelNames,
    imported: ModelNames,
    pydantic_v2_root_model_type: type[DataModel] | None,
    use_deferred_annotations: bool,
) -> tuple[set[ModelName], set[ModelName]]:
    """Collect (strong_deps, all_deps) used by keep_model_order sorting.

    - strong_deps: base class references (within-module, non-imported)
    - all_deps: base class refs + (optionally) field refs (within-module, non-imported)
    """
    class_name = model.class_name
    base_class_refs = {b.reference.short_name for b in model.base_classes if b.reference}
    field_refs = {t.reference.short_name for f in model.fields for t in f.data_type.all_data_types if t.reference}

    if (
        use_deferred_annotations
        and not isinstance(model, TypeAliasBase)
        and not _is_pydantic_v2_root_model(model, pydantic_v2_root_model_type)
    ):
        field_refs = set()

    strong = {r for r in base_class_refs if r in model_names and r not in imported and r != class_name}
    deps = {r for r in (base_class_refs | field_refs) if r in model_names and r not in imported and r != class_name}
    return strong, deps


def _build_keep_model_order_dependency_maps(
    models: list[DataModel],
    *,
    model_names: ModelNames,
    imported: ModelNames,
    pydantic_v2_root_model_type: type[DataModel] | None,
    use_deferred_annotations: bool,
) -> _KeepModelOrderDeps:
    strong_deps: ModelDeps = {}
    all_deps: ModelDeps = {}
    for model in models:
        strong, deps = _collect_keep_model_order_deps(
            model,
            model_names=model_names,
            imported=imported,
            pydantic_v2_root_model_type=pydantic_v2_root_model_type,
            use_deferred_annotations=use_deferred_annotations,
        )
        strong_deps[model.class_name] = strong
        all_deps[model.class_name] = deps
    return _KeepModelOrderDeps(strong=strong_deps, all=all_deps)


def _build_keep_model_order_components(
    all_deps: ModelDeps,
    order_index: OrderIndex,
) -> _KeepModelOrderComponents:
    graph: ClassGraph = {(name,): {(dep,) for dep in deps} for name, deps in all_deps.items()}
    sccs = strongly_connected_components(graph)
    components: Components = [sorted((node[0] for node in scc), key=order_index.__getitem__) for scc in sccs]
    components.sort(key=lambda members: min(order_index[n] for n in members))
    comp_of: ComponentOf = {name: i for i, members in enumerate(components) for name in members}
    return _KeepModelOrderComponents(components=components, comp_of=comp_of)


def _build_keep_model_order_component_edges(
    all_deps: ModelDeps,
    comp_of: ComponentOf,
    num_components: int,
) -> ComponentEdges:
    comp_edges: ComponentEdges = {i: set() for i in range(num_components)}
    for name, deps in all_deps.items():
        name_comp = comp_of[name]
        for dep in deps:
            if (dep_comp := comp_of[dep]) != name_comp:
                comp_edges[dep_comp].add(name_comp)
    return comp_edges


def _build_keep_model_order_component_order(
    components: Components,
    comp_edges: ComponentEdges,
    order_index: OrderIndex,
) -> list[ComponentId]:
    comp_key = [min(order_index[n] for n in members) for members in components]
    return stable_toposort(
        list(range(len(components))),
        comp_edges,
        key=lambda component_id: comp_key[component_id],
    )


def _build_keep_model_ordered_names(
    ordered_comp_ids: list[ComponentId],
    components: Components,
    strong_deps: ModelDeps,
    order_index: OrderIndex,
) -> list[ModelName]:
    ordered_names: list[ModelName] = []
    for component_id in ordered_comp_ids:
        members = components[component_id]
        if len(members) > 1:
            strong_edges: dict[ModelName, set[ModelName]] = {n: set() for n in members}
            member_set = set(members)
            for base in members:
                derived_members = {member for member in members if base in strong_deps.get(member, set()) & member_set}
                strong_edges[base].update(derived_members)
            members = stable_toposort(members, strong_edges, key=order_index.__getitem__)
        ordered_names.extend(members)
    return ordered_names


def _reorder_models_keep_model_order(
    models: list[DataModel],
    imports: Imports,
    *,
    pydantic_v2_root_model_type: type[DataModel] | None,
    use_deferred_annotations: bool,
) -> None:
    """Reorder models deterministically based on their dependencies.

    Starts from class_name order and only moves models when required to satisfy dependencies.
    Cycles are kept as SCC groups; within each SCC, base-class dependencies are prioritized.
    """
    models.sort(key=lambda x: x.class_name)
    imported: ModelNames = {i for v in imports.values() for i in v}
    model_by_name = {m.class_name: m for m in models}
    model_names: ModelNames = set(model_by_name)
    order_index: OrderIndex = {m.class_name: i for i, m in enumerate(models)}

    deps = _build_keep_model_order_dependency_maps(
        models,
        model_names=model_names,
        imported=imported,
        pydantic_v2_root_model_type=pydantic_v2_root_model_type,
        use_deferred_annotations=use_deferred_annotations,
    )
    comps = _build_keep_model_order_components(deps.all, order_index)
    comp_edges = _build_keep_model_order_component_edges(deps.all, comps.comp_of, len(comps.components))
    ordered_comp_ids = _build_keep_model_order_component_order(comps.components, comp_edges, order_index)
    ordered_names = _build_keep_model_ordered_names(ordered_comp_ids, comps.components, deps.strong, order_index)
    models[:] = [model_by_name[name] for name in ordered_names]


def _sort_internal_module_models(
    models: list[DataModel],
    pydantic_v2_root_model_type: type[DataModel] | None,
) -> list[DataModel]:
    """Order models moved to _internal.py so runtime base classes are defined first."""
    model_paths = {model.path for model in models}
    order_index = {model.path: index for index, model in enumerate(models)}
    edges: dict[str, set[str]] = {model.path: set() for model in models}

    def add_dependency(model: DataModel, reference_path: str | None) -> None:
        if reference_path in model_paths and reference_path != model.path:
            edges[reference_path].add(model.path)

    for model in models:
        for base_class in model.base_classes:
            add_dependency(model, base_class.reference.path if base_class.reference else None)
        if _is_pydantic_v2_root_model(model, pydantic_v2_root_model_type):
            for field in model.fields:
                for data_type in field.data_type.all_data_types:
                    add_dependency(model, data_type.reference.path if data_type.reference else None)

    sorted_paths = stable_toposort(list(order_index), edges, key=order_index.__getitem__)
    model_by_path = {model.path: model for model in models}
    return [model_by_path[path] for path in sorted_paths]


SPECIAL_PATH_FORMAT: str = "#-datamodel-code-generator-#-{}-#-special-#"


def get_special_path(keyword: str, path: list[str]) -> list[str]:
    """Create a special path marker for internal reference tracking."""
    return [*path, SPECIAL_PATH_FORMAT.format(keyword)]


escape_characters = str.maketrans({
    "\u0000": r"\x00",  # Null byte
    "\\": r"\\",
    "'": r"\'",
    "\b": r"\b",
    "\f": r"\f",
    "\n": r"\n",
    "\r": r"\r",
    "\t": r"\t",
})


def dump_templates(templates: list[DataModel]) -> str:
    """Join model templates into a single code string."""
    return "\n\n\n".join(str(m) for m in templates)


def iter_models_field_data_types(
    models: Iterable[DataModel],
) -> Iterator[tuple[DataModel, DataModelFieldBase, DataType]]:
    """Yield (model, field, data_type) for all models, fields, and nested data types."""
    for model in models:
        for field in model.fields:
            for data_type in field.data_type.all_data_types:
                yield model, field, data_type


def _unwrap_type_alias(data_type: DataType) -> DataType:
    """Follow type alias references to the underlying data type."""
    current = data_type
    seen: set[int] = set()
    while current.reference and isinstance(current.reference.source, TypeAliasBase):
        source = current.reference.source
        if id(source) in seen or not source.fields:
            break
        seen.add(id(source))
        current = source.fields[0].data_type
    return current


def _contains_model_reference(data_type: DataType) -> bool:
    """Check if a data type tree contains any reference to a non-alias model."""
    stack = [data_type]
    seen: set[int] = set()
    while stack:
        resolved = _unwrap_type_alias(stack.pop())
        resolved_id = id(resolved)
        if resolved_id in seen:
            continue
        seen.add(resolved_id)
        if (
            resolved.reference
            and isinstance(resolved.reference.source, DataModel)
            and not isinstance(resolved.reference.source, Enum)
            and not resolved.reference.source.is_alias
        ):
            return True
        if resolved.dict_key:
            stack.append(resolved.dict_key)
        stack.extend(resolved.data_types)
    return False


def _needs_validate_default(data_type: DataType) -> bool:
    """Check if a field needs validate_default=True to coerce defaults into model instances."""
    resolved = _unwrap_type_alias(data_type)
    return _contains_model_reference(resolved)


def _alias_base_class_imports(
    model: DataModel,
    aliased_imports: dict[tuple[str | None, str], Import],
) -> None:
    """Apply aliased imports to a model's base classes and their _additional_imports."""
    for base_class in model.base_classes:
        if not base_class.import_:
            continue
        key = (base_class.import_.from_, base_class.import_.import_)
        if key not in aliased_imports:
            continue
        old_import = base_class.import_
        aliased_import = aliased_imports[key]
        base_class.type = aliased_import.alias
        base_class.import_ = aliased_import
        for i, additional_import in enumerate(model._additional_imports):  # pragma: no branch  # noqa: SLF001
            if (
                additional_import.from_ == old_import.from_ and additional_import.import_ == old_import.import_
            ):  # pragma: no branch
                model._additional_imports[i] = aliased_import  # noqa: SLF001
                break


def _clear_model_imports_cache(models: Iterable[DataModel]) -> None:
    """Clear per-model imports caches after import-affecting mutations."""
    # Parser post-processing rewrites fields, data types, and aliases in bulk.
    # Clear at the end of those rewrite steps so later imports are recomputed.
    for model in models:
        model.clear_imports_cache()


ReferenceMapSet = dict[str, set[str]]
SortedDataModels = dict[str, DataModel]

MAX_RECURSION_COUNT: int = sys.getrecursionlimit()


def add_model_path_to_list(
    paths: list[str] | None,
    model: DataModel,
    /,
) -> list[str]:
    """
    Auxiliary method which adds model path to list, provided the following hold.

    - model is not a type alias
    - path is not already in the list.

    """
    if paths is None:
        paths = []
    if model.is_alias:
        return paths
    if (path := model.path) in paths:
        return paths
    paths.append(path)
    return paths


def sort_data_models(  # noqa: PLR0912, PLR0913, PLR0914, PLR0915
    unsorted_data_models: list[DataModel],
    sorted_data_models: SortedDataModels | None = None,
    require_update_action_models: list[str] | None = None,
    recursion_count: int = MAX_RECURSION_COUNT,
    generation_index: GenerationIndex | None = None,
    *,
    pydantic_v2_root_model_type: type[DataModel] | None = None,
) -> tuple[list[DataModel], SortedDataModels, list[str]]:
    """Sort data models by dependency order for correct forward references."""
    if sorted_data_models is None:
        sorted_data_models = OrderedDict()

    if require_update_action_models is None:
        require_update_action_models = []
    require_update_action_model_paths = set(require_update_action_models)

    def add_require_update_action_model(model: DataModel) -> None:
        if model.is_alias:
            return
        path = model.path
        if path in require_update_action_model_paths:
            return
        require_update_action_models.append(path)
        require_update_action_model_paths.add(path)

    def get_reference_classes(model: DataModel) -> frozenset[str]:
        if generation_index is None:
            return model.reference_classes
        return generation_index.reference_classes_for_model(model)

    sorted_model_count: int = len(sorted_data_models)
    sorted_model_paths = set(sorted_data_models)

    unresolved_references: list[DataModel] = []
    for model in unsorted_data_models:
        reference_classes = get_reference_classes(model)
        if not reference_classes:
            sorted_data_models[model.path] = model
            sorted_model_paths.add(model.path)
        elif model.path in reference_classes and len(reference_classes) == 1:  # only self-referencing
            sorted_data_models[model.path] = model
            sorted_model_paths.add(model.path)
            add_require_update_action_model(model)
        elif not reference_classes - {model.path} - sorted_model_paths:  # reference classes have been resolved
            sorted_data_models[model.path] = model
            sorted_model_paths.add(model.path)
            if model.path in reference_classes:
                add_require_update_action_model(model)
        else:
            unresolved_references.append(model)

    if unresolved_references:
        if sorted_model_count != len(sorted_data_models) and recursion_count:
            try:
                return sort_data_models(
                    unresolved_references,
                    sorted_data_models,
                    require_update_action_models,
                    recursion_count - 1,
                    generation_index,
                    pydantic_v2_root_model_type=pydantic_v2_root_model_type,
                )
            except RecursionError:  # pragma: no cover
                pass

        # sort on base_class dependency
        seen_orderings: set[tuple[str, ...]] = set()
        while True:
            ordered_models: list[tuple[int, DataModel]] = []
            # Build lookup dict for O(1) index access instead of O(n) list.index()
            path_to_index = {m.path: idx for idx, m in enumerate(unresolved_references)}
            for model in unresolved_references:
                if _is_pydantic_v2_root_model(model, pydantic_v2_root_model_type):
                    indexes = [
                        path_to_index[ref_path]
                        for f in model.fields
                        for t in f.data_type.all_data_types
                        if t.reference and (ref_path := t.reference.path) in path_to_index
                    ]
                else:
                    indexes = [
                        path_to_index[b.reference.path]
                        for b in model.base_classes
                        if b.reference and b.reference.path in path_to_index
                    ]

                if indexes:
                    ordered_models.append((
                        max(indexes),
                        model,
                    ))
                else:
                    ordered_models.append((
                        -1,
                        model,
                    ))

            sorted_unresolved_models = [m[1] for m in sorted(ordered_models, key=operator.itemgetter(0))]
            if sorted_unresolved_models == unresolved_references:
                break

            sig = tuple(m.path for m in sorted_unresolved_models)
            if sig in seen_orderings:
                # Base-class dependency order has no fixed point (e.g. cyclic inheritance with
                # discriminators). Further iterations only permute the list; use stable order.
                unresolved_references.sort(key=lambda m: m.path)
                break

            seen_orderings.add(sig)
            unresolved_references = sorted_unresolved_models

        # circular reference
        unsorted_data_model_names = set(path_to_index.keys())
        for model in unresolved_references:
            reference_classes = get_reference_classes(model)
            unresolved_model = reference_classes - {model.path} - sorted_model_paths
            base_models = [getattr(s.reference, "path", None) for s in model.base_classes]
            update_action_parent = require_update_action_model_paths.intersection(base_models)
            if not unresolved_model:
                sorted_data_models[model.path] = model
                sorted_model_paths.add(model.path)
                if update_action_parent:
                    add_require_update_action_model(model)
                continue

            if not unresolved_model - unsorted_data_model_names:
                sorted_data_models[model.path] = model
                sorted_model_paths.add(model.path)
                add_require_update_action_model(model)
                continue

            # unresolved
            unresolved_classes = ", ".join(
                f"[class: {item.path} references: {get_reference_classes(item)}]" for item in unresolved_references
            )
            msg = f"A Parser can not resolve classes: {unresolved_classes}."
            raise Exception(msg)  # noqa: TRY002

    return unresolved_references, sorted_data_models, require_update_action_models


def sort_base_classes_for_mro(
    sorted_data_models: SortedDataModels,
    generation_store: GenerationStore | None = None,
) -> None:
    """Sort base classes in each model to ensure valid Python MRO.

    When a class inherits from multiple base classes where some bases inherit
    from others, Python's C3 linearization requires that child classes appear
    before their parent classes in the inheritance list.

    For example, if B inherits from A, then class C(A, B) is invalid but
    class C(B, A) is valid.
    """
    for model in sorted_data_models.values():
        base_classes = model.base_classes
        if len(base_classes) <= 1:
            continue

        # Build set of base class paths for quick lookup
        base_class_paths = {b.reference.path for b in base_classes if b.reference}

        def get_ancestors(
            ref_path: str,
            base_class_paths: set[str] = base_class_paths,
        ) -> set[str]:
            """Get all ancestor paths that are in our base class list."""
            ancestors: set[str] = set()
            source_model = sorted_data_models.get(ref_path)
            if source_model is None:  # pragma: no cover
                return ancestors
            to_visit = [
                bc.reference.path
                for bc in source_model.base_classes
                if bc.reference and bc.reference.path in base_class_paths
            ]
            while to_visit:
                parent_path = to_visit.pop()
                if parent_path in ancestors:
                    continue
                ancestors.add(parent_path)
                parent_model = sorted_data_models.get(parent_path)
                if not parent_model:  # pragma: no cover
                    continue
                to_visit.extend(
                    bc.reference.path
                    for bc in parent_model.base_classes
                    if bc.reference and bc.reference.path in base_class_paths
                )
            return ancestors

        # Build ancestor map for each base class
        ancestor_map = {b.reference.path: get_ancestors(b.reference.path) for b in base_classes if b.reference}

        def sort_key(
            bc: BaseClassDataType,
            ancestor_map: dict[str, set[str]] = ancestor_map,
        ) -> int:
            """Sort key: classes that are ancestors of others come later."""
            if not bc.reference:
                return 0
            path = bc.reference.path
            # Count how many other base classes have this one as an ancestor
            return sum(1 for other_path in ancestor_map if path in ancestor_map.get(other_path, set()))

        # Use stable sort to preserve original order for elements with equal keys
        sorted_base_classes = sorted(base_classes, key=sort_key)
        if all(
            sorted_base_class is base_class
            for sorted_base_class, base_class in zip(sorted_base_classes, base_classes, strict=True)
        ):
            continue
        set_model_base_classes(model, sorted_base_classes, generation_store)


def relative(
    current_module: str,
    reference: str,
    *,
    reference_is_module: bool = False,
    current_is_init: bool = False,
) -> tuple[str, str]:
    """Find relative module path.

    Args:
        current_module: Current module path (e.g., "foo.bar")
        reference: Reference path (e.g., "foo.baz.ClassName" or "foo.baz" if reference_is_module)
        reference_is_module: If True, treat reference as a module path (not module.class)
        current_is_init: If True, treat current_module as a package __init__.py (adds depth)

    Returns:
        Tuple of (from_path, import_name) for constructing import statements
    """
    if current_is_init:
        current_module_path = [*current_module.split("."), "__init__"] if current_module else ["__init__"]
    else:
        current_module_path = current_module.split(".") if current_module else []

    if reference_is_module:
        reference_path = reference.split(".") if reference else []
        name = reference_path[-1] if reference_path else ""
    else:
        *reference_path, name = reference.split(".")

    if current_module_path == reference_path:
        return "", ""

    i = 0
    for x, y in zip(current_module_path, reference_path, strict=False):
        if x != y:
            break
        i += 1

    left = "." * (len(current_module_path) - i)
    right = ".".join(reference_path[i:])

    if not left:
        left = "."
    if not right:
        right = name
    elif "." in right:
        extra, right = right.rsplit(".", 1)
        left += extra

    return left, right


def is_ancestor_package_reference(current_module: str, reference: str) -> bool:
    """Check if reference is in an ancestor package (__init__.py).

    When the reference's module path is an ancestor (prefix) of the current module,
    the reference is in an ancestor package's __init__.py file.

    Args:
        current_module: The current module path (e.g., "v0.mammal.canine")
        reference: The full reference path (e.g., "v0.Animal")

    Returns:
        True if the reference is in an ancestor package, False otherwise.

    Examples:
        - current="v0.animal", ref="v0.Animal" -> True (immediate parent)
        - current="v0.mammal.canine", ref="v0.Animal" -> True (grandparent)
        - current="v0.animal", ref="v0.animal.Dog" -> False (same or child)
        - current="pets", ref="Animal" -> True (root package is immediate parent)
    """
    current_path = current_module.split(".") if current_module else []
    *reference_path, _ = reference.split(".")

    if not current_path:
        return False

    # Case 1: Direct parent package (includes root package when reference_path is empty)
    # e.g., current="pets", ref="Animal" -> current_path[:-1]=[] == reference_path=[]
    if current_path[:-1] == reference_path:
        return True

    # Case 2: Deeper ancestor package (reference_path must be non-empty proper prefix)
    # e.g., current="v0.mammal.canine", ref="v0.Animal" -> ["v0"] is prefix of ["v0","mammal","canine"]
    return (
        len(reference_path) > 0
        and len(reference_path) < len(current_path)
        and current_path[: len(reference_path)] == reference_path
    )


def exact_import(from_: str, import_: str, short_name: str) -> tuple[str, str]:
    """Create exact import path to avoid relative import issues."""
    if from_ == len(from_) * ".":
        # Prevents "from . import foo" becoming "from ..foo import Foo"
        # or "from .. import foo" becoming "from ...foo import Foo"
        # when our imported module has the same parent
        return f"{from_}{import_}", short_name
    return f"{from_}.{import_}", short_name


def get_module_directory(module: tuple[str, ...]) -> tuple[str, ...]:
    """Get the directory portion of a module tuple.

    Note: Module tuples in module_models do NOT include .py extension.
    The last element is either the module name (e.g., "issuing") or empty for root.

    Examples:
        ("pkg",) -> ("pkg",) - root module
        ("pkg", "issuing") -> ("pkg",) - submodule
        ("foo", "bar", "baz") -> ("foo", "bar") - deeply nested module
    """
    if not module:
        return ()
    if len(module) == 1:
        return module
    return module[:-1]


def title_to_class_name(title: str) -> str:
    """Convert a schema title to a valid Python class name."""
    classname = _CLASS_NAME_SEPARATOR_PATTERN.sub(" ", title)
    return "".join(x for x in classname.title() if not x.isspace())


def _find_base_classes(model: DataModel) -> list[DataModel]:
    """Get direct base class DataModels."""
    return [b.reference.source for b in model.base_classes if b.reference and isinstance(b.reference.source, DataModel)]


def _find_field(original_name: str, models: list[DataModel]) -> DataModelFieldBase | None:
    """Find a field by original_name in the models and their base classes."""
    for model in models:
        for field in model.iter_all_fields():
            if field.original_name == original_name:
                return field
    return None


def _copy_data_types(data_types: list[DataType]) -> list[DataType]:
    """Deep copy a list of DataType objects, preserving references."""
    copied_data_types: list[DataType] = []
    for data_type_ in data_types:
        if data_type_.reference:
            copied_data_types.append(data_type_.__class__(reference=data_type_.reference))
        elif data_type_.data_types:
            copied_data_type = data_type_.model_copy()
            copied_data_type.data_types = _copy_data_types(data_type_.data_types)
            copied_data_types.append(copied_data_type)
        else:
            copied_data_types.append(data_type_.model_copy())
    return copied_data_types


class Result(BaseModel):
    """Generated code result with optional source file reference."""

    model_config = ConfigDict(defer_build=True)

    body: str
    future_imports: str = ""
    source: Optional[Path] = None  # noqa: UP045


class Source(BaseModel):
    """Schema source file with path and content."""

    model_config = ConfigDict(defer_build=True)

    path: Path
    text: str = ""
    raw_data: Any | None = None

    @classmethod
    def from_path(
        cls,
        path: Path,
        base_path: Path,
        encoding: str,
    ) -> Source:
        """Create a Source from a file path relative to base_path."""
        return cls(
            path=path.relative_to(base_path),
            text=path.read_text(encoding=encoding),
        )

    @classmethod
    def from_cached_path(cls, path: Path, base_path: Path, encoding: str, *, keep_text: bool = False) -> Source:
        """Create a Source from a cached parsed file path relative to base_path."""
        data, raw_data = _read_parser_source_data_from_path(path, encoding)
        return cls(
            path=path.relative_to(base_path),
            text=data.decode(encoding) if keep_text else "",
            raw_data=raw_data,
        )

    @classmethod
    def from_dict(cls, data: dict[str, YamlValue]) -> Source:
        """Create a Source from a dict."""
        return cls(path=Path(), raw_data=data)


def _is_any_variant(data_type: DataType) -> bool:
    return data_type.type == ANY or (
        not data_type.reference and not data_type.data_types and not data_type.literals and not data_type.type
    )


_DedupItem = TypeVar("_DedupItem")


def _iter_first_seen_duplicates(
    items: Iterable[_DedupItem],
    key_fn: Callable[[_DedupItem], tuple[HashableComparable, ...]],
) -> Iterator[tuple[_DedupItem, _DedupItem]]:
    seen: dict[tuple[HashableComparable, ...], _DedupItem] = {}
    for item in items:
        key = key_fn(item)
        if key in seen:
            yield seen[key], item
            continue
        seen[key] = item


def _check_discriminator_mapping_paths(
    model: DataModel | Reference,
    mapping: dict[str, str],
    discriminator_values: list[DiscriminatorValue],
) -> None:
    for name, path in mapping.items():
        if (model.path.split("#/")[-1] != path.split("#/")[-1]) and (
            path.startswith("#/") or model.path[:-1] != path.split("/")[-1]
        ):
            t_path = path[str(path).find("/") + 1 :]
            t_disc = model.path[: str(model.path).find("#")].lstrip("../")  # noqa: B005
            t_disc_2 = "/".join(t_disc.split("/")[1:])
            if t_path not in {t_disc, t_disc_2}:  # pragma: no branch
                continue
        discriminator_values.append(name)


def _get_discriminator_field_value(discriminator_field: DataModelFieldBase) -> DiscriminatorValue | None:
    const_value = discriminator_field.extras.get("const")
    if const_value is not None:
        return const_value

    literals = discriminator_field.data_type.literals
    if len(literals) == 1:
        return literals[0]

    enum_source = discriminator_field.data_type.find_source(Enum)
    if enum_source and len(enum_source.fields) == 1:
        raw_default = enum_source.fields[0].default
        if isinstance(raw_default, str):
            return raw_default.strip("'\"")
        return raw_default
    return None


def _get_enum_from_base(discriminator_model: DataModel, field_name: str) -> Enum | None:
    for base_class in discriminator_model.base_classes:
        if not base_class.reference or not base_class.reference.source:  # pragma: no cover
            continue
        base_model = base_class.reference.source
        if not (
            _is_dataclass_data_model(base_model)
            or _is_msgspec_struct(base_model)
            or _is_pydantic_v2_base_model(base_model)
        ):  # pragma: no cover
            continue
        base_data_model = cast("DataModel", base_model)
        for base_field in base_data_model.fields:  # pragma: no branch
            if field_name not in {base_field.original_name, base_field.name}:  # pragma: no cover
                continue
            if enum_from_base := base_field.data_type.find_source(Enum):  # pragma: no branch
                return enum_from_base
    return None


def _get_model_module_name(model: DataModel, model_path_to_module_name: Mapping[str, str]) -> str:
    return model_path_to_module_name.get(model.path, model.module_name)


def _get_data_type_target_full_name(
    data_type: DataType,
    reference: Reference,
    model_path_to_module_name: Mapping[str, str],
) -> str:
    if (ref_module_name := model_path_to_module_name.get(reference.path)) is None:
        ref_module_name = data_type.full_name.rsplit(".", 1)[0] if "." in data_type.full_name else ""
    return f"{ref_module_name}.{reference.short_name}" if ref_module_name else reference.short_name


def _register_data_type_import(
    data_type: DataType,
    model: DataModel,
    imports: Imports,
    scoped_model_resolver: ModelResolver,
    model_path_to_module_name: dict[str, str] | None = None,
) -> None:
    match data_type.reference:
        case None:
            return
        case reference:
            pass

    model_path_to_module_name = model_path_to_module_name or {}
    from_, import_ = full_path = relative(
        _get_model_module_name(model, model_path_to_module_name),
        _get_data_type_target_full_name(data_type, reference, model_path_to_module_name),
    )
    if imports.use_exact:
        from_, import_ = full_path = exact_import(from_, import_, reference.short_name)
    if not (from_ and import_):
        return

    alias = scoped_model_resolver.add(full_path, import_)
    data_type.alias = alias.name if reference.short_name == import_ else f"{alias.name}.{reference.short_name}"
    imports.append([
        Import(
            from_=from_,
            import_=import_,
            alias=alias.name,
            reference_path=reference.path,
        )
    ])


def _resolve_module_file(module_: ModulePath, results: dict[ModulePath, Result]) -> tuple[ModulePath, bool]:
    is_init = False

    if module_:
        if len(module_) == 1:
            parent: ModulePath = ("__init__.py",)
            if parent not in results:
                results[parent] = Result(body="")
        else:
            for i in range(1, len(module_)):
                parent = (*module_[:i], "__init__.py")
                if parent not in results:
                    results[parent] = Result(body="")
        if (*module_, "__init__.py") in results:
            return (*module_, "__init__.py"), True
        return tuple(part.replace("-", "_") for part in (*module_[:-1], f"{module_[-1]}.py")), is_init

    return ("__init__.py",), is_init


def _format_body_safe(body: str, code_formatter: CodeFormatter) -> str:
    try:
        return code_formatter.format_code(body)
    except Exception as exc:  # noqa: BLE001
        warn(
            f"Failed to format code: {exc!r}. Emitting unformatted output.",
            stacklevel=1,
        )
        return body


class Parser(ABC, Generic[ParserConfigT, SchemaFeaturesT]):
    """Abstract base class for schema parsers.

    Provides the parsing algorithm and code generation. Subclasses implement
    parse_raw() to handle specific schema formats.

    Type Parameters:
        ParserConfigT: The configuration type for this parser.
        SchemaFeaturesT: The schema features type (JsonSchemaFeatures or subclass).
    """

    @property
    @abstractmethod
    def schema_features(self) -> SchemaFeaturesT:
        """Get schema features based on detected version.

        Returns:
            Schema features instance with version-specific flags.
        """
        ...

    _config_class_name: ClassVar[str] = "ParserConfig"
    _cache_local_sources_during_parse: ClassVar[bool] = False
    _cache_parsed_sources_from_path: ClassVar[bool] = False

    @classmethod
    def _get_config_class(cls) -> type[ParserConfig]:
        """Return the config class for this parser.

        Uses _config_class_name class variable to dynamically import the config class.
        Subclasses should set _config_class_name to their config class name.
        """
        import importlib  # noqa: PLC0415

        module = importlib.import_module("datamodel_code_generator.config")
        return getattr(module, cls._config_class_name)

    @classmethod
    def _create_default_config(cls, options: ParserConfigDict) -> ParserConfigT:
        """Create a default config from options.

        Uses _get_config_class() to determine which config class to instantiate.
        """
        config_class = cls._get_config_class()

        config_class.model_rebuild(
            _types_namespace={
                "StrictTypes": StrictTypes,
                "DataModel": DataModel,
                "DataModelFieldBase": DataModelFieldBase,
                "DataTypeManager": DataTypeManager,
            }
        )
        return config_class.model_validate(options)  # ty: ignore[invalid-return-type]

    def _create_data_model(self, model_type: type[DataModel] | None = None, **kwargs: Any) -> DataModel:
        """Create data model instance with dataclass_arguments support for DataClass."""
        # Add class decorators if not already provided
        if "decorators" not in kwargs and self.class_decorators:
            kwargs["decorators"] = list(self.class_decorators)
        data_model_class = model_type or self.data_model_type
        if _is_dataclass_data_model(data_model_class) or _is_pydantic_v2_dataclass(data_model_class):
            # Use dataclass_arguments from kwargs, or fall back to self.dataclass_arguments
            # If both are None, construct from legacy frozen_dataclasses/keyword_only flags
            dataclass_arguments = kwargs.pop("dataclass_arguments", None)
            if dataclass_arguments is None:
                dataclass_arguments = self.dataclass_arguments
            if dataclass_arguments is None:
                # Construct from legacy flags for library API compatibility
                dataclass_arguments = {}
                if self.frozen_dataclasses:
                    dataclass_arguments["frozen"] = True
                if self.keyword_only:
                    dataclass_arguments["kw_only"] = True
            kwargs["dataclass_arguments"] = dataclass_arguments
            kwargs.pop("frozen", None)
            kwargs.pop("keyword_only", None)
        else:
            kwargs.pop("dataclass_arguments", None)
        return data_model_class(**kwargs)

    def __init__(  # noqa: PLR0912, PLR0915
        self,
        source: str | Path | list[Path] | ParseResult | dict[str, YamlValue],
        *,
        config: ParserConfigT | None = None,
        **options: Unpack[ParserConfigDict],
    ) -> None:
        """Initialize the Parser with configuration options.

        Args:
            source: The schema source to parse.
            config: Optional ParserConfig object with all configuration options.
            **options: Individual configuration options (alternative to config).

        Raises:
            ValueError: If both config and **options are provided.
        """
        if config is not None and options:
            msg = "Cannot specify both 'config' and keyword arguments. Use one or the other."
            raise ValueError(msg)

        if config is None:
            config = self._create_default_config(options)  # ty: ignore[invalid-argument-type]

        self.config = config

        self.keyword_only = config.keyword_only
        self.target_pydantic_version = config.target_pydantic_version
        self.frozen_dataclasses = config.frozen_dataclasses
        self.data_type_manager: DataTypeManager = config.data_type_manager_type(
            python_version=config.target_python_version,
            use_standard_collections=config.use_standard_collections,
            use_generic_container_types=config.use_generic_container_types,
            use_non_positive_negative_number_constrained_types=config.use_non_positive_negative_number_constrained_types,
            use_decimal_for_multiple_of=config.use_decimal_for_multiple_of,
            strict_types=config.strict_types,
            use_union_operator=config.use_union_operator,
            use_pendulum=config.use_pendulum,
            use_standard_primitive_types=config.use_standard_primitive_types,
            use_object_type=config.use_object_type,
            target_datetime_class=config.target_datetime_class,
            target_date_class=config.target_date_class,
            treat_dot_as_module=config.treat_dot_as_module or False,
            use_serialize_as_any=config.use_serialize_as_any,
        )
        self.data_model_type: type[DataModel] = config.data_model_type
        self.data_model_root_type: type[DataModel] = config.data_model_root_type
        self.pydantic_v2_root_model_type: type[DataModel] | None = _get_pydantic_v2_root_model_type(
            self.data_model_root_type
        )
        self.data_model_field_type: type[DataModelFieldBase] = config.data_model_field_type

        self.imports: Imports = Imports(config.use_exact_imports)
        self.use_exact_imports: bool = config.use_exact_imports
        self.use_type_checking_imports: bool | None = config.use_type_checking_imports
        self._append_additional_imports(additional_imports=config.additional_imports)
        self.class_decorators: list[str] = config.class_decorators or []

        self.base_class: str | None = config.base_class
        self.base_class_map: dict[str, str | list[str]] | None = config.base_class_map
        self.target_python_version: PythonVersion = config.target_python_version
        self.builtin_names: frozenset[str] = _get_builtin_names_for_target(self.target_python_version)
        self.generation_store, self.results = GenerationStore.create_with_results()
        self.model_metadata: ModelMetadata | None = None
        self.dump_resolve_reference_action: Callable[[Iterable[str]], str] | None = config.dump_resolve_reference_action
        self.validation: bool = config.validation
        self.field_constraints: bool = config.field_constraints
        self.snake_case_field: bool = config.snake_case_field
        self.strip_default_none: bool = config.strip_default_none
        self.serialization_aliases: Mapping[str, str] = config.serialization_aliases or {}
        self.apply_default_values_for_required_fields: bool = config.apply_default_values_for_required_fields
        self.force_optional_for_required_fields: bool = config.force_optional_for_required_fields
        self.use_schema_description: bool = config.use_schema_description
        self.use_field_description: bool = config.use_field_description
        self.use_field_description_example: bool = config.use_field_description_example
        self.use_inline_field_description: bool = config.use_inline_field_description
        self.use_single_line_docstring: bool = config.use_single_line_docstring
        self.use_default_kwarg: bool = config.use_default_kwarg
        self.use_missing_sentinel: bool = config.use_missing_sentinel
        self._data_model_field_common_kwargs_cache: dict[str, Any] = {"use_missing_sentinel": self.use_missing_sentinel}
        self.reuse_model: bool = config.reuse_model
        self.reuse_scope: ReuseScope | None = config.reuse_scope
        self.shared_module_name: str = config.shared_module_name
        self.encoding: str = config.encoding
        self.enum_field_as_literal: LiteralType | None = config.enum_field_as_literal
        self.enum_field_as_literal_map: dict[str, str] = config.enum_field_as_literal_map or {}
        self.ignore_enum_constraints: bool = config.ignore_enum_constraints
        self.set_default_enum_member: bool = config.set_default_enum_member
        self.use_subclass_enum: bool = config.use_subclass_enum
        self.use_specialized_enum: bool = config.use_specialized_enum
        self.strict_nullable: bool = config.strict_nullable
        self.use_generic_container_types: bool = config.use_generic_container_types
        self.use_union_operator: bool = config.use_union_operator
        self.enable_faux_immutability: bool = config.enable_faux_immutability
        self.custom_class_name_generator: Callable[[str], str] | None = config.custom_class_name_generator
        self.field_extra_keys: set[str] = config.field_extra_keys or set()
        self.field_extra_keys_without_x_prefix: set[str] = config.field_extra_keys_without_x_prefix or set()
        self.model_extra_keys: set[str] = config.model_extra_keys or set()
        self.model_extra_keys_without_x_prefix: set[str] = config.model_extra_keys_without_x_prefix or set()
        self.field_include_all_keys: bool = config.field_include_all_keys

        self.remote_text_cache: DefaultPutDict[str, str] = config.remote_text_cache or DefaultPutDict()
        self.current_source_path: Path | None = None
        self.use_title_as_name: bool = config.use_title_as_name
        self.infer_union_variant_names: bool = config.infer_union_variant_names
        self.use_operation_id_as_name: bool = config.use_operation_id_as_name
        self.use_unique_items_as_set: bool = config.use_unique_items_as_set
        self.use_tuple_for_fixed_items: bool = config.use_tuple_for_fixed_items
        self.use_closed_typed_dict: bool = config.use_closed_typed_dict
        self.allof_merge_mode: AllOfMergeMode = config.allof_merge_mode
        self.allof_class_hierarchy: AllOfClassHierarchy = config.allof_class_hierarchy
        self.dataclass_arguments = config.dataclass_arguments

        if config.base_path:
            self.base_path = config.base_path
        elif isinstance(source, Path):
            self.base_path = source.absolute() if source.is_dir() else source.absolute().parent
        else:
            self.base_path = Path.cwd()

        self.source: str | Path | list[Path] | ParseResult | dict[str, YamlValue] = source
        self._cache_local_sources = False
        self._local_source_cache: tuple[Source, ...] | None = None
        self._use_parsed_source_cache = (
            _is_parsed_source_cache_enabled()
            and self._cache_parsed_sources_from_path
            and isinstance(source, Path | list)
        )
        self.custom_template_dir = config.custom_template_dir
        self.extra_template_data: defaultdict[str, Any] = config.extra_template_data or defaultdict(dict)
        self.validators = config.validators
        self.generate_schema_validators: bool = config.generate_schema_validators
        if typed_extra_plain_annotation_key := self.data_model_type.TYPED_EXTRA_PLAIN_ANNOTATION_TEMPLATE_DATA_KEY:
            self.extra_template_data.setdefault(ALL_MODEL, {})[typed_extra_plain_annotation_key] = (
                config.target_python_version.has_native_deferred_annotations
            )

        if self.validators:
            for model_name, model_config in self.validators.items():
                self.extra_template_data[model_name]["validators"] = [
                    v.model_dump(mode="json") for v in model_config.validators
                ]

        self.use_generic_base_class: bool = config.use_generic_base_class
        self.generic_base_class_config: dict[str, Any] = {}

        if config.allow_population_by_field_name:
            if config.use_generic_base_class:
                self.generic_base_class_config["allow_population_by_field_name"] = True
            else:
                self.extra_template_data[ALL_MODEL]["allow_population_by_field_name"] = True

        if config.alias_generator:
            if config.use_generic_base_class:
                self.generic_base_class_config["allow_population_by_field_name"] = True
                self.generic_base_class_config["alias_generator"] = config.alias_generator
                self.extra_template_data[ALL_MODEL]["_alias_generator"] = config.alias_generator
            else:
                self.extra_template_data[ALL_MODEL]["allow_population_by_field_name"] = True
                self.extra_template_data[ALL_MODEL]["alias_generator"] = config.alias_generator

        if config.no_alias:
            self.extra_template_data[ALL_MODEL]["_no_alias"] = True

        if config.allow_extra_fields:
            if config.use_generic_base_class:
                self.generic_base_class_config["allow_extra_fields"] = True
            else:
                self.extra_template_data[ALL_MODEL]["allow_extra_fields"] = True

        if config.extra_fields:
            if config.use_generic_base_class:
                self.generic_base_class_config["extra_fields"] = config.extra_fields
            else:
                self.extra_template_data[ALL_MODEL]["extra_fields"] = config.extra_fields

        if config.enable_faux_immutability:
            if config.use_generic_base_class:
                self.generic_base_class_config["allow_mutation"] = False
            else:
                self.extra_template_data[ALL_MODEL]["allow_mutation"] = False

        if config.use_attribute_docstrings:
            if config.use_generic_base_class:
                self.generic_base_class_config["use_attribute_docstrings"] = True
            else:
                self.extra_template_data[ALL_MODEL]["use_attribute_docstrings"] = True
        if config.use_single_line_docstring:
            self.extra_template_data[ALL_MODEL]["use_single_line_docstring"] = True

        if config.target_pydantic_version:
            if config.use_generic_base_class:
                self.generic_base_class_config["target_pydantic_version"] = config.target_pydantic_version
            else:
                self.extra_template_data[ALL_MODEL]["target_pydantic_version"] = config.target_pydantic_version
        if config.schema_validator_base_class_name:
            self.extra_template_data[ALL_MODEL]["schema_validator_base_class_name"] = (
                config.schema_validator_base_class_name
            )
        if config.generate_schema_validators:
            self.extra_template_data[ALL_MODEL]["schema_runtime_validation_enabled"] = True

        self.model_resolver = ModelResolver(
            base_url=source.geturl() if isinstance(source, ParseResult) else None,
            singular_name_suffix="" if config.disable_appending_item_suffix else None,
            aliases=config.aliases,
            model_name_map=config.model_name_map,
            empty_field_name=config.empty_enum_field_name,
            snake_case_field=config.snake_case_field,
            custom_class_name_generator=config.custom_class_name_generator,
            base_path=self.base_path,
            original_field_name_delimiter=config.original_field_name_delimiter,
            special_field_name_prefix=config.special_field_name_prefix,
            remove_special_field_name_prefix=config.remove_special_field_name_prefix,
            capitalise_enum_members=config.capitalise_enum_members,
            no_alias=config.no_alias,
            use_subclass_enum=config.use_subclass_enum,
            target_python_version=config.target_python_version,
            parent_scoped_naming=config.parent_scoped_naming,
            treat_dot_as_module=config.treat_dot_as_module,
            naming_strategy=config.naming_strategy,
            duplicate_name_suffix_map=config.duplicate_name_suffix,
            class_name_prefix=config.class_name_prefix,
            class_name_suffix=config.class_name_suffix,
            class_name_affix_scope=config.class_name_affix_scope,
            skip_affix_for_root=config.class_name is not None,
            default_value_overrides=config.default_value_overrides,
        )
        self.class_name: str | None = config.class_name
        self.allow_leading_underscore_class_name: bool = config.allow_leading_underscore_class_name
        self.wrap_string_literal: bool | None = config.wrap_string_literal
        self.allow_remote_refs: bool | None = config.allow_remote_refs
        self.allow_private_network: bool = config.allow_private_network
        self.http_headers: Sequence[tuple[str, str]] | None = config.http_headers
        self.http_local_ref_path: Path | None = config.http_local_ref_path
        self.http_query_parameters: Sequence[tuple[str, str]] | None = config.http_query_parameters
        self.http_ignore_tls: bool = config.http_ignore_tls
        self.http_timeout: float | None = config.http_timeout
        self.use_annotated: bool = config.use_annotated
        if self.use_annotated and not self.field_constraints:  # pragma: no cover
            msg = "`use_annotated=True` has to be used with `field_constraints=True`"
            raise Exception(msg)  # noqa: TRY002
        self.use_serialize_as_any: bool = config.use_serialize_as_any
        self.use_non_positive_negative_number_constrained_types = (
            config.use_non_positive_negative_number_constrained_types
        )
        self.use_double_quotes = config.use_double_quotes
        self.allow_responses_without_content = config.allow_responses_without_content
        self.collapse_root_models = config.collapse_root_models
        self.collapse_root_models_name_strategy = config.collapse_root_models_name_strategy
        self.collapse_reuse_models = config.collapse_reuse_models
        self.skip_root_model = config.skip_root_model
        self.use_root_model_sequence_interface = config.use_root_model_sequence_interface
        self.use_type_alias = config.use_type_alias
        self.capitalise_enum_members = config.capitalise_enum_members
        self.keep_model_order = config.keep_model_order
        self.use_one_literal_as_default = config.use_one_literal_as_default
        self.use_enum_values_in_discriminator = config.use_enum_values_in_discriminator
        self.known_third_party = config.known_third_party
        self.custom_formatter = config.custom_formatters
        self.custom_formatters_kwargs = config.custom_formatters_kwargs
        self.treat_dot_as_module = config.treat_dot_as_module
        self.default_field_extras: dict[str, Any] | None = config.default_field_extras
        self.formatters: list[Formatter] | None = config.formatters
        self.builtin_format_line_length: int | None = config.builtin_format_line_length
        self.defer_formatting: bool = config.defer_formatting
        self.type_mappings: dict[tuple[str, str], str] = Parser._parse_type_mappings(config.type_mappings)
        self.type_overrides: dict[str, str] = config.type_overrides or {}
        self._type_override_imports: dict[str, Import] = {
            key: Import.from_full_path(value) for key, value in self.type_overrides.items()
        }
        self._model_type_override_imports: dict[str, Import] = {
            key: import_ for key, import_ in self._type_override_imports.items() if "." not in key
        }
        self.read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = config.read_only_write_only_model_type
        self.use_frozen_field: bool = config.use_frozen_field
        self.use_serialization_alias: bool = config.use_serialization_alias
        self.use_default_factory_for_optional_nested_models: bool = (
            config.use_default_factory_for_optional_nested_models
        )
        self.field_type_collision_strategy: FieldTypeCollisionStrategy | None = config.field_type_collision_strategy

    def _data_model_field_common_kwargs(self) -> dict[str, Any]:
        return self._data_model_field_common_kwargs_cache

    def _should_preserve_explicit_root_class_name(self, class_name: str) -> bool:
        if not self.allow_leading_underscore_class_name:
            return False
        if class_name != self.class_name:
            return False
        return class_name.startswith("_") and ModelResolver.validate_name(class_name)

    @property
    def field_name_model_type(self) -> ModelType:
        """Get the ModelType for field name validation based on data_model_type.

        Returns ModelType.PYDANTIC for Pydantic models (which have reserved attributes
        like 'schema', 'model_fields', etc.), and ModelType.CLASS for other model types
        (TypedDict, dataclass, msgspec) which don't have such constraints.
        """
        if _is_pydantic_v2_base_model(self.data_model_type):
            return ModelType.PYDANTIC
        return ModelType.CLASS

    def get_serialization_alias(
        self,
        original_field_name: str,
        field_name: str,
        class_name: str | None = None,
    ) -> str | None:
        """Get an explicit serialization alias for a field."""
        if not self.serialization_aliases:
            return None
        keys = []
        if class_name is not None:  # pragma: no branch
            keys.extend((f"{class_name}.{original_field_name}", f"{class_name}.{field_name}"))
        keys.extend((original_field_name, field_name))
        for key in keys:
            if key in self.serialization_aliases:
                return self.serialization_aliases[key]
        return None

    @staticmethod
    def _parse_type_mappings(type_mappings: list[str] | None) -> dict[tuple[str, str], str]:
        """Parse type mappings from CLI format to internal format.

        Supports two formats:
        - "type+format=target" (e.g., "string+binary=string")
        - "format=target" (e.g., "binary=string", assumes type="string")

        Returns a dict mapping (type, format) tuples to target type names.
        """
        if not type_mappings:
            return {}

        result: dict[tuple[str, str], str] = {}
        for mapping in type_mappings:
            if "=" not in mapping:
                msg = f"Invalid type mapping format: {mapping!r}. Expected 'type+format=target' or 'format=target'."
                raise ValueError(msg)

            source, target = mapping.split("=", 1)
            if "+" in source:
                type_, format_ = source.split("+", 1)
            else:
                # Default to "string" type if only format is specified
                type_ = "string"
                format_ = source

            result[type_, format_] = target

        return result

    @property
    def iter_source(self) -> Iterator[Source]:
        """Iterate over all source files to be parsed."""
        if self._cache_local_sources:
            if (cached_sources := self._local_source_cache) is None:
                cached_sources = tuple(self._iter_source_uncached())
                self._local_source_cache = cached_sources
            for source in cached_sources:
                if source.raw_data is None:
                    yield Source(path=source.path, text=source.text)
                else:  # pragma: no cover
                    yield source.model_copy(deep=True)
            return
        yield from self._iter_source_uncached()

    def _iter_source_uncached(self) -> Iterator[Source]:
        match self.source:
            case str():
                yield Source(path=Path(), text=self.source)
            case dict():
                yield Source.from_dict(self.source)
            case Path() as path:  # pragma: no cover
                if path.is_dir():
                    for p in sorted(path.rglob("*"), key=lambda p: p.name):
                        if p.is_file():
                            yield self._source_from_path(p)
                else:
                    yield self._source_from_path(path)
            case list() as paths:  # pragma: no cover
                for path in paths:
                    yield self._source_from_path(path)
            case _:
                yield Source(
                    path=Path(self.source.path),
                    text=self.remote_text_cache.get_or_put(
                        self.source.geturl(), default_factory=self._get_text_from_url
                    ),
                )

    def _source_from_path(self, path: Path) -> Source:
        if self._use_parsed_source_cache:
            return Source.from_cached_path(path, self.base_path, self.encoding, keep_text=self.validation)
        return Source.from_path(path, self.base_path, self.encoding)

    def _append_additional_imports(self, additional_imports: list[str] | None) -> None:
        if additional_imports is None:
            additional_imports = []

        for additional_import_string in additional_imports:
            if additional_import_string is None:  # pragma: no cover
                continue
            new_import = Import.from_full_path(additional_import_string)
            self.imports.append(new_import)

    def _resolve_base_class(
        self, class_name: str, custom_base_path: str | list[str] | None = None
    ) -> str | list[str] | None:
        """Resolve base class(es) with priority: base_class_map > customBasePath > base_class."""

        def normalize(value: str | list[str] | None) -> str | list[str] | None:
            if value is None:  # pragma: no cover
                return None
            if isinstance(value, list):
                seen: set[str] = set()
                result = [v for v in value if isinstance(v, str) and v and v not in seen and not seen.add(v)]
                if not result:
                    return None
                return result[0] if len(result) == 1 else result
            return value or None

        if self.base_class_map and class_name in self.base_class_map:
            return normalize(self.base_class_map[class_name])
        if custom_base_path:
            return normalize(custom_base_path)
        return self.base_class or None

    def _get_text_from_url(self, url: str) -> str:
        from datamodel_code_generator.http import DEFAULT_HTTP_TIMEOUT, get_body  # noqa: PLC0415

        timeout = self.http_timeout if self.http_timeout is not None else DEFAULT_HTTP_TIMEOUT
        return self.remote_text_cache.get_or_put(
            url,
            default_factory=lambda _url: get_body(
                url,
                self.http_headers,
                self.http_ignore_tls,
                self.http_query_parameters,
                timeout,
                allow_private_network=self.allow_private_network,
            ),
        )

    @classmethod
    def get_url_path_parts(cls, url: ParseResult) -> list[str]:
        """Split URL into scheme/host and path components."""
        return [
            f"{url.scheme}://{url.netloc}",
            *url.path.split("/")[1:],
        ]

    @property
    def data_type(self) -> type[DataType]:
        """Get the DataType class from the type manager."""
        return self.data_type_manager.data_type

    @abstractmethod
    def parse_raw(self) -> None:
        """Parse the raw schema source. Must be implemented by subclasses."""
        raise NotImplementedError

    @classmethod
    def _replace_model_in_list(
        cls,
        models: list[DataModel],
        original: DataModel,
        replacement: DataModel,
    ) -> None:
        """Replace model at its position in list."""
        # Use direct assignment instead of insert+remove for O(n) instead of O(2n)
        idx = models.index(original)
        models[idx] = replacement

    def __delete_duplicate_models(self, models: list[DataModel]) -> None:  # noqa: PLR0912
        model_class_names: dict[str, DataModel] = {}
        model_to_duplicate_models: defaultdict[DataModel, list[DataModel]] = defaultdict(list)
        # Use set for O(1) membership checks and collect removals for batch processing
        models_set = set(models)
        models_to_remove: set[DataModel] = set()
        for model in models:
            if model in models_to_remove:  # pragma: no cover
                continue
            if isinstance(model, self.data_model_root_type):
                root_data_type = model.fields[0].data_type

                # backward compatible
                # Remove duplicated root model
                if (
                    root_data_type.reference
                    and not root_data_type.is_dict
                    and not root_data_type.is_list
                    and root_data_type.reference.source in models_set
                    and root_data_type.reference.name
                    == self.model_resolver.get_class_name(model.reference.original_name, unique=False).name
                ):
                    self.generation_store.redirect_reference_users(model.reference, root_data_type.reference)
                    models_to_remove.add(model)
                    self.generation_store.detach_model_data_type_refs(model)
                    continue

                # Remove self from all DataModel children's base_classes
                for child in model.reference.iter_data_model_children():
                    self.generation_store.set_base_classes(
                        child,
                        [bc for bc in child.base_classes if bc.reference != model.reference],
                    )
                    if not child.base_classes:  # pragma: no cover
                        self.generation_store.reset_base_classes(child)

            class_name = model.duplicate_class_name or model.class_name
            if class_name in model_class_names:
                original_model = model_class_names[class_name]
                if model.get_dedup_key(model.duplicate_class_name, use_default=False) == original_model.get_dedup_key(
                    original_model.duplicate_class_name, use_default=False
                ):
                    model_to_duplicate_models[original_model].append(model)
                    continue
            model_class_names[class_name] = model
        for model, duplicate_models in model_to_duplicate_models.items():
            for duplicate_model in duplicate_models:
                self.generation_store.redirect_reference_users(duplicate_model.reference, model.reference)
                # Deduplicate base_classes in all DataModel children
                for child in duplicate_model.reference.iter_data_model_children():
                    self.generation_store.set_base_classes(
                        child,
                        {f"{c.module_name}.{c.type_hint}": c for c in child.base_classes}.values(),
                    )
                models_to_remove.add(duplicate_model)

        if self.reuse_model and self.collapse_reuse_models:
            max_iterations, iteration = len(models), 0
            while True:
                iteration += 1
                if iteration > max_iterations:  # pragma: no cover
                    msg = f"Deduplication exceeded max iterations ({max_iterations})"
                    raise RuntimeError(msg)

                content_key_to_models: dict[tuple[Any, ...], list[DataModel]] = defaultdict(list)
                for model in models:
                    if model not in models_to_remove and not isinstance(model, self.data_model_root_type):
                        model._dedup_key_cache.clear()  # noqa: SLF001
                        content_key_to_models[model.get_dedup_key(None, use_default=True)].append(model)

                if not (
                    duplicates := [
                        (canonical := group[0], dup)
                        for group in content_key_to_models.values()
                        if len(group) > 1
                        for dup in group[1:]
                        if dup not in models_to_remove
                    ]
                ):
                    break

                for canonical, duplicate in duplicates:
                    self.generation_store.redirect_model_reference_users(duplicate, models, canonical.reference)
                    for child in duplicate.reference.iter_data_model_children():  # pragma: no cover
                        self.generation_store.set_base_classes(
                            child,
                            {c.reference: c for c in child.base_classes}.values(),
                        )
                    models_to_remove.add(duplicate)

        # Batch removal: O(n) instead of O(n²)
        if models_to_remove:
            models[:] = [m for m in models if m not in models_to_remove]

    def __replace_duplicate_name_in_module(self, models: list[DataModel]) -> None:
        scoped_model_resolver = ModelResolver(
            exclude_names={i.alias or i.import_ for m in models for i in m.imports},
            duplicate_name_suffix="Model",
            custom_class_name_generator=(lambda name: name) if self.custom_class_name_generator else None,
        )

        model_names: dict[str, DataModel] = {}
        for model in models:
            class_name: str = model.class_name
            generated_name: str = scoped_model_resolver.add(
                [model.path],
                class_name,
                unique=True,
                class_name=True,
                preserve_class_name=self._should_preserve_explicit_root_class_name(class_name),
            ).name
            if class_name != generated_name:
                model.class_name = generated_name
            model_names[model.class_name] = model

        for model in models:
            duplicate_name = model.duplicate_class_name
            # check only first desired name
            if duplicate_name and duplicate_name not in model_names:
                del model_names[model.class_name]
                model.class_name = duplicate_name
                model_names[duplicate_name] = model

    def __change_from_import(  # noqa: PLR0912, PLR0913, PLR0914
        self,
        models: list[DataModel],
        imports: Imports,
        scoped_model_resolver: ModelResolver,
        *,
        init: bool,
        internal_modules: set[tuple[str, ...]] | None = None,
        model_path_to_module_name: dict[str, str] | None = None,
    ) -> None:
        model_paths = {model.path for model in models}
        internal_modules = internal_modules or set()
        model_path_to_module_name = model_path_to_module_name or {}

        for model in models:
            scoped_model_resolver.add([model.path], model.class_name)
        for model in models:
            before_import = model.imports
            imports.append(before_import)
            current_module_name = _get_model_module_name(model, model_path_to_module_name)
            # Some imports are derived from type hints, so aliases can affect them.
            import_sensitive_alias_changed = False
            for data_type in model.all_data_types:
                match data_type.reference:
                    case None:
                        continue
                    case reference if reference.path in model_paths:
                        continue
                    case reference:
                        pass

                target_full_name = _get_data_type_target_full_name(data_type, reference, model_path_to_module_name)

                if isinstance(data_type, BaseClassDataType):
                    left, right = relative(current_module_name, target_full_name)
                    is_ancestor = is_ancestor_package_reference(current_module_name, target_full_name)
                    from_ = left if is_ancestor else (f"{left}{right}" if left.endswith(".") else f"{left}.{right}")
                    import_ = reference.short_name
                    full_path = from_, import_
                else:
                    from_, import_ = full_path = relative(current_module_name, target_full_name)
                    if imports.use_exact:
                        from_, import_ = full_path = exact_import(from_, import_, reference.short_name)
                    import_ = import_.replace("-", "_")
                    current_module_path = tuple(current_module_name.split(".")) if current_module_name else ()
                    if (  # pragma: no cover
                        len(current_module_path) > 1
                        and current_module_path[-1].count(".") > 0
                        and not self.treat_dot_as_module
                    ):
                        rel_path_depth = current_module_path[-1].count(".")
                        from_ = from_[rel_path_depth:]

                    ref_module = tuple(target_full_name.split(".")[:-1])

                    is_module_class_collision = (
                        ref_module and import_ == reference.short_name and ref_module[-1] == import_
                    )

                    if (
                        from_
                        and not imports.use_exact
                        and (ref_module in internal_modules or is_module_class_collision)
                    ):
                        from_ = f"{from_}{import_}" if from_.endswith(".") else f"{from_}.{import_}"
                        import_ = reference.short_name
                        full_path = from_, import_

                alias = scoped_model_resolver.add(full_path, import_).name

                name = reference.short_name
                if from_ and import_ and alias != name:
                    data_type.alias = alias if reference.short_name == import_ else f"{alias}.{name}"
                    import_sensitive_alias_changed = True

                if init and not target_full_name.startswith(current_module_name + "."):
                    from_ = "." + from_
                imports.append(
                    Import(
                        from_=from_,
                        import_=import_,
                        alias=alias,
                        reference_path=reference.path,
                    ),
                )
            if import_sensitive_alias_changed:  # pragma: no cover
                model.clear_imports_cache()
                after_import = model.imports
                if before_import != after_import:
                    imports.append(after_import)

    @classmethod
    def __extract_inherited_enum(cls, models: list[DataModel]) -> None:
        for model in models.copy():
            if model.fields:
                continue
            enums: list[Enum] = []
            for base_model in model.base_classes:
                if not base_model.reference:
                    continue
                source_model = base_model.reference.source
                if isinstance(source_model, Enum):
                    enums.append(source_model)
            if enums:
                merged_enum = enums[0].__class__(
                    fields=[f for e in enums for f in e.fields],
                    description=model.description,
                    reference=model.reference,
                )
                cls._replace_model_in_list(models, model, merged_enum)

    def _create_discriminator_data_type(
        self,
        enum_source: Enum | None,
        discriminator_values: list[DiscriminatorValue],
        discriminator_model: DataModel,
        imports: Imports,
    ) -> DataType:
        """Create a data type for discriminator field, using enum literals if available."""
        if enum_source:
            if self.use_enum_values_in_discriminator:
                enum_class_name = enum_source.reference.short_name
                enum_member_literals: list[tuple[str, str]] = []
                for value in discriminator_values:
                    member = enum_source.find_member(value, coerce_strings=True)
                    if member and member.field.name:
                        enum_member_literals.append((enum_class_name, member.field.name))
                    else:  # pragma: no cover
                        enum_member_literals.append((enum_class_name, str(value)))
                data_type = self.data_type(enum_member_literals=enum_member_literals)
                if enum_source.module_path != discriminator_model.module_path:  # pragma: no cover
                    imports.append(Import.from_full_path(enum_source.name))
            else:
                data_type = self.data_type(
                    literals=[
                        Parser._get_enum_discriminator_literal(enum_source, value) for value in discriminator_values
                    ]
                )
        else:
            data_type = self.data_type(literals=discriminator_values)
        return data_type

    @staticmethod
    def _get_enum_discriminator_literal(enum_source: Enum, value: DiscriminatorValue) -> DiscriminatorValue:
        member = enum_source.find_member(value, coerce_strings=True)
        if not member:
            return value

        member_value = evaluate_member_value(member.field.default)
        if isinstance(member_value, (str, int, bool)):
            return member_value
        return value

    def __apply_discriminator_type(  # noqa: PLR0912, PLR0914, PLR0915
        self,
        models: list[DataModel],
        imports: Imports,
    ) -> None:
        for model in models:  # noqa: PLR1702
            for field in model.fields:
                discriminator = field.extras.get("discriminator")
                if not discriminator or not isinstance(discriminator, dict):
                    continue
                property_name = discriminator.get("propertyName")
                if not property_name:  # pragma: no cover
                    continue
                field_name, alias = self.model_resolver.get_valid_field_name_and_alias(
                    field_name=property_name, model_type=self.field_name_model_type
                )
                discriminator["propertyName"] = field_name
                mapping = discriminator.get("mapping", {})
                # Any type cannot be a discriminated union variant (Pydantic v2 rejects it)
                has_any_variant = any(_is_any_variant(dt) for dt in field.data_type.data_types)
                if has_any_variant:  # pragma: no cover
                    field.extras.pop("discriminator", None)
                    field.data_type.discriminator = None
                    continue
                for data_type in field.data_type.data_types:
                    if not data_type.reference:  # pragma: no cover
                        continue
                    discriminator_model = data_type.reference.source

                    if (
                        not isinstance(discriminator_model, DataModel) or not discriminator_model.SUPPORTS_DISCRIMINATOR
                    ):  # pragma: no cover
                        continue

                    discriminator_values: list[DiscriminatorValue] = []

                    for discriminator_field in discriminator_model.fields:
                        if field_name not in {discriminator_field.original_name, discriminator_field.name}:
                            continue
                        discriminator_value = _get_discriminator_field_value(discriminator_field)
                        if discriminator_value is not None:
                            discriminator_values = [discriminator_value]
                            break
                    # Reuse models are created as empty subclasses with a "/reuse" path suffix.
                    # Scan inherited fields to recover the discriminator literal from the base.
                    if not discriminator_values and discriminator_model.path.endswith("/reuse"):
                        for discriminator_field in discriminator_model.iter_all_fields():  # pragma: no branch
                            if field_name not in {discriminator_field.original_name, discriminator_field.name}:
                                continue
                            discriminator_value = _get_discriminator_field_value(discriminator_field)
                            if discriminator_value is not None:  # pragma: no branch
                                discriminator_values = [discriminator_value]
                                break

                    if not discriminator_values and mapping:
                        _check_discriminator_mapping_paths(
                            discriminator_model,
                            mapping,
                            discriminator_values,
                        )

                        if len(discriminator_values) == 0:
                            for base_class in discriminator_model.base_classes:
                                if not base_class.reference:
                                    continue

                                _check_discriminator_mapping_paths(base_class.reference, mapping, discriminator_values)

                        if not discriminator_values:
                            discriminator_values = [discriminator_model.path.split("/")[-1]]

                    if not discriminator_values:
                        discriminator_values = [discriminator_model.path.split("/")[-1]]

                    if not discriminator_values:  # pragma: no cover
                        msg = f"Discriminator type is not found. {data_type.reference.path}"
                        raise RuntimeError(msg)

                    has_one_literal = False
                    for discriminator_field in discriminator_model.fields:
                        if field_name not in {discriminator_field.original_name, discriminator_field.name}:
                            continue
                        literals = discriminator_field.data_type.literals
                        const_value = discriminator_field.extras.get("const")
                        expected_value = discriminator_values[0] if discriminator_values else None

                        # Check if literals match (existing behavior)
                        literals_match = len(literals) == 1 and literals[0] == expected_value
                        # Check if const value matches (for msgspec with type: string + const)
                        const_match = const_value is not None and const_value == expected_value

                        if literals_match:
                            has_one_literal = True
                            if _is_msgspec_struct(discriminator_model):  # pragma: no cover
                                _add_msgspec_base_class_kwarg(discriminator_model, "tag_field", f"'{field_name}'")
                                _add_msgspec_base_class_kwarg(discriminator_model, "tag", repr(expected_value))
                                discriminator_field.extras["is_classvar"] = True
                            # Found the discriminator field, no need to keep looking
                            break

                        # For msgspec with const value but no literal (type: string + const case)
                        if const_match and _is_msgspec_struct(discriminator_model):  # pragma: no cover
                            has_one_literal = True
                            _add_msgspec_base_class_kwarg(discriminator_model, "tag_field", f"'{field_name}'")
                            _add_msgspec_base_class_kwarg(discriminator_model, "tag", repr(const_value))
                            discriminator_field.extras["is_classvar"] = True
                            break

                        enum_source = discriminator_field.data_type.find_source(Enum)
                        if self.use_enum_values_in_discriminator:
                            enum_source = enum_source or _get_enum_from_base(discriminator_model, field_name)

                        for field_data_type in discriminator_field.data_type.all_data_types:
                            if field_data_type.reference:  # pragma: no cover
                                self.generation_store.detach_data_type_ref(field_data_type)

                        self.generation_store.replace_field_type(
                            discriminator_field,
                            self._create_discriminator_data_type(
                                enum_source,
                                discriminator_values,
                                discriminator_model,
                                imports,
                            ),
                        )
                        discriminator_field.data_type.parent = discriminator_field
                        discriminator_field.required = True
                        imports.append(discriminator_field.imports)
                        has_one_literal = True
                    if not has_one_literal:
                        new_data_type = self._create_discriminator_data_type(
                            _get_enum_from_base(discriminator_model, field_name),
                            discriminator_values,
                            discriminator_model,
                            imports,
                        )
                        # Handle multiple aliases (Pydantic v2 AliasChoices)
                        single_alias: str | None = None
                        validation_aliases: list[str] | None = None
                        if isinstance(alias, list):
                            validation_aliases = alias
                        else:
                            single_alias = alias
                        self.generation_store.append_field(
                            discriminator_model,
                            self.data_model_field_type(
                                name=field_name,
                                data_type=new_data_type,
                                required=True,
                                alias=single_alias,
                                validation_aliases=validation_aliases,
                                serialization_alias=self.get_serialization_alias(
                                    property_name, field_name, discriminator_model.name
                                ),
                                use_serialization_alias=self.use_serialization_alias,
                                **self._data_model_field_common_kwargs(),
                            ),
                        )
            has_imported_literal = any(import_ == IMPORT_LITERAL for import_ in imports)
            if has_imported_literal:  # pragma: no cover
                imports.append(IMPORT_LITERAL)

    @classmethod
    def _create_set_from_list(cls, data_type: DataType) -> DataType | None:
        if data_type.is_list:
            new_data_type = data_type.model_copy()
            new_data_type.is_list = False
            new_data_type.is_set = True
            for data_type_ in new_data_type.data_types:
                data_type_.parent = new_data_type
            return new_data_type
        if data_type.data_types:  # pragma: no cover
            for nested_data_type in data_type.data_types[:]:
                set_data_type = cls._create_set_from_list(nested_data_type)
                if set_data_type:  # pragma: no cover
                    nested_data_type.swap_with(set_data_type)
            return data_type
        return None  # pragma: no cover

    def __replace_unique_list_to_set(self, models: list[DataModel]) -> None:
        changed = False
        for model in models:
            for model_field in model.fields:
                if not self.use_unique_items_as_set:
                    continue

                if not (model_field.constraints and model_field.constraints.unique_items):
                    continue
                set_data_type = self._create_set_from_list(model_field.data_type)
                if set_data_type:  # pragma: no cover
                    # Check if default list elements are hashable before converting type
                    if isinstance(model_field.default, list):
                        try:
                            converted_default = set(model_field.default)
                        except TypeError:
                            # Elements are not hashable (e.g., contains dicts)
                            # Skip both type and default conversion to keep consistency
                            continue
                        model_field.default = converted_default
                    self.generation_store.replace_field_type(model_field, set_data_type)
                    changed = True
        if changed:
            _clear_model_imports_cache(models)

    @classmethod
    def __collect_set_item_references(cls, models: list[DataModel]) -> set[str]:
        """Collect reference paths of all types used as set/frozenset items."""
        references: set[str] = set()
        for model in models:
            for field in model.fields:
                for data_type in field.data_type.all_data_types:
                    if data_type.is_set or data_type.is_frozen_set:
                        for item_type in data_type.data_types:
                            references.update(
                                nested.reference.path for nested in item_type.all_data_types if nested.reference
                            )
        return references

    @classmethod
    def __mark_set_item_models_hashable(cls, models: list[DataModel]) -> None:
        """Mark models used as set/frozenset items with hash flag for __hash__ generation."""
        set_item_references = cls.__collect_set_item_references(models)

        for model in models:
            if model.reference.path in set_item_references:
                if isinstance(model, Enum):
                    continue
                class_body_lines = model.extra_template_data.setdefault("class_body_lines", [])
                class_body_lines.append("__hash__ = object.__hash__")

    @classmethod
    def __set_reference_default_value_to_field(cls, models: list[DataModel]) -> None:
        for model in models:
            for model_field in model.fields:
                if not model_field.data_type.reference or model_field.has_default:
                    continue
                if (
                    isinstance(model_field.data_type.reference.source, DataModel)
                    and model_field.data_type.reference.source.default != UNDEFINED
                ):
                    # pragma: no cover
                    model_field.default = model_field.data_type.reference.source.default

    def __reuse_model(self, models: list[DataModel], require_update_action_models: list[str]) -> None:
        if not self.reuse_model or self.reuse_scope == ReuseScope.Tree:
            return
        duplicates = []
        reuse_candidates = (
            model
            for model in models.copy()
            if not (self.collapse_root_models and isinstance(model, self.data_model_root_type))
        )
        for cached_model, model in _iter_first_seen_duplicates(reuse_candidates, lambda item: item.get_dedup_key()):
            cached_model_reference = cached_model.reference
            if isinstance(model, Enum) or self.collapse_reuse_models:
                self.generation_store.redirect_model_reference_users(model, models, cached_model_reference)
                duplicates.append(model)
            else:
                inherited_model = model.create_reuse_model(cached_model_reference)
                self.generation_store.redirect_model_reference_users(model, models, inherited_model.reference)
                if cached_model_reference.path in require_update_action_models:
                    add_model_path_to_list(require_update_action_models, inherited_model)
                self._replace_model_in_list(models, model, inherited_model)

        for duplicate in duplicates:
            models.remove(duplicate)

    def __find_duplicate_models_across_modules(  # noqa: PLR6301
        self,
        module_models: list[tuple[tuple[str, ...], list[DataModel]]],
    ) -> list[tuple[tuple[str, ...], DataModel, tuple[str, ...], DataModel]]:
        """Find duplicate models across all modules by comparing render output and imports."""
        all_models: list[tuple[tuple[str, ...], DataModel]] = []
        for module, models in module_models:
            all_models.extend((module, model) for model in models)

        duplicates: list[tuple[tuple[str, ...], DataModel, tuple[str, ...], DataModel]] = []

        for (canonical_module, canonical_model), (module, model) in _iter_first_seen_duplicates(
            all_models,
            lambda item: item[1].get_dedup_key(),
        ):
            duplicates.append((module, model, canonical_module, canonical_model))

        return duplicates

    def __validate_shared_module_name(
        self,
        module_models: list[tuple[tuple[str, ...], list[DataModel]]],
    ) -> None:
        """Validate that the shared module name doesn't conflict with existing modules."""
        shared_module = self.shared_module_name
        existing_module_names = {module[0] for module, _ in module_models if module}
        if shared_module in existing_module_names:
            msg = (
                f"Schema file or directory '{shared_module}' conflicts with the shared module name. "
                f"Use --shared-module-name to specify a different name."
            )
            raise Error(msg)

    def __create_shared_module_from_duplicates(  # noqa: PLR0912
        self,
        module_models: list[tuple[tuple[str, ...], list[DataModel]]],
        duplicates: list[tuple[tuple[str, ...], DataModel, tuple[str, ...], DataModel]],
        require_update_action_models: list[str],
    ) -> tuple[tuple[str, ...], list[DataModel]]:
        """Create shared module with canonical models and replace duplicates with inherited models."""
        shared_module = self.shared_module_name

        shared_models: list[DataModel] = []
        canonical_to_shared_ref: dict[DataModel, Reference] = {}
        canonical_models_seen: set[DataModel] = set()

        # Process in order of first appearance in duplicates to ensure stable ordering
        for _, _, _, canonical in duplicates:
            if canonical in canonical_models_seen:
                continue
            canonical_models_seen.add(canonical)
            self.generation_store.update_model_reference(canonical, new_file_path=Path(f"{shared_module}.py"))
            canonical_to_shared_ref[canonical] = canonical.reference
            shared_models.append(canonical)

        supports_inheritance = _is_pydantic_v2_base_model(self.data_model_type) or _is_dataclass_data_model(
            self.data_model_type
        )

        module_models_sets: dict[tuple[str, ...], set[DataModel]] = {
            module: set(models) for module, models in module_models
        }
        models_to_remove: dict[tuple[str, ...], set[DataModel]] = defaultdict(set)

        for duplicate_module, duplicate_model, _, canonical_model in duplicates:
            shared_ref = canonical_to_shared_ref[canonical_model]
            models_set = module_models_sets.get(duplicate_module)
            if not models_set or duplicate_model not in models_set:  # pragma: no cover
                msg = f"Duplicate model {duplicate_model.name} not found in module {duplicate_module}"
                raise RuntimeError(msg)

            for module, models in module_models:  # pragma: no branch
                if module != duplicate_module:
                    continue
                if isinstance(duplicate_model, Enum) or not supports_inheritance or self.collapse_reuse_models:
                    self.generation_store.redirect_model_reference_users(duplicate_model, models, shared_ref)
                    models_to_remove[module].add(duplicate_model)
                else:
                    inherited_model = duplicate_model.create_reuse_model(shared_ref)
                    self.generation_store.redirect_model_reference_users(
                        duplicate_model,
                        models,
                        inherited_model.reference,
                    )
                    if shared_ref.path in require_update_action_models:
                        add_model_path_to_list(require_update_action_models, inherited_model)
                    self._replace_model_in_list(models, duplicate_model, inherited_model)
                break

        for canonical in canonical_models_seen:
            for module, models_set in module_models_sets.items():
                if canonical in models_set:
                    models_to_remove[module].add(canonical)
                    break
            else:  # pragma: no cover
                msg = f"Canonical model {canonical.name} not found in any module"
                raise RuntimeError(msg)

        for module, models in module_models:
            to_remove = models_to_remove.get(module)
            if to_remove:
                models[:] = [m for m in models if m not in to_remove]

        return (shared_module,), shared_models

    def __reuse_model_tree_scope(
        self,
        module_models: list[tuple[tuple[str, ...], list[DataModel]]],
        require_update_action_models: list[str],
    ) -> tuple[tuple[str, ...], list[DataModel]] | None:
        """Deduplicate models across all modules, placing shared models in shared.py."""
        if not self.reuse_model or self.reuse_scope != ReuseScope.Tree:
            return None

        duplicates = self.__find_duplicate_models_across_modules(module_models)
        if not duplicates:
            return None

        self.__validate_shared_module_name(module_models)
        return self.__create_shared_module_from_duplicates(module_models, duplicates, require_update_action_models)

    def __collapse_root_models(  # noqa: PLR0912, PLR0914, PLR0915
        self,
        models: list[DataModel],
        unused_models: list[DataModel],
        imports: Imports,
        scoped_model_resolver: ModelResolver,
        model_path_to_module_name: dict[str, str] | None = None,
    ) -> None:
        if not self.collapse_root_models:
            return

        generation_store = self.generation_store
        generation_index = generation_store.index

        for model in models:  # noqa: PLR1702
            for model_field in model.fields:
                for data_type in model_field.data_type.all_data_types:
                    reference = data_type.reference
                    if not reference or not isinstance(reference.source, self.data_model_root_type):
                        # If the data type is not a reference, we can't collapse it.
                        # If it's a reference to a root model type, we don't do anything.
                        continue

                    # Use root-type as model_field type
                    root_type_model = reference.source
                    root_type_field = root_type_model.fields[0]

                    if (
                        self.field_constraints
                        and isinstance(root_type_field.constraints, ConstraintsBase)
                        and root_type_field.constraints.has_constraints
                        and any(d for d in model_field.data_type.all_data_types if d.is_dict or d.is_union or d.is_list)
                    ):
                        continue  # pragma: no cover

                    if root_type_field.data_type.reference:
                        if self.collapse_root_models_name_strategy is None:
                            continue

                        inner_reference = root_type_field.data_type.reference
                        inner_model = cast("DataModel", inner_reference.source)

                        if self.collapse_root_models_name_strategy == CollapseRootModelsNameStrategy.Parent:
                            root_model_wrappers, direct_refs = generation_index.root_collapse_reference_usage(
                                inner_reference,
                                excluded_model=root_type_model,
                                root_model_type=self.data_model_root_type,
                            )

                            if len(root_model_wrappers) > 1:
                                warn(
                                    f"Cannot apply 'parent' strategy for '{inner_model.class_name}' - "
                                    f"it is referenced by multiple root models: "
                                    f"{[m.class_name for m in root_model_wrappers]}. Skipping collapse.",
                                    stacklevel=2,
                                )
                                continue

                            if direct_refs:
                                warn(
                                    f"Cannot apply 'parent' strategy for '{inner_model.class_name}' - "
                                    f"it is directly referenced by non-wrapper models. Skipping collapse.",
                                    stacklevel=2,
                                )
                                continue

                            generation_store.update_model_reference(
                                inner_model,
                                class_name=root_type_model.class_name,
                                reference_name=root_type_model.class_name,
                                new_path=root_type_model.reference.path,
                            )

                        assert isinstance(root_type_model, DataModel)

                        has_remaining_root_references = generation_index.has_data_type_references_other_than(
                            root_type_model.reference,
                            data_type,
                        )
                        generation_store.collapse_root_data_type(data_type, inner_reference)

                        imports.remove_referenced_imports(root_type_model.path)
                        if not has_remaining_root_references:
                            unused_models.append(root_type_model)

                        continue

                    # set copied data_type
                    copied_data_type = root_type_field.data_type.model_copy()
                    if isinstance(data_type.parent, self.data_model_field_type):
                        # for field
                        # override empty field by root-type field
                        model_field.extras = {
                            **root_type_field.extras,
                            **model_field.extras,
                        }
                        model_field.process_const()

                        if self.field_constraints:
                            model_field.constraints = ConstraintsBase.merge_constraints(
                                root_type_field.constraints, model_field.constraints
                            )

                        self.generation_store.replace_field_type(data_type.parent, copied_data_type)

                    elif isinstance(data_type.parent, DataType) and data_type.parent.is_list:
                        if self.field_constraints:
                            model_field.constraints = ConstraintsBase.merge_constraints(
                                root_type_field.constraints, model_field.constraints
                            )
                        discriminator = root_type_field.extras.get("discriminator")
                        if discriminator and _is_pydantic_v2_data_model_field(root_type_field):
                            has_any_variant = any(_is_any_variant(dt) for dt in copied_data_type.data_types)
                            if not has_any_variant:  # pragma: no branch
                                prop_name = (
                                    discriminator.get("propertyName")
                                    if isinstance(discriminator, dict)
                                    else discriminator
                                )
                                field_name, _ = self.model_resolver.get_valid_field_name_and_alias(
                                    field_name=prop_name,
                                    model_type=self.field_name_model_type,
                                )
                                copied_data_type.discriminator = field_name
                        assert isinstance(data_type.parent, DataType)
                        self.generation_store.replace_nested_data_type(data_type.parent, data_type, copied_data_type)

                    elif isinstance(data_type.parent, DataType):
                        # for data_type
                        self.generation_store.replace_nested_data_type(data_type.parent, data_type, copied_data_type)
                    else:  # pragma: no cover
                        continue

                    for d in copied_data_type.all_data_types:
                        _register_data_type_import(
                            d,
                            model,
                            imports,
                            scoped_model_resolver,
                            model_path_to_module_name,
                        )

                    original_field = get_most_of_parent(data_type, DataModelFieldBase)
                    if original_field:  # pragma: no cover
                        # TODO: Improve detection of reference type
                        # Use list instead of set because Import is not hashable
                        excluded_imports = [IMPORT_OPTIONAL, IMPORT_UNION]
                        field_imports = [i for i in original_field.imports if i not in excluded_imports]
                        imports.append(field_imports)

                    generation_store.detach_data_type_ref(data_type)

                    assert isinstance(root_type_model, DataModel)

                    imports.remove_referenced_imports(root_type_model.path)
                    if not generation_index.has_data_type_references(root_type_model.reference):
                        unused_models.append(root_type_model)

    def __set_default_enum_member(
        self,
        models: list[DataModel],
    ) -> None:
        if not self.set_default_enum_member:
            return
        for _, model_field, data_type in iter_models_field_data_types(models):
            if model_field.default is None:
                continue
            if data_type.reference and isinstance(data_type.reference.source, Enum):  # pragma: no cover
                if isinstance(model_field.default, list):
                    enum_member: list[Member] | (Member | None) = [
                        e for e in (data_type.reference.source.find_member(d) for d in model_field.default) if e
                    ]
                else:
                    enum_member = data_type.reference.source.find_member(model_field.default)
                if not enum_member:
                    continue
                model_field.default = enum_member
                if data_type.alias:
                    if isinstance(enum_member, list):
                        for enum_member_ in enum_member:
                            enum_member_.alias = data_type.alias  # ty: ignore[unresolved-attribute]
                    else:
                        enum_member.alias = data_type.alias

    def __set_validate_default_on_fields(  # noqa: PLR6301
        self,
        models: list[DataModel],
    ) -> None:
        """Set validate_default=True on fields with structured defaults needing validation."""
        for model in models:
            if isinstance(model, Enum):
                continue
            for model_field in model.fields:
                if model_field.required and not model_field.use_default_with_required:
                    continue
                if model_field.default is None or model_field.default is UNDEFINED:
                    continue
                if isinstance(model_field.default, Member):
                    continue
                if not _needs_validate_default(model_field.data_type):
                    continue
                model_field.extras["validate_default"] = True

    def __override_required_field(
        self,
        models: list[DataModel],
    ) -> None:
        changed = False
        for model in models:
            if isinstance(model, (Enum, self.data_model_root_type)):
                continue
            for index, model_field in enumerate(model.fields[:]):
                data_type = model_field.data_type
                if (
                    not model_field.original_name  # noqa: PLR0916
                    or data_type.data_types
                    or data_type.reference
                    or data_type.type
                    or data_type.literals
                    or data_type.dict_key
                ):
                    continue

                original_field = _find_field(model_field.original_name, _find_base_classes(model))
                if not original_field:
                    self.generation_store.remove_field(model, model_field)
                    changed = True
                    continue
                copied_original_field = original_field.model_copy()
                if original_field.data_type.reference:
                    data_type = self.data_type_manager.data_type(
                        reference=original_field.data_type.reference,
                    )
                elif original_field.data_type.data_types:
                    data_type = original_field.data_type.model_copy()
                    data_type.data_types = _copy_data_types(original_field.data_type.data_types)
                    for data_type_ in data_type.data_types:
                        data_type_.parent = data_type
                else:
                    data_type = original_field.data_type.model_copy()
                data_type.parent = copied_original_field
                self.generation_store.replace_field_type(copied_original_field, data_type)
                copied_original_field.parent = model
                copied_original_field.required = True
                if self.apply_default_values_for_required_fields and copied_original_field.has_default:
                    copied_original_field.use_default_with_required = True
                self.generation_store.insert_field(model, index, copied_original_field)
                self.generation_store.remove_field(model, model_field)
                changed = True
        if changed:
            _clear_model_imports_cache(models)

    def __sort_models(
        self,
        models: list[DataModel],
        imports: Imports,
        *,
        use_deferred_annotations: bool,
    ) -> None:
        if not self.keep_model_order:
            return

        _reorder_models_keep_model_order(
            models,
            imports,
            pydantic_v2_root_model_type=self.pydantic_v2_root_model_type,
            use_deferred_annotations=use_deferred_annotations,
        )

    def __change_field_name(
        self,
        models: list[DataModel],
    ) -> None:
        if not self.data_model_type.SUPPORTS_FIELD_RENAMING:
            return

        rename_type = self.field_type_collision_strategy == FieldTypeCollisionStrategy.RenameType
        all_class_names = {cast("str", m.class_name) for m in models if m.class_name}

        resolver = ModelResolver(
            snake_case_field=self.snake_case_field,
            remove_suffix_number=True,
        )

        for model in models:
            if "Enum" in model.base_class or not model.BASE_CLASS:
                continue

            for field in (field for field in model.fields if field.name != self.data_model_type.TYPED_EXTRA_FIELD_NAME):
                filed_name = field.name
                reference_type_names: set[str] = set()
                colliding_reference: Reference | None = None

                for data_type in field.data_type.all_data_types:
                    if not data_type.reference:
                        continue
                    reference_type_names.add(data_type.reference.short_name)
                    if rename_type and colliding_reference is None and data_type.reference.short_name == filed_name:
                        colliding_reference = data_type.reference

                if colliding_reference is not None:
                    resolver._reset_for_reuse(all_class_names.copy())  # noqa: SLF001
                    source = cast("DataModel", colliding_reference.source)
                    resolver.exclude_names.add(cast("str", filed_name))
                    new_class_name = resolver.add(["type"], cast("str", source.class_name)).name  # ty: ignore[redundant-cast]
                    source.class_name = new_class_name
                    all_class_names.add(new_class_name)
                elif not rename_type:
                    resolver._reset_for_reuse(reference_type_names)  # noqa: SLF001
                    new_filed_name = resolver.add(["field"], cast("str", filed_name)).name
                    if filed_name != new_filed_name:
                        field.alias = filed_name
                        field.name = new_filed_name

                if (current_name := field.name) in self.builtin_names and any(
                    _is_builtin_type_collision(current_name, dt) for dt in field.data_type.all_data_types
                ):
                    if field.alias is None:
                        field.alias = filed_name
                    field.name = f"{current_name}_"

    def __set_one_literal_on_default(self, models: list[DataModel]) -> None:
        if not self.use_one_literal_as_default:
            return
        for model in models:
            for model_field in model.fields:
                if not model_field.required or len(model_field.data_type.literals) != 1:
                    continue
                model_field.default = model_field.data_type.literals[0]
                model_field.required = False
                if model_field.nullable is not True:  # pragma: no cover
                    model_field.nullable = False

    def __fix_dataclass_field_ordering(self, models: list[DataModel]) -> None:
        """Fix field ordering for dataclasses with inheritance after defaults are set."""
        for model in models:
            if (inherited := self.__get_dataclass_inherited_info(model)) is None:
                continue
            inherited_names, has_default = inherited
            field_has_assignment = Parser._get_field_assignment_checker(model)
            if not has_default or not any(
                self.__is_new_required_field(f, inherited_names, field_has_assignment) for f in model.fields
            ):
                continue

            if _is_msgspec_struct(model):
                _add_msgspec_base_class_kwarg(model, "kw_only", "True")
            elif self.target_python_version.has_kw_only_dataclass:
                for field in model.fields:
                    if self.__is_new_required_field(field, inherited_names, field_has_assignment):
                        field.extras["kw_only"] = True
            else:  # pragma: no cover
                warn(
                    f"Dataclass '{model.class_name}' has a field ordering conflict due to inheritance. "
                    f"An inherited field has a default value, but new required fields are added. "
                    f"This will cause a TypeError at runtime. Consider using --target-python-version 3.10 "
                    f"or higher to enable automatic field(kw_only=True) fix.",
                    category=UserWarning,
                    stacklevel=2,
                )
            self.generation_store.set_fields(model, sorted(model.fields, key=field_has_assignment))

    @classmethod
    def __get_dataclass_inherited_info(cls, model: DataModel) -> tuple[set[str], bool] | None:
        """Get inherited field names and whether any has default. Returns None if not applicable."""
        if not model.SUPPORTS_KW_ONLY:
            return None
        base_class_kw_only = None
        if _is_msgspec_struct(model):
            base_class_kw_only = model.extra_template_data.get("base_class_kwargs", {}).get("kw_only")
        if not model.base_classes or model.dataclass_arguments.get("kw_only") or base_class_kw_only in {True, "True"}:
            return None

        field_has_assignment = cls._get_field_assignment_checker(model)
        inherited_names: set[str] = set()
        has_default = False
        for base in model.base_classes:
            if not base.reference or not isinstance(base.reference.source, DataModel):
                continue  # pragma: no cover
            for f in base.reference.source.iter_all_fields():
                if not f.name or f.extras.get("init") is False:
                    continue  # pragma: no cover
                inherited_names.add(f.name)
                if field_has_assignment(f):
                    has_default = True

        for f in model.fields:
            if f.name not in inherited_names or f.extras.get("init") is False:
                continue
            if field_has_assignment(f):  # pragma: no branch
                has_default = True
        return (inherited_names, has_default) if inherited_names else None

    @staticmethod
    def _get_field_assignment_checker(model: DataModel) -> Callable[[DataModelFieldBase], bool]:
        if _is_msgspec_struct(model):
            from datamodel_code_generator.model.msgspec import has_field_assignment  # noqa: PLC0415

            return has_field_assignment
        from datamodel_code_generator.model.dataclass import has_field_assignment  # noqa: PLC0415

        return has_field_assignment

    def __is_new_required_field(  # noqa: PLR6301
        self,
        field: DataModelFieldBase,
        inherited: set[str],
        field_has_assignment: Callable[[DataModelFieldBase], bool],
    ) -> bool:
        """Check if field is a new required init field."""
        return field.name not in inherited and field.extras.get("init") is not False and not field_has_assignment(field)

    def __remove_overridden_models(self, models: list[DataModel]) -> list[DataModel]:
        """Remove models that are being overridden by custom types (model-level only).

        Only model-level overrides (keys without dots) cause model removal.
        Scoped overrides (ClassName.field) only affect specific fields.
        """
        if not self._model_type_override_imports:
            return models
        return [m for m in models if m.class_name not in self._model_type_override_imports]

    def __apply_type_overrides(self, models: list[DataModel]) -> None:
        """Replace type references with custom import types.

        Supports two key formats:
        - Model-level: {"CustomType": "my_app.Type"} - applies to all references
        - Scoped: {"User.field": "my_app.Type"} - applies to specific field only

        Scoped overrides take priority over model-level overrides.
        """
        if not self._type_override_imports:
            return
        for model in models:
            fields_overridden = False
            for field in model.fields:
                # Check scoped override first: "ClassName.field_name"
                scoped_key = f"{model.class_name}.{field.name}"
                match self._type_override_imports.get(scoped_key):
                    case Import() as override_import:
                        self._apply_override_to_field(field, override_import)
                        fields_overridden = True
                    case None if self._model_type_override_imports:
                        # Apply model-level overrides to nested types
                        fields_overridden = self._apply_override_to_data_type(field.data_type) or fields_overridden
                    case None:
                        pass
            if fields_overridden:
                model.clear_imports_cache()
            if not self._model_type_override_imports:
                continue
            base_classes_overridden = False
            for base_class in model.base_classes:
                if self._apply_override_to_data_type(base_class):
                    base_classes_overridden = True
                    for import_ in base_class.all_imports:
                        Parser._append_model_import(model, import_)
            if base_classes_overridden:
                model.clear_imports_cache()

    def _apply_override_to_field(self, field: DataModelFieldBase, override_import: Import) -> None:
        """Apply override to entire field's data_type."""
        data_type = deepcopy(field.data_type)
        data_type.import_ = override_import
        data_type.alias = override_import.import_
        self.generation_store.replace_field_type(field, data_type)
        self.generation_store.detach_data_type_ref(field.data_type)
        self.generation_store.set_nested_data_types(field.data_type, [])

    @staticmethod
    def _append_model_import(model: DataModel, import_: Import) -> None:
        """Append an import to a model once."""
        if import_ in model.imports:
            return
        model._additional_imports.append(import_)  # noqa: SLF001

    def _apply_override_to_data_type(self, data_type: DataType) -> bool:
        """Recursively apply model-level overrides to a DataType."""
        overridden = False
        if data_type.reference and (override_import := self._model_type_override_imports.get(data_type.reference.name)):
            data_type.import_ = override_import
            data_type.alias = override_import.import_
            self.generation_store.detach_data_type_ref(data_type)
            overridden = True
        # Handle nested types (List[CustomType], Optional[CustomType], etc.)
        for nested in data_type.data_types:
            overridden = self._apply_override_to_data_type(nested) or overridden
        if data_type.dict_key:
            overridden = self._apply_override_to_data_type(data_type.dict_key) or overridden
        return overridden

    @staticmethod
    def __disable_union_operator_for_forward_ref_parents(data_type: DataType) -> None:
        parent = data_type.parent
        while isinstance(parent, DataType):
            if parent.is_union:
                parent.use_union_operator = False
            parent = parent.parent

    @classmethod
    def __update_type_aliases(
        cls,
        models: list[DataModel],
        pydantic_v2_root_model_type: type[DataModel] | None,
        *,
        use_deferred_annotations: bool = True,
    ) -> None:
        """Update type aliases and RootModels to properly handle forward references per PEP 484.

        When annotations are not deferred (no ``from __future__ import annotations`` and no
        native PEP 649 deferred annotations), self-referencing fields in regular DataModel
        classes also need their type aliased to a quoted string so that static analysers
        (e.g. Ruff F821) and older runtimes do not trip over the forward reference.
        """
        model_index: dict[str, int] = {m.class_name: i for i, m in enumerate(models)}

        for i, model in enumerate(models):
            is_type_alias_or_root = isinstance(model, TypeAliasBase) or _is_pydantic_v2_root_model(
                model,
                pydantic_v2_root_model_type,
            )
            # When annotations are deferred (from __future__ or native PEP-649) only
            # TypeAliasBase / RootModel need quoting; regular DataModels are fine as-is.
            # Class-body __annotations__ dicts are an exception: their right hand side is
            # evaluated immediately, so typed extra references still need forward refs.
            process_all_fields = is_type_alias_or_root or not use_deferred_annotations
            if not process_all_fields and not any(
                getattr(field, "use_pydantic_extra_annotations_dict", False) for field in model.fields
            ):
                continue
            if isinstance(model, TypeStatement):
                continue

            has_forward_ref = False
            for field in model.fields:
                if not process_all_fields and not getattr(field, "use_pydantic_extra_annotations_dict", False):
                    continue
                for data_type in field.data_type.all_data_types:
                    if not data_type.reference:
                        continue
                    source = data_type.reference.source
                    if not isinstance(source, DataModel):
                        continue  # pragma: no cover
                    if isinstance(source, TypeStatement):
                        continue  # pragma: no cover
                    if source.module_path != model.module_path:
                        continue
                    name = data_type.reference.short_name
                    source_index = model_index.get(name)
                    if source_index is not None and source_index >= i:
                        data_type.alias = f'"{name}"'
                        cls.__disable_union_operator_for_forward_ref_parents(data_type)
                        has_forward_ref = True

            if has_forward_ref:
                model.has_forward_reference = True

    @classmethod
    def __postprocess_result_modules(cls, results: dict[tuple[str, ...], Result]) -> dict[tuple[str, ...], Result]:
        def process(input_tuple: tuple[str, ...]) -> tuple[str, ...]:
            r = []
            for item in input_tuple:
                p = item.split(".")
                if len(p) > 1:
                    r.extend(p[:-1])
                    r.append(p[-1])
                else:
                    r.append(item)

            if len(r) >= 2:  # noqa: PLR2004
                r = [*r[:-2], f"{r[-2]}.{r[-1]}"]
            return tuple(r)

        results = {process(k): v for k, v in results.items()}

        init_result = next(v for k, v in results.items() if k[-1] == "__init__.py")
        folders = {t[:-1] if t[-1].endswith(".py") else t for t in results}
        for folder in folders:
            for i in range(len(folder)):
                subfolder = folder[: i + 1]
                init_file = (*subfolder, "__init__.py")
                results.update({init_file: init_result})
        return results

    def __change_imported_model_name(
        self,
        models: list[DataModel],
        imports: Imports,
        scoped_model_resolver: ModelResolver,
    ) -> None:
        imported_names = {
            imports.alias[from_][i] if i in imports.alias[from_] and i != imports.alias[from_][i] else i
            for from_, import_ in imports.items()
            for i in import_
        }
        for model in models:
            if model.class_name not in imported_names:  # pragma: no cover
                continue

            self.generation_store.update_model_reference(  # pragma: no cover
                model,
                reference_name=scoped_model_resolver.add(
                    path=get_special_path("imported_name", model.path.split("/")),
                    original_name=model.reference.name,
                    unique=True,
                    class_name=True,
                ).name,
            )

    def __alias_shadowed_imports(  # noqa: PLR6301
        self,
        models: list[DataModel],
        all_model_field_names: set[str],
    ) -> None:
        aliased_imports: dict[tuple[str | None, str], Import] = {}
        for _, _model_field, data_type in iter_models_field_data_types(models):
            if data_type and data_type.import_ and data_type.type in all_model_field_names:
                key = (data_type.import_.from_, data_type.import_.import_)
                if key not in aliased_imports:
                    aliased_imports[key] = Import(
                        from_=data_type.import_.from_,
                        import_=data_type.import_.import_,
                        alias=data_type.type + "_aliased",
                        reference_path=data_type.import_.reference_path,
                    )

        if not aliased_imports:
            return

        for _, _model_field, data_type in iter_models_field_data_types(models):
            if data_type and data_type.import_:
                key = (data_type.import_.from_, data_type.import_.import_)
                if key in aliased_imports:
                    aliased_import = aliased_imports[key]
                    data_type.type = aliased_import.alias
                    data_type.import_ = aliased_import

        for model in models:
            _alias_base_class_imports(model, aliased_imports)
        _clear_model_imports_cache(models)

    def __apply_generic_base_class(  # noqa: PLR0912, PLR0914, PLR0915
        self,
        processed_models: Sequence[ModuleContext],
    ) -> None:
        if not self.use_generic_base_class or not self.generic_base_class_config:
            return

        all_target_models: set[DataModel] = set()
        modules_with_targets: list[tuple[tuple[str, ...], list[DataModel], list[DataModel], Imports]] = []

        for module, _mod_key, models, _init, imports, _scoped_model_resolver in processed_models:
            if not models:  # pragma: no cover
                continue

            target_models = [
                m for m in models if m.SUPPORTS_GENERIC_BASE_CLASS and not isinstance(m, self.data_model_root_type)
            ]

            if target_models:
                modules_with_targets.append((module, models, target_models, imports))
                all_target_models.update(target_models)

        if not modules_with_targets:
            return

        root_modules: list[tuple[tuple[str, ...], list[DataModel], list[DataModel], Imports]] = []
        for module_entry in modules_with_targets:
            _module, _models, target_models, _imports = module_entry
            has_root_model = False
            for model in target_models:
                parent_refs = [bc.reference for bc in model.base_classes if bc.reference]
                has_target_model_parent = any(ref.source in all_target_models for ref in parent_refs)
                if not has_target_model_parent:
                    has_root_model = True
                    break
            if has_root_model:
                root_modules.append(module_entry)

        if not root_modules:  # pragma: no cover
            root_modules = [modules_with_targets[0]]

        first_root_module, first_root_models, first_root_target_models, _first_root_imports = root_modules[0]
        first_root_file_path = first_root_target_models[0].file_path if first_root_target_models else None

        base_class_ref = Reference(path=GENERIC_BASE_CLASS_PATH, name=GENERIC_BASE_CLASS_NAME)

        base_class_model = self.data_model_type.create_base_class_model(
            config=self.generic_base_class_config,
            reference=base_class_ref,
            custom_template_dir=self.custom_template_dir,
            keyword_only=self.keyword_only,
            treat_dot_as_module=self.treat_dot_as_module,
        )

        if base_class_model is None:
            return

        base_class_model.file_path = first_root_file_path
        first_root_models.insert(0, base_class_model)

        base_class_dt = BaseClassDataType(type=base_class_ref.name, reference=base_class_ref)

        original_base_class = self.data_model_type.BASE_CLASS
        original_import = Import.from_full_path(original_base_class) if original_base_class else None
        first_root_module_name = ".".join(first_root_module[:-1]) if first_root_module else ""
        for module, _models, target_models, imports in modules_with_targets:
            current_module_name = ".".join(module[:-1]) if module else ""
            is_first_root = module == first_root_module
            for model in target_models:
                if original_import:  # pragma: no branch
                    additional_imports = model._additional_imports  # noqa: SLF001
                    model._additional_imports = [i for i in additional_imports if i != original_import]  # noqa: SLF001
                parent_refs = [bc.reference for bc in model.base_classes if bc.reference]
                has_target_model_parent = any(ref.source in all_target_models for ref in parent_refs)
                if has_target_model_parent:
                    pass
                elif parent_refs:  # pragma: no cover
                    self.generation_store.set_base_classes(model, [base_class_dt, *model.base_classes])
                else:
                    self.generation_store.set_base_classes(model, [base_class_dt])
            if not is_first_root and original_import:
                imports.remove(original_import)
                from_ = relative(current_module_name, first_root_module_name)[0]
                from_ = (
                    f"{from_}{first_root_module[-1].replace('.py', '')}"
                    if from_.endswith(".")
                    else f"{from_}.{first_root_module[-1].replace('.py', '')}"
                )
                imports.append(Import(from_=from_, import_=base_class_ref.name))

    @classmethod
    def _collect_exports_for_init(
        cls,
        module: tuple[str, ...],
        processed_models: Sequence[ModuleContext],
        scope: AllExportsScope,
    ) -> list[tuple[str, tuple[str, ...], str]]:
        """Collect exports for __init__.py based on scope."""
        exports: list[tuple[str, tuple[str, ...], str]] = []
        normalized_module = tuple(part.replace("-", "_") for part in module)
        base = normalized_module[:-1] if normalized_module[-1] == "__init__.py" else normalized_module
        base_len = len(base)

        for proc_module, _, proc_models, _, _, _ in processed_models:
            normalized_proc_module = tuple(part.replace("-", "_") for part in proc_module)
            if not proc_models or normalized_proc_module == normalized_module:
                continue
            last = normalized_proc_module[-1]
            prefix = normalized_proc_module[:-1] if last == "__init__.py" else (*normalized_proc_module[:-1], last[:-3])
            if prefix[:base_len] != base or (depth := len(prefix) - base_len) < 1:
                continue
            if scope == AllExportsScope.Children and depth != 1:
                continue
            rel = prefix[base_len:]
            exports.extend(
                (ref.short_name, rel, ".".join(rel))
                for m in proc_models
                if (ref := m.reference) and not ref.short_name.startswith("_")
            )
        return exports

    @classmethod
    def _resolve_export_collisions(
        cls,
        exports: list[tuple[str, tuple[str, ...], str]],
        strategy: AllExportsCollisionStrategy | None,
        reserved: set[str] | None = None,
    ) -> dict[str, list[tuple[str, tuple[str, ...], str]]]:
        """Resolve name collisions in exports based on strategy."""
        reserved = reserved or set()
        by_name: dict[str, list[tuple[str, tuple[str, ...], str]]] = {}
        for item in exports:
            by_name.setdefault(item[0], []).append(item)

        if not (colliding := {n for n, items in by_name.items() if len(items) > 1 or n in reserved}):
            return dict(by_name)
        if (effective := strategy or AllExportsCollisionStrategy.Error) == AllExportsCollisionStrategy.Error:
            cls._raise_collision_error(by_name, colliding)

        used: set[str] = {n for n in by_name if n not in colliding} | reserved
        result = {n: items for n, items in by_name.items() if n not in colliding}

        for name in sorted(colliding):
            for item in sorted(by_name[name], key=lambda x: len(x[1])):
                new_name = cls._make_prefixed_name(
                    item[0], item[1], used, minimal=effective == AllExportsCollisionStrategy.MinimalPrefix
                )
                if new_name in reserved:
                    msg = (
                        f"Cannot resolve collision: '{new_name}' conflicts with __init__.py model. "
                        "Please rename one of the models."
                    )
                    raise Error(msg)
                result[new_name] = [item]
                used.add(new_name)
        return result

    @classmethod
    def _raise_collision_error(  # pragma: no cover
        cls,
        by_name: dict[str, list[tuple[str, tuple[str, ...], str]]],
        colliding: set[str],
    ) -> None:
        """Raise an error with collision details."""
        details = []
        for n in colliding:
            if len(items := by_name[n]) > 1:
                details.append(f"  '{n}' is defined in: {', '.join(f'.{s}' for _, _, s in items)}")
            else:
                details.append(f"  '{n}' conflicts with a model in __init__.py")
        raise Error(
            "Name collision detected with --all-exports-scope:\n"
            + "\n".join(details)
            + "\n\nUse --all-exports-collision-strategy to specify how to handle collisions."
        )

    @staticmethod
    def _make_prefixed_name(name: str, path: tuple[str, ...], used: set[str], *, minimal: bool) -> str:
        """Generate a prefixed name, using minimal or full prefix."""
        if minimal:
            for depth in range(1, len(path) + 1):
                if (candidate := "".join(p.title().replace("_", "") for p in path[-depth:]) + name) not in used:
                    return candidate
        return "".join(p.title().replace("_", "") for p in path) + name

    @classmethod
    def _build_all_exports_code(
        cls,
        resolved: dict[str, list[tuple[str, tuple[str, ...], str]]],
    ) -> Imports:
        """Build import statements from resolved exports."""
        export_imports = Imports()
        for export_name, items in resolved.items():
            for orig, _, short in items:
                export_imports.append(
                    Import(from_=f".{short}", import_=orig, alias=export_name if export_name != orig else None)
                )
        return export_imports

    @classmethod
    def _collect_used_names_from_models(
        cls,
        models: list[DataModel],
        model_imports: Mapping[DataModel, tuple[Import, ...]] | None = None,
    ) -> set[str]:
        """Collect identifiers referenced by models before rendering."""
        names: set[str] = set()

        def add(name: str | None) -> None:
            if not name:
                return
            names.add(name.split(".")[0])

        def collect_data_type_names(data_type: DataType) -> None:
            add(data_type.alias or data_type.type)
            if data_type.reference:
                add(data_type.reference.short_name)

        for model in models:
            add(model.class_name)
            add(model.duplicate_class_name)
            for base in model.base_classes:
                add(base.type_hint)
            imports = model_imports[model] if model_imports is not None else model.imports
            for import_ in imports:
                add(import_.alias or import_.import_.split(".")[-1])
            for field in model.fields:
                if field.extras.get("is_classvar"):
                    continue
                add(field.name)
                add(field.alias)
                field.data_type.walk(collect_data_type_names)
        return names

    def __generate_forwarder_content(  # noqa: PLR6301
        self,
        original_module: tuple[str, ...],
        internal_module: tuple[str, ...],
        class_mappings: list[tuple[str, str]],
        *,
        is_init: bool = False,
    ) -> str:
        """Generate forwarder module content that re-exports classes from _internal.

        Args:
            original_module: The original module tuple (e.g., ("issuing",) or ())
            internal_module: The _internal module tuple (e.g., ("_internal",))
            class_mappings: List of (original_name, new_name) tuples, sorted by original_name
            is_init: True if this is a package __init__.py, False for regular .py files

        Returns:
            The forwarder module content as a string
        """
        original_str = ".".join(original_module)
        internal_str = ".".join(internal_module)
        from_dots, module_name = relative(original_str, internal_str, reference_is_module=True, current_is_init=is_init)
        relative_import = f"{from_dots}{module_name}"

        imports = Imports()
        for original_name, new_name in class_mappings:
            if original_name == new_name:
                imports.append(Import(from_=relative_import, import_=new_name))
            else:
                imports.append(Import(from_=relative_import, import_=new_name, alias=original_name))

        return f"{imports.dump()}\n\n{imports.dump_all()}\n"

    def __compute_internal_module_path(  # noqa: PLR6301
        self,
        scc_modules: set[tuple[str, ...]],
        existing_modules: set[tuple[str, ...]],
        *,
        base_name: str = "_internal",
    ) -> tuple[str, ...]:
        """Compute the internal module path for an SCC."""
        directories = [get_module_directory(m) for m in sorted(scc_modules)]

        if not directories or any(not d for d in directories):
            prefix: tuple[str, ...] = ()
        else:
            path_strings = ["/".join(d) for d in directories]
            common = os.path.commonpath(path_strings)
            prefix = tuple(common.split("/")) if common else ()

        base_module = (base_name,) if not prefix else (*prefix, base_name)

        if base_module in existing_modules:
            counter = 1
            while True:
                candidate = (*prefix, f"{base_name}_{counter}") if prefix else (f"{base_name}_{counter}",)
                if candidate not in existing_modules:
                    return candidate
                counter += 1

        return base_module

    def __collect_scc_models(  # noqa: PLR6301
        self,
        scc: set[tuple[str, ...]],
        result_modules: dict[tuple[str, ...], list[DataModel]],
    ) -> tuple[list[DataModel], dict[int, tuple[str, ...]]]:
        """Collect all models from SCC modules.

        Returns:
            - List of all models in the SCC
            - Mapping from model id to its original module
        """
        all_models: list[DataModel] = []
        model_to_module: dict[int, tuple[str, ...]] = {}
        for scc_module in sorted(scc):
            for model in result_modules[scc_module]:
                all_models.append(model)
                model_to_module[id(model)] = scc_module
        return all_models, model_to_module

    def __rename_and_relocate_scc_models(
        self,
        all_scc_models: list[DataModel],
        model_to_original_module: dict[int, tuple[str, ...]],
        internal_module: tuple[str, ...],
        internal_path: Path,
    ) -> tuple[defaultdict[tuple[str, ...], list[tuple[str, str]]], dict[str, str]]:
        """Rename duplicate classes and relocate models to internal module.

        Returns:
            Tuple of:
            - Mapping from original module to list of (original_name, new_name) tuples.
            - Mapping from old reference paths to new reference paths.
        """
        class_name_counts = Counter(model.class_name for model in all_scc_models)
        class_name_seen: dict[str, int] = {}
        internal_module_str = ".".join(internal_module)
        module_class_mappings: defaultdict[tuple[str, ...], list[tuple[str, str]]] = defaultdict(list)
        path_mapping: dict[str, str] = {}

        with self.generation_store.defer_refresh():
            for model in all_scc_models:
                original_class_name = model.class_name
                original_module = model_to_original_module[id(model)]
                old_path = model.path  # Save old path before updating

                if class_name_counts[original_class_name] > 1:
                    seen_count = class_name_seen.get(original_class_name, 0)
                    new_class_name = f"{original_class_name}_{seen_count}" if seen_count > 0 else original_class_name
                    class_name_seen[original_class_name] = seen_count + 1
                else:
                    new_class_name = original_class_name

                new_path = f"{internal_module_str}.{new_class_name}"
                self.generation_store.update_model_reference(
                    model,
                    reference_name=new_class_name,
                    new_path=new_path,
                    new_file_path=internal_path,
                )

                module_class_mappings[original_module].append((original_class_name, new_class_name))
                path_mapping[old_path] = new_path

        return module_class_mappings, path_mapping

    def __build_module_dependency_graph(
        self,
        module_models_list: list[tuple[tuple[str, ...], list[DataModel]]],
    ) -> dict[tuple[str, ...], set[tuple[str, ...]]]:
        """Build a directed graph of module dependencies."""
        path_to_module: dict[str, tuple[str, ...]] = {}
        for module, models in module_models_list:
            for model in models:
                path_to_module[model.path] = module

        graph: dict[tuple[str, ...], set[tuple[str, ...]]] = {}

        def add_cross_module_edge(ref_path: str, source_module: tuple[str, ...]) -> None:
            """Add edge if ref_path points to a different module."""
            if ref_path in path_to_module:
                target_module = path_to_module[ref_path]
                if target_module != source_module:
                    graph[source_module].add(target_module)

        for module, models in module_models_list:
            graph[module] = set()

            for model in models:
                for reference_path in self.generation_store.index.reference_classes_for_model(model):
                    add_cross_module_edge(reference_path, module)

        return graph

    def __resolve_circular_imports(  # noqa: PLR0914
        self,
        module_models_list: list[tuple[tuple[str, ...], list[DataModel]]],
    ) -> tuple[
        list[tuple[tuple[str, ...], list[DataModel]]],
        set[tuple[str, ...]],
        dict[tuple[str, ...], tuple[tuple[str, ...], list[tuple[str, str]]]],
        dict[str, str],
    ]:
        """Resolve circular imports by merging all SCCs into _internal.py modules.

        Uses Tarjan's algorithm to find strongly connected components (SCCs) in the
        module dependency graph. All modules in each SCC are merged into a single
        _internal.py module to break import cycles. Original modules become thin
        forwarders that re-export their classes from _internal.

        Returns:
            - Updated module_models_list with models moved to _internal modules
            - Set of _internal modules created
            - Forwarder map: original_module -> (internal_module, [(original_name, new_name)])
            - Path mapping: old_reference_path -> new_reference_path
        """
        graph = self.__build_module_dependency_graph(module_models_list)

        circular_sccs = find_circular_sccs(graph)

        forwarder_map: dict[tuple[str, ...], tuple[tuple[str, ...], list[tuple[str, str]]]] = {}
        all_path_mappings: dict[str, str] = {}

        if not circular_sccs:
            return module_models_list, set(), forwarder_map, all_path_mappings

        # All circular SCCs are problematic and should be merged into _internal.py
        # to break the import cycles.
        problematic_sccs = circular_sccs

        existing_modules = {module for module, _ in module_models_list}
        internal_modules_created: set[tuple[str, ...]] = set()

        result_modules: dict[tuple[str, ...], list[DataModel]] = {
            module: list(models) for module, models in module_models_list
        }

        for scc in problematic_sccs:
            internal_module = self.__compute_internal_module_path(scc, existing_modules | internal_modules_created)
            internal_modules_created.add(internal_module)
            internal_path = Path("/".join(internal_module))

            all_scc_models, model_to_original_module = self.__collect_scc_models(scc, result_modules)
            module_class_mappings, path_mapping = self.__rename_and_relocate_scc_models(
                all_scc_models, model_to_original_module, internal_module, internal_path
            )
            all_scc_models = _sort_internal_module_models(all_scc_models, self.pydantic_v2_root_model_type)
            all_path_mappings.update(path_mapping)

            for scc_module in scc:
                if scc_module in result_modules:  # pragma: no branch
                    result_modules[scc_module] = []
                if scc_module in module_class_mappings:  # pragma: no branch
                    sorted_mappings = sorted(module_class_mappings[scc_module], key=operator.itemgetter(0))
                    forwarder_map[scc_module] = (internal_module, sorted_mappings)
            result_modules[internal_module] = all_scc_models

        new_module_models: list[tuple[tuple[str, ...], list[DataModel]]] = [
            (internal_module, result_modules[internal_module])
            for internal_module in sorted(internal_modules_created)
            if internal_module in result_modules  # pragma: no branch
        ]

        for module, _ in module_models_list:
            if module not in internal_modules_created:  # pragma: no branch
                new_module_models.append((module, result_modules.get(module, [])))

        return new_module_models, internal_modules_created, forwarder_map, all_path_mappings

    def __get_resolve_reference_action_parts(
        self,
        models: list[DataModel],
        require_update_action_models: list[str],
        *,
        use_deferred_annotations: bool,
    ) -> list[str]:
        """Return the trailing rebuild/update calls for the given module's models."""
        if self.dump_resolve_reference_action is None:
            return []

        require_update_action_model_paths = set(require_update_action_models)
        required_paths_in_module = {m.path for m in models if m.path in require_update_action_model_paths}

        if (
            use_deferred_annotations
            and required_paths_in_module
            and _is_pydantic_v2_dump_resolve_reference_action(self.dump_resolve_reference_action)
        ):
            module_positions = {m.reference.short_name: i for i, m in enumerate(models) if m.reference}
            module_model_names = set(module_positions)

            forward_needed: set[str] = set()
            for model in models:
                if model.path not in required_paths_in_module or not model.reference:
                    continue
                name = model.reference.short_name
                pos = module_positions[name]
                refs = {
                    t.reference.short_name
                    for f in model.fields
                    for t in f.data_type.all_data_types
                    if t.reference and t.reference.short_name in module_model_names
                }
                if name in refs or any(module_positions.get(r, -1) > pos for r in refs):
                    forward_needed.add(model.path)

            # Propagate requirement through inheritance.
            changed = True
            required_filtered = set(forward_needed)
            while changed:
                changed = False
                for model in models:
                    if not model.reference or model.path in required_filtered:
                        continue
                    base_paths = {b.reference.path for b in model.base_classes if b.reference}
                    if base_paths & required_filtered:
                        required_filtered.add(model.path)
                        changed = True

            required_paths_in_module = required_filtered

        return [
            "\n",
            self.dump_resolve_reference_action(
                m.reference.short_name for m in models if m.reference and m.path in required_paths_in_module
            ),
        ]

    def _prepare_parse_config(
        self,
        with_import: bool | None,  # noqa: FBT001
        disable_future_imports: bool,  # noqa: FBT001
        all_exports_scope: AllExportsScope | None,
        all_exports_collision_strategy: AllExportsCollisionStrategy | None,
        module_split_mode: ModuleSplitMode | None,
    ) -> ParseConfig:
        """Prepare configuration for the parse operation."""
        use_deferred_annotations = bool(
            self.target_python_version.has_native_deferred_annotations or (with_import and not disable_future_imports)
        )

        if (
            with_import
            and not disable_future_imports
            and not self.target_python_version.has_native_deferred_annotations
        ):
            self.imports.append(IMPORT_ANNOTATIONS)

        return ParseConfig(
            with_import=bool(with_import),
            use_deferred_annotations=use_deferred_annotations,
            code_formatter=None,
            module_split_mode=module_split_mode,
            all_exports_scope=all_exports_scope,
            all_exports_collision_strategy=all_exports_collision_strategy,
        )

    def _build_code_formatter(
        self,
        settings_path: Path | None,
        *,
        is_multi_module_output: bool,
    ) -> CodeFormatter:
        from datamodel_code_generator.format import CodeFormatter, resolve_use_type_checking_imports  # noqa: PLC0415

        effective_use_type_checking_imports = resolve_use_type_checking_imports(
            self.use_type_checking_imports,
            is_multi_module_output=is_multi_module_output,
            formatters=self.formatters,
            requires_runtime_imports_with_ruff_check=self.data_model_type.REQUIRES_RUNTIME_IMPORTS_WITH_RUFF_CHECK,
        )
        return CodeFormatter(
            self.target_python_version,
            settings_path,
            self.wrap_string_literal,
            skip_string_normalization=not self.use_double_quotes,
            known_third_party=self.known_third_party,
            custom_formatters=self.custom_formatter,
            custom_formatters_kwargs=self.custom_formatters_kwargs,
            encoding=self.encoding,
            formatters=self.formatters,
            builtin_format_line_length=self.builtin_format_line_length,
            use_type_checking_imports=effective_use_type_checking_imports,
            defer_formatting=self.defer_formatting,
        )

    def _build_module_structure(
        self,
        sorted_data_models: SortedDataModels,
        require_update_action_models: list[str],
        module_split_mode: ModuleSplitMode | None,
    ) -> tuple[
        ModuleModels,
        set[ModulePath],
        ForwarderMap,
        dict[str, str],
        dict[DataModel, tuple[ModulePath, list[DataModel]]],
        dict[str, str],
    ]:
        """Build module structure from sorted models."""

        def module_key(data_model: DataModel) -> ModulePath:
            if module_split_mode == ModuleSplitMode.Single:
                file_name = camel_to_snake(data_model.class_name)
                return (*data_model.module_path, file_name)
            return tuple(data_model.module_path)

        def sort_key(data_model: DataModel) -> tuple[int, ModulePath]:
            key = module_key(data_model)
            return (len(key), key)

        grouped_models = groupby(
            sorted(sorted_data_models.values(), key=sort_key, reverse=True),
            key=module_key,
        )

        module_models: ModuleModels = []
        model_to_module_models: dict[DataModel, tuple[ModulePath, list[DataModel]]] = {}
        model_path_to_module_name: dict[str, str] = {}

        previous_module: ModulePath = ()
        for module, models in ((k, [*v]) for k, v in grouped_models):
            for model in models:
                model_to_module_models[model] = module, models
                if module_split_mode == ModuleSplitMode.Single:
                    model_path_to_module_name[model.path] = ".".join(module)
            self.__delete_duplicate_models(models)
            self.__replace_duplicate_name_in_module(models)
            if len(previous_module) - len(module) > 1:
                module_models.extend(
                    (previous_module[:parts], []) for parts in range(len(previous_module) - 1, len(module), -1)
                )
            module_models.append((module, models))
            previous_module = module

        shared_module_entry = self.__reuse_model_tree_scope(module_models, require_update_action_models)
        if shared_module_entry:
            module_models.insert(0, shared_module_entry)

        module_models, internal_modules, forwarder_map, path_mapping = self.__resolve_circular_imports(module_models)

        if path_mapping:
            require_update_action_models[:] = [path_mapping.get(path, path) for path in require_update_action_models]

        return (
            module_models,
            internal_modules,
            forwarder_map,
            path_mapping,
            model_to_module_models,
            model_path_to_module_name,
        )

    def _process_single_module(  # noqa: PLR0913, PLR0917
        self,
        module_: ModulePath,
        models: list[DataModel],
        results: dict[ModulePath, Result],
        config: ParseConfig,
        internal_modules: set[ModulePath],
        model_path_to_module_name: dict[str, str],
        require_update_action_models: list[str],
        unused_models: list[DataModel],
    ) -> ModuleContext:
        """Process a single module and return its context."""
        imports = Imports(self.use_exact_imports)
        module, is_init = _resolve_module_file(module_, results)

        all_module_fields = {field.name for model in models for field in model.fields if field.name is not None}
        scoped_model_resolver = ModelResolver(exclude_names=all_module_fields)

        self.__alias_shadowed_imports(models, all_module_fields)
        self.__override_required_field(models)
        self.__replace_unique_list_to_set(models)
        self.__change_from_import(
            models,
            imports,
            scoped_model_resolver,
            init=is_init,
            internal_modules=internal_modules,
            model_path_to_module_name=model_path_to_module_name,
        )
        self.__extract_inherited_enum(models)
        self.__set_reference_default_value_to_field(models)
        self.__reuse_model(models, require_update_action_models)
        self.__collapse_root_models(models, unused_models, imports, scoped_model_resolver, model_path_to_module_name)
        self.__set_default_enum_member(models)
        self.__sort_models(models, imports, use_deferred_annotations=config.use_deferred_annotations)
        self.__change_field_name(models)
        self.__apply_discriminator_type(models, imports)
        self.__set_one_literal_on_default(models)
        self.__fix_dataclass_field_ordering(models)
        models = self.__remove_overridden_models(models)
        self.__apply_type_overrides(models)
        self.__update_type_aliases(
            models, self.pydantic_v2_root_model_type, use_deferred_annotations=config.use_deferred_annotations
        )
        self.__set_validate_default_on_fields(models)
        _clear_model_imports_cache(models)

        return ModuleContext(module, module_, models, is_init, imports, scoped_model_resolver)

    def _finalize_modules(
        self,
        contexts: list[ModuleContext],
        unused_models: list[DataModel],
        model_to_module_models: dict[DataModel, tuple[ModulePath, list[DataModel]]],
        module_to_import: dict[ModulePath, Imports],
    ) -> None:
        """Finalize module processing: apply generic base class and remove unused imports."""
        all_models = [model for ctx in contexts for model in ctx.models]
        self.__mark_set_item_models_hashable(all_models)
        self.__apply_generic_base_class(contexts)
        model_imports = {model: model.imports for ctx in contexts for model in ctx.models}

        for ctx in contexts:
            for model in ctx.models:
                ctx.imports.append(model_imports[model])

        for unused_model in unused_models:
            module, models = model_to_module_models[unused_model]
            if unused_model in models:  # pragma: no branch
                imports = module_to_import[module]
                imports.remove(model_imports.get(unused_model, unused_model.imports))
                models.remove(unused_model)

        for ctx in contexts:
            used_names = self._collect_used_names_from_models(ctx.models, model_imports)
            ctx.imports.remove_unused(used_names)

        for ctx in contexts:
            # If any model in this module needs typing_extensions.TypedDict (e.g. for PEP 728
            # closed/extra_items backport), remove typing.TypedDict to avoid duplicate imports.
            if (
                any(IMPORT_TYPED_DICT_BACKPORT in model_imports[model] for model in ctx.models)
                and IMPORT_TYPED_DICT_BACKPORT.import_ in ctx.imports.get(IMPORT_TYPED_DICT_BACKPORT.from_, set())
                and IMPORT_TYPED_DICT.import_ in ctx.imports.get(IMPORT_TYPED_DICT.from_, set())
            ):
                while ctx.imports.counter.get((IMPORT_TYPED_DICT.from_, IMPORT_TYPED_DICT.import_), 0) > 0:
                    ctx.imports.remove(IMPORT_TYPED_DICT)

        for ctx in contexts:
            self.__change_imported_model_name(ctx.models, ctx.imports, ctx.scoped_model_resolver)
            self.__set_validate_default_on_fields(ctx.models)

    def _generate_module_output(  # noqa: PLR0913, PLR0917
        self,
        ctx: ModuleContext,
        config: ParseConfig,
        contexts: list[ModuleContext],
        forwarder_map: ForwarderMap,
        require_update_action_models: list[str],
        future_imports_str: str,
    ) -> Result | None:
        """Generate output for a single module."""
        result: list[str] = []
        export_imports: Imports | None = None

        if config.all_exports_scope is not None and ctx.module[-1] == "__init__.py":
            child_exports = self._collect_exports_for_init(ctx.module, contexts, config.all_exports_scope)
            if child_exports:
                local_model_names = {
                    m.reference.short_name
                    for m in ctx.models
                    if m.reference and not m.reference.short_name.startswith("_")  # pragma: no branch
                }
                resolved_exports = self._resolve_export_collisions(
                    child_exports, config.all_exports_collision_strategy, local_model_names
                )
                export_imports = self._build_all_exports_code(resolved_exports)

        if ctx.models:
            if config.with_import:
                import_parts = [s for s in [future_imports_str, str(self.imports), str(ctx.imports)] if s]
                result += [*import_parts, "\n"]

            if export_imports:
                result += [str(export_imports), ""]
                for m in ctx.models:
                    if m.reference and not m.reference.short_name.startswith("_"):  # pragma: no branch
                        export_imports.add_export(m.reference.short_name)
                result += [export_imports.dump_all(multiline=True) + "\n"]

            module_code = self.data_model_type.render_module_code(ctx.models)
            if module_code:
                result += [module_code, ""]

            code = dump_templates(ctx.models)
            result += [code]

            result += self.__get_resolve_reference_action_parts(
                ctx.models,
                require_update_action_models,
                use_deferred_annotations=config.use_deferred_annotations,
            )

        if not result and ctx.module_key in forwarder_map:
            internal_module, class_mappings = forwarder_map[ctx.module_key]
            forwarder_content = self.__generate_forwarder_content(
                ctx.module_key, internal_module, class_mappings, is_init=ctx.is_init
            )
            result = [forwarder_content]

        if not result and not ctx.is_init:
            return None

        body = "\n".join(result)
        if config.code_formatter:
            body = _format_body_safe(body, config.code_formatter)

        return Result(
            body=body,
            future_imports=future_imports_str,
            source=ctx.models[0].file_path if ctx.models else None,
        )

    def _generate_empty_init_exports(
        self,
        results: dict[ModulePath, Result],
        contexts: list[ModuleContext],
        config: ParseConfig,
        future_imports_str: str,
    ) -> None:
        """Generate exports for empty __init__.py files."""
        if config.all_exports_scope is None:  # pragma: no cover
            return
        processed_init_modules = {ctx.module for ctx in contexts if ctx.module[-1] == "__init__.py"}
        for init_module, init_result in list(results.items()):
            if init_module[-1] != "__init__.py" or init_module in processed_init_modules or init_result.body:
                continue
            child_exports = self._collect_exports_for_init(init_module, contexts, config.all_exports_scope)
            if child_exports:
                resolved = self._resolve_export_collisions(child_exports, config.all_exports_collision_strategy, set())
                export_imports = self._build_all_exports_code(resolved)
                import_parts = [s for s in [future_imports_str, str(self.imports)] if s] if config.with_import else []
                parts = import_parts + (["\n"] if import_parts else [])
                parts += [str(export_imports), "", export_imports.dump_all(multiline=True)]
                body = "\n".join(parts)
                if config.code_formatter:
                    body = _format_body_safe(body, config.code_formatter)
                results[init_module] = Result(
                    body=body,
                    future_imports=future_imports_str,
                )

    @staticmethod
    def _field_metadata(field: DataModelFieldBase) -> ModelFieldMetadata:
        source_name = field.original_name or field.alias or field.name or ""
        return {
            "name": field.name or source_name,
            "alias": field.alias or source_name,
            "original_name": field.original_name,
            "type": field.type_hint,
            "required": field.required,
        }

    @classmethod
    def _model_metadata(
        cls,
        model: DataModel,
        module: ModulePath,
        source_reference_paths: Mapping[DataModel, str],
    ) -> GeneratedModelMetadata:
        source_ref = source_reference_paths[model]
        title = model.extra_template_data.get("title")
        return {
            "class_name": model.class_name,
            "name": model.name,
            "module": _module_name_from_module_path(module),
            "source_ref": source_ref,
            "source_path": _source_path_from_reference_path(source_ref),
            "title": title if isinstance(title, str) else None,
            "fields": [cls._field_metadata(field) for field in model.fields],
        }

    @classmethod
    def _build_model_metadata(
        cls,
        contexts: list[ModuleContext],
        source_reference_paths: Mapping[DataModel, str],
    ) -> ModelMetadata:
        return {
            "version": 1,
            "models": [
                cls._model_metadata(model, ctx.module, source_reference_paths)
                for ctx in contexts
                for model in ctx.models
                if model in source_reference_paths
            ],
        }

    def _dispose(self) -> None:
        """Break reference cycles in the parsed object graph.

        Models, fields, data types, and references point at each other in
        cycles, which keeps the whole graph alive until a full garbage
        collection pass. Severing the back references lets ordinary reference
        counting reclaim the graph as soon as the parser is dropped, which
        matters for processes that call generate() repeatedly.
        """
        self.generation_store._dispose(self.model_resolver.references.values())  # noqa: SLF001
        self.model_resolver.references.clear()
        self._reset_local_source_cache()

    def _reset_local_source_cache(self) -> None:
        self._cache_local_sources = False
        self._local_source_cache = None

    def parse(  # noqa: PLR0913, PLR0914, PLR0917
        self,
        with_import: bool | None = True,  # noqa: FBT001, FBT002
        format_: bool | None = True,  # noqa: FBT001, FBT002
        settings_path: Path | None = None,
        disable_future_imports: bool = False,  # noqa: FBT001, FBT002
        all_exports_scope: AllExportsScope | None = None,
        all_exports_collision_strategy: AllExportsCollisionStrategy | None = None,
        module_split_mode: ModuleSplitMode | None = None,
        collect_model_metadata: bool = False,  # noqa: FBT001, FBT002
    ) -> str | dict[tuple[str, ...], Result]:
        """Parse schema and generate code, returning single file or module dict."""
        self.parse_raw()

        config = self._prepare_parse_config(
            with_import,
            disable_future_imports,
            all_exports_scope,
            all_exports_collision_strategy,
            module_split_mode,
        )

        _, sorted_data_models, require_update_action_models = sort_data_models(
            self.results,
            generation_index=self.generation_store.index,
            pydantic_v2_root_model_type=self.pydantic_v2_root_model_type,
        )
        source_reference_paths: dict[DataModel, str] = {}
        if collect_model_metadata:
            source_reference_paths = {model: model.reference.path for model in sorted_data_models.values()}
        sort_base_classes_for_mro(sorted_data_models, self.generation_store)

        (
            module_models,
            internal_modules,
            forwarder_map,
            _path_mapping,
            model_to_module_models,
            model_path_to_module_name,
        ) = self._build_module_structure(sorted_data_models, require_update_action_models, module_split_mode)

        if format_:
            config = config._replace(
                code_formatter=self._build_code_formatter(
                    settings_path,
                    is_multi_module_output=self.defer_formatting or len(module_models) > 1,
                )
            )

        results: dict[ModulePath, Result] = {}
        unused_models: list[DataModel] = []
        module_to_import: dict[ModulePath, Imports] = {}
        contexts: list[ModuleContext] = []

        for module_, models in module_models:
            ctx = self._process_single_module(
                module_,
                models,
                results,
                config,
                internal_modules,
                model_path_to_module_name,
                require_update_action_models,
                unused_models,
            )
            module_to_import[module_] = ctx.imports
            contexts.append(ctx)

        self._finalize_modules(contexts, unused_models, model_to_module_models, module_to_import)

        root_init: ModulePath = ("__init__.py",)
        if root_init not in results:
            top_level_dirs = {k[0] for k in results if len(k) >= 2}  # noqa: PLR2004
            if len(top_level_dirs) > 1:
                results[root_init] = Result(body="")

        future_imports = self.imports.extract_future()
        future_imports_str = str(future_imports)

        for ctx in contexts:
            result = self._generate_module_output(
                ctx, config, contexts, forwarder_map, require_update_action_models, future_imports_str
            )
            if result is not None:
                results[ctx.module] = result

        if config.all_exports_scope is not None:
            self._generate_empty_init_exports(results, contexts, config, future_imports_str)

        if collect_model_metadata:
            self.model_metadata = self._build_model_metadata(contexts, source_reference_paths)
        else:
            self.model_metadata = None

        if [*results] == [("__init__.py",)]:
            single_result = results["__init__.py",]
            return single_result.body

        results = {tuple(i.replace("-", "_") for i in k): v for k, v in results.items()}
        return (
            self.__postprocess_result_modules(results)
            if self.treat_dot_as_module
            else {
                tuple((part[: part.rfind(".")].replace(".", "_") + part[part.rfind(".") :]) for part in k): v
                for k, v in results.items()
            }
        )
