"""Annotate properties

Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018

Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021

Silique (https://www.silique.fr)
Copyright (C) 2022-2026

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 typing import Union, List
from tiramisu.setting import PROPERTIES_MAKE_SENSE
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.annotator.variable import Walk
from rougail.convert.object_model import Calculation
from rougail.utils import NAME_REGEXP
from rougail.convert.object_model import VariableCalculation


PROPERTIES = (
    "hidden",
    "frozen",
    "force_default_on_freeze",
    "force_store_value",
    "disabled",
    "mandatory",
)


class Annotator(Walk):
    """Annotate properties"""

    level = 90

    def __init__(self, objectspace, *args) -> None:
        self.objectspace = objectspace
        if not self.objectspace.paths:
            return
        self.frozen = {}
        self.variables_default_transitive = []
        self.convert_sequence()
        self.convert_family()
        self.convert_variable()

    def convert_sequence(self) -> None:
        for variable_path in self.objectspace.leaders:
            variable = self.objectspace.paths[variable_path]
            if variable.hidden:
                family = self.objectspace.paths[variable.path.rsplit(".", 1)[0]]
                hidden = variable.hidden
                if hidden is not True:
                    hidden = hidden.model_copy()
                    if hidden.ori_path is None:
                        hidden.ori_path = variable_path
                family.hidden = hidden
                variable.hidden = None

    def convert_family(self) -> None:
        """convert families"""
        for family in self.get_families():
            self.family_variable_property(family)
            self.convert_tag(family)
            # collect for force_default_on_freeze
            if family.hidden:
                if family.hidden is True:
                    frozen = True
                else:
                    frozen = family.hidden.model_copy()
                    frozen.attribute_name = "frozen"
                    if frozen.ori_path is None:
                        frozen.ori_path = family.path
                self.set_variable_frozen_inside_family(
                    family.path,
                    frozen,
                )

    def set_variable_frozen_inside_family(
        self,
        family_path: str,
        frozen: Union[bool, Calculation],
    ) -> None:
        for variable_path in self.objectspace.parents[family_path]:
            if variable_path in self.objectspace.families:
                # it's a family
                self.set_variable_frozen_inside_family(
                    variable_path,
                    frozen,
                )
            else:
                # it's a variable
                variable = self.objectspace.paths[variable_path]
                # if frozen is already true or hidden for variable is true => always frozen
                if (
                    self.frozen.get(variable.path) is True
                    or variable.hidden is True
                    or frozen is True
                ):
                    self.frozen[variable.path] = True
                else:
                    if variable.path in self.frozen:
                        if not isinstance(self.frozen[variable.path], list):
                            self.frozen[variable.path] = [frozen]
                        self.frozen[variable.path].append(frozen)
                    else:
                        self.frozen[variable.path] = frozen

    def convert_variable(self) -> None:
        """convert variables"""
        for variable in self.get_variables():
            if variable.type == "symlink":
                continue
            self.convert_variable_property(variable)

    def convert_variable_property(
        self,
        variable: dict,
    ) -> None:
        """convert properties"""
        path = variable.path
        self.family_variable_property(variable)
        if variable.hidden:
            if variable.hidden is True:
                self.frozen[path] = True
            elif self.frozen.get(path) is not True:
                frozen = variable.hidden.model_copy()
                frozen.attribute_name = "frozen"
                if frozen.ori_path is None:
                    frozen.ori_path = path
                if path in self.frozen:
                    if not isinstance(self.frozen, list):
                        self.frozen[path] = [self.frozen[path]]
                    self.frozen[path].append(frozen)
                else:
                    self.frozen[path] = frozen
        if path in self.frozen:
            self.objectspace.properties.add(path, "frozen", self.frozen[path])
            if not variable.auto_save and not variable.secret_manager:
                # if auto_save, save calculated value
                self.objectspace.properties.add(path, "force_default_on_freeze", True)
        if not variable.empty and self.objectspace.multis.get(variable.path, False):
            # a multi could not have "None" has value
            # to permit it, just add empty="false"
            self.objectspace.properties.add(path, "notempty", True)
        if variable.unique:
            self.objectspace.properties.add(path, "unique", True)
        if variable.unique is False:
            self.objectspace.properties.add(path, "notunique", True)
        if variable.auto_save:
            self.objectspace.properties.add(path, "force_store_value", True)
        self.convert_tag(variable)

    def convert_tag(self, variable):
        if not variable.tags:
            return
        for tag in variable.tags:
            self.check_tag(tag, variable.xmlfiles)
            self.objectspace.properties.add(variable.path, tag, True)
        self.objectspace.informations.add(variable.path, "tags", tuple(variable.tags))

    def check_tag(
        self,
        tag: str,
        xmlfiles: List[str],
    ):
        match = NAME_REGEXP.search(tag)
        if not match:
            msg = _(
                'invalid tag name "{0}" should only contains lowercase ascii character, number or _'
            ).format(tag)
            raise DictConsistencyError(msg, 82, xmlfiles)
        if tag in self.objectspace.modes_level:
            msg = _(
                'invalid tag name "{0}" should not be a name of an existing mode'
            ).format(tag)
            raise DictConsistencyError(msg, 82, xmlfiles)
        if tag in PROPERTIES_MAKE_SENSE:
            msg = _(
                'invalid tag name "{0}" should not be the name of an available properties'
            ).format(tag)
            raise DictConsistencyError(msg, 82, xmlfiles)

    def family_variable_property(
        self,
        obj: dict,
    ) -> None:
        for prop in PROPERTIES:
            if not hasattr(obj, prop):
                continue
            value = getattr(obj, prop)
            if not value:
                continue
            self.objectspace.properties.add(obj.path, prop, value)
        if obj.mode:
            self.objectspace.properties.add(obj.path, obj.mode, True)
