"""
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 tiramisu import undefined
from rougail.annotator.variable import Walk

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


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.populate_family()
        self.populate_variable()

    def populate_family(self) -> None:
        """Set doc, path, ... to family"""
        for family in self.get_families():
            self.convert_variable_property(family)
            if family.type != "dynamic":
                continue
            if not isinstance(family.dynamic, list):
                self.dynamic_values(family, family.dynamic)
            else:
                for value in family.dynamic:
                    self.dynamic_values(family, value, return_a_list=False)
            self.calculation_to_information(
                family.path,
                "dynamic",
                family.dynamic,
                family.version,
            )

    def dynamic_values(
        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
            if variable.type == "choice":
                self.calculation_to_information(
                    variable.path,
                    "choice",
                    variable.choices,
                    variable.version,
                )
            default = variable.default
            if default is None and variable.path in self.objectspace.default_multi:
                default = self.objectspace.default_multi[variable.path]
            self.calculation_to_information(
                variable.path,
                "default",
                default,
                variable.version,
            )
            self.calculation_to_information(
                variable.path,
                "validators",
                variable.validators,
                variable.version,
            )
            if variable.path in self.objectspace.leaders and not default:
                self.add_examples_values(variable)
            self.convert_variable_property(variable)

    def convert_variable_property(
        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,
            )

    def calculation_to_information(
        self,
        path: str,
        prop: str,
        values,
        version: str,
    ):
        """tranform calculation to an information"""
        if not isinstance(values, list):
            if not isinstance(values, Calculation):
                return
            self._calculation_to_information(
                path,
                prop,
                self._calculation_to_string(path, prop, values, version),
            )
        else:
            datas = []
            one_is_calculation = False
            for idx, val in enumerate(values):
                data = self._calculation_to_string(path, prop, val, version)
                if data is None:
                    continue
                if "type" in data:
                    one_is_calculation = True
                datas.append(data)
            if one_is_calculation:
                self._calculation_to_information(
                    path,
                    prop,
                    datas,
                )

    def _calculation_to_information(
        self,
        path: str,
        prop: str,
        datas: Union[dict, list[dict]],
    ) -> None:
        if datas is None:
            return
        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 isinstance(values, JinjaCalculation):
            return {
                "type": "jinja",
                "value": self._calculation_to_information_jinja(values),
            }
        if isinstance(values, (VariableCalculation, VariablePropertyCalculation)):
            variable_path = self._get_path_from_variable(values, version, 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,
            }
        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_jinja(self, values):
        if values.description:
            return values.description
        return True

    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
