#!/usr/bin/env python3
"""
Modify Identified Faces GUI implementation for PunimTag
"""

import os
import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk

from config import DEFAULT_FACE_TOLERANCE
from database import DatabaseManager
from face_processing import FaceProcessor
from gui_core import GUICore


class ModifyIdentifiedGUI:
    """Handles the View and Modify Identified Faces GUI interface"""

    def __init__(self, db_manager: DatabaseManager, face_processor: FaceProcessor, verbose: int = 0):
        self.db = db_manager
        self.face_processor = face_processor
        self.verbose = verbose
        self.gui_core = GUICore()

    def modifyidentified(self) -> int:
        """Open the View and Modify Identified Faces window"""
        # Simple tooltip implementation
        class ToolTip:
            def __init__(self, widget, text):
                self.widget = widget
                self.text = text
                self.tooltip_window = None
                self.widget.bind("<Enter>", self.on_enter)
                self.widget.bind("<Leave>", self.on_leave)

            def on_enter(self, event=None):
                if self.tooltip_window or not self.text:
                    return
                x, y, _, _ = self.widget.bbox("insert") if hasattr(self.widget, 'bbox') else (0, 0, 0, 0)
                x += self.widget.winfo_rootx() + 25
                y += self.widget.winfo_rooty() + 25

                self.tooltip_window = tw = tk.Toplevel(self.widget)
                tw.wm_overrideredirect(True)
                tw.wm_geometry(f"+{x}+{y}")

                label = tk.Label(tw, text=self.text, justify=tk.LEFT,
                                 background="#ffffe0", relief=tk.SOLID, borderwidth=1,
                                 font=("tahoma", "8", "normal"))
                label.pack(ipadx=1)

            def on_leave(self, event=None):
                if self.tooltip_window:
                    self.tooltip_window.destroy()
                    self.tooltip_window = None

        # Create the main window
        root = tk.Tk()
        root.title("View and Modify Identified Faces")
        root.resizable(True, True)

        # Track window state to prevent multiple destroy calls
        window_destroyed = False
        temp_crops = []
        right_panel_images = []  # Keep PhotoImage refs alive
        selected_person_id = None

        # Hide window initially to prevent flash at corner
        root.withdraw()

        # Set up protocol handler for window close button (X)
        def on_closing():
            nonlocal window_destroyed
            # Cleanup temp crops
            for crop in list(temp_crops):
                try:
                    if os.path.exists(crop):
                        os.remove(crop)
                except Exception:
                    pass
            temp_crops.clear()
            if not window_destroyed:
                window_destroyed = True
                try:
                    root.destroy()
                except tk.TclError:
                    pass  # Window already destroyed

        root.protocol("WM_DELETE_WINDOW", on_closing)

        # Set up window size saving
        saved_size = self.gui_core.setup_window_size_saving(root, "gui_config.json")

        # Create main frame
        main_frame = ttk.Frame(root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # Configure grid weights
        root.columnconfigure(0, weight=1)
        root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=2)
        main_frame.rowconfigure(1, weight=1)

        # Title label
        title_label = ttk.Label(main_frame, text="View and Modify Identified Faces", font=("Arial", 16, "bold"))
        title_label.grid(row=0, column=0, columnspan=2, pady=(0, 10), sticky=tk.W)

        # Left panel: People list
        people_frame = ttk.LabelFrame(main_frame, text="People", padding="10")
        people_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 8))
        people_frame.columnconfigure(0, weight=1)

        # Search controls (Last Name) with label under the input (match auto-match style)
        last_name_search_var = tk.StringVar()
        search_frame = ttk.Frame(people_frame)
        search_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 6))

        # Entry on the left
        search_entry = ttk.Entry(search_frame, textvariable=last_name_search_var, width=20)
        search_entry.grid(row=0, column=0, sticky=tk.W)

        # Buttons to the right of the entry
        buttons_row = ttk.Frame(search_frame)
        buttons_row.grid(row=0, column=1, sticky=tk.W, padx=(6, 0))
        search_btn = ttk.Button(buttons_row, text="Search", width=8)
        search_btn.pack(side=tk.LEFT, padx=(0, 5))
        clear_btn = ttk.Button(buttons_row, text="Clear", width=6)
        clear_btn.pack(side=tk.LEFT)

        # Helper label directly under the entry
        last_name_label = ttk.Label(search_frame, text="Type Last Name", font=("Arial", 8), foreground="gray")
        last_name_label.grid(row=1, column=0, sticky=tk.W, pady=(2, 0))

        people_canvas = tk.Canvas(people_frame, bg='white')
        people_scrollbar = ttk.Scrollbar(people_frame, orient="vertical", command=people_canvas.yview)
        people_list_inner = ttk.Frame(people_canvas)
        people_canvas.create_window((0, 0), window=people_list_inner, anchor="nw")
        people_canvas.configure(yscrollcommand=people_scrollbar.set)

        people_list_inner.bind(
            "<Configure>",
            lambda e: people_canvas.configure(scrollregion=people_canvas.bbox("all"))
        )

        people_canvas.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        people_scrollbar.grid(row=1, column=1, sticky=(tk.N, tk.S))
        people_frame.rowconfigure(1, weight=1)

        # Right panel: Faces for selected person
        faces_frame = ttk.LabelFrame(main_frame, text="Faces", padding="10")
        faces_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
        faces_frame.columnconfigure(0, weight=1)
        faces_frame.rowconfigure(0, weight=1)

        style = ttk.Style()
        canvas_bg_color = style.lookup('TFrame', 'background') or '#d9d9d9'
        # Match auto-match UI: set gray background for left canvas and remove highlight border
        try:
            people_canvas.configure(bg=canvas_bg_color, highlightthickness=0)
        except Exception:
            pass
        faces_canvas = tk.Canvas(faces_frame, bg=canvas_bg_color, highlightthickness=0)
        faces_scrollbar = ttk.Scrollbar(faces_frame, orient="vertical", command=faces_canvas.yview)
        faces_inner = ttk.Frame(faces_canvas)
        faces_canvas.create_window((0, 0), window=faces_inner, anchor="nw")
        faces_canvas.configure(yscrollcommand=faces_scrollbar.set)

        faces_inner.bind(
            "<Configure>",
            lambda e: faces_canvas.configure(scrollregion=faces_canvas.bbox("all"))
        )

        # Track current person for responsive face grid
        current_person_id = None
        current_person_name = ""
        resize_job = None

        # Track unmatched faces (temporary changes)
        unmatched_faces = set()  # All face IDs unmatched across people (for global save)
        unmatched_by_person = {}  # person_id -> set(face_id) for per-person undo
        original_faces_data = []  # store original faces data for potential future use

        def on_faces_canvas_resize(event):
            nonlocal resize_job
            if current_person_id is None:
                return
            # Debounce re-render on resize
            try:
                if resize_job is not None:
                    root.after_cancel(resize_job)
            except Exception:
                pass
            resize_job = root.after(150, lambda: show_person_faces(current_person_id, current_person_name))

        faces_canvas.bind("<Configure>", on_faces_canvas_resize)

        faces_canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        faces_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))

        # Load people from DB with counts
        people_data = []  # list of dicts: {id, name, count, first_name, last_name}
        people_filtered = None  # filtered subset based on last name search

        def load_people():
            nonlocal people_data
            with self.db.get_db_connection() as conn:
                cursor = conn.cursor()
                cursor.execute(
                    """
                    SELECT p.id, p.first_name, p.last_name, p.middle_name, p.maiden_name, p.date_of_birth, COUNT(f.id) as face_count
                    FROM people p
                    JOIN faces f ON f.person_id = p.id
                    GROUP BY p.id, p.last_name, p.first_name, p.middle_name, p.maiden_name, p.date_of_birth
                    HAVING face_count > 0
                    ORDER BY p.last_name, p.first_name COLLATE NOCASE
                    """
                )
                people_data = []
                for (pid, first_name, last_name, middle_name, maiden_name, date_of_birth, count) in cursor.fetchall():
                    # Create full name display with all available information
                    name_parts = []
                    if first_name:
                        name_parts.append(first_name)
                    if middle_name:
                        name_parts.append(middle_name)
                    if last_name:
                        name_parts.append(last_name)
                    if maiden_name:
                        name_parts.append(f"({maiden_name})")

                    full_name = ' '.join(name_parts) if name_parts else "Unknown"

                    # Create detailed display with date of birth if available
                    display_name = full_name
                    if date_of_birth:
                        display_name += f" - Born: {date_of_birth}"

                    people_data.append({
                        'id': pid,
                        'name': display_name,
                        'full_name': full_name,
                        'first_name': first_name or "",
                        'last_name': last_name or "",
                        'middle_name': middle_name or "",
                        'maiden_name': maiden_name or "",
                        'date_of_birth': date_of_birth or "",
                        'count': count
                    })
            # Re-apply filter (if any) after loading
            try:
                apply_last_name_filter()
            except Exception:
                pass

        # Wire up search controls now that helper functions exist
        try:
            search_btn.config(command=lambda: apply_last_name_filter())
            clear_btn.config(command=lambda: clear_last_name_filter())
            search_entry.bind('<Return>', lambda e: apply_last_name_filter())
        except Exception:
            pass

        def apply_last_name_filter():
            nonlocal people_filtered
            query = last_name_search_var.get().strip().lower()
            if query:
                people_filtered = [p for p in people_data if p.get('last_name', '').lower().find(query) != -1]
            else:
                people_filtered = None
            populate_people_list()
            # Update right panel based on filtered results
            source = people_filtered if people_filtered is not None else people_data
            if source:
                # Load faces for the first person in the list
                first = source[0]
                try:
                    # Update selection state
                    for child in people_list_inner.winfo_children():
                        for widget in child.winfo_children():
                            if isinstance(widget, ttk.Label):
                                widget.config(font=("Arial", 10))
                    # Bold the first label if present
                    first_row = people_list_inner.winfo_children()[0]
                    for widget in first_row.winfo_children():
                        if isinstance(widget, ttk.Label):
                            widget.config(font=("Arial", 10, "bold"))
                            break
                except Exception:
                    pass
                # Show faces for the first person
                show_person_faces(first['id'], first.get('full_name') or first.get('name') or "")
            else:
                # No matches: clear faces panel
                clear_faces_panel()

        def clear_last_name_filter():
            nonlocal people_filtered
            last_name_search_var.set("")
            people_filtered = None
            populate_people_list()
            # After clearing, load faces for the first available person if any
            if people_data:
                first = people_data[0]
                try:
                    for child in people_list_inner.winfo_children():
                        for widget in child.winfo_children():
                            if isinstance(widget, ttk.Label):
                                widget.config(font=("Arial", 10))
                    first_row = people_list_inner.winfo_children()[0]
                    for widget in first_row.winfo_children():
                        if isinstance(widget, ttk.Label):
                            widget.config(font=("Arial", 10, "bold"))
                            break
                except Exception:
                    pass
                show_person_faces(first['id'], first.get('full_name') or first.get('name') or "")
            else:
                clear_faces_panel()

        def clear_faces_panel():
            for w in faces_inner.winfo_children():
                w.destroy()
            # Cleanup temp crops
            for crop in list(temp_crops):
                try:
                    if os.path.exists(crop):
                        os.remove(crop)
                except Exception:
                    pass
            temp_crops.clear()
            right_panel_images.clear()

        def unmatch_face(face_id: int):
            """Temporarily unmatch a face from the current person"""
            nonlocal unmatched_faces, unmatched_by_person
            unmatched_faces.add(face_id)
            # Track per-person for Undo
            person_set = unmatched_by_person.get(current_person_id)
            if person_set is None:
                person_set = set()
                unmatched_by_person[current_person_id] = person_set
            person_set.add(face_id)
            # Refresh the display
            show_person_faces(current_person_id, current_person_name)

        def undo_changes():
            """Undo all temporary changes"""
            nonlocal unmatched_faces, unmatched_by_person
            if current_person_id in unmatched_by_person:
                for fid in list(unmatched_by_person[current_person_id]):
                    unmatched_faces.discard(fid)
                unmatched_by_person[current_person_id].clear()
            # Refresh the display
            show_person_faces(current_person_id, current_person_name)

        def save_changes():
            """Save unmatched faces to database"""
            if not unmatched_faces:
                return

            # Confirm with user
            result = messagebox.askyesno(
                "Confirm Changes",
                f"Are you sure you want to unlink {len(unmatched_faces)} face(s) from '{current_person_name}'?\n\n"
                "This will make these faces unidentified again."
            )

            if not result:
                return

            # Update database
            with self.db.get_db_connection() as conn:
                cursor = conn.cursor()
                for face_id in unmatched_faces:
                    cursor.execute('UPDATE faces SET person_id = NULL WHERE id = ?', (face_id,))
                conn.commit()

            # Store count for message before clearing
            unlinked_count = len(unmatched_faces)

            # Clear unmatched faces and refresh
            unmatched_faces.clear()
            original_faces_data.clear()

            # Refresh people list to update counts
            load_people()
            populate_people_list()

            # Refresh faces display
            show_person_faces(current_person_id, current_person_name)

            messagebox.showinfo("Changes Saved", f"Successfully unlinked {unlinked_count} face(s) from '{current_person_name}'.")

        def show_person_faces(person_id: int, person_name: str):
            nonlocal current_person_id, current_person_name, unmatched_faces, original_faces_data
            current_person_id = person_id
            current_person_name = person_name
            clear_faces_panel()

            # Determine how many columns fit the available width
            available_width = faces_canvas.winfo_width()
            if available_width <= 1:
                available_width = faces_frame.winfo_width()
            tile_width = 150  # approx tile + padding
            cols = max(1, available_width // tile_width)

            # Header row
            header = ttk.Label(faces_inner, text=f"Faces for: {person_name}", font=("Arial", 12, "bold"))
            header.grid(row=0, column=0, columnspan=cols, sticky=tk.W, pady=(0, 5))

            # Control buttons row
            button_frame = ttk.Frame(faces_inner)
            button_frame.grid(row=1, column=0, columnspan=cols, sticky=tk.W, pady=(0, 10))

            # Enable Undo only if current person has unmatched faces
            current_has_unmatched = bool(unmatched_by_person.get(current_person_id))
            undo_btn = ttk.Button(button_frame, text="↶ Undo changes",
                                  command=lambda: undo_changes(),
                                  state="disabled" if not current_has_unmatched else "normal")
            undo_btn.pack(side=tk.LEFT, padx=(0, 10))

            # Note: Save button moved to bottom control bar

            # Query faces for this person
            with self.db.get_db_connection() as conn:
                cursor = conn.cursor()
                cursor.execute(
                    """
                    SELECT f.id, f.location, ph.path, ph.filename
                    FROM faces f
                    JOIN photos ph ON ph.id = f.photo_id
                    WHERE f.person_id = ?
                    ORDER BY f.id DESC
                    """,
                    (person_id,)
                )
                rows = cursor.fetchall()

            # Filter out unmatched faces
            visible_rows = [row for row in rows if row[0] not in unmatched_faces]

            if not visible_rows:
                ttk.Label(faces_inner, text="No faces found." if not rows else "All faces unmatched.", foreground="gray").grid(row=2, column=0, sticky=tk.W)
                return

            # Grid thumbnails with responsive column count
            row_index = 2  # Start after header and buttons
            col_index = 0
            for face_id, location, photo_path, filename in visible_rows:
                crop_path = self.face_processor._extract_face_crop(photo_path, location, face_id)
                thumb = None
                if crop_path and os.path.exists(crop_path):
                    try:
                        img = Image.open(crop_path)
                        img.thumbnail((130, 130), Image.Resampling.LANCZOS)
                        photo_img = ImageTk.PhotoImage(img)
                        temp_crops.append(crop_path)
                        right_panel_images.append(photo_img)
                        thumb = photo_img
                    except Exception:
                        thumb = None

                tile = ttk.Frame(faces_inner, padding="5")
                tile.grid(row=row_index, column=col_index, padx=5, pady=5, sticky=tk.N)

                # Create a frame for the face image with X button overlay
                face_frame = ttk.Frame(tile)
                face_frame.grid(row=0, column=0)

                canvas = tk.Canvas(face_frame, width=130, height=130, bg=canvas_bg_color, highlightthickness=0)
                canvas.grid(row=0, column=0)
                if thumb is not None:
                    canvas.create_image(65, 65, image=thumb)
                else:
                    canvas.create_text(65, 65, text="🖼️", fill="gray")

                # X button to unmatch face - pin exactly to the canvas' top-right corner
                x_canvas = tk.Canvas(face_frame, width=12, height=12, bg='red',
                                     highlightthickness=0, relief="flat")
                x_canvas.create_text(6, 6, text="✖", fill="white", font=("Arial", 8, "bold"))
                # Click handler
                x_canvas.bind("<Button-1>", lambda e, fid=face_id: unmatch_face(fid))
                # Hover highlight: change bg, show white outline, and hand cursor
                x_canvas.bind("<Enter>", lambda e, c=x_canvas: c.configure(bg="#cc0000", highlightthickness=1, highlightbackground="white", cursor="hand2"))
                x_canvas.bind("<Leave>", lambda e, c=x_canvas: c.configure(bg="red", highlightthickness=0, cursor=""))
                # Anchor to the canvas' top-right regardless of layout/size
                try:
                    x_canvas.place(in_=canvas, relx=1.0, x=0, y=0, anchor='ne')
                except Exception:
                    # Fallback to absolute coords if relative placement fails
                    x_canvas.place(x=118, y=0)

                ttk.Label(tile, text=f"ID {face_id}", foreground="gray").grid(row=1, column=0)

                col_index += 1
                if col_index >= cols:
                    col_index = 0
                    row_index += 1

        def populate_people_list():
            for w in people_list_inner.winfo_children():
                w.destroy()
            source = people_filtered if people_filtered is not None else people_data
            if not source:
                empty_label = ttk.Label(people_list_inner, text="No people match filter", foreground="gray")
                empty_label.grid(row=0, column=0, sticky=tk.W, pady=4)
                return
            for idx, person in enumerate(source):
                row = ttk.Frame(people_list_inner)
                row.grid(row=idx, column=0, sticky=(tk.W, tk.E), pady=4)
                # Freeze per-row values to avoid late-binding issues
                row_person = person
                row_idx = idx

                # Make person name clickable
                def make_click_handler(p_id, p_name, p_idx):
                    def on_click(event):
                        nonlocal selected_person_id
                        # Reset all labels to normal font
                        for child in people_list_inner.winfo_children():
                            for widget in child.winfo_children():
                                if isinstance(widget, ttk.Label):
                                    widget.config(font=("Arial", 10))
                        # Set clicked label to bold
                        event.widget.config(font=("Arial", 10, "bold"))
                        selected_person_id = p_id
                        # Show faces for this person
                        show_person_faces(p_id, p_name)
                    return on_click

                # Edit (rename) button
                def start_edit_person(row_frame, person_record, row_index):
                    for w in row_frame.winfo_children():
                        w.destroy()

                    # Use pre-loaded data instead of database query
                    cur_first = person_record.get('first_name', '')
                    cur_last = person_record.get('last_name', '')
                    cur_middle = person_record.get('middle_name', '')
                    cur_maiden = person_record.get('maiden_name', '')
                    cur_dob = person_record.get('date_of_birth', '')

                    # Create a larger container frame for the text boxes and labels
                    edit_container = ttk.Frame(row_frame)
                    edit_container.pack(side=tk.LEFT, fill=tk.X, expand=True)

                    # First name field with label
                    first_frame = ttk.Frame(edit_container)
                    first_frame.grid(row=0, column=0, padx=(0, 10), pady=(0, 5), sticky=tk.W)

                    first_var = tk.StringVar(value=cur_first)
                    first_entry = ttk.Entry(first_frame, textvariable=first_var, width=15)
                    first_entry.pack(side=tk.TOP)
                    first_entry.focus_set()

                    first_label = ttk.Label(first_frame, text="First Name", font=("Arial", 8), foreground="gray")
                    first_label.pack(side=tk.TOP, pady=(2, 0))

                    # Last name field with label
                    last_frame = ttk.Frame(edit_container)
                    last_frame.grid(row=0, column=1, padx=(0, 10), pady=(0, 5), sticky=tk.W)

                    last_var = tk.StringVar(value=cur_last)
                    last_entry = ttk.Entry(last_frame, textvariable=last_var, width=15)
                    last_entry.pack(side=tk.TOP)

                    last_label = ttk.Label(last_frame, text="Last Name", font=("Arial", 8), foreground="gray")
                    last_label.pack(side=tk.TOP, pady=(2, 0))

                    # Middle name field with label
                    middle_frame = ttk.Frame(edit_container)
                    middle_frame.grid(row=0, column=2, padx=(0, 10), pady=(0, 5), sticky=tk.W)

                    middle_var = tk.StringVar(value=cur_middle)
                    middle_entry = ttk.Entry(middle_frame, textvariable=middle_var, width=15)
                    middle_entry.pack(side=tk.TOP)

                    middle_label = ttk.Label(middle_frame, text="Middle Name", font=("Arial", 8), foreground="gray")
                    middle_label.pack(side=tk.TOP, pady=(2, 0))

                    # Maiden name field with label
                    maiden_frame = ttk.Frame(edit_container)
                    maiden_frame.grid(row=1, column=0, padx=(0, 10), pady=(0, 5), sticky=tk.W)

                    maiden_var = tk.StringVar(value=cur_maiden)
                    maiden_entry = ttk.Entry(maiden_frame, textvariable=maiden_var, width=15)
                    maiden_entry.pack(side=tk.TOP)

                    maiden_label = ttk.Label(maiden_frame, text="Maiden Name", font=("Arial", 8), foreground="gray")
                    maiden_label.pack(side=tk.TOP, pady=(2, 0))

                    # Date of birth field with label and calendar button
                    dob_frame = ttk.Frame(edit_container)
                    dob_frame.grid(row=1, column=1, columnspan=2, padx=(0, 10), pady=(0, 5), sticky=tk.W)

                    # Create a frame for the date picker
                    date_picker_frame = ttk.Frame(dob_frame)
                    date_picker_frame.pack(side=tk.TOP)

                    dob_var = tk.StringVar(value=cur_dob)
                    dob_entry = ttk.Entry(date_picker_frame, textvariable=dob_var, width=20, state='readonly')
                    dob_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)

                    # Calendar button
                    calendar_btn = ttk.Button(date_picker_frame, text="📅", width=3, command=lambda: open_calendar())
                    calendar_btn.pack(side=tk.RIGHT, padx=(5, 0))

                    dob_label = ttk.Label(dob_frame, text="Date of Birth", font=("Arial", 8), foreground="gray")
                    dob_label.pack(side=tk.TOP, pady=(2, 0))

                    def open_calendar():
                        """Open a visual calendar dialog to select date of birth"""
                        from datetime import datetime, date
                        import calendar

                        # Create calendar window
                        calendar_window = tk.Toplevel(root)
                        calendar_window.title("Select Date of Birth")
                        calendar_window.resizable(False, False)
                        calendar_window.transient(root)
                        calendar_window.grab_set()

                        # Calculate center position before showing the window
                        window_width = 400
                        window_height = 400
                        screen_width = calendar_window.winfo_screenwidth()
                        screen_height = calendar_window.winfo_screenheight()
                        x = (screen_width // 2) - (window_width // 2)
                        y = (screen_height // 2) - (window_height // 2)

                        # Set geometry with center position before showing
                        calendar_window.geometry(f"{window_width}x{window_height}+{x}+{y}")

                        # Calendar variables
                        current_date = datetime.now()

                        # Check if there's already a date selected
                        existing_date_str = dob_var.get().strip()
                        if existing_date_str:
                            try:
                                existing_date = datetime.strptime(existing_date_str, '%Y-%m-%d').date()
                                display_year = existing_date.year
                                display_month = existing_date.month
                                selected_date = existing_date
                            except ValueError:
                                # If existing date is invalid, use default
                                display_year = current_date.year - 25
                                display_month = 1
                                selected_date = None
                        else:
                            # Default to 25 years ago
                            display_year = current_date.year - 25
                            display_month = 1
                            selected_date = None

                        # Month names
                        month_names = ["January", "February", "March", "April", "May", "June",
                                       "July", "August", "September", "October", "November", "December"]

                        # Configure custom styles for better visual highlighting
                        style = ttk.Style()

                        # Selected date style - bright blue background with white text
                        style.configure("Selected.TButton",
                                        background="#0078d4",
                                        foreground="white",
                                        font=("Arial", 9, "bold"),
                                        relief="raised",
                                        borderwidth=2)
                        style.map("Selected.TButton",
                                  background=[("active", "#106ebe")],
                                  relief=[("pressed", "sunken")])

                        # Today's date style - orange background
                        style.configure("Today.TButton",
                                        background="#ff8c00",
                                        foreground="white",
                                        font=("Arial", 9, "bold"),
                                        relief="raised",
                                        borderwidth=1)
                        style.map("Today.TButton",
                                  background=[("active", "#e67e00")],
                                  relief=[("pressed", "sunken")])

                        # Calendar-specific normal button style (don't affect global TButton)
                        style.configure("Calendar.TButton",
                                        font=("Arial", 9),
                                        relief="flat")
                        style.map("Calendar.TButton",
                                  background=[["active", "#e1e1e1"]],
                                  relief=[["pressed", "sunken"]])

                        # Main frame
                        main_cal_frame = ttk.Frame(calendar_window, padding="10")
                        main_cal_frame.pack(fill=tk.BOTH, expand=True)

                        # Header frame with navigation
                        header_frame = ttk.Frame(main_cal_frame)
                        header_frame.pack(fill=tk.X, pady=(0, 10))

                        # Month/Year display and navigation
                        nav_frame = ttk.Frame(header_frame)
                        nav_frame.pack()

                        def update_calendar():
                            """Update the calendar display"""
                            # Clear existing calendar
                            for widget in calendar_frame.winfo_children():
                                widget.destroy()

                            # Update header
                            month_year_label.config(text=f"{month_names[display_month-1]} {display_year}")

                            # Get calendar data
                            cal = calendar.monthcalendar(display_year, display_month)

                            # Day headers
                            day_headers = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
                            for i, day in enumerate(day_headers):
                                label = ttk.Label(calendar_frame, text=day, font=("Arial", 9, "bold"))
                                label.grid(row=0, column=i, padx=1, pady=1, sticky="nsew")

                            # Calendar days
                            for week_num, week in enumerate(cal):
                                for day_num, day in enumerate(week):
                                    if day == 0:
                                        # Empty cell
                                        label = ttk.Label(calendar_frame, text="")
                                        label.grid(row=week_num+1, column=day_num, padx=1, pady=1, sticky="nsew")
                                    else:
                                        # Day button
                                        def make_day_handler(day_value):
                                            def select_day():
                                                nonlocal selected_date
                                                selected_date = date(display_year, display_month, day_value)
                                                # Reset all buttons to normal calendar style
                                                for widget in calendar_frame.winfo_children():
                                                    if isinstance(widget, ttk.Button):
                                                        widget.config(style="Calendar.TButton")
                                                # Highlight selected day with prominent style
                                                for widget in calendar_frame.winfo_children():
                                                    if isinstance(widget, ttk.Button) and widget.cget("text") == str(day_value):
                                                        widget.config(style="Selected.TButton")
                                            return select_day

                                        day_btn = ttk.Button(calendar_frame, text=str(day),
                                                             command=make_day_handler(day),
                                                             width=3, style="Calendar.TButton")
                                        day_btn.grid(row=week_num+1, column=day_num, padx=1, pady=1, sticky="nsew")

                                        # Check if this day should be highlighted
                                        is_today = (display_year == current_date.year and
                                                    display_month == current_date.month and
                                                    day == current_date.day)
                                        is_selected = (selected_date and
                                                       selected_date.year == display_year and
                                                       selected_date.month == display_month and
                                                       selected_date.day == day)

                                        if is_selected:
                                            day_btn.config(style="Selected.TButton")
                                        elif is_today:
                                            day_btn.config(style="Today.TButton")

                        # Navigation functions
                        def prev_year():
                            nonlocal display_year
                            display_year = max(1900, display_year - 1)
                            update_calendar()

                        def next_year():
                            nonlocal display_year
                            display_year = min(current_date.year, display_year + 1)
                            update_calendar()

                        def prev_month():
                            nonlocal display_month, display_year
                            if display_month > 1:
                                display_month -= 1
                            else:
                                display_month = 12
                                display_year = max(1900, display_year - 1)
                            update_calendar()

                        def next_month():
                            nonlocal display_month, display_year
                            if display_month < 12:
                                display_month += 1
                            else:
                                display_month = 1
                                display_year = min(current_date.year, display_year + 1)
                            update_calendar()

                        # Navigation buttons
                        prev_year_btn = ttk.Button(nav_frame, text="<<", width=3, command=prev_year)
                        prev_year_btn.pack(side=tk.LEFT, padx=(0, 2))

                        prev_month_btn = ttk.Button(nav_frame, text="<", width=3, command=prev_month)
                        prev_month_btn.pack(side=tk.LEFT, padx=(0, 5))

                        month_year_label = ttk.Label(nav_frame, text="", font=("Arial", 12, "bold"))
                        month_year_label.pack(side=tk.LEFT, padx=5)

                        next_month_btn = ttk.Button(nav_frame, text=">", width=3, command=next_month)
                        next_month_btn.pack(side=tk.LEFT, padx=(5, 2))

                        next_year_btn = ttk.Button(nav_frame, text=">>", width=3, command=next_year)
                        next_year_btn.pack(side=tk.LEFT)

                        # Calendar grid frame
                        calendar_frame = ttk.Frame(main_cal_frame)
                        calendar_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

                        # Configure grid weights
                        for i in range(7):
                            calendar_frame.columnconfigure(i, weight=1)
                        for i in range(7):
                            calendar_frame.rowconfigure(i, weight=1)

                        # Buttons frame
                        buttons_frame = ttk.Frame(main_cal_frame)
                        buttons_frame.pack(fill=tk.X)

                        def select_date():
                            """Select the date and close calendar"""
                            if selected_date:
                                date_str = selected_date.strftime('%Y-%m-%d')
                                dob_var.set(date_str)
                                calendar_window.destroy()
                            else:
                                messagebox.showwarning("No Date Selected", "Please select a date from the calendar.")

                        def cancel_selection():
                            """Cancel date selection"""
                            calendar_window.destroy()

                        # Buttons
                        ttk.Button(buttons_frame, text="Select", command=select_date).pack(side=tk.LEFT, padx=(0, 5))
                        ttk.Button(buttons_frame, text="Cancel", command=cancel_selection).pack(side=tk.LEFT)

                        # Initialize calendar
                        update_calendar()

                    def save_rename():
                        new_first = first_var.get().strip()
                        new_last = last_var.get().strip()
                        new_middle = middle_var.get().strip()
                        new_maiden = maiden_var.get().strip()
                        new_dob = dob_var.get().strip()

                        if not new_first and not new_last:
                            messagebox.showwarning("Invalid name", "At least one of First or Last name must be provided.")
                            return

                        # Check for duplicates in local data first (based on first and last name only)
                        for person in people_data:
                            if person['id'] != person_record['id'] and person['first_name'] == new_first and person['last_name'] == new_last:
                                display_name = f"{new_last}, {new_first}".strip(", ").strip()
                                messagebox.showwarning("Duplicate name", f"A person named '{display_name}' already exists.")
                                return

                        # Single database access - save to database
                        with self.db.get_db_connection() as conn:
                            cursor = conn.cursor()
                            cursor.execute('UPDATE people SET first_name = ?, last_name = ?, middle_name = ?, maiden_name = ?, date_of_birth = ? WHERE id = ?',
                                           (new_first, new_last, new_middle, new_maiden, new_dob, person_record['id']))
                            conn.commit()

                        # Update local data structure
                        person_record['first_name'] = new_first
                        person_record['last_name'] = new_last
                        person_record['middle_name'] = new_middle
                        person_record['maiden_name'] = new_maiden
                        person_record['date_of_birth'] = new_dob

                        # Recreate the full display name with all available information
                        name_parts = []
                        if new_first:
                            name_parts.append(new_first)
                        if new_middle:
                            name_parts.append(new_middle)
                        if new_last:
                            name_parts.append(new_last)
                        if new_maiden:
                            name_parts.append(f"({new_maiden})")

                        full_name = ' '.join(name_parts) if name_parts else "Unknown"

                        # Create detailed display with date of birth if available
                        display_name = full_name
                        if new_dob:
                            display_name += f" - Born: {new_dob}"

                        person_record['name'] = display_name
                        person_record['full_name'] = full_name

                        # Refresh list
                        current_selected_id = person_record['id']
                        populate_people_list()
                        # Reselect and refresh right panel header if needed
                        if selected_person_id == current_selected_id or selected_person_id is None:
                            # Find updated name
                            updated = next((p for p in people_data if p['id'] == current_selected_id), None)
                            if updated:
                                # Bold corresponding label
                                for child in people_list_inner.winfo_children():
                                    # child is row frame: contains label and button
                                    widgets = child.winfo_children()
                                    if not widgets:
                                        continue
                                    lbl = widgets[0]
                                    if isinstance(lbl, ttk.Label) and lbl.cget('text').startswith(updated['name'] + " ("):
                                        lbl.config(font=("Arial", 10, "bold"))
                                        break
                                # Update right panel header by re-showing faces
                                show_person_faces(updated['id'], updated['name'])

                    def cancel_edit():
                        # Rebuild the row back to label + edit
                        for w in row_frame.winfo_children():
                            w.destroy()
                        rebuild_row(row_frame, person_record, row_index)

                    save_btn = ttk.Button(row_frame, text="💾", width=3, command=save_rename)
                    save_btn.pack(side=tk.LEFT, padx=(5, 0))
                    cancel_btn = ttk.Button(row_frame, text="✖", width=3, command=cancel_edit)
                    cancel_btn.pack(side=tk.LEFT, padx=(5, 0))

                    # Configure custom disabled button style for better visibility
                    style = ttk.Style()
                    style.configure("Disabled.TButton",
                                    background="#d3d3d3",  # Light gray background
                                    foreground="#808080",  # Dark gray text
                                    relief="flat",
                                    borderwidth=1)

                    def validate_save_button():
                        """Enable/disable save button based on required fields"""
                        first_val = first_var.get().strip()
                        last_val = last_var.get().strip()
                        dob_val = dob_var.get().strip()

                        # Enable save button only if both name fields and date of birth are provided
                        has_first = bool(first_val)
                        has_last = bool(last_val)
                        has_dob = bool(dob_val)

                        if has_first and has_last and has_dob:
                            save_btn.config(state="normal")
                            # Reset to normal styling when enabled
                            save_btn.config(style="TButton")
                        else:
                            save_btn.config(state="disabled")
                            # Apply custom disabled styling for better visibility
                            save_btn.config(style="Disabled.TButton")

                    # Set up validation callbacks for all input fields
                    first_var.trace('w', lambda *args: validate_save_button())
                    last_var.trace('w', lambda *args: validate_save_button())
                    middle_var.trace('w', lambda *args: validate_save_button())
                    maiden_var.trace('w', lambda *args: validate_save_button())
                    dob_var.trace('w', lambda *args: validate_save_button())

                    # Initial validation
                    validate_save_button()

                    # Keyboard shortcuts (only work when save button is enabled)
                    def try_save():
                        if save_btn.cget('state') == 'normal':
                            save_rename()

                    first_entry.bind('<Return>', lambda e: try_save())
                    last_entry.bind('<Return>', lambda e: try_save())
                    middle_entry.bind('<Return>', lambda e: try_save())
                    maiden_entry.bind('<Return>', lambda e: try_save())
                    dob_entry.bind('<Return>', lambda e: try_save())
                    first_entry.bind('<Escape>', lambda e: cancel_edit())
                    last_entry.bind('<Escape>', lambda e: cancel_edit())
                    middle_entry.bind('<Escape>', lambda e: cancel_edit())
                    maiden_entry.bind('<Escape>', lambda e: cancel_edit())
                    dob_entry.bind('<Escape>', lambda e: cancel_edit())

                def rebuild_row(row_frame, p, i):
                    # Edit button (on the left)
                    edit_btn = ttk.Button(row_frame, text="✏️", width=3, command=lambda r=row_frame, pr=p, ii=i: start_edit_person(r, pr, ii))
                    edit_btn.pack(side=tk.LEFT, padx=(0, 5))
                    # Add tooltip to edit button
                    ToolTip(edit_btn, "Update name")
                    # Label (clickable) - takes remaining space
                    name_lbl = ttk.Label(row_frame, text=f"{p['name']} ({p['count']})", font=("Arial", 10))
                    name_lbl.pack(side=tk.LEFT, fill=tk.X, expand=True)
                    name_lbl.bind("<Button-1>", make_click_handler(p['id'], p['name'], i))
                    name_lbl.config(cursor="hand2")
                    # Bold if selected
                    if (selected_person_id is None and i == 0) or (selected_person_id == p['id']):
                        name_lbl.config(font=("Arial", 10, "bold"))

                # Build row contents with edit button
                rebuild_row(row, row_person, row_idx)

        # Initial load
        load_people()
        populate_people_list()

        # Show first person's faces by default and mark selected
        if people_data:
            selected_person_id = people_data[0]['id']
            show_person_faces(people_data[0]['id'], people_data[0]['name'])

        # Control buttons
        control_frame = ttk.Frame(main_frame)
        control_frame.grid(row=3, column=0, columnspan=2, pady=(10, 0), sticky=tk.E)

        def on_quit():
            nonlocal window_destroyed
            # Warn if there are pending unmatched faces (unsaved changes)
            try:
                if unmatched_faces:
                    result = messagebox.askyesnocancel(
                        "Unsaved Changes",
                        "You have pending changes that are not saved.\n\n"
                        "Yes: Save and quit\n"
                        "No: Quit without saving\n"
                        "Cancel: Return to window"
                    )
                    if result is None:
                        # Cancel
                        return
                    if result is True:
                        # Save then quit
                        on_save_all_changes()
                    # If result is False, fall through and quit without saving
            except Exception:
                # If any issue occurs, proceed to normal quit
                pass
            on_closing()
            if not window_destroyed:
                window_destroyed = True
                try:
                    root.destroy()
                except tk.TclError:
                    pass  # Window already destroyed

        def on_save_all_changes():
            # Use global unmatched_faces set; commit all across people
            nonlocal unmatched_faces
            if not unmatched_faces:
                messagebox.showinfo("Nothing to Save", "There are no pending changes to save.")
                return
            result = messagebox.askyesno(
                "Confirm Save",
                f"Unlink {len(unmatched_faces)} face(s) across all people?\n\nThis will make them unidentified."
            )
            if not result:
                return
            with self.db.get_db_connection() as conn:
                cursor = conn.cursor()
                for face_id in unmatched_faces:
                    cursor.execute('UPDATE faces SET person_id = NULL WHERE id = ?', (face_id,))
                conn.commit()
            count = len(unmatched_faces)
            unmatched_faces.clear()
            # Refresh people list and right panel for current selection
            load_people()
            populate_people_list()
            if current_person_id is not None and current_person_name:
                show_person_faces(current_person_id, current_person_name)
            messagebox.showinfo("Changes Saved", f"Successfully unlinked {count} face(s).")

        quit_btn = ttk.Button(control_frame, text="❌ Quit", command=on_quit)
        quit_btn.pack(side=tk.RIGHT)
        save_btn_bottom = ttk.Button(control_frame, text="💾 Save changes", command=on_save_all_changes)
        save_btn_bottom.pack(side=tk.RIGHT, padx=(0, 10))

        # Show the window
        try:
            root.deiconify()
            root.lift()
            root.focus_force()
        except tk.TclError:
            # Window was destroyed before we could show it
            return 0

        # Main event loop
        try:
            root.mainloop()
        except tk.TclError:
            pass  # Window was destroyed

        return 0


