"""
Silique (https://www.silique.fr)
Copyright (C) 2024-2025

This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

from warnings import warn
from typing import Optional
from itertools import chain
from re import compile

from tiramisu import Calculation, groups
from tiramisu.error import ConfigError, display_list, PropertiesOptionError
from rougail.tiramisu import display_xmlfiles, normalize_family
from rougail.utils import undefined, get_properties_to_string, PROPERTY_ATTRIBUTE
from rougail.error import VariableCalculationDependencyError, RougailWarning

from .config import OutPuts
from .i18n import _
from .utils import dump, to_phrase, calc_path
from .example import Examples
from .changelog import Changelog


HIDDEN_PROPERTIES = [
    "hidden",
    "disabled",
]


class RougailOutputDoc(Examples, Changelog):
    """Rougail Output Doc:
    Generate documentation from rougail description files
    """

    def __init__(
        self,
        config: "Config",
        *,
        rougailconfig: "RougailConfig" = None,
        **kwargs,
    ):
        # Import here to avoid circular import
        from rougail.tiramisu import CONVERT_OPTION

        self.convert_option = CONVERT_OPTION
        if rougailconfig is None:
            from rougail import RougailConfig

            rougailconfig = RougailConfig
            if rougailconfig["step.output"] != "doc":
                rougailconfig["step.output"] = "doc"
        if rougailconfig["step.output"] != "doc":
            raise Exception("doc is not set as step.output")
        self.outputs = OutPuts().get()
        self.conf = config
        self.modes_level = rougailconfig["modes_level"]
        if self.modes_level:
            self.disabled_modes = rougailconfig["doc.disabled_modes"]
            if self.disabled_modes:
                self.conf.property.setdefault(
                    frozenset(self.disabled_modes), "read_write", "append"
                )
        else:
            self.disabled_modes = []
        self.conf.property.read_write()
        # self.conf.property.remove("cache")
        self.rougailconfig = rougailconfig
        self.informations = None
        try:
            groups.namespace
            self.support_namespace = True
        except AttributeError:
            self.support_namespace = False
        self.property_to_string = get_properties_to_string()
        self.formatter = None
        super().__init__()

    def run(self) -> str:
        """Print documentation in stdout"""
        self.load()
        self.load_formatter()
        return_string = ""
        contents = self.rougailconfig["doc.contents"]
        if "variables" in contents:
            return_string += self.formatter.run(self.informations)
        if "example" in contents:
            return_string += self.gen_doc_examples()
        if "changelog" in contents:
            return_string += self.gen_doc_changelog()
        return True, return_string

    def load_formatter(self) -> str:
        output_format = self.rougailconfig["doc.output_format"]
        self.formatter = self.outputs[output_format](self.rougailconfig, support_namespace=self.support_namespace)

    def print(self) -> None:
        ret, data = self.run()
        print(data)
        return ret

    def load(self):
        self.dynamic_paths = {}
        config = self.conf.unrestraint
        self.populate_dynamics(config=config)
        informations = self.parse_families(config)
        if informations is None:
            informations = {}
        self.informations = informations

    def populate_dynamics(self, *, config=None, reload=False):
        if config is None:
            config = self.conf.unrestraint
        self._populate_dynamics(config, reload)

    def _populate_dynamics(self, family, reload, uncalculated=False) -> None:
        def populate(child, uncalculated):
            if child.isoptiondescription():
                type_ = "family"
            else:
                type_ = "variable"
            if child.isdynamic():
                self.populate_dynamic(child, type_, reload, uncalculated)
            if child.isoptiondescription():
                self._populate_dynamics(child, reload, uncalculated)
        for child in family.list(uncalculated=uncalculated):
            populate(child, uncalculated)
        if not uncalculated:
            for child in family.list(uncalculated=True):
                if child.isdynamic() and child.path(uncalculated=True) not in self.dynamic_paths:
                    populate(family, uncalculated=True)

    def populate_dynamic(self, obj, type_, reload, uncalculated) -> None:
        path = obj.path(uncalculated=True)
        if path not in self.dynamic_paths:
            new_name = True
            description = obj.description(uncalculated=True)
            name = obj.name(uncalculated=True)
            self.dynamic_paths[path] = {
                "names": [],
                "identifiers": [],
                "path": path,
            }
            if not obj.information.get("forced_description", False):
                self.dynamic_paths[path]["description"] = self._convert_description(
                    description, type_, its_a_path=False
                )
            elif obj.isoptiondescription():
                self.dynamic_paths[path]["description"] = self._convert_description(
                    description, type_, its_a_path=True
                )
        if uncalculated:
            return
        dynamic_obj = self.dynamic_paths[path]
        if reload and obj.identifiers() in dynamic_obj["identifiers"]:
            return
        dynamic_obj["names"].append(obj.name())
        dynamic_obj["identifiers"].append(obj.identifiers())

    def parse_families(self, family) -> dict:
        informations = {}
        leader = None
        for child in family.list():
            if self.is_inaccessible_user_data(child):
                continue
            if child.type(only_self=True) == "symlink":
                continue
            if not child.isoptiondescription():
                leader = self.parse_variable(child, leader, informations)
            else:
                self.parse_family(child, informations)
        return informations

    def is_inaccessible_user_data(self, child):
        """If family is not accessible in read_write mode (to load user_data),
        do not comment this family
        """
        properties = child.property.get(uncalculated=True)
        for hidden_property in HIDDEN_PROPERTIES:
            if hidden_property in properties:
                return True

            calculation = child.information.get(f"{hidden_property}_calculation", None)
            if calculation and calculation.get("type") == "variable":
                variable_path, value, condition = calculation["value"]
                variable = self.conf.forcepermissive.option(variable_path)
                try:
                    variable.value.get()
                except AttributeError:
                    variable = None
                if variable and self.is_inaccessible_user_data(variable):
                    try:
                        variable_value = self._get_unmodified_default_value(variable)
                    except VariableCalculationDependencyError:
                        pass
                    else:
                        if (condition == "when" and value == variable_value) or (condition == "when_not" and value != variable_value):
                            return True
        if not child.isoptiondescription():
            for hidden_property in self.disabled_modes:
                if hidden_property in properties:
                    return True
        return False

    def parse_family(self, family, informations: dict, *, force_injection=False) -> None:
        path = family.path(uncalculated=True)
        name = family.name(uncalculated=True)
        sub_informations = self.parse_families(family)
        if not force_injection and not sub_informations:
            return
        #        if self.with_family:
        family_informations = self._populate_family(
            family,
            path,
        )
        if family_informations is not False:
            informations[name] = {
                "type": self._get_family_type(family),
                "informations": family_informations,
                "children": sub_informations,
            }

    def parse_variable(
        self,
        variable,
        leader: dict,
        informations: dict,
        *,
        only_one=False,
    ) -> Optional[dict]:
        path = variable.path(uncalculated=True)
        name = variable.name(uncalculated=True)
        potential_leader = None
        if variable.isdynamic():
            # information is already set
            potential_leader = self._parse_variable_dynamic(
                variable, leader, name, path, informations, only_one
            )
        elif variable.isfollower() and variable.index():
            self._parse_variable_follower_with_index(
                variable, leader, name, informations
            )
        else:
            potential_leader = self.parse_variable_normal(
                variable, leader, name, path, informations
            )
        if potential_leader:
            leader = potential_leader
        return leader

    def parse_variable_normal(
        self, variable, leader, name: str, path: str, informations: dict
    ) -> Optional[dict]:
        if variable.isdynamic():
            sub_informations = self.dynamic_paths[path]
        elif variable.isfollower() and path in informations:  #  variable.index():
            sub_informations = informations[name]
        else:
            sub_informations = {}
        if not self._populate_variable(
            variable,
            sub_informations,
        ):
            return None
        self._add_examples(variable, sub_informations, leader)
        informations[name] = sub_informations
        if variable.isleader():
            return sub_informations
        return None

    def _parse_variable_follower_with_index(
        self, variable, leader: dict, name: str, informations: dict
    ) -> None:
        if (variable.index() + 1) > len(leader["gen_examples"][-1]):
            return
        informations[name]["gen_examples"][-1][variable.index()] = self._get_example(
            variable, informations[name], None
        )

    def _parse_variable_dynamic(
        self, variable, leader, name, path, informations, only_one
    ) -> None:
#        if path not in self.dynamic_paths:
#            self.populate_dynamic(variable, path)
        dynamic_variable = self.dynamic_paths[path]
        if (not only_one or path in informations) and "type" in dynamic_variable:
            dynamic_variable["gen_examples"].append(
                self._get_example(variable, dynamic_variable, leader)
            )
            if variable.isleader():
                return dynamic_variable
            if not only_one:
                return None
        return self.parse_variable_normal(variable, leader, name, path, informations)

    def _get_family_type(self, family) -> str:
        if self.support_namespace and family.group_type() is groups.namespace:
            return "namespace"
        if family.isleadership():
            return "leadership"
        if family.isdynamic(only_self=True):
            return "dynamic"
        return "family"

    def _populate_family(
        self,
        family,
        path: str,
    ) -> dict:
        if family.isdynamic():
            informations = self.dynamic_paths[path]
        else:
            informations = {}
        if not self._populate(family, informations, "family"):
            return False
        if family.isleadership():
            informations.setdefault("help", []).append(
                _("This family contains lists of variable blocks")
            )
        if family.isdynamic(only_self=True):
            identifiers = self._to_string(family, "dynamic", do_not_raise=True)
            if identifiers is None:
                identifiers = family.identifiers(only_self=True)
            if not isinstance(identifiers, list):
                identifiers = [identifiers]
            informations["identifier"] = identifiers
            informations.setdefault("help", []).append(
                _("This family builds families dynamically")
            )
        return informations

    def _populate_variable(
        self,
        variable,
        informations: dict,
    ):
        informations["type"] = "variable"
        default = self._get_default(
            variable,
        )
        if default is not None:
            informations["default"] = {"name": _("Default"), "values": default}
        self._parse_type(
            variable,
            informations,
        )
        if not self._populate(variable, informations, "variable"):
            return False
        if variable.ismulti():
            multi = not variable.isfollower() or variable.issubmulti()
        else:
            multi = False
        if multi:
            informations["multiple"] = True
        examples = variable.information.get("examples", None)
        if examples is None:
            examples = variable.information.get("test", None)
        if examples is not None:
            if len(examples) == 1:
                name = _("Example")
                values = examples[0]
            else:
                name = _("Examples")
                values = list(examples)
            informations["examples"] = {
                "name": name,
                "values": values
            }
        tags = variable.information.get("tags", None)
        if tags:
            if len(tags) == 1:
                name = _("Tag")
                values = tags[0]
            else:
                name = _("Tags")
                values = list(tags)
            informations["tags"] = {
                "name": name,
                "values": values,
            }
        alternative_name = variable.information.get("alternative_name", None)
        if alternative_name:
            informations["alternative_name"] = alternative_name
        return True

    def _populate(
        self,
        child,
        informations: dict,
        type_: str,
    ):
        need_disabled, properties = self._parse_properties(child, informations)
        if not need_disabled:
            return False
        name = child.name(uncalculated=True)
        if child.information.get("forced_description", False):
            if (
                not child.isoptiondescription()
                or not self.support_namespace
                or child.group_type() is not groups.namespace
            ):
                if (
                    child.isoptiondescription()
                    or not child.isfollower()
                    or not child.index()
                ):
                    warning = _('No attribute "description" for "{0}" in {1}').format(
                        child.path(uncalculated=True),
                        display_xmlfiles(child.information.get("ymlfiles")),
                    )
                    warn(
                        warning,
                        RougailWarning,
                    )
            if child.isoptiondescription():
                description = self._convert_description(
                    child.description(uncalculated=True), type_, its_a_path=True
                )
            else:
                description = None
        else:
            description = self._convert_description(
                child.description(uncalculated=True), type_, its_a_path=False
            )
        if not child.isdynamic():
            informations["path"] = child.path(uncalculated=True)
            informations["names"] = [child.name()]
            if description is not None:
                informations["description"] = description
        help_ = child.information.get("help", None)
        if help_:
            informations["help"] = [to_phrase(help_)]
        if "properties" in informations:
            informations["properties"].extend(properties)
        else:
            informations["properties"] = properties
        return True

    def _convert_description(self, description, type_, its_a_path=False):
        if not its_a_path:
            description = to_phrase(description, type_)
        return description

    def _add_examples(self, variable, informations: dict, leader) -> None:
        if not variable.index():
            example = self._get_example(variable, informations, leader)
            informations["gen_examples"] = [example]
        informations["mandatory_without_value"] = "mandatory" in variable.property.get(
            uncalculated=True
        ) and (
            not variable.information.get("default_value_makes_sense", True)
            or variable.value.get(uncalculated=True) in [None, []]
        )

    def _get_example(self, variable, informations: dict, leader):
        example = informations.get("examples", {}).get("values")
        if example is not None:
            if isinstance(example, tuple):
                example = list(example)
            if informations.get("multiple"):
                if not isinstance(example, list):
                    example = [example]
            else:
                if isinstance(example, list):
                    index = variable.index()
                    if index is None or len(example) - 1 >= index:
                        index = 0
                    example = example[index]
        else:
            if variable.information.get("fake_default", False):
                default = None
            else:
                try:
                    default = variable.value.get()
                except ConfigError:
                    default = None
            if default not in [None, []]:
                example = default
            else:
                example = self.get_type_default_value(
                    variable, informations
                )
        if leader is not None and variable.isfollower():
            example = [example] + [undefined] * (len(leader["gen_examples"][-1]) - 1)
        return example

    def get_type_default_value(self, variable, informations):
        example = self.convert_option.get(variable.information.get("type"), {}).get(
            "example", None
        )
        if example is None:
            example = "xxx"
        if informations.get("multiple"):
            example = [example]
        return example

    def _parse_type(
        self,
        child,
        informations,
    ):
        variable_type = child.information.get("type")
        doc_type = self.convert_option.get(variable_type, {"params": {}})
        informations["variable_type"] = doc_type.get("msg", variable_type)
        # extra parameters for types
        option = child.get()
        validators = []
        if "params" in doc_type:
            for param, msg in doc_type["params"].items():
                value = option.impl_get_extra(f"_{param}")
                if value is None:
                    value = option.impl_get_extra(param)
                if value is not None and value is not False:
                    if isinstance(value, set):
                        value = list(value)
                    if isinstance(value, list):
                        value = display_list(value, add_quote=True)
                    if "doc" in msg:
                        validators.append(msg['doc'].format(value))
                    else:
                        validators.append(msg['description'].format(value))

        # get validation information from annotator
        for name in child.information.list():
            if not name.startswith("validators_calculation"):
                continue
            validators.extend(
                self._to_string(
                    child,
                    "validators",
                )
            )
            break
        if variable_type == "regexp":
            validators.append(
                _('text based with regular expressions "{0}"').format(child.pattern())
            )
        if validators:
            if len(validators) == 1:
                key = _("Validator")
                validators = validators[0]
            else:
                key = _("Validators")
            informations["validators"] = {"name": key, "values": validators}
        if variable_type == "choice":
            choices = self._to_string(child, "choice", do_not_raise=True)
            if choices is None:
                choices = child.value.list()
                for idx, val in enumerate(choices):
                    if isinstance(val, Calculation):
                        choices[idx] = self._to_string(child, "choice", f"_{idx}")
            informations["choices"] = {"name": _("Choices"), "values": choices}

    def _parse_properties(
        self,
        child,
        child_informations,
    ):
        informations = []
        properties = child.property.get(uncalculated=True)
        for mode in self.modes_level:
            if mode not in properties:
                continue
            informations.append(
                {
                    "type": "mode",
                    "name": mode,
                }
            )
            break
        for prop, translated_prop in self.property_to_string:
            if "not_for_commandline" in properties:
                child_informations["not_for_commandline"] = True
                continue
            if prop in properties:
                prop_obj = {
                    "type": "property",
                    "name": translated_prop,
                }
            elif child.information.get(f"{prop}_calculation", False):
                annotation = self._to_string(child, prop)
                if annotation is None or isinstance(annotation, bool):
                    if annotation is None and prop in HIDDEN_PROPERTIES:
                        return False, {}
                    if not annotation:
                        continue
                    prop_obj = {
                        "type": "property",
                        "name": translated_prop,
                    }
                else:
                    prop_obj = {
                        "type": "property",
                        "name": translated_prop,
                        "annotation": annotation,
                    }
            else:
                # this property is not in the variable so, do not comment it
                continue
            informations.append(prop_obj)
        return True, informations

    def _get_default(
        self,
        variable,
    ):
        default = self._to_string(variable, "default", do_not_raise=True)
        if default is not None:
            if default == []:
                default = None
            return default
        if variable.information.get("default_value_makes_sense", True):
            default_ = variable.value.get(uncalculated=True)
            if not isinstance(default_, Calculation):
                default = default_
        if default == []:
            default = None
        return default

    def _to_string(
        self,
        child,
        prop,
        do_not_raise=False,
    ):
        calculation = child.information.get(f"{prop}_calculation", None)
        if not calculation:
            if do_not_raise:
                return None
            raise Exception(
                f'cannot find "{prop}_calculation" information, '
                "do you have declare doc has a plugins?"
            )
        if isinstance(calculation, list):
            values = []
            for cal in calculation:
                value = self._calculation_to_string(child, cal, prop, inside_list=True)
                if value is not None:
                    values.append(value)
            return values
        return self._calculation_to_string(child, calculation, prop)

    def _calculation_to_string(self, child, calculation, prop, inside_list=False):
        if "description" in calculation:
            values = calculation["description"]
#            if not values.endswith("."):
#                values += "."
            return calculation
        if "type" not in calculation:
            return calculation["value"]
        if calculation["type"] == "jinja":
            values = self._calculation_jinja_to_string(child, calculation, prop)
        elif calculation["type"] == "variable":
            values = self._calculation_variable_to_string(child, calculation, prop)
        elif calculation["type"] == "identifier":
            if prop in PROPERTY_ATTRIBUTE:
                values = calculation["value"]
            else:
                values = _("the value of the identifier")
        elif calculation["type"] == "information":
            values = calculation["value"]
        else:
            values = _("the value of the {0}").format(calculation["type"])
#        if not inside_list and isinstance(values, str) and not values.endswith("."):
#            values += "."
        return values

    def _calculation_jinja_to_string(self, child, calculation, prop):
        if calculation["value"] is not True:
            values = calculation["value"]
        else:
            values = _("depends on a calculation")
            if (
                child.isoptiondescription()
                or not child.isfollower()
                or not child.index()
            ):
                warning = _(
                    '"{0}" is a calculation for {1} but has no description in {2}'
                ).format(
                    prop,
                    child.path(),
                    display_xmlfiles(child.information.get("ymlfiles")),
                )
                warn(
                    warning,
                    RougailWarning,
                )
        return values

    def _calculation_variable_to_string(self, child, calculation, prop):
        if prop in PROPERTY_ATTRIBUTE:
            values = self._calculation_variable_to_string_known_property(child, calculation, prop)
        else:
            if calculation["optional"]:
                path = calculation["value"]
                if "{{ identifier }}" in path:
                    if path not in self.dynamic_paths:
                        return None
                else:
                    try:
                        self.conf.forcepermissive.option(path).get()
                    except AttributeError:
                        return None
            if not calculation["optional"]:
                true_msg = _('the value of the variable "{0}"')
            else:
                true_msg = _('the value of the variable "{0}" if it is defined')
            if "{{ identifier }}" in calculation["ori_path"]:
                values = []
                all_is_undocumented = False
                for cpath, description, identifiers in self.get_annotation_variable(calculation["value"], calculation["ori_path"]):
                    if cpath:
                        all_is_undocumented = False
                        path_obj = {
                                "path": cpath,
                            }
                        if identifiers:
                            path_obj["identifiers"] = identifiers
                        values.append({
                            "message": true_msg,
                            "path": path_obj,
                            "description": description,
                        })
                    else:
                        if all_is_undocumented is None:
                            all_is_undocumented = True
                        values.append(_("the value of an undocumented variable"))
                if all_is_undocumented:
                    if len(values) > 1:
                        values = _("the values of undocumented variables")
                    else:
                        values = values[0]
            else:
                # FIXME A MUTUALISER AUSSI
                variable_path = calculation["ori_path"]
                variable = self.conf.forcepermissive.option(variable_path)
                try:
                    isfollower = variable.isfollower()
                except AttributeError as err:
                    pass
                else:
                    if not isfollower and self.is_inaccessible_user_data(variable):
                        try:
                            uncalculated = variable.value.get(uncalculated=True)
                        except PropertiesOptionError:
                            true_msg = None
                        else:
                            if uncalculated and not isinstance(
                                uncalculated, Calculation
                            ):
                                if isinstance(uncalculated, list):
                                    true_msg = {
                                        "submessage": _(
                                            "(from an undocumented variable)"
                                        ),
                                        "values": uncalculated,
                                    }
                                else:
                                    if not isinstance(uncalculated, str):
                                        uncalculated = dump(uncalculated)
                                    true_msg = _(
                                        "{0} (from an undocumented variable)"
                                    ).format(uncalculated)
                            else:
                                true_msg = _("depends on an undocumented variable")
                if true_msg:
                    if isinstance(true_msg, dict):
                        values = true_msg
                    else:
                        try:
                            description = self._convert_description(self.conf.option(calculation["ori_path"]).description(uncalculated=True), "description", its_a_path=False)
                        except AttributeError:
                            description = calculation["ori_path"]
                        values = {
                            "message": true_msg,
                            "path": {
                                "path": calculation["ori_path"],
                            },
                            "description": description,
                        }
                else:
                    values = None
        return values

    def _calculation_variable_to_string_known_property(self, child, calculation, prop):
        variable_path, value, condition = calculation["value"]
        if isinstance(value, str):
            str_value = value
        else:
            str_value = dump(value)
        values = []
        if "{{ identifier }}" in calculation["ori_path"] or "{{ identifier }}" in variable_path:
            variables = self.get_annotation_variable(variable_path, calculation["ori_path"])
        else:
            option = self.conf.option(variable_path)
            try:
                is_inaccessible = self.is_inaccessible_user_data(option)
            except AttributeError as err:
                if err.code != "option-not-found":
                    raise err from err
                is_inaccessible = True
            if is_inaccessible:
                variables = [[None, None, None]]
            else:
                description = self._convert_description(option.description(uncalculated=True), "description", its_a_path=False)
                variables = [[variable_path, description, None]]
        for cpath, description, identifiers in variables:
            if not cpath:
                variable = self.conf.forcepermissive.option(variable_path)
                try:
                    variable_value = self._get_unmodified_default_value(variable)
                except PropertiesOptionError as err:
                    if calculation["propertyerror"]:
                        raise err from err
                    variable_value = value
                except VariableCalculationDependencyError:
                    values.append(_("depends on an undocumented variable"))
                    continue
                except AttributeError as err:
                    #                    if err.code != "option-not-found" or not calculation.get("optional", False):
                    #                        raise err from err
                    return calculation.get("default", False)
                if (
                    condition == "when"
                    and value == variable_value
                    or condition == "when_not"
                    and value != variable_value
                ):
                    if prop in HIDDEN_PROPERTIES:
                        return False
                    # always "prop"
                    return True
                # never "prop"
                return False
            else:
                if condition == "when_not":
                    if calculation["optional"]:
                        if not calculation["propertyerror"]:
                            msg = _(
                                'when the variable "{{0}}" is defined, accessible and hasn\'t the value "{0}"'
                            )
                        else:
                            msg = _(
                                'when the variable "{{0}}" is defined and hasn\'t the value "{0}"'
                            )
                    elif not calculation["propertyerror"]:
                        msg = _('when the variable "{{0}}" is accessible and hasn\'t the value "{0}"')
                    else:
                        msg = _('when the variable "{{0}}" hasn\'t the value "{0}"')
                else:
                    if calculation["optional"]:
                        if not calculation["propertyerror"]:
                            msg = _(
                                'when the variable "{{0}}" is defined, is accessible and has the value "{0}"'
                            )
                        else:
                            msg = _(
                                'when the variable "{{0}}" is defined and has the value "{0}"'
                            )
                    elif not calculation["propertyerror"]:
                        msg = _('when the variable "{{0}}" is accessible and has the value "{0}"')
                    else:
                        msg = _('when the variable "{{0}}" has the value "{0}"')
                path_obj = {
                        "path": variable_path,
                    }
                if identifiers:
                    path_obj["identifiers"] = identifiers

                values.append({
                    "message": msg.format(str_value),
                    "path": path_obj,
                    "description": description,
                })
        if len(values) == 1:
            return values[0]
        return values

    def get_annotation_variable(self, current_path, ori_path):
        if current_path == ori_path:
            regexp = None
        else:
            regexp = compile(
                "^"
                + ori_path.replace("{{ identifier }}", "(.*)")
                + "$"
            )
        information = self.dynamic_paths[current_path]
        path = information["path"]
        for identifiers in information["identifiers"]:
            cpath = calc_path(path, identifiers=identifiers)
            if regexp and not regexp.search(cpath):
                continue
            if self.is_inaccessible_user_data(self.conf.option(cpath)):
                yield None, None, None
            else:
                description = self._convert_description(self.conf.option(path).description(uncalculated=True), "description", its_a_path=False)
                if "{{ identifier }}" in path:
                    yield path, description, identifiers.copy()
                else:
                    yield path, description, None

    def _get_unmodified_default_value(self, child):
        calculation = child.information.get(f"default_calculation", None)
        if not calculation:
            return child.value.get()
        if calculation["type"] == "variable":
            variable = self.conf.forcepermissive.option(calculation["value"])
            if variable and self.is_inaccessible_user_data(variable):
                return self._get_unmodified_default_value(variable)
        raise VariableCalculationDependencyError()
