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

from __future__ import annotations

import re

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

# ── regex patterns (fallback) ────────────────────────────────────────────────
RE_IMPORT  = re.compile(r"^\s*import\s+([\w.]+);", re.MULTILINE)
RE_CLASS   = re.compile(r"\b(class|interface|enum|record)\s+([A-Za-z_][\w$]*)")
RE_METHOD  = re.compile(r"\b(?:public|protected|private)?\s*(?:static\s+)?(?:final\s+)?[\w<>,\[\]]+\s+([A-Za-z_][\w$]*)\s*\([^;{}]*\)\s*\{", re.MULTILINE)
RE_CALL    = re.compile(r"\b([A-Za-z_][\w$]*)\s*\(")
KEYWORDS   = {"if", "for", "while", "switch", "catch", "new", "return", "super", "this", "try"}


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

def _ts_parse_java(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("java")
    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="java")

    # imports
    for node in walk_type(root, "import_declaration"):
        text = nt(node, src).strip()
        # strip "import ", optional "static ", and trailing ";"
        text = re.sub(r"^import\s+(?:static\s+)?", "", text).rstrip("; \n")
        if text:
            result.dependencies.append(DependencyRecord(
                path=text, type="import", line_number=sl(node),
                is_local=not text.startswith("java") and "." not in text.split(".")[-1],
            ))

    # top-level type declarations: class, interface, enum, record
    class_nodes: list[tuple[str, object]] = []
    for kind_node_type in ("class_declaration", "interface_declaration", "enum_declaration", "record_declaration", "annotation_type_declaration"):
        for node in walk_type(root, kind_node_type):
            name_node = node.child_by_field_name("name")
            if name_node is None:
                continue
            name = nt(name_node, src)
            kind = kind_node_type.split("_")[0]  # "class", "interface", "enum", "record"
            result.entities.append(EntityRecord(
                name=name, qualified_name=name, type=kind, kind=kind,
                start_line=sl(node), end_line=el(node),
                language="java", scope="global",
            ))
            class_nodes.append((name, node))

    # methods and constructors within each type declaration body
    for class_name, class_node in class_nodes:
        body_node = class_node.child_by_field_name("body")
        if body_node is None:
            continue
        for method_node_type in ("method_declaration", "constructor_declaration"):
            for method_node in walk_type(body_node, method_node_type):
                name_node = method_node.child_by_field_name("name")
                if name_node is None:
                    continue
                mname = nt(name_node, src)
                qname = f"{class_name}.{mname}"
                etype = "constructor" if method_node_type == "constructor_declaration" else "method"
                params_node = method_node.child_by_field_name("parameters")
                sig = f"{mname}({nt(params_node, src)})" if params_node else f"{mname}()"
                # visibility from modifiers
                vis = "public"
                mod_node = method_node.child_by_field_name("modifiers")
                if mod_node:
                    mod_text = nt(mod_node, src)
                    if "private" in mod_text:
                        vis = "private"
                    elif "protected" in mod_text:
                        vis = "protected"
                result.entities.append(EntityRecord(
                    name=mname, qualified_name=qname, type=etype, kind=etype,
                    signature=sig, start_line=sl(method_node), end_line=el(method_node),
                    parent=class_name, visibility=vis,
                    language="java", scope="class",
                ))
                result.relations.append(RelationRecord(
                    source_qualified_name=class_name, target_qualified_name=qname,
                    relation_type="contains", line_number=sl(method_node),
                ))

                # call relations within method body
                body = method_node.child_by_field_name("body")
                if body:
                    for call in walk_type(body, "method_invocation"):
                        m_name_node = call.child_by_field_name("name")
                        if m_name_node:
                            callee = nt(m_name_node, src)
                            if callee not in KEYWORDS:
                                result.relations.append(RelationRecord(
                                    source_qualified_name=qname, target_qualified_name=callee,
                                    relation_type="calls",
                                    line_number=src[:call.start_byte].count(b"\n") + 1,
                                    context=callee,
                                ))

    return result


# ── regex fallback (original implementation) ─────────────────────────────────

def _regex_parse_java(file_path: str, content: str) -> ParseResult:
    entities: list[EntityRecord] = []
    dependencies: list[DependencyRecord] = []
    relations: list[RelationRecord] = []

    for match in RE_IMPORT.finditer(content):
        dependencies.append(DependencyRecord(path=match.group(1), type="import", line_number=_line(content, match.start())))

    class_name = ""
    for match in RE_CLASS.finditer(content):
        kind, name = match.groups()
        class_name = class_name or name
        line_number = _line(content, match.start())
        entities.append(EntityRecord(name=name, qualified_name=name, type=kind, kind=kind, start_line=line_number, end_line=line_number, language="java", scope="global"))

    methods: list[EntityRecord] = []
    for match in RE_METHOD.finditer(content):
        method_name = match.group(1)
        if method_name in {"if", "for", "while", "switch", "catch"}:
            continue
        line_number = _line(content, match.start())
        qualified = f"{class_name}.{method_name}" if class_name else method_name
        method = EntityRecord(name=method_name, qualified_name=qualified, type="method", kind="method", start_line=line_number, end_line=line_number, language="java", scope="class" if class_name else "global", parent=class_name)
        entities.append(method)
        methods.append(method)
        if class_name:
            relations.append(RelationRecord(source_qualified_name=class_name, target_qualified_name=qualified, relation_type="contains", line_number=line_number))

    lines = content.splitlines()
    for method in methods:
        start = max(0, method.start_line - 1)
        body = "\n".join(lines[start: min(len(lines), start + 80)])
        for call in RE_CALL.finditer(body):
            target = call.group(1)
            if target in KEYWORDS or target == method.name:
                continue
            relations.append(RelationRecord(source_qualified_name=method.qualified_name or method.name, target_qualified_name=target, relation_type="calls", line_number=method.start_line + body.count("\n", 0, call.start()), context=target))

    return ParseResult(file_path=file_path, language="java", entities=entities, dependencies=dependencies, relations=relations)


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

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


def _line(content: str, offset: int) -> int:
    return content.count("\n", 0, offset) + 1

