# Source code for dataclass_wizard.property_wizard

    from dataclasses import MISSING, Field, field as dataclass_field
    from functools import wraps
    from typing import Dict, Any, Type, Union, Tuple, Optional

    from .constants import PY314_OR_ABOVE, PY310_OR_ABOVE
    from .type_def import T, NoneType
    from .utils.typing_compat import (
        get_origin, get_args, is_generic, is_literal, is_annotated, eval_forward_ref_if_needed
    )

    AnnotationType = Dict[str, Type[T]]
    AnnotationReplType = Dict[str, str]

    def get_resolved_annotations(obj) -> Dict[str, Any]:
        # Python 3.14+: annotationlib.get_annotations supports explicit formats
        if PY314_OR_ABOVE:
            from annotationlib import get_annotations, Format  # 3.14+
            return get_annotations(obj, format=Format.VALUE)

        # Python 3.10–3.13: inspect.get_annotations is best practice
        # eval_str=False keeps strings unresolved
        if PY310_OR_ABOVE:
            from inspect import get_annotations
            return get_annotations(obj, eval_str=True)

        # Python 3.9: use typing_extensions backport (supports get_annotations + format/eval_str behavior)
        from typing_extensions import get_annotations
        try:
            # newer typing_extensions mirrors 3.10+ signature
            return get_annotations(obj, eval_str=True)
        except TypeError:
            # ultra-defensive fallback
            return obj.__dict__.get("__annotations__", {}) or {}

    def property_wizard(*args, **kwargs):
        """
        Adds support for field properties with default values in dataclasses.

        For examples of usage, please see the `Using Field Properties`_ section in
        the docs. I also added `an answer`_ on a SO article that deals with using
        such properties in dataclasses.

        .. _Using Field Properties: https://dcw.ritviknag.com/en/latest/using_field_properties.html
        .. _an answer: https://stackoverflow.com/a/68488125/10237506
        """
        cls: Type = type(*args, **kwargs)
        cls_dict: Dict[str, Any] = args[2]
        # https://docs.python.org/3.14/whatsnew/3.14.html#implications-for-readers-of-annotations
        annotations: AnnotationType = get_resolved_annotations(cls)

        # For each property, we want to replace the annotation for the underscore-
        # leading field associated with that property with the 'public' field
        # name, and this mapping helps us keep a track of that.
        annotation_repls: AnnotationReplType = {}

        for f, val in cls_dict.items():

            if isinstance(val, property):

                if val.fset is None:
                    # The property is read-only, not settable
                    continue

                if not f.startswith('_'):
                    # The property is marked as 'public' (i.e. no leading
                    # underscore)
                    _process_public_property(
                        cls, f, val, annotations, annotation_repls)
                else:
                    # The property is marked as 'private'
                    _process_underscored_property(
                        cls, f, val, annotations, annotation_repls)

        if annotation_repls:
            # Use a comprehension approach because we want to replace a
            # key while preserving the insertion order, because the order
            # of fields does matter when the constructor is called.
            cls.__annotations__ = {annotation_repls.get(f, f): ftype
                                   for f, ftype in annotations.items()}

        return cls

    def _process_public_property(cls: Type, public_f: str, val: property,
                                 annotations: AnnotationType,
                                 annotation_repls: AnnotationReplType):
        """
        Handles the case when the property is marked as 'public' (i.e. no leading
        underscore)
        """

        # The field with a leading underscore
        under_f = '_' + public_f

        # The field value that defines either a `default` or `default_factory`
        fval: Field = dataclass_field()

        # This flag is used to keep a track of whether we already have a default
        # value set (either from the public or the underscored field)
        is_set: bool = False

        if public_f not in annotations and under_f not in annotations:
            # adding this to check if it's a regular property (not
            # associated with a dataclass field)
            return

        if under_f in annotations:
            # Also add it to the list of class annotations to replace later
            #   (this is what `dataclasses` uses to add the field to the
            #   constructor)
            annotation_repls[under_f] = public_f

            try:
                # Get the value of the underscored field
                v = getattr(cls, under_f)
            except AttributeError:
                # The underscored field is probably type-annotated but not defined
                #   i.e. my_var: str
                fval = _default_from_annotation(cls, annotations, under_f)
            else:
                # Check if the value of underscored field is a dataclass Field. If
                # so, we can use the `default` or `default_factory` if one is set.
                if isinstance(v, Field):
                    fval, is_set = _process_field(cls, annotations, under_f, v)
                else:
                    fval.default = v
                    is_set = True
                # Delete the field that starts with an underscore. This is needed
                # since we'll be replacing the annotation for `under_f` later, and
                # `dataclasses` will complain if it sees a variable which is a
                # `Field` that appears to be missing a type annotation.
                delattr(cls, under_f)

        if public_f in annotations and not is_set:
            fval = _default_from_annotation(cls, annotations, public_f)

        # Wraps the `setter` for the property
        val = val.setter(_wrapper(val.fset, fval))

        # Set the field that does not start with an underscore
        setattr(cls, public_f, val)

    def _process_underscored_property(cls: Type, under_f: str, val: property,
                                      annotations: AnnotationType,
                                      annotation_repls: AnnotationReplType):
        """
        Handles the case when the property is marked as 'private' (i.e. leads with
        an underscore)
        """

        # The field *without* a leading underscore
        public_f = under_f.lstrip('_')

        # The field value that defines either a `default` or `default_factory`
        fval: Field = dataclass_field()

        if public_f not in annotations and under_f not in annotations:
            # adding this to check if it's a regular property (not
            # associated with a dataclass field)
            return

        if under_f in annotations:
            # Also add it to the list of class annotations to replace later
            #   (this is what `dataclasses` uses to add the field to the
            #   constructor)
            annotation_repls[under_f] = public_f
            fval = _default_from_annotation(cls, annotations, under_f)

        if public_f in annotations:
            # First, get the type annotation for the public field
            fval = _default_from_annotation(cls, annotations, public_f)

            if hasattr(cls, public_f):
                # Get the value of the field without a leading underscore
                v = getattr(cls, public_f)
                # Check if the value of public field is a dataclass Field. If so,
                # we can use the `default` or `default_factory` if one is set.
                if isinstance(v, Field):
                    fval = _process_field(cls, annotations, public_f, v)[0]
                else:
                    fval.default = v

        # Wraps the `setter` for the property
        val = val.setter(_wrapper(val.fset, fval))

        # Replace the value of the field without a leading underscore
        setattr(cls, public_f, val)

        # Delete the property associated with the underscored field name.
        # This is technically not needed, but it supports cases where we
        # define an attribute with the same name as the property, i.e.
        #    @property
        #    def _wheels(self)
        #        return self._wheels
        delattr(cls, under_f)

    def _process_field(cls: Type, cls_annotations: AnnotationType,
                       field: str, field_val: Field) -> Tuple[Field, bool]:
        """
        Get the default value for `field`, which is defined as a
        :class:`dataclasses.Field`.

        Returns a two-element tuple of (fval, is_set), where `is_set` will be
        False when no `default` or `default_factory` is defined for the Field;
        in that case, `fval` will be the default value from the annotated type
        instead.
        """

        if field_val.default is not MISSING:
            return field_val, True
        elif field_val.default_factory is not MISSING:
            return field_val, True
        else:
            field_val = _default_from_annotation(cls, cls_annotations, field)
            return field_val, False

    def _default_from_annotation(
            cls: Type, cls_annotations: AnnotationType, field: str) -> Field:
        """
        Get the default value for the type annotated on a field. Note that we
        include a check to see if the annotated type is a `Generic` type from the
        ``typing`` module.
        """

        default_type = cls_annotations.get(field)

        try:
            default_type = eval_forward_ref_if_needed(default_type, cls)
        except NameError:
            # Since we are run as a metaclass, we can only evaluate types that are
            # available when the base class `cls` is declared; thus, we can run
            # into an error when the annotation has a forward reference to a class
            # or type that is not yet defined.
            default_type = None

        if is_generic(default_type):
            # Annotated type is a Generic from the `typing` module
            return _default_from_generic_type(cls, default_type, field)

        return _default_from_type(default_type)

    def _default_from_type(default_type: Type[T]) -> Field:
        """
        Get the default value for a type. If it's a mutable type, we want to
        use the `default_factory` instead; otherwise, we just use the default
        value from the no-args constructor for the type.
        """

        try:
            # Check if it's callable with no args
            default = default_type()
        except TypeError:
            return dataclass_field()
        else:
            # Check for mutable types, as they need to use a default factory.
            if isinstance(default, (list, dict, set)):
                return dataclass_field(default_factory=default_type)
            # Else, we can just return the default value without a factory.
            return dataclass_field(default=default)

    def _default_from_generic_type(
            cls: Type,
            default_type: Type[T],
            field: Optional[str] = None) -> Field:
        """
        Process a Generic type from the `typing` module, and return the default
        value (or default factory) for the annotated type.
        """

        args = get_args(default_type)
        origin = get_origin(default_type)

        if is_annotated(default_type):
            # The Generic type appears as `Annotated[T, extras...]`
            default_type, *extras = args
            # Loop over and search for any `dataclasses.Field` types
            for extra in extras:
                if isinstance(extra, Field):
                    return _process_field(
                        cls, {field: default_type}, field, extra)[0]
            # Else, if none of the extras are particularly useful, just process
            # type `T`, which can be either a concrete or Generic sub-type.
            return _default_from_annotation(cls, {field: default_type}, field)

        if is_literal(default_type):
            # The Generic type appears as `Literal["r", "r+", ...]`
            return dataclass_field(default=_default_from_typing_args(args))

        if origin is Union:
            # The Generic type appears as `Optional[T]` or `Union[T1, T2, ...]`
            default_type = _default_from_typing_args(args)
            return _default_from_type(default_type)

        return _default_from_type(origin)

    def _default_from_typing_args(args: Optional[Tuple[Type[T], ...]]):
        """
        `args` is the type arguments for a generic annotated type from the
        ``typing`` module. For example, given a generic type `Union[str, int]`,
        the args will be a tuple of (str, int).

        If `None` is included in the typed args for `cls`, then it's perfectly
        valid to return `None` as the default. Otherwise, we'll just use the first
        type in the list of args.

        """

        if args and NoneType not in args:
            try:
                return args[0]
            except TypeError:   # pragma: no cover
                return None
        return None

    def _wrapper(fset, fval: Field):
        """
        Wraps the property `setter` method to check if we are passed in a property
        object itself, which will be true when no initial value is specified.

        ``fval`` here is a :class:`dataclasses.Field` that contains either a
        `default` or `default_factory`.
        """

        if fval.default_factory is not MISSING:
            # The initial value for the property is returned from a default
            # factory.
            default_factory = fval.default_factory

            @wraps(fset)
            def new_fset(self, value):
                if isinstance(value, property):
                    value = default_factory()
                fset(self, value)

        else:
            # The initial value for the property is just a default value.
            default = None if fval.default is MISSING else fval.default

            @wraps(fset)
            def new_fset(self, value):
                if isinstance(value, property):
                    value = default
                fset(self, value)

        return new_fset