import logging
from typing import List
from typing import Optional

from docutils import nodes
from docutils.nodes import NodeVisitor
from lsprotocol.types import DocumentSymbol
from lsprotocol.types import Position
from lsprotocol.types import Range
from lsprotocol.types import SymbolKind


class SymbolVisitor(NodeVisitor):
    """A visitor used to build the hierarchy we return from a
    ``textDocument/documentSymbol`` request.

    """

    def __init__(self, logger: Optional[logging.Logger] = None, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.logger = logger or logging.getLogger(__name__)

        self.symbols: List[DocumentSymbol] = []
        """Holds the top-level symbols for the document"""

        self.symbol_stack: List[DocumentSymbol] = []
        """Used to maintain the current symbol hierarchy."""

    @property
    def current_symbol(self) -> Optional[DocumentSymbol]:
        if len(self.symbol_stack) == 0:
            return None

        return self.symbol_stack[-1]

    def push_symbol(self):
        symbol = DocumentSymbol(
            name="",
            kind=SymbolKind.String,
            range=Range(
                start=Position(line=1, character=0),
                end=Position(line=1, character=10),
            ),
            selection_range=Range(
                start=Position(line=1, character=0),
                end=Position(line=1, character=10),
            ),
            children=[],
        )
        current_symbol = self.current_symbol

        if not current_symbol:
            self.symbols.append(symbol)
        else:
            if current_symbol.children is None:
                current_symbol.children = [symbol]
            else:
                current_symbol.children.append(symbol)

        self.symbol_stack.append(symbol)
        return symbol

    def pop_symbol(self):
        self.symbol_stack.pop()

    def visit_section(self, node: nodes.Node) -> None:
        self.push_symbol()

    def depart_section(self, node: nodes.Node) -> None:
        self.pop_symbol()

    def visit_title(self, node: nodes.Node) -> None:
        symbol = self.current_symbol
        has_parent = True

        if symbol is None:
            has_parent = False
            symbol = self.push_symbol()

        name = node.astext()
        line = (node.line or 1) - 1

        symbol.name = name
        symbol.range.start.line = line
        symbol.range.end.line = line
        symbol.range.end.character = len(name) - 1
        symbol.selection_range.start.line = line
        symbol.selection_range.end.line = line
        symbol.selection_range.end.character = len(name) - 1

        if not has_parent:
            self.pop_symbol()

    def depart_title(self, node: nodes.Node) -> None:
        pass

    def visit_a_directive(self, node: nodes.Element):
        symbol = self.push_symbol()

        name = node["text"]  # type: ignore
        line = (node.line or 1) - 1

        symbol.name = name
        symbol.kind = SymbolKind.Class
        symbol.range.start.line = line
        symbol.range.end.line = line
        symbol.range.end.character = len(name) - 1
        symbol.selection_range.start.line = line
        symbol.selection_range.end.line = line
        symbol.selection_range.end.character = len(name) - 1

    def depart_a_directive(self, node: nodes.Node):
        self.pop_symbol()

    # TODO: Enable symbols for roles
    #       However the reported line numbers can be inaccurate...
    def visit_a_role(self, node: nodes.Node) -> None:
        ...

    def depart_a_role(self, node: nodes.Node) -> None:
        ...

    # TODO: Enable symbols for definition list items
    #       However the reported line numbers appear to be inaccurate...

    def visit_Text(self, node: nodes.Node) -> None:
        pass

    def depart_Text(self, node: nodes.Node) -> None:
        pass

    def unknown_visit(self, node: nodes.Node) -> None:
        pass

    def unknown_departure(self, node: nodes.Node) -> None:
        pass
