# SPDX-FileCopyrightText: 2022 Chris Heywood
#
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

from gettext import gettext as _
from gi.repository import GObject

from enum import IntEnum, auto
from typing import Optional, Self

from iotas.note import Note


class CategorySpecialPurpose(IntEnum):
    ALL = auto()
    UNCATEGORISED = auto()


class Category(GObject.Object):
    # Translators: Title
    ALL_TITLE = _("All Notes")
    # Translators: Title
    UNCATEGORISED_TITLE = _("Uncategorized")

    def __init__(self, name: str, note_count: int) -> None:
        super().__init__()
        self.__parent = None
        self.name = name
        self.__sub_category_name = ""
        self.__child_note_count = 0
        self.__note_count_with_children = 0
        self.note_count = note_count
        self.__children: list[Category] = []
        self.__special_purpose: Optional[CategorySpecialPurpose] = None

    def includes_note(self, note: Note) -> bool:
        """Whether category or sub-category includes note.

        :param Note note: Note to check
        :returns: Whether included
        :rtype: bool
        """
        if note.locally_deleted:
            include = False
        elif self.__special_purpose == CategorySpecialPurpose.ALL:
            include = True
        elif self.__special_purpose == CategorySpecialPurpose.UNCATEGORISED:
            include = note.category == ""
        elif note.category == self.__name:
            include = True
        elif note.category.startswith(self.__name + "/"):
            include = True
        else:
            include = False
        return include

    def increase_note_count(self, amount: int = 1) -> None:
        """Increase note count.

        :param int amount: Amount to change by
        """
        self.note_count += amount
        if self.__parent is not None:
            self.__parent.increase_child_note_count(amount)

    def decrease_note_count(self, amount: int = 1) -> None:
        """Decrease note count.

        :param int amount: Amount to change by
        """
        self.note_count -= amount
        if self.__parent is not None:
            self.__parent.decrease_child_note_count(amount)

    def increase_child_note_count(self, amount: int = 1) -> None:
        """Increase children's note count.

        :param int amount: Amount to change by
        """
        self.child_note_count += amount
        if self.__parent is not None:
            self.__parent.increase_child_note_count(amount)

    def decrease_child_note_count(self, amount: int = 1) -> None:
        """Decrease children's note count.

        :param int amount: Amount to change by
        """
        self.child_note_count -= amount
        if self.__parent is not None:
            self.__parent.decrease_child_note_count(amount)

    def add_child(self, category: Category) -> None:
        """Link a child category.

        :param Category category: The new child
        """
        if category not in self.__children:
            self.__children.append(category)

    def remove_child(self, category: Category) -> None:
        """Unlink a child category.

        :param Category category: The child
        """
        if category in self.__children:
            self.__children.remove(category)

    def get_parent(self) -> Optional[Self]:
        """Get the parent category.

        :returns: The parent or None
        :rtype: Optional[Self]
        """
        return self.__parent

    def set_parent(self, category: Optional[Category]) -> None:
        """Set the parent category.

        :param Optional[Category] category: The parent or None
        """
        self.__parent = category
        if category is not None:
            self.__refresh_sub_category_name()

    # TODO remove with CategoryManager.recreate_category_for_expandable_change and Gtk v4.10+
    def duplicate(self) -> Self:
        """Duplicate the category.

        :returns: The duplicate
        :rtype: Self
        """
        new_category = Category(self.name, self.__note_count)
        new_category.set_parent(self.__parent)
        for child in self.__children:
            new_category.add_child(child)
            child.set_parent(new_category)
            new_category.increase_child_note_count(child.note_count)
        return new_category

    @GObject.Property(type=str, default="")
    def name(self) -> str:
        return self.__name

    @name.setter
    def set_name(self, value: str) -> None:
        self.__name = value
        self.__refresh_display_name()
        self.__sub_category_path = "/" in value
        if self.__sub_category_path:
            self.__refresh_sub_category_name()

    @GObject.Property(type=str, default="")
    def display_name(self) -> str:
        return self.__display_name

    @GObject.Property(type=int, default=0)
    def note_count(self) -> int:
        return self.__note_count

    @note_count.setter
    def set_note_count(self, value: int) -> None:
        self.__note_count = value
        self.note_count_with_children = self.child_note_count + value

    @GObject.Property(type=int, default=0)
    def child_note_count(self) -> int:
        return self.__child_note_count

    @child_note_count.setter
    def set_child_note_count(self, value: int) -> None:
        self.__child_note_count = value
        self.note_count_with_children = self.note_count + value

    @GObject.Property(type=int, default=0)
    def note_count_with_children(self) -> int:
        return self.__note_count_with_children

    @note_count_with_children.setter
    def set_note_count_with_children(self, value: int) -> None:
        self.__note_count_with_children = value

    @GObject.Property(type=int, default=False)
    def number_of_direct_children(self) -> int:
        return len(self.__children)

    @GObject.Property(type=int, default=None)
    def special_purpose(self) -> Optional[CategorySpecialPurpose]:
        return self.__special_purpose

    @special_purpose.setter
    def set_special_purpose(self, value: Optional[CategorySpecialPurpose]) -> None:
        self.__special_purpose = value

    @GObject.Property(type=str, default="")
    def sub_category_name(self) -> str:
        return self.__sub_category_name

    @GObject.Property(type=bool, default=False)
    def is_sub_category(self) -> bool:
        return self.__sub_category_path

    def __refresh_display_name(self):
        self.__display_name = Category.generate_display_name(self.__name)

    def __refresh_sub_category_name(self):
        if self.__parent is None and not self.__sub_category_path:
            return
        self.__sub_category_name = Category.generate_sub_category_name(self.__name)

    @staticmethod
    def generate_display_name(name: str) -> str:
        if "/" in name:
            return " / ".join(name.split("/"))
        else:
            return name

    @staticmethod
    def generate_sub_category_name(name: str) -> str:
        if "/" in name:
            return " / ".join(name.split("/")[1:])
        else:
            # Shouldn't occur
            return name
