#!/bin/python

import locale
import curses
import os
import subprocess
import re
from time import sleep
import zipfile
from io import BytesIO
import tempfile
import json
import atexit
import shutil

PROGRAM_NAME = "Stellar Commander"
PROGRAM_VERSION = "0.1"

NAVIGATION_HISTORY = {}

DRIVE_DEVICE = "8"
CBM_FILE_TYPES = {
    'PRG': 'Program',
    'SEQ': 'Sequential',
    'USR': 'User',
    'REL': 'Relative',
    'DEL': 'Deleted'
}


DEFAULT_CONFIG = {
    'device_id': "8",
    'connection_type': "serial1",
    'connection_types': ["auto", "original", "serial1", "serial2", "parallel"]
}

def load_config():
    config_path = os.path.expanduser("~/.stellarcommander.cfg")
    if os.path.exists(config_path):
        try:
            with open(config_path, 'r') as f:
                return json.load(f)
        except:
            return DEFAULT_CONFIG.copy()
    return DEFAULT_CONFIG.copy()

def save_config(config):
    config_path = os.path.expanduser("~/.stellarcommander.cfg")
    with open(config_path, 'w') as f:
        json.dump(config, f)

def cleanup_temp_files():
    temp_dir = tempfile.gettempdir()
    for filename in os.listdir(temp_dir):
        if filename.startswith('tmp') or filename.endswith('.prg'):
            try:
                filepath = os.path.join(temp_dir, filename)
                if os.path.isfile(filepath):
                    os.unlink(filepath)
                elif os.path.isdir(filepath):
                    shutil.rmtree(filepath)
            except Exception:
                pass

        
def setup_colors():
    curses.start_color()
    curses.use_default_colors()

    curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLUE)    # Header (yellow)
    curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN)     # Footer
    curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)    # Highlight
    curses.init_pair(4, curses.COLOR_CYAN, -1)                     # Normal text
    curses.init_pair(5, curses.COLOR_YELLOW, curses.COLOR_BLUE)    # Window border
    curses.init_pair(6, curses.COLOR_RED, -1)                      # Error text
    curses.init_pair(7, curses.COLOR_GREEN, -1)                    # Success text


def list_dir(path):
    try:
        if path.endswith('.zip'):
            try:
                with zipfile.ZipFile(path) as z:
                    # Filter out directories and sort case-insensitive
                    entries = [name for name in z.namelist() if not name.endswith('/')]
                    return ['..'] + sorted(entries, key=lambda x: x.lower())
            except zipfile.BadZipFile:
                return ['[Invalid ZIP file]']
        
        # Normal directory listing
        entries = os.listdir(path)
        entries = sorted(entries, key=lambda x: (not os.path.isdir(os.path.join(path, x)), x.lower()))
        return ['..'] + entries
    except Exception:
        return ['..']


def extract_from_zip(zip_path, filename):
    try:
        with zipfile.ZipFile(zip_path) as z:
            with z.open(filename) as zf:
                content = zf.read()
                # Create temp dir with original filename structure
                temp_dir = tempfile.mkdtemp()
                original_name = os.path.basename(filename)
                temp_path = os.path.join(temp_dir, original_name)
                with open(temp_path, 'wb') as f:
                    f.write(content)
                return temp_path, original_name
    except Exception as e:
        print(f"Extraction error: {e}")
        return None, None

def list_zip_contents(zip_path):
    try:
        with zipfile.ZipFile(zip_path) as z:
            return z.namelist()
    except:
        return []

def is_in_zip(current_path):
    return current_path.endswith('.zip') or '.zip/' in current_path

def parse_cbm_directory(lines):
    parsed_lines = []
    disk_title = ""
    disk_id = ""
    free_blocks = ""
    
    for line in lines:
        # Extract disk title and ID
        if line.startswith('0 ."'):
            #match = re.search(r'0\s+\.\"([^"]+)\"\s+([^\s]+)\s+[^\s]+', line)
            match = re.search(r'\.\s*"([^"]+)"\s+(\d{2,4})', line)
            if match:
                disk_title = match.group(1).strip()
                disk_id = match.group(2).strip()
                
        # Extract free blocks (keep this for footer)
        if 'blocks free' in line.lower():
            free_blocks = line.strip()
            
        # Skip all header/footer/status lines (0, 1, 2 lines and blocks free)
        if not line.strip() or line.startswith(('0 ', '1 ', '2 ')) or ',' in line.lower() or 'blocks free' in line.lower():
            continue
            
        # Parse and keep only file entries
        match = re.search(r'"([^"]+)"\s+([A-Z]{3})\s+(\d+)', line)
        if match:
            name, file_type, blocks = match.groups()
            file_type_desc = CBM_FILE_TYPES.get(file_type, file_type)
            parsed_lines.append(f'"{name}" {file_type} {blocks:>4} {file_type_desc}')
        else:
            parsed_lines.append(line)
    
            
    return parsed_lines, disk_title, disk_id, free_blocks

def get_disk_directory():
    try:
        result = subprocess.run(
            ["cbmctrl", "dir", DRIVE_DEVICE],
            capture_output=True, text=True, check=True
        )
        lines = result.stdout.splitlines()
        
        return parse_cbm_directory(lines)
    except subprocess.CalledProcessError as e:
        return ["[Error reading CBM drive]"], "", "", ""

def get_disk_status():
    try:
        status_result = subprocess.run(
            ["cbmctrl", "status", DRIVE_DEVICE],
            capture_output=True, text=True, check=True
        )
        return status_result.stdout.strip()
    except subprocess.CalledProcessError:
        return "[Status error]"

def is_c64_program(filename):
    return os.path.splitext(filename)[1].lower() == '.prg'


def ask_disk_name_and_id(stdscr):
    curses.echo()
    h, w = stdscr.getmaxyx()
    win_h, win_w = 9, 40
    win_y = (h - win_h) // 2
    win_x = (w - win_w) // 2
    win = curses.newwin(win_h, win_w, win_y, win_x)
    win.border()
    win.addstr(1, 2, "🧽 C64 Disk Format", curses.A_BOLD)
    win.addstr(3, 2, "Name (max 16): ")
    win.refresh()
    name = win.getstr(3, 20, 16).decode("utf-8").upper().strip()
    win.addstr(5, 2, "ID (e.g. 01): ")
    win.refresh()
    disk_id = win.getstr(5, 20, 2).decode("utf-8").strip()
    curses.noecho()
    return name, disk_id


def transfer_d64_image(stdscr, path, source_is_drive, display_name=None, config=None):
    h, w = stdscr.getmaxyx()
    popup_h, popup_w = 10, 52  # Taller window for track display
    popup_y, popup_x = (h-popup_h)//2, (w-popup_w)//2
    
    progress_win = curses.newwin(popup_h, popup_w, popup_y, popup_x)
    progress_win.border()

    if config is None:
        config = {'connection_type': 'serial1'}

    # Simple display logic - use exactly what we're given
    display = display_name if display_name else os.path.basename(path)
    direction = f"💾 {'Creating' if source_is_drive else 'Transferring'}: {display}"
    
    progress_win.addstr(1, 2, direction, curses.A_BOLD)
    # Set appropriate direction message
    if source_is_drive:
        direction = f"💾 Creating: {os.path.basename(path)}"
        cmd = ["d64copy", "-v", "-t", config['connection_type'], "-w", DRIVE_DEVICE, path]
    else:
        direction = f"💾 Transferring: {os.path.basename(path)}"
        cmd = ["d64copy", "-v", "-t", config['connection_type'], "-w", path, DRIVE_DEVICE]

    progress_win.addstr(1, 2, direction, curses.A_BOLD)


    filename = os.path.basename(path)[:popup_w-4]
    progress_win.addstr(1, 2, f"💾 Transferring: {display_name}", curses.A_BOLD)
    
    # Permanent progress elements
    progress_win.addstr(3, 2, "Overall: [")
    progress_win.addstr(3, popup_w-6, "]   ")
    progress_win.addstr(5, 2, "Current Track: ")
    progress_win.addstr(7, 2, "Sectors: ")
    progress_win.refresh()

    try:
        process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            universal_newlines=True,
            bufsize=1
        )

        total_sectors = 683  # Default for 35-track disks
        completed_sectors = 0
        current_track = 0

        while True:
            output = process.stdout.readline()
            if not output and process.poll() is not None:
                break

            # Parse track progress lines (e.g., " 3: *****-*****-*****-**-     8%    59/683")
            if re.match(r'^\s*\d+:', output):
                parts = output.split()
                try:
                    # Update current track
                    current_track = int(parts[0].strip(':'))
                    
                    # Update completed sectors
                    if '/' in parts[-1]:
                        completed_sectors = int(parts[-1].split('/')[0])
                        total_sectors = int(parts[-1].split('/')[1])
                    
                    # Calculate percentage
                    percent = min(100, (completed_sectors * 100) // total_sectors)
                    
                    # Update display
                    progress_win.addstr(5, 17, f"{current_track}/35")
                    progress_win.addstr(7, 10, f" {completed_sectors}/{total_sectors}")
                    
                    # Draw progress bar
                    bar_width = popup_w - 17
                    filled = (percent * bar_width) // 100
                    progress_win.addstr(3, 12, "=" * filled + " " * (bar_width - filled))
                    progress_win.addstr(3, popup_w-8, f"] {percent}%")
                    
                    progress_win.refresh()
                except (ValueError, IndexError):
                    pass  # Skip malformed lines

        if process.returncode == 0:
            progress_win.addstr(8, 2, "✅ Transfer complete!", curses.color_pair(7))
        else:
            raise subprocess.CalledProcessError(process.returncode, process.args)

    except Exception as e:
        progress_win.addstr(8, 2, f"❌ Error: {str(e)[:popup_w-10]}", curses.color_pair(6))

    progress_win.refresh()
    progress_win.getch()
    return process.returncode == 0 if 'process' in locals() else False

def show_config_menu(stdscr, config):
    h, w = stdscr.getmaxyx()
    win_h, win_w = 12, 50
    win_y = (h - win_h) // 2
    win_x = (w - win_w) // 2
    win = curses.newwin(win_h, win_w, win_y, win_x)
    win.border()
    win.addstr(1, 2, "Configuration Menu", curses.A_BOLD)
    
    # Device ID selection
    win.addstr(3, 2, "1. C64 Device ID (8/9): ")
    win.addstr(3, 26, config['device_id'], curses.A_REVERSE)
    
    # Connection type selection
    win.addstr(5, 2, "2. Connection Type: ")
    # Initial display - no padding needed
    win.addstr(5, 22, config['connection_type'], curses.A_REVERSE)
    
    win.addstr(7, 2, "S. Save configuration")
    win.addstr(8, 2, "C. Cancel without saving")
    
    win.refresh()
    
    while True:
        key = win.getch()
        if key == ord('1'):
            config['device_id'] = '9' if config['device_id'] == '8' else '8'
            win.addstr(3, 26, config['device_id'], curses.A_REVERSE)
        elif key == ord('2'):
            # Clear the previous value
            win.addstr(5, 22, " " * 8) 
            
            # Cycle through connection types
            if config['connection_type'] == "serial1":
                config['connection_type'] = "serial2"
            elif config['connection_type'] == "serial2":
                config['connection_type'] = "parallel"
            elif config['connection_type'] == "parallel":
                config['connection_type'] = "auto"
            elif config['connection_type'] == "auto":
                config['connection_type'] = "original"    
            else:
                config['connection_type'] = "serial1"
            
            # Write new value without padding
            win.addstr(5, 22, config['connection_type'], curses.A_REVERSE)
        elif key in (ord('s'), ord('S')):
            save_config(config)
            return True  # Config changed
        elif key in (ord('c'), ord('C')) or key == 27:  # ESC
            return False  # Config not changed
        
        win.refresh()


def draw_interface(stdscr, current_row_left, current_row_right, file_list, current_path, 
                  disk_dir_lines, focus, disk_title, disk_id, free_blocks, disk_status=None):
    stdscr.clear()
    h, w = stdscr.getmaxyx()

    # 1. Draw the full-width header bar at the very top (row 0)
    header_text = f" {PROGRAM_NAME} v{PROGRAM_VERSION}"
    header_x = max(0, (w - len(header_text)) // 2)
    stdscr.attron(curses.color_pair(1))
    # Fill entire top row with background color
    stdscr.addstr(0, 0, " " * w)
    # Write centered program name
    stdscr.addstr(0, header_x, header_text)
    stdscr.attroff(curses.color_pair(1))

    # 2. Adjust all panel content to start at row 1 (below header)
    panel_start_y = 1
    left_width = w // 2 - 1
    right_start = left_width + 2

    # 3. Draw panel borders (starting at row 1)
    stdscr.attron(curses.color_pair(5))
    # Left panel border
    stdscr.addstr(panel_start_y, 0, "╔" + "═" * (left_width - 2) + "╗")
    for y in range(panel_start_y + 1, h - 2):
        stdscr.addstr(y, 0, "║")
        stdscr.addstr(y, left_width - 1, "║")
    stdscr.addstr(h - 2, 0, "╚" + "═" * (left_width - 2) + "╝")
    
    # Right panel border
    stdscr.addstr(panel_start_y, right_start, "╔" + "═" * (w - right_start - 2) + "╗")
    for y in range(panel_start_y + 1, h - 2):
        stdscr.addstr(y, right_start, "║")
        stdscr.addstr(y, w - 1, "║")
    stdscr.addstr(h - 2, right_start, "╚" + "═" * (w - right_start - 2) + "╝")
    stdscr.attroff(curses.color_pair(5))

    # 4. Draw panel headers (now at row 1)
    stdscr.attron(curses.color_pair(1))
    # PC side header
    pc_header = f"PC: {current_path[:left_width - 10]}"
    stdscr.addstr(panel_start_y, 2, pc_header.ljust(left_width - 2))
    
    # C64 side header
    c64_header = f"C64: Drive {DRIVE_DEVICE}"
    debug_info = []
    if disk_title:
        debug_info.append(f"Title:'{disk_title}'")
    if disk_id:
        debug_info.append(f"ID:'{disk_id}'")
    if debug_info:
        c64_header += " [" + " ".join(debug_info) + "]"
    stdscr.addstr(panel_start_y, right_start + 2, c64_header.ljust(w - right_start - 4))
    stdscr.attroff(curses.color_pair(1))

    # 5. Draw file lists (starting at row 2)
    for idx, entry in enumerate(file_list):
        y = idx + panel_start_y + 1  # Adjusted y-position
        if y >= h - 3:
            break
        full_path = os.path.join(current_path, entry)

        display_name = entry
        if entry == '..':
            display_name = entry
        elif current_path.endswith('.zip'):
            display_name = entry  # Plain display inside ZIP
        elif entry.endswith('.zip'):
            display_name = entry + "/"  # Show ZIP as directory
        elif os.path.isdir(os.path.join(current_path, entry)):
            display_name = entry + "/"


        if focus == "left" and idx == current_row_left:
            stdscr.attron(curses.color_pair(3))
            stdscr.addstr(y, 2, display_name[:left_width - 4].ljust(left_width - 4))
            stdscr.attroff(curses.color_pair(3))
        else:
            stdscr.attron(curses.color_pair(4))
            stdscr.addstr(y, 2, display_name[:left_width - 4].ljust(left_width - 4))
            stdscr.attroff(curses.color_pair(4))

    # Right window disk contents
    for idx, line in enumerate(disk_dir_lines):
        y = idx + panel_start_y + 1  # Adjusted y-position
        if y >= h - 4:
            break
        if focus == "right" and idx == current_row_right:
            stdscr.attron(curses.color_pair(3))
            stdscr.addstr(y, right_start + 2, line[:w - right_start - 4].ljust(w - right_start - 4))
            stdscr.attroff(curses.color_pair(3))
        else:
            stdscr.attron(curses.color_pair(4))
            stdscr.addstr(y, right_start + 2, line[:w - right_start - 4].ljust(w - right_start - 4))
            stdscr.attroff(curses.color_pair(4))

    # Free blocks and status display
    if free_blocks or disk_status:
        status_display = free_blocks if free_blocks else ""
        if disk_status and disk_status != "[Status error]":
            if status_display:
                status_display += f" | {disk_status}"
            else:
                status_display = disk_status
            
        stdscr.attron(curses.color_pair(4))
        stdscr.addstr(h - 3, right_start + 2, status_display[:w - right_start - 4].ljust(w - right_start - 4))
        stdscr.attroff(curses.color_pair(4))

    # Footer
    stdscr.attron(curses.color_pair(2))
    footer_text = "F1 Help | F2 Config | F3 Refresh | F4 Image | F5 Copy | F6 Format | F8 Delete | TAB Switch | Q Quit"
    stdscr.addstr(h - 1, 0, footer_text.ljust(w - 1))
    stdscr.attroff(curses.color_pair(2))

    stdscr.refresh()




def show_progress_popup(stdscr, message):
    h, w = stdscr.getmaxyx()
    lines = message.splitlines()
    win_h = len(lines) + 4
    win_w = max(len(line) for line in lines) + 6
    win_y = (h - win_h) // 2
    win_x = (w - win_w) // 2
    win = curses.newwin(win_h, win_w, win_y, win_x)
    win.border()
    for i, line in enumerate(lines):
        win.addstr(2 + i, 3, line)
    win.refresh()

def show_help_popup(stdscr):
    h, w = stdscr.getmaxyx()
    win_h, win_w = 16, 50
    win_y = (h - win_h) // 2
    win_x = (w - win_w) // 2
    help_win = curses.newwin(win_h, win_w, win_y, win_x)
    help_win.border()
    help_win.addstr(1, 2, "Stellar Commander Help", curses.A_BOLD)
    help_win.addstr(3, 2, "TAB - Switch between PC and C64 drive")
    help_win.addstr(4, 2, "F1   - Show this help")
    help_win.addstr(5, 2, "F2   - Configuration")
    help_win.addstr(6, 2, "F3   - Refresh directory listing")
    help_win.addstr(7, 2, "F4   - Write .d64 image (either way)")
    help_win.addstr(8, 2, "F5   - Copy selected file (either way)")
    help_win.addstr(9, 2, "F6   - Format C64 disk")
    help_win.addstr(10, 2, "F8   - Delete selected file")
    help_win.addstr(11, 2, "Arrow keys - Navigate files")
    help_win.addstr(12, 2, "Enter     - Open directory")
    help_win.addstr(14, 2, "Press any key to close")
    help_win.refresh()
    help_win.getch()

def format_diskette(stdscr, set_refresh_flag):
    name, disk_id = ask_disk_name_and_id(stdscr)
    
    if not name or not disk_id:
        show_info_popup(stdscr, "❌ Disk name and ID are required!")
        return

    show_progress_popup(stdscr, f"💾 Formatting\n{name},{disk_id}\nDrive {DRIVE_DEVICE}...")
    stdscr.refresh()

    try:
        # Reset drive first
        subprocess.run(["cbmctrl", "reset"], check=True)
        sleep(1)  # Give drive time to reset
        
        # Format the disk
        result = subprocess.run(
            ["cbmformat", "-s", DRIVE_DEVICE, f"{name},{disk_id}"],
            check=True, capture_output=True, text=True
        )

        # Parse output for status
        status_line = "00, OK"
        for line in result.stdout.splitlines():
            if any(s in line for s in ["00,", "20,", "21,", "74,"]):
                status_line = line.strip()
                break

        show_info_popup(stdscr, f"✅ Formatted\n{name},{disk_id}\n\nStatus:\n{status_line}")
        set_refresh_flag()

    except subprocess.CalledProcessError as e:
        err = e.stderr.strip() or e.stdout.strip() or "Unknown error"
        show_info_popup(stdscr, f"❌ Format error\n\n{err}")

def show_info_popup(stdscr, message):
    lines = message.strip().splitlines()
    h, w = stdscr.getmaxyx()

    max_line_length = max(len(line.encode("utf-8")) for line in lines)
    win_h = len(lines) + 4
    win_w = max_line_length + 6

    win_y = (h - win_h) // 2
    win_x = (w - win_w) // 2

    win = curses.newwin(win_h, win_w, win_y, win_x)
    win.box()

    # Color error/success messages appropriately
    if "❌" in message:
        color = curses.color_pair(6)
    elif "✅" in message:
        color = curses.color_pair(7)
    else:
        color = curses.color_pair(4)

    for i, line in enumerate(lines):
        win.addstr(2 + i, 3, line, color)

    win.addstr(win_h - 2, (win_w - 16) // 2, "[Press any key]")
    win.refresh()
    win.getch()

def cleanup_navigation_history(current_path):
    global NAVIGATION_HISTORY
    # Keep only paths that are parents of the current path
    NAVIGATION_HISTORY = {path: pos for path, pos in NAVIGATION_HISTORY.items() 
                         if current_path.startswith(path)}

def main(stdscr):
    setup_colors()
    curses.curs_set(0)
    current_path = os.getcwd()
    files = list_dir(current_path)
    current_row_left = 0
    current_row_right = 0
    focus = "left"

    # Load config
    config = load_config()
    global DRIVE_DEVICE
    DRIVE_DEVICE = config['device_id']

    disk_dir, disk_title, disk_id, free_blocks = get_disk_directory()

    while True:
        # Get status separately
        disk_status = get_disk_status()
        
        draw_interface(stdscr, current_row_left, current_row_right, files,
                      current_path, disk_dir, focus, disk_title, disk_id, 
                      free_blocks, disk_status)
        key = stdscr.getch()
        refresh_needed = False

        if key == 9:  # TAB
            focus = "right" if focus == "left" else "left"

        elif focus == "left":
            if key == curses.KEY_UP and current_row_left > 0:
                current_row_left -= 1
            elif key == curses.KEY_DOWN and current_row_left < len(files) - 1:
                current_row_left += 1
            elif key == curses.KEY_HOME:  # POS1/Home key
                current_row_left = 0
            elif key == curses.KEY_END:   # End key
                current_row_left = max(0, len(files) - 1)

            elif key in [curses.KEY_ENTER, 10, 13]:  # Enter key
                selected = files[current_row_left]
                full_path = os.path.join(current_path, selected)

                # Store current position before navigating
                if current_path not in NAVIGATION_HISTORY:
                    NAVIGATION_HISTORY[current_path] = current_row_left
                else:
                    NAVIGATION_HISTORY[current_path] = current_row_left
    
                if selected == "..":
                    # Get parent directory
                    parent_path = os.path.dirname(current_path)
                    
                    # Navigate up
                    current_path = parent_path
                    files = list_dir(current_path)
                    
                    # Restore cursor position if available
                    if current_path in NAVIGATION_HISTORY:
                        current_row_left = NAVIGATION_HISTORY[current_path]
                    else:
                        current_row_left = 0
    
                elif selected.endswith('.zip') or (current_path.endswith('.zip') and os.path.isdir(full_path)):
                    # Store current position before entering ZIP
                    if current_path not in NAVIGATION_HISTORY:
                        NAVIGATION_HISTORY[current_path] = current_row_left
                    
                    # Navigate into ZIP
                    current_path = full_path
                    files = list_dir(current_path)
                    current_row_left = 0
    
                elif os.path.isdir(full_path):
                    # Store current position before entering directory
                    if current_path not in NAVIGATION_HISTORY:
                        NAVIGATION_HISTORY[current_path] = current_row_left
                    
                    # Navigate into directory
                    current_path = full_path
                    files = list_dir(current_path)
                    current_row_left = 0
            # In the main loop after changing directories:
            cleanup_navigation_history(current_path)

        else:  # focus == "right"
            if key == curses.KEY_UP and current_row_right > 0:
                current_row_right -= 1
            elif key == curses.KEY_DOWN and current_row_right < len(disk_dir) - 1:
                current_row_right += 1
            elif key == curses.KEY_HOME:  # POS1/Home key
                current_row_right = 0
            elif key == curses.KEY_END:   # End key
                current_row_right = max(0, len(disk_dir) - 1)

        if key == curses.KEY_F1:
            show_help_popup(stdscr)
        elif key == curses.KEY_F3:
            disk_dir, disk_title, disk_id, free_blocks = get_disk_directory()
            files = list_dir(current_path)

        elif key == curses.KEY_F2:  # Or another available F-key
            config_changed = show_config_menu(stdscr, config)
            if config_changed:
                DRIVE_DEVICE = config['device_id']
                disk_dir, disk_title, disk_id, free_blocks = get_disk_directory()

        elif key == curses.KEY_F4:
            if focus == "left":
                selected = files[current_row_left]
                full_path = os.path.join(current_path, selected)



                # Handle D64 inside ZIP
                if current_path.endswith('.zip') and selected.lower().endswith('.d64'):
                    try:
                        # Extract and get both temp path and original name
                        temp_path, original_name = extract_from_zip(current_path, selected)
                        if not temp_path:
                            show_info_popup(stdscr, "❌ Failed to extract from ZIP!")
                            continue

                        # Confirm transfer
                        confirm = False
                        try:
                            h, w = stdscr.getmaxyx()
                            win = curses.newwin(7, 50, (h-7)//2, (w-50)//2)
                            win.border()
                            win.addstr(1, 2, "Transfer from ZIP archive", curses.A_BOLD)
                            win.addstr(2, 2, f"ZIP: {os.path.basename(current_path)}")
                            win.addstr(3, 2, f"Image: {selected[:40]}")
                            win.addstr(5, 2, "Confirm? (Y/N)")
                            win.refresh()

                            confirm = win.getch() in (ord('y'), ord('Y'))
                            if confirm and transfer_d64_image(stdscr, temp_path, 
                                       source_is_drive=False, 
                                       display_name=original_name,  # Use original_name here
                                       config=config):
                                disk_dir, disk_title, disk_id, free_blocks = get_disk_directory()
                        finally:
                            # Clean up temp file if it exists
                            if temp_path and os.path.exists(temp_path):
                                os.unlink(temp_path)
                                temp_dir = os.path.dirname(temp_path)
                                if os.path.exists(temp_dir):
                                    os.rmdir(temp_dir)

                    except Exception as e:
                        show_info_popup(stdscr, f"❌ ZIP extraction failed:\n{str(e)}")


                # Handle regular D64 file
                elif selected.lower().endswith('.d64'):
                    if not os.path.isfile(full_path):
                        show_info_popup(stdscr, "❌ File not found!")
                    else:
                        # [Keep your existing confirmation and transfer code]
                        h, w = stdscr.getmaxyx()
                        win = curses.newwin(6, 50, (h-6)//2, (w-50)//2)
                        win.border()
                        win.addstr(1, 2, "Transfer .d64 image to C64 disk?", curses.A_BOLD)
                        win.addstr(3, 2, f"File: {selected[:40]}")
                        win.addstr(4, 2, "Confirm? (Y/N)")
                        win.refresh()

                        confirm = win.getch()
                        if confirm in (ord('y'), ord('Y')):
                            transfer_d64_image(stdscr, full_path, source_is_drive=False, display_name=selected[:40], config=config)
                            disk_dir, disk_title, disk_id, free_blocks = get_disk_directory()

                else:
                    show_info_popup(stdscr, "❌ Not a .d64 disk image!\nPlease select a .d64 file")
            elif focus == "right" and is_in_zip(current_path):
                show_info_popup(stdscr, "❌ Function disabled\n\nCannot transfer disk images\nwhile browsing inside ZIP archive")
                continue
            elif focus == "right":
                # New right-panel C64 disk to .d64 image
                h, w = stdscr.getmaxyx()
                win = curses.newwin(7, 52, (h-7)//2, (w-52)//2)  # Consistent 52-wide window
                win.border()
                win.addstr(1, 2, "💾 Create Disk Image", curses.A_BOLD)
                win.addstr(3, 2, "Save as (.d64): ")
        
                curses.echo()
                filename = ""
                try:
                    filename = win.getstr(3, 18, 16).decode('utf-8').strip()
                    if not filename.lower().endswith('.d64'):
                        filename += '.d64'
                
                    target_path = os.path.join(current_path, filename)
            
                    if os.path.exists(target_path):
                        show_info_popup(stdscr, f"❌ File exists!\n{filename}")
                    else:
                        # Reuse progress display logic
                        if transfer_d64_image(stdscr, target_path, source_is_drive=True, display_name=filename, config=config):
                            files = list_dir(current_path)  # Refresh file list
                finally:
                    curses.noecho()
        elif key == curses.KEY_F5:
            if focus == "left":
                selected = files[current_row_left]
                full_path = os.path.join(current_path, selected)
        
                if current_path.endswith('.zip'):
                    # Handle transfer from within ZIP archive
                    if not is_c64_program(selected):
                        show_info_popup(stdscr, "❌ Not a C64 program!\nOnly .PRG files can be copied")
                        continue
                
                    try:
                        # Create temp file
                        temp_path = extract_from_zip(current_path, selected)
                        if not temp_path:
                            show_info_popup(stdscr, "❌ Failed to extract from ZIP!")
                            continue
                    
                        show_progress_popup(stdscr, f"⏳ Copying to C64\n{selected}...")
                
                        # Use the original filename (not temp path) for CBM write
                        result = subprocess.run(
                            ["cbmwrite", "-t", config['connection_type'], DRIVE_DEVICE, temp_path, selected],
                            check=True,
                            capture_output=True,
                            text=True
                        )
                
                        show_info_popup(stdscr, f"✅ Program copied to C64:\n{selected}")
                        disk_dir, disk_title, disk_id, free_blocks = get_disk_directory()
                
                    except subprocess.CalledProcessError as e:
                        err = e.stderr.strip() or e.stdout.strip() or "Unknown error"
                        show_info_popup(stdscr, f"❌ Copy error:\n{err}")
                    finally:
                        # Clean up temp file if it exists
                        if temp_path and os.path.exists(temp_path):
                            os.unlink(temp_path)
                       
                else:
                    # Existing non-ZIP handling
                    if os.path.isdir(full_path):
                        show_info_popup(stdscr, "❌ Cannot copy directories!")
                    elif not is_c64_program(selected):
                        show_info_popup(stdscr, "❌ Not a C64 program!\nOnly .PRG files can be copied")
                    else:
                        show_progress_popup(stdscr, f"⏳ Copying to C64\n{selected}...")
                        try:
                            subprocess.run(
                                ["cbmwrite", "-t", config['connection_type'], DRIVE_DEVICE, full_path, selected],
                                check=True,
                                capture_output=True,
                                text=True
                            )
                            show_info_popup(stdscr, f"✅ Program copied to C64:\n{selected}")
                            disk_dir, disk_title, disk_id, free_blocks = get_disk_directory()
                        except subprocess.CalledProcessError as e:
                            err = e.stderr.strip() or e.stdout.strip() or "Unknown error"
                            show_info_popup(stdscr, f"❌ Copy error:\n{err}")
            if focus == "right" and is_in_zip(current_path):
                show_info_popup(stdscr, "❌ Function disabled\n\nCannot copy files from C64\nwhile browsing inside ZIP archive")
                continue
            elif focus == "right":
                # Copy from C64 to PC
                if current_row_right < len(disk_dir):
                    selected_line = disk_dir[current_row_right]

                    match = re.search(r'"([^"]+)"\s+([A-Za-z]{3})\b', selected_line)
                    if match:
                        name_only = match.group(1).strip()
                        filetype = match.group(2).strip().upper()
            
                        # Sanitize filename
                        clean_name = "".join(c for c in name_only if c.isalnum() or c in (' ', '-', '_'))
                        pc_filename = f"{clean_name}.{filetype.lower() if filetype != 'PRG' else 'prg'}"
                        target_path = os.path.join(current_path, pc_filename)

                        if not os.path.dirname(target_path) == os.path.abspath(current_path):
                            show_info_popup(stdscr, f"❌ Invalid target path!\n{target_path}")
                            continue

                        # Check if file exists
                        if os.path.exists(target_path):
                            show_info_popup(stdscr, f"❌ File exists:\n{pc_filename}\n\nOverwrite not implemented")
                            continue

                        show_progress_popup(stdscr, f"⏳ Copying from C64\n{pc_filename}...")
                        try:
                            # Explicitly log the command
                            cmd = ["cbmread", "-t", config['connection_type'], DRIVE_DEVICE, name_only, target_path]
                            result = subprocess.run(
                                cmd,
                                check=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                text=True,
                                encoding='ascii',
                                errors='ignore'
                            )
                
                            show_info_popup(stdscr, f"✅ File copied from C64:\n{pc_filename}")
                            files = list_dir(current_path)
                        except subprocess.CalledProcessError as e:
                            err = e.stderr.strip() or e.stdout.strip() or "Unknown error"
                            show_info_popup(stdscr, f"❌ Copy error:\n{err}")
                    else:
                        show_info_popup(stdscr, f"❌ Could not parse:\n{selected_line}")
        elif key == curses.KEY_F6:
            def refresh_disk_dir(): 
                nonlocal disk_dir, disk_title, disk_id, free_blocks
                disk_dir, disk_title, disk_id, free_blocks = get_disk_directory()
            format_diskette(stdscr, refresh_disk_dir)

        elif key == curses.KEY_F8:
            if focus == "left":
                if 0 <= current_row_left < len(files):
                    filename = files[current_row_left]
                    full_path = os.path.join(current_path, filename)
                    if os.path.isfile(full_path):
                        try:
                            os.remove(full_path)
                            show_info_popup(stdscr, f"🗑️ File deleted:\n{filename}")
                            files = list_dir(current_path)
                        except Exception as e:
                            show_info_popup(stdscr, f"❌ Delete error:\n{e}")
                    else:
                        show_info_popup(stdscr, "❌ Not a regular file!")
            elif focus == "right":
                if 0 <= current_row_right < len(disk_dir):
                    selected_line = disk_dir[current_row_right]
                    match = re.search(r'"([^"]+)"', selected_line)
                    if match:
                        name_only = match.group(1)
                        try:
                            # Send scratch command
                            subprocess.run(
                                ["cbmctrl", "pcommand", DRIVE_DEVICE, f's0:{name_only}'],
                                check=True
                            )
                            
                            # Wait and check status
                            show_progress_popup(stdscr, f"Deleting file on C64:\n{name_only}...")
                            sleep(1)  # Give drive time to process
                            
                            status = subprocess.run(
                                ["cbmctrl", "status", DRIVE_DEVICE],
                                capture_output=True, text=True, check=True
                            )
                            
                            status_line = status.stdout.strip()
                            if "files scratched" in status_line.lower():
                                show_info_popup(stdscr, f"🗑️ C64 file deleted:\n{name_only}\n\nStatus:\n{status_line}")
                                disk_dir, disk_title, disk_id, free_blocks = get_disk_directory()
                            else:
                                show_info_popup(stdscr, f"❌ Error:\n{status_line}")
                        except subprocess.CalledProcessError as e:
                            err = e.stderr.strip() or e.stdout.strip() or str(e)
                            show_info_popup(stdscr, f"❌ Delete error:\n{err}")
                    else:
                        show_info_popup(stdscr, "❌ Could not parse filename!")
        elif key == ord('q'):
            break

if __name__ == "__main__":
    curses.wrapper(main)

atexit.register(cleanup_temp_files)
