import board
from adafruit_pybadger import pybadger
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
from adafruit_display_shapes.rect import Rect
from adafruit_pcf8523.pcf8523 import PCF8523
import sdcardio
import storage
import time
import displayio
import gc
import analogio

# Constants
MIN_COUNTDOWN = 10  # Minimum countdown time in seconds
MAX_COUNTDOWN = 5940  # Maximum countdown time in seconds (99 minutes)
DEFAULT_COUNTDOWN = 600  # Default countdown time in seconds (5 minutes)
NUM_NEOPIXELS = 5  # Number of NeoPixels on the PyBadge
STOPWATCH_COLOR_INTERVAL = 10  # Change color every 10 seconds in stopwatch mode

# Countdown color progression thresholds
COUNTDOWN_TWO_THIRDS = 0.67  # Change to yellow at 2/3 time remaining
COUNTDOWN_ONE_THIRD = 0.33  # Change to magenta at 1/3 time remaining

# Color constants
RESET_COLOR = (255, 0, 0)  # Red color for reset state
COLOR_OFF = (0, 0, 0)  # Off/Black
COLOR_GREEN = (0, 255, 0)  # Green
COLOR_BLUE = (0, 0, 255)  # Blue
COLOR_YELLOW = (255, 255, 0)  # Yellow
COLOR_MAGENTA = (255, 0, 255)  # Magenta

# Mode names
MODE_NAMES = ('Stopwatch', 'Countdown', 'Timer', 'Clock')

def convert_to_12hour(hour):
    """Convert 24-hour time to 12-hour format with AM/PM"""
    if hour == 0:
        return 12, "AM"
    elif hour < 12:
        return hour, "AM"
    elif hour == 12:
        return 12, "PM"
    else:
        return hour - 12, "PM"

def play_song():
    for i in range(len(tns)):
        print("Playing tone:", tns[i])
        pybadger.play_tone(tns[i], 0.1)

def adjust_countdown_time(amount):
    global countdown_duration
    pybadger.play_file(wvs[1])
    countdown_duration += amount
    # Clamp between minimum and maximum
    if countdown_duration < MIN_COUNTDOWN:
        countdown_duration = MIN_COUNTDOWN
    elif countdown_duration > MAX_COUNTDOWN:
        countdown_duration = MAX_COUNTDOWN
    print(f"Countdown set to {countdown_duration} seconds")

def time_display():
    t = rtc_pcf.datetime
    hour_12, am_pm = convert_to_12hour(t.tm_hour)
    return f"{hour_12}:{t.tm_min:02d} {am_pm}"

def rotate_neopixel(current_time, color, interval):
    """Rotate NeoPixel animation - non-blocking with configurable interval"""
    global current_pixel, last_pixel_update
    if current_time - last_pixel_update >= interval:
        # Turn off current pixel
        pybadger.pixels[current_pixel] = COLOR_OFF
        # Move to next pixel
        current_pixel += 1
        if current_pixel > NUM_NEOPIXELS - 1:
            current_pixel = 0
        # Turn on new pixel
        pybadger.pixels[current_pixel] = color
        last_pixel_update = current_time

display = board.DISPLAY

i2c = board.I2C()
rtc_pcf = PCF8523(i2c)

spi = board.SPI()
cs = board.D10

# Try to mount SD card with error handling
try:
    sdcard = sdcardio.SDCard(spi, cs)
    vfs = storage.VfsFat(sdcard)
    storage.mount(vfs, "/sd")
    sd_available = True
    print("SD card mounted successfully")
except Exception as e:
    sd_available = False
    print(f"SD card not available: {e}")

t = rtc_pcf.datetime

# Log startup time to SD card if available
if sd_available:
    try:
        on_time = f"{t.tm_year}{t.tm_mon:02}{t.tm_mday:02}_{t.tm_hour:02}{t.tm_min:02}{t.tm_sec:02}"
        print(on_time)
        with open("/sd/starts.csv", "a") as f:
            f.write(on_time + "\r\n")
    except Exception as e:
        print(f"Failed to write to SD card: {e}")

print(f"The date is {t.tm_mon}/{t.tm_mday}/{t.tm_year}")
print(f"The time is {t.tm_hour}:{t.tm_min:02}:{t.tm_sec:02}")

pybadger.pixels.brightness = 0.1
pybadger.pixels.fill(0)
print(pybadger.light)

wvs = [                   # This is a list of wav files available - to use a file refer to the list index i.e. wvs[0] for the first file
    "/wav/click3.wav",
    "/wav/pop2.wav",
    "/wav/blip2.wav",
    "/wav/snare_01.wav",
    "/wav/beep.wav",
    "/wav/confirmation_001.wav",
    "/wav/confirmation_002.wav",
    "/wav/confirmation_003.wav",
    "/wav/confirmation_004.wav",
    "/wav/threeTone1.wav",
    "/wav/threeTone2.wav",
    "/wav/twoTone1.wav",
    "/wav/twoTone2.wav",
    "/wav/crash_01.wav",
    "/wav/chimes.wav",
    "/wav/wand.wav",
]

tns = [      # This is a little ditty of notes to play when the countdown timer expires - know the tune?
    659.3, 622.3, 659.3, 622.3, 659.3, 
    493.9, 587.3, 523.3, 440.0, 440, 440, 440, 440, 0, 0, 0, 494, 494, 494, 494, 494, 0, 0, 0, 523, 523, 523, 523, 523
]

font8 = bitmap_font.load_font("/fonts/Calibri-12.pcf")
font15 = bitmap_font.load_font("/fonts/Calibri-15.pcf")
font12 = bitmap_font.load_font("/fonts/ComicSansMS-10.pcf")
font38 = bitmap_font.load_font("/fonts/Calibri-38.pcf")

main_group = displayio.Group()

background = Rect(0, 0, display.width, display.height, fill=0x000088)

# Display layout positions
LABEL_X_OFFSET = 5
TITLE_Y_POS = 5
MAIN_DISPLAY_Y_POS = 25
STATUS_Y_POS = 65
INFO_Y_POS = 100

bv_label = label.Label(font12, anchored_position=(display.width - 5, display.height - 5), anchor_point=(1.0, 1.0), text="", color=0x9999FF)
line_1 = label.Label(font15, anchored_position=(LABEL_X_OFFSET, TITLE_Y_POS), anchor_point=(0.0, 0.0), text="", color=0xFFAA33)
line_1a = label.Label(font38, anchored_position=(80, MAIN_DISPLAY_Y_POS), anchor_point=(0.5, 0.0), text="", color=0xFFFF00)
line_2 = label.Label(font15, anchored_position=(LABEL_X_OFFSET, STATUS_Y_POS), anchor_point=(0.0, 0.0), text="", color=0xFFFFFF)
line_3 = label.Label(font12, anchored_position=(LABEL_X_OFFSET, INFO_Y_POS), anchor_point=(0.0, 0.0), text="", color=0x00FF00)
line_clk = label.Label(font8, anchored_position=(display.width - 3, 3), anchor_point=(1.0, 0.0), text="", color=0xFFFFFF)

main_group.append(background)
main_group.append(line_1)
main_group.append(line_1a)
main_group.append(line_2)
main_group.append(line_3)
main_group.append(bv_label)
main_group.append(line_clk)

display.root_group = main_group

# Mode: 0 = Stopwatch, 1 = Countdown, 2 = Clock, 3 = Timer
mode = 0

# Stopwatch variables
stopwatch_running = False
stopwatch_start_time = 0
stopwatch_elapsed = 0
stopwatch_lap_mode = False  # True when displaying lap time
stopwatch_lap_time = 0  # The frozen lap time to display

# Timer variables
timer_running = False
timer_start_time = 0
timer_elapsed = 0
timer_clock_start_hour = 0
timer_clock_start_min = 0

# Countdown variables
countdown_running = False
countdown_paused = False
countdown_start_time = 0
countdown_pause_elapsed = 0
countdown_duration = DEFAULT_COUNTDOWN
countdown_remaining = 0
last_minute_notified = -1  # Track last minute we played sound for

# Button state tracking for debouncing
start_pressed = False
select_pressed = False
a_pressed = False
b_pressed = False
up_pressed = False
down_pressed = False
left_pressed = False
right_pressed = False

# Color cycling for NeoPixels
colors = [(0, 255, 0), (255, 0, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), (0, 255, 255)]
# Green, Red, Blue, Yellow, Magenta, Cyan
color_index = 0

# Display update tracking - only update when values change
prev_stopwatch_time = ""
prev_stopwatch_status = ""
prev_countdown_time = ""
prev_countdown_status = ""
prev_countdown_set = ""
prev_mode = -1  # Track mode changes

# NeoPixel animation tracking
current_pixel = 0
pcolor = colors[0]
last_pixel_update = 0  # Timestamp of last NeoPixel update
PIXEL_UPDATE_INTERVAL = 1.0  # Update NeoPixels every 1 second in countdown mode

# Memory monitoring
last_gc_time = 0
GC_INTERVAL = 5.0  # Run garbage collection every 5 seconds
last_mem_update = 0
MEM_UPDATE_INTERVAL = 2.0  # Update memory display every 2 seconds

# Clock display monitoring
last_clk_update = 0
CLK_UPDATE_INTERVAL = 30.0  # Update clock display every 30 seconds

# Battery voltage monitoring
battery_voltage_cached = 0.0
last_battery_update = 0
BATTERY_UPDATE_INTERVAL = 10.0  # Update battery voltage every 10 seconds

# Clock mode optimization
prev_clock_minute = -1  # Track last displayed minute in clock mode

# Initial garbage collection
gc.collect()
print(f"Initial free memory: {gc.mem_free()} bytes")

# Set up battery voltage monitoring on A6
battery_pin = analogio.AnalogIn(board.A6)

def get_battery_voltage():
    """Read the battery voltage from A6 (connected to voltage divider)"""
    # Read the raw analog value (0-65535)
    raw_value = battery_pin.value
    # Convert to voltage (reference voltage is typically 3.3V)
    voltage = (raw_value / 65535.0) * 3.3
    # If using a voltage divider, multiply by the divider ratio
    # For PyBadge, the divider is typically 2:1, so multiply by 2
    battery_voltage = voltage * 2
    return battery_voltage

# Initialize and print battery voltage
battery_voltage_cached = get_battery_voltage()
print(f"Battery voltage: {battery_voltage_cached:.2f}V")

# Loop forever so you can enjoy your text
while True:
    # Get current time once at start of loop
    current_time = time.monotonic()
    
    # SELECT button - toggle between modes with debouncing
    if pybadger.button.select:
        bv_label.text = f"{battery_voltage_cached:.2f}v"
        if not select_pressed:
            select_pressed = True
            # Only allow mode switch when nothing is running
            if not stopwatch_running and not countdown_running and not timer_running:
                pybadger.play_file(wvs[8])
                mode = (mode + 1) % 4  # Cycle through 0, 1, 2, 3
                print(f"Mode: {MODE_NAMES[mode]}")
    else:
        select_pressed = False
    
    if mode == 0:  # STOPWATCH MODE
        # Update mode title if mode changed
        if mode != prev_mode:
            line_1.text = "STOPWATCH"
            line_3.text = "START=Run/Stop - B for Lap\nSELECT=Mode   A=Reset"
            line_1a.text = "00:00.0"
            line_2.text = "STOPPED"
            prev_stopwatch_time = "00:00.0"
            prev_stopwatch_status = "STOPPED"
            prev_mode = mode
        
        # START button - toggle start/stop
        if pybadger.button.start:
            if not start_pressed:
                start_pressed = True
                pybadger.play_file(wvs[2])
                if not stopwatch_running:
                    stopwatch_running = True
                    stopwatch_start_time = time.monotonic() - stopwatch_elapsed
                    print("Stopwatch started")
                else:
                    stopwatch_running = False
                    stopwatch_elapsed = time.monotonic() - stopwatch_start_time
                    print("Stopwatch stopped")
        else:
            start_pressed = False
        
        # A button - reset
        if pybadger.button.a:
            if not a_pressed and not stopwatch_running:  # Only allow reset when stopped
                a_pressed = True
                pybadger.play_file(wvs[9])
                stopwatch_running = False
                stopwatch_elapsed = 0
                stopwatch_start_time = 0
                stopwatch_lap_mode = False
                stopwatch_lap_time = 0
                print("Stopwatch reset")
                pcolor = RESET_COLOR
        else:
            a_pressed = False
        
        # B button - lap timing (only when running)
        if pybadger.button.b:
            if not b_pressed and stopwatch_running:
                b_pressed = True
                pybadger.play_file(wvs[7])
                if not stopwatch_lap_mode:
                    # Freeze display at current time
                    stopwatch_lap_time = time.monotonic() - stopwatch_start_time
                    stopwatch_lap_mode = True
                    print(f"Lap time: {stopwatch_lap_time:.1f}s")
                else:
                    # Resume showing current time
                    stopwatch_lap_mode = False
                    print("Lap resumed")
        else:
            b_pressed = False
        
        # Update elapsed time if running
        if stopwatch_running:
            stopwatch_elapsed = time.monotonic() - stopwatch_start_time
            # Change color based on interval
            color_index = int(stopwatch_elapsed // STOPWATCH_COLOR_INTERVAL) % len(colors)
            pcolor = colors[color_index]
        
        # Display stopwatch time
        # Use lap time if in lap mode, otherwise use current elapsed time
        display_time = stopwatch_lap_time if stopwatch_lap_mode else stopwatch_elapsed
        tenths = int(display_time * 10)
        seconds = tenths // 10
        remaining_tenths = tenths % 10
        minutes = seconds // 60
        remaining_seconds = seconds % 60
        
        # Only update display if time changed
        display_time_str = f"{minutes:02d}:{remaining_seconds:02d}.{remaining_tenths}"
        if display_time_str != prev_stopwatch_time:
            line_1a.text = display_time_str
            prev_stopwatch_time = display_time_str
        
        # Only update status if changed
        if stopwatch_lap_mode:
            current_status = "LAP - B to RESUME"
        else:
            current_status = "RUNNING" if stopwatch_running else "STOPPED"
        if current_status != prev_stopwatch_status:
            line_2.text = current_status
            prev_stopwatch_status = current_status
        
        # Animate NeoPixels while running (non-blocking)
        if stopwatch_running:
            rotate_neopixel(current_time, pcolor, 0.1)
        
        # Small sleep to prevent tight looping
        time.sleep(0.01)
    
    elif mode == 1:  # COUNTDOWN MODE
        # Update mode title if mode changed
        if mode != prev_mode:
            line_1.text = "COUNTDOWN"
            # Display countdown in MM:SS format
            total_seconds = int(countdown_duration)
            minutes = total_seconds // 60
            seconds = total_seconds % 60
            line_1a.text = f"{minutes:02d}:{seconds:02d}"
            line_2.text = "STOPPED\nUP/DOWN to set time"
            prev_countdown_time = f"{minutes:02d}:{seconds:02d}"
            prev_countdown_status = "STOPPED\nUP/DOWN to set time"
            prev_mode = mode
        
        # UP button - increase countdown time (when not running)
        if pybadger.button.up:
            if not up_pressed and not countdown_running:
                up_pressed = True
                adjust_countdown_time(60)  # Add 1 minute
        else:
            up_pressed = False
        
        # DOWN button - decrease countdown time (when not running)
        if pybadger.button.down:
            if not down_pressed and not countdown_running:
                down_pressed = True
                adjust_countdown_time(-60)  # Subtract 1 minute
        else:
            down_pressed = False
        
        # RIGHT button - increase countdown time by 10 seconds (when not running)
        if pybadger.button.right:
            if not right_pressed and not countdown_running:
                right_pressed = True
                adjust_countdown_time(10)  # Add 10 seconds
        else:
            right_pressed = False
        
        # LEFT button - decrease countdown time by 10 seconds (when not running)
        if pybadger.button.left:
            if not left_pressed and not countdown_running:
                left_pressed = True
                adjust_countdown_time(-10)  # Subtract 10 seconds
        else:
            left_pressed = False
        
        # B button - reset countdown time to default (when not running)
        if pybadger.button.b:
            if not b_pressed and not countdown_running:
                b_pressed = True
                pybadger.play_file(wvs[4])
                countdown_duration = DEFAULT_COUNTDOWN
                print(f"Countdown reset to {DEFAULT_COUNTDOWN // 60}:00")
        else:
            b_pressed = False
        
        # START button - toggle start/pause countdown
        if pybadger.button.start:
            if not start_pressed:
                start_pressed = True
                pybadger.play_file(wvs[5])
                if not countdown_running:
                    # Start or resume countdown
                    countdown_running = True
                    countdown_paused = False
                    countdown_start_time = time.monotonic() - countdown_pause_elapsed
                    print("Countdown started")
                elif countdown_paused:
                    # Resume from pause
                    countdown_paused = False
                    countdown_start_time = time.monotonic() - countdown_pause_elapsed
                    print("Countdown resumed")
                else:
                    # Pause the countdown
                    countdown_paused = True
                    countdown_pause_elapsed = time.monotonic() - countdown_start_time
                    print("Countdown paused")
        else:
            start_pressed = False
        
        # A button - reset countdown (only when stopped or paused)
        if pybadger.button.a and (not countdown_running or countdown_paused):
            if not a_pressed:
                a_pressed = True
                pybadger.play_file(wvs[6])
                countdown_running = False
                countdown_paused = False
                countdown_start_time = 0
                countdown_pause_elapsed = 0
                last_minute_notified = -1
                print("Countdown reset")
                pcolor = RESET_COLOR
        else:
            a_pressed = False
        
        # Update countdown
        if countdown_running and not countdown_paused:
            elapsed = time.monotonic() - countdown_start_time
            countdown_remaining = countdown_duration - elapsed
            
            # Check for even minute milestones
            minutes_remaining = int(countdown_remaining // 60)
            if minutes_remaining > 0 and minutes_remaining % 2 == 0 and minutes_remaining != last_minute_notified:
                pybadger.play_file(wvs[12])
                last_minute_notified = minutes_remaining
                print(f"Even minute reached: {minutes_remaining}")
            
            if countdown_remaining <= 0:
                countdown_remaining = 0
                countdown_running = False
                countdown_paused = False
                countdown_pause_elapsed = 0
                print("Countdown finished!")
                # Play song 3 times with 2 second pauses, allowing cancellation
                for i in range(3):
                    play_song()
                    if i < 2:  # Don't pause after the last song
                        # Check for button press to cancel alarm
                        cancel_start = time.monotonic()
                        while time.monotonic() - cancel_start < 2:
                            if pybadger.button.a or pybadger.button.start or pybadger.button.select:
                                print("Alarm cancelled")
                                break
                            time.sleep(0.05)
                        else:
                            continue
                        # If we broke out of while loop, break out of for loop too
                        break
                pcolor = RESET_COLOR  # Red when done
            else:
                # Color based on time remaining
                if countdown_remaining <= 60:  # Last minute
                    pcolor = RESET_COLOR  # Red
                elif countdown_remaining <= countdown_duration * COUNTDOWN_ONE_THIRD:
                    pcolor = COLOR_MAGENTA
                elif countdown_remaining <= countdown_duration * COUNTDOWN_TWO_THIRDS:
                    pcolor = COLOR_YELLOW
                else:
                    pcolor = COLOR_GREEN
        elif countdown_paused:
            # When paused, set color to blue and preserve remaining time
            pcolor = COLOR_BLUE
            countdown_remaining = countdown_duration - countdown_pause_elapsed
        else:
            countdown_remaining = countdown_duration
        
        # Display countdown time
        total_seconds = int(countdown_remaining)
        minutes = total_seconds // 60
        seconds = total_seconds % 60
        
        # Only update display if time changed
        display_time_str = f"{minutes:02d}:{seconds:02d}"
        if display_time_str != prev_countdown_time:
            line_1a.text = display_time_str
            prev_countdown_time = display_time_str
        
        # Only update status if changed
        if countdown_paused:
            current_status = "PAUSED"
        elif countdown_running:
            current_status = "RUNNING"
        else:
            current_status = "STOPPED\nUP/DOWN to set time"
        
        if current_status != prev_countdown_status:
            line_2.text = current_status
            prev_countdown_status = current_status
        
        # Only update set time if changed
        set_minutes = countdown_duration // 60
        set_seconds = countdown_duration % 60
        current_set = f"Set: {set_minutes:02d}:{set_seconds:02d}"
        if current_set != prev_countdown_set:
            line_3.text = current_set
            prev_countdown_set = current_set
        
        # Animate NeoPixels while running (non-blocking, updates every second)
        if countdown_running and not countdown_paused:
            rotate_neopixel(current_time, pcolor, PIXEL_UPDATE_INTERVAL)
        elif countdown_paused:
            # Keep current pixel lit blue when paused
            if current_pixel > NUM_NEOPIXELS - 1:
                current_pixel = NUM_NEOPIXELS - 1
            pybadger.pixels[current_pixel] = pcolor  # Light up blue
        
        # Small sleep to prevent tight looping
        time.sleep(0.01)
    
    elif mode == 2:  # TIMER MODE
        # Update mode title if mode changed
        if mode != prev_mode:
            line_1.text = "TIMER"
            line_3.text = "START=Run/Stop   A=Reset\nSELECT=Mode"
            line_1a.text = "00:00"
            line_2.text = "Ready"
            prev_mode = mode
        
        # START button - toggle start/stop
        if pybadger.button.start:
            if not start_pressed:
                start_pressed = True
                pybadger.play_file(wvs[2])
                if not timer_running:
                    timer_running = True
                    timer_start_time = time.monotonic() - timer_elapsed
                    # Capture the current clock time only when starting from zero
                    if timer_elapsed == 0:
                        t = rtc_pcf.datetime
                        timer_clock_start_hour = t.tm_hour
                        timer_clock_start_min = t.tm_min
                        print(f"Timer started at {timer_clock_start_hour}:{timer_clock_start_min:02d}")
                    else:
                        print("Timer resumed")
                else:
                    timer_running = False
                    timer_elapsed = time.monotonic() - timer_start_time
                    print("Timer stopped")
        else:
            start_pressed = False
        
        # A button - reset
        if pybadger.button.a:
            if not a_pressed and not timer_running:  # Only allow reset when stopped
                a_pressed = True
                pybadger.play_file(wvs[9])
                timer_running = False
                timer_elapsed = 0
                timer_start_time = 0
                timer_clock_start_hour = 0
                timer_clock_start_min = 0
                print("Timer reset")
                pcolor = RESET_COLOR
        else:
            a_pressed = False
        
        # Update elapsed time if running
        if timer_running:
            timer_elapsed = time.monotonic() - timer_start_time
            # Change color based on time
            color_index = int(timer_elapsed // 10) % len(colors)
            pcolor = colors[color_index]
        
        # Display timer time
        total_seconds = int(timer_elapsed)
        hours = total_seconds // 3600
        remaining_seconds = total_seconds % 3600
        minutes = remaining_seconds // 60
        seconds = remaining_seconds % 60
        
        # Display format changes based on elapsed time
        if hours > 0 or total_seconds >= 3600:  # Show hours:minutes after 60 minutes
            timer_display = f"{hours:02d}:{minutes:02d}"
        else:  # Show minutes:seconds
            timer_display = f"{minutes:02d}:{seconds:02d}"
        
        line_1a.text = timer_display
        
        # Display start time instead of RUNNING/STOPPED
        if timer_running or timer_elapsed > 0:
            # Convert to 12-hour format
            hour_12, am_pm = convert_to_12hour(timer_clock_start_hour)
            start_time_str = f"Started: {hour_12}:{timer_clock_start_min:02d} {am_pm}"
            line_2.text = start_time_str
        else:
            line_2.text = "Ready"
        
        # Animate NeoPixels while running (rotate every second)
        if timer_running:
            rotate_neopixel(current_time, pcolor, 1.0)
        else:
            time.sleep(0.01)
    
    elif mode == 3:  # CLOCK MODE
        # Update mode title if mode changed
        if mode != prev_mode:
            line_1.text = "CLOCK"
            line_3.text = "SELECT=Mode"
            prev_mode = mode
            prev_clock_minute = -1  # Force update on mode entry
        
        # Get current time from RTC
        t = rtc_pcf.datetime
        
        # Only update display when minute changes
        if t.tm_min != prev_clock_minute:
            # Convert to 12-hour format with AM/PM
            hour_12, am_pm = convert_to_12hour(t.tm_hour)
            
            # Format time string
            time_str = f"{hour_12}:{t.tm_min:02d} {am_pm}"
            line_1a.text = time_str
            
            # Format date string
            date_str = f"{t.tm_mon}/{t.tm_mday}/{t.tm_year}"
            line_2.text = date_str
            
            prev_clock_minute = t.tm_min
        
        # Set NeoPixels to off
        pcolor = COLOR_OFF
        pybadger.pixels.fill(pcolor)
        
        # Small sleep
        time.sleep(0.5)
    
    # Periodic garbage collection
    if current_time - last_gc_time >= GC_INTERVAL:
        gc.collect()
        last_gc_time = current_time
    
    # Periodic memory monitoring
    if current_time - last_mem_update >= MEM_UPDATE_INTERVAL:
        mem_free_kb = gc.mem_free() // 1024
        last_mem_update = current_time
    
    # Update clock display
    if current_time - last_clk_update >= CLK_UPDATE_INTERVAL:
        line_clk.text = time_display()
        last_clk_update = current_time
    
    # Update battery voltage cache
    if current_time - last_battery_update >= BATTERY_UPDATE_INTERVAL:
        battery_voltage_cached = get_battery_voltage()
        last_battery_update = current_time