"""
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 FileWriter(Tool):
    """Tool to write or replace content in a file, optionally within a
    line range."""

    name = "file_writer"
    description = (
        "Write content to a file. When start_line and end_line "
        "are omitted the entire file is overwritten (or created). "
        "When start_line and/or end_line are provided, only that "
        "range of lines (1-indexed, inclusive) is replaced with "
        "the new content. Prefer this tool for creating new files "
        "or rewriting a file in full; to modify part of an "
        "existing file, prefer file_editor (string-replacement), "
        "which does not depend on line numbers that may be stale. "
        "You should skip and continue working "
        "on other files if the user rejects the write on a file."
    )

    def summary(self, args_dict: dict) -> str:
        path = args_dict.get("path", "")
        nlines = len(args_dict.get("content", "").splitlines())
        start, end = args_dict.get("start_line"), args_dict.get("end_line")
        if start is not None or end is not None:
            return f"write {path}:{start or 1}-{end or ''} ({nlines} lines)"
        return f"write {path} ({nlines} lines)"

    def needs_confirmation(self, args_dict: dict) -> tuple:
        path = os.path.expanduser(args_dict.get("path", ""))
        start = args_dict.get("start_line")
        end = args_dict.get("end_line")
        if start is None and end is None:
            if os.path.isfile(path):
                return True, f"Overwrite entire file: {path}"
            else:
                return True, f"Create new file: {path}"
        else:
            return True, f"Replace lines in: {path}"

    def confirmation_context(self, args_dict: dict) -> str:
        """Build a unified diff between old and new content, highlighted with
        Pygments, and return it as a pre-formatted string for the terminal."""

        path = os.path.expanduser(args_dict.get("path", ""))
        new_content = args_dict.get("content", "")
        start = args_dict.get("start_line")
        end = args_dict.get("end_line")

        # --- Determine old lines ----
        if os.path.isfile(path):
            try:
                with open(path, "r") as f:
                    old_lines = [l.rstrip("\n") for l in f.readlines()]
            except Exception as e:
                return f"(unable to read existing file: {e})"
        else:
            old_lines = []

        # --- Determine new lines (simulate what __call__ would produce) ---
        if start is None and end is None:
            # Full-file write / create
            new_lines = new_content.splitlines()
        else:
            # Range replacement
            total = len(old_lines)
            s = int(start) if start is not None else 1
            e = int(end) if end is not None else total
            e = min(e, total)

            new_lines = list(old_lines)
            new_lines[s - 1 : e] = new_content.splitlines()

        return render_diff("\n".join(old_lines), "\n".join(new_lines), path)

    def __call__(
        self, path: str, content: str, start_line: int = None, end_line: int = None
    ) -> str:
        try:
            path = os.path.expanduser(path)

            # Full-file write / create
            if start_line is None and end_line is None:
                os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
                with open(path, "w") as f:
                    f.write(content)
                line_count = content.count("\n") + (0 if content.endswith("\n") else 1)
                return f"Successfully wrote {line_count} lines to {path}."

            # Range replacement
            if not os.path.isfile(path):
                return (
                    f"Error: '{path}' does not exist. Cannot do a "
                    "range write on a non-existent file."
                )

            with open(path, "r") as f:
                lines = f.readlines()

            total = len(lines)

            if start_line is not None:
                start_line = int(start_line)
                if start_line < 1:
                    return "Error: start_line must be >= 1."
            else:
                start_line = 1

            if end_line is not None:
                end_line = int(end_line)
                if end_line < start_line:
                    return "Error: end_line must be >= start_line."
            else:
                end_line = total

            if start_line > total + 1:
                return (
                    f"Error: start_line ({start_line}) is beyond the "
                    f"end of the file ({total} lines)."
                )

            # Clamp end_line
            end_line = min(end_line, total)

            # Build the replacement lines, ensuring each ends with '\n'
            new_lines = content.splitlines(True)
            if new_lines and not new_lines[-1].endswith("\n"):
                new_lines[-1] += "\n"

            lines[start_line - 1 : end_line] = new_lines

            with open(path, "w") as f:
                f.writelines(lines)

            new_total = len(lines)
            return (
                f"Successfully replaced lines {start_line}-{end_line} "
                f"in {path}. File now has {new_total} lines."
            )
        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 write.",
            },
            "content": {
                "type": "string",
                "description": "The text content to write.",
            },
            "start_line": {
                "type": "integer",
                "description": "First line of the range to "
                "replace (1-indexed, inclusive). "
                "Omit for a full-file write.",
            },
            "end_line": {
                "type": "integer",
                "description": "Last line of the range to "
                "replace (1-indexed, inclusive). "
                "Omit for a full-file write.",
            },
        },
        "required": ["path", "content"],
    }
