"""
Code Snippet Extractor

Given a file path and a list of relevant symbol names (functions/classes),
extracts their source bodies without reading the entire file into context.

This is how Kodara achieves the "important code snippets" step:
  - Index tells us WHICH files and symbols are relevant
  - Extractor reads ONLY those symbols from those files
  - Everything else is skipped

Result: developer gets actual code, not just metadata — but only what matters.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import Optional


# Max lines to include per symbol body (prevents huge functions flooding context)
MAX_LINES_PER_SYMBOL = 40
# Max total lines per file
MAX_LINES_PER_FILE = 80


def extract_snippets(
    file_path: str,
    symbols: list[str],
    language: str = "",
) -> str:
    """
    Extract source snippets for the given symbols from a file.

    Args:
        file_path: Absolute path to the source file.
        symbols:   List of function/class names to extract.
        language:  Programming language hint (for parser selection).

    Returns:
        A formatted string with the extracted snippets, or empty string on failure.
    """
    try:
        source = Path(file_path).read_text(encoding="utf-8", errors="replace")
    except Exception:
        return ""

    lines = source.splitlines()

    if not symbols:
        # No specific symbols — return the file header (imports + first few lines)
        return _file_header(lines, file_path)

    snippets: list[str] = []
    found: set[str] = set()

    for symbol in symbols:
        snippet = _extract_symbol(lines, symbol, language)
        if snippet:
            snippets.append(snippet)
            found.add(symbol)
        if sum(len(s.splitlines()) for s in snippets) >= MAX_LINES_PER_FILE:
            break

    if not snippets:
        # Symbols not found — return header so we have something useful
        return _file_header(lines, file_path)

    return "\n\n".join(snippets)


def _extract_symbol(lines: list[str], symbol: str, language: str) -> str:
    """
    Find a function or class definition by name and extract its body.

    Handles: Python, JavaScript/TypeScript, Go, Java, Rust, Ruby, PHP.
    Falls back to indentation-based extraction for unknown languages.
    """
    # Patterns to match function/class/method definitions
    patterns = [
        # Python: def foo( / class Foo(
        rf"^(\s*)(async\s+)?(def|class)\s+{re.escape(symbol)}\s*[:(]",
        # JS/TS: function foo( / const foo = ( / foo( / async foo(
        rf"^(\s*)(export\s+)?(default\s+)?(async\s+)?function\s+{re.escape(symbol)}\s*[\({{]",
        rf"^(\s*)(export\s+)?(const|let|var)\s+{re.escape(symbol)}\s*=\s*(async\s+)?\(",
        rf"^(\s*)(async\s+)?{re.escape(symbol)}\s*\(",
        # Go: func Foo(
        rf"^(\s*)func\s+(\w+\s+)?{re.escape(symbol)}\s*\(",
        # Rust: fn foo(
        rf"^(\s*)(pub\s+)?(async\s+)?fn\s+{re.escape(symbol)}\s*[\(<]",
        # Java/C#: returnType foo(
        rf"^(\s*)(\w+\s+)*{re.escape(symbol)}\s*\(",
        # Ruby: def foo
        rf"^(\s*)def\s+{re.escape(symbol)}\b",
        # Class definitions (generic)
        rf"^(\s*)(public\s+|private\s+|abstract\s+)?(class|interface|struct|enum)\s+{re.escape(symbol)}\b",
    ]

    start_line = None
    base_indent = 0

    for i, line in enumerate(lines):
        for pattern in patterns:
            if re.match(pattern, line, re.IGNORECASE):
                start_line = i
                # Measure base indentation
                base_indent = len(line) - len(line.lstrip())
                break
        if start_line is not None:
            break

    if start_line is None:
        return ""

    # Extract body: continue until we return to same or lower indentation
    body_lines = [lines[start_line]]
    i = start_line + 1
    brace_depth = lines[start_line].count("{") - lines[start_line].count("}")
    in_brace_lang = "{" in lines[start_line]

    while i < len(lines) and len(body_lines) < MAX_LINES_PER_SYMBOL:
        line = lines[i]
        stripped = line.strip()

        if in_brace_lang:
            # Brace-based (JS, Go, Java, Rust, C#)
            brace_depth += line.count("{") - line.count("}")
            body_lines.append(line)
            if brace_depth <= 0 and stripped:
                break
        else:
            # Indentation-based (Python, Ruby)
            if stripped == "":
                body_lines.append(line)
            elif (len(line) - len(line.lstrip())) > base_indent:
                body_lines.append(line)
            elif len(body_lines) == 1:
                # Inline definition like `def foo: return x`
                body_lines.append(line)
                break
            else:
                break
        i += 1

    # Trim trailing blank lines
    while body_lines and not body_lines[-1].strip():
        body_lines.pop()

    if not body_lines:
        return ""

    # Truncate with note if too long
    if len(body_lines) > MAX_LINES_PER_SYMBOL:
        body_lines = body_lines[:MAX_LINES_PER_SYMBOL]
        body_lines.append(f"    ... ({len(lines) - start_line - MAX_LINES_PER_SYMBOL} more lines)")

    return "\n".join(body_lines)


def _file_header(lines: list[str], file_path: str) -> str:
    """Return imports + first meaningful lines when no symbols matched."""
    header: list[str] = []
    for line in lines[:30]:
        stripped = line.strip()
        # Include import/require lines and first non-blank content
        if (stripped.startswith(("import ", "from ", "require", "use ", "package ", "#include"))
                or stripped.startswith(("export ", "module ", "class ", "def ", "func "))):
            header.append(line)
        elif stripped and not stripped.startswith("#") and not stripped.startswith("//"):
            header.append(line)
        if len(header) >= 15:
            break
    return "\n".join(header)
