# NextGIS Connect
# Copyright (C) 2026  NextGIS
#
# 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 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, see <https://www.gnu.org/licenses/>.

from typing import List, Optional, cast

from qgis.PyQt.QtCore import QModelIndex, QObject, QSortFilterProxyModel

from .model import QNGWResourceItem, QNGWResourceTreeModel


class NgConnectProxyModel(QSortFilterProxyModel):
    def __init__(self, parent: Optional[QObject]) -> None:
        super().__init__(parent)
        self.setDynamicSortFilter(True)
        self.__resources_id = []
        self.__expandex_resources = []

    def set_resources_id(self, resources_id: List[int]) -> None:
        self.__resources_id = resources_id

        self.invalidateFilter()

        self.layoutAboutToBeChanged.emit()
        self.__extract_expanded_resources()
        self.layoutChanged.emit()

    @property
    def expanded_resources(self) -> List[int]:
        return self.__expandex_resources

    def hasChildren(
        self,
        parent: QModelIndex = QModelIndex(),  # noqa: B008
    ) -> bool:
        source_model = self.sourceModel()
        if source_model is None:
            return False

        source_parent = self.mapToSource(parent)
        return super().hasChildren(parent) or source_model.canFetchMore(
            source_parent
        )

    def canFetchMore(self, parent: QModelIndex) -> bool:
        source_model = self.sourceModel()
        if source_model is None:
            return False

        return source_model.canFetchMore(self.mapToSource(parent))

    def fetchMore(self, parent: QModelIndex) -> None:
        source_model = self.sourceModel()
        if source_model is None:
            return

        source_model.fetchMore(self.mapToSource(parent))

    def filterAcceptsRow(
        self, source_row: int, source_parent: QModelIndex
    ) -> bool:
        if self.sourceModel() is None or len(self.__resources_id) == 0:
            return True

        source_index = self.sourceModel().index(source_row, 0, source_parent)
        return (
            self.__accept_resource(source_index)
            or self.__accept_ancestor(source_index)
            or self.__accept_descendant(source_index)
        )

    def __accept_resource(self, source_index: QModelIndex) -> bool:
        resource_id = source_index.data(QNGWResourceItem.NGWResourceIdRole)

        return resource_id in self.__resources_id

    def __accept_ancestor(self, source_index: QModelIndex) -> bool:
        parent_index = source_index.parent()
        if not parent_index.isValid():
            return False

        if self.__accept_resource(parent_index):
            return True

        return self.__accept_ancestor(parent_index)

    def __accept_descendant(self, source_index: QModelIndex) -> bool:
        for row in range(self.sourceModel().rowCount(source_index)):
            child_index = self.sourceModel().index(row, 0, source_index)
            if self.__accept_resource(child_index):
                return True
            if self.__accept_descendant(child_index):
                return True

        return False

    def __extract_expanded_resources(self) -> None:
        self.__expandex_resources = []

        if self.sourceModel() is None or (
            len(self.__resources_id) == 1 and self.__resources_id[0] == -1
        ):
            return

        model = cast(QNGWResourceTreeModel, self.sourceModel())

        closest_to_root = self.__extract_closest_to_root()
        expanded_resources = set()

        for resource_id in closest_to_root:
            index = model.index_from_id(resource_id)
            while index and index.isValid():
                parent = index.parent()
                if not parent.isValid():
                    break
                parent_id = model.resource(parent).resource_id
                expanded_resources.add(parent_id)
                index = parent

        self.__expandex_resources = list(expanded_resources)

    def __extract_closest_to_root(self) -> List[int]:
        closest_to_root = set()

        model = cast(QNGWResourceTreeModel, self.sourceModel())

        for resource_id in self.__resources_id:
            index = model.index_from_id(resource_id)

            has_parent_in_found_list = False

            while index and index.isValid():
                parent = index.parent()
                if not parent.isValid():
                    break

                parent_id = model.resource(parent).resource_id
                if parent_id != 0 and parent_id in self.__resources_id:
                    has_parent_in_found_list = True
                    break

                index = parent

            if not has_parent_in_found_list:
                closest_to_root.add(resource_id)

        return list(closest_to_root)
