"""Advanced Filesystem Tools — tree, diff, insert_lines

Complements the basic filesystem tools with project-level overview,
file comparison, and precise line-level insertion.
"""

import os
import re
from pathlib import Path
from typing import Any, Dict, List

from clawagents.tools.registry import Tool, ToolResult

ROOT = Path.cwd()

IGNORE_DIRS = {
    "node_modules", ".git", ".venv", "venv", "env",
    "__pycache__", "dist", "build", ".next", ".cache",
    ".idea", ".vscode", "coverage", ".tox", ".mypy_cache",
}


def _safe_path(p: str) -> Path:
    resolved = (ROOT / p).resolve()
    if resolved != ROOT and not str(resolved).startswith(str(ROOT) + os.sep):
        raise ValueError(f"Path traversal blocked: {p}")
    return resolved


# ─── tree ───────────────────────────────────────────────────────────────────

class TreeTool:
    name = "tree"
    description = (
        "Show a recursive directory tree. Much faster than ls for getting a project overview. "
        "Automatically skips node_modules, .git, __pycache__, etc."
    )
    parameters = {
        "path": {"type": "string", "description": "Root directory. Default: current directory"},
        "max_depth": {"type": "number", "description": "Max depth to recurse. Default: 4"},
    }

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        root = _safe_path(str(args.get("path", ".")))
        try:
            max_depth = max(1, min(10, int(args.get("max_depth", 4))))
        except (TypeError, ValueError):
            max_depth = 4

        if not root.is_dir():
            return ToolResult(success=False, output="", error=f"Not a directory: {root}")

        try:
            rel = root.relative_to(ROOT)
        except ValueError:
            rel = root
        lines = [str(rel) or "."]
        counts = {"files": 0, "dirs": 0}
        MAX_ENTRIES = 500

        def walk(d: Path, prefix: str, depth: int):
            if counts["files"] + counts["dirs"] >= MAX_ENTRIES:
                return

            try:
                entries = sorted(d.iterdir(), key=lambda e: (not e.is_dir(), e.name.lower()))
            except PermissionError:
                return

            for i, entry in enumerate(entries):
                if counts["files"] + counts["dirs"] >= MAX_ENTRIES:
                    lines.append(f"{prefix}... (truncated at {MAX_ENTRIES} entries)")
                    return

                is_last = i == len(entries) - 1
                connector = "└── " if is_last else "├── "
                child_prefix = prefix + ("    " if is_last else "│   ")

                if entry.is_dir():
                    counts["dirs"] += 1
                    lines.append(f"{prefix}{connector}{entry.name}/")
                    if depth < max_depth and entry.name not in IGNORE_DIRS:
                        walk(entry, child_prefix, depth + 1)
                else:
                    counts["files"] += 1
                    lines.append(f"{prefix}{connector}{entry.name}")

        walk(root, "", 1)
        lines.append(f"\n{counts['dirs']} directories, {counts['files']} files")
        return ToolResult(success=True, output="\n".join(lines))


# ─── diff ───────────────────────────────────────────────────────────────────

class DiffTool:
    name = "diff"
    description = (
        "Compare two files and show their differences in unified diff format. "
        "Useful for reviewing changes before or after edits."
    )
    parameters = {
        "file_a": {"type": "string", "description": "Path to the first file", "required": True},
        "file_b": {"type": "string", "description": "Path to the second file", "required": True},
        "context_lines": {"type": "number", "description": "Lines of context around changes. Default: 3"},
    }

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        path_a = _safe_path(str(args.get("file_a", "")))
        path_b = _safe_path(str(args.get("file_b", "")))
        try:
            ctx = max(0, min(20, int(args.get("context_lines", 3))))
        except (TypeError, ValueError):
            ctx = 3

        if not path_a.exists():
            return ToolResult(success=False, output="", error=f"File not found: {path_a}")
        if not path_b.exists():
            return ToolResult(success=False, output="", error=f"File not found: {path_b}")

        try:
            import difflib
            lines_a = path_a.read_text("utf-8").splitlines(keepends=True)
            lines_b = path_b.read_text("utf-8").splitlines(keepends=True)

            try:
                name_a = str(path_a.relative_to(ROOT))
                name_b = str(path_b.relative_to(ROOT))
            except ValueError:
                name_a, name_b = str(path_a), str(path_b)

            diff = list(difflib.unified_diff(lines_a, lines_b, fromfile=name_a, tofile=name_b, n=ctx))
            if not diff:
                return ToolResult(success=True, output="Files are identical.")

            return ToolResult(success=True, output="".join(diff))
        except Exception as e:
            return ToolResult(success=False, output="", error=f"diff failed: {str(e)}")


# ─── insert_lines ──────────────────────────────────────────────────────────

class InsertLinesTool:
    name = "insert_lines"
    description = (
        "Insert text at a specific line number in a file. Line 0 inserts at the top; "
        "a line beyond the file length appends at the end. More precise than edit_file for adding new code."
    )
    parameters = {
        "path": {"type": "string", "description": "Path to the file", "required": True},
        "line": {"type": "number", "description": "Line number to insert before (1-indexed). 0 = top of file.", "required": True},
        "content": {"type": "string", "description": "The text to insert", "required": True},
    }

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        file_path = _safe_path(str(args.get("path", "")))
        try:
            line_num = max(0, int(args.get("line", 0)))
        except (TypeError, ValueError):
            line_num = 0
        content = str(args.get("content", ""))

        if not content:
            return ToolResult(success=False, output="", error="No content to insert")

        try:
            if not file_path.exists():
                return ToolResult(success=False, output="", error=f"File not found: {file_path}")

            existing = file_path.read_text("utf-8")
            lines = existing.split("\n")
            insert_idx = min(line_num, len(lines))

            new_lines = content.split("\n")
            for i, nl in enumerate(new_lines):
                lines.insert(insert_idx + i, nl)

            file_path.write_text("\n".join(lines), "utf-8")

            return ToolResult(
                success=True,
                output=f"Inserted {len(new_lines)} line(s) at line {insert_idx} in {file_path} (now {len(lines)} lines total)",
            )
        except Exception as e:
            return ToolResult(success=False, output="", error=f"insert_lines failed: {str(e)}")


advanced_fs_tools: List[Tool] = [TreeTool(), DiffTool(), InsertLinesTool()]
