import curses
import time
import tempfile
import requests
from api.auth import get_spotify_client


#!/usr/bin/env python3

import sys
import os
from PIL import Image
import requests
from io import BytesIO


def get_terminal_size():
    """Get terminal size in columns and rows"""
    try:
        columns, rows = os.get_terminal_size()
        return columns, rows
    except (OSError, AttributeError):
        return 80, 24  # Default fallback


def resize_image(image, max_width=None, max_height=None):
    """Resize image to fit terminal while maintaining aspect ratio"""
    if max_width is None or max_height is None:
        cols, rows = get_terminal_size()
        max_width = max_width or cols
        max_height = max_height or (rows - 2) * 2  # Unicode blocks are 2:1 ratio

    # Calculate scaling factor
    width, height = image.size
    scale = min(max_width / width, max_height / height)

    # Resize image
    new_width = int(width * scale)
    new_height = int(height * scale)

    # Ensure even height for half-block characters
    if new_height % 2 != 0:
        new_height -= 1

    return image.resize((new_width, new_height), Image.Resampling.LANCZOS)


def rgb_to_ansi(r, g, b, background=False):
    """Convert RGB to ANSI escape code"""
    if background:
        return f"\033[48;2;{r};{g};{b}m"
    else:
        return f"\033[38;2;{r};{g};{b}m"


def get_image_as_unicode_blocks(image_path, max_width=None, max_height=None, with_ansi=True):
    """
    Convert image to Unicode half-blocks (▄) for double vertical resolution
    Returns list of strings - with or without ANSI codes
    """
    # Load image
    if image_path.startswith("http"):
        # Handle URL
        response = requests.get(image_path)
        img = Image.open(BytesIO(response.content))
    else:
        # Handle local file
        img = Image.open(image_path)

    # Convert to RGB if necessary
    if img.mode != "RGB":
        img = img.convert("RGB")

    # Resize image
    img = resize_image(img, max_width, max_height)
    width, height = img.size

    pixels = img.load()

    # Process image using half-blocks for better resolution
    output = []
    for y in range(0, height, 2):  # Process 2 rows at a time
        row = ""
        for x in range(width):
            # Get top pixel
            if y < height:
                r1, g1, b1 = pixels[x, y]
            else:
                r1, g1, b1 = 0, 0, 0

            # Get bottom pixel
            if y + 1 < height:
                r2, g2, b2 = pixels[x, y + 1]
            else:
                r2, g2, b2 = 0, 0, 0

            if with_ansi:
                # Use lower half block with top color as foreground and bottom as background
                row += rgb_to_ansi(r2, g2, b2, background=False)  # Bottom pixel (foreground)
                row += rgb_to_ansi(r1, g1, b1, background=True)  # Top pixel (background)
                row += "▄"  # Lower half block character
            else:
                # Just use a simple character based on brightness for curses
                avg_brightness = (r1 + g1 + b1 + r2 + g2 + b2) // 6
                if avg_brightness > 200:
                    row += "█"
                elif avg_brightness > 150:
                    row += "▓"
                elif avg_brightness > 100:
                    row += "▒"
                elif avg_brightness > 50:
                    row += "░"
                else:
                    row += " "

        if with_ansi:
            row += "\033[0m"  # Reset colors
        output.append(row)

    return output


def get_image_as_ascii_art(image_path, max_width=None, max_height=None):
    """
    Convert image to simple ASCII art for curses compatibility
    Returns list of simple ASCII strings without color codes
    """
    # ASCII characters from dark to light
    ascii_chars = " .:-=+*#%@"

    # Load image
    if image_path.startswith("http"):
        # Handle URL
        response = requests.get(image_path)
        img = Image.open(BytesIO(response.content))
    else:
        # Handle local file
        img = Image.open(image_path)

    # Convert to grayscale
    img = img.convert("L")

    # Resize image
    if max_height:
        max_height = max_height // 2  # Characters are roughly 2:1 ratio
    img = resize_image(img, max_width, max_height)
    width, height = img.size

    pixels = img.load()

    # Process image
    output = []
    for y in range(height):
        row = ""
        for x in range(width):
            brightness = pixels[x, y]
            # Map brightness to ASCII character
            char_index = int((brightness / 255) * (len(ascii_chars) - 1))
            row += ascii_chars[char_index]
        output.append(row)

    return output


def display_image_unicode_blocks(image_path, max_width=None, max_height=None):
    """
    Display image using Unicode half-blocks (▄) for double vertical resolution
    Each character position shows two pixels vertically
    """
    output = get_image_as_unicode_blocks(image_path, max_width, max_height)
    print("\n".join(output))


def display_image_full_blocks(image_path, max_width=None, max_height=None):
    """
    Display image using full blocks (█) - simpler but lower resolution
    Each character position shows one pixel
    """
    # Load image
    if image_path.startswith("http"):
        # Handle URL
        response = requests.get(image_path)
        img = Image.open(BytesIO(response.content))
    else:
        # Handle local file
        img = Image.open(image_path)

    # Convert to RGB if necessary
    if img.mode != "RGB":
        img = img.convert("RGB")

    # Resize image (account for character aspect ratio)
    if max_height:
        max_height = max_height // 2  # Characters are roughly 2:1 ratio
    img = resize_image(img, max_width, max_height)
    width, height = img.size

    pixels = img.load()

    # Process image
    output = []
    for y in range(height):
        row = ""
        for x in range(width):
            r, g, b = pixels[x, y]
            row += rgb_to_ansi(r, g, b, background=False)
            row += "█"  # Full block character

        row += "\033[0m"  # Reset colors
        output.append(row)

    # Print the image
    print("\n".join(output))


def display_image_quarters(image_path, max_width=None, max_height=None):
    """
    Display image using quarter blocks for 2x2 pixel resolution per character
    Uses Unicode quadrant characters for maximum resolution
    """
    # Quadrant block characters
    BLOCKS = {
        (0, 0, 0, 0): " ",
        (1, 0, 0, 0): "▘",
        (0, 1, 0, 0): "▝",
        (1, 1, 0, 0): "▀",
        (0, 0, 1, 0): "▖",
        (1, 0, 1, 0): "▌",
        (0, 1, 1, 0): "▞",
        (1, 1, 1, 0): "▛",
        (0, 0, 0, 1): "▗",
        (1, 0, 0, 1): "▚",
        (0, 1, 0, 1): "▐",
        (1, 1, 0, 1): "▜",
        (0, 0, 1, 1): "▄",
        (1, 0, 1, 1): "▙",
        (0, 1, 1, 1): "▟",
        (1, 1, 1, 1): "█",
    }

    # Load image
    if image_path.startswith("http"):
        response = requests.get(image_path)
        img = Image.open(BytesIO(response.content))
    else:
        img = Image.open(image_path)

    # Convert to RGB
    if img.mode != "RGB":
        img = img.convert("RGB")

    # Resize image (each character represents 2x2 pixels)
    if max_width:
        max_width *= 2
    if max_height:
        max_height *= 2

    img = resize_image(img, max_width, max_height)
    width, height = img.size

    # Ensure dimensions are even
    if width % 2 != 0:
        width -= 1
    if height % 2 != 0:
        height -= 1

    img = img.crop((0, 0, width, height))
    pixels = img.load()

    # Process image in 2x2 blocks
    output = []
    for y in range(0, height, 2):
        row = ""
        for x in range(0, width, 2):
            # Get 2x2 pixel block colors
            colors = []
            for dy in range(2):
                for dx in range(2):
                    if y + dy < height and x + dx < width:
                        colors.append(pixels[x + dx, y + dy])
                    else:
                        colors.append((0, 0, 0))

            # Calculate average color
            avg_r = sum(c[0] for c in colors) // 4
            avg_g = sum(c[1] for c in colors) // 4
            avg_b = sum(c[2] for c in colors) // 4

            # Determine which pixels are "on" (brighter than average)
            threshold = (avg_r + avg_g + avg_b) // 3
            pattern = []
            for c in colors:
                brightness = sum(c) // 3
                pattern.append(1 if brightness > threshold else 0)

            # Get the appropriate block character
            block_char = BLOCKS.get(tuple(pattern), "█")

            # Apply average color
            row += rgb_to_ansi(avg_r, avg_g, avg_b, background=False)
            row += block_char

        row += "\033[0m"
        output.append(row)

    print("\n".join(output))


def main():
    image_path = "/Users/aaryanshsahay/Downloads/The_Strokes_-_Ist_Tis_It_US_cover.png"

    # Try different display modes
    print("Testing image display in terminal...")
    print("\n" + "=" * 60)
    print("Using half-blocks (best quality):")
    print("=" * 60 + "\n")

    try:
        display_image_unicode_blocks(image_path, max_width=60, max_height=30)
    except Exception as e:
        print(f"Error with half-blocks: {e}")
        # Try with a different image if the first one fails
        try:
            # Create a simple test image
            from PIL import Image

            # Create a gradient test image
            img = Image.new("RGB", (100, 100))
            pixels = img.load()
            for y in range(100):
                for x in range(100):
                    r = int(255 * x / 100)
                    g = int(255 * y / 100)
                    b = 128
                    pixels[x, y] = (r, g, b)

            # Save and display
            test_path = "/tmp/test_gradient.png"
            img.save(test_path)

            print("\nDisplaying test gradient image:")
            display_image_unicode_blocks(test_path, max_width=40, max_height=20)

        except Exception as e2:
            print(f"Error creating test image: {e2}", file=sys.stderr)


def get_user_playlists(spotify_client):
    playlists = []
    results = spotify_client.current_user_playlists(limit=50)

    while results:
        playlists.extend(results["items"])
        if results["next"]:
            results = spotify_client.next(results)
        else:
            break

    return playlists


def display_playlists(playlists):
    if not playlists:
        print("\nNo playlists found.")
        return

    print(f"\n{'=' * 60}")
    print(f"Found {len(playlists)} playlist(s)")
    print(f"{'=' * 60}\n")

    for idx, playlist in enumerate(playlists, 1):
        print(f"Playlist #{idx}")
        print(f"  Name: {playlist['name']}")
        print(f"  Tracks: {playlist['tracks']['total']}")
        print(f"  Public: {'Yes' if playlist['public'] else 'No'}")
        print(f"  Owner: {playlist['owner']['display_name']}")

        if playlist.get("description"):
            description = playlist["description"][:100]
            if len(playlist["description"]) > 100:
                description += "..."
            print(f"  Description: {description}")

        if playlist.get("collaborative"):
            print(f"  Collaborative: Yes")

        print(f"  URL: {playlist['external_urls']['spotify']}")
        print("-" * 40)


def format_duration(ms):
    """Convert milliseconds to MM:SS format"""
    seconds = ms // 1000
    minutes = seconds // 60
    seconds = seconds % 60
    return f"{minutes}:{seconds:02d}"


def get_current_playback(spotify):
    """Get currently playing track information"""
    try:
        playback = spotify.current_playback()
        if playback and playback.get("item"):
            track = playback["item"]
            artists = ", ".join([artist["name"] for artist in track.get("artists", [])])

            # Get album art URL (smallest size for terminal)
            album_images = track.get("album", {}).get("images", [])
            album_art_url = album_images[-1]["url"] if album_images else None  # Get smallest image

            return {
                "name": track.get("name", "Unknown"),
                "artists": artists,
                "album": track.get("album", {}).get("name", "Unknown"),
                "progress": playback.get("progress_ms", 0),
                "duration": track.get("duration_ms", 0),
                "is_playing": playback.get("is_playing", False),
                "album_art_url": album_art_url,
                "track_id": track.get("id", ""),  # To check if track changed
            }
    except:
        pass
    return None


def main(stdscr):
    # Turn off cursor
    curses.curs_set(0)

    # Initialize color pairs
    curses.start_color()
    curses.use_default_colors()  # Use terminal's default colors (usually black background)
    curses.init_pair(1, curses.COLOR_WHITE, -1)  # Normal text on default background
    curses.init_pair(2, curses.COLOR_CYAN, -1)  # Headers on default background
    curses.init_pair(3, curses.COLOR_YELLOW, -1)  # Info text on default background

    # Authenticate
    stdscr.clear()
    stdscr.addstr(0, 0, "Authenticating with Spotify...", curses.color_pair(3))
    stdscr.refresh()

    try:
        spotify = get_spotify_client()
        user = spotify.current_user()
        username = user["display_name"]
    except Exception as e:
        stdscr.clear()
        stdscr.addstr(0, 0, f"Authentication failed: {str(e)}")
        stdscr.addstr(2, 0, "Press any key to exit...")
        stdscr.refresh()
        stdscr.getch()
        return

    # Load playlists
    stdscr.clear()
    stdscr.addstr(0, 0, f"Loading playlists for {username}...", curses.color_pair(3))
    stdscr.refresh()

    try:
        playlists = get_user_playlists(spotify)
    except Exception as e:
        stdscr.clear()
        stdscr.addstr(0, 0, f"Failed to load playlists: {str(e)}")
        stdscr.addstr(2, 0, "Press any key to exit...")
        stdscr.refresh()
        stdscr.getch()
        return

    if not playlists:
        stdscr.clear()
        stdscr.addstr(0, 0, "No playlists found!")
        stdscr.addstr(2, 0, "Press any key to exit...")
        stdscr.refresh()
        stdscr.getch()
        return

    # Main navigation variables
    current_row = 0
    scroll_offset = 0
    mode = "playlists"  # Can be "playlists" or "tracks"
    selected_playlist = None
    tracks = []

    # Playback caching
    cached_playback = None
    last_playback_fetch = 0
    playback_fetch_interval = 5  # Fetch every 5 seconds
    cached_album_art = []  # Cache rendered album art
    last_track_id = None

    def draw_now_playing(h, w):
        """Draw currently playing track on the right side"""
        nonlocal cached_playback, last_playback_fetch, cached_album_art, last_track_id

        # Only fetch new data if cache is expired
        current_time = time.time()
        if current_time - last_playback_fetch > playback_fetch_interval:
            cached_playback = get_current_playback(spotify)
            last_playback_fetch = current_time

            # Check if track changed to reload album art
            if cached_playback and cached_playback.get("track_id") != last_track_id:
                last_track_id = cached_playback.get("track_id")
                # Load album art
                if cached_playback.get("album_art_url"):
                    try:
                        # Get album art with Unicode blocks (no ANSI for curses compatibility)
                        cached_album_art = get_image_as_unicode_blocks(
                            cached_playback["album_art_url"],
                            max_width=30,
                            max_height=25,
                            with_ansi=False,  # Use grayscale blocks for curses
                        )
                    except Exception:
                        cached_album_art = []

        current = cached_playback
        if not current:
            return

        # Calculate right panel dimensions
        right_panel_width = min(50, w // 2)  # Increased width for album art
        x_start = w - right_panel_width - 1

        # Draw border and header
        stdscr.addstr(0, x_start - 1, "│", curses.color_pair(2))
        header = "NOW PLAYING"
        stdscr.addstr(
            0, x_start + (right_panel_width - len(header)) // 2, header, curses.color_pair(2)
        )

        # Draw separator
        for i in range(1, h - 1):
            try:
                stdscr.addstr(i, x_start - 1, "│", curses.color_pair(2))
            except:
                pass

        # Draw album art if available
        y_pos = 2
        if cached_album_art:
            max_art_lines = min(12, len(cached_album_art))  # Limit height
            for i, line in enumerate(cached_album_art[:max_art_lines]):
                if y_pos + i < h - 10:  # Leave room for track info
                    try:
                        # Center the album art
                        art_width = min(25, len(line))
                        art_x = x_start + max(0, (right_panel_width - art_width) // 2)
                        # Display Unicode block art
                        stdscr.addstr(y_pos + i, art_x, line[:art_width], curses.color_pair(1))
                    except:
                        pass
            y_pos += max_art_lines + 1

        # Status (Playing/Paused)
        status = "▶ Playing" if current["is_playing"] else "⏸ Paused"
        try:
            stdscr.addstr(
                y_pos,
                x_start,
                status[: right_panel_width - 1],
                curses.color_pair(2) if current["is_playing"] else curses.color_pair(3),
            )
        except:
            pass
        y_pos += 1

        # Track name
        track_name = current["name"][: right_panel_width - 1]
        try:
            stdscr.addstr(y_pos, x_start, track_name, curses.color_pair(1))
        except:
            pass
        y_pos += 1

        # Artists
        artists = current["artists"][: right_panel_width - 1]
        try:
            stdscr.addstr(y_pos, x_start, artists, curses.color_pair(3))
        except:
            pass
        y_pos += 1

        # Album
        album = current["album"][: right_panel_width - 1]
        try:
            stdscr.addstr(y_pos, x_start, album, curses.color_pair(3))
        except:
            pass
        y_pos += 2

        # Progress bar with estimated progress
        # Calculate estimated progress based on time elapsed since last fetch
        time_elapsed = (time.time() - last_playback_fetch) * 1000  # Convert to ms
        estimated_progress = current["progress"] + (time_elapsed if current["is_playing"] else 0)
        estimated_progress = min(estimated_progress, current["duration"])  # Don't exceed duration

        progress_pct = estimated_progress / current["duration"] if current["duration"] > 0 else 0
        bar_width = right_panel_width - 2
        filled = int(bar_width * progress_pct)
        empty = bar_width - filled
        progress_bar = "█" * filled + "░" * empty
        try:
            stdscr.addstr(y_pos, x_start, progress_bar[:bar_width], curses.color_pair(2))
        except:
            pass
        y_pos += 1

        # Time
        time_str = (
            f"{format_duration(int(estimated_progress))} / {format_duration(current['duration'])}"
        )
        try:
            stdscr.addstr(
                y_pos,
                x_start + (right_panel_width - len(time_str)) // 2,
                time_str[: right_panel_width - 1],
                curses.color_pair(3),
            )
        except:
            pass

    def print_playlists(selected_row_idx, scroll_offset):
        stdscr.clear()
        h, w = stdscr.getmaxyx()

        # Header
        header = f"SPOTIFY PLAYLISTS ({len(playlists)} total)"
        stdscr.addstr(0, 0, header, curses.color_pair(2))
        stdscr.addstr(1, 0, "↑/↓: Navigate | Enter: View Tracks | Q: Quit", curses.color_pair(3))
        stdscr.addstr(2, 0, "-" * min(w - 1, 80))

        # Calculate visible range
        visible_height = h - 5  # Leave room for header and footer

        # Adjust scroll offset if needed
        if selected_row_idx < scroll_offset:
            scroll_offset = selected_row_idx
        elif selected_row_idx >= scroll_offset + visible_height:
            scroll_offset = selected_row_idx - visible_height + 1

        # Display playlists
        for i in range(visible_height):
            idx = scroll_offset + i
            if idx >= len(playlists):
                break

            playlist = playlists[idx]
            name = playlist["name"][:50]
            track_count = playlist.get("tracks", {}).get("total", 0)

            # Add '>' indicator for selected item
            if idx == selected_row_idx:
                indicator = "> "
            else:
                indicator = "  "

            display_text = f"{indicator}{idx + 1:3}. {name:<50} [{track_count} tracks]"

            if len(display_text) > w - 1:
                display_text = display_text[: w - 4] + "..."

            y_pos = i + 3
            stdscr.addstr(y_pos, 0, display_text, curses.color_pair(1))

        # Footer with position info
        if len(playlists) > visible_height:
            footer = f"[{selected_row_idx + 1}/{len(playlists)}]"
            stdscr.addstr(h - 1, 0, footer, curses.color_pair(3))

        # Draw now playing panel
        draw_now_playing(h, w)

        stdscr.refresh()
        return scroll_offset

    def print_tracks(selected_row_idx, scroll_offset):
        stdscr.clear()
        h, w = stdscr.getmaxyx()

        # Header
        playlist_name = selected_playlist["name"][: w - 20] if selected_playlist else "Unknown"
        header = f"TRACKS: {playlist_name} ({len(tracks)} tracks)"
        stdscr.addstr(0, 0, header, curses.color_pair(2))
        stdscr.addstr(
            1, 0, "↑/↓: Navigate | Backspace: Back to Playlists | Q: Quit", curses.color_pair(3)
        )
        stdscr.addstr(2, 0, "-" * min(w - 1, 80))

        # Calculate visible range
        visible_height = h - 5  # Leave room for header and footer

        # Adjust scroll offset if needed
        if selected_row_idx < scroll_offset:
            scroll_offset = selected_row_idx
        elif selected_row_idx >= scroll_offset + visible_height:
            scroll_offset = selected_row_idx - visible_height + 1

        # Display tracks
        for i in range(visible_height):
            idx = scroll_offset + i
            if idx >= len(tracks):
                break

            track = tracks[idx]
            name = track["name"][:35]
            artists = track["artists"][:25]
            duration = track["duration"]

            # Add '>' indicator for selected item
            if idx == selected_row_idx:
                indicator = "> "
            else:
                indicator = "  "

            display_text = f"{indicator}{idx + 1:3}. {name:<35} - {artists:<25} [{duration}]"

            if len(display_text) > w - 1:
                display_text = display_text[: w - 4] + "..."

            y_pos = i + 3
            stdscr.addstr(y_pos, 0, display_text, curses.color_pair(1))

        # Footer with position info
        if len(tracks) > visible_height:
            footer = f"[{selected_row_idx + 1}/{len(tracks)}]"
            stdscr.addstr(h - 1, 0, footer, curses.color_pair(3))

        # Draw now playing panel
        draw_now_playing(h, w)

        stdscr.refresh()
        return scroll_offset

    # Initial display
    scroll_offset = print_playlists(current_row, scroll_offset)

    # Set non-blocking input with timeout (refreshes every 100ms for responsive navigation)
    stdscr.timeout(100)

    # Main loop
    while True:
        key = stdscr.getch()

        # If no key pressed (timeout), just refresh the display
        if key == -1:
            if mode == "playlists":
                scroll_offset = print_playlists(current_row, scroll_offset)
            elif mode == "tracks":
                scroll_offset = print_tracks(current_row, scroll_offset)
            continue

        if key == ord("q") or key == ord("Q"):
            break

        if mode == "playlists":
            if key == curses.KEY_UP and current_row > 0:
                current_row -= 1
                scroll_offset = print_playlists(current_row, scroll_offset)
            elif key == curses.KEY_DOWN and current_row < len(playlists) - 1:
                current_row += 1
                scroll_offset = print_playlists(current_row, scroll_offset)
            elif key == curses.KEY_ENTER or key in [10, 13]:
                # Load tracks for selected playlist
                selected_playlist = playlists[current_row]

                stdscr.clear()
                stdscr.addstr(
                    0,
                    0,
                    f"Loading tracks for '{selected_playlist['name']}'...",
                    curses.color_pair(3),
                )
                stdscr.refresh()

                try:
                    # Get all tracks from the playlist
                    results = spotify.playlist_tracks(selected_playlist["id"])
                    raw_tracks = results["items"]

                    # Handle pagination
                    while results["next"]:
                        results = spotify.next(results)
                        raw_tracks.extend(results["items"])

                    # Extract track information
                    tracks = []
                    for item in raw_tracks:
                        track_info = item.get("track")
                        if track_info:
                            artists = ", ".join(
                                [artist["name"] for artist in track_info.get("artists", [])]
                            )
                            tracks.append(
                                {
                                    "name": track_info.get("name", "Unknown"),
                                    "artists": artists,
                                    "album": track_info.get("album", {}).get("name", "Unknown"),
                                    "duration": format_duration(track_info.get("duration_ms", 0)),
                                }
                            )

                    if tracks:
                        mode = "tracks"
                        current_row = 0
                        scroll_offset = 0
                        scroll_offset = print_tracks(current_row, scroll_offset)
                    else:
                        stdscr.clear()
                        stdscr.addstr(
                            0, 0, "No tracks found in this playlist!", curses.color_pair(3)
                        )
                        stdscr.addstr(2, 0, "Press any key to go back...")
                        stdscr.refresh()
                        stdscr.getch()
                        scroll_offset = print_playlists(current_row, scroll_offset)

                except Exception as e:
                    stdscr.clear()
                    stdscr.addstr(0, 0, f"Error loading tracks: {str(e)}", curses.color_pair(3))
                    stdscr.addstr(2, 0, "Press any key to go back...")
                    stdscr.refresh()
                    stdscr.getch()
                    scroll_offset = print_playlists(current_row, scroll_offset)

        elif mode == "tracks":
            if key == curses.KEY_UP and current_row > 0:
                current_row -= 1
                scroll_offset = print_tracks(current_row, scroll_offset)
            elif key == curses.KEY_DOWN and current_row < len(tracks) - 1:
                current_row += 1
                scroll_offset = print_tracks(current_row, scroll_offset)
            elif key == curses.KEY_BACKSPACE or key == 127 or key == 8:
                # Go back to playlists
                mode = "playlists"
                current_row = 0
                scroll_offset = 0
                scroll_offset = print_playlists(current_row, scroll_offset)


if __name__ == "__main__":
    try:
        curses.wrapper(main)
        print("\nThanks for using Spotify Terminal Browser!")
    except KeyboardInterrupt:
        print("\n\nInterrupted by user")
    except Exception as e:
        print(f"\n\nError: {str(e)}")
        print("Note: This app requires a terminal that supports curses.")
        print("Try running it directly in your terminal, not through an IDE.")
