"""
Copyright (C) 2024-2026 Mo Zhou <lumin@debian.org>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

import difflib
import os
import pty
import re
import select
import shlex
import subprocess
import tempfile
from collections import deque

from rich.console import Console
from rich.live import Live
from rich.panel import Panel
from rich.text import Text

from .base import Tool, _diff_stat, _short, render_diff


class RipgrepSearcher(Tool):
    """Tool to search file contents using ripgrep (rg)."""

    name = "ripgrep_searcher"
    description = (
        "Search file contents for a pattern using ripgrep (rg). "
        "Supports literal and regex patterns, glob filtering, "
        "case-insensitive search, context lines, file type "
        "filters, and a files-only mode that lists matching "
        "filenames without showing the matched lines."
    )

    def summary(self, args_dict: dict) -> str:
        return (
            f"rg {_short(args_dict.get('query', ''))} in {args_dict.get('path', '.')}"
        )

    def __call__(
        self,
        query: str,
        path: str = ".",
        glob: str = None,
        case_insensitive: bool = False,
        regex: bool = True,
        files_only: bool = False,
        context_lines: int = None,
        max_count: int = None,
        type_filter: str = None,
        max_output_chars: int = 4000,
        extra_args: str = None,
    ) -> str:
        try:
            path = os.path.expanduser(path)
            if not os.path.exists(path):
                return f"Error: '{path}' does not exist."

            cmd = ["rg", "--no-heading", "--line-number", "--color=never"]

            if not regex:
                cmd.append("--fixed-strings")
            if case_insensitive:
                cmd.append("--ignore-case")
            if files_only:
                cmd.append("--files-with-matches")
            if context_lines is not None:
                cmd.extend(["--context", str(int(context_lines))])
            if max_count is not None:
                cmd.extend(["--max-count", str(int(max_count))])
            if glob is not None:
                cmd.extend(["--glob", str(glob)])
            if type_filter is not None:
                cmd.extend(["--type", str(type_filter)])
            if extra_args is not None:
                cmd.extend(shlex.split(extra_args))

            cmd.append(query)
            cmd.append(path)

            result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)

            # rg returns 1 for "no matches" (not an error)
            if result.returncode == 2:
                return f"Error: {result.stderr.strip()}"
            if result.returncode == 1:
                return "(no matches)"

            out = result.stdout
            if not out.strip():
                return "(no matches)"

            limit = int(max_output_chars) if max_output_chars else 4000
            if len(out) > limit:
                out = out[:limit] + "\n...(output truncated)..."
            return out
        except subprocess.TimeoutExpired:
            return "Error: ripgrep timed out after 30 seconds."
        except FileNotFoundError:
            return (
                "Error: 'rg' (ripgrep) is not installed. "
                "Install it with: apt install ripgrep"
            )
        except Exception as e:
            return f"Error: {str(e)}"

    parameters = {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "The search pattern (regex by "
                "default, or literal if regex "
                "is false).",
            },
            "path": {
                "type": "string",
                "description": "File or directory to search in. " "Defaults to '.'.",
            },
            "glob": {
                "type": "string",
                "description": "Glob pattern to filter files "
                "(e.g. '*.py'). Prefix with '!' "
                "to exclude (e.g. '!*.min.js').",
            },
            "case_insensitive": {
                "type": "boolean",
                "description": "If true, search case-"
                "insensitively. Defaults to false.",
            },
            "regex": {
                "type": "boolean",
                "description": "If true (default), treat query "
                "as a regex. If false, treat it "
                "as a literal string.",
            },
            "files_only": {
                "type": "boolean",
                "description": "If true, only list filenames "
                "that contain a match, without "
                "showing the matched lines. "
                "Defaults to false.",
            },
            "context_lines": {
                "type": "integer",
                "description": "Number of lines of context to "
                "show before and after each match.",
            },
            "max_count": {
                "type": "integer",
                "description": "Maximum number of matches to " "show per file.",
            },
            "type_filter": {
                "type": "string",
                "description": "Restrict search to a known file "
                "type (e.g. 'py', 'js', 'c', "
                "'html'). Maps to rg --type.",
            },
            "max_output_chars": {
                "type": "integer",
                "description": "Maximum number of characters to "
                "return before truncating the "
                "output. Defaults to 4000.",
            },
            "extra_args": {
                "type": "string",
                "description": "Additional arguments passed " "directly to rg.",
            },
        },
        "required": ["query"],
    }
