import sys
import json
from caches.navigator_cache import navigator_cache as nc
from caches.favourites_cache import COLLECTION_FAVOURITE_TYPES, custom_favourite_types, custom_favourite_type, get_custom_folder_name, favourites_cache, favourites_context_label
from modules import kodi_utils as ku, settings as ks, parental_control
# logger = ku.logger

media_path, ls, build_url, notification, list_dirs = ku.media_path, ku.local_string, ku.build_url, ku.notification, ku.list_dirs
make_listitem, add_item, add_dir, end_directory, add_items = ku.make_listitem, ku.add_item, ku.add_dir, ku.end_directory, ku.add_items
set_content, set_view_mode, set_sort_method, set_category = ku.set_content, ku.set_view_mode, ku.set_sort_method, ku.set_category
_in_str, mov_str, tv_str, edit_str = ls(32484), ls(32028), ls(32029), ls(32705)
browse_str, add_menu_str, s_folder_str = ls(32706), ls(32730), ls(32731)


def _square_network_logo(icon):
	try:
		from modules.artwork_utils import square_logo
		return square_logo(icon, 'network') or icon
	except Exception:
		return icon

class Navigator:
	def __init__(self, params):
		params['handle'] = int(sys.argv[1])
		# Les menus de navigation ne doivent pas imposer le fanart alkoFlix.
		# On laisse le skin appliquer son fond naturel quand l'item n'a pas
		# de vraie image de fond.
		params['fanart'] = ''
		self.params = params
		self.params_get = self.params.get
		self.list_name = self.params_get('action', 'RootList')

	def downloads(self):
		dl_str, pr_str, im_str = ls(32107), ls(32485), ls(32798)
		mov_path, ep_path = ks.download_directory('movie'), ks.download_directory('episode')
		prem_path, im_path = ks.download_directory('premium'), ks.download_directory('image')
		n_ins = _in_str % (dl_str.upper(), '')
		self._add_item({'mode': 'navigator.folder_navigator', 'folder_path': mov_path , 'name': mov_str}, 'movies.png' , n_ins)
		self._add_item({'mode': 'navigator.folder_navigator', 'folder_path': ep_path  , 'name': tv_str }, 'tv.png'     , n_ins)
		self._add_item({'mode': 'navigator.folder_navigator', 'folder_path': prem_path, 'name': pr_str }, 'premium.png', n_ins)
		self._add_item({'mode': 'browser_image', 'folder_path': im_path, 'name': im_str }, 'pictures.png' , n_ins, False)
		self._end_directory()

	def discover_main(self):
		discover_str, his_str, help_str = ls(32451), ls(32486), ls(32487)
		movh_str, tvh_str = '%s %s' % (mov_str, his_str), '%s %s' % (tv_str, his_str)
		n_ins = _in_str % (discover_str.upper(), '')
		self._add_item({'mode': 'discover.movie', 'media_type': 'movie',    'name': mov_str }, 'movies.png', n_ins)
		self._add_item({'mode': 'discover.tvshow', 'media_type': 'tvshow',  'name': tv_str  }, 'tv.png', n_ins)
		self._add_item({'mode': 'discover.history', 'media_type': 'movie',  'name': movh_str}, 'recent.png', n_ins)
		self._add_item({'mode': 'discover.history', 'media_type': 'tvshow', 'name': tvh_str }, 'recent.png', n_ins)
		self._add_item({'mode': 'discover.help', 'name': help_str}, 'info2.png', n_ins, False)
		self._end_directory()

	def premium(self):
		from modules.debrid import debrid_enabled
		debrids = debrid_enabled()
		if 'alldebrid' in debrids: self.alldebrid()
		if 'real-debrid' in debrids: self.real_debrid()
		if 'torbox' in debrids: self.torbox()
		self._end_directory()

	def easynews(self):
		easy_str, se_str, acc_str = ls(32070), ls(32450), ls(32494)
		n_ins = _in_str % (easy_str.upper(), '')
		self._add_item({'mode': 'search_history', 'action': 'easynews_video', 'name': se_str }, 'search.png'  , n_ins)
		self._add_item({'mode': 'easynews.account_info', 'name': acc_str}, 'easynews.png', n_ins, False)

	def real_debrid(self):
		rd_str, acc_str, his_str, cloud_str = ls(32054), ls(32494), ls(32486), ls(32496)
		clca_str, n_ins = ls(32497) % rd_str, _in_str % (rd_str.upper(), '')
		self._add_item({'mode': 'real_debrid.rd_torrent_cloud',     'name': cloud_str}, 'cloud.png', n_ins)
		self._add_item({'mode': 'real_debrid.show_account_info',    'name': acc_str  }, 'info2.png', n_ins, False)
		self._add_item({'mode': 'clear_cache', 'cache': 'rd_cloud', 'name': clca_str }, 'broom-ball.png', n_ins, False)

	def premiumize(self):
		notification('Premiumize n’est plus supporté par alkoFlix')
		self._end_directory()

	def alldebrid(self):
		ad_str, acc_str = ls(32063), ls(32494)
		magnet_str, links_str, history_str = 'Magnets', 'Liens', 'Historique'
		clca_str, n_ins = ls(32497) % ad_str, _in_str % (ad_str.upper(), '')
		self._add_item({'mode': 'alldebrid.ad_torrent_cloud',       'name': magnet_str }, 'aimant.png', n_ins)
		self._add_item({'mode': 'alldebrid.ad_links',               'name': links_str  }, 'download.png', n_ins)
		self._add_item({'mode': 'alldebrid.ad_downloads',           'name': history_str}, 'recent.png', n_ins)
		self._add_item({'mode': 'alldebrid.show_account_info',      'name': acc_str    }, 'info2.png', n_ins, False)
		self._add_item({'mode': 'clear_cache', 'cache': 'ad_cloud', 'name': clca_str   }, 'broom-ball.png', n_ins, False)

	def torbox(self):
		tor_str, usenet_str, web_str = 'Torrent', 'Usenet', ls(33150)
		tb_str, cloud_str, ai_str = 'TorBox', ls(32496), ls(32494)
		clca_str, n_ins = ls(32497) % tb_str, _in_str % (tb_str.upper(), '')
		self._add_item({'mode': 'torbox.tb_torrent_cloud', 'media_type': 'torent', 'name': tor_str   }, 'magnet.png', n_ins)
		self._add_item({'mode': 'torbox.tb_torrent_cloud', 'media_type': 'usenet', 'name': usenet_str}, 'cloud.png', n_ins)
		self._add_item({'mode': 'torbox.show_account_info', 'name': ai_str    }, 'info2.png', n_ins, False)
		self._add_item({'mode': 'clear_cache', 'cache': 'tb_cloud', 'name': clca_str  }, 'broom-ball.png', n_ins, False)

	def offcloud(self):
		notification('Offcloud n’est plus supporté par alkoFlix')
		self._end_directory()

	def easydebrid(self):
		notification('EasyDebrid n’est plus supporté par alkoFlix')
		self._end_directory()

	def _fav_has(self, favourite_types):
		try:
			from caches.favourites_cache import has_favourites_types
			return has_favourites_types(favourite_types)
		except: return False

	def _fav_add_if(self, favourite_types, url_params, iconImage='favourites.png', prefix='', isFolder=True):
		if not self._fav_has(favourite_types): return False
		self._add_item(url_params, iconImage, prefix, isFolder)
		return True

	def favourites(self):
		# Racine Favoris : dossiers directs, sans cascade de sous-dossiers thématiques.
		fav_str = 'Favoris (alkoFlix)'
		clear_fav_str = 'Vider les favoris'
		n_ins, c_n_ins = _in_str % (fav_str.upper(), ''), _in_str % (ls(32524).upper(), '')
		has_any = False
		has_any = self._fav_add_if(('movie', 'movie_family', 'movie_animation', 'movie_documentary', 'movie_tv_movie'), {'mode': 'build_movie_list', 'action': 'favourites_movies_all', 'name': 'Favoris (Films)', 'fav_type': 'movie'}, 'favourites.png', n_ins) or has_any
		has_any = self._fav_add_if(('tvshow', 'tv_family', 'tv_animation', 'tv_documentary', 'tv_reality', 'tv_talkshow', 'tv_news'), {'mode': 'build_tvshow_list', 'action': 'favourites_tvshows_all', 'name': 'Favoris (Séries)', 'fav_type': 'tvshow'}, 'favourites.png', n_ins) or has_any
		has_any = self._fav_add_if(('season', 'season_family', 'season_animation', 'season_documentary'), {'mode': 'build_season_list', 'action': 'favourites_seasons_all', 'name': 'Favoris (Saisons)', 'parent_fav_type': 'tvshow'}, 'favourites.png', n_ins) or has_any
		has_any = self._fav_add_if(('episode', 'episode_family', 'episode_animation', 'episode_documentary'), {'mode': 'build_season_list', 'action': 'favourites_episodes_all', 'name': 'Favoris (Épisodes)', 'parent_fav_type': 'tvshow'}, 'favourites.png', n_ins) or has_any
		has_any = self._fav_add_if(('anime_movie', 'anime_tv', 'anime_season', 'anime_episode'), {'mode': 'navigator.favourites_anime', 'name': 'Animés japonais'}, 'favourites.png', n_ins) or has_any
		has_any = self._fav_add_if(COLLECTION_FAVOURITE_TYPES, {'mode': 'catalogue.sagas_favourites', 'name': 'Sagas'}, 'favourites.png', n_ins) or has_any
		has_any = self._fav_add_if(('person',), {'mode': 'people.favourites', 'name': 'Personnes'}, 'favourites.png', n_ins) or has_any
		has_any = self._fav_add_if(('list',), {'mode': 'navigator.favourites_lists', 'name': 'Listes'}, 'favourites.png', n_ins) or has_any
		if has_any and not ku.external_browse():
			self._add_item({'mode': 'favourites_choice', 'cache': 'clear_favourites', 'fav_group': 'all', 'name': clear_fav_str}, 'broom-ball.png', c_n_ins, False)
		self._end_directory()


	def _custom_favourites_movie_items(self, folder_id, folder_name):
		items = []
		try:
			fav_type = custom_favourite_type(folder_id, 'movie')
			media_ids = [i.get('tmdb_id') for i in favourites_cache.get_favourites(fav_type) or [] if i.get('tmdb_id')]
			if media_ids:
				from menus.movies import Movies
				menu = Movies({'action': 'favourites_custom_movies', 'id_type': 'tmdb_id', 'fav_type': fav_type, 'name': folder_name})
				menu.list = media_ids
				items.extend(menu.worker() or [])
		except Exception as e:
			ku.logger('custom favourites movies error', '%s: %s' % (type(e).__name__, e))
		return items

	def _custom_favourites_tv_items(self, folder_id, folder_name):
		items = []
		try:
			fav_type = custom_favourite_type(folder_id, 'tvshow')
			media_ids = [i.get('tmdb_id') for i in favourites_cache.get_favourites(fav_type) or [] if i.get('tmdb_id')]
			if media_ids:
				from menus.tvshows import TVShows
				menu = TVShows({'action': 'favourites_custom_tvshows', 'id_type': 'tmdb_id', 'fav_type': fav_type, 'name': folder_name})
				menu.list = media_ids
				items.extend(menu.worker() or [])
		except Exception as e:
			ku.logger('custom favourites tv error', '%s: %s' % (type(e).__name__, e))
		return items

	def _custom_favourites_season_episode_items(self, folder_id, folder_name):
		items = []
		try:
			from menus.seasons import Seasons
			menu = Seasons({'action': 'favourites_custom', 'name': folder_name})
			season_type = custom_favourite_type(folder_id, 'season')
			episode_type = custom_favourite_type(folder_id, 'episode')
			items.extend(menu.build_favourite_seasons(season_type) or [])
			items.extend(menu.build_favourite_episodes(episode_type) or [])
		except Exception as e:
			ku.logger('custom favourites season episode error', '%s: %s' % (type(e).__name__, e))
		return items

	def _custom_favourites_collection_items(self, folder_id):
		items = []
		try:
			from indexers import tmdb_api
			image_resolution = ks.get_resolution()
			fav_type = custom_favourite_type(folder_id, 'collection')
			for favourite in favourites_cache.get_favourites(fav_type) or []:
				collection_id = favourite.get('tmdb_id')
				if not collection_id: continue
				name = favourite.get('title') or 'Saga'
				poster, fanart, plot = media_path('sets.png'), self.params_get('fanart'), ''
				try:
					data = tmdb_api.tmdb_movies_collection(collection_id) or {}
					name = data.get('name') or name
					plot = data.get('overview') or ''
					if data.get('poster_path'): poster = tmdb_api.tmdb_image_base % (image_resolution.get('poster', 'w780'), data.get('poster_path'))
					if data.get('backdrop_path'): fanart = tmdb_api.tmdb_image_base % (image_resolution.get('fanart', 'w1280'), data.get('backdrop_path'))
				except Exception: pass
				url = build_url({'mode': 'build_movie_list', 'action': 'tmdb_movies_collection', 'collection_id': collection_id, 'name': name, 'fav_type': fav_type})
				listitem = make_listitem()
				listitem.setLabel(name)
				try: ku.set_listitem_video_info(listitem, {'plot': plot, 'mediatype': 'set', 'title': name})
				except: pass
				listitem.setArt({'icon': poster, 'poster': poster, 'thumb': poster, 'fanart': fanart, 'landscape': fanart or poster, 'banner': poster})
				try:
					if ks.context_favourites_enabled():
						listitem.addContextMenuItems([(favourites_context_label(fav_type, collection_id), 'RunPlugin(%s)' % build_url({'mode': 'favourites_choice', 'media_type': 'collection', 'fav_type': fav_type, 'tmdb_id': collection_id, 'title': name}))], replaceItems=False)
				except: pass
				items.append((url, listitem, True))
		except Exception as e:
			ku.logger('custom favourites collections error', '%s: %s' % (type(e).__name__, e))
		return items

	def _custom_favourites_people_items(self, folder_id):
		items = []
		try:
			from indexers.tmdb_api import tmdb_people_full_info, tmdb_image_base
			fav_type = custom_favourite_type(folder_id, 'person')
			poster_empty = media_path('people.png')
			for favourite in favourites_cache.get_favourites(fav_type) or []:
				person_id = favourite.get('tmdb_id')
				if not person_id: continue
				name = favourite.get('title') or 'Personne'
				try: person = tmdb_people_full_info(person_id) or {}
				except Exception: person = {}
				name = person.get('name') or name
				profile = person.get('profile_path') or ''
				poster = tmdb_image_base % ('w185', profile) if profile else poster_empty
				image = tmdb_image_base % ('h632', profile) if profile else poster_empty
				url = build_url({'mode': 'person_data_dialog', 'actor_name': name, 'actor_id': person_id, 'actor_image': image})
				listitem = make_listitem()
				listitem.setLabel(name)
				listitem.setArt({'icon': poster, 'poster': poster, 'thumb': poster, 'fanart': self.params_get('fanart'), 'banner': poster})
				try:
					listitem.addContextMenuItems([(favourites_context_label(fav_type, person_id), 'RunPlugin(%s)' % build_url({'mode': 'favourites_choice', 'media_type': 'person', 'fav_type': fav_type, 'tmdb_id': person_id, 'title': name}))], replaceItems=False)
				except: pass
				items.append((url, listitem, False))
		except Exception as e:
			ku.logger('custom favourites people error', '%s: %s' % (type(e).__name__, e))
		return items

	def _custom_favourites_list_items(self, folder_id):
		items = []
		try:
			fav_type = custom_favourite_type(folder_id, 'list')
			icon = media_path('favourites.png')
			for fav in favourites_cache.get_favourites(fav_type) or []:
				try: payload = json.loads(fav.get('tmdb_id') or '{}')
				except Exception: payload = {}
				mode = payload.get('mode')
				if mode not in ('build_trakt_list', 'build_tmdb_list', 'build_mdb_list'): continue
				name = fav.get('title') or 'Liste'
				url_params = {'mode': mode, 'name': name}
				for key in ('user', 'slug', 'list_id', 'list_type', 'media_filter'):
					value = payload.get(key)
					if value not in ('', None): url_params[key] = value
				url = build_url(url_params)
				listitem = make_listitem()
				listitem.setLabel(name)
				listitem.setArt({'icon': icon, 'poster': icon, 'thumb': icon, 'fanart': self.params_get('fanart'), 'banner': icon, 'landscape': icon})
				try:
					fav_params = dict(url_params)
					fav_params['target_mode'] = fav_params.get('mode')
					fav_params.update({'mode': 'favourites_choice', 'media_type': 'list', 'fav_type': fav_type, 'list_service': payload.get('service', ''), 'title': name, 'name': name})
					cm = ku.menu_editor_context(url_params, name, icon, True)
					cm.append(('Favoris (alkoFlix)', 'RunPlugin(%s)' % build_url(fav_params)))
					listitem.addContextMenuItems(cm, replaceItems=False)
				except Exception: pass
				items.append((url, listitem, True))
		except Exception as e:
			ku.logger('custom favourites lists error', '%s: %s' % (type(e).__name__, e))
		return items

	def favourites_custom(self):
		folder_id = self.params_get('folder_id')
		folder_name = get_custom_folder_name(folder_id, self.params_get('name', 'Dossier personnalisé'))
		n_ins = _in_str % (folder_name.upper(), '')
		items = []
		items.extend(self._custom_favourites_movie_items(folder_id, folder_name))
		items.extend(self._custom_favourites_tv_items(folder_id, folder_name))
		items.extend(self._custom_favourites_season_episode_items(folder_id, folder_name))
		items.extend(self._custom_favourites_collection_items(folder_id))
		items.extend(self._custom_favourites_people_items(folder_id))
		items.extend(self._custom_favourites_list_items(folder_id))
		if items:
			add_items(self.params_get('handle'), items)
			if not ku.external_browse():
				self._add_item({'mode': 'favourites_choice', 'cache': 'clear_favourites', 'fav_group': 'custom', 'fav_types': '|'.join(custom_favourite_types(folder_id)), 'name': 'Vider ce dossier'}, 'broom-ball.png', n_ins, False)
		self._end_directory()

	def favourites_lists(self):
		# Favoris de listes : un seul dossier racine, peu importe le service.
		try:
			from caches.favourites_cache import favourites_cache
			items = favourites_cache.get_favourites(self.params_get('fav_type', 'list')) or []
		except Exception:
			items = []
		n_ins = _in_str % ('LISTES', '')
		for fav in items:
			try:
				payload = json.loads(fav.get('tmdb_id') or '{}')
			except Exception:
				payload = {}
			mode = payload.get('mode')
			if mode not in ('build_trakt_list', 'build_tmdb_list', 'build_mdb_list'):
				continue
			name = fav.get('title') or 'Liste'
			url_params = {'mode': mode, 'name': name}
			for key in ('user', 'slug', 'list_id', 'list_type', 'media_filter'):
				value = payload.get(key)
				if value not in ('', None): url_params[key] = value
			icon = media_path('favourites.png')
			url_params['iconImage'] = icon
			url = build_url(url_params)
			listitem = make_listitem()
			listitem.setLabel(name)
			listitem.setArt({'icon': icon, 'poster': icon, 'thumb': icon, 'fanart': self.params_get('fanart'), 'banner': icon, 'landscape': icon})
			try:
				fav_params = dict(url_params)
				fav_params['target_mode'] = fav_params.get('mode')
				fav_params.update({'mode': 'favourites_choice', 'media_type': 'list', 'fav_type': 'list', 'list_service': payload.get('service', ''), 'title': name, 'name': name})
				cm = ku.menu_editor_context(url_params, name, icon, True)
				cm.append(('Favoris (alkoFlix)', 'RunPlugin(%s)' % build_url(fav_params)))
				listitem.addContextMenuItems(cm, replaceItems=False)
			except Exception:
				pass
			add_item(self.params_get('handle'), url, listitem, True)
		self._end_directory()

	def favourites_series(self):
		# Conservé pour le menu racine Séries, qui garde ses sous-dossiers propres.
		fav_str = ls(32453)
		n_ins = _in_str % (fav_str.upper(), '')
		group = self.params_get('fav_group', 'root')
		mapping = {
			'root': ('favourites_tvshows', 'favourites_seasons', 'favourites_episodes', 'tvshow', 'season', 'episode', 'series_root'),
			'family': ('favourites_tv_family', 'favourites_seasons_family', 'favourites_episodes_family', 'tv_family', 'season_family', 'episode_family', 'series_family'),
			'animation': ('favourites_tv_animation', 'favourites_seasons_animation', 'favourites_episodes_animation', 'tv_animation', 'season_animation', 'episode_animation', 'series_animation'),
			'anime': ('favourites_anime_tv', 'favourites_anime_seasons', 'favourites_anime_episodes', 'anime_tv', 'anime_season', 'anime_episode', 'series_anime'),
			'documentary': ('favourites_tv_documentary', 'favourites_seasons_documentary', 'favourites_episodes_documentary', 'tv_documentary', 'season_documentary', 'episode_documentary', 'series_documentary')
		}
		tv_action, season_action, episode_action, tv_fav_type, season_fav_type, episode_fav_type, clear_group = mapping.get(group, mapping['root'])
		has_any = False
		# Le dossier Séries reste visible même vide, comme en navigation dédiée.
		self._add_item({'mode': 'build_tvshow_list', 'action': tv_action, 'name': 'Séries', 'fav_type': tv_fav_type}, 'favourites.png', n_ins)
		has_any = True
		has_any = self._fav_add_if((season_fav_type,), {'mode': 'build_season_list', 'action': season_action, 'name': 'Saisons', 'parent_fav_type': tv_fav_type}, 'favourites.png', n_ins) or has_any
		has_any = self._fav_add_if((episode_fav_type,), {'mode': 'build_season_list', 'action': episode_action, 'name': 'Épisodes', 'parent_fav_type': tv_fav_type}, 'favourites.png', n_ins) or has_any
		self._end_directory()

	def _favourites_contextual(self, movie_action, movie_type, tv_action, season_action, episode_action, tv_type, season_type, episode_type, include_sagas=False, saga_fav_type='collection', saga_type=''):
		# Favoris contextuels : tout est listé directement dans le dossier Favoris du menu.
		# Les paramètres de contexte sont volontairement répétés dans les URL : ils
		# empêchent Kodi de retomber sur les favoris globaux quand une entrée provient
		# d'un ancien cache navigateur.
		fav_str = ls(32453)
		n_ins = _in_str % (fav_str.upper(), '')
		has_any = False
		movie_params = {'mode': 'build_movie_list', 'action': movie_action, 'name': 'Favoris (Films)', 'fav_type': movie_type}
		tv_params = {'mode': 'build_tvshow_list', 'action': tv_action, 'name': 'Favoris (Séries)', 'fav_type': tv_type}
		season_params = {'mode': 'build_season_list', 'action': season_action, 'name': 'Favoris (Saisons)', 'parent_fav_type': tv_type}
		episode_params = {'mode': 'build_season_list', 'action': episode_action, 'name': 'Favoris (Épisodes)', 'parent_fav_type': tv_type}
		if movie_type == 'movie_family': movie_params['family_type'] = 'movie_family'
		if tv_type == 'tv_family':
			tv_params['family_type'] = 'series_family'
			tv_params['fav_group'] = 'series_family'
			season_params['parent_fav_type'] = 'tv_family'
			season_params['fav_group'] = 'series_family'
			episode_params['parent_fav_type'] = 'tv_family'
			episode_params['fav_group'] = 'series_family'
		# Les deux sous-dossiers principaux doivent rester visibles dans le dossier
		# Favoris du menu dédié, même s'ils sont vides. C'est le comportement attendu
		# par les utilisateurs et cela rend immédiatement visible où ajouter/transférer.
		self._add_item(movie_params, 'favourites.png', n_ins)
		self._add_item(tv_params, 'favourites.png', n_ins)
		has_any = True
		has_any = self._fav_add_if((season_type,), season_params, 'favourites.png', n_ins) or has_any
		has_any = self._fav_add_if((episode_type,), episode_params, 'favourites.png', n_ins) or has_any
		if include_sagas:
			has_any = self._fav_add_if((saga_fav_type,), {'mode': 'catalogue.sagas_favourites', 'name': 'Favoris (Sagas)', 'fav_type': saga_fav_type, 'saga_type': saga_type}, 'favourites.png', n_ins) or has_any
		self._end_directory()

	def favourites_family(self):
		self._favourites_contextual('favourites_movie_family', 'movie_family', 'favourites_tv_family', 'favourites_seasons_family', 'favourites_episodes_family', 'tv_family', 'season_family', 'episode_family', True, 'collection_family', 'family')

	def favourites_animation(self):
		self._favourites_contextual('favourites_movie_animation', 'movie_animation', 'favourites_tv_animation', 'favourites_seasons_animation', 'favourites_episodes_animation', 'tv_animation', 'season_animation', 'episode_animation', True, 'collection_animation', 'animation')

	def favourites_anime(self):
		self._favourites_contextual('favourites_anime_movie', 'anime_movie', 'favourites_anime_tv', 'favourites_anime_seasons', 'favourites_anime_episodes', 'anime_tv', 'anime_season', 'anime_episode', True, 'collection_anime', 'anime')

	def favourites_documentary(self):
		self._favourites_contextual('favourites_movie_documentary', 'movie_documentary', 'favourites_tv_documentary', 'favourites_seasons_documentary', 'favourites_episodes_documentary', 'tv_documentary', 'season_documentary', 'episode_documentary')

	def _my_content_statuses(self):
		trakt_status = ku.get_setting('trakt_user') not in ('', None)
		tmdb_status = ku.get_setting('tmdb.account_id') not in ('', None)
		mdblist_status = ku.get_setting('mdblist.access_token') not in ('', None) or ku.get_setting('mdblist.token') not in ('', None)
		return trakt_status, tmdb_status, mdblist_status

	def my_content(self):
		trakt_status, tmdb_status, mdblist_status = self._my_content_statuses()
		active_services = []
		if trakt_status: active_services.append(('navigator.my_content_trakt', 'Trakt', 'trakt.png'))
		if mdblist_status: active_services.append(('navigator.my_content_mdblist', 'MDBList', 'mdblist.png'))
		if tmdb_status: active_services.append(('navigator.my_content_tmdb', 'TMDb', 'tmdb.png'))

		if len(active_services) > 1:
			n_ins = _in_str % (ls(32454).upper(), '')
			for mode, name, icon in active_services:
				self._add_item({'mode': mode, 'name': name, 'exclude_external': 'true'}, icon, n_ins)
			self._end_directory()
			return

		if trakt_status: self._add_my_content_trakt()
		elif mdblist_status: self._add_my_content_mdblist()
		elif tmdb_status: self._add_my_content_tmdb()
		else: self._add_my_content_trakt(include_personal=False)
		self._end_directory()

	def my_content_trakt(self):
		self._add_my_content_trakt()
		self._end_directory()

	def my_content_mdblist(self):
		self._add_my_content_mdblist()
		self._end_directory()

	def my_content_tmdb(self):
		self._add_my_content_tmdb()
		self._end_directory()

	def _add_my_content_trakt(self, include_personal=True):
		trakt_str, coll_str, wlist_str, fav_str, ls_str = ls(32037), ls(32499), ls(32500), ls(32453), ls(32501)
		t_n_ins = _in_str % (trakt_str.upper(), '')
		user_str, l_str, ai_str = ls(32065), ls(32501), ls(32494)
		tu_str = 'Listes tendances'
		pu_str = 'Listes populaires'
		sea_str = 'Recherche de listes'
		if include_personal:
			# Raccourcis personnels Trakt visibles dans Mes listes > Trakt.
			# Favoris doit rester au même niveau que Watchlist et Collection.
			self._add_item({'mode': 'navigator.trakt_watchlists' , 'name': wlist_str}, 'thumbtack.png', t_n_ins)
			self._add_item({'mode': 'navigator.trakt_favorites'  , 'name': fav_str  }, 'favourites.png', t_n_ins)
			self._add_item({'mode': 'navigator.trakt_collections', 'name': coll_str }, 'sets.png', t_n_ins)
			self._add_item({'mode': 'navigator.trakt_lists'      , 'name': ls_str   }, 'trakt.png', t_n_ins)
			self._add_item({'mode': 'build_tvshow_list', 'action': 'in_progress_tvshows', 'watched_indicators': '1', 'name': ls(32481)}, 'recent.png', t_n_ins)
			self._add_item({'mode': 'build_tvshow_list', 'action': 'trakt_in_progress_tvshows', 'watched_indicators': '1', 'hide_watched_items': 'true', 'name': ls(33122)}, 'in_progress_tvshow.png', t_n_ins)
			self._add_item({'mode': 'build_next_episode', 'watched_indicators': '1', 'name': ls(32483)}, 'next_episode.png', t_n_ins)
			self._add_item({'mode': 'trakt.trakt_account_info'   , 'name': ai_str   }, 'info2.png', t_n_ins, False)
		self._add_item({'mode': 'build_trakt_list.get_trakt_trending_popular_lists', 'list_type': 'trending', 'name': tu_str }, 'rocket.png', t_n_ins)
		self._add_item({'mode': 'build_trakt_list.get_trakt_trending_popular_lists', 'list_type': 'popular' , 'name': pu_str }, 'popular.png', t_n_ins)
		self._add_item({'mode': 'build_trakt_list.search_trakt_lists'                                       , 'name': sea_str}, 'search.png', t_n_ins)

	def _add_my_content_mdblist(self):
		coll_str, wlist_str, fav_str = ls(32499), ls(32500), ls(32453)
		ai_str, ml_str = ls(32494), ls(32454)
		pu_str, sea_str = 'Listes populaires', 'Recherche de listes'
		m_n_ins = _in_str % ('MDBList'.upper(), '')
		self._add_item({'mode': 'navigator.mdblist_watchlists' , 'name': wlist_str}, 'thumbtack.png', m_n_ins)
		self._add_item({'mode': 'navigator.mdblist_favorites'  , 'name': fav_str  }, 'favourites.png', m_n_ins)
		self._add_item({'mode': 'navigator.mdblist_collections', 'name': coll_str }, 'sets.png', m_n_ins)
		self._add_item({'mode': 'build_mdb_list.get_mdbl_lists', 'list_type': 'my_lists', 'name': ml_str}, 'mdblist.png', m_n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'in_progress_tvshows', 'watched_indicators': '2', 'name': ls(32481)}, 'recent.png', m_n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'trakt_in_progress_tvshows', 'watched_indicators': '2', 'hide_watched_items': 'true', 'name': ls(33122)}, 'in_progress_tvshow.png', m_n_ins)
		self._add_item({'mode': 'build_next_episode', 'watched_indicators': '2', 'name': ls(32483)}, 'next_episode.png', m_n_ins)
		self._add_item({'mode': 'mdblist.mdbl_account_info', 'name': ai_str}, 'info2.png', m_n_ins, False)
		self._add_item({'mode': 'build_mdb_list.get_mdbl_toplists', 'name': pu_str}, 'popular.png', m_n_ins)
		self._add_item({'mode': 'build_mdb_list.search_mdbl_lists', 'name': sea_str}, 'search.png', m_n_ins)


	def connected_lists(self):
		media_type = self.params_get('menu_type', 'movie')
		media_label = mov_str if media_type == 'movie' else tv_str
		list_ins = _in_str % (('%s - %s' % (ls(32501), media_label)).upper(), '')
		trakt_status, tmdb_status, mdblist_status = self._my_content_statuses()
		active_services = []
		if trakt_status: active_services.append(('navigator.connected_lists_trakt', 'Trakt', 'trakt.png'))
		if mdblist_status: active_services.append(('navigator.connected_lists_mdblist', 'MDBList', 'mdblist.png'))
		if tmdb_status: active_services.append(('navigator.connected_lists_tmdb', 'TMDb', 'tmdb.png'))

		# Films > Listes et Séries > Listes suivent la même logique que le menu Listes racine :
		# un seul service connecté = arborescence dépliée directement; plusieurs services = dossiers par service.
		if len(active_services) == 1:
			mode = active_services[0][0]
			if mode == 'navigator.connected_lists_trakt': return self.connected_lists_trakt()
			if mode == 'navigator.connected_lists_mdblist': return self.connected_lists_mdblist()
			if mode == 'navigator.connected_lists_tmdb': return self.connected_lists_tmdb()

		if len(active_services) > 1:
			for mode, name, icon in active_services:
				self._add_item({'mode': mode, 'menu_type': media_type, 'name': name, 'exclude_external': 'true'}, icon, list_ins)
		else:
			self._add_item({'mode': 'open_settings', 'query': '4.0', 'exclude_external': 'true', 'name': 'Aucun service de listes connecté'}, 'info2.png', list_ins, False)
		self._end_directory()

	def _media_list_mode_action(self, action):
		media_type = self.params_get('menu_type', 'movie')
		return ('build_movie_list', action, mov_str) if media_type == 'movie' else ('build_tvshow_list', action, tv_str)

	def connected_lists_trakt(self):
		media_type = self.params_get('menu_type', 'movie')
		mode, _, media_label = self._media_list_mode_action('')
		n_ins = _in_str % (('TRAKT - %s' % media_label).upper(), '')
		self._add_item({'mode': mode, 'action': 'trakt_watchlist', 'name': ls(32500)}, 'thumbtack.png', n_ins)
		self._add_item({'mode': mode, 'action': 'trakt_favorites', 'name': ls(32453)}, 'favourites.png', n_ins)
		self._add_item({'mode': mode, 'action': 'trakt_collection', 'name': ls(32499)}, 'sets.png', n_ins)
		self._add_item({'mode': 'build_trakt_list.get_trakt_lists', 'list_type': 'my_lists', 'media_filter': media_type, 'name': ls(32454)}, 'user.png', n_ins)
		self._add_item({'mode': 'build_trakt_list.get_trakt_lists', 'list_type': 'liked_lists', 'media_filter': media_type, 'name': ls(32502)}, 'favourites.png', n_ins)
		self._add_item({'mode': 'build_trakt_list.get_trakt_trending_popular_lists', 'list_type': 'trending', 'media_filter': media_type, 'name': 'Listes tendances'}, 'rocket.png', n_ins)
		self._add_item({'mode': 'build_trakt_list.get_trakt_trending_popular_lists', 'list_type': 'popular', 'media_filter': media_type, 'name': 'Listes populaires'}, 'popular.png', n_ins)
		self._add_item({'mode': 'build_trakt_list.search_trakt_lists', 'media_filter': media_type, 'name': 'Recherche de listes'}, 'search.png', n_ins)
		self._end_directory()

	def connected_lists_mdblist(self):
		media_type = self.params_get('menu_type', 'movie')
		mode, _, media_label = self._media_list_mode_action('')
		n_ins = _in_str % (('MDBLIST - %s' % media_label).upper(), '')
		self._add_item({'mode': mode, 'action': 'mdblist_watchlist', 'name': ls(32500)}, 'thumbtack.png', n_ins)
		self._add_item({'mode': mode, 'action': 'mdblist_favorites', 'name': ls(32453)}, 'favourites.png', n_ins)
		self._add_item({'mode': mode, 'action': 'mdblist_collection', 'name': ls(32499)}, 'sets.png', n_ins)
		self._add_item({'mode': 'build_mdb_list.get_mdbl_lists', 'list_type': 'my_lists', 'media_filter': media_type, 'name': ls(32454)}, 'mdblist.png', n_ins)
		self._add_item({'mode': 'build_mdb_list.get_mdbl_toplists', 'media_filter': media_type, 'name': 'Listes populaires'}, 'popular.png', n_ins)
		self._add_item({'mode': 'build_mdb_list.search_mdbl_lists', 'media_filter': media_type, 'name': 'Recherche de listes'}, 'search.png', n_ins)
		self._end_directory()

	def connected_lists_tmdb(self):
		media_type = self.params_get('menu_type', 'movie')
		mode, _, media_label = self._media_list_mode_action('')
		n_ins = _in_str % (('TMDB - %s' % media_label).upper(), '')
		self._add_item({'mode': mode, 'action': 'tmdb_watchlist', 'name': ls(32500)}, 'thumbtack.png', n_ins)
		self._add_item({'mode': mode, 'action': 'tmdb_favorite', 'name': ls(32453)}, 'favourites.png', n_ins)
		self._add_item({'mode': mode, 'action': 'tmdb_recommendations', 'name': ls(32503)}, 'announce.png', n_ins)
		self._add_item({'mode': 'build_tmdb_list.get_tmdb_lists', 'media_filter': media_type, 'name': ls(32454)}, 'user.png', n_ins)
		self._end_directory()

	def mdblist_watchlists(self):
		wlist_str = ls(32500)
		n_ins = _in_str % (('MDBList %s' % wlist_str).upper(), '')
		self._add_item({'mode': 'build_movie_list', 'action': 'mdblist_watchlist', 'name': mov_str}, 'thumbtack.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'mdblist_watchlist', 'name': tv_str}, 'thumbtack.png', n_ins)
		self._end_directory()

	def mdblist_favorites(self):
		fav_str = ls(32453)
		n_ins = _in_str % (('MDBList %s' % fav_str).upper(), '')
		self._add_item({'mode': 'build_movie_list', 'action': 'mdblist_favorites', 'name': mov_str}, 'favourites.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'mdblist_favorites', 'name': tv_str}, 'favourites.png', n_ins)
		self._end_directory()

	def mdblist_collections(self):
		coll_str = ls(32499)
		n_ins = _in_str % (('MDBList %s' % coll_str).upper(), '')
		self._add_item({'mode': 'build_movie_list', 'action': 'mdblist_collection', 'name': mov_str}, 'sets.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'mdblist_collection', 'name': tv_str}, 'sets.png', n_ins)
		self._end_directory()

	def _add_my_content_tmdb(self):
		wlist_str, fav_str, rec_str, ml_str = ls(32500), ls(32453), ls(32503), ls(32454)
		tmdb_n_ins = _in_str % ('TMDb'.upper(), '')
		self._add_item({'mode': 'build_movie_list', 'action': 'tmdb_watchlist'       , 'name': '%s (%s)' % (wlist_str, mov_str)}, 'thumbtack.png', tmdb_n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'tmdb_watchlist'      , 'name': '%s (%s)' % (wlist_str, tv_str) }, 'thumbtack.png', tmdb_n_ins)
		self._add_item({'mode': 'build_tmdb_list.get_tmdb_lists'                     , 'name': ml_str                         }, 'user.png', tmdb_n_ins)
		self._add_item({'mode': 'build_movie_list', 'action': 'tmdb_favorite'        , 'name': '%s (%s)' % (fav_str, mov_str)  }, 'favourites.png', tmdb_n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'tmdb_favorite'       , 'name': '%s (%s)' % (fav_str, tv_str)   }, 'favourites.png', tmdb_n_ins)
		self._add_item({'mode': 'build_movie_list', 'action': 'tmdb_recommendations' , 'name': '%s (%s)' % (rec_str, mov_str)  }, 'announce.png', tmdb_n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'tmdb_recommendations', 'name': '%s (%s)' % (rec_str, tv_str)   }, 'announce.png', tmdb_n_ins)

	def tmdb_favorites(self):
		fav_str = ls(32453)
		n_ins = _in_str % (('TMDb %s' % fav_str).upper(), '')
		self._add_item({'mode': 'build_movie_list', 'action': 'tmdb_favorite',  'name': mov_str}, 'favourites.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'tmdb_favorite', 'name': tv_str }, 'favourites.png', n_ins)
		self._end_directory()

	def trakt_collections(self):
		# Utilise 'new_page' pour indiquer le type de liste à traiter lors de l'utilisation de 'trakt_collection_lists'
		t_str, col_str = ls(32037), ls(32499)
		tcol_str = '%s %s' % (t_str, col_str)
		n_ins = _in_str % (tcol_str.upper(), '')
		mrec_str, mran_str = '%s %s' % (ls(32498), mov_str), '%s %s' % (ls(32504), mov_str)
		tvrec_str, tvran_str, ra_str = '%s %s' % (ls(32498), tv_str), '%s %s' % (ls(32504), tv_str), '%s %s' % (ls(32505), ls(32506))
		n_ins = _in_str % (col_str.upper(), '')
		self._add_item({'mode': 'build_movie_list', 'action': 'trakt_collection'                             , 'name': mov_str  }, 'sets.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'trakt_collection'                            , 'name': tv_str   }, 'sets.png', n_ins)
		self._add_item({'mode': 'build_movie_list', 'action': 'trakt_collection_lists', 'new_page': 'recent' , 'name': mrec_str }, 'star.png', n_ins)
		self._add_item({'mode': 'build_movie_list', 'action': 'trakt_collection_lists', 'new_page': 'random' , 'name': mran_str }, 'shuffle.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'trakt_collection_lists', 'new_page': 'recent', 'name': tvrec_str}, 'star.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'trakt_collection_lists', 'new_page': 'random', 'name': tvran_str}, 'shuffle.png', n_ins)
		self._add_item({'mode': 'build_my_calendar', 'recently_aired': 'true'                                , 'name': ra_str   }, 'calendar.png', n_ins)
		self._end_directory()

	def trakt_watchlists(self):
		t_str, watchlist_str = ls(32037), ls(32500)
		trakt_watchlist_str = '%s %s' % (t_str, watchlist_str)
		n_ins = _in_str % (trakt_watchlist_str.upper(), '')
		tmdb_status = ku.get_setting('tmdb.account_id') not in ('', None)
		self._add_item({'mode': 'build_movie_list', 'action': 'trakt_watchlist',  'name': mov_str}, 'thumbtack.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'trakt_watchlist', 'name': tv_str }, 'thumbtack.png', n_ins)
		self._add_item({'mode': 'tmdb.import_trakt_watchlist',                    'name': ls(33147)}, 'arrow-up-right-from-square.png', n_ins, False) if tmdb_status else None
		self._end_directory()


	def trakt_favorites(self):
		t_str, fav_str = ls(32037), ls(32453)
		trakt_favorites_str = '%s %s' % (t_str, fav_str)
		n_ins = _in_str % (trakt_favorites_str.upper(), '')
		self._add_item({'mode': 'build_movie_list', 'action': 'trakt_favorites',  'name': mov_str}, 'favourites.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'trakt_favorites', 'name': tv_str }, 'favourites.png', n_ins)
		self._end_directory()

	def trakt_lists(self):
		t_str, ml_str, ll_str, rec_str = ls(32037), ls(32454), ls(32502), ls(32503)
		cal_str, ani_str, drp_str = ls(32081), ls(33148), ls(33149)
		n_ins = _in_str % (t_str.upper(), '')
		self._add_item({'mode': 'build_trakt_list.get_trakt_lists', 'list_type': 'my_lists'   , 'name': ml_str }, 'user.png', n_ins)
		self._add_item({'mode': 'build_trakt_list.get_trakt_lists', 'list_type': 'liked_lists', 'name': ll_str }, 'favourites.png', n_ins)
		self._add_item({'mode': 'navigator.trakt_recommendations'                             , 'name': rec_str}, 'announce.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'trakt_droplist'               , 'name': drp_str}, 'eye-slash.png', n_ins)
		self._add_item({'mode': 'build_my_calendar'                                           , 'name': cal_str}, 'calendar.png', n_ins)
		self._add_item({'mode': 'build_my_anime_calendar'                                     , 'name': ani_str}, 'anime.png', n_ins)
		self._end_directory()

	def trakt_recommendations(self):
		rec_str = ls(32503)
		n_ins = _in_str % (rec_str.upper(), '')
		self._add_item({'mode': 'build_movie_list', 'action': 'trakt_recommendations',  'name': mov_str}, 'announce.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'trakt_recommendations', 'name': tv_str }, 'announce.png', n_ins)
		self._end_directory()

	def imdb_watchlists(self):
		imdb_str, watchlist_str = ls(32064), ls(32500)
		imdb_watchlist_str = '%s %s' % (imdb_str, watchlist_str)
		n_ins = _in_str % (imdb_watchlist_str.upper(), '')
		self._add_item({'mode': 'build_movie_list', 'action': 'imdb_watchlist',  'name': mov_str}, 'thumbtack.png', n_ins)
		self._add_item({'mode': 'build_tvshow_list', 'action': 'imdb_watchlist', 'name': tv_str }, 'thumbtack.png', n_ins)
		self._end_directory()

	def imdb_lists(self):
		imdb_str, lists_str = ls(32064), ls(32501)
		imdb_lists_str = '%s %s' % (imdb_str, lists_str)
		n_ins = _in_str % (imdb_lists_str.upper(), '')
		self._add_item({'mode': 'imdb_build_user_lists', 'media_type': 'movie',  'name': mov_str}, 'film.png', n_ins)
		self._add_item({'mode': 'imdb_build_user_lists', 'media_type': 'tvshow', 'name': tv_str }, 'tv.png', n_ins)
		self._end_directory()

	def search(self):
		search_str, people_str, clca_str, help_str = ls(32450), ls(32507), ls(32497), ls(32487)
		coll_str, clear_search_str = 'Sagas de films', clca_str % search_str
		kw_mov, kw_tv = 'Mot-clé TMDb (%s)' % mov_str, 'Mot-clé TMDb (%s)' % tv_str
		n_ins, s_n_ins = _in_str % (ls(32524).upper(), ''), _in_str % (search_str.upper(), '')
		self._add_item({'mode': 'navigator.discover_main',                         'name': ls(32451)       }, 'discover.png', s_n_ins)
		self._add_item({'mode': 'search_history', 'action': 'movie',               'name': mov_str         }, 'search.png' , s_n_ins)
		self._add_item({'mode': 'search_history', 'action': 'tvshow',              'name': tv_str          }, 'search.png'    , s_n_ins)
		self._add_item({'mode': 'search_history', 'action': 'people',              'name': people_str      }, 'search.png', s_n_ins)
		self._add_item({'mode': 'search_history', 'action': 'tmdb_collections',    'name': coll_str        }, 'search.png'  , s_n_ins)
		self._add_item({'mode': 'search_history', 'action': 'tmdb_keyword_movie',  'name': kw_mov          }, 'search_tmdb.png'  , s_n_ins)
		self._add_item({'mode': 'search_history', 'action': 'tmdb_keyword_tvshow', 'name': kw_tv           }, 'search_tmdb.png'  , s_n_ins)
		self._add_item({'mode': 'tmdb_keyword_help', 'name': help_str, 'exclude_external': 'true'}, 'info2.png', s_n_ins, False)
		self._add_item({'mode': 'clear_search_history',                            'name': clear_search_str}, 'broom-ball.png'        , n_ins, False)
		self._end_directory()

	def settings(self):
		changelog_str, log_utils, views_str, ms_str = ls(32508), ls(32777), ls(32510), ls(32455)
		settings_str, changelog_log_viewer_str = ls(32456), log_utils
		alkoflix_settings_str = 'Réglages alkoFlix'
		shortcut_manager_str = 'Gestion des dossiers perso'
		n_ins = _in_str % (settings_str.upper(), '')
		self._add_item({'mode': 'open_settings',                 'query': '0.0', 'exclude_external': 'true', 'name': alkoflix_settings_str}, 'settings.png', n_ins, False)
		self._add_item({'mode': 'myservices',                    'name': ms_str                  }, 'user.png', n_ins, False)
		self._add_item({'mode': 'navigator.log_utils',           'name': changelog_log_viewer_str}, 'file-circle-exclamation.png', n_ins)
		self._add_item({'mode': 'navigator.set_view_modes',      'name': views_str               }, 'brush.png', n_ins)
		self._add_item({'mode': 'navigator.shortcut_folders',    'name': shortcut_manager_str    }, 'myfolder.png', n_ins)
		self._end_directory()

	def set_view_modes(self):
		set_views_str, lists_str, root_str, movies_str = ls(32510), ls(32501), ls(32457), ls(32028)
		tvshows_str, season_str, episode_str = ls(32029), ls(32537), ls(32506)
		premium_files_str, ep_lists_str = ls(32485), 'Listes d\'épisodes'
		n_ins, reset_str = _in_str % (set_views_str.upper(), ''), 'Réinitialiser toutes les vues'
		self._add_item({'mode': 'choose_view', 'view_type': 'view.main', 'content': '', 'exclude_external': 'true'                  , 'name': root_str         }, 'alkoflix.png', n_ins)
		self._add_item({'mode': 'choose_view', 'view_type': 'view.movies', 'content': 'movies', 'exclude_external': 'true'          , 'name': movies_str       }, 'movies.png', n_ins)
		self._add_item({'mode': 'choose_view', 'view_type': 'view.tvshows', 'content': 'tvshows', 'exclude_external': 'true'        , 'name': tvshows_str      }, 'tv.png', n_ins)
		self._add_item({'mode': 'choose_view', 'view_type': 'view.seasons', 'content': 'seasons', 'exclude_external': 'true'        , 'name': season_str       }, 'myfolder.png', n_ins)
		self._add_item({'mode': 'choose_view', 'view_type': 'view.episodes', 'content': 'episodes', 'exclude_external': 'true'      , 'name': episode_str      }, 'on_the_air.png', n_ins)
		self._add_item({'mode': 'choose_view', 'view_type': 'view.episodes_lists', 'content': 'episodes', 'exclude_external': 'true', 'name': ep_lists_str     }, 'lists.png', n_ins)
		self._add_item({'mode': 'choose_view', 'view_type': 'view.premium', 'content': 'files', 'exclude_external': 'true'          , 'name': premium_files_str}, 'premium.png', n_ins)
		self._add_item({'mode': 'clear_view', 'view_type': 'all'                                                                    , 'name': reset_str        }, 'arrow-rotate-right.png', n_ins, False)
		self._end_directory()

	def log_utils(self):
		alkoflix_vstr, log_path = ku.get_addoninfo('version'), 'special://home/addons/%s/changelog.txt'
		kl_loc, mt_str = 'special://logpath/kodi.log', log_path % 'plugin.video.alkoflix'
		alkoflix_str, cl_str, lut_str, k_str, lv_str = ls(32036), ls(32508), ls(32777), ls(32538), ls(32509)
		mh_str, klv_h, klu_h = '%s  (v.%s)' % (alkoflix_str, alkoflix_vstr), '%s %s' % (k_str, lv_str), ls(32853)
		cl_n_ins, lu_n_ins = _in_str % (cl_str.upper(), ''), _in_str % (lut_str.upper(), '')
		self._add_item({'mode': 'show_text', 'heading': mh_str, 'file': mt_str, 'exclude_external': 'true',                    'name': mh_str}, 'file-circle-exclamation.png', cl_n_ins, False)
		self._add_item({'mode': 'show_text', 'heading': klv_h, 'file': kl_loc, 'kodi_log': 'true', 'exclude_external': 'true', 'name': klv_h }, 'kodi.png', lu_n_ins, False)
		self._add_item({'mode': 'upload_logfile', 'exclude_external': 'true',                                                  'name': klu_h }, 'file-import.png', lu_n_ins, False)
		self._end_directory()

	def years(self):
		from modules.meta_lists import years
		menu_type = self.params_get('menu_type')
		mode = 'build_movie_list' if menu_type == 'movie' else 'build_tvshow_list'
		action = 'tmdb_movies_year' if menu_type == 'movie' else 'tmdb_tv_year'
		lst_ins = self.make_list_name(menu_type)
		for i in years():
			list_name = '%s: %s %s' % (lst_ins.upper(), str(i), ls(32460))
			self._add_item({'mode': mode, 'action': action, 'year': str(i), 'name': str(i)}, 'calendar.png', list_name=list_name)
		self._end_directory()

	def anime_years(self):
		from modules.meta_lists import years
		menu_type = self.params_get('menu_type')
		mode = 'build_movie_list' if menu_type == 'movie' else 'build_tvshow_list'
		action = 'tmdb_moviesanime_year' if menu_type == 'movie' else 'tmdb_tvanime_year'
		lst_ins = self.make_list_name(menu_type)
		for i in years():
			list_name = 'ANIME %s: %s %s' % (lst_ins.upper(), str(i), ls(32460))
			self._add_item({'mode': mode, 'action': action, 'year': str(i), 'name': str(i)}, 'calendar.png', list_name=list_name)
		self._end_directory()

	def genres(self):
		import json
		menu_type = self.params_get('menu_type')
		if menu_type == 'movie':
			from modules.meta_lists import movie_genres as genre_list
			mode, action = 'build_movie_list', 'tmdb_movies_genres'
		else:
			from modules.meta_lists import tvshow_genres as genre_list
			mode, action = 'build_tvshow_list', 'tmdb_tv_genres'
		# Les genres isolés dans des menus dédiés sont retirés des menus généraux
		# pour éviter les doublons dans Films/Séries.
		excluded_genres = {'16'}
		if menu_type == 'movie':
			excluded_genres.update(('99', '10770'))
		else:
			excluded_genres.update(('99', '10763', '10764', '10767'))
		genre_list = {genre: value for genre, value in genre_list.items() if str(value[0]) not in excluded_genres}
		lst_ins = self.make_list_name(menu_type)
		self._add_item({'mode': 'navigator.multiselect_genres', 'genre_list': json.dumps(genre_list), 'menu_type': menu_type, 'exclude_external': 'true', 'name': ls(32789)}, 'filter.png', isFolder=False)
		for genre, value in sorted(genre_list.items()):
			if str(value[0]) == '16': continue
			list_name = '%s: %s %s' % (lst_ins.upper(), genre, ls(32470))
			self._add_item({'mode': mode, 'action': action, 'genre_id': value[0], 'name': genre}, value[1], list_name=list_name)
		self._end_directory()

	def anime_genres(self):
		menu_type = self.params_get('menu_type')
		if menu_type == 'movie':
			from modules.meta_lists import movie_genres as genre_list
			mode, action = 'build_movie_list', 'tmdb_moviesanime_genres'
		else:
			from modules.meta_lists import tvshow_genres as genre_list
			mode, action = 'build_tvshow_list', 'tmdb_tvanime_genres'
		lst_ins = self.make_list_name(menu_type)
		for genre, value in sorted(genre_list.items()):
			list_name = 'ANIME %s: %s %s' % (lst_ins.upper(), genre, ls(32470))
			self._add_item({'mode': mode, 'action': action, 'genre_id': value[0], 'name': genre}, value[1], list_name=list_name)
		self._end_directory()

	def multiselect_genres(self):
		import json
		def _builder():
			for genre, value in genre_list.items():
				function_list_append(value[0])
				yield {'line1': genre, 'icon': '%s%s' % (icon_path, value[1])}
		menu_type, genre_list, icon_path = self.params['menu_type'], self.params['genre_list'], media_path()
		function_list = []
		function_list_append = function_list.append
		genre_list = dict(sorted(json.loads(genre_list).items()))
		list_items = list(_builder())
		kwargs = {'items': json.dumps(list_items), 'heading': ls(32847), 'enumerate': 'false', 'multi_choice': 'true', 'multi_line': 'false'}
		genre_ids = ku.select_dialog(function_list, **kwargs)
		if genre_ids is None: return
		genre_id = ','.join(genre_ids)
		if menu_type == 'movie': url = {'mode': 'build_movie_list', 'action': 'tmdb_movies_genres', 'genre_id': genre_id}
		else: url = {'mode': 'build_tvshow_list', 'action': 'tmdb_tv_genres', 'genre_id': genre_id}
		return ku.execute_builtin('Container.Update(%s)' % build_url(url))

	def networks(self):
		from modules.meta_lists import networks
		lst_ins = self.make_list_name(self.params_get('menu_type'))
		for item in sorted(networks, key=lambda k: k['name']):
			list_name = '%s: %s %s' % (lst_ins.upper(), item['name'], ls(32480))
			self._add_item({'mode': 'build_tvshow_list', 'action': 'tmdb_tv_networks', 'network_id': item['id'], 'name': item['name']}, _square_network_logo(item['logo']), list_name=list_name)
		self._end_directory()

	def because_you_watched(self):
		from caches.watched_cache import get_watched_info_movie, get_watched_info_tv
		def _convert_alkoflix_watched_episodes_info(watched_indicators):
			seen = set()
			_watched = get_watched_info_tv(watched_indicators)
			_watched.sort(key=lambda x: (x[0], x[1], x[2]), reverse=True)
			return [(i[0], i[3], i[4], [(i[1], i[2])]) for i in _watched if not (i[0] in seen or seen.add(i[0]))]
		watched_indicators = ks.watched_indicators()
		media_type = self.params_get('menu_type')
		function = get_watched_info_movie if media_type == 'movie' else _convert_alkoflix_watched_episodes_info
		mode = 'build_movie_list' if media_type == 'movie' else 'build_tvshow_list'
		action = 'tmdb_movies_recommendations' if media_type == 'movie' else 'tmdb_tv_recommendations'
		recently_watched = function(watched_indicators)
		recently_watched = sorted(recently_watched, key=lambda k: k[2], reverse=True)
		because_ins = '%s  [B]%s[/B]' % (ls(32474), '%s')
		for item in recently_watched:
			tmdb_id = item[0]
			if media_type == 'movie': name = because_ins % item[1]
			else:
				season, episode = item[3][-1]
				name = because_ins % '%s - %sx%s' % (item[1], season, episode)
			self._add_item({'mode': mode, 'action': action, 'tmdb_id': tmdb_id, 'exclude_external': 'true', 'name': name}, 'because_you_watched.png')
		self._end_directory()

	def folder_navigator(self):
		import os
		from modules.utils import clean_file_name, normalize
		def _process():
			for item, isFolder in items:
				try:
					url = os.path.join(folder_path, item)
					listitem = make_listitem()
					listitem.setLabel(clean_file_name(normalize(item)))
					listitem.setArt({'fanart': fanart})
					yield (url, listitem, isFolder)
				except: pass
		handle, fanart = self.params_get('handle'), self.params_get('fanart')
		folder_path = self.params_get('folder_path')
		sources_folders = self.params_get('sources_folders')
		dirs, files = list_dirs(folder_path)
		items = [(i, True) for i in dirs] + [(i, False) for i in files]
		add_items(handle, list(_process()))
		set_sort_method(handle, 'files')
		self._end_directory()

	def sources_folders(self):
		name_str = '%s (%s): %s\n     %s'
		for source in ('folder1', 'folder2', 'folder3', 'folder4', 'folder5'):
			for media_type in ('movie', 'tvshow'):
				folder_path = ks.source_folders_directory(media_type, source)
				if not folder_path: continue
				name = name_str % (source.upper(), self.make_list_name(media_type).upper(), ku.get_setting('%s.display_name' % source).upper(), folder_path)
				self._add_item({'mode': 'navigator.folder_navigator','sources_folders': 'True', 'folder_path': folder_path, 'name': name}, 'myfolder.png')
		self._end_directory()

	def shortcut_folders(self):
		def _builder():
			for i in folders:
				try:
					cm = []
					cm_append = cm.append
					contents = eval(i[1])
					name = i[0]
					folder_icon = nc.get_shortcut_folder_icon(name)
					icon = folder_icon if '://' in str(folder_icon) else '%s%s' % (icon_path, folder_icon)
					display_name = name
					url_params = {'name': name, 'iconImage': folder_icon, 'external_list_item': 'True'}
					url = build_url({'mode': 'navigator.build_shortcut_folder_list', 'shortcut_folder': 'True', **url_params})
					cm_append((rename_str, 'RunPlugin(%s)'% build_url({'mode': 'menu_editor.shortcut_folder_rename', 'list_name': name})))
					cm_append((choose_icon_str, 'RunPlugin(%s)'% build_url({'mode': 'menu_editor.shortcut_folder_choose_icon', 'list_name': name})))
					if folder_icon != 'myfolder.png':
						cm_append((reset_icon_str, 'RunPlugin(%s)'% build_url({'mode': 'menu_editor.shortcut_folder_reset_icon', 'list_name': name})))
					cm_append((delete_str, 'RunPlugin(%s)'% build_url({'mode': 'menu_editor.shortcut_folder_delete', 'list_name': name})))
					listitem = make_listitem()
					listitem.setLabel(display_name)
					listitem.addContextMenuItems(cm)
					listitem.setArt({'icon': icon, 'poster': icon, 'thumb': icon, 'fanart': fanart, 'banner': icon})
					yield (url, listitem, True)
				except: pass
		handle, fanart, icon_path = self.params_get('handle'), self.params_get('fanart'), media_path()
		short_str, delete_str, new_folder_str = ls(32514), ls(32703), ls(32702)
		rename_str, choose_icon_str, reset_icon_str = 'Renommer ce dossier', 'Choisir une icône', 'Réinitialiser l’icône'
		add_dir(handle, {'mode': 'menu_editor.shortcut_folder_make'}, new_folder_str, media_path('folder-plus.png'), isFolder=False)
		folders = nc.get_shortcut_folders()
		if folders: add_items(handle, list(_builder()))
		self._end_directory()

	def build_shortcut_folder_list(self):
		def _item_icon_path(icon_name):
			if icon_name in ('', 'None', None): return media_path('myfolder.png')
			if icon_name == 'alkoflix.png': return ku.get_addoninfo('icon')
			if '://' in str(icon_name): return icon_name
			return '%s%s' % (icon_path, icon_name)

		def _process():
			for item_position, item in enumerate(contents):
				try:
					item = dict(item)
					cm = []
					cm_append = cm.append
					item_get = item.get
					mode_check = item_get('mode', '')
					if mode_check == 'navigator.downloads' or mode_check == 'downloader' or '_downloads' in mode_check: continue
					display_name = item_get('name', 'Error: No Name')
					# Les dossiers perso imbriqués gardent un dossier cible séparé du titre/alias
					# affiché. On peut donc renommer l'entrée sans casser l'ouverture du vrai dossier.
					if mode_check == 'navigator.build_shortcut_folder_list':
						folder_name = item_get('shortcut_folder_name') or item_get('folder_name') or item_get('list_name') or display_name
						item['shortcut_folder'] = 'True'
						item['shortcut_folder_name'] = folder_name
						if not item_get('custom_icon'):
							item['iconImage'] = nc.get_shortcut_folder_icon(folder_name)
						url_item = dict(item)
						url_item['name'] = folder_name
						url = build_url(url_item)
					else:
						url = build_url(item)
					item_get = item.get
					icon = _item_icon_path(item_get('iconImage'))
					isFolder = False if item_get('isFolder', '') == 'false' else True
					edit_params = {'mode': 'menu_editor.edit_menu_shortcut_folder', 'active_list': list_name, 'position': item_position, 'menu_item': json.dumps(item, ensure_ascii=False)}
					item_action_params = {'active_list': list_name, 'position': item_position, 'menu_item': json.dumps(item, ensure_ascii=False)}
					cm_append(('Renommer cette entrée', 'RunPlugin(%s)' % build_url(dict(item_action_params, mode='menu_editor.shortcut_folder_item_rename'))))
					cm_append(('Choisir une icône pour cette entrée', 'RunPlugin(%s)' % build_url(dict(item_action_params, mode='menu_editor.shortcut_folder_item_choose_icon'))))
					if item_get('original_name'):
						cm_append(('Réinitialiser le nom de cette entrée', 'RunPlugin(%s)' % build_url(dict(item_action_params, mode='menu_editor.shortcut_folder_item_reset_name'))))
					if item_get('original_iconImage'):
						cm_append(('Réinitialiser l’icône de cette entrée', 'RunPlugin(%s)' % build_url(dict(item_action_params, mode='menu_editor.shortcut_folder_item_reset_icon'))))
					if mode_check == 'navigator.build_shortcut_folder_list':
						folder_name = item_get('shortcut_folder_name') or display_name
						folder_icon = nc.get_shortcut_folder_icon(folder_name)
						cm_append(('Renommer le dossier source', 'RunPlugin(%s)' % build_url({'mode': 'menu_editor.shortcut_folder_rename', 'list_name': folder_name})))
						cm_append(('Choisir une icône pour le dossier source', 'RunPlugin(%s)' % build_url({'mode': 'menu_editor.shortcut_folder_choose_icon', 'list_name': folder_name})))
						if folder_icon != 'myfolder.png':
							cm_append(('Réinitialiser l’icône du dossier source', 'RunPlugin(%s)' % build_url({'mode': 'menu_editor.shortcut_folder_reset_icon', 'list_name': folder_name})))
					cm_append((edit_str,'RunPlugin(%s)' % build_url(edit_params)))
					try:
						if isFolder:
							cm += ku.tmdb_genre_sort_context(item, display_name, item_get('iconImage', ''), isFolder)
							cm += ku.list_sort_context(item, display_name, item_get('iconImage', ''), isFolder)
					except: pass
					listitem = make_listitem()
					listitem.setLabel(display_name)
					if cm: listitem.addContextMenuItems(cm)
					listitem.setArt({'icon': icon, 'poster': icon, 'thumb': icon, 'fanart': fanart, 'banner': icon})
					yield (url, listitem, isFolder)
				except: pass
		handle, fanart, icon_path = self.params_get('handle'), self.params_get('fanart'), media_path()
		list_name = self.params_get('name')
		contents = nc.get_shortcut_folder_contents(list_name)
		add_items(handle, list(_process()))
		self._end_directory()

	def main(self):
		def build_main_lists():
			for item_position, item in enumerate(contents):
				try:
					cm = []
					cm_append = cm.append
					item_get = item.get
					mode_check = item_get('mode', '')
					if mode_check == 'navigator.downloads' or mode_check == 'downloader' or '_downloads' in mode_check: continue
					if mode_check == 'navigator.build_shortcut_folder_list':
						folder_name = item_get('shortcut_folder_name') or item_get('name', '')
						item['shortcut_folder'] = 'True'
						item['shortcut_folder_name'] = folder_name
						if not item_get('custom_icon'):
							item['iconImage'] = nc.get_shortcut_folder_icon(folder_name)
						item_get = item.get
					if item_get('iconImage') in ('', 'None', None, 'myfolder.png'): icon = media_path('myfolder.png')
					elif item_get('iconImage') == 'alkoflix.png': icon = ku.get_addoninfo('icon')
					elif item_get('network_id', '') != '' or '://' in str(item_get('iconImage', '')): icon = item_get('iconImage')
					else: icon = '%s%s' % (icon_path, item_get('iconImage'))
					isFolder = False if item_get('isFolder') == 'false' else True
					cm_append((edit_str, 'RunPlugin(%s)' % build_url({'mode': 'menu_editor.edit_menu', 'active_list': self.list_name, 'position': item_position})))
					cm_append((browse_str, 'RunPlugin(%s)' % build_url({'mode': 'menu_editor.browse', 'active_list': self.list_name})))
					try:
						if isFolder:
							cm += ku.tmdb_genre_sort_context(item, ls(item_get('name', '')), item_get('iconImage', ''), isFolder)
							cm += ku.list_sort_context(item, ls(item_get('name', '')), item_get('iconImage', ''), isFolder)
					except: pass
					listitem = make_listitem()
					listitem.setLabel(ls(item_get('name', '')))
					if cm: listitem.addContextMenuItems(cm)
					listitem.setArt({'icon': icon, 'poster': icon, 'thumb': icon, 'fanart': fanart, 'banner': icon})
					url_item = dict(item)
					if mode_check == 'navigator.build_shortcut_folder_list':
						url_item['name'] = item_get('shortcut_folder_name') or item_get('name', '')
					yield (build_url(url_item), listitem, isFolder)
				except: pass
		handle, fanart, icon_path = self.params_get('handle'), self.params_get('fanart'), media_path()
		contents = nc.currently_used_list(self.list_name)
		# Respecte les menus personnalisés de l'utilisateur. Les nouvelles entrées
		# par défaut sont synchronisées avec le cache seulement quand la définition
		# embarquée a changé, sans écraser les déplacements/ajouts existants.
		contents = self._sync_default_menu_updates(contents)
		contents = self._normalize_editable_menu_routes(contents)
		contents = self._remove_deprecated_animation_amazon_channels(contents)
		contents = self._normalize_series_menu_updates(contents)
		contents = self._normalize_sagas_favourites_routes(contents)
		contents = self._normalize_contextual_favourites_routes(contents)
		# Corrige les anciens libellés/icônes déjà stockés dans le cache utilisateur.
		# Cela évite de demander une reconstruction manuelle du menu après la mise à jour.
		if self.list_name in ('RootList', 'MovieList', 'TVShowList', 'DocumentaryList', 'FamilyList', 'AnimeList', 'AnimationList', 'AnimationMovieList', 'AnimeMovieList'):
			contents = list(contents)
			anime_name_map = {
				('build_anime_calendar', '', ''): 33155,
				('build_tvshow_list', 'trakt_tvanime_trending', ''): 33156,
				('build_tvshow_list', 'trakt_tvanime_most_watched', ''): 33157,
				('build_tvshow_list', 'tmdb_tvanime_popular', ''): 33158,
				('build_tvshow_list', 'tmdb_tvanime_premieres', ''): 33159,
				('navigator.anime_genres', '', 'tvshow'): 33160,
				('navigator.anime_years', '', 'tvshow'): 33161,
				('build_movie_list', 'trakt_moviesanime_trending', ''): 33162,
				('build_movie_list', 'trakt_moviesanime_most_watched', ''): 33163,
				('build_movie_list', 'tmdb_moviesanime_popular', ''): 33164,
				('build_movie_list', 'tmdb_moviesanime_latest_releases', ''): 33165,
				('navigator.anime_genres', '', 'movie'): 33166,
				('navigator.anime_years', '', 'movie'): 33167
			}
			for item in contents:
				# Force les libellés favoris de premier niveau, même si l'ancien menu
				# est déjà stocké dans le cache navigateur ou dans une liste éditée.
				# Sans cette normalisation, Kodi peut continuer d'afficher « Favoris »
				# ou « Séries » malgré la définition corrigée dans menu_lists.py.
				if self.list_name == 'MovieList' and item.get('action') == 'favourites_movies':
					item['name'] = 'Favoris (Films)'
				if self.list_name == 'TVShowList' and (
					(item.get('mode') == 'navigator.favourites_series' and item.get('fav_group', 'root') == 'root')
					or item.get('action') == 'favourites_tvshows'
				):
					item['name'] = 'Favoris (Séries)'
				if item.get('action') in ('watched_movies', 'watched_tvshows'):
					item['name'] = 32475
				if item.get('action') == 'in_progress_tvshows':
					item['iconImage'] = 'recent.png'
				if self.list_name == 'TVShowList' and item.get('action') == 'favourites_tvshows':
					item['mode'] = 'navigator.favourites_series'
					item['fav_group'] = 'root'
					item.pop('action', None)
					item['name'] = 'Favoris (Séries)'
				if self.list_name == 'TVShowList' and item.get('action') == 'tmdb_tv_premieres':
					item['iconImage'] = 'dvd.png'
				if self.list_name == 'TVShowList' and item.get('action') == 'trakt_in_progress_tvshows':
					# Dans le menu Séries racine, « Vos visionnages en cours » doit suivre le suivi actif
					# de l'utilisateur : local alkoFlix, Trakt ou MDBList.
					item.pop('watched_indicators', None)
					item['hide_watched_items'] = 'true'
					item['name'] = 33122
					item['iconImage'] = 'in_progress_tvshow.png'
				if self.list_name == 'RootList':
					if item.get('mode') == 'navigator.premium':
						item['name'] = 'Débrideur(s)'
					if item.get('action') == 'DocumentaryList':
						item['name'] = 'Docus & Télé'
						item['iconImage'] = 'genre_documentary.png'
					if item.get('action') == 'FamilyList':
						item['name'] = 'Famille'
						item['iconImage'] = 'genre_family.png'
					if item.get('mode') == 'catalogue.family_animation_root' or item.get('action') == 'AnimationList':
						item['name'] = 'Dessins animés'
						item['iconImage'] = 'genre_animation.png'
						item['mode'] = 'navigator.main'
						item['action'] = 'AnimationList'
				if self.list_name == 'AnimeList':
					# Les libellés du nouveau menu animé sont définis directement dans menu_lists.
					# L'ancien remappage ne doit plus écraser ces noms. On restaure toutefois
					# les icônes des entrées déjà présentes dans le cache utilisateur.
					if item.get('mode') == 'catalogue.native_anime':
						if item.get('native_type') == 'anime_series':
							item['iconImage'] = 'tv.png'
						elif item.get('native_type') == 'anime_movie':
							item['iconImage'] = 'movies.png'
				if self.list_name == 'AnimationMovieList':
					if item.get('action') == 'tmdb_movies_company' and str(item.get('company_id', '')) == '3':
						item['iconImage'] = 'pixar.png'
					if item.get('action') == 'tmdb_movies_public_list' and str(item.get('list_id', '')) == '338':
						item['iconImage'] = 'disney.png'
				if self.list_name == 'AnimeMovieList':
					if item.get('action') == 'tmdb_movies_company' and str(item.get('company_id', '')) == '10342':
						item['iconImage'] = 'ghibli.png'


		# Les favoris font maintenant partie des définitions de menus synchronisées.
		# Ils ne sont plus réinjectés dynamiquement ici, afin que l'éditeur de menus
		# puisse réellement les déplacer, retirer et restaurer comme les autres entrées.

		# Les favoris contextuels ne doivent vivre qu'à la racine du menu thématique.
		# Les anciens caches pouvaient garder des dossiers Favoris dans les sous-arborescences.
		contextual_submenus = (
			'DocumentaryMovieRootList', 'DocumentarySeriesRootList',
			'DocumentaryMovieDocumentaryList', 'DocumentaryTvMovieList',
			'DocumentarySeriesDocumentaryList', 'DocumentarySeriesRealityList',
			'DocumentarySeriesTalkshowList', 'DocumentarySeriesNewsList',
			'FamilyMovieList', 'FamilySeriesList',
			'AnimationMovieList', 'AnimationSeriesList',
			'AnimeMovieList', 'AnimeSeriesList'
		)
		if self.list_name in contextual_submenus:
			contents = [i for i in contents if not str(i.get('mode', '')).startswith('navigator.favourites_')]

		if self.list_name == 'FamilySeriesList':
			# Les anciennes entrées du sous-menu Séries familiales peuvent ne pas
			# contenir fav_type/family_type. Dans ce cas, le menu contextuel retombe
			# sur tvshow et inscrit le favori dans le panier global.
			normalized_contents = []
			for item in contents:
				item = dict(item)
				mode, action = str(item.get('mode', '')), str(item.get('action', ''))
				if mode == 'build_tvshow_list' and (action.startswith('tmdb_tv_family_') or action == 'tmdb_tv_networks'):
					item.setdefault('family_type', 'series_family')
					item.setdefault('fav_type', 'tv_family')
				elif mode.startswith('catalogue.family_'):
					item.setdefault('family_type', 'series_family')
				normalized_contents.append(item)
			contents = normalized_contents

		if self.list_name == 'FamilyList':
			# La racine Famille doit rester une porte d'entrée simple. Les anciens
			# raccourcis directs sont retirés des menus déjà en cache : ils vivent
			# maintenant dans Films familiaux ou Séries familiales.
			legacy_family_root_modes = (
				'catalogue.family_animation_root',
				'catalogue.family_genre_groups',
				'catalogue.family_provider_groups',
				'catalogue.family_amazon_channel_groups',
				'catalogue.family_kids_networks'
			)
			contents = [i for i in contents if i.get('mode') not in legacy_family_root_modes]


		if self.list_name in ('MovieList', 'TVShowList'):
			# Les anciens raccourcis « À venir » peuvent rester dans le cache utilisateur
			# après une mise à jour. Ils sont retirés sans toucher aux autres entrées.
			legacy_upcoming_actions = ('tmdb_movies_upcoming', 'tmdb_tv_upcoming')
			contents = [i for i in contents if i.get('action') not in legacy_upcoming_actions]

		if self.list_name == 'AnimeList':
			# Depuis la v1.3.6, la racine Animés japonais sert uniquement de porte d'entrée.
			# Les accès séries/films restent disponibles dans leurs sous-dossiers dédiés.
			def _is_legacy_anime_root_item(item):
				mode = item.get('mode', '')
				action = item.get('action', '')
				if mode in ('build_anime_calendar', 'catalogue.native_anime_groups', 'catalogue.native_anime_year_groups', 'catalogue.native_anime_provider_groups'):
					return True
				if mode in ('navigator.anime_genres', 'navigator.anime_years'):
					return True
				if mode == 'build_tvshow_list' and action.startswith(('tmdb_tvanime_', 'trakt_tvanime_')):
					return True
				if mode == 'build_movie_list' and action.startswith(('tmdb_moviesanime_', 'trakt_moviesanime_')):
					return True
				return False
			contents = [i for i in contents if not _is_legacy_anime_root_item(i)]

		# Le catalogue oersonnalisé est optionnel : il apparaît au menu racine uniquement
		# si un manifest personnalisé est configuré dans les réglages.
		if self.list_name == 'RootList':
			contents = list(contents)
			normalized = []
			has_family_menu = False
			has_family_animation_menu = False
			for item in contents:
				if item.get('mode') == 'navigator.discover_main':
					continue
				if item.get('action') == 'DocumentaryList':
					item['name'] = 'Docus & Télé'
					item['iconImage'] = 'genre_documentary.png'
				if item.get('action') == 'FamilyList':
					if has_family_menu: continue
					item['name'] = 'Famille'
					item['iconImage'] = 'genre_family.png'
					has_family_menu = True
				if item.get('mode') == 'catalogue.family_animation_root' or item.get('action') == 'AnimationList':
					if has_family_animation_menu: continue
					item['name'] = 'Dessins animés'
					item['iconImage'] = 'genre_animation.png'
					item['mode'] = 'navigator.main'
					item['action'] = 'AnimationList'
					has_family_animation_menu = True
				normalized.append(item)
			contents = normalized
			# Ne pas réinjecter automatiquement le menu Dessins animés s'il a été retiré
			# depuis l'éditeur de menus. La restauration doit rester contrôlée par
			# l'éditeur, afin de respecter les menus personnalisés de l'utilisateur.
			if parental_control.hide_anime_menu():
				contents = [i for i in contents if i.get('action') != 'AnimeList']
			# Le Catalogue personnalisé est géré par _sync_default_menu_updates().
			# Il ne doit pas être injecté à l'affichage seulement, sinon l'éditeur de menus
			# reçoit une position qui n'existe pas dans la liste sauvegardée.
			# Résultat : impossible de le déplacer/retirer proprement.
			if not self._custom_catalogue_enabled():
				contents = [i for i in contents if i.get('mode') != 'catalogue.root']
		contents = self._ensure_required_favourites_shortcuts(contents)
		add_items(handle, list(build_main_lists()))
		self._end_directory()

	def _ensure_required_favourites_shortcuts(self, contents):
		# Correctif de sécurité v1.4.17 : certains dossiers sont structurels.
		# Ils doivent rester visibles même si une ancienne liste utilisateur/cache
		# ne contient plus l'entrée par défaut. On couvre aussi Personnes et les
		# quatre familles de Sagas, pas seulement Films/Séries/Famille/Docus/Anime.
		required_map = {
			'MovieList': [
				{'name': 'Sagas', 'iconImage': 'sets.png', 'mode': 'navigator.main', 'action': 'SagasMovieList'},
				{'name': 'Favoris (Films)', 'iconImage': 'favourites.png', 'mode': 'build_movie_list', 'action': 'favourites_movies', 'fav_type': 'movie'}
			],
			'TVShowList': [
				{'name': 'Favoris (Séries)', 'iconImage': 'favourites.png', 'mode': 'navigator.favourites_series', 'fav_group': 'root'}
			],
			'DocumentaryList': [
				{'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': 'navigator.favourites_documentary'}
			],
			'FamilyList': [
				{'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': 'navigator.favourites_family'}
			],
			'AnimationList': [
				{'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': 'navigator.favourites_animation'}
			],
			'AnimeList': [
				{'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': 'navigator.favourites_anime'}
			],
			'PeopleList': [
				{'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': 'people.favourites'}
			],
			'FamilyMovieList': [
				{'name': 'Sagas', 'iconImage': 'sets.png', 'mode': 'navigator.main', 'action': 'SagasFamilyList'}
			],
			'AnimationMovieList': [
				{'name': 'Sagas', 'iconImage': 'sets.png', 'mode': 'navigator.main', 'action': 'SagasAnimationList'}
			],
			'AnimeMovieList': [
				{'name': 'Sagas', 'iconImage': 'sets.png', 'mode': 'navigator.main', 'action': 'SagasAnimeList'}
			],
			'SagasMovieList': [
				{'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': 'catalogue.sagas_favourites', 'saga_type': 'movie', 'fav_type': 'collection'}
			],
			'SagasFamilyList': [
				{'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': 'catalogue.sagas_favourites', 'saga_type': 'family', 'fav_type': 'collection_family'}
			],
			'SagasAnimationList': [
				{'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': 'catalogue.sagas_favourites', 'saga_type': 'animation', 'fav_type': 'collection_animation'}
			],
			'SagasAnimeList': [
				{'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': 'catalogue.sagas_favourites', 'saga_type': 'anime', 'fav_type': 'collection_anime'}
			]
		}
		required_items = required_map.get(self.list_name)
		if not required_items: return contents
		contents = [dict(i) for i in contents]
		changed = False
		for required in required_items:
			req_key = self._menu_item_key(required)
			found = False
			for item in contents:
				if self._menu_item_key(item) == req_key or (item.get('mode') == required.get('mode') and item.get('action', '') == required.get('action', '')):
					# On répare l'entrée existante au lieu d'en créer une deuxième.
					if item != dict(item, **required):
						item.update(required)
						changed = True
					found = True
					break
			if found: continue
			# Insertion douce : après l'entrée précédente dans le menu par défaut, sinon à la fin.
			insert_at = len(contents)
			try:
				from modules.menu_lists import main_menus
				defaults = list(main_menus.get(self.list_name) or [])
				default_keys = [self._menu_item_key(i) for i in defaults]
				if req_key in default_keys:
					idx = default_keys.index(req_key)
					content_keys = [self._menu_item_key(i) for i in contents]
					for previous in reversed(defaults[:idx]):
						prev_key = self._menu_item_key(previous)
						if prev_key in content_keys:
							insert_at = content_keys.index(prev_key) + 1
							break
			except Exception:
				pass
			contents.insert(insert_at, dict(required))
			changed = True
		if changed:
			try:
				default_contents, edited_contents = nc.get_main_lists(self.list_name)
				if edited_contents: nc.set_list(self.list_name, 'edited', contents)
			except Exception:
				pass
		return contents

	def _menu_item_key(self, item):
		item_get = item.get
		return (
			item_get('mode', ''), item_get('action', ''), item_get('menu_type', ''),
			item_get('native_type', ''), item_get('family_type', ''), item_get('provider_id', ''),
			item_get('network_id', ''), item_get('genre_id', ''), item_get('language_code', ''),
			item_get('year', '')
		)

	def _custom_catalogue_enabled(self):
		try:
			from catalogue.catalogs import has_custom_manifest
			return bool(has_custom_manifest())
		except:
			return False

	def _custom_catalogue_item(self):
		try:
			from catalogue.catalogs import custom_manifest_label, custom_manifest_icon
			return {'name': custom_manifest_label(), 'iconImage': custom_manifest_icon(), 'mode': 'catalogue.root'}
		except:
			return {'name': 'Catalogue personnalisé', 'iconImage': 'folder.png', 'mode': 'catalogue.root'}

	def _normalize_series_menu_updates(self, contents):
		# Nettoie les listes Séries déjà sauvegardées dans navigator.db/cache.
		# Le menu « Diffusées aujourd’hui » a été retiré du menu Séries racine,
		# et « Nouveautés » doit partager l’icône des nouveautés du menu Films.
		if self.list_name != 'TVShowList':
			return contents
		new_contents = []
		changed = False
		for item in contents:
			item = dict(item)
			if item.get('action') == 'tmdb_tv_airing_today':
				changed = True
				continue
			if item.get('action') == 'tmdb_tv_premieres' and item.get('iconImage') != 'dvd.png':
				item['iconImage'] = 'dvd.png'
				changed = True
			new_contents.append(item)
		if changed:
			try:
				default, edited = nc.get_main_lists(self.list_name)
				if edited:
					nc.set_list(self.list_name, 'edited', new_contents)
			except Exception:
				pass
		return new_contents

	def _remove_deprecated_animation_amazon_channels(self, contents):
		# Retire les anciens raccourcis Amazon Channels déjà présents dans les menus
		# édités/cache utilisateur des sous-sections Dessins animés. Le changement de
		# menu_lists.py empêche les nouvelles installations de les recevoir, mais les
		# listes déjà sauvegardées doivent être nettoyées à l'affichage.
		if self.list_name not in ('AnimationMovieList', 'AnimationSeriesList'):
			return contents
		deprecated_family_type = 'movie_animation' if self.list_name == 'AnimationMovieList' else 'series_animation'
		new_contents = []
		changed = False
		for item in contents:
			mode = str(item.get('mode', '') or '')
			name = str(item.get('name', '') or '').lower()
			family_type = str(item.get('family_type', '') or '')
			is_deprecated = (
				mode == 'catalogue.family_amazon_channels'
				and (family_type in ('', deprecated_family_type) or 'amazon channels' in name)
			)
			if is_deprecated:
				changed = True
				continue
			new_contents.append(item)
		if changed:
			try:
				default, edited = nc.get_main_lists(self.list_name)
				if edited:
					nc.set_list(self.list_name, 'edited', new_contents)
			except Exception:
				pass
		return new_contents

	def _normalize_contextual_favourites_routes(self, contents):
		# Corrige les menus thématiques déjà en cache qui pointaient encore vers
		# les favoris globaux. Le dossier Favoris de Famille/Docus/Animation/Anime
		# doit ouvrir son panier spécialisé, sinon les favoris se mélangent.
		context = {
			'DocumentaryList': 'navigator.favourites_documentary',
			'FamilyList': 'navigator.favourites_family',
			'AnimationList': 'navigator.favourites_animation',
			'AnimeList': 'navigator.favourites_anime'
		}.get(self.list_name)
		if not context: return contents
		new_contents, found = [], False
		for item in contents:
			item = dict(item)
			mode = str(item.get('mode') or '')
			name = str(item.get('name') or '').strip().lower()
			icon = str(item.get('iconImage') or '')
			is_favourites_item = (
				mode == 'navigator.favourites'
				or mode.startswith('navigator.favourites_')
				or (name in ('favoris', '32453') and 'favourites' in icon)
			)
			if is_favourites_item:
				if found: continue
				item.update({'name': 'Favoris', 'iconImage': 'favourites.png', 'mode': context})
				for key in ('action', 'fav_group', 'group', 'fav_type', 'family_type', 'documentary_type', 'native_type', 'saga_type'):
					item.pop(key, None)
				found = True
			new_contents.append(item)
		return new_contents

	def _normalize_sagas_favourites_routes(self, contents):
		# Les anciennes listes Sagas déjà en cache ouvraient toujours le dossier
		# Favoris global. On réinjecte ici le contexte du menu parent sans écraser
		# les déplacements faits dans l'éditeur de menus.
		saga_context = {
			'SagasMovieList': ('movie', 'collection'),
			'SagasFamilyList': ('family', 'collection_family'),
			'SagasAnimationList': ('animation', 'collection_animation'),
			'SagasAnimeList': ('anime', 'collection_anime')
		}.get(self.list_name)
		if not saga_context: return contents
		saga_type, fav_type = saga_context
		normalized = []
		for item in contents:
			item = dict(item)
			if item.get('mode') == 'catalogue.sagas_favourites':
				item['saga_type'] = saga_type
				item['fav_type'] = fav_type
			elif item.get('mode') == 'build_movie_list' and item.get('action') == 'tmdb_movies_curated_collections':
				item['saga_type'] = saga_type
				item['fav_type'] = fav_type
			elif item.get('mode') == 'search_history' and item.get('action') == 'tmdb_collections':
				item['saga_type'] = saga_type
				item['fav_type'] = fav_type
			normalized.append(item)
		return normalized

	def _sync_default_menu_updates(self, contents):
		try:
			from modules.menu_lists import main_menus
			if self.list_name not in main_menus: return contents
			default_contents, edited_contents = nc.get_main_lists(self.list_name)
			new_defaults = list(main_menus[self.list_name])
			if self.list_name == 'RootList':
				custom_item = self._custom_catalogue_item()
				custom_key = self._menu_item_key(custom_item)
				if self._custom_catalogue_enabled():
					if not any(self._menu_item_key(i) == custom_key for i in new_defaults):
						new_defaults.insert(min(4, len(new_defaults)), custom_item)
					# Met à jour l'icône du catalogue personnalisé même si l'utilisateur a
					# déplacé ou renommé le dossier. Le nom n'est forcé que pour l'entrée
					# générique, afin de respecter l'éditeur de menus.
					updated_contents = []
					for item in contents:
						if self._menu_item_key(item) == custom_key:
							item = dict(item)
							if str(item.get('name') or '') in ('Catalogue personnalisé', 'French Stream'):
								item['name'] = custom_item.get('name', item.get('name'))
							item['iconImage'] = custom_item.get('iconImage', item.get('iconImage'))
						updated_contents.append(item)
					contents = updated_contents
				else:
					# Si le manifest personnalisé est retiré, on masque le dossier même si
					# une ancienne liste éditée le contenait encore.
					contents = [i for i in contents if self._menu_item_key(i) != custom_key]
			# Si le cache par défaut est déjà aligné, on ne force rien. Cela permet
			# à l'utilisateur de retirer/déplacer les menus ensuite sans qu'ils reviennent.
			if default_contents == new_defaults: return contents
			merged = list(contents)
			merged_keys = [self._menu_item_key(i) for i in merged]
			for item in new_defaults:
				item_key = self._menu_item_key(item)
				if item_key in merged_keys: continue
				default_index = new_defaults.index(item)
				insert_at = len(merged)
				# Insère après le plus proche élément précédent déjà présent, selon l'ordre
				# du menu par défaut. L'ordre personnalisé global reste donc respecté autant
				# que possible.
				for previous in reversed(new_defaults[:default_index]):
					previous_key = self._menu_item_key(previous)
					if previous_key in merged_keys:
						insert_at = merged_keys.index(previous_key) + 1
						break
				merged.insert(insert_at, dict(item))
				merged_keys.insert(insert_at, item_key)
			if merged != contents:
				if edited_contents: nc.set_list(self.list_name, 'edited', merged)
				contents = merged
			# Met à jour la référence par défaut pour que la migration ne se répète pas.
			nc.set_list(self.list_name, 'default', new_defaults)
		except Exception as e:
			try: ku.logger('menu default sync error', '%s | %s' % (self.list_name, e))
			except: pass
		return contents

	def _normalize_editable_menu_routes(self, contents):
		# Plusieurs anciens dossiers étaient affichés via des routes catalogue directes.
		# Ils sont convertis ici vers de vraies listes éditables (navigator.main), afin
		# d'obtenir partout les mêmes options : déplacer, retirer, restaurer.
		try:
			saga_map = {'movie': 'SagasMovieList', 'family': 'SagasFamilyList', 'animation': 'SagasAnimationList', 'anime': 'SagasAnimeList'}
			family_map = {
				'movie_family': ('FamilyMovieList', 'Films familiaux', 'movies.png'),
				'series_family': ('FamilySeriesList', 'Séries familiales', 'tv.png'),
				'movie_animation': ('AnimationMovieList', 'Films d’animation', 'movies.png'),
				'series_animation': ('AnimationSeriesList', 'Séries d’animation', 'tv.png')
			}
			anime_map = {
				'anime_series': ('AnimeSeriesList', 'Séries animées japonaises', 'tv.png'),
				'anime_movie': ('AnimeMovieList', 'Films animés japonais', 'movies.png')
			}
			documentary_root_map = {
				'catalogue.documentary_movie_root': ('DocumentaryMovieRootList', 'Films', 'movies.png'),
				'catalogue.documentary_series_root': ('DocumentarySeriesRootList', 'Séries', 'tv.png')
			}
			documentary_section_map = {
				'movie_documentary': ('DocumentaryMovieDocumentaryList', 'Films documentaires', 'genre_documentary.png'),
				'movie_tv_movie': ('DocumentaryTvMovieList', 'Téléfilms', 'genre_tv.png'),
				'series_documentary': ('DocumentarySeriesDocumentaryList', 'Séries documentaires', 'genre_documentary.png'),
				'series_reality': ('DocumentarySeriesRealityList', 'Téléréalité', 'genre_reality.png'),
				'series_talkshow': ('DocumentarySeriesTalkshowList', 'Talk-shows', 'genre_talk.png'),
				'series_news': ('DocumentarySeriesNewsList', 'Actualités', 'genre_news.png')
			}
			new_contents = []
			changed = False
			for item in contents:
				item = dict(item)
				mode = item.get('mode', '')
				if mode == 'people.root':
					item.update({'mode': 'navigator.main', 'action': 'PeopleList', 'name': 'Personnes', 'iconImage': 'user.png'})
					changed = True
				elif mode == 'catalogue.sagas_root':
					action = saga_map.get(item.get('saga_type', 'movie'), 'SagasMovieList')
					item.update({'mode': 'navigator.main', 'action': action, 'name': item.get('name') or 'Sagas', 'iconImage': 'sets.png'})
					item.pop('saga_type', None)
					changed = True
				elif mode in documentary_root_map:
					action, name, icon = documentary_root_map[mode]
					item.update({'mode': 'navigator.main', 'action': action, 'name': name, 'iconImage': icon})
					changed = True
				elif mode == 'catalogue.documentary_section':
					info = documentary_section_map.get(item.get('documentary_type'))
					if info:
						action, name, icon = info
						item.update({'mode': 'navigator.main', 'action': action, 'name': name, 'iconImage': icon})
						item.pop('documentary_type', None)
						changed = True
				elif mode == 'catalogue.family_section':
					info = family_map.get(item.get('family_type'))
					if info:
						action, name, icon = info
						item.update({'mode': 'navigator.main', 'action': action, 'name': name, 'iconImage': icon})
						item.pop('family_type', None)
						changed = True
				elif mode == 'catalogue.native_anime':
					info = anime_map.get(item.get('native_type'))
					if info:
						action, name, icon = info
						item.update({'mode': 'navigator.main', 'action': action, 'name': name, 'iconImage': icon})
						item.pop('native_type', None)
						changed = True
				new_contents.append(item)
			if changed:
				try:
					default, edited = nc.get_main_lists(self.list_name)
					if edited:
						nc.set_list(self.list_name, 'edited', new_contents)
				except: pass
			return new_contents
		except Exception as e:
			try: ku.logger('menu editable route normalization error', '%s | %s' % (self.list_name, e))
			except: pass
			return contents

	def make_list_name(self, menu_type):
		return menu_type.replace('tvshow', tv_str).replace('movie', mov_str)

	def _add_item(self, url_params, iconImage='', prefix='', isFolder=True, list_name=''):
		handle, fanart = self.params_get('handle'), self.params_get('fanart')
		if not isFolder: url_params['isFolder'] = 'false'
		if iconImage in ('', 'None', None, 'myfolder.png'): icon = media_path('myfolder.png')
		elif iconImage == 'alkoflix.png': icon = ku.get_addoninfo('icon')
		elif 'network_id' in url_params: icon = _square_network_logo(iconImage)
		elif '://' in str(iconImage): icon = iconImage
		else: icon = media_path(iconImage)
		url_params['iconImage'] = icon
		url = build_url(url_params)
		listitem = make_listitem()
		listitem.setLabel(url_params['name'])
		listitem.setArt({'icon': icon, 'poster': icon, 'thumb': icon, 'fanart': fanart, 'banner': icon, 'landscape': icon})
		try:
			if isFolder and not str(url_params.get('mode', '')).startswith('menu_editor.'):
				cm = ku.menu_editor_context(url_params, url_params.get('name', ''), url_params.get('iconImage', ''), isFolder)
				cm += ku.tmdb_genre_sort_context(url_params, url_params.get('name', ''), url_params.get('iconImage', ''), isFolder)
				cm += ku.list_sort_context(url_params, url_params.get('name', ''), url_params.get('iconImage', ''), isFolder)
				if cm: listitem.addContextMenuItems(cm, replaceItems=False)
		except: pass
		add_item(handle, url, listitem, isFolder)

	def _end_directory(self):
		handle, fanart = self.params_get('handle'), self.params_get('fanart')
		set_category(handle, ls(self.params_get('name')))
		set_content(handle, '')
		# Le menu racine dépend de certains réglages dynamiques, notamment l'URL
		# du catalogue personnalisé. On évite donc le cache disque Kodi pour que
		# l'ajout/retrait du dossier soit visible sans redémarrage.
		if self.list_name == 'RootList': end_directory(handle, cacheToDisc=False)
		else: end_directory(handle)
		set_view_mode('view.main', '')

