"""Rust parser — tree-sitter primary (accurate end_line), regex fallback."""

from __future__ import annotations

import re

from codectx.parser.types import DependencyRecord, EntityRecord, ParseResult

# ── regex patterns (fallback) ────────────────────────────────────────────────
_USE_RE       = re.compile(r"^\s*use\s+([\w::{},\s*]+);", re.MULTILINE)
_MOD_DECL_RE  = re.compile(r"^\s*(?:pub\s+)?mod\s+(\w+)\s*;", re.MULTILINE)
_STRUCT_RE    = re.compile(r"^(\s*)(?:pub(?:\(\w+\))?\s+)?struct\s+(\w+)", re.MULTILINE)
_ENUM_RE      = re.compile(r"^(\s*)(?:pub(?:\(\w+\))?\s+)?enum\s+(\w+)", re.MULTILINE)
_TRAIT_RE     = re.compile(r"^(\s*)(?:pub(?:\(\w+\))?\s+)?trait\s+(\w+)", re.MULTILINE)
_FN_RE        = re.compile(r"^(\s*)(?:pub(?:\(\w+\))?\s+)?(?:async\s+)?fn\s+(\w+)\s*(?:<[^>]*)?\s*\(([^)]*)\)", re.MULTILINE)
_CONST_RE     = re.compile(r"^(\s*)(?:pub(?:\(\w+\))?\s+)?const\s+(\w+)\s*:", re.MULTILINE)
_TYPE_ALIAS_RE = re.compile(r"^(\s*)(?:pub(?:\(\w+\))?\s+)?type\s+(\w+)\s*=", re.MULTILINE)


# ── tree-sitter implementation ───────────────────────────────────────────────

def _ts_parse_rust(file_path: str, content: str, src: bytes) -> "ParseResult | None":
    try:
        from codectx.parser.ts_util import get_parser, node_text as nt, start_line as sl, end_line as el, walk_type
    except ImportError:
        return None

    parser, _ = get_parser("rust")
    if parser is None:
        return None
    try:
        tree = parser.parse(src)
    except Exception:
        return None

    root = tree.root_node
    result = ParseResult(file_path=file_path, language="rust")

    # use declarations
    for node in walk_type(root, "use_declaration"):
        text = nt(node, src).strip()
        path = re.sub(r"^use\s+", "", text).rstrip("; \n")
        is_local = path.startswith(("crate::", "super::", "self::"))
        result.dependencies.append(DependencyRecord(
            path=path, type="use", line_number=sl(node), is_local=is_local,
        ))

    # extern crate
    for node in walk_type(root, "extern_crate_declaration"):
        name_node = node.child_by_field_name("name")
        if name_node:
            result.dependencies.append(DependencyRecord(
                path=nt(name_node, src), type="extern_crate", line_number=sl(node),
            ))

    # top-level structs, enums, traits
    for ts_type, etype in [("struct_item", "struct"), ("enum_item", "enum"), ("trait_item", "trait")]:
        for node in walk_type(root, ts_type):
            name_node = node.child_by_field_name("name")
            if name_node is None:
                continue
            name = nt(name_node, src)
            result.entities.append(EntityRecord(
                name=name, qualified_name=name, type=etype, kind=etype,
                start_line=sl(node), end_line=el(node),
                visibility=_rust_vis(node, src),
                language="rust",
            ))

    # type aliases and constants
    for ts_type, etype in [("type_item", "type_alias"), ("const_item", "constant")]:
        for node in walk_type(root, ts_type):
            name_node = node.child_by_field_name("name")
            if name_node:
                name = nt(name_node, src)
                result.entities.append(EntityRecord(
                    name=name, qualified_name=name, type=etype,
                    start_line=sl(node), end_line=el(node), language="rust",
                ))

    # top-level functions (not inside impl blocks)
    for node in walk_type(root, "function_item"):
        # skip if parent is impl body
        if node.parent and node.parent.type == "declaration_list":
            continue
        name_node = node.child_by_field_name("name")
        if name_node is None:
            continue
        name = nt(name_node, src)
        params_node = node.child_by_field_name("parameters")
        sig = f"fn {name}{nt(params_node, src)}" if params_node else f"fn {name}()"
        result.entities.append(EntityRecord(
            name=name, qualified_name=name, type="function", kind="function",
            signature=sig, start_line=sl(node), end_line=el(node),
            visibility=_rust_vis(node, src), language="rust",
        ))

    # impl blocks → methods
    for impl_node in walk_type(root, "impl_item"):
        type_node = impl_node.child_by_field_name("type")
        impl_type = nt(type_node, src).split("<")[0].strip() if type_node else ""
        body_node = impl_node.child_by_field_name("body")
        if body_node is None:
            continue
        for fn_node in walk_type(body_node, "function_item"):
            name_node = fn_node.child_by_field_name("name")
            if name_node is None:
                continue
            name = nt(name_node, src)
            qname = f"{impl_type}.{name}" if impl_type else name
            params_node = fn_node.child_by_field_name("parameters")
            sig = f"fn {name}{nt(params_node, src)}" if params_node else f"fn {name}()"
            result.entities.append(EntityRecord(
                name=name, qualified_name=qname, type="method", kind="method",
                signature=sig, start_line=sl(fn_node), end_line=el(fn_node),
                parent=impl_type, visibility=_rust_vis(fn_node, src), language="rust",
            ))

    return result


def _rust_vis(node, src: bytes) -> str:
    """Check if a node has a `pub` visibility modifier."""
    for child in node.children:
        if child.type == "visibility_modifier":
            return "public"
    return "private"


# ── regex fallback ────────────────────────────────────────────────────────────

def _regex_parse_rust(file_path: str, content: str) -> ParseResult:
    result = ParseResult(file_path=file_path, language="rust")

    def _ln(pos: int) -> int:
        return content[:pos].count("\n") + 1

    def _doc_before(pos: int) -> str:
        lines = content[:pos].splitlines()
        docs = []
        for line in reversed(lines):
            s = line.strip()
            if s.startswith("///"):
                docs.append(s[3:].strip())
            else:
                break
        return " ".join(reversed(docs))

    for m in _USE_RE.finditer(content):
        path = m.group(1).strip()
        result.dependencies.append(DependencyRecord(
            path=path, type="use", line_number=_ln(m.start()),
            is_local=path.startswith(("crate::", "super::", "self::")),
        ))
    for m in _MOD_DECL_RE.finditer(content):
        result.dependencies.append(DependencyRecord(
            path=m.group(1), type="mod", line_number=_ln(m.start()), is_local=True,
        ))
    for pattern, etype in [(_STRUCT_RE, "struct"), (_ENUM_RE, "enum"), (_TRAIT_RE, "trait")]:
        for m in pattern.finditer(content):
            result.entities.append(EntityRecord(
                name=m.group(2), type=etype, qualified_name=m.group(2),
                start_line=_ln(m.start()), end_line=_ln(m.start()),
                docs=_doc_before(m.start()), language="rust",
            ))
    for m in _FN_RE.finditer(content):
        result.entities.append(EntityRecord(
            name=m.group(2), type="function", qualified_name=m.group(2),
            signature=f"fn {m.group(2)}({m.group(3) or ''})",
            start_line=_ln(m.start()), end_line=_ln(m.start()),
            docs=_doc_before(m.start()),
            visibility="public" if "pub" in content[m.start():m.start() + 30] else "private",
            language="rust",
        ))
    for m in _CONST_RE.finditer(content):
        result.entities.append(EntityRecord(
            name=m.group(2), type="constant", qualified_name=m.group(2),
            start_line=_ln(m.start()), end_line=_ln(m.start()), language="rust",
        ))
    for m in _TYPE_ALIAS_RE.finditer(content):
        result.entities.append(EntityRecord(
            name=m.group(2), type="type_alias", qualified_name=m.group(2),
            start_line=_ln(m.start()), end_line=_ln(m.start()), language="rust",
        ))
    return result


# ── public entry point ────────────────────────────────────────────────────────

def parse_rust(file_path: str, content: str) -> ParseResult:
    src = content.encode("utf-8")
    result = _ts_parse_rust(file_path, content, src)
    if result is not None:
        return result
    return _regex_parse_rust(file_path, content)
