"""
Silique (https://www.silique.fr)
Copyright (C) 2022-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 typing import Any, Optional
from io import BytesIO

from tiramisu import owners

from rougail.utils import undefined


class CommonOutput:

    def __init__(
        self,
        family,
        parent,
        root,
        _yaml,
        out,
        *,
#        is_leader: bool = False,
        root_family: Optional["OutputFamily"] = None,
        no_icon: bool = False,
        ) -> None:
        self.tree = None
        self.root = root
        self.parent = parent
        if isinstance(family, tuple):
            raise Exception()
        self.family = family
#        self.is_leader = is_leader
        self.no_icon = no_icon
        self._yaml = _yaml
        self.out = out
        if root_family is None:
            root_family = self
            self.variable_default_enable = False
            self.variable_hidden_enable = False
            self.value_modified_enable = False
            self.value_unmodified_enable = False
            self.value_default_enable = False
        self.root_family = root_family

    def header(self):
        raise NotImplementedError()

    def display_errors(
        self,
        errors,
    ) -> None:
        tree = self.error_header()
        for error in errors:
            self.parse_error(tree, error)
        self.out.append(tree)

    def parse_error(self, tree, error):
        if isinstance(error, list):
            for err in error:
                self.parse_error(tree, err)
            return tree
        elif isinstance(error, dict):
            for key, value in error.items():
                if key is None:
                    # it's variables, no more families
                    self.parse_error(tree, value)
                else:
                    sub_tree = self.parse_error(tree, key)
                    self.parse_error(sub_tree, value)
        else:
            return self.display_error(tree, error)

    def add_family(
        self,
        family,
    ) -> 'OutputFamily':
        properties = family.property.get()
        if "hidden" in properties:
            self.root_family.variable_hidden_enable = True
            color = self.variable_hidden_color
        else:
            self.root_family.variable_default_enable = True
            color = None
        family_output = self.colorize(
                [
                    {
                        "value": family.description(),
                        "color": color,
                        "loaded_from": None,
                    }
                ]
            )
        return self.__class__(
            family_output,
            self.get_tree(),
            self.root,
            self._yaml,
            self.out,
            root_family = self.root_family,
        )

    def add_variable(
        self, option, value: Any = undefined, leader_index: Optional[int] = None
    ):
        values = []
        properties = option.property.get()
        if not option.owner.isdefault() and option.information.get(
            "default_value_makes_sense", True
        ):
            self.get_modified_value(option, value, values)
        if "force_store_value" not in properties or not values:
            self.get_default_values(option, values, leader_index)
        #
        if "hidden" in properties:
            self.root_family.variable_hidden_enable = True
            variable_color = self.variable_hidden_color
        else:
            self.root_family.variable_default_enable = True
            variable_color = self.variable_normal_color
        key = self.colorize(
            [
                {
                    "value": option.description(),
                    "color": variable_color,
                    "loaded_from": None,
                }
            ]
        )
        value = self.colorize(
            values,
            option,
        )
        return key, value

    def get_modified_value(self, option: "Option", value: Any, values: list) -> None:
        self.root_family.value_modified_enable = True
        follower_index = option.index()
        if follower_index is not None:
            loaded_from = option.information.get(
                f"loaded_from_{follower_index}", None
            )
        else:
            loaded_from = option.information.get("loaded_from", None)
        if value is undefined:
            value = option.value.get()
        values.append(
            {
                "value": value,
                "color": self.value_modified_color,
                "loaded_from": loaded_from,
            }
        )

    def get_default_values(self, option: "Option", values: list, leader_index: Optional[int]) -> None:
        meta_config = self.root.config
        meta_option = option
        index = option.index()
        while True:
            default_value = meta_option.value.default()
            if leader_index is not None:
                if len(default_value) > leader_index:
                    default_value = default_value[leader_index]
                else:
                    default_value = None
            is_root_metaconfig = False
            if (
                meta_config is None
                or not meta_config.path()
                or "." not in meta_config.path()
            ):
                is_root_metaconfig = True
            if (
                (values and default_value is None) or default_value == []
            ) and is_root_metaconfig:
                break
            if not values:
                self.root_family.value_unmodified_enable = True
                color = self.value_unmodified_color
            else:
                self.root_family.value_default_enable = True
                color = self.value_default_color
            meta_config = self.get_subconfig_with_default_value(meta_option)
            meta_option = meta_config.option(option.path(), index)
            if is_root_metaconfig:
                loaded_from = None
            else:
                if index is not None:
                    key = f"loaded_from_{index}"
                else:
                    key = "loaded_from"
                loaded_from = meta_option.information.get(key, None)
            values.append(
                {
                    "value": default_value,
                    "color": color,
                    "loaded_from": loaded_from,
                }
            )
            if is_root_metaconfig:
                break
#
#    def get_default_value(self, option: "Option", values: list, leader_index: Optional[int]) -> None:
#        follower_index = option.index()
##        if leader_index is None and follower_index is not None:
##            if not option.isfollower() or not option.issubmulti():
##                default_value = None
##            else:
##                default_value = []
##        else:
##            default_value = None
#        if not values:
#            self.root_family.value_unmodified_enable = True
#            color = self.value_unmodified_color
#        else:
#            self.root_family.value_default_enable = True
#            color = self.value_default_color
#        if follower_index is not None:
#            loaded_from = option.information.get(
#                f"loaded_from_{follower_index}", None
#            )
#        else:
#            loaded_from = option.information.get("loaded_from", None)
#        values.append(
#            {
#                "value": default_value,
#                "color": color,
#                "loaded_from": loaded_from,
#            }
#        )

    def get_subconfig_with_default_value(self, config):
        default_owner = config.owner.default()
        if default_owner == owners.default:
            return self.root.config
        if not self.root.config_owner_is_path:
            meta_config = self.root.config
            while True:
                meta_config = meta_config.parent()
                if not meta_config.owner.isdefault():
                    break
        else:
            meta_config = self.root.metaconfig
            for child in default_owner.split(".")[1:]:
                meta_config = meta_config.config(child)
        return meta_config

    def convert_value(
        self,
        option,
        value: Any,
    ) -> str:
        """Dump variable, means transform bool, ... to yaml string"""
        if (
            value is not None
            and not self.root.show_secrets
            and option.type() == "password"
        ):
            return "*" * 10
        if isinstance(value, str):
            return value
        with BytesIO() as ymlfh:
            self._yaml.dump(value, ymlfh)
            ret = ymlfh.getvalue().decode("utf-8").strip()
        if ret.endswith("..."):
            ret = ret[:-3].strip()
        return ret
