#!/usr/bin/env python3
"""
Integrated Identify Panel for PunimTag Dashboard
Embeds the full identify GUI functionality into the dashboard frame
"""

import os
import time
import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk
from typing import List, Dict, Tuple, Optional

from src.core.config import DEFAULT_BATCH_SIZE, DEFAULT_FACE_TOLERANCE
from src.core.database import DatabaseManager
from src.core.face_processing import FaceProcessor
from src.gui.gui_core import GUICore


class IdentifyPanel:
    """Integrated identify panel that embeds the full identify GUI functionality into the dashboard"""
    
    def __init__(self, parent_frame: ttk.Frame, db_manager: DatabaseManager, 
                 face_processor: FaceProcessor, gui_core: GUICore, on_navigate_home=None, verbose: int = 0):
        """Initialize the identify panel"""
        self.parent_frame = parent_frame
        self.db = db_manager
        self.face_processor = face_processor
        self.gui_core = gui_core
        self.on_navigate_home = on_navigate_home
        self.verbose = verbose
        
        # Panel state
        self.is_active = False
        self.current_faces = []
        self.current_face_index = 0
        self.face_status = {}
        self.face_person_names = {}
        self.face_selection_states = {}
        self.identify_data_cache = {}
        self.current_face_crop_path = None
        
        # Caching system for all faces data
        self.all_faces_cache = []  # Cache all faces from database
        self.cache_loaded = False  # Flag to track if cache is loaded
        
        # GUI components
        self.components = {}
        self.main_frame = None
        
    def create_panel(self) -> ttk.Frame:
        """Create the identify panel with all GUI components"""
        self.main_frame = ttk.Frame(self.parent_frame)
        
        # Configure grid weights for full screen responsiveness
        self.main_frame.columnconfigure(0, weight=1)  # Left panel
        self.main_frame.columnconfigure(1, weight=1)  # Right panel for similar faces
        self.main_frame.rowconfigure(0, weight=0)  # Info label - fixed height
        self.main_frame.rowconfigure(1, weight=0)  # Filter row - fixed height
        self.main_frame.rowconfigure(2, weight=0)  # Checkboxes row - fixed height
        self.main_frame.rowconfigure(3, weight=0)  # Configuration row - fixed height
        self.main_frame.rowconfigure(4, weight=1)  # Main panels row - expandable
        
        # Photo info with larger font for full screen
        self.components['info_label'] = tk.Label(self.main_frame, text="", font=("Arial", 12, "bold"))
        self.components['info_label'].grid(row=0, column=0, columnspan=2, pady=(0, 15), sticky=tk.W)
        
        # Create all GUI components
        self._create_gui_components()
        
        # Create main content panels
        self._create_main_panels()
        
        return self.main_frame
    
    def _calculate_face_canvas_size(self) -> int:
        """Calculate responsive face canvas size based on available window space"""
        try:
            # Get the main window to determine available space
            root = self.main_frame.winfo_toplevel()
            root.update_idletasks()  # Ensure geometry is calculated
            
            # Try to get the actual window height first, fall back to screen height
            try:
                window_height = root.winfo_height()
                if window_height <= 1:  # Window not yet rendered
                    window_height = root.winfo_screenheight()
            except:
                window_height = root.winfo_screenheight()
            
            # Estimate space used by other UI elements (in pixels)
            # Title bar + menu: ~80px, filters: ~120px, config: ~60px, form: ~120px, buttons: ~40px, padding: ~40px
            used_height = 460
            
            # Calculate available height for face canvas
            available_height = window_height - used_height
            
            # Define size constraints
            min_size = 200  # Minimum usable size
            max_size = 400  # Maximum size (original)
            preferred_size = 300  # Good balance for most screens
            
            # Calculate responsive size based on available height
            if available_height < 500:  # Very small window (laptop with small resolution)
                face_size = min_size
            elif available_height < 600:  # Small laptop screen
                face_size = min(max_size, max(min_size, available_height // 2))
            elif available_height < 800:  # Medium laptop screen
                face_size = min(max_size, max(min_size, available_height // 2.5))
            else:  # Large screen
                face_size = preferred_size
            
            # Ensure it's a reasonable size
            face_size = max(min_size, min(max_size, int(face_size)))
            
            if self.verbose > 0:
                print(f"📐 Window height: {window_height}px, Available: {available_height}px, Face canvas: {face_size}x{face_size}px")
            
            return face_size
            
        except Exception as e:
            # Fallback to default size if calculation fails
            if self.verbose > 0:
                print(f"⚠️  Error calculating face canvas size: {e}, using default 300px")
            return 300
    
    def _update_face_canvas_size(self):
        """Update face canvas size dynamically (for window resize events)"""
        try:
            if 'face_canvas' not in self.components:
                return
            
            # Calculate new size
            new_size = self._calculate_face_canvas_size()
            canvas = self.components['face_canvas']
            
            # Get current size
            current_width = canvas.winfo_width()
            current_height = canvas.winfo_height()
            
            # Only update if size has changed significantly (avoid constant updates)
            if abs(current_width - new_size) > 10 or abs(current_height - new_size) > 10:
                # Update canvas size
                canvas.configure(width=new_size, height=new_size)
                
                # Refresh the current face image if there's one displayed
                if self.current_faces and self.current_face_index < len(self.current_faces):
                    face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model = self.current_faces[self.current_face_index]
                    if self.current_face_crop_path:
                        self._update_face_image(self.current_face_crop_path, photo_path)
                
                if self.verbose > 0:
                    print(f"📐 Updated face canvas size to {new_size}x{new_size}px")
                    
        except Exception as e:
            if self.verbose > 0:
                print(f"⚠️  Error updating face canvas size: {e}")
    
    def _create_gui_components(self):
        """Create all GUI components for the identify interface"""
        # Create variables for form data
        self.components['compare_var'] = tk.BooleanVar()
        self.components['unique_var'] = tk.BooleanVar()
        self.components['first_name_var'] = tk.StringVar()
        self.components['last_name_var'] = tk.StringVar()
        self.components['middle_name_var'] = tk.StringVar()
        self.components['maiden_name_var'] = tk.StringVar()
        self.components['date_of_birth_var'] = tk.StringVar()
        
        # Date filter variables
        self.components['date_from_var'] = tk.StringVar(value="")
        self.components['date_to_var'] = tk.StringVar(value="")
        self.components['date_processed_from_var'] = tk.StringVar(value="")
        self.components['date_processed_to_var'] = tk.StringVar(value="")
        
        # Quality filter variable (0-100%)
        self.components['quality_filter_var'] = tk.IntVar(value=0)
        
        # Date filter controls
        date_filter_frame = ttk.LabelFrame(self.main_frame, text="Filter", padding="5")
        date_filter_frame.grid(row=1, column=0, pady=(0, 0), sticky=tk.W)
        date_filter_frame.columnconfigure(1, weight=0)
        date_filter_frame.columnconfigure(4, weight=0)
        
        # Date from
        ttk.Label(date_filter_frame, text="Taken date: from").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
        self.components['date_from_entry'] = ttk.Entry(date_filter_frame, textvariable=self.components['date_from_var'], width=10, state='readonly')
        self.components['date_from_entry'].grid(row=0, column=1, sticky=tk.W, padx=(0, 5))
        
        # Calendar button for date from
        def open_calendar_from():
            self._open_date_picker(self.components['date_from_var'])
        
        self.components['date_from_btn'] = ttk.Button(date_filter_frame, text="📅", width=3, command=open_calendar_from)
        self.components['date_from_btn'].grid(row=0, column=2, padx=(0, 10))
        
        # Date to
        ttk.Label(date_filter_frame, text="to").grid(row=0, column=3, sticky=tk.W, padx=(0, 5))
        self.components['date_to_entry'] = ttk.Entry(date_filter_frame, textvariable=self.components['date_to_var'], width=10, state='readonly')
        self.components['date_to_entry'].grid(row=0, column=4, sticky=tk.W, padx=(0, 5))
        
        # Calendar button for date to
        def open_calendar_to():
            self._open_date_picker(self.components['date_to_var'])
        
        self.components['date_to_btn'] = ttk.Button(date_filter_frame, text="📅", width=3, command=open_calendar_to)
        self.components['date_to_btn'].grid(row=0, column=5, padx=(0, 10))
        
        # Apply filter button
        def apply_date_filter():
            """Apply date filters and reload faces"""
            self._apply_date_filters()
        
        self.components['apply_filter_btn'] = ttk.Button(date_filter_frame, text="Apply Filter", command=apply_date_filter)
        self.components['apply_filter_btn'].grid(row=0, column=6, padx=(10, 0))
        
        # Date processed filter (second row)
        ttk.Label(date_filter_frame, text="Processed date: from").grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        self.components['date_processed_from_entry'] = ttk.Entry(date_filter_frame, textvariable=self.components['date_processed_from_var'], width=10, state='readonly')
        self.components['date_processed_from_entry'].grid(row=1, column=1, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        
        # Calendar button for date processed from
        def open_calendar_processed_from():
            self._open_date_picker(self.components['date_processed_from_var'])
        
        self.components['date_processed_from_btn'] = ttk.Button(date_filter_frame, text="📅", width=3, command=open_calendar_processed_from)
        self.components['date_processed_from_btn'].grid(row=1, column=2, padx=(0, 10), pady=(10, 0))
        
        # Date processed to
        ttk.Label(date_filter_frame, text="to").grid(row=1, column=3, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        self.components['date_processed_to_entry'] = ttk.Entry(date_filter_frame, textvariable=self.components['date_processed_to_var'], width=10, state='readonly')
        self.components['date_processed_to_entry'].grid(row=1, column=4, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        
        # Calendar button for date processed to
        def open_calendar_processed_to():
            self._open_date_picker(self.components['date_processed_to_var'])
        
        self.components['date_processed_to_btn'] = ttk.Button(date_filter_frame, text="📅", width=3, command=open_calendar_processed_to)
        self.components['date_processed_to_btn'].grid(row=1, column=5, padx=(0, 10), pady=(10, 0))
        
        # Quality filter (third row)
        ttk.Label(date_filter_frame, text="Min quality:").grid(row=2, column=0, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        
        # Quality slider frame
        quality_slider_frame = ttk.Frame(date_filter_frame)
        quality_slider_frame.grid(row=2, column=1, columnspan=5, sticky=tk.W, pady=(10, 0))
        
        # Quality slider
        self.components['quality_slider'] = tk.Scale(
            quality_slider_frame,
            from_=0,
            to=100,
            orient=tk.HORIZONTAL,
            variable=self.components['quality_filter_var'],
            length=250,
            tickinterval=25,
            resolution=5,
            showvalue=0
        )
        self.components['quality_slider'].pack(side=tk.LEFT, padx=(0, 10))
        
        # Quality value label
        self.components['quality_value_label'] = ttk.Label(
            quality_slider_frame,
            text=f"{self.components['quality_filter_var'].get()}%",
            font=("Arial", 10, "bold"),
            width=6
        )
        self.components['quality_value_label'].pack(side=tk.LEFT, padx=(0, 5))
        
        # Update label when slider changes
        def update_quality_label(*args):
            quality = self.components['quality_filter_var'].get()
            self.components['quality_value_label'].config(text=f"{quality}%")
            
            # Color code the label
            if quality == 0:
                color = "gray"
            elif quality < 30:
                color = "orange"
            elif quality < 60:
                color = "#E67E22"  # orange
            else:
                color = "green"
            self.components['quality_value_label'].config(foreground=color)
        
        self.components['quality_filter_var'].trace('w', update_quality_label)
        update_quality_label()  # Initialize
        
        # Quality filter help text
        ttk.Label(
            quality_slider_frame,
            text="(0 = all faces)",
            font=("Arial", 8),
            foreground="gray"
        ).pack(side=tk.LEFT)
        
        # Unique checkbox under the filter frame
        def on_unique_change():
            """Handle unique faces checkbox change - filter main face list like old implementation"""
            if self.components['unique_var'].get():
                # Check if identification has started
                if not self.current_faces:
                    messagebox.showinfo("Start Identification First", 
                                      "Please click 'Start Identification' before applying the unique faces filter.")
                    self.components['unique_var'].set(False)
                    return
                
                # Show progress message
                print("🔄 Applying unique faces filter...")
                self.main_frame.update()  # Update UI to show the message
                
                # Store original count
                original_count = len(self.current_faces)
                
                # Apply unique faces filtering to the main face list
                try:
                    self.current_faces = self._filter_unique_faces_from_list(self.current_faces)
                    filtered_count = len(self.current_faces)
                    removed_count = original_count - filtered_count
                    
                    print(f"✅ Filter applied: {filtered_count} unique faces remaining ({removed_count} duplicates hidden)")
                    
                    if removed_count > 0:
                        messagebox.showinfo("Unique Faces Filter Applied", 
                                          f"Showing {filtered_count} unique faces.\n"
                                          f"Hidden {removed_count} duplicate faces (≥60% match confidence).")
                    else:
                        messagebox.showinfo("No Duplicates Found", 
                                          "All faces appear to be unique - no duplicates to hide.")
                except Exception as e:
                    print(f"⚠️  Error applying filter: {e}")
                    messagebox.showerror("Filter Error", f"Error applying unique faces filter:\n{e}")
                    # Revert checkbox state
                    self.components['unique_var'].set(False)
                    return
            else:
                # Reload the original unfiltered face list from cache
                print("🔄 Reloading all faces from cache...")
                self.main_frame.update()  # Update UI to show the message
                
                # Get batch size
                try:
                    batch_size = int(self.components['batch_var'].get().strip())
                except Exception:
                    batch_size = DEFAULT_BATCH_SIZE
                
                # Get quality filter
                min_quality = self.components['quality_filter_var'].get()
                min_quality_score = min_quality / 100.0
                
                # Get sort option
                sort_display = self.components['sort_var'].get()
                sort_by = self.sort_value_map.get(sort_display, "quality")
                
                # Reload faces with current filters and sort option from cache
                if self.cache_loaded:
                    self.current_faces = self._filter_cached_faces(min_quality_score, sort_by, batch_size)
                    print(f"✅ Reloaded: {len(self.current_faces)} faces from cache")
                else:
                    print("⚠️  Cache not loaded - please click 'Start Identification' first")
            
            # Reset to first face and update display
            self.current_face_index = 0
            if self.current_faces:
                self._update_current_face()
                self._update_button_states()
                
                # Update similar faces if compare is enabled
                if self.components['compare_var'].get():
                    face_id, _, _, _, _, _, _, _, _ = self.current_faces[self.current_face_index]
                    self._update_similar_faces(face_id)
        
        self.components['unique_check'] = ttk.Checkbutton(self.main_frame, text="Unique faces only", 
                                                         variable=self.components['unique_var'],
                                                         command=on_unique_change)
        self.components['unique_check'].grid(row=2, column=0, sticky=tk.W, padx=(0, 5), pady=0)
        
        # Compare checkbox on the same row as Unique
        def on_compare_change():
            # Toggle the similar faces functionality
            if self.components['compare_var'].get():
                # Enable select all/clear all buttons
                self.components['select_all_btn'].config(state='normal')
                self.components['clear_all_btn'].config(state='normal')
                # Update similar faces if we have a current face
                if self.current_faces and self.current_face_index < len(self.current_faces):
                    face_id, _, _, _, _, _, _, _, _ = self.current_faces[self.current_face_index]
                    self._update_similar_faces(face_id)
            else:
                # Disable select all/clear all buttons
                self.components['select_all_btn'].config(state='disabled')
                self.components['clear_all_btn'].config(state='disabled')
                # Clear similar faces content
                scrollable_frame = self.components['similar_scrollable_frame']
                for widget in scrollable_frame.winfo_children():
                    widget.destroy()
                # Show message that compare is disabled
                no_compare_label = ttk.Label(scrollable_frame, text="Enable 'Compare similar faces' to see similar faces", 
                                           foreground="gray", font=("Arial", 10))
                no_compare_label.pack(pady=20)
        
        self.components['compare_check'] = ttk.Checkbutton(self.main_frame, text="Compare similar faces", 
                                                          variable=self.components['compare_var'],
                                                          command=on_compare_change)
        self.components['compare_check'].grid(row=2, column=1, sticky=tk.W, padx=(0, 5), pady=0)
        
        # Command variable for button callbacks
        self.components['command_var'] = tk.StringVar()
        
        # Batch size configuration
        batch_frame = ttk.LabelFrame(self.main_frame, text="Configuration", padding="5")
        batch_frame.grid(row=3, column=0, columnspan=2, pady=(10, 0), sticky=tk.W)
        
        ttk.Label(batch_frame, text="Batch size:").pack(side=tk.LEFT, padx=(0, 5))
        self.components['batch_var'] = tk.StringVar(value=str(DEFAULT_BATCH_SIZE))
        batch_entry = ttk.Entry(batch_frame, textvariable=self.components['batch_var'], width=8)
        batch_entry.pack(side=tk.LEFT, padx=(0, 10))
        
        # Sort option
        ttk.Label(batch_frame, text="Sort by:").pack(side=tk.LEFT, padx=(10, 5))
        self.components['sort_var'] = tk.StringVar(value="quality")
        sort_options = [
            ("Quality (Best First)", "quality"),
            ("Quality (Worst First)", "quality_asc"),
            ("Date Taken (Newest First)", "date_taken"),
            ("Date Taken (Oldest First)", "date_taken_asc"),
            ("Date Added (Newest First)", "date_added"),
            ("Date Added (Oldest First)", "date_added_asc"),
            ("Filename (A-Z)", "filename"),
            ("Filename (Z-A)", "filename_desc"),
            ("Detection Confidence (High First)", "confidence"),
            ("Detection Confidence (Low First)", "confidence_asc")
        ]
        sort_combo = ttk.Combobox(batch_frame, textvariable=self.components['sort_var'], 
                                 values=[opt[0] for opt in sort_options], state="readonly", width=25)
        sort_combo.pack(side=tk.LEFT, padx=(0, 10))
        
        # Map display names to sort values
        self.sort_value_map = {opt[0]: opt[1] for opt in sort_options}
        
        # Add sort change handler
        def on_sort_change(event=None):
            """Handle sort option change - refresh face list if identification is active"""
            if self.is_active and self.cache_loaded:
                # Show progress message
                print("🔄 Refreshing face list with new sort order...")
                self.main_frame.update()
                
                # Get batch size
                try:
                    batch_size = int(self.components['batch_var'].get().strip())
                except Exception:
                    batch_size = DEFAULT_BATCH_SIZE
                
                # Get quality filter
                min_quality = self.components['quality_filter_var'].get()
                min_quality_score = min_quality / 100.0
                
                # Get new sort option
                sort_display = self.components['sort_var'].get()
                sort_by = self.sort_value_map.get(sort_display, "quality")
                
                # Apply new sort order to cached data
                self.current_faces = self._filter_cached_faces(min_quality_score, sort_by, batch_size)
                
                # Reset to first face and update display
                self.current_face_index = 0
                if self.current_faces:
                    self._update_current_face()
                    self._update_button_states()
                    
                    # Update similar faces if compare is enabled
                    if self.components['compare_var'].get():
                        face_id, _, _, _, _, _, _, _, _ = self.current_faces[self.current_face_index]
                        self._update_similar_faces(face_id)
                    
                    print(f"✅ Refreshed: {len(self.current_faces)} faces with new sort order")
                else:
                    print("⚠️  No faces found with current filters and sort order")
        
        sort_combo.bind('<<ComboboxSelected>>', on_sort_change)
        
        # Start button
        start_btn = ttk.Button(batch_frame, text="🚀 Start Identification", command=self._start_identification)
        start_btn.pack(side=tk.LEFT, padx=(10, 0))
    
    def _create_main_panels(self):
        """Create the main left and right panels"""
        # Left panel for face display and identification
        self.components['left_panel'] = ttk.LabelFrame(self.main_frame, text="Face Identification", padding="10")
        self.components['left_panel'].grid(row=4, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))
        
        # Right panel for similar faces (always visible)
        self.components['right_panel'] = ttk.LabelFrame(self.main_frame, text="Similar Faces", padding="10")
        self.components['right_panel'].grid(row=4, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 0))
        
        # Create left panel content
        self._create_left_panel_content()
        
        # Create right panel content
        self._create_right_panel_content()
    
    def _create_left_panel_content(self):
        """Create the left panel content for face identification"""
        left_panel = self.components['left_panel']
        
        # Create a main content frame that can expand
        main_content_frame = ttk.Frame(left_panel)
        main_content_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
        
        # Calculate responsive face canvas size based on available space
        face_canvas_size = self._calculate_face_canvas_size()
        
        # Face image display - responsive size for better layout on different screens
        self.components['face_canvas'] = tk.Canvas(main_content_frame, width=face_canvas_size, height=face_canvas_size, bg='white', relief='sunken', bd=2)
        self.components['face_canvas'].pack(pady=(0, 15))
        
        # Person name fields
        name_frame = ttk.LabelFrame(main_content_frame, text="Person Information", padding="5")
        name_frame.pack(fill=tk.X, pady=(0, 10))
        
        # First name
        ttk.Label(name_frame, text="First name *:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5), pady=2)
        first_name_entry = ttk.Entry(name_frame, textvariable=self.components['first_name_var'], width=20)
        first_name_entry.grid(row=0, column=1, sticky=tk.W, padx=(0, 10), pady=2)
        
        # Last name
        ttk.Label(name_frame, text="Last name *:").grid(row=0, column=2, sticky=tk.W, padx=(0, 5), pady=2)
        last_name_entry = ttk.Entry(name_frame, textvariable=self.components['last_name_var'], width=20)
        last_name_entry.grid(row=0, column=3, sticky=tk.W, pady=2)
        
        # Middle name
        ttk.Label(name_frame, text="Middle name:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=2)
        middle_name_entry = ttk.Entry(name_frame, textvariable=self.components['middle_name_var'], width=20)
        middle_name_entry.grid(row=1, column=1, sticky=tk.W, padx=(0, 10), pady=2)
        
        # Maiden name
        ttk.Label(name_frame, text="Maiden name:").grid(row=1, column=2, sticky=tk.W, padx=(0, 5), pady=2)
        maiden_name_entry = ttk.Entry(name_frame, textvariable=self.components['maiden_name_var'], width=20)
        maiden_name_entry.grid(row=1, column=3, sticky=tk.W, pady=2)
        
        # Date of birth
        dob_frame = ttk.Frame(name_frame)
        dob_frame.grid(row=2, column=0, columnspan=4, sticky=tk.W, pady=2)
        
        ttk.Label(dob_frame, text="Date of birth *:").pack(side=tk.LEFT, padx=(0, 5))
        dob_entry = ttk.Entry(dob_frame, textvariable=self.components['date_of_birth_var'], width=15, state='readonly')
        dob_entry.pack(side=tk.LEFT, padx=(0, 5))
        
        def open_dob_calendar():
            self._open_date_picker(self.components['date_of_birth_var'])
        
        dob_calendar_btn = ttk.Button(dob_frame, text="📅", width=3, command=open_dob_calendar)
        dob_calendar_btn.pack(side=tk.LEFT)
        
        # Add event handlers to update Identify button state
        def update_identify_button_state(*args):
            self._update_identify_button_state()
        
        self.components['first_name_var'].trace('w', update_identify_button_state)
        self.components['last_name_var'].trace('w', update_identify_button_state)
        self.components['date_of_birth_var'].trace('w', update_identify_button_state)
        
        # Required field asterisks are now included in the label text
        
        # Add autocomplete for last name
        self._setup_last_name_autocomplete(last_name_entry)
        
        # Control buttons
        button_frame = ttk.Frame(main_content_frame)
        button_frame.pack(fill=tk.X, pady=(10, 0))
        
        self.components['identify_btn'] = ttk.Button(button_frame, text="✅ Identify", command=self._identify_face, state='disabled')
        self.components['identify_btn'].pack(side=tk.LEFT, padx=(0, 5))
        
        self.components['back_btn'] = ttk.Button(button_frame, text="⬅️ Back", command=self._go_back)
        self.components['back_btn'].pack(side=tk.LEFT, padx=(0, 5))
        
        self.components['next_btn'] = ttk.Button(button_frame, text="➡️ Next", command=self._go_next)
        self.components['next_btn'].pack(side=tk.LEFT, padx=(0, 5))
        
        self.components['quit_btn'] = ttk.Button(button_frame, text="❌ Exit Identify Faces", command=self._quit_identification)
        self.components['quit_btn'].pack(side=tk.RIGHT)
    
    def _create_right_panel_content(self):
        """Create the right panel content for similar faces"""
        right_panel = self.components['right_panel']
        
        # Select All/Clear All buttons
        select_frame = ttk.Frame(right_panel)
        select_frame.pack(fill=tk.X, pady=(0, 10))
        
        self.components['select_all_btn'] = ttk.Button(select_frame, text="Select All", command=self._select_all_similar)
        self.components['select_all_btn'].pack(side=tk.LEFT, padx=(0, 5))
        
        self.components['clear_all_btn'] = ttk.Button(select_frame, text="Clear All", command=self._clear_all_similar)
        self.components['clear_all_btn'].pack(side=tk.LEFT)
        
        # Initially disable these buttons
        self.components['select_all_btn'].config(state='disabled')
        self.components['clear_all_btn'].config(state='disabled')
        
        # Create a frame to hold canvas and scrollbar
        canvas_frame = ttk.Frame(right_panel)
        canvas_frame.pack(fill=tk.BOTH, expand=True)
        
        # Create canvas for similar faces with scrollbar
        similar_canvas = tk.Canvas(canvas_frame, bg='lightgray', relief='sunken', bd=2)
        similar_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # Similar faces scrollbars
        similar_v_scrollbar = ttk.Scrollbar(canvas_frame, orient='vertical', command=similar_canvas.yview)
        similar_v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        similar_canvas.configure(yscrollcommand=similar_v_scrollbar.set)
        
        # Create scrollable frame for similar faces
        self.components['similar_scrollable_frame'] = ttk.Frame(similar_canvas)
        similar_canvas.create_window((0, 0), window=self.components['similar_scrollable_frame'], anchor='nw')
        
        # Configure scrollable frame to expand with canvas
        def configure_scroll_region(event):
            similar_canvas.configure(scrollregion=similar_canvas.bbox("all"))
        
        self.components['similar_scrollable_frame'].bind('<Configure>', configure_scroll_region)
        
        # Store canvas reference for scrolling
        self.components['similar_canvas'] = similar_canvas
        
        # Add initial message when compare is disabled
        no_compare_label = ttk.Label(self.components['similar_scrollable_frame'], text="Enable 'Compare similar faces' to see similar faces", 
                                   foreground="gray", font=("Arial", 10))
        no_compare_label.pack(pady=20)
    
    def _start_identification(self):
        """Start the identification process - loads all faces into cache and applies initial filtering"""
        try:
            batch_size = int(self.components['batch_var'].get().strip())
            if batch_size <= 0:
                raise ValueError
        except Exception:
            messagebox.showerror("Error", "Please enter a valid positive integer for batch size.")
            return
        
        # Get date filters
        date_from = self.components['date_from_var'].get().strip() or None
        date_to = self.components['date_to_var'].get().strip() or None
        date_processed_from = self.components['date_processed_from_var'].get().strip() or None
        date_processed_to = self.components['date_processed_to_var'].get().strip() or None
        
        # Get quality filter
        min_quality = self.components['quality_filter_var'].get()
        min_quality_score = min_quality / 100.0
        
        # Get sort option
        sort_display = self.components['sort_var'].get()
        sort_by = self.sort_value_map.get(sort_display, "quality")
        
        # Load all faces into cache (only database access point)
        print("🔄 Loading faces from database...")
        self._load_all_faces_cache(date_from, date_to, date_processed_from, date_processed_to)
        
        if not self.all_faces_cache:
            messagebox.showinfo("No Faces", "🎉 All faces have been identified!")
            return
        
        # Apply quality filtering and sorting to cached data
        print("🔄 Applying quality filter and sorting...")
        self.current_faces = self._filter_cached_faces(min_quality_score, sort_by, batch_size)
        
        if not self.current_faces:
            messagebox.showinfo("No Faces", f"No faces found with quality >= {min_quality}%.\nTry lowering the quality filter.")
            return
        
        # Pre-fetch data for optimal performance
        self.identify_data_cache = self._prefetch_identify_data(self.current_faces)
        
        # Reset state
        self.current_face_index = 0
        self.face_status = {}
        self.face_person_names = {}
        self.face_selection_states = {}
        
        # Show the first face
        self._update_current_face()
        
        # Enable/disable buttons
        self._update_button_states()
        
        self.is_active = True
        print(f"✅ Started identification with {len(self.current_faces)} faces (from {len(self.all_faces_cache)} total cached)")
    
    def _get_unidentified_faces(self, batch_size: int, date_from: str = None, date_to: str = None, 
                               date_processed_from: str = None, date_processed_to: str = None,
                               min_quality_score: float = 0.0, sort_by: str = "quality") -> List[Tuple]:
        """Get unidentified faces from database with optional date filtering (no quality filtering at DB level)"""
        with self.db.get_db_connection() as conn:
            cursor = conn.cursor()
            
            # Build the SQL query with optional date filtering only
            # Include DeepFace metadata: face_confidence, quality_score, detector_backend, model_name
            query = '''
                SELECT f.id, f.photo_id, p.path, p.filename, f.location,
                       f.face_confidence, f.quality_score, f.detector_backend, f.model_name
                FROM faces f 
                JOIN photos p ON f.photo_id = p.id 
                WHERE f.person_id IS NULL 
            '''
            params = []
            
            # Add date taken filtering if specified
            if date_from:
                query += ' AND p.date_taken >= ?'
                params.append(date_from)
            
            if date_to:
                query += ' AND p.date_taken <= ?'
                params.append(date_to)
            
            # Add date processed filtering if specified
            if date_processed_from:
                query += ' AND DATE(p.date_added) >= ?'
                params.append(date_processed_from)
            
            if date_processed_to:
                query += ' AND DATE(p.date_added) <= ?'
                params.append(date_processed_to)
            
            # Order by selected sort option
            sort_clause = self._get_sort_clause(sort_by)
            query += f' ORDER BY {sort_clause}'
            
            query += ' LIMIT ?'
            params.append(batch_size)
            
            cursor.execute(query, params)
            return cursor.fetchall()
    
    def _get_sort_clause(self, sort_by: str) -> str:
        """Get SQL ORDER BY clause based on sort option"""
        sort_clauses = {
            "quality": "f.quality_score DESC",
            "quality_asc": "f.quality_score ASC",
            "date_taken": "p.date_taken DESC",
            "date_taken_asc": "p.date_taken ASC",
            "date_added": "p.date_added DESC", 
            "date_added_asc": "p.date_added ASC",
            "filename": "p.filename ASC",
            "filename_desc": "p.filename DESC",
            "confidence": "f.face_confidence DESC",
            "confidence_asc": "f.face_confidence ASC"
        }
        return sort_clauses.get(sort_by, "f.quality_score DESC")  # Default to quality DESC
    
    def _load_all_faces_cache(self, date_from: str = None, date_to: str = None, 
                             date_processed_from: str = None, date_processed_to: str = None) -> None:
        """Load all unidentified faces into cache with date filtering only"""
        with self.db.get_db_connection() as conn:
            cursor = conn.cursor()
            
            # Build the SQL query with optional date filtering only
            query = '''
                SELECT f.id, f.photo_id, p.path, p.filename, f.location,
                       f.face_confidence, f.quality_score, f.detector_backend, f.model_name,
                       p.date_taken, p.date_added
                FROM faces f 
                JOIN photos p ON f.photo_id = p.id 
                WHERE f.person_id IS NULL 
            '''
            params = []
            
            # Add date taken filtering if specified
            if date_from:
                query += ' AND p.date_taken >= ?'
                params.append(date_from)
            
            if date_to:
                query += ' AND p.date_taken <= ?'
                params.append(date_to)
            
            # Add date processed filtering if specified
            if date_processed_from:
                query += ' AND DATE(p.date_added) >= ?'
                params.append(date_processed_from)
            
            if date_processed_to:
                query += ' AND DATE(p.date_added) <= ?'
                params.append(date_processed_to)
            
            # Order by quality DESC by default (can be re-sorted later)
            query += ' ORDER BY f.quality_score DESC'
            
            cursor.execute(query, params)
            self.all_faces_cache = cursor.fetchall()
            self.cache_loaded = True
            
            if self.verbose > 0:
                print(f"📦 Loaded {len(self.all_faces_cache)} faces into cache")
    
    def _filter_cached_faces(self, min_quality_score: float = 0.0, sort_by: str = "quality", 
                            batch_size: int = None) -> List[Tuple]:
        """Filter cached faces by quality and apply sorting"""
        if not self.cache_loaded:
            return []
        
        # Filter by quality
        filtered_faces = []
        for face_data in self.all_faces_cache:
            face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model, date_taken, date_added = face_data
            quality_score = quality if quality is not None else 0.0
            
            if quality_score >= min_quality_score:
                # Return in the same format as the original method (without date_taken, date_added)
                filtered_faces.append((face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model))
        
        # Apply sorting
        if sort_by == "quality":
            filtered_faces.sort(key=lambda x: x[6] if x[6] is not None else 0.0, reverse=True)
        elif sort_by == "quality_asc":
            filtered_faces.sort(key=lambda x: x[6] if x[6] is not None else 0.0, reverse=False)
        elif sort_by == "date_taken":
            # Need to get date_taken from cache for sorting
            filtered_faces_with_dates = []
            for face_data in self.all_faces_cache:
                face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model, date_taken, date_added = face_data
                quality_score = quality if quality is not None else 0.0
                if quality_score >= min_quality_score:
                    filtered_faces_with_dates.append((face_data, date_taken))
            
            filtered_faces_with_dates.sort(key=lambda x: x[1] or "", reverse=True)
            filtered_faces = [x[0][:9] for x in filtered_faces_with_dates]  # Remove date fields
        elif sort_by == "date_taken_asc":
            filtered_faces_with_dates = []
            for face_data in self.all_faces_cache:
                face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model, date_taken, date_added = face_data
                quality_score = quality if quality is not None else 0.0
                if quality_score >= min_quality_score:
                    filtered_faces_with_dates.append((face_data, date_taken))
            
            filtered_faces_with_dates.sort(key=lambda x: x[1] or "", reverse=False)
            filtered_faces = [x[0][:9] for x in filtered_faces_with_dates]
        elif sort_by == "date_added":
            filtered_faces_with_dates = []
            for face_data in self.all_faces_cache:
                face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model, date_taken, date_added = face_data
                quality_score = quality if quality is not None else 0.0
                if quality_score >= min_quality_score:
                    filtered_faces_with_dates.append((face_data, date_added))
            
            filtered_faces_with_dates.sort(key=lambda x: x[1] or "", reverse=True)
            filtered_faces = [x[0][:9] for x in filtered_faces_with_dates]
        elif sort_by == "date_added_asc":
            filtered_faces_with_dates = []
            for face_data in self.all_faces_cache:
                face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model, date_taken, date_added = face_data
                quality_score = quality if quality is not None else 0.0
                if quality_score >= min_quality_score:
                    filtered_faces_with_dates.append((face_data, date_added))
            
            filtered_faces_with_dates.sort(key=lambda x: x[1] or "", reverse=False)
            filtered_faces = [x[0][:9] for x in filtered_faces_with_dates]
        elif sort_by == "filename":
            filtered_faces.sort(key=lambda x: x[3] or "", reverse=False)
        elif sort_by == "filename_desc":
            filtered_faces.sort(key=lambda x: x[3] or "", reverse=True)
        elif sort_by == "confidence":
            filtered_faces.sort(key=lambda x: x[5] if x[5] is not None else 0.0, reverse=True)
        elif sort_by == "confidence_asc":
            filtered_faces.sort(key=lambda x: x[5] if x[5] is not None else 0.0, reverse=False)
        
        # Apply batch size limit if specified
        if batch_size and batch_size > 0:
            filtered_faces = filtered_faces[:batch_size]
        
        return filtered_faces
    
    def _prefetch_identify_data(self, faces: List[Tuple]) -> Dict:
        """Pre-fetch all needed data to avoid repeated database queries"""
        cache = {
            'photo_paths': {},
            'people_names': [],
            'last_names': [],
            'face_encodings': {}
        }
        
        with self.db.get_db_connection() as conn:
            cursor = conn.cursor()
            
            # Get photo paths
            photo_ids = [face[1] for face in faces]
            if photo_ids:
                placeholders = ','.join(['?' for _ in photo_ids])
                cursor.execute(f'SELECT id, path FROM photos WHERE id IN ({placeholders})', photo_ids)
                cache['photo_paths'] = dict(cursor.fetchall())
            
            # Get people names
            cursor.execute('SELECT id, first_name, last_name, middle_name, maiden_name, date_of_birth FROM people ORDER BY last_name, first_name')
            cache['people_names'] = cursor.fetchall()
            
            # Pre-fetch unique last names for autocomplete (no DB during typing)
            cursor.execute('SELECT DISTINCT last_name FROM people WHERE last_name IS NOT NULL AND TRIM(last_name) <> ""')
            _last_rows = cursor.fetchall()
            cache['last_names'] = sorted({r[0].strip() for r in _last_rows if r and isinstance(r[0], str) and r[0].strip()})
            
            # Get face encodings for similar face matching
            face_ids = [face[0] for face in faces]
            if face_ids:
                placeholders = ','.join(['?' for _ in face_ids])
                cursor.execute(f'SELECT id, encoding FROM faces WHERE id IN ({placeholders})', face_ids)
                cache['face_encodings'] = dict(cursor.fetchall())
        
        return cache
    
    def _filter_unique_faces_from_list(self, faces_list: List[Tuple]) -> List[Tuple]:
        """Filter face list to show only unique ones, hiding duplicates with high/medium confidence matches"""
        if not faces_list:
            return faces_list
        
        # Extract face IDs from the list
        face_ids = [face_tuple[0] for face_tuple in faces_list]
        
        # Get face encodings from database for all faces
        face_encodings = {}
        with self.db.get_db_connection() as conn:
            cursor = conn.cursor()
            placeholders = ','.join('?' * len(face_ids))
            cursor.execute(f'''
                SELECT id, encoding 
                FROM faces 
                WHERE id IN ({placeholders}) AND encoding IS NOT NULL
            ''', face_ids)
            
            for face_id, encoding_blob in cursor.fetchall():
                try:
                    import numpy as np
                    # Load encoding as numpy array (not pickle)
                    encoding = np.frombuffer(encoding_blob, dtype=np.float64)
                    face_encodings[face_id] = encoding
                except Exception:
                    continue
        
        # If we don't have enough encodings, return original list
        if len(face_encodings) < 2:
            return faces_list
        
        # Calculate distances between all faces using existing encodings
        face_distances = {}
        face_id_list = list(face_encodings.keys())
        
        for i, face_id1 in enumerate(face_id_list):
            for j, face_id2 in enumerate(face_id_list):
                if i != j:
                    try:
                        import numpy as np
                        encoding1 = face_encodings[face_id1]
                        encoding2 = face_encodings[face_id2]
                        
                        # Calculate distance using cosine similarity (DeepFace compatible)
                        # Normalize encodings
                        encoding1_norm = encoding1 / np.linalg.norm(encoding1)
                        encoding2_norm = encoding2 / np.linalg.norm(encoding2)
                        
                        # Cosine distance = 1 - cosine similarity
                        cosine_similarity = np.dot(encoding1_norm, encoding2_norm)
                        distance = 1.0 - cosine_similarity
                        
                        face_distances[(face_id1, face_id2)] = distance
                    except Exception as e:
                        # If calculation fails, assume no match
                        print(f"⚠️  Error calculating distance between faces {face_id1} and {face_id2}: {e}")
                        face_distances[(face_id1, face_id2)] = 1.0
        
        # Apply unique faces filtering
        unique_faces = []
        seen_face_groups = set()
        
        for face_tuple in faces_list:
            face_id = face_tuple[0]
            
            # Skip if we don't have encoding for this face
            if face_id not in face_encodings:
                unique_faces.append(face_tuple)
                continue
            
            # Find all faces that match this one with high/medium confidence
            matching_face_ids = set([face_id])  # Include self
            for other_face_id in face_encodings.keys():
                if other_face_id != face_id:
                    distance = face_distances.get((face_id, other_face_id), 1.0)
                    confidence_pct, _ = self.face_processor._get_calibrated_confidence(distance)
                    
                    # If this face matches with high/medium confidence
                    if confidence_pct >= 60:
                        matching_face_ids.add(other_face_id)
            
            # Create a sorted tuple to represent this group of matching faces
            face_group = tuple(sorted(matching_face_ids))
            
            # Only show this face if we haven't seen this group before
            if face_group not in seen_face_groups:
                seen_face_groups.add(face_group)
                unique_faces.append(face_tuple)
        
        return unique_faces
    
    def _update_current_face(self):
        """Update the display for the current face"""
        if not self.current_faces or self.current_face_index >= len(self.current_faces):
            return
        
        face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model = self.current_faces[self.current_face_index]
        
        # Update info label with DeepFace metadata
        info_text = f"Face {self.current_face_index + 1} of {len(self.current_faces)} - {filename}"
        if face_conf is not None and face_conf > 0:
            info_text += f" | Detection: {face_conf*100:.1f}%"
        if quality is not None:
            info_text += f" | Quality: {quality*100:.0f}%"
        if detector:
            info_text += f" | {detector}/{model}" if model else f" | {detector}"
        self.components['info_label'].config(text=info_text)
        
        # Extract and display face crop (show_faces is always True)
        face_crop_path = self.face_processor._extract_face_crop(photo_path, location, face_id)
        self.current_face_crop_path = face_crop_path
        
        # Update face image
        self._update_face_image(face_crop_path, photo_path)
        
        # Check if face is already identified
        is_identified = face_id in self.face_status and self.face_status[face_id] == 'identified'
        
        # Restore person name input - restore saved name or use database/empty value
        self._restore_person_name_input(face_id, is_identified)
        
        # Update similar faces if compare is enabled
        if self.components['compare_var'].get():
            self._update_similar_faces(face_id)
    
    def _update_face_image(self, face_crop_path: str, photo_path: str):
        """Update the face image display"""
        try:
            if face_crop_path and os.path.exists(face_crop_path):
                # Get current canvas size
                canvas = self.components['face_canvas']
                canvas_width = canvas.winfo_width()
                canvas_height = canvas.winfo_height()
                
                # Use actual canvas size if available, otherwise use configured size
                if canvas_width <= 1 or canvas_height <= 1:
                    # Canvas not yet rendered, use configured size
                    canvas_width = canvas['width']
                    canvas_height = canvas['height']
                
                # Load and display face crop
                image = Image.open(face_crop_path)
                # Resize to exactly fill the current canvas frame
                image = image.resize((canvas_width, canvas_height), Image.Resampling.LANCZOS)
                photo = ImageTk.PhotoImage(image)
                
                # Clear canvas and display image
                canvas.delete("all")
                # Position image at top-left corner like the original
                canvas.create_image(0, 0, image=photo, anchor=tk.NW)
                canvas.image = photo  # Keep a reference
                
                # Add photo icon exactly at the image's top-right corner
                # Image starts at (0, 0) and fills the canvas
                self.gui_core.create_photo_icon(canvas, photo_path, icon_size=25,
                                              face_x=0, face_y=0, 
                                              face_width=canvas_width, face_height=canvas_height,
                                              canvas_width=canvas_width, canvas_height=canvas_height)
            else:
                # Clear canvas if no image
                canvas = self.components['face_canvas']
                canvas.delete("all")
                # Center the "No face image" text
                canvas_width = canvas.winfo_width() if canvas.winfo_width() > 1 else canvas['width']
                canvas_height = canvas.winfo_height() if canvas.winfo_height() > 1 else canvas['height']
                canvas.create_text(canvas_width//2, canvas_height//2, text="No face image", fill="gray")
        except Exception as e:
            print(f"Error updating face image: {e}")
    
    
    def _restore_person_name_input(self, face_id: int, is_identified: bool):
        """Restore person name input fields"""
        try:
            if is_identified:
                # Get person data from database
                with self.db.get_db_connection() as conn:
                    cursor = conn.cursor()
                    cursor.execute('''
                        SELECT p.first_name, p.last_name, p.middle_name, p.maiden_name, p.date_of_birth 
                        FROM people p 
                        JOIN faces f ON p.id = f.person_id 
                        WHERE f.id = ?
                    ''', (face_id,))
                    result = cursor.fetchone()
                    
                    if result:
                        first_name, last_name, middle_name, maiden_name, date_of_birth = result
                        self.components['first_name_var'].set(first_name or "")
                        self.components['last_name_var'].set(last_name or "")
                        self.components['middle_name_var'].set(middle_name or "")
                        self.components['maiden_name_var'].set(maiden_name or "")
                        self.components['date_of_birth_var'].set(date_of_birth or "")
                    else:
                        # Clear all fields if no person found
                        self._clear_form()
            else:
                # Restore from saved data if available
                if face_id in self.face_person_names:
                    person_data = self.face_person_names[face_id]
                    if isinstance(person_data, dict):
                        self.components['first_name_var'].set(person_data.get('first_name', ''))
                        self.components['last_name_var'].set(person_data.get('last_name', ''))
                        self.components['middle_name_var'].set(person_data.get('middle_name', ''))
                        self.components['maiden_name_var'].set(person_data.get('maiden_name', ''))
                        self.components['date_of_birth_var'].set(person_data.get('date_of_birth', ''))
                    else:
                        # Legacy string format
                        self.components['first_name_var'].set(person_data or "")
                        self.components['last_name_var'].set("")
                        self.components['middle_name_var'].set("")
                        self.components['maiden_name_var'].set("")
                        self.components['date_of_birth_var'].set("")
                else:
                    # Clear all fields for new face
                    self._clear_form()
                    
        except Exception as e:
            print(f"❌ Error restoring person name input: {e}")
            # Clear form on error
            self._clear_form()
    
    def _update_identify_button_state(self):
        """Update the identify button state based on form completion"""
        first_name = self.components['first_name_var'].get().strip()
        last_name = self.components['last_name_var'].get().strip()
        date_of_birth = self.components['date_of_birth_var'].get().strip()
        
        # Enable button only if all required fields are filled
        if first_name and last_name and date_of_birth:
            self.components['identify_btn'].config(state='normal')
        else:
            self.components['identify_btn'].config(state='disabled')
    
    
    def _setup_last_name_autocomplete(self, last_name_entry):
        """Setup autocomplete functionality for last name field - exactly like old implementation"""
        # Create listbox for suggestions (as overlay attached to main frame, not clipped by frames)
        last_name_listbox = tk.Listbox(self.main_frame, height=8)
        last_name_listbox.place_forget()  # Hide initially
        
        def _show_suggestions():
            """Show filtered suggestions in listbox"""
            all_last_names = self.identify_data_cache.get('last_names', [])
            typed = self.components['last_name_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 all_last_names if n.lower().startswith(low)][:10]
            
            # Update listbox
            last_name_listbox.delete(0, tk.END)
            for name in filtered:
                last_name_listbox.insert(tk.END, name)
            
            # Show listbox if we have suggestions (as overlay)
            if filtered:
                # Ensure geometry is up to date before positioning
                self.main_frame.update_idletasks()
                # Absolute coordinates of entry relative to screen
                entry_root_x = last_name_entry.winfo_rootx()
                entry_root_y = last_name_entry.winfo_rooty()
                entry_height = last_name_entry.winfo_height()
                # Convert to coordinates relative to main frame
                main_frame_origin_x = self.main_frame.winfo_rootx()
                main_frame_origin_y = self.main_frame.winfo_rooty()
                place_x = entry_root_x - main_frame_origin_x
                place_y = entry_root_y - main_frame_origin_y + entry_height
                place_width = last_name_entry.winfo_width()
                # Calculate how many rows fit to bottom of window
                available_px = max(60, self.main_frame.winfo_height() - place_y - 8)
                # Approximate row height in pixels; 18 is a reasonable default for Tk listbox rows
                approx_row_px = 18
                rows_fit = max(3, min(len(filtered), available_px // approx_row_px))
                last_name_listbox.configure(height=rows_fit)
                last_name_listbox.place(x=place_x, y=place_y, width=place_width)
                last_name_listbox.selection_clear(0, tk.END)
                last_name_listbox.selection_set(0)  # Select first item
                last_name_listbox.activate(0)  # Activate first item
            else:
                last_name_listbox.place_forget()
        
        def _hide_suggestions():
            """Hide the suggestions listbox"""
            last_name_listbox.place_forget()
        
        def _on_listbox_select(event=None):
            """Handle listbox selection and hide list"""
            selection = last_name_listbox.curselection()
            if selection:
                selected_name = last_name_listbox.get(selection[0])
                self.components['last_name_var'].set(selected_name)
            _hide_suggestions()
            last_name_entry.focus_set()

        def _on_listbox_click(event):
            """Handle mouse click selection"""
            try:
                index = last_name_listbox.nearest(event.y)
                if index is not None and index >= 0:
                    selected_name = last_name_listbox.get(index)
                    self.components['last_name_var'].set(selected_name)
            except:
                pass
            _hide_suggestions()
            last_name_entry.focus_set()
            return 'break'
        
        def _on_key_press(event):
            """Handle key navigation in entry"""
            nonlocal navigating_to_listbox, escape_pressed, enter_pressed
            if event.keysym == 'Down':
                if last_name_listbox.winfo_ismapped():
                    navigating_to_listbox = True
                    last_name_listbox.focus_set()
                    last_name_listbox.selection_clear(0, tk.END)
                    last_name_listbox.selection_set(0)
                    last_name_listbox.activate(0)
                return 'break'
            elif event.keysym == 'Escape':
                escape_pressed = True
                _hide_suggestions()
                return 'break'
            elif event.keysym == 'Return':
                enter_pressed = True
                return 'break'
        
        def _on_listbox_key(event):
            """Handle key navigation in listbox"""
            nonlocal enter_pressed, escape_pressed
            if event.keysym == 'Return':
                enter_pressed = True
                _on_listbox_select(event)
                return 'break'
            elif event.keysym == 'Escape':
                escape_pressed = True
                _hide_suggestions()
                last_name_entry.focus_set()
                return 'break'
            elif event.keysym == 'Up':
                selection = last_name_listbox.curselection()
                if selection and selection[0] > 0:
                    # Move up in listbox
                    last_name_listbox.selection_clear(0, tk.END)
                    last_name_listbox.selection_set(selection[0] - 1)
                    last_name_listbox.see(selection[0] - 1)
                else:
                    # At top, go back to entry field
                    _hide_suggestions()
                    last_name_entry.focus_set()
                return 'break'
            elif event.keysym == 'Down':
                selection = last_name_listbox.curselection()
                max_index = last_name_listbox.size() - 1
                if selection and selection[0] < max_index:
                    # Move down in listbox
                    last_name_listbox.selection_clear(0, tk.END)
                    last_name_listbox.selection_set(selection[0] + 1)
                    last_name_listbox.see(selection[0] + 1)
                return 'break'
        
        # Track if we're navigating to listbox to prevent auto-hide
        navigating_to_listbox = False
        escape_pressed = False
        enter_pressed = False
        
        def _safe_hide_suggestions():
            """Hide suggestions only if not navigating to listbox"""
            nonlocal navigating_to_listbox
            if not navigating_to_listbox:
                _hide_suggestions()
            navigating_to_listbox = False
        
        def _safe_show_suggestions():
            """Show suggestions only if escape or enter wasn't just pressed"""
            nonlocal escape_pressed, enter_pressed
            if not escape_pressed and not enter_pressed:
                _show_suggestions()
            escape_pressed = False
            enter_pressed = False
        
        # Bind events
        last_name_entry.bind('<KeyRelease>', lambda e: _safe_show_suggestions())
        last_name_entry.bind('<KeyPress>', _on_key_press)
        last_name_entry.bind('<FocusOut>', lambda e: self.main_frame.after(150, _safe_hide_suggestions))  # Delay to allow listbox clicks
        last_name_listbox.bind('<Button-1>', _on_listbox_click)
        last_name_listbox.bind('<KeyPress>', _on_listbox_key)
        last_name_listbox.bind('<Double-Button-1>', _on_listbox_click)
    
    def _clear_form(self):
        """Clear the identification form"""
        self.components['first_name_var'].set('')
        self.components['last_name_var'].set('')
        self.components['middle_name_var'].set('')
        self.components['maiden_name_var'].set('')
        self.components['date_of_birth_var'].set('')
    
    def _update_similar_faces(self, face_id: int):
        """Update the similar faces panel"""
        # Enable select all/clear all buttons
        self.components['select_all_btn'].config(state='normal')
        self.components['clear_all_btn'].config(state='normal')
        
        # Find similar faces using the filtered method like the original
        similar_faces = self.face_processor._get_filtered_similar_faces(face_id, DEFAULT_FACE_TOLERANCE, include_same_photo=False, face_status=self.face_status)
        
        # Clear existing similar faces
        scrollable_frame = self.components['similar_scrollable_frame']
        for widget in scrollable_frame.winfo_children():
            widget.destroy()
        
        # Display similar faces
        if similar_faces:
            
            # Sort by confidence (distance) - highest confidence first (lowest distance)
            similar_faces.sort(key=lambda x: x['distance'])
            
            # Ensure photo paths are available for similar faces
            self._ensure_photo_paths_for_similar_faces(similar_faces)
            
            # Display similar faces using the original approach
            self._display_similar_faces_in_panel(scrollable_frame, similar_faces, face_id)
            
            # Update canvas scroll region
            canvas = self.components['similar_canvas']
            canvas.update_idletasks()
            canvas.configure(scrollregion=canvas.bbox("all"))
        else:
            no_faces_label = ttk.Label(scrollable_frame, text="No similar faces found", 
                                     foreground="gray", font=("Arial", 10))
            no_faces_label.pack(pady=20)
    
    def _ensure_photo_paths_for_similar_faces(self, similar_faces):
        """Ensure photo paths are available in cache for similar faces"""
        # Get photo IDs from similar faces that are not in cache
        missing_photo_ids = []
        for face_data in similar_faces:
            photo_id = face_data['photo_id']
            if photo_id not in self.identify_data_cache['photo_paths']:
                missing_photo_ids.append(photo_id)
        
        # Fetch missing photo paths from database
        if missing_photo_ids:
            with self.db.get_db_connection() as conn:
                cursor = conn.cursor()
                placeholders = ','.join(['?' for _ in missing_photo_ids])
                cursor.execute(f'SELECT id, path FROM photos WHERE id IN ({placeholders})', missing_photo_ids)
                missing_photo_paths = dict(cursor.fetchall())
                
                # Add to cache
                self.identify_data_cache['photo_paths'].update(missing_photo_paths)
    
    def _display_similar_faces_in_panel(self, parent_frame, similar_faces_data, current_face_id):
        """Display similar faces in a panel - based on original implementation"""
        # Store similar face variables for Select All/Clear All functionality
        similar_face_vars = []
        
        # Create all similar faces using auto-match style display
        for i, face_data in enumerate(similar_faces_data[:10]):  # Limit to 10 faces
            similar_face_id = face_data['face_id']
            filename = face_data['filename']
            distance = face_data['distance']
            quality = face_data.get('quality_score', 0.5)
            
            # Calculate calibrated confidence (actual match probability)
            confidence_pct, confidence_desc = self.face_processor._get_calibrated_confidence(distance)
            
            # Create match frame using auto-match style
            match_frame = ttk.Frame(parent_frame)
            match_frame.pack(fill=tk.X, padx=5, pady=5)
            
            # Checkbox for this match (reusing auto-match checkbox style)
            match_var = tk.BooleanVar()
            similar_face_vars.append((similar_face_id, match_var))
            
            # Store the variable for later use
            if similar_face_id not in self.face_selection_states:
                self.face_selection_states[similar_face_id] = {}
            self.face_selection_states[similar_face_id]['var'] = match_var
            
            # Restore previous checkbox state if available (auto-match style)
            if similar_face_id in self.face_selection_states and 'var' in self.face_selection_states[similar_face_id]:
                prev_var = self.face_selection_states[similar_face_id]['var']
                if hasattr(prev_var, 'get'):
                    match_var.set(prev_var.get())
            
            # Checkbox
            checkbox = ttk.Checkbutton(match_frame, variable=match_var)
            checkbox.pack(side=tk.LEFT, padx=(0, 5))
            
            # Face image (moved to be right after checkbox)
            try:
                photo_id = face_data['photo_id']
                location = face_data['location']
                photo_path = self.identify_data_cache['photo_paths'].get(photo_id, '')
                
                if photo_path:
                    face_crop_path = self.face_processor._extract_face_crop(photo_path, location, similar_face_id)
                    if face_crop_path and os.path.exists(face_crop_path):
                        image = Image.open(face_crop_path)
                        image.thumbnail((100, 100), Image.Resampling.LANCZOS)
                        photo = ImageTk.PhotoImage(image)
                        
                        # Create a canvas for the face image to allow photo icon drawing
                        face_canvas = tk.Canvas(match_frame, width=100, height=100, highlightthickness=0)
                        face_canvas.pack(side=tk.LEFT, padx=(0, 5))
                        face_canvas.create_image(50, 50, image=photo, anchor=tk.CENTER)
                        face_canvas.image = photo  # Keep reference
                        
                        # Add photo icon exactly at the image's top-right corner
                        self.gui_core.create_photo_icon(face_canvas, photo_path, icon_size=20,
                                                      face_x=0, face_y=0, 
                                                      face_width=100, face_height=100,
                                                      canvas_width=100, canvas_height=100)
                    else:
                        # Face crop extraction failed or file doesn't exist
                        print(f"Face crop not available for face {similar_face_id}: {face_crop_path}")
                        # Create placeholder canvas
                        face_canvas = tk.Canvas(match_frame, width=100, height=100, highlightthickness=0, bg='lightgray')
                        face_canvas.pack(side=tk.LEFT, padx=(0, 5))
                        face_canvas.create_text(50, 50, text="No\nImage", fill="gray", font=("Arial", 10))
                else:
                    # Photo path not found in cache
                    print(f"Photo path not found for photo_id {photo_id} in cache")
                    # Create placeholder canvas
                    face_canvas = tk.Canvas(match_frame, width=100, height=100, highlightthickness=0, bg='lightgray')
                    face_canvas.pack(side=tk.LEFT, padx=(0, 5))
                    face_canvas.create_text(50, 50, text="No\nPath", fill="gray", font=("Arial", 10))
            except Exception as e:
                print(f"Error creating similar face widget for face {similar_face_id}: {e}")
                # Create placeholder canvas on error
                face_canvas = tk.Canvas(match_frame, width=100, height=100, highlightthickness=0, bg='lightgray')
                face_canvas.pack(side=tk.LEFT, padx=(0, 5))
                face_canvas.create_text(50, 50, text="Error", fill="red", font=("Arial", 10))
            
            # Confidence label with color coding and description
            confidence_text = f"{confidence_pct:.0f}% {confidence_desc}"
            if confidence_pct >= 80:
                color = "green"
            elif confidence_pct >= 70:
                color = "orange"
            elif confidence_pct >= 60:
                color = "red"
            else:
                color = "gray"
            
            confidence_label = ttk.Label(match_frame, text=confidence_text, foreground=color, font=("Arial", 8, "bold"))
            confidence_label.pack(side=tk.LEFT, padx=(0, 5))
            
            # Filename
            filename_label = ttk.Label(match_frame, text=filename, font=("Arial", 8))
            filename_label.pack(side=tk.LEFT, padx=(0, 5))
        
        # Store the similar face variables for Select All/Clear All functionality
        self.similar_face_vars = similar_face_vars
    
    def _get_confidence_description(self, confidence_pct: float) -> str:
        """Get confidence description based on percentage"""
        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 _identify_face(self):
        """Identify the current face"""
        first_name = self.components['first_name_var'].get().strip()
        last_name = self.components['last_name_var'].get().strip()
        date_of_birth = self.components['date_of_birth_var'].get().strip()
        
        if not first_name or not last_name or not date_of_birth:
            self.gui_core.create_large_messagebox(self.main_frame, "Missing Information", "Please fill in first name, last name, and date of birth.", "warning")
            return
        
        if not self.current_faces or self.current_face_index >= len(self.current_faces):
            return
        
        face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model = self.current_faces[self.current_face_index]
        
        # Get person data
        person_data = {
            'first_name': first_name,
            'last_name': last_name,
            'middle_name': self.components['middle_name_var'].get().strip(),
            'maiden_name': self.components['maiden_name_var'].get().strip(),
            'date_of_birth': date_of_birth
        }
        
        # Store the identification
        self.face_person_names[face_id] = person_data
        self.face_status[face_id] = 'identified'
        
        # Save to database
        self._save_identification(face_id, person_data)
        
        # If compare mode is active, identify selected similar faces
        if self.components['compare_var'].get():
            self._identify_selected_similar_faces(person_data)
        
        # Clear the form after successful identification
        self._clear_form()
        
        # Move to next face
        self._go_next()
    
    def _save_identification(self, face_id: int, person_data: Dict):
        """Save face identification to database"""
        try:
            with self.db.get_db_connection() as conn:
                cursor = conn.cursor()
                
                # Normalize names to title case for case-insensitive matching
                normalized_data = {
                    'first_name': person_data['first_name'].strip().title(),
                    'last_name': person_data['last_name'].strip().title(),
                    'middle_name': person_data['middle_name'].strip().title() if person_data['middle_name'] else '',
                    'maiden_name': person_data['maiden_name'].strip().title() if person_data['maiden_name'] else '',
                    'date_of_birth': person_data['date_of_birth'].strip()
                }
                
                # Check if person already exists (case-insensitive)
                cursor.execute('''
                    SELECT id FROM people 
                    WHERE LOWER(first_name) = LOWER(?) AND LOWER(last_name) = LOWER(?) 
                    AND LOWER(COALESCE(middle_name, '')) = LOWER(?) AND LOWER(COALESCE(maiden_name, '')) = LOWER(?) 
                    AND date_of_birth = ?
                ''', (normalized_data['first_name'], normalized_data['last_name'], 
                      normalized_data['middle_name'], normalized_data['maiden_name'], normalized_data['date_of_birth']))
                
                person_row = cursor.fetchone()
                if person_row:
                    person_id = person_row[0]
                else:
                    # Create new person
                    cursor.execute('''
                        INSERT INTO people (first_name, last_name, middle_name, maiden_name, date_of_birth)
                        VALUES (?, ?, ?, ?, ?)
                    ''', (normalized_data['first_name'], normalized_data['last_name'], 
                          normalized_data['middle_name'], normalized_data['maiden_name'], normalized_data['date_of_birth']))
                    person_id = cursor.lastrowid
                
                # Update face with person_id
                cursor.execute('UPDATE faces SET person_id = ? WHERE id = ?', (person_id, face_id))
                
                conn.commit()
                
        except Exception as e:
            print(f"Error saving identification: {e}")
            messagebox.showerror("Error", f"Failed to save identification: {e}")
    
    def _identify_selected_similar_faces(self, person_data: Dict):
        """Identify selected similar faces with the same person"""
        if hasattr(self, 'similar_face_vars'):
            for face_id, var in self.similar_face_vars:
                if var.get():
                    # This face is selected, identify it
                    self.face_person_names[face_id] = person_data
                    self.face_status[face_id] = 'identified'
                    self._save_identification(face_id, person_data)
    
    def _go_back(self):
        """Go back to the previous face that meets quality criteria"""
        # Validate navigation (check for unsaved changes)
        validation_result = self._validate_navigation()
        if validation_result == 'cancel':
            return  # Cancel navigation
        elif validation_result == 'save_and_continue':
            # Save the current identification before proceeding
            if self.current_faces and self.current_face_index < len(self.current_faces):
                face_id, _, _, _, _, _, _, _, _ = self.current_faces[self.current_face_index]
                first_name = self.components['first_name_var'].get().strip()
                last_name = self.components['last_name_var'].get().strip()
                date_of_birth = self.components['date_of_birth_var'].get().strip()
                if first_name and last_name and date_of_birth:
                    person_data = {
                        'first_name': first_name,
                        'last_name': last_name,
                        'middle_name': self.components['middle_name_var'].get().strip(),
                        'maiden_name': self.components['maiden_name_var'].get().strip(),
                        'date_of_birth': date_of_birth
                    }
                    self.face_person_names[face_id] = person_data
                    self.face_status[face_id] = 'identified'
        elif validation_result == 'discard_and_continue':
            # Clear the form but don't save
            self._clear_form()
        
        # Get quality filter
        min_quality = self.components['quality_filter_var'].get()
        min_quality_score = min_quality / 100.0
        
        # Find previous qualifying face
        found = False
        
        for i in range(self.current_face_index - 1, -1, -1):
            _, _, _, _, _, _, quality, _, _ = self.current_faces[i]
            quality_score = quality if quality is not None else 0.0
            
            if quality_score >= min_quality_score:
                self.current_face_index = i
                found = True
                break
        
        if found:
            self._update_current_face()
            self._update_button_states()
        else:
            messagebox.showinfo("No Previous Face", 
                              f"No previous face with quality >= {min_quality}%.")
    
    def _go_next(self):
        """Go to the next face that meets quality criteria"""
        # Validate navigation (check for unsaved changes)
        validation_result = self._validate_navigation()
        if validation_result == 'cancel':
            return  # Cancel navigation
        elif validation_result == 'save_and_continue':
            # Save the current identification before proceeding
            if self.current_faces and self.current_face_index < len(self.current_faces):
                face_id, _, _, _, _, _, _, _, _ = self.current_faces[self.current_face_index]
                first_name = self.components['first_name_var'].get().strip()
                last_name = self.components['last_name_var'].get().strip()
                date_of_birth = self.components['date_of_birth_var'].get().strip()
                if first_name and last_name and date_of_birth:
                    person_data = {
                        'first_name': first_name,
                        'last_name': last_name,
                        'middle_name': self.components['middle_name_var'].get().strip(),
                        'maiden_name': self.components['maiden_name_var'].get().strip(),
                        'date_of_birth': date_of_birth
                    }
                    self.face_person_names[face_id] = person_data
                    self.face_status[face_id] = 'identified'
        elif validation_result == 'discard_and_continue':
            # Clear the form but don't save
            self._clear_form()
        
        # Get quality filter
        min_quality = self.components['quality_filter_var'].get()
        min_quality_score = min_quality / 100.0
        
        # Find next qualifying face
        original_index = self.current_face_index
        found = False
        
        for i in range(self.current_face_index + 1, len(self.current_faces)):
            _, _, _, _, _, _, quality, _, _ = self.current_faces[i]
            quality_score = quality if quality is not None else 0.0
            
            if quality_score >= min_quality_score:
                self.current_face_index = i
                found = True
                break
        
        if found:
            self._update_current_face()
            self._update_button_states()
        else:
            # No more qualifying faces in current batch, try to load more
            self._load_more_faces()
    
    def _load_more_faces(self):
        """Load more faces from cache if available"""
        if not self.cache_loaded:
            messagebox.showinfo("Complete", "🎉 All faces have been identified!")
            self._quit_identification()
            return
        
        # Get quality filter
        min_quality = self.components['quality_filter_var'].get()
        min_quality_score = min_quality / 100.0
        
        # Get sort option
        sort_display = self.components['sort_var'].get()
        sort_by = self.sort_value_map.get(sort_display, "quality")
        
        # Get batch size
        try:
            batch_size = int(self.components['batch_var'].get().strip())
        except Exception:
            batch_size = DEFAULT_BATCH_SIZE
        
        # Get more faces from cache (extend current batch)
        current_batch_size = len(self.current_faces)
        new_batch_size = current_batch_size + batch_size
        
        more_faces = self._filter_cached_faces(min_quality_score, sort_by, new_batch_size)
        
        if len(more_faces) > current_batch_size:
            # Add new faces to current faces
            new_faces = more_faces[current_batch_size:]
            self.current_faces.extend(new_faces)
            self.current_face_index += 1
            self._update_current_face()
            self._update_button_states()
            print(f"✅ Loaded {len(new_faces)} more faces from cache")
        else:
            # No more faces
            messagebox.showinfo("Complete", "🎉 All faces have been identified!")
            self._quit_identification()
    
    def _update_button_states(self):
        """Update button states based on current position"""
        self.components['back_btn'].config(state='normal' if self.current_face_index > 0 else 'disabled')
        self.components['next_btn'].config(state='normal' if self.current_face_index < len(self.current_faces) - 1 else 'disabled')
    
    def _select_all_similar(self):
        """Select all similar faces"""
        if hasattr(self, 'similar_face_vars'):
            for face_id, var in self.similar_face_vars:
                var.set(True)
    
    def _clear_all_similar(self):
        """Clear all similar face selections"""
        if hasattr(self, 'similar_face_vars'):
            for face_id, var in self.similar_face_vars:
                var.set(False)
    
    def _quit_identification(self):
        """Quit the identification process"""
        # First check for unsaved changes in the current form
        validation_result = self._validate_navigation()
        if validation_result == 'cancel':
            return  # Cancel quit
        elif validation_result == 'save_and_continue':
            # Save the current identification before proceeding
            if self.current_faces and self.current_face_index < len(self.current_faces):
                face_id, _, _, _, _, _, _, _, _ = self.current_faces[self.current_face_index]
                first_name = self.components['first_name_var'].get().strip()
                last_name = self.components['last_name_var'].get().strip()
                date_of_birth = self.components['date_of_birth_var'].get().strip()
                if first_name and last_name and date_of_birth:
                    person_data = {
                        'first_name': first_name,
                        'last_name': last_name,
                        'middle_name': self.components['middle_name_var'].get().strip(),
                        'maiden_name': self.components['maiden_name_var'].get().strip(),
                        'date_of_birth': date_of_birth
                    }
                    self.face_person_names[face_id] = person_data
                    self.face_status[face_id] = 'identified'
        elif validation_result == 'discard_and_continue':
            # Clear the form but don't save
            self._clear_form()
        
        # Check for pending identifications
        pending_identifications = self._get_pending_identifications()
        
        if pending_identifications:
            result = self.gui_core.create_large_messagebox(
                self.main_frame,
                "Save Pending Identifications?",
                f"You have {len(pending_identifications)} pending identifications.\n\n"
                "Do you want to save them before quitting?\n\n"
                "• Yes: Save all pending identifications and quit\n"
                "• No: Quit without saving\n"
                "• Cancel: Return to identification",
                "askyesnocancel"
            )
            
            if result is True:  # Yes - Save and quit
                self._save_all_pending_identifications()
            elif result is False:  # No - Quit without saving
                pass
            else:  # Cancel - Don't quit
                return
        
        # Clean up
        self._cleanup()
        self.is_active = False
        
        # Navigate to home if callback is available (dashboard mode)
        if self.on_navigate_home:
            self.on_navigate_home()
    
    def _validate_navigation(self):
        """Validate that navigation is safe (no unsaved changes)"""
        # Check if there are any unsaved changes in the form
        first_name = self.components['first_name_var'].get().strip()
        last_name = self.components['last_name_var'].get().strip()
        date_of_birth = self.components['date_of_birth_var'].get().strip()
        
        # If all three required fields are filled, ask for confirmation
        if first_name and last_name and date_of_birth:
            result = self.gui_core.create_large_messagebox(
                self.main_frame,
                "Unsaved Changes",
                "You have unsaved changes in the identification form.\n\n"
                "Do you want to save them before continuing?\n\n"
                "• Yes: Save current identification and continue\n"
                "• No: Discard changes and continue\n"
                "• Cancel: Stay on current face",
                "askyesnocancel"
            )
            
            if result is True:  # Yes - Save and continue
                return 'save_and_continue'
            elif result is False:  # No - Discard and continue
                return 'discard_and_continue'
            else:  # Cancel - Don't navigate
                return 'cancel'
        
        return 'continue'  # No changes, safe to continue

    def _get_pending_identifications(self) -> List[int]:
        """Get list of face IDs with pending identifications"""
        pending = []
        for face_id, person_data in self.face_person_names.items():
            if face_id not in self.face_status or self.face_status[face_id] != 'identified':
                # Check if form has complete data
                if (person_data.get('first_name') and 
                    person_data.get('last_name') and 
                    person_data.get('date_of_birth')):
                    pending.append(face_id)
        return pending
    
    def _save_all_pending_identifications(self):
        """Save all pending identifications"""
        for face_id in self._get_pending_identifications():
            person_data = self.face_person_names[face_id]
            self._save_identification(face_id, person_data)
            self.face_status[face_id] = 'identified'
    
    def _cleanup(self):
        """Clean up resources"""
        if self.current_face_crop_path:
            self.face_processor.cleanup_face_crops(self.current_face_crop_path)
        
        # Clear state
        self.current_faces = []
        self.current_face_index = 0
        self.face_status = {}
        self.face_person_names = {}
        self.face_selection_states = {}
        self.identify_data_cache = {}
        if hasattr(self, 'similar_face_vars'):
            self.similar_face_vars = []
        
        # Clear cache
        self.all_faces_cache = []
        self.cache_loaded = False
        
        # Clear right panel content
        scrollable_frame = self.components['similar_scrollable_frame']
        for widget in scrollable_frame.winfo_children():
            widget.destroy()
        # Show message that compare is disabled
        no_compare_label = ttk.Label(scrollable_frame, text="Enable 'Compare similar faces' to see similar faces", 
                                   foreground="gray", font=("Arial", 10))
        no_compare_label.pack(pady=20)
        
        # Clear form
        self._clear_form()
        
        # Clear info label
        self.components['info_label'].config(text="")
        
        # Clear face canvas
        self.components['face_canvas'].delete("all")
    
    def _apply_date_filters(self):
        """Apply quality and sort filters to cached data (no database access)"""
        # Check if cache is loaded
        if not self.cache_loaded:
            messagebox.showinfo("Start Identification First", 
                              "Please click 'Start Identification' to load faces before applying filters.")
            return
        
        # Get quality filter
        min_quality = self.components['quality_filter_var'].get()
        min_quality_score = min_quality / 100.0
        
        # Get sort option
        sort_display = self.components['sort_var'].get()
        sort_by = self.sort_value_map.get(sort_display, "quality")
        
        # Get batch size
        try:
            batch_size = int(self.components['batch_var'].get().strip())
        except Exception:
            batch_size = DEFAULT_BATCH_SIZE
        
        # Apply filtering to cached data
        print("🔄 Applying filters to cached data...")
        self.current_faces = self._filter_cached_faces(min_quality_score, sort_by, batch_size)
        
        if not self.current_faces:
            messagebox.showinfo("No Faces Found", 
                              f"No faces found with quality >= {min_quality}%.\n"
                              f"Try lowering the quality filter or click 'Start Identification' to reload from database.")
            return
        
        # Reset state
        self.current_face_index = 0
        self.face_status = {}
        self.face_person_names = {}
        self.face_selection_states = {}
        
        # Pre-fetch data
        self.identify_data_cache = self._prefetch_identify_data(self.current_faces)
        
        # Show the first face
        self._update_current_face()
        self._update_button_states()
        
        self.is_active = True
        print(f"✅ Applied filters: {len(self.current_faces)} faces (from {len(self.all_faces_cache)} total cached)")
    
    def _find_next_qualifying_face(self, min_quality: int):
        """Find the next face that meets the quality criteria"""
        if not self.current_faces:
            return
        
        min_quality_score = min_quality / 100.0  # Convert percentage to 0-1 scale
        
        # Search from current index forward
        original_index = self.current_face_index
        found = False
        
        for i in range(self.current_face_index, len(self.current_faces)):
            _, _, _, _, _, _, quality, _, _ = self.current_faces[i]
            quality_score = quality if quality is not None else 0.0
            
            if quality_score >= min_quality_score:
                self.current_face_index = i
                found = True
                break
        
        # If not found forward, search from beginning
        if not found:
            for i in range(0, original_index):
                _, _, _, _, _, _, quality, _, _ = self.current_faces[i]
                quality_score = quality if quality is not None else 0.0
                
                if quality_score >= min_quality_score:
                    self.current_face_index = i
                    found = True
                    break
        
        if found:
            self._update_current_face()
            self._update_button_states()
        else:
            messagebox.showinfo("No Qualifying Faces", 
                              f"No faces found with quality >= {min_quality}%.\n"
                              f"Lower the quality filter to see more faces.")
    
    def _open_date_picker(self, date_var: tk.StringVar):
        """Open date picker dialog"""
        current_date = date_var.get()
        selected_date = self.gui_core.create_calendar_dialog(None, "Select Date", current_date)
        if selected_date is not None:
            date_var.set(selected_date)
    
    def activate(self):
        """Activate the panel"""
        self.is_active = True
    
    def deactivate(self):
        """Deactivate the panel"""
        if self.is_active:
            self._cleanup()
            self.is_active = False
    
    def update_layout(self):
        """Update panel layout for responsiveness"""
        if hasattr(self, 'components') and 'similar_canvas' in self.components:
            # Update similar faces canvas scroll region
            canvas = self.components['similar_canvas']
            canvas.update_idletasks()
            canvas.configure(scrollregion=canvas.bbox("all"))
        
        # Update face canvas size if needed (for window resize events)
        if hasattr(self, 'components') and 'face_canvas' in self.components:
            self._update_face_canvas_size()
