# -*- coding: utf-8 -*-
# gui/components/_tree_state_management.py
"""Сервис управления состоянием дерева: поиск, раскрытие, перезагрузка (композиция)."""

from __future__ import annotations

from typing import TYPE_CHECKING, Optional

from PySide6.QtCore import Qt
from PySide6.QtWidgets import QTreeWidgetItem, QAbstractItemView
from error_logger import log_exception

if TYPE_CHECKING:
    from gui.components.topology_tree_widget import TopologyTreeWidget


class TreeStateManager:
    """Поиск узлов, снимок/восстановление раскрытия, перезагрузка дерева."""

    def __init__(self, host: "TopologyTreeWidget") -> None:
        self._host = host

    def refresh_node(self, node_type: str, node_id: str) -> None:
        """Обновление данных узла и его детей."""
        found_item = self._find_item_by_data(node_type, node_id)
        if found_item:
            while found_item.childCount() > 0:
                found_item.removeChild(found_item.child(0))
            item_data = found_item.data(0, Qt.UserRole)
            if item_data:
                item_data['children_loaded'] = False
                found_item.setData(0, Qt.UserRole, item_data)
                if item_data.get('has_children'):
                    stub = QTreeWidgetItem(["Загрузка..."])
                    stub.setData(0, Qt.UserRole, {"is_stub": True})
                    found_item.addChild(stub)
            self._host._tree.resizeColumnToContents(0)

    def _find_item_by_data(self, node_type: str, node_id: str) -> Optional[QTreeWidgetItem]:
        """Поиск элемента дерева по типу и ID."""
        def search(item: QTreeWidgetItem) -> Optional[QTreeWidgetItem]:
            d = item.data(0, Qt.UserRole)
            if d and d.get('type') == node_type and d.get('id') == node_id:
                return item
            for i in range(item.childCount()):
                result = search(item.child(i))
                if result:
                    return result
            return None
        for i in range(self._host._tree.topLevelItemCount()):
            result = search(self._host._tree.topLevelItem(i))
            if result:
                return result
        return None

    def _find_or_load_item_by_data(
        self, node_type: str, node_id: str,
    ) -> Optional[QTreeWidgetItem]:
        """Найти узел, лениво подгружая и раскрывая ветки."""
        node_type = str(node_type or "")
        node_id = str(node_id or "")
        if not node_type or not node_id:
            return None
        found = self._find_item_by_data(node_type, node_id)
        if found is not None:
            return found

        def search(item: QTreeWidgetItem) -> Optional[QTreeWidgetItem]:
            d = item.data(0, Qt.UserRole) or {}
            if d.get("is_stub") or d.get("is_error"):
                return None
            ct = str(d.get("type") or "")
            ci = str(d.get("id") or "")
            if ct == node_type and ci == node_id:
                return item
            if ct == node_type and ci != node_id:
                return None
            if bool(d.get("has_children")) and not bool(d.get("children_loaded")):
                try:
                    self._host._node_builder.load_children(item)
                except Exception as e:
                    log_exception(__name__, "_find_or_load.load_children", e)
            for idx in range(item.childCount()):
                result = search(item.child(idx))
                if result is not None:
                    try:
                        item.setExpanded(True)
                    except Exception as e:
                        log_exception(__name__, "_find_or_load.setExpanded", e)
                    return result
            return None

        for idx in range(self._host._tree.topLevelItemCount()):
            result = search(self._host._tree.topLevelItem(idx))
            if result is not None:
                return result
        return None

    @staticmethod
    def _expand_item_parents(item: QTreeWidgetItem) -> None:
        """Раскрыть цепочку родителей до корня."""
        parent = item.parent()
        while parent is not None:
            parent.setExpanded(True)
            parent = parent.parent()

    def clear_tree(self) -> None:
        """Полная очистка дерева."""
        self._host._tree.clear()
        self._host._loading_nodes.clear()

    @staticmethod
    def _item_key(item: QTreeWidgetItem) -> tuple[str, str] | None:
        """Уникальный ключ узла: (type, id)."""
        data = item.data(0, Qt.UserRole) or {}
        nt = str(data.get("type") or "")
        ni = str(data.get("id") or "")
        if not nt or not ni:
            return None
        if data.get("is_stub") or data.get("is_error"):
            return None
        return nt, ni

    def _collect_expanded_paths(self) -> list[list[tuple[str, str]]]:
        """Снять снимок раскрытых узлов как пути от корня."""
        paths: list[list[tuple[str, str]]] = []
        def walk(item: QTreeWidgetItem, path: list[tuple[str, str]]) -> None:
            key = self._item_key(item)
            if key is None:
                return
            path = [*path, key]
            if item.isExpanded():
                paths.append(path)
            for idx in range(item.childCount()):
                walk(item.child(idx), path)
        for idx in range(self._host._tree.topLevelItemCount()):
            walk(self._host._tree.topLevelItem(idx), [])
        return paths

    def _find_child_by_key(self, parent: QTreeWidgetItem, key: tuple[str, str]) -> Optional[QTreeWidgetItem]:
        for idx in range(parent.childCount()):
            child = parent.child(idx)
            if self._item_key(child) == key:
                return child
        return None

    def _find_root_by_key(self, key: tuple[str, str]) -> Optional[QTreeWidgetItem]:
        for idx in range(self._host._tree.topLevelItemCount()):
            item = self._host._tree.topLevelItem(idx)
            if self._item_key(item) == key:
                return item
        return None

    def _expand_path(self, path: list[tuple[str, str]]) -> None:
        """Раскрыть путь root->... с подзагрузкой ленивых детей."""
        if not path:
            return
        current = self._find_root_by_key(path[0])
        if current is None:
            return
        current.setExpanded(True)
        for key in path[1:]:
            data = current.data(0, Qt.UserRole) or {}
            if data.get("has_children") and not data.get("children_loaded"):
                self._host._node_builder.load_children(current)
            child = self._find_child_by_key(current, key)
            if child is None:
                return
            child.setExpanded(True)
            current = child

    def _restore_tree_state(
        self,
        expanded_paths: list[list[tuple[str, str]]],
        selected_key: tuple[str, str] | None,
    ) -> None:
        """Восстановить раскрытие и выбранный элемент после reload."""
        for path in expanded_paths:
            self._expand_path(path)
        if selected_key:
            sel = self._find_item_by_data(selected_key[0], selected_key[1])
            if sel is not None:
                self._host._tree.setCurrentItem(sel)
                self._host._tree.scrollToItem(
                    sel, QAbstractItemView.ScrollHint.PositionAtCenter,
                )

    def reload_tree(self, preserve_state: bool = True) -> None:
        """Полная перезагрузка дерева с опциональным сохранением состояния."""
        expanded_paths: list[list[tuple[str, str]]] = []
        selected_key: tuple[str, str] | None = None
        if preserve_state:
            expanded_paths = self._collect_expanded_paths()
            current = self._host._tree.currentItem()
            if current is not None:
                selected_key = self._item_key(current)
        self.clear_tree()
        self._host._node_builder.load_root_nodes()
        if preserve_state:
            self._restore_tree_state(expanded_paths, selected_key)
        self._host._tree.resizeColumnToContents(0)

    def select_node(
        self,
        node_type: str,
        node_id: str,
        *,
        emit_selected: bool = False,
        allow_load: bool = True,
        expand_parents: bool = True,
    ) -> bool:
        """Программно выделить узел дерева. Возвращает True при успехе."""
        rt = str(node_type or "")
        ri = str(node_id or "")
        if allow_load:
            item = self._find_or_load_item_by_data(rt, ri)
        else:
            item = self._find_item_by_data(rt, ri)
        if item is None:
            return False
        if expand_parents:
            self._expand_item_parents(item)
        self._host._tree.setCurrentItem(item)
        self._host._tree.scrollToItem(
            item, QAbstractItemView.ScrollHint.PositionAtCenter,
        )
        if emit_selected:
            d = item.data(0, Qt.UserRole) or {}
            if not d.get("is_stub") and not d.get("is_error"):
                self._host.nodeSelected.emit(
                    str(d.get("type") or ""),
                    str(d.get("id") or ""),
                    dict(d.get("display_data") or {}),
                )
        return True
