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

distribued with GPL-2 or later license

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""

from typing import Union
from re import compile

from rougail.utils import undefined, PROPERTY_ATTRIBUTE
from rougail.annotator.variable import Walk

from rougail.output_doc.i18n import _
from rougail.convert.object_model import (
    Calculation,
    JinjaCalculation,
    VariableCalculation,
    VariablePropertyCalculation,
    IdentifierCalculation,
    IdentifierPropertyCalculation,
    InformationCalculation,
    IndexCalculation,
    NamespaceCalculation,
    CONVERT_OPTION,
)


class Annotator(Walk):
    """Annotate for documentation"""

    level = 95

    def __init__(
        self,
        objectspace,
        *args,  # pylint: disable=unused-argument
    ) -> None:
        if not objectspace.paths:
            return
        self.objectspace = objectspace
        self.change_default_value = self.objectspace.rougailconfig[
            "doc.change_default_value"
        ]
        self.regexp_description_get_paths = None
        self.populate_family()
        self.populate_variable()

    def populate_family(self) -> None:
        """Set doc, path, ... to family"""
        for family in self.get_families():
            self.add_informations_from_properties(family)
            if family.type == "dynamic":
                self.force_default_value_in_suffix_variable(family)
                self.calculation_to_information(
                    family.path,
                    "dynamic",
                    family.dynamic,
                    family.version,
                )
            path = family.path
            if path in self.objectspace.forced_descriptions:
                self.objectspace.informations.add(
                    path,
                    "forced_description",
                    True,
                )

    def force_default_value_in_suffix_variable(self, family) -> None:
        if not self.change_default_value:
            return
        if not isinstance(family.dynamic, list):
            self._force_default_value_in_suffix_variable(family, family.dynamic)
        else:
            for value in family.dynamic:
                self._force_default_value_in_suffix_variable(
                    family, value, return_a_list=False
                )

    def _force_default_value_in_suffix_variable(
        self,
        family,
        value,
        *,
        return_a_list=True,
    ) -> None:
        """For dynamic we must have values to document it"""
        if not isinstance(value, Calculation):
            return
        value_added = False
        if isinstance(value, (VariableCalculation, VariablePropertyCalculation)):
            variable = self.objectspace.paths.get_with_dynamic(
                value.variable,
                family.path,
                value.version,
                value.namespace,
                value.xmlfiles,
            )[0]
            if variable and not variable.default:
                values = self.add_examples_values(variable)
                value_added = True
        if not value_added:
            default_values = ["example"]
            if not return_a_list:
                default_values = default_values[0]
            value.default_values = default_values

    def add_examples_values(self, variable) -> list:
        """Check examples or test information to define examples values in a variable"""
        values = self.objectspace.informations.get(variable.path).get("examples", None)
        if not values:
            values = self.objectspace.informations.get(variable.path).get("test", None)
        if isinstance(values, tuple):
            values = list(values)
        if values:
            variable.default = list(values)
        else:
            variable.default = [CONVERT_OPTION[variable.type]["example"]]
        self.objectspace.informations.add(
            variable.path, "default_value_makes_sense", False
        )
        return values

    def populate_variable(self) -> None:
        """convert variables"""
        for variable in self.get_variables():
            if variable.type == "symlink":
                continue
            path = variable.path
            if variable.type == "choice":
                self.calculation_to_information(
                    path,
                    "choice",
                    variable.choices,
                    variable.version,
                )
            default = variable.default
            if default is None and path in self.objectspace.default_multi:
                default = self.objectspace.default_multi[path]
            self.calculation_to_information(
                path,
                "default",
                default,
                variable.version,
            )
            self.calculation_to_information(
                path,
                "validators",
                variable.validators,
                variable.version,
            )
            if (
                self.change_default_value
                and path in self.objectspace.leaders
                and not default
            ):
                self.add_examples_values(variable)
            self.add_informations_from_properties(variable)
            if path in self.objectspace.forced_descriptions:
                self.objectspace.informations.add(
                    path,
                    "forced_description",
                    True,
                )

    def add_informations_from_properties(
        self,
        variable: dict,
    ) -> None:
        """convert properties"""
        for prop in PROPERTY_ATTRIBUTE:
            prop_value = getattr(variable, prop, None)
            if not prop_value:
                continue
            self.calculation_to_information(
                variable.path,
                prop,
                prop_value,
                variable.version,
            )
            if isinstance(prop_value, Calculation):
                prop_value.default_values = False

    def calculation_to_information(
        self,
        path: str,
        prop: str,
        values,
        version: str,
    ):
        """tranform calculation to an information"""
        one_is_calculation = False
        if not isinstance(values, list):
            if not isinstance(values, Calculation):
                return
            one_is_calculation = True
            datas = self.calculation_to_string(path, prop, values, version)
        else:
            datas = []
            for idx, val in enumerate(values):
                data = self.calculation_to_string(path, prop, val, version)
                if data is None:
                    continue
                if "type" in data or "description" in data:
                    one_is_calculation = True
                datas.append(data)
        if one_is_calculation:
            self.objectspace.informations.add(
                path,
                f"{prop}_calculation",
                datas,
            )

    def calculation_to_string(
        self,
        path: str,
        prop: str,
        values,
        version: str,
    ):
        if not isinstance(values, Calculation):
            return {"value": values}
        if values.description:
            if not self.regexp_description_get_paths:
                self.regexp_description_get_paths = compile('"(.*?)"')
            index = 0
            description = values.description
            variables = []
            for r_path in self.regexp_description_get_paths.findall(description):
                variable, identifiers = self.objectspace.paths.get_with_dynamic(
                    r_path,
                    path,
                    values.version,
                    values.namespace,
                    values.xmlfiles,
                )
                if variable:
                    description = description.replace(f'"{r_path}"', f'"{{{index}}}"')
                    v = {"path": variable.path, "description": variable.description}
                    if identifiers:
                        v["identifiers"] = identifiers
                    variables.append(v)
                    index += 1
            ret = {"description": description}
            if variables:
                ret["variables"] = variables
            return ret
        if isinstance(values, JinjaCalculation):
            if values.description:
                value = values.description
            else:
                value = True
            return {
                "type": "jinja",
                "value": value,
            }
        if isinstance(values, (VariableCalculation, VariablePropertyCalculation)):
            variable_path = self.get_path_from_variable(values, version, values.path)
            value = self.calculation_to_information_variable(
                variable_path, values, prop, version, path
            )
            if value is None:
                return
            return {
                "type": "variable",
                "value": value,
                "ori_path": variable_path,
                "optional": values.optional,
                "propertyerror": values.propertyerror,
            }
        if isinstance(values, InformationCalculation):
            return {
                "type": "information",
                "value": self.calculation_to_information_information(
                    values, version, path
                ),
            }
        if isinstance(values, (IdentifierCalculation, IdentifierPropertyCalculation)):
            return {
                "type": "identifier",
                "value": self.calculation_to_information_identifier(
                    values, prop, version
                ),
            }
        if isinstance(values, IndexCalculation):
            return {
                "type": "index",
                "value": True,
            }
        if isinstance(values, NamespaceCalculation):
            return {
                "type": "namespace",
                "value": True,
            }
        raise Exception(f'unknown calculation {type(values)} "{values}"')

    def calculation_to_information_variable(
        self, variable_path: str, values, prop: str, version: str, path: str
    ) -> str:
        # is optional
        variable = self.objectspace.paths.get_with_dynamic(
            variable_path,
            path,
            values.version,
            values.namespace,
            values.xmlfiles,
        )[0]
        if isinstance(values, VariableCalculation) and values.optional and not variable:
            return None
        if variable:
            variable_path = variable.path
        if prop in PROPERTY_ATTRIBUTE:
            # get comparative value
            if values.when_not is not undefined:
                value = values.when_not
                condition = "when_not"
            elif values.when is not undefined:
                value = values.when
                condition = "when"
            else:
                value = True
                condition = "when"

            # set message
            values_calculation = (variable_path, value, condition)
        else:
            values_calculation = variable_path

        return values_calculation

    def calculation_to_information_information(
        self, values, version: str, path: str
    ) -> Union[str, bool]:
        if values.variable:
            variable_path = self.get_path_from_variable(values, version, path)
            return _('the value of the information "{0}" of the variable "{1}"').format(
                values.information, variable_path
            )
        return _('the value of the global information "{0}"').format(values.information)

    def calculation_to_information_identifier(
        self, values, prop: str, version: str
    ) -> Union[str, bool]:
        if version != "1.0" and prop in PROPERTY_ATTRIBUTE:
            if values.when is not undefined:
                return _('when the identifier is "{0}"').format(values.when)
            if values.when_not is not undefined:
                return _('when the identifier is not "{0}"').format(values.when_not)
        return True

    def get_path_from_variable(self, values, version: str, path: str) -> str:
        variable_path = values.variable
        paths = self.objectspace.paths
        if version != "1.0" and paths.regexp_relative.search(variable_path):
            variable_path = paths.get_full_path(
                variable_path,
                path,
            )
        elif version == "1.0" and "{{ suffix }}" in variable_path:
            variable_path = variable_path.replace("{{ suffix }}", "{{ identifier }}")
        return variable_path
