import os
import random
import typing

from qtpy import QtWidgets as QW

from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG

from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientLocation
from hydrus.client import ClientPaths
from hydrus.client.gui import ClientGUIMedia
from hydrus.client.gui import ClientGUIMenus
from hydrus.client.media import ClientMedia
from hydrus.client.media import ClientMediaManagers

def AddDuplicatesMenu( win: QW.QWidget, menu: QW.QMenu, location_context: ClientLocation.LocationContext, focus_singleton: ClientMedia.Media, num_selected: int, collections_selected: bool ):
    
    # TODO: I am hesitating making this async since we'll have duplicate relations available in the MediaResult soon enough
    # it would be great to have it in the Canvas though, hence the refactoring. needs a bit more reworking for that, but a good step forward
    
    multiple_selected = num_selected > 1
    
    duplicates_menu = ClientGUIMenus.GenerateMenu( menu )
    
    focused_hash = focus_singleton.GetHash()
    
    combined_local_location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
    
    if CG.client_controller.DBCurrentlyDoingJob():
        
        file_duplicate_info = {}
        all_local_files_file_duplicate_info = {}
        
    else:
        
        file_duplicate_info = CG.client_controller.Read( 'file_duplicate_info', location_context, focused_hash )
        
        if location_context.current_service_keys.isdisjoint( CG.client_controller.services_manager.GetServiceKeys( HC.SPECIFIC_LOCAL_FILE_SERVICES ) ):
            
            all_local_files_file_duplicate_info = {}
            
        else:
            
            all_local_files_file_duplicate_info = CG.client_controller.Read( 'file_duplicate_info', combined_local_location_context, focused_hash )
            
        
    
    focus_is_in_duplicate_group = False
    focus_is_in_alternate_group = False
    focus_has_fps = False
    focus_has_potentials = False
    focus_can_be_searched = focus_singleton.GetMime() in HC.FILES_THAT_HAVE_PERCEPTUAL_HASH
    
    if len( file_duplicate_info ) == 0:
        
        ClientGUIMenus.AppendMenuLabel( duplicates_menu, 'could not fetch file\'s duplicates (db currently locked)' )
        
    else:
        
        view_duplicate_relations_jobs = []
        
        if len( file_duplicate_info[ 'counts' ] ) > 0:
            
            view_duplicate_relations_jobs.append( ( location_context, file_duplicate_info ) )
            
        
        if len( all_local_files_file_duplicate_info ) > 0 and len( all_local_files_file_duplicate_info[ 'counts' ] ) > 0 and all_local_files_file_duplicate_info != file_duplicate_info:
            
            view_duplicate_relations_jobs.append( ( combined_local_location_context, all_local_files_file_duplicate_info ) )
            
        
        for ( job_location_context, job_duplicate_info ) in view_duplicate_relations_jobs:
            
            file_duplicate_types_to_counts = job_duplicate_info[ 'counts' ]
            
            ClientGUIMenus.AppendSeparator( duplicates_menu )
            
            if len( view_duplicate_relations_jobs ) > 1:
                
                label = '-for {}-'.format( job_location_context.ToString( CG.client_controller.services_manager.GetName ) )
                
                ClientGUIMenus.AppendMenuLabel( duplicates_menu, label )
                
            
            if HC.DUPLICATE_MEMBER in file_duplicate_types_to_counts:
                
                if job_duplicate_info[ 'is_king' ]:
                    
                    ClientGUIMenus.AppendMenuLabel( duplicates_menu, 'this is the best quality file of its group' )
                    
                else:
                    
                    num_other_dupe_members_in_this_domain = file_duplicate_types_to_counts[ HC.DUPLICATE_MEMBER ]
                    
                    if num_other_dupe_members_in_this_domain == 0:
                        
                        ClientGUIMenus.AppendMenuLabel( duplicates_menu, 'cannot show the best quality file of this file\'s group here, it is not in this domain', 'The king of this group has probably been deleted from this domain.' )
                        
                    else:
                        
                        ClientGUIMenus.AppendMenuItem( duplicates_menu, 'show the best quality file of this file\'s group', 'Load up a new search with this file\'s best quality duplicate.', ClientGUIMedia.ShowDuplicatesInNewPage, job_location_context, focused_hash, HC.DUPLICATE_KING )
                        
                    
                
                ClientGUIMenus.AppendSeparator( duplicates_menu )
                
            
            for duplicate_type in ( HC.DUPLICATE_MEMBER, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_FALSE_POSITIVE, HC.DUPLICATE_POTENTIAL ):
                
                if duplicate_type in file_duplicate_types_to_counts:
                    
                    count = file_duplicate_types_to_counts[ duplicate_type ]
                    
                    if count > 0:
                        
                        label = 'view {} {}'.format( HydrusData.ToHumanInt( count ), HC.duplicate_type_string_lookup[ duplicate_type ] )
                        
                        ClientGUIMenus.AppendMenuItem( duplicates_menu, label, 'Show these duplicates in a new page.', ClientGUIMedia.ShowDuplicatesInNewPage, job_location_context, focused_hash, duplicate_type )
                        
                        if duplicate_type == HC.DUPLICATE_MEMBER:
                            
                            focus_is_in_duplicate_group = True
                            
                        elif duplicate_type == HC.DUPLICATE_ALTERNATE:
                            
                            focus_is_in_alternate_group = True
                            
                        elif duplicate_type == HC.DUPLICATE_FALSE_POSITIVE:
                            
                            focus_has_fps = True
                            
                        elif duplicate_type == HC.DUPLICATE_POTENTIAL:
                            
                            focus_has_potentials = True
                            
                        
                    
                
            
        
    
    ClientGUIMenus.AppendSeparator( duplicates_menu )
    
    focus_is_definitely_king = len( file_duplicate_info ) > 0 and file_duplicate_info[ 'is_king' ]
    
    dissolution_actions_available = focus_can_be_searched or focus_is_in_duplicate_group or focus_is_in_alternate_group or focus_has_fps
    
    single_action_available = dissolution_actions_available or not focus_is_definitely_king
    
    if multiple_selected or single_action_available:
        
        duplicates_action_submenu = ClientGUIMenus.GenerateMenu( duplicates_menu )
        
        if len( file_duplicate_info ) == 0:
            
            ClientGUIMenus.AppendMenuLabel( duplicates_action_submenu, 'could not fetch info to check for available file actions (db currently locked)' )
            
        else:
            
            if not focus_is_definitely_king:
                
                ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set this file as the best quality of its group', 'Set the focused media to be the King of its group.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_KING ) )
                
            
        
        ClientGUIMenus.AppendSeparator( duplicates_action_submenu )
        
        if multiple_selected:
            
            label = 'set this file as better than the ' + HydrusData.ToHumanInt( num_selected - 1 ) + ' other selected'
            
            ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, label, 'Set the focused media to be better than the other selected files.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_BETTER ) )
            
            num_pairs = num_selected * ( num_selected - 1 ) / 2 # com // ations -- n!/2(n-2)!
            
            num_pairs_text = HydrusData.ToHumanInt( num_pairs ) + ' pairs'
            
            ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set all selected as same quality duplicates', 'Set all the selected files as same quality duplicates.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_SAME_QUALITY ) )
            
            ClientGUIMenus.AppendSeparator( duplicates_action_submenu )
            
            ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set all selected as alternates', 'Set all the selected files as alternates.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE ) )
            
            ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set a relationship with custom metadata merge options', 'Choose which duplicates status to set to this selection and customise non-default duplicate metadata merge options.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_CUSTOM ) )
            
            if collections_selected:
                
                ClientGUIMenus.AppendSeparator( duplicates_action_submenu )
                
                ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set selected collections as groups of alternates', 'Set files in the selection which are collected together as alternates.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE_COLLECTIONS ) )
                
            
            #
            
            ClientGUIMenus.AppendSeparator( duplicates_action_submenu )
            
            duplicates_edit_action_submenu = ClientGUIMenus.GenerateMenu( duplicates_action_submenu )
            
            for duplicate_type in ( HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY ):
                
                ClientGUIMenus.AppendMenuItem( duplicates_edit_action_submenu, 'for ' + HC.duplicate_type_string_lookup[duplicate_type], 'Edit what happens when you set this status.', ClientGUIMedia.EditDuplicateContentMergeOptions, win, duplicate_type )
                
            
            if CG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
                
                ClientGUIMenus.AppendMenuItem( duplicates_edit_action_submenu, 'for ' + HC.duplicate_type_string_lookup[HC.DUPLICATE_ALTERNATE] + ' (advanced!)', 'Edit what happens when you set this status.', ClientGUIMedia.EditDuplicateContentMergeOptions, win, HC.DUPLICATE_ALTERNATE )
                
            
            ClientGUIMenus.AppendMenu( duplicates_action_submenu, duplicates_edit_action_submenu, 'edit default duplicate metadata merge options' )
            
            #
            
            ClientGUIMenus.AppendSeparator( duplicates_action_submenu )
            
            ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set all possible pair combinations as \'potential\' duplicates for the duplicates filter.', 'Queue all these files up in the duplicates filter.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_POTENTIAL ) )
            
        
        if dissolution_actions_available:
            
            ClientGUIMenus.AppendSeparator( duplicates_action_submenu )
            
            duplicates_single_dissolution_menu = ClientGUIMenus.GenerateMenu( duplicates_action_submenu )
            
            if focus_can_be_searched:
                
                ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'schedule this file to be searched for potentials again', 'Queue this file for another potentials search. Will not remove any existing potentials.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_RESET_FOCUSED_POTENTIAL_SEARCH ) )
                
            
            if focus_has_potentials:
                
                ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'remove this file\'s potential relationships', 'Clear out this file\'s potential relationships.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_FOCUSED_POTENTIALS ) )
                
            
            if focus_is_in_duplicate_group:
                
                if not focus_is_definitely_king:
                    
                    ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'remove this file from its duplicate group', 'Extract this file from its duplicate group and reset its search status.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_FOCUSED_FROM_DUPLICATE_GROUP ) )
                    
                
                ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'dissolve this file\'s duplicate group completely', 'Completely eliminate this file\'s duplicate group and reset all files\' search status.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_FOCUSED_DUPLICATE_GROUP ) )
                
            
            if focus_is_in_alternate_group:
                
                ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'remove this file from its alternate group', 'Extract this file\'s duplicate group from its alternate group and reset the duplicate group\'s search status.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_FOCUSED_FROM_ALTERNATE_GROUP ) )
                
                ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'dissolve this file\'s alternate group completely', 'Completely eliminate this file\'s alternate group and all duplicate group members. This resets search status for all involved files.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_FOCUSED_ALTERNATE_GROUP ) )
                
            
            if focus_has_fps:
                
                ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'delete all false-positive relationships this file\'s alternate group has with other groups', 'Clear out all false-positive relationships this file\'s alternates group has with other groups and resets search status.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_CLEAR_FOCUSED_FALSE_POSITIVES ) )
                
            
            ClientGUIMenus.AppendMenu( duplicates_action_submenu, duplicates_single_dissolution_menu, 'remove/reset for this file' )
            
        
        if multiple_selected:
            
            if CG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
                
                duplicates_multiple_dissolution_menu = ClientGUIMenus.GenerateMenu( duplicates_action_submenu )
                
                ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'schedule these files to be searched for potentials again', 'Queue these files for another potentials search. Will not remove any existing potentials.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_RESET_POTENTIAL_SEARCH ) )
                ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'remove these files\' potential relationships', 'Clear out these files\' potential relationships.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_POTENTIALS ) )
                ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'dissolve these files\' duplicate groups completely', 'Completely eliminate these files\' duplicate groups and reset all files\' search status.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_DUPLICATE_GROUP ) )
                ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'dissolve these files\' alternate groups completely', 'Completely eliminate these files\' alternate groups and all duplicate group members. This resets search status for all involved files.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_ALTERNATE_GROUP ) )
                ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'delete all false-positive relationships these files\' alternate groups have with other groups', 'Clear out all false-positive relationships these files\' alternates groups has with other groups and resets search status.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_CLEAR_FALSE_POSITIVES ) )
                
                ClientGUIMenus.AppendMenu( duplicates_action_submenu, duplicates_multiple_dissolution_menu, 'remove/reset for all selected' )
                
            
        
        ClientGUIMenus.AppendMenu( duplicates_menu, duplicates_action_submenu, 'set relationship' )
        
    
    if len( duplicates_menu.actions() ) == 0:
        
        ClientGUIMenus.AppendMenuLabel( duplicates_menu, 'no file relationships or actions available for this file at present' )
        
    
    ClientGUIMenus.AppendMenu( menu, duplicates_menu, 'file relationships' )
    

def AddFileViewingStatsMenu( menu, medias: typing.Collection[ ClientMedia.Media ] ):
    
    if len( medias ) == 0:
        
        return
        
    
    view_style = CG.client_controller.new_options.GetInteger( 'file_viewing_stats_menu_display' )
    
    if view_style == CC.FILE_VIEWING_STATS_MENU_DISPLAY_NONE:
        
        return
        
    
    if len( medias ) == 1:
        
        ( media, ) = medias
        
        fvsm = media.GetFileViewingStatsManager()
        
    else:
        
        fvsm = ClientMediaManagers.FileViewingStatsManager.STATICGenerateCombinedManager( [ media.GetFileViewingStatsManager() for media in medias ] )
        
    
    if view_style == CC.FILE_VIEWING_STATS_MENU_DISPLAY_MEDIA_AND_PREVIEW_SUMMED:
        
        combined_line = fvsm.GetPrettyViewsLine( ( CC.CANVAS_MEDIA_VIEWER, CC.CANVAS_PREVIEW ) )
        
        ClientGUIMenus.AppendMenuLabel( menu, combined_line )
        
    else:
        
        media_line = fvsm.GetPrettyViewsLine( ( CC.CANVAS_MEDIA_VIEWER, ) )
        preview_line = fvsm.GetPrettyViewsLine( ( CC.CANVAS_PREVIEW, ) )
        
        if view_style == CC.FILE_VIEWING_STATS_MENU_DISPLAY_MEDIA_ONLY:
            
            ClientGUIMenus.AppendMenuLabel( menu, media_line )
            
        elif view_style == CC.FILE_VIEWING_STATS_MENU_DISPLAY_MEDIA_AND_PREVIEW_IN_SUBMENU:
            
            submenu = ClientGUIMenus.GenerateMenu( menu )
            
            ClientGUIMenus.AppendMenuLabel( submenu, preview_line )
            
            ClientGUIMenus.AppendMenu( menu, submenu, media_line )
            
        elif view_style == CC.FILE_VIEWING_STATS_MENU_DISPLAY_MEDIA_AND_PREVIEW_STACKED:
            
            ClientGUIMenus.AppendMenuLabel( menu, media_line )
            ClientGUIMenus.AppendMenuLabel( menu, preview_line )
            
        
    

def AddKnownURLsViewCopyMenu( win, menu, focus_media, selected_media = None ):
    
    # figure out which urls this focused file has
    
    if focus_media.IsCollection():
        
        focus_media = focus_media.GetDisplayMedia()
        
    
    focus_urls = focus_media.GetLocationsManager().GetURLs()
    
    focus_matched_labels_and_urls = []
    focus_unmatched_urls = []
    focus_labels_and_urls = []
    
    if len( focus_urls ) > 0:
        
        for url in focus_urls:
            
            try:
                
                url_class = CG.client_controller.network_engine.domain_manager.GetURLClass( url )
                
            except HydrusExceptions.URLClassException:
                
                continue
                
            
            if url_class is None:
                
                focus_unmatched_urls.append( url )
                
            else:
                
                label = url_class.GetName() + ': ' + url
                
                focus_matched_labels_and_urls.append( ( label, url ) )
                
            
        
        focus_matched_labels_and_urls.sort()
        focus_unmatched_urls.sort()
        
        focus_labels_and_urls = list( focus_matched_labels_and_urls )
        
        focus_labels_and_urls.extend( ( ( url, url ) for url in focus_unmatched_urls ) )
        
    
    # figure out which urls these selected files have
    
    selected_media_url_classes = set()
    multiple_or_unmatching_selection_url_classes = False
    
    if selected_media is not None:
        
        selected_media = ClientMedia.FlattenMedia( selected_media )
        
        if len( selected_media ) > 1:
            
            SAMPLE_SIZE = 256
            
            if len( selected_media ) > SAMPLE_SIZE:
                
                selected_media_sample = random.sample( selected_media, SAMPLE_SIZE )
                
            else:
                
                selected_media_sample = selected_media
                
            
            for media in selected_media_sample:
                
                media_urls = media.GetLocationsManager().GetURLs()
                
                for url in media_urls:
                    
                    try:
                        
                        url_class = CG.client_controller.network_engine.domain_manager.GetURLClass( url )
                        
                    except HydrusExceptions.URLClassException:
                        
                        continue
                        
                    
                    if url_class is None:
                        
                        multiple_or_unmatching_selection_url_classes = True
                        
                    else:
                        
                        selected_media_url_classes.add( url_class )
                        
                    
                
            
            if len( selected_media_url_classes ) > 1:
                
                multiple_or_unmatching_selection_url_classes = True
                
            
        
    
    if len( focus_labels_and_urls ) > 0 or len( selected_media_url_classes ) > 0 or multiple_or_unmatching_selection_url_classes:
        
        urls_menu = ClientGUIMenus.GenerateMenu( menu )
        
        urls_visit_menu = ClientGUIMenus.GenerateMenu( urls_menu )
        urls_copy_menu = ClientGUIMenus.GenerateMenu( urls_menu )
        
        # copy each this file's urls (of a particular type)
        
        if len( focus_labels_and_urls ) > 0:
            
            MAX_TO_SHOW = 15
            
            description = 'Open this url in your web browser.'
            
            ClientGUIMenus.SpamItems( urls_visit_menu, [ ( label, description, HydrusData.Call( ClientPaths.LaunchURLInWebBrowser, url ) ) for ( label, url ) in focus_labels_and_urls ], MAX_TO_SHOW )
            ClientGUIMenus.SpamLabels( urls_copy_menu, focus_labels_and_urls, MAX_TO_SHOW )
            
        
        # copy this file's urls
        
        there_are_focus_url_classes_to_action = len( focus_matched_labels_and_urls ) > 1
        multiple_or_unmatching_focus_url_classes = len( focus_unmatched_urls ) > 0 and len( focus_labels_and_urls ) > 1 # if there are unmatched urls and more than one thing total
        
        if there_are_focus_url_classes_to_action or multiple_or_unmatching_focus_url_classes:
            
            ClientGUIMenus.AppendSeparator( urls_visit_menu )
            ClientGUIMenus.AppendSeparator( urls_copy_menu )
            
        
        if there_are_focus_url_classes_to_action:
            
            urls = [ url for ( label, url ) in focus_matched_labels_and_urls ]
            
            label = 'open this file\'s ' + HydrusData.ToHumanInt( len( urls ) ) + ' recognised urls in your web browser'
            
            ClientGUIMenus.AppendMenuItem( urls_visit_menu, label, 'Open these urls in your web browser.', ClientGUIMedia.OpenURLs, urls )
            
            urls_string = os.linesep.join( urls )
            
            label = 'copy this file\'s ' + HydrusData.ToHumanInt( len( urls ) ) + ' recognised urls to your clipboard'
            
            ClientGUIMenus.AppendMenuItem( urls_copy_menu, label, 'Copy these urls to your clipboard.', CG.client_controller.pub, 'clipboard', 'text', urls_string )
            
        
        if multiple_or_unmatching_focus_url_classes:
            
            urls = [ url for ( label, url ) in focus_labels_and_urls ]
            
            label = 'open this file\'s ' + HydrusData.ToHumanInt( len( urls ) ) + ' urls in your web browser'
            
            ClientGUIMenus.AppendMenuItem( urls_visit_menu, label, 'Open these urls in your web browser.', ClientGUIMedia.OpenURLs, urls )
            
            urls_string = os.linesep.join( urls )
            
            label = 'copy this file\'s ' + HydrusData.ToHumanInt( len( urls ) ) + ' urls to your clipboard'
            
            ClientGUIMenus.AppendMenuItem( urls_copy_menu, label, 'Copy this url to your clipboard.', CG.client_controller.pub, 'clipboard', 'text', urls_string )
            
        
        # now by url match type
        
        there_are_selection_url_classes_to_action = len( selected_media_url_classes ) > 0
        
        if there_are_selection_url_classes_to_action or multiple_or_unmatching_selection_url_classes:
            
            ClientGUIMenus.AppendSeparator( urls_visit_menu )
            ClientGUIMenus.AppendSeparator( urls_copy_menu )
            
        
        if there_are_selection_url_classes_to_action:
            
            selected_media_url_classes = list( selected_media_url_classes )
            
            selected_media_url_classes.sort( key = lambda url_class: url_class.GetName() )
            
            for url_class in selected_media_url_classes:
                
                label = 'open files\' ' + url_class.GetName() + ' urls in your web browser'
                
                ClientGUIMenus.AppendMenuItem( urls_visit_menu, label, 'Open this url class in your web browser for all files.', ClientGUIMedia.OpenMediaURLClassURLs, selected_media, url_class )
                
                label = 'copy files\' ' + url_class.GetName() + ' urls'
                
                ClientGUIMenus.AppendMenuItem( urls_copy_menu, label, 'Copy this url class for all files.', ClientGUIMedia.CopyMediaURLClassURLs, selected_media, url_class )
                
            
        
        # now everything
        
        if multiple_or_unmatching_selection_url_classes:
            
            label = 'open all files\' urls'
            
            ClientGUIMenus.AppendMenuItem( urls_visit_menu, label, 'Open urls in your web browser for all files.', ClientGUIMedia.OpenMediaURLs, selected_media )
            
            label = 'copy all files\' urls'
            
            ClientGUIMenus.AppendMenuItem( urls_copy_menu, label, 'Copy urls for all files.', ClientGUIMedia.CopyMediaURLs, selected_media )
            
        
        #
        
        ClientGUIMenus.AppendMenu( urls_menu, urls_visit_menu, 'open' )
        ClientGUIMenus.AppendMenu( urls_menu, urls_copy_menu, 'copy' )
        
        ClientGUIMenus.AppendMenu( menu, urls_menu, 'known urls' )
        
    

def AddLocalFilesMoveAddToMenu( win: QW.QWidget, menu: QW.QMenu, local_duplicable_to_file_service_keys: typing.Collection[ bytes ], local_moveable_from_and_to_file_service_keys: typing.Collection[ typing.Tuple[ bytes, bytes ] ], multiple_selected: bool, process_application_command_call ):
    
    if len( local_duplicable_to_file_service_keys ) == 0 and len( local_moveable_from_and_to_file_service_keys ) == 0:
        
        return
        
    
    local_action_menu = ClientGUIMenus.GenerateMenu( menu )
    
    if len( local_duplicable_to_file_service_keys ) > 0:
        
        menu_tuples = []
        
        for s_k in local_duplicable_to_file_service_keys:
            
            application_command = CAC.ApplicationCommand(
                command_type = CAC.APPLICATION_COMMAND_TYPE_CONTENT,
                data = ( s_k, HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ADD, None )
            )
            
            label = CG.client_controller.services_manager.GetName( s_k )
            description = 'Duplicate the files to this local file service.'
            call = HydrusData.Call( process_application_command_call, application_command )
            
            menu_tuples.append( ( label, description, call ) )
            
        
        if multiple_selected:
            
            submenu_name = 'add selected to'
            
        else:
            
            submenu_name = 'add to'
            
        
        ClientGUIMenus.AppendMenuOrItem( local_action_menu, submenu_name, menu_tuples )
        
    
    if len( local_moveable_from_and_to_file_service_keys ) > 0:
        
        menu_tuples = []
        
        for ( source_s_k, dest_s_k ) in local_moveable_from_and_to_file_service_keys:
            
            application_command = CAC.ApplicationCommand(
                command_type = CAC.APPLICATION_COMMAND_TYPE_CONTENT,
                data = ( dest_s_k, HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_MOVE, source_s_k )
            )
            
            label = 'from {} to {}'.format( CG.client_controller.services_manager.GetName( source_s_k ), CG.client_controller.services_manager.GetName( dest_s_k ) )
            description = 'Add the files to the destination and delete from the source.'
            call = HydrusData.Call( process_application_command_call, application_command )
            
            menu_tuples.append( ( label, description, call ) )
            
        
        if multiple_selected:
            
            submenu_name = 'move selected'
            
        else:
            
            submenu_name = 'move'
            
        
        ClientGUIMenus.AppendMenuOrItem( local_action_menu, submenu_name, menu_tuples )
        
    
    ClientGUIMenus.AppendMenu( menu, local_action_menu, 'local services' )
    

def AddManageFileViewingStatsMenu( win: QW.QWidget, menu: QW.QMenu, flat_medias: typing.Collection[ ClientMedia.MediaSingleton ] ):
    
    # add test here for if media actually has stats, edit them, all that
    
    submenu = ClientGUIMenus.GenerateMenu( menu )
    
    ClientGUIMenus.AppendMenuItem( submenu, 'clear', 'Clear all the recorded file viewing stats for the selected files.', ClientGUIMedia.DoClearFileViewingStats, win, flat_medias )
    
    ClientGUIMenus.AppendMenu( menu, submenu, 'viewing stats' )
    

def AddPrettyInfoLines( menu, pretty_info_lines ):
    
    def add_pretty_info_str( m, line ):
        
        ClientGUIMenus.AppendMenuLabel( m, line )
        
    
    def add_pretty_info_rows( m, rows ):
        
        for row in rows:
            
            if isinstance( row, str ):
                
                add_pretty_info_str( m, row )
                
            else:
                
                try:
                    
                    ( submenu_label, rows ) = row
                    
                except:
                    
                    add_pretty_info_str( m, 'unknown submenu' )
                    
                    continue
                    
                
                lines_submenu = ClientGUIMenus.GenerateMenu( m )
                
                add_pretty_info_rows( lines_submenu, rows )
                
                ClientGUIMenus.AppendMenu( m, lines_submenu, submenu_label )
                
            
        
    
    add_pretty_info_rows( menu, pretty_info_lines )
    

def AddServiceKeyLabelsToMenu( menu, service_keys, phrase ):
    
    services_manager = CG.client_controller.services_manager
    
    if len( service_keys ) == 1:
        
        ( service_key, ) = service_keys
        
        name = services_manager.GetName( service_key )
        
        label = phrase + ' ' + name
        
        ClientGUIMenus.AppendMenuLabel( menu, label )
        
    else:
        
        submenu = ClientGUIMenus.GenerateMenu( menu )
        
        for service_key in service_keys:
            
            name = services_manager.GetName( service_key )
            
            ClientGUIMenus.AppendMenuLabel( submenu, name )
            
        
        ClientGUIMenus.AppendMenu( menu, submenu, phrase + HC.UNICODE_ELLIPSIS )
        
    

def AddServiceKeysToMenu( menu, service_keys, submenu_name, description, bare_call ):
    
    menu_tuples = []
    
    services_manager = CG.client_controller.services_manager
    
    for service_key in service_keys:
        
        label = services_manager.GetName( service_key )
        
        this_call = HydrusData.Call( bare_call, service_key )
        
        menu_tuples.append( ( label, description, this_call ) )
        
    
    ClientGUIMenus.AppendMenuOrItem( menu, submenu_name, menu_tuples )
    
