"""Midnight Commander-style TUI for rclone."""

import curses
import os
import socket
import subprocess
from typing import List, Optional, Tuple

from jusfltuls.rclone_scanner import run_rclone_mount, run_rclone_view


class Panel:
    """Represents a panel in the MC-style interface."""

    def __init__(self, name: str):
        self.name = name
        self.items: List[str] = []
        self.selected_index = 0
        self.is_active = False
        self.offset = 0  # Offset for scrolling

    def set_items(self, items: List[str]) -> None:
        """Set the items to display in the panel."""
        self.items = items
        # Ensure selected index is within bounds
        if self.selected_index >= len(self.items):
            self.selected_index = max(0, len(self.items) - 1)
        if self.offset >= len(self.items):
            self.offset = 0

    def move_up(self) -> None:
        """Move selection up."""
        if self.selected_index > 0:
            self.selected_index -= 1
            if self.selected_index < self.offset:
                self.offset = self.selected_index

    def move_down(self, height: int) -> None:
        """Move selection down."""
        if self.selected_index < len(self.items) - 1:
            self.selected_index += 1
            if self.selected_index >= self.offset + height:
                self.offset = self.selected_index - height + 1

    def move_home(self) -> None:
        """Move selection to top."""
        self.selected_index = 0
        self.offset = 0

    def move_end(self, height: int) -> None:
        """Move selection to bottom."""
        if self.items:
            self.selected_index = len(self.items) - 1
            if self.selected_index >= height:
                self.offset = self.selected_index - height + 1
            else:
                self.offset = 0

    def move_pgup(self, height: int) -> None:
        """Move selection up by height."""
        self.selected_index = max(0, self.selected_index - height)
        if self.selected_index < self.offset:
            self.offset = self.selected_index

    def move_pgdown(self, height: int) -> None:
        """Move selection down by height."""
        if self.items:
            self.selected_index = min(len(self.items) - 1, self.selected_index + height)
            if self.selected_index >= self.offset + height:
                self.offset = self.selected_index - height + 1

    def get_selected_item(self) -> Optional[str]:
        """Get the currently selected item."""
        if 0 <= self.selected_index < len(self.items):
            return self.items[self.selected_index]
        return None


class RcloneManagerUI:
    """Midnight Commander-style rclone UI."""

    # Color pairs
    COLOR_NORMAL = 1
    COLOR_SELECTED = 2
    COLOR_HEADER = 3
    COLOR_BORDER = 4
    COLOR_FOOTER = 5
    COLOR_DIR = 6
    # Provider colors
    COLOR_ONEDRIVE = 7
    COLOR_DRIME = 8
    COLOR_MEGA = 9
    COLOR_PCLOUD = 10
    COLOR_PIKPAK = 15
    COLOR_GDRIVE = 16
    # Provider header colors (white text on colored background)
    COLOR_HEADER_ONEDRIVE = 11
    COLOR_HEADER_DRIME = 12
    COLOR_HEADER_MEGA = 13
    COLOR_HEADER_PCLOUD = 14
    COLOR_HEADER_PIKPAK = 17
    COLOR_HEADER_GDRIVE = 18
    COLOR_HEADER_DEFAULT = 19

    def __init__(self, stdscr):
        self.stdscr = stdscr
        self.left_panel = Panel("Local")
        self.right_panel = Panel("Remote")
        self.active_panel = "left"  # "left" or "right"
        self.error_message: Optional[str] = None
        self.status_message: Optional[str] = None

        self.local_path = os.getcwd()
        self.remote_name = ""
        self.remote_path = ""

        # Track if restic version has been checked
        self._restic_checked = False

        # Password mode: PASS_OFF (no password, uses --insecure-no-password)
        #                 PASS_ON_ (password-enabled mode)
        self.password_mode = "PASS_OFF"
        self._session_password: Optional[str] = None

        # Initialize curses
        curses.noecho()
        curses.cbreak()
        self.stdscr.keypad(True)
        curses.curs_set(0)  # Hide cursor
        self.stdscr.nodelay(True)  # Non-blocking input
        self._init_colors()

        # Get terminal dimensions
        self.height, self.width = self.stdscr.getmaxyx()

    def get_restic_password(self) -> Optional[str]:
        """Get restic password for PASS_ON_ mode.

        Priority:
        1. RESTIC_PASSWORD environment variable
        2. RESTIC_PASSWORD_FILE environment variable (read file content)
        3. Session password (prompt user once per session)

        Returns:
            Password string or None if no password available
        """
        # 1. Check RESTIC_PASSWORD env var
        env_password = os.environ.get("RESTIC_PASSWORD")
        if env_password:
            return env_password

        # 2. Check RESTIC_PASSWORD_FILE env var
        password_file = os.environ.get("RESTIC_PASSWORD_FILE")
        if password_file:
            try:
                with open(password_file, "r") as f:
                    return f.read().strip()
            except Exception:
                pass  # Fall through to prompt

        # 3. Use session password or prompt user
        if self._session_password is not None:
            return self._session_password

        return None

    def prompt_for_password(self) -> bool:
        """Prompt user for restic password.

        Returns:
            True if password was entered, False if cancelled
        """
        # Save current curses state
        curses.def_prog_mode()
        curses.curs_set(1)
        curses.endwin()
        curses.reset_shell_mode()
        os.system("stty sane")

        print("\n" + "=" * 60)
        print("  RESTIC PASSWORD REQUIRED")
        print("=" * 60)
        print("\nEnter password for restic repository (empty to cancel):")

        try:
            import getpass

            password = getpass.getpass("Password: ")
            if password:
                self._session_password = password
                print("\nPassword saved for this session.")
                print("Press Enter to continue...")
                input()
                os.system("stty sane")
                curses.reset_prog_mode()
                curses.curs_set(0)
                self.stdscr.refresh()
                return True
            else:
                print("\nPassword entry cancelled.")
                print("Press Enter to continue...")
                input()
                os.system("stty sane")
                curses.reset_prog_mode()
                curses.curs_set(0)
                self.stdscr.refresh()
                return False
        except (EOFError, KeyboardInterrupt):
            print("\nPassword entry cancelled.")
            print("Press Enter to continue...")
            try:
                input()
            except:
                pass
            os.system("stty sane")
            curses.reset_prog_mode()
            curses.curs_set(0)
            self.stdscr.refresh()
            return False

    def toggle_password_mode(self) -> None:
        """Toggle between PASS_OFF and PASS_ON_ modes."""
        if self.password_mode == "PASS_OFF":
            # Switch to PASS_ON_ mode
            password = self.get_restic_password()
            if password is None:
                # Need to prompt for password
                if self.prompt_for_password():
                    self.password_mode = "PASS_ON_"
                    self.set_status("Password mode: PASS_ON_")
                else:
                    self.set_status("Password mode: PASS_OFF (no password entered)")
            else:
                self.password_mode = "PASS_ON_"
                self.set_status("Password mode: PASS_ON_ (using env/file password)")
        else:
            # Switch to PASS_OFF mode
            self.password_mode = "PASS_OFF"
            self.set_status("Password mode: PASS_OFF")

    def _check_restic_version(self) -> bool:
        """Check restic version before first use.

        Returns:
            True if version is OK or user chose to continue
        """
        if self._restic_checked:
            return True

        from jusfltuls.rclone_scanner import check_restic_version, prompt_download

        is_ok, version, message = check_restic_version()
        if not is_ok:
            # Show error dialog
            dialog_h = 10
            dialog_w = 70
            dialog_y = (self.height - dialog_h) // 2
            dialog_x = (self.width - dialog_w) // 2

            try:
                win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
                win.box()
                win.addstr(
                    1, 2, "RESTIC VERSION ERROR", curses.A_BOLD | curses.A_REVERSE
                )
                win.addstr(3, 4, message[:62])
                win.addstr(5, 4, "Minimum required: 0.18")
                win.addstr(6, 4, "Download: github.com/restic/restic/releases")
                win.addstr(8, 4, "Press any key to cancel backup/restore")
                win.refresh()
                win.nodelay(False)
                win.getch()
            except:
                pass

            # Also offer download via console
            curses.def_prog_mode()
            curses.endwin()
            curses.reset_shell_mode()
            os.system("stty sane")

            download_url = "https://github.com/restic/restic/releases/download/v0.18.1/restic_0.18.1_linux_amd64.bz2"
            if prompt_download("restic", download_url):
                print("\nPlease install restic manually from the URL above.")
                print("Then restart mcrc.")
                print("\nPress Enter to continue without backup/restore...")
                try:
                    input()
                except:
                    pass

            # Restore curses state
            curses.reset_prog_mode()
            curses.curs_set(0)
            self.stdscr.refresh()
            self.draw()

            self._restic_checked = True  # Don't check again this session
            return False

        self._restic_checked = True
        return True

    def _init_colors(self) -> None:
        """Initialize color pairs."""
        if curses.has_colors():
            curses.start_color()
            try:
                curses.use_default_colors()
                bg = -1
            except:
                bg = curses.COLOR_BLACK

            # Define color pairs
            curses.init_pair(self.COLOR_NORMAL, curses.COLOR_WHITE, bg)
            curses.init_pair(self.COLOR_SELECTED, curses.COLOR_BLACK, curses.COLOR_CYAN)
            curses.init_pair(self.COLOR_HEADER, curses.COLOR_WHITE, curses.COLOR_BLUE)
            curses.init_pair(self.COLOR_BORDER, curses.COLOR_CYAN, bg)
            curses.init_pair(self.COLOR_FOOTER, curses.COLOR_BLACK, curses.COLOR_WHITE)
            curses.init_pair(self.COLOR_DIR, curses.COLOR_YELLOW, bg)
            # Provider colors (text colors)
            curses.init_pair(self.COLOR_ONEDRIVE, curses.COLOR_BLUE, bg)
            curses.init_pair(self.COLOR_DRIME, curses.COLOR_GREEN, bg)
            curses.init_pair(self.COLOR_MEGA, curses.COLOR_RED, bg)
            curses.init_pair(self.COLOR_PCLOUD, curses.COLOR_CYAN, bg)
            curses.init_pair(self.COLOR_PIKPAK, curses.COLOR_YELLOW, bg)
            curses.init_pair(self.COLOR_GDRIVE, curses.COLOR_MAGENTA, bg)
            # Provider header colors (white text on colored background)
            curses.init_pair(
                self.COLOR_HEADER_ONEDRIVE, curses.COLOR_WHITE, curses.COLOR_BLUE
            )
            curses.init_pair(
                self.COLOR_HEADER_DRIME, curses.COLOR_WHITE, curses.COLOR_GREEN
            )
            curses.init_pair(
                self.COLOR_HEADER_MEGA, curses.COLOR_WHITE, curses.COLOR_RED
            )
            curses.init_pair(
                self.COLOR_HEADER_PCLOUD, curses.COLOR_BLACK, curses.COLOR_CYAN
            )
            curses.init_pair(
                self.COLOR_HEADER_PIKPAK, curses.COLOR_BLACK, curses.COLOR_YELLOW
            )
            curses.init_pair(
                self.COLOR_HEADER_GDRIVE, curses.COLOR_WHITE, curses.COLOR_MAGENTA
            )
            curses.init_pair(
                self.COLOR_HEADER_DEFAULT, curses.COLOR_BLACK, curses.COLOR_WHITE
            )

    def _get_provider_header_color(self, remote_name: str) -> int:
        """Get header color pair for a remote provider (white text on colored background).

        Args:
            remote_name: Remote name (e.g., "onedrive:", "mega:")

        Returns:
            Header color pair constant
        """
        if not remote_name:
            return self.COLOR_HEADER

        provider = remote_name.rstrip(":").lower()

        if provider == "onedrive":
            return self.COLOR_HEADER_ONEDRIVE
        elif provider == "drime":
            return self.COLOR_HEADER_DRIME
        elif provider == "mega":
            return self.COLOR_HEADER_MEGA
        elif provider == "pcloud":
            return self.COLOR_HEADER_PCLOUD
        elif provider == "pikpak":
            return self.COLOR_HEADER_PIKPAK
        elif provider == "gdrive":
            return self.COLOR_HEADER_GDRIVE
        else:
            return self.COLOR_HEADER_DEFAULT

    def _get_provider_color(self, remote_name: str) -> int:
        """Get color pair for a remote provider.

        Args:
            remote_name: Remote name (e.g., "onedrive:", "mega:")

        Returns:
            Color pair constant
        """
        if not remote_name:
            return self.COLOR_NORMAL

        # Extract provider name (remove trailing colon)
        provider = remote_name.rstrip(":").lower()

        if provider == "onedrive":
            return self.COLOR_ONEDRIVE
        elif provider == "drime":
            return self.COLOR_DRIME
        elif provider == "mega":
            return self.COLOR_MEGA
        elif provider == "pcloud":
            return self.COLOR_PCLOUD
        elif provider == "pikpak":
            return self.COLOR_PIKPAK
        elif provider == "gdrive":
            return self.COLOR_GDRIVE
        else:
            return self.COLOR_NORMAL

    def update_local(self) -> None:
        """Update local directory listing."""
        from jusfltuls.rclone_scanner import list_local_dir

        items = list_local_dir(self.local_path)
        if self.local_path != "/":
            items = [".."] + items
        self.left_panel.set_items(items)

    def update_remote(self) -> None:
        """Update remote directory listing."""
        if not self.remote_name:
            return
        from jusfltuls.rclone_scanner import list_remote_dir

        items = list_remote_dir(self.remote_name, self.remote_path)
        if self.remote_path:
            items = [".."] + items
        self.right_panel.set_items(items)

    def switch_panel(self) -> None:
        """Switch between left and right panels."""
        if self.active_panel == "left":
            self.active_panel = "right"
            self.left_panel.is_active = False
            self.right_panel.is_active = True
        else:
            self.active_panel = "left"
            self.left_panel.is_active = True
            self.right_panel.is_active = False

    def move_up(self) -> None:
        """Move selection up in active panel."""
        if self.active_panel == "left":
            self.left_panel.move_up()
        else:
            self.right_panel.move_up()

    def move_down(self) -> None:
        """Move selection down in active panel."""
        panel_height = self.height - 6  # Estimated height for content
        if self.active_panel == "left":
            self.left_panel.move_down(panel_height)
        else:
            self.right_panel.move_down(panel_height)

    def move_home(self) -> None:
        """Move selection to top in active panel."""
        if self.active_panel == "left":
            self.left_panel.move_home()
        else:
            self.right_panel.move_home()

    def move_end(self) -> None:
        """Move selection to bottom in active panel."""
        panel_height = self.height - 6
        if self.active_panel == "left":
            self.left_panel.move_end(panel_height)
        else:
            self.right_panel.move_end(panel_height)

    def move_pgup(self) -> None:
        """Move selection up by height in active panel."""
        panel_height = self.height - 6
        if self.active_panel == "left":
            self.left_panel.move_pgup(panel_height)
        else:
            self.right_panel.move_pgup(panel_height)

    def move_pgdown(self) -> None:
        """Move selection down by height in active panel."""
        panel_height = self.height - 6
        if self.active_panel == "left":
            self.left_panel.move_pgdown(panel_height)
        else:
            self.right_panel.move_pgdown(panel_height)

    def _safe_addstr(self, y: int, x: int, text: str, attr: int = 0) -> None:
        """Safely add a string to the screen, handling boundary issues."""
        try:
            if x < self.width and y < self.height:
                max_len = self.width - x
                if max_len > 0:
                    self.stdscr.addnstr(y, x, text, max_len, attr)
        except curses.error:
            pass

    def draw(self) -> None:
        """Draw the complete UI."""
        self.stdscr.clear()
        self.height, self.width = self.stdscr.getmaxyx()

        # Calculate panel dimensions
        panel_width = (self.width - 3) // 2
        panel_height = self.height - 4  # Leave room for header and footer

        # Draw header
        self._draw_header()

        # Draw left panel (Local)
        self._draw_panel(
            self.left_panel,
            1,
            0,
            panel_height,
            panel_width,
            f"Local: {self.local_path}",
        )

        # Draw separator line between panels
        for y in range(1, self.height - 2):
            self._safe_addstr(
                y, panel_width + 1, "│", curses.color_pair(self.COLOR_BORDER)
            )

        # Draw right panel (Remote)
        self._draw_panel(
            self.right_panel,
            1,
            panel_width + 2,
            panel_height,
            panel_width,
            f"Remote: {self.remote_name}{self.remote_path}",
        )

        # Draw footer
        self._draw_footer()

        self.stdscr.refresh()

    def _draw_header(self) -> None:
        """Draw the top header bar with provider-colored background."""
        hostname = socket.gethostname()

        # Get provider-specific header color
        header_attr = (
            curses.color_pair(self._get_provider_header_color(self.remote_name))
            | curses.A_BOLD
        )

        # Build header text with password mode indicator on left
        mode_indicator = f"[{self.password_mode}]"
        left_part = f" {mode_indicator} Rclone Manager (mcrc) - "
        remote_part = self.remote_name.rstrip(":")
        right_part = f" - {hostname} "

        # Calculate positions for centering
        total_len = len(left_part) + len(remote_part) + len(right_part)
        padding_left = (self.width - total_len) // 2
        padding_right = self.width - total_len - padding_left

        # Ensure padding is not negative
        if padding_left < 0:
            padding_left = 0
        if padding_right < 0:
            padding_right = 0

        # Draw entire header with provider-colored background
        full_text = (
            " " * padding_left
            + left_part
            + remote_part
            + right_part
            + " " * padding_right
        )
        self._safe_addstr(0, 0, full_text[: self.width], header_attr)

    def _draw_panel(
        self, panel: Panel, y: int, x: int, height: int, width: int, title: str
    ) -> None:
        """Draw a panel with its contents."""
        # Draw panel border
        border_attr = curses.color_pair(self.COLOR_BORDER)
        if panel.is_active:
            border_attr |= curses.A_BOLD

        # Top border with title (truncated to fit)
        display_title = (
            title if len(title) <= width - 4 else "..." + title[-(width - 7) :]
        )
        self._safe_addstr(
            y, x, "+" + f" {display_title} ".center(width - 2, "-") + "+", border_attr
        )

        # Content area
        content_start_y = y + 1
        content_height = height - 2  # Account for borders

        total_items = len(panel.items)

        for i in range(content_height):
            item_idx = panel.offset + i
            row_y = content_start_y + i

            if row_y >= y + height - 1:
                break

            if item_idx < total_items:
                item_text = panel.items[item_idx]
                is_selected = (item_idx == panel.selected_index) and panel.is_active

                if is_selected:
                    attr = curses.color_pair(self.COLOR_SELECTED) | curses.A_BOLD
                elif item_text.endswith("/"):
                    attr = curses.color_pair(self.COLOR_DIR)
                else:
                    attr = curses.color_pair(self.COLOR_NORMAL)

                # Truncate or pad to fit
                display_text = item_text[: width - 2]
                line = f"|{display_text:<{width - 2}}|"
                self._safe_addstr(row_y, x, line, attr)
            else:
                # Empty line
                line = f"|{' ' * (width - 2)}|"
                self._safe_addstr(row_y, x, line, curses.color_pair(self.COLOR_NORMAL))

        # Bottom border
        bottom_y = y + height - 1
        if bottom_y < self.height - 2:
            self._safe_addstr(bottom_y, x, "+" + "-" * (width - 2) + "+", border_attr)

    def _draw_footer(self) -> None:
        """Draw the bottom footer bar."""
        footer_y = self.height - 2
        keys = [
            ("2", "Mount"),
            ("3", "Check"),
            ("4", "View"),
            ("5", "Copy"),
            ("7", "Mkdir"),
            ("8", "Delete"),
            ("9", "Sync"),
            ("b", "Backup"),
            ("e", "Restore"),
            ("p", "PwdMode"),
            ("r", "Refresh"),
            ("h", "Help"),
            ("q", "Quit"),
        ]

        footer_parts = [f" {key}:{action} " for key, action in keys]
        footer_text = "".join(footer_parts)

        if self.status_message:
            footer_text += f" | {self.status_message}"
        elif self.error_message:
            footer_text += f" | ERROR: {self.error_message[:30]}"

        footer_text = footer_text.ljust(self.width)[: self.width]
        self._safe_addstr(
            footer_y, 0, footer_text, curses.color_pair(self.COLOR_FOOTER)
        )

    def handle_check(self) -> None:
        """Handle check operation (F3) - compare current directories."""
        # Determine src and dst paths from current directories in both panels
        src = self.local_path
        if not src.endswith("/"):
            src += "/"
        dst = f"{self.remote_name}{self.remote_path}"

        # Show dialog with warning
        dialog_h = 10
        dialog_w = 60
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
        win.box()
        win.addstr(1, 2, "CURRENT DIRS CHECK:", curses.A_BOLD)
        win.addstr(2, 4, f"SRC: {src}"[:52])
        win.addstr(3, 4, f"DST: {dst}"[:52])
        win.addstr(5, 4, "WARNING: --size-only comparison")
        win.addstr(6, 4, "Press 'Y' to CHECK, any other key to CANCEL")
        win.refresh()

        confirm_key = win.getch()
        if confirm_key not in (ord("y"), ord("Y")):
            self.set_status("Check cancelled.")
            return

        # Save current curses state
        curses.def_prog_mode()
        curses.curs_set(1)
        curses.endwin()
        curses.reset_shell_mode()
        os.system("stty sane")

        from jusfltuls.rclone_scanner import run_rclone_check

        try:
            run_rclone_check(src, dst)
        except Exception as e:
            print(f"Internal Error in check execution: {e}")
            print("Press Enter to return...")
            import sys

            sys.stdin.readline()

        os.system("stty sane")
        curses.reset_prog_mode()
        curses.curs_set(0)
        self.stdscr.refresh()
        self.draw()

        self.set_status("Check operation completed.")

    def handle_mount(self) -> None:
        """Handle mount operation (F2) - mount remote at /mnt/<remotename>."""
        # Extract remote name without trailing colon
        remote_base = self.remote_name.rstrip(":")
        mountpoint = f"/mnt/{remote_base}"

        # Show dialog with mount information
        dialog_h = 10
        dialog_w = 60
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
        win.box()
        win.addstr(1, 2, "MOUNT REMOTE:", curses.A_BOLD)
        win.addstr(2, 4, f"Remote: {self.remote_name}"[:52])
        win.addstr(3, 4, f"Mountpoint: {mountpoint}"[:52])
        win.addstr(5, 4, "This will:")
        win.addstr(6, 4, "- Create /mnt/<remotename> if needed")
        win.addstr(7, 4, "- Set chmod +t and chown to current user")
        win.addstr(8, 4, "Press 'Y' to MOUNT, any other key to CANCEL")
        win.refresh()

        confirm_key = win.getch()
        if confirm_key not in (ord("y"), ord("Y")):
            self.set_status("Mount cancelled.")
            return

        # Save current curses state
        curses.def_prog_mode()
        curses.curs_set(1)
        curses.endwin()
        curses.reset_shell_mode()
        os.system("stty sane")

        success = False
        message = ""
        try:
            success, message = run_rclone_mount(self.remote_name, mountpoint)
            print("\n" + "=" * 60)
            if success:
                print(f"\033[92m{message}\033[0m")
            else:
                print(f"\033[91m{message}\033[0m")
            print("=" * 60)
            print("\nPress Enter to return to mcrc...")
            import sys

            sys.stdin.readline()
        except Exception as e:
            print(f"Internal Error in mount execution: {e}")
            print("Press Enter to return...")
            import sys

            sys.stdin.readline()

        os.system("stty sane")
        curses.reset_prog_mode()
        curses.curs_set(0)
        self.stdscr.refresh()
        self.draw()

        if success:
            self.set_status(f"Mounted at {mountpoint}")
        else:
            self.set_error("Mount failed")

    def handle_view(self) -> None:
        """Handle view operation (F4) - view text files."""
        # Get selected item from active panel
        if self.active_panel == "left":
            selected = self.left_panel.get_selected_item()
            if not selected or selected == "..":
                self.set_error("Select a file to view")
                return
            if selected.endswith("/"):
                self.set_error("Cannot view directories")
                return
            src_path = os.path.join(self.local_path, selected)
            is_remote = False
        else:
            selected = self.right_panel.get_selected_item()
            if not selected or selected == "..":
                self.set_error("Select a file to view")
                return
            if selected.endswith("/"):
                self.set_error("Cannot view directories")
                return
            src_path = f"{self.remote_name}{self.remote_path}{selected}"
            is_remote = True

        # Check if viewable
        viewable_extensions = {".txt", ".md", ".org", ".config"}
        basename = os.path.basename(src_path)
        _, ext = os.path.splitext(basename)
        is_viewable = (
            ext.lower() in viewable_extensions
            or basename == "config"
            or ".config" in basename
        )

        if not is_viewable:
            self.set_error(f"Cannot view file type: {basename}")
            return

        # Show dialog
        dialog_h = 8
        dialog_w = 60
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
        win.box()
        win.addstr(1, 2, "VIEW FILE:", curses.A_BOLD)
        win.addstr(2, 4, f"File: {basename}"[:52])
        win.addstr(4, 4, "This will open the file in your default editor")
        win.addstr(5, 4, "(set via $EDITOR, or vi/nano)")
        win.addstr(6, 4, "Press 'Y' to VIEW, any other key to CANCEL")
        win.refresh()

        confirm_key = win.getch()
        if confirm_key not in (ord("y"), ord("Y")):
            self.set_status("View cancelled.")
            return

        # Save current curses state
        curses.def_prog_mode()
        curses.curs_set(1)
        curses.endwin()
        curses.reset_shell_mode()
        os.system("stty sane")

        success = False
        message = ""
        try:
            success, message = run_rclone_view(src_path, is_remote)
            if not success:
                print(f"\n\033[91m{message}\033[0m")
                print("\nPress Enter to return to mcrc...")
                import sys

                sys.stdin.readline()
        except Exception as e:
            print(f"Internal Error in view execution: {e}")
            print("Press Enter to return...")
            import sys

            sys.stdin.readline()

        os.system("stty sane")
        curses.reset_prog_mode()
        curses.curs_set(0)
        self.stdscr.refresh()
        self.draw()

        if success:
            self.set_status(message)
        else:
            self.set_error(message)

    def handle_copy(self) -> None:
        """Handle copy operation (F5)."""
        # Determine src and dst paths
        if self.active_panel == "left":
            selected = self.left_panel.get_selected_item()
            if not selected or selected == "..":
                self.set_error("Select a file or directory to copy")
                return

            src = os.path.join(self.local_path, selected)

            # Destination directory in the other panel
            dst_dir = f"{self.remote_name}{self.remote_path}"
            # Destination full path (including the name)
            dst_full = f"{self.remote_name}{self.remote_path}{selected}"

            # For directories, MC behavior is to copy the dir into the dst_dir
            # rclone copy src_dir dst_dir/src_dir
            if selected.endswith("/"):
                target_copy_dst = dst_full
            else:
                target_copy_dst = dst_dir

        else:
            selected = self.right_panel.get_selected_item()
            if not selected or selected == "..":
                self.set_error("Select a file or directory to copy")
                return

            src = f"{self.remote_name}{self.remote_path}{selected}"

            # Destination directory in the local panel
            dst_dir = self.local_path
            # Destination full path
            dst_full = os.path.join(self.local_path, selected)

            if selected.endswith("/"):
                target_copy_dst = dst_full
            else:
                target_copy_dst = dst_dir

        # Show dialog to select copy mode based on type
        is_dir = selected.endswith("/")
        dialog_h = 12
        dialog_w = 60
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
        win.box()
        win.addstr(1, 2, "COPY OPERATION:", curses.A_BOLD)
        win.addstr(2, 2, f"SRC: {src}"[:56])
        win.addstr(3, 2, f"DST: {dst_full if is_dir else dst_dir}"[:56])

        if is_dir:
            win.addstr(5, 4, "2. Dir traverse (rclone copy)")
            win.addstr(6, 4, "3. Dir no-traverse (rclone copy --no-traverse)")
            win.addstr(7, 4, "4. Dry run (rclone copy --dry-run)")
            win.addstr(9, 4, "Press 2, 3, 4 or Escape to cancel")
        else:
            win.addstr(5, 4, "1. One file (rclone copyto)")
            win.addstr(9, 4, "Press 1 or Escape to cancel")

        win.refresh()

        mode = 0
        dry_run = False
        while True:
            key = win.getch()
            if is_dir and key in (ord("2"), ord("3"), ord("4")):
                mode = int(chr(key))
                if mode == 4:
                    mode = 2  # Use regular traverse mode for dry-run
                    dry_run = True
                break
            elif not is_dir and key == ord("1"):
                mode = 1
                break
            elif key == 27:  # Escape
                break

        if mode > 0:
            # Save current curses state
            curses.def_prog_mode()
            curses.curs_set(1)  # Show cursor for shell

            # Critical sequence for shelling out from curses
            # 1. End curses mode
            curses.endwin()

            # 2. Reset the terminal to shell mode (pre-curses state)
            # This handles standard terminal configuration
            curses.reset_shell_mode()

            # 3. Forced reset of stty as a fail-safe for the "staircase" effect
            # and ^M input issues in some environments.
            os.system("stty sane")

            from jusfltuls.rclone_scanner import run_rclone_copy

            # Mode 1 (copyto) uses full destination path
            # Modes 2 & 3 (copy) use target_copy_dst which handles the dir-in-dir logic
            final_dst = dst_full if mode == 1 else target_copy_dst

            try:
                run_rclone_copy(mode, src, final_dst, dry_run=dry_run)
            except Exception as e:
                # We are out of curses here, can print normally
                print(f"Internal Error in copy execution: {e}")
                print("Press Enter to return...")
                import sys

                sys.stdin.readline()

            # Ensure terminal is clean before returning to curses
            os.system("stty sane")

            # Restore curses state
            curses.reset_prog_mode()
            curses.curs_set(0)  # Hide cursor again
            self.stdscr.refresh()
            self.draw()  # Force full redraw

            # Reload panels after copy
            self.update_local()
            self.update_remote()
            self.set_status("Copy operation completed.")

    def handle_sync(self) -> None:
        """Handle sync operation (dangerous!)."""
        # Determine src and dst paths based on active panel (cursor position)
        if self.active_panel == "left":
            # Sync from local to remote
            src = self.local_path
            if not src.endswith("/"):
                src += "/"
            dst = f"{self.remote_name}{self.remote_path}"
        else:
            # Sync from remote to local
            src = f"{self.remote_name}{self.remote_path}"
            dst = self.local_path
            if not dst.endswith("/"):
                dst += "/"

        # Show dialog to select sync mode
        dialog_h = 10
        dialog_w = 60
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
        win.box()
        win.addstr(
            1, 2, "CURRENT DIRS SYNC (DANGEROUS):", curses.A_BOLD | curses.A_REVERSE
        )
        win.addstr(2, 2, f"SRC: {src}"[:56])
        win.addstr(3, 2, f"DST: {dst}"[:56])
        win.addstr(5, 4, "1. DRY RUN (rclone sync --dry-run)")
        win.addstr(6, 4, "2. REAL SYNC (DELETES FILES IN DST!)")
        win.addstr(8, 4, "Press 1, 2 or Escape to cancel")
        win.refresh()

        mode = 0
        while True:
            key = win.getch()
            if key in (ord("1"), ord("2")):
                mode = int(chr(key))
                break
            elif key == 27:  # Escape
                break

        if mode > 0:
            dry_run = mode == 1

            # Additional confirmation for real sync
            if not dry_run:
                win.clear()
                win.box()
                win.addstr(
                    1,
                    2,
                    "!!! FINAL SYNC CONFIRMATION !!!",
                    curses.A_BOLD | curses.A_REVERSE,
                )
                win.addstr(2, 2, f"SRC: {src}"[:56])
                win.addstr(3, 2, f"DST: {dst}"[:56])
                win.addstr(5, 4, "REALLY SYNC? DESTINATION FILES")
                win.addstr(6, 4, "NOT IN SOURCE WILL BE DELETED!")
                win.addstr(8, 4, "Press 'Y' to SYNC, any other key to CANCEL")
                win.refresh()
                confirm_key = win.getch()
                if confirm_key not in (ord("y"), ord("Y")):
                    self.set_status("Sync cancelled.")
                    return

            # Save current curses state
            curses.def_prog_mode()
            curses.curs_set(1)  # Show cursor for shell
            curses.endwin()
            curses.reset_shell_mode()
            os.system("stty sane")

            from jusfltuls.rclone_scanner import run_rclone_sync

            try:
                run_rclone_sync(src, dst, dry_run=dry_run)
            except Exception as e:
                print(f"Internal Error in sync execution: {e}")

            print("Press Enter to return...")
            import sys

            sys.stdin.readline()

            os.system("stty sane")
            curses.reset_prog_mode()
            curses.curs_set(0)  # Hide cursor again
            self.stdscr.refresh()
            self.draw()  # Force full redraw

            # Reload panels after sync
            self.update_local()
            self.update_remote()
            self.set_status(f"Sync {'dry-run ' if dry_run else ''}completed.")

    def handle_backup(self) -> None:
        """Handle restic backup operation ('b' key) from local folder."""
        # Check restic version first
        if not self._check_restic_version():
            self.set_error("Restic version check failed")
            return

        if self.active_panel != "left":
            self.set_error("Backup is only available for Local panel")
            return

        selected = self.left_panel.get_selected_item()
        if not selected or selected == "..":
            self.set_error("Select a local folder to backup")
            return

        if not selected.endswith("/"):
            self.set_error("Select a folder (not file) to backup")
            return

        local_folder = os.path.join(self.local_path, selected.rstrip("/"))
        folder_name = selected.rstrip("/")
        backup_name = f"restic_{folder_name}"

        # Show dialog
        dialog_h = 10
        dialog_w = 70
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
        win.box()
        win.addstr(1, 2, "RESTIC BACKUP:", curses.A_BOLD)
        win.addstr(2, 4, f"Source: {local_folder}"[:62])
        win.addstr(3, 4, f"Backup name: {backup_name}"[:62])
        win.addstr(5, 4, "Excludes: .venv/*, uv.lock, .*~")
        win.addstr(7, 4, "Press 'Y' to BACKUP, any other key to CANCEL")
        win.refresh()

        confirm_key = win.getch()
        if confirm_key not in (ord("y"), ord("Y")):
            self.set_status("Backup cancelled.")
            return

        # Create backup marker file before running backup
        import socket
        from datetime import datetime

        hostname = socket.gethostname()
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%a")
        remote_base = self.remote_name.rstrip(":")
        marker_filename = f".backup_restic_{timestamp}_{hostname}_{remote_base}"
        marker_path = os.path.join(local_folder, marker_filename)

        try:
            # Get the 5 latest files with formatted timestamps
            # Use find to get files sorted by modification time
            find_cmd = ["find", ".", "-type", "f", "-printf", "%T@ %p\\n"]
            result = subprocess.run(
                find_cmd, cwd=local_folder, capture_output=True, text=True, timeout=30
            )

            latest_files = []
            if result.returncode == 0 and result.stdout:
                # Parse the output and sort by timestamp (newest first)
                lines = result.stdout.strip().split("\n")
                file_entries = []
                for line in lines:
                    parts = line.split(" ", 1)
                    if len(parts) == 2:
                        try:
                            unix_time = float(parts[0])
                            filepath = parts[1]
                            file_entries.append((unix_time, filepath))
                        except ValueError:
                            continue

                # Sort by timestamp descending and take top 5
                file_entries.sort(reverse=True)
                for unix_time, filepath in file_entries[:5]:
                    dt = datetime.fromtimestamp(unix_time)
                    time_str = dt.strftime("%Y%m%d_%H%M%S")
                    latest_files.append(f"{time_str} {filepath}")

            # Write the marker file
            with open(marker_path, "w") as f:
                f.write(f"Backup marker created: {timestamp}\n")
                f.write(f"Hostname: {hostname}\n")
                f.write(f"Source: {local_folder}\n")
                f.write("\n5 latest files:\n")
                for entry in latest_files:
                    f.write(f"{entry}\n")

        except Exception as e:
            # If marker creation fails, continue with backup anyway
            pass

        # Save current curses state
        curses.def_prog_mode()
        curses.curs_set(1)
        curses.endwin()
        curses.reset_shell_mode()
        os.system("stty sane")

        from jusfltuls.rclone_scanner import run_restic_backup

        # Get password if in PASS_ON_ mode
        password = None
        if self.password_mode == "PASS_ON_":
            password = self.get_restic_password()

        success = False
        message = ""
        try:
            success, message = run_restic_backup(
                self.remote_name, local_folder, folder_name, password=password
            )
            print("\n" + "=" * 60)
            if success:
                print(f"\033[92m{message}\033[0m")
            else:
                print(f"\033[91m{message}\033[0m")
            print("=" * 60)
            print("\nPress Enter to return to mcrc...")
            import sys

            sys.stdin.readline()
        except Exception as e:
            print(f"Internal Error in backup execution: {e}")
            print("Press Enter to return...")
            import sys

            sys.stdin.readline()

        os.system("stty sane")
        curses.reset_prog_mode()
        curses.curs_set(0)
        self.stdscr.refresh()
        self.draw()

        if success:
            self.set_status(message)
            # Refresh remote panel to show new backup folder
            self.update_remote()
        else:
            # Check for specific error types
            if "BANDWIDTH_LIMIT" in message:
                self.set_error("Bandwidth limit exceeded. Please try again later.")
            elif "NEEDS_PASSWORD" in message:
                self.set_error(
                    "Repository requires password. Press 'p' to enable password mode."
                )
            else:
                self.set_error(message)

    def handle_restore(self) -> None:
        """Handle restic restore operation ('e' key) to remote restic_* folder."""
        # Check restic version first
        if not self._check_restic_version():
            self.set_error("Restic version check failed")
            return

        if self.active_panel != "right":
            self.set_error("Restore is only available for Remote panel")
            return

        selected = self.right_panel.get_selected_item()
        if not selected or selected == "..":
            self.set_error("Select a restic backup folder to restore")
            return

        if not selected.endswith("/"):
            self.set_error("Select a folder (not file)")
            return

        backup_name = selected.rstrip("/")
        if not backup_name.startswith("restic_"):
            self.set_error("Selected folder must start with 'restic_'")
            return

        # Calculate target folder paths for display
        tmp_folder = f"/tmp/{backup_name}_restored"
        local_name = backup_name[7:]  # Remove "restic_" prefix
        local_target = os.path.join(self.local_path, local_name)

        # Show dialog with restore options
        dialog_h = 14
        dialog_w = 70
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
        win.box()
        win.addstr(1, 2, "RESTIC RESTORE:", curses.A_BOLD)
        win.addstr(2, 4, f"Backup: {backup_name}"[:62])
        win.addstr(4, 4, "WARNING: This will restore latest snapshot")
        win.addstr(6, 4, "1. Restore to /tmp/ (safe - recommended)")
        win.addstr(7, 4, f"   → {tmp_folder}"[:62], curses.A_DIM)
        win.addstr(8, 4, "2. Restore to local folder (risky)")
        win.addstr(9, 4, f"   → {local_target}"[:62], curses.A_DIM)
        win.addstr(11, 4, "Press 1, 2 or Escape to cancel")
        win.refresh()

        mode = 0
        while True:
            key = win.getch()
            if key in (ord("1"), ord("2")):
                mode = int(chr(key))
                break
            elif key == 27:  # Escape
                break

        if mode == 0:
            self.set_status("Restore cancelled.")
            return

        if mode == 1:
            target_folder = tmp_folder
        else:
            target_folder = local_target

            # Confirm risky restore
            win.clear()
            win.box()
            win.addstr(1, 2, "!!! WARNING !!!", curses.A_BOLD | curses.A_REVERSE)
            win.addstr(3, 4, f"This will OVERWRITE: {target_folder}"[:62])
            win.addstr(5, 4, "Existing files may be LOST!")
            win.addstr(7, 4, "Press 'Y' to RESTORE, any other key to CANCEL")
            win.refresh()

            confirm_key = win.getch()
            if confirm_key not in (ord("y"), ord("Y")):
                self.set_status("Restore cancelled.")
                return

        # Save current curses state
        curses.def_prog_mode()
        curses.curs_set(1)
        curses.endwin()
        curses.reset_shell_mode()
        os.system("stty sane")

        from jusfltuls.rclone_scanner import run_restic_restore

        # Get password if in PASS_ON_ mode
        password = None
        if self.password_mode == "PASS_ON_":
            password = self.get_restic_password()

        success = False
        message = ""
        try:
            success, message = run_restic_restore(
                self.remote_name, backup_name, target_folder, password=password
            )
            print("\n" + "=" * 60)
            if success:
                print(f"\033[92m{message}\033[0m")

                # Create restore marker file in target folder
                try:
                    import socket
                    from datetime import datetime

                    hostname = socket.gethostname()
                    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%a")
                    remote_base = self.remote_name.rstrip(":")
                    # Format: .backup_restic_<timestamp>_<remote>_<hostname>
                    marker_filename = (
                        f".backup_restic_{timestamp}_{remote_base}_{hostname}"
                    )
                    marker_path = os.path.join(target_folder, marker_filename)

                    # Get the 5 latest files in the restored folder
                    find_cmd = ["find", ".", "-type", "f", "-printf", "%T@ %p\\n"]
                    result = subprocess.run(
                        find_cmd,
                        cwd=target_folder,
                        capture_output=True,
                        text=True,
                        timeout=30,
                    )

                    latest_files = []
                    if result.returncode == 0 and result.stdout:
                        lines = result.stdout.strip().split("\n")
                        file_entries = []
                        for line in lines:
                            parts = line.split(" ", 1)
                            if len(parts) == 2:
                                try:
                                    unix_time = float(parts[0])
                                    filepath = parts[1]
                                    file_entries.append((unix_time, filepath))
                                except ValueError:
                                    continue

                        file_entries.sort(reverse=True)
                        for unix_time, filepath in file_entries[:5]:
                            dt = datetime.fromtimestamp(unix_time)
                            time_str = dt.strftime("%Y%m%d_%H%M%S")
                            latest_files.append(f"{time_str} {filepath}")

                    # Write the marker file
                    with open(marker_path, "w") as f:
                        f.write(f"Restore marker created: {timestamp}\n")
                        f.write(f"Hostname: {hostname}\n")
                        f.write(f"Source backup: {backup_name}\n")
                        f.write(f"Restored to: {target_folder}\n")
                        f.write("\n5 latest files after restore:\n")
                        for entry in latest_files:
                            f.write(f"{entry}\n")

                except Exception as e:
                    print(f"\033[93mMarker file creation failed: {e}\033[0m")
            else:
                print(f"\033[91m{message}\033[0m")
            print("=" * 60)
            print("\nPress Enter to return to mcrc...")
            import sys

            sys.stdin.readline()
        except Exception as e:
            print(f"Internal Error in restore execution: {e}")
            print("Press Enter to return...")
            import sys

            sys.stdin.readline()

        os.system("stty sane")
        curses.reset_prog_mode()
        curses.curs_set(0)
        self.stdscr.refresh()
        self.draw()

        if success:
            self.set_status(message)
            # Refresh local panel
            self.update_local()
        else:
            # Check for specific error types
            if "BANDWIDTH_LIMIT" in message:
                self.set_error("Bandwidth limit exceeded. Please try again later.")
            elif "NEEDS_PASSWORD" in message:
                self.set_error(
                    "Repository requires password. Press 'p' to enable password mode."
                )
            else:
                self.set_error(message)

    def _show_restic_snapshots(self, backup_folder: str) -> None:
        """Show dialog with restic snapshots for a backup folder.

        Args:
            backup_folder: The restic_ folder name (e.g., "restic_mybackup/")
        """
        from jusfltuls.rclone_scanner import run_restic_snapshots

        backup_name = backup_folder.rstrip("/")

        # Create dialog first (before running command)
        # Use maximum available space (leave 2 chars margin on each side)
        dialog_h = min(22, self.height - 2)
        dialog_w = self.width - 4
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = 2

        try:
            win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
            win.box()
            win.addstr(
                1,
                2,
                f"RESTIC SNAPSHOTS: {backup_name}",
                curses.A_BOLD | curses.A_REVERSE,
            )
            # Show retrieving message immediately
            win.addstr(3, 4, "Retrieving snapshots, please wait...", curses.A_DIM)
            win.refresh()

            # Get password if in PASS_ON_ mode
            password = None
            if self.password_mode == "PASS_ON_":
                password = self.get_restic_password()

            # Run snapshots command (this may take time)
            success, output = run_restic_snapshots(
                self.remote_name, backup_name, password=password
            )

            # Check for specific error types
            needs_password = False
            bandwidth_limit = False
            if not success:
                # Check for NEEDS_PASSWORD marker (includes timeout and password errors)
                if "NEEDS_PASSWORD" in output:
                    needs_password = True
                elif self.password_mode == "PASS_OFF":
                    error_lower = output.lower()
                    # Check for bandwidth limit exceeded
                    if "509" in output or "bandwidth limit exceeded" in error_lower:
                        bandwidth_limit = True
                    # Check for password-related errors
                    elif any(
                        phrase in error_lower
                        for phrase in [
                            "password",
                            "wrong password",
                            "invalid password",
                            "unable to open repo",
                            "decrypt",
                            "key",
                        ]
                    ):
                        needs_password = True

            # If bandwidth limit exceeded, show specific message
            if bandwidth_limit:
                win.clear()
                win.box()
                win.addstr(
                    1,
                    2,
                    f"RESTIC SNAPSHOTS: {backup_name}",
                    curses.A_BOLD | curses.A_REVERSE,
                )
                win.addstr(
                    3, 4, "BANDWIDTH LIMIT EXCEEDED!", curses.A_BOLD | curses.A_REVERSE
                )
                win.addstr(5, 4, "The remote storage has reached its bandwidth limit.")
                win.addstr(6, 4, "Please try again later.")
                win.addstr(8, 4, "Press any key to close...")
                win.refresh()
                win.nodelay(False)
                win.getch()
                return

            # If password is needed, show dialog asking user to enable password mode
            if needs_password:
                win.clear()
                win.box()
                win.addstr(
                    1,
                    2,
                    f"RESTIC SNAPSHOTS: {backup_name}",
                    curses.A_BOLD | curses.A_REVERSE,
                )
                win.addstr(3, 4, "This repository requires a password!", curses.A_BOLD)
                win.addstr(5, 4, "The repository was initialized with a password")
                win.addstr(6, 4, "but you are currently in PASS_OFF mode.")
                win.addstr(8, 4, "Press 'p' to enable password mode first")
                win.addstr(9, 4, "or any other key to cancel")
                win.refresh()

                key = win.getch()
                if key in (ord("p"), ord("P")):
                    # User wants to enable password mode
                    self.toggle_password_mode()
                    # Try again with password
                    if self.password_mode == "PASS_ON_":
                        password = self.get_restic_password()
                        if password:
                            win.clear()
                            win.box()
                            win.addstr(
                                1,
                                2,
                                f"RESTIC SNAPSHOTS: {backup_name}",
                                curses.A_BOLD | curses.A_REVERSE,
                            )
                            win.addstr(3, 4, "Retrying with password...", curses.A_DIM)
                            win.refresh()
                            success, output = run_restic_snapshots(
                                self.remote_name, backup_name, password=password
                            )
                # Continue to display results below

            # Clear the dialog content (except border and title)
            for row in range(2, dialog_h - 1):
                win.addstr(row, 1, " " * (dialog_w - 2))

            # Redraw border and title
            win.box()
            win.addstr(
                1,
                2,
                f"RESTIC SNAPSHOTS: {backup_name}",
                curses.A_BOLD | curses.A_REVERSE,
            )

            if success:
                # Split output into lines and display
                lines = output.split("\n")
                max_display_lines = dialog_h - 4
                for i, line in enumerate(lines[:max_display_lines]):
                    if 2 + i < dialog_h - 1:
                        # Truncate line to fit dialog width
                        display_line = line[: dialog_w - 4]
                        win.addstr(2 + i, 2, display_line)
            else:
                win.addstr(3, 4, "Error retrieving snapshots:", curses.A_BOLD)
                # Wrap error message
                error_lines = output.split("\n")
                for i, line in enumerate(error_lines[:5]):
                    if 5 + i < dialog_h - 1:
                        win.addstr(5 + i, 4, line[: dialog_w - 8])

            win.addstr(dialog_h - 2, 4, "Press any key to close...")
            win.refresh()

            # Wait for key press
            win.nodelay(False)
            win.getch()
        except curses.error:
            # If curses fails, show error in status
            self.set_error("Cannot display snapshots dialog")

    def handle_delete(self) -> None:
        """Handle delete operation (F8) for remote."""
        if self.active_panel != "right":
            self.set_error("Delete is only implemented for Remote panel")
            return

        selected = self.right_panel.get_selected_item()
        if not selected or selected == "..":
            self.set_error("Select a remote directory/file to delete")
            return

        is_directory = selected.endswith("/")
        remote_path = f"{self.remote_name}{self.remote_path}{selected}"

        # Show dialog to confirm delete
        dialog_h = 8
        dialog_w = 60
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
        win.box()

        if is_directory:
            win.addstr(
                1,
                2,
                "PURGE REMOTE DIRECTORY:",
                curses.A_BOLD | curses.color_pair(self.COLOR_DIR),
            )
            win.addstr(2, 4, f"{remote_path}"[:52])
            win.addstr(4, 4, "1. DRY RUN (rclone purge --dry-run)")
            win.addstr(5, 4, "2. REAL PURGE (DANGEROUS!)")
            win.addstr(6, 4, "Press 1, 2 or Escape to cancel")
        else:
            win.addstr(
                1,
                2,
                "DELETE REMOTE FILE:",
                curses.A_BOLD | curses.color_pair(self.COLOR_NORMAL),
            )
            win.addstr(2, 4, f"{remote_path}"[:52])
            win.addstr(4, 4, "Really delete this file?")
            win.addstr(6, 4, "Press 'Y' to DELETE, any other key to CANCEL")

        win.refresh()

        if is_directory:
            mode = 0
            while True:
                key = win.getch()
                if key in (ord("1"), ord("2")):
                    mode = int(chr(key))
                    break
                elif key == 27:  # Escape
                    break

            if mode > 0:
                dry_run = mode == 1

                # Additional confirmation for real purge
                if not dry_run:
                    # Reuse window for second confirmation
                    win.clear()
                    win.box()
                    win.addstr(
                        1,
                        2,
                        "!!! FINAL CONFIRMATION !!!",
                        curses.A_BOLD | curses.A_REVERSE,
                    )
                    win.addstr(3, 4, "REALLY PURGE THIS REMOTE PATH?")
                    win.addstr(4, 4, f"{remote_path}"[:52])
                    win.addstr(6, 4, "Press 'Y' to DELETE, any other key to CANCEL")
                    win.refresh()
                    confirm_key = win.getch()
                    if confirm_key not in (ord("y"), ord("Y")):
                        self.set_status("Delete cancelled.")
                        return

                # Save current curses state
                curses.def_prog_mode()
                curses.curs_set(1)  # Show cursor for shell
                curses.endwin()
                curses.reset_shell_mode()
                os.system("stty sane")

                from jusfltuls.rclone_scanner import run_rclone_purge

                try:
                    run_rclone_purge(remote_path, dry_run=dry_run)
                except Exception as e:
                    print(f"Internal Error in purge execution: {e}")
                    print("Press Enter to return...")
                    import sys

                    sys.stdin.readline()

                os.system("stty sane")
                curses.reset_prog_mode()
                curses.curs_set(0)  # Hide cursor again
                self.stdscr.refresh()
                self.draw()  # Force full redraw

                # Reload panels after delete
                self.update_remote()
                self.set_status(f"Purge {'dry-run ' if dry_run else ''}completed.")
        else:
            # File deletion flow
            while True:
                key = win.getch()
                if key in (ord("y"), ord("Y")):
                    # Save current curses state
                    curses.def_prog_mode()
                    curses.curs_set(1)  # Show cursor for shell
                    curses.endwin()
                    curses.reset_shell_mode()
                    os.system("stty sane")

                    from jusfltuls.rclone_scanner import run_rclone_deletefile

                    try:
                        run_rclone_deletefile(remote_path)
                    except Exception as e:
                        print(f"Internal Error in deletefile execution: {e}")
                        print("Press Enter to return...")
                        import sys

                        sys.stdin.readline()

                    os.system("stty sane")
                    curses.reset_prog_mode()
                    curses.curs_set(0)  # Hide cursor again
                    self.stdscr.refresh()
                    self.draw()  # Force full redraw

                    # Reload panels after delete
                    self.update_remote()
                    self.set_status("File deleted.")
                    break
                elif key != -1:  # Any other key to cancel
                    self.set_status("Delete cancelled.")
                    break

    def handle_enter(self) -> None:
        """Handle Enter key press."""
        if self.active_panel == "left":
            selected = self.left_panel.get_selected_item()
            if selected == "..":
                # Remember current directory name for cursor positioning after going up
                prev_dir = os.path.basename(self.local_path.rstrip("/")) + "/"
                self.local_path = os.path.dirname(self.local_path)
                self.update_local()

                # Find the previous directory in the new list and select it
                if prev_dir in self.left_panel.items:
                    self.left_panel.selected_index = self.left_panel.items.index(
                        prev_dir
                    )
                    # Adjust offset if needed
                    panel_height = self.height - 6
                    if self.left_panel.selected_index >= panel_height:
                        self.left_panel.offset = (
                            self.left_panel.selected_index - panel_height + 1
                        )
                else:
                    self.left_panel.selected_index = 0
                    self.left_panel.offset = 0
            elif selected and selected.endswith("/"):
                self.local_path = os.path.join(self.local_path, selected.rstrip("/"))
                self.update_local()
                self.left_panel.selected_index = 0
                self.left_panel.offset = 0
        else:
            selected = self.right_panel.get_selected_item()
            if selected == "..":
                if self.remote_path and self.remote_path != "/":
                    # Remember current directory name for cursor positioning after going up
                    # remote_path typically looks like "dir1/dir2/" or "dir1/"
                    path_stripped = self.remote_path.rstrip("/")
                    prev_dir = os.path.basename(path_stripped) + "/"

                    parts = path_stripped.split("/")
                    self.remote_path = "/".join(parts[:-1])
                    if self.remote_path and not self.remote_path.endswith("/"):
                        self.remote_path += "/"
                    if self.remote_path == "/":
                        self.remote_path = ""

                    self.update_remote()

                    # Find the previous directory in the new list and select it
                    if prev_dir in self.right_panel.items:
                        self.right_panel.selected_index = self.right_panel.items.index(
                            prev_dir
                        )
                        # Adjust offset if needed
                        panel_height = self.height - 6
                        if self.right_panel.selected_index >= panel_height:
                            self.right_panel.offset = (
                                self.right_panel.selected_index - panel_height + 1
                            )
                    else:
                        self.right_panel.selected_index = 0
                        self.right_panel.offset = 0
            elif selected and selected.endswith("/"):
                # Check if we are in root and folder starts with restic_
                if self.remote_path == "" and selected.startswith("restic_"):
                    self._show_restic_snapshots(selected)
                else:
                    self.remote_path = f"{self.remote_path}{selected}"
                    self.update_remote()
                    self.right_panel.selected_index = 0
                    self.right_panel.offset = 0

    def handle_mkdir(self) -> None:
        """Handle mkdir operation (F7) for remote."""
        if self.active_panel != "right":
            self.set_error("Mkdir is only implemented for Remote panel")
            return

        # Show dialog to enter directory name
        dialog_h = 8
        dialog_w = 60
        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
        win.box()
        win.addstr(
            1,
            2,
            "CREATE REMOTE DIRECTORY:",
            curses.A_BOLD | curses.color_pair(self.COLOR_DIR),
        )
        win.addstr(3, 4, "Enter directory name:")
        win.refresh()

        # Enable cursor and echo for input
        curses.curs_set(1)
        curses.echo()
        win.nodelay(False)

        # Get input
        curses.noecho()
        curses.curs_set(0)

        # Use a simple approach - collect chars
        dirname = ""
        input_x = 4
        input_y = 4
        max_input_len = 50

        win.move(input_y, input_x)
        win.refresh()

        while True:
            key = win.getch()
            if key in (10, 13, curses.KEY_ENTER):  # Enter
                break
            elif key == 27:  # Escape
                self.set_status("Mkdir cancelled.")
                return
            elif key == curses.KEY_BACKSPACE or key == 127:  # Backspace
                if dirname:
                    dirname = dirname[:-1]
                    win.addstr(input_y, input_x + len(dirname), " ")
                    win.move(input_y, input_x + len(dirname))
                    win.refresh()
            elif 32 <= key <= 126 and len(dirname) < max_input_len:  # Printable chars
                dirname += chr(key)
                win.addch(input_y, input_x + len(dirname) - 1, key)
                win.refresh()

        dirname = dirname.strip()
        if not dirname:
            self.set_status("Mkdir cancelled (empty name).")
            return

        # Create full path
        remote_path = f"{self.remote_name}{self.remote_path}{dirname}"

        # Save current curses state
        curses.def_prog_mode()
        curses.curs_set(1)
        curses.endwin()
        curses.reset_shell_mode()
        os.system("stty sane")

        from jusfltuls.rclone_scanner import run_rclone_mkdir

        try:
            run_rclone_mkdir(remote_path)
        except Exception as e:
            print(f"Internal Error in mkdir execution: {e}")
            print("Press Enter to return...")
            import sys

            sys.stdin.readline()

        os.system("stty sane")
        curses.reset_prog_mode()
        curses.curs_set(0)
        self.stdscr.refresh()
        self.draw()

        # Reload panels after mkdir
        self.update_remote()
        self.set_status(f"Directory '{dirname}' created.")

    def handle_help(self) -> None:
        """Handle help command (F1/h) - show all allowed keys."""
        dialog_h = 22
        dialog_w = 70

        # Check if terminal is large enough
        if self.height < dialog_h or self.width < dialog_w:
            # Show error message instead
            msg = "Terminal too small for help"
            if self.height >= 3 and self.width >= len(msg) + 4:
                dialog_y = (self.height - 3) // 2
                dialog_x = (self.width - len(msg) - 4) // 2
                try:
                    win = curses.newwin(3, len(msg) + 4, dialog_y, dialog_x)
                    win.box()
                    win.addstr(1, 2, msg)
                    win.refresh()
                    win.nodelay(False)
                    win.getch()
                except:
                    pass
            self.set_error(msg)
            return

        dialog_y = (self.height - dialog_h) // 2
        dialog_x = (self.width - dialog_w) // 2

        try:
            win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x)
            win.box()
            win.addstr(
                1, 2, "MCRC - Help / Key Bindings", curses.A_BOLD | curses.A_REVERSE
            )

            # Key bindings list
            bindings = [
                ("↑↓", "Navigate up/down"),
                ("PgUp/PgDn", "Page up/down"),
                ("Home/End", "Go to first/last item"),
                ("Tab/←→", "Switch between panels"),
                ("Enter", "Enter directory"),
                ("", ""),
                ("F2 / 2", "Mount remote at /mnt/<remote>"),
                ("F3 / 3", "Check/compare (size-only)"),
                ("F4 / 4", "View text file"),
                ("F5 / 5", "Copy file/directory"),
                ("F7 / 7", "Create directory (remote)"),
                ("F8 / 8", "Delete file/directory (remote)"),
                ("F9 / 9 / s", "Sync src to dest (with delete)"),
                ("b", "Backup local folder (restic)"),
                ("e", "Restore backup to /tmp/ (restic)"),
                ("p", "Toggle password mode (restic)"),
                ("r", "Refresh panels"),
                ("", ""),
                ("F1 / h", "Show this help"),
                ("q", "Quit application"),
            ]

            row = 3
            for key, action in bindings:
                if key and row < dialog_h - 1:
                    win.addstr(row, 4, f"{key:12} {action}"[:62])
                row += 1

            win.addstr(dialog_h - 2, 4, "Press any key to close...")
            win.refresh()

            # Wait for any key
            win.nodelay(False)
            win.getch()
        except curses.error:
            # If curses fails, just set an error
            self.set_error("Cannot display help")

    def set_status(self, message: str) -> None:
        """Set a status message."""
        self.status_message = message
        self.error_message = None

    def set_error(self, message: str) -> None:
        """Set an error message."""
        self.error_message = message
        self.status_message = None
