import os
import shutil
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
import re
from collections import defaultdict

# Initialize global variables
folder_path = ""
groups = {}
display_to_group_map = {}
pending_renames = {}  # Stores pending renames: {original_group_name: new_group_name}
pending_merges = []    # Stores pending merges: list of group_names to merge into new_group_name

def clean_filename(filename, redundant_words):
    """Clean filename according to specified rules (remove numbers, repeated words)."""
    # 1. Ignore text before and including '：'.
    filename = filename.split('：', 1)[-1]

    # 2. Remove file extension.
    filename = os.path.splitext(filename)[0]

    # 3. Replace delimiters with spaces, and transform to lowercase.
    words = filename.replace('_', ' ').lower().split()

    # Check if the entire filename is numeric
    if all(part.isdigit() for part in words):
        return ' '.join(words)

    # 4. Replace numeric groups with spaces
    words = [re.sub(r'\d+', ' ', word).strip() for word in words]

    # Flatten the words split further on spaces
    words = ' '.join(words).split()

    # 5. Remove redundant words/groups of characters.
    unique_words = [w for w in words if w not in redundant_words]

    # 6. Remove words that are less than 3 characters and ensure uniqueness across filename
    seen = set()
    cleaned_words = [word for word in unique_words if len(word) > 2 and not (word in seen or seen.add(word))]

    # Join cleaned words back into a string
    cleaned_filename = ' '.join(cleaned_words)

    # Use a placeholder for files that end up without significant content
    return cleaned_filename if cleaned_filename else "misc_files"


def find_redundant_words(files):
    """Identify groups of characters that appear in all filenames."""
    if not files:
        print("No files provided to find redundant words.")  # Debugging statement
        return []

    # Split filenames on '：' and take the relevant part
    processed_files = [file.split('：', 1)[-1].replace('_', ' ').lower().split() for file in files]

    # Find common words across all processed filenames
    common_words = set(processed_files[0])
    for file_words in processed_files[1:]:
        common_words.intersection_update(file_words)

    redundant_words = list(common_words)
    print(f"Redundant words identified: {redundant_words}")  # Debugging statement
    return redundant_words


def extract_sort_key(filename):
    """
    Extract numerical suffix from the filename for sorting.

    Args:
        filename (str): The filename.

    Returns:
        int: Numerical suffix for sorting.
    """
    # Extract part after 's_' and before file extension
    # Example: 'Ifrete_knit_s_13.jpg' -> 13
    match = re.search(r's_(\d+)', filename.lower())
    if match:
        return int(match.group(1))
    return 0  # Default if pattern not found


def prepare_groups(files, grouping_enabled):
    """Prepare groups of files by cleaned filenames or treat all as one group."""
    if not files:
        print("No files to prepare groups.")  # Debugging statement
        return {}

    if grouping_enabled:
        # Identify redundant words/groups of characters
        redundant_words = find_redundant_words(files)

        temp_groups = defaultdict(list)

        for file in files:
            cleaned_name = clean_filename(file, redundant_words)
            print(f"File: {file} => Cleaned Name: {cleaned_name}")  # Debugging statement
            temp_groups[cleaned_name].append(file)

        # Sort files within each group based on numerical suffix
        for group in temp_groups:
            temp_groups[group].sort(key=extract_sort_key)
            print(f"Group '{group}' sorted files: {temp_groups[group]}")  # Debugging statement

        print(f"Final Groups: {dict(temp_groups)}")  # Debugging statement
        return temp_groups
    else:
        # Treat all files as a single group named "All Files"
        print("Grouping disabled; treating all files as a single group.")  # Debugging statement
        return {"All Files": files}


def split_into_subgroups(files, file_num):
    """
    Split files into subgroups based on file_num and remaining files rule.

    Args:
        files (list): List of filenames.
        file_num (int): Number of files per subgroup.

    Returns:
        list: List of subgroups (each subgroup is a list of filenames).
    """
    subgroups = []
    total_files = len(files)
    index = 0

    while index < total_files:
        remaining = total_files - index
        # If remaining files are less than twice the file_num and more than file_num, group all remaining
        if remaining <= 2 * file_num and remaining > file_num:
            subgroups.append(files[index:])
            break
        else:
            subgroups.append(files[index:index + file_num])
            index += file_num

    print(f"Subgroups created with file_num={file_num}: {subgroups}")  # Debugging statement
    return subgroups


def capitalize_folder_name(folder_name):
    """Capitalize folder name excluding certain short words."""
    short_words = {'of', 'is', 'a', 'an'}
    words = folder_name.split()
    capitalized = [word.capitalize() if word.lower() not in short_words else word for word in words]
    modified_name = ' '.join(capitalized)
    print(f"Capitalized folder name: {modified_name}")  # Debugging statement
    return modified_name


def show_folder_contents(event=None):
    """Display files or subfolders in the selected group (Preview)."""
    try:
        selection = folder_listbox.curselection()
        if not selection:
            content_listbox.delete(0, tk.END)
            print("No folder selected.")  # Debugging statement
            return

        folder_display_name = folder_listbox.get(selection[0])
        original_group_name = display_to_group_map.get(folder_display_name, folder_display_name)
        print(f"Displaying contents of: {original_group_name}")  # Debugging statement

        # Clear previous contents
        content_listbox.delete(0, tk.END)

        # Get the list of files in the selected group, considering pending operations
        files_in_group = groups.get(original_group_name, [])

        # Apply pending renames or merges for preview
        preview_files = files_in_group.copy()
        if original_group_name in pending_renames:
            new_group_name = pending_renames[original_group_name]
            print(f"Preview Rename: '{original_group_name}' will be renamed to '{new_group_name}'")  # Debugging
        # For merges, the structure is handled separately

        if not preview_files:
            content_listbox.insert(tk.END, "No files in this group.")
            print("No files in this group.")  # Debugging statement
            return

        if group_files_var.get() and enable_grouping_var.get():
            file_num_str = file_number_var.get().strip()
            if file_num_str:
                try:
                    file_num = int(file_num_str)
                    if file_num <= 0:
                        raise ValueError
                except ValueError:
                    messagebox.showerror("Invalid Input", "Please enter a valid positive integer for file grouping.")
                    content_listbox.delete(0, tk.END)  # Clear contents if invalid input
                    print("Invalid file_num entered.")  # Debugging statement
                    return
                # Calculate subgroups based on the file_num
                subgroups = split_into_subgroups(preview_files, file_num)

                for idx, subgroup in enumerate(subgroups, 1):
                    # Update subfolder naming to include a hyphen separator
                    subfolder_display_name = f"{folder_display_name} - {idx}"
                    content_listbox.insert(tk.END, f"{subfolder_display_name}:")
                    for file in subgroup:
                        content_listbox.insert(tk.END, f"  - {file}")
            else:
                # If file_num is empty, just display the grouped files without further subgroups
                for file in preview_files:
                    content_listbox.insert(tk.END, file)
        elif group_files_var.get() and not enable_grouping_var.get():
            file_num_str = file_number_var.get().strip()
            if file_num_str:
                try:
                    file_num = int(file_num_str)
                    if file_num <= 0:
                        raise ValueError
                except ValueError:
                    messagebox.showerror("Invalid Input", "Please enter a valid positive integer for file grouping.")
                    content_listbox.delete(0, tk.END)  # Clear contents if invalid input
                    print("Invalid file_num entered.")  # Debugging statement
                    return
                # Calculate subgroups based on the file_num
                subgroups = split_into_subgroups(preview_files, file_num)

                for idx, subgroup in enumerate(subgroups, 1):
                    # Use parent folder name as the base for subfolder naming
                    subfolder_display_name = f"{parent_name_var.get().strip()} - {idx}"
                    content_listbox.insert(tk.END, f"{subfolder_display_name}:")
                    for file in subgroup:
                        content_listbox.insert(tk.END, f"  - {file}")
            else:
                # If file_num is empty, just display all files without subgroups
                for file in preview_files:
                    content_listbox.insert(tk.END, file)
        else:
            for file in preview_files:
                content_listbox.insert(tk.END, file)
    except Exception as e:
        messagebox.showerror("Error", f"An error occurred while displaying folder contents:\n{str(e)}")
        print(f"Error in show_folder_contents: {str(e)}")


def load_directory():
    """Load the selected directory, prepare groups, and update the displayed folders."""
    global folder_path, groups, pending_renames, pending_merges
    print("Load Directory button clicked.")  # Debugging statement
    folder_path = filedialog.askdirectory(title="Select the folder containing your files")
    print(f"Selected folder: {folder_path}")  # Debugging statement
    if not folder_path:
        print("No folder selected.")  # Debugging statement
        return
    parent_folder_name = os.path.basename(folder_path)
    parent_name_var.set(parent_folder_name)
    try:
        # Only consider files, ignore subdirectories
        files = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))]
        print(f"Files found: {files}")  # Debugging statement
        groups = prepare_groups(files, enable_grouping_var.get())
        print(f"Groups prepared: {groups}")  # Debugging statement
        # Reset any pending operations
        pending_renames.clear()
        pending_merges.clear()
        update_displayed_folders()
    except Exception as e:
        messagebox.showerror("Error", f"An error occurred while loading the directory:\n{str(e)}")
        print(f"Error in load_directory: {str(e)}")


def apply_options(*args):
    """Apply the selected options and update the preview."""
    if not folder_path:
        print("No directory loaded; skipping apply_options.")  # Debugging statement
        return  # Do nothing if no directory is loaded
    try:
        load_directory_preview()
    except Exception as e:
        messagebox.showerror("Error", f"An error occurred while applying options:\n{str(e)}")
        print(f"Error in apply_options: {str(e)}")


def load_directory_preview():
    """Load the directory and update the display based on the current settings without moving files."""
    global groups
    try:
        # Only consider files, ignore subdirectories
        files = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))]
        print(f"Preview - Files found: {files}")  # Debugging statement
        groups = prepare_groups(files, enable_grouping_var.get())
        print(f"Preview - Groups prepared: {groups}")  # Debugging statement
        update_displayed_folders()
    except Exception as e:
        messagebox.showerror("Error", f"An error occurred while updating the preview:\n{str(e)}")
        print(f"Error in load_directory_preview: {str(e)}")


def update_displayed_folders():
    """Update the folder_listbox based on current groups and selected options."""
    global display_to_group_map
    parent_prefix = parent_name_var.get().strip()
    add_prefix = prefix_var.get()
    capitalize_names = capitalize_var.get()

    folder_listbox.delete(0, tk.END)
    display_to_group_map.clear()
    for folder_name in sorted(groups.keys()):
        # Determine the display folder name based on current settings and pending renames
        display_name = folder_name
        if folder_name in pending_renames:
            display_name = pending_renames[folder_name]
            print(f"Pending Rename: '{folder_name}' will be displayed as '{display_name}'")  # Debugging

        if enable_grouping_var.get():
            # When grouping is enabled
            if folder_name != "All Files":
                if capitalize_names:
                    modified_folder_name = capitalize_folder_name(display_name)
                else:
                    modified_folder_name = display_name

                if add_prefix:
                    if modified_folder_name:
                        # Prefix is added after capitalization
                        modified_folder_name = f"{parent_prefix} - {modified_folder_name}"
                    else:
                        # If group folder name is empty, only prefix
                        modified_folder_name = parent_prefix
                # else, no prefix added
            else:
                # When grouping is enabled but all files are under "All Files"
                # Treat similarly to grouping disabled
                modified_folder_name = parent_prefix
        else:
            # When grouping is disabled
            if add_prefix:
                # Prefix is used as the base name for subfolders
                modified_folder_name = parent_prefix
            else:
                # No prefix; since grouping is disabled, "All Files" is treated as the base
                modified_folder_name = parent_prefix

        folder_listbox.insert(tk.END, modified_folder_name)
        display_to_group_map[modified_folder_name] = folder_name
        print(f"Displayed folder: {modified_folder_name} mapped to group: {folder_name}")  # Debugging statement

    # Automatically select the first folder and show its contents
    if folder_listbox.size() > 0:
        folder_listbox.select_set(0)
        show_folder_contents()
    else:
        print("No groups to display.")  # Debugging statement


def execute_sorting():
    """Execute the sorting: move files into organized folders and subfolders based on selected options."""
    if not folder_path or not groups:
        messagebox.showerror("Error", "No directory loaded or no files grouped. Please load a directory first.")
        print("Execute Sorting failed: No directory loaded or no files grouped.")  # Debugging statement
        return

    try:
        parent_prefix = parent_name_var.get().strip()
        add_prefix = prefix_var.get()
        group_files = group_files_var.get()
        capitalize_names = capitalize_var.get()
        grouping_enabled = enable_grouping_var.get()
        file_num_str = file_number_var.get().strip()

        # Determine file_num only if group_files is enabled and file_num_str is not empty
        if group_files and file_num_str:
            try:
                file_num = int(file_num_str)
                if file_num <= 0:
                    raise ValueError
            except ValueError:
                messagebox.showerror("Invalid Input", "Please enter a valid positive integer for file grouping.")
                print("Invalid file_num entered during execute_sorting.")  # Debugging statement
                return
        else:
            file_num = None  # No grouping by file number

        # Apply pending renames
        for original_group, new_group in pending_renames.items():
            if original_group not in groups:
                continue
            # Update the group name
            groups[new_group] = groups.pop(original_group)
            print(f"Renaming group from '{original_group}' to '{new_group}'")  # Debugging statement

        # Clear pending renames after applying
        pending_renames.clear()

        # Apply pending merges
        for merge_operation in pending_merges:
            group_names = merge_operation['groups']
            new_group_name = merge_operation['new_group']
            merged_files = []

            # Create new group
            groups[new_group_name] = merged_files

            for group_name in group_names:
                if group_name not in groups:
                    continue
                merged_files.extend(groups[group_name])
                del groups[group_name]
                print(f"Merging group '{group_name}' into '{new_group_name}'")  # Debugging statement

        # Clear pending merges after applying
        pending_merges.clear()

        for folder_display_name, group_name in display_to_group_map.items():
            files = groups.get(group_name, [])

            if grouping_enabled and group_name != "All Files":
                # Grouping Enabled and multiple groups
                if capitalize_names:
                    base_folder_name = capitalize_folder_name(group_name)
                else:
                    base_folder_name = group_name

                if add_prefix:
                    if base_folder_name:
                        # Prefix is added after capitalization
                        modified_folder_name = f"{parent_prefix} - {base_folder_name}"
                    else:
                        # If group folder name is empty, only prefix
                        modified_folder_name = parent_prefix
                else:
                    modified_folder_name = base_folder_name

                new_folder_path = os.path.join(folder_path, modified_folder_name)

                # Prevent creating a folder with the same name as the parent folder
                if os.path.abspath(new_folder_path) == os.path.abspath(folder_path):
                    messagebox.showerror("Error", f"Cannot create a folder with the same name as the parent folder: {modified_folder_name}")
                    print(f"Cannot create folder with the same name as parent: {modified_folder_name}")  # Debugging statement
                    continue

                os.makedirs(new_folder_path, exist_ok=True)
                print(f"Created folder: {new_folder_path}")  # Debugging statement

                if group_files and file_num:
                    subgroups = split_into_subgroups(files, file_num)
                    for idx, subgroup in enumerate(subgroups, 1):
                        subfolder_name = f"{modified_folder_name} - {idx}"
                        subfolder_path = os.path.join(new_folder_path, subfolder_name)
                        os.makedirs(subfolder_path, exist_ok=True)
                        print(f"Created subfolder: {subfolder_path}")  # Debugging statement
                        for file in subgroup:
                            src = os.path.join(folder_path, file)
                            dest = os.path.join(subfolder_path, file)
                            shutil.move(src, dest)
                            print(f"Moved '{file}' to '{subfolder_path}'")  # Debugging statement
                else:
                    for file in files:
                        src = os.path.join(folder_path, file)
                        dest = os.path.join(new_folder_path, file)
                        shutil.move(src, dest)
                        print(f"Moved '{file}' to '{new_folder_path}'")  # Debugging statement

            elif not grouping_enabled and group_name == "All Files":
                # Grouping Disabled: Create subfolders based on file_num directly in the parent directory
                if group_files and file_num:
                    subgroups = split_into_subgroups(files, file_num)
                    for idx, subgroup in enumerate(subgroups, 1):
                        subfolder_name = f"{parent_prefix} - {idx}"
                        subfolder_path = os.path.join(folder_path, subfolder_name)

                        # Prevent creating a folder with the same name as the parent folder
                        if os.path.abspath(subfolder_path) == os.path.abspath(folder_path):
                            messagebox.showerror("Error", f"Cannot create a subfolder with the same name as the parent folder: {subfolder_name}")
                            print(f"Cannot create subfolder with the same name as parent: {subfolder_name}")  # Debugging statement
                            continue

                        os.makedirs(subfolder_path, exist_ok=True)
                        print(f"Created subfolder: {subfolder_path}")  # Debugging statement
                        for file in subgroup:
                            src = os.path.join(folder_path, file)
                            dest = os.path.join(subfolder_path, file)
                            shutil.move(src, dest)
                            print(f"Moved '{file}' to '{subfolder_path}'")  # Debugging statement
                elif not group_files:
                    # If grouping is disabled and file grouping is not enabled,
                    # and all files are already in the parent folder, do nothing
                    print("Grouping disabled and file grouping not enabled; no action taken.")  # Debugging statement
                    pass
            else:
                # Edge Case: grouping_enabled but only "All Files" group exists
                # Treat it as grouping disabled
                if group_files and file_num:
                    subgroups = split_into_subgroups(files, file_num)
                    for idx, subgroup in enumerate(subgroups, 1):
                        subfolder_name = f"{parent_prefix} - {idx}"
                        subfolder_path = os.path.join(folder_path, subfolder_name)

                        # Prevent creating a folder with the same name as the parent folder
                        if os.path.abspath(subfolder_path) == os.path.abspath(folder_path):
                            messagebox.showerror("Error", f"Cannot create a subfolder with the same name as the parent folder: {subfolder_name}")
                            print(f"Cannot create subfolder with the same name as parent: {subfolder_name}")  # Debugging statement
                            continue

                        os.makedirs(subfolder_path, exist_ok=True)
                        print(f"Created subfolder: {subfolder_path}")  # Debugging statement
                        for file in subgroup:
                            src = os.path.join(folder_path, file)
                            dest = os.path.join(subfolder_path, file)
                            shutil.move(src, dest)
                            print(f"Moved '{file}' to '{subfolder_path}'")  # Debugging statement
                elif not group_files:
                    # Move all files into a single subfolder named with parent prefix
                    subfolder_name = f"{parent_prefix}"
                    subfolder_path = os.path.join(folder_path, subfolder_name)

                    # Prevent creating a folder with the same name as the parent folder
                    if os.path.abspath(subfolder_path) == os.path.abspath(folder_path):
                        # If the subfolder name is the same as the parent folder, skip creation
                        print(f"Subfolder name same as parent; skipping creation: {subfolder_name}")  # Debugging statement
                        pass
                    else:
                        os.makedirs(subfolder_path, exist_ok=True)
                        print(f"Created subfolder: {subfolder_path}")  # Debugging statement
                        for file in files:
                            src = os.path.join(folder_path, file)
                            dest = os.path.join(subfolder_path, file)
                            shutil.move(src, dest)
                            print(f"Moved '{file}' to '{subfolder_path}'")  # Debugging statement

        messagebox.showinfo("Success", "Files sorted and organized successfully!")
        load_directory()  # Reload to reflect changes without opening the folder
        print("Files sorted and organized successfully.")  # Debugging statement
    except Exception as e:
        messagebox.showerror("Error", f"An error occurred during sorting:\n{str(e)}")
        print(f"Error in execute_sorting: {str(e)}")


# ---------------------------- New Functions ----------------------------

def rename_selected_folder():
    """Rename the selected folder directly in the listbox."""
    selected_indices = folder_listbox.curselection()
    if len(selected_indices) != 1:
        messagebox.showinfo("Rename Folder", "Please select exactly one folder to rename.")
        print("Rename operation aborted: Incorrect number of selections.")  # Debugging statement
        return

    index = selected_indices[0]
    old_display_name = folder_listbox.get(index)
    original_group_name = display_to_group_map.get(old_display_name, old_display_name)
    print(f"Renaming folder: {original_group_name}")  # Debugging statement

    # Create an Entry widget overlaying the listbox item
    entry = tk.Entry(frame, width=40)
    entry.insert(0, old_display_name)
    entry.select_range(0, tk.END)
    entry.focus()

    # Get the coordinates of the selected item
    bbox = folder_listbox.bbox(index)
    if bbox:
        entry.place(x=folder_listbox.winfo_x() + bbox[0], y=folder_listbox.winfo_y() + bbox[1], width=bbox[2], height=bbox[3])
        print(f"Entry widget placed at x={folder_listbox.winfo_x() + bbox[0]}, y={folder_listbox.winfo_y() + bbox[1]}")  # Debugging statement

    def on_enter(event=None):
        new_display_name = entry.get().strip()
        print(f"New display name entered: {new_display_name}")  # Debugging statement
        if not new_display_name:
            messagebox.showerror("Invalid Name", "Folder name cannot be empty.")
            print("Rename operation aborted: Empty name.")  # Debugging statement
            return
        if new_display_name in display_to_group_map and new_display_name not in pending_renames.values():
            messagebox.showerror("Duplicate Name", "A folder with this name already exists.")
            print("Rename operation aborted: Duplicate name.")  # Debugging statement
            return

        # Ensure new_display_name does not create conflict in pending renames
        for pending_new in pending_renames.values():
            if new_display_name == pending_new:
                messagebox.showerror("Duplicate Name", "A pending rename to this name already exists.")
                print("Rename operation aborted: Pending duplicate name.")  # Debugging statement
                return

        # Register the pending rename
        pending_renames[original_group_name] = new_display_name
        print(f"Registered pending rename: '{original_group_name}' -> '{new_display_name}'")  # Debugging statement

        # Update the listbox to reflect the pending rename
        folder_listbox.delete(index)
        folder_listbox.insert(index, new_display_name)
        display_to_group_map[new_display_name] = original_group_name
        del display_to_group_map[old_display_name]
        print(f"Updated listbox and mapping with pending rename '{new_display_name}'")  # Debugging statement

        entry.destroy()
        # Update the preview to reflect the rename
        show_folder_contents()
        print(f"Pending rename '{original_group_name}' -> '{new_display_name}' displayed.")  # Debugging statement

    # Bind Enter key to submit the rename
    entry.bind("<Return>", on_enter)

    # Bind clicking anywhere else to submit the rename
    def on_click_anywhere(event):
        widget = event.widget
        if widget != entry:
            on_enter()

    root.bind("<Button-1>", on_click_anywhere)

    # Ensure that clicking outside destroys the entry widget
    def on_focus_out(event):
        on_enter()

    entry.bind("<FocusOut>", on_focus_out)


def merge_selected_folders():
    """Merge the selected folders into a single folder."""
    selected_indices = folder_listbox.curselection()
    if len(selected_indices) < 2:
        messagebox.showinfo("Merge Folders", "Please select two or more folders to merge.")
        print("Merge operation aborted: Less than two folders selected.")  # Debugging statement
        return

    selected_display_names = [folder_listbox.get(i) for i in selected_indices]
    selected_group_names = [display_to_group_map.get(name, name) for name in selected_display_names]
    print(f"Merging folders: {selected_group_names}")  # Debugging statement

    # Suggest the first selected folder's name as default
    default_new_folder_name = selected_display_names[0]
    new_folder_name = simpledialog.askstring("Merge Folders", "Enter the name for the merged folder:", initialvalue=default_new_folder_name)
    print(f"New folder name for merge: {new_folder_name}")  # Debugging statement
    if not new_folder_name:
        messagebox.showerror("Invalid Name", "Folder name cannot be empty.")
        print("Merge operation aborted: Empty new folder name.")  # Debugging statement
        return
    if new_folder_name in display_to_group_map and new_folder_name not in [merge['new_group'] for merge in pending_merges]:
        messagebox.showerror("Duplicate Name", "A folder with this name already exists.")
        print("Merge operation aborted: Duplicate new folder name.")  # Debugging statement
        return

    # Ensure new_folder_name does not conflict with existing pending merges
    for merge_op in pending_merges:
        if new_folder_name == merge_op['new_group']:
            messagebox.showerror("Duplicate Name", "A pending merge to this name already exists.")
            print("Merge operation aborted: Pending duplicate new folder name.")  # Debugging statement
            return

    # Register the pending merge
    pending_merges.append({'groups': selected_group_names, 'new_group': new_folder_name})
    print(f"Registered pending merge: {selected_group_names} -> '{new_folder_name}'")  # Debugging statement

    # Update the listbox to reflect the pending merge
    # Remove merged folders from display
    for i in reversed(selected_indices):
        display_name = folder_listbox.get(i)
        folder_listbox.delete(i)
        del display_to_group_map[display_name]
        print(f"Removed '{display_name}' from display list for merging.")  # Debugging statement

    # Add the new merged folder to the display list
    folder_listbox.insert(tk.END, new_folder_name)
    display_to_group_map[new_folder_name] = new_folder_name
    print(f"Added merged folder '{new_folder_name}' to display list.")  # Debugging statement

    # Update the preview to reflect the merge
    show_folder_contents()
    print(f"Pending merge displayed as '{new_folder_name}'")  # Debugging statement


def create_context_menu(event):
    """Create and display a context menu for the folder listbox."""
    try:
        context_menu = tk.Menu(root, tearoff=0)
        context_menu.add_command(label="Rename", command=rename_selected_folder)

        # Check if multiple selections exist for merging
        selected_indices = folder_listbox.curselection()
        if len(selected_indices) > 1:
            context_menu.add_command(label="Merge Selected Folders", command=merge_selected_folders)
        context_menu.post(event.x_root, event.y_root)
    except Exception as e:
        messagebox.showerror("Error", f"An error occurred while creating the context menu:\n{str(e)}")
        print(f"Error creating context menu: {str(e)}")  # Debugging statement


# ---------------------------- GUI Setup ----------------------------

# GUI Setup
root = tk.Tk()
root.title("File Sorter")
root.geometry("900x600")  # Increased window size for better layout

frame = tk.Frame(root)
frame.pack(pady=20, padx=20, fill=tk.BOTH, expand=True)

# Parent Folder Name
parent_name_label = tk.Label(frame, text="Parent Folder Name:")
parent_name_label.grid(row=0, column=0, padx=10, sticky='e')

parent_name_var = tk.StringVar()
parent_name_entry = tk.Entry(frame, width=30, textvariable=parent_name_var)
parent_name_entry.grid(row=0, column=1, padx=10, sticky='w')
parent_name_var.trace_add('write', lambda *args: apply_options())

# Add Prefix Checkbox
prefix_var = tk.IntVar()
prefix_check = tk.Checkbutton(frame, text="Add Parent Name Prefix", variable=prefix_var, command=apply_options)
prefix_check.grid(row=1, column=0, columnspan=4, pady=5, sticky='w')

# Group Files Checkbox and Entry
group_files_var = tk.IntVar()
group_files_check = tk.Checkbutton(frame, text="Group by file number:", variable=group_files_var, command=apply_options)
group_files_check.grid(row=2, column=0, sticky='e')

file_number_label = tk.Label(frame, text="Files per subfolder:")
file_number_label.grid(row=2, column=2, padx=10, sticky='e')

file_number_var = tk.StringVar(value="30")  # Default for file grouping
file_number_entry = tk.Entry(frame, width=5, textvariable=file_number_var)
file_number_entry.grid(row=2, column=3, sticky='w', padx=(0,10))
file_number_var.trace_add('write', lambda *args: apply_options())

# Capitalize Names Checkbox
capitalize_var = tk.IntVar()
capitalize_check = tk.Checkbutton(frame, text="Capitalize Folder Names", variable=capitalize_var, command=apply_options)
capitalize_check.grid(row=3, column=0, columnspan=4, pady=5, sticky='w')

# Folder Listbox (Preview) Header with New Checkbox
folder_listbox_header_frame = tk.Frame(frame)
folder_listbox_header_frame.grid(row=4, column=0, columnspan=4, sticky='w', pady=(10,0))

enable_grouping_var = tk.IntVar(value=1)  # New variable for grouping toggle
enable_grouping_check = tk.Checkbutton(
    folder_listbox_header_frame,
    text="Enable Folder Grouping",
    variable=enable_grouping_var,
    command=apply_options
)
enable_grouping_check.pack(side='left')

folder_listbox_label = tk.Label(folder_listbox_header_frame, text="Grouped Folders (Preview):")
folder_listbox_label.pack(side='left', padx=(10,0))

# Updated Listbox with multiple selection and double-click enabled
folder_listbox = tk.Listbox(frame, width=40, height=15, exportselection=False, selectmode=tk.EXTENDED)
folder_listbox.grid(row=5, column=0, columnspan=2, pady=5, sticky='nsew')
folder_listbox.bind("<<ListboxSelect>>", show_folder_contents)
folder_listbox.bind("<Double-Button-1>", lambda event: rename_selected_folder())  # Double-click to rename

# Bind right-click for context menu
folder_listbox.bind("<Button-3>", create_context_menu)  # Right-click on Windows
folder_listbox.bind("<Button-2>", create_context_menu)  # Right-click on macOS

# Make the Listbox expand with the window
frame.grid_rowconfigure(5, weight=1)
frame.grid_columnconfigure(1, weight=1)

# Content Listbox (Preview)
content_listbox_label = tk.Label(frame, text="Folder Contents (Preview):")
content_listbox_label.grid(row=4, column=2, columnspan=2, pady=(10,0))

content_listbox = tk.Listbox(frame, width=40, height=15, exportselection=False)
content_listbox.grid(row=5, column=2, columnspan=2, pady=5, sticky='nsew')

# Make the Content Listbox expand with the window
frame.grid_columnconfigure(3, weight=1)

# Browse and Execute Buttons
browse_button = tk.Button(frame, text="Browse Directory", command=load_directory)
browse_button.grid(row=6, column=0, pady=10, sticky='w')

execute_button = tk.Button(frame, text="Execute Sorting", command=execute_sorting)
execute_button.grid(row=6, column=3, pady=10, sticky='e')

root.mainloop()

There are several bugs in this script.
1) When I try to merge the following folders, I use the default merge folder name (Fur Coat).
Fur Coat
Fur Coat Furcoat
Furcoat
it gives me a warning saying "A folder with this name already exists".
If I were to move the content from Fur Coat Furcoat and Furcoat to Fur Coat, it should not give me this error.
More importantly, when I rename the merge folder using the word Fur, the folder will appear in the Folder Listbox but without any content.