"""
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 FileEditor(Tool):
    """Tool to edit a file by exact string replacement (diff-based).

    This is the preferred way to modify an existing file: instead of
    addressing lines by number (which drift as soon as an earlier edit
    changes the line count), the caller supplies the exact text to find
    (old_string) and its replacement (new_string).  The edit is anchored to
    content, so it either applies to the intended spot or fails loudly."""

    name = "file_editor"
    description = (
        "Edit an existing file by replacing an exact string. Provide "
        "old_string (the exact text currently in the file, including "
        "whitespace and indentation) and new_string (its replacement). "
        "old_string must match exactly once, unless replace_all is true; "
        "if it is missing or ambiguous the edit fails without changing the "
        "file, so include enough surrounding context to make it unique. "
        "This is preferred over file_writer for modifying part of a file "
        "because it does not rely on line numbers. To delete text, use an "
        "empty new_string. Skip and continue on other files if the user "
        "rejects the edit."
    )

    def _load(self, path: str):
        """Return (old_content, error). error is None on success."""
        if not os.path.isfile(path):
            return None, f"Error: '{path}' is not a file or does not exist."
        try:
            with open(path, "r") as f:
                return f.read(), None
        except Exception as e:
            return None, f"Error: {str(e)}"

    def _compute(self, path: str, old_string: str, new_string: str, replace_all: bool):
        """Validate and produce (new_content, error)."""
        old_content, err = self._load(path)
        if err:
            return None, err
        if old_string == new_string:
            return None, "Error: old_string and new_string are identical."
        count = old_content.count(old_string)
        if count == 0:
            return None, (
                "Error: old_string was not found in the file. It "
                "must match the file contents exactly, including "
                "whitespace and indentation."
            )
        if count > 1 and not replace_all:
            return None, (
                f"Error: old_string appears {count} times in the "
                "file. Provide more surrounding context to make it "
                "unique, or set replace_all=true to replace every "
                "occurrence."
            )
        new_content = old_content.replace(old_string, new_string)
        return new_content, None

    def summary(self, args_dict: dict) -> str:
        path = args_dict.get("path", "")
        added, removed = _diff_stat(
            args_dict.get("old_string", ""),
            args_dict.get("new_string", ""),
        )
        return f"edit {path} (+{added} -{removed})"

    def needs_confirmation(self, args_dict: dict) -> tuple:
        path = os.path.expanduser(args_dict.get("path", ""))
        return True, f"Edit file: {path}"

    def confirmation_context(self, args_dict: dict) -> str:
        path = os.path.expanduser(args_dict.get("path", ""))
        old_content, err = self._load(path)
        if err:
            return err
        new_content, err = self._compute(
            path,
            args_dict.get("old_string", ""),
            args_dict.get("new_string", ""),
            args_dict.get("replace_all", False),
        )
        if err:
            return err
        return render_diff(old_content, new_content, path)

    def __call__(
        self, path: str, old_string: str, new_string: str, replace_all: bool = False
    ) -> str:
        try:
            path = os.path.expanduser(path)
            old_content, err = self._load(path)
            if err:
                return err
            new_content, err = self._compute(path, old_string, new_string, replace_all)
            if err:
                return err

            with open(path, "w") as f:
                f.write(new_content)

            n = old_content.count(old_string) if replace_all else 1
            plural = "occurrence" if n == 1 else "occurrences"
            return f"Successfully edited {path} " f"({n} {plural} replaced)."
        except Exception as e:
            return f"Error: {str(e)}"

    parameters = {
        "type": "object",
        "properties": {
            "path": {
                "type": "string",
                "description": "Absolute or relative path to the file to edit.",
            },
            "old_string": {
                "type": "string",
                "description": "The exact text to find and replace, including "
                "whitespace and indentation. Must be unique in "
                "the file unless replace_all is true.",
            },
            "new_string": {
                "type": "string",
                "description": "The text to replace old_string with. Use an "
                "empty string to delete the matched text.",
            },
            "replace_all": {
                "type": "boolean",
                "description": "If true, replace every occurrence of old_string. "
                "Defaults to false (require a unique match).",
            },
        },
        "required": ["path", "old_string", "new_string"],
    }
