#!/usr/bin/env python3
"""
Common GUI utilities and widgets for PunimTag
"""

import os
import json
import tempfile
from PIL import Image, ImageTk
from typing import Optional, Dict, Any

from src.core.config import DEFAULT_CONFIG_FILE, DEFAULT_WINDOW_SIZE, ICON_SIZE


class GUICore:
    """Common GUI utilities and helper functions"""
    
    def __init__(self):
        """Initialize GUI core utilities"""
        pass
    
    def setup_window_size_saving(self, root, config_file: str = DEFAULT_CONFIG_FILE) -> str:
        """Set up window size saving functionality"""
        # Load saved window size
        saved_size = DEFAULT_WINDOW_SIZE
        
        if os.path.exists(config_file):
            try:
                with open(config_file, 'r') as f:
                    config = json.load(f)
                    saved_size = config.get('window_size', DEFAULT_WINDOW_SIZE)
            except:
                saved_size = DEFAULT_WINDOW_SIZE
        
        # Calculate center position before showing window
        try:
            width = int(saved_size.split('x')[0])
            height = int(saved_size.split('x')[1])
            x = (root.winfo_screenwidth() // 2) - (width // 2)
            y = (root.winfo_screenheight() // 2) - (height // 2)
            root.geometry(f"{saved_size}+{x}+{y}")
        except:
            # Fallback to default geometry if positioning fails
            root.geometry(saved_size)
        
        # Track previous size to detect actual resizing
        last_size = None
        
        def save_window_size(event=None):
            nonlocal last_size
            if event and event.widget == root:
                current_size = f"{root.winfo_width()}x{root.winfo_height()}"
                # Only save if size actually changed
                if current_size != last_size:
                    last_size = current_size
                    try:
                        config = {'window_size': current_size}
                        with open(config_file, 'w') as f:
                            json.dump(config, f)
                    except:
                        pass  # Ignore save errors
        
        # Bind resize event
        root.bind('<Configure>', save_window_size)
        return saved_size
    
    def create_photo_icon(self, canvas, photo_path: str, icon_size: int = ICON_SIZE, 
                         icon_x: int = None, icon_y: int = None, 
                         canvas_width: int = None, canvas_height: int = None,
                         face_x: int = None, face_y: int = None,
                         face_width: int = None, face_height: int = None,
                         callback: callable = None) -> Optional[int]:
        """Create a reusable photo icon with tooltip on a canvas"""
        import tkinter as tk
        import subprocess
        import platform
        
        def open_source_photo(event):
            """Open the source photo in the system's default photo viewer"""
            try:
                system = platform.system()
                if system == "Windows":
                    # Windows - use default photo viewer
                    os.startfile(photo_path)
                elif system == "Darwin":  # macOS
                    # macOS - use default Preview or Photos app
                    subprocess.Popen(["open", photo_path])
                else:  # Linux and others
                    # Linux - use xdg-open which opens with default viewer
                    subprocess.Popen(["xdg-open", photo_path])
            except Exception as e:
                print(f"❌ Could not open photo: {e}")
        
        # Create tooltip for the icon
        tooltip = None
        
        def show_tooltip(event):
            nonlocal tooltip
            if tooltip:
                tooltip.destroy()
            tooltip = tk.Toplevel()
            tooltip.wm_overrideredirect(True)
            tooltip.wm_geometry(f"+{event.x_root+10}+{event.y_root+10}")
            label = tk.Label(tooltip, text="Show original photo", 
                           background="lightyellow", relief="solid", borderwidth=1,
                           font=("Arial", 9))
            label.pack()
        
        def hide_tooltip(event):
            nonlocal tooltip
            if tooltip:
                tooltip.destroy()
                tooltip = None
        
        # Calculate icon position
        if icon_x is None or icon_y is None:
            if face_x is not None and face_y is not None and face_width is not None and face_height is not None:
                # Position relative to face image - exactly in the top-right corner
                icon_x = face_x + face_width - icon_size
                icon_y = face_y
            else:
                # Position relative to canvas - exactly in the corner
                if canvas_width is None:
                    canvas_width = canvas.winfo_width()
                if canvas_height is None:
                    canvas_height = canvas.winfo_height()
                icon_x = canvas_width - icon_size
                icon_y = 0
        
        # Ensure icon stays within canvas bounds
        if canvas_width is None:
            canvas_width = canvas.winfo_width()
        if canvas_height is None:
            canvas_height = canvas.winfo_height()
        icon_x = min(icon_x, canvas_width - icon_size)
        icon_y = max(icon_y, 0)
        
        # Draw the photo icon
        canvas.create_rectangle(icon_x, icon_y, icon_x + icon_size, icon_y + icon_size, 
                              fill="white", outline="black", width=1, tags="photo_icon")
        canvas.create_text(icon_x + icon_size//2, icon_y + icon_size//2, 
                         text="📷", font=("Arial", 10), tags="photo_icon")
        
        # Bind events
        canvas.tag_bind("photo_icon", "<Button-1>", open_source_photo)
        canvas.tag_bind("photo_icon", "<Enter>", lambda e: (canvas.config(cursor="hand2"), show_tooltip(e)))
        canvas.tag_bind("photo_icon", "<Leave>", lambda e: (canvas.config(cursor=""), hide_tooltip(e)))
        canvas.tag_bind("photo_icon", "<Motion>", lambda e: (show_tooltip(e) if tooltip else None))
        
        return tooltip  # Return tooltip reference for cleanup if needed
    
    def create_confidence_badge(self, parent, confidence_pct: float):
        """Create a colorful confidence badge with percentage and label.
        Returns a frame containing a small colored circle (with percent) and a text label.
        """
        import tkinter as tk
        from tkinter import ttk
        
        # Determine color and label
        if confidence_pct >= 80:
            color = "#27AE60"  # green
            label = "Very High"
            text_color = "white"
        elif confidence_pct >= 70:
            color = "#F1C40F"  # yellow
            label = "High"
            text_color = "black"
        elif confidence_pct >= 60:
            color = "#E67E22"  # orange
            label = "Medium"
            text_color = "white"
        elif confidence_pct >= 50:
            color = "#E74C3C"  # red
            label = "Low"
            text_color = "white"
        else:
            color = "#2C3E50"  # dark blue/black
            label = "Very Low"
            text_color = "white"
        
        badge_frame = ttk.Frame(parent)
        
        # Draw circle sized to roughly match the label font height
        size = 14
        style = ttk.Style()
        bg_color = style.lookup('TFrame', 'background') or '#d9d9d9'
        canvas = tk.Canvas(badge_frame, width=size, height=size, highlightthickness=0, bg=bg_color)
        canvas.grid(row=0, column=0, padx=(0, 4))
        canvas.create_oval(1, 1, size-1, size-1, fill=color, outline=color)
        
        # Text label right to the circle
        label_widget = ttk.Label(badge_frame, text=f"{int(round(confidence_pct))}% {label}", font=("Arial", 9, "bold"))
        label_widget.grid(row=0, column=1, sticky="w")
        
        return badge_frame
    
    def create_face_crop_image(self, photo_path: str, face_location: dict, 
                              face_id: int, crop_size: int = 100) -> Optional[str]:
        """Create a face crop image for display"""
        try:
            # Parse location from string format (DeepFace format only)
            if isinstance(face_location, str):
                face_location = eval(face_location)
            
            # DeepFace format: {x, y, w, h}
            if not isinstance(face_location, dict):
                raise ValueError(f"Expected DeepFace dict format, got {type(face_location)}")
            
            x = face_location.get('x', 0)
            y = face_location.get('y', 0)
            w = face_location.get('w', 0)
            h = face_location.get('h', 0)
            
            left = x
            top = y
            right = x + w
            bottom = y + h
            
            # Load the image
            with Image.open(photo_path) as image:
                # Add padding around the face
                face_width = right - left
                face_height = bottom - top
                padding_x = int(face_width * 0.2)
                padding_y = int(face_height * 0.2)
                
                # Calculate crop bounds with padding
                crop_left = max(0, left - padding_x)
                crop_top = max(0, top - padding_y)
                crop_right = min(image.width, right + padding_x)
                crop_bottom = min(image.height, bottom + padding_y)
                
                # Crop the face
                face_crop = image.crop((crop_left, crop_top, crop_right, crop_bottom))
                
                # Resize to standard size
                face_crop = face_crop.resize((crop_size, crop_size), Image.Resampling.LANCZOS)
                
                # Create temporary file
                temp_dir = tempfile.gettempdir()
                face_filename = f"face_{face_id}_display.jpg"
                face_path = os.path.join(temp_dir, face_filename)
                
                face_crop.save(face_path, "JPEG", quality=95)
                return face_path
                
        except Exception as e:
            return None
    
    def create_photo_thumbnail(self, photo_path: str, thumbnail_size: int = 150) -> Optional[ImageTk.PhotoImage]:
        """Create a thumbnail for display"""
        try:
            if not os.path.exists(photo_path):
                return None
            
            with Image.open(photo_path) as img:
                img.thumbnail((thumbnail_size, thumbnail_size), Image.Resampling.LANCZOS)
                return ImageTk.PhotoImage(img)
        except Exception:
            return None
    
    def create_comparison_image(self, unid_crop_path: str, match_crop_path: str, 
                               person_name: str, confidence: float) -> Optional[str]:
        """Create a side-by-side comparison image"""
        try:
            # Load both face crops
            unid_img = Image.open(unid_crop_path)
            match_img = Image.open(match_crop_path)
            
            # Resize both to same height for better comparison
            target_height = 300
            unid_ratio = target_height / unid_img.height
            match_ratio = target_height / match_img.height
            
            unid_resized = unid_img.resize((int(unid_img.width * unid_ratio), target_height), Image.Resampling.LANCZOS)
            match_resized = match_img.resize((int(match_img.width * match_ratio), target_height), Image.Resampling.LANCZOS)
            
            # Create comparison image
            total_width = unid_resized.width + match_resized.width + 20  # 20px gap
            comparison = Image.new('RGB', (total_width, target_height + 60), 'white')
            
            # Paste images
            comparison.paste(unid_resized, (0, 30))
            comparison.paste(match_resized, (unid_resized.width + 20, 30))
            
            # Add labels
            from PIL import ImageDraw, ImageFont
            draw = ImageDraw.Draw(comparison)
            try:
                # Try to use a font
                font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16)
            except:
                font = ImageFont.load_default()
            
            draw.text((10, 5), "UNKNOWN", fill='red', font=font)
            draw.text((unid_resized.width + 30, 5), f"{person_name.upper()}", fill='green', font=font)
            draw.text((10, target_height + 35), f"Confidence: {confidence:.1%}", fill='blue', font=font)
            
            # Save comparison image
            temp_dir = tempfile.gettempdir()
            comparison_path = os.path.join(temp_dir, f"face_comparison_{person_name}.jpg")
            comparison.save(comparison_path, "JPEG", quality=95)
            
            return comparison_path
            
        except Exception as e:
            return None
    
    def get_confidence_description(self, confidence_pct: float) -> str:
        """Get human-readable confidence description"""
        if confidence_pct >= 80:
            return "🟢 (Very High)"
        elif confidence_pct >= 70:
            return "🟡 (High)"
        elif confidence_pct >= 60:
            return "🟠 (Medium)"
        elif confidence_pct >= 50:
            return "🔴 (Low)"
        else:
            return "⚫ (Very Low)"
    
    def center_window(self, root, width: int = None, height: int = None):
        """Center a window on the screen"""
        if width is None:
            width = root.winfo_width()
        if height is None:
            height = root.winfo_height()
        
        screen_width = root.winfo_screenwidth()
        screen_height = root.winfo_screenheight()
        
        x = (screen_width - width) // 2
        y = (screen_height - height) // 2
        
        root.geometry(f"{width}x{height}+{x}+{y}")
    
    def create_tooltip(self, widget, text: str):
        """Create a tooltip for a widget"""
        def show_tooltip(event):
            tooltip = tk.Toplevel()
            tooltip.wm_overrideredirect(True)
            tooltip.wm_geometry(f"+{event.x_root+10}+{event.y_root+10}")
            
            label = tk.Label(tooltip, text=text, background="lightyellow", 
                           relief="solid", borderwidth=1, font=("Arial", 9))
            label.pack()
            
            widget.tooltip = tooltip
        
        def hide_tooltip(event):
            if hasattr(widget, 'tooltip'):
                widget.tooltip.destroy()
                del widget.tooltip
        
        widget.bind('<Enter>', show_tooltip)
        widget.bind('<Leave>', hide_tooltip)
    
    def create_progress_bar(self, parent, text: str = "Processing..."):
        """Create a progress bar dialog"""
        import tkinter as tk
        from tkinter import ttk
        
        progress_window = tk.Toplevel(parent)
        progress_window.title("Progress")
        progress_window.resizable(False, False)
        
        # Center the progress window
        progress_window.transient(parent)
        progress_window.grab_set()
        
        frame = ttk.Frame(progress_window, padding="20")
        frame.pack()
        
        label = ttk.Label(frame, text=text)
        label.pack(pady=(0, 10))
        
        progress = ttk.Progressbar(frame, mode='indeterminate')
        progress.pack(fill='x', pady=(0, 10))
        progress.start()
        
        # Center the window
        progress_window.update_idletasks()
        x = (progress_window.winfo_screenwidth() // 2) - (progress_window.winfo_width() // 2)
        y = (progress_window.winfo_screenheight() // 2) - (progress_window.winfo_height() // 2)
        progress_window.geometry(f"+{x}+{y}")
        
        return progress_window, progress
    
    def create_confirmation_dialog(self, parent, title: str, message: str) -> bool:
        """Create a confirmation dialog"""
        import tkinter as tk
        from tkinter import messagebox
        
        result = messagebox.askyesno(title, message, parent=parent)
        return result
    
    def create_large_messagebox(self, parent, title: str, message: str, msg_type: str = "warning") -> bool:
        """Create a larger messagebox dialog that fits text without wrapping"""
        import tkinter as tk
        from tkinter import ttk, messagebox
        
        # Calculate appropriate size based on message length
        lines = message.count('\n') + 1
        max_line_length = max(len(line) for line in message.split('\n'))
        
        # Set width to accommodate text (minimum 400, maximum 800)
        width = max(400, min(800, max_line_length * 8 + 100))
        # Set height based on number of lines (minimum 200, maximum 500)
        height = max(200, min(500, lines * 25 + 150))
        
        # Calculate center position first
        screen_width = parent.winfo_screenwidth() if parent else tk._default_root.winfo_screenwidth()
        screen_height = parent.winfo_screenheight() if parent else tk._default_root.winfo_screenheight()
        x = (screen_width - width) // 2
        y = (screen_height - height) // 2
        
        # Create a custom dialog for better control over size
        dialog = tk.Toplevel(parent)
        dialog.title(title)
        dialog.transient(parent)
        dialog.grab_set()
        
        # Set geometry with position in one call to prevent jumping
        dialog.geometry(f"{width}x{height}+{x}+{y}")
        
        # Create main frame
        main_frame = ttk.Frame(dialog, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # Add message label
        message_label = tk.Label(main_frame, text=message, font=("Arial", 10), 
                               justify=tk.LEFT, wraplength=width-100)
        message_label.pack(pady=(0, 20))
        
        # Add buttons based on message type
        button_frame = ttk.Frame(main_frame)
        button_frame.pack()
        
        def close_dialog(result_value):
            """Close dialog and set result"""
            dialog._result = result_value
            dialog.destroy()
        
        if msg_type == "warning":
            ttk.Button(button_frame, text="OK", command=lambda: close_dialog(True)).pack(side=tk.LEFT, padx=5)
        elif msg_type == "askyesno":
            ttk.Button(button_frame, text="Yes", command=lambda: close_dialog(True)).pack(side=tk.LEFT, padx=5)
            ttk.Button(button_frame, text="No", command=lambda: close_dialog(False)).pack(side=tk.LEFT, padx=5)
        elif msg_type == "askyesnocancel":
            ttk.Button(button_frame, text="Yes", command=lambda: close_dialog(True)).pack(side=tk.LEFT, padx=5)
            ttk.Button(button_frame, text="No", command=lambda: close_dialog(False)).pack(side=tk.LEFT, padx=5)
            ttk.Button(button_frame, text="Cancel", command=lambda: close_dialog(None)).pack(side=tk.LEFT, padx=5)
        
        # Wait for dialog to close
        dialog.wait_window()
        return getattr(dialog, '_result', None)
    
    def create_input_dialog(self, parent, title: str, prompt: str, default: str = "") -> Optional[str]:
        """Create an input dialog"""
        import tkinter as tk
        from tkinter import simpledialog
        
        result = simpledialog.askstring(title, prompt, initialvalue=default, parent=parent)
        return result
    
    def create_file_dialog(self, parent, title: str, filetypes: list = None) -> Optional[str]:
        """Create a file dialog"""
        import tkinter as tk
        from tkinter import filedialog
        
        if filetypes is None:
            filetypes = [("Image files", "*.jpg *.jpeg *.png *.gif *.bmp *.tiff")]
        
        result = filedialog.askopenfilename(title=title, filetypes=filetypes, parent=parent)
        return result if result else None
    
    def create_directory_dialog(self, parent, title: str) -> Optional[str]:
        """Create a directory dialog"""
        import tkinter as tk
        from tkinter import filedialog
        
        result = filedialog.askdirectory(title=title, parent=parent)
        return result if result else None
    
    def cleanup_temp_files(self, file_paths: list):
        """Clean up temporary files"""
        for file_path in file_paths:
            try:
                if os.path.exists(file_path):
                    os.remove(file_path)
            except:
                pass  # Ignore cleanup errors
    
    def create_calendar_dialog(self, parent, title: str, initial_date: str = None) -> Optional[str]:
        """Create a calendar dialog for date selection"""
        import tkinter as tk
        from tkinter import ttk
        from datetime import datetime, date
        import calendar
        
        # Create calendar window
        calendar_window = tk.Toplevel(parent)
        calendar_window.title(title)
        calendar_window.resizable(False, False)
        calendar_window.transient(parent)
        calendar_window.grab_set()
        
        # Calculate center position
        window_width = 400
        window_height = 400
        screen_width = calendar_window.winfo_screenwidth()
        screen_height = calendar_window.winfo_screenheight()
        x = (screen_width // 2) - (window_width // 2)
        y = (screen_height // 2) - (window_height // 2)
        calendar_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
        
        # Calendar variables
        current_date = datetime.now()
        selected_date = None
        
        # Create custom styles for calendar buttons
        style = ttk.Style()
        style.configure("Calendar.TButton", padding=(2, 2))
        style.configure("Selected.TButton", background="lightblue")
        style.configure("Today.TButton", background="lightyellow")
        
        # Check if there's already a date selected
        if initial_date:
            try:
                selected_date = datetime.strptime(initial_date, '%Y-%m-%d').date()
                display_year = selected_date.year
                display_month = selected_date.month
            except ValueError:
                display_year = current_date.year
                display_month = current_date.month
                selected_date = None
        else:
            display_year = current_date.year
            display_month = current_date.month
        
        # Month names
        month_names = ["January", "February", "March", "April", "May", "June",
                      "July", "August", "September", "October", "November", "December"]
        
        # Main frame
        main_cal_frame = ttk.Frame(calendar_window, padding="10")
        main_cal_frame.pack(fill=tk.BOTH, expand=True)
        
        # Header frame with navigation
        header_frame = ttk.Frame(main_cal_frame)
        header_frame.pack(fill=tk.X, pady=(0, 10))
        
        # Month/Year display and navigation
        nav_frame = ttk.Frame(header_frame)
        nav_frame.pack()
        
        # Month/Year label
        month_year_label = ttk.Label(nav_frame, text="", font=("Arial", 12, "bold"))
        month_year_label.pack(side=tk.LEFT, padx=10)
        
        def update_calendar():
            """Update the calendar display"""
            # Update month/year label
            month_year_label.configure(text=f"{month_names[display_month-1]} {display_year}")
            
            # Clear existing calendar
            for widget in calendar_frame.winfo_children():
                widget.destroy()
            
            # Get calendar data
            cal = calendar.monthcalendar(display_year, display_month)
            
            # Day headers
            day_headers = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
            for i, day in enumerate(day_headers):
                header_label = ttk.Label(calendar_frame, text=day, font=("Arial", 10, "bold"))
                header_label.grid(row=0, column=i, padx=2, pady=2, sticky="nsew")
            
            # Calendar days
            for week_num, week in enumerate(cal):
                for day_num, day in enumerate(week):
                    if day == 0:
                        # Empty cell
                        empty_label = ttk.Label(calendar_frame, text="")
                        empty_label.grid(row=week_num+1, column=day_num, padx=2, pady=2, sticky="nsew")
                    else:
                        # Day button
                        day_date = date(display_year, display_month, day)
                        is_selected = selected_date == day_date
                        is_today = day_date == current_date.date()
                        is_future = day_date > current_date.date()
                        
                        if is_future:
                            # Disable future dates
                            day_btn = ttk.Button(calendar_frame, text=str(day), 
                                               state='disabled', style="Calendar.TButton")
                        else:
                            # Create day selection handler
                            def make_day_handler(day_value):
                                def select_day():
                                    nonlocal selected_date
                                    selected_date = date(display_year, display_month, day_value)
                                    # Reset all buttons to normal calendar style
                                    for widget in calendar_frame.winfo_children():
                                        if isinstance(widget, ttk.Button):
                                            widget.config(style="Calendar.TButton")
                                    # Highlight selected day
                                    for widget in calendar_frame.winfo_children():
                                        if isinstance(widget, ttk.Button) and widget.cget("text") == str(day_value):
                                            widget.config(style="Selected.TButton")
                                return select_day
                            
                            day_btn = ttk.Button(calendar_frame, text=str(day), 
                                               command=make_day_handler(day),
                                               style="Calendar.TButton")
                        
                        day_btn.grid(row=week_num+1, column=day_num, padx=1, pady=1, sticky="nsew")
                        
                        # Apply initial styling
                        if is_selected:
                            day_btn.config(style="Selected.TButton")
                        elif is_today and not is_future:
                            day_btn.config(style="Today.TButton")
        
        
        def prev_month():
            nonlocal display_month, display_year
            display_month -= 1
            if display_month < 1:
                display_month = 12
                display_year -= 1
            update_calendar()
        
        def next_month():
            nonlocal display_month, display_year
            display_month += 1
            if display_month > 12:
                display_month = 1
                display_year += 1
            
            # Don't allow navigation to future months
            if date(display_year, display_month, 1) > current_date.date():
                display_month -= 1
                if display_month < 1:
                    display_month = 12
                    display_year -= 1
                return
            
            update_calendar()
        
        def prev_year():
            nonlocal display_year
            display_year -= 1
            update_calendar()
        
        def next_year():
            nonlocal display_year
            display_year += 1
            
            # Don't allow navigation to future years
            if display_year > current_date.year:
                display_year -= 1
                return
            
            update_calendar()
        
        # Navigation buttons
        prev_year_btn = ttk.Button(nav_frame, text="<<", width=3, command=prev_year)
        prev_year_btn.pack(side=tk.LEFT)
        
        prev_month_btn = ttk.Button(nav_frame, text="<", width=3, command=prev_month)
        prev_month_btn.pack(side=tk.LEFT, padx=(5, 0))
        
        next_month_btn = ttk.Button(nav_frame, text=">", width=3, command=next_month)
        next_month_btn.pack(side=tk.LEFT, padx=(5, 0))
        
        next_year_btn = ttk.Button(nav_frame, text=">>", width=3, command=next_year)
        next_year_btn.pack(side=tk.LEFT)
        
        # Calendar grid frame
        calendar_frame = ttk.Frame(main_cal_frame)
        calendar_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
        
        # Configure grid weights
        for i in range(7):
            calendar_frame.columnconfigure(i, weight=1)
        for i in range(7):
            calendar_frame.rowconfigure(i, weight=1)
        
        # Buttons frame
        buttons_frame = ttk.Frame(main_cal_frame)
        buttons_frame.pack(fill=tk.X)
        
        def select_date():
            """Select the date and close calendar"""
            if selected_date:
                calendar_window.selected_date = selected_date.strftime('%Y-%m-%d')
            else:
                calendar_window.selected_date = ""
            calendar_window.destroy()
        
        def cancel_selection():
            """Cancel date selection"""
            calendar_window.destroy()
        
        # Buttons (matching original layout)
        ttk.Button(buttons_frame, text="Select", command=select_date).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(buttons_frame, text="Cancel", command=cancel_selection).pack(side=tk.LEFT)
        
        # Initialize calendar display
        update_calendar()
        
        # Wait for window to close
        calendar_window.wait_window()
        
        # Return selected date or None
        return getattr(calendar_window, 'selected_date', None)
    
    def create_autocomplete_entry(self, parent, suggestions: list, callback: callable = None):
        """Create an entry widget with autocomplete functionality"""
        import tkinter as tk
        from tkinter import ttk
        
        # Create entry
        entry_var = tk.StringVar()
        entry = ttk.Entry(parent, textvariable=entry_var)
        
        # Create listbox for suggestions
        listbox = tk.Listbox(parent, height=8)
        listbox.place_forget()  # Hide initially
        
        def show_suggestions():
            """Show filtered suggestions in listbox"""
            typed = entry_var.get().strip()
            
            if not typed:
                filtered = []  # Show nothing if no typing
            else:
                low = typed.lower()
                # Only show names that start with the typed text
                filtered = [n for n in suggestions if n.lower().startswith(low)][:10]
            
            # Update listbox
            listbox.delete(0, tk.END)
            for name in filtered:
                listbox.insert(tk.END, name)
            
            # Show listbox if we have suggestions
            if filtered:
                # Position listbox below entry
                entry.update_idletasks()
                x = entry.winfo_x()
                y = entry.winfo_y() + entry.winfo_height()
                width = entry.winfo_width()
                
                listbox.place(x=x, y=y, width=width)
                listbox.selection_clear(0, tk.END)
                listbox.selection_set(0)  # Select first item
                listbox.activate(0)  # Activate first item
            else:
                listbox.place_forget()
        
        def hide_suggestions():
            """Hide the suggestions listbox"""
            listbox.place_forget()
        
        def on_listbox_select(event=None):
            """Handle listbox selection and hide list"""
            selection = listbox.curselection()
            if selection:
                selected_name = listbox.get(selection[0])
                entry_var.set(selected_name)
            hide_suggestions()
            entry.focus_set()
            if callback:
                callback(selected_name)
        
        def on_listbox_click(event):
            """Handle mouse click selection"""
            try:
                index = listbox.nearest(event.y)
                if index is not None and index >= 0:
                    selected_name = listbox.get(index)
                    entry_var.set(selected_name)
            except:
                pass
            hide_suggestions()
            entry.focus_set()
            if callback:
                callback(selected_name)
            return 'break'
        
        def on_key_press(event):
            """Handle key navigation in entry"""
            if event.keysym == 'Down':
                if listbox.winfo_viewable():
                    listbox.focus_set()
                    listbox.selection_clear(0, tk.END)
                    listbox.selection_set(0)
                    listbox.activate(0)
                return 'break'
            elif event.keysym == 'Escape':
                hide_suggestions()
                return 'break'
            elif event.keysym == 'Return':
                return 'break'
        
        def on_listbox_key(event):
            """Handle key navigation in listbox"""
            if event.keysym == 'Return':
                on_listbox_select(event)
                return 'break'
            elif event.keysym == 'Escape':
                hide_suggestions()
                entry.focus_set()
                return 'break'
            elif event.keysym == 'Up':
                selection = listbox.curselection()
                if selection and selection[0] > 0:
                    # Move up in listbox
                    listbox.selection_clear(0, tk.END)
                    listbox.selection_set(selection[0] - 1)
                    listbox.see(selection[0] - 1)
                else:
                    # At top, go back to entry field
                    hide_suggestions()
                    entry.focus_set()
                return 'break'
            elif event.keysym == 'Down':
                selection = listbox.curselection()
                max_index = listbox.size() - 1
                if selection and selection[0] < max_index:
                    # Move down in listbox
                    listbox.selection_clear(0, tk.END)
                    listbox.selection_set(selection[0] + 1)
                    listbox.see(selection[0] + 1)
                return 'break'
        
        # Bind events
        entry.bind('<KeyRelease>', lambda e: show_suggestions())
        entry.bind('<KeyPress>', on_key_press)
        entry.bind('<FocusOut>', lambda e: parent.after(150, hide_suggestions))  # Delay to allow listbox clicks
        listbox.bind('<Button-1>', on_listbox_click)
        listbox.bind('<KeyPress>', on_listbox_key)
        listbox.bind('<Double-Button-1>', on_listbox_click)
        
        return entry, entry_var, listbox
    
    def show_image_viewer(self, parent, photo_path: str):
        """Show an in-app image viewer dialog with zoom functionality"""
        import tkinter as tk
        from tkinter import ttk
        from PIL import Image, ImageTk
        
        try:
            # Create viewer window
            viewer = tk.Toplevel(parent)
            viewer.title(f"Photo Viewer - {os.path.basename(photo_path)}")
            
            # Get screen dimensions
            screen_width = viewer.winfo_screenwidth()
            screen_height = viewer.winfo_screenheight()
            
            # Load original image
            original_img = Image.open(photo_path)
            img_width, img_height = original_img.size
            
            # Calculate optimal window size (90% of screen, but respecting aspect ratio)
            max_width = int(screen_width * 0.9)
            max_height = int(screen_height * 0.9)
            
            # Calculate initial scaling to fit image while maintaining aspect ratio
            width_ratio = max_width / img_width
            height_ratio = max_height / img_height
            initial_scale = min(width_ratio, height_ratio, 1.0)  # Don't upscale initially
            
            # Window size based on initial scale
            window_width = min(int(img_width * initial_scale) + 40, max_width)
            window_height = min(int(img_height * initial_scale) + 150, max_height)
            
            # Center window on screen
            x = (screen_width - window_width) // 2
            y = (screen_height - window_height) // 2
            
            viewer.geometry(f"{window_width}x{window_height}+{x}+{y}")
            
            # Create main frame with padding
            main_frame = ttk.Frame(viewer, padding="10")
            main_frame.pack(fill=tk.BOTH, expand=True)
            
            # Zoom controls frame
            zoom_frame = ttk.Frame(main_frame)
            zoom_frame.pack(fill=tk.X, pady=(0, 5))
            
            # Create canvas with scrollbars for large images
            canvas_frame = ttk.Frame(main_frame)
            canvas_frame.pack(fill=tk.BOTH, expand=True)
            
            # Canvas
            canvas = tk.Canvas(canvas_frame, bg='gray25', highlightthickness=0)
            canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
            
            # Scrollbars
            v_scrollbar = ttk.Scrollbar(canvas_frame, orient='vertical', command=canvas.yview)
            v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
            h_scrollbar = ttk.Scrollbar(main_frame, orient='horizontal', command=canvas.xview)
            h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
            
            canvas.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
            
            # Zoom state
            current_zoom = initial_scale
            zoom_levels = [0.1, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0]
            
            # Info label
            info_label = ttk.Label(main_frame, text="", font=("Arial", 9))
            info_label.pack(pady=(5, 0))
            
            def update_image(zoom_scale):
                """Update the displayed image with the given zoom scale"""
                nonlocal current_zoom
                current_zoom = zoom_scale
                
                # Calculate new dimensions
                new_width = int(img_width * zoom_scale)
                new_height = int(img_height * zoom_scale)
                
                # Resize image
                img_resized = original_img.resize((new_width, new_height), Image.Resampling.LANCZOS)
                photo = ImageTk.PhotoImage(img_resized)
                
                # Clear canvas and create new image
                canvas.delete("all")
                canvas.create_image(new_width // 2, new_height // 2, 
                                  image=photo, anchor=tk.CENTER, tags="image")
                canvas.image = photo  # Keep reference
                
                # Update scroll region
                canvas.configure(scrollregion=(0, 0, new_width, new_height))
                
                # Update info label
                info_text = f"Image: {os.path.basename(photo_path)} | " \
                           f"Original: {img_width}×{img_height}px | " \
                           f"Zoom: {zoom_scale*100:.0f}% | " \
                           f"Display: {new_width}×{new_height}px"
                info_label.config(text=info_text)
                
                # Update zoom buttons state
                zoom_in_btn.config(state='normal' if zoom_scale < zoom_levels[-1] else 'disabled')
                zoom_out_btn.config(state='normal' if zoom_scale > zoom_levels[0] else 'disabled')
                zoom_reset_btn.config(state='normal' if zoom_scale != 1.0 else 'disabled')
            
            def zoom_in():
                """Zoom in to the next level"""
                next_zoom = min([z for z in zoom_levels if z > current_zoom], default=current_zoom)
                if next_zoom > current_zoom:
                    update_image(next_zoom)
            
            def zoom_out():
                """Zoom out to the previous level"""
                prev_zoom = max([z for z in zoom_levels if z < current_zoom], default=current_zoom)
                if prev_zoom < current_zoom:
                    update_image(prev_zoom)
            
            def zoom_fit():
                """Zoom to fit window"""
                update_image(initial_scale)
            
            def zoom_100():
                """Zoom to 100% (actual size)"""
                update_image(1.0)
            
            def on_mouse_wheel(event):
                """Handle mouse wheel zoom"""
                # Get mouse position relative to canvas
                canvas_x = canvas.canvasx(event.x)
                canvas_y = canvas.canvasy(event.y)
                
                # Determine zoom direction
                if event.delta > 0 or event.num == 4:  # Zoom in
                    zoom_in()
                elif event.delta < 0 or event.num == 5:  # Zoom out
                    zoom_out()
            
            # Zoom control buttons
            ttk.Label(zoom_frame, text="Zoom:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5))
            
            zoom_out_btn = ttk.Button(zoom_frame, text="−", width=3, command=zoom_out)
            zoom_out_btn.pack(side=tk.LEFT, padx=2)
            
            zoom_in_btn = ttk.Button(zoom_frame, text="+", width=3, command=zoom_in)
            zoom_in_btn.pack(side=tk.LEFT, padx=2)
            
            zoom_reset_btn = ttk.Button(zoom_frame, text="Fit", width=5, command=zoom_fit)
            zoom_reset_btn.pack(side=tk.LEFT, padx=2)
            
            zoom_100_btn = ttk.Button(zoom_frame, text="100%", width=5, command=zoom_100)
            zoom_100_btn.pack(side=tk.LEFT, padx=2)
            
            ttk.Label(zoom_frame, text="(Use mouse wheel to zoom)", 
                     font=("Arial", 8), foreground="gray").pack(side=tk.LEFT, padx=(10, 0))
            
            # Close button
            button_frame = ttk.Frame(main_frame)
            button_frame.pack(pady=(5, 0))
            
            close_btn = ttk.Button(button_frame, text="Close", command=viewer.destroy)
            close_btn.pack()
            
            # Bind mouse wheel events (different on different platforms)
            canvas.bind("<MouseWheel>", on_mouse_wheel)  # Windows/Mac
            canvas.bind("<Button-4>", on_mouse_wheel)    # Linux scroll up
            canvas.bind("<Button-5>", on_mouse_wheel)    # Linux scroll down
            
            # Keyboard shortcuts
            viewer.bind('<Escape>', lambda e: viewer.destroy())
            viewer.bind('<Return>', lambda e: viewer.destroy())
            viewer.bind('<plus>', lambda e: zoom_in())
            viewer.bind('<minus>', lambda e: zoom_out())
            viewer.bind('<equal>', lambda e: zoom_in())  # + without shift
            viewer.bind('<KP_Add>', lambda e: zoom_in())  # Numpad +
            viewer.bind('<KP_Subtract>', lambda e: zoom_out())  # Numpad -
            viewer.bind('f', lambda e: zoom_fit())
            viewer.bind('F', lambda e: zoom_fit())
            viewer.bind('1', lambda e: zoom_100())
            
            # Initialize image at initial scale
            update_image(initial_scale)
            
            # Make modal (wait for window to be visible before grabbing)
            viewer.transient(parent)
            viewer.update_idletasks()  # Ensure window is rendered
            viewer.deiconify()  # Ensure window is shown
            viewer.wait_visibility()  # Wait for window to become visible
            viewer.grab_set()  # Now safe to grab focus
            
        except Exception as e:
            import tkinter.messagebox as messagebox
            messagebox.showerror("Error", f"Could not display image:\n{e}", parent=parent)