# -*- coding: utf-8 -*-
# plugin.video.alkoflix
# resources/lib/indexers/metadata.py
# © Les alKODIques

from indexers import tmdb_api as tmdb, fanarttv_api as fanarttv
from caches.meta_cache import MetaCache
from modules.utils import jsondate_to_datetime, subtract_dates, TaskPool
# from modules.kodi_utils import logger

movie_data, tvshow_data, tmdb_english_translation = tmdb.movie_details, tmdb.tvshow_details, tmdb.english_translation
movie_external_id, tvshow_external_id, season_episodes_details = tmdb.movie_external_id, tmdb.tvshow_external_id, tmdb.season_episodes_details
default_fanarttv_data, fanarttv_get, fanarttv_add = fanarttv.default_fanart_nometa, fanarttv.get, fanarttv.add
subtract_dates_function, jsondate_to_datetime_function = subtract_dates, jsondate_to_datetime
backup_resolutions, writer_credits = {'poster': 'w780', 'fanart': 'w1280', 'still': 'original', 'profile': 'h632'}, ('Author', 'Writer', 'Screenplay', 'Characters')
alt_titles_test, trailers_test, finished_show_check, empty_value_check = ('US', 'GB', 'UK', ''), ('Trailer', 'Teaser'), ('Ended', 'Canceled'), ('', 'None', None)
tmdb_image_base, youtube_url, date_format = tmdb.tmdb_image_base, 'plugin://plugin.video.youtube/play/?video_id=%s', '%Y-%m-%d'
EXPIRES_2_DAYS, EXPIRES_4_DAYS, EXPIRES_7_DAYS, EXPIRES_14_DAYS, EXPIRES_182_DAYS = 2, 4, 7, 14, 182

# Version de sélection des images stockée dans le cache des métadonnées.
# À incrémenter quand l'ordre de priorité des visuels change, afin que les
# anciennes entrées soient reconstruites automatiquement à la prochaine lecture.
ARTWORK_SELECTION_VERSION = 5

# Les traces cache hit/miss/set des métadonnées TMDb sont volontairement
# silencieuses. Ces opérations peuvent se produire des centaines de fois dans
# les widgets et les longues listes, même quand tout fonctionne correctement.
# Les erreurs réelles continuent d'être journalisées dans les blocs concernés.
def _metadata_debug(action, media_type, id_type, media_id, extra=''):
	return

# Construit l'ordre de priorité des langues pour les visuels.
def build_language_priority(image_language='fr', original_language='', prefer_neutral=False, allow_neutral=True):
	primary = image_language if image_language in ('fr', 'en') else 'fr'
	fallback = 'en' if primary == 'fr' else 'fr'
	neutral_languages = (None, '', 'xx', 'null', '00')
	original_language = str(original_language or '').lower().strip()
	ordered = []
	if prefer_neutral:
		ordered.extend(neutral_languages)
		ordered.extend((primary, fallback))
		if original_language and original_language not in ordered and original_language not in neutral_languages:
			ordered.append(original_language)
	else:
		ordered.extend((primary, fallback))
		# Quand il n'existe ni visuel FR ni EN, on retombe d'abord sur la VO
		# (espagnol, italien, japonais, etc.) avant les images neutres/sans texte.
		if original_language and original_language not in ordered and original_language not in neutral_languages:
			ordered.append(original_language)
		if allow_neutral:
			ordered.extend(neutral_languages)
	result = []
	for lang in ordered:
		if lang not in result:
			result.append(lang)
	return tuple(result)

# Fusionne les listes d'images TMDb en évitant les doublons sur file_path.
def merge_tmdb_image_lists(base_items, extra_items):
	merged, seen = [], set()
	for item in (base_items or []) + (extra_items or []):
		try: file_path = item.get('file_path')
		except: file_path = None
		if not file_path or file_path in seen:
			continue
		seen.add(file_path)
		merged.append(item)
	return merged

# Si la VO n'est ni FR ni EN, TMDb peut l'avoir exclue de la réponse initiale.
# On va donc rechercher les images de la langue originale, puis les fusionner.
def enrich_images_with_original_language(media_type, data, user_info):
	try:
		original_language = str((data or {}).get('original_language') or '').lower().strip()
		image_language = user_info.get('image_language', 'fr')
		primary = image_language if image_language in ('fr', 'en') else 'fr'
		fallback = 'en' if primary == 'fr' else 'fr'
		if not original_language or original_language in (primary, fallback, 'null', 'xx', '00'):
			return data.get('images') or {}
		tmdb_id = data.get('id')
		if not tmdb_id:
			return data.get('images') or {}
		media_key = 'movies' if media_type == 'movie' else 'tv'
		include_languages = ','.join([primary, fallback, original_language, 'null'])
		extra_images = tmdb.tmdb_media_images(media_key, tmdb_id, include_languages=include_languages)
		if not isinstance(extra_images, dict):
			return data.get('images') or {}
		images = dict(data.get('images') or {})
		for key in ('posters', 'backdrops', 'logos'):
			images[key] = merge_tmdb_image_lists(images.get(key, []), extra_images.get(key, []))
		return images
	except:
		return data.get('images') or {}

# Sélectionne une image TMDb selon la langue prioritaire choisie par l'utilisateur.
def pick_tmdb_art(items, image_resolution, size_key, require_png=False, prefer_neutral=False, image_language='fr', original_language='', allow_neutral=True):
	if not items:
		return ''

	lang_order = build_language_priority(image_language=image_language, original_language=original_language, prefer_neutral=prefer_neutral, allow_neutral=allow_neutral)

	for lang in lang_order:
		try:
			candidates = [i for i in items if i.get('iso_639_1') == lang]
		except:
			candidates = []

		if require_png:
			candidates = [i for i in candidates if i.get('file_path', '').lower().endswith('png')]

		if not candidates:
			continue

		try:
			candidates.sort(key=lambda x: (float(x.get('vote_average', 0)), int(x.get('vote_count', 0))), reverse=True)
		except:
			pass

		file_path = candidates[0].get('file_path')
		if file_path:
			return tmdb_image_base % (image_resolution[size_key], file_path)

	return ''

def pick_movie_certification(release_dates, country_code):
	# Récupère la certification d'un film pour le pays choisi dans les réglages.
	# Aucun fallback US n'est appliqué si un autre pays est choisi : mieux vaut vide que faux.
	try:
		results = release_dates.get('results') or []
		for country in results:
			if country.get('iso_3166_1') != country_code: continue
			for item in country.get('release_dates') or []:
				certification = (item.get('certification') or '').strip()
				if certification: return certification
	except:
		pass
	return ''

def pick_tv_certification(content_ratings, country_code):
	# Récupère la certification d'une série pour le pays choisi dans les réglages.
	try:
		results = content_ratings.get('results') or []
		for item in results:
			if item.get('iso_3166_1') != country_code: continue
			rating = (item.get('rating') or '').strip()
			if rating: return rating
	except:
		pass
	return ''

def certification_country_changed(meta, user_info):
	# Les métadonnées sont cachées longtemps. Si l'utilisateur change de pays,
	# on force une reconstruction pour récupérer les bons sigles de classification.
	try:
		if meta.get('blank_entry', False): return False
		return meta.get('certification_country', 'US') != user_info.get('certification_country', 'US')
	except:
		return False


def image_language_changed(meta, user_info):
	# Les URL d'images choisies sont stockées dans le cache des métadonnées.
	# Si la langue prioritaire ou l'algorithme de sélection change, on reconstruit
	# l'entrée concernée sans attendre un vidage manuel du cache.
	try:
		if meta.get('blank_entry', False): return False
		return (
			meta.get('image_language') != user_info.get('image_language', 'fr')
			or int(meta.get('artwork_selection_version', 0) or 0) != ARTWORK_SELECTION_VERSION
		)
	except:
		return False


# Récupère et met en cache les métadonnées d’un film.
def movie_meta(id_type, media_id, user_info, current_date):
	if id_type == 'trakt_dict':
		if media_id.get('tmdb'): id_type, media_id = 'tmdb_id', media_id['tmdb']
		elif media_id.get('imdb'): id_type, media_id = 'imdb_id', media_id['imdb']
		else: id_type, media_id = None, None
	if media_id is None: return {}
	metacache = MetaCache()
	metacache_get, metacache_set = metacache.get, metacache.set
	fanarttv_data, language, image_language, extra_fanart_enabled, fanart_client_key = None, user_info['language'], user_info.get('image_language', 'fr'), user_info['extra_fanart_enabled'], user_info['fanart_client_key']
	meta = metacache_get('movie', id_type, media_id)
	if meta and 'tmdb_id' in meta: _metadata_debug('cache hit', 'movie', id_type, media_id)
	elif meta: _metadata_debug('cache expired/artwork reuse', 'movie', id_type, media_id)
	else: _metadata_debug('cache miss', 'movie', id_type, media_id)
	if meta:
		if 'tmdb_id' in meta:
			cert_changed = certification_country_changed(meta, user_info)
			img_changed = image_language_changed(meta, user_info)
			if cert_changed or img_changed:
				_metadata_debug('cache refresh', 'movie', id_type, media_id, 'cert_changed=%s | image_changed=%s' % (cert_changed, img_changed))
				# Si seule la certification change, on peut conserver les images Fanart.tv.
				# Si la langue/stratégie d'images change, on force une vraie resélection.
				if cert_changed and not img_changed: fanarttv_data = metacache.make_fanart_dict(meta)
				metacache.delete('movie', id_type, media_id, meta=meta, dbcon=None)
				meta = None
	if meta:
		if 'tmdb_id' in meta:
			if meta.get('fanart_lang_fixed', False):
				if not meta.get('fanart_added', False) and extra_fanart_enabled:
					meta = fanarttv_add('movies', image_language, meta['tmdb_id'], fanart_client_key, meta, meta.get('original_language'))
					metacache_set('movie', id_type, meta, movie_expiry(current_date, meta))
				return meta
		else: fanarttv_data = dict(meta)
	try:
		tmdb_api = user_info['tmdb_api']
		_metadata_debug('request details', 'movie', id_type, media_id, 'language=%s' % language)
		if id_type == 'tmdb_id' or id_type == 'imdb_id': data = movie_data(media_id, language, tmdb_api)
		else:
			external_result = movie_external_id(id_type, media_id, tmdb_api)
			if not external_result: data = None
			else: data = movie_data(external_result['id'], language, tmdb_api)
		if not data or data.get('success', True) is False:
			if id_type == 'tmdb_id': meta = {'tmdb_id': media_id, 'imdb_id': 'tt0000000', 'tvdb_id': '0000000', 'fanart_added': True, 'blank_entry': True}
			else: meta = {'tmdb_id': '0000000', 'imdb_id': media_id, 'tvdb_id': '0000000', 'fanart_added': True, 'blank_entry': True}
			metacache_set('movie', id_type, meta, EXPIRES_2_DAYS)
			return meta
		if language != 'en':
			if data['overview'] in empty_value_check:
				media_id, id_type = data['id'], 'tmdb_id'
				eng_data = movie_data(media_id, 'en', tmdb_api)
				eng_overview = eng_data['overview']
				data['overview'] = eng_overview
				if 'videos' in data:
					all_trailers = data['videos']['results']
					if all_trailers:
						try: trailer_test = [i for i in all_trailers if i['site'] == 'YouTube' and i['type'] in trailers_test]
						except: trailer_test = False
					else: trailer_test = False
				else: trailer_test = False
				if not trailer_test:
					if 'videos' in eng_data:
						eng_all_trailers = eng_data['videos']['results']
						if eng_all_trailers:
							data['videos']['results'] = eng_all_trailers
		if not fanarttv_data and extra_fanart_enabled: fanarttv_data = fanarttv_get('movies', image_language, data['id'], fanart_client_key, data.get('original_language'))
		meta = build_movie_meta(data, user_info, fanarttv_data)
		metacache_set('movie', id_type, meta, movie_expiry(current_date, meta))
		_metadata_debug('cache set', 'movie', id_type, media_id)
	except: pass
	return meta

# Récupère et met en cache les métadonnées d’une série TV.
def tvshow_meta(id_type, media_id, user_info, current_date):
	if id_type == 'trakt_dict':
		if media_id.get('tmdb'): id_type, media_id = 'tmdb_id', media_id['tmdb']
		elif media_id.get('imdb'): id_type, media_id = 'imdb_id', media_id['imdb']
		elif media_id.get('tvdb'): id_type, media_id = 'tvdb_id', media_id['tvdb']
		else: id_type, media_id = None, None
	if media_id is None: return {}
	metacache = MetaCache()
	metacache_get, metacache_set = metacache.get, metacache.set
	fanarttv_data, language, image_language, extra_fanart_enabled, fanart_client_key = None, user_info['language'], user_info.get('image_language', 'fr'), user_info['extra_fanart_enabled'], user_info['fanart_client_key']
	meta = metacache_get('tvshow', id_type, media_id)
	if meta and 'tmdb_id' in meta: _metadata_debug('cache hit', 'tvshow', id_type, media_id)
	elif meta: _metadata_debug('cache expired/artwork reuse', 'tvshow', id_type, media_id)
	else: _metadata_debug('cache miss', 'tvshow', id_type, media_id)
	if meta:
		if 'tmdb_id' in meta:
			cert_changed = certification_country_changed(meta, user_info)
			img_changed = image_language_changed(meta, user_info)
			if cert_changed or img_changed:
				_metadata_debug('cache refresh', 'tvshow', id_type, media_id, 'cert_changed=%s | image_changed=%s' % (cert_changed, img_changed))
				# Si seule la certification change, on peut conserver les images Fanart.tv.
				# Si la langue/stratégie d'images change, on force une vraie resélection.
				if cert_changed and not img_changed: fanarttv_data = metacache.make_fanart_dict(meta)
				metacache.delete('tvshow', id_type, media_id, meta=meta, dbcon=None)
				meta = None
	if meta:
		if 'tmdb_id' in meta:
			if meta.get('fanart_lang_fixed', False):
				if not meta.get('fanart_added', False) and extra_fanart_enabled:
					meta = fanarttv_add('tv', image_language, meta['tvdb_id'], fanart_client_key, meta, meta.get('original_language'))
					metacache_set('tvshow', id_type, meta, tvshow_expiry(current_date, meta))
				return meta
		else: fanarttv_data = dict(meta)
	try:
		tmdb_api = user_info['tmdb_api']
		_metadata_debug('request details', 'tvshow', id_type, media_id, 'language=%s' % language)
		if id_type == 'tmdb_id':
			data = tvshow_data(media_id, language, tmdb_api)
		else:
			external_result = tvshow_external_id(id_type, media_id, tmdb_api)
			if not external_result: data = None
			else: data = tvshow_data(external_result['id'], language, tmdb_api)
		if not data or data.get('success', True) is False:
			if id_type == 'tmdb_id': meta = {'tmdb_id': media_id, 'imdb_id': 'tt0000000', 'tvdb_id': '0000000', 'fanart_added': True, 'blank_entry': True}
			elif id_type == 'imdb_id': meta = {'tmdb_id': '0000000', 'imdb_id': media_id, 'tvdb_id': '0000000', 'fanart_added': True, 'blank_entry': True}
			else: meta = {'tmdb_id': '0000000', 'imdb_id': 'tt0000000', 'tvdb_id': media_id, 'fanart_added': True, 'blank_entry': True}
			metacache_set('tvshow', id_type, meta, EXPIRES_2_DAYS)
			return meta
		if language != 'en':
			if data['overview'] in empty_value_check:
				media_id, id_type = data['id'], 'tmdb_id'
				eng_data = tvshow_data(media_id, 'en', tmdb_api)
				eng_overview = eng_data['overview']
				data['overview'] = eng_overview
				if 'videos' in data:
					all_trailers = data['videos']['results']
					if all_trailers:
						try: trailer_test = [i for i in all_trailers if i['site'] == 'YouTube' and i['type'] in trailers_test]
						except: trailer_test = False
					else: trailer_test = False
				else: trailer_test = False
				if not trailer_test:
					if 'videos' in eng_data:
						eng_all_trailers = eng_data['videos']['results']
						if eng_all_trailers:
							data['videos']['results'] = eng_all_trailers
		if not fanarttv_data and extra_fanart_enabled: fanarttv_data = fanarttv_get('tv', image_language, data['external_ids']['tvdb_id'], fanart_client_key, data.get('original_language'))
		meta = build_tvshow_meta(data, user_info, fanarttv_data)
		metacache_set('tvshow', id_type, meta, tvshow_expiry(current_date, meta))
		_metadata_debug('cache set', 'tvshow', id_type, media_id)
	except: pass
	return meta

# Récupère les métadonnées de tous les épisodes d’une saison.
def season_episodes_meta(season, meta, user_info):
	def _process():
		for ep_data in data:
			writer, director, guest_stars = '', '', []
			ep_data_get = ep_data.get
			title, plot, premiered = ep_data_get('name'), ep_data_get('overview'), ep_data_get('air_date')
			season, episode, ep_type = ep_data_get('season_number'), ep_data_get('episode_number'), ep_data_get('episode_type')
			rating, votes, still_path = ep_data_get('vote_average'), ep_data_get('vote_count'), ep_data_get('still_path')
			ep_type = ep_details.get(ep_type) or ep_details.get(episode) or ep_type or ''
			if ep_type == 'mid_season_finale': ep_details[episode + 1] = 'mid_season_premiere'
			if still_path: thumb = tmdb_image_base % (still_resolution, still_path)
			else: thumb = None
			try: duration = ep_data_get('runtime') * 60
			except: duration = 60 * 60
			guest_stars_list = ep_data_get('guest_stars')
			if guest_stars_list:
				try: guest_stars = [
					{'name': i['name'], 'role': i['character'], 'thumbnail': tmdb_image_base % (profile_resolution, i['profile_path']) if i['profile_path'] else ''}
					for i in guest_stars_list
				]
				except: pass
			crew = ep_data_get('crew')
			if crew:
				try: writer = ', '.join([i['name'] for i in crew if i['job'] in writer_credits])
				except: pass
				try: director = [i['name'] for i in crew if i['job'] == 'Director'][0]
				except: pass
			yield {
				'thumb': thumb, 'title': title, 'guest_stars': guest_stars, 'plot': plot, 'premiered': premiered,
				'director': director, 'writer': writer, 'rating': rating, 'votes': votes, 'mediatype': 'episode',
				'episode_type': ep_type, 'season': season, 'episode': episode, 'duration': duration
			}
	metacache = MetaCache()
	metacache_get, metacache_set = metacache.get, metacache.set
	media_id, data = meta['tmdb_id'], None
	string = '%s_%s' % (media_id, season)
	data = metacache_get('season', 'tmdb_id', string)
	if data:
		_metadata_debug('cache hit', 'season', 'tmdb_id', string)
		return data
	_metadata_debug('cache miss', 'season', 'tmdb_id', string)
	try:
		show_ended, total_seasons = meta['status'] in finished_show_check, meta['total_seasons']
		expiration = EXPIRES_182_DAYS if show_ended or total_seasons > int(season) else EXPIRES_4_DAYS
		premiere = 'series_premiere' if int(season) == 1 else 'season_premiere'
		finale = 'series_finale' if show_ended and int(season) == total_seasons else 'season_finale'
		ep_details = {1: premiere, 'mid_season': 'mid_season_finale', 'finale': finale}
		image_resolution = user_info.get('image_resolution', backup_resolutions)
		still_resolution, profile_resolution = image_resolution['still'], image_resolution['profile']
		data = season_episodes_details(media_id, season, user_info['language'], user_info['tmdb_api'])['episodes']
		data = list(_process())
		metacache_set('season', 'tmdb_id', data, expiration, string)
		_metadata_debug('cache set', 'season', 'tmdb_id', string, 'episodes=%s' % len(data))
	except: pass
	return data

# Récupère les métadonnées de tous les épisodes d’une série.
def all_episodes_meta(meta, user_info, Thread):
	def _get_tmdb_episodes(season):
		try: data.extend(season_episodes_meta(season, meta, user_info))
		except: pass
	try:
		data = []
		seasons = [(i['season_number'],) for i in meta['season_data']] # TaskPool requires tuple
		for i in TaskPool().tasks(_get_tmdb_episodes, seasons, Thread): i.join()
	except: pass
	return data

# Récupère le titre anglais officiel d’un média depuis TMDb.
def english_translation(media_type, media_id, user_info):
	key = 'title' if media_type == 'movie' else 'name'
	translations = tmdb_english_translation(media_type, media_id, user_info['tmdb_api'])
	try: english = [i['data'][key] for i in translations if i['iso_639_1'] == 'en'][0]
	except: english = ''
	return english

# Détermine la durée de cache des métadonnées d’un film.
def movie_expiry(current_date, meta):
	try:
		difference = subtract_dates_function(current_date, jsondate_to_datetime_function(meta['premiered'], date_format, remove_time=True))
		if difference < 0: expiration = abs(difference) + 1
		elif difference <= 14: expiration = EXPIRES_7_DAYS
		elif difference <= 30: expiration = EXPIRES_14_DAYS
		else: expiration = EXPIRES_182_DAYS
	except: return EXPIRES_7_DAYS
	return max(expiration, EXPIRES_7_DAYS)

# Détermine la durée de cache des métadonnées d’une série TV.
def tvshow_expiry(current_date, meta):
	try:
		if meta['status'] in finished_show_check: return EXPIRES_182_DAYS
		next_episode_to_air = meta['extra_info'].get('next_episode_to_air')
		if not next_episode_to_air: return EXPIRES_7_DAYS
		expiration = subtract_dates_function(jsondate_to_datetime_function(next_episode_to_air['air_date'], date_format, remove_time=True), current_date)
	except: return EXPIRES_4_DAYS
	return max(expiration, EXPIRES_4_DAYS)

# Construit le dictionnaire de métadonnées complet pour un film.
def build_movie_meta(data, user_info, fanarttv_data=None):
	image_resolution = user_info.get('image_resolution', backup_resolutions)
	image_language = user_info.get('image_language', 'fr')
	data_get = data.get
	cast, all_trailers, country, country_codes = [], [], [], []
	writer, mpaa, director, trailer, studio = '', '', '', '', ''
	tmdb_id, imdb_id = data_get('id', ''), data_get('imdb_id', '')
	rating, votes = data_get('vote_average', ''), data_get('vote_count', '')
	plot, tagline, premiered = data_get('overview', ''), data_get('tagline', ''), data_get('release_date', '')
	poster_path, backdrop_path = data_get('poster_path'), data_get('backdrop_path')
	original_language = data_get('original_language', '')
	images = enrich_images_with_original_language('movie', data, user_info)
	poster = pick_tmdb_art(images.get('posters', []), image_resolution, 'poster', image_language=image_language, original_language=original_language)
	poster_text = pick_tmdb_art(images.get('posters', []), image_resolution, 'poster', image_language=image_language, original_language=original_language, allow_neutral=False)
	if not poster and poster_path: poster = tmdb_image_base % (image_resolution['poster'], poster_path)
	elif not poster: poster = ''
	fanart = pick_tmdb_art(images.get('backdrops', []), image_resolution, 'fanart', prefer_neutral=True, image_language=image_language, original_language=original_language)
	if not fanart and backdrop_path: fanart = tmdb_image_base % (image_resolution['fanart'], backdrop_path)
	elif not fanart: fanart = ''
	landscape = pick_tmdb_art(images.get('backdrops', []), image_resolution, 'fanart', image_language=image_language, original_language=original_language)
	landscape_text = pick_tmdb_art(images.get('backdrops', []), image_resolution, 'fanart', image_language=image_language, original_language=original_language, allow_neutral=False)
	if not landscape: landscape = fanart
	tmdblogo = pick_tmdb_art(images.get('logos', []), image_resolution, 'fanart', require_png=True, image_language=image_language, original_language=original_language)
	title, original_title = data_get('title'), data_get('original_title')
	try: english_title = [i['data']['title'] for i in data_get('translations')['translations'] if i['iso_639_1'] == 'en'][0]
	except: english_title = None
	try: year = str(data_get('release_date').split('-')[0] or 0)
	except: year = ''
	try: duration = int(data_get('runtime', '90') * 60)
	except: duration = 0
	genres_data = data_get('genres') or []
	try: genre = ', '.join([i['name'] for i in genres_data])
	except: genre = []
	try: genre_ids = [str(i.get('id')) for i in genres_data if i.get('id')]
	except: genre_ids = []
	rootname = '%s (%s)' % (title, year)
	companies = data_get('production_companies')
	if companies:
		if len(companies) == 1: studio = [i['name'] for i in companies][0]
		else:
			try: studio = [i['name'] for i in companies if i['logo_path'] not in empty_value_check][0] or [i['name'] for i in companies][0]
			except: pass
	production_countries = data_get('production_countries')
	if production_countries:
		country = [i['name'] for i in production_countries]
		country_codes = [i['iso_3166_1'] for i in production_countries]
	certification_country = user_info.get('certification_country', 'US')
	release_dates = data_get('release_dates')
	if release_dates: mpaa = pick_movie_certification(release_dates, certification_country)
	credits = data_get('credits')
	if credits:
		all_cast = credits.get('cast')
		if all_cast:
			try: cast = [
				{'name': i['name'], 'role': i['character'], 'thumbnail': tmdb_image_base % (image_resolution['profile'], i['profile_path']) if i['profile_path'] else ''}
				for i in all_cast
			]
			except: pass
		crew = credits.get('crew')
		if crew:
			try: writer = ', '.join([i['name'] for i in crew if i['job'] in writer_credits])
			except: pass
			try: director = [i['name'] for i in crew if i['job'] == 'Director'][0]
			except: pass
	alternative_titles = data_get('alternative_titles')
	if alternative_titles:
		alternatives = alternative_titles['titles']
		alternative_titles = [i['title'] for i in alternatives if i['iso_3166_1'] in alt_titles_test]
	videos = data_get('videos')
	if videos:
		all_trailers = videos['results']
		try: trailer = [youtube_url % i['key'] for i in all_trailers if i['site'] == 'YouTube' and i['type'] in trailers_test][0]
		except: pass
	status, homepage = data_get('status', 'N/A'), data_get('homepage', 'N/A')
	belongs_to_collection = data_get('belongs_to_collection')
	if belongs_to_collection: ei_collection_name, ei_collection_id = belongs_to_collection['name'], belongs_to_collection['id']
	else: ei_collection_name, ei_collection_id = None, None
	try: ei_budget = '${:,}'.format(data_get('budget'))
	except: ei_budget = '$0'
	try: ei_revenue = '${:,}'.format(data_get('revenue'))
	except: ei_revenue = '$0'
	extra_info = {'status': status, 'collection_name': ei_collection_name, 'collection_id': ei_collection_id, 'budget': ei_budget, 'revenue': ei_revenue, 'homepage': homepage}
	meta_dict = {
		'tmdb_id': tmdb_id, 'imdb_id': imdb_id, 'tvdb_id': 'None', 'imdbnumber': imdb_id, 'tmdblogo': tmdblogo,
		'poster': poster, 'poster_text': poster_text, 'fanart': fanart, 'landscape_tmdb': landscape, 'landscape_tmdb_text': landscape_text, 'year': year, 'title': title, 'rootname': rootname, 'original_language': original_language,
		'original_title': original_title, 'english_title': english_title, 'alternative_titles': alternative_titles,
		'tagline': tagline, 'plot': plot, 'mpaa': mpaa, 'certification_country': certification_country, 'studio': studio, 'director': director, 'writer': writer,
		'duration': duration, 'premiered': premiered, 'genre': genre, 'genre_ids': genre_ids, 'rating': rating, 'votes': votes,
		'country': country, 'country_codes': country_codes, 'trailer': trailer, 'all_trailers': all_trailers,
		'cast': cast, 'extra_info': extra_info, 'mediatype': 'movie', 'meta_language': user_info.get('language', ''),
		'fanart_lang_fixed': True, 'image_language': image_language, 'artwork_selection_version': ARTWORK_SELECTION_VERSION
	}
	if fanarttv_data: meta_dict.update(fanarttv_data)
	else: meta_dict.update(default_fanarttv_data)
	if not meta_dict.get('landscape'): meta_dict['landscape'] = landscape
	return meta_dict

# Construit le dictionnaire de métadonnées complet pour une série TV.
def build_tvshow_meta(data, user_info, fanarttv_data=None):
	image_resolution = user_info.get('image_resolution', backup_resolutions)
	image_language = user_info.get('image_language', 'fr')
	data_get = data.get
	cast, all_trailers, country, country_codes = [], [], [], []
	writer, mpaa, director, trailer, studio = '', '', '', '', ''
	external_ids = data_get('external_ids')
	tmdb_id, imdb_id, tvdb_id = data_get('id', ''), external_ids.get('imdb_id', ''), external_ids.get('tvdb_id', 'None')
	rating, votes = data_get('vote_average', ''), data_get('vote_count', '')
	plot, tagline, premiered = data_get('overview', ''), data_get('tagline', ''), data_get('first_air_date', '')
	season_data, total_seasons, total_aired_eps = data_get('seasons'), data_get('number_of_seasons'), data_get('number_of_episodes')
	poster_path, backdrop_path = data_get('poster_path'), data_get('backdrop_path')
	original_language = data_get('original_language', '')
	images = enrich_images_with_original_language('tv', data, user_info)
	poster = pick_tmdb_art(images.get('posters', []), image_resolution, 'poster', image_language=image_language, original_language=original_language)
	poster_text = pick_tmdb_art(images.get('posters', []), image_resolution, 'poster', image_language=image_language, original_language=original_language, allow_neutral=False)
	poster_text = pick_tmdb_art(images.get('posters', []), image_resolution, 'poster', image_language=image_language, original_language=original_language, allow_neutral=False)
	if not poster and poster_path: poster = tmdb_image_base % (image_resolution['poster'], poster_path)
	elif not poster: poster = ''
	fanart = pick_tmdb_art(images.get('backdrops', []), image_resolution, 'fanart', prefer_neutral=True, image_language=image_language, original_language=original_language)
	if not fanart and backdrop_path: fanart = tmdb_image_base % (image_resolution['fanart'], backdrop_path)
	elif not fanart: fanart = ''
	landscape = pick_tmdb_art(images.get('backdrops', []), image_resolution, 'fanart', image_language=image_language, original_language=original_language)
	landscape_text = pick_tmdb_art(images.get('backdrops', []), image_resolution, 'fanart', image_language=image_language, original_language=original_language, allow_neutral=False)
	if not landscape: landscape = fanart
	tmdblogo = pick_tmdb_art(images.get('logos', []), image_resolution, 'fanart', require_png=True, image_language=image_language, original_language=original_language)
	title, original_title = data_get('name'), data_get('original_name')
	try: english_title = [i['data']['name'] for i in data_get('translations')['translations'] if i['iso_639_1'] == 'en'][0]
	except: english_title = None
	try: year = str(data_get('first_air_date').split('-')[0] or 0)
	except: year = ''
	try: duration = min(data_get('episode_run_time')) * 60
	except: duration = 0
	genres_data = data_get('genres') or []
	try: genre = ', '.join([i['name'] for i in genres_data])
	except: genre = []
	try: genre_ids = [str(i.get('id')) for i in genres_data if i.get('id')]
	except: genre_ids = []
	rootname = '%s (%s)' % (title, year)
	networks = data_get('networks')
	if networks:
		if len(networks) == 1: studio = [i['name'] for i in networks][0]
		else:
			try: studio = [i['name'] for i in networks if i['logo_path'] not in empty_value_check][0] or [i['name'] for i in networks][0]
			except: pass
	production_countries = data_get('production_countries')
	if production_countries:
		country = [i['name'] for i in production_countries]
		country_codes = [i['iso_3166_1'] for i in production_countries]
	certification_country = user_info.get('certification_country', 'US')
	content_ratings = data_get('content_ratings')
	release_dates = data_get('release_dates')
	if content_ratings: mpaa = pick_tv_certification(content_ratings, certification_country)
	elif release_dates: mpaa = pick_movie_certification(release_dates, certification_country)
	credits = data_get('credits')
	if credits:
		all_cast = credits.get('cast')
		if all_cast:
			try: cast = [
				{'name': i['name'], 'role': i['character'], 'thumbnail': tmdb_image_base % (image_resolution['profile'], i['profile_path']) if i['profile_path'] else ''}
				for i in all_cast
			]
			except: pass
		crew = credits.get('crew')
		if crew:
			try: writer = ', '.join([i['name'] for i in crew if i['job'] in writer_credits])
			except: pass
			try: director = [i['name'] for i in crew if i['job'] == 'Director'][0]
			except: pass
	# Certaines séries TMDb (anthologies, séries à distribution variable) ont
	# credits.cast vide mais aggregate_credits.cast rempli. TMDb Helper s'appuie
	# justement sur ces crédits agrégés, on les utilise donc comme fallback.
	if not cast:
		aggregate_credits = data_get('aggregate_credits')
		aggregate_cast = aggregate_credits.get('cast') if aggregate_credits else []
		if aggregate_cast:
			try:
				cast = []
				for i in aggregate_cast:
					roles = i.get('roles') or []
					characters = []
					for role in roles:
						character = role.get('character') or ''
						if character and character not in characters: characters.append(character)
					role_label = ' / '.join(characters[:2])
					cast.append({'name': i['name'], 'role': role_label, 'thumbnail': tmdb_image_base % (image_resolution['profile'], i['profile_path']) if i.get('profile_path') else ''})
			except: pass
	alternative_titles = data_get('alternative_titles')
	if alternative_titles:
		alternatives = alternative_titles['results']
		alternative_titles = [i['title'] for i in alternatives if i['iso_3166_1'] in alt_titles_test]
	videos = data_get('videos')
	if videos:
		all_trailers = videos['results']
		try: trailer = [youtube_url % i['key'] for i in all_trailers if i['site'] == 'YouTube' and i['type'] in trailers_test][0]
		except: pass
	status, _type, homepage = data_get('status', 'N/A'), data_get('type', 'N/A'), data_get('homepage', 'N/A')
	created_by = data_get('created_by')
	if created_by:
		try: ei_created_by = ', '.join([i['name'] for i in created_by])
		except: ei_created_by = 'N/A'
	else: ei_created_by = 'N/A'
	ei_next_ep = data_get('next_episode_to_air')
	ei_last_ep = data_get('last_episode_to_air')
	if ei_last_ep and not status in finished_show_check: total_aired_eps = ei_last_ep['episode_number'] + sum([
			i['episode_count'] for i in season_data if 0 < i['season_number'] < ei_last_ep['season_number']
		])
	extra_info = {'status': status, 'type': _type, 'homepage': homepage, 'created_by': ei_created_by, 'next_episode_to_air': ei_next_ep, 'last_episode_to_air': ei_last_ep}
	meta_dict = {
		'tmdb_id': tmdb_id, 'imdb_id': imdb_id, 'tvdb_id': tvdb_id, 'imdbnumber': imdb_id, 'tmdblogo': tmdblogo,
		'poster': poster, 'poster_text': poster_text, 'fanart': fanart, 'landscape_tmdb': landscape, 'landscape_tmdb_text': landscape_text, 'year': year, 'title': title, 'rootname': rootname, 'tvshowtitle': title, 'original_language': original_language,
		'original_title': original_title, 'english_title': english_title, 'alternative_titles': alternative_titles,
		'tagline': tagline, 'plot': plot, 'mpaa': mpaa, 'certification_country': certification_country, 'studio': studio, 'director': director, 'writer': writer,
		'duration': duration, 'premiered': premiered, 'genre': genre, 'genre_ids': genre_ids, 'rating': rating, 'votes': votes,
		'country': country, 'country_codes': country_codes, 'trailer': trailer, 'all_trailers': all_trailers,
		'cast': cast, 'extra_info': extra_info, 'mediatype': 'tvshow', 'meta_language': user_info.get('language', ''),
		'status': status, 'total_aired_eps': total_aired_eps, 'total_seasons': total_seasons, 'season_data': season_data,
		'fanart_lang_fixed': True, 'image_language': image_language, 'artwork_selection_version': ARTWORK_SELECTION_VERSION
	}
	if fanarttv_data: meta_dict.update(fanarttv_data)
	else: meta_dict.update(default_fanarttv_data)
	if not meta_dict.get('landscape'): meta_dict['landscape'] = landscape
	return meta_dict

# Retourne le meilleur titre à afficher selon la langue disponible.
def get_title(meta, language=None):
	if 'custom_title' in meta: return meta['custom_title']
	if not language: language = meta.get('meta_language', '')
	if language == 'en': title = meta['title']
	else: title = meta.get('english_title')
	if not title:
		try:
			from settings import metadata_user_info
			meta_user_info = metadata_user_info()
			media_type = 'movie' if meta.get('mediatype') == 'movie' else 'tv'
			english_title = tmdb_english_translation(media_type, meta['tmdb_id'], meta_user_info)
			if english_title: title = english_title
			else: title = meta['original_title']
		except: pass
	if not title: title = meta['original_title']
	if '(' in title: title = title.split('(')[0]
	if '/' in title: title = title.replace('/', ' ')
	return title

# Génère l’URL du poster RPDB pour un média.
def rpdb_get(media_type, media_id, api_key):
	if api_key and media_id:
		if media_id.startswith('tt'): id_type = 'imdb'
		else: id_type, media_id = 'tmdb', '%s-%s' % (media_type, media_id)
		url = 'https://api.ratingposterdb.com/%s/%s/poster-default/%s.jpg'
		rpdb_data = {'rpdb': url % (api_key, id_type, media_id), 'rpdb_added': True}
	else: rpdb_data = {'rpdb': '', 'rpdb_added': False}
	return rpdb_data
