import json
from windows import BaseDialog
from modules import settings
from modules.debrid import Source
from modules.kodi_utils import media_path, path_exists, hide_busy_dialog, dialog, select_dialog, ok_dialog, local_string as ls
from modules.settings import get_art_provider, get_fanart_data, info_icons, provider_sort_ranks
from modules.source_result_utils import (
	selector_custom_source_label, selector_source_type_label, selector_source_type_icon,
	selector_source_type_badge_icon, selector_source_type_badge_icon_color,
	selector_source_site_icon, selector_source_site_label, selector_source_site_label_for_item, selector_cache_status_icon,
	selector_cache_status_icon_color, source_progress_line1
)
# from modules.kodi_utils import logger



fanart_empty = media_path('transparent.png')
poster_empty = media_path('box_office.png')
info_icons_dict = {k: media_path(v) for k, v in info_icons()}

def resolve_clearlogo(meta):
	# Logo principal pour les fenêtres de sources.
	# Si Fanart.tv est prioritaire mais ne retourne rien, on retombe sur TMDb.
	# Si Fanart.tv n'est pas utilisé, on garde TMDb prioritaire avec Fanart.tv en secours.
	if get_fanart_data():
		return meta.get('clearlogo') or meta.get('tmdblogo') or ''
	return meta.get('tmdblogo') or meta.get('clearlogo') or ''


def resolve_source_window_art(meta):
	# Centralise les visuels utilisés par les fenêtres de sources.
	# Garde la même priorité d'images pour les sélecteurs et la fenêtre de progression.
	poster_main, poster_backup, fanart_main, fanart_backup = get_art_provider()
	fanart = meta.get(fanart_main) or meta.get(fanart_backup) or fanart_empty
	poster = settings.resolve_poster_art(meta, poster_empty)
	if get_fanart_data():
		landscape = settings.resolve_landscape_art(meta, meta.get('tvshow.landscape') or fanart)
	else:
		landscape = meta.get('landscape_tmdb') or meta.get('tvshow.landscape') or fanart
	return {'poster': poster, 'fanart': fanart, 'landscape': landscape, 'clearlogo': resolve_clearlogo(meta)}

def provider_display_label(provider):
	# Libellé court uniquement pour l'affichage UI.
	# La clé technique reste intacte ailleurs pour ne pas casser les icônes / API.
	provider_value = provider or ''
	provider_key = lower(provider_value).replace('.me', '')
	if provider_key == 'alldebrid': return 'AD'
	return upper(provider_value)

CLOUD_DEBRID_LABELS = {'ad_cloud': 'AD', 'rd_cloud': 'RD', 'tb_cloud': 'TB'}

def _cloud_key_from_text(value):
	try:
		value = str(value or '').strip().lower().replace('-', '_').replace(' ', '_')
		if value in CLOUD_DEBRID_LABELS: return value
		if 'ad_cloud' in value: return 'ad_cloud'
		if 'rd_cloud' in value: return 'rd_cloud'
		if 'tb_cloud' in value: return 'tb_cloud'
		if 'cloud' in value and ('all_debrid' in value or 'alldebrid' in value): return 'ad_cloud'
		if 'cloud' in value and ('real_debrid' in value or 'realdebrid' in value): return 'rd_cloud'
		if 'cloud' in value and 'torbox' in value: return 'tb_cloud'
	except:
		pass
	return ''

def cloud_debrid_display_label(item):
	# Affichage strictement réservé aux vrais résultats issus du cloud du débrideur.
	# Un simple lien caché AD/RD/TB ne doit jamais être affiché comme CLOUD.
	try:
		if not isinstance(item, dict): return ''
		for key in ('scrape_provider', 'source', 'provider', 'provider_name', 'display_name', 'source_site'):
			cloud_key = _cloud_key_from_text(item.get(key))
			if cloud_key: return CLOUD_DEBRID_LABELS.get(cloud_key, '')
		if item.get('ad_cloud_section'): return 'AD'
	except:
		pass
	return ''

def source_language_label(*values):
	# Détecte une étiquette courte de langue à afficher près de la taille.
	# Si MULTI est combiné à une version française explicite (VF2, VFF, VFQ, FR...),
	# on affiche la version française plutôt que le libellé générique MULTI.
	text = ' '.join(string(value or '') for value in values)
	text = upper(text)
	for char in ('[', ']', '(', ')', '{', '}', '|', '/', '.', ',', ';', ':', '-', '_', '+'):
		text = text.replace(char, ' ')
	tokens = [token for token in text.split() if token]
	compact = ''.join(tokens)
	if 'VOSTFR' in compact: return 'VOSTFR'
	if 'VF2' in tokens or 'VF2' in compact: return 'VF2'
	if 'VFQ' in tokens or 'VFQ' in compact: return 'VFQ'
	if 'VOQ' in tokens or 'VOQ' in compact: return 'VOQ'
	if 'VFF' in tokens or 'VFF' in compact: return 'VFF'
	if 'VFI' in tokens or 'VFI' in compact: return 'VFI'
	if 'TRUEFRENCH' in compact: return 'TRUEFR'
	if 'FRENCH' in tokens or 'FR' in tokens or 'FRA' in tokens or 'FRE' in tokens or 'FRN' in tokens: return 'FR'
	if 'VF' in tokens: return 'VF'
	if 'MULTILANG' in compact or 'MULTIPLELANGUAGES' in compact or 'MULTI' in tokens: return 'MULTI'
	if 'ENGLISH' in tokens or 'EN' in tokens: return 'EN'
	return ''


def local_cache_display_label(label, item):
	# Indicateur visuel : les sources relues depuis le cache local sont affichées en vert.
	# Le texte brut reste inchangé côté données métier.
	try:
		if not isinstance(item, dict): return label
		is_cache_hit = item.get('local_cache') or item.get('provider_cache_hit') or item.get('provider_hash_hit')
		if not is_cache_hit: return label
		return '[COLOR %s][B]%s[/B][/COLOR]' % (LOCAL_CACHE_COLOR, label)
	except:
		return label
extra_info_choices = (
	('PACK', 'PACK'), ('DOLBY VISION', 'D/VISION'), ('HIGH DYNAMIC RANGE (HDR)', 'HDR'), ('HYBRID', 'HYBRID'), ('AV1', 'AV1'),
	('HEVC (X265)', 'HEVC'), ('REMUX', 'REMUX'), ('BLURAY', 'BLURAY'), ('SDR', 'SDR'), ('3D', '3D'), ('DOLBY ATMOS', 'ATMOS'), ('DOLBY TRUEHD', 'TRUEHD'),
	('DOLBY DIGITAL EX', 'DD-EX'), ('DOLBY DIGITAL PLUS', 'DD+'), ('DOLBY DIGITAL', 'DD'), ('DTS-HD MASTER AUDIO', 'DTS-HD MA'), ('DTS-X', 'DTS-X'),
	('DTS-HD', 'DTS-HD'), ('DTS', 'DTS'), ('ADVANCED AUDIO CODING (AAC)', 'AAC'), ('MP3', 'MP3'), ('8 CHANNEL AUDIO', '8CH'), ('7 CHANNEL AUDIO', '7CH'),
	('6 CHANNEL AUDIO', '6CH'), ('2 CHANNEL AUDIO', '2CH'), ('DVD SOURCE', 'DVD'), ('WEB SOURCE', 'WEB'), ('MULTIPLE LANGUAGES', 'MULTI-LANG'), ('SUBTITLES', 'SUBS')
)
quality_choices, pack_check = ('4K', '1080P', '720P', 'SD', 'TELE', 'CAM', 'SCR'), ('true', 'show', 'season')
extra_info_str, down_file_str, browse_pack_str, down_pack_str, cloud_str = ls(32605), ls(32747), ls(32746), ls(32007), ls(32016)
filter_str, clr_filter_str, filters_ignored, start_full_scrape = ls(32152), ls(32153), ls(32686), ls(32529)
filter_quality, filter_provider, filter_title, filter_extraInfo = ls(32154), ls(32157), ls(32679), ls(32169)
run_plugin_str, ignored_str, check_str = 'RunPlugin(%s)', '[COLOR dodgerblue](%s)[/COLOR]', 'Vérifier l’état du cache'
en_seek_str, en_dl_str = 'EN: PLAY (SEEK ENABLED)', 'EN: PLAY (FROM DOWNLOAD)'
tb_cnp_str = 'TB: CACHE AND PLAY'
LOCAL_CACHE_COLOR = 'FF43D17A'
string, upper, lower = str, str.upper, str.lower

def selector_media_icon(icon_name):
	# Vérifie uniquement les icônes natives existantes; les sources/sites personnalisés restent en texte.
	try:
		if not icon_name: return ''
		path = icon_name if str(icon_name).startswith('special://') else media_path(icon_name)
		return path if path_exists(path) else ''
	except:
		return ''

class SourceResults(BaseDialog):
	def __init__(self, *args, **kwargs):
		BaseDialog.__init__(self, args)
		self.window_style = kwargs.get('window_style', 'list contrast details')
		self.window_id = kwargs.get('window_id')
		self.results = kwargs.get('results')
		self.meta = kwargs.get('meta')
		self.info_highlights_dict = kwargs.get('scraper_settings')
		self.prescrape = kwargs.get('prescrape')
		self.selected = (None, '')
		if kwargs.get('filters_ignored'): self.filters_ignored = ignored_str % filters_ignored
		else: self.filters_ignored = ''
		self.make_items()
		self.set_properties()

	def onInit(self):
		self.filter_applied = False
		try:
			self.win = self.getControl(self.window_id)
			self.win.addItems(self.item_list)
			self._set_results_focus()
		except:
			self.close()

	def _set_results_focus(self):
		self.safe_set_focus(self.window_id)

	def run(self):
		try: self.doModal()
		finally:
			self.clearProperties()
			hide_busy_dialog()
		return self.selected

	def get_provider_and_path(self, provider):
		if provider in info_icons_dict: provider_path = info_icons_dict[provider]
		else: provider, provider_path = 'folders', info_icons_dict['folders']
		return provider, provider_path

	def get_quality_and_path(self, quality):
		quality_path = info_icons_dict[quality]
		return quality, quality_path

	def _selected_result_item(self):
		chosen_listitem = self.safe_get_listitem(self.window_id)
		if chosen_listitem is None: return None
		if chosen_listitem.getProperty('alkoskins.spacer') == 'true': return None
		return chosen_listitem

	def _source_from_item(self, listitem):
		try: return json.loads(listitem.getProperty('source') or '{}')
		except: return {}

	def _open_results_info(self, listitem):
		if listitem is None or self.is_closing(): return
		kwargs = dict(item=listitem, fanart=self.original_landscape())
		return self.open_window(('windows.sources', 'ResultsInfo'), 'sources_info.xml', **kwargs)

	def _play_uncached_source(self, source):
		magnet_url = str(source.get('url')).startswith('magnet')
		if magnet_url: link = Source(source, self.meta).manual_add_magnet_to_cloud()
		else: link = Source(source, self.meta).manual_add_nzb_to_cloud()
		if link is None: return
		source['unrestricted_link'] = link
		self.selected = ('play', source)
		return self.close()

	def _handle_source_selection(self, chosen_listitem):
		if chosen_listitem.getProperty('alkoskins.perform_full_search') == 'true':
			self.selected = ('perform_full_search', '')
			return self.close()
		source = self._source_from_item(chosen_listitem)
		if not source: return
		cache_provider = string(source.get('cache_provider') or '')
		if 'Uncached' in cache_provider or 'UNCACHED' in chosen_listitem.getProperty('alkoskins.source_type'):
			return self._play_uncached_source(source)
		self.selected = ('play', source)
		return self.close()

	def _handle_context_choice(self, choice, chosen_listitem, source, highlight):
		if choice is None: return
		if 'clear_results_filter' in choice: return self.clear_filter()
		elif 'results_filter' in choice: return self.filter_results()
		elif 'results_info' in choice: return self._open_results_info(chosen_listitem)
		elif 'seekable_easynews' in choice:
			link = Source(source, self.meta).resolve_internal_sources(True)
			if not link is None:
				source['unrestricted_link'] = link
				self.selected = ('play', source)
				return self.close()
		elif 'browse_packs' in choice:
			link = Source(source, self.meta).browse_packs(highlight)
			if not link == 'cancel':
				source['unrestricted_link'] = link
				self.selected = ('play', source)
				return self.close()
		elif 'manual_add_magnet_to_cloud' in choice: Source(source, self.meta).manual_add_magnet_to_cloud()
		elif 'unchecked_magnet_status' in choice: Source(source, self.meta).unchecked_magnet_status()
		else: self.execute_code(choice)

	def _open_context_menu(self, chosen_listitem):
		source = self._source_from_item(chosen_listitem)
		if not source: return
		highlight = chosen_listitem.getProperty('alkoskins.highlight')
		kwargs = dict(item=source, meta=self.meta, highlight=highlight, filter_applied=self.filter_applied)
		choice = self.open_window(('windows.sources', 'ResultsContextMenu'), 'contextmenu.xml', **kwargs)
		return self._handle_context_choice(choice, chosen_listitem, source, highlight)

	def onAction(self, action):
		if action in self.closing_actions:
			if self.filter_applied: return self.clear_filter()
			self.selected = (None, '')
			return self.close()
		if self.is_closing(): return
		chosen_listitem = self._selected_result_item()
		if chosen_listitem is None: return
		if action in self.selection_actions:
			return self._handle_source_selection(chosen_listitem)
		elif action == self.info_actions:
			return self._open_results_info(chosen_listitem)
		elif action in self.context_actions:
			return self._open_context_menu(chosen_listitem)

	def make_items(self):
		def builder():
			for count, item in enumerate(self.results, 1):
				try:
					get = item.get
					listitem = self.make_listitem()
					set_property = listitem.setProperty
					scrape_provider = item['scrape_provider']
					source = get('source')
					quality = get('quality', 'SD')
					basic_quality, quality_icon = self.get_quality_and_path(lower(quality))
					try: name = upper(get('URLName', 'N/A'))
					except: name = 'N/A'
					pack = get('package', 'false') in pack_check
					cloud_debrid_label = cloud_debrid_display_label(item)
					extra_info = get('extraInfo', '') or 'N/A'
					extra_info = extra_info.rstrip('| ')
					if scrape_provider == 'external':
						custom_source_label = selector_custom_source_label(item)
						if custom_source_label:
							source_site = get('tracker') or get('source_site') or ''
						else:
							source_site = get('tracker') or get('source_site') or get('provider')
						source_site = upper(source_site)
						provider_id = get('debrid', source_site).replace('.me', '')
						provider = upper(provider_id)
						provider_lower = lower(provider_id)
						provider_icon = self.get_provider_and_path(provider_lower)[1]
						if 'cache_provider' in item:
							if 'Uncached' in item['cache_provider']:
								key = 'uncached'
								set_property('alkoskins.source_type',
									'UNCACHED (%d SEEDERS)' % get('seeders', 0)
									if 'seeders' in item else
									'UNCACHED')
								set_property('alkoskins.highlight', self.info_highlights_dict[key])
							else:
								key = basic_quality
								source_type_label = selector_source_type_label(item, source=source, pack=pack)
								set_property('alkoskins.source_type', source_type_label)
								set_property('alkoskins.source_type_icon', selector_media_icon(selector_source_type_icon(item, source=source, pack=pack)))
								set_property('alkoskins.highlight', self.info_highlights_dict[key])
						else:
							key = basic_quality
							set_property('alkoskins.source_type', selector_source_type_label(item, source=source, pack=pack))
							set_property('alkoskins.highlight', self.info_highlights_dict[key])
						set_property('alkoskins.name', name)
						set_property('alkoskins.provider_key', provider_lower)
						set_property('alkoskins.provider', provider_display_label(provider_id))
					else:
						source_site = upper(source)
						provider, provider_icon = self.get_provider_and_path(lower(source))
						key = basic_quality
						set_property('alkoskins.highlight', self.info_highlights_dict[key])
						set_property('alkoskins.name', name)
						# Les liens déjà présents dans le cloud débrideur reçoivent un
						# affichage dédié plus bas (icône CLOUD + AD/RD/TB + libellé CLOUD).
						if cloud_debrid_label:
							set_property('alkoskins.source_type', cloud_debrid_label)
							set_property('alkoskins.source_type_icon', '')
						else:
							set_property('alkoskins.source_type', 'DIRECT')
							set_property('alkoskins.source_type_icon', selector_media_icon('selector_play.png'))
						set_property('alkoskins.provider_key', provider)
						set_property('alkoskins.provider', provider_display_label(provider))
					set_property('alkoskins.source_type_icon', selector_media_icon(selector_source_type_icon(item, source=source, pack=pack)))
					set_property('alkoskins.source_site_raw', source_site)
					set_property('alkoskins.source_site', local_cache_display_label(selector_source_site_label_for_item(item, source_site), item))
					set_property('alkoskins.source_site_icon', selector_media_icon(selector_source_site_icon(source_site)))
					if cloud_debrid_label:
						set_property('alkoskins.source_type_badge_icon', selector_media_icon('selector_cloud.png'))
						set_property('alkoskins.source_type_badge_icon_color', 'FFFFFFFF')
						set_property('alkoskins.source_type', cloud_debrid_label)
						set_property('alkoskins.source_type_icon', '')
						set_property('alkoskins.source_site_raw', 'CLOUD')
						set_property('alkoskins.source_site', 'CLOUD')
						set_property('alkoskins.source_site_icon', '')
					set_property('alkoskins.local_cache', 'true' if get('local_cache') else '')
					set_property('alkoskins.local_cache_label', get('local_cache_label', ''))
					set_property('alkoskins.cache_provider', get('cache_provider', ''))
					set_property('alkoskins.source_type_badge_icon', selector_media_icon(selector_source_type_badge_icon(item)))
					set_property('alkoskins.source_type_badge_icon_color', selector_source_type_badge_icon_color(item))
					if cloud_debrid_label:
						set_property('alkoskins.source_type_badge_icon', selector_media_icon('selector_cloud.png'))
						set_property('alkoskins.source_type_badge_icon_color', 'FFFFFFFF')
					set_property('alkoskins.cache_status_icon', selector_cache_status_icon(item))
					set_property('alkoskins.cache_status_icon_color', selector_cache_status_icon_color(item))
					set_property('alkoskins.provider_icon', provider_icon)
					set_property('alkoskins.quality_icon', quality_icon)
					size_label = get('size_label', '')
					try:
						if str(size_label or '').strip().lower().replace(' ', '') in ('0', '0.0', '0.00', '0gb', '0.0gb', '0.00gb', '0gib', '0.0gib', '0.00gib'):
							size_label = ''
					except: pass
					set_property('alkoskins.size_label', size_label or '')
					set_property('alkoskins.extra_info', extra_info)
					set_property('alkoskins.language_label', source_language_label(extra_info, name))
					quality_highlight = self.info_highlights_dict.get(basic_quality, listitem.getProperty('alkoskins.highlight') or 'FFFFFFFF')
					set_property('alkoskins.quality_highlight', quality_highlight)
					quality_upper = upper(quality)
					quality_sub = 'UHD' if quality_upper == '4K' else ('FHD' if quality_upper == '1080P' else quality_upper)
					set_property('alkoskins.quality', quality_upper)
					set_property('alkoskins.quality_sub', quality_sub)
					set_property('alkoskins.count', '%02d.' % count)
					set_property('alkoskins.hash', get('hash', 'N/A'))
					set_property('source', json.dumps(item))
					yield listitem
				except: pass
		try:
			highlight_type = self.info_highlights_dict['highlight_type']
			self.item_list = list(builder())
			self.total_results = string(len(self.item_list))
			# Ajoute des éléments vides en fin de liste pour que le dernier vrai lien
			# puisse remonter au-dessus du bas de fenêtre lors du défilement.
			for _ in range(1):
				spacer_listitem = self.make_listitem()
				spacer_listitem.setProperty('source', '{}')
				spacer_listitem.setProperty('alkoskins.spacer', 'true')
				self.item_list.append(spacer_listitem)
			if not self.prescrape: return
			prescrape_listitem = self.make_listitem()
			prescrape_listitem.setProperty('source', '{}')
			prescrape_listitem.setProperty('alkoskins.perform_full_search', 'true')
			prescrape_listitem.setProperty('alkoskins.start_full_scrape', '***%s***' % upper(start_full_scrape))
			self.item_list.append(prescrape_listitem)
			# Ajoute aussi un espace après l'action de recherche complète.
			# Sans ce tampon, certains skins gardent ce bouton collé au bas de la fenêtre.
			post_prescrape_spacer = self.make_listitem()
			post_prescrape_spacer.setProperty('source', '{}')
			post_prescrape_spacer.setProperty('alkoskins.spacer', 'true')
			self.item_list.append(post_prescrape_spacer)
		except: pass

	def set_properties(self):
		art = resolve_source_window_art(self.meta)
		self.setProperty('alkoskins.window_style', self.window_style)
		self.setProperty('alkoskins.fanart', art['fanart'])
		self.setProperty('alkoskins.poster', art['poster'])
		self.setProperty('alkoskins.landscape', art['landscape'])
		self.setProperty('alkoskins.title', self.meta['title'])
		self.setProperty('alkoskins.clearlogo', art['clearlogo'])
		self.setProperty('alkoskins.plot', self.meta['plot'])
		self.setProperty('alkoskins.total_results', self.total_results)
		self.setProperty('alkoskins.filters_ignored', self.filters_ignored)
		self.setProperty('alkoskins.scrape_time', '%.2f' % self.meta['scrape_time'])

	def original_landscape(self):
		# Fanart/landscape transmis à la fenêtre "Info supplémentaire".
		# Cette méthode reste le point d'accès stable pour le menu contextuel.
		return self.getProperty('alkoskins.landscape') or self.getProperty('alkoskins.fanart') or fanart_empty

	def filter_results(self):
		choices = [(filter_quality, 'quality'), (filter_provider, 'provider'), (filter_title, 'keyword_title'), (filter_extraInfo, 'extra_info')]
		list_items = [{'line1': item[0]} for item in choices]
		heading = filter_str.replace('[B]', '').replace('[/B]', '')
		kwargs = {'items': json.dumps(list_items), 'heading': heading, 'enumerate': 'false', 'multi_choice': 'false', 'multi_line': 'false'}
		main_choice = select_dialog([i[1] for i in choices], **kwargs)
		if main_choice is None: return
		if main_choice in ('quality', 'provider'):
			if main_choice == 'quality': choice_sorter = quality_choices
			else:
				sort_ranks = provider_sort_ranks()
				choice_sorter = sorted(sort_ranks.keys(), key=sort_ranks.get)
				choice_sorter = [upper(i) for i in choice_sorter]
			filter_property = 'alkoskins.%s' % main_choice
			duplicates = set()
			provider_choices = [
				i.getProperty(filter_property)
				for i in self.item_list
				if not (i.getProperty(filter_property) in duplicates or duplicates.add(i.getProperty(filter_property)))
				and not i.getProperty(filter_property) == ''
			]
			sort_order = {name: idx for idx, name in enumerate(choice_sorter)}
			provider_choices.sort(key=lambda name: sort_order.get(name, len(sort_order)))
			list_items = [{'line1': item} for item in provider_choices]
			kwargs = {'items': json.dumps(list_items), 'heading': heading, 'enumerate': 'false', 'multi_choice': 'true', 'multi_line': 'false'}
			choice = select_dialog(provider_choices, **kwargs)
			if choice is None: return
			filtered_list = [i for i in self.item_list if any(x in i.getProperty(filter_property) for x in choice)]
		elif main_choice == 'keyword_title':
			keywords = dialog.input(ls(33184))
			if not keywords: return
			keywords.replace(' ', '')
			keywords = keywords.split(',')
			choice = [upper(i) for i in keywords]
			filtered_list = [i for i in self.item_list if all(x in i.getProperty('alkoskins.name') for x in choice)]
		else:# extra_info
			list_items = [{'line1': item[0]} for item in extra_info_choices]
			kwargs = {'items': json.dumps(list_items), 'heading': heading, 'enumerate': 'false', 'multi_choice': 'true', 'multi_line': 'false'}
			choice = select_dialog(extra_info_choices, **kwargs)
			if choice is None: return
			choice = [i[1] for i in choice]
			filtered_list = [i for i in self.item_list if all(x in i.getProperty('alkoskins.extra_info') for x in choice)]
		if not filtered_list: return ok_dialog(text=32760, top_space=True)
		self.filter_applied = True
		self.win.reset()
		self.win.addItems(filtered_list)
		self._set_results_focus()
		self.setProperty('alkoskins.total_results', string(len(filtered_list)))

	def clear_filter(self):
		if self.is_closing(): return
		try: self.win.reset()
		except: return
		self.setProperty('alkoskins.total_results', self.total_results)
		self.onInit()

class ResultsInfo(BaseDialog):
	def __init__(self, *args, **kwargs):
		BaseDialog.__init__(self, args)
		self.focus_id = 10
		self.item = kwargs['item']
		self.fanart = kwargs.get('fanart') or ''
		self.set_properties()

	def onInit(self):
		# La fenêtre d'informations ne contient presque que des contrôles décoratifs.
		# Un bouton technique invisible fournit un focus stable à Kodi et évite
		# certains retours « Window id does not exist » à la fermeture.
		self.sleep(80)
		self.safe_set_focus(self.focus_id)

	def run(self):
		try: self.doModal()
		finally: self.clearProperties()

	def onAction(self, action):
		return self.close()

	def onClick(self, controlID):
		return self.close()

	def get_provider_and_path(self):
		provider = lower(self.item.getProperty('alkoskins.provider_key') or self.item.getProperty('alkoskins.provider'))
		if provider == 'ad': provider = 'alldebrid'
		if provider in info_icons_dict: provider_path = info_icons_dict[provider]
		else: provider_path = info_icons_dict['folders']
		return provider, provider_path

	def get_quality_and_path(self):
		quality = lower(self.item.getProperty('alkoskins.quality'))
		quality_path = info_icons_dict[quality]
		return quality, quality_path

	def set_properties(self):
		provider, provider_path = self.get_provider_and_path()
		quality, quality_path = self.get_quality_and_path()
		self.setProperty('alkoskins.results.info.fanart', self.fanart)
		self.setProperty('alkoskins.results.info.name', self.item.getProperty('alkoskins.name'))
		self.setProperty('alkoskins.results.info.source_type', self.item.getProperty('alkoskins.source_type'))
		self.setProperty('alkoskins.results.info.source_site', self.item.getProperty('alkoskins.source_site'))
		self.setProperty('alkoskins.results.info.size_label', self.item.getProperty('alkoskins.size_label'))
		self.setProperty('alkoskins.results.info.extra_info', self.item.getProperty('alkoskins.extra_info'))
		self.setProperty('alkoskins.results.info.highlight', self.item.getProperty('alkoskins.highlight'))
		self.setProperty('alkoskins.results.info.quality_highlight', self.item.getProperty('alkoskins.quality_highlight') or self.item.getProperty('alkoskins.highlight'))
		self.setProperty('alkoskins.results.info.hash', self.item.getProperty('alkoskins.hash'))
		self.setProperty('alkoskins.results.info.provider', provider_display_label(self.item.getProperty('alkoskins.provider') or provider))
		self.setProperty('alkoskins.results.info.quality', quality)
		self.setProperty('alkoskins.results.info.quality_sub', self.item.getProperty('alkoskins.quality_sub'))
		self.setProperty('alkoskins.results.info.provider_icon', provider_path)
		self.setProperty('alkoskins.results.info.quality_icon', quality_path)
		self.setProperty('alkoskins.results.info.source_type_badge_icon', self.item.getProperty('alkoskins.source_type_badge_icon') or 'tools.png')
		self.setProperty('alkoskins.results.info.source_type_badge_icon_color', self.item.getProperty('alkoskins.highlight') or 'FFFFFFFF')

class ResultsContextMenu(BaseDialog):
	def __init__(self, *args, **kwargs):
		BaseDialog.__init__(self, args)
		self.window_id = 2020
		self.item = kwargs['item']
		self.highlight = kwargs['highlight']
		self.meta = kwargs['meta']
		self.filter_applied = kwargs['filter_applied']
		self.item_list = []
		self.selected = None
		self._prepare_item_context()
		self.make_menu()
		self.set_properties()

	def _prepare_item_context(self):
		# Données normalisées pour le menu contextuel.
		# Le but est d'éviter de relire / redeviner les mêmes champs dans chaque action.
		get = self.item.get
		self.provider_source = string(get('source') or '')
		self.magnet_url = string(get('url') or 'None')
		self.info_hash = string(get('hash') or 'None')
		self.scrape_provider = string(get('scrape_provider') or '')
		self.cache_provider = string(get('cache_provider') or 'None')
		self.debrid_provider = get('debrid')

	def _add_context_action(self, label, mode):
		self.item_list.append(self.make_contextmenu_item(label, run_plugin_str, {'mode': mode}))

	def _has_cache_provider(self, *providers):
		return any(provider in self.cache_provider for provider in providers)

	def _is_uncached(self):
		return 'Uncached' in self.cache_provider

	def _can_browse_pack(self):
		return (
			'package' in self.item
			and self.provider_source == 'torrent'
			and string(self.magnet_url).startswith('magnet')
			and bool(self.info_hash and self.info_hash != 'None')
			and self.debrid_provider in ('real-debrid', 'alldebrid', 'torbox')
		)

	def onInit(self):
		try:
			win = self.getControl(self.window_id)
			win.addItems(self.item_list)
			self.safe_set_focus(self.window_id)
		except: self.close()

	def run(self):
		try: self.doModal()
		finally: self.clearProperties()
		return self.selected

	def onAction(self, action):
		if action in self.closing_actions: return self.close()
		if action in self.selection_actions:
			chosen_listitem = self.safe_get_listitem(self.window_id)
			if chosen_listitem is None: return self.close()
			self.selected = chosen_listitem.getProperty('alkoskins.context.action')
			return self.close()
		elif action in self.context_actions: return self.close()

	def make_menu(self):
		if self._has_cache_provider('real-debrid', 'alldebrid'):
			self._add_context_action(check_str, 'unchecked_magnet_status')
		if 'easynews' in self.scrape_provider:
			self._add_context_action(en_seek_str, 'seekable_easynews')
		if self.filter_applied:
			self._add_context_action(clr_filter_str, 'clear_results_filter')
		else:
			self._add_context_action(filter_str, 'results_filter')
		self._add_context_action(extra_info_str, 'results_info')
		if self._is_uncached(): return
		if self._can_browse_pack():
			self._add_context_action(browse_pack_str, 'browse_packs')
		if self.provider_source == 'torrent':
			self._add_context_action(cloud_str, 'manual_add_magnet_to_cloud')

	def set_properties(self):
		self.setProperty('alkoskins.context.highlight', self.highlight)

class ProgressMedia(BaseDialog):
	def __init__(self, *args, **kwargs):
		BaseDialog.__init__(self, args)
		self.is_canceled = False
		self.selected = None
		self.meta = kwargs['meta']
		self.text = kwargs.get('text', '')
		self.enable_buttons = kwargs.get('enable_buttons', False)
		self.true_button = kwargs.get('true_button', '')
		self.false_button = kwargs.get('false_button', '')
		self.focus_button = kwargs.get('focus_button', 10)
		self.percent = float(kwargs.get('percent', 0))
		self.make_items()
		self.set_properties()

	def onInit(self):
		if self.enable_buttons: self.allow_buttons()

	def run(self):
		try: self.doModal()
		finally: self.clearProperties()
		return self.selected

	def iscanceled(self):
		if self.enable_buttons: return self.selected
		else: return self.is_canceled

	def onAction(self, action):
		if action in self.closing_actions:
			self.is_canceled = True
			return self.close()

	def onClick(self, controlID):
		self.selected = controlID == 10
		return self.close()

	def allow_buttons(self):
		self.setProperty('alkoskins.source_progress.buttons', 'true')
		self.setProperty('alkoskins.source_progress.true_button', self.true_button)
		self.setProperty('alkoskins.source_progress.false_button', self.false_button)
		self.update(self.text, self.percent)
		self.safe_set_focus(self.focus_button)

	def make_items(self):
		art = resolve_source_window_art(self.meta)
		self.title = self.meta['title']
		self.year = str(self.meta['year'])
		self.poster = art['poster']
		self.fanart = art['fanart']
		self.clearlogo = art['clearlogo']

	def set_properties(self):
		self.setProperty('alkoskins.source_progress.title', self.title)
		self.setProperty('alkoskins.source_progress.year', self.year)
		self.setProperty('alkoskins.source_progress.poster', self.poster)
		self.setProperty('alkoskins.source_progress.fanart', self.fanart)
		self.setProperty('alkoskins.source_progress.clearlogo', self.clearlogo)

	def update(self, content='', percent=0):
		try:
			self.getControl(2000).setText(content)
			self.getControl(5000).setPercent(percent)
		except: pass
