"""Go 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'^\ *import\ +(?:[\w.]+\ +)?"([^"]+)"', re.MULTILINE)
RE_FUNC   = re.compile(r'^\ *func\ *(?:\(([^)]+)\)\ *)?(\w+)\ *(\([^)]*\))', re.MULTILINE)
RE_TYPE   = re.compile(r'^\ *type\ +(\w+)\ +(struct|interface)\b', re.MULTILINE)
RE_VAR    = re.compile(r'^\ *(?:var|const)\ +(\w+)', re.MULTILINE)
RE_CALL   = re.compile(r'(?<!func\ )(?:[A-Za-z_]\w*\.)?([A-Za-z_]\w*)\ *\(')
GO_KEYWORDS = {"if", "for", "switch", "return", "make", "new", "len", "cap", "append", "delete", "panic", "recover"}


# ── tree-sitter helpers ───────────────────────────────────────────────────────

def _recv_type(recv_param_list, src: bytes) -> str:
    """Extract receiver type name from a Go method_declaration receiver parameter_list."""
    if recv_param_list is None:
        return ""
    for child in recv_param_list.children:
        if child.type == "parameter_declaration":
            type_node = child.child_by_field_name("type")
            if type_node is None:
                continue
            if type_node.type == "pointer_type":
                for tc in type_node.children:
                    if tc.type == "type_identifier":
                        return src[tc.start_byte:tc.end_byte].decode("utf-8", errors="replace")
            elif type_node.type == "type_identifier":
                return src[type_node.start_byte:type_node.end_byte].decode("utf-8", errors="replace")
    return ""


def _ts_parse_go(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("go")
    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="go")

    # imports
    for node in walk_type(root, "import_spec"):
        path_node = node.child_by_field_name("path")
        if path_node:
            path = nt(path_node, src).strip('"').strip("`")
            result.dependencies.append(DependencyRecord(
                path=path, type="import", line_number=sl(path_node), is_local=path.startswith("."),
            ))

    # top-level functions
    func_nodes: list[tuple[str, object]] = []  # (qualified_name, tree_node)
    for node in walk_type(root, "function_declaration"):
        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"func {name}{nt(params_node, src)}" if params_node else f"func {name}()"
        entity = EntityRecord(
            name=name, qualified_name=name, type="function", kind="function",
            signature=sig, start_line=sl(node), end_line=el(node),
            visibility="private" if name[:1].islower() else "public",
            scope="global", language="go",
        )
        result.entities.append(entity)
        func_nodes.append((name, node))

    # methods
    for node in walk_type(root, "method_declaration"):
        recv_node = node.child_by_field_name("receiver")
        name_node = node.child_by_field_name("name")
        if name_node is None:
            continue
        name = nt(name_node, src)
        recv = _recv_type(recv_node, src)
        qname = f"{recv}.{name}" if recv else name
        params_node = node.child_by_field_name("parameters")
        sig = f"func ({recv}) {name}{nt(params_node, src)}" if params_node else f"func ({recv}) {name}()"
        entity = EntityRecord(
            name=name, qualified_name=qname, type="method", kind="method",
            signature=sig, start_line=sl(node), end_line=el(node),
            parent=recv, visibility="private" if name[:1].islower() else "public",
            scope="class", language="go",
        )
        result.entities.append(entity)
        func_nodes.append((qname, node))
        if recv:
            result.relations.append(RelationRecord(
                source_qualified_name=recv, target_qualified_name=qname,
                relation_type="contains", line_number=sl(node),
            ))

    # struct / interface types
    for type_decl in walk_type(root, "type_declaration"):
        for spec in walk_type(type_decl, "type_spec"):
            name_node = spec.child_by_field_name("name")
            type_val = spec.child_by_field_name("type")
            if name_node is None or type_val is None:
                continue
            kind = type_val.type  # "struct_type" | "interface_type" | ...
            if "struct" in kind:
                etype = "struct"
            elif "interface" in kind:
                etype = "interface"
            else:
                continue
            name = nt(name_node, src)
            result.entities.append(EntityRecord(
                name=name, qualified_name=name, type=etype, kind=etype,
                start_line=sl(spec), end_line=el(spec),
                visibility="private" if name[:1].islower() else "public",
                scope="global", language="go",
            ))

    # var / const
    for node in walk_type(root, "var_spec", "const_spec"):
        name_node = node.child_by_field_name("name")
        if name_node is None:
            for c in node.children:
                if c.is_named and c.type == "identifier":
                    name_node = c
                    break
        if name_node:
            ename = nt(name_node, src)
            result.entities.append(EntityRecord(
                name=ename, qualified_name=ename,
                type="variable" if node.type == "var_spec" else "constant",
                start_line=sl(node), end_line=el(node),
                scope="global", language="go",
            ))

    # call relations
    for qname, fn_node in func_nodes:
        body = fn_node.child_by_field_name("body")
        if body is None:
            continue
        for call in walk_type(body, "call_expression"):
            fn_field = call.child_by_field_name("function")
            if fn_field is None:
                continue
            if fn_field.type == "identifier":
                callee = nt(fn_field, src)
            elif fn_field.type == "selector_expression":
                field = fn_field.child_by_field_name("field")
                callee = nt(field, src) if field else ""
            else:
                continue
            if callee and callee not in GO_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


def _regex_parse_go(file_path: str, content: str) -> ParseResult:
    entities: list[EntityRecord] = []
    dependencies: list[DependencyRecord] = []
    relations: list[RelationRecord] = []
    lines = content.splitlines()
    function_ranges: list[tuple[str, str, int, int]] = []

    for match in RE_IMPORT.finditer(content):
        line_number = content.count("\n", 0, match.start()) + 1
        path = match.group(1)
        dependencies.append(
            DependencyRecord(
                path=path,
                type="import",
                line_number=line_number,
                is_local=path.startswith("."),
            )
        )

    for match in RE_FUNC.finditer(content):
        line_number = content.count("\n", 0, match.start()) + 1
        receiver = (match.group(1) or "").strip()
        name = match.group(2)
        params = match.group(3)
        parent = ""
        qualified_name = name
        entity_type = "function"
        scope = "global"
        if receiver:
            receiver_name = receiver.split()[-1].lstrip("*")
            parent = receiver_name
            qualified_name = f"{receiver_name}.{name}"
            entity_type = "method"
            scope = "class"
            relations.append(
                RelationRecord(
                    source_qualified_name=receiver_name,
                    target_qualified_name=qualified_name,
                    relation_type="contains",
                    line_number=line_number,
                )
            )
        line_text = lines[line_number - 1] if line_number - 1 < len(lines) else ""
        entities.append(
            EntityRecord(
                name=name,
                qualified_name=qualified_name,
                type=entity_type,
                kind=entity_type,
                signature=line_text.strip() or f"func {name}{params}",
                start_line=line_number,
                end_line=line_number,
                parent=parent,
                visibility="private" if name[:1].islower() else "public",
                scope=scope,
                language="go",
            )
        )
        function_ranges.append((qualified_name, name, match.start(), line_number))

    for match in RE_TYPE.finditer(content):
        line_number = content.count("\n", 0, match.start()) + 1
        name = match.group(1)
        kind = match.group(2)
        entities.append(
            EntityRecord(
                name=name,
                qualified_name=name,
                type=kind,
                kind=kind,
                start_line=line_number,
                end_line=line_number,
                visibility="private" if name[:1].islower() else "public",
                scope="global",
                language="go",
            )
        )

    for match in RE_VAR.finditer(content):
        line_number = content.count("\n", 0, match.start()) + 1
        name = match.group(1)
        entities.append(
            EntityRecord(
                name=name,
                qualified_name=name,
                type="variable",
                kind="variable",
                start_line=line_number,
                end_line=line_number,
                visibility="private" if name[:1].islower() else "public",
                scope="global",
                language="go",
            )
        )

    for index, (qualified_name, _, start_offset, start_line) in enumerate(function_ranges):
        end_offset = function_ranges[index + 1][2] if index + 1 < len(function_ranges) else len(content)
        body = content[start_offset:end_offset]
        for call in RE_CALL.finditer(body):
            target = call.group(1)
            if target in GO_KEYWORDS or target == qualified_name.rsplit(".", 1)[-1]:
                continue
            line_number = start_line + body.count("\n", 0, call.start())
            relations.append(
                RelationRecord(
                    source_qualified_name=qualified_name,
                    target_qualified_name=target,
                    relation_type="calls",
                    line_number=line_number,
                    context=target,
                )
            )

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


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