"""Node ID generation for Carve.

Language-agnostic ID generation using tree-sitter's grammar-defined fields.
Node IDs are hierarchical paths like 'module::ClassName::method_name'.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from tree_sitter import Node


# Body wrapper types whose children are "transparent" in the ID hierarchy.
# These nodes exist in the tree (for structural operations like insert)
# but their children's IDs skip them, giving cleaner paths.
BODY_WRAPPER_TYPES = frozenset(
    {
        "block",
        "class_body",
        "statement_block",
        "compound_statement",
        "do_group",
    }
)

# HTML element types that get shorthand names instead of type[index] IDs.
HTML_ELEMENT_SHORTHANDS: dict[str, str] = {
    "script_element": "script",
    "style_element": "style",
}

# CSS node types that use selector-based or child-based naming.
CSS_NAMED_TYPES: dict[str, str] = {
    "rule_set": "selectors",
    "keyframes_statement": "keyframes_name",
}


def _extract_css_node_name(node: Node) -> str | None:
    """Extract a name from a CSS node using selector or keyframes_name children.

    CSS nodes don't use tree-sitter's ``name`` field. Instead:
    - ``rule_set`` nodes use their ``selectors`` child text (e.g. ``.player``, ``#board``)
    - ``keyframes_statement`` nodes use their ``keyframes_name`` child text (e.g. ``pulse``)
    """
    child_type = CSS_NAMED_TYPES.get(node.type)
    if child_type is None:
        return None
    for child in node.named_children:
        if child.type == child_type and child.text:
            return child.text.decode("utf-8").strip()
    return None


def _extract_toml_node_name(node: Node) -> str | None:
    """Extract a name from a TOML node.

    - ``table`` nodes use their first dotted_key/bare_key child text (e.g. ``[server]``)
    - ``table_array_element`` nodes use their first dotted_key/bare_key child text
    """
    if node.type not in ("table", "table_array_element"):
        return None
    for child in node.named_children:
        if child.type in ("bare_key", "dotted_key", "quoted_key") and child.text:
            return child.text.decode("utf-8")
    return None


def _extract_yaml_node_name(node: Node) -> str | None:
    """Extract a name from a YAML node.

    - ``block_mapping_pair`` and ``flow_pair`` use their ``key`` field text.
    """
    if node.type not in ("block_mapping_pair", "flow_pair"):
        return None
    key = node.child_by_field_name("key")
    if key and key.text:
        return key.text.decode("utf-8")
    return None


def _extract_markdown_node_name(node: Node) -> str | None:
    """Extract a name from a Markdown node.

    - ``atx_heading`` → heading content text (e.g. ``Introduction``)
    - ``setext_heading`` → heading content text
    - ``fenced_code_block`` → info string language (e.g. ``python``)
    """
    if node.type in ("atx_heading", "setext_heading"):
        for child in node.named_children:
            if child.type in ("heading_content", "inline") and child.text:
                return child.text.decode("utf-8").strip()
            if child.type == "paragraph" and child.text:
                return child.text.decode("utf-8").strip()
        return None
    if node.type == "fenced_code_block":
        for child in node.named_children:
            if child.type == "info_string" and child.text:
                return child.text.decode("utf-8").strip()
        return None
    return None


def extract_node_name(node: Node) -> str | None:
    """Extract the name from a tree-sitter node using the grammar's 'name' field.

    Language-agnostic — works for any tree-sitter grammar that defines a
    ``name`` field (Python, JavaScript, etc.). Falls back to CSS-specific
    naming (selectors, keyframes) and HTML element shorthands.

    Returns:
        The name string if the node has a ``name`` field, None otherwise.
    """
    name_child = node.child_by_field_name("name")
    if name_child and name_child.text:
        return name_child.text.decode("utf-8")

    # CSS-specific: extract selector or keyframes name
    css_name = _extract_css_node_name(node)
    if css_name is not None:
        return css_name

    # HTML element shorthands: script_element -> "script", style_element -> "style"
    if node.type in HTML_ELEMENT_SHORTHANDS:
        return HTML_ELEMENT_SHORTHANDS[node.type]

    # TOML-specific: table/table_array_element key names
    toml_name = _extract_toml_node_name(node)
    if toml_name is not None:
        return toml_name

    # YAML-specific: block_mapping_pair/flow_pair key names
    yaml_name = _extract_yaml_node_name(node)
    if yaml_name is not None:
        return yaml_name

    # Markdown-specific: heading text, code block info strings
    md_name = _extract_markdown_node_name(node)
    if md_name is not None:
        return md_name

    return None
